Skip to content

Commit 5cc423c

Browse files
authored
Merge pull request #96 from PropGit/WiFi
Port list stabilization
2 parents ae78b6e + e2d866f commit 5cc423c

File tree

5 files changed

+138
-51
lines changed

5 files changed

+138
-51
lines changed

BlocklyPropClient.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
# Please verify that the version number in the local about.txt and the
3737
# ./package/win-resources/blocklypropclient-installer.iss matches this.
3838
# -----------------------------------------------------------------------
39-
VERSION = "0.6.2"
39+
VERSION = "0.6.3"
4040

4141

4242
# Enable logging for functions outside of the class definition

BlocklyServer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ def index(self):
6262
def ports(self):
6363
cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'
6464
self.queue.put((3, 'DEBUG', 'Port list retrieved'))
65-
self.logger.debug('Port list retreived')
65+
self.logger.debug('Port list request received')
6666

6767
ports = self.propellerLoad.get_ports()
6868
if len(ports) > 0:
6969
filtered_ports = []
7070
for port in ports:
71-
self.logger.debug('Port %s discovered.', port)
7271
# Filter out Bluetooth ports; they are risky to open and scan
7372
if ' bt ' not in port.lower() and 'bluetooth' not in port.lower():
73+
self.logger.debug('Port %s discovered.', port)
7474
filtered_ports.append(port)
7575
else:
7676
self.logger.debug("Port %s filtered from the list.", port)

PropellerLoad.py

+133-46
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,33 @@
1010
module_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+
1330
class 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

188275
def isWiFiName(string, wifiName):
@@ -192,17 +279,17 @@ def isWiFiName(string, wifiName):
192279

193280
def 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

198285
def 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

203290
def 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

208295
def 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:]

about.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Version: v0.6.2
1+
Version: v0.6.3
22

33
Contributors:
44
- Michel Lampo

package/blocklypropclient-installer.iss

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
33

44
#define MyAppName "BlocklyPropClient"
5-
#define MyAppVersion "0.6.2"
5+
#define MyAppVersion "0.6.3"
66
#define MyAppPublisher "Parallax Inc."
77
#define MyAppURL "http://blockly.parallax.com/"
88
#define MyAppExeName "BlocklyPropClient.exe"

0 commit comments

Comments
 (0)