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,299 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'timeout'
|
|
5
|
+
|
|
6
|
+
# Meshtastic client API over Bluetooth Low Energy, using Linux BlueZ and Ruby D-Bus.
|
|
7
|
+
module Meshtastic
|
|
8
|
+
module Bluetooth
|
|
9
|
+
autoload :BlueZ, 'meshtastic/bluetooth/bluez'
|
|
10
|
+
|
|
11
|
+
def self.scan(opts = {})
|
|
12
|
+
BlueZ.scan(adapter: opts.fetch(:adapter, 'hci0'), timeout: opts.fetch(:timeout, 5))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Connect to an already paired BLE address (not a mesh node ID).
|
|
16
|
+
def self.connect(opts = {})
|
|
17
|
+
connection = BlueZ.new(address: opts[:address], adapter: opts.fetch(:adapter, 'hci0'), timeout: opts.fetch(:timeout, 15))
|
|
18
|
+
connection.connect
|
|
19
|
+
bluetooth_obj = {
|
|
20
|
+
bluetooth_conn: connection, address: opts[:address], tx_mutex: Mutex.new,
|
|
21
|
+
rx_mutex: Mutex.new, from_radio_queue: Queue.new, config_queue: Queue.new,
|
|
22
|
+
proto_data: [], console_data: []
|
|
23
|
+
}
|
|
24
|
+
bluetooth_obj[:rx_thread] = start_reader(bluetooth_obj)
|
|
25
|
+
if opts.fetch(:want_config, true)
|
|
26
|
+
mesh = Meshtastic::MeshInterface.new
|
|
27
|
+
bytes = mesh.start_config
|
|
28
|
+
bluetooth_obj[:config_id] = mesh.config_id
|
|
29
|
+
send_to_radio(bluetooth_obj: bluetooth_obj, to_radio: bytes)
|
|
30
|
+
end
|
|
31
|
+
@last_bluetooth_obj = bluetooth_obj
|
|
32
|
+
bluetooth_obj
|
|
33
|
+
rescue StandardError
|
|
34
|
+
bluetooth_obj ? disconnect(bluetooth_obj: bluetooth_obj) : connection&.close
|
|
35
|
+
raise
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private_class_method def self.start_reader(handle)
|
|
39
|
+
Thread.new do
|
|
40
|
+
until handle[:closing]
|
|
41
|
+
bytes = handle[:bluetooth_conn].read
|
|
42
|
+
if bytes.empty?
|
|
43
|
+
sleep 0.1
|
|
44
|
+
next
|
|
45
|
+
end
|
|
46
|
+
receive_bytes(handle, bytes)
|
|
47
|
+
end
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
handle[:rx_error] = IOError.new("Bluetooth receive failed: #{e.message}") unless handle[:closing]
|
|
50
|
+
ensure
|
|
51
|
+
handle[:from_radio_queue].close
|
|
52
|
+
handle[:config_queue].close
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private_class_method def self.receive_bytes(handle, bytes)
|
|
57
|
+
message = Meshtastic::FromRadio.decode(bytes)
|
|
58
|
+
if message.my_info
|
|
59
|
+
handle[:my_info] = message.my_info.to_h
|
|
60
|
+
handle[:my_node_num] = message.my_info.my_node_num
|
|
61
|
+
end
|
|
62
|
+
handle[:metadata] = message.metadata.to_h if message.metadata
|
|
63
|
+
handle[:rx_mutex].synchronize do
|
|
64
|
+
handle[:proto_data] << message.to_h
|
|
65
|
+
handle[:console_data] << "#{message.log_record.message}\n" if message.log_record
|
|
66
|
+
end
|
|
67
|
+
handle[:from_radio_queue] << message
|
|
68
|
+
if message.payload_variant == :config_complete_id && message.config_complete_id == handle[:config_id]
|
|
69
|
+
handle[:config_complete] = true
|
|
70
|
+
handle[:config_queue].close
|
|
71
|
+
end
|
|
72
|
+
rescue Google::Protobuf::ParseError => e
|
|
73
|
+
warn "Meshtastic::Bluetooth: failed to decode FromRadio (#{e.message})"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private_class_method def self.handle_for(opts)
|
|
77
|
+
handle = opts[:bluetooth_obj] || @last_bluetooth_obj
|
|
78
|
+
raise ArgumentError, 'bluetooth_obj is required; call connect first' unless handle
|
|
79
|
+
|
|
80
|
+
handle
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.wait_for_config(opts = {})
|
|
84
|
+
handle = handle_for(opts)
|
|
85
|
+
raise ArgumentError, 'connect with want_config: true first' unless handle[:config_id]
|
|
86
|
+
|
|
87
|
+
handle[:config_queue].pop(timeout: opts.fetch(:timeout, 10))
|
|
88
|
+
raise handle[:rx_error] if handle[:rx_error]
|
|
89
|
+
raise IOError, 'Bluetooth connection closed' if handle[:closing]
|
|
90
|
+
raise Timeout::Error, "No configuration response from #{handle[:address]}" unless handle[:config_complete]
|
|
91
|
+
|
|
92
|
+
handle
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.recv_from_radio(opts = {})
|
|
96
|
+
handle = handle_for(opts)
|
|
97
|
+
timeout = opts.fetch(:timeout, 5)
|
|
98
|
+
timeout = nil if timeout&.negative?
|
|
99
|
+
message = handle[:from_radio_queue].pop(timeout: timeout)
|
|
100
|
+
raise handle[:rx_error] if message.nil? && handle[:rx_error]
|
|
101
|
+
|
|
102
|
+
message
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.drain_from_radio(opts = {})
|
|
106
|
+
handle = handle_for(opts)
|
|
107
|
+
messages = []
|
|
108
|
+
opts.fetch(:max, 256).times do
|
|
109
|
+
message = recv_from_radio(bluetooth_obj: handle, timeout: 0)
|
|
110
|
+
break unless message
|
|
111
|
+
|
|
112
|
+
messages << message
|
|
113
|
+
end
|
|
114
|
+
messages
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.dump_stdout_data(opts = {}, &)
|
|
118
|
+
Meshtastic::Serial.dump_stdout_data(opts.merge(serial_obj: handle_for(opts)), &)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.flush_data(opts = {})
|
|
122
|
+
Meshtastic::Serial.flush_data(opts.merge(serial_obj: handle_for(opts)))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Yield the same enriched FromRadio hashes as Serial, without opening a UART.
|
|
126
|
+
def self.subscribe(opts = {})
|
|
127
|
+
handle = handle_for(opts)
|
|
128
|
+
psks = opts.fetch(:psks, { LongFast: 'AQ==' }).dup
|
|
129
|
+
raise ArgumentError, 'psks must be a hash' unless psks.is_a?(Hash)
|
|
130
|
+
|
|
131
|
+
psks[:LongFast] = '1PG7OiApB1nwvP+rz05pAQ==' if psks[:LongFast] == 'AQ=='
|
|
132
|
+
psks = Meshtastic::MeshInterface.new.get_cipher_keys(psks: psks)
|
|
133
|
+
includes = opts[:include].to_s.split(',').map(&:strip)
|
|
134
|
+
excludes = opts[:exclude].to_s.split(',').map(&:strip)
|
|
135
|
+
loop do
|
|
136
|
+
message = recv_from_radio(bluetooth_obj: handle, timeout: opts[:timeout])
|
|
137
|
+
break if message.nil? && handle[:from_radio_queue].closed?
|
|
138
|
+
next unless message
|
|
139
|
+
|
|
140
|
+
decoded = message.to_h
|
|
141
|
+
if decoded[:packet]
|
|
142
|
+
# Share the existing pure packet decoder; BLE never uses Serial's IO methods.
|
|
143
|
+
decoded[:packet] = Meshtastic::Serial.send(:enrich_packet,
|
|
144
|
+
message: decoded[:packet], psks: psks,
|
|
145
|
+
gps_metadata: opts[:gps_metadata], include_raw: opts[:include_raw],
|
|
146
|
+
raw_packet: opts[:include_raw] ? message.to_proto : nil)
|
|
147
|
+
end
|
|
148
|
+
source = decoded.inspect
|
|
149
|
+
next unless includes.all? { |term| source.include?(term) } && excludes.none? { |term| source.include?(term) }
|
|
150
|
+
|
|
151
|
+
if block_given?
|
|
152
|
+
yield decoded
|
|
153
|
+
else
|
|
154
|
+
begin
|
|
155
|
+
puts JSON.pretty_generate(decoded)
|
|
156
|
+
rescue JSON::GeneratorError
|
|
157
|
+
puts decoded.inspect
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
rescue Interrupt
|
|
162
|
+
disconnect(bluetooth_obj: handle)
|
|
163
|
+
rescue StandardError
|
|
164
|
+
disconnect(bluetooth_obj: handle)
|
|
165
|
+
raise
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Write one complete serialized ToRadio to GATT. BLE does not use UART headers.
|
|
169
|
+
def self.send_to_radio(opts = {})
|
|
170
|
+
handle = opts[:bluetooth_obj]
|
|
171
|
+
raise ArgumentError, 'bluetooth_obj is required' unless handle
|
|
172
|
+
raise IOError, 'Bluetooth connection closed' if handle[:closing]
|
|
173
|
+
|
|
174
|
+
message = opts[:to_radio]
|
|
175
|
+
body = case message
|
|
176
|
+
when Meshtastic::ToRadio then message.to_proto
|
|
177
|
+
when String then message.b
|
|
178
|
+
else raise ArgumentError, 'to_radio must be Meshtastic::ToRadio or a serialized String'
|
|
179
|
+
end
|
|
180
|
+
raise ArgumentError, 'ToRadio payload exceeds 512 bytes' if body.bytesize > Meshtastic::MAX_TO_FROM_RADIO_SIZE
|
|
181
|
+
|
|
182
|
+
begin
|
|
183
|
+
handle[:tx_mutex].synchronize { handle[:bluetooth_conn].write(body) }
|
|
184
|
+
rescue StandardError
|
|
185
|
+
disconnect(bluetooth_obj: handle)
|
|
186
|
+
raise
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def self.send_text(opts = {})
|
|
191
|
+
handle = opts[:bluetooth_obj]
|
|
192
|
+
raise ArgumentError, 'bluetooth_obj is required' unless handle
|
|
193
|
+
|
|
194
|
+
text = opts.fetch(:text, 'SYN').to_s
|
|
195
|
+
max_len = Meshtastic::Constants::DATA_PAYLOAD_LEN
|
|
196
|
+
raise ArgumentError, "Text Length > #{max_len} Bytes" if text.bytesize > max_len
|
|
197
|
+
|
|
198
|
+
args = opts.merge(text: text, via: :radio, psks: nil, channel: opts.fetch(:channel, 0), from: opts[:from] || handle[:my_node_num] || 0)
|
|
199
|
+
send_to_radio(bluetooth_obj: handle, to_radio: Meshtastic::MeshInterface.new.send_text(args))
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def self.send_data(opts = {})
|
|
203
|
+
handle = opts[:bluetooth_obj]
|
|
204
|
+
raise ArgumentError, 'bluetooth_obj is required' unless handle
|
|
205
|
+
raise ArgumentError, 'data must be Meshtastic::Data' unless opts[:data].is_a?(Meshtastic::Data)
|
|
206
|
+
|
|
207
|
+
args = opts.merge(via: :radio, psks: nil, channel: opts.fetch(:channel, 0), from: opts[:from] || handle[:my_node_num] || 0)
|
|
208
|
+
send_to_radio(bluetooth_obj: handle, to_radio: Meshtastic::MeshInterface.new.send_data(args))
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def self.disconnect(opts = {})
|
|
212
|
+
handle = opts[:bluetooth_obj]
|
|
213
|
+
return unless handle
|
|
214
|
+
return if handle[:closing]
|
|
215
|
+
|
|
216
|
+
handle[:closing] = true
|
|
217
|
+
handle[:from_radio_queue].close
|
|
218
|
+
handle[:config_queue].close
|
|
219
|
+
begin
|
|
220
|
+
handle[:tx_mutex].synchronize do
|
|
221
|
+
handle[:bluetooth_conn].write(Meshtastic::ToRadio.new(disconnect: true).to_proto)
|
|
222
|
+
end
|
|
223
|
+
rescue StandardError
|
|
224
|
+
# A lost BLE link cannot receive a disconnect request.
|
|
225
|
+
nil
|
|
226
|
+
ensure
|
|
227
|
+
handle[:bluetooth_conn].close
|
|
228
|
+
reader = handle[:rx_thread]
|
|
229
|
+
reader.join(1) if reader && reader != Thread.current
|
|
230
|
+
end
|
|
231
|
+
nil
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def self.authors
|
|
235
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n "
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def self.help
|
|
239
|
+
puts "Send and receive Meshtastic messages over Bluetooth Low Energy (Linux BlueZ).
|
|
240
|
+
BLE writes unframed ToRadio protobufs (no UART START1/START2 header). Pair first with bluetoothctl.
|
|
241
|
+
|
|
242
|
+
USAGE:
|
|
243
|
+
devices = #{self}.scan(
|
|
244
|
+
adapter: 'optional - BlueZ adapter (default: hci0)',
|
|
245
|
+
timeout: 'optional - discovery seconds (default: 5)'
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
bluetooth_obj = #{self}.connect(
|
|
249
|
+
address: 'required - BLE address (AA:BB:CC:DD:EE:FF), not a mesh node ID',
|
|
250
|
+
adapter: 'optional - BlueZ adapter (default: hci0)',
|
|
251
|
+
timeout: 'optional - D-Bus/connect seconds (default: 15)',
|
|
252
|
+
want_config: 'optional - request full node DB after connect (default: true)'
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
#{self}.wait_for_config(
|
|
256
|
+
bluetooth_obj: 'required - bluetooth_obj connected with want_config: true',
|
|
257
|
+
timeout: 'optional - seconds to await configuration (default: 10)'
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
#{self}.send_to_radio(
|
|
261
|
+
bluetooth_obj: 'required - bluetooth_obj returned from #connect',
|
|
262
|
+
to_radio: 'required - Meshtastic::ToRadio OR serialized String'
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
from_radio = #{self}.recv_from_radio(
|
|
266
|
+
bluetooth_obj: 'optional - bluetooth_obj (default: most recently opened connection)',
|
|
267
|
+
timeout: 'optional - seconds (default: 5; 0 = poll; nil = block forever)'
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
msgs = #{self}.drain_from_radio(bluetooth_obj: bluetooth_obj, max: 256)
|
|
271
|
+
|
|
272
|
+
#{self}.subscribe(
|
|
273
|
+
bluetooth_obj: 'required - bluetooth_obj returned from #connect',
|
|
274
|
+
include: 'optional - comma-delimited string(s) to include',
|
|
275
|
+
exclude: 'optional - comma-delimited string(s) to exclude'
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
#{self}.send_text(
|
|
279
|
+
bluetooth_obj: 'required - bluetooth_obj returned from #connect',
|
|
280
|
+
to: 'optional - Destination ID (Default: \"!ffffffff\")',
|
|
281
|
+
channel: 'optional - channel index (Default: 0)',
|
|
282
|
+
text: 'optional - Text Message (Default: SYN)',
|
|
283
|
+
want_ack: 'optional - Want Acknowledgement (Default: false)'
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
#{self}.send_data(
|
|
287
|
+
bluetooth_obj: 'required - bluetooth_obj returned from #connect',
|
|
288
|
+
data: 'required - Meshtastic::Data',
|
|
289
|
+
to: 'optional - Destination ID (Default: \"!ffffffff\")',
|
|
290
|
+
channel: 'optional - channel index (Default: 0)'
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
#{self}.disconnect(bluetooth_obj: bluetooth_obj)
|
|
294
|
+
|
|
295
|
+
#{self}.authors
|
|
296
|
+
"
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: meshtastic/lorawan_bridge.proto
|
|
4
|
+
|
|
5
|
+
require 'google/protobuf'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
descriptor_data = "\n\x1fmeshtastic/lorawan_bridge.proto\x12\nmeshtastic\"\xfc\x07\n\rLoRaWANBridge\x12\x32\n\x06uplink\x18\x01 \x01(\x0b\x32 .meshtastic.LoRaWANBridge.UplinkH\x00\x12\x36\n\x08\x64ownlink\x18\x02 \x01(\x0b\x32\".meshtastic.LoRaWANBridge.DownlinkH\x00\x12\x37\n\ttx_result\x18\x03 \x01(\x0b\x32\".meshtastic.LoRaWANBridge.TxResultH\x00\x12\x37\n\x05\x63hunk\x18\x04 \x01(\x0b\x32&.meshtastic.LoRaWANBridge.PayloadChunkH\x00\x1a\xaf\x01\n\x06Uplink\x12\x0f\n\x07\x66req_hz\x18\x01 \x01(\x07\x12\x0c\n\x04tmst\x18\x02 \x01(\x07\x12\x10\n\x08rssi_x10\x18\x03 \x01(\x11\x12\x0f\n\x07snr_x10\x18\x04 \x01(\x11\x12\x14\n\x0cradio_params\x18\x05 \x01(\r\x12\x13\n\x0b\x66sk_bitrate\x18\t \x01(\r\x12\x0f\n\x07payload\x18\x06 \x01(\x0c\x12\x12\n\npayload_id\x18\x07 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x08 \x01(\r\x1a\xef\x01\n\x08\x44ownlink\x12\x0f\n\x07\x66req_hz\x18\x01 \x01(\x07\x12\x0c\n\x04tmst\x18\x02 \x01(\x07\x12\x14\n\x0cradio_params\x18\x03 \x01(\r\x12\x11\n\tpower_dbm\x18\x04 \x01(\r\x12\x11\n\timmediate\x18\x05 \x01(\x08\x12\x17\n\x0finvert_polarity\x18\x06 \x01(\x08\x12\x0e\n\x06no_crc\x18\x07 \x01(\x08\x12\x11\n\tmay_defer\x18\x08 \x01(\x08\x12\x12\n\nrequest_id\x18\x0c \x01(\r\x12\x0f\n\x07payload\x18\t \x01(\x0c\x12\x12\n\npayload_id\x18\n \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x0b \x01(\r\x1aN\n\x0cPayloadChunk\x12\x12\n\npayload_id\x18\x01 \x01(\r\x12\x13\n\x0b\x63hunk_index\x18\x02 \x01(\r\x12\x15\n\rpayload_chunk\x18\x03 \x01(\x0c\x1a\x8d\x02\n\x08TxResult\x12\x0c\n\x04tmst\x18\x01 \x01(\x07\x12\x39\n\x06status\x18\x02 \x01(\x0e\x32).meshtastic.LoRaWANBridge.TxResult.Status\x12\x12\n\nrequest_id\x18\x03 \x01(\r\"\xa3\x01\n\x06Status\x12\x08\n\x04NONE\x10\x00\x12\x0c\n\x08TOO_LATE\x10\x01\x12\r\n\tTOO_EARLY\x10\x02\x12\x14\n\x10\x43OLLISION_PACKET\x10\x03\x12\x14\n\x10\x43OLLISION_BEACON\x10\x04\x12\x0b\n\x07TX_FREQ\x10\x05\x12\x0c\n\x08TX_POWER\x10\x06\x12\x10\n\x0cGPS_UNLOCKED\x10\x07\x12\x0c\n\x08\x44\x45\x46\x45RRED\x10\x08\x12\x0b\n\x07\x44ROPPED\x10\tB\t\n\x07variantBi\n\x14org.meshtastic.protoB\x13LoRaWANBridgeProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3"
|
|
9
|
+
|
|
10
|
+
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
|
+
pool.add_serialized_file(descriptor_data)
|
|
12
|
+
|
|
13
|
+
module Meshtastic
|
|
14
|
+
LoRaWANBridge = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.LoRaWANBridge").msgclass
|
|
15
|
+
LoRaWANBridge::Uplink = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.LoRaWANBridge.Uplink").msgclass
|
|
16
|
+
LoRaWANBridge::Downlink = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.LoRaWANBridge.Downlink").msgclass
|
|
17
|
+
LoRaWANBridge::PayloadChunk = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.LoRaWANBridge.PayloadChunk").msgclass
|
|
18
|
+
LoRaWANBridge::TxResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.LoRaWANBridge.TxResult").msgclass
|
|
19
|
+
LoRaWANBridge::TxResult::Status = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("meshtastic.LoRaWANBridge.TxResult.Status").enummodule
|
|
20
|
+
end
|
|
@@ -477,7 +477,9 @@ module Meshtastic
|
|
|
477
477
|
decoder = Meshtastic::StoreAndForward
|
|
478
478
|
when :TELEMETRY_APP
|
|
479
479
|
decoder = Meshtastic::Telemetry
|
|
480
|
-
when :TEXT_MESSAGE_APP
|
|
480
|
+
when :TEXT_MESSAGE_APP
|
|
481
|
+
return payload.dup.force_encoding(Encoding::UTF_8).scrub
|
|
482
|
+
when :UNKNOWN_APP
|
|
481
483
|
decoder = Meshtastic::Data
|
|
482
484
|
when :TRACEROUTE_APP
|
|
483
485
|
decoder = Meshtastic::RouteDiscovery
|
data/lib/meshtastic/mqtt.rb
CHANGED
|
@@ -213,14 +213,8 @@ module Meshtastic
|
|
|
213
213
|
if message.is_a?(Hash)
|
|
214
214
|
flat_message = message.values.join(' ')
|
|
215
215
|
|
|
216
|
-
disp =
|
|
217
|
-
|
|
218
|
-
# include_arr.first == message[:id] ||
|
|
219
|
-
# include_arr.all? { |include| flat_message.include?(include) }
|
|
220
|
-
# )
|
|
221
|
-
|
|
222
|
-
disp = true if !exclude_arr.intersect?(flat_message) &&
|
|
223
|
-
include_arr.all? { |include| flat_message.include?(include) }
|
|
216
|
+
disp = exclude_arr.none? { |exc| flat_message.include?(exc) } &&
|
|
217
|
+
include_arr.all? { |inc| flat_message.include?(inc) }
|
|
224
218
|
|
|
225
219
|
if disp
|
|
226
220
|
if block_given?
|
|
@@ -276,33 +270,12 @@ module Meshtastic
|
|
|
276
270
|
opts[:topic] = absolute_topic
|
|
277
271
|
opts[:via] = :mqtt
|
|
278
272
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
mui = Meshtastic::MeshInterface.new
|
|
273
|
+
text = opts.fetch(:text, 'SYN').to_s
|
|
274
|
+
max_len = Meshtastic::Constants::DATA_PAYLOAD_LEN
|
|
275
|
+
raise ArgumentError, "ERROR: Text Length > #{max_len} Bytes" if text.bytesize > max_len
|
|
283
276
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
total_chunks.times do |i|
|
|
287
|
-
chunk_num = i + 1
|
|
288
|
-
chunk_prefix = " (#{chunk_num} of #{total_chunks})\n"
|
|
289
|
-
chunk_prefix_len = chunk_prefix.bytesize
|
|
290
|
-
start_index = i * (max_bytes - chunk_prefix_len)
|
|
291
|
-
end_index = (start_index + (max_bytes - chunk_prefix_len)) - 1
|
|
292
|
-
chunk = "#{chunk_prefix} #{text.byteslice(start_index..end_index)}"
|
|
293
|
-
# This addresses a weird bug in the protocal if the first byte
|
|
294
|
-
# is an h or H followed by a single byte, which returns
|
|
295
|
-
# {} or {bitfiled: INT}
|
|
296
|
-
opts[:text] = chunk
|
|
297
|
-
protobuf_chunk = mui.send_text(opts)
|
|
298
|
-
mqtt_obj.publish(absolute_topic, protobuf_chunk)
|
|
299
|
-
sleep 0.3
|
|
300
|
-
end
|
|
301
|
-
else
|
|
302
|
-
opts[:text] = " #{text}"
|
|
303
|
-
protobuf_text = mui.send_text(opts)
|
|
304
|
-
mqtt_obj.publish(absolute_topic, protobuf_text)
|
|
305
|
-
end
|
|
277
|
+
opts[:text] = text
|
|
278
|
+
mqtt_obj.publish(absolute_topic, Meshtastic::MeshInterface.new.send_text(opts))
|
|
306
279
|
rescue StandardError => e
|
|
307
280
|
raise e
|
|
308
281
|
end
|