-
Notifications
You must be signed in to change notification settings - Fork 1
/
meshtastic-mqtt-client.py
668 lines (655 loc) · 38.4 KB
/
meshtastic-mqtt-client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
#!/bin/python
# Las dependencias:
from ast import Not
#from types import NoneType
import yaml
#import json for future version
import time
import os
import sys
import argparse
import paho.mqtt.client as mqtt
import logging
#import logging.handlers as handlers
import PySimpleGUI as sg
import sqlite3 as sl
#import sqlalchemy for future version
import random
#from queue import Queue
#from logging import NullHandler
#PROTOBUF dependencies copied from joshpirihi/meshtastic-mqtt
import portnums_pb2 as portnums_pb2
from portnums_pb2 import ENVIRONMENTAL_MEASUREMENT_APP, POSITION_APP
import mesh_pb2 as mesh_pb2
import mqtt_pb2 as mqtt_pb2
import environmental_measurement_pb2 as environmental_measurement_pb2
#from google.protobuf.json_format import MessageToJson
# First we define the GUI
def generate_main_window(menu_def):
sg.theme('Reddit')
sg.set_options(element_padding=(1, 1))
layout = [
[sg.Menu(menu_def, tearoff=False, pad=(200, 1))],
[sg.Text('Meshtastic MQTT Client')],
[sg.Output(size=(80,25),key='-OUTPUT_RADIO-')],
[sg.Button('Send Message to every node ',key='-SEND-'), sg.InputText(key='-MSGINPUT-'),
sg.Checkbox('Want Response',key='-WNTRSPTF-')],
[sg.Button('Send Message to one node ', key='-SEND_ONE_NODE-'),sg.Text('message'),sg.InputText(size=(20,1),key='-NODE_MSG-'),
sg.Text('Node'),sg.Combo(["Please update node list."],size=(10,1),key='-NODELIST-')],
[sg.Button('Connect to MQTT Server ', key='-CONNECT-')], [sg.Button('Disconnect from MQTT Server', key='-DISCONNECT-')]]
return sg.Window('Meshtastic MQTT Client', layout, finalize=True, grab_anywhere=True)
def generate_gps_window(menu_def):
sg.theme('Reddit')
sg.set_options(element_padding=(1, 1))
layout = [
[sg.Menu(menu_def, tearoff=False, pad=(200, 1))],
[sg.Text('GPS Client Positions')],
[sg.Output(size=(80,25),key='-POSITIONS-')],
[sg.Button('Export Positions',key='-EXPORT-')]]
return sg.Window('GPS Client Positions', layout, finalize=True, grab_anywhere=True)
def generate_properties_window(menu_def):
sg.theme('Reddit')
sg.set_options(element_padding=(1, 1))
layout = [
[sg.Menu(menu_def, tearoff=False, pad=(200, 1))],
[sg.Text('Configuration Properties',font = ("Arial", 16))],
[sg.Text("MQTT", font = ("Arial", 16))],
[sg.Text("Gateway IP: " + mqtt_ip, font = ("Arial", 12))],
[sg.Text("MQTT User: " + mqtt_user, font = ("Arial", 12))],
[sg.Text("MQTT Password: " + mqtt_pass, font = ("Arial", 12))],
[sg.Text("Your Meshtastic Network",font = ("Arial", 16))],
[sg.Text("Meshtastic Channel Name: " + mesh_channel_id, font = ("Arial", 12))],
[sg.Text("Meshtastic Gateway ID: " + mesh_gateway_id, font = ("Arial", 12))],
[sg.Text("Your Device Network IDs", font = ("Arial", 16))],
[sg.Text("Your client ID: " + mesh_client_id, font = ("Arial", 12))],
[sg.Text("Your full ID: " + mesh_client_full_id, font = ("Arial", 12))],
[sg.Text("Your MAC Address: " + mesh_client_macaddr, font = ("Arial", 12))],
[sg.Text("Your Device Names", font = ("Arial", 16))],
[sg.Text("Device Long Name: " + mesh_client_long_name, font = ("Arial", 12))],
[sg.Text("Short Name: " + mesh_client_short_name, font = ("Arial", 12))]]
return sg.Window('Properties Window', layout, finalize=True, keep_on_top=True, grab_anywhere=True)
def generate_nodes_window(menu_def,node_list):
try:
sg.theme('Reddit')
sg.set_options(element_padding=(1, 1))
headings_list = ["Node Wireless ID", "Node Full ID", "Node Long Name", "Node Short Name", "MacAddress"]
layout = [
[sg.Menu(menu_def, tearoff=False, pad=(200, 1))],
[sg.Text('This is a list of detected nodes and their info.', font = ("Arial", 16))],
[sg.Table(values=node_list , headings = headings_list, max_col_width=35, auto_size_columns=True)]]
return sg.Window('List of Known Nodes', layout, finalize=True, keep_on_top=True, grab_anywhere=True)
except Exception as exception:
logging.critical(f"There was a problem generating the nodes window. The exception is {exception}")
def generate_about_window(menu_def):
try:
sg.theme('Reddit')
sg.set_options(element_padding=(1, 1))
headings_list = ["Node Wireless ID", "Node Full ID", "Node Long Name", "Node Short Name", "MacAddress"]
layout = [
[sg.Menu(menu_def, tearoff=False, pad=(200, 1))],
[sg.Text('This is a list of detected nodes and their info.', font = ("Arial", 16))]]
return sg.Window('List of Known Nodes', layout, finalize=True, keep_on_top=True, grab_anywhere=True)
except Exception as exception:
logging.critical(f"There was a problem generating the nodes window. The exception is {exception}")
def generate_help_window(menu_def):
try:
sg.theme('Reddit')
sg.set_options(element_padding=(1, 1))
layout = [
[sg.Menu(menu_def, tearoff=False, pad=(200, 1))],
[sg.Text('This program is pretty simple. It connects via MQTT to the server configured on the config.yaml file (if you are not sure about it, you can the configuration by clicking File, Properties). You can send messages by clicking on Send message to every node, after inputting text on the box next to it. You can also send your node information to your network by going to File, Send node info and check all detected nodes by going to Nodes, List Nodes. In order for the program to work, at least ONE node must be connected to the MQTT server (with MQTT uplink and downlink enabled and with NO encryption) and the Meshtastic network! ', font = ("Arial", 12))]]
return sg.Window('Help Window', layout, finalize=True, keep_on_top=True, grab_anywhere=True)
except Exception as exception:
logging.critical(f"There was a problem generating the Help window. The exception is {exception}")
def load_config():
try:
config_dir = os.path.dirname(os.path.realpath(__file__))
config_file = os.path.join(config_dir, 'config/config.yaml')
configuration = open(config_file, 'r')
logging.info(f"Opening the following file: {config_file}")
config = yaml.safe_load(configuration)
return config
except Exception as exception:
logging.critical(f"There's an error accessing your config.yml file, the error is the following: {exception}")
print("There's no config yaml file in the program's folder, please check the logs.")
sys.exit()
# Connect to MQTT funcionts - this I took directly from the paho docs.
def on_connect(client, userdata, flags, rc):
if int(rc) == 0:
logging.info(f"Succesfully connected to the server.The rc is {rc}.")
client.subscribe("$SYS/#")
elif int(rc) == 1:
logging.info(f"The connection was refused due to an incorrect protocol version.The rc is {rc}.")
print("The connection was refused due to an incorrect protocol version.")
elif int(rc) == 2:
logging.info(f"The connection was refused due to an incorrect client identifier. The rc is {rc}.")
print("The connection was refused due to an incorrect client identifier.")
elif int(rc) == 3:
logging.info(f"The connection was refused, the server is unavailable or there is mistake in the IP address.The rc is {rc}.")
print("The connection was refused, the server is unavailable or there is mistake in the IP address.The rc is {rc}.")
elif int(rc) == 4:
logging.info(f"The connection was refused due to lack of authorization (wrong user or password).The rc is {rc}.")
print("The connection was refused due to lack of authorization (wrong user or password).")
elif int(rc) == 5:
logging.info(f"The connection was refused due to lack of authorization (wrong user or password).The rc is {rc}.")
print("The connection was refused due to lack of authorization (wrong user or password).")
def on_message(client, userdata, msg):
if "$SYS/" in msg.topic: # I filter out the $SYS internal mqtt topic
pass
else:
logging.debug("You have received the following message:" +str(msg.payload) + " on topic: " + str(msg.topic))
decode_message(msg)
def decode_message(msg):
try:
full_message = msg.payload
#I add service envelope decoding from protobuf
se = mqtt_pb2.ServiceEnvelope()
se.ParseFromString(full_message)
#print(dir(se.ParseFromString(full_message)))
decoded_message = se.packet
#I use portnums to detect the type of message (text, position or nodeinfo)
if str(getattr(decoded_message, "encrypted")) != "b''":
print("Encrypted message received.")
logging.info(getattr(decoded_message, "encrypted"))
logging.info("Encrypted message received, please check the settings on your Meshtastic uplink device.")
return None
else:
if decoded_message.decoded.portnum == portnums_pb2.TEXT_MESSAGE_APP:
text_message = decoded_message.decoded.payload.decode("utf-8")
mesh_node_id = str(getattr(decoded_message, "from"))
if mesh_node_id == mesh_client_id:
pass
else:
node_id=check_id(mesh_node_id)
node_full_id, node_long_name, node_short_name = get_name(node_id)
if node_long_name[0] is not None:
print(node_long_name[0] + ": " + text_message)
else:
print(mesh_node_id + ": " + text_message)
main_window.Refresh()
save_text_message(text_message, mesh_node_id)
elif decoded_message.decoded.portnum == portnums_pb2.POSITION_APP:
mesh_node_id = str(getattr(decoded_message, "from"))
if mesh_node_id == mesh_client_id:
pass
else:
decoded_packet = mesh_pb2.Position()
decoded_packet.ParseFromString(decoded_message.decoded.payload)
battery_level = decoded_packet.battery_level
lat = decoded_packet.latitude_i * 1e-7
lon = decoded_packet.longitude_i * 1e-7
altitude= decoded_packet.altitude
node_time = decoded_packet.time
save_position(lat, lon, altitude, node_time, battery_level, mesh_node_id)
elif decoded_message.decoded.portnum == portnums_pb2.NODEINFO_APP:
#Work in progress
decoded_packet=mesh_pb2.User()
decoded_packet.ParseFromString(decoded_message.decoded.payload)
mesh_node_id = str(getattr(decoded_message, "from"))
if mesh_node_id == mesh_client_id:
pass
else:
node_id=check_id(mesh_node_id)
node_full_id = str(getattr(decoded_packet, "id"))
node_long_name = str(getattr(decoded_packet,"long_name"))
node_short_name = str(getattr(decoded_packet,"short_name"))
node_macaddr = getattr(decoded_packet,"macaddr") #The macaddress is a Hex in bytes, so I have use the .hex() method and then make it a string to save it
node_macaddr = str(node_macaddr.hex())
node_macaddr = node_macaddr.upper() #I make it all uppercase so it looks nicer in the DB
print("Node information received for node:" + node_long_name)
main_window.Refresh()
save_node_info(mesh_node_id,node_full_id,node_long_name,node_short_name,node_macaddr)
else:
mesh_node_id = str(getattr(decoded_message, "from"))
save_full_message(full_message,mesh_node_id)
except Exception as exception:
logging.critical(f'There was an error decoding your received message. You get the following error: {exception} ')
def encode_message(encoded_message,mesh_channel_id,mesh_gateway_id,mesh_client_id):
try:
packet = mesh_pb2.MeshPacket()
setattr(packet,"from",int(mesh_client_id))
#setattr(packet,"to",random.getrandbits(32))
setattr(packet, "to",4294967295)
setattr(packet,"id",random.getrandbits(32))
setattr(packet,"rx_time",1658889528)
setattr(packet,"hop_limit",3)
packet.decoded.CopyFrom(encoded_message)
mesh_se = mqtt_pb2.ServiceEnvelope()
mesh_se.channel_id = mesh_channel_id
mesh_se.gateway_id = mesh_client_id
mesh_se.packet.CopyFrom(packet)
mesh_se=mesh_se.SerializeToString()
return mesh_se
except Exception as exception:
logging.critical(f'There was an error encoding you message. You get the following error: {exception} ')
def encode_text_message(message):
try:
decoded_message = bytes(message,"utf-8")
encoded_message = mesh_pb2.Data()
encoded_message.portnum = portnums_pb2.TEXT_MESSAGE_APP
encoded_message.payload = decoded_message
return encoded_message
except Exception as exception:
logging.critical(f'There was an error encoding the text portion of you message. You get the following error: {exception} ')
def encode_info_message(user_short_name,user_long_name, user_id, user_macaddr):
try:
decoded_user_id = bytes(user_id,"utf-8")
decoded_user_long = bytes(user_long_name,"utf-8")
decoded_user_short = bytes(user_short_name,"utf-8")
decoded_user_hw_model = 255
user_macaddr = user_macaddr.lower()
decoded_user_macaddr = bytes.fromhex(user_macaddr)
user_payload = mesh_pb2.User()
setattr(user_payload, "id",decoded_user_id)
setattr(user_payload,"long_name",decoded_user_long )
setattr(user_payload,"short_name",decoded_user_short)
setattr(user_payload,"macaddr",decoded_user_macaddr)
setattr(user_payload,"hw_model",decoded_user_hw_model)
user_payload = user_payload.SerializeToString()
encoded_message = mesh_pb2.Data()
encoded_message.portnum = portnums_pb2.NODEINFO_APP
encoded_message.payload = user_payload
return encoded_message
except Exception as exception:
logging.critical(f'There was an error encoding the nodeinfo portion of your message. You get the following error: {exception} ')
def encode_send_to_node(node_name, encoded_message):
try:
node_mesh_id = get_id_from_name(node_name)
packet = mesh_pb2.MeshPacket()
setattr(packet,"from",int(mesh_client_id))
#setattr(packet,"to",random.getrandbits(32))
setattr(packet, "to",int(node_mesh_id[0]))
setattr(packet,"id",random.getrandbits(32))
setattr(packet,"rx_time",1658889528)
setattr(packet,"hop_limit",3)
packet.decoded.CopyFrom(encoded_message)
mesh_se = mqtt_pb2.ServiceEnvelope()
mesh_se.channel_id = mesh_channel_id
mesh_se.gateway_id = mesh_client_id
mesh_se.packet.CopyFrom(packet)
mesh_se=mesh_se.SerializeToString()
return mesh_se
except Exception as exception:
logging.critical(f'There was an error encoding you message. You get the following error: {exception} ')
def get_id_from_name(node_name):
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f'''SELECT node_mesh_id FROM NODE WHERE long_name = '{node_name}';''')
node_mesh_id = cur.fetchone()
return node_mesh_id
def check_id(mesh_node_id):
try:
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f'''SELECT COUNT(node_id) FROM NODE WHERE node_mesh_id = '{str(mesh_node_id)}';''')
sql_id_results = int(cur.fetchone()[0] or 0)
if (sql_id_results == 0):
logging.info("The node mesh id was not registered on the database. We will create a new record.")
sqlite_query = f'''INSERT INTO NODE (node_mesh_id) VALUES ('{str(mesh_node_id)}');'''
cur.execute(str(sqlite_query))
con.commit()
logging.info(f"{str(mesh_node_id)} has been inserted in the NODE table.")
cur.execute(f'''SELECT node_id FROM NODE WHERE node_mesh_id = '{str(mesh_node_id)}';''')
node_id = str(cur.fetchone()[0])
else:
cur.execute(f'''SELECT node_id FROM NODE WHERE node_mesh_id = '{mesh_node_id}';''')
node_id = int(cur.fetchone()[0])
return node_id
except Exception as exception:
logging.critical(f' There was a problem getting your node id. The exception is: {exception} ')
def get_name(node_id):
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f'''SELECT node_full_id FROM NODE WHERE node_id = '{node_id}';''')
node_full_id = cur.fetchone()
cur.execute(f'''SELECT long_name FROM NODE WHERE node_id = '{node_id}';''')
node_long_name = cur.fetchone()
cur.execute(f'''SELECT short_name FROM NODE WHERE node_id = '{node_id}';''')
node_short_name = cur.fetchone()
return node_full_id, node_long_name, node_short_name
def get_node_list():
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f'''SELECT node_mesh_id, node_full_id, long_name, short_name, macaddr FROM NODE;''')
node_list= cur.fetchall()
return node_list
def get_positions():
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f'''SELECT lat, long, altitude, battery FROM POSITION;''')
position_list= cur.fetchall()
return position_list
def get_last_position(node_id):
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f'''SELECT lat, long, altitude, battery FROM POSITION WHERE node_id = {node_id} order by Timestamp desc;''')
position_list= cur.fetchone()
return position_list
def get_text_messages():
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f'''SELECT * FROM TEXTMESSAGE;''')
text_message_list= cur.fetchall()
return text_message_list
def get_full_messages():
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f'''SELECT * FROM MESSAGE;''')
full_message_list= cur.fetchall()
return full_message_list
def save_full_message(full_message,mesh_node_id):
try:
node_id = check_id(mesh_node_id)
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
sqlite_query = 'INSERT INTO MESSAGE (node_id,fullmessage) VALUES (?,?);'
data_tuple = (node_id,str(full_message))
cur.execute(str(sqlite_query), data_tuple)
con.commit()
except Exception as exception:
logging.critical(f' There was a problem saving the full message to your database. The exception is: {exception} ')
def save_text_message(text_message, mesh_node_id):
try:
node_id = check_id(mesh_node_id)
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f''' INSERT INTO TEXTMESSAGE (node_id,textmessage) VALUES ('{node_id}','{text_message}');''')
con.commit()
except Exception as exception:
logging.critical(f'There was an error saving your text message. The exception is: {exception} ')
def save_position(lat,lon,altitude,node_time,battery_level,mesh_node_id):
try:
node_id = check_id(mesh_node_id)
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f''' INSERT INTO POSITION (node_id,lat,long,altitude,node_time,battery) VALUES ('{node_id}','{lat}','{lon}', '{altitude}','{node_time}','{battery_level}');''')
con.commit()
except Exception as exception:
logging.critical(f'There was an error saving your position, the exception is: {exception} ')
def save_node_info(mesh_node_id,node_full_id,node_long_name,node_short_name,node_macaddr):
try:
node_id = check_id(mesh_node_id)
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(f''' UPDATE NODE SET node_full_id = '{node_full_id}', long_name = '{node_long_name}', short_name = '{node_short_name}', macaddr = '{node_macaddr}' WHERE node_id = {node_id} ;''')
con.commit()
except Exception as exception:
logging.critical(f'There was an error saving node info. The exception is: {exception} ')
def on_publish(client, userdata, mid):
logging.debug("the published message status:" + str(int(userdata or 0)) + " (0 means published)")
logging.debug("the published message id is:" + str(mid))
def mqtt_publish_dict(mqtt_dict, client, mqtt_ip):
for x, y in mqtt_dict.items():
client.connect(mqtt_ip, 1883, 60)
client.publish(str(x), str(y), qos=1, retain=True).wait_for_publish
logging.debug("You have sent the following payload: " + str(y))
logging.debug("To the following topic: " + str(x))
logging.debug("On the server with IP: " + mqtt_ip)
def mqtt_publish_message(message,mqtt_topic,client,mqtt_ip,mesh_client_id):
try:
client.publish(str(mqtt_topic), message, qos=1, retain=False).wait_for_publish
logging.debug("You have sent the following payload: " + str(message))
logging.debug("To the following topic: " + str(mqtt_topic))
logging.debug("On the server with IP: " + mqtt_ip)
except Exception as exception:
logging.critical(f'There was an error sending you message. You get the following error: {exception} ')
def get_mqtt_config(config):
try:
mqtt_dict = config['MQTT']
mqtt_ip = mqtt_dict['GATEWAY_ip']
mqtt_user = mqtt_dict['MQTT_USER']
mqtt_pass = mqtt_dict['MQTT_PW']
period = mqtt_dict['TIME_PERIOD']
logging.debug("This is your mqtt dictionary:" + str(mqtt_dict))
except Exception as exception:
logging.critical(f'Your YAML is missing something in the MQTT section. You get the following error: {exception} ')
return mqtt_ip, mqtt_user, mqtt_pass, period
def get_meshtastic_config(config):
try:
meshtastic_dict = dict(config['MESHTASTIC'])
mesh_channel_id = str(meshtastic_dict['CHANNEL_ID'])
logging.debug(f"This is your channel id: {str(mesh_channel_id)}")
mesh_client_id = str(meshtastic_dict['CLIENT_ID'])
logging.debug(f"This is your client id: {str(mesh_client_id)}")
mesh_gateway_id = str(meshtastic_dict['GATEWAY_ID'])
logging.debug(f"This is your gateway id: {str(mesh_gateway_id)}")
mesh_client_full_id = str(meshtastic_dict['CLIENT_FULL_ID'])
logging.debug(f"This is your node full id: {str(mesh_client_full_id)}")
mesh_client_long_name= str(meshtastic_dict['LONG_NAME'])
logging.debug(f"This is your node Long Name: {str(mesh_client_long_name)}")
mesh_client_short_name= str(meshtastic_dict['SHORT_NAME'])
logging.debug(f"This is your node short name: {str(mesh_client_short_name)}")
mesh_client_macaddr= str(meshtastic_dict['MACADDR'])
logging.debug(f"This is your node macaddr: {str(mesh_client_macaddr)}")
common_ports_dict = meshtastic_dict['COMMON_PORTS']
extra_ports_dict = {}
try:
if "EXTRA_PORTS" in meshtastic_dict.keys():
extra_ports_list = meshtastic_dict['EXTRA_PORTS']
for port in extra_ports_list:
port_number = port['PORT_NUMBER']
port_name = port['PORT_NAME']
port_ha_class = port['HA_CLASS']
extra_ports_dict[port_number]= {"port_number": port_number, "port_name": port_name, "port_ha_class": port_ha_class}
ports_count = len(extra_ports_dict)
logging.info(f"You have {ports_count} extra ports configured.")
logging.debug(f"This is the configuration information they have: {str(extra_ports_dict)}")
else:
logging.debug("You have no extra ports configured.")
except Exception as exception:
logging.critical(f'Your YAML is missing something in the ports section. You get the following error: {exception} ')
return mesh_channel_id, mesh_client_id, mesh_gateway_id,mesh_client_full_id ,mesh_client_long_name, mesh_client_short_name,mesh_client_macaddr, common_ports_dict, extra_ports_dict
except Exception as exception:
logging.critical(f'Your YAML is missing something in the Meshtastic section. You get the following error: {exception}')
def gateway_subscribe(client, mqtt_ip, common_ports_dict, extra_ports_dict, mesh_channel_id, mesh_gateway_id):
try:
#for port in common_ports_list:
mesh_mqtt_topic = "msh/1/c/" + str(mesh_channel_id) + "/#" #+ "/" + str(mesh_gateway_id) + "/" + port[0]
client.subscribe(mesh_mqtt_topic)
logging.info(f"You are now subscribed to {mesh_mqtt_topic}.")
except Exception as exception:
logging.error(f"There is an error in your power sensor collection. The error is the following: {exception}")
def client_db():
try:
con = sl.connect('config/meshtastic-mqtt-client.db')
cur = con.cursor()
cur.execute(''' SELECT count(name) FROM sqlite_master WHERE type='table' and name ='NODE' ''')
if cur.fetchone()[0]==1 :
print('NODE table exists.')
else:
cur.execute("""CREATE TABLE NODE(node_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, node_mesh_id INTEGER, node_full_id VARCHAR(255), long_name VARCHAR(255), short_name VARCHAR(255), macaddr VARCHAR (255), Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP);""")
print("NODE table created.")
cur.execute(''' SELECT count(name) FROM sqlite_master WHERE type='table' and name ='MESSAGE' ''')
if cur.fetchone()[0]==1 :
print('MESSAGE table exists.')
else:
cur.execute("""CREATE TABLE MESSAGE(message_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, node_id INTEGER, fullmessage VARCHAR(255),Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (node_id) REFERENCES NODE(node_id));""")
print("MESSAGE table created.")
cur.execute(''' SELECT count(name) FROM sqlite_master WHERE type='table' and name ='POSITION' ''')
if cur.fetchone()[0]==1 :
print('POSITION table exists.')
else:
cur.execute("""CREATE TABLE POSITION(position_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, node_id INTEGER, lat VARCHAR(255),long VARCHAR(255),altitude VARCHAR(255),node_time DATETIME, battery VARCHAR(255),Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (node_id) REFERENCES NODE(node_id));""")
print("POSITION table created.")
cur.execute(''' SELECT count(name) FROM sqlite_master WHERE type='table' and name ='TEXTMESSAGE' ''')
if cur.fetchone()[0]==1 :
print('TEXTMESSAGE table exists.')
else:
cur.execute("""CREATE TABLE TEXTMESSAGE(textmessage_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, node_id INTEGER, textmessage VARCHAR(255),Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (node_id) REFERENCES NODE(node_id));""")
print("TEXTMESSAGE table created.")
cur.execute(''' SELECT count(name) FROM sqlite_master WHERE type='table' and name ='GATEWAY' ''')
if cur.fetchone()[0]==1 :
print('GATEWAY table exists.')
else:
cur.execute("""CREATE TABLE GATEWAY(gateway_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, node_id INTEGER, node_mesh_id VARCHAR(255),Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (node_id) REFERENCES NODE(node_id));""")
print("GATEWAY table created.")
save_node_info(mesh_client_id,mesh_client_full_id,mesh_client_long_name,mesh_client_short_name,mesh_client_macaddr)
except Exception as exception:
logging.critical(f"There was a problem starting you database. The exception is {exception}")
#def sql_db(): for future cases with external connection
def main(): # Here i have the main program
"""Main function to run window in a loop."""
try:
#Here I load yaml configuration files and create variables for the elements in the yaml
global config
config = load_config()
except Exception as exception:
logging.critical(f"Please check your YAML, it might be missing somethings. The exception is {exception}")
#Create mqtt global variables
global mqtt_ip, mqtt_user, mqtt_pass, period
try:
mqtt_ip, mqtt_user, mqtt_pass, period = get_mqtt_config(config)
except Exception as exception:
logging.critical(f"Please check your YAML, it might be missing some parts in the MQTT configuration. The exception is {exception}")
try:
global mesh_channel_id, mesh_client_id, mesh_gateway_id,mesh_client_full_id ,mesh_client_long_name, mesh_client_short_name,mesh_client_macaddr, common_ports_dict, extra_ports_dict
mesh_channel_id, mesh_client_id, mesh_gateway_id,mesh_client_full_id ,mesh_client_long_name, mesh_client_short_name,mesh_client_macaddr, common_ports_dict, extra_ports_dict = get_meshtastic_config(config)
except Exception as exception:
logging.critical(f'Your YAML is missing something in the Meshtastic section. You get the following error: {exception} ')
# I define the ports list with all the meshtastic ports
try:
client_db()
except Exception as exception:
logging.critical(f"There was an error connecting to the Database. The exception is {exception}")
global main_window
# I define my window menu and my first window
menu_def = ['File', ['Properties','Change Language - not yet implemented', 'Send node info', 'Exit']],['Nodes', ['Gateway Information','List Nodes']],['Messages',['See Crypto Key - Not yet implemented', 'Export Message History - not yet implemented']],['GPS',['Open GPS Tab - soon', 'Export GPS Locations']], ['&Help',['&Help', 'About...']]
main_window = generate_main_window(menu_def)
main_window.move(main_window.current_location()[0]-350, main_window.current_location()[1]+0)
#output_window = main_window
#I first copy methods according to Paho MQTT documentation, then I set the mqtt user and password (which is why I needed first the get_mqtt method, then I start the connection and finnally I create the network loop.)
try:
client = mqtt.Client("meshtastic-mqtt-user")
client.on_message = on_message
client.on_publish= on_publish
client.username_pw_set(mqtt_user, password=mqtt_pass)
client.enable_logger(logger=logger)
client.on_connect = on_connect
client.connect(mqtt_ip, 1883, 60)
print("Connecting")
client.loop_start()
print("Succesfully connected to the MQTT Broker.")
except Exception as exception:
logging.critical(f"There seems to be a problem connecting to the mqtt server. The exception is {exception}")
try:
time.sleep(1)
#I wait 1 second and subscribe to the mqtt_broker's gateway topic from my Meshtastic Gateway
gateway_subscribe(client, mqtt_ip, common_ports_dict, extra_ports_dict, mesh_channel_id, mesh_gateway_id)
except Exception as exception:
logging.critical(f"There seems to be a problem subscribing to the topics. The exception is {exception}")
try:
print("Sending Node info to all other clients.")
encoded_message = encode_info_message(mesh_client_short_name,mesh_client_long_name, mesh_client_full_id, mesh_client_macaddr)
mesh_se = encode_message(encoded_message,mesh_channel_id,mesh_gateway_id,mesh_client_id)
mqtt_topic = "msh/1/c/" + mesh_channel_id +"/" + mesh_gateway_id
mqtt_publish_message(mesh_se,mqtt_topic,client,mqtt_ip,mesh_client_id)
except Exception as exception:
logging.critical(f"There seems to be a problem sending your Node Information. The exception is {exception}")
while(True):
window, event, values = sg.read_all_windows()
if sg.WIN_CLOSED:
main_window.close()
quit()
#else:
#window, event, values = sg.read_all_windows()
if window == sg.WIN_CLOSED:
force_exit = True
client.disconnect
client.loop_stop()
window.close()
quit()
elif event == 'Exit':
force_exit = True
client.disconnect
client.loop_stop()
window.close()
quit()
elif event == sg.WIN_CLOSED:
window.close()
if event == 'Open GPS Tab':
#If the Open GPS Tab option is clicked, I generate the GPS Window
gps_window = generate_gps_window(menu_def)
#And put it nice and tidy on the right of the main window
gps_window.move(main_window.current_location()[0]+680, main_window.current_location()[1]+0)
if event == '-DISCONNECT-':
client.disconnect
client.loop_stop()
print("Disconnected from MQTT Server.")
logging.info("Manually disconnected from MQTT Server.")
if event == '-CONNECT-':
try:
print("Connecting")
client.connect(mqtt_ip, 1883, 60)
client.loop_start()
gateway_subscribe(client, mqtt_ip, common_ports_dict, extra_ports_dict, mesh_channel_id, mesh_gateway_id)
print("Succesfully connected to the MQTT Broker.")
except Exception as exception:
logging.critical(f"There seems to be a problem connecting to the mqtt server. The exception is {exception}")
elif event == '-SEND-': #if user clicks send message take input and send to radio
try:
message=str(values['-MSGINPUT-'])
mqtt_topic = "msh/1/c/" + mesh_channel_id +"/" + mesh_gateway_id
print(mesh_client_long_name + "(You): " + message)
encoded_message = encode_text_message(message)
mesh_se = encode_message(encoded_message,mesh_channel_id,mesh_gateway_id,mesh_client_id)
mqtt_publish_message(mesh_se,mqtt_topic,client,mqtt_ip,mesh_client_id)
except Exception as exception:
sg.popup(f'There was an error sending your message. The exception is the following: {exception}')
elif event == 'Send node info':
try:
print("Sending Node info to all other clients.")
encoded_message = encode_info_message(mesh_client_short_name,mesh_client_long_name, mesh_client_full_id, mesh_client_macaddr)
mesh_se = encode_message(encoded_message,mesh_channel_id,mesh_gateway_id,mesh_client_id)
mqtt_topic = "msh/1/c/" + mesh_channel_id +"/" + mesh_gateway_id
mqtt_publish_message(mesh_se,mqtt_topic,client,mqtt_ip,mesh_client_id)
except Exception as exception:
logging.critical(f"There seems to be a problem sending your Node Information. The exception is {exception}")
elif event == 'Properties':
#If the properties option is clicked, I generate the properties window
generate_properties_window(menu_def)
elif event == 'List Nodes':
nodes_list = get_node_list()
node_name_list=[]
for i in nodes_list:
node_name=i[2]
node_name_list.append(node_name)
#title_list = [i["name"] for i in file[item]]
window['-NODELIST-'].update(values=node_name_list )
generate_nodes_window(menu_def, nodes_list)
elif event == '-SEND_ONE_NODE-':
message = values['-NODE_MSG-']
node_name = values['-NODELIST-']
encoded_message = encode_text_message(message)
mesh_se = encode_send_to_node(node_name,encoded_message)
mqtt_publish_message(mesh_se,mqtt_topic,client,mqtt_ip,mesh_client_id)
elif event == 'About..':
generate_help_window(menu_def)
#We define some arguments to be parsed as well as help messages and description for the script.
parser = argparse.ArgumentParser(description='This is a simple python GUI that connects to an MQTT server to send and receive messages sent by a Meshtastic MQTT Gateway.')
parser.add_argument('-i', action='store_true', help='Run once to only create the entities in your MQTT broker (and see them in home assistant), in a silent initialization.')
parser.add_argument('-DEBUG', action='store_true', help='Add Debug messages to log.')
args = parser.parse_args()
#We define the logic and place where we're gonna log things
log_dir = os.path.dirname(os.path.realpath(__file__))
log_fname = os.path.join(log_dir, 'config/meshtastic-mqtt-client.log') #I define a relative path for the log to be saved on the same folder as my py
formatter = logging.Formatter("[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s")
logger = logging.getLogger() # I define format and instantiate first logger
#fh = handlers.RotatingFileHandler(log_fname, mode='w', maxBytes=100000, backupCount=3)
#fh.setFormatter(formatter)
#logger.addHandler(fh)
if getattr(args,'i'):
logging.info("Started in silent iniatilization mode, so stopping now.")
logging.info("Initialization complete.")
# client.disconnect
# client.loop_stop()
quit()
if getattr(args,'DEBUG'):
logger.setLevel(logging.DEBUG) #I create an option to run the program in debug mode and receive more information on the logs
else:
logger.setLevel(logging.INFO)
if getattr(args,'i'):
logging.info("Running with -i in silent initialization mode. No GUI will open.")
if getattr(args,'DEBUG'):
logging.info("Running with -DEBUG in DEBUG log mode.")
if __name__== '__main__':
main()