The Blinkenlight API
"Blinkenlight API" is a software interface used by clients and servers to communicate.
Physically the API comes as set of C header and source files to be compiled into client and server programs.
It is designed to operate on Blinkenlight panels in a high level way. No more dealing with network sockets, wires, lamps, switches or bit patterns! Instead a console panel is abstracted as a set of "controls".
Server, Panels & Controls
Over the Blinkenlight API a simple hierarchy of objects is defined:
- A client can connect to one of many servers (over the network).
- one server controls several panels (over multiple BlinkenBoards on a BlinkenBus)
- one panel contains many controls.
- controls are "input controls" (switches, dials, etc.) or "output controls" (lamps, digit displays, etc.). In each case, their current state is given by a single long integer value.
"Controls"?
The term "control" is used here as "element of a graphical user interface", another term is "widget".
Examples for controls on a Windows program (here Firefox) are: menus, buttons, edit fields, drop-down boxes.
Examples for controls on a blinkenlight console panel are: LED rows, switch banks, rotary knobs.
You see: many lights and switches are grouped according to their function and treated as one object.
The state of a control is described by a single integer value.
Two examples from the PDP-11/70 console panel above:
- the Data Switches are set to octal 00707720, so this input control has a value of 233424 (decimal).
- the DATA LED row shows the octal pattern 063637, so this output control has a value of 26527 (decimal).
Network layer
A Blinkenlight API client communicates with a Blinkenlight API server through the "Remote Procedure Call" protocoll (RPC). More precisely, the Sun implementation ONCRPC is used. Over RPC, a client can call code (procedures) on a remote server, with parameters and results transmitted transparently over the network.
While ONCRPC is quite old, it's lightwidth yet powerful. In contrast to plain socket communication, it handles type conversion between different architectures, assembly of data spanning multiple network pakets and error management. It is part of every Linux distribution. Also a free implementation for Microsoft Windows exists.
Building a RPC application consists essentially of these steps
- Define the functions to call. and the data structures to transmit.
- use the tool "rpcgen" to generated C interface code ("stubs") both for the client and the server application.
- Complete the client and the server by implementing functionality.
- Finally on the server, a special "portmapper" service must run.
Code alarm!
Here are parts of the Blinkenlight C-header file for API clients. This should give you an idea how to use it (sorry for those long identifiers!)
// a control is one functional element on the panel. It's state is given by "value".
typedef struct blinkenlight_control_struct {
char name[MAX_BLINKENLIGHT_NAME_LEN];
unsigned char is_input; // 0 = out, 1 = in
u_int64_t value; // 64bit? for instance the LED row of a PDP-10 register is 36 bit!
...
} blinkenlight_control_t;
// a blinkenlight panel is a set of controls
typedef struct blinkenlight_panel_struct {
char name[MAX_BLINKENLIGHT_NAME_LEN];
unsigned controls_count;
blinkenlight_control_t controls[MAX_BLINKENLIGHT_PANEL_CONTROLS];
...
} blinkenlight_panel_t;
// a list of panels
typedef struct blinkenlight_panel_list_struct {
unsigned panels_count;
blinkenlight_panel_t panels[MAX_BLINKENLIGHT_PANELS];
...
} blinkenlight_panel_list_t;
typedef struct {
char *rpc_server_hostname ;
blinkenlight_panel_list_t *panel_list; // list of all panels published by server
...
} blinkenlight_api_client_t ;
// create and destroy a client object.
blinkenlight_api_client_t *blinkenlight_api_client_constructor(void) ;
void blinkenlight_api_client_destructor(blinkenlight_api_client_t *_this) ;
// auxilliary: get last error text
char *blinkenlight_api_client_get_error_text(blinkenlight_api_client_t *_this) ;
// manage connection to server
blinkenlight_api_status_t blinkenlight_api_client_connect(blinkenlight_api_client_t *_this, char *host_servername) ;
blinkenlight_api_status_t blinkenlight_api_client_disconnect(blinkenlight_api_client_t *_this) ;
blinkenlight_api_status_t blinkenlight_api_client_get_serverinfo(blinkenlight_api_client_t *_this, char *buffer, int buffersize) ;
// query panels and their controls from the server
blinkenlight_api_status_t blinkenlight_api_client_get_controls(blinkenlight_api_client_t *_this, blinkenlight_panel_t *blp);
blinkenlight_api_status_t blinkenlight_api_client_get_panels_and_controls(blinkenlight_api_client_t *_this);
// read new values for input controls from server
blinkenlight_api_status_t blinkenlight_api_client_get_inputcontrols_values(blinkenlight_api_client_t *_this, blinkenlight_panel_t *blp);
// write changed values for output controls to the server
blinkenlight_api_status_t blinkenlight_api_client_set_outputcontrols_values(blinkenlight_api_client_t *_this, blinkenlight_panel_t *blp);
You will notice that reading and writing of control values is only possible for all controls of the panel at once (..._set_outputcontrols_values(), ..._get_inputcontrols_values()). This should force the programmer to use few big transactions, not many small ones. There is a high constant overhead for a network transmission independent of its payload. The same is true for system calls to the /dev/blinkenbus file interface: the call is costly, the transmitted data volume is cheap.