IdeasXWSCView.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. '''
  2. IdeasXWSCView.py
  3. TODO: Fix activate / deactivate functionality [done]
  4. TODO: Fix subscribe / unsubscribe functionality in backend [done]
  5. TODO: Develop queue message thinger for sub unsub with feedback from backend
  6. TODO: Develop structure for creating alias for device
  7. TODO: Develop structure for saving information into backend
  8. '''
  9. import sys
  10. import time
  11. import sip
  12. from PyQt5 import QtCore, QtGui, QtWidgets
  13. from pyqt.mainwindow2 import Ui_MainWindow
  14. from pyqt.ideasxdevice import Ui_IdeasXDevice
  15. from IdeasXWSCBackend import IdeasXWSCNetworkThread
  16. from pyqt.encoderconfigurationdialog import Ui_SwitchConfigDialog
  17. from pyqt.devicedialog import Ui_Dialog
  18. from ParsingTools import ParsingTools
  19. class IdeasXSwitchDialog(QtWidgets.QDialog):
  20. def __init__(self, switch, assignedKey):
  21. super(IdeasXSwitchDialog, self).__init__()
  22. self.__ui = Ui_SwitchConfigDialog()
  23. self.__ui.setupUi(self)
  24. self.__ui.buttonApply.clicked.connect(self.submitOnClose)
  25. self.key = assignedKey[0]
  26. self.enable = assignedKey[1]
  27. self.switch = switch
  28. self.__ui.checkSwitchEnable.setChecked(self.enable)
  29. self.__ui.lineSwitchKey.setText(self.key)
  30. def submitOnClose(self):
  31. self.key = self.__ui.lineSwitchKey.text()
  32. self.enable = self.__ui.checkSwitchEnable.isChecked()
  33. self.accept()
  34. class IdeasXDeviceInformationDialog(QtWidgets.QDialog):
  35. newDeviceName = QtCore.pyqtSignal(['QString'], name='newDeviceName')
  36. def __init__(self):
  37. super(IdeasXDeviceInformationDialog, self).__init__()
  38. self.__ui = Ui_Dialog()
  39. self.__ui.setupUi(self)
  40. self.__ui.lineAlias.textEdited.connect(lambda: self.newDeviceName.emit(self.__ui.lineAlias.text()))
  41. def updateDisplay(self, encoder):
  42. self.__ui.labelBatteryCapacity.setText(str(encoder['soc']))
  43. self.__ui.labelBatteryVoltage.setText(str(encoder['vcell']))
  44. self.__ui.labelLowBattery.setText(str(encoder['lb']))
  45. self.__ui.labelChargeState.setText('N/A')
  46. self.__ui.labelActiveFlag.setText('N/A')
  47. self.__ui.labelAliveFlag.setText(str(encoder['alive']))
  48. self.__ui.labelFirmwareVersion.setText(str(encoder['firmware_version']))
  49. self.__ui.labelHardwareVersion.setText(str(encoder['hardware_version']))
  50. self.__ui.labelOTAFlag.setText(str(encoder['ota']))
  51. self.__ui.labelROMSlot.setText(str(encoder['rom']))
  52. self.__ui.labelMAC.setText(str(encoder['module_id']))
  53. self.__ui.labelRSSI.setText(str(encoder['rssi']))
  54. self.__ui.labelSSID.setText(encoder['ssid'])
  55. class IdeasXDeviceManager():
  56. def __init__(self, deviceClass, wsc):
  57. self.__devices = {}
  58. self.__deviceClass = deviceClass
  59. self.__deviceLayout = QtWidgets.QVBoxLayout()
  60. self.__deviceLayout.setAlignment(QtCore.Qt.AlignTop)
  61. self.__deviceLayout.setContentsMargins(9, 0, 9, 0)
  62. self.__deviceLayout.setSpacing(0)
  63. self.__wsc = wsc
  64. def refreshDevices(self, devices):
  65. for deviceMAC in list(devices.keys()):
  66. if deviceMAC in self.__devices.keys():
  67. print("Updating Device")
  68. self.__devices[deviceMAC].updateDevice(devices[deviceMAC])
  69. else:
  70. print("Adding Device")
  71. self.__devices[deviceMAC] = self.__deviceClass(devices[deviceMAC], self.__wsc)
  72. self.__deviceLayout.addWidget(self.__devices[deviceMAC])
  73. for deviceMAC in list(self.__devices.keys()):
  74. if deviceMAC not in devices.keys():
  75. print("Removing Device")
  76. self.removeDevice(deviceMAC)
  77. def removeDevice(self, deviceMAC):
  78. self.__deviceLayout.removeWidget(self.__devices[deviceMAC])
  79. sip.delete(self.__devices[deviceMAC])
  80. self.__devices.pop(deviceMAC)
  81. def returnLayout(self):
  82. return self.__deviceLayout
  83. def filterDevices(self, searchPhase):
  84. print("This currently doesn't work")
  85. def printDevices(self):
  86. print(self.__devices)
  87. class IdeasXEncoder(QtWidgets.QWidget):
  88. sendCommand = QtCore.pyqtSignal(['QString'], name='sendCommand')
  89. activateDevice = QtCore.pyqtSignal(['QString', 'QString'], name='deactivateDevice')
  90. deactivateDevice = QtCore.pyqtSignal(['QString', 'QString'], name='activateDevice')
  91. __pathToIcon = {'network': './icon/network/',
  92. 'battery': './icon/battery/',
  93. 'battery_charging': './icon/battery/',
  94. 'switch': './icon/switch/'
  95. }
  96. __icon = {'network': ['network-wireless-offline-symbolic.png',
  97. 'network-wireless-signal-weak-symbolic.png',
  98. 'network-wireless-signal-ok-symbolic.png',
  99. 'network-wireless-signal-good-symbolic.png',
  100. 'network-wireless-signal-excellent-symbolic.png'],
  101. 'battery': ['battery-empty-symbolic.png',
  102. 'battery-caution-symbolic.png',
  103. 'battery-low-symbolic.png',
  104. 'battery-good-symbolic.png',
  105. 'battery-full-symbolic.png'],
  106. 'battery_charging': ['battery-empty-charging-symbolic.png',
  107. 'battery-caution-charging-symbolic.png',
  108. 'battery-low-charging-symbolic.png',
  109. 'battery-good-charging-symbolic.png',
  110. 'battery-full-charged-symbolic.png'],
  111. 'switch': ['switch-one-enabled.png',
  112. 'switch-one-disabled.png',
  113. 'switch-two-enabled.png',
  114. 'switch-two-disabled.png',
  115. 'switch-adaptive-enabled.png',
  116. 'switch-adaptive-disabled.png']
  117. }
  118. __deviceType = '/encoder/'
  119. def __init__(self, encoder, wsc):
  120. self.__deviceName = None
  121. self.__wsc = wsc
  122. # Setup UI components
  123. super(IdeasXEncoder, self).__init__()
  124. self.__ui = Ui_IdeasXDevice()
  125. self.__ui.setupUi(self)
  126. self._parserTools = ParsingTools()
  127. self.updateDevice(encoder)
  128. self.updateSwitchIcons()
  129. # Setup Signals
  130. self.setupMenu()
  131. self.__ui.buttonActivate.clicked.connect(self.activateEncoder)
  132. self.__ui.buttonSwitchOne.clicked.connect(lambda: self.openSwitchDialog(self.__wsc.keyEmulator.switchOne,
  133. self.__wsc.keyEmulator.getAssignedKey(self.__strModuleID, self.__wsc.keyEmulator.switchOne)))
  134. self.__ui.buttonSwitchTwo.clicked.connect(lambda: self.openSwitchDialog(self.__wsc.keyEmulator.switchTwo,
  135. self.__wsc.keyEmulator.getAssignedKey(self.__strModuleID, self.__wsc.keyEmulator.switchTwo)))
  136. self.activateDevice.connect(self.__wsc.activateEncoder)
  137. self.deactivateDevice.connect(self.__wsc.deactivateEncoder)
  138. def openDeviceInformation(self):
  139. dialog = IdeasXDeviceInformationDialog()
  140. dialog.updateDisplay(self.__wsc.encoders[self.__strModuleID])
  141. dialog.newDeviceName.connect(self.setDeviceAlisas)
  142. dialog.exec()
  143. def openSwitchDialog(self, switch, assignedKey):
  144. dialog = IdeasXSwitchDialog(switch, assignedKey)
  145. if dialog.exec_():
  146. if dialog.key != None and len(dialog.key) == 1:
  147. self.__wsc.keyEmulator.assignKey(self.__strModuleID, dialog.switch, dialog.key, dialog.enable)
  148. self.updateSwitchIcons()
  149. def setupSwitchIcon(self, path):
  150. icon = QtGui.QIcon()
  151. iconPath = self.__pathToIcon['switch']
  152. iconPath = iconPath + path
  153. icon.addPixmap(QtGui.QPixmap(iconPath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
  154. return icon
  155. def setupMenu(self):
  156. shutdownAction = QtWidgets.QAction('Shutdown Encoder', self)
  157. resetAction = QtWidgets.QAction("Reset Encoder", self)
  158. testKeysAction = QtWidgets.QAction("Test Keys", self)
  159. openInfoAction = QtWidgets.QAction("Device Information", self)
  160. startOTAAction = QtWidgets.QAction("OTA Update", self)
  161. testKeysAction.triggered.connect(self.testKeys)
  162. openInfoAction.triggered.connect(self.openDeviceInformation)
  163. startOTAAction.triggered.connect(lambda: self.__wsc.updateDevice(self.__strModuleID, None))
  164. shutdownAction.triggered.connect(lambda: self.__wsc.shutdownDevice(self.__strModuleID, None))
  165. resetAction.triggered.connect(lambda: self.__wsc.resetDevice(self.__strModuleID, None))
  166. deviceMenu = QtWidgets.QMenu()
  167. deviceMenu.addAction(shutdownAction)
  168. deviceMenu.addAction(resetAction)
  169. deviceMenu.addAction(openInfoAction)
  170. deviceMenu.addSection("Engineering Tools")
  171. deviceMenu.addAction(testKeysAction)
  172. deviceMenu.addAction(startOTAAction)
  173. self.__ui.buttonMenu.setPopupMode(2)
  174. self.__ui.buttonMenu.setMenu(deviceMenu)
  175. self.__ui.buttonMenu.setStyleSheet("* { padding-right: 3px } QToolButton::menu-indicator { image: none }")
  176. def activateEncoder(self):
  177. if self.__ui.buttonActivate.text() == "Activate":
  178. print("Activating Encoder: " + self.__ui.labelModuleID.text())
  179. self.activateDevice.emit(self.__strModuleID, self.__deviceType)
  180. self.__ui.buttonActivate.setText("Deactivate")
  181. else:
  182. print("Deactivating Encoder: " + self.__ui.labelModuleID.text())
  183. self.deactivateDevice.emit(self.__strModuleID, self.__deviceType)
  184. self.__ui.buttonActivate.setText("Activate")
  185. def updateDevice(self, encoder):
  186. self.__rssi = encoder['rssi']
  187. self.__soc = self._parserTools.calculateSOC(encoder['soc'])
  188. self.__vcell = self._parserTools.calculateVCell(encoder['vcell'])
  189. self.__strModuleID = self._parserTools.macToString(encoder['module_id'])
  190. self.__updateTime = encoder['time']
  191. self.__ota = encoder['ota']
  192. if self.__deviceName == None:
  193. self.setModuleID(self.__strModuleID)
  194. self.setSOCIcon(self.__soc)
  195. self.setRSSIIcon(self.__rssi)
  196. self.setStatusTime(self.__updateTime)
  197. self.setOTAIcon(self.__ota)
  198. def setOTAIcon(self, ota):
  199. if ota:
  200. self.__ui.labelOTA.show()
  201. else:
  202. self.__ui.labelOTA.hide()
  203. def setModuleID(self, strModuleID):
  204. self.__ui.labelModuleID.setText(strModuleID)
  205. def setDeviceAlisas(self, label):
  206. self.__deviceName = label
  207. if label != None or label != "":
  208. self.__ui.labelModuleID.setText(label)
  209. else:
  210. self.__ui.labelModuleID.setText(self.__strModuleID)
  211. def setSOCIcon(self, soc):
  212. if soc >= 75:
  213. batteryIcon = 4
  214. elif soc >= 50 and soc < 75:
  215. batteryIcon = 3
  216. elif soc >= 25 and soc < 50:
  217. batteryIcon = 2
  218. elif soc >=10 and soc < 25:
  219. batteryIcon = 1
  220. elif soc < 10:
  221. batteryIcon = 0
  222. batteryIcon = self.__pathToIcon['battery']+self.__icon['battery'][batteryIcon]
  223. self.__ui.labelBattery.setPixmap(QtGui.QPixmap(batteryIcon))
  224. self.__ui.labelBattery.setToolTip(str(soc) + "%")
  225. def setStatusTime(self, updateTime):
  226. lastUpdate = time.ctime(updateTime).replace(" ", " ").split(" ")
  227. currentTime = time.ctime().replace(" ", " ").split(" ")
  228. if currentTime[1] != lastUpdate[1] or currentTime[2] != lastUpdate[2] or currentTime[4] != lastUpdate[4]:
  229. lastUpdate = lastUpdate[1] + " " + lastUpdate[2] + " " + lastUpdate[4]
  230. else:
  231. lastUpdate = lastUpdate[3]
  232. self.__ui.labelStatus.setText("Last Update: " + lastUpdate)
  233. def setRSSIIcon(self, rssi):
  234. if rssi >= -50:
  235. rssiIcon = 4
  236. elif rssi >= -60 and rssi < -50:
  237. rssiIcon = 3
  238. elif rssi >= -70 and rssi < -60:
  239. rssiIcon = 2
  240. elif rssi < -70:
  241. rssiIcon = 1
  242. rssiIcon = self.__pathToIcon['network'] + self.__icon['network'][rssiIcon]
  243. self.__ui.labelSignal.setPixmap(QtGui.QPixmap(rssiIcon))
  244. self.__ui.labelSignal.setToolTip(str(rssi) + " dBm")
  245. def updateSwitchIcons(self):
  246. keys = self.__wsc.keyEmulator.getAssignedKeys(self.__strModuleID)
  247. if keys[self.__wsc.keyEmulator.switchOne][1]:
  248. iconPath = self.__icon['switch'][0]
  249. else:
  250. iconPath = self.__icon['switch'][1]
  251. self.__ui.buttonSwitchOne.setIcon(self.setupSwitchIcon(iconPath))
  252. if keys[self.__wsc.keyEmulator.switchTwo][1]:
  253. iconPath = self.__icon['switch'][2]
  254. else:
  255. iconPath = self.__icon['switch'][3]
  256. self.__ui.buttonSwitchTwo.setIcon(self.setupSwitchIcon(iconPath))
  257. if keys[self.__wsc.keyEmulator.switchAdaptive][1]:
  258. iconPath = self.__icon['switch'][4]
  259. else:
  260. iconPath = self.__icon['switch'][5]
  261. self.__ui.buttonSwitchAdaptive.setIcon(self.setupSwitchIcon(iconPath))
  262. def testKeys(self):
  263. time.sleep(3)
  264. for payload in [1, 0, 2, 0, 4, 0]:
  265. self.__wsc.keyEmulator.emulateKey(self.__strModuleID, payload)
  266. time.sleep(0.1)
  267. class IdeasXMainWindow(QtWidgets.QMainWindow):
  268. def __init__(self, wsc):
  269. super(IdeasXMainWindow, self).__init__()
  270. self.__ui = Ui_MainWindow()
  271. self.__ui.setupUi(self)
  272. self.__wsc = wsc
  273. p = self.__ui.contentEncoder.palette()
  274. p.setColor(self.backgroundRole(), QtCore.Qt.white)
  275. self.__ui.contentEncoder.setPalette(p)
  276. self.__ui.statusMessageWidget = QtWidgets.QLabel()
  277. self.__ui.statusMessageWidget.setText("Starting WSC...")
  278. self.__ui.statusMessageWidget.setAlignment(QtCore.Qt.AlignLeft)
  279. self.__ui.statusbar.addWidget(self.__ui.statusMessageWidget, 1)
  280. self.__org = 'IdeasX'
  281. self.__app = 'Workstation-Client'
  282. self.restoreSettings()
  283. #self.__ui.buttonSettings.clicked.connect(self.updateBrokerSettings)
  284. self.__ui.buttonBoxNetwork.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.updateBrokerSettings)
  285. self.__ui.buttonBoxNetwork.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.restoreBrokerSettings)
  286. def setEncoderLayout(self, layout):
  287. self.__ui.contentEncoder.setLayout(layout)
  288. def setStatusBarMessage(self, msg):
  289. self.__ui.statusbar.clearMessage()
  290. self.__ui.statusMessageWidget.setText(msg)
  291. def setStatusBarUpdate(self, msg):
  292. self.__ui.statusbar.showMessage(msg)
  293. def saveSettings(self):
  294. '''
  295. Saves various backend and front information via Qt's system agnoistic classes.
  296. The following information is saved and restored:
  297. 1) MainWindow size and position
  298. 2) Switch configuration and assigned keys
  299. 3) Encoder Nicknames
  300. 4) Broker URL and Ports
  301. '''
  302. # MainWindow Settings
  303. settings = QtCore.QSettings(self.__org, self.__app)
  304. settings.beginGroup("MainWindow")
  305. settings.setValue("size", self.size())
  306. settings.setValue("pos", self.pos())
  307. settings.endGroup()
  308. def updateBrokerSettings(self):
  309. print(self.__ui.networkBroker.text())
  310. self.__NetworkBroker = self.__ui.networkBroker.text()
  311. self.__LocalBroker = self.__ui.localBroker.text()
  312. self.__NetworkPort = int(self.__ui.networkPort.text())
  313. self.__LocalPort = int(self.__ui.localPort.text())
  314. settings = QtCore.QSettings(self.__org, self.__app)
  315. settings.beginGroup("Broker")
  316. settings.setValue('NetworkBroker', self.__NetworkBroker)
  317. settings.setValue('NetworkPort', self.__NetworkPort)
  318. settings.setValue('LocalBroker', self.__LocalBroker)
  319. settings.setValue('LocalPort', self.__LocalPort)
  320. settings.endGroup()
  321. self.saveSettings()
  322. #self.__ui.statusbar.showMessage("Updated Broker settings. Please Restart the WSC.")
  323. self.__wsc.guiRestartWSC()
  324. def restoreSettings(self):
  325. settings = QtCore.QSettings(self.__org, self.__app)
  326. settings.beginGroup("MainWindow")
  327. self.resize(settings.value("size", QtCore.QSize(525, 648)))
  328. self.move(settings.value("pos", QtCore.QPoint(0, 0)))
  329. settings.endGroup()
  330. settings.beginGroup("Broker")
  331. self.__NetworkBroker = settings.value('NetworkBroker', 'ideasx.duckdns.org')
  332. self.__NetworkPort = settings.value('NetworkPort', 1883)
  333. self.__LocalBroker = settings.value('LocalBroker', '10.42.0.1')
  334. self.__LocalPort = settings.value('LocalPort', 1883)
  335. settings.endGroup()
  336. self.__ui.networkBroker.setText(self.__NetworkBroker)
  337. self.__ui.networkPort.setText(str(self.__NetworkPort))
  338. self.__ui.localBroker.setText(self.__LocalBroker)
  339. self.__ui.localPort.setText(str(self.__LocalPort))
  340. settings.beginGroup('OTAServer')
  341. self.__OTAServer = settings.value('OTAServer', 'ideasx.duckdns.org')
  342. settings.endGroup()
  343. def restoreBrokerSettings(self):
  344. settings = QtCore.QSettings(self.__org, self.__app)
  345. settings.beginGroup("Broker")
  346. self.__NetworkBroker = settings.value('NetworkBroker', 'ideasx.duckdns.org')
  347. self.__NetworkPort = settings.value('NetworkPort', 1883)
  348. self.__LocalBroker = settings.value('LocalBroker', '10.42.0.1')
  349. self.__LocalPort = settings.value('LocalPort', 1883)
  350. settings.endGroup()
  351. self.__ui.networkBroker.setText(self.__NetworkBroker)
  352. self.__ui.networkPort.setText(str(self.__NetworkPort))
  353. self.__ui.localBroker.setText(self.__LocalBroker)
  354. self.__ui.localPort.setText(str(self.__LocalPort))
  355. def closeEvent(self, event):
  356. self.saveSettings()
  357. super(IdeasXMainWindow, self).closeEvent(event)
  358. if __name__ == "__main__":
  359. app = QtWidgets.QApplication(sys.argv)
  360. app.setWindowIcon(QtGui.QIcon('icon/logo/ideasx.png'))
  361. wsc = IdeasXWSCNetworkThread()
  362. mainWindow = IdeasXMainWindow(wsc)
  363. encoderManager = IdeasXDeviceManager(IdeasXEncoder, wsc)
  364. mainWindow.setEncoderLayout(encoderManager.returnLayout())
  365. wsc.encoderUpdate.connect(encoderManager.refreshDevices)
  366. wsc.networkStatus.connect(mainWindow.setStatusBarMessage)
  367. wsc.networkUpdate.connect(mainWindow.setStatusBarUpdate)
  368. #wsc.guiStartWorkstationClient('ideasx.duckdns.org')
  369. wsc.guiStartWorkstationClient()
  370. #timer = QtCore.QTimer()
  371. #timer.timeout.connect(mainWindow.hideEncoder)
  372. #timer.start(1000)
  373. #time.sleep(0.5)
  374. mainWindow.show()
  375. sys.exit(app.exec_())