SENSBLUE ATLAS/Datasheet: Difference between revisions
Appearance
| Line 8,370: | Line 8,370: | ||
==== 24.15 SMS Actor ==== | ==== 24.15 SMS Actor ==== | ||
=== 25. Included Software Stack === | ===== 24.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 <code>.../runtime/in</code>topic 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 <code>OUT##</code>/<code>RL#</code> commands (see Command execution). | |||
===== 24.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. | |||
* '''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 <code>RL#:Open|Close</code> 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''' ====== | |||
{| class="wikitable" | |||
|'''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 ====== | |||
{| class="wikitable" | |||
|'''SMS text''' | |||
|'''Action''' | |||
|'''Auto-reply SMS (sent after execution)''' | |||
|'''Destructive''' | |||
|- | |||
|<nowiki>OUT[##]:High|Low</nowiki> | |||
| set_digital_output | |||
|<nowiki>OUT[##]St:High|Low</nowiki> | |||
|No | |||
|- | |||
|<nowiki>RL[1|2]:Open|Close</nowiki> | |||
| set_relay | |||
|<nowiki>RL[1|2]St:Open|Close</nowiki> | |||
|No | |||
|- | |||
|<nowiki>VPN:On|Off</nowiki> | |||
|set_vpn | |||
|<nowiki>VPNST:On<IpAddress>|VPNST:Off</nowiki> | |||
|No | |||
|- | |||
|<nowiki>SSH:On|Off</nowiki> | |||
|set_ssh | |||
|<nowiki>SSHST:On|Off</nowiki> | |||
|No | |||
|- | |||
|<nowiki>MobileData:On|Off</nowiki> | |||
|set_mobile_data | |||
|<nowiki>MobileDataStatus:On|Off</nowiki> | |||
|Yes (interrupts mobile connectivity) | |||
|} | |||
* <code>##</code> is a zero-padded digital output index <code>01-08</code>, mapped to <code>DO1-DO8</code>. Any other index (e.g. <code>OUT09</code>, <code>OUT99</code>) is treated as an unrecognised SMS. | |||
* <code>RL1</code>/<code>RL2</code> map to the same <code>DO1</code>/<code>DO2</code> hardware 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 (<code>SMS_RECEIVED</code>) but no action is taken. | |||
* An SMS whose recognised lines are not all the '''same action''' (e.g. a relay command mixed with <code>reboot</code>, or with a digital output command) is treated the same way: reported via <code>SMS_RECEIVED</code>, 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.:<syntaxhighlight> | |||
RL1:Close | |||
RL2:Close | |||
</syntaxhighlight>This is accepted because both lines are the same action (<code>set_relay</code>). But '''every recognised line in the SMS must be the same action''' — mixing, e.g.:<syntaxhighlight> | |||
RL1:Close | |||
reboot | |||
</syntaxhighlight>is '''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 <code>set_relay</code> line mixed with a <code>set_digital_output</code> 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. <code>RL1:Close</code> then <code>RL1:Open</code>), the last one wins. | |||
* Each line is tracked as its own <code>SMS_COMMAND_STARTED</code> / command-result MQTT event (see MQTT Interface), with <code>smsPayload</code> set 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. <code>RL1St:Close\nRL2St:Close</code>). Lines whose action failed contribute no reply line, mirroring the single-command behaviour. | |||
===== 25.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, and can be disabled via configuration. | |||
====== b) Listening for SMS ====== | |||
The actor runs a background thread subscribed to ModemManager D-Bus signals (<code>Added</code> and <code>Completed</code> on the <code>Modem.Messaging</code> 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. | |||
<br> | |||
[[File:Listening for SMS.svg|center|frameless|2118x2118px]] | |||
<br> | |||
====== 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: | |||
* '''<code>reboot</code>''' — runs <code>systemctl reboot</code>. | |||
* '''<code>resetMobileConnection</code>''' — runs <code>mmcli -m <modem> --disable</code>, waits 3s, then <code>mmcli -m <modem> --enable</code>. | |||
'''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: | |||
* '''<code>set_digital_output</code>''' (<code>OUT##:High|Low</code>) — calls the Digital Outputs actor (<code>busi2c-3</code> process) over an MQTT request/response bridge: publishes <code><nowiki>{"action": "SET", "taskParams": {"DO<n>": {"state": "high"|"low"}}}</nowiki></code> to <code>digitalOutputs/runtime/in</code> and awaits the matching response on <code>digitalOutputs/runtime/out</code> (correlated by <code>id</code>, 5s timeout). | |||
* '''<code>set_relay</code>''' (<code>RL1|2:Open|Close</code>) — same bridge mechanism, targeting the Relays actor (<code>busi2c-1</code> process) via <code>relayOutputs/runtime/in</code>/<code>relayOutputs/runtime/out</code>. | |||
* '''<code>set_ssh</code>''' (<code>SSH:On|Off</code>) — runs <code>systemctl start|stop</code> on both <code>ssh.socket</code> and <code>ssh.service</code> (start/stop only, does not change boot-time enablement). Both units are stopped/started — <code>ssh.socket</code> alone stays listening on :22 and socket-activates <code>ssh.service</code> on the next connection, so stopping only the service would not actually block new SSH sessions. | |||
* '''<code>set_mobile_data</code>''' (<code>MobileData:On|Off</code>) — reuses the same <code>nmcli</code>/systemd modem enable/disable flow as the MQTT <code>SET_MODEM_STATE</code> command (not the <code>mmcli</code>-based path used by <code>resetMobileConnection</code>), so there is a single mechanism for enabling/disabling the modem connection. | |||
* '''<code>set_vpn</code>''' (<code>VPN:On|Off</code>) — runs <code>systemctl start|stop</code> on <code>wg-quick@wg0.service</code> (the WireGuard tunnel, already provisioned with keys/peer/AllowedIPs on both the device and the gateway — this command only starts/stops it, it never touches the config). On <code>On</code>, the reply includes the tunnel's IPv4 address read back via <code>ip -4 addr show dev wg0</code> (e.g. <code>VPNST:On10.100.0.28</code>); if the address can't be read, the reply is still <code>VPNST:On</code> with no address. On <code>Off</code>, the reply is <code>VPNST:Off</code>. | |||
===== 25.15.4 MQTT Interface ===== | |||
The SMS Actor only '''publishes''' — it does not subscribe to any inbound command topic. | |||
'''Publish topic:''' <code>sms/runtime/out</code> (QoS 0, not retained) | |||
All messages follow the standard ATLAS MQTT envelope, with the result carried in <code>task.taskResult</code> (not <code>task.taskParams</code>, unlike the request/response actors):<syntaxhighlight lang="json"> | |||
{ | |||
"id": "<TIMESTAMP>", | |||
"origin": "SMSActor", | |||
"task": { | |||
"action": "<ACTION_OR_COMMAND_NAME>", | |||
"taskResult": { } | |||
} | |||
} | |||
</syntaxhighlight> | |||
* <code>id</code>: millisecond epoch timestamp, generated fresh for each publish. The three messages describing the same SMS (see below) are '''not correlated by <code>id</code>'''. | |||
* <code>origin</code>: always the actor's configured name (<code>SMSActor</code>). | |||
===== 25.15.5 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:'''<syntaxhighlight lang="json"> | |||
{ | |||
"id": "1782142697993", | |||
"origin": "SMSActor", | |||
"task": { | |||
"action": "SMS_RECEIVED", | |||
"taskResult": { | |||
"success": true, | |||
"from": "+351912345678", | |||
"text": "some sms body", | |||
"state": "received" | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
{| class="wikitable" | |||
!Field | |||
!Type | |||
!Description | |||
|- | |||
|<code>success</code> | |||
|boolean | |||
|<code>true</code> if the SMS was read successfully. | |||
|- | |||
|<code>from</code> | |||
|string | |||
|Sender's phone number, as reported by the modem. | |||
|- | |||
|<code>text</code> | |||
|string | |||
|Full text body of the SMS. | |||
|- | |||
|<code>state</code> | |||
|string | |||
|Normalised delivery state reported by ModemManager (e.g. <code>"received"</code>, <code>"sent"</code>, <code>"failed"</code>). | |||
|} | |||
====== 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:'''<syntaxhighlight lang="json"> | |||
{ | |||
"id": "1782142697994", | |||
"origin": "SMSActor", | |||
"task": { | |||
"action": "SMS_COMMAND_STARTED", | |||
"taskResult": { | |||
"success": true, | |||
"from": "+351912345678", | |||
"smsPayload": "resetMobileConnection", | |||
"command": "reset_mobile_connection", | |||
"answer": "resetOnGoing", | |||
"destructive": true | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
{| class="wikitable" | |||
!Field | |||
!Type | |||
!Description | |||
|- | |||
|<code>success</code> | |||
|boolean | |||
|<code>true</code> if the command was recognised and the auto-reply (if any) was sent. | |||
|- | |||
|<code>from</code> | |||
|string | |||
|Sender's phone number. | |||
|- | |||
|<code>smsPayload</code> | |||
|string | |||
|Original SMS text that triggered the command. | |||
|- | |||
|<code>command</code> | |||
|string | |||
|Internal action identifier (e.g. <code>"reboot"</code>, <code>"reset_mobile_connection"</code>). | |||
|- | |||
|<code>answer</code> | |||
|string | |||
|Auto-reply text sent back to the sender. <code>null</code>/absent if none is defined. | |||
|- | |||
|<code>destructive</code> | |||
|boolean | |||
|<code>true</code> if the command changes device state (both current commands are). | |||
|} | |||
====== c) Command result ====== | |||
Published once the command's action has finished executing. The <code>task.action</code> field is the internal command identifier (e.g. <code>"reboot"</code>, <code>"reset_mobile_connection"</code>), not a fixed name. | |||
'''Example — <code>reset_mobile_connection</code>:'''<syntaxhighlight lang="json"> | |||
{ | |||
"id": "1782142697995", | |||
"origin": "SMSActor", | |||
"task": { | |||
"action": "reset_mobile_connection", | |||
"taskResult": { | |||
"success": true, | |||
"from": "+351912345678", | |||
"smsPayload": "resetMobileConnection", | |||
"answer": "resetOnGoing", | |||
"result": { | |||
"success": true, | |||
"step": "completed", | |||
"returnCode": 0, | |||
"stdout": "", | |||
"stderr": "" | |||
} | |||
} | |||
} | |||
} | |||
</syntaxhighlight>'''Example — <code>reboot</code>:'''<syntaxhighlight lang="json"> | |||
{ | |||
"id": "1782142697996", | |||
"origin": "SMSActor", | |||
"task": { | |||
"action": "reboot", | |||
"taskResult": { | |||
"success": true, | |||
"from": "+351912345678", | |||
"smsPayload": "reboot", | |||
"result": { | |||
"success": true, | |||
"returnCode": 0, | |||
"stdout": "", | |||
"stderr": "" | |||
} | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
{| class="wikitable" | |||
!Field | |||
!Type | |||
!Description | |||
|- | |||
|<code>success</code> | |||
|boolean | |||
|<code>true</code> if the command handler ran (does not necessarily mean the action itself fully succeeded — check <code>result</code>). | |||
|- | |||
|<code>from</code> | |||
|string | |||
|Sender's phone number. | |||
|- | |||
|<code>smsPayload</code> | |||
|string | |||
|Original SMS text that triggered the command. | |||
|- | |||
|<code>answer</code> | |||
|string | |||
|Auto-reply text sent, if any. | |||
|- | |||
|<code>result</code> | |||
|object | |||
|Outcome of the underlying action. See below. | |||
|} | |||
'''<code>result</code> object (reset_mobile_connection):''' | |||
{| class="wikitable" | |||
!Field | |||
!Type | |||
!Description | |||
|- | |||
|<code>success</code> | |||
|boolean | |||
|<code>true</code> if both the disable and enable steps completed without error. | |||
|- | |||
|<code>step</code> | |||
|string | |||
|<code>"completed"</code> on full success, or <code>"disable"</code> / <code>"enable"</code> indicating which step failed. | |||
|- | |||
|<code>returnCode</code> | |||
|number | |||
|Exit code of the <code>mmcli</code> process for the (last executed) step. | |||
|- | |||
|<code>stdout</code> | |||
|string | |||
|Standard output of the <code>mmcli</code> command. | |||
|- | |||
|<code>stderr</code> | |||
|string | |||
|Standard error of the <code>mmcli</code> command. | |||
|- | |||
|<code>error</code> | |||
|string | |||
|Present instead of the above if no modem could be found (<code>"no_modem_found"</code>). | |||
|} | |||
'''<code>result</code> object (reboot):''' | |||
{| class="wikitable" | |||
!Field | |||
!Type | |||
!Description | |||
|- | |||
|<code>success</code> | |||
|boolean | |||
|<code>true</code> if <code>systemctl reboot</code> was issued successfully. | |||
|- | |||
|<code>returnCode</code> | |||
|number | |||
|Exit code of the <code>systemctl reboot</code> command. | |||
|- | |||
|<code>stdout</code> | |||
|string | |||
|Standard output of the command. | |||
|- | |||
|<code>stderr</code> | |||
|string | |||
|Standard error of the command. | |||
|} | |||
'''Defensive fallback''' (should not normally occur, since SMS text is pre-validated against the command table before dispatch):<syntaxhighlight lang="json"> | |||
{ | |||
"success": false, | |||
"error": "handler_not_found", | |||
"action": "<action>" | |||
} | |||
</syntaxhighlight> | |||
====== 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. <code>answer</code> is always <code>null</code> 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 <code>smsPayload</code> is that line's text, not the full SMS body.<syntaxhighlight lang="json"> | |||
{ | |||
"id": "1782142697994", | |||
"origin": "SMSActor", | |||
"task": { | |||
"action": "SMS_COMMAND_STARTED", | |||
"taskResult": { | |||
"success": true, | |||
"from": "+351912345678", | |||
"smsPayload": "OUT01:High", | |||
"command": "set_digital_output", | |||
"answer": null | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
====== e) Command result (parameterized commands) ====== | |||
Published once the command's action has finished. <code>task.action</code> is the internal command name (<code>set_digital_output</code>, <code>set_relay</code>, <code>set_ssh</code>, <code>set_mobile_data</code>, <code>set_vpn</code>). <code>taskResult.answer</code> is the SMS reply that was actually sent — <code>null</code>/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 <code>smsPayload</code> (that line's text) and <code>result</code>. However, <code>taskResult.answer</code> here is that line's own reply text (e.g. <code>"RL1St:Close"</code>); the SMS actually sent to the user is the '''combination''' of every successful line's answer (see Multiple commands per SMS). | |||
'''Example — <code>set_digital_output</code>:'''<syntaxhighlight lang="json"> | |||
{ | |||
"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" | |||
} | |||
} | |||
} | |||
} | |||
</syntaxhighlight>'''Example — <code>set_relay</code>:'''<syntaxhighlight lang="json"> | |||
{ | |||
"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" } | |||
} | |||
} | |||
} | |||
</syntaxhighlight>'''Example — <code>set_ssh</code>:'''<syntaxhighlight lang="json"> | |||
{ | |||
"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" } | |||
} | |||
} | |||
} | |||
</syntaxhighlight>'''Example — <code>set_mobile_data</code>:'''<syntaxhighlight lang="json"> | |||
{ | |||
"task": { | |||
"action": "set_mobile_data", | |||
"taskResult": { | |||
"success": true, | |||
"from": "+351912345678", | |||
"smsPayload": "MobileData:Off", | |||
"answer": "MobileDataStatus:Off", | |||
"result": { "success": true, "answer": "MobileDataStatus:Off" } | |||
} | |||
} | |||
} | |||
</syntaxhighlight>'''Example — <code>set_vpn</code>:'''<syntaxhighlight lang="json"> | |||
{ | |||
"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" | |||
} | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
{| class="wikitable" | |||
!Field | |||
!Type | |||
!Description | |||
|- | |||
|<code>success</code> | |||
|boolean | |||
|<code>true</code> if the action fully succeeded. When <code>false</code>, no SMS auto-reply was sent. | |||
|- | |||
|<code>from</code> | |||
|string | |||
|Sender's phone number. | |||
|- | |||
|<code>smsPayload</code> | |||
|string | |||
|Original SMS text that triggered the command. | |||
|- | |||
|<code>answer</code> | |||
|<nowiki>string | null</nowiki> | |||
|SMS reply text actually sent, or <code>null</code> if none was sent (failure). | |||
|- | |||
|<code>result</code> | |||
|object | |||
|Handler-specific outcome. Always has <code>success</code>; <code>set_digital_output</code>/<code>set_relay</code> also carry the bridged actor's response (<code>output</code>/<code>relay</code>, <code>state</code>); <code>set_ssh</code>/<code>set_vpn</code> carry <code>returnCode</code>/<code>stdout</code>/<code>stderr</code>; <code>set_vpn</code> also carries <code>tunnelIp</code> (the wg0 IPv4 address, <code>null</code> if <code>On</code> succeeded but the address couldn't be read back). | |||
|} | |||
===== 25.15.6 Interpreting the Results ===== | |||
====== a) <code>success: false</code> on SMS_RECEIVED ====== | |||
The modem reported a new SMS object, but its content could not be read after repeated retries (typically a multipart message that never completed assembly). The message is deleted and no further processing occurs. | |||
====== b) <code>destructive: true</code> ====== | |||
Both currently supported commands (<code>reboot</code>, <code>reset_mobile_connection</code>) change device state. There is no non-destructive command in the current table. | |||
====== c) <code>result.error: "no_modem_found"</code> ====== | |||
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 <code>SMS_RECEIVED</code> 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 <code>reboot</code>, or with a digital output command), the whole message is rejected: a warning is logged server-side and the SMS is reported via <code>SMS_RECEIVED</code>, 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 <code>RL#</code> lines) is still fully supported. | |||
====== f) Digital output / relay bridge timeout ====== | |||
<code>set_digital_output</code> and <code>set_relay</code> 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, <code>success</code> is <code>false</code> in the command result, and no auto-reply is sent. | |||
====== g) VPN systemd unit ====== | |||
<code>set_vpn</code> runs <code>systemctl start|stop</code> on <code>wg-quick@wg0.service</code>, confirmed on-device via <code>systemctl status wg-quick@wg0</code>. This is the WireGuard tunnel used to reach the device from the gateway; its config (keys, peer, <code>AllowedIPs</code>) is provisioned out-of-band on both ends and is never touched by this command — it only starts/stops the already-configured tunnel. <code>wg-quick@wg0.service</code> is a oneshot unit with <code>RemainAfterExit=yes</code>, so <code>systemctl start</code> only returns once wg-quick has finished assigning the interface address, which is why no extra wait/poll is needed before reading back the IP for the <code>On</code> reply. There is a separate, unrelated read-only <code>systemctl is-active openvpn</code> check elsewhere in the codebase (used only for a status display, not for control) — it targets a different, apparently unused OpenVPN unit and has no bearing on this command. | |||
====== h) SSH systemd units ====== | |||
<code>set_ssh</code> runs <code>systemctl start|stop</code> on both <code>ssh.socket</code> and <code>ssh.service</code>, confirmed on-device via <code>systemctl list-units | grep -i ssh</code>. On this image, <code>ssh.service</code> is <code>TriggeredBy: ssh.socket</code> and both are enabled independently — stopping only <code>ssh.service</code> leaves <code>ssh.socket</code> listening on :22, and it socket-activates a fresh <code>sshd</code> on the next connection attempt, so SSH would stay reachable despite <code>SSH:Off</code> reporting success. Some distros name the service unit <code>sshd</code> instead; reconfirm both unit names if targeting a different image. | |||
===== '''25.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. | |||
'''25.15.8 There is no periodic check — all processing after boot is triggered by modem D-Bus signals.''' | |||
'''25.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. | |||
* <code>set_digital_output</code>/<code>set_relay</code> are the only commands that depend on another actor process (<code>busi2c-3</code>/<code>busi2c-1</code>) being up and reachable over MQTT; all other commands run entirely within the SMS actor's own process. | |||
=== 26. Included Software Stack === | |||
<br> | <br> | ||
[[File:AtlasCode.svg|center|frameless|583x583px]] | [[File:AtlasCode.svg|center|frameless|583x583px]] | ||
| Line 8,376: | Line 8,913: | ||
<br> | <br> | ||
=== | === 27. Software Stack and IPC Architecture === | ||
<br> | <br> | ||
[[File:Hardware Peripherals Library Architecture.svg|center|frameless|1934x1934px]] | [[File:Hardware Peripherals Library Architecture.svg|center|frameless|1934x1934px]] | ||
=== | === 28. Related Products === | ||
{| class="wikitable" | {| class="wikitable" | ||
|'''PA''' | |'''PA''' | ||
| Line 8,393: | Line 8,930: | ||
{{SimilarProductsTable}} | {{SimilarProductsTable}} | ||
=== | === 29. Accessories === | ||
{| class="wikitable" | {| class="wikitable" | ||
|'''PA''' | |'''PA''' | ||
| Line 8,420: | Line 8,957: | ||
|} | |} | ||
=== | === 30. Document History === | ||
{| class="wikitable" | {| class="wikitable" | ||
|'''Version''' | |'''Version''' | ||
| Line 8,439: | Line 8,976: | ||
|} | |} | ||
=== | === 31. SENSBLUE ATLAS in-the-field === | ||
[IMAGE PLACEHOLDER] | [IMAGE PLACEHOLDER] | ||