Skip to content

Commit

Permalink
Merge pull request #96 from PropGit/WiFi
Browse files Browse the repository at this point in the history
Port list stabilization
  • Loading branch information
PropGit authored May 8, 2017
2 parents ae78b6e + e2d866f commit 5cc423c
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 51 deletions.
2 changes: 1 addition & 1 deletion BlocklyPropClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
# Please verify that the version number in the local about.txt and the
# ./package/win-resources/blocklypropclient-installer.iss matches this.
# -----------------------------------------------------------------------
VERSION = "0.6.2"
VERSION = "0.6.3"


# Enable logging for functions outside of the class definition
Expand Down
4 changes: 2 additions & 2 deletions BlocklyServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ def index(self):
def ports(self):
cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'
self.queue.put((3, 'DEBUG', 'Port list retrieved'))
self.logger.debug('Port list retreived')
self.logger.debug('Port list request received')

ports = self.propellerLoad.get_ports()
if len(ports) > 0:
filtered_ports = []
for port in ports:
self.logger.debug('Port %s discovered.', port)
# Filter out Bluetooth ports; they are risky to open and scan
if ' bt ' not in port.lower() and 'bluetooth' not in port.lower():
self.logger.debug('Port %s discovered.', port)
filtered_ports.append(port)
else:
self.logger.debug("Port %s filtered from the list.", port)
Expand Down
179 changes: 133 additions & 46 deletions PropellerLoad.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,33 @@
module_logger = logging.getLogger('blockly.loader')


# Elements of Port Records (portRec)
prUID = 0
prName = 1
prIP = 2
prMAC = 3
prLife = 4

# Max lifetime+1 for wired (w) and wifi (wf) Port Records to remain without refresh
wMaxLife = 2
wfMaxLife = 4

# Wi-Fi Record Headers
wfNameHdr = "Name: '"
wfIPHdr = "', IP: "
wfMACHdr = ", MAC: "


class PropellerLoad:
loading = False
# COM & WiFi-Name ports list
discovering = False
# "Unique identifier" ports list
ports = []
# Full WiFi ports list
wports = []
# Port Record list- contains wired (UID) and wireless ports (UID, Name, IP, MAC)
portRec = []
# Lists for manipulation
wnames = []
wlnames = []


def __init__(self):
Expand Down Expand Up @@ -50,41 +71,41 @@ def __init__(self):


def get_ports(self):
# Find COM/Wi-Fi serial ports
self.logger.info('Received port list request')

# Return last results if we're currently downloading
if self.loading:
# Search for wired/wireless serial ports
# Return previous results if we're currently downloading to a port or discovering ports
if self.loading or self.discovering:
return self.ports

self.logger.info("Generating ports list")
self.logger.info("Generating new ports list")
# Set discovering flag to prevent interruption
self.discovering = True

# Get COM ports
(success, out, err) = loader(self, ["-P"])
if success:
self.ports = out.splitlines()
self.ports.sort(None, None, False)
else:
self.logger.debug('COM Port request returned %s', err)
try:
# Find wired & wireless serial ports
(success, out, err) = loader(self, ["-P", "-W"])
# Process wired response
if success:
# Update port records (in self.portRec)
updatePorts(self, out.splitlines())
# Extract unique port names (UID; from port records) and sort them alphabetically
wnames = [wiredport[prUID] for wiredport in self.portRec if wiredport[prName] == ""]
wnames.sort(None, None, False)
wlnames = [wirelessport[prUID] for wirelessport in self.portRec if wirelessport[prName] != ""]
wlnames.sort(None, None, False)
# Assign to return list (with wired group on top, wireless group below) in a single step
# to avoid partial results being used by parallel calling process
self.ports = wnames + wlnames
self.logger.debug('Found %s ports', len(self.ports))
else:
# Error with external loader
self.logger.error('Serial port request returned %s', err)
self.ports = []

# Get Wi-Fi ports
(success, out, err) = loader(self, ["-W"])
if success:
# Save Wi-Fi port record(s)
self.wports = out.splitlines()
# Extract Wi-Fi module names (from Wi-Fi records) and sort them
wnames = []
for i in range(len(self.wports)):
wnames.extend([getWiFiName(self.wports[i])])
wnames.sort(None, None, False)
else:
self.logger.debug('WiFi Port request returned %s', err)

# Append Wi-Fi ports to COM ports list
self.ports.extend(wnames)
self.logger.debug('Found %s ports', len(self.ports))
return self.ports

return self.ports
finally:
# Done, clear discovering flag to process other events
self.discovering = False


def download(self, action, file_to_load, com_port):
Expand All @@ -106,24 +127,26 @@ def download(self, action, file_to_load, com_port):
# # launch path is blank; try extracting from argv
# self.appdir = os.path.dirname(os.path.realpath(sys.argv[0]))

# Set command download to RAM or EEPROM and to run afterward download
# Set command to download to RAM or EEPROM and to run afterward download
command = []
if self.loaderAction[action]["compile-options"] != "":
# if RAM/EEPROM compile-option not empty, add it to the list
command.extend([self.loaderAction[action]["compile-options"]])
command.extend(["-r"])

# Add requested port
# Specify requested port
if com_port is not None:
# Find port(s) named com_port
targetWiFi = [l for l in self.wports if isWiFiName(l, com_port)]
if len(targetWiFi) > 0:
# Found Wi-Fi match
self.logger.debug('Requested port %s is at %s', com_port, getWiFiIP(targetWiFi[0]))
# Determine port type and insert into command
wlports = [wirelessport for wirelessport in self.portRec if wirelessport[prName] != ""]
wlnames = [names[prUID] for names in wlports]
if com_port in wlnames:
# Found wireless port match
IPAddr = [ips[prIP] for ips in wlports][wlnames.index(com_port)]
self.logger.debug('Requested port %s is at %s', com_port, IPAddr)
command.extend(["-i"])
command.extend([getWiFiIP(targetWiFi[0]).encode('ascii', 'ignore')])
command.extend([IPAddr.encode('ascii', 'ignore')])
else:
# Not Wi-Fi match, should be COM port
# Not wireless port match, should be wired port
self.logger.debug('Requested port is %s', com_port)
command.extend(["-p"])
command.extend([com_port.encode('ascii', 'ignore')])
Expand Down Expand Up @@ -180,9 +203,73 @@ def loader(self, cmdOptions):
return False, '', 'Exception: OSError'


def updatePorts(self, strings):
# Merge strings into Port Record list
# Ensures unique entries (UIDs), updates existing entries, and removes ancient entries
# Records "age" with each update unless refreshed by a matching port; those older than xMaxLife-1 are considered ancient
for newPort in strings:
if not isWiFiStr(newPort):
# Wired port- search for existing identifier
if newPort in [port[prUID] for port in self.portRec]:
# Found existing- just refresh life
self.portRec[[port[prUID] for port in self.portRec].index(newPort)][prLife] = wMaxLife
else:
# No match- create new entry (UID, n/a, n/a, n/a, MaxLife)
self.portRec.append([newPort, '', '', '', wMaxLife])
else:
# Wireless port- search for its MAC address within known ports
if not getWiFiMAC(newPort) in [port[prMAC] for port in self.portRec]:
# No MAC match- enter as unique port record
enterUniqueWiFiPort(self, newPort)
else:
# Found MAC match- update record as necessary
idx = [port[prMAC] for port in self.portRec].index(getWiFiMAC(newPort))
if self.portRec[idx][prName] == getWiFiName(newPort):
# Name hasn't changed- leave Name and UID, update IP and Life
self.portRec[idx][prIP] = getWiFiIP(newPort)
self.portRec[idx][prLife] = wfMaxLife
else:
# Name has changed- replace entire record with guaranteed-unique entry
self.portRec.pop(idx)
enterUniqueWiFiPort(self, newPort)


# Age records
for port in self.portRec:
port[prLife] = port[prLife] - 1
# Remove ancients
while 0 in [port[prLife] for port in self.portRec]:
self.portRec.pop([port[prLife] for port in self.portRec].index(0))


def enterUniqueWiFiPort(self, newPort):
# Enter newPort as unique port record
# If name matches another, it will be made unique by appending one or more if its MAC digits
# Start with UID = Name
Name = getWiFiName(newPort)+'-'
UID = Name[:-1]
# Prep modifer (MAC address without colons)
Modifier = getWiFiMAC(newPort).replace(":", "")

# Check for unique name (UID)
Size = 1
while UID in [port[prUID] for port in self.portRec]:
# Name is duplicate- modify for unique name
UID = Name + Modifier[-Size:]
Size += 1
if Size == len(Modifier):
# Ran out of digits? Repeat Modifier
Name = UID
Size = 1

# UID is unique, create new entry (UID, Name, IP, MAC, MaxLife)
self.portRec.append([UID, getWiFiName(newPort), getWiFiIP(newPort), getWiFiMAC(newPort), wfMaxLife])



def isWiFiStr(string):
# Return True if string is a Wi-Fi record string, False otherwise
return (string.find(wfNameHdr) > -1) and (string.find(wfIPHdr) > -1) and (string.find(wfMACHdr) > -1)


def isWiFiName(string, wifiName):
Expand All @@ -192,17 +279,17 @@ def isWiFiName(string, wifiName):

def getWiFiName(string):
# Return Wi-Fi Module Name from string, or None if not found
return strBetween(string, "Name: '", "', IP: ")
return strBetween(string, wfNameHdr, wfIPHdr)


def getWiFiIP(string):
# Return Wi-Fi Module IP address from string, or None if not found
return strBetween(string, "', IP: ", ", MAC: ")
return strBetween(string, wfIPHdr, wfMACHdr)


def getWiFiMAC(string):
# Return Wi-Fi Module MAC address from string, or None if not found
return strAfter(string, ", MAC: ")
return strAfter(string, wfMACHdr)


def strBetween(string, startStr, endStr):
Expand All @@ -225,4 +312,4 @@ def strAfter(string, startStr):
if sPos == -1: return None
sPos += len(startStr)
# Return string after
return string[sPos:-1]
return string[sPos:]
2 changes: 1 addition & 1 deletion about.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Version: v0.6.2
Version: v0.6.3

Contributors:
- Michel Lampo
Expand Down
2 changes: 1 addition & 1 deletion package/blocklypropclient-installer.iss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "BlocklyPropClient"
#define MyAppVersion "0.6.2"
#define MyAppVersion "0.6.3"
#define MyAppPublisher "Parallax Inc."
#define MyAppURL "http://blockly.parallax.com/"
#define MyAppExeName "BlocklyPropClient.exe"
Expand Down

0 comments on commit 5cc423c

Please sign in to comment.