wsc_device_encoder.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. import logging
  2. import sys
  3. import time
  4. import sip
  5. from wsc_tools import ParsingTools
  6. from wsc_tools import Switch
  7. from PyQt5 import QtCore
  8. from PyQt5 import QtGui
  9. from PyQt5 import QtWidgets
  10. from pyqt.ideasxdevice import Ui_IdeasXDevice
  11. from pyqt.encoderconfigurationdialog import Ui_SwitchConfigDialog
  12. from pyqt.devicedialog import Ui_Dialog
  13. pt = ParsingTools()
  14. logging.basicConfig( level=logging.DEBUG)
  15. log = logging.getLogger("wsc_device_encoder")
  16. class Encoder():
  17. FAIL = 0
  18. SUCCESS = 1
  19. ALIVE_VALUE = b'1'
  20. ALIVE_TOPIC = "alive"
  21. DEVICE_HEALTH_TOPIC = "encoder/+/health/#"
  22. DEVICE_HEALTH_QOS = 0
  23. SHUTDOWN_COMMAND_TOPIC = "shutdown"
  24. RESTART_COMMAND_TOPIC = "restart"
  25. OTA_COMMAND_TOPIC = "ota"
  26. UART_TX_COMMAND_TOPIC = "uart"
  27. COMMAND_RETAIN = False
  28. COMMAND_QOS = 1
  29. DATA_BUTTON_QOS = 0
  30. def __init__(self, device_id, mqttc):
  31. self.__device_id = device_id
  32. self.__label_default = b""
  33. self.__hw_version_default = b"0,0"
  34. self.__fw_version_default = b"0,0"
  35. self.__alive_default = b"0"
  36. self.__vcell_default = b"0"
  37. self.__charge_default = b"0"
  38. self.__lbi_default = b"0"
  39. self.__soc_default = b"0"
  40. self.__rom_default = b"0"
  41. self.__ota_default = b"0"
  42. self.__wireless_default = b"0"
  43. self.__ssid_default = b""
  44. self.__bssid_default = b""
  45. self.__rssi_default = b"0"
  46. self.__auth_default = b"0"
  47. self.__time_default = time.time()
  48. self.__fields = {"device_id": self.__device_id,
  49. "label": self.__label_default,
  50. "hw_ver": self.__hw_version_default,
  51. "fw_ver": self.__fw_version_default,
  52. "alive": self.__alive_default,
  53. "vcell": self.__vcell_default,
  54. "charge": self.__charge_default,
  55. "lbi": self.__lbi_default,
  56. "soc": self.__soc_default,
  57. "rom": self.__rom_default,
  58. "ota": self.__ota_default,
  59. "wireless": self.__wireless_default,
  60. "ssid": self.__ssid_default,
  61. "bssid": self.__bssid_default,
  62. "rssi": self.__rssi_default,
  63. "auth": self.__auth_default,
  64. "time": self.__time_default}
  65. self.__commands = {'update': self.update,
  66. 'restart': self.restart,
  67. 'shutdown': self.shutdown}
  68. self.__mqttc = mqttc
  69. self.switchOne = Switch()
  70. self.switchOne.setConfig("1")
  71. self.switchTwo = Switch()
  72. self.switchTwo.setConfig("2")
  73. self.switchAdaptive = Switch()
  74. self.RAW_COMMAND_TOPIC = "encoder/" + device_id + "/command/"
  75. self.RAW_DATA_TOPIC = "encoder/" + device_id + "/data/"
  76. self.__previous_button_payload = 0b0
  77. def getDeviceID(self):
  78. return self.__device_id
  79. def updateField(self, field, value):
  80. if field in self.__fields.keys():
  81. self.__fields[field] = value
  82. self.__fields['time'] = time.time()
  83. return Encoder.SUCCESS
  84. else:
  85. return Encoder.FAIL
  86. def listFieldNames(self):
  87. return self.__fields.keys()
  88. def listFields(self):
  89. return self.__fields
  90. def listCommandNames(self):
  91. return self.__commands.keys()
  92. def listCommands(self):
  93. return self.__commmands
  94. def getField(self, field):
  95. return self.__fields[field]
  96. def activate(self):
  97. self.__mqttc.subscribe(self.RAW_DATA_TOPIC + "button", qos=self.DATA_BUTTON_QOS)
  98. self.__mqttc.message_callback_add(self.RAW_DATA_TOPIC + "button", self.data_button_cb)
  99. log.info("activated device" + self.__device_id)
  100. def deactivate(self):
  101. self.__mqttc.unsubscribe(self.RAW_DATA_TOPIC + "button")
  102. log.info("deactivated device" + self.__device_id)
  103. # This needs to be modified for the following:
  104. # 1. only activate switch if there is change....i.e. don't activating switch 1 again which switch 1 is already
  105. # depressed and switch 2 is pressed
  106. # 2. don't be pushing mad buttons because shit is broken
  107. def data_button_cb(self, mqttc, backend_data, msg):
  108. topic = msg.topic
  109. payload = int.from_bytes(msg.payload, 'little')
  110. switchTwo = 0b100
  111. switchOne = 0b100000
  112. # press / release key
  113. if ((payload & switchOne) ^ (self.__previous_button_payload & switchOne)):
  114. print("switchOne Change")
  115. if (payload & switchOne):
  116. print("Deactivated!")
  117. self.switchOne.releaseKey()
  118. self.switchOne.turnOffOutlet()
  119. else:
  120. print("Activated!")
  121. self.switchOne.pressKey()
  122. self.switchOne.turnOnOutlet()
  123. if ((payload & switchTwo) ^ (self.__previous_button_payload & switchTwo)):
  124. print("switchTwo Change")
  125. if (payload & switchTwo):
  126. print("Deactivated!")
  127. self.switchTwo.releaseKey()
  128. self.switchTwo.turnOffOutlet()
  129. else:
  130. print("Activated!")
  131. self.switchTwo.pressKey()
  132. self.switchTwo.turnOnOutlet()
  133. self.__previous_button_payload = payload
  134. log.debug("encoder " + self.__device_id + " button payload: " + bin(payload))
  135. def update(self):
  136. self.__mqttc.publish(self.RAW_COMMAND_TOPIC+self.OTA_COMMAND_TOPIC, b'1', qos=self.COMMAND_QOS, retain=False)
  137. log.info("sent OTA command")
  138. def restart(self):
  139. self.__mqttc.publish(self.RAW_COMMAND_TOPIC+self.RESTART_COMMAND_TOPIC, b'1', qos=self.COMMAND_QOS, retain=False)
  140. log.info("sent restart command")
  141. def shutdown(self):
  142. self.__mqttc.publish(self.RAW_COMMAND_TOPIC+self.SHUTDOWN_COMMAND_TOPIC, b'1', qos=self.COMMAND_QOS, retain=False)
  143. log.info("sent shutdown command")
  144. def locate(self):
  145. self.__mqttc.publish(self.RAW_COMMAND_TOPIC+self.LOCATE_COMMAND_TOPIC, b'1', qos=self.COMMAND_QOS, retain=False)
  146. class EncoderUI(QtWidgets.QWidget):
  147. sendCommand = QtCore.pyqtSignal(['QString'], name='sendCommand')
  148. activateDevice = QtCore.pyqtSignal(['QString', 'QString'], name='deactivateDevice')
  149. deactivateDevice = QtCore.pyqtSignal(['QString', 'QString'], name='activateDevice')
  150. __pathToIcon = {'network': './icon/network/',
  151. 'battery': './icon/battery/',
  152. 'battery_charging': './icon/battery/',
  153. 'switch': './icon/switch/'
  154. }
  155. __icon = {'network': ['network-wireless-offline-symbolic.png',
  156. 'network-wireless-signal-weak-symbolic.png',
  157. 'network-wireless-signal-ok-symbolic.png',
  158. 'network-wireless-signal-good-symbolic.png',
  159. 'network-wireless-signal-excellent-symbolic.png'],
  160. 'battery': ['battery-empty-symbolic.png',
  161. 'battery-caution-symbolic.png',
  162. 'battery-low-symbolic.png',
  163. 'battery-good-symbolic.png',
  164. 'battery-full-symbolic.png'],
  165. 'battery_charging': ['battery-empty-charging-symbolic.png',
  166. 'battery-caution-charging-symbolic.png',
  167. 'battery-low-charging-symbolic.png',
  168. 'battery-good-charging-symbolic.png',
  169. 'battery-full-charged-symbolic.png'],
  170. 'switch': ['switch-one-enabled.png',
  171. 'switch-one-disabled.png',
  172. 'switch-two-enabled.png',
  173. 'switch-two-disabled.png',
  174. 'switch-adaptive-enabled.png',
  175. 'switch-adaptive-disabled.png']
  176. }
  177. __deviceType = 'encoder/'
  178. def __init__(self, encoder):
  179. self.__deviceName = None
  180. self.__encoder = encoder
  181. self.__org = 'IdeasX'
  182. self.__app = 'Workstation-Client'
  183. self.restoreSettings()
  184. # Setup UI components
  185. super(EncoderUI, self).__init__()
  186. self.__ui = Ui_IdeasXDevice()
  187. self.__ui.setupUi(self)
  188. self.updateDevice(encoder)
  189. self.updateSwitchIcons()
  190. # Setup Signals
  191. self.setupMenu()
  192. self.__ui.buttonActivate.clicked.connect(self.activateEncoder)
  193. self.__ui.buttonSwitchOne.clicked.connect(lambda: self.openSwitchDialog(self.__encoder.switchOne))
  194. self.__ui.buttonSwitchTwo.clicked.connect(lambda: self.openSwitchDialog(self.__encoder.switchTwo))
  195. #self.activateDevice.connect(self.__wsc.activateEncoder)
  196. #self.deactivateDevice.connect(self.__wsc.deactivateEncoder)
  197. def saveSettings(self, device_name):
  198. settings = QtCore.QSettings(self.__org, self.__app)
  199. settings.beginGroup(self.__encoder.getDeviceID())
  200. settings.setValue("name", device_name)
  201. settings.setValue("type", "encoder")
  202. settings.endGroup()
  203. self.setDeviceAlisas(device_name)
  204. def restoreSettings(self):
  205. settings = QtCore.QSettings(self.__org, self.__app)
  206. settings = QtCore.QSettings(self.__org, self.__app)
  207. settings.beginGroup(self.__encoder.getDeviceID())
  208. self.__deviceName = settings.value("name", self.__encoder.getDeviceID())
  209. settings.endGroup()
  210. def openDeviceInformation(self):
  211. dialog = InfoUI()
  212. dialog.updateDisplay(self.__encoder.listFields())
  213. dialog.newDeviceName.connect(self.saveSettings)
  214. dialog.exec()
  215. def openSwitchDialog(self, switch):
  216. dialog = SwitchUI(switch)
  217. if dialog.exec_():
  218. if dialog.key != None and len(dialog.key) == 1:
  219. switch.setConfig(dialog.key, latch=False,interval=0.0, release=False, enable=dialog.enable, outletEnable=dialog.outletActive)
  220. self.updateSwitchIcons()
  221. def setupSwitchIcon(self, path):
  222. icon = QtGui.QIcon()
  223. iconPath = self.__pathToIcon['switch']
  224. iconPath = iconPath + path
  225. icon.addPixmap(QtGui.QPixmap(iconPath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
  226. return icon
  227. def setupMenu(self):
  228. # create menu actions
  229. shutdownAction = QtWidgets.QAction('Shutdown', self)
  230. resetAction = QtWidgets.QAction("Reset", self)
  231. testKeysAction = QtWidgets.QAction("Test Keys", self)
  232. openInfoAction = QtWidgets.QAction("Information", self)
  233. startOTAAction = QtWidgets.QAction("OTA Update", self)
  234. # connect signals to funcitons
  235. #testKeysAction.triggered.connect(self.testKeys)
  236. openInfoAction.triggered.connect(self.openDeviceInformation)
  237. startOTAAction.triggered.connect(lambda: self.__encoder.update())
  238. shutdownAction.triggered.connect(lambda: self.__encoder.shutdown())
  239. resetAction.triggered.connect(lambda: self.__encoder.restart())
  240. # create menu options
  241. deviceMenu = QtWidgets.QMenu()
  242. deviceMenu.addAction(shutdownAction)
  243. deviceMenu.addAction(resetAction)
  244. deviceMenu.addAction(openInfoAction)
  245. deviceMenu.addSection("Engineering Tools")
  246. #deviceMenu.addAction(testKeysAction)
  247. #deviceMenu.addAction(startOTAAction)
  248. self.__ui.buttonMenu.setPopupMode(2)
  249. self.__ui.buttonMenu.setMenu(deviceMenu)
  250. self.__ui.buttonMenu.setStyleSheet("* { padding-right: 3px } QToolButton::menu-indicator { image: none }")
  251. def activateEncoder(self):
  252. if self.__ui.buttonActivate.text() == "Activate":
  253. self.__encoder.activate()
  254. self.__ui.buttonActivate.setText("Deactivate")
  255. else:
  256. self.__encoder.deactivate()
  257. self.__ui.buttonActivate.setText("Activate")
  258. def updateDevice(self, encoder):
  259. self.__encoder = encoder
  260. self.__rssi = encoder.getField('rssi')
  261. self.__soc = pt.calculateSOC(encoder.getField('soc'))
  262. self.__vcell = pt.calculateVCell(encoder.getField('vcell'))
  263. self.__strModuleID = encoder.getField('device_id')
  264. self.__updateTime = encoder.getField('time')
  265. self.__ota = encoder.getField('ota')
  266. if self.__deviceName == None:
  267. self.setModuleID(self.__strModuleID)
  268. else:
  269. self.setDeviceAlisas(self.__deviceName)
  270. self.setSOCIcon(self.__soc)
  271. self.setRSSIIcon(self.__rssi)
  272. self.setStatusTime(self.__updateTime)
  273. self.setOTAIcon(self.__ota)
  274. def setOTAIcon(self, ota):
  275. if ota == '1':
  276. self.__ui.labelOTA.show()
  277. else:
  278. self.__ui.labelOTA.hide()
  279. def setModuleID(self, strModuleID):
  280. self.__ui.labelModuleID.setText(strModuleID)
  281. def setDeviceAlisas(self, label):
  282. self.__deviceName = label
  283. if label != None or label != "":
  284. self.__ui.labelModuleID.setText(label)
  285. else:
  286. self.__ui.labelModuleID.setText(self.__strModuleID)
  287. def setSOCIcon(self, soc):
  288. soc = int(soc)
  289. if soc >= 75:
  290. batteryIcon = 4
  291. elif soc >= 50 and soc < 75:
  292. batteryIcon = 3
  293. elif soc >= 25 and soc < 50:
  294. batteryIcon = 2
  295. elif soc >=10 and soc < 25:
  296. batteryIcon = 1
  297. elif soc < 10:
  298. batteryIcon = 0
  299. batteryIcon = self.__pathToIcon['battery']+self.__icon['battery'][batteryIcon]
  300. self.__ui.labelBattery.setPixmap(QtGui.QPixmap(batteryIcon))
  301. self.__ui.labelBattery.setToolTip(str(soc) + "%")
  302. def setStatusTime(self, updateTime):
  303. lastUpdate = time.ctime(updateTime).replace(" ", " ").split(" ")
  304. currentTime = time.ctime().replace(" ", " ").split(" ")
  305. if currentTime[1] != lastUpdate[1] or currentTime[2] != lastUpdate[2] or currentTime[4] != lastUpdate[4]:
  306. lastUpdate = lastUpdate[1] + " " + lastUpdate[2] + " " + lastUpdate[4]
  307. else:
  308. lastUpdate = lastUpdate[3]
  309. self.__ui.labelStatus.setText("Last Update: " + lastUpdate)
  310. def setRSSIIcon(self, rssi):
  311. rssi = int(rssi)
  312. if rssi >= -50:
  313. rssiIcon = 4
  314. elif rssi >= -60 and rssi < -50:
  315. rssiIcon = 3
  316. elif rssi >= -70 and rssi < -60:
  317. rssiIcon = 2
  318. elif rssi < -70:
  319. rssiIcon = 1
  320. rssiIcon = self.__pathToIcon['network'] + self.__icon['network'][rssiIcon]
  321. self.__ui.labelSignal.setPixmap(QtGui.QPixmap(rssiIcon))
  322. self.__ui.labelSignal.setToolTip(str(rssi) + " dBm")
  323. def updateSwitchIcons(self):
  324. switch1 = self.__encoder.switchOne
  325. switch2 = self.__encoder.switchTwo
  326. switchA = self.__encoder.switchAdaptive
  327. switchA.deactivate()
  328. if switch1.active or switch1.outletActive:
  329. iconPath = self.__icon['switch'][0]
  330. else:
  331. iconPath = self.__icon['switch'][1]
  332. self.__ui.buttonSwitchOne.setIcon(self.setupSwitchIcon(iconPath))
  333. if switch2.active or switch2.outletActive:
  334. iconPath = self.__icon['switch'][2]
  335. else:
  336. iconPath = self.__icon['switch'][3]
  337. self.__ui.buttonSwitchTwo.setIcon(self.setupSwitchIcon(iconPath))
  338. if switchA.active:
  339. iconPath = self.__icon['switch'][4]
  340. else:
  341. iconPath = self.__icon['switch'][5]
  342. self.__ui.buttonSwitchAdaptive.setIcon(self.setupSwitchIcon(iconPath))
  343. def testKeys(self):
  344. time.sleep(3)
  345. for payload in [1, 0, 2, 0, 4, 0]:
  346. self.__wsc.keyEmulator.emulateKey(self.__strModuleID, payload)
  347. time.sleep(0.1)
  348. class SwitchUI(QtWidgets.QDialog):
  349. def __init__(self, switch):
  350. super(SwitchUI, self).__init__()
  351. self.__ui = Ui_SwitchConfigDialog()
  352. self.__ui.setupUi(self)
  353. self.__ui.buttonApply.clicked.connect(self.submitOnClose)
  354. self.key = switch.getKey()
  355. self.enable = switch.getActive()
  356. self.outletActive = switch.getOutletActive()
  357. self.switch = switch
  358. self.__ui.checkSwitchEnable.setChecked(self.enable)
  359. self.__ui.checkOutletEnable.setChecked(self.outletActive)
  360. self.__ui.lineSwitchKey.setText(self.key)
  361. def submitOnClose(self):
  362. self.key = self.__ui.lineSwitchKey.text()
  363. self.enable = self.__ui.checkSwitchEnable.isChecked()
  364. self.outletActive = self.__ui.checkOutletEnable.isChecked()
  365. self.accept()
  366. class InfoUI(QtWidgets.QDialog):
  367. newDeviceName = QtCore.pyqtSignal(['QString'], name='newDeviceName')
  368. def __init__(self):
  369. super(InfoUI, self).__init__()
  370. self.__ui = Ui_Dialog()
  371. self.__ui.setupUi(self)
  372. self.__ui.lineAlias.textEdited.connect(lambda: self.newDeviceName.emit(self.__ui.lineAlias.text()))
  373. def updateDisplay(self, encoder):
  374. self.__ui.labelBatteryCapacity.setText(encoder['soc'].decode('utf-8'))
  375. self.__ui.labelBatteryVoltage.setText(str(encoder['vcell'].decode('utf-8')))
  376. self.__ui.labelLowBattery.setText(str(encoder['lbi'].decode('utf-8')))
  377. self.__ui.labelChargeState.setText('N/A')
  378. self.__ui.labelActiveFlag.setText('N/A')
  379. self.__ui.labelAliveFlag.setText(str(encoder['alive'].decode('utf-8')))
  380. self.__ui.labelFirmwareVersion.setText(str(encoder['fw_ver'].decode('utf-8')))
  381. self.__ui.labelHardwareVersion.setText(str(encoder['hw_ver'].decode('utf-8')))
  382. self.__ui.labelOTAFlag.setText(str(encoder['ota'].decode('utf-8')))
  383. self.__ui.labelROMSlot.setText(str(encoder['rom'].decode('utf-8')))
  384. self.__ui.labelMAC.setText(str(encoder['device_id']))
  385. self.__ui.labelRSSI.setText(str(encoder['rssi'].decode('utf-8')))
  386. self.__ui.labelSSID.setText(str(encoder['ssid'].decode('utf-8')))
  387. if __name__ == "__main__":
  388. deviceID = '23:45:21:23:32'
  389. encoder = Encoder(deviceID, None)
  390. print(pt.calculateSOC(encoder.getField('soc')))
  391. app = QtWidgets.QApplication(sys.argv)
  392. encoderUI = EncoderUI(encoder)
  393. encoderUI.show()
  394. sys.exit(app.exec_())