Browse Source

modified for new backend

curiousmuch 6 years ago
parent
commit
fe904ef411
12 changed files with 1125 additions and 1000 deletions
  1. 209 0
      .vscode/launch.json
  2. 0 21
      IdeasXMessages_pb2.py
  3. 0 463
      IdeasXWSCBackend.py
  4. 0 459
      IdeasXWSCView.py
  5. 0 56
      ParsingTools.py
  6. 17 0
      README.md
  7. 0 1
      icon/network/Archive/arc/network-wired-offline-symbolic.svg
  8. 146 0
      wsc_client.py
  9. 83 0
      wsc_device.py
  10. 381 0
      wsc_device_encoder.py
  11. 113 0
      wsc_tools.py
  12. 176 0
      wsc_ui.py

+ 209 - 0
.vscode/launch.json

@@ -0,0 +1,209 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+    
+        {
+            "name": "Python",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": true,
+            "pythonPath": "${config:python.pythonPath}",
+            "program": "${file}",
+            "cwd": "${workspaceRoot}",
+            "env": {},
+            "envFile": "${workspaceRoot}/.env",
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit",
+                "RedirectOutput"
+            ]
+        },
+        {
+            "name": "PySpark",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": true,
+            "osx": {
+                "pythonPath": "${env:SPARK_HOME}/bin/spark-submit"
+            },
+            "windows": {
+                "pythonPath": "${env:SPARK_HOME}/bin/spark-submit.cmd"
+            },
+            "linux": {
+                "pythonPath": "${env:SPARK_HOME}/bin/spark-submit"
+            },
+            "program": "${file}",
+            "cwd": "${workspaceRoot}",
+            "env": {},
+            "envFile": "${workspaceRoot}/.env",
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit",
+                "RedirectOutput"
+            ]
+        },
+        {
+            "name": "Python Module",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": true,
+            "pythonPath": "${config:python.pythonPath}",
+            "module": "module.name",
+            "cwd": "${workspaceRoot}",
+            "env": {},
+            "envFile": "${workspaceRoot}/.env",
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit",
+                "RedirectOutput"
+            ]
+        },
+        {
+            "name": "Integrated Terminal/Console",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": true,
+            "pythonPath": "${config:python.pythonPath}",
+            "program": "${file}",
+            "cwd": "",
+            "console": "integratedTerminal",
+            "env": {},
+            "envFile": "${workspaceRoot}/.env",
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit"
+            ]
+        },
+        {
+            "name": "External Terminal/Console",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": true,
+            "pythonPath": "${config:python.pythonPath}",
+            "program": "${file}",
+            "cwd": "",
+            "console": "externalTerminal",
+            "env": {},
+            "envFile": "${workspaceRoot}/.env",
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit"
+            ]
+        },
+        {
+            "name": "Django",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": true,
+            "pythonPath": "${config:python.pythonPath}",
+            "program": "${workspaceRoot}/manage.py",
+            "cwd": "${workspaceRoot}",
+            "args": [
+                "runserver",
+                "--noreload",
+                "--nothreading"
+            ],
+            "env": {},
+            "envFile": "${workspaceRoot}/.env",
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit",
+                "RedirectOutput",
+                "DjangoDebugging"
+            ]
+        },
+        {
+            "name": "Flask",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": false,
+            "pythonPath": "${config:python.pythonPath}",
+            "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter",
+            "cwd": "${workspaceRoot}",
+            "env": {
+                "FLASK_APP": "${workspaceRoot}/quickstart/app.py"
+            },
+            "args": [
+                "run",
+                "--no-debugger",
+                "--no-reload"
+            ],
+            "envFile": "${workspaceRoot}/.env",
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit",
+                "RedirectOutput"
+            ]
+        },
+        {
+            "name": "Flask (old)",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": false,
+            "pythonPath": "${config:python.pythonPath}",
+            "program": "${workspaceRoot}/run.py",
+            "cwd": "${workspaceRoot}",
+            "args": [],
+            "env": {},
+            "envFile": "${workspaceRoot}/.env",
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit",
+                "RedirectOutput"
+            ]
+        },
+        {
+            "name": "Pyramid",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": true,
+            "pythonPath": "${config:python.pythonPath}",
+            "cwd": "${workspaceRoot}",
+            "env": {},
+            "envFile": "${workspaceRoot}/.env",
+            "args": [
+                "${workspaceRoot}/development.ini"
+            ],
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit",
+                "RedirectOutput",
+                "Pyramid"
+            ]
+        },
+        {
+            "name": "Watson",
+            "type": "python",
+            "request": "launch",
+            "stopOnEntry": true,
+            "pythonPath": "${config:python.pythonPath}",
+            "program": "${workspaceRoot}/console.py",
+            "cwd": "${workspaceRoot}",
+            "args": [
+                "dev",
+                "runserver",
+                "--noreload=True"
+            ],
+            "env": {},
+            "envFile": "${workspaceRoot}/.env",
+            "debugOptions": [
+                "WaitOnAbnormalExit",
+                "WaitOnNormalExit",
+                "RedirectOutput"
+            ]
+        },
+        {
+            "name": "Attach (Remote Debug)",
+            "type": "python",
+            "request": "attach",
+            "localRoot": "${workspaceRoot}",
+            "remoteRoot": "${workspaceRoot}",
+            "port": 3000,
+            "secret": "my_secret",
+            "host": "localhost"
+        }
+    ]
+}

File diff suppressed because it is too large
+ 0 - 21
IdeasXMessages_pb2.py


+ 0 - 463
IdeasXWSCBackend.py

@@ -1,463 +0,0 @@
-'''
-Title: IdeasXDatabaseManager Class
-Author: Tyler Berezowsky 
-Description: 
-
-This class requires the following functionality: 
-
-1) Connect to the IdeasX system (MQTT Client) 
-    - Connect using the devices MAC Address as the Client ID 
-    - Autoreconnect if the device failts 
-    - The abililty to start a broker in a seperate thread if no broker is available 
-    - The ability to store settings in a SQLite File or setting .txt file. 
-2) The ability to induce a system wide keypress in the following systems: 
-    - Windows 
-    - Mac 
-    - Linux
-3) Create a table in memory of the IdeasX devices currently in the system
-4) Parse IdeasX messages types given nothing more than a protofile 
-5) Subscribe to IdeasX devices 
-6) Invoke keystrokes if proper messages in a command is sent. 
-
-TODO: Develop logger class for print statements 
-TODO: Fix subscribe / unsubscribe functionality
-TODO: Implement Queue which will send signal when subscription ACK is RX
-TODO: Expand exceptions to only parser not general actions
-'''
-import sys, time, collections
-from ParsingTools import ParsingTools
-from PyQt5.QtCore import QObject, pyqtSignal, QSettings
-import paho.mqtt.client as mqtt  
-
-try:     
-    import IdeasXMessages_pb2 as IdeasXMessages
-except ImportError: 
-    print("The python classes for IdeasX are missing. Try running the Makefile in" +
-            "ideasX-messages.")
-
-    
-class IdeasXWSCNetworkThread(QObject): 
-    
-    # define Qt signals (I don't understand why this is here) 
-    encoderUpdate = pyqtSignal([dict], name='encoderUpdate')
-    networkStatus = pyqtSignal([str], name='networkStatus')
-    networkUpdate = pyqtSignal([str], name='networkUpdate')
-    settingsError = pyqtSignal([str], name='settingsError')
-    
-    def __init__(self, settingFile=None, clientID = None, debug=True, mqttdebug=True):
-        super(IdeasXWSCNetworkThread, self).__init__()
-        # Private Class Flags and Variables
-        self.__clientID = clientID
-        self.__settingFile = settingFile
-        self.__debug = debug 
-        self.__mqttDebug = mqttdebug 
-        self.__errorIndex = 0 
-        self.__refreshCb = None
-        
-        self.__org = 'IdeasX'
-        self.__app = 'Workstation-Client'
-        
-        # MQTT Topics 
-        self.__DEVICETYPE = ["/encoder/+", "/actuator/+"]
-        self.__COMMANDTOPIC = "/command"
-        self.__DATATOPIC = "/data"
-        self.__HEALTHTOPIC = "/health"
-                
-        # Data Structure for Encoders / Actuators 
-        self.encoders = {}
-        self.subscribedEncoders = []
-        
-        self.actuators = {}
-        self.subscribedActuators = []
-        
-        # IdeasX Parsers
-        self.__healthParser = IdeasXMessages.HealthMessage()
-        self.__dataParser = IdeasXMessages.DataMessage()
-        self.__commandParser = IdeasXMessages.CommandMessage()
-        self.__parserTools = ParsingTools()
-        self.keyEmulator = IdeasXKeyEmulator()
-        
-        # MQTT Client Object
-        self._mqttc = mqtt.Client(self.__clientID, clean_session=True, userdata=None, protocol='MQTTv311')
-        
-        # Setup Callback Functions for each device type
-        for device in self.__DEVICETYPE:
-            self._mqttc.message_callback_add(device+self.__HEALTHTOPIC, self.mqtt_on_health)
-            self._mqttc.message_callback_add(device+self.__DATATOPIC, self.mqtt_on_data)
-            self._mqttc.message_callback_add(device+self.__COMMANDTOPIC, self.mqtt_on_command)       
-        
-        self._mqttc.on_connect = self.mqtt_on_connect
-        self._mqttc.on_disconnect = self.mqtt_on_disconnect 
-        #self._mqttc.on_subscribe = self.mqtt_on_subscribe 
-        #self._mqttc.on_unsubscribe = self.mqtt_on_unsubscribe
-        
-        if self.__mqttDebug: 
-            self._mqttc.on_log = self.mqtt_on_log 
-
-    '''
-     MQTT Callback Functions
-    '''
-
-    def mqtt_on_connect(self, mqttc, backend_data, flags, rc): 
-        if rc == 0: 
-            self.printInfo('Connected to %s: %s' % (mqttc._host, mqttc._port))
-            self.networkStatus.emit("Connected to %s: %s" % (mqttc._host, mqttc._port))
-        else: 
-            self.printInfo('rc: ' + str(rc))
-            self.networkStatus.emit('Connection Failure (rc: ' +str(rc))
-        self.printLine()
-
-    def mqtt_on_disconnect(self, mqttc, backend_data, rc):
-        if self.__debug: 
-            if rc != 0: 
-                self.printError("Client disconnected and its a mystery why!")
-                self.networkStatus.emit("Uh No! WSC was disconnected!")
-            else: 
-                self.printInfo("Client successfully disconnected.") 
-                self.networkStatus.emit("Uh No! WSC was disconnected!")
-            self.printLine()   
-            
-    def mqtt_on_log(self, mqttc, backend_data, level, string):
-        print(string)
-        self.printLine()         
-                        
-    def mqtt_on_data(self, mqttc, backend_data, msg):
-        self.printInfo("Data Message")
-        self.printLine()
-        try: 
-            self.__dataParser.ParseFromString(msg.payload)
-            print("GPIO States: " + bin(self.__dataParser.button))
-            self.keyEmulator.emulateKey( self.__parserTools.getModuleIDfromTopic(msg.topic),self.__dataParser.button)
-        except Exception as ex: 
-            self.printError("Failure to parse message")
-            if self.__debug:
-                print("Raw Message: %s" %msg.payload)
-            template = "An exception of type {0} occured. Arguments:\n{1!r}"
-            message = template.format(type(ex).__name__, ex.args)
-            print(message)
-            
-            self.printLine()
-            
-            
-    def mqtt_on_health(self, mqttc, backend_data, msg):
-        self.printInfo("Health Message")
-        self.printLine()
-        try: 
-            self.__healthParser.ParseFromString(msg.payload)
-            macID = self.__parserTools.macToString(self.__healthParser.module_id)
-            
-            if self.__healthParser.alive:
-                temp_list = []
-                for field in self.__healthParser.ListFields():
-                    temp_list.append((field[0].name, field[1]))                     # copy health message fields in to list 
-                temp_list.append(('time', time.time()))                             # add time the message was RX to the end
-                self.encoders[macID] = collections.OrderedDict(temp_list)           # store into a ordered dictionary
-                self.encoderUpdate.emit(self.getDevices())                          # send signal to the GUI
-            else:
-                try: 
-                    self.encoders.pop(macID)                                        # if the encoder isn't alive attempt to remove from dictionary
-                    self.encoderUpdate.emit()                                       # send signal to GUI
-                except KeyError: 
-                    self.printError("Encoder ID " +macID+" is not stored")
-            
-            if self.__debug:
-                for encoder, fields in zip(self.encoders.keys(), self.encoders.values()): 
-                    print(str(encoder) +" : "+ str(fields))
-                self.printLine()
-        except Exception as e:
-            print(e) 
-            self.printError("Error: Failure to parse message")
-            if self.__debug:
-                print("Raw Message: %s" %msg.payload)
-            self.printLine()
-            try: 
-                deviceID = msg.topic.split('/')[2]
-                self.encoders.pop(deviceID)
-                self.encoderUpdate.emit(self.getDevices())
-                self.deactivateEncoder(deviceID)
-            except: 
-                print("This is a fucking joke anyway")
-
-    def mqtt_on_command(self, mqttc, backend_data, msg):
-        pass
-        
-    def cmdStartWorkstationClient(self, ip="server.ideasX.tech", port=1883, keepAlive=60):     
-        self.ip = ip 
-        self.port = port 
-        self.keepAlive = keepAlive 
-        
-        self.printLine()
-        self.printInfo("Starting Workstation Client (WSC)")
-        self.printLine()
-        
-        try:  
-            self._mqttc.connect(self.ip, self.port, self.keepAlive)      # connect to broker
-            
-            for device in self.__DEVICETYPE:
-                self._mqttc.subscribe(device + self.__HEALTHTOPIC, 1)
-            self._mqttc.loop_forever() # need to use blocking loop otherwise python will kill process
-        except:
-            self.printError("There was a fucking mistake here.")
-            sys.exit(1)
-            
-    def guiStartWorkstationClient(self, ip=None, port=1883, keepAlive=60):
-        
-        self.keepAlive = keepAlive
-        
-        if ip == None:
-            settings = QSettings(self.__org, self.__app) 
-            settings.beginGroup('Broker')
-            self.ip = settings.value('NetworkBroker', 'ideasx.duckdns.org')
-            self.port = settings.value('NetworkPort', 1883)
-            #self.__LocalBroker = settings.value('LocalBroker', '10.42.0.1')
-            self.__LocalPort = settings.value('LocalPort', 1883)
-            settings.endGroup()
-        else:
-            self.printLine()
-            self.printInfo("Loading hardcoded defaults")
-            self.printLine()
-            self.ip = ip 
-            self.port = port 
-        
-        self.printLine()
-        self.printInfo("Starting Workstation Client (WSC)")
-        self.printLine()
-        
-        try: 
-            self._mqttc.connect(self.ip, int(self.port), self.keepAlive)
-            for device in self.__DEVICETYPE:
-                self._mqttc.subscribe(device + self.__HEALTHTOPIC, 0)
-            self._mqttc.loop_start() # start MQTT Client Thread 
-        except: 
-            self.printError("There was a fucking mistake here.")
-            self.networkStatus.emit("Oh-no! Broker settings are incorrect or there is a network failure")
-#             sys.exit(1)
-            
-    def guiRestartWSC(self):
-        self.killWSC()
-        self.networkUpdate.emit("Restarting WSC...")
-        self.guiStartWorkstationClient()
-            
-    def restartWSC(self):
-        self.printInfo("This really doesn't do anything")
-    
-    def killWSC(self):
-        self._mqttc.loop_stop()
-        self.printInfo("Murdered MQTT thread.")
-        
-    def getDevices(self):
-        return self.encoders
-            
-    def activateEncoder(self, deviceMACAddress, deviceType=None):
-        '''
-        Subscribe to device's data topic and send activate command if device 
-        is not active. 
-        
-        * Currently does not confirm subscribe is successful 
-        * Currently does not send the activate command as it does not exist
-        
-        deviceType = str 
-        deviceMACAddress = str(MAC_ID)
-        '''
-        if deviceMACAddress not in self.encoders.keys():
-            self.printError("Device " + deviceMACAddress + " is not currently in the IdeasX system.")
-        else:
-            if deviceType == None: 
-                deviceDataTopic = "/encoder/" + deviceMACAddress + self.__DATATOPIC
-            else: 
-                deviceDataTopic = deviceType + deviceMACAddress + self.__DATATOPIC
-                 
-            self._mqttc.subscribe(deviceDataTopic, 1)
-            self.subscribedEncoders.append(deviceMACAddress)
-            if self.__debug: 
-                self.printInfo("Device " + deviceMACAddress + " data topic was subscribed")
-                    
-    def deactivateEncoder(self, deviceMACAddress, deviceType=None, forceAction=False):
-        '''
-        Unsubscribe from device's data topic and send deactive command if no other WSC are using device. 
-        
-        * Currently does not confirm unsubscribe is successful 
-        * Currently does not send the deactive command as it does not exist and I don't know how to sync that shit. 
-        '''
-        if (deviceMACAddress in self.encoders.keys()) or (forceAction): 
-            if deviceType == None: 
-                deviceDataTopic = self.__DEVICETYPE[0] + deviceMACAddress + self.__DATATOPIC
-            else: 
-                deviceDataTopic = deviceType + deviceMACAddress + self.__DATATOPIC 
-            self._mqttc.unsubscribe(deviceDataTopic)
-            self.subscribedEncoders.remove(deviceMACAddress)
-            if self.__debug: 
-                self.printInfo("Device " + deviceMACAddress + " data topic was unsubscribed")
-        else: 
-            self.printError("Device " + deviceMACAddress + " is not currently in the IdeasX System")  
-            
-    def resetDevice(self, deviceMACAddress, deviceType=None):
-        self.__commandParser.command = self.__commandParser.RESTART
-        self._mqttc.publish(self.__DEVICETYPE[0][:-1] + deviceMACAddress + self.__COMMANDTOPIC,
-                            self.__commandParser.SerializeToString().decode('utf-8') ,
-                            qos=1,
-                            retain=False)       
-        self.networkUpdate.emit("Sent reset command to device " + deviceMACAddress)
-
-    def shutdownDevice(self, deviceMACAddress, deviceType=None):
-        self.__commandParser.command = self.__commandParser.SHUT_DOWN
-        self._mqttc.publish(self.__DEVICETYPE[0][:-1] + deviceMACAddress + self.__COMMANDTOPIC,
-                            self.__commandParser.SerializeToString().decode('utf-8') ,
-                            qos=1,
-                            retain=False)
-        self.networkUpdate.emit("Sent shutdown command to device " + deviceMACAddress)
-        
-    def updateDevice(self, deviceMACAddress, deviceType=None):
-        self.__commandParser.command = self.__commandParser.OTA_UPDATE
-        self._mqttc.publish(self.__DEVICETYPE[0][:-1] + deviceMACAddress + self.__COMMANDTOPIC,
-                    self.__commandParser.SerializeToString().decode('utf-8') ,
-                    qos=1,
-                    retain=False)
-        self.networkUpdate.emit("Sent OTA update request to device " + deviceMACAddress)
-        
-    #def configureIMU(self, deviceMACAddress, deviceType=None):
-        
-        
-    def printLine(self):
-        print('-'*70)
-        
-    def printError(self, errorStr):
-        self.__errorIndex = self.__errorIndex + 1
-        print("WSC Error #" + str(self.__errorIndex) + ": " + errorStr)
-    
-    def printInfo(self, msgStr):
-        print("WSC: " + msgStr)
-        
-
-from pykeyboard import PyKeyboard
-        
-class IdeasXKeyEmulator():
-    def __init__(self):
-        self.__system = sys.platform
-        self.printInfo("Detected system is " + self.__system) 
-        self.__k = PyKeyboard() 
-        self.switchOne = 0
-        self.switchTwo = 1
-        self.switchAdaptive = 2
-        self.__assignedKeys = {'default': {self.switchOne: ["1", True, 0], 
-                                           self.switchTwo: ["2", True, 0], 
-                                           self.switchAdaptive: ["3", False, 0]}}
-        self.__activeEncoders = []
-        
-    def activateEncoder(self, encoder):
-        if encoder not in self.__activeEncoders: 
-            self.__activeEncoders.append(encoder)
-
-    def deactivateEncoder(self, encoder):
-        if encoder in self.__activeEncoders:
-            self.__activeEncoders.pop(encoder)
-        
-    def assignKey(self, encoder, switch, key, active=True):
-        if switch not in [self.switchOne, self.switchTwo, self.switchAdaptive]:  
-            raise ValueError("Must be IdeasXKeyEmulator() provided switch")
-        
-        if encoder not in list(self.__assignedKeys.keys()): 
-            self.__assignedKeys[encoder] = self.__assignedKeys['default'].copy()
-        
-        print(self.__assignedKeys)
-            
-        self.__assignedKeys[encoder][switch] = [key, active]
-        if active == False: 
-            self.__k.release_key(key)
-            
-    def getAssignedKeys(self, encoder):
-        if encoder not in self.__assignedKeys.keys(): 
-            encoder = 'default'
-        return self.__assignedKeys[encoder]
-            
-    def getAssignedKey(self, encoder, switch):
-        if encoder not in self.__assignedKeys.keys(): 
-            encoder = 'default'
-        return self.__assignedKeys[encoder][switch]
-    
-    def getKeyDatabase(self):
-        return self.__assignedKeys 
-    
-    def getDefaultKeyEntry(self):
-        return self.__assignedKeys['default']
-    
-    def setKeyDatabase(self, db):
-        self.__assignedKeys = db
-        
-    def emulateKey(self, encoder, buttonPayload, deviceType=None):
-        '''
-             This is horrible and needs to be improved
-        '''
-        if encoder in self.__activeEncoders or True: 
-            if encoder not in self.__assignedKeys.keys(): 
-                encoder = 'default'
-            assignedKeys = self.__assignedKeys[encoder]
-            
-            for switch in [self.switchOne, self.switchTwo, self.switchAdaptive]:  
-                if (buttonPayload&(1<<switch)!=0):
-                    if assignedKeys[switch][1]:
-                        self.__k.tap_key(assignedKeys[switch][0])
-                #else: 
-                    #self.__k.release_key(assignedKeys[switch][0])
-
-            
-    def printInfo(self, msg):
-        print("EM: " + msg)    
-        
-    
-    #def emulatePress(self, buttonPayload):
-        
-        
-        
-if __name__ == "__main__": 
-    Host = "ideasx.duckdns.org"
-#    Host = "192.168.0.101"
-#    Host = "10.42.0.1"
-    Port = 1883 
-    KeepAlive = 30
-    msgFlag = False;     
-    deviceID = None; 
-    cmdPayload = None; 
-    cmdArg = None;
-    cmdTest = False; 
-#     
-#     encodeId = '23:34'
-#     
-#     km = IdeasXKeyEmulator()
-#     
-#     km.activateEncoder(encodeId)
-#     km.emulateKey(encodeId, 1)
-#     time.sleep(0.1)
-#     km.emulateKey(encodeId, 0) 
-#     time.sleep(0.1)
-# 
-#     km.emulateKey(encodeId, 2)
-#     time.sleep(0.1)
-#     km.emulateKey(encodeId, 0)
-#     time.sleep(0.1)
-#     km.emulateKey(encodeId, 4)
-#     time.sleep(0.1)
-#     km.emulateKey(encodeId, 0)
-    
-
-       
-    wsc = IdeasXWSCNetworkThread()
-         
-    if cmdTest: 
-        wsc.cmdStartWorkstationClient(Host, Port, KeepAlive)
-    else: 
-        wsc.guiStartWorkstationClient(Host, Port, KeepAlive)
-        time.sleep(3)
-        
-        (result, mid) = wsc._mqttc.subscribe('/encoders/18:fe:34:d2:6f:68/health', qos=0)
-        print(result, mid)
-        (result, mid) = wsc._mqttc.subscribe('/encoders/18:fe:34:d2:6f:68/health', qos=0)
-        print(result, mid)
-        wsc.activateEncoder('18:fe:34:d2:6f:68')
-#         print(wsc.subscribedEncoders)
-#         time.sleep(2)
-#         wsc.deactivateEncoder('18:fe:34:d2:6f:68')
-#         print(wsc.subscribedEncoders)
-        time.sleep(10)
-        wsc.killWSC()
-        

+ 0 - 459
IdeasXWSCView.py

@@ -1,459 +0,0 @@
-
-'''
-IdeasXWSCView.py 
-
-TODO: Fix activate / deactivate functionality [done]
-TODO: Fix subscribe / unsubscribe functionality in backend [done]
-TODO: Develop queue message thinger for sub unsub with feedback from backend
-
-TODO: Develop structure for creating alias for device
-TODO: Develop structure for saving information into backend
-
-'''
-import sys
-import time
-import sip
-from PyQt5 import QtCore, QtGui, QtWidgets
-from pyqt.mainwindow2 import Ui_MainWindow
-from pyqt.ideasxdevice import Ui_IdeasXDevice
-from IdeasXWSCBackend import IdeasXWSCNetworkThread
-from pyqt.encoderconfigurationdialog import Ui_SwitchConfigDialog
-from pyqt.devicedialog import Ui_Dialog
-from ParsingTools import ParsingTools
-
-
-class IdeasXSwitchDialog(QtWidgets.QDialog):
-    
-    def __init__(self, switch, assignedKey): 
-        super(IdeasXSwitchDialog, self).__init__()
-        self.__ui = Ui_SwitchConfigDialog()
-        self.__ui.setupUi(self)
-        self.__ui.buttonApply.clicked.connect(self.submitOnClose)
-        self.key = assignedKey[0]
-        self.enable = assignedKey[1]
-        self.switch = switch
-        
-        self.__ui.checkSwitchEnable.setChecked(self.enable)
-        self.__ui.lineSwitchKey.setText(self.key)
-        
-    def submitOnClose(self):
-        self.key = self.__ui.lineSwitchKey.text() 
-        self.enable = self.__ui.checkSwitchEnable.isChecked()
-        self.accept()
-        
-class IdeasXDeviceInformationDialog(QtWidgets.QDialog):
-    newDeviceName = QtCore.pyqtSignal(['QString'], name='newDeviceName')
-    
-    def __init__(self):
-        super(IdeasXDeviceInformationDialog, self).__init__()
-        self.__ui = Ui_Dialog()
-        self.__ui.setupUi(self)
-        self.__ui.lineAlias.textEdited.connect(lambda: self.newDeviceName.emit(self.__ui.lineAlias.text()))
-        
-    def updateDisplay(self, encoder):
-        self.__ui.labelBatteryCapacity.setText(str(encoder['soc']))
-        self.__ui.labelBatteryVoltage.setText(str(encoder['vcell']))
-        self.__ui.labelLowBattery.setText(str(encoder['lb']))
-        self.__ui.labelChargeState.setText('N/A')
-        
-        self.__ui.labelActiveFlag.setText('N/A')
-        self.__ui.labelAliveFlag.setText(str(encoder['alive'])) 
-        self.__ui.labelFirmwareVersion.setText(str(encoder['firmware_version'])) 
-        self.__ui.labelHardwareVersion.setText(str(encoder['hardware_version'])) 
-        self.__ui.labelOTAFlag.setText(str(encoder['ota']))
-        self.__ui.labelROMSlot.setText(str(encoder['rom'])) 
-        
-        self.__ui.labelMAC.setText(str(encoder['module_id']))
-        self.__ui.labelRSSI.setText(str(encoder['rssi'])) 
-        self.__ui.labelSSID.setText(encoder['ssid']) 
-        
-    
-
-class IdeasXDeviceManager():
-    def __init__(self, deviceClass, wsc): 
-        self.__devices = {} 
-        self.__deviceClass = deviceClass
-        self.__deviceLayout = QtWidgets.QVBoxLayout()
-        self.__deviceLayout.setAlignment(QtCore.Qt.AlignTop)
-        self.__deviceLayout.setContentsMargins(9, 0, 9, 0)
-        self.__deviceLayout.setSpacing(0)
-        self.__wsc = wsc
-          
-    def refreshDevices(self, devices):
-        for deviceMAC in list(devices.keys()): 
-            if deviceMAC in self.__devices.keys(): 
-                print("Updating Device")
-                self.__devices[deviceMAC].updateDevice(devices[deviceMAC])
-            else: 
-                print("Adding Device")
-                self.__devices[deviceMAC] = self.__deviceClass(devices[deviceMAC], self.__wsc)
-                self.__deviceLayout.addWidget(self.__devices[deviceMAC])
-        for deviceMAC in list(self.__devices.keys()): 
-            if deviceMAC not in devices.keys(): 
-                print("Removing Device")
-                self.removeDevice(deviceMAC)
-                
-    def removeDevice(self, deviceMAC): 
-        self.__deviceLayout.removeWidget(self.__devices[deviceMAC])
-        sip.delete(self.__devices[deviceMAC])
-        self.__devices.pop(deviceMAC)
-        
-    def returnLayout(self):
-        return self.__deviceLayout
-        
-    def filterDevices(self, searchPhase):
-        print("This currently doesn't work")
-        
-    def printDevices(self):
-        print(self.__devices)
-
-class IdeasXEncoder(QtWidgets.QWidget):
-    sendCommand = QtCore.pyqtSignal(['QString'], name='sendCommand')
-    activateDevice = QtCore.pyqtSignal(['QString', 'QString'], name='deactivateDevice')
-    deactivateDevice = QtCore.pyqtSignal(['QString', 'QString'], name='activateDevice')
-    __pathToIcon = {'network': './icon/network/', 
-                     'battery': './icon/battery/', 
-                     'battery_charging': './icon/battery/',
-                     'switch': './icon/switch/'
-                    }
-    __icon = {'network': ['network-wireless-offline-symbolic.png',
-                               'network-wireless-signal-weak-symbolic.png',
-                               'network-wireless-signal-ok-symbolic.png',
-                               'network-wireless-signal-good-symbolic.png',
-                               'network-wireless-signal-excellent-symbolic.png'],
-                    'battery': ['battery-empty-symbolic.png', 
-                                'battery-caution-symbolic.png',
-                                'battery-low-symbolic.png', 
-                                'battery-good-symbolic.png',
-                                'battery-full-symbolic.png'],
-                    'battery_charging': ['battery-empty-charging-symbolic.png', 
-                                         'battery-caution-charging-symbolic.png', 
-                                         'battery-low-charging-symbolic.png', 
-                                         'battery-good-charging-symbolic.png', 
-                                         'battery-full-charged-symbolic.png'],
-                    'switch': ['switch-one-enabled.png', 
-                               'switch-one-disabled.png', 
-                               'switch-two-enabled.png', 
-                               'switch-two-disabled.png', 
-                               'switch-adaptive-enabled.png', 
-                               'switch-adaptive-disabled.png']
-                   }
-    __deviceType = '/encoder/'
-
-    
-    def __init__(self, encoder, wsc): 
-        self.__deviceName = None
-        self.__wsc = wsc
-        
-        # Setup UI components
-        super(IdeasXEncoder, self).__init__()
-        self.__ui = Ui_IdeasXDevice()
-        self.__ui.setupUi(self)
-        self._parserTools = ParsingTools()
-        self.updateDevice(encoder)        
-        self.updateSwitchIcons()
-        
-        # Setup Signals
-        self.setupMenu()
-        self.__ui.buttonActivate.clicked.connect(self.activateEncoder)
-        self.__ui.buttonSwitchOne.clicked.connect(lambda: self.openSwitchDialog(self.__wsc.keyEmulator.switchOne,
-                                                                                 self.__wsc.keyEmulator.getAssignedKey(self.__strModuleID, self.__wsc.keyEmulator.switchOne)))
-        self.__ui.buttonSwitchTwo.clicked.connect(lambda: self.openSwitchDialog(self.__wsc.keyEmulator.switchTwo,
-                                                                                self.__wsc.keyEmulator.getAssignedKey(self.__strModuleID, self.__wsc.keyEmulator.switchTwo)))
-        self.activateDevice.connect(self.__wsc.activateEncoder)
-        self.deactivateDevice.connect(self.__wsc.deactivateEncoder)
-        
-    def openDeviceInformation(self):
-        dialog = IdeasXDeviceInformationDialog()
-        dialog.updateDisplay(self.__wsc.encoders[self.__strModuleID])
-        dialog.newDeviceName.connect(self.setDeviceAlisas)
-        dialog.exec()
-    
-    def openSwitchDialog(self, switch, assignedKey):
-        dialog = IdeasXSwitchDialog(switch, assignedKey)
-        if dialog.exec_():                
-            if dialog.key != None and len(dialog.key) == 1: 
-                self.__wsc.keyEmulator.assignKey(self.__strModuleID, dialog.switch, dialog.key, dialog.enable)
-                self.updateSwitchIcons()
-                        
-    def setupSwitchIcon(self, path):
-        icon = QtGui.QIcon()
-        iconPath = self.__pathToIcon['switch']
-        iconPath = iconPath + path
-        icon.addPixmap(QtGui.QPixmap(iconPath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
-        return icon
-              
-    def setupMenu(self):
-        shutdownAction = QtWidgets.QAction('Shutdown Encoder', self)
-        resetAction = QtWidgets.QAction("Reset Encoder", self)
-        testKeysAction = QtWidgets.QAction("Test Keys", self)
-        openInfoAction = QtWidgets.QAction("Device Information", self)
-        startOTAAction = QtWidgets.QAction("OTA Update", self)
-
-        testKeysAction.triggered.connect(self.testKeys)
-        openInfoAction.triggered.connect(self.openDeviceInformation)
-        startOTAAction.triggered.connect(lambda: self.__wsc.updateDevice(self.__strModuleID, None))
-        shutdownAction.triggered.connect(lambda: self.__wsc.shutdownDevice(self.__strModuleID, None))
-        resetAction.triggered.connect(lambda: self.__wsc.resetDevice(self.__strModuleID, None))
-
-                
-        deviceMenu = QtWidgets.QMenu()
-        deviceMenu.addAction(shutdownAction)
-        deviceMenu.addAction(resetAction)
-        deviceMenu.addAction(openInfoAction)
-        deviceMenu.addSection("Engineering Tools")
-        deviceMenu.addAction(testKeysAction)
-        deviceMenu.addAction(startOTAAction)
-        
-        self.__ui.buttonMenu.setPopupMode(2)
-        self.__ui.buttonMenu.setMenu(deviceMenu)
-        self.__ui.buttonMenu.setStyleSheet("* { padding-right: 3px } QToolButton::menu-indicator { image: none }")    
-        
-    def activateEncoder(self):
-        if self.__ui.buttonActivate.text() == "Activate":
-            print("Activating Encoder: " + self.__ui.labelModuleID.text())
-            self.activateDevice.emit(self.__strModuleID, self.__deviceType)
-            self.__ui.buttonActivate.setText("Deactivate")
-        else: 
-            print("Deactivating Encoder: " + self.__ui.labelModuleID.text())
-            self.deactivateDevice.emit(self.__strModuleID, self.__deviceType)
-            self.__ui.buttonActivate.setText("Activate")
-        
-    def updateDevice(self, encoder):      
-        self.__rssi = encoder['rssi']
-        self.__soc = self._parserTools.calculateSOC(encoder['soc'])
-        self.__vcell = self._parserTools.calculateVCell(encoder['vcell'])
-        self.__strModuleID = self._parserTools.macToString(encoder['module_id'])
-        self.__updateTime = encoder['time']
-        self.__ota = encoder['ota']
-        
-        if self.__deviceName == None:
-            self.setModuleID(self.__strModuleID)
-        self.setSOCIcon(self.__soc)
-        self.setRSSIIcon(self.__rssi)
-        self.setStatusTime(self.__updateTime)
-        self.setOTAIcon(self.__ota)
-        
-    def setOTAIcon(self, ota):
-        if ota: 
-            self.__ui.labelOTA.show()
-        else: 
-            self.__ui.labelOTA.hide()
-
-    def setModuleID(self, strModuleID):      
-        self.__ui.labelModuleID.setText(strModuleID)
-        
-    def setDeviceAlisas(self, label):
-        self.__deviceName = label
-        if label != None or label != "": 
-            self.__ui.labelModuleID.setText(label)
-        else: 
-            self.__ui.labelModuleID.setText(self.__strModuleID)
-
-    def setSOCIcon(self, soc):
-        if soc >= 75: 
-            batteryIcon = 4
-        elif soc >= 50 and soc < 75: 
-            batteryIcon = 3
-        elif soc >= 25 and soc < 50: 
-            batteryIcon = 2 
-        elif soc >=10 and soc < 25: 
-            batteryIcon = 1
-        elif soc < 10: 
-            batteryIcon = 0 
-        batteryIcon = self.__pathToIcon['battery']+self.__icon['battery'][batteryIcon]
-        self.__ui.labelBattery.setPixmap(QtGui.QPixmap(batteryIcon))
-        self.__ui.labelBattery.setToolTip(str(soc) + "%")
-        
-    def setStatusTime(self, updateTime):
-        lastUpdate = time.ctime(updateTime).replace("  ", " ").split(" ")
-        currentTime = time.ctime().replace("  ", " ").split(" ")
-        if currentTime[1] != lastUpdate[1] or currentTime[2] != lastUpdate[2] or currentTime[4] != lastUpdate[4]: 
-            lastUpdate = lastUpdate[1] + " " + lastUpdate[2] + " " + lastUpdate[4]
-        else: 
-            lastUpdate = lastUpdate[3]
-        self.__ui.labelStatus.setText("Last Update: " + lastUpdate)
-        
-    def setRSSIIcon(self, rssi): 
-        if rssi >= -50: 
-            rssiIcon = 4
-        elif rssi >= -60 and rssi < -50: 
-            rssiIcon = 3 
-        elif rssi >= -70 and rssi < -60: 
-            rssiIcon = 2 
-        elif rssi < -70: 
-            rssiIcon = 1  
-        rssiIcon = self.__pathToIcon['network'] + self.__icon['network'][rssiIcon]  
-        self.__ui.labelSignal.setPixmap(QtGui.QPixmap(rssiIcon))
-        self.__ui.labelSignal.setToolTip(str(rssi) + " dBm")
-        
-    def updateSwitchIcons(self):
-        keys = self.__wsc.keyEmulator.getAssignedKeys(self.__strModuleID)
-        if keys[self.__wsc.keyEmulator.switchOne][1]:
-            iconPath = self.__icon['switch'][0]
-        else: 
-            iconPath = self.__icon['switch'][1]
-        
-        self.__ui.buttonSwitchOne.setIcon(self.setupSwitchIcon(iconPath))
-            
-        if keys[self.__wsc.keyEmulator.switchTwo][1]:
-            iconPath = self.__icon['switch'][2]
-        else: 
-            iconPath =  self.__icon['switch'][3]
-        
-        self.__ui.buttonSwitchTwo.setIcon(self.setupSwitchIcon(iconPath))
-        
-        if keys[self.__wsc.keyEmulator.switchAdaptive][1]:
-            iconPath = self.__icon['switch'][4]
-        else:
-            iconPath = self.__icon['switch'][5]
-            
-        self.__ui.buttonSwitchAdaptive.setIcon(self.setupSwitchIcon(iconPath))
-
-    def testKeys(self):
-        time.sleep(3)
-        for payload in [1, 0, 2, 0, 4, 0]:
-            self.__wsc.keyEmulator.emulateKey(self.__strModuleID, payload)
-            time.sleep(0.1)
-
-
-class IdeasXMainWindow(QtWidgets.QMainWindow):
-    def __init__(self, wsc):
-        super(IdeasXMainWindow, self).__init__()
-        self.__ui = Ui_MainWindow()
-        self.__ui.setupUi(self)
-        self.__wsc = wsc 
-        
-        p = self.__ui.contentEncoder.palette()
-        p.setColor(self.backgroundRole(), QtCore.Qt.white)
-        self.__ui.contentEncoder.setPalette(p)
-        
-        self.__ui.statusMessageWidget = QtWidgets.QLabel()
-        self.__ui.statusMessageWidget.setText("Starting WSC...")
-        self.__ui.statusMessageWidget.setAlignment(QtCore.Qt.AlignLeft)
-        self.__ui.statusbar.addWidget(self.__ui.statusMessageWidget, 1)
-                
-        self.__org = 'IdeasX'
-        self.__app = 'Workstation-Client'
-        
-        self.restoreSettings()
-        
-        #self.__ui.buttonSettings.clicked.connect(self.updateBrokerSettings)
-        self.__ui.buttonBoxNetwork.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.updateBrokerSettings)
-        self.__ui.buttonBoxNetwork.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.restoreBrokerSettings)
-        
-
-    def setEncoderLayout(self, layout):
-        self.__ui.contentEncoder.setLayout(layout)
-        
-    def setStatusBarMessage(self, msg):
-        self.__ui.statusbar.clearMessage()
-        self.__ui.statusMessageWidget.setText(msg)
-        
-    def setStatusBarUpdate(self, msg):
-        self.__ui.statusbar.showMessage(msg)
-
-    def saveSettings(self):
-        '''
-            Saves various backend and front information via Qt's system agnoistic classes. 
-            The following information is saved and restored: 
-            
-            1) MainWindow size and position 
-            2) Switch configuration and assigned keys 
-            3) Encoder Nicknames
-            4) Broker URL and Ports
-        '''
-        # MainWindow Settings
-        settings = QtCore.QSettings(self.__org, self.__app)
-        settings.beginGroup("MainWindow")
-        settings.setValue("size", self.size())
-        settings.setValue("pos", self.pos())
-        settings.endGroup()      
-        
-    def updateBrokerSettings(self):
-        print(self.__ui.networkBroker.text())
-        self.__NetworkBroker = self.__ui.networkBroker.text()
-        self.__LocalBroker = self.__ui.localBroker.text()
-        self.__NetworkPort = int(self.__ui.networkPort.text())
-        self.__LocalPort = int(self.__ui.localPort.text())  
-        
-        settings = QtCore.QSettings(self.__org, self.__app) 
-        settings.beginGroup("Broker")
-        settings.setValue('NetworkBroker', self.__NetworkBroker)
-        settings.setValue('NetworkPort', self.__NetworkPort)
-        settings.setValue('LocalBroker', self.__LocalBroker)
-        settings.setValue('LocalPort', self.__LocalPort)
-        settings.endGroup()
-        self.saveSettings()
-        
-        #self.__ui.statusbar.showMessage("Updated Broker settings. Please Restart the WSC.")
-        self.__wsc.guiRestartWSC()
-        
-    def restoreSettings(self):
-        settings = QtCore.QSettings(self.__org, self.__app)
-        settings.beginGroup("MainWindow")
-        self.resize(settings.value("size", QtCore.QSize(525, 648)))
-        self.move(settings.value("pos", QtCore.QPoint(0, 0)))
-        settings.endGroup()
-        
-        settings.beginGroup("Broker")
-        self.__NetworkBroker = settings.value('NetworkBroker', 'ideasx.duckdns.org')
-        self.__NetworkPort = settings.value('NetworkPort', 1883)
-        self.__LocalBroker = settings.value('LocalBroker', '10.42.0.1')
-        self.__LocalPort = settings.value('LocalPort', 1883)
-        settings.endGroup()
-        
-        self.__ui.networkBroker.setText(self.__NetworkBroker)
-        self.__ui.networkPort.setText(str(self.__NetworkPort))
-        self.__ui.localBroker.setText(self.__LocalBroker)
-        self.__ui.localPort.setText(str(self.__LocalPort))
-        
-        settings.beginGroup('OTAServer')
-        self.__OTAServer = settings.value('OTAServer', 'ideasx.duckdns.org')
-        settings.endGroup()
-        
-    def restoreBrokerSettings(self):
-        settings = QtCore.QSettings(self.__org, self.__app)
-
-        settings.beginGroup("Broker")
-        self.__NetworkBroker = settings.value('NetworkBroker', 'ideasx.duckdns.org')
-        self.__NetworkPort = settings.value('NetworkPort', 1883)
-        self.__LocalBroker = settings.value('LocalBroker', '10.42.0.1')
-        self.__LocalPort = settings.value('LocalPort', 1883)
-        settings.endGroup()
-        
-        self.__ui.networkBroker.setText(self.__NetworkBroker)
-        self.__ui.networkPort.setText(str(self.__NetworkPort))
-        self.__ui.localBroker.setText(self.__LocalBroker)
-        self.__ui.localPort.setText(str(self.__LocalPort))
-        
-        
-    def closeEvent(self, event):
-        self.saveSettings()
-        super(IdeasXMainWindow, self).closeEvent(event)
-        
-        
-        
-if __name__ == "__main__":
-    app = QtWidgets.QApplication(sys.argv)
-    app.setWindowIcon(QtGui.QIcon('icon/logo/ideasx.png'))
-    wsc = IdeasXWSCNetworkThread()
-    mainWindow = IdeasXMainWindow(wsc)
-    encoderManager = IdeasXDeviceManager(IdeasXEncoder, wsc)
-    mainWindow.setEncoderLayout(encoderManager.returnLayout())
-    
-    wsc.encoderUpdate.connect(encoderManager.refreshDevices)
-    wsc.networkStatus.connect(mainWindow.setStatusBarMessage)
-    wsc.networkUpdate.connect(mainWindow.setStatusBarUpdate)
-    #wsc.guiStartWorkstationClient('ideasx.duckdns.org')
-    wsc.guiStartWorkstationClient()
-    
-    
-    #timer = QtCore.QTimer()
-    #timer.timeout.connect(mainWindow.hideEncoder)
-    #timer.start(1000)
-    #time.sleep(0.5)
-    
-    mainWindow.show()
-    sys.exit(app.exec_())
-    

+ 0 - 56
ParsingTools.py

@@ -1,56 +0,0 @@
-class ParsingTools():
-    def macToString(self, mac_bytes):
-        ''' Convert uint8 byte string to "XX:XX:XX:XX:XX" 
-        '''
-        mac_str = ""
-        for byte in mac_bytes: 
-            mac_str = mac_str + format(byte, '02x') + ':'
-        return mac_str[:-1].format('utf-8')
-            
-    def calculateVCell(self, raw_Vcell):
-        return raw_Vcell*1.25e-3
-    
-    def calculateSOC(self, raw_SOC):
-        return raw_SOC.to_bytes(2, 'big')[0]
-    
-    def getModuleIDfromTopic(self, topic):
-        return topic.split('/')[2]
-        
-class FieldGenerator():
-    def generateMACID(self):
-        import numpy as np
-        macID = np.random.randint(255, size=5)
-        macStr = ""
-        for val in macID: 
-            macStr = macStr + format(val, 'x') + ":" 
-        return macStr[:-1]
-
-    def generateRSSI(self):
-        import numpy as np 
-        rssi = np.random.randint(80)
-        rssiStr = "RSSI:  -" + str(rssi) + "dBm"
-        return rssiStr 
-    
-    def generateSOC(self):
-        import numpy as np
-        soc = np.random.randint(100)
-        socStr = "Battery:  " + str(soc) + "%"
-        return socStr
-        
-    def generateStatus(self):
-        import numpy as np 
-        hr = np.random.randint(12) + 1
-        min = np.random.randint(60)
-        ampm = np.random.randint(1)
-        statusStr = "Last Update: " + str(hr) + ":" + str(min)
-        return statusStr
-    
-
-if __name__ == '__main__':
-    print("I never developed self-test, but if I did they would go here.")
-    
-    pt = ParsingTools()
-    
-    print("Testing macToString")
-    print(pt.macToString(b'023430'))
-    

+ 17 - 0
README.md

@@ -0,0 +1,17 @@
+# IdeasX Supervisor 
+
+The purpose of this application is to provide an interface for therapists and care takers to configure and maintain devices in the IdeasX system. The application is cross platform and written in python3.X and PyQt (Qt5). The application currently has the following dependencies: 
+    + Python 3.X 
+    + PyQt (GUI)
+    + Paho-MQTT (Network Communication)
+    + PySerial  (Configuration of IdeasX device over USB)
+
+The software is split into a few different files for sanity. 
+
+**wsc_client.py** contains majority of the networking code for handling ideasX devices. 
+
+**wsc_device.py** contains classes for each device in the IdeasX system to decode data recieved by the wsc\_client.py networking thread and commands which can be send to each device. 
+
+**wsc_tools.py** contains helper tools created wsc\_io.py file (normally dumb things like converting a byte value from the wsc\_client into a percentage etc.)
+
+**wsc_ui.py** contains the UI for the appication

+ 0 - 1
icon/network/Archive/arc/network-wired-offline-symbolic.svg

@@ -1 +0,0 @@
-network-wired-disconnected-symbolic.svg

+ 146 - 0
wsc_client.py

@@ -0,0 +1,146 @@
+import sys, time, collections
+from PyQt5.QtCore import QObject, pyqtSignal, QSettings
+import paho.mqtt.client as mqtt
+import logging
+import wsc_device
+import wsc_device_encoder
+from wsc_tools import ParsingTools
+
+#FORMAT = '%(asctime)-15s'
+logging.basicConfig( level=logging.DEBUG)
+log = logging.getLogger("wsc_client") 
+
+
+class WSC_Client(QObject):
+
+    # define Qt signals (I don't understand why this is here)
+    encoderUpdate = pyqtSignal([dict], name='encoderUpdate')
+    networkStatus = pyqtSignal([str], name='networkStatus')
+    networkUpdate = pyqtSignal([str], name='networkUpdate')
+    settingsError = pyqtSignal([str], name='settingsError')
+
+    def __init__(self, settingFile=None, clientID = None, debug=True, mqttdebug=True):
+        super(WSC_Client, self).__init__()
+        # Private Class Flags and Variables
+        self.__clientID = clientID
+        self.__settingFile = settingFile
+        self.__debug = debug
+        self.__mqttDebug = mqttdebug
+        self.__errorIndex = 0
+        self.__refreshCb = None
+
+        self.__org = 'IdeasX'
+        self.__app = 'Workstation-Client'
+
+        # MQTT Client Object
+        self._mqttc = mqtt.Client(self.__clientID, clean_session=True, userdata=None, protocol=mqtt.MQTTv311)
+
+        # IdeasX Device Managers / Parsers 
+        self.__encoderManager = wsc_device.DeviceManager(wsc_device_encoder.Encoder, self._mqttc, self.encoderUpdate)
+        self.__parserTools = ParsingTools()
+
+        self._mqttc.on_connect = self.wsc_on_connect
+        self._mqttc.on_disconnect = self.wsc_on_disconnect
+
+        if self.__mqttDebug:
+            self._mqttc.on_log = self.mqtt_on_log
+
+    '''
+     MQTT Callback Functions
+    '''
+
+    def wsc_on_connect(self, mqttc, backend_data, flags, rc):
+        if rc == 0:
+            log.info('Connected to %s: %s' % (mqttc._host, mqttc._port))
+            self.networkStatus.emit("Connected to %s: %s" % (mqttc._host, mqttc._port))
+        else:
+            log.info('rc: ' + str(rc))
+            self.networkStatus.emit('Connection Failure (rc: ' +str(rc))
+
+    def wsc_on_disconnect(self, mqttc, backend_data, rc):
+        if self.__debug:
+            if rc != 0:
+                log.warning("Client disconnected and its a mystery why!")
+                self.networkStatus.emit("Uh No! WSC was disconnected!")
+            else:
+                log.info("Client successfully disconnected.")
+                self.networkStatus.emit("Uh No! WSC was disconnected!")
+            self.printLine()
+
+    def mqtt_on_log(self, mqttc, backend_data, level, string):
+        log.debug(string)
+
+
+    def StartWorkstationClient(self, ip=None, port=1883, keepAlive=60, gui=False):
+        self.keepAlive = keepAlive
+
+        if ip == None or ip == "":
+            settings = QSettings(self.__org, self.__app)
+            settings.beginGroup('Broker')
+            self.ip = settings.value('NetworkBroker', 'ideasx.duckdns.org')
+            self.port = settings.value('NetworkPort', 1883)
+            self.__LocalBroker = settings.value('LocalBroker', '10.42.0.1')
+            self.__LocalPort = settings.value('LocalPort', 1883)
+            settings.endGroup()
+        else:
+            log.info("Loading hardcoded defaults")
+            self.ip = ip
+            self.port = port
+
+        try:
+            self._mqttc.connect(self.ip, int(self.port), self.keepAlive)
+            self.__encoderManager.setupMQTT()
+            if gui: 
+                log.info("Starting WSC Backend (GUI Version)")
+                self._mqttc.loop_start()    # start MQTT Client Thread
+            else: 
+                log.info("Starting WSC Backend (CMD Version)")
+                self._mqttc.loop_forever()  # needs to be blocking in CMD mode
+        except Exception as e:
+            # this needs to be updated to look at the exception errors 
+             log.critical("Error connecting to IdeasX")
+             log.critical(e)   
+             self.networkStatus.emit("Oh-no! Broker settings are incorrect or there is a network failure")
+             #sys.exit(1)
+
+    def restartWSC(self):
+        self.killWSC()
+        self.networkUpdate.emit("Restarting WSC...")
+        self.StartWorkstationClient()
+
+    def killWSC(self):
+        self._mqttc.loop_stop()
+        log.info("Murdered MQTT thread.")
+
+    def printLine(self):
+        print('-'*70)
+
+if __name__ == "__main__":
+    Host = "127.0.0.1"
+    Port = 1883
+    KeepAlive = 30
+    msgFlag = False;
+    deviceID = None;
+    cmdPayload = None;
+    cmdArg = None;
+    cmdTest = True;
+
+    wsc = WSC_Client()
+
+    if cmdTest:
+        wsc.StartWorkstationClient(Host, Port, KeepAlive, gui=False)
+    else:
+        wsc.StartWorkstationClient(Host, Port, KeepAlive, gui=True)
+        time.sleep(3)
+
+        (result, mid) = wsc._mqttc.subscribe('/encoders/18:fe:34:d2:6f:68/health', qos=0)
+        print(result, mid)
+        (result, mid) = wsc._mqttc.subscribe('/encoders/18:fe:34:d2:6f:68/health', qos=0)
+        print(result, mid)
+        wsc.activateEncoder('18:fe:34:d2:6f:68')
+#         print(wsc.subscribedEncoders)
+#         time.sleep(2)
+#         wsc.deactivateEncoder('18:fe:34:d2:6f:68')
+#         print(wsc.subscribedEncoders)
+        time.sleep(10)
+        wsc.killWSC()

+ 83 - 0
wsc_device.py

@@ -0,0 +1,83 @@
+import time
+from wsc_tools import ParsingTools
+from wsc_tools import Switch
+from wsc_device_encoder import Encoder
+
+class DeviceManager():
+    FAIL = 0 
+    SUCCESS = 1
+    
+    def __init__(self, deviceClass, mqttc, healthSignal=None):
+        self.__healthSignal = healthSignal
+        self.__devices = {} 
+        self.__deviceClass = deviceClass
+        self.__parserTools = ParsingTools()
+        self.__mqttc = mqttc 
+
+
+    def setupMQTT(self):
+        self.__mqttc.subscribe(self.__deviceClass.DEVICE_HEALTH_TOPIC,
+                               self.__deviceClass.DEVICE_HEALTH_QOS)
+
+        self.__mqttc.message_callback_add(self.__deviceClass.DEVICE_HEALTH_TOPIC, 
+                                          self.mqtt_health_cb)
+
+    def mqtt_health_cb(self, mqttc, backend_data, msg): 
+        macID = self.__parserTools.getIDfromTopic(msg.topic)
+        field = self.__parserTools.getFieldfromTopic(msg.topic)
+        r = self.updateDevice(macID, field, msg.payload)
+        if (r == DeviceManager.FAIL): 
+            self.addDevice(macID, mqttc)
+            self.updateDevice(macID, field, msg.payload)
+        if (self.__healthSignal):
+            self.__healthSignal.emit(self.listAliveDevices())        
+        
+    def updateDevice(self, device_id, field, payload):
+        if device_id in self.__devices.keys(): 
+            if (self.__devices[device_id].updateField(field, payload) == self.__deviceClass.SUCCESS):
+                return DeviceManager.SUCCESS 
+        else: 
+            return DeviceManager.FAIL 
+    
+    def listAliveDevices(self): 
+        tempDict = {}
+        for device in self.__devices.keys(): 
+            e = self.__devices[device]
+            if (e.getField(self.__deviceClass.ALIVE_TOPIC) == self.__deviceClass.ALIVE_VALUE):
+                tempDict[device] = e
+        return tempDict
+    
+    def listDevices(self):
+        return self.__devices.keys()
+    
+    def addDevice(self, device_id, mqttc): 
+        self.__devices[device_id] = self.__deviceClass(device_id, mqttc)
+        
+    def removeDevice(self, device_id):
+        if device_id in self.__devices.keys():
+            self.__devices.pop(device_id)
+            return DeviceManager.SUCCESS
+        else: 
+            return DeviceManager.FAIL
+        
+    def getDevice(self, device_id): 
+        if device_id in self.__devices.keys(): 
+            return self.__devices[device_id]
+        else: 
+            return DeviceManager.FAIL
+        
+            
+if __name__ == "__main__": 
+    device_id0 = "12:23:32:32:32:32"
+    device_id1 = "12:23:32:32:31:32"
+
+    dm = DeviceManager(Encoder, None)
+    print("Updating Nonexistanct Device Result" + str(dm.updateDevice(device_id0, 'ota', b'1')))
+    print("Adding device:" + str(dm.addDevice(device_id0)))
+    print("Adding device:" + str(dm.addDevice(device_id1)))
+
+    print("Updating Device Result" + str(dm.updateDevice(device_id0, 'ota', '1')))
+    print("Updating Device Result" + str(dm.updateDevice(device_id1, 'alive', '1')))
+    
+    print(dm.listAliveDevices())
+

+ 381 - 0
wsc_device_encoder.py

@@ -0,0 +1,381 @@
+import logging
+import sys
+import time 
+import sip
+from wsc_tools import ParsingTools
+from wsc_tools import Switch
+from PyQt5 import QtCore
+from PyQt5 import QtGui
+from PyQt5 import QtWidgets
+from pyqt.ideasxdevice import Ui_IdeasXDevice
+from pyqt.encoderconfigurationdialog import Ui_SwitchConfigDialog
+from pyqt.devicedialog import Ui_Dialog
+
+pt = ParsingTools()
+logging.basicConfig( level=logging.DEBUG)
+log = logging.getLogger("wsc_device_encoder") 
+
+class Encoder():
+    FAIL = 0
+    SUCCESS = 1
+    ALIVE_VALUE = b'1'
+    ALIVE_TOPIC = "alive" 
+    DEVICE_HEALTH_TOPIC = "encoder/+/health/#"
+    DEVICE_HEALTH_QOS = 0
+    SHUTDOWN_COMMAND_TOPIC = "shutdown"
+    RESTART_COMMAND_TOPIC = "restart"
+    OTA_COMMAND_TOPIC = "ota"
+    UART_TX_COMMAND_TOPIC = "uart"
+    COMMAND_RETAIN = False 
+    COMMAND_QOS = 1 
+
+    def __init__(self, device_id, mqttc):
+        self.__device_id  = device_id
+        self.__hw_version_default =   b"0,0"
+        self.__fw_version_default =   b"0,0"
+        self.__alive_default  =    b"0"
+        self.__vcell_default   =   b"0"
+        self.__charge_default  =   b"0"
+        self.__lbi_default  =      b"0"
+        self.__soc_default  =      b"0"
+        self.__rom_default  =      b"0"
+        self.__ota_default  =      b"0"
+        self.__wireless_default  = b"0"
+        self.__ssid_default    =   b""
+        self.__bssid_default   =   b""
+        self.__rssi_default    =   b"0"
+        self.__auth_default    =   b"0"
+        self.__time_default    =   time.time()
+        self.__fields = {"device_id":   self.__device_id,
+                         "hw_ver":      self.__hw_version_default,
+                         "fw_ver":      self.__fw_version_default,
+                         "alive":       self.__alive_default,
+                         "vcell":       self.__vcell_default,
+                         "charge":      self.__charge_default,
+                         "lbi":         self.__lbi_default,
+                         "soc":         self.__soc_default,
+                         "rom":         self.__rom_default,
+                         "ota":         self.__ota_default,
+                         "wireless":    self.__wireless_default,
+                         "ssid":        self.__ssid_default,
+                         "bssid":       self.__bssid_default,
+                         "rssi":        self.__rssi_default,
+                         "auth":        self.__auth_default,
+                         "time":        self.__time_default}
+        self.__commands = {'update':    self.update,
+                           'restart':   self.restart,
+                           'shutdown':  self.shutdown}
+        self.__mqttc = mqttc
+        self.switchOne = Switch()
+        self.switchTwo = Switch()
+        self.switchAdaptive = Switch()
+        self.RAW_COMMAND_TOPIC = "encoder/" + device_id + "/command/"
+
+
+    def updateField(self, field, value):
+        if field in self.__fields.keys():
+            self.__fields[field] = value
+            self.__fields['time'] = time.time()
+            return Encoder.SUCCESS
+        else: 
+            return Encoder.FAIL
+        
+    def listFieldNames(self):
+        return self.__fields.keys()
+        
+    def listFields(self):
+        return self.__fields
+
+    def listCommandNames(self):
+        return self.__commands.keys() 
+
+    def listCommands(self):
+        return self.__commmands
+    
+    def getField(self, field):
+        return self.__fields[field]
+
+    def update(self): 
+        self.__mqttc.publish(self.RAW_COMMAND_TOPIC+self.OTA_COMMAND_TOPIC, b'1', qos=1, retain=False)
+        log.info("sent OTA command")
+
+    def restart(self):
+        self.__mqttc.publish(self.RAW_COMMAND_TOPIC+self.RESTART_COMMAND_TOPIC, b'1', qos=1, retain=False)
+        log.info("sent restart command")  
+
+    def shutdown(self): 
+        self.__mqttc.publish(self.RAW_COMMAND_TOPIC+self.SHUTDOWN_COMMAND_TOPIC, b'1', qos=1, retain=False)
+        log.info("sent shutdown command")  
+
+class EncoderUI(QtWidgets.QWidget):
+    sendCommand = QtCore.pyqtSignal(['QString'], name='sendCommand')
+    activateDevice = QtCore.pyqtSignal(['QString', 'QString'], name='deactivateDevice')
+    deactivateDevice = QtCore.pyqtSignal(['QString', 'QString'], name='activateDevice')
+    __pathToIcon = {'network': './icon/network/',
+                     'battery': './icon/battery/',
+                     'battery_charging': './icon/battery/',
+                     'switch': './icon/switch/'
+                    }
+    __icon =    {'network':   ['network-wireless-offline-symbolic.png',
+                            'network-wireless-signal-weak-symbolic.png',
+                            'network-wireless-signal-ok-symbolic.png',
+                            'network-wireless-signal-good-symbolic.png',
+                            'network-wireless-signal-excellent-symbolic.png'],
+                'battery': ['battery-empty-symbolic.png',
+                            'battery-caution-symbolic.png',
+                            'battery-low-symbolic.png',
+                            'battery-good-symbolic.png',
+                            'battery-full-symbolic.png'],
+                'battery_charging': ['battery-empty-charging-symbolic.png',
+                                        'battery-caution-charging-symbolic.png',
+                                        'battery-low-charging-symbolic.png',
+                                        'battery-good-charging-symbolic.png',
+                                        'battery-full-charged-symbolic.png'],
+                'switch': ['switch-one-enabled.png',
+                            'switch-one-disabled.png',
+                            'switch-two-enabled.png',
+                            'switch-two-disabled.png',
+                            'switch-adaptive-enabled.png',
+                            'switch-adaptive-disabled.png']
+                   }
+    __deviceType = 'encoder/'
+
+
+    def __init__(self, encoder):
+        self.__deviceName = None
+        self.__encoder = encoder
+
+        # Setup UI components
+        super(EncoderUI, self).__init__()
+        self.__ui = Ui_IdeasXDevice()
+        self.__ui.setupUi(self)
+        self.updateDevice(encoder)
+        self.updateSwitchIcons()
+
+        # Setup Signals
+        self.setupMenu()
+        #self.__ui.buttonActivate.clicked.connect(self.activateEncoder)
+        self.__ui.buttonSwitchOne.clicked.connect(lambda: self.openSwitchDialog(self.__encoder.switchOne))
+        self.__ui.buttonSwitchTwo.clicked.connect(lambda: self.openSwitchDialog(self.__encoder.switchTwo))
+        #self.activateDevice.connect(self.__wsc.activateEncoder)
+        #self.deactivateDevice.connect(self.__wsc.deactivateEncoder)
+
+    def openDeviceInformation(self):
+        dialog = InfoUI()
+        dialog.updateDisplay(self.__encoder.listFields())
+        dialog.newDeviceName.connect(self.setDeviceAlisas)
+        dialog.exec()
+
+    def openSwitchDialog(self, switch):
+        dialog = SwitchUI(switch)
+        if dialog.exec_():
+            if dialog.key != None and len(dialog.key) == 1:
+                switch.setConfig(dialog.key, latch=False,interval=0.0, release=False, enable=dialog.enable)
+                self.updateSwitchIcons()
+
+    def setupSwitchIcon(self, path):
+        icon = QtGui.QIcon()
+        iconPath = self.__pathToIcon['switch']
+        iconPath = iconPath + path
+        icon.addPixmap(QtGui.QPixmap(iconPath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+        return icon
+
+    def setupMenu(self):
+
+        # create menu actions 
+        shutdownAction = QtWidgets.QAction('Shutdown', self)
+        resetAction = QtWidgets.QAction("Reset", self)
+        testKeysAction = QtWidgets.QAction("Test Keys", self)
+        openInfoAction = QtWidgets.QAction("Information", self)
+        startOTAAction = QtWidgets.QAction("OTA Update", self)
+
+        # connect signals to funcitons 
+        #testKeysAction.triggered.connect(self.testKeys)
+        openInfoAction.triggered.connect(self.openDeviceInformation)
+        startOTAAction.triggered.connect(lambda: self.__encoder.update())
+        shutdownAction.triggered.connect(lambda: self.__encoder.shutdown())
+        resetAction.triggered.connect(lambda: self.__encoder.restart())
+
+        # create menu options 
+        deviceMenu = QtWidgets.QMenu()
+        deviceMenu.addAction(shutdownAction)
+        deviceMenu.addAction(resetAction)
+        deviceMenu.addAction(openInfoAction)
+        deviceMenu.addSection("Engineering Tools")
+        #deviceMenu.addAction(testKeysAction)
+        #deviceMenu.addAction(startOTAAction)
+
+        self.__ui.buttonMenu.setPopupMode(2)
+        self.__ui.buttonMenu.setMenu(deviceMenu)
+        self.__ui.buttonMenu.setStyleSheet("* { padding-right: 3px } QToolButton::menu-indicator { image: none }")
+
+    def activateEncoder(self):
+        if self.__ui.buttonActivate.text() == "Activate":
+            log.info("Activating Encoder: " + self.__ui.labelModuleID.text())
+            self.activateDevice.emit(self.__strModuleID, self.__deviceType)
+            self.__ui.buttonActivate.setText("Deactivate")
+        else:
+            log.info("Deactivating Encoder: " + self.__ui.labelModuleID.text())
+            self.deactivateDevice.emit(self.__strModuleID, self.__deviceType)
+            self.__ui.buttonActivate.setText("Activate")
+
+    def updateDevice(self, encoder):
+        self.__encoder = encoder
+        self.__rssi = encoder.getField('rssi')
+        self.__soc = pt.calculateSOC(encoder.getField('soc'))
+        self.__vcell = pt.calculateVCell(encoder.getField('vcell'))
+        self.__strModuleID = encoder.getField('device_id')
+        self.__updateTime = encoder.getField('time')
+        self.__ota = encoder.getField('ota')
+
+        if self.__deviceName == None:
+            self.setModuleID(self.__strModuleID)
+        self.setSOCIcon(self.__soc)
+        self.setRSSIIcon(self.__rssi)
+        self.setStatusTime(self.__updateTime)
+        self.setOTAIcon(self.__ota)
+
+    def setOTAIcon(self, ota):
+        if ota == '1':
+            self.__ui.labelOTA.show()
+        else:
+            self.__ui.labelOTA.hide()
+
+    def setModuleID(self, strModuleID):
+        self.__ui.labelModuleID.setText(strModuleID)
+
+    def setDeviceAlisas(self, label):
+        self.__deviceName = label
+        if label != None or label != "":
+            self.__ui.labelModuleID.setText(label)
+        else:
+            self.__ui.labelModuleID.setText(self.__strModuleID)
+
+    def setSOCIcon(self, soc):
+        soc = int(soc)
+        if soc >= 75:
+            batteryIcon = 4
+        elif soc >= 50 and soc < 75:
+            batteryIcon = 3
+        elif soc >= 25 and soc < 50:
+            batteryIcon = 2
+        elif soc >=10 and soc < 25:
+            batteryIcon = 1
+        elif soc < 10:
+            batteryIcon = 0
+        batteryIcon = self.__pathToIcon['battery']+self.__icon['battery'][batteryIcon]
+        self.__ui.labelBattery.setPixmap(QtGui.QPixmap(batteryIcon))
+        self.__ui.labelBattery.setToolTip(str(soc) + "%")
+
+    def setStatusTime(self, updateTime):
+        lastUpdate = time.ctime(updateTime).replace("  ", " ").split(" ")
+        currentTime = time.ctime().replace("  ", " ").split(" ")
+        if currentTime[1] != lastUpdate[1] or currentTime[2] != lastUpdate[2] or currentTime[4] != lastUpdate[4]:
+            lastUpdate = lastUpdate[1] + " " + lastUpdate[2] + " " + lastUpdate[4]
+        else:
+            lastUpdate = lastUpdate[3]
+        self.__ui.labelStatus.setText("Last Update: " + lastUpdate)
+
+    def setRSSIIcon(self, rssi):
+        rssi = int(rssi)
+        if rssi >= -50:
+            rssiIcon = 4
+        elif rssi >= -60 and rssi < -50:
+            rssiIcon = 3
+        elif rssi >= -70 and rssi < -60:
+            rssiIcon = 2
+        elif rssi < -70:
+            rssiIcon = 1
+        rssiIcon = self.__pathToIcon['network'] + self.__icon['network'][rssiIcon]
+        self.__ui.labelSignal.setPixmap(QtGui.QPixmap(rssiIcon))
+        self.__ui.labelSignal.setToolTip(str(rssi) + " dBm")
+
+    def updateSwitchIcons(self):
+        switch1 = self.__encoder.switchOne
+        switch2 = self.__encoder.switchTwo
+        switchA = self.__encoder.switchAdaptive
+        switchA.deactivate()
+
+        if switch1.active:
+            iconPath = self.__icon['switch'][0]
+        else:
+            iconPath = self.__icon['switch'][1]
+
+        self.__ui.buttonSwitchOne.setIcon(self.setupSwitchIcon(iconPath))
+
+        if switch2.active:
+            iconPath = self.__icon['switch'][2]
+        else:
+            iconPath =  self.__icon['switch'][3]
+
+        self.__ui.buttonSwitchTwo.setIcon(self.setupSwitchIcon(iconPath))
+
+        if switchA.active: 
+            iconPath = self.__icon['switch'][4]
+        else:
+            iconPath = self.__icon['switch'][5]
+
+        self.__ui.buttonSwitchAdaptive.setIcon(self.setupSwitchIcon(iconPath))
+
+    def testKeys(self):
+        time.sleep(3)
+        for payload in [1, 0, 2, 0, 4, 0]:
+            self.__wsc.keyEmulator.emulateKey(self.__strModuleID, payload)
+            time.sleep(0.1)
+
+class SwitchUI(QtWidgets.QDialog):
+
+    def __init__(self, switch):
+        super(SwitchUI, self).__init__()
+        self.__ui = Ui_SwitchConfigDialog()
+        self.__ui.setupUi(self)
+        self.__ui.buttonApply.clicked.connect(self.submitOnClose)
+        self.key = switch.getKey()
+        self.enable = switch.getActive()
+        self.switch = switch
+
+        self.__ui.checkSwitchEnable.setChecked(self.enable)
+        self.__ui.lineSwitchKey.setText(self.key)
+
+    def submitOnClose(self):
+        self.key = self.__ui.lineSwitchKey.text()
+        self.enable = self.__ui.checkSwitchEnable.isChecked()
+        self.accept()
+
+class InfoUI(QtWidgets.QDialog):
+    newDeviceName = QtCore.pyqtSignal(['QString'], name='newDeviceName')
+
+    def __init__(self):
+        super(InfoUI, self).__init__()
+        self.__ui = Ui_Dialog()
+        self.__ui.setupUi(self)
+        self.__ui.lineAlias.textEdited.connect(lambda: self.newDeviceName.emit(self.__ui.lineAlias.text()))
+
+    def updateDisplay(self, encoder):
+        self.__ui.labelBatteryCapacity.setText(encoder['soc'].decode('utf-8'))
+        self.__ui.labelBatteryVoltage.setText(str(encoder['vcell'].decode('utf-8')))
+        self.__ui.labelLowBattery.setText(str(encoder['lbi'].decode('utf-8')))
+        self.__ui.labelChargeState.setText('N/A')
+
+        self.__ui.labelActiveFlag.setText('N/A')
+        self.__ui.labelAliveFlag.setText(str(encoder['alive'].decode('utf-8')))
+        self.__ui.labelFirmwareVersion.setText(str(encoder['fw_ver'].decode('utf-8')))
+        self.__ui.labelHardwareVersion.setText(str(encoder['hw_ver'].decode('utf-8')))
+        self.__ui.labelOTAFlag.setText(str(encoder['ota'].decode('utf-8')))
+        self.__ui.labelROMSlot.setText(str(encoder['rom'].decode('utf-8')))
+
+        self.__ui.labelMAC.setText(str(encoder['device_id']))
+        self.__ui.labelRSSI.setText(str(encoder['rssi'].decode('utf-8')))
+        self.__ui.labelSSID.setText(str(encoder['ssid'].decode('utf-8')))
+
+if __name__ == "__main__":
+    deviceID = '23:45:21:23:32'
+
+    
+    encoder = Encoder(deviceID, None)
+    print(pt.calculateSOC(encoder.getField('soc')))
+    app = QtWidgets.QApplication(sys.argv)
+    encoderUI = EncoderUI(encoder)
+    
+    encoderUI.show() 
+    sys.exit(app.exec_())

+ 113 - 0
wsc_tools.py

@@ -0,0 +1,113 @@
+import  pyautogui as ue
+
+class ParsingTools():
+    def macToString(self, mac_bytes):
+        ''' Convert uint8 byte string to "XX:XX:XX:XX:XX"
+        '''
+        mac_str = ""
+        for byte in mac_bytes:
+            mac_str = mac_str + format(byte, '02x') + ':'
+        return mac_str[:-1].format('utf-8')
+
+    def calculateVCell(self, raw_Vcell):
+        raw_Vcell = int(raw_Vcell.decode('utf-8'))
+        return raw_Vcell*1.25e-3
+
+    def calculateSOC(self, raw_SOC):
+        raw_SOC = int(raw_SOC.decode('utf-8'))
+        return raw_SOC.to_bytes(2, 'big')[0]
+
+    def getIDfromTopic(self, topic):
+        return topic.split('/')[1]
+    
+    def getFieldfromTopic(self, topic): 
+        return topic.split('/')[3]
+
+    def getStr(self, byteCode): 
+        return byteCode.decode('utf-8')
+
+class FieldGenerator():
+    def generateMACID(self):
+        import numpy as np
+        macID = np.random.randint(255, size=5)
+        macStr = ""
+        for val in macID:
+            macStr = macStr + format(val, 'x') + ":"
+        return macStr[:-1]
+
+    def generateRSSI(self):
+        import numpy as np
+        rssi = np.random.randint(80)
+        rssiStr = "RSSI:  -" + str(rssi) + "dBm"
+        return rssiStr
+
+    def generateSOC(self):
+        import numpy as np
+        soc = np.random.randint(100)
+        socStr = "Battery:  " + str(soc) + "%"
+        return socStr
+
+    def generateStatus(self):
+        import numpy as np
+        hr = np.random.randint(12) + 1
+        min = np.random.randint(60)
+        ampm = np.random.randint(1)
+        statusStr = "Last Update: " + str(hr) + ":" + str(min)
+        return statusStr
+
+class Switch(): 
+    def __init__(self):
+        self.__key = "1"
+        self.__latch = False 
+        self.__latchState = 0
+        self.__timer = 0 
+        self.__release = True
+        self.__interval = 0.0    # milliseconds 
+        self.active = True
+
+    def getKey(self): 
+        return self.__key 
+    
+    def getActive(self): 
+        return self.active
+
+    def setConfig(self, key, latch=False, interval=0.0, release=False, enable=True):
+        # release old key if currently held down
+        self.releaseKey() 
+        self.__latchState = 0
+        self.active = enable
+
+        self.__key = key 
+        self.__latch = latch 
+        self.__interval = interval
+        self.__release = release 
+    
+    def activate(self):
+        self.active = True 
+
+    def deactivate(self): 
+        self.active = False
+    
+    def pressKey(self): 
+        if self.__release: 
+            ue.keyDown(self.__key)
+        else: 
+            ue.typewrite(self.__key, interval=self.__interval)
+    
+    def releaseKey(self): 
+        if self.__release:
+            ue.keyUp(self.__key)
+        
+
+
+if __name__ == '__main__':
+    print("I never developed self-test, but if I did they would go here.")
+
+    pt = ParsingTools()
+
+    print("Testing macToString")
+    print(pt.macToString(b'023430'))
+    print("Testing getModuleIDfromTopic")
+    print(pt.getIDfromTopic("encoder/12:23:32:32:32/health/ota"))
+    print("Testing getFieldfromTopic")
+    print(pt.getFieldfromTopic("encoder/12:23:32:32:32/health/ota"))

+ 176 - 0
wsc_ui.py

@@ -0,0 +1,176 @@
+import logging
+import sys
+import time
+import sip
+from wsc_tools import ParsingTools
+from wsc_client import WSC_Client
+from wsc_device_encoder import EncoderUI
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+from pyqt.mainwindow2 import Ui_MainWindow
+from pyqt.ideasxdevice import Ui_IdeasXDevice
+from pyqt.encoderconfigurationdialog import Ui_SwitchConfigDialog
+from pyqt.devicedialog import Ui_Dialog
+
+logging.basicConfig( level=logging.DEBUG)
+log = logging.getLogger("wsc_ui") 
+
+class UIDeviceManager():
+    
+    def __init__(self, deviceClass, wsc):
+        self.__devices = {}
+        self.__deviceClass = deviceClass
+        self.__deviceLayout = QtWidgets.QVBoxLayout()
+        self.__deviceLayout.setAlignment(QtCore.Qt.AlignTop)
+        self.__deviceLayout.setContentsMargins(9, 0, 9, 0)
+        self.__deviceLayout.setSpacing(0)
+        self.__wsc = wsc
+
+    def refreshDevices(self, devices):
+        for deviceMAC in list(devices.keys()):
+            if deviceMAC in self.__devices.keys():
+                self.__devices[deviceMAC].updateDevice(devices[deviceMAC])
+            else:
+                self.__devices[deviceMAC] = self.__deviceClass(devices[deviceMAC])
+                self.__deviceLayout.addWidget(self.__devices[deviceMAC])
+        for deviceMAC in list(self.__devices.keys()):
+            if deviceMAC not in devices.keys():
+                self.removeDevice(deviceMAC)
+
+    def removeDevice(self, deviceMAC):
+        self.__deviceLayout.removeWidget(self.__devices[deviceMAC])
+        sip.delete(self.__devices[deviceMAC])
+        self.__devices.pop(deviceMAC)
+
+    def returnLayout(self):
+        return self.__deviceLayout
+
+    def filterDevices(self, searchPhase):
+        log.debug("This currently doesn't work")
+
+    def printDevices(self):
+        print(self.__devices)
+
+
+class MainWindow(QtWidgets.QMainWindow):
+    def __init__(self, wsc):
+        super(MainWindow, self).__init__()
+        self.__ui = Ui_MainWindow()
+        self.__ui.setupUi(self)
+        self.__wsc = wsc
+
+        p = self.__ui.contentEncoder.palette()
+        p.setColor(self.backgroundRole(), QtCore.Qt.white)
+        self.__ui.contentEncoder.setPalette(p)
+
+        self.__ui.statusMessageWidget = QtWidgets.QLabel()
+        self.__ui.statusMessageWidget.setText("Starting WSC...")
+        self.__ui.statusMessageWidget.setAlignment(QtCore.Qt.AlignLeft)
+        self.__ui.statusbar.addWidget(self.__ui.statusMessageWidget, 1)
+
+        self.__org = 'IdeasX'
+        self.__app = 'Workstation-Client'
+
+        self.restoreSettings()
+
+        #self.__ui.buttonSettings.clicked.connect(self.updateBrokerSettings)
+        self.__ui.buttonBoxNetwork.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.updateBrokerSettings)
+        self.__ui.buttonBoxNetwork.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.restoreBrokerSettings)
+
+
+    def setEncoderLayout(self, layout):
+        self.__ui.contentEncoder.setLayout(layout)
+
+    def setStatusBarMessage(self, msg):
+        self.__ui.statusbar.clearMessage()
+        self.__ui.statusMessageWidget.setText(msg)
+
+    def setStatusBarUpdate(self, msg):
+        self.__ui.statusbar.showMessage(msg)
+
+    def saveSettings(self):
+        # MainWindow Settings
+        settings = QtCore.QSettings(self.__org, self.__app)
+        settings.beginGroup("MainWindow")
+        settings.setValue("size", self.size())
+        settings.setValue("pos", self.pos())
+        settings.endGroup()
+
+    def updateBrokerSettings(self):
+        print(self.__ui.networkBroker.text())
+        self.__NetworkBroker = self.__ui.networkBroker.text()
+        self.__LocalBroker = self.__ui.localBroker.text()
+        self.__NetworkPort = int(self.__ui.networkPort.text())
+        self.__LocalPort = int(self.__ui.localPort.text())
+
+        settings = QtCore.QSettings(self.__org, self.__app)
+        settings.beginGroup("Broker")
+        settings.setValue('NetworkBroker', self.__NetworkBroker)
+        settings.setValue('NetworkPort', self.__NetworkPort)
+        settings.setValue('LocalBroker', self.__LocalBroker)
+        settings.setValue('LocalPort', self.__LocalPort)
+        settings.endGroup()
+        self.saveSettings()
+        self.__wsc.restartWSC()
+
+    def restoreSettings(self):
+        settings = QtCore.QSettings(self.__org, self.__app)
+        settings.beginGroup("MainWindow")
+        self.resize(settings.value("size", QtCore.QSize(525, 648)))
+        self.move(settings.value("pos", QtCore.QPoint(0, 0)))
+        settings.endGroup()
+
+        settings.beginGroup("Broker")
+        self.__NetworkBroker = settings.value('NetworkBroker', 'ideasx.duckdns.org')
+        self.__NetworkPort = settings.value('NetworkPort', 1883)
+        self.__LocalBroker = settings.value('LocalBroker', '10.42.0.1')
+        self.__LocalPort = settings.value('LocalPort', 1883)
+        settings.endGroup()
+
+        self.__ui.networkBroker.setText(self.__NetworkBroker)
+        self.__ui.networkPort.setText(str(self.__NetworkPort))
+        self.__ui.localBroker.setText(self.__LocalBroker)
+        self.__ui.localPort.setText(str(self.__LocalPort))
+
+        settings.beginGroup('OTAServer')
+        self.__OTAServer = settings.value('OTAServer', 'ideasx.duckdns.org')
+        settings.endGroup()
+
+    def restoreBrokerSettings(self):
+        settings = QtCore.QSettings(self.__org, self.__app)
+
+        settings.beginGroup("Broker")
+        self.__NetworkBroker = settings.value('NetworkBroker', 'ideasx.duckdns.org')
+        self.__NetworkPort = settings.value('NetworkPort', 1883)
+        self.__LocalBroker = settings.value('LocalBroker', '10.42.0.1')
+        self.__LocalPort = settings.value('LocalPort', 1883)
+        settings.endGroup()
+
+        self.__ui.networkBroker.setText(self.__NetworkBroker)
+        self.__ui.networkPort.setText(str(self.__NetworkPort))
+        self.__ui.localBroker.setText(self.__LocalBroker)
+        self.__ui.localPort.setText(str(self.__LocalPort))
+
+
+    def closeEvent(self, event):
+        self.saveSettings()
+        super(MainWindow, self).closeEvent(event)
+
+
+
+if __name__ == "__main__":
+    app = QtWidgets.QApplication(sys.argv)
+    app.setWindowIcon(QtGui.QIcon('icon/logo/ideasx.png'))
+    wsc = WSC_Client()
+    mainWindow = MainWindow(wsc)
+    encoderManager = UIDeviceManager(EncoderUI, wsc)
+    mainWindow.setEncoderLayout(encoderManager.returnLayout())
+
+    wsc.encoderUpdate.connect(encoderManager.refreshDevices)
+    wsc.networkStatus.connect(mainWindow.setStatusBarMessage)
+    wsc.networkUpdate.connect(mainWindow.setStatusBarUpdate)
+
+    wsc.StartWorkstationClient(gui=True)
+
+    mainWindow.show()
+    sys.exit(app.exec_())

Some files were not shown because too many files changed in this diff