IdeasXWSCView.py 18 KB

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