meshtastic 0.0.176 → 0.0.177
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/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +0 -21
- data/Gemfile +1 -0
- data/README.md +79 -82
- data/lib/meshtastic/bluetooth/bluez.rb +187 -0
- data/lib/meshtastic/bluetooth.rb +299 -0
- data/lib/meshtastic/lorawan_bridge_pb.rb +20 -0
- data/lib/meshtastic/mesh_interface.rb +3 -1
- data/lib/meshtastic/mqtt.rb +7 -34
- data/lib/meshtastic/{serial_interface.rb → serial.rb} +144 -106
- data/lib/meshtastic/stream_interface.rb +1 -1
- data/lib/meshtastic/version.rb +1 -1
- data/lib/meshtastic.rb +3 -1
- data/spec/lib/meshtastic/bluetooth/bluez_spec.rb +190 -0
- data/spec/lib/meshtastic/bluetooth_spec.rb +146 -0
- data/spec/lib/meshtastic/lorawan_bridge_pb_spec.rb +6 -0
- data/spec/lib/meshtastic/mqtt_spec.rb +160 -1
- data/spec/lib/meshtastic/serial_spec.rb +295 -0
- metadata +23 -3
- data/spec/lib/meshtastic/serial_interface_spec.rb +0 -78
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'pty'
|
|
5
|
+
|
|
6
|
+
describe Meshtastic::Serial do # rubocop:disable Metrics/BlockLength
|
|
7
|
+
def with_serial_link(opts = {})
|
|
8
|
+
PTY.open do |device, host|
|
|
9
|
+
serial_obj = described_class.connect({ block_dev: host.path, want_config: false }.merge(opts))
|
|
10
|
+
begin
|
|
11
|
+
yield serial_obj, device
|
|
12
|
+
ensure
|
|
13
|
+
described_class.disconnect(serial_obj: serial_obj)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def radio_frame(message)
|
|
19
|
+
body = message.to_proto
|
|
20
|
+
[Meshtastic::START1, Meshtastic::START2, body.bytesize].pack('CCn') + body
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe '.wait_for_config' do
|
|
24
|
+
it 'times out rather than accepting a mismatched configuration ID' do
|
|
25
|
+
with_serial_link(want_config: true) do |serial_obj, device|
|
|
26
|
+
wrong_id = serial_obj[:config_id] ^ 1
|
|
27
|
+
device.write(radio_frame(Meshtastic::FromRadio.new(config_complete_id: wrong_id)))
|
|
28
|
+
expect do
|
|
29
|
+
described_class.wait_for_config(serial_obj: serial_obj, timeout: 0.05)
|
|
30
|
+
end.to raise_error(Timeout::Error, /No configuration response/)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'waits for the matching configuration response without consuming received messages' do
|
|
35
|
+
with_serial_link(want_config: true) do |serial_obj, device|
|
|
36
|
+
request = Timeout.timeout(1) do
|
|
37
|
+
expect(device.read(32)).to eq(([Meshtastic::START2] * 32).pack('C*'))
|
|
38
|
+
header = device.read(4)
|
|
39
|
+
Meshtastic::ToRadio.decode(device.read(header.unpack('CCn').last))
|
|
40
|
+
end
|
|
41
|
+
device.write(radio_frame(Meshtastic::FromRadio.new(my_info: Meshtastic::MyNodeInfo.new(my_node_num: 123))))
|
|
42
|
+
device.write(radio_frame(Meshtastic::FromRadio.new(config_complete_id: request.want_config_id)))
|
|
43
|
+
expect(described_class.wait_for_config(serial_obj: serial_obj, timeout: 1)).to eq(serial_obj)
|
|
44
|
+
expect(serial_obj[:my_node_num]).to eq(123)
|
|
45
|
+
expect(described_class.recv_from_radio(serial_obj: serial_obj, timeout: 0).payload_variant).to eq(:my_info)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe 'serial reception' do
|
|
51
|
+
it 'receives after an idle interval longer than the UART read timeout' do
|
|
52
|
+
with_serial_link do |serial_obj, device|
|
|
53
|
+
expect(described_class.recv_from_radio(serial_obj: serial_obj, timeout: 0.6)).to be_nil
|
|
54
|
+
message = Meshtastic::FromRadio.new(my_info: Meshtastic::MyNodeInfo.new(my_node_num: 123))
|
|
55
|
+
device.write(radio_frame(message))
|
|
56
|
+
expect(described_class.recv_from_radio(serial_obj: serial_obj, timeout: 1)).to eq(message)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'skips oversized headers and malformed protobufs before the next valid frame' do
|
|
61
|
+
with_serial_link do |serial_obj, device|
|
|
62
|
+
message = Meshtastic::FromRadio.new(my_info: Meshtastic::MyNodeInfo.new(my_node_num: 123))
|
|
63
|
+
expect do
|
|
64
|
+
device.write([Meshtastic::START1, Meshtastic::START2, 513].pack('CCn'))
|
|
65
|
+
device.write([Meshtastic::START1, Meshtastic::START2, 1, 255].pack('CCnC'))
|
|
66
|
+
device.write(radio_frame(message))
|
|
67
|
+
expect(described_class.recv_from_radio(serial_obj: serial_obj, timeout: 1)).to eq(message)
|
|
68
|
+
end.to output(/failed to decode FromRadio/).to_stderr
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'unblocks an indefinite receive when disconnected' do
|
|
73
|
+
with_serial_link do |serial_obj, _device|
|
|
74
|
+
receiver = Thread.new { described_class.recv_from_radio(serial_obj: serial_obj, timeout: nil) }
|
|
75
|
+
begin
|
|
76
|
+
described_class.disconnect(serial_obj: serial_obj)
|
|
77
|
+
expect(receiver.join(1)).to eq(receiver)
|
|
78
|
+
expect(receiver.value).to be_nil
|
|
79
|
+
expect(described_class.drain_from_radio(serial_obj: serial_obj)).to eq([])
|
|
80
|
+
ensure
|
|
81
|
+
receiver.kill.join
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'treats text payloads as UTF-8, never as nested protobuf messages' do
|
|
87
|
+
with_serial_link do |serial_obj, device|
|
|
88
|
+
text = "\n\x02hi"
|
|
89
|
+
device.write(radio_frame(Meshtastic::FromRadio.new(packet: Meshtastic::MeshPacket.new(
|
|
90
|
+
decoded: Meshtastic::Data.new(portnum: :TEXT_MESSAGE_APP, payload: text)
|
|
91
|
+
))))
|
|
92
|
+
Timeout.timeout(1) do
|
|
93
|
+
described_class.subscribe(serial_obj: serial_obj) do |message|
|
|
94
|
+
expect(message.dig(:packet, :decoded, :payload)).to eq(text)
|
|
95
|
+
expect(message.dig(:packet, :decoded, :payload).encoding).to eq(Encoding::UTF_8)
|
|
96
|
+
break
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'reports an unplugged device to blocked receivers' do
|
|
103
|
+
with_serial_link do |serial_obj, device|
|
|
104
|
+
device.close
|
|
105
|
+
expect { described_class.recv_from_radio(serial_obj: serial_obj, timeout: 1) }.to raise_error(IOError)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'keeps independent receive queues and disconnect state for each device' do
|
|
110
|
+
with_serial_link do |first, first_device|
|
|
111
|
+
with_serial_link do |second, second_device|
|
|
112
|
+
first_message = Meshtastic::FromRadio.new(my_info: Meshtastic::MyNodeInfo.new(my_node_num: 1))
|
|
113
|
+
second_message = Meshtastic::FromRadio.new(my_info: Meshtastic::MyNodeInfo.new(my_node_num: 2))
|
|
114
|
+
second_device.write(radio_frame(second_message))
|
|
115
|
+
first_device.write(radio_frame(first_message))
|
|
116
|
+
expect(described_class.recv_from_radio(serial_obj: first, timeout: 1)).to eq(first_message)
|
|
117
|
+
expect(described_class.recv_from_radio(serial_obj: second, timeout: 1)).to eq(second_message)
|
|
118
|
+
described_class.disconnect(serial_obj: first)
|
|
119
|
+
second_device.write(radio_frame(second_message))
|
|
120
|
+
expect(described_class.recv_from_radio(serial_obj: second, timeout: 1)).to eq(second_message)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'returns immediately when polling an empty queue with timeout zero' do
|
|
126
|
+
with_serial_link do |_serial_obj, _device|
|
|
127
|
+
result = Timeout.timeout(0.2) { described_class.recv_from_radio(timeout: 0) }
|
|
128
|
+
expect(result).to be_nil
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'yields received text through subscription filters' do
|
|
133
|
+
with_serial_link do |serial_obj, device|
|
|
134
|
+
%w[hidden hello].each do |text|
|
|
135
|
+
device.write(radio_frame(Meshtastic::FromRadio.new(packet: Meshtastic::MeshPacket.new(
|
|
136
|
+
from: 123, decoded: Meshtastic::Data.new(portnum: :TEXT_MESSAGE_APP, payload: text)
|
|
137
|
+
))))
|
|
138
|
+
end
|
|
139
|
+
received = nil
|
|
140
|
+
Timeout.timeout(2) do
|
|
141
|
+
described_class.subscribe(serial_obj: serial_obj, include: 'TEXT_MESSAGE_APP', exclude: 'hidden') do |message|
|
|
142
|
+
received = message.dig(:packet, :decoded, :payload)
|
|
143
|
+
break
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
expect(received).to eq('hello')
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'resynchronizes overlapping start bytes and receives a fragmented text packet' do
|
|
151
|
+
with_serial_link do |_serial_obj, device|
|
|
152
|
+
message = Meshtastic::FromRadio.new(packet: Meshtastic::MeshPacket.new(
|
|
153
|
+
from: 123, decoded: Meshtastic::Data.new(portnum: :TEXT_MESSAGE_APP, payload: 'hello')
|
|
154
|
+
))
|
|
155
|
+
device.write("boot\n\x94".b)
|
|
156
|
+
radio_frame(message).each_byte { |byte| device.write([byte].pack('C')) }
|
|
157
|
+
expect(described_class.recv_from_radio(timeout: 1)).to eq(message)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def fake_serial_obj
|
|
163
|
+
written = +''.b
|
|
164
|
+
serial_conn = Object.new
|
|
165
|
+
serial_conn.define_singleton_method(:write) do |b|
|
|
166
|
+
written << b
|
|
167
|
+
b.bytesize
|
|
168
|
+
end
|
|
169
|
+
serial_conn.define_singleton_method(:flush) { true }
|
|
170
|
+
serial_conn.define_singleton_method(:closed?) { false }
|
|
171
|
+
serial_conn.define_singleton_method(:close) { true }
|
|
172
|
+
serial_conn.define_singleton_method(:written) { written }
|
|
173
|
+
{ serial_conn: serial_conn, written: written, my_node_num: 0xb0b }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe '.send_to_radio' do
|
|
177
|
+
it 'writes the entire frame when the transport accepts only part of each write' do
|
|
178
|
+
serial_obj = fake_serial_obj
|
|
179
|
+
written = serial_obj[:written]
|
|
180
|
+
serial_obj[:serial_conn].define_singleton_method(:write) do |bytes|
|
|
181
|
+
written << bytes.byteslice(0, 2)
|
|
182
|
+
[bytes.bytesize, 2].min
|
|
183
|
+
end
|
|
184
|
+
message = Meshtastic::ToRadio.new(want_config_id: 42)
|
|
185
|
+
described_class.send_to_radio(serial_obj: serial_obj, to_radio: message)
|
|
186
|
+
expect(written).to eq(radio_frame(message))
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'frames a ToRadio with START1/START2 and big-endian length' do
|
|
190
|
+
serial_obj = fake_serial_obj
|
|
191
|
+
to_radio = Meshtastic::ToRadio.new
|
|
192
|
+
to_radio.want_config_id = 42
|
|
193
|
+
body = to_radio.to_proto
|
|
194
|
+
|
|
195
|
+
described_class.send_to_radio(serial_obj: serial_obj, to_radio: to_radio)
|
|
196
|
+
frame = serial_obj[:serial_conn].written
|
|
197
|
+
|
|
198
|
+
expect(frame.getbyte(0)).to eq(Meshtastic::START1)
|
|
199
|
+
expect(frame.getbyte(1)).to eq(Meshtastic::START2)
|
|
200
|
+
expect((frame.getbyte(2) << 8) + frame.getbyte(3)).to eq(body.bytesize)
|
|
201
|
+
expect(frame.byteslice(4, body.bytesize)).to eq(body)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it 'accepts a pre-serialized String body' do
|
|
205
|
+
serial_obj = fake_serial_obj
|
|
206
|
+
body = Meshtastic::ToRadio.new.tap { |t| t.want_config_id = 7 }.to_proto
|
|
207
|
+
described_class.send_to_radio(serial_obj: serial_obj, to_radio: body)
|
|
208
|
+
frame = serial_obj[:serial_conn].written
|
|
209
|
+
expect(frame.bytesize).to eq(4 + body.bytesize)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
describe '.send_text' do
|
|
214
|
+
it 'rejects text exceeding the byte limit without closing the connection' do
|
|
215
|
+
serial_obj = fake_serial_obj
|
|
216
|
+
expect do
|
|
217
|
+
described_class.send_text(serial_obj: serial_obj, text: 'é' * Meshtastic::Constants::DATA_PAYLOAD_LEN)
|
|
218
|
+
end.to raise_error(ArgumentError, /Bytes/)
|
|
219
|
+
expect(serial_obj[:written]).to be_empty
|
|
220
|
+
expect(serial_obj[:closing]).to be_nil
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it 'lets the firmware supply the sender before my_info arrives' do
|
|
224
|
+
serial_obj = fake_serial_obj
|
|
225
|
+
serial_obj[:my_node_num] = nil
|
|
226
|
+
described_class.send_text(serial_obj: serial_obj, text: 'ping')
|
|
227
|
+
expect(Meshtastic::ToRadio.decode(serial_obj[:written].byteslice(4..)).packet.from).to eq(0)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'writes a decoded ToRadio mesh packet for the radio to encrypt' do
|
|
231
|
+
serial_obj = fake_serial_obj
|
|
232
|
+
described_class.send_text(
|
|
233
|
+
serial_obj: serial_obj,
|
|
234
|
+
text: 'ping',
|
|
235
|
+
to: '!ffffffff',
|
|
236
|
+
channel: 0
|
|
237
|
+
)
|
|
238
|
+
frame = serial_obj[:serial_conn].written
|
|
239
|
+
body_len = (frame.getbyte(2) << 8) + frame.getbyte(3)
|
|
240
|
+
tr = Meshtastic::ToRadio.decode(frame.byteslice(4, body_len))
|
|
241
|
+
|
|
242
|
+
expect(tr.packet).not_to be_nil
|
|
243
|
+
expect(tr.packet.decoded).not_to be_nil
|
|
244
|
+
expect(tr.packet.decoded.payload).to eq('ping')
|
|
245
|
+
expect(tr.packet.encrypted.to_s).to eq('')
|
|
246
|
+
expect(tr.packet.from).to eq(0xb0b)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
describe '.send_data' do
|
|
251
|
+
it 'sends a binary application packet over a real serial transport' do
|
|
252
|
+
with_serial_link do |serial_obj, device|
|
|
253
|
+
data = Meshtastic::Data.new(portnum: :PRIVATE_APP, payload: "\x00\xff".b)
|
|
254
|
+
described_class.send_data(serial_obj: serial_obj, data: data, to: '!0000007b', channel: 1, want_ack: true)
|
|
255
|
+
packet = Timeout.timeout(1) do
|
|
256
|
+
device.read(32)
|
|
257
|
+
header = device.read(4)
|
|
258
|
+
expect(header.bytes.first(2)).to eq([Meshtastic::START1, Meshtastic::START2])
|
|
259
|
+
Meshtastic::ToRadio.decode(device.read(header.unpack('CCn').last)).packet
|
|
260
|
+
end
|
|
261
|
+
expect(packet.decoded).to eq(data)
|
|
262
|
+
expect(packet.to).to eq(123)
|
|
263
|
+
expect(packet.from).to eq(0)
|
|
264
|
+
expect(packet.channel).to eq(1)
|
|
265
|
+
expect(packet.want_ack).to be(true)
|
|
266
|
+
expect(packet.id).to be_positive
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
describe '.disconnect' do
|
|
272
|
+
it 'closes a failed transport without recursively sending disconnect packets' do
|
|
273
|
+
serial_obj = fake_serial_obj
|
|
274
|
+
writes = 0
|
|
275
|
+
serial_obj[:serial_conn].define_singleton_method(:write) do |_bytes|
|
|
276
|
+
writes += 1
|
|
277
|
+
raise IOError, 'disconnected USB device'
|
|
278
|
+
end
|
|
279
|
+
expect(serial_obj[:serial_conn]).to receive(:close)
|
|
280
|
+
expect { described_class.disconnect(serial_obj: serial_obj) }.not_to raise_error
|
|
281
|
+
expect(writes).to eq(1)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
it 'returns nil and closes the serial connection' do
|
|
285
|
+
serial_obj = fake_serial_obj
|
|
286
|
+
expect(described_class.disconnect(serial_obj: serial_obj)).to be_nil
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
describe '.help' do
|
|
291
|
+
it 'prints usage without raising' do
|
|
292
|
+
expect { described_class.help }.to output(/USAGE/).to_stdout
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: meshtastic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.177
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 0day Inc.
|
|
@@ -205,6 +205,20 @@ dependencies:
|
|
|
205
205
|
- - '='
|
|
206
206
|
- !ruby/object:Gem::Version
|
|
207
207
|
version: 3.10.2
|
|
208
|
+
- !ruby/object:Gem::Dependency
|
|
209
|
+
name: ruby-dbus
|
|
210
|
+
requirement: !ruby/object:Gem::Requirement
|
|
211
|
+
requirements:
|
|
212
|
+
- - '='
|
|
213
|
+
- !ruby/object:Gem::Version
|
|
214
|
+
version: 0.25.0
|
|
215
|
+
type: :runtime
|
|
216
|
+
prerelease: false
|
|
217
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
218
|
+
requirements:
|
|
219
|
+
- - '='
|
|
220
|
+
- !ruby/object:Gem::Version
|
|
221
|
+
version: 0.25.0
|
|
208
222
|
- !ruby/object:Gem::Dependency
|
|
209
223
|
name: rvm
|
|
210
224
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -278,6 +292,8 @@ files:
|
|
|
278
292
|
- lib/meshtastic/apponly_pb.rb
|
|
279
293
|
- lib/meshtastic/atak.rb
|
|
280
294
|
- lib/meshtastic/atak_pb.rb
|
|
295
|
+
- lib/meshtastic/bluetooth.rb
|
|
296
|
+
- lib/meshtastic/bluetooth/bluez.rb
|
|
281
297
|
- lib/meshtastic/cannedmessages.rb
|
|
282
298
|
- lib/meshtastic/cannedmessages_pb.rb
|
|
283
299
|
- lib/meshtastic/channel.rb
|
|
@@ -295,6 +311,7 @@ files:
|
|
|
295
311
|
- lib/meshtastic/interdevice_pb.rb
|
|
296
312
|
- lib/meshtastic/localonly.rb
|
|
297
313
|
- lib/meshtastic/localonly_pb.rb
|
|
314
|
+
- lib/meshtastic/lorawan_bridge_pb.rb
|
|
298
315
|
- lib/meshtastic/mesh_beacon_pb.rb
|
|
299
316
|
- lib/meshtastic/mesh_interface.rb
|
|
300
317
|
- lib/meshtastic/mesh_pb.rb
|
|
@@ -311,8 +328,8 @@ files:
|
|
|
311
328
|
- lib/meshtastic/remote_hardware_pb.rb
|
|
312
329
|
- lib/meshtastic/rtttl.rb
|
|
313
330
|
- lib/meshtastic/rtttl_pb.rb
|
|
331
|
+
- lib/meshtastic/serial.rb
|
|
314
332
|
- lib/meshtastic/serial_hal_pb.rb
|
|
315
|
-
- lib/meshtastic/serial_interface.rb
|
|
316
333
|
- lib/meshtastic/storeforward.rb
|
|
317
334
|
- lib/meshtastic/storeforward_pb.rb
|
|
318
335
|
- lib/meshtastic/stream_interface.rb
|
|
@@ -332,6 +349,8 @@ files:
|
|
|
332
349
|
- spec/lib/meshtastic/apponly_spec.rb
|
|
333
350
|
- spec/lib/meshtastic/atak_pb_spec.rb
|
|
334
351
|
- spec/lib/meshtastic/atak_spec.rb
|
|
352
|
+
- spec/lib/meshtastic/bluetooth/bluez_spec.rb
|
|
353
|
+
- spec/lib/meshtastic/bluetooth_spec.rb
|
|
335
354
|
- spec/lib/meshtastic/cannedmessages_pb_spec.rb
|
|
336
355
|
- spec/lib/meshtastic/cannedmessages_spec.rb
|
|
337
356
|
- spec/lib/meshtastic/channel_pb_spec.rb
|
|
@@ -349,6 +368,7 @@ files:
|
|
|
349
368
|
- spec/lib/meshtastic/interdevice_pb_spec.rb
|
|
350
369
|
- spec/lib/meshtastic/localonly_pb_spec.rb
|
|
351
370
|
- spec/lib/meshtastic/localonly_spec.rb
|
|
371
|
+
- spec/lib/meshtastic/lorawan_bridge_pb_spec.rb
|
|
352
372
|
- spec/lib/meshtastic/mesh_beacon_pb_spec.rb
|
|
353
373
|
- spec/lib/meshtastic/mesh_interface_spec.rb
|
|
354
374
|
- spec/lib/meshtastic/mesh_pb_spec.rb
|
|
@@ -366,7 +386,7 @@ files:
|
|
|
366
386
|
- spec/lib/meshtastic/rtttl_pb_spec.rb
|
|
367
387
|
- spec/lib/meshtastic/rtttl_spec.rb
|
|
368
388
|
- spec/lib/meshtastic/serial_hal_pb_spec.rb
|
|
369
|
-
- spec/lib/meshtastic/
|
|
389
|
+
- spec/lib/meshtastic/serial_spec.rb
|
|
370
390
|
- spec/lib/meshtastic/storeforward_pb_spec.rb
|
|
371
391
|
- spec/lib/meshtastic/storeforward_spec.rb
|
|
372
392
|
- spec/lib/meshtastic/stream_interface_spec.rb
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
describe Meshtastic::SerialInterface do
|
|
6
|
-
def fake_serial_obj
|
|
7
|
-
written = +''.b
|
|
8
|
-
serial_conn = Object.new
|
|
9
|
-
serial_conn.define_singleton_method(:write) do |b|
|
|
10
|
-
written << b
|
|
11
|
-
b.bytesize
|
|
12
|
-
end
|
|
13
|
-
serial_conn.define_singleton_method(:flush) { true }
|
|
14
|
-
serial_conn.define_singleton_method(:closed?) { false }
|
|
15
|
-
serial_conn.define_singleton_method(:close) { true }
|
|
16
|
-
serial_conn.define_singleton_method(:written) { written }
|
|
17
|
-
{ serial_conn: serial_conn, written: written, my_node_num: 0xb0b }
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
describe '.send_to_radio' do
|
|
21
|
-
it 'frames a ToRadio with START1/START2 and big-endian length' do
|
|
22
|
-
serial_obj = fake_serial_obj
|
|
23
|
-
to_radio = Meshtastic::ToRadio.new
|
|
24
|
-
to_radio.want_config_id = 42
|
|
25
|
-
body = to_radio.to_proto
|
|
26
|
-
|
|
27
|
-
described_class.send_to_radio(serial_obj: serial_obj, to_radio: to_radio)
|
|
28
|
-
frame = serial_obj[:serial_conn].written
|
|
29
|
-
|
|
30
|
-
expect(frame.getbyte(0)).to eq(Meshtastic::START1)
|
|
31
|
-
expect(frame.getbyte(1)).to eq(Meshtastic::START2)
|
|
32
|
-
expect((frame.getbyte(2) << 8) + frame.getbyte(3)).to eq(body.bytesize)
|
|
33
|
-
expect(frame.byteslice(4, body.bytesize)).to eq(body)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it 'accepts a pre-serialized String body' do
|
|
37
|
-
serial_obj = fake_serial_obj
|
|
38
|
-
body = Meshtastic::ToRadio.new.tap { |t| t.want_config_id = 7 }.to_proto
|
|
39
|
-
described_class.send_to_radio(serial_obj: serial_obj, to_radio: body)
|
|
40
|
-
frame = serial_obj[:serial_conn].written
|
|
41
|
-
expect(frame.bytesize).to eq(4 + body.bytesize)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
describe '.send_text' do
|
|
46
|
-
it 'writes a decoded ToRadio mesh packet for the radio to encrypt' do
|
|
47
|
-
serial_obj = fake_serial_obj
|
|
48
|
-
described_class.send_text(
|
|
49
|
-
serial_obj: serial_obj,
|
|
50
|
-
text: 'ping',
|
|
51
|
-
to: '!ffffffff',
|
|
52
|
-
channel: 0
|
|
53
|
-
)
|
|
54
|
-
frame = serial_obj[:serial_conn].written
|
|
55
|
-
body_len = (frame.getbyte(2) << 8) + frame.getbyte(3)
|
|
56
|
-
tr = Meshtastic::ToRadio.decode(frame.byteslice(4, body_len))
|
|
57
|
-
|
|
58
|
-
expect(tr.packet).not_to be_nil
|
|
59
|
-
expect(tr.packet.decoded).not_to be_nil
|
|
60
|
-
expect(tr.packet.decoded.payload).to eq('ping')
|
|
61
|
-
expect(tr.packet.encrypted.to_s).to eq('')
|
|
62
|
-
expect(tr.packet.from).to eq(0xb0b)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
describe '.disconnect' do
|
|
67
|
-
it 'returns nil and closes the serial connection' do
|
|
68
|
-
serial_obj = fake_serial_obj
|
|
69
|
-
expect(described_class.disconnect(serial_obj: serial_obj)).to be_nil
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
describe '.help' do
|
|
74
|
-
it 'prints usage without raising' do
|
|
75
|
-
expect { described_class.help }.to output(/USAGE/).to_stdout
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|