meshtastic 0.0.179 → 0.0.181
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/documentation/README.md +3 -2
- data/documentation/admin-channel.md +64 -0
- data/documentation/admin-config.md +59 -0
- data/documentation/admin-firmware-nordic.md +86 -0
- data/documentation/admin-firmware-serial.md +180 -0
- data/documentation/admin-firmware.md +126 -0
- data/documentation/admin.md +88 -35
- data/documentation/atak.md +93 -10
- data/documentation/channel.md +1 -28
- data/documentation/config.md +1 -27
- data/documentation/mesh-interface.md +7 -0
- data/documentation/meshtastic.md +2 -2
- data/lib/meshtastic/admin/channel.rb +164 -0
- data/lib/meshtastic/admin/config.rb +213 -0
- data/lib/meshtastic/admin/firmware/ble.rb +207 -0
- data/lib/meshtastic/admin/firmware/nordic_dfu.rb +218 -0
- data/lib/meshtastic/admin/firmware/serial_bootloader.rb +233 -0
- data/lib/meshtastic/admin/firmware.rb +288 -0
- data/lib/meshtastic/admin.rb +688 -34
- data/lib/meshtastic/apponly.rb +19 -7
- data/lib/meshtastic/atak.rb +295 -17
- data/lib/meshtastic/bluetooth/bluez.rb +20 -1
- data/lib/meshtastic/bluetooth.rb +70 -59
- data/lib/meshtastic/cannedmessages.rb +13 -6
- data/lib/meshtastic/channel.rb +3 -25
- data/lib/meshtastic/clientonly.rb +19 -7
- data/lib/meshtastic/config.rb +3 -24
- data/lib/meshtastic/config_pb.rb +2 -1
- data/lib/meshtastic/connection_status.rb +12 -5
- data/lib/meshtastic/deviceonly.rb +19 -7
- data/lib/meshtastic/localonly.rb +19 -7
- data/lib/meshtastic/mesh_interface.rb +19 -0
- data/lib/meshtastic/module_config.rb +13 -7
- data/lib/meshtastic/mqtt.rb +72 -35
- data/lib/meshtastic/paxcount.rb +17 -6
- data/lib/meshtastic/portnums.rb +13 -6
- data/lib/meshtastic/position.rb +16 -6
- data/lib/meshtastic/remote_hardware.rb +27 -11
- data/lib/meshtastic/rtttl.rb +17 -8
- data/lib/meshtastic/serial.rb +81 -70
- data/lib/meshtastic/storeforward.rb +13 -6
- data/lib/meshtastic/storeforward_pb.rb +1 -1
- data/lib/meshtastic/stream_interface.rb +11 -0
- data/lib/meshtastic/tcp.rb +73 -22
- data/lib/meshtastic/telemetry.rb +15 -6
- data/lib/meshtastic/traceroute.rb +15 -6
- data/lib/meshtastic/util.rb +3 -1
- data/lib/meshtastic/version.rb +1 -1
- data/lib/meshtastic/xmodem.rb +12 -5
- data/lib/meshtastic.rb +22 -3
- data/spec/conventions_spec.rb +321 -0
- data/spec/lib/meshtastic/admin/channel_spec.rb +202 -0
- data/spec/lib/meshtastic/admin/config_spec.rb +114 -0
- data/spec/lib/meshtastic/admin/firmware/ble_spec.rb +170 -0
- data/spec/lib/meshtastic/admin/firmware/nordic_dfu_spec.rb +248 -0
- data/spec/lib/meshtastic/admin/firmware/serial_bootloader_spec.rb +263 -0
- data/spec/lib/meshtastic/admin/firmware_spec.rb +315 -0
- data/spec/lib/meshtastic/admin_spec.rb +412 -1
- data/spec/lib/meshtastic/apponly_spec.rb +1 -1
- data/spec/lib/meshtastic/atak_spec.rb +129 -4
- data/spec/lib/meshtastic/bluetooth_spec.rb +1 -1
- data/spec/lib/meshtastic/channel_spec.rb +2 -20
- data/spec/lib/meshtastic/config_spec.rb +2 -20
- data/spec/lib/meshtastic/connection_status_spec.rb +1 -1
- data/spec/lib/meshtastic/deviceonly_spec.rb +1 -1
- data/spec/lib/meshtastic/localonly_spec.rb +1 -1
- data/spec/lib/meshtastic/mesh_interface_spec.rb +31 -0
- data/spec/lib/meshtastic/portnums_spec.rb +2 -2
- metadata +24 -6
data/documentation/admin.md
CHANGED
|
@@ -1,54 +1,107 @@
|
|
|
1
1
|
# Meshtastic::Admin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Build and send `AdminMessage` on `ADMIN_APP` through a connected `serial_obj`, `bluetooth_obj`, `tcp_obj`, or `mqtt_obj`. These operations can change configuration, reboot, erase files, or reset a device. Sending is not confirmation of successful execution.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Addressing and defaults
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Radio transports default `to` to their connected `my_node_num` and `from` to zero (the local PhoneAPI client). Complete the transport configuration handshake first, or supply `to` explicitly.
|
|
8
|
+
- Remote administration requires an explicit unicast destination: an integer or `!` followed by exactly eight hexadecimal digits. Broadcast, zero, malformed strings, and missing destinations are rejected. MQTT always requires `to`.
|
|
9
|
+
- Getter requests default `want_response: true`; state-changing operations default false. Explicit `want_response` overrides this. `want_ack` requests a separate routing acknowledgment. Some firmware setters answer with `ROUTING_APP` rather than an admin response when a response is requested.
|
|
10
|
+
- Common transport options include `channel`, `hop_limit`, `want_ack`, `last_packet_id`, and MQTT `psks`. Device-owned remote PKI uses `pki_encrypted` and `public_key` on radio transports; MQTT channel encryption is not PKI admin authorization.
|
|
11
|
+
- `get_channel(index: 0)` takes a **zero-based** index 0–7 and adds one exactly once for the wire. Raw `get_channel_request` passed to `encode`/`send` is already a wire value. `set_channel` retains the Channel protobuf's zero-based index.
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
- `send` — wrap and deliver (`want_response` default true)
|
|
11
|
-
- `reboot(seconds: 5)`
|
|
12
|
-
- `shutdown(seconds: 5)`
|
|
13
|
-
- `get_owner` / `set_owner(long_name:, short_name:, owner:)`
|
|
14
|
-
- `get_channel(index:)` / `set_channel(channel_settings:)`
|
|
15
|
-
- `get_config(config_type:)` / `set_config(config:)`
|
|
16
|
-
- `nodedb_reset`
|
|
17
|
-
- `help` / `authors`
|
|
13
|
+
## Encoding and validation
|
|
18
14
|
|
|
19
|
-
`
|
|
15
|
+
`encode` returns an `AdminMessage`, copying rather than modifying `message:` when provided. Supply exactly one payload variant. Conflicting variants, unknown option names, missing payloads, and nil payload values are rejected rather than silently overwriting the protobuf oneof. Protobuf field types and numeric bounds are enforced by the generated Ruby classes. False and zero are preserved, including `nodedb_reset: false`, `get_config_request: :DEVICE_CONFIG`, and backup location `:FLASH`.
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
Every generated payload field is available through `encode` and `send`, including response fields for tooling. Wrappers below accept the common transport options as well. Missing required scalar values are not silently converted to zero or empty text. An explicit empty ringtone or canned-message string may clear it.
|
|
22
18
|
|
|
23
|
-
##
|
|
19
|
+
## Operation catalog
|
|
20
|
+
|
|
21
|
+
| Group | Methods and payload arguments |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| Identity | `get_owner`; `set_owner(owner:)` or `set_owner(long_name:, short_name:)`; `set_ham_mode(ham:)` or callsign/frequency/power/name fields |
|
|
24
|
+
| Channels | `get_channel(index: 0)`; `set_channel(channel_settings:)` (or `channel_pb:`) |
|
|
25
|
+
| Configuration | `get_config(config_type: :DEVICE_CONFIG)`; `set_config(config:)`; `get_module_config(module_config_type: :MQTT_CONFIG)`; `set_module_config(module_config:)` |
|
|
26
|
+
| UI | `get_ui_config`; `store_ui_config(ui_config:)`; `send_input_event(event:)` or `event_code`, `kb_char`, `touch_x`, `touch_y` |
|
|
27
|
+
| Canned messages | `get_canned_messages`; `set_canned_messages(messages:)` with pipe-separated text |
|
|
28
|
+
| Ringtone | `get_ringtone`; `set_ringtone(ringtone:)` with RTTTL text |
|
|
29
|
+
| Device information | `get_device_metadata`; `get_device_connection_status`; `get_node_remote_hardware_pins` |
|
|
30
|
+
| Position/time | `set_fixed_position(position:)` or `lat`, `lon`, `altitude`; `remove_fixed_position`; `set_time(time:)` with Unix seconds |
|
|
31
|
+
| Node database | `remove_by_nodenum(node_num:)`; `set_favorite_node(node_num:)`; `remove_favorite_node(node_num:)`; `set_ignored_node(node_num:)`; `remove_ignored_node(node_num:)`; `toggle_muted_node(node_num:)`; `add_contact(contact:)` |
|
|
32
|
+
| Edit transactions | `begin_edit`; `commit_edit` — defer implicit persistence/reboot for owner/channel/config/module changes until commit |
|
|
33
|
+
| Preferences | `backup_preferences(location: :FLASH)`; `restore_preferences(location: :FLASH)`; `remove_backup_preferences(location: :FLASH)`; location may also be `:SD` |
|
|
34
|
+
| Files and sensors | `delete_file(path:)`; `set_scale(scale:)`; `sensor_config(sensor_config:)` |
|
|
35
|
+
| Authentication | `key_verification(key_verification:)`; `lockdown_auth(lockdown_auth:)` — typed protobuf payloads, not an automatic authentication workflow |
|
|
36
|
+
| Power | `reboot(seconds: 5)`; `shutdown(seconds: 5)`; negative delays cancel pending actions |
|
|
37
|
+
| Firmware | `enter_dfu`; `ota_request(event:)` with `AdminMessage::OTAEvent`; `reboot_ota(seconds: 5)` for deprecated legacy firmware only |
|
|
38
|
+
| Resets | `factory_reset_device(value: 1)`; `factory_reset_config(value: 1)`; `nodedb_reset(preserve_favorites: true)`; `exit_simulator` |
|
|
39
|
+
| Utilities | `encode`; `send`; `request`; `decode`; `response`; `help`; `authors` |
|
|
40
|
+
|
|
41
|
+
Device reset clears BLE bonds; config reset preserves them. `nodedb_reset(preserve_favorites: false)` requests removal of favorites too, but firmware CLIENT_BASE/ROUTER/ROUTER_LATE roles can preserve favorites regardless. DFU is hardware-specific (upstream documents NRF52); OTA depends on the installed loader and supported mode. Modern firmware uses `ota_request`; `reboot_ota_seconds` is deprecated and absent from the inspected current AdminModule switch. Use [Admin::Firmware](admin-firmware.md) for the firmware transfer workflow.
|
|
42
|
+
|
|
43
|
+
## Responses, correlation and session passkeys
|
|
44
|
+
|
|
45
|
+
`decode(payload: bytes)` decodes raw AdminMessage bytes. `decode(packet:)` accepts a protobuf `FromRadio`, `MeshPacket`, or `Data` and requires decoded `ADMIN_APP` data. Malformed protobuf bytes raise the protobuf decoder error.
|
|
46
|
+
|
|
47
|
+
`response(packet:, request_id:, from:)` accepts a `FromRadio` or `MeshPacket`, optionally matches the outgoing packet ID and numeric sender, and returns:
|
|
24
48
|
|
|
25
49
|
```ruby
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
serial_obj = Meshtastic::Serial.connect(block_dev: '/dev/ttyACM0')
|
|
29
|
-
Meshtastic::Serial.wait_for_config(serial_obj: serial_obj)
|
|
30
|
-
|
|
31
|
-
Meshtastic::Admin.set_owner(serial_obj: serial_obj, long_name: 'Node', short_name: 'N1')
|
|
32
|
-
Meshtastic::Admin.get_owner(serial_obj: serial_obj)
|
|
33
|
-
Meshtastic::Admin.get_config(serial_obj: serial_obj, config_type: :LORA_CONFIG)
|
|
34
|
-
Meshtastic::Admin.get_channel(serial_obj: serial_obj, index: 0)
|
|
35
|
-
Meshtastic::Admin.reboot(serial_obj: serial_obj, seconds: 5)
|
|
36
|
-
# Meshtastic::Admin.shutdown(serial_obj: serial_obj, seconds: 5)
|
|
37
|
-
# Meshtastic::Admin.nodedb_reset(serial_obj: serial_obj)
|
|
50
|
+
{ message: admin_message, variant: :get_owner_response, value: owner,
|
|
51
|
+
session_passkey: passkey_bytes, request_id: packet_id, from: node_number }
|
|
38
52
|
```
|
|
39
53
|
|
|
40
|
-
|
|
54
|
+
Unrelated ports, non-response variants, encrypted/absent data, and mismatched IDs/senders return nil. These helpers do not authenticate packets or decrypt encrypted packets. `response` remains a pure decoder; use `request` for synchronous routing-error handling.
|
|
55
|
+
|
|
56
|
+
### Synchronous request/readback
|
|
57
|
+
|
|
58
|
+
`request` accepts raw `send` options or `message: AdminMessage`, plus `timeout:` (positive finite seconds, default 10), `request_id:` and `wait:`. By default it sends and waits on the connected radio's `from_radio_queue`. A getter returns the response hash above plus `result:` (the transport submission result). It requires both the outgoing request ID and target node, and the expected response variant. A local routing ACK is **not** remote readback. State-changing requests wait for a target-correlated `ROUTING_APP/NONE` and return `{ variant: :routing, value: :NONE, request_id:, from:, result: }`; even this is acknowledgment, not verification of persistent configuration.
|
|
41
59
|
|
|
42
60
|
```ruby
|
|
43
|
-
Meshtastic::Admin.
|
|
44
|
-
serial_obj: serial_obj,
|
|
45
|
-
|
|
61
|
+
reply = Meshtastic::Admin.request(
|
|
62
|
+
serial_obj: serial_obj, # alternatively bluetooth_obj: or tcp_obj:
|
|
63
|
+
to: '!aabbccdd',
|
|
64
|
+
message: Meshtastic::AdminMessage.new(get_device_metadata_request: true),
|
|
65
|
+
timeout: 10
|
|
46
66
|
)
|
|
67
|
+
version = reply[:value].firmware_version
|
|
47
68
|
```
|
|
48
69
|
|
|
70
|
+
After reboot, reconnect, complete the transport handshake, and make this request on the **new handle**. Match the returned version against the expected firmware; neither a successful upload nor an ACK establishes firmware health.
|
|
71
|
+
|
|
72
|
+
- `Timeout::Error` means no matching response arrived within the monotonic receive budget, or the receive queue closed. The budget includes automatic session acquisition. Blocking transport writes retain their underlying transport's I/O behavior; this is not an interrupting write timeout.
|
|
73
|
+
- `Admin::RoutingError` exposes `reason`, `request_id`, and `from`. Correlated nonzero routing errors from the target **or connected local radio** terminate immediately (including local PKI/no-route failures).
|
|
74
|
+
- Unrelated packets are retained and returned to the queue on success or failure. Deferred packets can move behind newer queued traffic. If the queue closes, the handle receives a replacement closed queue retaining those packets. Do not retain a separate queue reference across disconnects.
|
|
75
|
+
- Pause any external `subscribe`/`recv_from_radio` consumer while a synchronous request owns the receive queue. Concurrent synchronous requests on one handle fail fast with `IOError`; Admin does not install a global transport dispatcher.
|
|
76
|
+
- MQTT has no compatible radio queue; synchronous requests and automatic acquisition are rejected rather than pretending submission is readback.
|
|
77
|
+
- `request(wait: false, ...)` retains the old `{ request_id:, result: }` submission-only API, including MQTT. Use your existing receive loop with `response` in that mode. Automatic remote session acquisition can still wait unless an explicit key or `auto_session: false` is supplied.
|
|
78
|
+
- IDs are generated or supplied with `request_id:` (2 through `0xffffffff`), overriding `last_packet_id`. Use fresh IDs; deliberately reusing one cannot distinguish a stale reply. ID 1 is excluded because transport predecessor zero requests a random ID.
|
|
79
|
+
|
|
80
|
+
### Automatic sessions
|
|
81
|
+
|
|
82
|
+
Remote state-changing `send` calls (including convenience setters, reboot, and firmware commands) automatically request `get_config_request: :SESSIONKEY_CONFIG` before transmitting when no passkey was supplied. Acquisition must return a correlated `get_config_response` containing exactly eight passkey bytes; otherwise the write is not sent. Reads and local PhoneAPI operations do not need acquisition. An explicit eight-byte `session_passkey:` or a passkey already in `message:` bypasses acquisition; `auto_session: false` explicitly disables it. For MQTT provide the key obtained through your own authorized receive workflow.
|
|
83
|
+
|
|
84
|
+
The key is cached **only in the connection handle, scoped by target node**. A matching synchronous getter refreshes that target's cache. Cache entries expire conservatively after 150 seconds measured from the start of acquisition; `refresh_session: true` forces a new acquisition. `ADMIN_BAD_SESSION_KEY` invalidates the target cache, so the **next caller-initiated** write acquires again. No state-changing request is automatically replayed, including on timeout or rejection. Reconnect with a new handle after reboot to discard old state.
|
|
85
|
+
|
|
86
|
+
The node's passkey is not its public/private key, channel PSK, or a substitute for remote admin authorization. Upstream exempts local PhoneAPI commands (`from == 0`) and enumerated getters/responses from passkey checks. Other remote commands require it. Firmware expires keys after 300 seconds and can rotate them when issuing a response after 150 seconds; another controller can therefore invalidate even a locally unexpired key. The library does not log keys or payloads; response hashes and connection handles contain secrets, so do not log or serialize them. `:SESSIONKEY_CONFIG` carries the key in the **AdminMessage envelope**, not the Config payload.
|
|
87
|
+
|
|
88
|
+
Convenience getters and `send` still return the transport submission result. They do not wait for readback; use `request` when verification matters.
|
|
89
|
+
|
|
90
|
+
## Sources and compatibility
|
|
91
|
+
|
|
92
|
+
Official upstream sources inspected for the wire contract and firmware behavior:
|
|
93
|
+
|
|
94
|
+
- [admin.proto](https://github.com/meshtastic/protobufs/blob/master/meshtastic/admin.proto)
|
|
95
|
+
- [AdminModule.cpp](https://github.com/meshtastic/firmware/blob/master/src/modules/AdminModule.cpp)
|
|
96
|
+
|
|
97
|
+
The generated protobuf schema can expose fields not implemented by an installed firmware build. Sensor, key-verification, lockdown, module-specific handlers, simulator, SD, DFU, and OTA functionality remain firmware/hardware dependent. Encoding a field does not establish that a device supports it. This implementation was verified with real Ruby protobuf encoding and fake transports, not live hardware.
|
|
98
|
+
|
|
49
99
|
## Related
|
|
50
100
|
|
|
51
|
-
- [
|
|
52
|
-
- [
|
|
53
|
-
- [
|
|
54
|
-
- [
|
|
101
|
+
- [Admin::Channel](admin-channel.md)
|
|
102
|
+
- [Admin::Config](admin-config.md)
|
|
103
|
+
- [Admin::Firmware](admin-firmware.md)
|
|
104
|
+
- [Channel](channel.md)
|
|
105
|
+
- [Config](config.md)
|
|
106
|
+
- [ModuleConfig](module-config.md)
|
|
107
|
+
- [RTTTL](rtttl.md)
|
data/documentation/atak.md
CHANGED
|
@@ -1,27 +1,110 @@
|
|
|
1
1
|
# Meshtastic::ATAK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TAK / ATAK over Meshtastic. Three wire formats, same as the official clients:
|
|
4
|
+
|
|
5
|
+
| Format | Port | Payload |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| V1 `ATAK_PLUGIN` | 72 | Bare `TAKPacket` (PLI, GeoChat, optional `detail` bytes) |
|
|
8
|
+
| V2 `ATAK_PLUGIN_V2` | 78 | `[flags][TAKPacketV2]`. This gem emits uncompressed frames (`flags=0xFF`) |
|
|
9
|
+
| V1 `ATAK_FORWARDER` | 257 | zlib-compressed CoT XML (single packet, max `DATA_PAYLOAD_LEN`) |
|
|
10
|
+
|
|
11
|
+
V2 typed payloads: GeoChat, aircraft, shapes, markers, range-and-bearing, routes, CASEVAC, emergency, task, TAKTALK, raw detail. Contact / group / status on V1; callsign / team / role / lat-lon on V2.
|
|
12
|
+
|
|
13
|
+
Firmware ≥ 2.8.0 speaks V2. Older radios use V1 PLI/chat, and generic CoT on the forwarder port.
|
|
14
|
+
|
|
15
|
+
Compressed V2 (zstd dictionary id 0 or 1) is not unpacked here; `decode_v2` raises unless `flags=0xFF`.
|
|
4
16
|
|
|
5
17
|
## Methods
|
|
6
18
|
|
|
7
|
-
- `encode
|
|
8
|
-
- `send`
|
|
19
|
+
- `encode` / `encode_v1` — `TAKPacket`
|
|
20
|
+
- `send` / `send_v1` / `send_chat` / `send_pli` — port 72
|
|
21
|
+
- `build_v2` / `encode_v2` / `wrap_v2` / `decode_v2` / `send_v2` — port 78
|
|
22
|
+
- `compress_cot` / `decompress_cot` / `send_cot` — port 257 (`cot:` XML string)
|
|
23
|
+
- `decode(payload:, portnum:)` — dispatches on port
|
|
9
24
|
- `help` / `authors`
|
|
10
25
|
|
|
11
|
-
|
|
26
|
+
`lat` / `lon` are decimal degrees (stored × 1e7). Pass a connected `serial_obj`, `bluetooth_obj`, or `tcp_obj`.
|
|
27
|
+
|
|
28
|
+
## V1 GeoChat and PLI
|
|
12
29
|
|
|
13
30
|
```ruby
|
|
14
|
-
Meshtastic::ATAK.
|
|
31
|
+
Meshtastic::ATAK.send_chat(
|
|
32
|
+
serial_obj: serial_obj,
|
|
33
|
+
message: 'ATAK chat',
|
|
34
|
+
to: 'ANDROID-aabbccdd',
|
|
35
|
+
callsign: 'ALPHA',
|
|
36
|
+
device_callsign: 'RADIO-1',
|
|
37
|
+
team: :Cyan,
|
|
38
|
+
role: :TeamMember,
|
|
39
|
+
battery: 87
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
Meshtastic::ATAK.send_pli(
|
|
43
|
+
serial_obj: serial_obj,
|
|
44
|
+
lat: 37.7749,
|
|
45
|
+
lon: -122.4194,
|
|
46
|
+
altitude: 10,
|
|
47
|
+
speed: 0,
|
|
48
|
+
course: 90,
|
|
49
|
+
callsign: 'ALPHA',
|
|
50
|
+
team: :Cyan
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## V2 typed events
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
Meshtastic::ATAK.send_v2(serial_obj: serial_obj, message: 'v2 chat', callsign: 'ALPHA')
|
|
58
|
+
|
|
59
|
+
Meshtastic::ATAK.send_v2(
|
|
15
60
|
serial_obj: serial_obj,
|
|
16
|
-
|
|
61
|
+
callsign: 'ALPHA',
|
|
62
|
+
lat: 37.7749,
|
|
63
|
+
lon: -122.4194,
|
|
64
|
+
aircraft: Meshtastic::AircraftTrack.new(icao: 'ABC123', flight: 'N1')
|
|
17
65
|
)
|
|
18
66
|
|
|
19
|
-
|
|
20
|
-
|
|
67
|
+
Meshtastic::ATAK.send_v2(serial_obj: serial_obj, shape: Meshtastic::DrawnShape.new(kind: :Kind_Circle, major_cm: 1000))
|
|
68
|
+
Meshtastic::ATAK.send_v2(serial_obj: serial_obj, marker: Meshtastic::Marker.new(kind: :Kind_Spot))
|
|
69
|
+
Meshtastic::ATAK.send_v2(serial_obj: serial_obj, route: Meshtastic::Route.new(prefix: 'R1'))
|
|
70
|
+
Meshtastic::ATAK.send_v2(serial_obj: serial_obj, casevac: Meshtastic::CasevacReport.new(title: 'CASEVAC'))
|
|
71
|
+
Meshtastic::ATAK.send_v2(serial_obj: serial_obj, emergency: Meshtastic::EmergencyAlert.new(type: :Type_Alert911))
|
|
72
|
+
Meshtastic::ATAK.send_v2(serial_obj: serial_obj, task: Meshtastic::TaskRequest.new(task_type: 'recon'))
|
|
73
|
+
Meshtastic::ATAK.send_v2(serial_obj: serial_obj, taktalk: Meshtastic::TakTalkMessage.new(text: 'hi', chatroom_id: 'room1'))
|
|
21
74
|
```
|
|
22
75
|
|
|
23
|
-
|
|
76
|
+
Receive V2:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
Meshtastic::Serial.subscribe(serial_obj: serial_obj, include: 'ATAK_PLUGIN') do |message|
|
|
80
|
+
port = message.dig(:packet, :decoded, :portnum)
|
|
81
|
+
payload = message.dig(:packet, :decoded, :payload)
|
|
82
|
+
decoded = Meshtastic::ATAK.decode(payload: payload, portnum: port)
|
|
83
|
+
p decoded
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Generic CoT (forwarder)
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
Meshtastic::ATAK.send_cot(
|
|
91
|
+
serial_obj: serial_obj,
|
|
92
|
+
cot: '<event type="b-m-p-s-m" uid="marker-1"><point lat="37.77" lon="-122.41"/></event>'
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Raises if zlib output exceeds `Meshtastic::Constants::DATA_PAYLOAD_LEN`. Multi-packet fountain (FTN) is not implemented; keep CoT small or use V2 typed payloads.
|
|
97
|
+
|
|
98
|
+
## Radio TAK module
|
|
99
|
+
|
|
100
|
+
Device role `TAK` / `TAK_TRACKER` and Module Config → TAK (`team` / `role`) are [Admin](admin.md) / [ModuleConfig](module-config.md), not this client:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
Meshtastic::ModuleConfig.get(serial_obj: serial_obj, module_config_type: :TAK_CONFIG)
|
|
104
|
+
```
|
|
24
105
|
|
|
25
106
|
## Related
|
|
26
107
|
|
|
27
|
-
- [Meshtastic::ModuleConfig](module-config.md)
|
|
108
|
+
- [Meshtastic::ModuleConfig](module-config.md)
|
|
109
|
+
- [Generated protobuf types](protobufs.md)
|
|
110
|
+
- [TAK wire formats](https://meshtastic.org/docs/software/apple/developer/tak-protocol/)
|
data/documentation/channel.md
CHANGED
|
@@ -1,30 +1,3 @@
|
|
|
1
1
|
# Meshtastic::Channel
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
This is a protobuf class, not a Ruby module. Do not add class methods named `send` or `encode`.
|
|
6
|
-
|
|
7
|
-
## Methods
|
|
8
|
-
|
|
9
|
-
- `get(serial_obj:, index:)` — `get_channel_request`
|
|
10
|
-
- `set(serial_obj:, channel:)` — `set_channel`
|
|
11
|
-
- `help` / `authors`
|
|
12
|
-
|
|
13
|
-
Also all protobuf instance fields: `index`, `settings` (`ChannelSettings`), `role` (`:DISABLED`, `:PRIMARY`, `:SECONDARY`).
|
|
14
|
-
|
|
15
|
-
## Example
|
|
16
|
-
|
|
17
|
-
```ruby
|
|
18
|
-
Meshtastic::Channel.get(serial_obj: serial_obj, index: 0)
|
|
19
|
-
|
|
20
|
-
settings = Meshtastic::ChannelSettings.new(name: 'LongFast', psk: "\x01")
|
|
21
|
-
channel = Meshtastic::Channel.new(index: 0, settings: settings, role: :PRIMARY)
|
|
22
|
-
Meshtastic::Channel.set(serial_obj: serial_obj, channel: channel)
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Listen for `ADMIN_APP` replies on the same transport.
|
|
26
|
-
|
|
27
|
-
## Related
|
|
28
|
-
|
|
29
|
-
- [Meshtastic::Admin](admin.md)
|
|
30
|
-
- [Meshtastic::Apponly](apponly.md) (`ChannelSet`)
|
|
3
|
+
Generated protobuf (`index`, `settings`, `role`). Get/set on a live node is [Meshtastic::Admin::Channel](admin-channel.md).
|
data/documentation/config.md
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
1
|
# Meshtastic::Config
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Methods
|
|
6
|
-
|
|
7
|
-
- `get(serial_obj:, config_type: :DEVICE_CONFIG)`
|
|
8
|
-
- `set(serial_obj:, config:)` — `config` is a `Meshtastic::Config`
|
|
9
|
-
- `help` / `authors`
|
|
10
|
-
|
|
11
|
-
Protobuf oneofs include `device`, `position`, `power`, `network`, `display`, `lora`, `bluetooth`, `security`, `sessionkey`, `device_ui`.
|
|
12
|
-
|
|
13
|
-
## Example
|
|
14
|
-
|
|
15
|
-
```ruby
|
|
16
|
-
Meshtastic::Config.get(serial_obj: serial_obj, config_type: :LORA_CONFIG)
|
|
17
|
-
|
|
18
|
-
config = Meshtastic::Config.new
|
|
19
|
-
config.device = Meshtastic::Config::DeviceConfig.new(role: :CLIENT)
|
|
20
|
-
Meshtastic::Config.set(serial_obj: serial_obj, config: config)
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
`config_type` list: [Admin](admin.md).
|
|
24
|
-
|
|
25
|
-
## Related
|
|
26
|
-
|
|
27
|
-
- [Meshtastic::Admin](admin.md)
|
|
28
|
-
- [Meshtastic::ModuleConfig](module-config.md)
|
|
29
|
-
- [Meshtastic::Localonly](localonly.md)
|
|
3
|
+
Generated protobuf (device, position, power, network, display, lora, bluetooth, security, sessionkey, device_ui). Get/set on a live node is [Meshtastic::Admin::Config](admin-config.md).
|
|
@@ -18,6 +18,13 @@ Packet builder used by Serial, Bluetooth, TCP, and MQTT. Instantiated internally
|
|
|
18
18
|
|
|
19
19
|
On Serial/Bluetooth/TCP, transports pass `psks: nil` so the radio owns channel crypto. MQTT must pass `psks`.
|
|
20
20
|
|
|
21
|
+
`send_data` and `send_packet` preserve `pki_encrypted: true` and `public_key:`
|
|
22
|
+
(32 raw bytes) for remote administrative requests. The connected radio performs
|
|
23
|
+
the public-key encryption; the Ruby client does not encrypt these packets itself.
|
|
24
|
+
Explicit PKI requests reject MQTT and host-side PSK encryption instead of silently
|
|
25
|
+
falling back to channel encryption. Supplying a recipient key does not grant admin
|
|
26
|
+
rights: the target must authorize the sending radio's key.
|
|
27
|
+
|
|
21
28
|
`send_text` refuses payloads larger than `Meshtastic::Constants::DATA_PAYLOAD_LEN`.
|
|
22
29
|
|
|
23
30
|
## Example
|
data/documentation/meshtastic.md
CHANGED
|
@@ -31,13 +31,13 @@ Routes a `Meshtastic::Data` payload to a connected radio.
|
|
|
31
31
|
|
|
32
32
|
```ruby
|
|
33
33
|
Meshtastic.deliver_data(
|
|
34
|
-
serial_obj: serial_obj, # or bluetooth_obj:
|
|
34
|
+
serial_obj: serial_obj, # or bluetooth_obj: / tcp_obj: / mqtt_obj:
|
|
35
35
|
data: Meshtastic::Data.new(portnum: :TEXT_MESSAGE_APP, payload: 'hi'),
|
|
36
36
|
port_num: Meshtastic::PortNum::TEXT_MESSAGE_APP
|
|
37
37
|
)
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Raises `ArgumentError` unless `data` is a `Meshtastic::Data` and one of `serial_obj`, `bluetooth_obj`, or `
|
|
40
|
+
Raises `ArgumentError` unless `data` is a `Meshtastic::Data` and one of `serial_obj`, `bluetooth_obj`, `tcp_obj`, or `mqtt_obj` is present.
|
|
41
41
|
|
|
42
42
|
## Related
|
|
43
43
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'meshtastic/channel_pb'
|
|
4
|
+
require 'meshtastic/apponly_pb'
|
|
5
|
+
require 'base64'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
module Meshtastic
|
|
9
|
+
module Admin
|
|
10
|
+
module Channel
|
|
11
|
+
public_class_method def self.build_settings(opts = {})
|
|
12
|
+
source = opts[:settings]
|
|
13
|
+
settings = if source.is_a?(Hash)
|
|
14
|
+
Meshtastic::ChannelSettings.new(source)
|
|
15
|
+
elsif source
|
|
16
|
+
Meshtastic::ChannelSettings.decode(source.to_proto)
|
|
17
|
+
else
|
|
18
|
+
Meshtastic::ChannelSettings.new
|
|
19
|
+
end
|
|
20
|
+
Meshtastic::ChannelSettings.descriptor.each do |field|
|
|
21
|
+
key = field.name.to_sym
|
|
22
|
+
next unless opts.key?(key)
|
|
23
|
+
|
|
24
|
+
value = opts[key]
|
|
25
|
+
value = field.subtype.msgclass.new(value) if value.is_a?(Hash) && field.subtype
|
|
26
|
+
settings[field.name] = value
|
|
27
|
+
end
|
|
28
|
+
raise ArgumentError, 'PSK must contain 0, 1, 16, or 32 raw bytes' unless [0, 1, 16, 32].include?(settings.psk.bytesize)
|
|
29
|
+
raise ArgumentError, 'channel name must be fewer than 12 bytes' unless settings.name.bytesize < 12
|
|
30
|
+
|
|
31
|
+
settings
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
public_class_method def self.build(opts = {})
|
|
35
|
+
channel = opts[:channel] ? Meshtastic::Channel.decode(opts[:channel].to_proto) : Meshtastic::Channel.new
|
|
36
|
+
index = opts.fetch(:index, channel.index)
|
|
37
|
+
raise ArgumentError, 'index must be an integer from 0 through 7' unless index.is_a?(Integer) && (0..7).cover?(index)
|
|
38
|
+
|
|
39
|
+
channel.index = index
|
|
40
|
+
channel.role = opts[:role] if opts[:role]
|
|
41
|
+
raise ArgumentError, 'role must be PRIMARY, SECONDARY, or DISABLED' unless %i[PRIMARY SECONDARY DISABLED].include?(channel.role)
|
|
42
|
+
|
|
43
|
+
channel.settings = build_settings(opts.merge(settings: opts.fetch(:settings, channel.settings)))
|
|
44
|
+
channel
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
public_class_method def self.get(opts = {})
|
|
48
|
+
index = opts[:index]
|
|
49
|
+
index = 0 if index.nil?
|
|
50
|
+
Admin.get_channel(opts.merge(index: index))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
public_class_method def self.set(opts = {})
|
|
54
|
+
channel = build(opts.merge({}))
|
|
55
|
+
builder_keys = Meshtastic::ChannelSettings.descriptor.map { |field| field.name.to_sym } + %i[channel index role settings]
|
|
56
|
+
merged = opts.except(*builder_keys).merge(channel_settings: channel)
|
|
57
|
+
Admin.set_channel(merged)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
public_class_method def self.export_url(opts = {})
|
|
61
|
+
channels = opts.fetch(:channels)
|
|
62
|
+
raise ArgumentError, 'exactly one primary channel is required' unless channels.one? { |channel| channel.role == :PRIMARY }
|
|
63
|
+
|
|
64
|
+
enabled = channels.select { |channel| channel.role == :PRIMARY || (opts[:include_all] != false && channel.role == :SECONDARY) }
|
|
65
|
+
raise ArgumentError, 'at most eight enabled channels can be shared' if enabled.length > 8
|
|
66
|
+
|
|
67
|
+
enabled = enabled.sort_by { |channel| [channel.role == :PRIMARY ? 0 : 1, channel.index] }
|
|
68
|
+
settings = enabled.map { |channel| build_settings(settings: channel.settings) }
|
|
69
|
+
channel_set = Meshtastic::ChannelSet.new(settings: settings, lora_config: opts[:lora_config])
|
|
70
|
+
"https://meshtastic.org/e/##{Base64.urlsafe_encode64(channel_set.to_proto, padding: false)}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
public_class_method def self.import_url(opts = {})
|
|
74
|
+
uri = URI.parse(opts.fetch(:url))
|
|
75
|
+
valid = uri.scheme == 'https' && uri.host == 'meshtastic.org' && %w[/e/ /d/].include?(uri.path)
|
|
76
|
+
valid &&= uri.userinfo.nil? && uri.port == 443 && uri.fragment&.match?(/\A[A-Za-z0-9_-]+={0,2}\z/)
|
|
77
|
+
raise ArgumentError, 'invalid channel URL' unless valid
|
|
78
|
+
|
|
79
|
+
channel_set = Meshtastic::ChannelSet.decode(Base64.urlsafe_decode64(uri.fragment))
|
|
80
|
+
raise ArgumentError, 'invalid channel URL' unless (1..8).cover?(channel_set.settings.length)
|
|
81
|
+
|
|
82
|
+
channel_set
|
|
83
|
+
rescue URI::InvalidURIError, Google::Protobuf::ParseError, ArgumentError, TypeError
|
|
84
|
+
raise ArgumentError, 'invalid channel URL'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
public_class_method def self.apply_url(opts = {})
|
|
88
|
+
channel_set = import_url(url: opts[:url])
|
|
89
|
+
raise ArgumentError, 'add-only or query URL application is not supported; import offline and select slots explicitly' if URI.parse(opts[:url]).query
|
|
90
|
+
|
|
91
|
+
transport = opts.except(:url)
|
|
92
|
+
channels = channel_set.settings.each_with_index.map do |settings, index|
|
|
93
|
+
build(index: index, role: index.zero? ? :PRIMARY : :SECONDARY, settings: settings)
|
|
94
|
+
end
|
|
95
|
+
results = channels.map { |channel| set(transport.merge(channel: channel)) }
|
|
96
|
+
results << Config.set_lora(transport.merge(lora: channel_set.lora_config)) if channel_set.lora_config
|
|
97
|
+
results
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
public_class_method def self.authors
|
|
101
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n "
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
public_class_method def self.help
|
|
105
|
+
puts "USAGE:
|
|
106
|
+
# Build ChannelSettings for a mesh channel.
|
|
107
|
+
#{self}.build_settings(
|
|
108
|
+
settings: 'optional - ChannelSettings protobuf or Hash copied before overlay',
|
|
109
|
+
name: 'optional - channel name shorter than twelve UTF-8 bytes',
|
|
110
|
+
psk: 'optional - raw PSK of zero, one, sixteen, or thirty-two bytes',
|
|
111
|
+
channel_num: 'optional - deprecated channel number; prefer Config LoRa channel_num',
|
|
112
|
+
id: 'optional - channel hash id',
|
|
113
|
+
uplink_enabled: 'optional - whether MQTT uplink is enabled',
|
|
114
|
+
downlink_enabled: 'optional - whether MQTT downlink is enabled',
|
|
115
|
+
module_settings: 'optional - ModuleSettings protobuf or field Hash for precision and mute',
|
|
116
|
+
use_aead: 'optional - whether AEAD crypto is enabled'
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Build a Channel protobuf with index, role, and settings.
|
|
120
|
+
#{self}.build(
|
|
121
|
+
channel: 'optional - existing Channel protobuf copied before overlay',
|
|
122
|
+
index: 'optional - integer channel slot zero through seven',
|
|
123
|
+
role: 'optional - :PRIMARY, :SECONDARY, or :DISABLED',
|
|
124
|
+
settings: 'optional - ChannelSettings protobuf or field Hash to attach'
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Request a channel slot from the node.
|
|
128
|
+
#{self}.get(
|
|
129
|
+
serial_obj: 'optional - serial handle from Meshtastic::Serial.connect',
|
|
130
|
+
index: 'optional - zero-based channel slot; Admin adds one on wire (default: 0)'
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Write a channel slot on the node.
|
|
134
|
+
#{self}.set(
|
|
135
|
+
serial_obj: 'optional - serial handle from Meshtastic::Serial.connect',
|
|
136
|
+
channel: 'optional - Channel protobuf to write',
|
|
137
|
+
index: 'optional - channel slot index when building a channel',
|
|
138
|
+
role: 'optional - :PRIMARY, :SECONDARY, or :DISABLED',
|
|
139
|
+
settings: 'optional - ChannelSettings protobuf or field Hash to attach'
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Export enabled channels to a sharing URL.
|
|
143
|
+
#{self}.export_url(
|
|
144
|
+
channels: 'required - array of Channel protobufs with one primary',
|
|
145
|
+
include_all: 'optional - include secondary channels unless false',
|
|
146
|
+
lora_config: 'optional - LoRaConfig protobuf included in the URL'
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Decode a sharing URL without writing hardware.
|
|
150
|
+
#{self}.import_url(url: 'required - Meshtastic e or d channel URL')
|
|
151
|
+
|
|
152
|
+
# Write URL channels and optional LoRa configuration.
|
|
153
|
+
#{self}.apply_url(
|
|
154
|
+
url: 'required - Meshtastic channel URL replacing slots from zero',
|
|
155
|
+
serial_obj: 'optional - connected serial transport; BLE, TCP, MQTT also supported'
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Print the AUTHOR(S) string for this module.
|
|
159
|
+
#{self}.authors
|
|
160
|
+
"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|