SENSBLUE ATLAS/MQTT Actors: Difference between revisions
No edit summary |
No edit summary |
||
| Line 5,814: | Line 5,814: | ||
|} | |} | ||
==== 14. | ==== 14.4 Runtime Operations ==== | ||
Supported runtime aliases include <small>'''READ, READ_CONTINUOUS, STOP_READ_CONTINUOUS, READ_PDO_CONTINUOUS, STOP_READ_PDO_CONTINUOUS, PING, NMT_COMMAND, SDO_READ, SDO_WRITE, READ_HEARTBEAT, RECV_PDO, RECV_EMCY'''</small>, and <small>'''QUERY_NODE_INFO'''</small><small>'''.'''</small> | Supported runtime aliases include <small>'''READ, READ_CONTINUOUS, STOP_READ_CONTINUOUS, READ_PDO_CONTINUOUS, STOP_READ_PDO_CONTINUOUS, PING, NMT_COMMAND, SDO_READ, SDO_WRITE, READ_HEARTBEAT, RECV_PDO, RECV_EMCY'''</small>, and <small>'''QUERY_NODE_INFO'''</small><small>'''.'''</small> | ||
Revision as of 13:45, 2 September 2026
Overview
port: 1883
address: AP-> 192.168.30.1
Eth1: 93.48.86.253
user: < not needed>
login <not needed>
1. Relay Outputs Actor
1.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to configure and obtain peripherals data. Connecting to the broker for this Area requires no credentials.
a) Set/reset digital output pin/pins state
Both relays default state is open.
Req-Topic: relayOutputs/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskParams": {
"DO1": {
"state": "open"
},
"DO2": {
"state": "open"
}
}
}
}
Reply-Topic: relayOutputs/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"DO1": {
"success": true,
"state": "open"
},
"DO2": {
"success": true,
"state": "open"
}
}
}
}
b) Get all relays state
Req-Topic: relayOutputs/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "READ"
}
}
Reply-Topic: relayOutputs/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"DO1": {
"success": true,
"state": "open"
},
"DO2": {
"success": true,
"state": "close"
}
}
}
}
c) Set all relays state
Req-Topic: relayOutputs/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SET",
"taskParams": {
"DO1": {
"state": "open"
},
"DO2": {
"state": "close"
}
}
}
}
Reply-Topic: relayOutputs/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"DO1": {
"success": true,
"state": "open"
},
"DO2": {
"success": true,
"state": "close"
}
}
}
}
2 RTC Actor
2.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to configure and obtain peripherals data. Connecting to the broker for this Area requires no credentials.
a) Set timer/alarm interruption
mode: Timer, alarm or none.
alarmSet: Date and time for alarm interruption (No effect if mode is timer or none).
timerSetSeconds: Period in seconds for timer interruption (No effect if mode is alarm or none).
Req-Topic: clock/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskParams": {
"CLOCK_SET_MODE": {
"mode": "timer",
"alarmSet": "[timestamp epoch ms]",
"timerSetSeconds": "<INT>"
}
}
}
}
Req-Topic: clock/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true,
"CLOCK_SET_MODE": {
"mode": "timer",
"alarmSet": "[timestamp epoch ms]",
"timerSetSeconds": "<INT>"
}
}
}
}
b) Toggle RTC sync logic (described below)
tcState: State of the synchronization logic of the RTC, this value is non-persistent so every-time a reboot happens the value return to default (by default the value is ON)
Req-Topic: clock/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "TOGGLE"
}
}
Req-Topic: clock/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true,
"rtcState": "OFF"
}
}
}
c) Read clock configs
mode: Timer, alarm or none.
alarmSet: Date and time for alarm interruption (No effect if mode is timer or none).
timerSetSeconds: Period in seconds for timer interruption (No effect if mode is alarm or none).
rtcState: Current state of the synchronization logic of the RTC.
Req-Topic: clock/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "READ"
}
}
Reply-Topic: clock/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true,
"CLOCK_CONFIGS": {
"mode": "timer",
"alarmSet": "[timestamp epoch ms]",
"timerSetSeconds": "<INT>",
"rtcState": "ON"
}
}
}
}
d) Read clock
Req-Topic: clock/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true,
"timestamp": "[timestamp epoch ms]"
}
}
}
2.2 Synchronization loop flowchart (every 30 seconds)
RTC Actor - Overview
3 System Health Actor
3.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker.
This interface allows any external App to receive system-level metrics such as CPU load, RAM usage, disk usage, temperature, core frequency, system throttle status and uptime.
No credentials are required to subscribe to this topic.
The System Health Actor does not receive commands. It is fully autonomous and publishes its metrics every 1 second.
3.2 Peripherals version
The Atlas peripherals version is on topic systemHealth/versions
{
"id": <TIMESTAMP>,
"origin": "system-health",
"version": "1.0.0"
}
3.3 Runtime Data
Topic: systemHealth/runtime/out
Publish rate: 1 Hz (one message per second, clock-jump safe)
a) Metrics Provided
| Key | Description | Unit |
|---|---|---|
| cpu_percent | Current CPU load | % |
| cpu_tempC | CPU temperature read from Linux thermal zones | ºC |
| cpu0_freq | CPU Core 0 current frequency | Hz |
| cpu1_freq | CPU Core 1 current frequency | Hz |
| cpu2_freq | CPU Core 2 current frequency | Hz |
| cpu3_freq | CPU Core 3 current frequency | Hz |
| cpu0_throttle | System throttle status register | Hex |
| ram_percent | RAM usage | % |
| disk_percent | Disk usage for filesystem root /
|
% |
| uptime_sec | Time since last reboot | seconds |
| emmc_rul | eMMC Remaining Useful Life object calculated from EmmcActor wear history | object |
Example Payload (systemHealth/runtime/out)
{
"id": <TIMESTAMP>,
"origin": "system-health",
"task": {
"taskResult": {
"cpu_percent": 7.1,
"cpu_tempC": 47.3,
"cpu0_freq": 1500000000,
"cpu1_freq": 1500000000,
"cpu2_freq": 1500000000,
"cpu3_freq": 1500000000,
"cpu0_throttle": "0x0",
"ram_percent": 32.9,
"disk_percent": 41.2,
"uptime_sec": 5321,
}
}
}
3.4 eMMC RUL - Remaining Useful Life
The SystemHealthActor also exposes the estimated eMMC Remaining Useful Life (RUL) inside the normal systemHealth/runtime/out payload.
The RUL data is calculated from the wear-level history maintained by the EmmcActor.
a) eMMC RUL Data Flow
b) Wear-Level Format
Each wear-level entry has the following format:
<WLxx:timestamp_ms>
Example:
<WL03:1782228844578>
| Part | Meaning |
|---|---|
| WL03 | eMMC wear-level tier |
| 1782228844578 | Unix timestamp in milliseconds when the wear-level was registered |
c) Wear-Level Interpretation
The wear-level tier represents a coarse estimation of consumed eMMC lifetime.
| Wear Level | Estimated Lifetime Consumed | Conservative Remaining |
|---|---|---|
| WL01 | 0% - 10% | 90% |
| WL02 | 10% - 20% | 80% |
| WL03 | 20% - 30% | 70% |
| WL04 | 30% - 40% | 60% |
| WL05 | 40% - 50% | 50% |
| WL06 | 50% - 60% | 40% |
| WL07 | 60% - 70% | 30% |
| WL08 | 70% - 80% | 20% |
| WL09 | 80% - 90% | 10% |
| WL10 | 90% - 100% | 0% |
| WL11 | Estimated lifetime exceeded | 0% |
When WL11 or higher is detected, the device should be considered beyond the estimated eMMC lifetime.
d) emmc_rul Payload
The calculated RUL is published inside the regular systemHealth/runtime/out payload.
Example:
{
"id": 1782230830898,
"origin": "system-health",
"task": {
"taskResult": {
"cpu_percent": 13.6,
"cpu_tempC": 63.3,
"disk_percent": 24.0,
"emmc_rul": {
"ok": true,
"state": "OK",
"current_wear_level": "WL03",
"current_tier": 3,
"consumed_percent_min": 20,
"consumed_percent_max": 30,
"consumed_percent_estimated": 25,
"remaining_percent_estimated": 75,
"remaining_percent_conservative": 70,
"history_entries": 3,
"estimated_rul_ms": null,
"estimated_rul_days": null,
"estimated_eol_timestamp_ms": null,
"confidence": "LOW",
"source": "emmc_actor",
"last_refresh_timestamp_ms": 1782230830898
},
"uptime_sec": 9339
}
}
}
e) emmc_rul Field Description
| Field | Type | Description |
|---|---|---|
| ok | boolean | Indicates whether the RUL calculation was successful. |
| state | string | General eMMC RUL state. Examples: OK, UNKNOWN, EXCEEDED_ESTIMATED_LIFETIME.
|
| current_wear_level | string | Current wear-level tier as text, for example WL01, WL03, or WL11.
|
| current_tier | integer | Numeric version of the wear level. For example, WL03 becomes 3.
|
| consumed_percent_min | number | Lower bound of estimated consumed lifetime for the current tier. |
| consumed_percent_max | number | Upper bound of estimated consumed lifetime for the current tier. |
| consumed_percent_estimated | number | Mid-point estimate between minimum and maximum consumed lifetime. |
| remaining_percent_estimated | number | Estimated remaining lifetime using the mid-point consumed value. |
| remaining_percent_conservative | number | Conservative remaining lifetime using the maximum consumed value. |
| history_entries | integer | Number of wear-history entries received from the EmmcActor.
|
| estimated_rul_ms | integer / null | Estimated remaining useful lifetime in milliseconds. null means there is not enough reliable time-history data.
|
| estimated_rul_days | number / null | Estimated remaining useful lifetime in days. null means time-based RUL is not available.
|
| estimated_eol_timestamp_ms | integer / null | Estimated end-of-life Unix timestamp in milliseconds. null means EOL cannot be estimated reliably.
|
| ms_per_wear_tier | integer / null | Average time observed for one wear-level tier transition. Only available when a valid transition history exists. |
| confidence | string | Confidence level of the RUL estimate. Suggested values: LOW, MEDIUM, HIGH.
|
| rul_reason | string / null | Optional explanation when time-based RUL is not calculated, for example OBSERVATION_WINDOW_TOO_SHORT.
|
| source | string | Source used for the calculation. Normally emmc_actor.
|
| last_refresh_timestamp_ms | integer | Unix timestamp in milliseconds when the SystemHealthActor last refreshed the eMMC RUL cache. |
f) Percentage-Based RUL Calculation
The percentage-based RUL can be calculated immediately from the current wear-level tier.
Formula:
consumed_percent_min = (current_tier - 1) * 10
consumed_percent_max = current_tier * 10
consumed_percent_estimated = (consumed_percent_min + consumed_percent_max) / 2
remaining_percent_estimated = 100 - consumed_percent_estimated
remaining_percent_conservative = 100 - consumed_percent_max
Example for WL03:
current_tier = 3
consumed_percent_min = (3 - 1) * 10 = 20
consumed_percent_max = 3 * 10 = 30
consumed_percent_estimated = (20 + 30) / 2 = 25
remaining_percent_estimated = 100 - 25 = 75
remaining_percent_conservative = 100 - 30 = 70
Result:
{
"current_wear_level": "WL03",
"consumed_percent_min": 20,
"consumed_percent_max": 30,
"consumed_percent_estimated": 25,
"remaining_percent_estimated": 75,
"remaining_percent_conservative": 70
}
g) Time-Based RUL Calculation
Time-based RUL estimates how many milliseconds, days, or calendar time remain until the eMMC reaches the end of the expected lifetime.
This calculation requires real historical wear transitions.
The minimum required data is:
WL01 at timestamp A
WL02 at timestamp B
Where:
timestamp B > timestamp A
Formula:
ms_per_wear_tier = (last_timestamp_ms - first_timestamp_ms) / (last_tier - first_tier)
remaining_tiers_to_wl10 = 10 - current_tier
estimated_rul_ms = remaining_tiers_to_wl10 * ms_per_wear_tier
estimated_eol_timestamp_ms = current_wear_timestamp_ms + estimated_rul_ms
Example:
WL01 at 1782225732187
WL03 at 1782235908273
Observed tier delta:
3 - 1 = 2 tiers
Observed time delta:
1782235908273 - 1782225732187 = 10,176,086 ms
Average time per tier:
10,176,086 / 2 = 5,088,043 ms per tier
Remaining tiers from WL03 to WL10:
10 - 3 = 7 tiers
Estimated RUL:
7 * 5,088,043 = 35,616,301 ms
35,616,301 ms = 0.41 days
This calculation is mathematically correct, but it may be operationally misleading if the observation window is too short.
h) Why Time-Based RUL Can Be null
The following fields can be null:
{
"estimated_rul_ms": null,
"estimated_rul_days": null,
"estimated_eol_timestamp_ms": null
}
This happens when the SystemHealthActor does not have enough reliable history to estimate the wear rate.
Common reasons:
| Reason | Description |
|---|---|
| NO_WEAR_HISTORY | No valid wear-history entries were received. |
| ONLY_ONE_HISTORY_ENTRY | Only one wear-level entry exists, so no rate can be calculated. |
| NON_INCREASING_TIMESTAMPS | Wear-level timestamps are not strictly increasing. |
| BACKFILLED_HISTORY | Several wear levels have the same timestamp, usually because they were synchronized or recreated at boot. |
| OBSERVATION_WINDOW_TOO_SHORT | The observed time span is too short to generate a trustworthy RUL estimate. |
Example with only one entry:
<WL01:1782225732187>The actor can calculate the percentage-based RUL:24.3.6 Peripherals health
WL01 = 0% - 10% consumed
remaining_percent_estimated = 95%
remaining_percent_conservative = 90%
But it cannot calculate:
estimated_rul_ms
estimated_rul_days
estimated_eol_timestamp_ms
because it does not know how fast the eMMC is wearing.
i) Important Rule: Do Not Trust Short Observation Windows
During development, stress tests, boot corrections, actor restarts, or history synchronization, several wear levels may be registered in a very short time.
Example:
<WL01:1782225732187>
<WL02:1782228844578>
<WL03:1782235908273>This may produce:
{
"ms_per_wear_tier": 5088043,
"estimated_rul_days": 0.41,
"confidence": "MEDIUM"
}
Although the math is correct, the result should not be trusted as a production RUL estimate.
The actor should avoid publishing a time-based RUL estimate when the observation period is too short.
Recommended rule:
Calculate time-based RUL only when:
- at least 2 real transitions exist;
- timestamps are strictly increasing;
- the total observation window is >= 7 days.For production deployments, a stricter rule may be used:
Minimum observation window >= 30 days.When the observation window is too short, publish:
{
"estimated_rul_ms": null,
"estimated_rul_days": null,
"estimated_eol_timestamp_ms": null,
"confidence": "LOW",
"rul_reason": "OBSERVATION_WINDOW_TOO_SHORT"
}
The percentage-based fields should still be published because they remain valid.
j) Recommended Confidence Rules
| Confidence | Meaning |
|---|---|
| LOW | Not enough real history exists, history was backfilled, or the observation window is too short. |
| MEDIUM | Real transitions exist and the observation window is acceptable, but the model is still an estimate. |
| HIGH | The eMMC has reached or exceeded WL11, or the state is known with high certainty.
|
Recommended behavior:
| Condition | Confidence | Time-Based RUL |
|---|---|---|
| No history | LOW | null |
| Only one entry | LOW | null |
| Same timestamp for multiple wear levels | LOW | null |
| Observation window below 7 days | LOW | null |
| Valid transitions over acceptable window | MEDIUM | calculated |
WL11 or higher
|
HIGH | 0 |
k) Critical State Handling
If the current tier is WL11 or higher, the eMMC has exceeded the estimated lifetime range.
Expected output:
{
"ok": true,
"state": "EXCEEDED_ESTIMATED_LIFETIME",
"current_wear_level": "WL11",
"current_tier": 11,
"consumed_percent_min": 100,
"consumed_percent_max": 100,
"consumed_percent_estimated": 100,
"remaining_percent_estimated": 0,
"remaining_percent_conservative": 0,
"estimated_rul_ms": 0,
"estimated_rul_days": 0,
"confidence": "HIGH"
}
Recommended service interpretation:
The device should be serviced or replaced.l) Example: Safe Output for Short Observation Window
If the device is already on WL03, but the transition history happened in a short test window, the safe output should be:
{
"emmc_rul": {
"ok": true,
"state": "OK",
"current_wear_level": "WL03",
"current_tier": 3,
"consumed_percent_min": 20,
"consumed_percent_max": 30,
"consumed_percent_estimated": 25,
"remaining_percent_estimated": 75,
"remaining_percent_conservative": 70,
"history_entries": 3,
"estimated_rul_ms": null,
"estimated_rul_days": null,
"estimated_eol_timestamp_ms": null,
"ms_per_wear_tier": 5088043,
"confidence": "LOW",
"rul_reason": "OBSERVATION_WINDOW_TOO_SHORT",
"source": "emmc_actor",
"last_refresh_timestamp_ms": 1782230830898
}
}
This output avoids reporting a misleading value such as:
{
"estimated_rul_days": 0.41
}
The correct interpretation is:
The eMMC is currently in WL03.
Estimated consumed lifetime is between 20% and 30%.
Estimated remaining lifetime is between 70% and 75%.
A reliable time-based RUL cannot yet be calculated.m) Summary
The emmc_rul object provides two different types of information:
| Type | Reliability | Description |
|---|---|---|
| Percentage-based RUL | Reliable immediately | Based directly on the current wear-level tier. |
| Time-based RUL | Requires history | Based on real wear-level transitions over time. |
The SystemHealthActor should always expose the percentage-based RUL when a valid wear level exists.
The time-based RUL should only be exposed when the history is reliable enough to avoid misleading estimates.
3.5 Peripherals health
In order get the peripherals health we had to implement a SystemHealth actor in each bus manager (busi2c-0, busi2c-1, etc....).
a) Example
For busi2c-0:
Reply topic: systemHealth/busi2c-0/runtime/out
Payload:
{
"id": <TIMESTAMP>,
"origin": "system-health-busi2c-0",
"task": {
"taskResult": {
"ADC": {
"status": "ERROR",
"description": "IRQ Retrying",
"last_report": 1770378753006,
"stats": {
"AI1": {
"samples": 563154,
"retries": 760548,
"discards": 253313
},
"AI2": {
"samples": 563154,
"retries": 760475,
"discards": 253314
},
"AI3": {
"samples": 563154,
"retries": 760528,
"discards": 253313
},
"AI4": {
"samples": 563154,
"retries": 760549,
"discards": 253312
}
}
},
"DAC": {
"status": "OK",
"description": "Running....",
"last_report": 1770378752576,
"stats": {
"AO1": {
"samples": 240755,
"retries": 0,
"discards": 0
},
"AO2": {
"samples": 240755,
"retries": 0,
"discards": 0
}
}
},
"RTC": {
"status": "OK",
"description": "",
"last_report": 1770378752094,
"stats": {
"samples": 248710,
"retries": 0,
"discards": 0
}
},
"TEMP": {
"status": "OK",
"description": "",
"last_report": 1770378752579,
"stats": {
"samples": 240754,
"retries": 0,
"discards": 0
}
}
}
}
}
For busi2c-1:
Reply topic: systemHealth/busi2c-1/runtime/out
Payload:
{
"id": <TIMESTAMP>,
"origin": "system-health-busi2c-1",
"task": {
"taskResult": {
"JOYSTICK": {
"status": "OK",
"description": "Running....",
"last_report": 1770382405050,
"stats": {
"read_all": {
"samples": 410154,
"retries": 0,
"discards": 0
},
"read_raw": {
"samples": 0,
"retries": 0,
"discards": 0
},
"buttons": {
"JDOWN": {
"reads": 410154,
"last_state": "pressed"
},
"JRIGHT": {
"reads": 410154,
"last_state": "pressed"
},
"JUP": {
"reads": 410154,
"last_state": "pressed"
},
"JLEFT": {
"reads": 410154,
"last_state": "pressed"
},
"JCENTER": {
"reads": 410154,
"last_state": "pressed"
}
}
}
},
"RELAYS": {
"status": "OK",
"description": "Relay Output MQTT connected",
"last_report": 1770298859099,
"stats": {
"read_all": {
"samples": 0,
"retries": 0,
"discards": 0
},
"set": {
"samples": 0,
"retries": 0,
"discards": 0
},
"set_many": {
"samples": 0,
"retries": 0,
"discards": 0
},
"raw_read_all": {
"samples": 0,
"retries": 0,
"discards": 0
},
"outputs": {
"DO1": {
"writes": 0,
"last_state": "unknown"
},
"DO2": {
"writes": 0,
"last_state": "unknown"
}
}
}
},
"DI": {
"status": "OK",
"description": "Running",
"last_report": 1770382404832,
"stats": {
"read_all": {
"samples": 181758,
"retries": 0,
"discards": 0
},
"read_raw": {
"samples": 0,
"retries": 0,
"discards": 0
},
"inputs": {
"DI1": {
"reads": 181758,
"last_state": "low"
},
"DI2": {
"reads": 181758,
"last_state": "low"
},
"DI3": {
"reads": 181758,
"last_state": "low"
},
"DI4": {
"reads": 181758,
"last_state": "low"
}
}
}
}
}
}
}
3.6 Commands
a) List usb devices
The list_usb_devices action returns a parsed list of USB devices detected on the system using the lsusb command.
Req-topic: systemHealth/runtime/in
{
"id": <TIMESTAMP>,
"origin": "Dev",
"task": {
"action": "LIST_USB_DEVICES"
}
}
Reply-topic: systemHealth/runtime/usb/devices/out
{
"id": <TIMESTAMP>,
"origin": "system-health",
"task": {
"action": "list_usb_devices",
"taskResult": {
"usb_devices": {
"success": true,
"command": "lsusb",
"returncode": 0,
"devices": [
{
"bus": "001",
"device": "001",
"vendor_id": "1d6b",
"product_id": "0002",
"description": "Linux Foundation 2.0 root hub",
"parsed": true
},
{
"bus": "001",
"device": "002",
"vendor_id": "0424",
"product_id": "9514",
"description": "Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub",
"parsed": true
},
{
"bus": "001",
"device": "003",
"vendor_id": "0424",
"product_id": "ec00",
"description": "Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter",
"parsed": true
},
{
"bus": "001",
"device": "004",
"vendor_id": "1e0e",
"product_id": "9001",
"description": "Qualcomm / Option SimTech, Incorporated",
"parsed": true
}
],
"stderr": ""
}
}
}
}
| JSON Path | Type | Example | Description |
|---|---|---|---|
| id | string /number | "get-hw-info-001" | Request identifier echoed back in the response. |
| origin | string | "system-health" | Actor/device that generated the response. |
| task | object | {...} | Container for the action response. |
| task.action | string | "list_usb_devices" | Action executed by the actor. |
| task.taskResult | object | {...} | Main result object for the action. |
| task.taskResult.usb_devices | object | {...} | Contains the USB device scan result. |
| task.taskResult.usb_devices.success | boolean | true | Indicates whether the lsusb
commandexecuted successfully. |
| task.taskResult.usb_devices.command | string | "lsusb" | Command used to collect USB information. |
| task.taskResult.usb_devices.returncode | number /null | 0 | Process return code. 0 means success. null
means the command was not executed ortimed out. |
| task.taskResult.usb_devices.devices | array | [...] | List of USB devices detected by lsusb. |
| task.taskResult.usb_devices.devices[ ].bus | string | "001" | USB bus number where the device wasdetected. |
| task.taskResult.usb_devices.devices[ ].device | string | "004" | USB device number assigned on that bus. |
| task.taskResult.usb_devices.devices[ ].vendor_id | string | "1e0e" | USB vendor ID in hexadecimal format. |
| task.taskResult.usb_devices.devices[ ].product_id | string | "9001" | USB product ID in hexadecimal format. |
| task.taskResult.usb_devices.devices[ ].description | string | "Qualcomm / Option SimTech,Incorporated" | Human-readable device description returnedby lsusb. |
| task.taskResult.usb_devices.devices[ ].parsed | boolean | true | Indicates whether the lsusb line was parsed successfully. |
| task.taskResult.usb_devices.devices[ ].raw | string | Optional | Present only when a device line could not beparsed.
Contains the original raw lsusb line. |
| task.taskResult.usb_devices.stderr | string | " " | Error output from the command. Empty stringmeans no error. |
b) List connectivity specs
The list_connectivity_specs action allows the SystemHealth Actor to return connectivity information for the device, including the status of:
- Wi-Fi
- LTE modem
- eth0
- eth1
- Tunnel interfaces, for possible commissioning use
Req-topic: systemHealth/runtime/in
{
"id": "connectivity-001",
"origin": "APP",
"task": {
"action": "list_connectivity_specs"
}
}
Reply-topic: systemHealth/runtime/connectivity/out
{
"id": <TIMESTAMP>,
"origin": "system-health",
"task": {
"action": "list_connectivity_specs",
"taskResult": {
"connectivity": {
"sources": {
"sysfs": "/sys/class/net",
"ip_link": {
"available": true,
"command": "ip -j -d link show",
"error": null
},
"network_manager": {
"available": true,
"command": "nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status",
"error": null
}
},
"interfaces": {
"wifi": {
"available": true,
"interfaces": [
{
"interface": "wlan0",
"available": true,
"status": "down",
"operstate": "down",
"admin_up": true,
"carrier": false,
"mtu": 1500,
"type": 1,
"link_kind": null,
"mac_address": "2c:cf:67:fd:60:40",
"ipv4": [],
"ipv6": [],
"network_manager": {
"device": "wlan0",
"type": "wifi",
"state": "disconnected",
"connection": null
},
"wifi": {
"available": true,
"command": "iw dev wlan0 link",
"connected": false
}
}
]
},
"lte_modem": {
"available": true,
"tool_available": true,
"command": "mmcli -L",
"modems": [
{
"id": "0",
"success": true,
"command": "mmcli -m 0 --output-json",
"manufacturer": "QUALCOMM INCORPORATED",
"model": "SIMCOM_SIM7600G-H",
"revision": "LE20B04SIM7600G22",
"hardware_revision": "10000",
"device": "/sys/devices/platform/scb/fe9c0000.xhci/usb1/1-1/1-1.4",
"drivers": [
"qmi_wwan",
"option"
],
"plugin": "simtech",
"primary_port": "cdc-wdm0",
"ports": [
{
"name": "cdc-wdm0",
"type": "qmi"
},
{
"name": "ttyUSB0",
"type": "ignored"
},
{
"name": "ttyUSB1",
"type": "gps"
},
{
"name": "ttyUSB2",
"type": "at"
},
{
"name": "ttyUSB3",
"type": "at"
},
{
"name": "ttyUSB4",
"type": "audio"
},
{
"name": "wwan0",
"type": "net"
}
],
"state": "failed",
"power_state": "on",
"access_technologies": [],
"signal_quality": {
"recent": "yes",
"value": "0"
},
"current_capabilities": [
"gsm-umts, lte"
],
"supported_capabilities": [
"gsm-umts, lte"
],
"operator_code": "--",
"operator_name": "--",
"registration_state": "--",
"packet_service_state": "--",
"sim_path": null
}
],
"network_interfaces": [
{
"interface": "wwan0",
"available": true,
"status": "down",
"operstate": "down",
"admin_up": false,
"carrier": null,
"mtu": 1500,
"type": 65534,
"link_kind": null,
"mac_address": null,
"ipv4": [],
"ipv6": [],
"network_manager": null
}
]
},
"eth0": {
"interface": "eth0",
"available": true,
"status": "connected",
"operstate": "up",
"admin_up": true,
"carrier": true,
"mtu": 1500,
"type": 1,
"link_kind": null,
"mac_address": "2c:cf:67:fd:60:3f",
"ipv4": [
{
"address": "172.16.20.57",
"prefixlen": 24,
"scope": "global",
"dynamic": true
}
],
"ipv6": [
{
"address": "fe80::7fc1:f65f:5729:d234",
"prefixlen": 64,
"scope": "link",
"dynamic": false
}
],
"network_manager": {
"device": "eth0",
"type": "ethernet",
"state": "connected",
"connection": "wired-ip-dynamic"
}
},
"eth1": {
"interface": "eth1",
"available": true,
"status": "unavailable",
"operstate": "down",
"admin_up": true,
"carrier": false,
"mtu": 1500,
"type": 1,
"link_kind": null,
"mac_address": "2c:cf:67:fd:60:41",
"ipv4": [],
"ipv6": [],
"network_manager": {
"device": "eth1",
"type": "ethernet",
"state": "unavailable",
"connection": null
}
},
"tunnel": {
"available": false,
"interfaces": []
}
}
}
}
}
}
| Field | Type | Description |
|---|---|---|
| id | string/number | Request/response identifier used to correlate therequest with the response. |
| origin | string | Origin of the message. In the response, this is normallythe SystemHealth actor name. |
| task.action | string | Executed action. For this feature, the value is list_connectivity_specs. |
| task.taskResult.connectivity.sources.sysfs | string | Linux sysfs path used to discover network interfaces, usually/sys/class/net. |
| task.taskResult.connectivity.sources.ip_link.available | boolean | Indicates whether the ip command was available andexecuted successfully. |
| task.taskResult.connectivity.sources.ip_link.command | string | Command used to retrieve detailed link information,for example ip -j -d link show. |
| task.taskResult.connectivity.sources.ip_link.error | string/null | Error returned by the ip command, or null if no erroroccurred. |
| task.taskResult.connectivity.sources.network_manager.available | boolean | Indicates whether NetworkManager / nmcli was available and executed successfully. |
| task.taskResult.connectivity.sources.network_manager.command | string | Command used to retrieve NetworkManager device status. |
| task.taskResult.connectivity.sources.network_manager.error | string/null | Error returned by nmcli, or null if no error occurred. |
| task.taskResult.connectivity.interfaces.wifi.available | boolean | Indicates whether at least one Wi-Fi interface was found. |
| task.taskResult.connectivity.interfaces.wifi.interfaces[ ] | array | List of detected Wi-Fi interfaces, usually including wlan0 when Wi-Fi exists. |
| wifi.interfaces[ ].interface | string | Linux interface name, for example wlan0. |
| wifi.interfaces[ ].available | boolean | Indicates whether the Wi-Fi interface exists in the system. |
| wifi.interfaces[ ].status | string | Normalized interface status, for example connected, down, unavailable, no_carrier, or not_available. |
| wifi.interfaces[ ].operstate | string / null | Operational state reported by Linux, for example up, down, or unknown. |
| wifi.interfaces[ ].admin_up | boolean / null | Indicates whether the interface is administratively enabled. |
| wifi.interfaces[ ].carrier | boolean / null | Indicates whether carrier/link is detected. |
| wifi.interfaces[ ].mtu | number / null | Maximum Transmission Unit of the interface. |
| wifi.interfaces[ ].type | number / null | Linux interface type. |
| wifi.interfaces[ ].link_kind | string / null | Special link type, used mainly for virtual/tunnel interfaces. Usually null for normal Wi-Fi. |
| wifi.interfaces[ ].mac_address | string / null | MAC address of the Wi-Fi interface. |
| wifi.interfaces[ ].ipv4[ ] | array | List of IPv4 addresses configured on the Wi-Fi interface. |
| wifi.interfaces[ ].ipv6[ ] | array | List of IPv6 addresses configured on the Wi-Fi interface. |
| wifi.interfaces[ ].network_manager.device | string | NetworkManager device name. |
| wifi.interfaces[ ].network_manager.type | string | NetworkManager interface type, usually wifi. |
| wifi.interfaces[ ].network_manager.state | string | NetworkManager state, for example connected, disconnected, or unavailable. |
| wifi.interfaces[ ].network_manager.connection | string / null | Active NetworkManager profile name, or null if none is active. |
| wifi.interfaces[ ].wifi.available | boolean | Indicates whether Wi-Fi details could be queried using iw. |
| wifi.interfaces[ ].wifi.command | string | Command used to query Wi-Fi link state, for example iw dev wlan0 link. |
| wifi.interfaces[ ].wifi.connected | boolean / null | Indicates whether the Wi-Fi interface is associated with a Wi-Fi network. |
| wifi.interfaces[ ].wifi.ssid | string | SSID of the connected Wi-Fi network, when connected. |
| wifi.interfaces[ ].wifi.bssid | string | BSSID/MAC address of the connected access point, when connected. |
| wifi.interfaces[ ].wifi.frequency_mhz | number | Wi-Fi frequency in MHz, when connected. |
| wifi.interfaces[ ].wifi.signal_dbm | number | Wi-Fi signal strength in dBm, when available. |
| wifi.interfaces[ ].wifi.tx_bitrate | string | Wi-Fi transmit bitrate, when available. |
| wifi.interfaces[ ].wifi.rx_bitrate | string | Wi-Fi receive bitrate, when available. |
| task.taskResult.connectivity.interfaces.lte_modem.available | boolean | Indicates whether at least one LTE modem was detected. |
| task.taskResult.connectivity.interfaces.lte_modem.tool_available | boolean | Indicates whether mmcli / ModemManager is available. |
| task.taskResult.connectivity.interfaces.lte_modem.command | string | Command used to list LTE modems, usually mmcli -L. |
| task.taskResult.connectivity.interfaces.lte_modem.modems[ ] | array | List of LTE modems detected by ModemManager. |
| lte_modem.modems[ ].id | string | Modem ID in ModemManager, for example 0. |
| lte_modem.modems[ ].success | boolean | Indicates whether detailed modem information wasread successfully. |
| lte_modem.modems[ ].command | string | Command used to read modem details, for example mmcli -m 0 --output-json. |
| lte_modem.modems[ ].manufacturer | string / null | Modem manufacturer. |
| lte_modem.modems[ ].model | string / null | Modem model. |
| lte_modem.modems[ ].revision | string / null | Modem firmware/software revision. |
| lte_modem.modems[ ].hardware_revision | string / null | Modem hardware revision. |
| lte_modem.modems[ ].device | string / null | Sysfs/USB path of the modem device. |
| lte_modem.modems[ ].drivers[ ] | array | Linux drivers associated with the modem, for example qmi_wwan or option. |
| lte_modem.modems[ ].plugin | string / null | ModemManager plugin used to manage the modem. |
| lte_modem.modems[ ].primary_port | string / null | Primary modem control port, for example cdc-wdm0. |
| lte_modem.modems[ ].ports[ ] | array | List of ports associated with the modem. |
| lte_modem.modems[ ].ports[ ].name | string | Port/interface name, for example cdc-wdm0, ttyUSB2, or wwan0. |
| lte_modem.modems[ ].ports[ ].type | string / null | Port type, for example qmi, at, gps, audio, net, or ignored. |
| lte_modem.modems[ ].state | string / null | General modem state according to ModemManager,for example registered, connected, or failed. |
| lte_modem.modems[ ].power_state | string / null | Modem power state, for example on, off, or low. |
| lte_modem.modems[ ].access_technologies[ ] | array | Current access technologies in use, for example LTE,UMTS, or GSM. |
| lte_modem.modems[ ].signal_quality.recent | string / null | Indicates whether the signal quality value is recent. |
| lte_modem.modems[ ].signal_quality.value | string / number / null | Signal quality value reported by the modem. |
| lte_modem.modems[ ].current_capabilities[ ] | array | Current modem capabilities. |
| lte_modem.modems[ ].supported_capabilities[ ] | array | Capabilities supported by the modem. |
| lte_modem.modems[ ].operator_code | string / null | MCC/MNC operator code. |
| lte_modem.modems[ ].operator_name | string / null | Operator name. |
| lte_modem.modems[ ].registration_state | string / null | Mobile network registration state. |
| lte_modem.modems[ ].packet_service_state | string / null | Mobile packet data service state. |
| lte_modem.modems[ ].sim_path | string / null | SIM path in ModemManager, when available. |
| task.taskResult.connectivity.interfaces.lte_modem.network_interfaces[] | array | List of Linux network interfaces associated with the LTE modem, usually wwan0. |
| lte_modem.network_interfaces[ ].interface | string | LTE network interface name, for example wwan0. |
| lte_modem.network_interfaces[ ].available | boolean | Indicates whether the LTE network interface exists. |
| lte_modem.network_interfaces[ ].status | string | Normalized LTE network interface status. |
| lte_modem.network_interfaces[ ].operstate | string / null | Operational state reported by Linux. |
| lte_modem.network_interfaces[ ].admin_up | boolean / null | Indicates whether the LTE network interface is administratively enabled. |
| lte_modem.network_interfaces[ ].carrier | boolean / null | Indicates whether carrier/link information is availableand active. |
| lte_modem.network_interfaces[ ].mtu | number / null | Maximum Transmission Unit of the LTE network interface. |
| lte_modem.network_interfaces[ ].type | number / null | Linux interface type. |
| lte_modem.network_interfaces[ ].link_kind | string / null | Link kind, when available. |
| lte_modem.network_interfaces[ ].mac_address | string / null | MAC address, when available. |
| lte_modem.network_interfaces[ ].ipv4[ ] | array | IPv4 addresses configured on the LTE network interface. |
| lte_modem.network_interfaces[ ].ipv6[ ] | array | IPv6 addresses configured on the LTE network interface. |
| lte_modem.network_interfaces[ ].network_manager | object / null | NetworkManager information for the LTE interface, when available. |
| task.taskResult.connectivity.interfaces.eth0.interface | string | Ethernet interface name, eth0. |
| task.taskResult.connectivity.interfaces.eth0.available | boolean | Indicates whether eth0 exists. |
| task.taskResult.connectivity.interfaces.eth0.status | string | Normalized eth0 status. |
| task.taskResult.connectivity.interfaces.eth0.operstate | string / null | Operational state of eth0. |
| task.taskResult.connectivity.interfaces.eth0.admin_up | boolean / null | Indicates whether eth0 is administratively enabled. |
| task.taskResult.connectivity.interfaces.eth0.carrier | boolean / null | Indicates whether eth0 has physical link. |
| task.taskResult.connectivity.interfaces.eth0.mtu | number / null | MTU of eth0. |
| task.taskResult.connectivity.interfaces.eth0.type | number / null | Linux interface type of eth0. |
| task.taskResult.connectivity.interfaces.eth0.link_kind | string / null | Link kind of eth0, usually null. |
| task.taskResult.connectivity.interfaces.eth0.mac_address | string / null | MAC address of eth0. |
| task.taskResult.connectivity.interfaces.eth0.ipv4[ ] | array | IPv4 addresses configured on eth0. |
| task.taskResult.connectivity.interfaces.eth0.ipv6[ ] | array | IPv6 addresses configured on eth0. |
| task.taskResult.connectivity.interfaces.eth0.network_manager | object / null | NetworkManager information for eth0. |
| task.taskResult.connectivity.interfaces.eth1.interface | string | Ethernet interface name, eth1. |
| task.taskResult.connectivity.interfaces.eth1.available | boolean | Indicates whether eth1 exists. |
| task.taskResult.connectivity.interfaces.eth1.status | string | Normalized eth1 status. |
| task.taskResult.connectivity.interfaces.eth1.operstate | string / null | Operational state of eth1. |
| task.taskResult.connectivity.interfaces.eth1.admin_up | boolean / null | Indicates whether eth1 is administratively enabled. |
| task.taskResult.connectivity.interfaces.eth1.carrier | boolean / null | Indicates whether eth1 has physical link. |
| task.taskResult.connectivity.interfaces.eth1.mtu | number / null | MTU of eth1. |
| task.taskResult.connectivity.interfaces.eth1.type | number / null | Linux interface type of eth1. |
| task.taskResult.connectivity.interfaces.eth1.link_kind | string / null | Link kind of eth1, usually null. |
| task.taskResult.connectivity.interfaces.eth1.mac_address | string / null | MAC address of eth1. |
| task.taskResult.connectivity.interfaces.eth1.ipv4[ ] | array | IPv4 addresses configured on eth1. |
| task.taskResult.connectivity.interfaces.eth1.ipv6[ ] | array | IPv6 addresses configured on eth1. |
| task.taskResult.connectivity.interfaces.eth1.network_manager | object / null | NetworkManager information for eth1. |
| task.taskResult.connectivity.interfaces.tunnel.available | boolean | Indicates whether at least one tunnel interface was detected. |
| task.taskResult.connectivity.interfaces.tunnel.interfaces[ ] | array | List of detected tunnel interfaces, for example tun0, tap0, wg0, or tailscale0. |
| tunnel.interfaces[ ].interface | string | Tunnel interface name. |
| tunnel.interfaces[ ].available | boolean | Indicates whether the tunnel interface exists. |
| tunnel.interfaces[ ].status | string | Normalized tunnel interface status. |
| tunnel.interfaces[ ].operstate | string / null | Operational state reported by Linux. |
| tunnel.interfaces[ ].admin_up | boolean / null | Indicates whether the tunnel interface isadministratively enabled. |
| tunnel.interfaces[ ].carrier | boolean / null | Carrier/link information, when available. |
| tunnel.interfaces[ ].mtu | number / null | Tunnel interface MTU. |
| tunnel.interfaces[ ].type | number / null | Linux interface type. |
| tunnel.interfaces[ ].link_kind | string / null | Tunnel link kind, for example tun, tap, or wireguard. |
| tunnel.interfaces[ ].mac_address | string / null | MAC address, when available. |
| tunnel.interfaces[ ].ipv4[ ] | array | IPv4 addresses configured on the tunnel interface. |
| tunnel.interfaces[ ].ipv6[ ] | array | IPv6 addresses configured on the tunnel interface. |
| tunnel.interfaces[ ].network_manager | object / null | NetworkManager information for the tunnel interface, when available. |
| ipv4[ ].address | string | IPv4 address assigned to the interface. |
| ipv4[ ].prefixlen | number | IPv4 network prefix length. Example: 24. |
| ipv4[ ].scope | string | IPv4 scope, for example global. |
| ipv4[ ].dynamic | boolean | Indicates whether the IPv4 address was dynamically assigned. |
| ipv6[ ].address | string | IPv6 address assigned to the interface. |
| ipv6[ ].prefixlen | number | IPv6 network prefix length. |
| ipv6[ ].scope | string | IPv6 scope, for example link or global. |
| ipv6[].dynamic | boolean | Indicates whether the IPv6 address was dynamically assigned. |
c) Get hardware info
Req-topic: systemHealth/runtime/in
{
"id": <TIMESTAMP>,
"origin": "Dev",
"task": {
"action": "GET_HW_INFO"
}
}
Reply-topic: systemHealth/runtime/hw/info/out
{
"id": <TIMESTAMP>,
"origin": "system-health",
"task": {
"action": "get_hw_info",
"taskResult": {
"cpu": {......},
"ram": {......},
"emmc": {......},
"block_devices": {......},
"filesystems": {......},
"mmc_sysfs": {......},
"emmc_extcsd": {......}
}
}
}
| Section | Description |
| cpu | CPU and board model information. |
| ram | RAM total, used, and free memory. |
| emmc | Filtered eMMC device and partition information. |
| block_devices | Complete block device list from lsblk, including eMMC, boot partitions, loop devices, zram, NVMe, USB storage, etc. |
| filesystems | Mounted filesystem usage from df -h. |
| mmc_sysfs | Low-level MMC/eMMC identity fields read from /sys/block/mmcblk*/device. |
| emmc_extcsd | Parsed eMMC EXT_CSD information from mmc extcsd read, including health, revision, RPMB, boot, and partitioning data. |
- CPU
The cpu object describes the detected board and CPU.
{
"board_model": "Raspberry Pi Compute Module 4 Rev 1.1",
"cpu": {
"architecture": "aarch64",
"op_modes": "32-bit, 64-bit",
"byte_order": "Little Endian",
"vendor": "ARM",
"model_name": "Cortex-A72",
"model": "3",
"stepping": "r0p3",
"cpu_count": 4,
"online_cpus": "0-3",
"threads_per_core": 1,
"cores_per_cluster": 4,
"clusters": 1,
"frequency": {
"scaling_percent": 60.0,
"min_mhz": 600.0,
"max_mhz": 1500.0,
"bogomips": 108.0
}
}
}
| Field | Type | Description |
|---|---|---|
| board_model | string / null | Hardware board model read from device-tree. |
| cpu.architecture | string | CPU architecture, for example aarch64. |
| cpu.op_modes | string | Supported CPU operation modes. |
| cpu.byte_order | string | CPU byte order. |
| cpu.vendor | string | CPU vendor. |
| cpu.model_name | string | CPU model name. |
| cpu.model | string / number | CPU model identifier. |
| cpu.stepping | string | CPU stepping/revision. |
| cpu.cpu_count | number | Number of detected CPU cores. |
| cpu.online_cpus | string | Online CPU list. |
| cpu.threads_per_core | number | Number of threads per core. |
| cpu.cores_per_cluster | number | Number of cores per cluster. |
| cpu.clusters | number | Number of CPU clusters. |
| cpu.frequency.scaling_percent | number / null | Current CPU frequency scaling percentage. |
| cpu.frequency.min_mhz | number / null | Minimum CPU frequency in MHz. |
| cpu.frequency.max_mhz | number / null | Maximum CPU frequency in MHz. |
| cpu.frequency.bogomips | number / null | BogoMIPS value reported by the system. |
- RAM
The ram object reports system memory usage.
| Field | Type | Description |
|---|---|---|
| total | string | Total system RAM. |
| used | string | Currently used RAM. |
| free | string | Currently free RAM. |
- EMMC
The emmc object is a filtered eMMC view. It focuses on the main eMMC disk and its partitions.
{
"devices": [
{
"name": "mmcblk0",
"model": null,
"serial": "0x121b491e",
"size": "29.1G",
"type": "disk",
"transport": "mmc",
"filesystem": null,
"mountpoints": [],
"partitions": [
{
"name": "mmcblk0p1",
"size": "512M",
"type": "part",
"filesystem": "vfat",
"mountpoints": [
"/boot/firmware"
]
},
{
"name": "mmcblk0p2",
"size": "28.6G",
"type": "part",
"filesystem": "ext4",
"mountpoints": [
"/"
]
}
]
}
]
}
Device fields
| Field | Type | Description |
|---|---|---|
| devices[ ] | array | List of detected main eMMC devices. |
| devices[ ].serial | string / null | eMMC block device name, for example mmcblk0. |
| devices[ ].size | string | Device model, when available. |
| devices[ ].type | string | Device serial number. |
| devices[ ].transport | string / null | Device size. |
| devices[ ].filesystem | string / null | Device type, usually disk. |
| devices[ ].mountpoints | array | Transport type, usually mmc. |
| devices[ ].partitions | array | Filesystem directly on the disk. Usually null when partitions exist. |
| devices[ ].name | string | Mountpoints directly associated with the disk. |
| devices[ ].model | string / null | Partitions belonging to this eMMC disk. |
Partition fields
| Field | Type | Description |
|---|---|---|
| name | string | Partition name, for example mmcblk0p1. |
| size | string | Partition size. |
| type | string | Partition type, usually part. |
| filesystem | string / null | Filesystem type, for example vfat or ext4. |
| mountpoints | array | Mountpoints for the partition. |
block_devices
The block_devices object is the complete storage/device tree returned by lsblk.
{
"devices": [
{
"name": "mmcblk0",
"model": null,
"serial": "0x121b491e",
"size": "29.1G",
"type": "disk",
"transport": "mmc",
"filesystem": null,
"mountpoints": [],
"children": [
{
"name": "mmcblk0p1",
"model": null,
"serial": null,
"size": "512M",
"type": "part",
"transport": "mmc",
"filesystem": "vfat",
"mountpoints": [
"/boot/firmware"
],
"children": []
}
]
}
],
"stderr": ""
}
| Field | Type | Description |
|---|---|---|
| devices | array | List of block devices. |
| stderr | string | Command error output, if any. |
| devices[index].name | string | Device name. |
| devices[index].model | string / null | Device model. |
| devices[index].serial | string / null | Device serial number. |
| devices[index].size | string | Device size. |
| devices[index].type | string | Device type, for example disk , part , or loop. |
| devices[index].transport | string / null | Transport type, for example mmc , nvme , or usb. |
| devices[index].filesystem | string / null | Filesystem type, for example ext4 , vfat , or swap. |
| devices[index].mountpoints | array | List of mountpoints. Empty if not mounted. |
| devices[index].children | array | Child block devices, usually partitions. |
File_systems
The file_systems object reports mounted filesystem usage, equivalent to df -h.
{
"filesystems": [
{
"source": "/dev/mmcblk0p2",
"filesystem": "ext4",
"size": "29G",
"used": "5.8G",
"available": "23G",
"use_percent": "21%",
"mountpoint": "/",
"parsed": true
},
{
"source": "/dev/nvme0n1p1",
"filesystem": "ext4",
"size": "234G",
"used": "2.1M",
"available": "222G",
"use_percent": "1%",
"mountpoint": "/mnt/nvme",
"parsed": true
}
],
"stderr": ""
}
| Field | Type | Description |
|---|---|---|
| filesystems | array | List of mounted filesystems. |
| stderr | string | Command error output, if any. |
| filesystems[index].source | string | Filesystem source, for example /dev/mmcblk0p2, tmpfs, or udev.
|
| filesystems[index].filesystem | string | Filesystem type, for example ext4, vfat, tmpfs, or devtmpfs.
|
| filesystems[index].size | string | Total filesystem size. |
| filesystems[index].used | string | Used space. |
| filesystems[index].available | string | Available space. |
| filesystems[index].use_percent | string | Used percentage. |
| filesystems[index].mountpoint | string | Mount location. |
| filesystems[index].parsed | boolean | true if the parser successfully parsed this filesystem entry.
|
mmc_sysfs
The mmc_sysfs object exposes low-level MMC/eMMC identity fields read from:
/sys/block/mmcblk*/device/This is useful for production traceability, device identification and diagnostics.
{
"source": "/sys/block",
"fields": [
"name",
"cid",
"csd",
"date",
"manfid",
"oemid",
"serial"
],
"devices": [
{
"device": "mmcblk0",
"base_path": "/sys/block/mmcblk0/device",
"available": true,
"fields": {
"name": "BJTD4R",
"cid": "150100424a5444345203121b491e7b00",
"csd": "d02701320f5903fff6dbffef8e404000",
"date": "07/2024",
"manfid": "0x000015",
"oemid": "0x0100",
"serial": "0x121b491e"
}
}
]
}
| Field | Type | Description |
|---|---|---|
| source | string | Base sysfs source path. |
| fields | array | List of sysfs fields collected. |
| devices | array | List of detected MMC/eMMC devices. |
| devices[index].device | string | MMC block device name, for example mmcblk0. |
| devices[index].base_path | string | Sysfs path used for this device. |
| devices[index].available | boolean | Indicates whether the sysfs path exists. |
| devices[index].fields.name | string / null | eMMC product name. |
| devices[index].fields.cid | string / null | Card Identification register. |
| devices[index].fields.csd | string / null | Card Specific Data register. |
| devices[index].fields.date | string / null | Manufacturing date. |
| devices[index].fields.manfid | string / null | Manufacturer ID. |
| devices[index].fields.oemid | string / null | OEM/application ID. |
| devices[index].fields.serial | string / null | eMMC serial number. |
emmc_extcsd
The emmc_extcsd object contains parsed information from:
mmc extcsd read /dev/mmcblk0This section is useful for:
| Category | Description |
|---|---|
| revision | EXT_CSD and MMC version. |
| health | eMMC lifetime and pre-EOL state. |
| boot | Boot partition/protection configuration. |
| rpmb | Replay Protected Memory Block information. |
| sector | Sector count and capacity information, when available. |
| partitioning | Partitioning and enhanced-area information. |
{
"command": "mmc extcsd read <device>",
"devices": [
{
"success": true,
"device": "/dev/mmcblk0",
"command": "mmc extcsd read /dev/mmcblk0",
"returncode": 0,
"revision": {
"ext_csd_revision": "1.8",
"mmc_version": "MMC 5.1"
},
"health": {},
"boot": {},
"rpmb": {},
"sector": {},
"partitioning": {}
}
]
}
| Field | Type | Description |
|---|---|---|
| command | string | Generic command pattern. |
| devices | array | Parsed EXT_CSD results per eMMC device. |
| success | boolean | true if EXT_CSD was read successfully for this device.
|
| devices[index].device | string | Device path, for example /dev/mmcblk0.
|
| devices[index].command | string | Exact command executed for this device. |
| devices[index].returncode | number / null | Process return code. |
| devices[index].revision | object | EXT_CSD revision and MMC version. |
| devices[index].health | object | Lifetime and pre-EOL information. |
| devices[index].boot | object | Boot-related information. |
| devices[index].rpmb | object | RPMB information. |
| devices[index].sector | object | Sector count and capacity data. |
| devices[index].partitioning | object | Partitioning and enhanced-area configuration. |
emmc_extcsd.devices[].revision
{
"ext_csd_revision": "1.8",
"mmc_version": "MMC 5.1"
}
| Field | Type | Description |
|---|---|---|
| ext_csd_revision | string | EXT_CSD revision. |
| mmc_version | string | MMC/eMMC standard version. |
emmc_extcsd.devices[].health
{
"life_time_estimation_a": {
"field": {
"register": "DEVICE_LIFE_TIME_EST_TYP_A",
"index": null,
"description": "eMMC Life Time Estimation A",
"value": "0x01",
"value_int": 1,
"value_hex": "0x1"
},
"decoded": {
"state": "normal",
"used_percent_min": 0,
"used_percent_max": 10
}
},
"life_time_estimation_b": {
"field": {
"register": "DEVICE_LIFE_TIME_EST_TYP_B",
"index": null,
"description": "eMMC Life Time Estimation B",
"value": "0x01",
"value_int": 1,
"value_hex": "0x1"
},
"decoded": {
"state": "normal",
"used_percent_min": 0,
"used_percent_max": 10
}
},
"pre_eol": {
"field": {
"register": "PRE_EOL_INFO",
"index": null,
"description": "eMMC Pre EOL information",
"value": "0x01",
"value_int": 1,
"value_hex": "0x1"
},
"decoded": {
"state": "normal"
}
}
}
Fields
| Field | Description |
|---|---|
| life_time_estimation_a | Wear estimation for eMMC memory type A. |
| life_time_estimation_b | Wear estimation for eMMC memory type B. |
| pre_eol | Pre-end-of-life state. |
Lifetime Decoding
| Raw Value | Decoded State | Meaning |
|---|---|---|
| 0x00 | not_defined | Not defined by device. |
| 0x01 | normal | 0% to 10% estimated lifetime used. |
| 0x02 | normal | 10% to 20% estimated lifetime used. |
| 0x03 | normal | 20% to 30% estimated lifetime used. |
| 0x04 | normal | 30% to 40% estimated lifetime used. |
| 0x05 | normal | 40% to 50% estimated lifetime used. |
| 0x06 | normal | 50% to 60% estimated lifetime used. |
| 0x07 | normal | 60% to 70% estimated lifetime used. |
| 0x08 | normal | 70% to 80% estimated lifetime used. |
| 0x09 | normal | 80% to 90% estimated lifetime used. |
| 0x0A | normal | 90% to 100% estimated lifetime used. |
| 0x0B | exceeded | Estimated maximum lifetime exceeded. |
Pre-EOL Decoding
| Raw Value | Decoded State | Meaning |
|---|---|---|
| 0x00 | not_defined | Not defined. |
| 0x01 | normal | Normal condition. |
| 0x02 | warning | Warning state. Monitor or plan replacement. |
| 0x03 | urgent | Urgent state. Replacement recommended. |
emmc_extcsd.devices[].boot
{
"partition_config": {
"field": null,
"decoded": null
},
"boot_config_protection": null,
"boot_bus_conditions": null,
"boot_write_protection": {
"register": "BOOT_WP",
"index": null,
"description": "Boot Area Write protection",
"value": "0x00",
"value_int": 0,
"value_hex": "0x0"
},
"boot_size": {
"field": null,
"decoded": null
}
}
| Field | Type | Description |
|---|---|---|
| partition_config | object / null | Boot partition configuration, if available. |
| boot_config_protection | object / null | Boot configuration protection status. |
| boot_bus_conditions | object / null | Boot bus width/mode conditions. |
| boot_write_protection | object / null | Boot area write protection status. |
| boot_size | object / null | Boot partition size, if available. |
emmc_extcsd.devices[].rpmb
RPMB means Replay Protected Memory Block. It is a protected eMMC area often used for secure storage.
{
"size": {
"field": {
"register": "RPMB_SIZE_MULT",
"index": null,
"description": "RPMB Size",
"value": "0x20",
"value_int": 32,
"value_hex": "0x20"
},
"decoded": {
"bytes": 4194304,
"kb": 4194.3,
"mb": 4.19,
"gb": 0.0,
"kib": 4096.0,
"mib": 4.0,
"gib": 0.0
}
}
}
| Field | Type | Description |
|---|---|---|
| size.field | object / null | Raw RPMB size register information. |
| size.decoded.bytes | number | RPMB size in bytes. |
| size.decoded.kb | number | RPMB size in decimal KB. |
| size.decoded.mb | number | RPMB size in decimal MB. |
| size.decoded.gb | number | RPMB size in decimal GB. |
| size.decoded.kib | number | RPMB size in KiB. |
| size.decoded.mib | number | RPMB size in MiB. |
| size.decoded.gib | number | RPMB size in GiB. |
emmc_extcsd.devices[ ].sector
{
"sector_count": null,
"sector_size_bytes": 512,
"user_capacity": null
}
| Field | Type | Description |
|---|---|---|
| sector_count | object / null | Parsed sector count register, if available. |
| sector_size_bytes | number | Sector size used for capacity calculation. |
| user_capacity | object / null | Calculated user capacity, if sector count is available. |
emmc_extcsd.devices[].partitioning
{
"partitioning_support": {
"register": "PARTITIONING_SUPPORT",
"index": null,
"description": "Partitioning Support",
"value": "0x07",
"value_int": 7,
"value_hex": "0x7"
},
"partition_setting_completed": {
"register": "PARTITION_SETTING_COMPLETED",
"index": null,
"description": "Partitioning Setting",
"value": "0x00",
"value_int": 0,
"value_hex": "0x0"
},
"partition_attribute": {
"register": "PARTITIONS_ATTRIBUTE",
"index": null,
"description": "Partitions attribute",
"value": "0x00",
"value_int": 0,
"value_hex": "0x0"
},
"max_enhanced_area_size": {
"register": "MAX_ENH_SIZE_MULT",
"index": null,
"description": "Max Enhanced Area Size",
"value": "0x000747",
"value_int": 1863,
"value_hex": "0x747"
},
"enhanced_user_area_start": {
"register": "ENH_START_ADDR",
"index": null,
"description": "Enhanced User Data Start Address",
"value": "0x00000000",
"value_int": 0,
"value_hex": "0x0"
},
"enhanced_user_area_size": {
"register": "ENH_SIZE_MULT",
"index": null,
"description": "Enhanced User Data Area Size",
"value": "0x000000",
"value_int": 0,
"value_hex": "0x0"
}
}
| Field | Type | Description |
|---|---|---|
| partitioning_support | object / null | Supported partitioning features. |
| partition_setting_completed | object / null | Indicates whether partition settings were finalized. |
| user_capacity | object / null | Partition attribute register. |
| max_enhanced_area_size | object / null | Maximum enhanced area size multiplier. |
| enhanced_user_area_start | object / null | Enhanced user data area start address. |
| enhanced_user_area_size | object / null | Enhanced user data area size. |
4. TempSensor Actor
4.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to configure and obtain peripherals data. Connecting to the broker for this Area requires no credentials.
a) Set temperature sensor configurations
Req-Topic: tempSensor/config/in
ALERT:
- activate: true(active)/false
- templow: alert temperature lower limit
- tempHigh: alert temperature upper limit
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskParams": {
"ALERT": {
"activate": true,
"tempLow": 0,
"tempHigh": 80
}
}
}
}
Reply-Topic: tempSensor/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true,
"ALERT": {
"activate": true,
"tempLow": 0,
"tempHigh": 80
}
}
}
}
b) Read temperature sensor configurations
Req-Topic: tempSensor/config/in
ALERT:
- activate: true(active)/false
LIMITS:
- templow: alert temperature lower limit
- tempHigh: alert temperature upper limit
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "READ"
}
}
Reply-Topic: tempSensor/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true,
"ALERT": {
"activate": true,
"tempLow": 0,
"tempHigh": 80
}
}
}
}
c) Read temperature and alert status
- unit: Temperature unit of measure
- alert: true (triggered) or false
Req-Topic: tempSensor/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "READ"
}
}
Reply-Topic: tempSensor/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true,
"data": {
"temperature": <_FLOAT_>,
"unit": "C",
"alert": true
}
}
}
}
5. TFT Actor
5.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to control and interact with the TFT display, or frontal panel LCD screen. Connecting to the broker for this Area requires no credentials.
5.2 TFT Display Actor
The TFT Actor is responsible for:
- Rendering static and informative screens on the TFT display
- Reacting to joystick input (LEFT/ RIGHT/ CENTER/ UP/ DOWN)
- Managing screen navigation and user confirmation flows
- Providing feedback to the application via MQTT
a) Screen Carousel Concept
The TFT actor maintains an ordered list of screens (carousel).
- Screens are navigated using the joystick:
- LEFT --> Previous screen
- RIGHT --> Next screen
- The carousel wraps around:
- LEFT on first screen --> last screen
- RIGHT on last screen --> first screen
b) Screen Types
The TFT actor supports the following screen types:
Image Screen
- Displays a static JPEG image
- Default screen shown at boot
- No user interaction required
Network Information Screen
- Display an interface with information about the networks of the device
- Updated on load
- No user interaction required
c) Joystick Navigation Logic
Carousel Navigation:
| Joystick Input | Action |
| LEFT | Previous screen |
| RIGHT | Next screen |
| DOWN | Refresh current screen |
d) Boot Behavior
At system boot:
- TFT actor initializes the display
- Last available carousel is loaded
- First screen is rendered
- Joystick subscriptions are activated
- Actor waits for runtime commands
e) Design Notes & Constraints
- TFT actor does not expose joystick state
- Joystick actor remains independent
- TFT actor internally consumes joystick events
- Carousel state is local to the TFT actor
- Screen rendering is synchronous; input handling is asynchronous
- Confirmation screens always require explicit CENTER press
5.3 User interaction through MQTT
Enabling interaction with the carousel using the topic tft/runtime/in . Actions available will be public to the user.
Runtime commands
Go to screen
Req-Topic: tft/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SET_CAROUSEL",
"params": {
"index": 2
}
}
}
Reply-Topic: tft/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "tft",
"task": {
"taskResult": {
"success": true,
"index": 2
}
}
}
Set TFT Screen Carousel Req-Topic: tft/runtime/in
{
"id": "TIMESTAMP",
"origin": "APP",
"task": {
"action": "SET_CAROUSEL",
"params": {
"startIndex": 0,
"screens": [
{
"type": "image",
"image": "default.jpg"
},
{
"type": "image",
"image": "status.jpg"
},
{
"type": "confirm",
"question": "Do you want to continue?"
}
]
}
}
}
Parameters:
| Field | Description |
| screens | Ordered list of screens |
| Start Index | Initial active screen (optional, default = 0) |
Reply
Reply-Topic: tft/runtime/out
{
"id": "TIMESTAMP",
"origin": "tft",
"task": {
"taskResult": {
"success": true,
"activeIndex": 0,
"totalScreens": 3
}
}
}
6. Analog Inputs Actor
6.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to configure and obtain peripherals data. Connecting to the broker for this Area requires no credentials.
a) Configurations
Topic: analogInputs/config/in - Set ADC channels configurations
mode
- continuous
- single
type
- voltage
- current
{
"id": TIMESTAMP,
"origin": "Dev",
"task": {
"taskParams": {
"AI1": {
"mode": "single",
"type": "voltage"
},
"AI2": {
"mode": "single",
"type": "voltage"
},
"AI3": {
"mode": "single",
"type": "current"
},
"AI4": {
"mode": "single",
"type": "current"
}
}
}
}
Reply-Topic: analogInputs/config/out
{
"id": TIMESTAMP,
"origin": "ioExpander",
"task": {
"taskResult": {
"AI1": {
"success": true
},
"AI2": {
"success": true
},
"AI3": {
"success": true
},
"AI4": {
"success": true
}
}
}
}
b) Runtime Data
Topic: analogInputs/runtime/in.
action:
- READ
- PAUSE (only applicable in continuous mode)
- RESUME (only applicable in continuous mode)
{
"id": TIMESTAMP,
"origin": "Dev",
"task": {
"taskParams": {
"AI1": {
"action": "READ"
},
"AI2": {
"action": "READ"
},
"AI3": {
"action": "READ"
},
"AI4": {
"action": "READ"
}
}
}
}
Reply-Topic: analogInputs/runtime/out
{
"id": TIMESTAMP,
"origin": "Dev",
"task": {
"taskResult": {
"AI1": {
"result": _FLOAT_,
"unit": "V",
},
"AI2": {
"result": _FLOAT_,
"unit": "V",
},
"AI3": {
"result": _FLOAT_,
"unit": "mA",
},
"AI4": {
"result": _FLOAT_,
"unit": "mA",
}
}
}
}
7. Analog Outputs Actor
7.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to configure and obtain peripherals data. Connecting to the broker for this Area requires no credentials.
a) Set analog outputs signal
Req-Topic: analogOutputs/config/in
Output: current within 0-20 mA
mode: on/off
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskParams": {
"mode": "on",
"AO1": {
"current": <_FLOAT_>
},
"AO2": {
"current": <_FLOAT_>
}
}
}
}
Reply-Topic: analogOutputs/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"mode": "on",
"AO1": {
"success": true,
"current": <_FLOAT_>
},
"AO2": {
"success": true,
"current": <_FLOAT_>
}
}
}
}
b) Set analog outputs
Req-Topic: analogOutputs/runtime/in
current: current within 0-20 mA
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskParams": {
"AO1": {
"action": "SET",
"current": <_FLOAT_>
},
"AO2": {
"action": "SET",
"current": <_FLOAT_>
}
}
}
}
Reply-Topic: analogOutputs/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"AO1": {
"success": true,
"current": <_FLOAT_>
},
"AO2": {
"success": true,
"current": <_FLOAT_>
}
}
}
}
c) Read analog outputs settings
Req-Topic: analogOutputs/runtime/in
current: current within 0-20 mA
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskParams": {
"AO1": {
"action": "READ"
},
"AO2": {
"action": "READ"
}
}
}
}
Reply-Topic: analogOutputs/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"AO1": {
"success": true,
"current": <_FLOAT_>
},
"AO2": {
"success": true,
"current": <_FLOAT_>
}
}
}
}
8. Digital Inputs Actor
8.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to configure and obtain peripherals data. Connecting to the broker for this Area requires no credentials.
a) Set digital input pin/pins speed fast/slow
The fast speed setting supports edge detection speed up to 100 Hz and the slow speed settings supports up to 10 Hz.
Fast Mode generates a message on every I/O state change, which may result in a high message payload during operation. Fast Mode should therefore only be enabled when strictly required.
Req-Topic: digitalInputs/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskParams": {
"DI1": {
"speed": "fast"
},
"DI2": {
"speed": "slow"
},
"DI3": {
"speed": "fast"
},
"DI4": {
"speed": "fast"
}
}
}
}
Reply-Topic: digitalInputs/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"DI1": {
"success": true,
"speed": "fast"
},
"DI2": {
"success": true,
"speed": "slow"
},
"DI3": {
"success": true,
"speed": "fast"
},
"DI4": {
"success": true,
"speed": "fast"
}
}
}
}
b) Get all digital input pins state
Req-Topic: digitalInputs/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "READ"
}
}
Reply-Topic: digitalInputs/runtime/out state: high/low
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"DI1": {
"success": true,
"state": "high"
},
"DI2": {
"success": true,
"state": "low"
},
"DI3": {
"success": true,
"state": "high"
},
"DI4": {
"success": true,
"state": "low"
}
}
}
}
9. Digital Outputs Actor
9.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to configure and obtain peripherals data. Connecting to the broker for this area requires no credentials.
a) Get all digital output pins state
Req-Topic: digitalOutputs/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "READ"
}
}
Reply-Topic: digitalOutputs/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"DO1": {
"success": true,
"state": "low"
},
"DO2": {
"success": true,
"state": "high"
},
"DO3": {
"success": true,
"state": "high"
},
"DO4": {
"success": true,
"state": "low"
},
"DO5": {
"success": true,
"state": "low"
},
"DO6": {
"success": true,
"state": "low"
},
"DO7": {
"success": true,
"state": "low"
},
"DO8": {
"success": true,
"state": "low"
}
}
}
}
b) Set all digital output pins state
Req-Topic: digitalOutputs/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskParams": {
"DO1": {
"action": "SET",
"state": "low"
},
"DO2": {
"action": "SET",
"state": "high"
},
"DO3": {
"action": "SET",
"state": "high"
},
"DO4": {
"action": "SET",
"state": "low"
},
"DO5": {
"action": "SET",
"state": "low"
},
"DO6": {
"action": "SET",
"state": "low"
},
"DO7": {
"action": "SET",
"state": "low"
},
"DO8": {
"action": "SET",
"state": "low"
}
}
}
}
Reply-Topic: digitalOutputs/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"DO1": {
"success": true,
"state": "low"
},
"DO2": {
"success": true,
"state": "high"
},
"DO3": {
"success": true,
"state": "high"
},
"DO4": {
"success": true,
"state": "high"
},
"DO5": {
"success": true,
"state": "high"
},
"DO6": {
"success": true,
"state": "low"
},
"DO7": {
"success": true,
"state": "low"
},
"DO8": {
"success": true,
"state": "low"
}
}
}
}
9.2 Notes
If the busi2c-3 is not powered, we will get this:
Topic: digit a lOutputs/runtime/ out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"DO1": {
"success": false,
"state": "DO_not_powered"
},
"DO2": {
"success": false,
"state": "DO_not_powered"
},
"DO3": {
"success": false,
"state": "DO_not_powered"
},
"DO4": {
"success": false,
"state": "DO_not_powered"
},
"DO5": {
"success": false,
"state": "DO_not_powered"
},
"DO6": {
"success": false,
"state": "DO_not_powered"
},
"DO7": {
"success": false,
"state": "DO_not_powered"
},
"DO8": {
"success": false,
"state": "DO_not_powered"
}
}
}
}
10. Joystick Actor
10.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to configure and obtain peripherals data. Connecting to the broker for this Area requires no credentials.
a) Get all joystick pins state
Req-Topic: joystick/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "READ"
}
}
Reply-Topic: joystick//runtime/out state: pressed/released
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"JLEFT": {
"success": true,
"state": "pressed"
},
"JRIGHT": {
"success": true,
"state": "released"
},
"JUP": {
"success": true,
"state": "released"
},
"JDOWN": {
"success": true,
"state": "released"
},
"JCENTER": {
"success": true,
"state": "released"
}
}
}
}
11. Buzzer Actor
11.1 Overview
The Buzzer Actor is responsible for controlling the onboard buzzer through MQTT commands. It receives runtime commands on an MQTT input topic, validates the requested buzzer operation, drives the buzzerthrough GPIO using libgpiod, and publishes status/final responses on an MQTT output topic.
The actor currently controls GPIO line 25 on /dev/gpiochip0, with ACTIVE_LOW = False. It creates a
GpiodBuzzerDriver instance and routes incoming MQTT actions to runtime handlers.
The supported runtime actions are:
| Action | Description |
| BEEP | Plays a finite beep sequence, an infinite beep sequence, or a continuous tone |
| STOP | Stops the currently active buzzer playback |
11.2 MQTT Topics
| Topic | Direction | Description |
| buzzer/runtime/in | Input | Receives buzzer commands |
| buzzer/runtime/out | Output | Publishes status messages and final command responses |
The actor subscribes to buzzer/runtime/in and publishes all status/final replies to buzzer/runtime/out.
11.3 Hardware and Datasheet Notes
The buzzer used is the MLT-8530 electro-magnetic SMD buzzer.
According to the datasheet:
| Parameter | Value |
| Rated voltage | 3.6 Vo-p |
| Operating voltage | 2.5 ~ 4.5 Vo-p |
| Rated current | Max. 95 mA |
| Rated test frequency | 2700 Hz |
| Duty cycle | 50% square wave |
| Sound output | Min. 80 dB at 10 cm |
| Coil resistance | 16 ± 3 Ω |
| Resonant frequency | 2700 Hz |
11.4 BEEP
The BEEP action supports two modes:
| Mode | Description |
| sequence | Plays one or more beeps with gaps between them |
| continuous | Plays one continuous tone until STOP is received |
If no mode is provided, the default mode is: sequence
All messages should be sent to the topic buzzer/runtime/in
a) Default Datasheet-Aligned Beep
{
"id": <TIMESTAMP>,
"origin": "APP",
"task": {
"action": "BEEP"
}
}
b) N Beeps
Play <BEEPS_QUANTITY> beeps at <FREQUENCY> Hz. Each beep lasts <BEEP_DURATION> seconds, with
<GAP_BETWEEN_BEEPS> seconds of silence between beeps.
{
"id": <TIMESTAMP>,
"origin": "APP",
"task": {
"action": "BEEP",
"params": {
"frequency_hz": <FREQUENCY>,
"duration_s": <BEEP_DURATION>,
"count": <BEEPS_QUANTITY>,
"gap_s": <GAP_BETWEEN_BEEPS>
}
}
}
If you want to play repeated beeps forever until a STOP command arrives then you only need to pass the count: 0
c) Continuous Tone
Play one continuous <FREQUENCY> Hz tone until STOP.
{
"id": <TIMESTAMP>,
"origin": "APP",
"task": {
"action": "BEEP",
"params": {
"mode": "continuous",
"frequency_hz": <FREQUENCY>
}
}
}
If frequency_hz is omitted, the handler uses the configured default frequency.
d) Stop Buzzer
If you want to stop the beep sequence, just send this payload to the buzzer/runtime/in
{
"id": <TIMESTAMP>,
"origin": "APP",
"task": {
"action": "STOP",
}
}
e) Buzzer payload parameters table
| Field | Required | Type | Default | Applies to | Description |
|---|---|---|---|---|---|
| task | Yes | object | - | All requests | Main task object. Contains the requested action and
optional parameters. |
| task.action | Yes | string | - | All requests | Action to execute. Supported values: "BEEP" and
"STOP". |
| task.params | No | object | {} | BEEP | Optional parameters for configuring the buzzer
behavior. Not needed for STOP. |
| task.params.mode | No | string | "sequence" | BEEP | Playback mode. Supported values: "sequence" and
"continuous". |
| task.params.frequency_hz | No | number | 2700.0 | BEEP | Buzzer frequency in Hz. Recommended/default value
is 2700 Hz , matching the buzzer resonant frequency. |
| task.params.duration_s | No | number | 0.2 | BEEP with
mode="sequence" |
Duration of each beep, in seconds. |
| task.params.count | No | integer | 1 | BEEP with
mode="sequence" |
Number of beeps to play. 0 means repeat forever
until STOP. |
| task.params.gap_s | No | number | 0.15 | BEEP with
mode="sequence" |
Silence gap between beeps, in seconds. |
| task.params.mode ="continuous" | No | string | - | BEEP | Plays one continuous tone until STOP . In this mode,
duration_s , count , and gap_s are ignored. |
11.5 Buzzer - diagram
11.6 Buzzer Command timing charts - Examples
The following charts represent the command-level buzzer state over time.
They do not show the internal 2700 Hz switching frequency.
Instead, they show when the buzzer is logically:
| State | Meaning |
| 1 | Buzzer active / sound ON |
| 0 | Buzzer inactive / silence |
a) Default BEEP command
{
"id": <TIMESTAMP>,
"origin": "APP",
"task": {
"action": "BEEP",
}
}
Default values used:
| Parameter | Value |
| duration_s | 0.2 s |
| count | 1 |
| gap_s | 0.15 s |
Buzzer State
duration_s = 0.2 s
count = 1
b) BEEP Sequence - 5 beeps
{
"id": 1779789836000,
"origin": "APP",
"task": {
"action": "BEEP",
"params": {
"duration_s": 1,
"count": 5,
"gap_s": <GAP_BETWEEN_BEEPS>
}
}
}
Buzzer State
gap_s = 0.5s
count = 5
c) Infinite BEEP Sequence
This happens when: "count": 0
{
"id": 1779789836000,
"origin": "APP",
"task": {
"action": "BEEP",
"params": {
"duration_s": 1,
"count": 0,
"gap_s": 0.5
}
}
}
Buzzer State
duration_s = 1.0 gap_s = 0.5
count = 0
d) Continuous BEEP
In continuous mode, the buzzer remains ON until a STOP command is received.
{
"id": 1779789836000,
"origin": "APP",
"task": {
"action": "BEEP",
"params": {
"mode": "continuous",
}
}
}
Buzzer State
mode = continuous
e) STOP
The STOP command immediately forces the buzzer to OFF.
Buzzer State
After STOP, the buzzer is forced to OFF.
12. Power Monitor actor
12.1 Overview
The Power Monitor actor is responsible for reading the ATLAS INA232 power monitor and publishing the measuredelectrical values through MQTT.
The actor publishes:
| Value | Unit | Description |
| voltage_v | V | Measured bus/input voltage |
| current_a | A | Measured current |
| power_w | W | Measured/calculated power |
The INA232 is a current, voltage, and power monitor with an I²C/SMBus interface. It reports current, busvoltage, and power values through internal registers. The calibration register must be configured to obtainvalid current and power results.
12.2 Runtime domain
- Automatic Runtime Publication
Publish topic:
ina/runtime/out
Period: 1000 ms === 1s
Response payload
{
"id": "<TIMESTAMP>",
"origin": "INA",
"task": {
"action": "PUBLISH_POWER",
"taskResult": {
"success": true,
"voltage_v": 24.12,
"current_a": 0.481,
"power_w": 11.6
}
}
}
| Field | Type | Required | Description |
| success | boolean | - | Indicates whether the read operation succeeded |
| voltage_v | number | V | Measured bus/input voltage |
| current_a | number | A | Measured current |
| power_w | number | W | Measured power |
13. Modem Actor
13.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker. Simplified interface to configure and obtainmodem (LTE) data. Connecting to the broker for this area requires no credentials.
13.2 Mobile (LTE Modem)
The Mobile actor manages the LTE modem using ModemManager (mmcli). It allows monitoring modem status, configuring connectivity (APN, operator,network mode), and performing diagnostics.
13.3 Scan Operators
Scans available network operators (requires SIM and modem enabled).
Req-Topic: modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SCAN_OPERATORS"
}
}
Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"operators": [
{
"id": "26801",
"name": "vodafone P",
"status": "available"
},
{
"id": "26806",
"name": "MEO",
"status": "available"
}
]
}
}
}
13.4 Set Operator
The supported modes are:
- auto → modem selects de operator automatically (default).
- manual→ user forces a specific operator using operator_id.
- Operators are identified using MCC + MNC codes:
| Operator | Code | Country |
| Vodafone PT | 26801 | Portugal |
| MEO | 26806 | Portugal |
| NOS | 26803 | Portugal |
These values are obtained dynamically via SCAN_OPERATORS
- Recommendations:
Default should always be auto
Use manual only when:
- You need to force roaming behavior
- You want to lock to a specific network for stability/testing
Auto Selection
Req-Topic: modem/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SET_OPERATOR",
"taskParams": {
"mode": "auto"
}
}
}
Reply-Topic: modem/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true
}
}
}
Manual Selection
Req-Topic: modem/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SET_OPERATOR",
"taskParams": {
"mode": "manual",
"operator_id": "26801"
}
}
}
Reply-Topic: modem/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true
}
}
}
13.5 Set APN Configuration
Supports automatic (predefined APN) or custom configuration.
Actual Behavior
When SET_APN is executed, the system performs the following sequence:
- Disconnects the current data connection (if connected)
- Applies the new APN configuration
- Initiates a new connection using the provided APN
- Waits until the modem reaches connected state
This means a full reconnection is automatically performed.
No additional RESET or manual reconnection is required.
Impact & Considerations
- Active data connection is interrupted
- Ongoing communications (MQTT, HTTP, VPN, etc.) will be dropped
- IP address may change after reconnection
- Operation may take several seconds (typically 5–20s)
- If connection fails, the modem may remain disconnected
Known APNs
| Operator | APN CONF
NAME |
APN | PROXY | PORT | MCC | MNC | AUTH
TYPE |
APN
TYPE |
CARRIER | OBS. |
| MEO, UZO | -- | internet | -- | -- | -- | -- | -- | -- | -- | -- |
| 1nce | -- | iot.1nce.net | -- | -- | -- | -- | -- | -- | -- | -- |
| Vodafone | Vodafone
Internet |
net2.vodafone.pt | iproxy.vodafone.pt | 80 | 268 | 01 | PAP | default | unspecified | Docs |
Supported Modes
- auto
- custom
| Mode | Description |
| auto | Uses modem default / SIM-provided APN |
| custom | Uses user-defined APN credentials |
- Auto APN (e.g. 1NCE)
Uses the default APN embedded in the modem or provided by the SIM.
Req-Topic: modem/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SET_APN",
"taskParams": {
"mode": "auto"
}
}
}
Reply-Topic: modem/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"apn": "internet",
"success": true
}
}
}
- Custom APN
Allows full control over APN configuration.
Req-Topic: modem/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SET_APN",
"taskParams": {
"mode": "custom",
"apn": "internet",
"user": "user",
"password": "pass"
}
}
}
Reply-Topic: modem/config/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"apn": "internet",
"success": true
}
}
}
13.6 Get Modem Status
Req-Topic: modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "GET_STATUS"
}
}
Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"modem": {
"present": true,
"enabled": true,
"state": "registered",
"signal": 34,
"access_tech": "lte",
"operator": "vodafone P",
"sim": "ready"
}
}
}
}
13.7 Enable / Disable Modem
Req-Topic: modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SET_MODEM_STATE",
"taskParams": {
"enabled": true
}
}
}
Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"enabled": true,
"success": true
}
}
}
13.8 Reset Modem
Performs a full modem reset and recovery sequence.
Req-Topic: modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "RESET"
}
}
Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"success": true
}
}
}
Actual Behavior
When RESET is executed, the system performs a complete modem restart flow:
- Executes system-level reset: ltectl reset-modem
- The modem disappears from the system (USB re-enumeration)
- The system waits for the modem to reappear
- Re-discovers the modem path
- Waits until the modem becomes ready
- Waits until the modem is connected again
This is a full recovery sequence, including automatic reconnection.
Impact on active connection
- Active data connection is immediately terminated
- All ongoing communications (MQTT, VPN, HTTP, etc.) will be interrupted
- Modem temporarily disappears from the system
- A new network registration is performed
- IP address will likely change
- Total recovery time may take 10–120 seconds
When to use
Use RESET only when:
- Modem is stuck in searching or not registering
- Connected but no data traffic is possible
- APN/operator changes are not taking effect
- Modem becomes unresponsive or unstable
When NOT to Use
Avoid using RESET :
- During OTA updates or file uploads
- During critical real-time operations
- If a simple reconnect ( SET_MODEM_STATE ) is sufficient
Application Considerations
Before RESET
- Ensure no critical operations are ongoing
- Persist any unsent or buffered data
- Expect loss of connectivity
After RESET
- Wait for modem to fully recover (can take up to ~2 minutes)
- Re-check modem status using: GET_STATUS
- Re-establish application-level connections: MQTT; VPN an Cloud sessions
13.9 Set Network Mode
Configures allowed radio technologies
Supported values:
- 2g
- 3g
- 4g
- auto
Req-Topic: modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SET_NETWORK_MODE",
"taskParams": {
"mode": "4g"
}
}
}
Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"mode": "4g",
"success": true
}
}
}
13.10 Get Signal Strength
Req-Topic: modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "GET_SIGNAL"
}
}
Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"signal": 34,
"unit": "%"
}
}
}
13.11 Get SIM card info
GET_SIM_INFO is a runtime action used to read SIM card information from the modem through ModemManager.
This action is read-only. It does not change the modem state, APN, operator, bearer, or NetworkManager configuration.
Req-Topic: modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "GET_SIM_INFO"
}
}
Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "modem-actor",
"task": {
"taskResult": {
"success": true,
"sim": {
"id": "0",
"path": "/org/freedesktop/ModemManager1/SIM/0",
"active": true,
"iccid": "8935101234567890123",
"imsi": "268060123456789",
"operator_id": "26806",
"operator_name": "MEO",
"emergency_numbers": [
"112"
],
"preferred_networks": [
"operator-code: 42507, access-technologies: gsm, umts, lte",
"operator-code: 20810, access-technologies: gsm, umts, lte",
"operator-code: 64710, access-technologies: gsm, umts, lte",
"operator-code: 37001, access-technologies: gsm, umts, lte",
"operator-code: 34002, access-technologies: gsm, umts, lte"
]
}
}
}
}
| Field | Type | Description |
| id | number / string | Same request ID received in the input payload. |
| origin | string | Actor name, usually modem-actor. |
| task.taskResult.success | boolean | Indicates if the SIM information was read successfully. |
| task.taskResult.sim.id | string | SIM object ID used by ModemManager. Usually 0. |
| task.taskResult.sim.path | string | ModemManager DBus path for the SIM object. |
| task.taskResult.sim.active | boolean / null | Indicates if the SIM is active, when available. |
| task.taskResult.sim.iccid | string / null | SIM ICCID / SIM identifier. This is the physical SIM card identifier. |
| task.taskResult.sim.imsi | string / null | IMSI associated with the SIM subscription. |
| task.taskResult.sim.operator_id | string / null | Mobile operator identifier from the SIM, when available. |
| task.taskResult.sim.operator_name | string / null | Operator name from the SIM, when available. |
| task.taskResult.sim.emergency_numbers | array | Emergency numbers reported by the SIM/modem. |
| task.taskResult.sim.preferred_networks | array | Preferred networks stored on the SIM, when available. |
13.12 Test Modem
Performs a basic diagnostic test:
- Modem detection
- SIM presence
- Network registration
- Data connectivity
Req-Topic: modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "TEST"
}
}
Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"hw": true,
"sim": true,
"network": true,
"data": true
}
}
}
13.13 Speed Test
Performs a low-data network speed test over the modem interface.
This command does not run a full bandwidth benchmark.
Instead, it performs a short and capped download burst on interface wwan0 , measures the number of received bytes during a fixed time window, and
returns an estimated download speed together with latency and a confidence level.
This approach is designed for IoT / field devices using capped cellular SIM cards, where a classic speed test would consume too much data.
Req-Topic: modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"action": "SPEED_TEST"
}
}
Reply-Topic: modem/runtime/out Success reply:
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"download_mbps": 12.4,
"success": true
}
}
}
Error reply Example when the modem is not connected:
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"download_mbps": 12.4,
"success": true
}
}
}
Example when an unexpected internal error occurs:
{
"id": "<TIMESTAMP>",
"origin": "modem-actor",
"task": {
"taskResult": {
"success": false,
"reason": "unexpected_exception"
}
}
}
Reply parameters
| Parameter | Type | Description |
| success | boolean | Indicates whether the speed test completed successfully. true = test executed, false = failed (see reason ). |
| download_mbps | number | Estimated download speed in megabits per second (Mbps). Calculated from the number of bytes received on interface
wwan0 during the measurement window. |
| bytes_sampled | number | Total number of received bytes measured on the modem interface ( wwan0 ) during the test window. Used as the raw input
for speed calculation. |
| duration_s | number | Measurement window duration in seconds. Current implementation uses 5 seconds. |
| latency_ms | number | null |
| confidence | string | Qualitative indicator of measurement reliability based on sampled bytes:• low → < 50,000 bytes• medium → 50,000–
299,999 bytes• high → ≥ 300,000 bytes |
| reason | string | Present only when success=false . Provides failure reason (e.g., not_connected (<state>) , unexpected_exception ). |
Data consumption notice
This command performs a real network download and therefore consumes mobile data.
Although the test is designed to be low-data, it still generates traffic:
- Typical consumption per test: ~100 KB to ~500 KB
- Latency measurement adds a negligible amount (ICMP ping)
Estimated data usage
| Frequency | Estimated consumption |
| 1 test/day (~100 KB) | ~3 MB/month |
| 1 test/day (~250 KB) | ~7.5 MB/month |
| 1 test/day (~500 KB) | ~15 MB/month |
Recommendation
For devices using capped SIM cards, it is recommended to:
- Avoid running this test at high frequency
- Use signal and modem status ( mmcli ) for regular monitoring
- Reserve speed tests for:
- commissioning
- troubleshooting
- periodic health checks
Running frequent or extended tests may significantly impact the data allowance.
13.14 Get Raw Modem Info (Reserved)
Req-Topic: reserved/modem/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "modem-actor",
"task": {
"taskResult": {
"success": false,
"reason": "not_connected (registered)"
}
}
}
Reply-Topic: reserved/modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "DEV",
"task": {
"taskResult": {
"raw": "mmcli -m 0 output..."
}
}
}
13.15 Example: Raw Modem Output ( mmcli )
Command:
mmcli -m 0 --output-json
Example output:
{
"modem": {
"3gpp": {
"5gnr": {
"registration-settings": {
"drx-cycle": "--",
"mico-mode": "--"
}
},
"enabled-locks": [],
"eps": {
"initial-bearer": {
"dbus-path": "--",
"settings": {
"apn": "--",
"ip-type": "--",
"password": "--",
"user": "--"
}
},
"ue-mode-operation": "--"
},
"imei": "862636056280785",
"network-rejection-access-technology": "--",
"network-rejection-error": "--",
"network-rejection-operator-id": "--",
"network-rejection-operator-name": "--",
"operator-code": "--",
"operator-name": "--",
"packet-service-state": "--",
"pco": "--",
"registration-state": "--"
},
"cdma": {
"activation-state": "--",
"cdma1x-registration-state": "--",
"esn": "--",
"evdo-registration-state": "--",
"meid": "--",
"nid": "--",
"sid": "--"
},
"dbus-path": "/org/freedesktop/ModemManager1/Modem/0",
"generic": {
"access-technologies": [],
"bearers": [],
"carrier-configuration": "ROW_Gen_VoLTE",
"carrier-configuration-revision": "05010822",
"current-bands": [
"egsm",
"dcs",
"pcs",
"g850",
"utran-1",
"utran-4",
"utran-6",
"utran-5",
"utran-8",
"utran-2",
"eutran-1",
"eutran-2",
"eutran-3",
"eutran-4",
"eutran-5",
"eutran-7",
"eutran-8",
"eutran-12",
"eutran-13",
"eutran-18",
"eutran-19",
"eutran-20",
"eutran-25",
"eutran-26",
"eutran-28",
"eutran-34",
"eutran-38",
"eutran-39",
"eutran-40",
"eutran-41",
"eutran-66",
"utran-19"
],
"current-capabilities": [
"gsm-umts, lte"
],
"current-modes": "allowed: 4g; preferred: none",
"device": "/sys/devices/platform/scb/fe9c0000.xhci/usb1/1-1/1-1.4",
"device-identifier": "2c759b7021f62d2e26973150063ea040b3662a65",
"drivers": [
"option",
"qmi_wwan"
],
"equipment-identifier": "862636056280785",
"hardware-revision": "10000",
"manufacturer": "QUALCOMM INCORPORATED",
"model": "SIMCOM_SIM7600G-H",
"own-numbers": [],
"physdev": "/sys/devices/platform/scb/fe9c0000.xhci/usb1/1-1/1-1.4",
"plugin": "simtech",
"ports": [
"cdc-wdm0 (qmi)",
"ttyUSB0 (ignored)",
"ttyUSB1 (gps)",
"ttyUSB2 (at)",
"ttyUSB3 (at)",
"ttyUSB4 (audio)",
"wwan0 (net)"
],
"power-state": "on",
"primary-port": "cdc-wdm0",
"primary-sim-slot": "1",
"revision": "LE20B04SIM7600G22",
"signal-quality": {
"recent": "yes",
"value": "0"
},
"sim": "/org/freedesktop/ModemManager1/SIM/0",
"sim-slots": [
"/org/freedesktop/ModemManager1/SIM/0",
"/"
],
"state": "failed",
"state-failed-reason": "sim-missing",
"supported-bands": [
"egsm",
"dcs",
"pcs",
"g850",
"utran-1",
"utran-4",
"utran-6",
"utran-5",
"utran-8",
"utran-2",
"eutran-1",
"eutran-2",
"eutran-3",
"eutran-4",
"eutran-5",
"eutran-7",
"eutran-8",
"eutran-12",
"eutran-13",
"eutran-18",
"eutran-19",
"eutran-20",
"eutran-25",
"eutran-26",
"eutran-28",
"eutran-34",
"eutran-38",
"eutran-39",
"eutran-40",
"eutran-41",
"eutran-66",
"utran-19"
],
"supported-capabilities": [
"gsm-umts, lte"
],
"supported-ip-families": [
"ipv4",
"ipv6",
"ipv4v6"
],
"supported-modes": [
"allowed: 2g; preferred: none",
"allowed: 3g; preferred: none",
"allowed: 4g; preferred: none",
"allowed: 2g, 3g; preferred: 3g",
"allowed: 2g, 3g; preferred: 2g",
"allowed: 2g, 4g; preferred: 4g",
"allowed: 2g, 4g; preferred: 2g",
"allowed: 3g, 4g; preferred: 4g",
"allowed: 3g, 4g; preferred: 3g",
"allowed: 2g, 3g, 4g; preferred: 4g",
"allowed: 2g, 3g, 4g; preferred: 3g",
"allowed: 2g, 3g, 4g; preferred: 2g"
],
"unlock-required": "--",
"unlock-retries": []
}
}
}
Field Reference - mmcli -m 0 --output-json
| JSON Path | Meaning | Example Value | Notes / Interpretation |
| modem.dbus-path | Internal modem path | /org/.../Modem/0 | Used by mmcli ( -m 0 ) |
| modem.generic.manufacturer | Modem vendor | QUALCOMM INCORPORATED | Hardware info |
| modem.generic.model | Modem model | SIM7600G-H | |
| modem.generic.revision | Firmware version | LE20B04SIM7600G22 | Useful for debugging |
| modem.generic.device | Linux device path | /sys/devices/... | USB device |
| modem.generic.drivers | Kernel drivers | option, qmi_wwan | Must be present |
| modem.generic.primary-port | Control interface | cdc-wdm0 | Critical (QMI control) |
| modem.generic.ports | Available interfaces | ttyUSB*, wwan0 | Data + AT + GPS |
| modem.generic.power-state | Power status | on | |
| modem.generic.state | Modem state | failed | Main status field |
| modem.generic.state-failed-reason | Failure reason | sim-missing | Root cause of failure |
| modem.generic.signal-quality.value | Signal strength (%) | 0 | 0 = no signal |
| modem.generic.current-capabilities | Active radio tech | gsm-umts, lte | |
| modem.generic.current-modes | Active mode | allowed: 4g | Controlled via API |
| modem.generic.current-bands | Active frequency bands | [eutran-1, ...] | Coverage related |
| modem.generic.supported-bands | Supported bands | [eutran-*, utran-*] | Hardware capability |
| modem.generic.supported-ip-families | IP support | ipv4, ipv6, ipv4v6 | |
| modem.generic.sim | SIM path | /org/.../SIM/0 | |
| modem.generic.sim-slots | SIM slots | [slot1, slot2] | Slot 2 empty here |
| modem.generic.primary-sim-slot | Active SIM slot | 1 | |
| modem.generic.bearers | Active data sessions | [ ] | Empty = no connection |
| modem.3gpp.imei | Device IMEI | 862636056280785 | Unique identifier |
| modem.3gpp.operator-name | Network name | -- | Not available (no SIM) |
| modem.3gpp.operator-code | MCC/MNC | -- | Not available |
| modem.3gpp.registration-state | Network state | -- | Not registered |
| modem.3gpp.packet-service-state | Data service state | -- | No data session |
| modem.3gpp.eps.initial-bearer.settings.apn | APN | -- | Not set / no connection |
13.16 Notes:
- If no modem is detected: Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"modem": {
"present": false
}
}
}
}
- If SIM is locked or missing: Reply-Topic: modem/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"taskResult": {
"modem": {
"sim": "locked"
}
}
}
}
- Operator scan may take several seconds and should be handled asynchronously.
- Some actions require:
- SIM card inserted
- Modem enabled
- cdc-wdm0 interface available
- Signal strength is reported as percentage and may be cached by ModemActor.
14. CANopen Actor
14.1 App Interfacing
User App interface and functionalities exposed to the open part of the MQTT broker.
The CANopen Actor provides a generic protocol interface to CANopen devices.
The actor does NOT interpret device-specific meaning (Object Dictionary semantics).
It only handles communication (HOW to talk).
14.2 Transport and Message Envelope
a) MQTT Topics
| Topic | Direction | Description |
canopenRM8007/config/in
|
Request | Configuration commands, open broker area. |
canopenRM8007/config/out
|
Reply | Configuration replies. |
canopenRM8007/runtime/in
|
Request | Runtime commands, open broker area. |
canopenRM8007/runtime/out
|
Reply | Runtime replies and every continuous sample publication. |
reserved/canopenRM8007/config/in
|
Request | Same configuration commands, reserved (internal) broker area. |
reserved/canopenRM8007/config/out
|
Reply | Configuration replies for the reserved area. |
reserved/canopenRM8007/runtime/in
|
Request | Same runtime commands, reserved (internal) broker area. |
reserved/canopenRM8007/runtime/out
|
Reply | Runtime replies for the reserved area. |
Every reply is published on the request topic with /in replaced by /out.
Continuous sample publications (READ_CONTINUOUS, READ_PDO_CONTINUOUS) are always published on canopenRM8007/runtime/out, even when the start command arrived on reserved/canopenRM8007/runtime/in. Only the start/stop acknowledgement follows the request topic.
All publications use QoS 0 and are not retained.
b) Alias Defaults
task.alias should always be sent. When it is missing or empty, the actor assumes:
| Request topic | Assumed alias |
|---|---|
.../config/in
|
CONFIG_CANOPEN
|
.../runtime/in
|
PING
|
A payload that is not valid JSON is logged and discarded. No reply is published.
c) Node Reference (Yes*)
Parameters marked Yes* in the tables below mean "one of node_id, nodeId or node". The actor resolves them in this order:
node- logical name previously stored withCONFIG_CANOPEN. The stored node configuration is merged withtaskParams, andtaskParamswins on conflicts. An unknown name fails withunknown configured node <name>.node_id- numeric CANopen node ID (1..127).nodeId- alternative spelling fornode_id.- Single-node fallback - if none of the three is present and exactly one node is configured, that node is used automatically. With zero configured nodes, or with more than one, the command fails with
taskParams must include node_id, nodeId or node.
The single-node fallback does not apply to READ: with no node reference, READ reads every configured node (see Read All Configured Nodes).
Yes** marks a parameter that is mandatory for the operation but can also be resolved from the stored node configuration when node is used.
d) Error Envelope
There are two different error shapes, and they differ in origin.
Handler-level error - the command reached its handler and the CANopen operation failed in a controlled way. origin is canopen-actor:
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "SDO_READ",
"taskResult": {
"success": false,
"error": "SDO timeout 0x6004:00"
}
}
}
Actor-level error - unknown alias, malformed parameters, or an exception raised inside a handler. origin is Dev, not canopen-actor, and taskResult carries only success and error:
{
"id": "<TIMESTAMP>",
"origin": "Dev",
"task": {
"alias": "SDO_READ",
"taskResult": {
"success": false,
"error": "missing integer parameter"
}
}
}
Applications must therefore key on task.taskResult.success, never on origin.
| Error message | Envelope | Cause |
|---|---|---|
unknown alias <ALIAS>
|
Dev
|
Alias not implemented for that topic family. |
taskParams must include node_id, nodeId or node
|
Dev
|
No node reference and the single-node fallback did not apply. |
unknown configured node <name>
|
Dev
|
node name was never stored with CONFIG_CANOPEN.
|
missing integer parameter
|
Dev
|
A required index or value was not sent.
|
invalid literal for int() with base 0: ...
|
Dev
|
A hex string parameter is malformed. |
timeout
|
canopen-actor
|
The hardware owner did not answer the internal RPC in time. |
SDO timeout 0x<IDX>:<SUB>
|
canopen-actor
|
The node did not answer the SDO request. |
SDO abort 0x<IDX>:<SUB> -> 0x<CODE> (<NAME>)
|
canopen-actor
|
The node rejected the SDO request. |
14.3 Configuration
Configure CANopen Nodes
Req-Topic: canopenRM8007/config/in
Stores CANopen defaults and logical node configuration inside the actor.
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"alias": "CONFIG_CANOPEN",
"taskParams": {
"defaults": {
"sdo_timeout": "<SDO_TIMEOUT>"
},
"nodes": {
"<NODE_NAME>": {
"node_id": "<NODE_ID>",
"steps_per_rev": "<STEPS_PER_REV>"
}
}
}
}
}
Reply-Topic: canopenRM8007/config/out
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "CONFIG_CANOPEN",
"taskResult": {
"defaults": {
"success": true
},
"motor_1": {
"success": true
}
}
}
}
Request Parameters
| Parameter | Type | Required | Example | Description |
| id | string | Yes | "1777041089180" | Unique request identifier. The same value isreturned in the response, allowing the applicationto match requests and responses. |
| origin | string | No | "APP" | Identifies who sent the request. Common values canbe
APP, NodeRED, CLI, or TestClient. |
| task | object | Yes | {...} | Main command object containing the CANopencommand alias and parameters. |
| task.alias | string | Yes | "CONFIG_CANOPEN" | Defines the command to execute. For this section,the alias must be
CONFIG_CANOPEN. |
| task.taskParams | object | Yes | {...} | Contains the configuration data to store inside theCANopen actor. |
| task.taskParams.defaults | object | No | { "sdo_timeout":2.0 } | Defines default CANopen settings used by the actorwhen no command-specific value is provided. |
| task.taskParams.defaults.sdo_timeout | number | No | 2.0 | Default timeout, in seconds, used for SDOoperations. For example, 2.0
means the actor waitsup to 2 seconds for an SDO response. |
| task.taskParams.nodes | object | Yes | { "motor_1": {...}} | List of logical CANopen nodes known by the actor.Each key is a user-defined node name. |
| task.taskParams.nodes.<NODE_NAME> | object | Yes | "motor_1" | Logical name used to reference a node later withoutrepeatedly sending its
node_id. |
| task.taskParams.nodes.<NODE_NAME>.node_id | integer | Yes | 5 | CANopen node ID on the CAN bus. Valid CANopennode IDs are normally in the range
1..127. |
| task.taskParams.nodes.<NODE_NAME>.steps_per_rev | integer | Recommended | 4096 | Number of raw position counts corresponding toone full mechanical revolution, 360°. The actor usesthis later to convert raw position into revolutions and angle_deg. |
Response Parameters
| Parameter | Type | Example | Description |
| id | string | "1777041089180" | Same request identifier received in the request. Used to correlate the
response with the original command. |
| origin | string | "canopen-actor" | Identifies the component that generated the response. For successful
handler replies, this is canopen-actor. |
| task | object | {...} | Main response object containing the executed alias and the command
result. |
| task.alias | string | "CONFIG_CANOPEN" | Alias of the command that was executed. |
| task.taskResult | object | {...} | Result object containing the configuration outcome. |
| task.taskResult.defaults.success | boolean | true | Indicates that the defaults configuration was successfully stored. This
field is present only if defaults was included in the request. |
| task.taskResult.<NODE_NAME>.success | boolean | true | Indicates that the logical node configuration was successfully stored.
Example: taskResult.motor_1.success = true. |
| task.taskResult.success | boolean | true | Generic success field. This appears when no specific defaults or nodes
result is generated. |
| task.taskResult.error | string | "unknown alias
CONFIG_X" |
Error description. This field is present only when the command fails. |
CONFIG_CANOPEN does not communicate with the CANopen device directly. It stores logical node configuration inside the actor so that later runtime
commands can refer to nodes by name instead of always sending the numeric node_id.
Configure one CANopen Transmit PDO
Configures a TPDO to transmit position data.
The handler validates pdo_num as 1..4, defaults map_index to 0x6004, map_subindex to 0x00, map_bits to 32, transmission_type to 255 , event_timer_ms
to 20, and inhibit_time_100us to 0.
Req-Topic: canopenRM8007/config/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"alias": "CONFIG_POSITION_TPDO",
"taskParams": {
"node_id": 5,
"pdo_num": 1,
"map_index": "0x6004",
"map_subindex": "0x00",
"map_bits": 32,
"transmission_type": 255,
"event_timer_ms": 2,
"inhibit_time_100us": 20,
"offset": 0,
"size": 4,
"signed": false,
"steps_per_rev": 4096,
"timeout": 2.0
}
}
}
Reply-Topic: canopenRM8007/config/out
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "CONFIG_POSITION_TPDO",
"taskResult": {
"success": true,
"node_id": 5,
"pdo_num": 1,
"comm_index": 6144,
"map_index": 6656,
"mapped_object": "0x6004:00/32",
"transmission_type": 255,
"event_timer_ms": 2,
"inhibit_time_100us": 20,
"cob_id": 389
}
}
}
Parameter description
| Parameter | Type | Required | Default | Description |
| node_id | int | Yes* | — | CANopen node ID. |
| node | string | Yes* | — | Logical node name configured previously with CONFIG_CANOPEN. |
| pdo_num | int | No | 1 | TPDO number. Valid range: 1..4. |
| map_index | int/string | No | 0x6004 | Object dictionary index to map. Hex strings such as "0x6004" are
supported. |
| map_subindex | int/string | No | 0x00 | Object dictionary subindex. |
| map_bits | int | No | 32 | Number of mapped bits. |
| transmission_type | int | No | 255 | CANopen TPDO transmission type. |
| event_timer_ms | int | No | 20 | TPDO event timer in milliseconds. |
| inhibit_time_100us | int | No | 0 | Inhibit time in 100 μs units. |
| offset | int | No | 0 | Byte offset used later when decoding PDO position. |
| size | int | No | 4 | Number of bytes used later when decoding PDO position. |
| signed | bool | No | false | Whether decoded raw position should be interpreted as signed. |
| steps_per_rev | int | Recommended | 4096 if saved into node
config |
Encoder steps per revolution. |
| timeout | float | No | actor default | SDO/NMT operation timeout. |
14.4 Runtime Operations
Supported runtime aliases include READ, READ_CONTINUOUS, STOP_READ_CONTINUOUS, READ_PDO_CONTINUOUS, STOP_READ_PDO_CONTINUOUS, PING, NMT_COMMAND, SDO_READ, SDO_WRITE, READ_HEARTBEAT, RECV_PDO, RECV_EMCY, and QUERY_NODE_INFO.
NMT Control
Req-Topic: canopenRM8007/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"alias": "CONFIG_CANOPEN",
"taskParams": {
"defaults": {
"sdo_timeout": "<SDO_TIMEOUT>"
},
"nodes": {
"<NODE_NAME>": {
"node_id": "<NODE_ID>",
"steps_per_rev": "<STEPS_PER_REV>"
}
}
}
}
}
Reply-Topic: canopenRM8007/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "NMT_COMMAND",
"taskResult": {
"success": true,
"id": "nmt-start-node5",
"node_id": 5,
"command": "START",
"confirmed": true,
"state": null
}
}
}
- Supported NMT Commands
| Command | Meaning | Notes |
| START | Puts the node into Operational state. | If wait = true, the actor waits for heartbeat state OPERATIONAL. |
| STOP | Puts the node into Stopped state. | If wait = true, the actor waits for heartbeat state STOPPED. |
| PRE_OP | Puts the node into Pre-Operational state. | Used before operations such as PDO remapping. |
| PRE-OP | Same as PRE_OP. | Alternative accepted spelling. |
| PREOP | Same as PRE_OP. | Alternative accepted spelling. |
| PRE_OPERATIONAL | Same as PRE_OP. | Alternative accepted spelling. |
| RESET_NODE | Resets the CANopen node application. | If wait = true, the actor waits for boot-up heartbeat. |
| RESET | Same as RESET_NODE. | Alternative accepted spelling. |
| RESET_COMM | Resets CANopen communication. | Sends NMT reset communication command. |
- Request Parameters
| Parameter | Type | Required | Example | Description |
| id | string | Yes | "<TIMESTAMP>" | Unique request identifier. The same value is returned in the response. |
| origin | string | No | "APP" | Identifies the sender of the request, for example APP , NodeRED , CLI , or
TestClient. |
| task | object | Yes | {...} | Main command object containing the command alias and parameters. |
| task.alias | string | Yes | "NMT_COMMAND" | Defines the command to execute. For this operation, it must be
NMT_COMMAND. |
| task.taskParams | object | Yes | {...} | Contains the NMT command parameters. |
| task.taskParams.node_id | integer | Yes | 5 | Numeric CANopen node ID that will receive the NMT command. |
| task.taskParams.nodeId | integer | Yes | 5 | Alternative spelling for node_id. |
| task.taskParams.node | string | Yes | "motor_1" | Logical node name previously configured with CONFIG_CANOPEN. |
| task.taskParams.command | string | No | "START" | NMT command to send. If omitted, the actor defaults to START. |
| task.taskParams.wait | boolean | No | false | If true, the actor waits for heartbeat/state confirmation when supported. If
false, the command is sent without waiting for confirmation. |
| task.taskParams.timeout | number | No | 5.0 | Timeout, in seconds, used when waiting for NMT confirmation.
Default is 5.0. |
| task.taskParams.sdo_timeout | number | No | 2.0 | Internal timeout used when creating/updating the CANopen node wrapper.
Usually not required for basic NMT usage. |
- Response Parameters
| Parameter | Type | Example | Description |
| id | string | "<TIMESTAMP>" | Same request identifier received in the request. Used to correlate request and
response. |
| origin | string | "canopen-actor" | Identifies the component that generated the response. |
| task | object | {...} | Main response object. |
| task.alias | string | "NMT_COMMAND" | Alias of the command that was executed. |
| task.taskResult | object | {...} | Result object containing the NMT command result. |
| task.taskResult.success | boolean | true | Indicates whether the actor successfully executed the NMT command. |
| task.taskResult.id | string | "nmt-start-node5" | Internal/request ID returned by the CANopen RPC call. Usually matches the
original request id. |
| task.taskResult.node_id | integer | 5 | CANopen node ID that received the NMT command. |
| task.taskResult.command | string | "START" | NMT command that was executed. For aliases such as RESET , the response may
normalize the command to RESET_NODE. |
| task.taskResult.confirmed | boolean | true | Indicates whether the command was considered successful. If wait = false , this
usually means the command was sent successfully. If wait = true , this means the expected heartbeat/state confirmation was received. |
| task.taskResult.state | string/null | null | Confirmed NMT state when wait = true and state confirmation is available.
Otherwise it is null . Possible values include OPERATIONAL , STOPPED , and PRE_OP. |
| task.taskResult.error | string | "unsupported NMT
command X" |
Error description. Present only when the command fails. |
SDO Read
Reads an object dictionary value using SDO upload.
Req-Topic: canopenRM8007/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"alias": "SDO_READ",
"taskParams": {
"node_id": 5,
"index": "0x6004",
"subindex": "0x00",
"timeout": 2.0
}
}
}
Reply-Topic: canopenRM8007/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "SDO_READ",
"taskResult": {
"success": true,
"id": "sdo-read-position-node5",
"node_id": 5,
"index": 24580,
"subindex": 0,
"value": 123456
}
}
}
- Request Parameters
| Parameter | Type | Required | Example | Description |
| id | string | Yes | "<TIMESTAMP>" | Unique request identifier. The same value is returned in the response. |
| origin | string | No | "APP" | Identifies the sender of the request, for example APP , NodeRED , CLI , or
TestClient. |
| task | object | Yes | {...} | Main command object containing the command alias and parameters. |
| task.alias | string | Yes | "SDO_READ" | Defines the command to execute. For this operation, it must be SDO_READ. |
| task.taskParams | object | Yes | {...} | Contains the SDO read parameters. |
| task.taskParams.node_id | integer | Yes | 5 | Numeric CANopen node ID to read from. |
| task.taskParams.nodeId | integer | Yes | 5 | Alternative spelling for node_id. |
| task.taskParams.node | string | Yes | "motor_1" | Logical node name previously configured with CONFIG_CANOPEN. |
| task.taskParams.index | integer/string | Yes | "0x6004" | CANopen object dictionary index to read. Hex strings such as "0x6004"
are supported. |
| task.taskParams.subindex | integer/string | No | "0x00" | CANopen object dictionary subindex to read. If omitted, defaults to 0. |
| task.taskParams.timeout | number | No | 2.0 | Timeout, in seconds, used for the SDO read operation. If omitted, the
actor default SDO timeout is used. |
- Response Parameters
| Parameter | Type | Example | Description |
| id | string | "<TIMESTAMP>" | Same request identifier received in the request. Used to correlate request and
response. |
| origin | string | "canopen-actor" | Identifies the component that generated the response. |
| task | object | {...} | Main response object. |
| task.alias | string | "SDO_READ" | Alias of the command that was executed. |
| task.taskResult | object | {...} | Result object containing the SDO read result.
task |
| task.taskResult.success | boolean | true | Indicates whether the SDO read was successful. |
| task.taskResult.id | string | "sdo-read-positionnode5" | Internal/request ID returned by the CANopen RPC call. Usually matches the
original request id. |
| task.taskResult.node_id | integer | 5 | CANopen node ID that was read. |
| task.taskResult.index | integer | 24580 | Object dictionary index that was read, returned as decimal. Example: 24580 =
0x6004. |
| task.taskResult.subindex | integer | 0 | Object dictionary subindex that was read. |
| task.taskResult.value | integer | 123456 | Raw value returned by the SDO read operation.
task. |
| task.taskResult.error | string | "SDO timeout
0x6004:00" |
Error description. Present only when the command fails.
SDO |
- SDO Write
Writes an object dictionary value using SDO download.
Req-Topic: canopenRM8007/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"alias": "SDO_WRITE",
"taskParams": {
"node_id": 5,
"index": "0x6003",
"subindex": "0x00",
"value": 0,
"size": 4,
"timeout": 2.0
}
}
}
Reply-Topic: canopenRM8007/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "SDO_WRITE",
"taskResult": {
"success": true,
"id": "sdo-write-node5",
"node_id": 5,
"index": 24579,
"subindex": 0,
"value": 0,
"size": 4
}
}
}
- Parameter descirption
| Parameter | Type | Required | Default | Description |
| node_id / nodeId / node | int/string | Yes | — | CANopen node reference. |
| index | int/string | Yes | — | Object dictionary index. |
| subindex | int/string | No | 0 | Object dictionary subindex. |
| value | int/string | Yes | — | Value to write. |
| size | int | No | 4 | Number of bytes: usually 1 , 2 , or 4. |
| timeout | float | No | actor default | SDO timeout in seconds. |
- Request Parameters
| Parameter | Type | Required | Example | Description |
| id | string | Yes | "<TIMESTAMP>" | Unique request identifier. The same value is returned in the response. |
| origin | string | No | "APP" | Identifies the sender of the request, for example APP , NodeRED , CLI , or
TestClient. |
| task | object | Yes | {...} | Main command object containing the command alias and parameters. |
| task.alias | string | Yes | "SDO_WRITE" | Defines the command to execute. For this operation, it must be
SDO_WRITE. |
| task.taskParams | object | Yes | {...} | Contains the SDO write parameters. |
| task.taskParams.node_id | integer | Yes* | 5 | Numeric CANopen node ID to write to. |
| task.taskParams.nodeId | integer | Yes* | 5 | Alternative spelling for node_id. |
| task.taskParams.node | string | Yes* | "motor_1" | Logical node name previously configured with CONFIG_CANOPEN. |
| task.taskParams.index | integer/string | Yes | "0x6003" | CANopen object dictionary index to write. Hex strings such as "0x6003"
are supported. |
| task.taskParams.subindex | integer/string | No | "0x00" | CANopen object dictionary subindex to write. If omitted, defaults to 0. |
| task.taskParams.value | integer/string | Yes | 0 | Value to write into the selected object dictionary entry. Hex strings are
also supported. |
| task.taskParams.size | integer | No | 4 | Number of bytes to write. Valid values are normally 1 , 2 , 3 , or 4 . If
omitted, defaults to 4. |
| task.taskParams.timeout | number | No | 2.0 | Timeout, in seconds, used for the SDO write operation. If omitted, the
actor default SDO timeout is used. |
- Response Parameters
| Parameter | Type | Example | Description |
| id | string | "<TIMESTAMP>" | Same request identifier received in the request. Used to correlate request andresponse. |
| origin | string | "canopen-actor" | Identifies the component that generated the response. |
| task | object | {...} | Main response object. |
| task.alias | string | "SDO_WRITE" | Alias of the command that was executed. |
| task.taskResult | object | {...} | Result object containing the SDO write result. |
| task.taskResult.success | boolean | true | Indicates whether the SDO write was successful. |
| task.taskResult.id | string | "sdo-write-node5" | Internal/request ID returned by the CANopen RPC call.
Usually matches theoriginal request id. |
| task.taskResult.node_id | integer | 5 | CANopen node ID that was written to. |
| task.taskResult.index | integer | 24579 | Object dictionary index that was written, returned as decimal.
Example: 24579 =0x6003. |
| task.taskResult.subindex | integer | 0 | Object dictionary subindex that was written. |
| task.taskResult.value | integer | 0 | Value that was written. |
| task.taskResult.size | integer | 4 | Number of bytes written. |
| task.taskResult.error | string | "SDO abort 0x6003:00 ->..." | Error description. Present only when the command fails. |
Read Node State
Reads position and returns raw value, revolutions, and angle. Internally, this reads the configured position object, defaulting to
0x6004:00. If steps_per_rev is not provided, the owner reads 0x6501:00 to obtain it.
Req-Topic: canopenRM8007/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"alias": "READ",
"taskParams": {
"node_id": 5,
"index": "0x6004",
"subindex": "0x00",
"steps_per_rev": 4096,
"timeout": 2.0
}
}
}
Reply-Topic: canopenRM8007/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "READ",
"taskResult": {
"success": true,
"id": "read-node5",
"node_id": 5,
"raw": 1024,
"steps_per_rev": 4096,
"revolutions": 0.25,
"angle_deg": 90.0,
"index": 24580,
"subindex": 0
}
}
}
- Request Parameters
| Parameter | Type | Required | Example | Description |
| id | string | Yes | "<TIMESTAMP>" | Unique request identifier. The same value is returned in theresponse. |
| origin | string | No | "APP" | Identifies the sender of the request, for example APP, NodeRED, CLI, or TestClient. |
| task | object | Yes | {...} | Main command object containing the command alias andparameters. |
| task.alias | string | Yes | "READ" | Defines the command to execute. For this operation, it must be READ. |
| task.taskParams | object | Yes | {...} | Contains the position read parameters. |
| task.taskParams.node_id | integer | Yes* | 5 | Numeric CANopen node ID to read from. |
| task.taskParams.nodeId | integer | Yes* | 5 | Alternative spelling for node_id. |
| task.taskParams.node | string | Yes* | "motor_1" | Logical node name previously configured with CONFIG_CANOPEN. |
| task.taskParams.index | integer/string | No | "0x6004" | CANopen object dictionary index used as the raw positionsource. If omitted, defaults to 0x6004. |
| task.taskParams.subindex | integer/string | No | "0x00" | CANopen object dictionary subindex used as the raw positionsource. If omitted, defaults to 0x00. |
| task.taskParams.steps_per_rev | integer | Recommended | 4096 | Number of raw counts per mechanical revolution. Used tocalculate
revolutions and angle_deg. If omitted, the ownerattempts to read 0x6501:00. |
| task.taskParams.timeout | number | No | 2.0 | Timeout, in seconds, used for the internal SDO reads. Ifomitted, the actor default SDO timeout is used. |
| task.taskParams.sdo_timeout | number | No | 2.0 | Internal SDO timeout used when creating/updating the nodewrapper. Usually not required. |
- Response Parameters
| Parameter | Type | Example | Description |
| id | string | "<TIMESTAMP>" | Same request identifier received in the request. Used to correlate request andresponse. |
| origin | string | "canopen-actor" | Identifies the component that generated the response. |
| task | object | {...} | Main response object. |
| task.alias | string | "READ" | Alias of the command that was executed. |
| task.taskResult | object | {...} | Result object containing the position read result. |
| task.taskResult.success | boolean | true | Indicates whether the position read was successful. |
| task.taskResult.id | string | "read-node5" | Internal/request ID returned by the CANopen RPC call. Usually matches theoriginal request id. |
| task.taskResult.node_id | integer | 5 | CANopen node ID that was read. |
| task.taskResult.raw | integer | 1024 | Raw position value read from the selected object dictionary entry. |
| task.taskResult.steps_per_rev | integer | 4096 | Number of raw counts per revolution used for the conversion. |
| task.taskResult.revolutions | number | 0.25 | Raw position converted into revolutions. Example: 1024 / 4096 = 0.25. |
| task.taskResult.angle_deg | number | 90.0 | Position inside one revolution, in degrees. Example: 0.25 × 360 = 90°. |
| task.taskResult.index | integer | 24580 | Object dictionary index read as the raw position source. Example: 24580 =0x6004. |
| task.taskResult.subindex | integer | 0 | Object dictionary subindex read as the raw position source. |
| task.taskResult.error | string | "SDO timeout0x6004:00" | Error description. Present only when the command fails. |
READ SDO Continuous
Starts continuous SDO-based position reading.
The initial command returns an acknowledgement. After that, continuous samples are published repeatedly to canopen/runtime/out
The continuous loop requires a single node reference and requires steps_per_rev.
- Start request
Req-Topic: canopenRM8007/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"alias": "READ_CONTINUOUS",
"taskParams": {
"node_id": 5,
"steps_per_rev": 4096,
"index": "0x6004",
"subindex": "0x00",
"timeout": 0.2,
"interval_ms": 20
}
}
}
Reply-Topic: canopenRM8007/runtime/out
// Start acknowledgement
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "READ_CONTINUOUS",
"taskResult": {
"success": true,
"running": true,
"mode": "single",
"interval_ms": 20
}
}
}
// Continuous sample response
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "READ_CONTINUOUS",
"taskResult": {
"success": true,
"node_id": 5,
"angle_deg": 90.0,
"steps_per_rev": 4096,
"revolutions": 0.25,
"raw": 1024,
"index": 24580,
"subindex": 0,
"continuous": true,
"request_id": "read-cont-node5"
}
}
}
- Start Request Parameters
| Parameter | Type | Required | Example | Description |
| id | string | Yes | "<TIMESTAMP>" | Unique request identifier. Continuous sample responses include
this value as request_id. |
| origin | string | No | "APP" | Identifies the sender of the request, for example APP, NodeRED,
CLI, or TestClient. |
| task | object | Yes | {...} | Main command object containing the command alias and
parameters. |
| task.alias | string | Yes | "READ_CONTINUOUS" | Defines the command to execute. For this operation, it must be
READ_CONTINUOUS. |
| task.taskParams | object | Yes | {...} | Contains the continuous read parameters. |
| task.taskParams.node_id | integer | Yes* | 5 | Numeric CANopen node ID to read continuously. |
| task.taskParams.nodeId | integer | Yes* | 5 | Alternative spelling for node_id. |
| task.taskParams.node | string | Yes* | "motor_1" | Logical node name previously configured with CONFIG_CANOPEN. |
| task.taskParams.steps_per_rev | integer | Yes** | 4096 | Number of raw counts per mechanical revolution. Required by
the fast continuous read loop to calculate revolutions and angle_deg. |
| task.taskParams.index | integer/string | No | "0x6004" | CANopen object dictionary index used as the raw position
source. If omitted, defaults to 0x6004. |
| task.taskParams.subindex | integer/string | No | "0x00" | CANopen object dictionary subindex used as the raw position
source. If omitted, defaults to 0x00. |
| task.taskParams.timeout | number | No | 0.2 | Timeout, in seconds, used for each fast SDO read cycle. If
omitted, defaults to 0.2. |
| task.taskParams.interval_ms | integer | No | 20 | Delay between continuous read cycles, in milliseconds. If 0 , the
loop runs again immediately after each cycle yields control. |
| task.taskParams.action | string | No | "START" | Optional action. If set to STOP , OFF , CANCEL , or DISABLE , the
actor stops the continuous read loop instead of starting it. |
- Start Acknowledgement Response Parameters
| Parameter | Type | Example | Description |
| id | string | "<TIMESTAMP>" | Same request identifier received in the start request. |
| origin | string | "canopen-actor" | Identifies the component that generated the response. |
| task | object | {...} | Main response object. |
| task.alias | string | "READ_CONTINUOUS" | Alias of the command that was executed. |
| task.taskResult | object | {...} | Result object confirming that continuous reading was started. |
| task.taskResult.success | boolean | true | Indicates whether the start command was accepted. |
| task.taskResult.running | boolean | true | Indicates that the continuous read loop is running. |
| task.taskResult.mode | string | "single" | Read mode. In this fast continuous path, a single node is used when node_id ,
nodeId , or node is provided. |
| task.taskResult.interval_ms | integer | 20 | Configured delay between continuous read cycles, in milliseconds. |
| task.taskResult.error | string | "no configured nodes" | Error description. Present only when the start command fails. |
- Continuous Sample Response Parameters
| Parameter | Type | Example | Description |
| id | integer/string | 1777041089180 | Sample response ID generated by the actor for each read cycle. |
| origin | string | "canopen-actor" | Identifies the component that generated the sample response. |
| task | object | {...} | Main response object. |
| task.alias | string | "READ_CONTINUOUS" | Alias of the running continuous command. |
| task.taskResult | object | {...} | Result object containing one continuous position sample. |
| task.taskResult.success | boolean | true | Indicates whether this specific read cycle was successful. |
| task.taskResult.node_id | integer | 5 | CANopen node ID that was read. |
| task.taskResult.angle_deg | number | 90.0 | Position inside one revolution, in degrees. |
| task.taskResult.steps_per_rev | integer | 4096 | Number of raw counts per revolution used for conversion. |
| task.taskResult.revolutions | number | 0.25 | Raw position converted into revolutions. |
| task.taskResult.raw | integer | 1024 | Raw position value read from the node. |
| task.taskResult.index | integer | 24580 | Object dictionary index read as the raw position source. Example: 24580 =
0x6004. |
| task.taskResult.subindex | integer | 0 | Object dictionary subindex read as the raw position source. |
| task.taskResult.continuous | boolean | true | Marks this response as a continuous sample, not just the initial
acknowledgement. |
| task.taskResult.request_id | string | "read-cont-node5" | Original request id that started the continuous loop. Useful to correlate
samples with the start command. |
| task.taskResult.error | string | "operation failed" | Error description for a failed sample. Present only when a read cycle fails. |
Stop request
You can stop using either STOP_READ_CONTINUOUS :
Req-Topic: canopenRM8007/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"alias": "STOP_READ_CONTINUOUS",
"taskParams": {}
}
}
Or by sending READ_CONTINUOUS with action: "STOP" :
{
"id": "stop-read-cont",
"origin": "APP",
"task": {
"alias": "READ_CONTINUOUS",
"taskParams": {
"action": "STOP"
}
}
}
Reply-Topic: canopenRM8007/runtime/out
{
"id": "stop-read-cont",
"origin": "canopen-actor",
"task": {
"alias": "READ_CONTINUOUS",
"taskResult": {
"success": true,
"running": false,
"stopped": true
}
}
}
- Stop Request Parameters
| Parameter | Type | Required | Example | Description |
| id | string | Yes | "stop-read-cont" | Unique request identifier. |
| origin | string | No | "APP" | Identifies the sender of the request. |
| task.alias | string | Yes | "READ_CONTINUOUS" or
"STOP_READ_CONTINUOUS" |
Command used to stop the continuous read loop. |
| task.taskParams.action | string | No | "STOP" | When using READ_CONTINUOUS , set action to STOP , OFF , CANCEL ,
or DISABLE to stop the loop. Not required when using STOP_READ_CONTINUOUS. |
- Stop Response Parameters
| Parameter | Type | Example | Description |
| id | string | "stop-read-cont" | Same request identifier received in the stop request. |
| origin | string | "canopen-actor" | Identifies the component that generated the response. |
| task.alias | string | "READ_CONTINUOUS" | Alias returned by the stop handler. |
| task.taskResult.success | boolean | true | Indicates whether the stop request was processed successfully. |
| task.taskResult.running | boolean | false | Indicates that the continuous read loop is no longer running. |
| task.taskResult.stopped | boolean | true | Indicates whether there was an active task that was actually stopped. If no continuous
task was running, this may be false. |
Continuous PDO Reading
Starts continuous PDO-based position reading.
This command does not perform repeated SDO reads. Instead, it waits for TPDO frames already being transmitted by the CANopen node.
Normally, the TPDO should be configured first using CONFIG_POSITION_TPDO.
The initial command returns an acknowledgement. After that, continuous sample responses are published repeatedly to: canopenRM8007/runtime/out
- Start Request
Req-Topic: canopenRM8007/runtime/in
{
"id": "<TIMESTAMP>",
"origin": "APP",
"task": {
"alias": "READ_PDO_CONTINUOUS",
"taskParams": {
"node_id": 5,
"pdo_num": 1,
"steps_per_rev": 4096,
"offset": 0,
"size": 4,
"signed": false,
"timeout": 1.0,
"interval_ms": 20
}
}
}
- Start Acknowledgement
Reply-Topic: canopenRM8007/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "READ_PDO_CONTINUOUS",
"taskResult": {
"success": true,
"running": true,
"mode": "pdo",
"pdo_num": 1,
"interval_ms": 20
}
}
}
- Continuous Sample Response
Reply-Topic: canopenRM8007/runtime/out
{
"id": "<TIMESTAMP>",
"origin": "canopen-actor",
"task": {
"alias": "READ_PDO_CONTINUOUS",
"taskResult": {
"success": true,
"node_id": 5,
"pdo_num": 1,
"angle_deg": 90.0,
"steps_per_rev": 4096,
"revolutions": 0.25,
"raw": 1024,
"data": [0, 4, 0, 0],
"offset": 0,
"size": 4,
"signed": false,
"continuous": true,
"request_id": "read-pdo-cont-node5"
}
}
}
Start Request Parameters
| Parameter | Type | Required | Example | Description |
| id | string | Yes | "<TIMESTAMP>" | Unique request identifier. Continuous sample responses include
this value as request_id. |
| origin | string | No | "APP" | Identifies the sender of the request, for example APP , NodeRED ,
CLI , or TestClient. |
| task | object | Yes | {...} | Main command object containing the command alias and
parameters. |
| task.alias | string | Yes | "READ_PDO_CONTINUOUS" | Defines the command to execute. |
| task.taskParams | object | Yes | {...} | Contains the PDO continuous read parameters. |
| task.taskParams.node_id | integer | Yes* | 5 | Numeric CANopen node ID to read from. |
| task.taskParams.nodeId | integer | Yes* | 5 | Alternative spelling for node_id. |
| task.taskParams.node | string | Yes* | "motor_1" | Logical node name previously configured with CONFIG_CANOPEN. If
used, the actor can reuse saved steps_per_rev , pdo_num , offset , size , and signed values from the node configuration. |
| task.taskParams.pdo_num | integer | No | 1 | TPDO number to listen to. If omitted, defaults to 1. |
| task.taskParams.steps_per_rev | integer | Yes** | 4096 | Number of raw position counts per mechanical revolution. Used
to convert raw into revolutions and angle_deg. |
| task.taskParams.offset | integer | No | 0 | Byte offset inside the PDO payload where the position value
starts. |
| task.taskParams.size | integer | No | 4 | Number of bytes to decode from the PDO payload. For a 32-bit
position value, use 4. |
| task.taskParams.signed | boolean | No | false | Defines whether the raw value should be decoded as signed or
unsigned. |
| task.taskParams.timeout | number | No | 1.0 | Timeout, in seconds, while waiting for each TPDO frame. If no
TPDO is received within this time, the cycle fails. |
| task.taskParams.interval_ms | integer | No | 20 | Delay between read cycles, in milliseconds. If 0 , the loop
immediately starts the next cycle after yielding control. |
| task.taskParams.action | string | No | "START" | Optional action. If set to STOP , OFF , CANCEL , or DISABLE , the
actor stops the continuous loop instead of starting it. |
- Start Acknowledgement Response Parameters
| Parameter | Type | Example | Description |
| id | string | "<TIMESTAMP>" | Same request identifier received in the start request. |
| origin | string | "canopen-actor" | Identifies the component that generated the response. |
| task | object | {...} | Main response object. |
| task.alias | string | "READ_PDO_CONTINUOUS" | Alias of the command that was executed. |
| task.taskResult | object | {...} | Result object confirming whether the continuous PDO read
was started. |
| task.taskResult.success | boolean | true | Indicates whether the start command was accepted. |
| task.taskResult.running | boolean | true | Indicates that the continuous PDO read loop is running. |
| task.taskResult.mode | string | "pdo" | Indicates that the loop is using PDO reception mode. |
| task.taskResult.pdo_num | integer | 1 | TPDO number being listened to. |
| task.taskResult.interval_ms | integer | 20 | Configured delay between read cycles, in milliseconds. |
| task.taskResult.error | string | "READ_PDO_CONTINUOUS requires a single
node" |
Error description. Present only when the start command
fails. |
- Continuous Sample Response Parameters
| Parameter | Type | Example | Description |
| id | integer/string | 1777041089180 | Sample response ID generated by the actor for each PDO read cycle. |
| origin | string | "canopen-actor" | Identifies the component that generated the sample. |
| task | object | {...} | Main response object. |
| task.alias | string | "READ_PDO_CONTINUOUS" | Alias of the running continuous command.
task. |
| task.taskResult | object | {...} | Result object containing one decoded PDO position sample. |
| task.taskResult.success | boolean | true | Indicates whether this specific PDO read cycle was successful. |
| task.taskResult.node_id | integer | 5 | CANopen node ID that produced the TPDO. |
| task.taskResult.pdo_num | integer | 1 | TPDO number received and decoded. |
| task.taskResult.angle_deg | number | 90.0 | Position inside one revolution, in degrees. |
| task.taskResult.steps_per_rev | integer | 4096 | Number of raw counts per revolution used for conversion. |
| task.taskResult.revolutions | number | 0.25 | Raw position converted into revolutions. Example: 1024 / 4096 = 0.25 . |
| task.taskResult.raw | integer | 1024 | Decoded raw position value from the PDO payload. |
| task.taskResult.data | array | [0, 4, 0, 0] | Raw PDO payload bytes received from the CANopen node. |
| task.taskResult.offset | integer | 0 | Byte offset used to extract the raw position value from data. |
| task.taskResult.size | integer | 4 | Number of bytes decoded from data. |
| task.taskResult.signed | boolean | false | Indicates whether the raw value was decoded as signed or unsigned. |
| task.taskResult.continuous | boolean | true | Marks this response as a continuous sample. |
| task.taskResult.request_id | string | "read-pdo-cont-node5" | Original request id that started the continuous loop. Useful to
correlate samples with the start command. |
| task.taskResult.error | string | "pdo operation failed" | Error description. Present only when a sample fails. |
Stop Request
Req-Topic: canopenRM8007/runtime/in
{
"id": "stop-pdo-cont",
"origin": "APP",
"task": {
"alias": "STOP_READ_PDO_CONTINUOUS",
"taskParams": {}
}
}
Alternative stop request using READ_PDO_CONTINUOUS:
{
"id": "stop-pdo-cont",
"origin": "APP",
"task": {
"alias": "READ_PDO_CONTINUOUS",
"taskParams": {
"action": "STOP"
}
}
}
Stop Response
Reply-Topic: canopenRM8007/runtime/out
{
"id": "stop-pdo-cont",
"origin": "canopen-actor",
"task": {
"alias": "READ_PDO_CONTINUOUS",
"taskResult": {
"success": true,
"running": false,
"stopped": true
}
}
}
- Stop Response Parameters
| Parameter | Type | Example | Description |
| id | string | "stop-pdo-cont" | Same request identifier received in the stop request. |
| origin | string | "canopen-actor" | Identifies the component that generated the response. |
| task.alias | string | "READ_PDO_CONTINUOUS" | Alias returned by the stop handler. |
| task.taskResult.success | boolean | true | Indicates whether the stop request was processed successfully. |
| task.taskResult.running | boolean | false | Indicates that the continuous PDO loop is no longer running. |
| task.taskResult.stopped | boolean | true | Indicates whether there was an active continuous task that was actually stopped. If
no task was running, this may be false. |
Decode Logic
The received PDO payload is decoded using:
- offset: first byte to read from the PDO payload;
- size: number of bytes to read;
- signed: whether the value is signed or unsigned;
- steps_per_rev: scaling factor used to calculate revolutions and angle.
The raw value is decoded as little-endian:
raw = int.from_bytes(data[offset : offset + size], byteorder="little", signed=signed)
Then:
revolutions = raw / steps_per_rev
angle_deg = ((raw % steps_per_rev) / steps_per_rev) * 360.0
14.4 Design Principles
- Protocol-Only Actor
The CANopen Actor is responsible for:
- CAN frame encoding/decoding;
- SDO communication;
- PDO reception (raw);
- NMT control;
- Heartbeat monitoring.
- No Device Semantics
The actor:
Does NOT know:
- What 0x6004 represents;
- Units (RPM, mm, °C, etc.);
- Device-specific logic.
Only returns:
- Raw values;
- Protocol-level responses.
- Node Management
- Nodes must be defined via configuration;
- Actor maintains node state internally;
- Supports multiple nodes simultaneously.
Summary
The CANopen Actor:
- Implements CANopen protocol communication;
- Provides generic MQTT interface;
- Supports multi-node networks;
- Keeps strict separation between transport and semantics.
15 SMS Actor
15.1 Overview
The ATLAS system can be remotely controlled over SMS. The SMS Actor listens to the cellular modem for incoming text messages and reacts to them in two ways: it reports every received SMS over MQTT, and it recognises a small set of predefined text commands that trigger actions directly on the device — without needing network or MQTT connectivity.
This is useful when a device is unreachable over its normal network path (e.g. no internet, VPN down, misconfigured mobile connection) but still has GSM/SIM coverage. An operator can send a plain SMS to the device's SIM number to remotely reboot it, reset its mobile connection, toggle its digital outputs/relays, or manage SSH/mobile data/VPN.
The SMS Actor does not expose a request/response MQTT command interface for external callers, like other actors (e.g. the eMMC actor). It is event-driven: it reacts to SMS messages arriving on the modem and publishes results to MQTT. There is no .../runtime/intopic for external callers to send it commands over MQTT. Internally, the actor does use an MQTT request/response bridge to the Digital Outputs and Relays actors to carry out the OUT##/RL# commands (see Command execution).
15.2 Supported SMS Commands
The actor recognises two kinds of commands:
- Fixed commands — the trimmed SMS body must exactly equal one of the values below. The auto-reply (if any) is fixed and is sent before the action executes, since the action itself may interrupt connectivity. What actually decides that timing is whether the command declares a static answer, not whether it takes a parameter. Only
resetMobileConnectiondeclares one.rebootis a fixed command with no static answer, so it follows the post-execution path like the parameterized commands — and because its handler returns no answer text, no SMS reply is ever sent for it. - Parameterized commands — the SMS body matches a pattern with a parameter (output index, on/off, open/close). The auto-reply reflects the actual result of the action and is sent after it executes.
A single SMS may contain multiple lines, but every recognised line must be the same action — e.g. several RL#:Open|Close lines to drive both relays together. Mixing different actions in one SMS (even two different parameterized commands, or a parameterized command together with a fixed one) is rejected outright: nothing executes. See Multiple commands per SMS.
a) Fixed commands
| SMS text | Action | Auto-reply SMS | Destructive |
| reboot | Reboots the device | none | Yes |
| resetMobileConnection | Resets the mobile connection (modem disable/enable cycle) | resetOnGoing | Yes |
b) Parameterized commands
| SMS text | Action | Auto-reply SMS (sent after execution) | Destructive |
| OUT[##]:High|Low | set_digital_output | OUT[##]St:High|Low | No |
| RL[1|2]:Open|Close | set_relay | RL[1|2]St:Open|Close | No |
| VPN:On|Off | set_vpn | VPNST:On<IpAddress>|VPNST:Off | No |
| SSH:On|Off | set_ssh | SSHST:On|Off | No |
| MobileData:On|Off | set_mobile_data | MobileDataStatus:On|Off | Yes (interrupts mobile connectivity) |
- The Destructive column above describes real-world impact, not the
destructiveflag published over MQTT. That flag is set only byrebootandresetMobileConnection.MobileData:Offcuts connectivity just as effectively but is not flagged, and the field is omitted entirely rather than sent asfalse(see SMS_COMMAND_STARTED).
##is a zero-padded digital output index01-08, mapped toDO1-DO8. Any other index (e.g.OUT09,OUT99) is treated as an unrecognised SMS.RL1/RL2map to the sameDO1/DO2hardware naming used internally by the relay actor.- Matching is case-sensitive and exact — the SMS body must equal/match one of the patterns above (after trimming whitespace).
- Any SMS that does not match a known fixed or parameterized command is still reported over MQTT (
SMS_RECEIVED) but no action is taken. - An SMS whose recognised lines are not all the same action (e.g. a relay command mixed with
reboot, or with a digital output command) is treated the same way: reported viaSMS_RECEIVED, but no command runs and no auto-reply is sent (see Multiple commands per SMS). - Unlike the fixed commands, a parameterized command's auto-reply is only sent when the action succeeds. If it fails, no SMS reply is sent, but the failure is still published over MQTT (see Command result (parameterized commands)).
Security note: there is currently no sender allow-list or authentication on inbound SMS commands. Any SMS with the exact matching text/pattern will trigger the corresponding action, regardless of sender. Restrict knowledge of the SIM number and treat it as a sensitive credential.
c) Multiple commands per SMS
An SMS body can carry several commands at once, one per line, so a single message can drive multiple outputs/relays together, e.g.:
RL1:Close
RL2:CloseThis is accepted because both lines are the same action (set_relay). But every recognised line in the SMS must be the same action — mixing, e.g.:
RL1:Close
rebootis rejected outright: none of the lines run, only a warning is logged, and no auto-reply is sent (same as an SMS matching no command at all). This applies to any combination of different actions, not just fixed-vs-parameterized — a set_relay line mixed with a set_digital_output line is rejected too.
- Each non-blank line is matched independently against the known command patterns (fixed and parameterized alike). Lines that don't match anything are silently skipped and don't count toward the action check — only the recognised lines' actions are compared.
- If every recognised line shares the same action, commands run sequentially, in the order their lines appear in the SMS. If the same output/relay appears more than once (e.g.
RL1:ClosethenRL1:Open), the last one wins. - Each line is tracked as its own
SMS_COMMAND_STARTED/ command-result MQTT event (see MQTT Interface), withsmsPayloadset to that line's text rather than the full SMS body. - The auto-replies for all lines that succeeded are combined into a single SMS reply, one line per successful command, in the same order the commands were received (e.g.
RL1St:Close\nRL2St:Close). Lines whose action failed contribute no reply line, mirroring the single-command behaviour.
15.3 How It Works
a) Startup cleanup
When the actor starts, it deletes all pre-existing SMS stored on the modem/SIM. This is a one-shot cleanup, not a periodic action.
It is controlled by the delete_existing_on_start constructor argument, which defaults to True and is not overridden where the actor is created. There is no configuration file, environment variable or MQTT command that changes it: disabling it requires a code change. The same applies to delete_sms_after_processing.
A failure during cleanup is logged as a warning and does not stop the actor. An SMS that could not be deleted stays in modem/SIM storage and is not added to the seen set, so a later D-Bus signal for that same message would be processed as if it were new.
b) Listening for SMS
The actor runs a background thread subscribed to ModemManager D-Bus signals (Added and Completed on the Modem.Messaging interface). There is no polling loop — processing is purely event-driven, triggered as soon as the modem reports a new SMS object.
Because multipart SMS may not be immediately readable, the actor retries reading a new message a few times before giving up (10 attempts, 0.75s apart). If the message still cannot be read, it is logged as a warning and deleted without being reported.
| Outcome | Behaviour |
Never readable — every mmcli -s <path> attempt failed
|
Logged as a warning, deleted and marked as seen. No SMS_RECEIVED is published and no command runs.
|
Readable but never ready — it was read, but stayed in state receiving/unknown with no text and no sender across all 10 attempts
|
The last snapshot read is used anyway and processing continues normally from there, including command matching. |
A message that becomes ready earlier returns immediately, without waiting out the remaining attempts.
c) Message deletion
Processed SMS are deleted from the modem/SIM after handling, by default. This keeps SIM storage from filling up and prevents the same message from being processed twice.
d) Command execution
Fixed commands: once matched, the fixed auto-reply (if any) is sent, then the actor waits briefly (2s) to let the MQTT publish and SMS reply/delete complete, then executes the underlying action:
reboot— runssystemctl reboot.resetMobileConnection— runsmmcli -m <modem> --disable, waits 3s, thenmmcli -m <modem> --enable.
Parameterized commands: the SMS is deleted immediately (same as fixed commands), the action executes, and only then — if it succeeded — is the computed auto-reply sent:
set_digital_output(OUT##:High|Low) — calls the Digital Outputs actor (busi2c-3process) over an MQTT request/response bridge: publishes{"action": "SET", "taskParams": {"DO<n>": {"state": "high"|"low"}}}todigitalOutputs/runtime/inand awaits the matching response ondigitalOutputs/runtime/out(correlated byid, 5s timeout).set_relay(RL1|2:Open|Close) — same bridge mechanism, targeting the Relays actor (busi2c-1process) viarelayOutputs/runtime/in/relayOutputs/runtime/out.set_ssh(SSH:On|Off) — runssystemctl start|stopon bothssh.socketandssh.service(start/stop only, does not change boot-time enablement). Both units are stopped/started —ssh.socketalone stays listening on :22 and socket-activatesssh.serviceon the next connection, so stopping only the service would not actually block new SSH sessions.set_mobile_data(MobileData:On|Off) — reuses the samenmcli/systemd modem enable/disable flow as the MQTTSET_MODEM_STATEcommand (not themmcli-based path used byresetMobileConnection), so there is a single mechanism for enabling/disabling the modem connection.set_vpn(VPN:On|Off) — runssystemctl start|stopon whichever VPN unit is installed. A device is provisioned with exactly one technology, never both, and the handler detects which one by checkingsystemctl show -p LoadStatefor each in turn:
| Order | Unit | Tunnel interface |
| --------- | -------------------------------- | --------------------- |
| 1 | wg-quick@wg0.service | wg0 |
| 2 | openvpn@pi | tun0 |
WireGuard is checked first, as the current default for new deployments. If neither unit is loadable, nothing is executed and the result is { "success": false, "error": "no VPN unit installed on this device" }.
The tunnel itself (keys, certificates, peer, AllowedIPs) is provisioned out-of-band on both the device and the gateway; this command only starts/stops the unit and never touches the config.
After a successful start/stop, the handler also runs systemctl enable|disable on the same unit so the requested state survives a reboot, and reports the outcome as result.persisted. A failure there is logged as a warning and does not fail the command: the tunnel is already in the requested state, only the next boot will not remember it.
On On, the reply includes the tunnel's IPv4 address read back via ip -4 addr show dev <interface> (e.g. VPNST:On10.100.0.28); if the address cannot be read, the reply is still VPNST:On with no address, and result.tunnelIp is null. On Off, the reply is VPNST:Off.
15.4 MQTT Interface
The SMS Actor publishes its events on a single topic and accepts no inbound commands over MQTT. It does subscribe to two topics, but only to collect the replies to its own bridge requests:
| Topic | Direction | Purpose |
sms/runtime/out
|
Publish | Every event described below. |
digitalOutputs/runtime/in
|
Publish | SET request for the OUT## command.
|
digitalOutputs/runtime/out
|
Subscribe | Reply to that request, correlated by id.
|
relayOutputs/runtime/in
|
Publish | SET request for the RL# command.
|
relayOutputs/runtime/out
|
Subscribe | Reply to that request, correlated by id.
|
A message on a bridge response topic whose id matches no pending request is silently ignored, as is one that is not valid JSON.
The MQTT client id is <name>-<epoch_ms>, freshly generated on each start so that a lingering previous process does not cause the broker to disconnect the new one. It is not the same as origin, which is the plain actor name.
Publish topic: sms/runtime/out (QoS 0, not retained)
All messages follow the standard ATLAS MQTT envelope, with the result carried in task.taskResult (not task.taskParams, unlike the request/response actors):
{
"id": "<TIMESTAMP>",
"origin": "SMSActor",
"task": {
"action": "<ACTION_OR_COMMAND_NAME>",
"taskResult": { }
}
}
id: millisecond epoch timestamp, generated fresh for each publish. The three messages describing the same SMS (see below) are not correlated byid.origin: always the actor's configured name (SMSActor).
There are three distinct events published to this topic.
a) SMS_RECEIVED
Published whenever a genuine incoming SMS is detected, whether or not it matches a known command.
Published message:
{
"id": "1782142697993",
"origin": "SMSActor",
"task": {
"action": "SMS_RECEIVED",
"taskResult": {
"success": true,
"from": "+351912345678",
"text": "some sms body",
"state": "received"
}
}
}
| Field | Type | Description |
|---|---|---|
success
|
boolean | true if the SMS was read successfully.
|
from
|
string | Sender's phone number, as reported by the modem. |
text
|
string | Full text body of the SMS. |
state
|
string | Normalised delivery state reported by ModemManager (e.g. "received", "sent", "failed").
|
b) SMS_COMMAND_STARTED
Published as soon as an incoming SMS matches a known command and its auto-reply (if any) has been sent — before the actual action executes.
Published message:
{
"id": "1782142697994",
"origin": "SMSActor",
"task": {
"action": "SMS_COMMAND_STARTED",
"taskResult": {
"success": true,
"from": "+351912345678",
"smsPayload": "resetMobileConnection",
"command": "reset_mobile_connection",
"answer": "resetOnGoing",
"destructive": true
}
}
}
| Field | Type | Description |
|---|---|---|
success
|
boolean | Always true. It marks that the command was recognised; the result of sending the auto-reply is not checked before publishing.
|
from
|
string | Sender's phone number. |
smsPayload
|
string | Original SMS text that triggered the command. |
command
|
string | Internal action identifier (e.g. "reboot", "reset_mobile_connection").
|
answer
|
string | Auto-reply text sent back to the sender. null/absent if none is defined.
|
destructive
|
boolean | true if the command is flagged destructive. Only reboot and resetMobileConnection are. The key is omitted when false, never sent as false.
|
c) Command result
Published once the command's action has finished executing. The task.action field is the internal command identifier (e.g. "reboot", "reset_mobile_connection"), not a fixed name.
Example — reset_mobile_connection:
{
"id": "1782142697995",
"origin": "SMSActor",
"task": {
"action": "reset_mobile_connection",
"taskResult": {
"success": true,
"from": "+351912345678",
"smsPayload": "resetMobileConnection",
"answer": "resetOnGoing",
"result": {
"success": true,
"step": "completed"
}
}
}
}
Example — reboot:
{
"id": "1782142697996",
"origin": "SMSActor",
"task": {
"action": "reboot",
"taskResult": {
"success": true,
"from": "+351912345678",
"smsPayload": "reboot",
"result": {
"success": true,
"returnCode": 0,
"stdout": "",
"stderr": ""
}
}
}
}
| Field | Type | Description |
|---|---|---|
success
|
boolean | Mirrors result.success, so it does reflect whether the action itself succeeded.
|
from
|
string | Sender's phone number. |
smsPayload
|
string | Original SMS text that triggered the command. |
answer
|
string | Auto-reply text submitted to the modem, if any. Delivery is not confirmed; see Known Limitations. |
result
|
object | Outcome of the underlying action. See below. |
result object (reset_mobile_connection):
| Field | Type | Description |
|---|---|---|
success
|
boolean | true if both the disable and enable steps completed without error.
|
step
|
string | "completed" on full success, or the failing step: "disable", "enable", "nmcli_connection_up" or "wait_connected".
|
returnCode
|
number | Exit code of the failing mmcli process. Present only on a disable or enable failure.
|
stdout
|
string | Standard output of the failing mmcli command. Present only on a disable/enable failure.
|
stderr
|
string | Standard error of the failing mmcli command. Present only on a disable/enable failure.
|
error
|
string | Present instead of the above if no modem could be found ("no_modem_found").
|
On success the result object carries only success and step; there is no returnCode, stdout or stderr, because the sequence spans several commands rather than a single one. The nmcli_connection_up and wait_connected failures likewise carry only success and step.
result object (reboot):
| Field | Type | Description |
|---|---|---|
success
|
boolean | true if systemctl reboot was issued successfully.
|
returnCode
|
number | Exit code of the systemctl reboot command.
|
stdout
|
string | Standard output of the command. |
stderr
|
string | Standard error of the command. |
If systemctl reboot itself raises (rather than returning a non-zero exit code), the result object is { "success": false, "error": "<exception text>" } instead, with no returnCode/stdout/stderr. The same shape is used by set_ssh, set_vpn and set_mobile_data when their subprocess call raises.
Defensive fallback (should not normally occur, since SMS text is pre-validated against the command table before dispatch):
{
"success": false,
"error": "handler_not_found",
"action": "<action>"
}
d) SMS_COMMAND_STARTED (parameterized commands)
Published as soon as an incoming SMS matches a parameterized command — before the action executes and before the auto-reply is known. answer is always null here; the actual reply is only computed after execution (see below).
If the SMS carries multiple commands (one per line, see Multiple commands per SMS), this event is published once per matched line, and smsPayload is that line's text, not the full SMS body.
{
"id": "1782142697994",
"origin": "SMSActor",
"task": {
"action": "SMS_COMMAND_STARTED",
"taskResult": {
"success": true,
"from": "+351912345678",
"smsPayload": "OUT01:High",
"command": "set_digital_output",
"answer": null
}
}
}
e) Command result (parameterized commands)
Published once the command's action has finished. task.action is the internal command name (set_digital_output, set_relay, set_ssh, set_mobile_data, set_vpn). taskResult.answer is the SMS reply that was actually sent — null/absent if the action failed or is not implemented.
For a multi-line SMS, this event is published once per matched line, each with its own smsPayload (that line's text) and result. However, taskResult.answer here is that line's own reply text (e.g. "RL1St:Close"); the SMS actually sent to the user is the combination of every successful line's answer (see Multiple commands per SMS).
Example — set_digital_output:
{
"id": "1782142697995",
"origin": "SMSActor",
"task": {
"action": "set_digital_output",
"taskResult": {
"success": true,
"from": "+351912345678",
"smsPayload": "OUT01:High",
"answer": "OUT01St:High",
"result": {
"success": true,
"output": "DO1",
"state": "high",
"answer": "OUT01St:High"
}
}
}
}
Example — set_relay:
{
"task": {
"action": "set_relay",
"taskResult": {
"success": true,
"from": "+351912345678",
"smsPayload": "RL1:Open",
"answer": "RL1St:Open",
"result": {
"success": true,
"relay": "RL1",
"state": "open",
"answer": "RL1St:Open"
}
}
}
}
Example — set_ssh:
{
"task": {
"action": "set_ssh",
"taskResult": {
"success": true,
"from": "+351912345678",
"smsPayload": "SSH:On",
"answer": "SSHST:On",
"result": {
"success": true,
"returnCode": 0,
"stdout": "",
"stderr": "",
"answer": "SSHST:On"
}
}
}
}
Example — set_mobile_data:
{
"task": {
"action": "set_mobile_data",
"taskResult": {
"success": true,
"from": "+351912345678",
"smsPayload": "MobileData:Off",
"answer": "MobileDataStatus:Off",
"result": { "success": true, "answer": "MobileDataStatus:Off" }
}
}
}
Example — set_vpn:
{
"task": {
"action": "set_vpn",
"taskResult": {
"success": true,
"from": "+351912345678",
"smsPayload": "VPN:On",
"answer": "VPNST:On10.100.0.28",
"result": {
"success": true,
"returnCode": 0,
"stdout": "",
"stderr": "",
"tunnelIp": "10.100.0.28",
"answer": "VPNST:On10.100.0.28"
}
}
}
}
| Field | Type | Description |
|---|---|---|
success
|
boolean | true if the action fully succeeded. When false, no SMS auto-reply was sent.
|
from
|
string | Sender's phone number. |
smsPayload
|
string | Original SMS text that triggered the command. |
answer
|
string | null | SMS reply text actually sent, or null if none was sent (failure).
|
result
|
object | Handler-specific outcome, always with success. See the table below.
|
result object per command:
| Command | Fields |
|---|---|
set_digital_output
|
output (DO1..DO8) and state, taken from the bridged actor's reply. When that reply carries no state for the output, the requested state is echoed back instead. A missing entry for the output means success: false.
|
set_relay
|
relay (RL1/RL2) and state, with the same fallback. Note the bridge request itself uses DO1/DO2 naming; only the result is renamed.
|
set_ssh
|
returnCode, stdout, stderr. Two units are driven (ssh.socket then ssh.service): returnCode is the first non-zero exit code across both, and stdout/stderr are the non-empty outputs of both, joined with a newline.
|
set_vpn
|
returnCode, stdout, stderr, persisted (whether systemctl enable/disable succeeded, false does not fail the command) and, on On, tunnelIp (null when the address could not be read back).
|
set_mobile_data
|
On success, only success and answer. On failure, step is "apply" or "wait_state"; there is no returnCode/stdout/stderr.
|
On an unexpected exception, every handler returns { "success": false, "error": "<exception text>" } instead of its usual fields.
15.6 Interpreting the Results
a) success: false on SMS_RECEIVED
success is always true on this event. It is a fixed field, not a status: an SMS whose content could not be read after the retries (typically a multipart message that never completed assembly) is deleted without any SMS_RECEIVED being published at all, so there is no such thing as SMS_RECEIVED with success: false. A message that goes missing between the modem receiving it and MQTT is only visible in the actor's warning log.
b) destructive: true
The command is one of the two flagged as able to interrupt connectivity or restart the device: reboot and reset_mobile_connection. For those, the SMS is deleted and any static auto-reply is sent before the action runs, precisely because the action may cut the path the reply would travel on.
The flag is absent on every other command. Note that this is a static property of the command table, not a judgement about impact: MobileData:Off disconnects the device just as thoroughly and carries no flag, so do not use the presence of destructive as a safety gate.
c) result.error: "no_modem_found"
The modem could not be located over D-Bus at the time the action ran. This can happen if the modem was disconnected or ModemManager restarted between the SMS being received and the action executing.
d) No response to unrecognised SMS
If an incoming SMS does not match any entry in the command table (fixed or parameterized), it is reported via SMS_RECEIVED only. No auto-reply is sent, and no action is executed.
e) Mixed commands in one SMS are rejected
If an incoming SMS's recognised lines are not all the same action (e.g. a relay command mixed with reboot, or with a digital output command), the whole message is rejected: a warning is logged server-side and the SMS is reported via SMS_RECEIVED, but no command runs and no auto-reply is sent — same as an SMS matching no command at all. Sending multiple lines of the same action (e.g. several RL# lines) is still fully supported.
f) Digital output / relay bridge timeout
set_digital_output and set_relay call the Digital Outputs / Relays actors over MQTT and wait up to 5s for a response. If that actor process is down or doesn't answer in time, the request raises a timeout, success is false in the command result, and no auto-reply is sent.
g) VPN systemd unit
set_vpn targets whichever VPN unit is actually installed, detected at command time with systemctl show -p LoadState <unit>:
| Order | Unit | Interface | Notes |
|---|---|---|---|
| 1 | wg-quick@wg0.service
|
wg0
|
WireGuard, the current default for new deployments. Checked first. |
| 2 | openvpn@pi
|
tun0
|
OpenVPN, used on devices provisioned before WireGuard. Checked as a fallback. |
A device is provisioned with exactly one of the two, never both. If neither unit is loadable, the command fails with error: "no VPN unit installed on this device" and nothing is started or stopped.
The tunnel config (keys, certificates, peer, AllowedIPs) is provisioned out-of-band on both the device and the gateway and is never touched by this command — it only starts/stops the already-configured unit, and enables/disables it so the state survives a reboot.
wg-quick@wg0.service is a oneshot unit with RemainAfterExit=yes, so systemctl start only returns once wg-quick has finished assigning the interface address. That is why no extra wait or poll is needed before reading the IP back for the On reply. Confirm the same holds for the installed openvpn@pi unit definition if it is ever changed.
A read-only systemctl is-active openvpn check exists elsewhere in the codebase for a status display. It is unrelated to this command and does not control anything.
h) SSH systemd units
set_ssh runs systemctl start|stop on both ssh.socket and ssh.service, confirmed on-device via systemctl list-units | grep -i ssh. On this image, ssh.service is TriggeredBy: ssh.socket and both are enabled independently — stopping only ssh.service leaves ssh.socket listening on :22, and it socket-activates a fresh sshd on the next connection attempt, so SSH would stay reachable despite SSH:Off reporting success. Some distros name the service unit sshd instead; reconfirm both unit names if targeting a different image.
15.7 Boot Behaviour
On every boot, the actor:
- Connects to MQTT and starts a background reconnector (exponential backoff, 1s → 30s cap, if the connection drops).
- Deletes all pre-existing SMS on the modem/SIM (unless disabled via configuration).
- Starts the D-Bus listener thread and begins reacting to incoming SMS.
There is no periodic check — all processing after boot is triggered by modem D-Bus signals.
15.8 Known Limitations
Behaviour that is easy to rely on by mistake, listed so integrations do not.
| # | Area | Limitation |
|---|---|---|
| 1 | Authentication | There is no sender allow-list and no authentication. Any handset that knows the SIM number can reboot the device, cut its mobile data, or drive its outputs. The SIM number is effectively a credential. |
| 2 | Auto-reply delivery | The result of mmcli --send is ignored by the caller. A reply that the modem refused to send is logged as a warning, but MQTT still reports it in answer as though it went out. answer means "submitted", never "delivered".
|
| 3 | Lost messages | An SMS that cannot be read after the retries produces no MQTT event at all. It is deleted and only a warning is logged, so an MQTT consumer cannot tell that a message arrived and was dropped. |
| 4 | Bridge errors | A set_digital_output/set_relay timeout surfaces as result.error set to an empty string, because the underlying timeout carries no message. A timeout also does not mean the output was left unchanged, only unconfirmed.
|
| 5 | destructive flag
|
Only reboot and reset_mobile_connection carry it. MobileData:Off is just as disruptive and is not flagged, so the field is not a safety gate.
|
| 6 | Configuration | delete_existing_on_start and delete_sms_after_processing are constructor arguments with no configuration surface. Changing them requires a code change.
|
| 7 | Delete failures | An SMS whose deletion fails is not added to the seen set, so a later D-Bus signal for the same message can execute the command a second time. |
| 8 | Seen-path tracking | Processed SMS paths are remembered in memory for the life of the process and never pruned. If ModemManager restarts and reuses a low object path (e.g. /SMS/1), a genuinely new message on that path is silently skipped.
|
| 9 | Destructive timing | Before reboot and resetMobileConnection the actor waits a fixed 2 s for the MQTT publish, the SMS reply and the deletion to complete. That is a delay, not a confirmation: on a slow modem the action can still cut them off.
|
| 10 | Mixed commands | One SMS carries one action type. A relay line mixed with a reboot line runs nothing at all, and the sender receives no reply telling them why.
|
| 11 | VPN persistence | systemctl enable/disable failing is only a warning. The tunnel is in the requested state now, but the next boot will not remember it. Check result.persisted.
|
15.9 Notes
- The SMS Actor keeps no persistent state across restarts (no EEPROM, file, or database). In-memory bookkeeping only tracks SMS already seen/being processed, to avoid double-handling from overlapping D-Bus signals.
- Message deletion after processing is enabled by default; this can be disabled via configuration if a full SMS audit trail is needed on the modem/SIM itself (subject to SIM storage limits).
- There is currently no sender allow-list or authentication on inbound SMS commands — any device holding the SIM number can trigger any of the commands above by sending the matching text.
set_digital_output/set_relayare the only commands that depend on another actor process (busi2c-3/busi2c-1) being up and reachable over MQTT; all other commands run entirely within the SMS actor's own process.