meshtastic 0.0.115 → 0.0.116
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 +1 -1
- data/lib/meshtastic/mqtt.rb +8 -3
- data/lib/meshtastic/serial.rb +351 -0
- data/lib/meshtastic/version.rb +1 -1
- data/lib/meshtastic.rb +1 -0
- data/spec/lib/meshtastic/serial_spec.rb +6 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46e1db876551e7f78c1b28f5dbd0d066346890aa31ea7e5969165b8c9142d84b
|
4
|
+
data.tar.gz: fb7c1b7ee22e0cb7b86cdcaaefc5c89d49fcd7c36f2dbcc9ac5185424bc81276
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffb5a5731c4d63e46816eec4de5538aa8b28637d00c79159811bea652757aa9260f3587bd8c9db7fed2842766e28fd17943a5bfe917d6d684ada85b4bef50592
|
7
|
+
data.tar.gz: 4145d728651ca25dfaffc8f99d2cfdc3ebd17904be545c5c395a1839287207a3fbd05696f90be8755982ca574be405f77f7377577bb0d6e8ab278c8f127e1601
|
data/Gemfile
CHANGED
data/lib/meshtastic/mqtt.rb
CHANGED
@@ -6,7 +6,6 @@ require 'json'
|
|
6
6
|
require 'mqtt'
|
7
7
|
require 'openssl'
|
8
8
|
require 'securerandom'
|
9
|
-
require 'tty-prompt'
|
10
9
|
|
11
10
|
# Avoiding Namespace Collisions
|
12
11
|
MQTTClient = MQTT::Client
|
@@ -15,7 +14,7 @@ MQTTClient = MQTT::Client
|
|
15
14
|
module Meshtastic
|
16
15
|
module MQTT
|
17
16
|
# Supported Method Parameters::
|
18
|
-
# mqtt_obj = Meshtastic::
|
17
|
+
# mqtt_obj = Meshtastic::MQTT.connect(
|
19
18
|
# host: 'optional - mqtt host (default: mqtt.meshtastic.org)',
|
20
19
|
# port: 'optional - mqtt port (defaults: 1883)',
|
21
20
|
# tls: 'optional - use TLS (default: false)',
|
@@ -58,7 +57,7 @@ module Meshtastic
|
|
58
57
|
end
|
59
58
|
|
60
59
|
# Supported Method Parameters::
|
61
|
-
# Meshtastic::
|
60
|
+
# Meshtastic::MQTT.subscribe(
|
62
61
|
# mqtt_obj: 'required - mqtt_obj returned from #connect method'
|
63
62
|
# root_topic: 'optional - root topic (default: msh)',
|
64
63
|
# region: 'optional - region e.g. 'US/VA', etc (default: US)',
|
@@ -145,6 +144,11 @@ module Meshtastic
|
|
145
144
|
if encrypted_message.to_s.length.positive? &&
|
146
145
|
message[:topic]
|
147
146
|
|
147
|
+
# if message[:pki_encrypted]
|
148
|
+
# # TODO: Display Decrypted PKI Message
|
149
|
+
# public_key = message[:public_key]
|
150
|
+
# dec_public_key = Base64.strict_decode64(public_key)
|
151
|
+
# else
|
148
152
|
packet_id = message[:id]
|
149
153
|
packet_from = message[:from]
|
150
154
|
|
@@ -164,6 +168,7 @@ module Meshtastic
|
|
164
168
|
cipher.iv = nonce
|
165
169
|
|
166
170
|
decrypted = cipher.update(encrypted_message) + cipher.final
|
171
|
+
# end
|
167
172
|
message[:decoded] = Meshtastic::Data.decode(decrypted).to_h
|
168
173
|
message[:encrypted] = :decrypted
|
169
174
|
end
|
@@ -0,0 +1,351 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'geocoder'
|
5
|
+
require 'io/wait'
|
6
|
+
require 'json'
|
7
|
+
require 'openssl'
|
8
|
+
require 'securerandom'
|
9
|
+
require 'uart'
|
10
|
+
|
11
|
+
# Plugin used to interact with Meshtastic nodes
|
12
|
+
module Meshtastic
|
13
|
+
module Serial
|
14
|
+
@session_data = []
|
15
|
+
|
16
|
+
# Supported Method Parameters::
|
17
|
+
# serial_obj = Meshtastic::Serial.connect(
|
18
|
+
# block_dev: 'optional - serial block device path (defaults to /dev/ttyUSB0)',
|
19
|
+
# baud: 'optional - (defaults to 9600)',
|
20
|
+
# data_bits: 'optional - (defaults to 8)',
|
21
|
+
# stop_bits: 'optional - (defaults to 1)',
|
22
|
+
# parity: 'optional - :even|:mark|:odd|:space|:none (defaults to :none)'
|
23
|
+
# )
|
24
|
+
|
25
|
+
public_class_method def self.connect(opts = {})
|
26
|
+
block_dev = opts[:block_dev] ||= '/dev/ttyUSB0'
|
27
|
+
raise "Invalid block device: #{block_dev}" unless File.exist?(block_dev)
|
28
|
+
|
29
|
+
baud = opts[:baud] ||= 9_600
|
30
|
+
data_bits = opts[:data_bits] ||= 8
|
31
|
+
stop_bits = opts[:stop_bits] ||= 1
|
32
|
+
parity = opts[:parity] ||= :none
|
33
|
+
|
34
|
+
case parity.to_s.to_sym
|
35
|
+
when :even
|
36
|
+
parity = 'E'
|
37
|
+
when :odd
|
38
|
+
parity = 'O'
|
39
|
+
when :none
|
40
|
+
parity = 'N'
|
41
|
+
end
|
42
|
+
raise "Invalid parity: #{opts[:parity]}" if parity.nil?
|
43
|
+
|
44
|
+
mode = "#{data_bits}#{parity}#{stop_bits}"
|
45
|
+
puts mode
|
46
|
+
|
47
|
+
serial_conn = UART.open(
|
48
|
+
block_dev,
|
49
|
+
baud,
|
50
|
+
mode
|
51
|
+
)
|
52
|
+
|
53
|
+
serial_obj = {}
|
54
|
+
serial_obj[:serial_conn] = serial_conn
|
55
|
+
serial_obj[:session_thread] = init_session_thread(
|
56
|
+
serial_conn: serial_conn
|
57
|
+
)
|
58
|
+
|
59
|
+
serial_obj
|
60
|
+
rescue StandardError => e
|
61
|
+
disconnect(serial_obj: serial_obj) unless serial_obj.nil?
|
62
|
+
raise e
|
63
|
+
end
|
64
|
+
|
65
|
+
# Supported Method Parameters::
|
66
|
+
# Meshtastic::Serial.subscribe(
|
67
|
+
# serial_obj: 'required - serial_obj returned from #connect method'
|
68
|
+
# root_topic: 'optional - root topic (default: msh)',
|
69
|
+
# region: 'optional - region e.g. 'US/VA', etc (default: US)',
|
70
|
+
# channel_topic: 'optional - channel ID path e.g. "2/stat/#" (default: "2/e/LongFast/#")',
|
71
|
+
# psks: 'optional - hash of :channel_id => psk key value pairs (default: { LongFast: "AQ==" })',
|
72
|
+
# qos: 'optional - quality of service (default: 0)',
|
73
|
+
# exclude: 'optional - comma-delimited string(s) to exclude in message (default: nil)',
|
74
|
+
# include: 'optional - comma-delimited string(s) to include on in message (default: nil)',
|
75
|
+
# gps_metadata: 'optional - include GPS metadata in output (default: false)',
|
76
|
+
# include_raw: 'optional - include raw packet data in output (default: false)'
|
77
|
+
# )
|
78
|
+
|
79
|
+
public_class_method def self.subscribe(opts = {})
|
80
|
+
serial_obj = opts[:serial_obj]
|
81
|
+
root_topic = opts[:root_topic] ||= 'msh'
|
82
|
+
region = opts[:region] ||= 'US'
|
83
|
+
channel_topic = opts[:channel_topic] ||= '2/e/LongFast/#'
|
84
|
+
# TODO: Support Array of PSKs and attempt each until decrypted
|
85
|
+
|
86
|
+
public_psk = '1PG7OiApB1nwvP+rz05pAQ=='
|
87
|
+
psks = opts[:psks] ||= { LongFast: public_psk }
|
88
|
+
raise 'ERROR: psks parameter must be a hash of :channel_id => psk key value pairs' unless psks.is_a?(Hash)
|
89
|
+
|
90
|
+
psks[:LongFast] = public_psk if psks[:LongFast] == 'AQ=='
|
91
|
+
psks = Meshtastic.get_cipher_keys(psks: psks)
|
92
|
+
|
93
|
+
qos = opts[:qos] ||= 0
|
94
|
+
json = opts[:json] ||= false
|
95
|
+
exclude = opts[:exclude]
|
96
|
+
include = opts[:include]
|
97
|
+
gps_metadata = opts[:gps_metadata] ||= false
|
98
|
+
include_raw = opts[:include_raw] ||= false
|
99
|
+
|
100
|
+
# NOTE: Use MQTT Explorer for topic discovery
|
101
|
+
full_topic = "#{root_topic}/#{region}/#{channel_topic}"
|
102
|
+
full_topic = "#{root_topic}/#{region}" if region == '#'
|
103
|
+
puts "Subscribing to: #{full_topic}"
|
104
|
+
serial_obj.subscribe(full_topic, qos)
|
105
|
+
|
106
|
+
# MQTT::ProtocolException: No Ping Response received for 23 seconds (MQTT::ProtocolException)
|
107
|
+
|
108
|
+
include_arr = include.to_s.split(',').map(&:strip)
|
109
|
+
exclude_arr = exclude.to_s.split(',').map(&:strip)
|
110
|
+
serial_obj.get_packet do |packet_bytes|
|
111
|
+
raw_packet = packet_bytes.to_s if include_raw
|
112
|
+
raw_topic = packet_bytes.topic ||= ''
|
113
|
+
raw_payload = packet_bytes.payload ||= ''
|
114
|
+
|
115
|
+
begin
|
116
|
+
disp = false
|
117
|
+
decoded_payload_hash = {}
|
118
|
+
message = {}
|
119
|
+
stdout_message = ''
|
120
|
+
|
121
|
+
if json
|
122
|
+
decoded_payload_hash = JSON.parse(raw_payload, symbolize_names: true)
|
123
|
+
else
|
124
|
+
# decoded_payload = Meshtastic::ToRadio.decode(raw_payload)
|
125
|
+
decoded_payload = Meshtastic::FromRadio.decode(raw_payload)
|
126
|
+
decoded_payload_hash = decoded_payload.to_h
|
127
|
+
end
|
128
|
+
|
129
|
+
next unless decoded_payload_hash[:packet].is_a?(Hash)
|
130
|
+
|
131
|
+
message = decoded_payload_hash[:packet] if decoded_payload_hash.keys.include?(:packet)
|
132
|
+
message[:topic] = raw_topic
|
133
|
+
message[:node_id_from] = "!#{message[:from].to_i.to_s(16)}"
|
134
|
+
message[:node_id_to] = "!#{message[:to].to_i.to_s(16)}"
|
135
|
+
if message.keys.include?(:rx_time)
|
136
|
+
rx_time_int = message[:rx_time]
|
137
|
+
if rx_time_int.is_a?(Integer)
|
138
|
+
rx_time_utc = Time.at(rx_time_int).utc.to_s
|
139
|
+
message[:rx_time_utc] = rx_time_utc
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
if message.keys.include?(:public_key)
|
144
|
+
raw_public_key = message[:public_key]
|
145
|
+
message[:public_key] = Base64.strict_encode64(raw_public_key)
|
146
|
+
end
|
147
|
+
|
148
|
+
# If encrypted_message is not nil, then decrypt
|
149
|
+
# the message prior to decoding.
|
150
|
+
encrypted_message = message[:encrypted]
|
151
|
+
if encrypted_message.to_s.length.positive? &&
|
152
|
+
message[:topic]
|
153
|
+
|
154
|
+
# if message[:pki_encrypted]
|
155
|
+
# # TODO: Display Decrypted PKI Message
|
156
|
+
# public_key = message[:public_key]
|
157
|
+
# dec_public_key = Base64.strict_decode64(public_key)
|
158
|
+
# else
|
159
|
+
packet_id = message[:id]
|
160
|
+
packet_from = message[:from]
|
161
|
+
|
162
|
+
nonce_packet_id = [packet_id].pack('V').ljust(8, "\x00")
|
163
|
+
nonce_from_node = [packet_from].pack('V').ljust(8, "\x00")
|
164
|
+
nonce = "#{nonce_packet_id}#{nonce_from_node}"
|
165
|
+
|
166
|
+
psk = psks[:LongFast]
|
167
|
+
target_channel = message[:topic].split('/')[-2].to_sym
|
168
|
+
psk = psks[target_channel] if psks.keys.include?(target_channel)
|
169
|
+
dec_psk = Base64.strict_decode64(psk)
|
170
|
+
|
171
|
+
cipher = OpenSSL::Cipher.new('AES-128-CTR')
|
172
|
+
cipher = OpenSSL::Cipher.new('AES-256-CTR') if dec_psk.length == 32
|
173
|
+
cipher.decrypt
|
174
|
+
cipher.key = dec_psk
|
175
|
+
cipher.iv = nonce
|
176
|
+
|
177
|
+
decrypted = cipher.update(encrypted_message) + cipher.final
|
178
|
+
# end
|
179
|
+
message[:decoded] = Meshtastic::Data.decode(decrypted).to_h
|
180
|
+
message[:encrypted] = :decrypted
|
181
|
+
end
|
182
|
+
|
183
|
+
if message[:decoded]
|
184
|
+
# payload = Meshtastic::Data.decode(message[:decoded][:payload]).to_h
|
185
|
+
payload = message[:decoded][:payload]
|
186
|
+
msg_type = message[:decoded][:portnum]
|
187
|
+
message[:decoded][:payload] = Meshtastic.decode_payload(
|
188
|
+
payload: payload,
|
189
|
+
msg_type: msg_type,
|
190
|
+
gps_metadata: gps_metadata
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
message[:raw_packet] = raw_packet if include_raw
|
195
|
+
decoded_payload_hash[:packet] = message
|
196
|
+
unless block_given?
|
197
|
+
message[:stdout] = 'pretty'
|
198
|
+
stdout_message = JSON.pretty_generate(decoded_payload_hash)
|
199
|
+
end
|
200
|
+
rescue Encoding::CompatibilityError,
|
201
|
+
Google::Protobuf::ParseError,
|
202
|
+
JSON::GeneratorError,
|
203
|
+
ArgumentError => e
|
204
|
+
|
205
|
+
unless e.is_a?(Encoding::CompatibilityError)
|
206
|
+
message[:decrypted] = e.message if e.message.include?('key must be')
|
207
|
+
message[:decrypted] = 'unable to decrypt - psk?' if e.message.include?('occurred during parsing')
|
208
|
+
decoded_payload_hash[:packet] = message
|
209
|
+
unless block_given?
|
210
|
+
puts "WARNING: #{e.inspect} - MSG IS >>>"
|
211
|
+
# puts e.backtrace
|
212
|
+
message[:stdout] = 'inspect'
|
213
|
+
stdout_message = decoded_payload_hash.inspect
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
next
|
218
|
+
ensure
|
219
|
+
include_arr = [message[:id].to_s] if include_arr.empty?
|
220
|
+
if message.is_a?(Hash)
|
221
|
+
flat_message = message.values.join(' ')
|
222
|
+
|
223
|
+
disp = true if exclude_arr.none? { |exclude| flat_message.include?(exclude) } && (
|
224
|
+
include_arr.first == message[:id] ||
|
225
|
+
include_arr.all? { |include| flat_message.include?(include) }
|
226
|
+
)
|
227
|
+
|
228
|
+
if disp
|
229
|
+
if block_given?
|
230
|
+
yield decoded_payload_hash
|
231
|
+
else
|
232
|
+
puts "\n"
|
233
|
+
puts '-' * 80
|
234
|
+
puts 'MSG:'
|
235
|
+
puts stdout_message
|
236
|
+
puts '-' * 80
|
237
|
+
puts "\n\n\n"
|
238
|
+
end
|
239
|
+
# else
|
240
|
+
# print '.'
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
rescue Interrupt
|
246
|
+
puts "\nCTRL+C detected. Exiting..."
|
247
|
+
rescue StandardError => e
|
248
|
+
raise e
|
249
|
+
ensure
|
250
|
+
serial_obj.disconnect if serial_obj
|
251
|
+
end
|
252
|
+
|
253
|
+
# Supported Method Parameters::
|
254
|
+
# Meshtastic.send_text(
|
255
|
+
# serial_obj: 'required - serial_obj returned from #connect method',
|
256
|
+
# from: 'required - From ID (String or Integer) (Default: "!00000b0b")',
|
257
|
+
# to: 'optional - Destination ID (Default: "!ffffffff")',
|
258
|
+
# topic: 'optional - topic to publish to (Default: "msh/US/2/e/LongFast/1")',
|
259
|
+
# channel: 'optional - channel (Default: 6)',
|
260
|
+
# text: 'optional - Text Message (Default: SYN)',
|
261
|
+
# want_ack: 'optional - Want Acknowledgement (Default: false)',
|
262
|
+
# want_response: 'optional - Want Response (Default: false)',
|
263
|
+
# hop_limit: 'optional - Hop Limit (Default: 3)',
|
264
|
+
# on_response: 'optional - Callback on Response',
|
265
|
+
# psks: 'optional - hash of :channel_id => psk key value pairs (default: { LongFast: "AQ==" })'
|
266
|
+
# )
|
267
|
+
public_class_method def self.send_text(opts = {})
|
268
|
+
serial_obj = opts[:serial_obj]
|
269
|
+
topic = opts[:topic] ||= 'msh/US/2/e/LongFast/#'
|
270
|
+
opts[:via] = :radio
|
271
|
+
|
272
|
+
# TODO: Implement chunked message to deal with large messages
|
273
|
+
protobuf_text = Meshtastic.send_text(opts)
|
274
|
+
|
275
|
+
serial_obj.publish(topic, protobuf_text)
|
276
|
+
rescue StandardError => e
|
277
|
+
raise e
|
278
|
+
end
|
279
|
+
|
280
|
+
# Supported Method Parameters::
|
281
|
+
# serial_obj = Meshtastic.disconnect(
|
282
|
+
# serial_obj: 'required - serial_obj returned from #connect method'
|
283
|
+
# )
|
284
|
+
public_class_method def self.disconnect(opts = {})
|
285
|
+
serial_obj = opts[:serial_obj]
|
286
|
+
|
287
|
+
serial_obj.disconnect if serial_obj
|
288
|
+
nil
|
289
|
+
rescue StandardError => e
|
290
|
+
raise e
|
291
|
+
end
|
292
|
+
|
293
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
294
|
+
|
295
|
+
public_class_method def self.authors
|
296
|
+
"AUTHOR(S):
|
297
|
+
0day Inc. <support@0dayinc.com>
|
298
|
+
"
|
299
|
+
end
|
300
|
+
|
301
|
+
# Display Usage for this Module
|
302
|
+
|
303
|
+
public_class_method def self.help
|
304
|
+
puts "USAGE:
|
305
|
+
serial_obj = #{self}.connect(
|
306
|
+
host: 'optional - mqtt host (default: mqtt.meshtastic.org)',
|
307
|
+
port: 'optional - mqtt port (defaults: 1883)',
|
308
|
+
tls: 'optional - use TLS (default: false)',
|
309
|
+
username: 'optional - mqtt username (default: meshdev)',
|
310
|
+
password: 'optional - (default: large4cats)',
|
311
|
+
client_id: 'optional - client ID (default: random 4-byte hex string)',
|
312
|
+
keep_alive: 'optional - keep alive interval (default: 15)',
|
313
|
+
ack_timeout: 'optional - acknowledgement timeout (default: 30)'
|
314
|
+
)
|
315
|
+
|
316
|
+
#{self}.subscribe(
|
317
|
+
serial_obj: 'required - serial_obj object returned from #connect method',
|
318
|
+
root_topic: 'optional - root topic (default: msh)',
|
319
|
+
region: 'optional - region e.g. 'US/VA', etc (default: US)',
|
320
|
+
channel_topic: 'optional - channel ID path e.g. '2/stat/#' (default: '2/e/LongFast/#')',
|
321
|
+
psks: 'optional - hash of :channel_id => psk key value pairs (default: { LongFast: 'AQ==' })',
|
322
|
+
qos: 'optional - quality of service (default: 0)',
|
323
|
+
json: 'optional - JSON output (default: false)',
|
324
|
+
exclude: 'optional - comma-delimited string(s) to exclude in message (default: nil)',
|
325
|
+
include: 'optional - comma-delimited string(s) to include on in message (default: nil)',
|
326
|
+
gps_metadata: 'optional - include GPS metadata in output (default: false)'
|
327
|
+
)
|
328
|
+
|
329
|
+
#{self}.send_text(
|
330
|
+
serial_obj: 'required - serial_obj returned from #connect method',
|
331
|
+
from: 'required - From ID (String or Integer) (Default: \"!00000b0b\")',
|
332
|
+
to: 'optional - Destination ID (Default: \"!ffffffff\")',
|
333
|
+
topic: 'optional - topic to publish to (default: 'msh/US/2/e/LongFast/1')',
|
334
|
+
channel: 'optional - channel (Default: 6)',
|
335
|
+
text: 'optional - Text Message (Default: SYN)',
|
336
|
+
want_ack: 'optional - Want Acknowledgement (Default: false)',
|
337
|
+
want_response: 'optional - Want Response (Default: false)',
|
338
|
+
hop_limit: 'optional - Hop Limit (Default: 3)',
|
339
|
+
on_response: 'optional - Callback on Response',
|
340
|
+
psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: 'AQ==' })'
|
341
|
+
)
|
342
|
+
|
343
|
+
serial_obj = #{self}.disconnect(
|
344
|
+
serial_obj: 'required - serial_obj object returned from #connect method'
|
345
|
+
)
|
346
|
+
|
347
|
+
#{self}.authors
|
348
|
+
"
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
data/lib/meshtastic/version.rb
CHANGED
data/lib/meshtastic.rb
CHANGED
@@ -45,6 +45,7 @@ module Meshtastic
|
|
45
45
|
autoload :Portnums, 'meshtastic/portnums'
|
46
46
|
autoload :RemoteHardware, 'meshtastic/remote_hardware'
|
47
47
|
autoload :RTTTL, 'meshtastic/rtttl'
|
48
|
+
autoload :Serial, 'meshtastic/serial'
|
48
49
|
autoload :Storeforward, 'meshtastic/storeforward'
|
49
50
|
autoload :Telemetry, 'meshtastic/telemetry'
|
50
51
|
autoload :Xmodem, 'meshtastic/xmodem'
|
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.116
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 0day Inc.
|
@@ -178,19 +178,19 @@ dependencies:
|
|
178
178
|
- !ruby/object:Gem::Version
|
179
179
|
version: 1.11.3.9
|
180
180
|
- !ruby/object:Gem::Dependency
|
181
|
-
name:
|
181
|
+
name: uart
|
182
182
|
requirement: !ruby/object:Gem::Requirement
|
183
183
|
requirements:
|
184
184
|
- - '='
|
185
185
|
- !ruby/object:Gem::Version
|
186
|
-
version: 0.
|
186
|
+
version: 1.0.0
|
187
187
|
type: :runtime
|
188
188
|
prerelease: false
|
189
189
|
version_requirements: !ruby/object:Gem::Requirement
|
190
190
|
requirements:
|
191
191
|
- - '='
|
192
192
|
- !ruby/object:Gem::Version
|
193
|
-
version: 0.
|
193
|
+
version: 1.0.0
|
194
194
|
- !ruby/object:Gem::Dependency
|
195
195
|
name: yard
|
196
196
|
requirement: !ruby/object:Gem::Requirement
|
@@ -267,6 +267,7 @@ files:
|
|
267
267
|
- lib/meshtastic/remote_hardware_pb.rb
|
268
268
|
- lib/meshtastic/rtttl.rb
|
269
269
|
- lib/meshtastic/rtttl_pb.rb
|
270
|
+
- lib/meshtastic/serial.rb
|
270
271
|
- lib/meshtastic/storeforward.rb
|
271
272
|
- lib/meshtastic/storeforward_pb.rb
|
272
273
|
- lib/meshtastic/telemetry.rb
|
@@ -314,6 +315,7 @@ files:
|
|
314
315
|
- spec/lib/meshtastic/remote_hardware_spec.rb
|
315
316
|
- spec/lib/meshtastic/rtttl_pb_spec.rb
|
316
317
|
- spec/lib/meshtastic/rtttl_spec.rb
|
318
|
+
- spec/lib/meshtastic/serial_spec.rb
|
317
319
|
- spec/lib/meshtastic/storeforward_pb_spec.rb
|
318
320
|
- spec/lib/meshtastic/storeforward_spec.rb
|
319
321
|
- spec/lib/meshtastic/telemetry_pb_spec.rb
|