diff --git a/BlocklyPropClient.py b/BlocklyPropClient.py index f7c99fc..eb34c14 100644 --- a/BlocklyPropClient.py +++ b/BlocklyPropClient.py @@ -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.5.4" +VERSION = "0.6.2" # Enable logging for functions outside of the class definition diff --git a/BlocklyServer.py b/BlocklyServer.py index 5f5724d..5b378d0 100644 --- a/BlocklyServer.py +++ b/BlocklyServer.py @@ -105,7 +105,7 @@ def load(self, action, binary, extension, comport=None): self.logger.debug('Loading program to device.') - (success, out, err) = self.propellerLoad.load(action, binary_file, comport) + (success, out, err) = self.propellerLoad.download(action, binary_file, comport) self.queue.put((10, 'INFO', 'Application loaded (%s)' % action)) self.logger.info('Application load complete.') diff --git a/PropellerLoad.py b/PropellerLoad.py index 09beba5..24e4ac2 100644 --- a/PropellerLoad.py +++ b/PropellerLoad.py @@ -12,121 +12,216 @@ class PropellerLoad: loading = False + # COM & WiFi-Name ports list ports = [] + # Full WiFi ports list + wports = [] + def __init__(self): + self.logger = logging.getLogger('blockly.loader') self.logger.info('Creating loader logger.') # Find the path from which application was launched # realpath expands to full path if __file__ or sys.argv[0] contains just a filename - self.appdir = os.path.dirname(os.path.realpath(__file__)) + self.appdir = os.path.dirname(os.path.realpath(__file__)) if self.appdir == "" or self.appdir == "/": - # launch path is blank; try extracting from argv + # launch path is blank; try extracting from argv self.appdir = os.path.dirname(os.path.realpath(sys.argv[0])) - self.logger.debug("PropellerLoad.py: Application running from: %s", self.appdir) + self.logger.debug("Application running from: %s", self.appdir) - self.propeller_load_executables = { - "Windows": "/propeller-tools/windows/propeller-load.exe", - "Linux": "/propeller-tools/linux/propeller-load", - "MacOS": "/propeller-tools/mac/propeller-load", - "Darwin": "/propeller-tools/mac/propeller-load" + self.loaderExe = { + "Windows": "/propeller-tools/windows/proploader.exe", + "Linux": "/propeller-tools/linux/proploader", + "MacOS": "/propeller-tools/mac/proploader", + "Darwin": "/propeller-tools/mac/proploader" } - self.load_actions = { - "RAM": {"compile-options": []}, - "EEPROM": {"compile-options": ["-e"]} + self.loaderAction = { + "RAM": {"compile-options": ""}, + "EEPROM": {"compile-options": "-e"} } - if not platform.system() in self.propeller_load_executables: + if not platform.system() in self.loaderExe: self.logger.error('The %s platform is not supported at this time.', platform.system()) - print("Unsupported", platform.system() + " is currently unsupported") + print(platform.system() + " is currently unsupported") exit(1) + def get_ports(self): - self.logger.info('Getting ports') + # Find COM/Wi-Fi serial ports + self.logger.info('Received port list request') + # Return last results if we're currently downloading if self.loading: return self.ports + self.logger.info("Generating ports list") + + # Get COM ports + (success, out, err) = loader(self, ["-P"]) + if success: + self.ports = out.splitlines() + else: + self.logger.debug('COM Port request returned %s', err) + + # 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 + + + def download(self, action, file_to_load, com_port): + # Download application to Propeller + # Set loading flag to prevent interruption + self.loading = True + + try: + # Patch - see if __init__ is back in full operation + if not self.appdir or self.appdir == "" or self.appdir == "/": + self.logger.info('ERROR: LOADER FOLDER NOT FOUND!') + return False, ' ', ' ' +# Patch below removed temporarily during platform testing +# # Patch until we figure out why the __init__ is not getting called +# if not self.appdir or self.appdir == "" or self.appdir == "/": +# # realpath expands to full path if __file__ or sys.argv[0] contains just a filename +# self.appdir = os.path.dirname(os.path.realpath(__file__)) +# if self.appdir == "" or self.appdir == "/": +# # 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 + 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 + 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])) + command.extend(["-i"]) + command.extend([getWiFiIP(targetWiFi[0]).encode('ascii', 'ignore')]) + else: + # Not Wi-Fi match, should be COM port + self.logger.debug('Requested port is %s', com_port) + command.extend(["-p"]) + command.extend([com_port.encode('ascii', 'ignore')]) + + # Add target file + command.extend([file_to_load.name.encode('ascii', 'ignore').replace('\\', '/')]) + + # Download + (success, out, err) = loader(self, command) + + # Return results + return success, out or '', err or '' + + finally: + # Done, clear loading flag to process other events + self.loading = False + + +def loader(self, cmdOptions): + # Launch Propeller Loader with cmdOptions and return True/False, output and error string + # cmdOptions must be a list + try: + # Form complete command line as a list: [path+exe, option {,more_options...} {, filename}] + cmdLine = [self.appdir + self.loaderExe[platform.system()]] + cmdLine.extend(cmdOptions) + + self.logger.debug('Running loader command: %s', cmdLine) + + # Run command if platform.system() == "Windows": - self.logger.info("Enumerating Windows ports") startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + process = subprocess.Popen(cmdLine, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo) + else: + process = subprocess.Popen(cmdLine, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - process = subprocess.Popen([self.appdir + self.propeller_load_executables[platform.system()], "-P"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo) - out, err = process.communicate() - self.logger.debug('Loader complete: Error code %s returned.', err) - self.ports = out.splitlines() - return self.ports + out, err = process.communicate() + + # If error, log extra error detail + if process.returncode: + self.logger.error("Error result: %s - %s", process.returncode, err) + self.logger.debug("Loader response: %s", out) + + if process.returncode == 0: + success = True else: - self.logger.info("Enumerating host ports") + success = False - ports = [port for (port, driver, usb) in list_ports.comports()] - self.logger.debug('Port count: %s', len(ports)) + return success, out or '', err or '' - self.ports = ports - return ports + except OSError as ex: + # Exception; log error and return fail status + self.logger.error("%s", ex.message) + return False, '', 'Exception: OSError' - def load(self, action, file_to_load, com_port): - self.loading = True - # Patch until we figure out why the __init__ is not getting called - if not self.appdir or self.appdir == '': - # realpath expands to full path if __file__ or sys.argv[0] contains just a filename - self.appdir = os.path.dirname(os.path.realpath(__file__)) - if self.appdir == "" or self.appdir == "/": - # launch path is blank; try extracting from argv - self.appdir = os.path.dirname(os.path.realpath(sys.argv[0])) - executable = self.appdir + self.propeller_load_executables[platform.system()] - self.logger.debug('Loader executable path is: %s)', executable) - executing_data = [executable, "-r"] - executing_data.extend(self.load_actions[action]["compile-options"]) - self.logger.debug('Loader commandline is: %s', executing_data) - if com_port is not None: - self.logger.info("Talking to com port.") - executing_data.append("-p") - executing_data.append(com_port.encode('ascii', 'ignore')) - executing_data.append(file_to_load.name.encode('ascii', 'ignore').replace('\\', '/')) - print(executing_data) - self.logger.info("Executing process %s", executing_data) +def isWiFiName(string, wifiName): +# Return True if string contains Wi-Fi Module record named wifiName + return getWiFiName(string) == wifiName - try: - if platform.system() == "Windows": - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - process = subprocess.Popen(executing_data, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo) - else: - process = subprocess.Popen(executing_data, stdout=subprocess.PIPE, stderr=subprocess.PIPE) +def getWiFiName(string): +# Return Wi-Fi Module Name from string, or None if not found + return strBetween(string, "Name: '", "', IP: ") - out, err = process.communicate() - self.logger.info("Load result is: %s", process.returncode) - self.logger.debug("Load error string: %s", err) - self.logger.debug("Load output string: %s", out) - if process.returncode == 0: - success = True - else: - success = False +def getWiFiIP(string): +# Return Wi-Fi Module IP address from string, or None if not found + return strBetween(string, "', IP: ", ", MAC: ") + + +def getWiFiMAC(string): +# Return Wi-Fi Module MAC address from string, or None if not found + return strAfter(string, ", MAC: ") - self.loading = False - return success, out or '', err or '' - except OSError as ex: - self.logger.error("%s", ex.message) +def strBetween(string, startStr, endStr): +# Return substring from string in between startStr and endStr, or None if no match + # Find startStr + sPos = string.find(startStr) + if sPos == -1: return None + sPos += len(startStr) + # Find endStr + ePos = string.find(endStr, sPos) + if ePos == -1: return None + # Return middle + return string[sPos:ePos] -def resource_path(relative): - return os.path.join( - os.environ.get( - "_MEIPASS2", - os.path.abspath(".") - ), - relative - ) +def strAfter(string, startStr): +# Return substring from string after startStr, or None if no match + # Find startStr + sPos = string.find(startStr) + if sPos == -1: return None + sPos += len(startStr) + # Return string after + return string[sPos:-1] diff --git a/about.txt b/about.txt index da5a5f6..b366a8a 100644 --- a/about.txt +++ b/about.txt @@ -1,4 +1,4 @@ -Version: v0.5.4 +Version: v0.6.2 Contributors: - Michel Lampo diff --git a/package/blocklypropclient-installer.iss b/package/blocklypropclient-installer.iss index 8d3f61b..2a3d85a 100644 --- a/package/blocklypropclient-installer.iss +++ b/package/blocklypropclient-installer.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "BlocklyPropClient" -#define MyAppVersion "0.5.4" +#define MyAppVersion "0.6.2" #define MyAppPublisher "Parallax Inc." #define MyAppURL "http://blockly.parallax.com/" #define MyAppExeName "BlocklyPropClient.exe" diff --git a/propeller-tools/linux/propeller-load b/propeller-tools/linux/propeller-load deleted file mode 100755 index 318db3f..0000000 Binary files a/propeller-tools/linux/propeller-load and /dev/null differ diff --git a/propeller-tools/linux/proploader b/propeller-tools/linux/proploader new file mode 100755 index 0000000..9e63489 Binary files /dev/null and b/propeller-tools/linux/proploader differ diff --git a/propeller-tools/mac/propeller-load b/propeller-tools/mac/propeller-load deleted file mode 100755 index 027e9c6..0000000 Binary files a/propeller-tools/mac/propeller-load and /dev/null differ diff --git a/propeller-tools/mac/proploader b/propeller-tools/mac/proploader new file mode 100755 index 0000000..9680379 Binary files /dev/null and b/propeller-tools/mac/proploader differ diff --git a/propeller-tools/windows/propeller-load.exe b/propeller-tools/windows/propeller-load.exe deleted file mode 100644 index 8a35f48..0000000 Binary files a/propeller-tools/windows/propeller-load.exe and /dev/null differ diff --git a/propeller-tools/windows/proploader.exe b/propeller-tools/windows/proploader.exe new file mode 100755 index 0000000..70da6d9 Binary files /dev/null and b/propeller-tools/windows/proploader.exe differ