Controls/RPC
This section describes general principles how the RPC mechanism was designed and implemented within the Platform and how it is supposed to be used.
PixelCore is an IoT Platform and as such is supposed to be used with a variety of IoT devices connected to it. Most common behaviour for such devices is a generation of data feed with some useful information which can be interpreted, stored and processed within the Platform. However sometimes those devices expose various functionality which is available on demand via sending some specific downlink communication command. For example change of the configuration of the sensor or activation of some function (open gate or lock, change fan rotation speed and other similar functions). Those functions can be of a very broad scope and the best fit for providing access for applications and users to such a type of functionality is an implementation of generic purpose RPC (Remote Procedure Call) mechanism.
Pixel Platform supports generic purpose RPC mechanism and allows applications and users to initiate execution of such functions exposed by devices (or actually by software components as well) using a combination of Control(s) data type and GraphQL Subscriptions. GraphQL Control data type is used to initiate the RPC call of a certain device and GraphQL Subscription is used to deliver the call information to corresponding responsible consumer, for example a Device Driver. This mechanism would allow to unlock enhanced device functionality up to the end user level (as the RPC calls capabilities are automatically propagated to the corresponding Objects) and also is a convenient way to expand Platform functionality as well, when it is reasonable to use this instrument.
High level RPC call workflow¶
In this section we will review the high level steps required to initiate and process RPC call implemented by some Device Driver.
RPC call implementation¶
RPC calls implementations are provided by corresponding software components, usually (but not limited to) device drivers, and are described to the Platform using Schema Controls. This data type allows to register a certain RPC call itself and a required list of parameters with matching description. Once any Object is instantiated based on Schema with Controls present, this Object registers (copies) all Controls from this Schema in Object Controls data structure. Essentially any change of Schema Controls will be automatically reflected in corresponding Object Controls as well.
Beside providing corresponding description of supported RPCs as part of the Schema, Device Drivers are responsible for reacting on such calls and executing them. To be able to catch a RPC call device driver has to subscribe to GraphQL Controls Subscription (see API section below). If the Platform receives the call request (new Controls data record is added) it would check for the application responsible for corresponding Scheme and would route the call to the matching subscription channel so if the device driver is subscribed to it, it would receive the call request.
Once the call is received the device driver implementing the call should confirm to the Platform that the call was successfully received. This action is expected to be unconditional and instant upon receival, otherwise the Platform may cancel the call (mark it as non-delivered) after a certain timeout. The confirmation of the call receival is provided via calling the corresponding GraphQL Controls Mutation which sets Ack field for this Control to True.
After the confirmation of the call receival the device driver may start to immediately implement required functionality of the device. That may include direct communication to the device using device specific protocol, calling other RPCs within the Platform and any other actions to make the required function work. If subsequent RPC calls are required, the device driver may initiate such a call and has an option to provide the information of the mother call to allow users to track calls in depth.
Device driver RPC call implementation may include reporting of the intermediary progress of the call execution. That may help users to have a better understanding of what is happening during the call and from another hand to register the status of the call execution in case the software module (device driver) would abnormally halt. In the latter case upon restart the device driver would check for non-finished calls and have an option to resume from a specific checkpoint. Intermediary call statuses are reported with same Control data type.
Once the execution is complete, Device Driver have to return the final status of the call execution to the Platform. This is done via setting the Done field to True with corresponding GraphQL Controls Mutation.
RPC call inititation¶
To be able to initiate the RPC call one needs to select the Object and select the RPC call of interest from ObjectControls. Once this is done, the call may be initiated via creating the record of Controls data type using the GraphQL API Controls Mutation. The field “params” has to be filled with a JSON object representing a simple set of key/value pairs, where key is a parameter name and value is a parameter value. Parameters, supported by each RPC call are described in the ObjectControls structure which contains information about all supported RPC calls by this object as well.
Once the call is created within the Platform it would be automatically routed to the software module responsible for its processing and execution. The caller may query the Platform for the updates or may subscribe to GraphQL API Controls Subscription to receive the updates as a live stream.
Stealth and Regular RPCs¶
The Platform supports two types of RPC call processing: Regular (described above) and Stealth. The key difference between the two is how the RPC call is traced within the Platform. In the case of Regular RPC call, the call is registered within the Platform via storing it in a database for further processing. Regular RPC call requires confirmation (ACK) by the receiver and that also includes access to the database. Essentially Regular RPC execution can be traced historically via API or Admin UI.
On the other hand, Stealth RPCs are being passed through the Platform in a way that they are not stored in any data storage at all. Stealth calls are simply routed to the receiver to speed up the processing of the call by Platform as much as possible. Essentially there is no way to return a confirmation (Acknowledgement) that such a call was successfully received and such calls can’t be traced historically as well.
As one may guess, Stealth RPCs are a good candidate for frequently called Device Driver’s payload decoding functions, which basically don’t return any execution status (or such status is not meaningful). Using Stealth PRC also helps to handle a high load of messages more efficiently without any significant loss of the functionality. Essentially in the case of rarely called functions which expose some IoT device functionality and return meaningful information as a result execution Regular RPC mechanism should be used.
For all Device Drivers payload decoding RPC calls (DecodeRaw()) are executed with RPCs in Stealth Mode.
RPC call example¶
The example below would demonstrate the standard flow for RPC call execution phases.
First, the call initiator registers the call with createControlsExecution Mutation.
mutation RpcSendDownlink {
createControlExecution(
input: {
controlExecution: {
objectId: "23e2875c-fcf5-45df-a79e-3180a92d2bb5",
name: "SendDownlink",
params: "{\"PORT\":8, \"RAW_DATA\": \"0100EE02330500070DD61100\", \"CONFIRMED\": true, \"EUI\": \"00EE1A783A9BD930\"}"
}
}
) {
controlExecution {
id
}
}
}
This Mutation will initiate SendDownlink() RPC call with the corresponding parameters set as below:
| Parameter | Value |
|---|---|
PORT |
8 (LoRaWAN port) |
RAW_DATA |
0100EE02330500070DD61100 (Raw payload string) |
CONFIRMED |
true (require ACK ) |
EUI |
00EE1A783A9BD930 (Target device EUI address) |
Returned ID (id) is the Call Execution ID and can be used later to address this particular execution flow.
This RPC call would be registered for Object UUID 23e2875c-fcf5-45df-a79e-3180a92d2bb5 (which is this example stands for Pixel LNS Bridge Object and exposes corresponding RPC call) and the Type of the call is Regular RPC (opposite to Stealth RPC which is described below).
As it was described above, the Platform would check to which Schema the Object UUID belongs to. After determining the Schema UUID it would check for applicationOwner field which represents UUID of the Application (User of type App) which supposed to process this call. If corresponding Application cannot be determined the Mutation would return an error. If the Application is successfully determined, the call would be registered and controller field for the controlsExecution would be populated with Application UUID. After that the Platform would deliver the message with the call parameters via controls subscription topic exactly and only to the corresponding Application for the further processing. Apparently if the Application is registered within the Platform but not subscribed to controls topic, it would not receive this notification.
Upon receive of the notification in controls topic, the responsible Application required to acknowledge the call with updateControlsExecutionAck Mutation:
mutation AckRPC {
updateControlExecutionAck(
input: {
controlsExecutionId: <CALL_EXEC_ID>
}) {
boolean
}
}
This mutation would return true or false to the App indicating if Ack was successfully set for corresponding call. At the same time corresponding notification would be generated to the controls topic to inform the caller that some Application in the Platform has picked up this RPC. After that the App can start processing of the call. During the actual call execution the App is welcome to provide back some intermediary statuses reports to the caller using createControlsExecutionReport mutation.
mutation RPCReport {
createControlExecutionReport(
input: {
linkedControlId: <CALL_EXEC_ID>
report: "StartedProcessing"
reportDetails: "{}"
done: false
error: false
}
) {
Int
}
}
The information from this mutation would be registered within the ControlsExecution data storage and would become available for later retrieval. For each report generated by the Application the caller would also receive the corresponding notification in controls topic. The bigInt returned by the mutation is the unique Report ID.
Once the call execution is done, the Application can generate the final report with field done set to true to indicate to the caller that this is the final report for this call. The field error supposed to be used to indicate if the call execution failed or encountered some other type of error during the execution. Corresponding detailed information can be provided in report field and details JSON object of arbitrary structure. Once the final report is registered, the corresponding call start record would also have field done updated to true.
mutation RPCFinalReport {
createControlExecutionReport(
input: {
linkedControlId: <CALL_EXEC_ID>
report: "Finished"
reportDetails: "{}"
done: true
error: false
}
) {
int
}
}
This Report supposed to finish RPC call processing within the Platform. After this, the call execution flow (including call initiation information and all reports) is available via Query ControlExecutions or controlExecutionsConnection for display or study. To store the reports, the same data structure is used with the following meaning mapping:
| Field | Type = 'RPC', meaning | Type = 'REPORT', meaning |
|---|---|---|
name |
RPC call name | short report description |
params |
RPC call params, JSON | report details, JSON |