def _receive(self) -> RSCPDTO: logger.debug("Waiting for response from " + str(self.ip)) decrypted_data = None wait = 0.01 while not decrypted_data: try: data = self.socket.recv(self.BUFFER_SIZE) logger.debug('Received ' + str(len(data)) + ' Bytes') if len(data) == 0: self.socket.close() raise RSCPCommunicationError( "Did not receive data from e3dc", logger) self.rscp_utils = RSCPUtils() decrypted_data = self.encrypt_decrypt.decrypt(data) except BlockingIOError: logger.debug('Keine Daten empfangen, warte ' + str(wait) + 's') time.sleep(wait) wait *= 2 if wait > 2: raise #rawdata = binascii.hexlify(decrypted_data) #logger.debug('Response RAW: ' + str(rawdata)) rscp_dto = self.rscp_utils.decode_data(decrypted_data) logger.debug("Received DTO Type: " + rscp_dto.type.name + ", DTO Tag: " + rscp_dto.tag.name) return rscp_dto
def __init__(self, username, password, ip, key): self.password = password self.username = username self.encrypt_decrypt = RSCPEncryptDecrypt(key) self.ip = ip self.socket = None self.rscp_utils = RSCPUtils()
def __init__(self, username, password, ip, key): self.password = password self.username = username self.ip = ip self.socket = None self.key = key self.waittime = 0.01 self.rscp_utils = RSCPUtils()
def __init__(self, username, password, identifier, url = None): logger.debug('Initialisiere E3DC-Websockets') if not url: url = 'wss://s10.e3dc.com/ws' self.password = password self.username = username self.rscp_utils = RSCPUtils() self.identifier = identifier self.url = url logger.debug('Init abgeschlossen')
def _receive(self) -> RSCPDTO: logger.info("Waiting for response from " + str(self.ip)) data = self.socket.recv(self.BUFFER_SIZE) if len(data) == 0: self.socket.close() raise RSCPCommunicationError("Did not receive data from e3dc", logger) self.rscp_utils = RSCPUtils() decrypted_data = self.encrypt_decrypt.decrypt(data) rscp_dto = self.rscp_utils.decode_data(decrypted_data) logger.debug("Received DTO Type: " + rscp_dto.type.name + ", DTO Tag: " + rscp_dto.tag.name) return rscp_dto
def test_decode_frame_raises_no_exception(): rscp = RSCPUtils() hex_ = "e3dc" + \ "1010" + \ int.to_bytes(1564732130, length=8, byteorder=sys.byteorder).hex() + \ int.to_bytes(94967295, length=4, byteorder=sys.byteorder).hex() + \ int.to_bytes(256, length=2, byteorder=sys.byteorder).hex() + \ bytearray(256).hex() checksum = zlib.crc32(bytes.fromhex(hex_)) complete_hex = hex_ + int.to_bytes( checksum, length=4, byteorder=sys.byteorder).hex() data, timestamp = rscp._decode_frame(bytes.fromhex(complete_hex)) assert timestamp == 1564827097.295
def test_decode_data_raises_checksum_exception(): rscp = RSCPUtils() hex_ = "e3dc" + \ "1010" + \ int.to_bytes(1564732130, length=8, byteorder=sys.byteorder).hex() + \ int.to_bytes(94967295, length=4, byteorder=sys.byteorder).hex() + \ int.to_bytes(256, length=2, byteorder=sys.byteorder).hex() + \ bytearray(256).hex() checksum = 156 complete_hex = hex_ + int.to_bytes( checksum, length=4, byteorder=sys.byteorder).hex() with pytest.raises(RSCPFrameError): rscp.decode_data(bytes.fromhex(complete_hex))
def test_decode_data_returns_correct_value(): rscp = RSCPUtils() hex_ = "e3dc" + \ "1010" + \ int.to_bytes(1564732130, length=8, byteorder=sys.byteorder).hex() + \ int.to_bytes(94967295, length=4, byteorder=sys.byteorder).hex() + \ int.to_bytes(11, length=2, byteorder=sys.byteorder).hex() + \ "01000001" + "07" + struct.pack("<H", 4).hex() + struct.pack("<I", 98562).hex() checksum = zlib.crc32(bytes.fromhex(hex_)) complete_hex = hex_ + int.to_bytes( checksum, length=4, byteorder=sys.byteorder).hex() rscp_dto = rscp.decode_data(bytes.fromhex(complete_hex)) assert rscp_dto.tag == RSCPTag(0x01000001) assert rscp_dto.type == RSCPType(0x07) assert rscp_dto.data == 98562
def test_encrypted_frame_can_be_decrypted(): encryptor = RSCPEncryptDecrypt("my_key") rscp_utils = RSCPUtils() encoded_data = rscp_utils.encode_data( RSCPDTO(RSCPTag.RSCP_REQ_AUTHENTICATION, RSCPType.Container, [ RSCPDTO(RSCPTag.RSCP_AUTHENTICATION_USER, RSCPType.CString, 'username'), RSCPDTO(RSCPTag.RSCP_AUTHENTICATION_PASSWORD, RSCPType.CString, 'password') ])) framed_data = rscp_utils.encode_frame(encoded_data) encrypted_data = encryptor.encrypt(framed_data) decrypted_data = encryptor.decrypt(encrypted_data) redecoded_data = rscp_utils.decode_data(decrypted_data) assert redecoded_data.tag == RSCPTag.RSCP_REQ_AUTHENTICATION assert redecoded_data.type == RSCPType.Container assert len(redecoded_data.data) == 2 assert redecoded_data.data[0].tag == RSCPTag.RSCP_AUTHENTICATION_USER assert redecoded_data.data[0].type == RSCPType.CString assert redecoded_data.data[0].data == 'username' assert redecoded_data.data[1].tag == RSCPTag.RSCP_AUTHENTICATION_PASSWORD assert redecoded_data.data[1].type == RSCPType.CString assert redecoded_data.data[1].data == 'password'
class E3DC: PORT = 5033 BUFFER_SIZE = 1024 * 32 def __init__(self, username, password, ip, key): self.password = password self.username = username self.ip = ip self.socket = None self.key = key self.waittime = 0.01 self.rscp_utils = RSCPUtils() def create_encrypt(self): self.encrypt_decrypt = RSCPEncryptDecrypt(self.key) def send_requests2(self, payload: [Union[RSCPDTO, RSCPTag]], waittime=0.0) -> [RSCPDTO]: """ This function will send a list of requests consisting of RSCPDTO's oder RSCPTag's to the e3dc and returns a list of responses. i.e. responses = send_requests([RSCPTag.EMS_REQ_BAT_SOC, RSCPTag.EMS_REQ_POWER_PV, RSCPTag.EMS_REQ_POWER_BAT, RSCPTag.EMS_REQ_POWER_GRID, RSCPTag.EMS_REQ_POWER_WB_ALL]) :param payload: A list of requests :return: A list of responses in form of RSCPDTO's """ dto_list: [RSCPDTO] = [] for payload_element in payload: if isinstance(payload_element, RSCPTag): dto_list.append(RSCPDTO(payload_element)) else: dto_list.append(payload_element) logger.debug("Sending " + str(len(dto_list)) + " requests to " + str(self.ip)) responses: [RSCPDTO] = [] dto: RSCPDTO for dto in dto_list: response = self.send_request(dto, True, waittime=waittime) responses.append(response) return responses def send_requests(self, payload: [Union[RSCPDTO, RSCPTag]], waittime=0.0) -> [RSCPDTO]: payload_all = bytes() for payload_element in payload: if isinstance(payload_element, RSCPTag): dto = RSCPDTO(payload_element) else: dto = payload_element payload_all += self.rscp_utils.encode_data(dto) prepared_data = self.rscp_utils.encode_frame(payload_all) response = self.send_request(prepared_data, True, waittime) responses: [RSCPDTO] = [] if response.type == RSCPType.Container: data: RSCPDTO for data in response: responses.append(data) else: responses.append(response) return responses def send_request(self, payload: Union[RSCPDTO, RSCPTag, bytes], keep_connection_alive: bool = False, waittime: float = 0.0) -> RSCPDTO: """ This will perform a single request. :param payload: The payload that defines the request :param keep_connection_alive: A flag whether to keep the connection alive or not :return: A response object as RSCPDTO """ if isinstance(payload, RSCPTag): payload = RSCPDTO(payload) if self.socket is None: self._connect() if isinstance(payload, bytes): prepared_data = payload else: encode_data = self.rscp_utils.encode_data(payload) prepared_data = self.rscp_utils.encode_frame(encode_data) #rawdata = binascii.hexlify(prepared_data) #logger.debug('Send RAW: ' + str(rawdata)) logger.debug('Send ' + str(len(prepared_data)) + ' Bytes') encrypted_data = self.encrypt_decrypt.encrypt(prepared_data) try: self.socket.send(encrypted_data) except: self._disconnect() raise wait = self.waittime + waittime if wait > 0.0: time.sleep(wait) response = self._receive() if response.type == RSCPType.Error: logger.debug("Error type returned: " + str(response.data)) raise (RSCPCommunicationError( 'Error type returned: ' + str(response.data), logger, response)) if not keep_connection_alive: self._disconnect() return response def _connect(self): if self.socket is None: logger.info("Trying to establish connection to " + str(self.ip)) self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((self.ip, self.PORT)) self.socket.setblocking(False) rscp_dto = RSCPDTO( RSCPTag.RSCP_REQ_AUTHENTICATION, RSCPType.Container, [ RSCPDTO(RSCPTag.RSCP_AUTHENTICATION_USER, RSCPType.CString, self.username), RSCPDTO(RSCPTag.RSCP_AUTHENTICATION_PASSWORD, RSCPType.CString, self.password) ], None) self.create_encrypt() result = self.send_request(rscp_dto, True) if result.type == RSCPType.Error: self._disconnect() raise RSCPAuthenticationError("Invalid username or password", logger) def _disconnect(self): logger.info("Closing connection to " + str(self.ip)) self.socket.close() self.socket = None def _receive(self) -> RSCPDTO: logger.debug("Waiting for response from " + str(self.ip)) decrypted_data = None wait = 0.01 while not decrypted_data: try: data = self.socket.recv(self.BUFFER_SIZE) logger.debug('Received ' + str(len(data)) + ' Bytes') if len(data) == 0: self.socket.close() raise RSCPCommunicationError( "Did not receive data from e3dc", logger) self.rscp_utils = RSCPUtils() decrypted_data = self.encrypt_decrypt.decrypt(data) except BlockingIOError: logger.debug('Keine Daten empfangen, warte ' + str(wait) + 's') time.sleep(wait) wait *= 2 if wait > 2: raise #rawdata = binascii.hexlify(decrypted_data) #logger.debug('Response RAW: ' + str(rawdata)) rscp_dto = self.rscp_utils.decode_data(decrypted_data) logger.debug("Received DTO Type: " + rscp_dto.type.name + ", DTO Tag: " + rscp_dto.tag.name) return rscp_dto
class E3DCWeb(E3DC): def __init__(self, username, password, identifier, url = None): logger.debug('Initialisiere E3DC-Websockets') if not url: url = 'wss://s10.e3dc.com/ws' self.password = password self.username = username self.rscp_utils = RSCPUtils() self.identifier = identifier self.url = url logger.debug('Init abgeschlossen') lasterror = None conid = None server_connection_id = None server_auth_level = None info_serial_number = None server_type = None ws = None next_response = None next_response_data = None def get_connected(self): if self.server_auth_level == 10 and self.server_connection_id and self.server_auth_level and self.identifier and self.conid: return True else: return False def set_connected(self, value): if value == False: self.server_auth_level = None self.server_connection_id = None self.server_auth_level = None self.ws = None self.server_type = None self.conid = None connected = property(get_connected, set_connected) def register_next_response(self): self.next_response = True self.next_response_data = None def getWeblogin(self): r = RSCPDTO(RSCPTag.SERVER_REQ_NEW_VIRTUAL_CONNECTION, rscp_type=RSCPType.Container) r += RSCPDTO(RSCPTag.SERVER_USER, RSCPType.CString, self.username) pass_md5 = hashlib.md5() pass_md5.update(self.password.encode('utf-8')) password = pass_md5.hexdigest() r += RSCPDTO(RSCPTag.SERVER_PASSWD, RSCPType.CString, password) r += RSCPDTO(RSCPTag.SERVER_IDENTIFIER, RSCPType.CString, self.identifier) r += RSCPDTO(RSCPTag.SERVER_TYPE, RSCPType.Int32, 4) r += RSCPDTO(RSCPTag.SERVER_HASH_CODE, RSCPType.Int32, 1234567890) return [r] def interpreter_serverdata(self, data): if not isinstance(data, list): data = [data] requests = [] for res in data: if res.name == 'SERVER_REGISTER_CONNECTION': logger.debug(res.name) self.server_connection_id = res['SERVER_CONNECTION_ID'].data self.server_auth_level = res['SERVER_AUTH_LEVEL'].data self.server_type = res['SERVER_TYPE'].data r = RSCPDTO(tag=RSCPTag.SERVER_CONNECTION_REGISTERED, rscp_type=RSCPType.Container) r += RSCPDTO(tag=RSCPTag.SERVER_CONNECTION_ID, rscp_type=RSCPType.Int64, data=self.server_connection_id) r += RSCPDTO(tag=RSCPTag.SERVER_AUTH_LEVEL, rscp_type=RSCPType.UChar8, data=self.server_auth_level) requests.append(r) if res.name == 'SERVER_UNREGISTER_CONNECTION': logger.debug(res.name) self.server_connection_id = None self.server_auth_level = None r = self.getWeblogin() requests += r elif res.name == 'SERVER_REQ_RSCP_CMD': if res['SERVER_RSCP_DATA']: p = [] rscp_data = res['SERVER_RSCP_DATA'] if self.next_response: if isinstance(rscp_data.data, RSCPDTO): self.next_response_data = rscp_data.data else: self.next_response_data = rscp_data self.next_response = None if 'INFO_SERIAL_NUMBER' in rscp_data: self.info_serial_number = rscp_data['INFO_SERIAL_NUMBER'].data if 'INFO_REQ_IP_ADDRESS' in rscp_data: p.append(RSCPDTO(RSCPTag.INFO_IP_ADDRESS, rscp_type=RSCPType.CString, data='0.0.0.0')) if 'INFO_REQ_SUBNET_MASK' in rscp_data: p.append(RSCPDTO(RSCPTag.INFO_SUBNET_MASK, rscp_type=RSCPType.CString, data='0.0.0.0')) if 'INFO_REQ_GATEWAY' in rscp_data: p.append(RSCPDTO(RSCPTag.INFO_GATEWAY, rscp_type=RSCPType.CString, data='0.0.0.0')) if 'INFO_REQ_DNS' in rscp_data: p.append(RSCPDTO(RSCPTag.INFO_DNS, rscp_type=RSCPType.CString, data='0.0.0.0')) if 'INFO_REQ_DHCP_STATUS' in rscp_data: p.append(RSCPDTO(RSCPTag.INFO_DHCP_STATUS, rscp_type=RSCPType.Bool, data=False)) if 'INFO_REQ_TIME' in rscp_data: # TODO: Zeitstempel korrekt bilden mit Berücksichtigung der Zeitzone current_time = time.time() seconds = math.ceil(current_time) nanoseconds = round((current_time - int(current_time)) * 1000) ts = struct.pack('<QI', seconds, nanoseconds) p.append(RSCPDTO(RSCPTag.INFO_TIME, rscp_type=RSCPType.ByteArray, data=ts)) if 'INFO_REQ_TIME_ZONE' in rscp_data: p.append(RSCPDTO(RSCPTag.INFO_TIME_ZONE, rscp_type=RSCPType.CString, data='GMT+2')) if 'INFO_REQ_UTC_TIME' in rscp_data: current_time = time.time() + 7200 seconds = math.ceil(current_time) nanoseconds = round((current_time - int(current_time)) * 1000) ts = struct.pack('<QI', seconds, nanoseconds) p.append(RSCPDTO(RSCPTag.INFO_UTC_TIME, rscp_type=RSCPType.ByteArray, data=ts)) if 'INFO_REQ_A35_SERIAL_NUMBER' in rscp_data: p.append(RSCPDTO(RSCPTag.INFO_A35_SERIAL_NUMBER, rscp_type=RSCPType.CString, data='123456')) if 'INFO_REQ_INFO' in rscp_data: info = RSCPDTO(RSCPTag.INFO_INFO, rscp_type=RSCPType.Container) hsh = self.username + str(self.server_connection_id) md5 = hashlib.md5(hsh.encode()).hexdigest() info += RSCPDTO(RSCPTag.INFO_SERIAL_NUMBER, rscp_type=RSCPType.CString, data='WEB_' + md5) info += RSCPDTO(RSCPTag.INFO_MAC_ADDRESS, rscp_type=RSCPType.CString, data='00:00:00:00:00:00') p.append(info) if len(p) > 0: requests.append(self.getRSCPToServer(p)) elif res.name == 'SERVER_REQ_PING': logger.debug(res.name) r = RSCPDTO(tag=RSCPTag.SERVER_PING) requests.append(r) return requests def getRSCPToServer(self, p): if not isinstance(p, list): p = [p] payload = b'' for payload_element in p: if isinstance(payload_element, RSCPTag): x = RSCPDTO(payload_element) else: x = payload_element payload += self.rscp_utils.encode_data(x) payload = self.rscp_utils.encode_frame(payload) r = RSCPDTO(tag=RSCPTag.SERVER_REQ_RSCP_CMD, rscp_type=RSCPType.Container) r += RSCPDTO(tag=RSCPTag.SERVER_CONNECTION_ID, rscp_type=RSCPType.Int64, data=self.server_connection_id) r += RSCPDTO(tag=RSCPTag.SERVER_AUTH_LEVEL, rscp_type=RSCPType.UChar8, data=self.server_auth_level) r += RSCPDTO(tag=RSCPTag.SERVER_RSCP_DATA_LEN, rscp_type=RSCPType.Int32, data=len(payload)) r += RSCPDTO(tag=RSCPTag.SERVER_RSCP_DATA, rscp_type=RSCPType.ByteArray, data=payload) return r def send_data(self, r, ws = None, waittime=None): if not ws: ws = self.ws dataframe = self.rscp_utils.encode_data(r) bindat = self.rscp_utils.encode_frame(dataframe, crc=True) logger.debug('Sende Daten ' + str(len(bindat))) ws.send(bindat, websocket.ABNF.OPCODE_BINARY) def close_ws(self): if self.ws and self.conid: self.ws.close() def start_ws(self): if self.connected: conid = 'ConID: ' + self.conid logger.warning(conid + ' - Websocket-Verbindung besteht bereits, eine erneute Verbindung ist nicht möglich') return False self.conid = str(round(time.time(),2)) conid = 'ConID: ' + self.conid def on_message(ws, message): try: data = self.rscp_utils.decode_server_data(message) res = self.interpreter_serverdata(data) for r in res: self.send_data(r, ws) self.lasterror = None except Exception as e: self.lasterror = e logger.exception(conid + ' - Fehler beim Verarbeiten der Daten : ' + str(e)) def on_error(ws, error): self.lasterror = error logger.error(conid + ' - Verbindungsfehler ' + str(error)) def on_close(ws): self.lasterror = 'Connection closed' self.connected = False logger.info(conid + ' - Verbindung geschlossen') #websocket.enableTrace(True) ws = websocket.WebSocketApp(self.url, on_message=on_message, on_error=on_error, on_close=on_close) self.ws = ws logger.debug(conid + ' - Starte Websocket-Verbindung mit ' + self.url) ws.run_forever() logger.debug(conid + ' - Websocket-Verbindung beendet') self.conid = None def __del__(self): self.ws.close()
class E3DC: PORT = 5033 BUFFER_SIZE = 1024 * 32 def __init__(self, username, password, ip, key): self.password = password self.username = username self.encrypt_decrypt = RSCPEncryptDecrypt(key) self.ip = ip self.socket = None self.rscp_utils = RSCPUtils() def send_requests(self, paylod: [Union[RSCPDTO, RSCPTag]]) -> [RSCPDTO]: """ This function will send a list of requests consisting of RSCPDTO's oder RSCPTag's to the e3dc and returns a list of responses. i.e. responses = send_requests([RSCPTag.EMS_REQ_BAT_SOC, RSCPTag.EMS_REQ_POWER_PV, RSCPTag.EMS_REQ_POWER_BAT, RSCPTag.EMS_REQ_POWER_GRID, RSCPTag.EMS_REQ_POWER_WB_ALL]) :param paylod: A list of requests :return: A list of responses in form of RSCPDTO's """ dto_list: [RSCPDTO] = [] for payload_element in paylod: if isinstance(payload_element, RSCPTag): dto_list.append(RSCPDTO(payload_element)) else: dto_list.append(payload_element) logger.info("Sending " + str(len(dto_list)) + " requests to " + str(self.ip)) responses: [RSCPDTO] = [] dto: RSCPDTO for dto in dto_list: responses.append(self.send_request(dto, True)) return responses def send_request(self, payload: Union[RSCPDTO, RSCPTag], keep_connection_alive: bool = False) -> RSCPDTO: """ This will perform a single request. :param payload: The payload that defines the request :param keep_connection_alive: A flag whether to keep the connection alive or not :return: A response object as RSCPDTO """ if isinstance(payload, RSCPTag): payload = RSCPDTO(payload) if self.socket is None: self._connect() encode_data = self.rscp_utils.encode_data(payload) prepared_data = self.rscp_utils.encode_frame(encode_data) encrypted_data = self.encrypt_decrypt.encrypt(prepared_data) self.socket.send(encrypted_data) response = self._receive() if response.type == RSCPType.Error: logger.error("Error type returned") raise (RSCPCommunicationError(None, logger)) if not keep_connection_alive: self._disconnect() return response def _connect(self): if self.socket is None: logger.info("Trying to establish connection to " + str(self.ip)) self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((self.ip, self.PORT)) rscp_dto = RSCPDTO( RSCPTag.RSCP_REQ_AUTHENTICATION, RSCPType.Container, [ RSCPDTO(RSCPTag.RSCP_AUTHENTICATION_USER, RSCPType.CString, self.username), RSCPDTO(RSCPTag.RSCP_AUTHENTICATION_PASSWORD, RSCPType.CString, self.password) ], None) result = self.send_request(rscp_dto, True) if result.type == RSCPType.Error: self._disconnect() raise RSCPAuthenticationError("Invalid username or password", logger) def _disconnect(self): logger.info("Closing connection to " + str(self.ip)) self.socket.close() self.socket = None def _receive(self) -> RSCPDTO: logger.info("Waiting for response from " + str(self.ip)) data = self.socket.recv(self.BUFFER_SIZE) if len(data) == 0: self.socket.close() raise RSCPCommunicationError("Did not receive data from e3dc", logger) self.rscp_utils = RSCPUtils() decrypted_data = self.encrypt_decrypt.decrypt(data) rscp_dto = self.rscp_utils.decode_data(decrypted_data) logger.debug("Received DTO Type: " + rscp_dto.type.name + ", DTO Tag: " + rscp_dto.tag.name) return rscp_dto