1010module_logger = logging .getLogger ('blockly.loader' )
1111
1212
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+
1330class PropellerLoad :
1431 loading = False
15- # COM & WiFi-Name ports list
32+ discovering = False
33+ # "Unique identifier" ports list
1634 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 = []
1940
2041
2142 def __init__ (self ):
@@ -50,41 +71,41 @@ def __init__(self):
5071
5172
5273 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 :
5877 return self .ports
5978
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
6182
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 = []
69103
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
86105
87- return self .ports
106+ finally :
107+ # Done, clear discovering flag to process other events
108+ self .discovering = False
88109
89110
90111 def download (self , action , file_to_load , com_port ):
@@ -106,24 +127,26 @@ def download(self, action, file_to_load, com_port):
106127# # launch path is blank; try extracting from argv
107128# self.appdir = os.path.dirname(os.path.realpath(sys.argv[0]))
108129
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
110131 command = []
111132 if self .loaderAction [action ]["compile-options" ] != "" :
112133 # if RAM/EEPROM compile-option not empty, add it to the list
113134 command .extend ([self .loaderAction [action ]["compile-options" ]])
114135 command .extend (["-r" ])
115136
116- # Add requested port
137+ # Specify requested port
117138 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 )
123146 command .extend (["-i" ])
124- command .extend ([getWiFiIP ( targetWiFi [ 0 ]) .encode ('ascii' , 'ignore' )])
147+ command .extend ([IPAddr .encode ('ascii' , 'ignore' )])
125148 else :
126- # Not Wi-Fi match, should be COM port
149+ # Not wireless port match, should be wired port
127150 self .logger .debug ('Requested port is %s' , com_port )
128151 command .extend (["-p" ])
129152 command .extend ([com_port .encode ('ascii' , 'ignore' )])
@@ -180,9 +203,73 @@ def loader(self, cmdOptions):
180203 return False , '' , 'Exception: OSError'
181204
182205
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 ])
183267
184268
185269
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 )
186273
187274
188275def isWiFiName (string , wifiName ):
@@ -192,17 +279,17 @@ def isWiFiName(string, wifiName):
192279
193280def getWiFiName (string ):
194281# Return Wi-Fi Module Name from string, or None if not found
195- return strBetween (string , "Name: '" , "', IP: " )
282+ return strBetween (string , wfNameHdr , wfIPHdr )
196283
197284
198285def getWiFiIP (string ):
199286# 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 )
201288
202289
203290def getWiFiMAC (string ):
204291# Return Wi-Fi Module MAC address from string, or None if not found
205- return strAfter (string , ", MAC: " )
292+ return strAfter (string , wfMACHdr )
206293
207294
208295def strBetween (string , startStr , endStr ):
@@ -225,4 +312,4 @@ def strAfter(string, startStr):
225312 if sPos == - 1 : return None
226313 sPos += len (startStr )
227314 # Return string after
228- return string [sPos :- 1 ]
315+ return string [sPos :]
0 commit comments