10
10
module_logger = logging .getLogger ('blockly.loader' )
11
11
12
12
13
+ # Elements of Port Records (portRec)
14
+ prUID = 0
15
+ prName = 1
16
+ prIP = 2
17
+ prMAC = 3
18
+ prLife = 4
19
+
20
+ # Max lifetime+1 for wired (w) and wifi (wf) Port Records to remain without refresh
21
+ wMaxLife = 2
22
+ wfMaxLife = 4
23
+
24
+ # Wi-Fi Record Headers
25
+ wfNameHdr = "Name: '"
26
+ wfIPHdr = "', IP: "
27
+ wfMACHdr = ", MAC: "
28
+
29
+
13
30
class PropellerLoad :
14
31
loading = False
15
- # COM & WiFi-Name ports list
32
+ discovering = False
33
+ # "Unique identifier" ports list
16
34
ports = []
17
- # Full WiFi ports list
18
- wports = []
35
+ # Port Record list- contains wired (UID) and wireless ports (UID, Name, IP, MAC)
36
+ portRec = []
37
+ # Lists for manipulation
38
+ wnames = []
39
+ wlnames = []
19
40
20
41
21
42
def __init__ (self ):
@@ -50,41 +71,41 @@ def __init__(self):
50
71
51
72
52
73
def get_ports (self ):
53
- # Find COM/Wi-Fi serial ports
54
- self .logger .info ('Received port list request' )
55
-
56
- # Return last results if we're currently downloading
57
- if self .loading :
74
+ # Search for wired/wireless serial ports
75
+ # Return previous results if we're currently downloading to a port or discovering ports
76
+ if self .loading or self .discovering :
58
77
return self .ports
59
78
60
- self .logger .info ("Generating ports list" )
79
+ self .logger .info ("Generating new ports list" )
80
+ # Set discovering flag to prevent interruption
81
+ self .discovering = True
61
82
62
- # Get COM ports
63
- (success , out , err ) = loader (self , ["-P" ])
64
- if success :
65
- self .ports = out .splitlines ()
66
- self .ports .sort (None , None , False )
67
- else :
68
- self .logger .debug ('COM Port request returned %s' , err )
83
+ try :
84
+ # Find wired & wireless serial ports
85
+ (success , out , err ) = loader (self , ["-P" , "-W" ])
86
+ # Process wired response
87
+ if success :
88
+ # Update port records (in self.portRec)
89
+ updatePorts (self , out .splitlines ())
90
+ # Extract unique port names (UID; from port records) and sort them alphabetically
91
+ wnames = [wiredport [prUID ] for wiredport in self .portRec if wiredport [prName ] == "" ]
92
+ wnames .sort (None , None , False )
93
+ wlnames = [wirelessport [prUID ] for wirelessport in self .portRec if wirelessport [prName ] != "" ]
94
+ wlnames .sort (None , None , False )
95
+ # Assign to return list (with wired group on top, wireless group below) in a single step
96
+ # to avoid partial results being used by parallel calling process
97
+ self .ports = wnames + wlnames
98
+ self .logger .debug ('Found %s ports' , len (self .ports ))
99
+ else :
100
+ # Error with external loader
101
+ self .logger .error ('Serial port request returned %s' , err )
102
+ self .ports = []
69
103
70
- # Get Wi-Fi ports
71
- (success , out , err ) = loader (self , ["-W" ])
72
- if success :
73
- # Save Wi-Fi port record(s)
74
- self .wports = out .splitlines ()
75
- # Extract Wi-Fi module names (from Wi-Fi records) and sort them
76
- wnames = []
77
- for i in range (len (self .wports )):
78
- wnames .extend ([getWiFiName (self .wports [i ])])
79
- wnames .sort (None , None , False )
80
- else :
81
- self .logger .debug ('WiFi Port request returned %s' , err )
82
-
83
- # Append Wi-Fi ports to COM ports list
84
- self .ports .extend (wnames )
85
- self .logger .debug ('Found %s ports' , len (self .ports ))
104
+ return self .ports
86
105
87
- return self .ports
106
+ finally :
107
+ # Done, clear discovering flag to process other events
108
+ self .discovering = False
88
109
89
110
90
111
def download (self , action , file_to_load , com_port ):
@@ -106,24 +127,26 @@ def download(self, action, file_to_load, com_port):
106
127
# # launch path is blank; try extracting from argv
107
128
# self.appdir = os.path.dirname(os.path.realpath(sys.argv[0]))
108
129
109
- # Set command download to RAM or EEPROM and to run afterward download
130
+ # Set command to download to RAM or EEPROM and to run afterward download
110
131
command = []
111
132
if self .loaderAction [action ]["compile-options" ] != "" :
112
133
# if RAM/EEPROM compile-option not empty, add it to the list
113
134
command .extend ([self .loaderAction [action ]["compile-options" ]])
114
135
command .extend (["-r" ])
115
136
116
- # Add requested port
137
+ # Specify requested port
117
138
if com_port is not None :
118
- # Find port(s) named com_port
119
- targetWiFi = [l for l in self .wports if isWiFiName (l , com_port )]
120
- if len (targetWiFi ) > 0 :
121
- # Found Wi-Fi match
122
- self .logger .debug ('Requested port %s is at %s' , com_port , getWiFiIP (targetWiFi [0 ]))
139
+ # Determine port type and insert into command
140
+ wlports = [wirelessport for wirelessport in self .portRec if wirelessport [prName ] != "" ]
141
+ wlnames = [names [prUID ] for names in wlports ]
142
+ if com_port in wlnames :
143
+ # Found wireless port match
144
+ IPAddr = [ips [prIP ] for ips in wlports ][wlnames .index (com_port )]
145
+ self .logger .debug ('Requested port %s is at %s' , com_port , IPAddr )
123
146
command .extend (["-i" ])
124
- command .extend ([getWiFiIP ( targetWiFi [ 0 ]) .encode ('ascii' , 'ignore' )])
147
+ command .extend ([IPAddr .encode ('ascii' , 'ignore' )])
125
148
else :
126
- # Not Wi-Fi match, should be COM port
149
+ # Not wireless port match, should be wired port
127
150
self .logger .debug ('Requested port is %s' , com_port )
128
151
command .extend (["-p" ])
129
152
command .extend ([com_port .encode ('ascii' , 'ignore' )])
@@ -180,9 +203,73 @@ def loader(self, cmdOptions):
180
203
return False , '' , 'Exception: OSError'
181
204
182
205
206
+ def updatePorts (self , strings ):
207
+ # Merge strings into Port Record list
208
+ # Ensures unique entries (UIDs), updates existing entries, and removes ancient entries
209
+ # Records "age" with each update unless refreshed by a matching port; those older than xMaxLife-1 are considered ancient
210
+ for newPort in strings :
211
+ if not isWiFiStr (newPort ):
212
+ # Wired port- search for existing identifier
213
+ if newPort in [port [prUID ] for port in self .portRec ]:
214
+ # Found existing- just refresh life
215
+ self .portRec [[port [prUID ] for port in self .portRec ].index (newPort )][prLife ] = wMaxLife
216
+ else :
217
+ # No match- create new entry (UID, n/a, n/a, n/a, MaxLife)
218
+ self .portRec .append ([newPort , '' , '' , '' , wMaxLife ])
219
+ else :
220
+ # Wireless port- search for its MAC address within known ports
221
+ if not getWiFiMAC (newPort ) in [port [prMAC ] for port in self .portRec ]:
222
+ # No MAC match- enter as unique port record
223
+ enterUniqueWiFiPort (self , newPort )
224
+ else :
225
+ # Found MAC match- update record as necessary
226
+ idx = [port [prMAC ] for port in self .portRec ].index (getWiFiMAC (newPort ))
227
+ if self .portRec [idx ][prName ] == getWiFiName (newPort ):
228
+ # Name hasn't changed- leave Name and UID, update IP and Life
229
+ self .portRec [idx ][prIP ] = getWiFiIP (newPort )
230
+ self .portRec [idx ][prLife ] = wfMaxLife
231
+ else :
232
+ # Name has changed- replace entire record with guaranteed-unique entry
233
+ self .portRec .pop (idx )
234
+ enterUniqueWiFiPort (self , newPort )
235
+
236
+
237
+ # Age records
238
+ for port in self .portRec :
239
+ port [prLife ] = port [prLife ] - 1
240
+ # Remove ancients
241
+ while 0 in [port [prLife ] for port in self .portRec ]:
242
+ self .portRec .pop ([port [prLife ] for port in self .portRec ].index (0 ))
243
+
244
+
245
+ def enterUniqueWiFiPort (self , newPort ):
246
+ # Enter newPort as unique port record
247
+ # If name matches another, it will be made unique by appending one or more if its MAC digits
248
+ # Start with UID = Name
249
+ Name = getWiFiName (newPort )+ '-'
250
+ UID = Name [:- 1 ]
251
+ # Prep modifer (MAC address without colons)
252
+ Modifier = getWiFiMAC (newPort ).replace (":" , "" )
253
+
254
+ # Check for unique name (UID)
255
+ Size = 1
256
+ while UID in [port [prUID ] for port in self .portRec ]:
257
+ # Name is duplicate- modify for unique name
258
+ UID = Name + Modifier [- Size :]
259
+ Size += 1
260
+ if Size == len (Modifier ):
261
+ # Ran out of digits? Repeat Modifier
262
+ Name = UID
263
+ Size = 1
264
+
265
+ # UID is unique, create new entry (UID, Name, IP, MAC, MaxLife)
266
+ self .portRec .append ([UID , getWiFiName (newPort ), getWiFiIP (newPort ), getWiFiMAC (newPort ), wfMaxLife ])
183
267
184
268
185
269
270
+ def isWiFiStr (string ):
271
+ # Return True if string is a Wi-Fi record string, False otherwise
272
+ return (string .find (wfNameHdr ) > - 1 ) and (string .find (wfIPHdr ) > - 1 ) and (string .find (wfMACHdr ) > - 1 )
186
273
187
274
188
275
def isWiFiName (string , wifiName ):
@@ -192,17 +279,17 @@ def isWiFiName(string, wifiName):
192
279
193
280
def getWiFiName (string ):
194
281
# Return Wi-Fi Module Name from string, or None if not found
195
- return strBetween (string , "Name: '" , "', IP: " )
282
+ return strBetween (string , wfNameHdr , wfIPHdr )
196
283
197
284
198
285
def getWiFiIP (string ):
199
286
# Return Wi-Fi Module IP address from string, or None if not found
200
- return strBetween (string , "', IP: " , ", MAC: " )
287
+ return strBetween (string , wfIPHdr , wfMACHdr )
201
288
202
289
203
290
def getWiFiMAC (string ):
204
291
# Return Wi-Fi Module MAC address from string, or None if not found
205
- return strAfter (string , ", MAC: " )
292
+ return strAfter (string , wfMACHdr )
206
293
207
294
208
295
def strBetween (string , startStr , endStr ):
@@ -225,4 +312,4 @@ def strAfter(string, startStr):
225
312
if sPos == - 1 : return None
226
313
sPos += len (startStr )
227
314
# Return string after
228
- return string [sPos :- 1 ]
315
+ return string [sPos :]
0 commit comments