Project

General

Profile

Erlang Driver API

An Erlang driver is written as an Erlang module of callback functions. When the front-end starts up, it obtains a list of drivers from its configuration file. The framework spawns a process for each driver instance specified in the configuration file and passes to it the driver's module name. Each instance of a driver is handled by a different Erlang process, so if an instance terminates, it won't affect other drivers.

Your driver module is free to start-up other Erlang processes, if needed. It is your responsibility, however to monitor the other processes. The framework will only monitor your driver process.

The ACSYS framework provides several standard drivers that are present on all front-ends. Several of the drivers are simple and their source code provides a nice introduction to writing a driver.

Driver Module API

This section describes the set of callbacks an Erlang driver needs to define, which the driver process will call at the appropriate time.


init/1

-callback init(any()) ->
    { 'ready', driver_state(), array:array(attr_spec()) } |
    { 'error', string() }.

When the driver process starts, it calls the module's init/1 function. This function is passed the same value that is specified in the front-end configuration file. This value can be any Erlang term() and should specify addressing information so the driver can access the associated hardware. For instance, if this driver speaks to a network device, this value would probably be an IP address. init/1 should do whatever initialization it needs and then return one of two values:

{ready, State, Attrs}

This value is returned if the driver is successfully initialized. State represents the state used by the driver instance and can be any Erlang term. This state will get passed to each callback. Attrs is an array of attribute specifications. The attribute is accessed through an ACNET device by placing its index in the second byte of the SSDN (which is the first, leftmost, two-digits on D80 SSDN displays).

{error, Reason}

This value should be returned if the driver couldn't initialize properly. Reason is a string that will be printed to the error log.


term/1

-callback term(driver_state()) ->
    'ok'.

If a driver is shutdown, this callback will be called to shutdown the hardware gracefully and free up resources. The callback is given the current state of the driver to complete its task. The return value has no importance and is ignored. Once this function returns, the process will terminate.


prep_reading/3

-callback prep_reading(driver_state(), #reading_context{}, drf2:event()) ->
    { 'ok', driver_state() } |
    { 'ok', driver_state(), request_state() } |
    { 'error', driver_state(), acnet:status() }.

This optional callback allows a driver to further validate the reading context and prepare its hardware to support the upcoming reading or fast-reading request. The #reading_context() parameter contains information that is constant for the duration of the request. By the time this function is called, the length, offset and attribute values of the request have been validated, so this callback should never check these parameters. One value in the reading context is a tag field which will be unique for every request. The driver can use the tag field to keep track of resources, since the same tag will be present when the reading/3 and done_reading/2 callbacks are called. One of three values can be returned:

{ok, NS}

The driver is ready to receive the read request(s). NS is a possibly updated driver state.

{ok, NS, RqState}

The driver is ready to receive the read request(s). NS is a possibly updated driver state. RqState is an Erlang term associated with this request and will be presented to all callbacks that handle this request.

{error, NS, ESts}

The request is rejected. NS is a possibly updated driver state and ESts is the ACNET error code to return to the remote requestor.

A lot of hardware can handle requests without preparation (either the hardware is too simple or it has a buffer from which values can be pulled.) In these cases, this function doesn't need to be defined.


prep_fastreading/3

-callback prep_fastreading(driver_state(), #reading_context{}, drf2:event()) ->
    { 'ok', driver_state() } |
    { 'ok', driver_state(), request_state() } |
    { 'error', driver_state(), acnet:status() }.

This optional callback has the same interface as prep_reading/3. It is for the use of Fast Time Plot or Snapshot Plot, if there is need for plot specific software or hardware setup. If a module does not have a prep_fastreading/3, the plotting packages will default to calling the prep_reading/3 callback for plot setup. Note that neither prep_fastreading/3 or prep_reading/3 are required.


reading/4

-callback reading(driver_state(), request_state(), #reading_context{}, #sync_event{}) ->
    { driver_state(), #device_reply{} } |
    { driver_state(), #device_reply{}, request_state() }.

reading/4 is called when the framework wants a reading from an attribute. The driver_state() parameter is the current state of the driver. request_state() represents the state associated with the current request. If the request state wasn't initialized (through prep_reading/3), this parameter will be the atom undefined. The #reading_context() holds the constant values of the request (i.e. length, offset, tag, etc.) The last parameter is the timestamp of the reading that the framework is interested in acquiring. The device driver should try to return a data sample occurring at the time represented by the timestamp.

The callback can return one of two values:

{State, DevReply}

State is the (possibly updated) driver_state(). DevReply is a device reply record which holds an ACNET status code and the data represented as a binary. The framework returns data to the control system in little-endian format, so make sure the values placed in the binary specify the 'little' attribute.

{State, DevReply, ReqState}

State is the (possibly updated) driver_state(). DevReply is a device reply record. ReqState is the (possibly updated) request state.


fastreading/4

-callback fastreading(driver_state(), request_state(), #reading_context{}, #sync_event{}) ->
    { driver_state(), #device_reply{}, request_state() }.

fastreading/4 is an optional callback that can provide multiple-point reading from the driver, suitable for use in Fast Time Plot or Snapshot Plot. It is called when the framework wants a new readout of this plot data. Like in reading/4, the driver_state() parameter is the current state of the driver, request_state() represents the state associated with the current request, and the #reading_context() holds the constant values of the request (i.e. length, offset, tag, etc.). The length in the #reading_context() for fastreading/4 is meaningless since the driver determines how many data points should be returned. The last parameter is the timestamp of the reading that the framework is interested in acquiring. The device driver should try to return a data sample occurring at the time represented by the timestamp. For plot data, this generally means all the data points accumulated up until the timestamp/event represented by the #sync_event{}.

If a module does not have a fastreading/4 function, the plotting packages will default to calling the reading/4 callbacks to support plotting at slow rates. The data returned in a #device_reply{} from fastreading/4 will be (timestamp, value) point pairs (converted to binary, like in reading) where the value is either 2 or 4 bytes, depending the atomic size for that attribute. In practice, if a module supports fastreading/4, it must also have a prep_reading/3 to initialize the information needed.

The timestamp in the (timestamp, value) pair must be a 16/little integer. The value must have the same format (length, byte order) as the normal reading value.


done_reading/3

-callback done_reading(device_state(), request_state(), #reading_context{}) ->
    { 'ok', driver_state() }.

done_reading/3 is called when a request ends. The driver can use this to free up resources allocated in prep_reading/3. This function returns a tuple containing a possibly updated driver state. Once this callback finishes, the request state will be freed.


done_fastreading/3

-callback done_fastreading(device_state(), request_state(), #reading_context{}) ->
    { 'ok', driver_state() }.

This optional callback has the same interface as done_reading/3. It is for the use of Fast Time Plot or Snapshot Plot, if there is need for plot specific software or hardware cleanup. If a module does not have a done_fastreading/3, the plotting packages will default to calling the done_reading/3 callback for plot cleanup. Note that neither done_fastreading/3 or done_reading/3 are required.


message/2

-callback message(driver_state(), term()) ->
    driver_state().

message/2 gets called whenever the driver receives a non-driver message. If a driver communicates with its hardware through a network socket, for instance, the network messages will be delivered through this callback. The first parameter is the driver's current state. The second parameter is the message pulled from the driver's message queue. This callback returns the (possibly updated) driver state.