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 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