Writing A Driver
There are three main uses of drivers:
- Streaming IO (TCP, SSH, UDP, Multicast etc.)
- HTTP Client
- Logic
From a driver structure standpoint there is no difference between these types.
- The same driver works over a TCP, UDP or SSH transport
- All drivers support HTTP methods with a defined URI endpoint
- All drivers have access to logic helpers when associated with a System
#
QueueThe queue is a list of potentially asynchronous tasks that should be performed in a sequence.
- Each task has a priority (defaults to
50
) - higher priority tasks run first - Tasks have names - if there's a name conflict, the newer task overwrites the older one
- Tasks have a timeout (defaults to
5.seconds
) - Tasks a set amount of re-tries (defaults to
3
before failing)
Tasks have a callback which can run the task
In most cases you won't need to use the queue explicitly, but it's good to understand that how it functions.
#
TransportThe transport loaded is defined by settings in the database.
#
Streaming IOYou should always tokenize your streams. You can do this automatically with the built-in tokenizer
Here are the ways to use streaming IO methods:
- Send and receive
- Send and callback
- Send immediately (no queuing)
You can also add a pre-processor to data coming in. This can be useful if you want to strip away a protocol layer. For example, if you are using Telnet and want to remove the telnet signals leaving the raw data for tokenizing
#
HTTP ClientAll drivers have built-in methods for performing HTTP requests.
- For streaming IO devices this defaults to
http://device.ip.address
(https
if the transport is using TLS / SSH) - All devices can provide a custom HTTP base URI
There are methods for all the typical HTTP verbs: get, post, put, patch, delete
#
Special SSH methodsSSH connections will attempt to open a shell to the remote device. Sometimes you may be able to execute operations independently.
#
Logic driversLogic drivers belong to a System and cannot be shared, which makes them different from other transports. All other drivers can appear in any number of systems.
You can access remote modules in the system via the system
helper
You can bind to state in remote modules
It's also possible to create shortcuts to other modules. This is powerful as these shortcuts are exposed as metadata. It allows Backoffice to perform system verification.
For example, consider the following video conference system:
Cross system communication is possible if you know the ID of the remote system.
#
SubscriptionsYou can dynamically bind to state of interest in remote modules
Like subscriptions, channels can be setup for broadcasting any data that might not need be exposed as state.
#
SchedulerThere is a built-in scheduler
#
SettingsSettings are stored as JSON and then extracted as required, serializing to the specified type. There are two types:
- Required settings - raise an error if the setting is unavailable
- Optional settings - return
nil
if the setting is unavailable
note
All settings will raise an error if they exist but fail to serialize (due to incorrect formatting etc.)
You can update the local settings of a module, persisting them to the database. Settings must be JSON serializable
#
LoggerThere is a logger available
warn
and above are written to diskdebug
andinfo
are only available when there is an open debugging session
The logging format has been pre-configured so all logging from PlaceOS is uniform and parsed as-is
#
MetadataMany components use metadata to simplify configuration.
generic_name
=> the name that a system should use to access the moduledescriptive_name
=> the manufacturers name for the devicedescription
=> notes or any other descriptive information you wish to addtcp_port
=> TCP port the TCP transport should connect toudp_port
=> UDP port the UDP transport should connect touri_base
=> The HTTP base for any HTTP requestsdefault_settings
=> Default or example settings that for configuring a module
#
SecurityBy default all public functions are exposed for execution. You can limit who is able to execute sensitive functions.
Use the Security
annotation to define the access level of the function.
The options are:
- Administrator
Level::Administrator
- Support
Level::Support
#
InterfacesDrivers can expose any methods that make sense for the device, service or logic they encapsulate. Across these there are often core sets of similar functionality. Interfaces provide a standard way of implementing and interacting with this.
Though optional, they're recommended as they make drivers more modular and less complex.
A full list of interfaces is available in the driver framework. This will expand over time to cover common, repeated patterns as they emerge.
#
Implementing an InterfaceEach interface is a module containing abstract methods, types and functionality built from these.
First include the module within the driver body.
You will then need to provide implementations of the abstract methods. The compiler will guide you in this.
Some interfaces will also provide default implementation for other methods. These may be overridden if the device or service provides a more efficient way to do the same thing. To keep compatibility, overridden methods must maintain feature and functional parity with the original.
#
Using an InterfaceYou can use the system.implementing
method from any logic module.
It returns a list of all drivers in the system which implement the Interface.
The accessor
macro provides a way to declare a dependency on a sibling driver for a specific function.
For more information on these and for usage examples, see logic drivers.