From c7f20e1b4b667153afab52db55cbe8065d051515 Mon Sep 17 00:00:00 2001 From: Eduard Carreras Date: Sat, 14 Jul 2018 10:20:52 +0200 Subject: [PATCH 1/8] Move __repr__ to base class --- reeprotocol/app_asdu.py | 41 ++++++++++++++++++---------------------- reeprotocol/base_asdu.py | 2 +- reeprotocol/protocol.py | 2 +- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/reeprotocol/app_asdu.py b/reeprotocol/app_asdu.py index 68ae19a..36918ca 100644 --- a/reeprotocol/app_asdu.py +++ b/reeprotocol/app_asdu.py @@ -19,7 +19,24 @@ def register_class(cls): class BaseAppAsdu(metaclass=AppAsduRegistry): - pass + + @property + def values(self): + return "\n".join([ + " {}: {}".format(k, v) for k,v in vars(self).items() + ]) + + def __repr__(self): + return '\n'.join([ + " -- {class_name} Begin --", + "{values}", + " -- {class_name} End --" + ]).format( + class_name=self.__class__.__name__, + values=self.values + ) + + class C_AC_NA_2(BaseAppAsdu): @@ -61,11 +78,6 @@ def length(self): def to_bytes(self): return bytes() - def __repr__(self): - output = " -- C_FS_NA_2 Begin -- \n" - output += " -- C_FS_NA_2 End \n" - return output - class C_CI_NU_2(BaseAppAsdu): type = 123 @@ -95,15 +107,6 @@ def to_bytes(self): response.extend(self.tiempo_final.to_bytes()) return response - def __repr__(self): - output = " -- C_CI_NU_2 Begin -- \n" - output += " primer_integrado: " + str(self.primer_integrado) + "\n" - output += " ultimo_integrado: " + str(self.ultimo_integrado) + "\n" - output += " tiempo_inicial: " + str(self.tiempo_inicial) + "\n" - output += " tiempo_final: " + str(self.tiempo_final) + "\n" - output += " -- C_CI_NU_2 End \n" - return output - class M_IT_TK_2(BaseAppAsdu): type = 11 @@ -129,14 +132,6 @@ def from_hex(self, data, cualificador_ev): self.tiempo = TimeA() self.tiempo.from_hex(data[position:position+5]) - def __repr__(self): - output = " -- M_IT_TK_2 Begin -- \n" - output += (" contadores (direccion objeto, total integrado" - ", cualificador) " + str(self.valores) + "\n") - output += " tiempo: " + str(self.tiempo) + "\n" - output += " -- M_IT_TK_2 End \n" - return output - class TimeA(): diff --git a/reeprotocol/base_asdu.py b/reeprotocol/base_asdu.py index ef33d3a..b617084 100644 --- a/reeprotocol/base_asdu.py +++ b/reeprotocol/base_asdu.py @@ -229,7 +229,7 @@ def __repr__(self): output += " direccion punto medida: " + str(self.dir_pm) + "\n" output += " direccion registro: " + str(self.dir_registro) + "\n" output += " CONTENIDO: " + (":".join("%02x" % b for b in self.data)) + "\n" - output += str(self.content) + output += str(self.content) + "\n" output += " checksum: " + str(self.checksum) + " " +hex(self.checksum) + "\n" output += " " + (":".join("%02x" % b for b in self.buffer)) + "\n" output += "----- VariableAsdu End -----" diff --git a/reeprotocol/protocol.py b/reeprotocol/protocol.py index 34f7910..edb4876 100644 --- a/reeprotocol/protocol.py +++ b/reeprotocol/protocol.py @@ -126,7 +126,7 @@ def initialize(self, physical_layer): self.asdu_parser = AsduParser() def send_frame(self, frame): - logger.info("sending frame {}".format(frame)) + logger.info("sending frame\n {}".format(frame)) logging.info("->" + ":".join("%02x" % b for b in frame.buffer)) self.physical_layer.send_bytes(frame.buffer) From 241842cb168fc399f3cf33efa5c09ecfeb81a45f Mon Sep 17 00:00:00 2001 From: MiquelIR Date: Thu, 19 Jul 2018 16:34:44 +0200 Subject: [PATCH 2/8] ADD new ASDUS --- examples/ip_cliente.py | 46 +++++++++++++++--------- reeprotocol/app_asdu.py | 78 +++++++++++++++++++++++++++++++++++++---- reeprotocol/protocol.py | 27 ++++++++++---- 3 files changed, 122 insertions(+), 29 deletions(-) diff --git a/examples/ip_cliente.py b/examples/ip_cliente.py index c9280d3..485591e 100644 --- a/examples/ip_cliente.py +++ b/examples/ip_cliente.py @@ -10,25 +10,37 @@ import reeprotocol.protocol import datetime + def run_example(ip, port, der, dir_pm, clave_pm): - physical_layer = reeprotocol.ip.Ip((ip, port)) - link_layer = reeprotocol.protocol.LinkLayer(der, dir_pm) - link_layer.initialize(physical_layer) - app_layer = reeprotocol.protocol.AppLayer() - app_layer.initialize(link_layer) + try: + physical_layer = reeprotocol.ip.Ip((ip, port)) + link_layer = reeprotocol.protocol.LinkLayer(der, dir_pm) + link_layer.initialize(physical_layer) + app_layer = reeprotocol.protocol.AppLayer() + app_layer.initialize(link_layer) + + physical_layer.connect() + link_layer.link_state_request() + link_layer.remote_link_reposition() + logging.info("before authentication") + resp = app_layer.authenticate(clave_pm) + logging.info("CLIENTE authenticate response {}".format(resp)) + logging.info("before read") + """ + for resp in app_layer\ + .read_integrated_totals(datetime.datetime(2017, 10, 1, 1, 0), + datetime.datetime(2017, 11, 1, 0, 0)): + logging.info("read response {}".format(resp)) + """ + for resp in app_layer.get_info(): + logging.info("read response {}".format(resp)) + except Exception: + raise + finally: + #app_layer.finish_session() + physical_layer.disconnect() + sys.exit(1) - physical_layer.connect() - link_layer.link_state_request() - link_layer.remote_link_reposition() - logging.info("before authentication") - resp = app_layer.authenticate(clave_pm) - logging.info("CLIENTE authenticate response {}".format(resp)) - logging.info("before read") - for resp in app_layer\ - .read_integrated_totals(datetime.datetime(2017, 10, 1, 1, 0), - datetime.datetime(2017, 11, 1, 0, 0)): - logging.info("read response {}".format(resp)) - physical_layer.disconnect() if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) diff --git a/reeprotocol/app_asdu.py b/reeprotocol/app_asdu.py index 36918ca..0a3e687 100644 --- a/reeprotocol/app_asdu.py +++ b/reeprotocol/app_asdu.py @@ -3,6 +3,18 @@ import datetime +__all__ = [ + 'C_AC_NA_2', + 'C_CI_NU_2', + 'C_FS_NA_2', + 'C_TI_NA_2', + 'C_RD_NA_2', + 'M_IT_TK_2', + 'M_TI_TA_2', + 'P_MP_NA_2' +] + + class AppAsduRegistry(type): types = dict() @@ -37,7 +49,63 @@ def __repr__(self): ) +class C_TI_NA_2(BaseAppAsdu): + """ + Leer fecha y hora actuales + """ + type = 103 + + def to_bytes(self): + return bytes() + + def from_hex(self, data, cualificador_ev): + pass +class P_MP_NA_2(BaseAppAsdu): + """ + Leer el código de fabricante e identificador de equipo + """ + type = 71 + data_length = 0x06 + + def __init__(self): + self.codigo_fabricante = None + self.codigo_equipo = None + + def to_bytes(self): + return bytes() + + def from_hex(self, data, cualificador_ev): + self.codigo_fabricante = struct.unpack("B", data[1:2])[0] + self.codigo_equipo = struct.unpack("I", data[2:6])[0] + + +class M_TI_TA_2(BaseAppAsdu): + """ + Fecha y hora actuales + """ + type = 72 + + def __init__(self): + self.tiempo = None + + def from_hex(self, data, cualificador_ev): + self.tiempo = TimeA() + self.tiempo.from_hex(data) + + +class C_RD_NA_2(BaseAppAsdu): + """ + Leer el código de fabricante e identificador de equipo + """ + type = 100 + causa_tm = 5 + + def to_bytes(self): + return bytes() + + def from_hex(self, data, cualificador_ev): + pass class C_AC_NA_2(BaseAppAsdu): """ @@ -58,14 +126,12 @@ def length(self): def to_bytes(self): return struct.pack("I", self.clave) - def __repr__(self): - output = " -- C_AC_NA_2 Begin -- \n" - output += " clave: " + str(self.clave) + "\n" - output += " -- C_AC_NA_2 End \n" - return output - class C_FS_NA_2(BaseAppAsdu): + """ + Finalizar sesión + """ + type = 187 def from_hex(self, data, cualificador_ev): diff --git a/reeprotocol/protocol.py b/reeprotocol/protocol.py index edb4876..28fd436 100644 --- a/reeprotocol/protocol.py +++ b/reeprotocol/protocol.py @@ -4,11 +4,8 @@ AsduParser, FixedAsdu, VariableAsdu ) import traceback -from .app_asdu import ( - C_AC_NA_2, - C_CI_NU_2, - C_FS_NA_2, -) +from .app_asdu import * +import math logger = logging.getLogger('reeprotocol') @@ -58,6 +55,9 @@ def process_requestresponse(self): raise ProtocolException("Didn't get ASDU") yield asdu_resp + if asdu_resp.causa_tm == 0x05: + logger.info("Request or asked") + break if asdu_resp.causa_tm == 0x07: logger.info("activation confirmation") break @@ -70,6 +70,8 @@ def process_requestresponse(self): if asdu_resp.causa_tm == 0x12: logger.info("requested integration period not available") raise IntegrationPeriodNotAvailable() + else: + raise Exception('causa no detectada') def authenticate(self, clave_pm): asdu = self.create_asdu_request(C_AC_NA_2(clave_pm)) @@ -93,7 +95,20 @@ def read_integrated_totals(self, start_date, end_date, register = 11): yield resp except IntegrationPeriodNotAvailable as e: pass - + + def read_datetime(self): + asdu = self.create_asdu_request(C_TI_NA_2()) + resps = list(self.process_request(asdu)) + for resp in self.process_requestresponse(): + yield resp + + def get_info(self): + #100 + asdu = self.create_asdu_request(C_RD_NA_2()) + resps = list(self.process_request(asdu)) + for resp in self.process_requestresponse(): + yield resp + def create_asdu_request(self, user_data, registro=0): asdu = VariableAsdu() asdu.c.res = 0 From 5f58983f0b82eaeac63b357cf6fe19f2e387b08c Mon Sep 17 00:00:00 2001 From: MiquelIR Date: Thu, 19 Jul 2018 16:35:09 +0200 Subject: [PATCH 3/8] MOD ASDU length - base_length and data_length --- reeprotocol/app_asdu.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/reeprotocol/app_asdu.py b/reeprotocol/app_asdu.py index 0a3e687..a88cfe8 100644 --- a/reeprotocol/app_asdu.py +++ b/reeprotocol/app_asdu.py @@ -32,6 +32,12 @@ def register_class(cls): class BaseAppAsdu(metaclass=AppAsduRegistry): + data_length = 0 + + @property + def length(self): + return self.data_length + 0x09 + @property def values(self): return "\n".join([ @@ -112,6 +118,7 @@ class C_AC_NA_2(BaseAppAsdu): used to send the password of the thing """ type = 183 + data_length = 0x04 def __init__(self, clave=0): self.clave = clave @@ -119,10 +126,6 @@ def __init__(self, clave=0): def from_hex(self, data, cualificador_ev): self.clave = struct.unpack("I", data)[0] - @property - def length(self): - return 0x0d - def to_bytes(self): return struct.pack("I", self.clave) @@ -137,16 +140,13 @@ class C_FS_NA_2(BaseAppAsdu): def from_hex(self, data, cualificador_ev): pass - @property - def length(self): - return 0x09 - def to_bytes(self): return bytes() class C_CI_NU_2(BaseAppAsdu): type = 123 + data_length = 0x0c def __init__(self, start_date=datetime.datetime.now(), end_date=datetime.datetime.now()): @@ -161,10 +161,6 @@ def from_hex(self, data, cualificador_ev): self.tiempo_inicial.from_hex(data[2:7]) self.tiempo_final.from_hex(data[7:12]) - @property - def length(self): - return 0x15 - def to_bytes(self): response = bytearray() response.extend(struct.pack("B", self.primer_integrado)) From 9014c63522bcc678e97afe4abb30dc114a56a80a Mon Sep 17 00:00:00 2001 From: MiquelIR Date: Thu, 19 Jul 2018 16:38:35 +0200 Subject: [PATCH 4/8] FIX code and use new data_length building asdu --- reeprotocol/protocol.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/reeprotocol/protocol.py b/reeprotocol/protocol.py index 28fd436..0347797 100644 --- a/reeprotocol/protocol.py +++ b/reeprotocol/protocol.py @@ -9,6 +9,17 @@ logger = logging.getLogger('reeprotocol') + +def parse_asdu(trama): + p = AsduParser() + for x in range(0, len(trama), 2): + a = p.append_and_get_if_completed(int(trama[x:x+2], 16)) + if not a: + raise Exception('Trama no completa!') + else: + return a + + class ProtocolException(Exception): pass @@ -39,7 +50,7 @@ def process_request(self, request_asdu): def process_requestresponse(self): """ this function makes a very ugly assumption, if you don't iterate over all elements, the program will fail""" - # TODO CHECK CORRECK ACK + # TODO CHECK CORRECT ACK while True: asdu = FixedAsdu() asdu.c.res = 0 @@ -117,8 +128,8 @@ def create_asdu_request(self, user_data, registro=0): asdu.c.fcv = 1 asdu.c.cf = 3 asdu.der = self.link_layer.der - asdu.cualificador_ev = 1 - asdu.causa_tm = 6 + asdu.cualificador_ev = math.ceil(getattr(user_data, 'data_length', 0x06)/0x06) + asdu.causa_tm = getattr(user_data, 'causa_tm', 6) asdu.dir_pm = self.link_layer.dir_pm # registro> 11 curvas horarias, 12 cuartohorarias, 21 resumenes diarios asdu.dir_registro = registro @@ -127,8 +138,6 @@ def create_asdu_request(self, user_data, registro=0): return asdu - - class LinkLayer(metaclass=ABCMeta): def __init__(self, der=None, dir_pm=None): @@ -145,7 +154,7 @@ def send_frame(self, frame): logging.info("->" + ":".join("%02x" % b for b in frame.buffer)) self.physical_layer.send_bytes(frame.buffer) - def get_frame(self, timeout = 60): + def get_frame(self, timeout=60): frame = None while not frame: bt = self.physical_layer.get_byte(timeout) From a52165a2ef7709d380207031a506ad261034eb18 Mon Sep 17 00:00:00 2001 From: MiquelIR Date: Mon, 6 Aug 2018 12:36:33 +0200 Subject: [PATCH 5/8] FIX length for integrated_totals function --- reeprotocol/app_asdu.py | 9 ++++++++- reeprotocol/protocol.py | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/reeprotocol/app_asdu.py b/reeprotocol/app_asdu.py index a88cfe8..4e5ae55 100644 --- a/reeprotocol/app_asdu.py +++ b/reeprotocol/app_asdu.py @@ -60,6 +60,7 @@ class C_TI_NA_2(BaseAppAsdu): Leer fecha y hora actuales """ type = 103 + causa_tm = 5 def to_bytes(self): return bytes() @@ -91,6 +92,7 @@ class M_TI_TA_2(BaseAppAsdu): Fecha y hora actuales """ type = 72 + causa_tm = 5 def __init__(self): self.tiempo = None @@ -146,7 +148,8 @@ def to_bytes(self): class C_CI_NU_2(BaseAppAsdu): type = 123 - data_length = 0x0c + data_length = 0x06 + causa_tm = 6 def __init__(self, start_date=datetime.datetime.now(), end_date=datetime.datetime.now()): @@ -169,6 +172,10 @@ def to_bytes(self): response.extend(self.tiempo_final.to_bytes()) return response + @property + def length(self): + return 0x15 + class M_IT_TK_2(BaseAppAsdu): type = 11 diff --git a/reeprotocol/protocol.py b/reeprotocol/protocol.py index 0347797..771cb69 100644 --- a/reeprotocol/protocol.py +++ b/reeprotocol/protocol.py @@ -96,7 +96,7 @@ def finish_session(self): except Exception as e: logger.exception("error finishing session {}".format(e)) - def read_integrated_totals(self, start_date, end_date, register = 11): + def read_integrated_totals(self, start_date, end_date, register=11): asdu = self.create_asdu_request(C_CI_NU_2(start_date, end_date), register) #do not remove this as we have to iterate over physical layer frames. @@ -108,6 +108,7 @@ def read_integrated_totals(self, start_date, end_date, register = 11): pass def read_datetime(self): + #103 asdu = self.create_asdu_request(C_TI_NA_2()) resps = list(self.process_request(asdu)) for resp in self.process_requestresponse(): From ae54558bfe989a12a0106b3b2ba62fcbb36b946c Mon Sep 17 00:00:00 2001 From: MiquelIR Date: Tue, 7 Aug 2018 14:12:30 +0200 Subject: [PATCH 6/8] MOD some code --- reeprotocol/app_asdu.py | 7 +++---- reeprotocol/protocol.py | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/reeprotocol/app_asdu.py b/reeprotocol/app_asdu.py index 4e5ae55..0705138 100644 --- a/reeprotocol/app_asdu.py +++ b/reeprotocol/app_asdu.py @@ -9,9 +9,9 @@ 'C_FS_NA_2', 'C_TI_NA_2', 'C_RD_NA_2', - 'M_IT_TK_2', - 'M_TI_TA_2', - 'P_MP_NA_2' + 'M_IT_TK_2', # M type are responses no need to be listed + 'M_TI_TA_2', # M type are responses no need to be listed + 'P_MP_NA_2', ] @@ -183,7 +183,6 @@ class M_IT_TK_2(BaseAppAsdu): def __init__(self): self.valores = [] self.tiempo = None - pass def from_hex(self, data, cualificador_ev): for i in range(0, cualificador_ev): diff --git a/reeprotocol/protocol.py b/reeprotocol/protocol.py index 771cb69..d4904a8 100644 --- a/reeprotocol/protocol.py +++ b/reeprotocol/protocol.py @@ -69,16 +69,16 @@ def process_requestresponse(self): if asdu_resp.causa_tm == 0x05: logger.info("Request or asked") break - if asdu_resp.causa_tm == 0x07: + elif asdu_resp.causa_tm == 0x07: logger.info("activation confirmation") break - if asdu_resp.causa_tm == 0x0A: + elif asdu_resp.causa_tm == 0x0A: logger.info("Activation terminated") break - if asdu_resp.causa_tm == 0x0E: + elif asdu_resp.causa_tm == 0x0E: logger.info("requested ASDU-type not available") raise RequestedASDUTypeNotAvailable() - if asdu_resp.causa_tm == 0x12: + elif asdu_resp.causa_tm == 0x12: logger.info("requested integration period not available") raise IntegrationPeriodNotAvailable() else: From fdfb2189cd59af37cd09b23d9c7b93213e67782e Mon Sep 17 00:00:00 2001 From: MiquelIR Date: Wed, 8 Aug 2018 10:12:39 +0200 Subject: [PATCH 7/8] MOD improve some comments and order methods --- reeprotocol/app_asdu.py | 54 +++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/reeprotocol/app_asdu.py b/reeprotocol/app_asdu.py index 0705138..4240b36 100644 --- a/reeprotocol/app_asdu.py +++ b/reeprotocol/app_asdu.py @@ -68,24 +68,6 @@ def to_bytes(self): def from_hex(self, data, cualificador_ev): pass -class P_MP_NA_2(BaseAppAsdu): - """ - Leer el código de fabricante e identificador de equipo - """ - type = 71 - data_length = 0x06 - - def __init__(self): - self.codigo_fabricante = None - self.codigo_equipo = None - - def to_bytes(self): - return bytes() - - def from_hex(self, data, cualificador_ev): - self.codigo_fabricante = struct.unpack("B", data[1:2])[0] - self.codigo_equipo = struct.unpack("I", data[2:6])[0] - class M_TI_TA_2(BaseAppAsdu): """ @@ -104,17 +86,37 @@ def from_hex(self, data, cualificador_ev): class C_RD_NA_2(BaseAppAsdu): """ - Leer el código de fabricante e identificador de equipo + Leer identificador de fabricante y equipo """ type = 100 causa_tm = 5 - + def to_bytes(self): return bytes() def from_hex(self, data, cualificador_ev): pass + +class P_MP_NA_2(BaseAppAsdu): + """ + Identificador del fabricante y equipo + """ + type = 71 + data_length = 0x06 + + def __init__(self): + self.codigo_fabricante = None + self.codigo_equipo = None + + def to_bytes(self): + return bytes() + + def from_hex(self, data, cualificador_ev): + self.codigo_fabricante = struct.unpack("B", data[1:2])[0] + self.codigo_equipo = struct.unpack("I", data[2:6])[0] + + class C_AC_NA_2(BaseAppAsdu): """ used to send the password of the thing @@ -147,6 +149,11 @@ def to_bytes(self): class C_CI_NU_2(BaseAppAsdu): + """ + Leer totales integrados operacionales repuestos periódicamente por intervalo + de tiempo y rango de direcciones + """ + type = 123 data_length = 0x06 causa_tm = 6 @@ -178,6 +185,11 @@ def length(self): class M_IT_TK_2(BaseAppAsdu): + """ + Totales integrados operacionales repuestos periódicamente, 4 octetos + (incrementos de energía, en kWh o kVARh) + """ + type = 11 def __init__(self): @@ -300,4 +312,4 @@ def __repr__(self): + (":".join("%02x" % b for b in self.to_bytes())) + "\n") output += " datetime: " + str(self.datetime) + "\n" output += " -- TiempoA End \n" - return output + return str(self.datetime) From f1b41326e5c6a9c8d2c4bc023180493c08aad2ac Mon Sep 17 00:00:00 2001 From: MiquelIR Date: Wed, 8 Aug 2018 10:13:19 +0200 Subject: [PATCH 8/8] TEST add test for the new methods --- tests/test_app_asdu.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_app_asdu.py b/tests/test_app_asdu.py index ffc8e22..549bcdb 100644 --- a/tests/test_app_asdu.py +++ b/tests/test_app_asdu.py @@ -41,3 +41,38 @@ def test_time_a_to_bytes(self): tiempo2 = app_asdu.TimeA() tiempo2.from_hex(thebytes) self.assertEqual(tiempo2.datetime, d) + + def test_M_TI_TA_2_from_hex(self): + c = app_asdu.M_TI_TA_2() + c.from_hex(bytes.fromhex("00 64 2a 44 12 08 12"), 1) + self.assertEqual(c.tiempo.datetime, datetime.datetime(2018, 4, 10, 4)) + + def test_P_MP_NA_2_from_hex(self): + c = app_asdu.P_MP_NA_2() + c.from_hex(bytes.fromhex("04 fb a2 97 42 24"), 1) + self.assertEqual(c.codigo_fabricante, 251) + self.assertEqual(c.codigo_equipo, 608343970) + + def test_C_CI_NU_2(self): + c = app_asdu.C_CI_NU_2(datetime.datetime(2018, 7, 1, 1), + datetime.datetime(2018, 8, 1, 0)) + self.assertEqual(c.to_bytes(), bytearray(bytes.fromhex("01 08 00 01 e1 07 12 00 00 61 08 12"))) + self.assertEqual(c.length, 21) + + def test_C_CI_NU_2_from_hex(self): + c = app_asdu.C_CI_NU_2() + c.from_hex(bytes.fromhex("01 08 00 01 e1 07 12 00 00 61 08 12"), 1) + self.assertEqual(c.primer_integrado, 1) + self.assertEqual(c.ultimo_integrado, 8) + self.assertEqual(c.tiempo_inicial.datetime, datetime.datetime(2018, 7, 1, 1)) + self.assertEqual(c.tiempo_final.datetime, datetime.datetime(2018, 8, 1, 0)) + + def test_M_IT_TK_2_from_hex(self): + c = app_asdu.M_IT_TK_2() + self.assertEqual(c.valores, []) + self.assertEqual(c.tiempo, None) + c.from_hex(bytes.fromhex("01 04 00 00 00 00 02 00 00 00 00 00 03 1b 00 00 00 00 04 00 00 00 00 00 05 00 00 00 00 00 06 00 00 00 00 00 07 00 00 00 00 80 08 00 00 00 00 80 00 81 e1 07 12"), 8) + + self.assertEqual(c.valores, [(1, 4, 0), (2, 0, 0), (3, 27, 0), (4, 0, 0) + , (5, 0, 0), (6, 0, 0), (7, 0, 128), (8, 0, 128)]) + self.assertEqual(c.tiempo.datetime, datetime.datetime(2018, 7, 1, 1))