Jump to content

SENSBLUE ATLAS/Datasheet: Difference between revisions

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>


=== 26. Software Stack and IPC Architecture ===
=== 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]]


=== 27. Related Products ===
=== 28. Related Products ===
{| class="wikitable"
{| class="wikitable"
|'''PA'''
|'''PA'''
Line 8,393: Line 8,930:
{{SimilarProductsTable}}
{{SimilarProductsTable}}


=== 28. Accessories ===
=== 29. Accessories ===
{| class="wikitable"
{| class="wikitable"
|'''PA'''
|'''PA'''
Line 8,420: Line 8,957:
|}
|}


=== 29. Document History ===
=== 30. Document History ===
{| class="wikitable"
{| class="wikitable"
|'''Version'''
|'''Version'''
Line 8,439: Line 8,976:
|}
|}


=== 30. SENSBLUE ATLAS in-the-field ===
=== 31. SENSBLUE ATLAS in-the-field ===
[IMAGE PLACEHOLDER]
[IMAGE PLACEHOLDER]