From 5c06fc131ae4a8a0d579b956c3dde5fb50a8bc28 Mon Sep 17 00:00:00 2001 From: sqozz Date: Wed, 27 Feb 2019 22:01:46 +0100 Subject: [PATCH 1/4] Add timeout and reconnect support --- example.py | 29 ++++++++++++++++++----------- sem6000.py | 54 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/example.py b/example.py index 1777856..ea3e874 100644 --- a/example.py +++ b/example.py @@ -1,16 +1,23 @@ -from sem6000 import SEMSocket import time -socket = SEMSocket('f0:c7:7f:0d:e7:17') +from sem6000 import SEMSocket -socket.login("1337") -socket.changePassword("1234") -socket.login("1234") -#socket.changePassword("1337") +# auto_reconnect_timeout enabled auto reconnect if sending a command fails. Valid values: +# None (default): everything that fails throws NotConnectedException's +# -1: infinite retries +# integer: seconds before exception is thrown + +socket = SEMSocket('f0:c7:7f:0d:e7:17', auto_reconnect_timeout=None) + +#socket.login("1337") +#socket.changePassword("1234") +#socket.login("1234") while True: - break; time.sleep(1) - socket.getStatus() - socket.setStatus(True) - print("=== {} ({}) ===".format(socket.mac_address, "on" if socket.powered else "off")) - print("\t{}V {}A → {}W@{}Hz".format(socket.voltage, socket.current, socket.power, socket.frequency)) + try: + socket.getStatus() + socket.setStatus(True) + print("=== {} ({}) ===".format(socket.mac_address, "on" if socket.powered else "off")) + print("\t{}V {}A → {}W@{}Hz".format(socket.voltage, socket.current, socket.power, socket.frequency)) + except SEMSocket.NotConnectedException: + socket.reconnect(-1) #infinite reconnect attempts diff --git a/sem6000.py b/sem6000.py index 4e9b803..d7f49f1 100644 --- a/sem6000.py +++ b/sem6000.py @@ -1,11 +1,11 @@ from bluepy import btle import struct import time -import pdb import uuid class SEMSocket(): password = "0000" + auto_reconnect_timeout = None powered = False voltage = 0 current = 0 @@ -16,18 +16,19 @@ class SEMSocket(): read_char = None write_char = None notify_char = None + connected = False - def __init__(self, mac): + def __init__(self, mac, auto_reconnect_timeout = None): self.mac_address = mac - self.btle_device = btle.Peripheral(self.mac_address,addrType=btle.ADDR_TYPE_PUBLIC,iface=0) - self.btle_handler = self.BTLEHandler(self) + self.auto_reconnect_timeout = auto_reconnect_timeout + try: + self.reconnect() + except self.NotConnectedException: + # initial connection may fail. It is up to the code what to do + pass - self.custom_service = self.btle_device.getServiceByUUID(0xfff0) - self.read_char = self.custom_service.getCharacteristics("0000fff1-0000-1000-8000-00805f9b34fb")[0] - print(self.read_char.read()) - self.write_char = self.custom_service.getCharacteristics("0000fff3-0000-1000-8000-00805f9b34fb")[0] - self.notify_char = self.custom_service.getCharacteristics("0000fff4-0000-1000-8000-00805f9b34fb")[0] - self.btle_device.setDelegate(self.btle_handler) + class NotConnectedException(Exception): + pass class BTLEMessage(): MAGIC_START = bytearray([0x0f]) @@ -73,6 +74,9 @@ class SEMSocket(): self.__data[1] = 1 + len(self.__payload) + 1 # cmd + payload + checksum def send(self): + if not self.__btle_device.connected: + self.__btle_device.reconnect(self.__btle_device.auto_reconnect_timeout) + self.__btle_device.write_char.write(self.__data, True) self.__btle_device.btle_device.waitForNotifications(5) @@ -110,7 +114,6 @@ class SEMSocket(): print ("Unknown message from Handle: 0x" + format(cHandle,'02X') + " Value: "+ format(data)) def getStatus(self): - print("GetStatus") #15, 5, 4, 0, 0, 0, 5, -1, -1 cmd = bytearray([0x04]) payload = bytearray([0x00, 0x00, 0x00]) @@ -118,7 +121,6 @@ class SEMSocket(): msg.send() def setStatus(self, status): - print("SetStatus:", status) # 0f 06 03 00 01 00 00 05 ff ff -> on # 0f 06 03 00 00 00 00 04 ff ff -> off cmd = bytearray([0x03]) @@ -127,7 +129,6 @@ class SEMSocket(): msg.send() def login(self, password): - print("Login") self.password = password cmd = bytearray([0x17]) payload = bytearray() @@ -145,7 +146,6 @@ class SEMSocket(): msg.send() def changePassword(self, newPassword): - print("Change Password") cmd = bytearray([0x17]) payload = bytearray() payload.append(0x00) @@ -162,6 +162,32 @@ class SEMSocket(): msg = self.BTLEMessage(self, cmd, payload) msg.send() + def __reconnect(self): + try: + self.btle_device = btle.Peripheral(self.mac_address,addrType=btle.ADDR_TYPE_PUBLIC,iface=0) + self.btle_handler = self.BTLEHandler(self) + + self.custom_service = self.btle_device.getServiceByUUID(0xfff0) + self.read_char = self.custom_service.getCharacteristics("0000fff1-0000-1000-8000-00805f9b34fb")[0] + self.write_char = self.custom_service.getCharacteristics("0000fff3-0000-1000-8000-00805f9b34fb")[0] + self.notify_char = self.custom_service.getCharacteristics("0000fff4-0000-1000-8000-00805f9b34fb")[0] + self.btle_device.setDelegate(self.btle_handler) + except btle.BTLEException as e: + self.connected = False + else: + self.connected = True + + def reconnect(self, timeout = None): + if timeout == None: + self.__reconnect() + else: + reconnect_start = time.time() + while abs(reconnect_start - time.time()) < timeout or timeout == -1: + self.__reconnect() + + if not self.connected: + raise self.NotConnectedException + #def SynVer(self): # print("SynVer") # self.read_char.read_value() From 4f8b698a146dc4816eefc710777ffb6fc25f401f Mon Sep 17 00:00:00 2001 From: sqozz Date: Wed, 27 Feb 2019 22:26:39 +0100 Subject: [PATCH 2/4] Change visibility of some properties --- sem6000.py | 192 ++++++++++++++++++++++++++--------------------------- 1 file changed, 95 insertions(+), 97 deletions(-) diff --git a/sem6000.py b/sem6000.py index d7f49f1..5079212 100644 --- a/sem6000.py +++ b/sem6000.py @@ -1,5 +1,4 @@ from bluepy import btle -import struct import time import uuid @@ -13,9 +12,10 @@ class SEMSocket(): frequency = 0 mac_address = "" custom_service = None - read_char = None - write_char = None - notify_char = None + _read_char = None + _write_char = None + _notify_char = None + _btle_device = None connected = False def __init__(self, mac, auto_reconnect_timeout = None): @@ -27,92 +27,6 @@ class SEMSocket(): # initial connection may fail. It is up to the code what to do pass - class NotConnectedException(Exception): - pass - - class BTLEMessage(): - MAGIC_START = bytearray([0x0f]) - MAGIC_END = bytearray([0xff, 0xff]) - __data = bytearray() - __cmd = bytearray(1) # cmd cannot be empty - __payload = bytearray() - - def __init__(self, btle_device, cmd=bytearray(), payload=bytearray()): - self.__btle_device = btle_device - self.cmd = cmd - self.payload = payload - - @property - def cmd(self): - return self.__cmd - - @cmd.setter - def cmd(self, cmd): - self.__data = self.MAGIC_START + bytearray(1) + cmd + self.payload + bytearray(1) + self.MAGIC_END - self.__cmd = cmd - self.__calc_length() - self.__calc_checksum() - - @property - def payload(self): - return self.__payload - - @payload.setter - def payload(self, payload): - self.__data = self.MAGIC_START + bytearray(1) + self.cmd + payload + bytearray(1) + self.MAGIC_END - self.__payload = payload - self.__calc_length() - self.__calc_checksum() - - def __calc_checksum(self): - checksum = 1 - for i in range(2, self.__data[1] + 2): - checksum += self.__data[i] - self.__data[-3] = checksum & 0xff - - def __calc_length(self): - self.__data[1] = 1 + len(self.__payload) + 1 # cmd + payload + checksum - - def send(self): - if not self.__btle_device.connected: - self.__btle_device.reconnect(self.__btle_device.auto_reconnect_timeout) - - self.__btle_device.write_char.write(self.__data, True) - self.__btle_device.btle_device.waitForNotifications(5) - - - class BTLEHandler(btle.DefaultDelegate): - def __init__(self, btle_device): - btle.DefaultDelegate.__init__(self) - self.btle_device = btle_device - - def handleNotification(self, cHandle, data): - message_type = data[2] - if message_type == 0x00: - if data[4] == 0x01: - print("Checksum error!") - else: - print("Unknown error:", data) - elif message_type == 0x03: #switch toggle - print("Switch toggled") - self.btle_device.getStatus() - elif message_type == 0x04: #status related data - self.btle_device.voltage = data[8] - self.btle_device.current = (data[9] << 8 | data[10]) / 1000 - self.btle_device.power = (data[5] << 16 | data[6] << 8 | data[7]) / 1000 - self.btle_device.frequency = data[11] - self.btle_device.powered = bool(data[4]) - elif message_type == 0x17: - if data[5] == 0x00 or data[5] == 0x01: - if not data[4]: - print("Login successful") - else: - print("Login failed") - else: - print("5th byte of login-response is > 1:", data) - else: - print ("Unknown message from Handle: 0x" + format(cHandle,'02X') + " Value: "+ format(data)) - def getStatus(self): #15, 5, 4, 0, 0, 0, 5, -1, -1 cmd = bytearray([0x04]) @@ -164,14 +78,14 @@ class SEMSocket(): def __reconnect(self): try: - self.btle_device = btle.Peripheral(self.mac_address,addrType=btle.ADDR_TYPE_PUBLIC,iface=0) - self.btle_handler = self.BTLEHandler(self) + self._btle_device = btle.Peripheral(self.mac_address,addrType=btle.ADDR_TYPE_PUBLIC,iface=0) + self._btle_handler = self.BTLEHandler(self) - self.custom_service = self.btle_device.getServiceByUUID(0xfff0) - self.read_char = self.custom_service.getCharacteristics("0000fff1-0000-1000-8000-00805f9b34fb")[0] - self.write_char = self.custom_service.getCharacteristics("0000fff3-0000-1000-8000-00805f9b34fb")[0] - self.notify_char = self.custom_service.getCharacteristics("0000fff4-0000-1000-8000-00805f9b34fb")[0] - self.btle_device.setDelegate(self.btle_handler) + self._custom_service = self._btle_device.getServiceByUUID(0xfff0) + self._read_char = self._custom_service.getCharacteristics("0000fff1-0000-1000-8000-00805f9b34fb")[0] + self._write_char = self._custom_service.getCharacteristics("0000fff3-0000-1000-8000-00805f9b34fb")[0] + self._notify_char = self._custom_service.getCharacteristics("0000fff4-0000-1000-8000-00805f9b34fb")[0] + self._btle_device.setDelegate(self._btle_handler) except btle.BTLEException as e: self.connected = False else: @@ -212,4 +126,88 @@ class SEMSocket(): # self.GetSynConfig() # #self.GetSN() + class NotConnectedException(Exception): + pass + class BTLEMessage(): + MAGIC_START = bytearray([0x0f]) + MAGIC_END = bytearray([0xff, 0xff]) + __data = bytearray() + __cmd = bytearray(1) # cmd cannot be empty + __payload = bytearray() + + def __init__(self, btle_device, cmd=bytearray(), payload=bytearray()): + self.__btle_device = btle_device + self.__cmd = cmd + self.__payload = payload + + @property + def cmd(self): + return self.__cmd + + @cmd.setter + def cmd(self, cmd): + self.__data = self.MAGIC_START + bytearray(1) + cmd + self.__payload + bytearray(1) + self.MAGIC_END + self.__cmd = cmd + self.__calc_length() + self.__calc_checksum() + + @property + def payload(self): + return self.__payload + + @payload.setter + def payload(self, payload): + self.__data = self.MAGIC_START + bytearray(1) + self.__cmd + payload + bytearray(1) + self.MAGIC_END + self.__payload = payload + self.__calc_length() + self.__calc_checksum() + + def __calc_checksum(self): + checksum = 1 + for i in range(2, self.__data[1] + 2): + checksum += self.__data[i] + self.__data[-3] = checksum & 0xff + + def __calc_length(self): + self.__data[1] = 1 + len(self.__payload) + 1 # cmd + payload + checksum + + def send(self): + if not self.__btle_device.connected: + self.__btle_device.reconnect(self.__btle_device.auto_reconnect_timeout) + + self.__btle_device._write_char.write(self.__data, True) + self.__btle_device._btle_device.waitForNotifications(5) + + + class BTLEHandler(btle.DefaultDelegate): + def __init__(self, btle_device): + btle.DefaultDelegate.__init__(self) + self.__btle_device = btle_device + + def handleNotification(self, cHandle, data): + message_type = data[2] + if message_type == 0x00: + if data[4] == 0x01: + print("Checksum error!") + else: + print("Unknown error:", data) + elif message_type == 0x03: #switch toggle + print("Switch toggled") + self.__btle_device.getStatus() + elif message_type == 0x04: #status related data + self.__btle_device.voltage = data[8] + self.__btle_device.current = (data[9] << 8 | data[10]) / 1000 + self.__btle_device.power = (data[5] << 16 | data[6] << 8 | data[7]) / 1000 + self.__btle_device.frequency = data[11] + self.__btle_device.powered = bool(data[4]) + elif message_type == 0x17: + if data[5] == 0x00 or data[5] == 0x01: + if not data[4]: + print("Login successful") + else: + print("Login failed") + else: + print("5th byte of login-response is > 1:", data) + else: + print ("Unknown message from Handle: 0x" + format(cHandle,'02X') + " Value: "+ format(data)) From 6c83bccbde6efd6fe65cacbc538e98ca18842f35 Mon Sep 17 00:00:00 2001 From: sqozz Date: Fri, 14 Jun 2019 19:51:47 +0200 Subject: [PATCH 3/4] Use setter for init BTLE payload data --- sem6000.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/sem6000.py b/sem6000.py index 5079212..a5d4989 100644 --- a/sem6000.py +++ b/sem6000.py @@ -48,10 +48,8 @@ class SEMSocket(): payload = bytearray() payload.append(0x00) payload.append(0x00) - payload.append(int(self.password[0])) - payload.append(int(self.password[1])) - payload.append(int(self.password[2])) - payload.append(int(self.password[3])) + for i in range(4): + payload.append(int(self.password[i])) payload.append(0x00) payload.append(0x00) payload.append(0x00) @@ -64,14 +62,10 @@ class SEMSocket(): payload = bytearray() payload.append(0x00) payload.append(0x01) - payload.append(int(newPassword[0])) - payload.append(int(newPassword[1])) - payload.append(int(newPassword[2])) - payload.append(int(newPassword[3])) - payload.append(int(self.password[0])) - payload.append(int(self.password[1])) - payload.append(int(self.password[2])) - payload.append(int(self.password[3])) + for i in range(4): + payload.append(int(self.newPassword[i])) + for i in range(4): + payload.append(int(self.password[i])) self.password = newPassword msg = self.BTLEMessage(self, cmd, payload) msg.send() @@ -138,8 +132,8 @@ class SEMSocket(): def __init__(self, btle_device, cmd=bytearray(), payload=bytearray()): self.__btle_device = btle_device - self.__cmd = cmd - self.__payload = payload + self.cmd = cmd + self.payload = payload @property def cmd(self): From bc34c9dd8fe17a226275f455f21ff12b6f7804ef Mon Sep 17 00:00:00 2001 From: sqozz Date: Wed, 13 Nov 2019 17:57:24 +0100 Subject: [PATCH 4/4] Add proper connect and disconnect functions This makes it possible to only open a short lived connection to the physical SEM6000 socket. A few old artefacts referencing __reconnect do still exist in the code. --- sem6000.py | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/sem6000.py b/sem6000.py index a5d4989..63c5283 100644 --- a/sem6000.py +++ b/sem6000.py @@ -16,7 +16,6 @@ class SEMSocket(): _write_char = None _notify_char = None _btle_device = None - connected = False def __init__(self, mac, auto_reconnect_timeout = None): self.mac_address = mac @@ -70,20 +69,19 @@ class SEMSocket(): msg = self.BTLEMessage(self, cmd, payload) msg.send() - def __reconnect(self): + @property + def connected(self): try: - self._btle_device = btle.Peripheral(self.mac_address,addrType=btle.ADDR_TYPE_PUBLIC,iface=0) - self._btle_handler = self.BTLEHandler(self) + if "conn" in self._btle_device.status().get("state"): + return True + else: + return False + except: + return False - self._custom_service = self._btle_device.getServiceByUUID(0xfff0) - self._read_char = self._custom_service.getCharacteristics("0000fff1-0000-1000-8000-00805f9b34fb")[0] - self._write_char = self._custom_service.getCharacteristics("0000fff3-0000-1000-8000-00805f9b34fb")[0] - self._notify_char = self._custom_service.getCharacteristics("0000fff4-0000-1000-8000-00805f9b34fb")[0] - self._btle_device.setDelegate(self._btle_handler) - except btle.BTLEException as e: - self.connected = False - else: - self.connected = True + def __reconnect(self): + self.disconnect() + self.connect() def reconnect(self, timeout = None): if timeout == None: @@ -96,6 +94,24 @@ class SEMSocket(): if not self.connected: raise self.NotConnectedException + def connect(self): + self.disconnect() + if not self._btle_device: + self._btle_device = btle.Peripheral(self.mac_address,addrType=btle.ADDR_TYPE_PUBLIC,iface=0) + else: + self._btle_device.connect(self.mac_address) + self._btle_handler = self.BTLEHandler(self) + + self._custom_service = self._btle_device.getServiceByUUID(0xfff0) + self._read_char = self._custom_service.getCharacteristics("0000fff1-0000-1000-8000-00805f9b34fb")[0] + self._write_char = self._custom_service.getCharacteristics("0000fff3-0000-1000-8000-00805f9b34fb")[0] + self._notify_char = self._custom_service.getCharacteristics("0000fff4-0000-1000-8000-00805f9b34fb")[0] + self._btle_device.setDelegate(self._btle_handler) + + def disconnect(self): + if self.connected == True: + self._btle_device.disconnect() + #def SynVer(self): # print("SynVer") # self.read_char.read_value() @@ -197,9 +213,7 @@ class SEMSocket(): self.__btle_device.powered = bool(data[4]) elif message_type == 0x17: if data[5] == 0x00 or data[5] == 0x01: - if not data[4]: - print("Login successful") - else: + if data[4]: print("Login failed") else: print("5th byte of login-response is > 1:", data)