-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmain.py
1629 lines (1274 loc) · 65.4 KB
/
main.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
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import json
from tkinter import ttk
import paho.mqtt.client as mqtt
import configparser
import os
from tkinter import *
from tkinter import filedialog as fd
from tkinter.font import BOLD
from tkinter.ttk import Combobox, Notebook
from tktooltip import ToolTip
import http_requests
import generate_certs
import themes
import event_clicks
import plots
import topics
import terminal
#these are used to log in
account_type = ''
api_key = ''
client_cert = ''
priv_key = ''
data_to_list_api = ['']
data_to_list_client = ['']
data_to_list_key = ['']
subscribed_topics_list = []
list_to_string = []
tab3_topic = [] #list to keep track of topics in Treeview in Tab3
client_id = ''
mqtt_topic_prefix = ''
mqtt_endpoint = ''
topic = None
http_create = None
http_get = None
certs_flag = None
client_flag = None
first_start_flag = 0
first_start_flag2 = 0
ACC_URL = 'https://api.nrfcloud.com/v1/account'
DEV_URL = 'https://api.nrfcloud.com/v1/devices'
PORT = 8883
KEEP_ALIVE = 30
myGrey = '#D9D9D9' #even lighter grey
lighter_grey = '#D3D3D3' #this is darker than myGrey
light_grey = '#ECEFF1'
middle_grey = '#768692'
dark_grey = '#333F48'
nordic_blue = '#00A9CE'
nordic_blueslate = '#0033A0'
nordic_lake = '#0077C8'
left_widgets_color = '#6a8c99'
dropdown_menu_bg = '#d6d6d6'
tooltip_bg = '#AEDAEB'
error_red_font_color = '#e30202'
main_logoff_color = '#505f63'
button_press_color = '#9ed3e8'
terminal_input_bg = '#e0dede'
listbox_select_bg = '#737c7d'
tab2_tooltip_bg = '#AEDAEB'
#universal font and background
myFont = 'Arial'
myBg = dark_grey
login_config = configparser.ConfigParser() #instantiate config parser for login info
topic_config = configparser.ConfigParser(allow_no_value=True) #config parser for topics
#Tkinter Shadow Functions
#whenever input boxes are modified, go to event_clicks to do work
def tab2_on_entry_click_left(event):
event_clicks.tab2_remove_shadow_text_left(tab2_topic_input)
def tab2_entry_focus_out_left(event):
event_clicks.tab2_insert_shadow_text_left(tab2_topic_input)
def tab2_on_entry_click_right(event):
event_clicks.tab2_remove_shadow_text_right(tab2_msg_input)
def tab2_entry_focus_out_right(event):
event_clicks.tab2_insert_shadow_text_right(tab2_msg_input)
def tab1_on_entry_click(event):
event_clicks.tab1_remove_shadow_text(tab1_sub_to_topic)
def tab1_entry_focus_out(event):
event_clicks.tab1_insert_shadow_text(tab1_sub_to_topic)
#interactive events customizations
def button_config(button):
button.configure(activeforeground='white', activebackground=button_press_color)
button.bind("<Enter>", button_hover)
button.bind("<Leave>", button_hover_leave)
def button_hover_leave(event): #go back to original color
button = event.widget
button['bg'] = orig_color
def button_hover(event): #change color of button when hovered over with mouse
global orig_color
button = event.widget
orig_color = button.cget("background") #grab button's original color
button['bg'] = "grey"
def enter_login_press(event): #for login screen
enter_login() #pass onto function as if the 'Enter' button was clicked
def terminal_enter_event(e):
terminal.terminal_enter()
#MQTT Callbacks
def on_log(client, userdata, level, buf):
global log_msg
terminal.terminal_print(buf)
log_msg = buf
def on_publish(client, userdata, mid):
#MQTT published message callback
terminal.terminal_print(str(mid))
def on_message(client, userdata, msg):
#MQTT message receive callback
#neatly organize the messages and place them in the messages tab and retain them
curr_msg_topic = msg.topic
try:
decoded_msg = msg.payload.decode('utf-8')
except Exception as e:
print('Cannot decode message: %s' % msg, e)
else:
check_message(decoded_msg, curr_msg_topic)
def on_unsubscribe(client, userdata, mid):
#MQTT topic unsubscribe callback
pass
#print('\nUnsubscribed.', mid)
def on_subscribe(client, userdata, mid, granted_qos):
#MQTT topic subscribe callback
insert_treeview_topic()
tab1_sub_to_topic.delete(0,END) #clear entry box
tab1_subscribed_list.insert(END, address) #add topic to listbox for subscribed topics
def on_connect(client, userdata, flags, rc):
#MQTT broker connect callback
global subscribed_topics_list
if rc == mqtt.CONNACK_ACCEPTED:
client.connected_flag = True
#If the program reconnects by itself, we need to unsubscribe from all topics and re-subscribe to them
#after. This means we need to clear our list array that holds the subscribed topics but keep the
#config parser file to remember what to subscribe back to.
if subscribed_topics_list: #if not empty
for topic in subscribed_topics_list:
client.unsubscribe(topic) #unsubcribe from all subscribed topics
subscribed_topics_list.clear() #clear subscription list
#check if we have a file saved for list of subscribed topics from previous session if applicable
check_subscribed_topics()
else:
print("Bad connection. Returned code = ", rc)
#My MQTT actions
def auto_subscribe(list):
global address
for value in list:
address = value
terminal.terminal_print("Auto-subscribing to: " + value)
client.subscribe(value, qos=1)
def do_publish():
if client_flag == 0:
terminal.terminal_print('Please select a target device.')
return
topic = tab2_topic_input.get() #grab what was in topic entry textbox
address = topics.compare_pubs(topic, mqtt_topic_prefix, target_device) #obtain address of topic
user_msg = tab2_msg_input.get() #grab what was in the msg entry textbox
msg_to_pub = pub_message_check(user_msg)
client.publish(topic=address, payload=json.dumps(msg_to_pub), qos=0, retain=True) #publish message to topic
tab2_topic_input.delete(0,END) #clear entry box
tab2_msg_input.delete(0,END) #clear entry box
def do_unsubscribe():
global address
global list_to_string
if client_flag == 0:
terminal.terminal_print(text='Please select a target device.')
return
topic = tab1_sub_to_topic.get() #grab user input from entry box
address = topics.compare_subs(topic, mqtt_topic_prefix, target_device)
tab1_sub_to_topic.delete(0,END) #clear entry box
tab1_subscribed_list.delete(ANCHOR)
if address in subscribed_topics_list: #check if it's in our list of subscribed topics
subscribed_topics_list.remove(address) #delete from our subscribed topics list
client.unsubscribe(address) #unsubscribe to topic
list_to_string = '\n'.join([str(elem) for elem in subscribed_topics_list]) #new list after unsubscribing to a topic
topic_config[account_type][target_device] = list_to_string #alter file
with open('saved_topics.ini', 'w') as topic_configfile:
topic_config.write(topic_configfile)
def do_subscribe(): #client.subscribe() work in here
global topic
global address
global list_to_string
if client_flag == 0:
terminal.terminal_print('Please select a target device.')
return
topic = tab1_sub_to_topic.get() #grab what was typed in the sub_to_topic entry box
address = topics.compare_subs(topic, mqtt_topic_prefix, target_device) #function to compare topics to get their actual address if selected from listbox
#check if it's in our list of subscribed topics
if address not in subscribed_topics_list: #make sure there's no duplicates
subscribed_topics_list.append(address) #add to our subscribed topics list
tab1_sub_to_topic.delete(0,END) #clear entry box
client.subscribe(address, qos=1) #subscribe
list_to_string = '\n'.join([str(elem) for elem in subscribed_topics_list])
topic_config[account_type][target_device] = list_to_string #add to file
with open('saved_topics.ini', 'w') as topic_configfile:
topic_config.write(topic_configfile)
else:
terminal.terminal_print('Already subscribed to topic.')
def check_subscribed_topics():
topics_to_subscribe = []
string_to_list = []
file_exists = os.path.exists('saved_topics.ini')
if file_exists is not True: #create file and add section for devices and subscribed topics for each
topic_config['Prod'] = {}
topic_config['Beta'] = {}
topic_config['Dev'] = {}
topic_config['Feat'] = {}
devices = [x for x in device_options if not x.startswith('Select')]
for i in devices: #put device under whichever account device was selected at login
topic_config[account_type][i] = ''
with open('saved_topics.ini', 'w') as topic_configfile:
topic_config.write(topic_configfile)
else: #parse existing file
topic_config.read('saved_topics.ini')
devices = [x for x in device_options if not x.startswith('Select')]
for i in devices:
device_on_file = topic_config.has_option(account_type, i) #check if devices are already on file, otherwise create them as keys
if device_on_file is False: #not on file, make a key for it
topic_config[account_type][i] = '' #put device under whichever account device was selected at login
with open('saved_topics.ini', 'w') as topic_configfile:
topic_config.write(topic_configfile)
#if all the devices are already on file then it will go straight to the code below
dict_to_list = topic_config.items(account_type, target_device) #this prints out all [(key, {value})] for the selected account type
for key, value in dict_to_list:
if key == target_device: #we only want the value for the selected target_device
topics_to_subscribe.append(value)
topics_to_subscribe = list(filter(None, topics_to_subscribe))
if len(topics_to_subscribe) == 0: #nothing to subscribe to, move on
return
else:
string_to_list = [x for y in topics_to_subscribe for x in y.split('\n')]
for value in string_to_list:
subscribed_topics_list.append(value) #store in a list of topics we have successfully subscribed to `
auto_subscribe(subscribed_topics_list)
def pub_message_check(msg): #user custom message
if msg == '' or msg == '{None}':
msg = None
else:
msg = msg
return msg
def check_message(message, curr_msg_topic): #unpack the message
try:
unpacked_json = json.loads(message) #try to convert to dict
except Exception as e:
print("Couldn't parse raw data: %s" % message, e)
else:
pass
if isinstance(unpacked_json, dict): #check if type dict
sort_message(unpacked_json, curr_msg_topic)
elif isinstance(unpacked_json, list):
list_to_dict = {k:v for e in unpacked_json for (k,v) in e.items()}
sort_message(list_to_dict, curr_msg_topic)
def sort_message(message, curr_msg_topic):
message_array = []
for key, value in message.items(): #iterate for single key-value pair
if isinstance(value, dict):
for key2, value2 in value.items(): #iterate for a value with value
value2 = str(value2)
value2 = value2.strip('{}') #this gets rid of brackets for "networkInfo"
#Unable to pick "lte" apart
if isinstance(value2, dict):
for key3, value3 in value2.items(): #iterate for a value with a value with value
value3 = str(value3)
value3 = value3.strip('{}')
curr_line = key3 + ':' + str(value3)
message_array.append(curr_line)
else:
curr_line = key2 + ':' + str(value2)
message_array.append(curr_line)
else:
curr_line = key + ': ' + str(value)
message_array.append(curr_line)
message_array = [x for x in message_array if not x.startswith('lte')] #just going to completely ignore 'lte' and everything after it for now
message_array = [x for x in message_array if not x.startswith('types')] #and 'types'
output_messages(message_array, curr_msg_topic) #pass to function to put into message tab
for k, v in message.items(): #find the data type we want
if k == 'appId' and v == 'RSRP':
for k, v in message.items(): #go through list again to find exact data value
if k =='data':
plots.get_data_rsrp(v) #send value to function to store in plot array
elif k == 'appId' and v == 'BUTTON':
for k, v in message.items():
if k =='data':
plots.get_data_button(v)
else: #none of the two above included in message, then send 0
no_data = 0
plots.get_data_rsrp(no_data)
plots.get_data_button(no_data)
def insert_treeview_topic():
global curr_msg_topic
curr_msg_topic = address
if curr_msg_topic not in tab3_topic:
short_topic = '...' + address[-10:] #only show the last 10 characters of a topic
tab3_tree.insert("", END, iid=curr_msg_topic, text=short_topic) #create parent section for topic
tab3_topic.append(curr_msg_topic)
else:
return
def output_messages(message_array, curr_msg_topic):
curr_msg_topic = str(curr_msg_topic)
#insert new messages in Treeview in its appropriate topic section
for i in message_array:
tab3_tree.insert(parent=curr_msg_topic, index=0, values=i, tags='dark') #child, insert from top
tab3_tree.insert(parent=curr_msg_topic, index=0, values='', tags='light') #blank line in between chunk of messages
#Tab 2 actions: clear/update listboxes and entry boxes
def tab2_update_msgBox(data): #add list of messages into box
tab2_messages_list.delete(0,END)
for item in data:
tab2_messages_list.insert(END, item)
def tab2_update_listBox(data): #update the listbox
tab2_listBox.delete(0,END) #clear the listbox
for item in data: #add topics to listbox
tab2_listBox.insert(END, item)
def do_clear2():
tab2_topic_input.delete(0,END) #clear entry box
tab2_msg_input.delete(0,END) #clear entry box
event_clicks.tab2_insert_shadow_text_left(tab2_topic_input) #put shadow texts back in
event_clicks.tab2_insert_shadow_text_right(tab2_msg_input)
def tab2_fillout_msg(e):
tab2_msg_input.delete(0,END)
tab2_msg_input.insert(0, tab2_messages_list.get(ANCHOR))
tab2_msg_input.config(fg='black')
def tab2_fillOut(e):
tab2_topic_input.delete(0,END) #delete whatever is in entry box
tab2_topic_input.insert(0, tab2_listBox.get(ANCHOR)) #add clicked list item to listbox
tab2_topic_input.config(fg='black')
#Tab 1 actions: clear/update listboxes and entry boxes
def do_clear():
tab1_sub_to_topic.delete(0,END)
event_clicks.tab1_insert_shadow_text(tab1_sub_to_topic) #insert shadow text
def tab1_fillOut_sub(e):
global subscribed_list
subscribed_list = []
tab1_sub_to_topic.delete(0,END) #clear bottom box
tab1_sub_to_topic.insert(0, tab1_subscribed_list.get(ANCHOR))
tab1_sub_to_topic.config(fg='black')
subscribed_list.append(tab1_subscribed_list.get(ANCHOR))
def tab1_fillOut(e):
tab1_sub_to_topic
tab1_sub_to_topic.delete(0,END) #clear bottom entry box
tab1_sub_to_topic.insert(0, tab1_listBox.get(ANCHOR)) #add clicked list item to listbox
tab1_sub_to_topic.config(fg='black')
def tab1_update_listBox(data): #update the listbox
tab1_listBox.delete(0,END) #clear the listbox
for item in data: #add topics to listbox
tab1_listBox.insert(END, item)
def tab1_checkKeyPress(e):
typed = tab1_searchBar.get() #grab what was typed in the search bar
if typed == '': #nothing was typed
data = sub_topic_list
else:
data = []
for item in sub_topic_list:
if typed.lower() in item.lower(): #convert everything to lowercase
data.append(item)
tab1_update_listBox(data) #update listbox with selected topics
def reset_device():
tab1_subscribed_list.delete(0, END) #empty subscribed topics listbox
device_info['state'] = NORMAL
device_info.delete(1.0, END)
device_info.insert(END, select_message, "align")
device_info['state'] = DISABLED
for i in tab3_tree.get_children(): #clear messages tab
tab3_tree.delete(i)
tab3_topic.clear() #clear treeview topics list
def change_device(*args):
global target_device
global device_specifics
global device_info
global client_flag
global first_start_flag2
device_specifics = []
target_device = device_list.get() #get user selection from dropdown menu
if client_flag == 0: #not connected to MQTT broker
if target_device == 'Select Device...':
device_info['state'] = NORMAL
device_info.delete(1.0, END)
device_info.insert(END, select_message, "align")
device_info['state'] = DISABLED
return #device not selected, do nothing
elif target_device != 'Select Device...':
pass #selected a device, continue with bottom code to grab device info
if client_flag == 1: #connected to MQTT broker
if target_device == 'Select Device...': #no device selected
target_device = None #disconnect target device and from MQTT broker
client_flag = 0
client.disconnect()
client.loop_stop()
if first_start_flag2 == 0:
first_start_flag2 += 1
return
reset_device() #clear messages and subscribed topics
return
elif target_device != 'Select Device...': #switching from one device to another
client.disconnect() #disconnect from the current device before switching
client.loop_stop() #then continue with bottom code
reset_device() #clear messages and subscribed topics
for device in http_get['items']: #change device details depending on the device selected
if device['id'] == target_device:
separate = 'T' #delete T and everything after it, only want the date
createdDate = device['$meta']['createdAt']
createdDate = createdDate.split(separate, 1)[0]
device_specifics = ['Dev ID: ' + device['id'],
'Type: ' + device['type'],
'Subtype: ' + device['subType'],
'Created On: ' + createdDate,
'Version: ' + device['$meta']['version']
]
devText = '\n'.join(device_specifics)
device_info['state'] = NORMAL
device_info.delete(1.0, END)
device_info.insert(END, devText)
device_info['state'] = DISABLED
connectMQTT() #connect to MQTT broker
def tab3_layout(tab3):
global tab3_tree
#tab3 layout: left column for treeview and right for plots
tab3.columnconfigure(0, weight=2)
tab3.columnconfigure(1, weight=1)
tab3.rowconfigure(0, weight=1)
#frame for left column
tab3_layout_left = Frame(tab3, highlightthickness=0, borderwidth=0)
tab3_layout_left.grid(column=0, row=0, padx=5, pady=10, sticky=W+E+N+S)
#use Treeview widget on Messages tab to display hierarchical collection of items
tab3_tree = ttk.Treeview(tab3_layout_left, columns=(1,2), show='tree headings')
tab3_tree_scroll = Scrollbar(tab3_layout_left, orient=VERTICAL, command=tab3_tree.yview)
tab3_tree.config(yscrollcommand=tab3_tree_scroll.set)
tab3_tree_scroll.pack(side=RIGHT, fill=Y)
tab3_tree.pack(padx=5, pady=5, fill=BOTH, expand=TRUE)
#set up columns with titles
tab3_tree.heading("#0", text='Topics')
tab3_tree.heading(1, text='Messages')
tab3_tree.heading(2, text='Data')
tab3_tree.column('#0', width=95, anchor=W)
tab3_tree.column(1, anchor=W)
tab3_tree.column(2, anchor=W)
style2 = ttk.Style()
style2.configure("Treeview.Heading", font=(myFont, 10, 'bold'), background=lighter_grey, foreground=nordic_blueslate)
style2.configure("Treeview", font=(myFont, 10), background='white')
tab3_tree.tag_configure('topic', font=(myFont, 10, 'bold'), background=lighter_grey, foreground=nordic_blueslate)
tab3_tree.tag_configure('light', background='#E8E8E8')
tab3_tree.tag_configure('dark', background='#DFDFDF')
#frame for right column (plots)
tab3_layout_right = Frame(tab3)
tab3_layout_right.grid(padx=5, pady=10, column=1, row=0, sticky=W+E+N+S)
tab3_layout_right.columnconfigure(0, weight=1, uniform=1)
tab3_layout_right.rowconfigure(0, weight=1, uniform=1)
tab3_layout_right.rowconfigure(1, weight=1, uniform=1)
#function for plot1
plots.graph_rsrp(tab3_layout_right)
#function for plot2
plots.graph_button(tab3_layout_right)
# TAB 3 DETAILS
#Treeview widget on left side of frame
#-Depending on which topic is expanded:
# (1) parent: topic
# (1) first child: most recent message
# and the details
# color code each one -- NOT DONE
# (1) second child: attach a "link" that opens up a pop-up listing all messages with scrollbar
# save messages to a .txt file or something, cap at (50)msgs
# if another topic is subbed to, create another parent tree for that topic:
# (2) parent: topic
# (2) first child: most recent message
# etc.
# we just need one function to create parents and its children - just iterate when
# subscribing to topic/remove when unsubscribing
#The right side will include graphs/visuals?
#For the plot:
# retrieve message
# grab the specific data value
# update data with line.set_data()
# remember subscribed topics so when we select a device, we automatically subscribe to them again
# save to .txt file
# write a new function that will read from that file every time we select a device
# to check if there are any topics we were watching previously so that it can be added
# as a parent of the Treeview widget.
def tab2_layout(tab2):
global tab2_listBox
global tab2_msg_input
global tab2_topic_input
global tab2_messages_list
#tab2 layout
tab2.columnconfigure(tuple(range(7)), weight=1) #eight columns
tab2.rowconfigure(tuple(range(3)), weight=1) #three rows
tab2_select_label = Label(tab2, text='Select a topic to send a message to: ')
tab2_select_label.config(font=(myFont, 13), background=myGrey)
tab2_select_label.grid(column=0, row=0, columnspan=4, padx=(0,5), pady=(15,0), ipady=5, sticky=N+S)
tab2_listbox_border = Frame(tab2, background='white')
tab2_listbox_border.grid(column=0, row=1, columnspan=4, rowspan=2, padx=(40,5), pady=(5,15),
sticky=W+E+N+S)
tab2_listBox = Listbox(tab2_listbox_border, borderwidth=0, highlightthickness=0, background='white')
tab2_scroll = Scrollbar(tab2_listbox_border, orient=VERTICAL, command=tab1_listBox.yview)
tab2_listBox.config(font=(myFont, 12), yscrollcommand=tab2_scroll.set, relief=RAISED,
selectbackground=listbox_select_bg)
tab2_scroll.pack(side=RIGHT, fill=Y)
tab2_listBox.pack(padx=20, pady=20, fill=BOTH, expand=TRUE)
#create list of "Publish" topics to insert into Listbox
pub_topic_list = []
pub_topic_list = topics.topics_to_pub(pub_topic_list)
tab2_update_listBox(pub_topic_list) #add topics to our list
tab2_listBox.bind("<<ListboxSelect>>", tab2_fillOut) #create a binding on the listbox onclick
tab2_help = Label(tab2, text="[ Help ]", background='#5595AD', foreground='white')
tab2_help.config(font=(myFont, 12), borderwidth=1, relief='raised', anchor=CENTER, justify=LEFT)
tab2_help.grid(column=0, row=3, padx=(40,0), pady=(5,15), ipadx=2, sticky=N+S)
ToolTip(tab2_help, msg='Click on a topic and a message\nabove to auto-fill or manually\ntype into the textbox.',
background=tab2_tooltip_bg, font=(myFont, 12), follow=True)
tab2_topic_input = Entry(tab2) #this is for the topic
tab2_topic_input.config(font=(myFont, 12))
tab2_topic_input.insert(0, 'Enter a topic...') #insert shadow text
tab2_topic_input.grid(column=1, row=3, columnspan=3, padx=5, pady=(5,15), sticky=W+E)
#remove/show shadow text when in or out of focus
tab2_topic_input.bind('<FocusIn>', tab2_on_entry_click_left)
tab2_topic_input.bind('<FocusOut>', tab2_entry_focus_out_left)
tab2_topic_input.config(fg='grey')
tab2_msg_input = Entry(tab2) #this is for the message
tab2_msg_input.config(font=(myFont, 12))
tab2_msg_input.insert(0, 'Enter a message...') #insert shadow text
tab2_msg_input.grid(column=4, row=3, columnspan=2, pady=(5,15), sticky=W+E)
#remove/show shadow text when in or out of focus
tab2_msg_input.bind('<FocusIn>', tab2_on_entry_click_right)
tab2_msg_input.bind('<FocusOut>', tab2_entry_focus_out_right)
tab2_msg_input.config(fg='grey')
#clear button to clear entry
tab2_clear_button = Button(tab2, text='Clear', command=do_clear2)
tab2_clear_button.grid(column=6, row=3, padx=5, pady=(0,15), sticky=W+E+N+S)
#make publish button go to publish function and clear entries
tab2_publish_button = Button(tab2, text="Publish", command=do_publish, background=nordic_blue,
foreground='white')
tab2_publish_button.grid(column=7, row=3, padx=(0,40), pady=(0,15), ipadx=20, sticky=W+E+N+S)
button_config(tab2_clear_button)
button_config(tab2_publish_button)
#common messages label
tab2_msgs_label = Label(tab2, text='Common Messages')
tab2_msgs_label.config(font=(myFont, 12), background=myGrey, anchor=CENTER, relief=GROOVE)
tab2_msgs_label.grid(column=4, row=0, columnspan=4, padx=(0,40), pady=(15,0), ipadx=100, ipady=1, sticky=N+S)
#list of messages
tab2_messages_border = Frame(tab2, background='white')
tab2_messages_border.grid(column=4, row=1, columnspan=4, rowspan=2, padx=(10,40), pady=(5,15),
sticky=W+E+N+S)
tab2_messages_list = Listbox(tab2_messages_border, borderwidth=0, highlightthickness=0, background='white')
tab2_msgsList_scroll = Scrollbar(tab2_messages_border, orient=VERTICAL, command=tab1_listBox.yview)
tab2_messages_list.config(font=(myFont, 12), yscrollcommand=tab2_msgsList_scroll.set, relief=RAISED,
selectbackground=listbox_select_bg)
tab2_msgsList_scroll.pack(side=RIGHT, fill=Y)
tab2_messages_list.pack(padx=20, pady=20, fill=BOTH, expand=TRUE)
tab2_messages_list.bind("<<ListboxSelect>>", tab2_fillout_msg) #create a binding on the listbox onclick
#create list of possible messages to publish
message_options = []
message_options = topics.messages_to_pub(message_options)
tab2_update_msgBox(message_options) #add topics to our list
tab2_messages_list.bind("<<ListboxSelect>>", tab2_fillout_msg) #create binding
def tab1_layout(tab1):
global tab1_listBox
global tab1_searchBar
global tab1_sub_to_topic
global sub_topic_list
global tab1_subscribed_list
#tab1 layout
tab1.columnconfigure(tuple(range(7)), weight=1) #eight columns
tab1.rowconfigure(tuple(range(3)), weight=1) #three rows
tab1_search = Label(tab1, text='Search: ')
tab1_search.config(font=(myFont, 13), background=myGrey)
tab1_search.grid(column=0, row=0, padx=(0,5), pady=(18,0), sticky=E+N+S)
tab1_searchBar = Entry(tab1)
tab1_searchBar.config(font=(myFont, 12))
tab1_searchBar.grid(column=1, row=0, columnspan=3, padx=(0,5), pady=(20,0), sticky=W+E)
tab1_listbox_border = Frame(tab1, background='white')
tab1_listbox_border.grid(column=0, row=1, columnspan=4, rowspan=2, padx=(40,5), pady=(5,15),
sticky=W+E+N+S)
tab1_listBox = Listbox(tab1_listbox_border, borderwidth=0, highlightthickness=0, background='white')
tab1_scroll = Scrollbar(tab1_listbox_border, orient=VERTICAL, command=tab1_listBox.yview)
tab1_listBox.config(font=(myFont, 12), yscrollcommand=tab1_scroll.set, relief=RAISED,
selectbackground=listbox_select_bg)
tab1_scroll.pack(side=RIGHT, fill=Y)
tab1_listBox.pack(padx=(20,5), pady=20, fill=BOTH, expand=TRUE)
#create list of "Subscribe" topics to insert into listbox
sub_topic_list = []
sub_topic_list = topics.topics_to_sub(sub_topic_list)
tab1_update_listBox(sub_topic_list) #add topics to our list
tab1_listBox.bind("<<ListboxSelect>>", tab1_fillOut) #create a binding on the listbox onclick
tab1_searchBar.bind("<KeyRelease>", tab1_checkKeyPress) #create binding for keys pressed while active on search bar
tab1_sub_to_topic = Entry(tab1)
tab1_sub_to_topic.config(font=(myFont, 12))
tab1_sub_to_topic.insert(0, 'Enter a topic...')
tab1_sub_to_topic.grid(column=0, row=3, columnspan=5, padx=(40,1), pady=(5,15), sticky=W+E)
#remove/show shadow text when in or out of focus
tab1_sub_to_topic.bind('<FocusIn>', tab1_on_entry_click)
tab1_sub_to_topic.bind('<FocusOut>', tab1_entry_focus_out)
tab1_sub_to_topic.config(fg='grey')
#clear button to clear entry
tab1_clear_button = Button(tab1, text='Clear', command=do_clear)
tab1_clear_button.grid(column=5, row=3, padx=(5,0), pady=(0,15), sticky=W+E+N+S)
#make subscribe button go to subscribe function and clear sub_to_topic Entry
tab1_sub_button = Button(tab1, text="Subscribe", command=do_subscribe, background=nordic_blue,
foreground='white')
tab1_sub_button.grid(column=6, row=3, padx=5, pady=(0,15), sticky=W+E+N+S)
#make unsubscribe button go to unsubscribe function and clear sub_to_topic Entry
tab1_unsub_button = Button(tab1, text="Unsubscribe", command=do_unsubscribe, background=nordic_lake,
foreground='white')
tab1_unsub_button.grid(column=7, row=3, padx=(0,40), pady=(0,15), sticky=W+E+N+S)
button_config(tab1_clear_button)
button_config(tab1_sub_button)
button_config(tab1_unsub_button)
#subscribed topics label
tab1_subbed_topics_label = Label(tab1, text='Subscribed Topics ')
tab1_subbed_topics_label.config(font=(myFont, 12), background=myGrey, anchor=CENTER, relief=GROOVE)
tab1_subbed_topics_label.grid(column=4, row=0, columnspan=4, padx=(0,40), pady=(15,0), ipadx=100, ipady=4)
#list of subscriptions box
tab1_subscribedList_border = Frame(tab1, background='white')
tab1_subscribedList_border.grid(column=4, row=1, columnspan=4, rowspan=2, padx=(10,40), pady=(5,15),
sticky=W+E+N+S)
tab1_subscribed_list = Listbox(tab1_subscribedList_border, borderwidth=0, highlightthickness=0, background='white')
tab1_second_scroll = Scrollbar(tab1_subscribedList_border, orient=VERTICAL, command=tab1_subscribed_list.yview)
tab1_third_scroll = Scrollbar(tab1_subscribedList_border, orient=HORIZONTAL, command=tab1_subscribed_list.xview)
tab1_subscribed_list.config(font=(myFont, 12), yscrollcommand=tab1_second_scroll.set, relief=RAISED,
xscrollcommand=tab1_third_scroll.set, selectbackground=listbox_select_bg)
tab1_second_scroll.pack(side=RIGHT, fill=Y)
tab1_third_scroll.pack(side=BOTTOM, fill=X)
tab1_subscribed_list.pack(padx=20, pady=20, fill=BOTH, expand=TRUE)
tab1_subscribed_list.bind("<<ListboxSelect>>", tab1_fillOut_sub) #create a binding on the listbox onclick
def create_right_frame(container):
global terminal_list
global terminal_input
global first_start_flag
global terminal
frame = Frame(container, background=light_grey)
frame.columnconfigure(0, weight=1) #one column
frame.rowconfigure(tuple(range(3)), weight=1) #four rows
if first_start_flag==0: #only run through this function once
first_start_flag = 1
themes.tab_theme() #set custom theme for tabs
#create sub/pub/msgs tabs
tabs = Notebook(frame)
tabs.grid(column=0, row=0, rowspan=2, padx=10, pady=10, sticky=W+E+N+S)
tab1 = Frame(tabs)
tab2 = Frame(tabs)
tab3 = Frame(tabs)
tab1.config(background=myGrey)
tab2.config(background=myGrey)
tab3.config(background=myGrey)
tab1.pack(expand=True, fill=BOTH)
tab2.pack(expand=True, fill=BOTH)
tab3.pack(expand=True, fill=BOTH)
tabs.add(tab1, text='Subscribe') #add frames to notebook
tabs.add(tab2, text='Publish')
tabs.add(tab3, text='Messages')
tab1_layout(tab1) #put stuff in each tab
tab2_layout(tab2)
tab3_layout(tab3)
#user terminal - redirect -UBACKS to this frame; connects, subscribes, publishes, messages
terminal_border = Frame(frame, background=dark_grey)
terminal_border.grid(column=0, row=2, padx=10, pady=(10,0),
ipadx=20, ipady=20, sticky=W+E+N+S)
terminal_list = Text(terminal_border, height=15, borderwidth=0, highlightthickness=0, wrap=CHAR)
terminal_scroll = Scrollbar(terminal_border, orient=VERTICAL, command=terminal_list.yview)
terminal_list.config(font=(myFont, 11), background=dark_grey, foreground='white',
yscrollcommand=terminal_scroll.set)
terminal_scroll.pack(side=RIGHT, fill=Y)
terminal_list.pack(padx=10, pady=(10,0), fill=BOTH, expand=TRUE)
#textbox for user terminal
terminal_input_frame = Frame(frame, background=light_grey)
terminal_input_frame.grid(column=0, row=3, padx=10, ipadx=2, ipady=1, sticky=W+E+N+S)
terminal_input = Entry(terminal_input_frame)
terminal_input.config(font=(myFont, 14), background=terminal_input_bg)
terminal_input.pack(padx=(0,5), pady=(0,8), fill=BOTH, expand=TRUE, side=LEFT)
#instantiate module Terminal
terminal = terminal.Terminal(terminal_list, terminal_input, mqtt_endpoint, mqtt_topic_prefix, client_id)
terminal.terminal_print("Welcome! Select a device to get started.") #first line in terminal
terminal.terminal_print("Type /help for more information.")
terminal_list['state'] = DISABLED
#enter terminal button
terminal_enter_button = Button(terminal_input_frame, text='Enter', command=terminal.terminal_enter)
terminal_enter_button.config(background=nordic_blue, foreground='white')
terminal_enter_button.pack(padx=(0,5), pady=(0,8), ipadx=15, fill=None, expand=FALSE, side=LEFT)
#clear terminal button
terminal_clear_button = Button(terminal_input_frame, text='Clear', command=terminal.terminal_clear)
terminal_clear_button.pack(padx=(0,5), pady=(0,8), ipadx=15, fill=None, expand=FALSE, side=LEFT)
#bind "Enter" key event with terminal_input
terminal_input.bind('<Return>', terminal_enter_event) #bind enter key with event function
button_config(terminal_enter_button)
button_config(terminal_clear_button)
for widget in frame.winfo_children():
widget.grid(padx=5, pady=3)
return frame
def create_left_frame(container):
global device_list
global device_info
global select_message
global device_options
frame = Frame(container)
frame.columnconfigure(0, weight=1) #one column
for i in range(7):
frame.rowconfigure(i, weight=1) #eight rows
frame.option_add("*Background", 'white')
frame.option_add("*Foreground", 'black')
#(0,0) select device drop down menu at top left
device_options = []
device_list = StringVar()
device_list.trace('w', change_device)
device_list.set("Select Device...")
device_options.append('Select Device...')
for device in http_get['items']:
if device['subType'] != 'account': #only include devices and not the account device
device_options.append(device['id']) #put device IDs in device_list array
select_device_frame = Frame(frame, borderwidth=2, relief="ridge")
select_device_frame.grid(column=0, row=0, padx=10, pady=(10,0), sticky=W+E+N+S)
select_device = OptionMenu(select_device_frame, device_list, *device_options) #dropdown menu
select_device.config(font=(myFont, 12), background=nordic_blue, foreground='white', activebackground=middle_grey)
select_device['menu'].configure(bg=dropdown_menu_bg, activebackground=middle_grey, bd=0, font=(myFont, 12))
select_device.pack(fill=BOTH, expand=TRUE)
#(0,1) below that, account and device information
device_info_frame = Frame(frame, background=left_widgets_color, borderwidth=2, relief=RAISED)
device_info_frame.grid(column=0, row=1, rowspan=2, padx=(30,20), pady=(0,5))
device_info = Text(device_info_frame, background=left_widgets_color, foreground='white', width=25, height=7, wrap=WORD)
device_info.config(highlightthickness=0, borderwidth=0, font=(myFont, 10), spacing1=5, spacing2=2, spacing3=2)
device_info.pack(padx=15, pady=(7,3), fill=BOTH, expand=TRUE)
device_info.tag_configure("align", justify='center')
device_info.tag_configure("bold", font=(None, 10, BOLD))
select_message = "\n\nSelect device above\n for more information."
device_info.insert(END, select_message, "align")
device_info['state'] = DISABLED
#(0,3) mqtt user information
mqtt_info_frame = Frame(frame, borderwidth=2, relief=RAISED, background=left_widgets_color)
mqtt_info_frame.grid(column=0, row=3, rowspan=3, padx=(30,20))
mqtt_info = Text(mqtt_info_frame, background=left_widgets_color, foreground='white', width=25, height=10, wrap=CHAR)
mqtt_info.config(highlightthickness=0, borderwidth=0, font=(myFont, 10), spacing1=5, spacing2=2, spacing3=2)
mqtt_info.pack(padx=15, pady=(10,10), fill=BOTH, expand=TRUE)
mqtt_info.tag_configure("align", justify='center') #configure tags
mqtt_info.tag_configure("bold", font=(None, 10, BOLD))
mqtt_info.tag_configure("spacing", font=(None, 3,))
mqtt_endpoint_text = 'MQTT Endpoint:'
mqtt_topic_prefix_text = 'MQTT Topic Prefix:'
mqtt_client_id_text = 'MQTT Client ID:'
mqtt_info.insert(END, mqtt_endpoint_text + '\n', "bold")
mqtt_info.insert(END, mqtt_endpoint + '\n')
mqtt_info.insert(END, '\n', "spacing")
mqtt_info.insert(END, mqtt_topic_prefix_text + '\n', "bold")
mqtt_info.insert(END, mqtt_topic_prefix + '\n')
mqtt_info.insert(END, '\n', "spacing")
mqtt_info.insert(END, mqtt_client_id_text + '\n', "bold")
mqtt_info.insert(END, client_id)
mqtt_info.tag_add("align", 1.0, END)
mqtt_info['state'] = DISABLED
#(0,4) show account type
account_type_frame = Frame(frame, borderwidth=2, relief=RAISED, background=left_widgets_color)
account_type_frame.grid(column=0, row=6, padx=(30,20))
account_type_text = Text(account_type_frame, background=left_widgets_color, foreground='white', width=25, height=2, wrap=CHAR)
account_type_text.config(highlightthickness=0, borderwidth=0, font=(myFont, 10), spacing1=5, spacing2=2, spacing3=2)
account_type_text.pack(padx=15, pady=7, fill=BOTH, expand=TRUE)
account_type_text.tag_configure("align", justify='center') #configure tags
account_type_text.tag_configure("bold", font=(None, 10, BOLD))
account_type_label = 'Account Type:'
account_type_text.insert(END, account_type_label + '\n', "bold")
account_type_text.insert(END, account_type)
account_type_text.tag_add("align", 1.0, END) #center all
account_type_text['state'] = DISABLED
#(0,7) bottom left, log off button
log_off_frame = Frame(frame, background=light_grey)
log_off_frame.grid(column=0, row=7, padx=10, pady=15, sticky=W+E+S)
log_off = Button(log_off_frame, text="Log Off", command=restartPopup)
log_off.config(background=main_logoff_color, foreground='white')
log_off.pack(pady=(0,10), fill=BOTH, expand=TRUE)
button_config(log_off)
for widget in frame.winfo_children():
widget.grid(padx=5, pady=1)
return frame
def main_screen():
#Main Screen Configuration
root.deiconify() #show main page
if first_start_flag == 0: #only build once
#build two frames for main window
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=6)
root.rowconfigure(0, weight=1)
left_frame = create_left_frame(root)
left_frame.config(background=light_grey)
left_frame.grid(column=0, row=0, sticky=W+E+N+S)
right_frame = create_right_frame(root)
right_frame.config(background=light_grey)
right_frame.grid(column=1, row=0, sticky=W+E+N+S)
def edit_login_config_file(flag, data):
#store new values into file
if flag == 1: #store into API
login_config[account_type]['api'] = api_key
elif flag == 2: #store into clientCert
login_config[account_type]['clientcert'] = client_cert
elif flag == 3: #store into privKey
login_config[account_type]['privkey'] = priv_key
else:
pass #do nothing if no input
with open('saved_login.ini', 'w') as configfile:
login_config.write(configfile) #write changes to file