diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/README.md b/README.md index d087d65..a0e0d44 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,47 @@ -# home-assistant-gama-300-dlms-meter -Home Assistant Integration for reading data from ELGama GAMA300 meters via RS485-TCP Gateway +# Home Assistant ELGAMA GAMA300 DLMS over TCPIP +Home Assistant Integration for reading data from ELGAMA GAMA300 electricity meter (GAMA100 not tested, but possibly supported too) via RS485-TCP Gateway + +## Features + +- Get most of live values from meter +- Add energy sensors for integration with energy usage +- Tracks daily / total energy consumption + +## Notes + +This code was written for couple of days without any skills in Python and HA architecture. That is second time I've used Python, so anyone is welcome to make this code better. + +## Connecting meter to gateway and HA + +First your meter must support RS485 interface (check your meter manual or model number to ensure your meter support it, but also you can try to connect optical reader to gateway). To connect meter to HA you need any RS485->TCP gateway (I've used HI-FLYING HF5142B, coz prefer wires, but you can use any other or try to connect it with esp32 / 8266 with RS485 converter bridging it to TCP socket). +On gateway side you should configure TCP server to forward data from RS485 port, it should be tcp server without authorization, you can choose any port to listen. + +#### RS485 params: +- Baud rate: 9600 +- Data bit: 8 +- Stop bit: 1 +- Parity: None + +![HF5142B_connection](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_connection.jpg) +![HF5142B_TCP_Server](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_TCP_Server.jpg) + +After connecting meter you need to add `home-assistant-gama-300-dlms-meter` integration using HACS to your HA. In you HA go to *Main Menu -> HACS -> Integrations*, in top right corner press 3 dots and click to "Custom Repositories". Add repository `https://github.com/astraliens/home-assistant-gama_300_dlms_meter_reader` and category `Integration`. After this step close modal add repository window and press "Explore & Download Repositories" blue button at the bottom right corner of screen and search for `Gama 300 DLMS Meter` integration. Press on it and in right bottom corner of screen press "Download" button. +After this you can simply add it like regular integration, specifiying IP and port of gateway and your meter serial number (you can find it on meter itself). In several seconds integration get data from meter, create all found sensors and will update them constantly. + +Pinout of meter shown on photo: +![gama_300_rs485_pinout](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/gama_300_rs485_pinout.jpg) + + +## Energy consumption monitoring + +You can add `Sum Li Active power+ (QI+QIV) Time integral 1 Rate 0` sensor to your energy consumption monitoring to calculate your overall spents + +## DLMS Protocol + +Many thanks to Gurux for Python library implementation which used in this integration to retrieve data from meter + +## Donations + +You can say thanks by donating for buying pizza at: + +Buy Me A Pizza \ No newline at end of file diff --git a/custom_components/gama_300_dlms_meter_reader/README.md b/custom_components/gama_300_dlms_meter_reader/README.md new file mode 100644 index 0000000..a0e0d44 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/README.md @@ -0,0 +1,47 @@ +# Home Assistant ELGAMA GAMA300 DLMS over TCPIP +Home Assistant Integration for reading data from ELGAMA GAMA300 electricity meter (GAMA100 not tested, but possibly supported too) via RS485-TCP Gateway + +## Features + +- Get most of live values from meter +- Add energy sensors for integration with energy usage +- Tracks daily / total energy consumption + +## Notes + +This code was written for couple of days without any skills in Python and HA architecture. That is second time I've used Python, so anyone is welcome to make this code better. + +## Connecting meter to gateway and HA + +First your meter must support RS485 interface (check your meter manual or model number to ensure your meter support it, but also you can try to connect optical reader to gateway). To connect meter to HA you need any RS485->TCP gateway (I've used HI-FLYING HF5142B, coz prefer wires, but you can use any other or try to connect it with esp32 / 8266 with RS485 converter bridging it to TCP socket). +On gateway side you should configure TCP server to forward data from RS485 port, it should be tcp server without authorization, you can choose any port to listen. + +#### RS485 params: +- Baud rate: 9600 +- Data bit: 8 +- Stop bit: 1 +- Parity: None + +![HF5142B_connection](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_connection.jpg) +![HF5142B_TCP_Server](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_TCP_Server.jpg) + +After connecting meter you need to add `home-assistant-gama-300-dlms-meter` integration using HACS to your HA. In you HA go to *Main Menu -> HACS -> Integrations*, in top right corner press 3 dots and click to "Custom Repositories". Add repository `https://github.com/astraliens/home-assistant-gama_300_dlms_meter_reader` and category `Integration`. After this step close modal add repository window and press "Explore & Download Repositories" blue button at the bottom right corner of screen and search for `Gama 300 DLMS Meter` integration. Press on it and in right bottom corner of screen press "Download" button. +After this you can simply add it like regular integration, specifiying IP and port of gateway and your meter serial number (you can find it on meter itself). In several seconds integration get data from meter, create all found sensors and will update them constantly. + +Pinout of meter shown on photo: +![gama_300_rs485_pinout](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/gama_300_rs485_pinout.jpg) + + +## Energy consumption monitoring + +You can add `Sum Li Active power+ (QI+QIV) Time integral 1 Rate 0` sensor to your energy consumption monitoring to calculate your overall spents + +## DLMS Protocol + +Many thanks to Gurux for Python library implementation which used in this integration to retrieve data from meter + +## Donations + +You can say thanks by donating for buying pizza at: + +Buy Me A Pizza \ No newline at end of file diff --git a/custom_components/gama_300_dlms_meter_reader/__init__.py b/custom_components/gama_300_dlms_meter_reader/__init__.py new file mode 100644 index 0000000..b354c0c --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/__init__.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from . import hub +from .const import DOMAIN + +# List of platforms to support. There should be a matching .py file for each, +# eg and +PLATFORMS: list[str] = ["sensor"] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + # Store an instance of the "connecting" class that does the work of speaking + # with your actual devices. + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = hub.Hub(hass, entry.data["host"], entry.data["port"], entry.data["serial"]) + + # This creates each HA object for each platform your device requires. + # It's done by calling the `async_setup_entry` function in each platform module. + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + # This is called when an entry/configured device is to be removed. The class + # needs to unload itself, and remove callbacks. See the classes for further + # details + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/custom_components/gama_300_dlms_meter_reader/config_flow.py b/custom_components/gama_300_dlms_meter_reader/config_flow.py new file mode 100644 index 0000000..a356372 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/config_flow.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import logging +from typing import Any + +import voluptuous as vol + +from homeassistant import config_entries, exceptions +from homeassistant.core import HomeAssistant + +from .const import DOMAIN # pylint:disable=unused-import +from .hub import Hub + +_LOGGER = logging.getLogger(__name__) + +# This is the schema that used to display the UI to the user. This simple +# schema has a single required host field, but it could include a number of fields +# such as username, password etc. See other components in the HA core code for +# further examples. +# Note the input displayed to the user will be translated. See the +# translations/.json file and strings.json. See here for further information: +# https://developers.home-assistant.io/docs/config_entries_config_flow_handler/#translations +# At the time of writing I found the translations created by the scaffold didn't +# quite work as documented and always gave me the "Lokalise key references" string +# (in square brackets), rather than the actual translated value. I did not attempt to +# figure this out or look further into it. +DATA_SCHEMA = vol.Schema({ + ("host"): str, + ("port"): int, + ("serial"): int, +}) + +async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]: + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + # Validate the data can be used to set up a connection. + + # This is a simple example to show an error in the UI for a short hostname + # The exceptions are defined at the end of this file, and are used in the + # `async_step_user` method below. + if len(data["host"]) < 3: + raise InvalidHost + + hub = Hub(hass, data["host"],data["port"],data["serial"]) + # The dummy hub provides a `test_connection` method to ensure it's working + # as expected + result = await hub.test_connection() + #if not result: + # If there is an error, raise an exception to notify HA that there was a + # problem. The UI will also show there was a problem + # raise CannotConnect + + # If your PyPI package is not built with async, pass your methods + # to the executor: + # await hass.async_add_executor_job( + # your_validate_func, data["username"], data["password"] + # ) + + # If you cannot connect: + # throw CannotConnect + # If the authentication is wrong: + # InvalidAuth + + # Return info that you want to store in the config entry. + # "Title" is what is displayed to the user for this hub device + # It is stored internally in HA as part of the device config. + # See `async_step_user` below for how this is used + return {"title": data["host"]} + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + + VERSION = 1 + # Pick one of the available connection classes in homeassistant/config_entries.py + # This tells HA if it should be asking for updates, or it'll be notified of updates + # automatically. This example uses PUSH, as the dummy hub will notify HA of + # changes. + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + # This goes through the steps to take the user through the setup process. + # Using this it is possible to update the UI and prompt for additional + # information. This example provides a single form (built from `DATA_SCHEMA`), + # and when that has some validated input, it calls `async_create_entry` to + # actually create the HA config entry. Note the "title" value is returned by + # `validate_input` above. + errors = {} + if user_input is not None: + try: + info = await validate_input(self.hass, user_input) + + return self.async_create_entry(title=info["title"], data=user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidHost: + # The error string is set here, and should be translated. + # This example does not currently cover translations, see the + # comments on `DATA_SCHEMA` for further details. + # Set the error on the `host` field, not the entire form. + errors["host"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + # If there is no user input or there were errors, show the form again, including any errors that were found with the input. + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidHost(exceptions.HomeAssistantError): + """Error to indicate there is an invalid hostname.""" diff --git a/custom_components/gama_300_dlms_meter_reader/const.py b/custom_components/gama_300_dlms_meter_reader/const.py new file mode 100644 index 0000000..06bb9d6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/const.py @@ -0,0 +1,3 @@ +# This is the internal name of the integration, it should also match the directory +# name for the integration. +DOMAIN = "gama_300_dlms_meter_reader" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/GXCmdParameter.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/GXCmdParameter.py new file mode 100644 index 0000000..444ce2f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/GXCmdParameter.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +#pylint: disable=too-few-public-methods +class GXCmdParameter(): + """ + This class is used to save command line parameters. + """ + def __init__(self): + """ + Constructor. + """ + # Command line parameter tag. + self.tag = str + # Command line parameter value. + self.parameter = str + # Parameter is missing. + self.missing = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/GXDLMSReader.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/GXDLMSReader.py new file mode 100644 index 0000000..d736556 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/GXDLMSReader.py @@ -0,0 +1,559 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import os +import datetime +import time +import traceback +from gurux_common.enums import TraceLevel +from gurux_common.io import Parity, StopBits +from gurux_common import ReceiveParameters, GXCommon, TimeoutException +from gurux_dlms import GXByteBuffer, GXReplyData, GXDLMSTranslator, GXDLMSException, GXDLMSAccessItem +from gurux_dlms.enums import InterfaceType, ObjectType, Authentication, Conformance, DataType,\ + Security, AssociationResult, SourceDiagnostic, AccessServiceCommandType +from gurux_dlms.objects import GXDLMSObject, GXDLMSObjectCollection, GXDLMSData, GXDLMSRegister,\ + GXDLMSDemandRegister, GXDLMSProfileGeneric, GXDLMSExtendedRegister +from gurux_net import GXNet +#from gurux_serial import GXSerial + +class GXDLMSReader: + #pylint: disable=too-many-public-methods, too-many-instance-attributes + def __init__(self, client, media, trace, invocationCounter): + #pylint: disable=too-many-arguments + self.replyBuff = bytearray(8 + 1024) + self.waitTime = 5000 + #self.logFile = open("logFile.txt", "w") + self.logFile = '' + self.trace = trace + self.media = media + self.invocationCounter = invocationCounter + self.client = client + if self.trace > TraceLevel.WARNING: + print("Authentication: " + str(self.client.authentication)) + print("ClientAddress: " + hex(self.client.clientAddress)) + print("ServerAddress: " + hex(self.client.serverAddress)) + + def disconnect(self): + #pylint: disable=broad-except + if self.media and self.media.isOpen(): + #print("DisconnectRequest") + reply = GXReplyData() + self.readDLMSPacket(self.client.disconnectRequest(), reply) + + def release(self): + #pylint: disable=broad-except + if self.media and self.media.isOpen(): + print("DisconnectRequest") + reply = GXReplyData() + try: + #Release is call only for secured connections. + #All meters are not supporting Release and it's causing + #problems. + if self.client.interfaceType == InterfaceType.WRAPPER or self.client.ciphering.security != Security.NONE: + self.readDataBlock(self.client.releaseRequest(), reply) + except Exception: + pass + # All meters don't support release. + + def close(self): + #pylint: disable=broad-except + if self.media and self.media.isOpen(): + print("DisconnectRequest") + reply = GXReplyData() + try: + #Release is call only for secured connections. + #All meters are not supporting Release and it's causing + #problems. + if self.client.interfaceType == InterfaceType.WRAPPER or self.client.ciphering.security != Security.NONE: + self.readDataBlock(self.client.releaseRequest(), reply) + except Exception: + pass + # All meters don't support release. + reply.clear() + self.readDLMSPacket(self.client.disconnectRequest(), reply) + self.media.close() + + @classmethod + def now(cls): + return datetime.datetime.now().strftime("%H:%M:%S") + + def writeTrace(self, line, level): + return + if self.trace >= level: + print(line) + self.logFile.write(line + "\n") + + def readDLMSPacket(self, data, reply=None): + if not reply: + reply = GXReplyData() + if isinstance(data, bytearray): + self.readDLMSPacket2(data, reply) + elif data: + for it in data: + reply.clear() + self.readDLMSPacket2(it, reply) + + def readDLMSPacket2(self, data, reply): + if not data: + return + notify = GXReplyData() + reply.error = 0 + eop = 0x7E + #In network connection terminator is not used. + if self.client.interfaceType == InterfaceType.WRAPPER and isinstance(self.media, GXNet): + eop = None + p = ReceiveParameters() + p.eop = eop + p.allData = True + p.waitTime = self.waitTime + if eop is None: + p.Count = 8 + else: + p.Count = 5 + self.media.eop = eop + rd = GXByteBuffer() + with self.media.getSynchronous(): + if not reply.isStreaming(): + self.writeTrace("TX: " + self.now() + "\t" + GXByteBuffer.hex(data), TraceLevel.VERBOSE) + self.media.send(data) + pos = 0 + try: + while not self.client.getData(rd, reply, notify): + if notify.data.size != 0: + if not notify.isMoreData(): + t = GXDLMSTranslator() + xml = t.dataToXml(notify.data) + print(xml) + notify.clear() + continue + if not p.eop: + p.count = self.client.getFrameSize(rd) + while not self.media.receive(p): + pos += 1 + if pos == 3: + raise TimeoutException("Failed to receive reply from the device in given time.") + print("Data send failed. Try to resend " + str(pos) + "/3") + self.media.send(data, None) + rd.set(p.reply) + p.reply = None + except Exception as e: + self.writeTrace("RX: " + self.now() + "\t" + str(rd), TraceLevel.ERROR) + raise e + self.writeTrace("RX: " + self.now() + "\t" + str(rd), TraceLevel.VERBOSE) + if reply.error != 0: + raise GXDLMSException(reply.error) + + def readDataBlock(self, data, reply): + if data: + if isinstance(data, (list)): + for it in data: + reply.clear() + self.readDataBlock(it, reply) + return reply.error == 0 + else: + self.readDLMSPacket(data, reply) + while reply.isMoreData(): + if reply.isStreaming(): + data = None + else: + data = self.client.receiverReady(reply) + self.readDLMSPacket(data, reply) + + def initializeOpticalHead(self): + if self.client.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + p = ReceiveParameters() + p.allData = True + p.eop = '\n' + p.waitTime = self.waitTime + with self.media.getSynchronous(): + data = "/?!\r\n" + self.writeTrace("TX: " + self.now() + "\t" + data, TraceLevel.VERBOSE) + self.media.send(data) + if not self.media.receive(p): + raise Exception("Failed to received reply from the media.") + + self.writeTrace("RX: " + self.now() + "\t" + str(p.reply), TraceLevel.VERBOSE) + #If echo is used. + if data.encode() == p.reply: + p.reply = None + if not self.media.receive(p): + raise Exception("Failed to received reply from the media.") + self.writeTrace("RX: " + self.now() + "\t" + str(p.reply), TraceLevel.VERBOSE) + + if not p.reply or p.reply[0] != ord('/'): + raise Exception("Invalid responce : " + str(p.reply)) + baudrate = chr(p.reply[4]) + if baudrate == '0': + bitrate = 300 + elif baudrate == '1': + bitrate = 600 + elif baudrate == '2': + bitrate = 1200 + elif baudrate == '3': + bitrate = 2400 + elif baudrate == '4': + bitrate = 4800 + elif baudrate == '5': + bitrate = 9600 + elif baudrate == '6': + bitrate = 19200 + else: + raise Exception("Unknown baud rate.") + + print("Bitrate is : " + str(bitrate)) + #Send ACK + #Send Protocol control character + controlCharacter = ord('2') + #"2" HDLC protocol procedure (Mode E) + #Mode control character + #"2" //(HDLC protocol procedure) (Binary mode) + modeControlCharacter = ord('2') + #Set mode E. + tmp = bytearray([0x06, controlCharacter, ord(baudrate), modeControlCharacter, 13, 10]) + p.reply = None + with self.media.getSynchronous(): + self.media.send(tmp) + #This sleep make sure that all meters can be read. + time.sleep(1) + self.writeTrace("TX: " + self.now() + "\t" + GXCommon.toHex(tmp), TraceLevel.VERBOSE) + p.waitTime = 200 + if self.media.receive(p): + self.writeTrace("RX: " + self.now() + "\t" + str(p.reply), TraceLevel.VERBOSE) + self.media.dataBits = 8 + self.media.parity = Parity.NONE + self.media.stopBits = StopBits.ONE + self.media.baudRate = bitrate + #This sleep make sure that all meters can be read. + time.sleep(1) + + def updateFrameCounter(self): + if self.invocationCounter and self.client.ciphering is not None and self.client.ciphering.security != Security.NONE: + self.initializeOpticalHead() + self.client.proposedConformance |= Conformance.GENERAL_PROTECTION + add = self.client.clientAddress + auth = self.client.authentication + security = self.client.ciphering.security + challenge = self.client.ctoSChallenge + try: + self.client.clientAddress = 16 + self.client.authentication = Authentication.NONE + self.client.ciphering.security = Security.NONE + reply = GXReplyData() + data = self.client.snrmRequest() + if data: + self.readDLMSPacket(data, reply) + self.client.parseUAResponse(reply.data) + size = self.client.hdlcSettings.maxInfoTX + 40 + self.replyBuff = bytearray(size) + reply.clear() + self.readDataBlock(self.client.aarqRequest(), reply) + self.client.parseAareResponse(reply.data) + reply.clear() + d = GXDLMSData(self.invocationCounter) + self.read(d, 2) + self.client.ciphering.invocationCounter = 1 + d.value + print("Invocation counter: " + str(self.client.ciphering.invocationCounter)) + self.disconnect() + #except Exception as ex: + finally: + self.client.clientAddress = add + self.client.authentication = auth + self.client.ciphering.security = security + self.client.ctoSChallenge = challenge + + def initializeConnection(self): + print("Standard: " + str(self.client.standard)) + if self.client.ciphering.security != Security.NONE: + print("Security: " + str(self.client.ciphering.security)) + print("System title: " + GXCommon.toHex(self.client.ciphering.systemTitle)) + print("Authentication key: " + GXCommon.toHex(self.client.ciphering.authenticationKey)) + print("Block cipher key: " + GXCommon.toHex(self.client.ciphering.blockCipherKey)) + if self.client.ciphering.dedicatedKey: + print("Dedicated key: " + GXCommon.toHex(self.client.ciphering.dedicatedKey)) + + self.updateFrameCounter() + self.initializeOpticalHead() + reply = GXReplyData() + data = self.client.snrmRequest() + + if data: + self.readDLMSPacket(data, reply) + self.client.parseUAResponse(reply.data) + size = self.client.hdlcSettings.maxInfoTX + 40 + self.replyBuff = bytearray(size) + reply.clear() + self.readDataBlock(self.client.aarqRequest(), reply) + self.client.parseAareResponse(reply.data) + reply.clear() + if self.client.authentication > Authentication.LOW: + try: + for it in self.client.getApplicationAssociationRequest(): + self.readDLMSPacket(it, reply) + self.client.parseApplicationAssociationResponse(reply.data) + except GXDLMSException as ex: + #Invalid password. + raise GXDLMSException(AssociationResult.PERMANENT_REJECTED, SourceDiagnostic.AUTHENTICATION_FAILURE) + + def read(self, item, attributeIndex): + data = self.client.read(item, attributeIndex)[0] + reply = GXReplyData() + self.readDataBlock(data, reply) + #Update data type on read. + if item.getDataType(attributeIndex) == DataType.NONE: + item.setDataType(attributeIndex, reply.valueType) + return self.client.updateValue(item, attributeIndex, reply.value) + + def readList(self, list_): + if list_: + data = self.client.readList(list_) + reply = GXReplyData() + values = list() + for it in data: + self.readDataBlock(it, reply) + if reply.value: + values.extend(reply.value) + reply.clear() + if len(values) != len(list_): + raise ValueError("Invalid reply. Read items count do not match.") + self.client.updateValues(list_, values) + + def write(self, item, attributeIndex): + data = self.client.write(item, attributeIndex) + self.readDLMSPacket(data) + + def readRowsByEntry(self, pg, index, count): + data = self.client.readRowsByEntry(pg, index, count) + reply = GXReplyData() + self.readDataBlock(data, reply) + return self.client.updateValue(pg, 2, reply.value) + + def readRowsByRange(self, pg, start, end): + reply = GXReplyData() + data = self.client.readRowsByRange(pg, start, end) + self.readDataBlock(data, reply) + return self.client.updateValue(pg, 2, reply.value) + + #Read values using Access request. + def readByAccess(self, list_): + if list_: + reply = GXReplyData() + data = self.client.accessRequest(None, list_) + self.readDataBlock(data, reply) + self.client.parseAccessResponse(list_, reply.data) + + def readScalerAndUnits(self): + #pylint: disable=broad-except + objs = self.client.objects.getObjects([ObjectType.REGISTER, ObjectType.EXTENDED_REGISTER, ObjectType.DEMAND_REGISTER]) + list_ = list() + try: + if self.client.negotiatedConformance & Conformance.ACCESS != 0: + for it in objs: + if isinstance(it, (GXDLMSRegister, GXDLMSExtendedRegister)): + if it.canRead(3): + list_.append(GXDLMSAccessItem(AccessServiceCommandType.GET, it, 3)) + elif isinstance(it, (GXDLMSDemandRegister)): + if it.canRead(4): + list_.append(GXDLMSAccessItem(AccessServiceCommandType.GET, it, 4)) + self.readByAccess(list_) + return + except Exception: + print("Failed to read scalers and units with access.") + try: + if self.client.negotiatedConformance & Conformance.MULTIPLE_REFERENCES != 0: + for it in objs: + if isinstance(it, (GXDLMSRegister, GXDLMSExtendedRegister)): + if it.canRead(3): + list_.append((it, 3)) + elif isinstance(it, (GXDLMSDemandRegister,)): + if it.canRead(4): + list_.append((it, 4)) + self.readList(list_) + except Exception: + self.client.negotiatedConformance &= ~Conformance.MULTIPLE_REFERENCES + if self.client.negotiatedConformance & Conformance.MULTIPLE_REFERENCES == 0: + for it in objs: + try: + if isinstance(it, (GXDLMSRegister,)): + if it.canRead(3): + self.read(it, 3) + elif isinstance(it, (GXDLMSDemandRegister,)): + if it.canRead(4): + self.read(it, 4) + except Exception: + pass + + def getProfileGenericColumns(self): + #pylint: disable=broad-except + profileGenerics = self.client.objects.getObjects(ObjectType.PROFILE_GENERIC) + for pg in profileGenerics: + self.writeTrace("Profile Generic " + str(pg.name) + "Columns:", TraceLevel.INFO) + try: + if pg.canRead(3): + self.read(pg, 3) + if self.trace > TraceLevel.WARNING: + sb = "" + for k, _ in pg.captureObjects: + if sb: + sb += " | " + sb += str(k.name) + sb += " " + desc = k.description + if desc: + sb += desc + self.writeTrace(sb, TraceLevel.INFO) + except Exception as ex: + self.writeTrace("Err! Failed to read columns:" + str(ex), TraceLevel.ERROR) + + def getReadOut(self): + #pylint: disable=unidiomatic-typecheck, broad-except + for it in self.client.objects: + if type(it) == GXDLMSObject: + print("Unknown Interface: " + it.objectType.__str__()) + continue + if isinstance(it, GXDLMSProfileGeneric): + continue + + self.writeTrace("-------- Reading " + str(it.objectType) + " " + str(it.name) + " " + it.description, TraceLevel.INFO) + for pos in it.getAttributeIndexToRead(True): + try: + if it.canRead(pos): + val = self.read(it, pos) + self.showValue(pos, val) + else: + self.writeTrace("Attribute" + str(pos) + " is not readable.", TraceLevel.INFO) + except Exception as ex: + self.writeTrace("Error! Index: " + str(pos) + " " + str(ex), TraceLevel.ERROR) + self.writeTrace(str(ex), TraceLevel.ERROR) + if not isinstance(ex, (GXDLMSException, TimeoutException)): + traceback.print_exc() + + def showValue(self, pos, val): + if isinstance(val, (bytes, bytearray)): + val = GXByteBuffer(val) + elif isinstance(val, list): + str_ = "" + for tmp in val: + if str_: + str_ += ", " + if isinstance(tmp, bytes): + str_ += GXByteBuffer.hex(tmp) + else: + str_ += str(tmp) + val = str_ + self.writeTrace("Index: " + str(pos) + " Value: " + str(val), TraceLevel.INFO) + + def getProfileGenerics(self): + #pylint: disable=broad-except,too-many-nested-blocks + cells = [] + profileGenerics = self.client.objects.getObjects(ObjectType.PROFILE_GENERIC) + for it in profileGenerics: + self.writeTrace("-------- Reading " + str(it.objectType) + " " + str(it.name) + " " + it.description, TraceLevel.INFO) + entriesInUse = self.read(it, 7) + entries = self.read(it, 8) + self.writeTrace("Entries: " + str(entriesInUse) + "/" + str(entries), TraceLevel.INFO) + pg = it + if entriesInUse == 0 or not pg.captureObjects: + continue + try: + cells = self.readRowsByEntry(pg, 1, 1) + if self.trace > TraceLevel.WARNING: + for rows in cells: + for cell in rows: + if isinstance(cell, bytearray): + self.writeTrace(GXByteBuffer.hex(cell) + " | ", TraceLevel.INFO) + else: + self.writeTrace(str(cell) + " | ", TraceLevel.INFO) + self.writeTrace("", TraceLevel.INFO) + except Exception as ex: + self.writeTrace("Error! Failed to read first row: " + str(ex), TraceLevel.ERROR) + if not isinstance(ex, (GXDLMSException, TimeoutException)): + traceback.print_exc() + try: + start = datetime.datetime.now() + end = start + start = start.replace(hour=0, minute=0, second=0, microsecond=0) + end = end.replace(minute=0, second=0, microsecond=0) + cells = self.readRowsByRange(it, start, end) + for rows in cells: + row = "" + for cell in rows: + if row: + row += " | " + if isinstance(cell, bytearray): + row += GXByteBuffer.hex(cell) + else: + row += str(cell) + self.writeTrace(row, TraceLevel.INFO) + except Exception as ex: + self.writeTrace("Error! Failed to read last day: " + str(ex), TraceLevel.ERROR) + + def getAssociationView(self): + reply = GXReplyData() + self.readDataBlock(self.client.getObjectsRequest(), reply) + self.client.parseObjects(reply.data, True, False) + #Access rights must read differently when short Name referencing is used. + if not self.client.useLogicalNameReferencing: + sn = self.client.objects.findBySN(0xFA00) + if sn and sn.version > 0: + try: + self.read(sn, 3) + except (GXDLMSException): + self.writeTrace("Access rights are not implemented for the meter.", TraceLevel.INFO) + + def readAll(self, outputFile): + try: + read = False + self.initializeConnection() + if outputFile and os.path.exists(outputFile): + try: + c = GXDLMSObjectCollection.load(outputFile) + self.client.objects.extend(c) + if self.client.objects: + read = True + except Exception: + read = False + if not read: + self.getAssociationView() + exit() + self.readScalerAndUnits() + self.getProfileGenericColumns() + self.getReadOut() + self.getProfileGenerics() + if outputFile: + self.client.objects.save(outputFile) + except (KeyboardInterrupt, SystemExit): + #Don't send anything if user is closing the app. + self.media = None + raise + finally: + self.close() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/MeterClient.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/MeterClient.py new file mode 100644 index 0000000..0853589 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/MeterClient.py @@ -0,0 +1,221 @@ +import os +import sys +import traceback +import json +#from gurux_serial import GXSerial +from os import path + +import logging +_LOGGER = logging.getLogger(__name__) + +sys.path.append(path.dirname(__file__)) # add current path to syspath so we can import dlms library and all imports inside dlms library will work + +from gurux_net import GXNet +from gurux_dlms.enums import ObjectType +from gurux_dlms.objects.GXDLMSObjectCollection import GXDLMSObjectCollection +#from GXSettings import GXSettings +from GXDLMSReader import GXDLMSReader +from gurux_dlms.GXDLMSClient import GXDLMSClient +from gurux_common.GXCommon import GXCommon +from gurux_dlms.enums.DataType import DataType +import locale +from gurux_dlms.GXDateTime import GXDateTime +from gurux_dlms.internal._GXCommon import _GXCommon +from gurux_dlms import GXDLMSException, GXDLMSExceptionResponse, GXDLMSConfirmedServiceError, GXDLMSTranslator +from gurux_dlms import GXByteBuffer, GXDLMSTranslatorMessage, GXReplyData +from gurux_dlms.enums import RequestTypes, Security, InterfaceType +from gurux_dlms.secure.GXDLMSSecureClient import GXDLMSSecureClient + + +#from gurux_dlms.enums import InterfaceType, Authentication, Security, Standard +#from gurux_dlms import GXDLMSClient +#from gurux_dlms.secure import GXDLMSSecureClient +#from gurux_dlms.GXByteBuffer import GXByteBuffer +#from gurux_dlms.objects import GXDLMSObject +from .gurux_common.enums import TraceLevel +#from gurux_common.io import Parity, StopBits, BaudRate +from .gurux_net.enums import NetworkType +#from gurux_net import GXNet +#from gurux_serial.GXSerial import GXSerial + +import socket + +class GXSettings: + # + # Constructor. + # + def __init__(self, host, port, serial): + self.media = None + self.trace = TraceLevel.INFO + self.invocationCounter = None + self.client = GXDLMSSecureClient(True) + # Objects to read. + self.readObjects = [] + self.outputFile = None + + self.media = GXNet(NetworkType.TCP, host, 0) + self.media.port = int(port) + self.client.useLogicalNameReferencing = False + self.client.serverAddress = GXDLMSClient.getServerAddressFromSerialNumber(int(serial)) + self.client.gbtWindowSize = int(1) + self.client.hdlcSettings.maxInfoRX = self.client.hdlcSettings.maxInfoTX = int(128) + #self.trace = TraceLevel.VERBOSE + self.trace = 0 + +class MeterClient: + def __init__(self,host,port,serial): + self.settings=GXSettings(host,port,serial) + self.reader=GXDLMSReader(self.settings.client, self.settings.media, self.settings.trace, self.settings.invocationCounter) + self.connected=False + self.connecting=False + self.host=host + self.port=port + self.serial=serial + + def connect(self): + self.connecting=True + self.settings.media.open() + self.reader.initializeConnection() + self.reader.getAssociationView() + self.connected=True # need to detect later if there were any errors during connection and define connected status based on this result + + def disconnect(self): + self.connecting=False + self.connected=False + self.reader.disconnect() + + def reconnect(self): + if(self.connecting==False): + self.disconnect() + self.connect() + + def online(self): + return self.connected + + def GetMeterValue(self,val): + if isinstance(val, (bytes, bytearray)): + val = GXByteBuffer(val) + elif isinstance(val, list): + str_ = "" + for tmp in val: + if str_: + str_ += ", " + if isinstance(tmp, bytes): + str_ += GXByteBuffer.hex(tmp) + else: + str_ += str(tmp) + val = str_ + return val + + def ReadItem(self,item,attr): + if(self.online()==False): + self.connect() + obj = self.settings.client.objects.findByLN(ObjectType.NONE, item) + if obj is None: + return '' + return self.reader.read(obj, attr) + + def ReadAllDataSYNC(self): + total_list=[] + for item in self.GetDataList(): + res=self.ReadItem(item['meter_param'],item['meter_attr']) + if 'multiplier' in item: + res=res * item['multiplier'] + + multiplier_type=type(item['multiplier']) + if(multiplier_type==float): + precision=self.MultiplierToPrecision(item['multiplier']) + res=round(res, precision) + + item['value']=res + if 'unit' not in item: + item['unit']='' + + total_list.append(item) + return total_list + + async def ReadAllData(self): + total_list=[] + for item in self.GetDataList(): + res=self.ReadItem(item['meter_param'],item['meter_attr']) + if 'multiplier' in item: + res=res * item['multiplier'] + + multiplier_type=type(item['multiplier']) + if(multiplier_type==float): + precision=self.MultiplierToPrecision(item['multiplier']) + res=round(res, precision) + + item['value']=res + if 'unit' not in item: + item['unit']='' + + total_list.append(item) + return total_list + + def ReadSingleData(self, param): + for item in self.GetDataList(): + if (item['meter_param']==param): + res=self.ReadItem(item['meter_param'],item['meter_attr']) + if 'multiplier' in item: + res=res * item['multiplier'] + + multiplier_type=type(item['multiplier']) + if(multiplier_type==float): + precision=self.MultiplierToPrecision(item['multiplier']) + res=round(res, precision) + + item['value']=res + if 'unit' not in item: + item['unit']='' + return item + + + async def ReadAllConfig(self): + total_list=[] + for item in self.GetMeterConfigList(): + res=self.ReadItem(item['meter_param'],item['meter_attr']) + item['value']=res + total_list.append(item) + return total_list + + async def ReadSingleConfig(self, param): + for item in self.GetMeterConfigList(): + if(item['meter_param']==param): + res=self.ReadItem(item['meter_param'],item['meter_attr']) + item['value']=res + return item + + def GetDataList(self): + basepath = path.dirname(__file__) + filepath = basepath + "/meter_data.json" + meter_data_file = open(filepath) + data = json.load(meter_data_file) + meter_data_file.close() + return data + + def GetMeterConfigList(self): + basepath = path.dirname(__file__) + filepath = basepath + "/meter_param.json" + meter_data_file = open(filepath) + data = json.load(meter_data_file) + meter_data_file.close() + return data + + def MultiplierToPrecision (self,val): + precision = 0 + while val % 1 != 0: + val *= 10 + precision += 1 + return precision + + def get_model(self): + #model_data=self.ReadSingleConfig('1.0.96.1.1.255') + #return model_data["value"] + return "Meter Model" + + def get_firmware_version(self): + return "Meter FW VERSION" + + def get_manufacturer(self): + return "DLMS Meter" \ No newline at end of file diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/README.md b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/README.md new file mode 100644 index 0000000..79c5a2f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/README.md @@ -0,0 +1,43 @@ +# Home Assistant GAMA300 DLMS over TCPIP +Home Assistant Integration for reading data from Elgama GAMA300 electricity meter (GAMA100 not tested, but possibly supported too) via RS485-TCP Gateway + +## Features + +- Get most of live values from meter +- Add energy sensors for integration with energy usage +- Tracks daily / total energy consumption + +## Notes + +This code was written for couple of days without any skills in Python and HA architecture. That is second time I've used Python, so anyone is welcome to make this code better. + +## Connecting meter to gateway and HA + +First your meter must support RS485 interface (check your meter manual or model number to ensure your meter support it, but also you can try to connect optical reader to gateway). To connect meter to HA you need any RS485->TCP gateway (I've used HI-FLYING HF5142B, coz prefer wires, but you can use any other or try to connect it with esp32 / 8266 with RS485 converter bridging it to TCP socket). +On gateway side you should configure TCP server to forward data from RS485 port, it should be tcp server without authorization, you can choose any port to listen. + +#### RS485 params: +- Baud rate: 9600 +- Data bit: 8 +- Stop bit: 1 +- Parity: None + +![HF5142B_connection](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_connection.jpg) +![HF5142B_TCP_Server](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_TCP_Server.jpg) + +After connecting meter you need to add `home-assistant-gama-300-dlms-meter` integration using HACS to your HA. In you HA go to *Main Menu -> HACS -> Integrations*, in top right corner press 3 dots and click to "Custom Repositories". Add repository `https://github.com/astraliens/home-assistant-gama_300_dlms_meter_reader` and category `Integration`. After this step close modal add repository window and press "Explore & Download Repositories" blue button at the bottom right corner of screen and search for `Gama 300 DLMS Meter` integration. Press on it and in right bottom corner of screen press "Download" button. +After this you can simply add it like regular integration, specifiying IP and port of gateway and your meter serial number (you can find it on meter itself). In several seconds integration get data from meter, create all found sensors and will update them constantly. + +Pinout of meter shown on photo: +![gama_300_rs485_pinout](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/gama_300_rs485_pinout.jpg) + + +## Energy consumption monitoring + +You can add `Sum Li Active power+ (QI+QIV) Time integral 1 Rate 0` sensor to your energy consumption monitoring to calculate your overall spents + +## Donations + +You can say thanks by donating for buying pizza at: + +Buy Me A Pizza \ No newline at end of file diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/GXCommon.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/GXCommon.py new file mode 100644 index 0000000..11174b0 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/GXCommon.py @@ -0,0 +1,98 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import sys + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class GXCommon: + #pylint: disable=too-few-public-methods + """General methods for communication.""" + + __NIBBLE = 4 + __HEX_ARRAY = "0123456789ABCDEFGH" + __LOW_BYTE_PART = 0x0F + + def __init__(self, data=None, senderInfo=None): + """ + Constructor. + """ + + @classmethod + def getVersion(cls): + """Get version.""" + return sys.version_info + + @classmethod + def toHex(cls, value): + """Convert data to hex.""" + #Return empty string if array is empty. + if not value: + return "" + hexChars = "" + #Python 2.7 handles bytes as a string array. It's changed to bytearray. + if sys.version_info < (3, 0) and not isinstance(value, bytearray): + value = bytearray(value) + for it in value: + hexChars += GXCommon.__HEX_ARRAY[it >> GXCommon.__NIBBLE] + hexChars += GXCommon.__HEX_ARRAY[it & GXCommon.__LOW_BYTE_PART] + hexChars += ' ' + return hexChars + + #Convert char hex value to byte value. + @classmethod + def ___getValue(cls, c): + #Id char. + if c.islower(): + c = c.upper() + pos = GXCommon.__HEX_ARRAY.find(c) + if pos == -1: + raise Exception("Invalid hex string") + return pos + + @classmethod + def hexToBytes(cls, value): + """Convert string to byte array.""" + buff = bytearray() + lastValue = -1 + for ch in value: + if ch != ' ': + if lastValue == -1: + lastValue = cls.___getValue(ch) + elif lastValue != -1: + buff.append(lastValue << GXCommon.__NIBBLE | cls.___getValue(ch)) + lastValue = -1 + elif lastValue != -1: + buff.append(cls.___getValue(ch)) + lastValue = -1 + return buff diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/IGXMedia.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/IGXMedia.py new file mode 100644 index 0000000..e18f78b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/IGXMedia.py @@ -0,0 +1,242 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import abc +from .GXCommon import GXCommon + +ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()}) +#pylint: disable=syntax-error +if GXCommon.getVersion() >= (2, 7): + class IGXMedia(ABC): + #pylint: disable=too-many-public-methods + """Common interface for all Media components. + Using this interface GXCommunication library enables communication with\ + different medias.""" + + @abc.abstractmethod + def addListener(self, listener): + """Start to listen media events.""" + + @abc.abstractmethod + def removeListener(self, listener): + """Stop to listen media events.""" + + @abc.abstractmethod + def copy(self, target): + """ Copies the content of the media to target media. + target : Target media.""" + + @abc.abstractmethod + def getName(self): + """ Returns name of the media. Media name is used to identify media + connection, so two different media connection can not return same media + name. + + Returns Media name.""" + + @abc.abstractproperty + def trace(self): + """Trace level of the IGXMedia.""" + + @abc.abstractmethod + def open(self): + """Opens the media.""" + + @abc.abstractmethod + def isOpen(self): + """Checks if the connection is established. + Returns True, if the connection is established.""" + + @abc.abstractmethod + def close(self): + """Closes the active connection.""" + + @abc.abstractmethod + def send(self, data, receiver): + """Sends data asynchronously. No reply from the receiver, whether or not the + operation was successful, is expected. + + data : Data to send to the device. + receiver : Media depend information of the receiver (optional).""" + + @abc.abstractmethod + def getMediaType(self): + """Returns media type as a string.""" + + @abc.abstractmethod + def getSettings(self): + """Get media settings as a XML string.""" + + @abc.abstractmethod + def setSettings(self, value): + """Set media settings as a XML string.""" + + @abc.abstractmethod + def getSynchronous(self): + """Locking this property makes the connection synchronized and stops sending + OnReceived events.""" + + @abc.abstractmethod + def getIsSynchronous(self): + """Checks if the connection is in synchronous mode.""" + + @abc.abstractmethod + def receive(self, args): + """Waits for more reply data After SendSync if whole packet is not received yet.""" + + @abc.abstractmethod + def resetSynchronousBuffer(self): + """Resets synchronous buffer.""" + + @abc.abstractmethod + def getBytesSent(self): + """Sent byte count.""" + + @abc.abstractmethod + def getBytesReceived(self): + """Received byte count.""" + + @abc.abstractmethod + def resetByteCounters(self): + """Resets BytesReceived and BytesSent counters.""" + + @abc.abstractmethod + def validate(self): + """Validate Media settings for connection open. Returns table of media + properties that must be set before media is valid to open.""" + + @abc.abstractproperty + def eop(self): + """End of the packet.""" +else: + class IGXMedia(ABC): + #pylint: disable=too-many-public-methods + """Common interface for all Media components. + Using this interface GXCommunication library enables communication with\ + different medias.""" + + @abc.abstractmethod + def addListener(self, listener): + """Start to listen media events.""" + + @abc.abstractmethod + def removeListener(self, listener): + """Stop to listen media events.""" + + @abc.abstractmethod + def copy(self, target): + """ Copies the content of the media to target media. + target : Target media.""" + + @abc.abstractmethod + def getName(self): + """ Returns name of the media. Media name is used to identify media + connection, so two different media connection can not return same media + name. + + Returns Media name.""" + + @abc.abstractproperty + def trace(self): + """Trace level of the IGXMedia.""" + + @abc.abstractmethod + def open(self): + """Opens the media.""" + + @abc.abstractmethod + def isOpen(self): + """Checks if the connection is established. + Returns True, if the connection is established.""" + + @abc.abstractmethod + def close(self): + """Closes the active connection.""" + + @abc.abstractmethod + def send(self, data, receiver): + """Sends data asynchronously. No reply from the receiver, whether or not the + operation was successful, is expected. + + data : Data to send to the device. + receiver : Media depend information of the receiver (optional).""" + + @abc.abstractmethod + def getMediaType(self): + """Returns media type as a string.""" + + @abc.abstractmethod + def getSettings(self): + """Get media settings as a XML string.""" + + @abc.abstractmethod + def setSettings(self, value): + """Set media settings as a XML string.""" + + @abc.abstractmethod + def getSynchronous(self): + """Locking this property makes the connection synchronized and stops sending + OnReceived events.""" + + @abc.abstractmethod + def getIsSynchronous(self): + """Checks if the connection is in synchronous mode.""" + + @abc.abstractmethod + def receive(self, args): + """Waits for more reply data After SendSync if whole packet is not received yet.""" + + @abc.abstractmethod + def resetSynchronousBuffer(self): + """Resets synchronous buffer.""" + + @abc.abstractmethod + def getBytesSent(self): + """Sent byte count.""" + + @abc.abstractmethod + def getBytesReceived(self): + """Received byte count.""" + + @abc.abstractmethod + def resetByteCounters(self): + """Resets BytesReceived and BytesSent counters.""" + + @abc.abstractmethod + def validate(self): + """Validate Media settings for connection open. Returns table of media + properties that must be set before media is valid to open.""" + + @abc.abstractproperty + def eop(self): + """End of the packet.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/IGXMediaListener.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/IGXMediaListener.py new file mode 100644 index 0000000..45756e4 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/IGXMediaListener.py @@ -0,0 +1,80 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import abc +ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()}) +class IGXMediaListener(ABC): + __metaclass__ = abc.ABCMeta + """Media component will notify events throught this interface.""" + + @abc.abstractmethod + def onError(self, sender, ex): + """ + Represents the method that will handle the error event of a Gurux + component. + + sender : The source of the event. + ex : An Exception object that contains the event data. + """ + + @abc.abstractmethod + def onReceived(self, sender, e): + """Media component sends received data through this method. + + sender : The source of the event. + e : Event arguments. + """ + + @abc.abstractmethod + def onMediaStateChange(self, sender, e): + """Media component sends notification, when its state changes. + sender : The source of the event. + e : Event arguments. + """ + + @abc.abstractmethod + def onTrace(self, sender, e): + """Called when the Media is sending or receiving data. + + sender : The source of the event. + e : Event arguments. + """ + + @abc.abstractmethod + def onPropertyChanged(self, sender, e): + """ + Event is raised when a property is changed on a component. + + sender : The source of the event. + e : Event arguments. + """ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/MediaStateEventArgs.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/MediaStateEventArgs.py new file mode 100644 index 0000000..558fd39 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/MediaStateEventArgs.py @@ -0,0 +1,53 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums.MediaState import MediaState + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class MediaStateEventArgs: + """An argument class for media state changed event.""" + #pylint: disable=too-few-public-methods + + def __init__(self, state=MediaState.CLOSED): + """ + Constructor. + + """ + ###Media state. + self.state = state + ###Is connection accepted. + self.accept = True + + def __str__(self): + return str(self.state) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/PropertyChangedEventArgs.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/PropertyChangedEventArgs.py new file mode 100644 index 0000000..fc95b1a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/PropertyChangedEventArgs.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class PropertyChangedEventArgs: + """Provides data for the PropertyChanged event.""" + #pylint: disable=too-few-public-methods + + def __init__(self, name=None): + """ + Constructor. + + name : Name of changed property. + """ + ###Name of changed property. + self.name = name + + def __str__(self): + return self.name diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/ReceiveEventArgs.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/ReceiveEventArgs.py new file mode 100644 index 0000000..0647e3a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/ReceiveEventArgs.py @@ -0,0 +1,56 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXCommon import GXCommon + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class ReceiveEventArgs: + """Argument class for IGXMedia data received events.""" + #pylint: disable=too-few-public-methods + + def __init__(self, data=None, senderInfo=None): + """ + Constructor. + + data : Received data. + senderInfo : Sender information. + """ + ###Received data. + self.data = data + self.senderInformation = senderInfo + + def __str__(self): + if isinstance(self.data, (bytearray, bytes)): + return self.senderInformation + ":" + GXCommon.toHex(self.data) + return self.senderInformation + ":" + str(self.data) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/ReceiveParameters.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/ReceiveParameters.py new file mode 100644 index 0000000..53e6d0b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/ReceiveParameters.py @@ -0,0 +1,85 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this ot property is not working. +#pylint: disable=bad-option-value, useless-object-inheritance +class ReceiveParameters(object): + """ReceiveParameters class is used when data is read synchronously.""" + #pylint: disable=too-few-public-methods + + def __init__(self): + """Constructor.""" + self.__waitTime = -1 + ###How long reply message is waited. + self.allData = False + ###Is all data read when End of packet is found. + self.reply = None + ###Received data. + self.peek = False + ###If true, returns the bytes from the buffer without removing. + self.__eop = None + ###End of packet waited for. + self.__count = 0 + ###Minimum count of bytes waited for. + + + def __getEop(self): + return self.__eop + + def __setEop(self, value): + self.__eop = value + + eop = property(__getEop, __setEop) + """The end of packet (EOP) waited for.""" + + def __getCount(self): + return self.__count + + def __setCount(self, value): + if value < 0: + raise ValueError("Count") + self.__count = value + + count = property(__getCount, __setCount) + """The number of reply data bytes to be read.""" + + def __getWaitTime(self): + return self.__waitTime + + def __setWaitTime(self, value): + self.__waitTime = value + + waitTime = property(__getWaitTime, __setWaitTime) + """Maximum time, in milliseconds, to wait for reply data. WaitTime -1 + (Default value) indicates infinite wait time.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/TimeoutException.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/TimeoutException.py new file mode 100644 index 0000000..fc31f5a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/TimeoutException.py @@ -0,0 +1,36 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +class TimeoutException(IOError): + """Generic Timeout error. TimeoutError is not implemented to Python 2.7.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/TraceEventArgs.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/TraceEventArgs.py new file mode 100644 index 0000000..7f4faa0 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/TraceEventArgs.py @@ -0,0 +1,83 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import datetime +from .GXCommon import GXCommon + +###Python 2 requires this ot property is not working. +#pylint: disable=bad-option-value, useless-object-inheritance +class TraceEventArgs(object): + #pylint: disable=too-few-public-methods + """Argument class for IGXMedia connection and disconnection events.""" + + def __init__(self, traceType, data, index=0, length=None): + """ + Constructor. + + type : Trace type. + data : Send or received data. + index : Index where data copy is started. + length : How many bytes are included to data. + """ + + ###Time stamp when data is send or received. + self.timestamp = datetime.datetime.now() + ###Trace type. + self.type = traceType + if isinstance(data, (bytearray, bytes)): + if length is None or index != 0: + length = len(data) - index + ###Send or received data. + self.__data = data[index:length] + else: + self.__data = data + + def __getData(self): + return self.__data + + def __setData(self, value): + self.__data = value + + data = property(__getData, __setData) + """Send or received data.""" + + def dataToString(self): + """Convert data to string.""" + if not self.data: + return "" + if isinstance(self.data, (bytearray, bytes)): + return GXCommon.toHex(self.data) + return str(self.data) + + def __str__(self): + return self.timestamp.strftime("%H:%M:%S") + "\t" + str(self.type) + "\t" + self.dataToString() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/__init__.py new file mode 100644 index 0000000..d97eda4 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/__init__.py @@ -0,0 +1,40 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXCommon import GXCommon +from .IGXMedia import IGXMedia +from .IGXMediaListener import IGXMediaListener +from .TraceEventArgs import TraceEventArgs +from .ReceiveEventArgs import ReceiveEventArgs +from .ReceiveParameters import ReceiveParameters +from .TimeoutException import TimeoutException diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/MediaState.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/MediaState.py new file mode 100644 index 0000000..69628bb --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/MediaState.py @@ -0,0 +1,58 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +#pylint: disable=broad-except,no-name-in-module +try: + from enum import Enum + __base = Enum +except Exception: + __base = object + +class MediaState(__base): + #pylint: disable=too-few-public-methods + """Available media state changes.""" + + CLOSED = 0 + ###Media is closed.### + + OPEN = 1 + ###Media is open.### + + OPENING = 2 + ###Media is opening.### + + CLOSING = 3 + ###Media is closing.### + + CHANGED = 3 + ###GXClients Media type has changed.### diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/TraceLevel.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/TraceLevel.py new file mode 100644 index 0000000..e83b963 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/TraceLevel.py @@ -0,0 +1,60 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXCommon import GXCommon + +#pylint: disable=no-name-in-module +if GXCommon.getVersion() < (3, 6): + __base = object +else: + from enum import IntFlag + __base = IntFlag + +class TraceLevel(__base): + ###Specifies trace levels.### + #pylint: disable=too-few-public-methods + + OFF = 0x0 + ###Output no tracing and debugging messages.### + + ERROR = 0x1 + ###Output error-handling messages.### + + WARNING = 0x2 + ###Output warnings and error-handling messages.### + + INFO = 0x4 + ###Output informational messages, warnings, and error-handling messages.### + + VERBOSE = 0x8 + ###Output all debugging and tracing messages.### diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/TraceTypes.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/TraceTypes.py new file mode 100644 index 0000000..04b9f81 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/TraceTypes.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXCommon import GXCommon + +#pylint: disable=no-name-in-module +if GXCommon.getVersion() < (3, 6): + __base = object +else: + from enum import Flag + __base = Flag + +class TraceTypes(__base): + #pylint: disable=too-few-public-methods + """Trace Type enumerates where trace is sent.""" + SENT = 0x1 + ###Data is sent. + RECEIVED = 0x2 + ###Data is received. + ERROR = 0x4 + ###Error has occurred. + WARNING = 0x8 + ###Warning. + INFO = 0x10 + ###Info. Example Media states are notified as info. diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/__init__.py new file mode 100644 index 0000000..8376aad --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/enums/__init__.py @@ -0,0 +1,36 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .MediaState import MediaState +from .TraceLevel import TraceLevel +from .TraceTypes import TraceTypes diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/BaudRate.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/BaudRate.py new file mode 100644 index 0000000..1092755 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/BaudRate.py @@ -0,0 +1,58 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +#pylint: disable=broad-except,no-name-in-module +try: + from enum import IntEnum + __base = IntEnum +except Exception: + __base = object + +class BaudRate(__base): + """Defines a list of commonly supported serial communication rates (baud rates).""" + BAUD_RATE_38400 = 38400 + """38,400 baud.""" + BAUD_RATE_19200 = 19200 + """19,200 baud.""" + BAUD_RATE_9600 = 9600 + """9,600 baud.""" + BAUD_RATE_4800 = 4800 + """4,800 baud.""" + BAUD_RATE_2400 = 2400 + """2,400 baud.""" + BAUD_RATE_1800 = 1800 + """1,800 baud.""" + BAUD_RATE_600 = 600 + """600 baud.""" + BAUD_RATE_300 = 300 + """300 baud.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/Handshake.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/Handshake.py new file mode 100644 index 0000000..7107c91 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/Handshake.py @@ -0,0 +1,61 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +#pylint: disable=broad-except,no-name-in-module +try: + from enum import IntEnum + __base = IntEnum +except Exception: + __base = object + +class Handshake(__base): + """Specifies the control protocol used in establishing a serial port communication.""" + + NONE = 0 + """No control is used for the handshake.""" + + X_ON_X_OFF = 1 + """The XON/XOFF software control protocol is used. The XOFF control is sent\ + to stop the transmission of data. The XON control is sent to resume the\ + transmission. These software controls are used instead of Request to Send\ + RTS) and Clear to Send (CTS) hardware controls.""" + + REQUEST_TO_SEND = 2 + """Request-to-Send (RTS) hardware flow control is used. RTS signals that\ + data is available for transmission. If the input buffer becomes full, the\ + RTS line will be set to false. The RTS line will be set to true when more\ + room becomes available in the input buffer.""" + + REQUEST_TO_SEND_X_ON_X_OFF = 3 + """Both the Request-to-Send (RTS) hardware control and the XON/XOFF software\ + controls are used.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/Parity.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/Parity.py new file mode 100644 index 0000000..8ad8484 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/Parity.py @@ -0,0 +1,52 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +#pylint: disable=broad-except,no-name-in-module +try: + from enum import IntEnum + __base = IntEnum +except Exception: + __base = object + +class Parity(__base): + """Specifies the parity bit for a System.IO.Ports.SerialPort object.""" + NONE = 0 + """No parity check occurs.""" + ODD = 1 + """Sets the parity bit so that the count of bits set is an odd number.""" + EVEN = 2 + """Sets the parity bit so that the count of bits set is an even number.""" + MARK = 3 + """Leaves the parity bit set to 1.""" + SPACE = 4 + """Leaves the parity bit set to 0.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/StopBits.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/StopBits.py new file mode 100644 index 0000000..407f338 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/StopBits.py @@ -0,0 +1,51 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +try: + #pylint: disable=broad-except,no-name-in-module + from enum import IntEnum + __base = IntEnum +except Exception: + __base = object + +class StopBits(__base): + """Specifies the number of stop bits used on the Serial Port.""" + + ONE = 0 + """One stop bit is used.""" + + TWO = 1 + """Two stop bits are used.""" + + ONE_POINT_FIVE = 2 + """1.5 stop bits are used.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/__init__.py new file mode 100644 index 0000000..f4414a8 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_common/io/__init__.py @@ -0,0 +1,37 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .BaudRate import BaudRate +from .Handshake import Handshake +from .Parity import Parity +from .StopBits import StopBits diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ActionRequestType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ActionRequestType.py new file mode 100644 index 0000000..0624c43 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ActionRequestType.py @@ -0,0 +1,57 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXIntEnum import GXIntEnum + +class ActionRequestType(GXIntEnum): + """ + Enumerates Action request types. + """ + + #Normal action. + NORMAL = 1 + + #Next block. + NEXT_BLOCK = 2 + + #Action with list. + WITH_LIST = 3 + + #Action with first block. + WITH_FIRST_BLOCK = 4 + + #Action with list and first block. + WITH_LIST_AND_FIRST_BLOCK = 5 + + #Action with block. + WITH_BLOCK = 6 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ActionResponseType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ActionResponseType.py new file mode 100644 index 0000000..31564a2 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ActionResponseType.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXIntEnum import GXIntEnum + +class ActionResponseType(GXIntEnum): + """ + Enumerates Action response types. + """ + + #Normal action. + NORMAL = 1 + #Action with block. + WITH_BLOCK = 2 + #Action with list. + WITH_LIST = 3 + #Action with next block. + NEXT_BLOCK = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/AesGcmParameter.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/AesGcmParameter.py new file mode 100644 index 0000000..7a38a5f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/AesGcmParameter.py @@ -0,0 +1,82 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums.Security import Security +from .objects.enums.SecuritySuite import SecuritySuite +from .CountType import CountType + + # pylint: disable=too-many-instance-attributes +class AesGcmParameter: + def __init__(self, tag=0, systemTitle=None, blockCipherKey=None, authenticationKey=None): + """ + Constructor. + + tag : Tag. + systemTitle : System title. + blockCipherKey : Block cipher key. + authenticationKey : Authentication key. + """ + self.tag = tag + self.security = Security.NONE + # Invocation counter. + self.invocationCounter = 0 + # Used security suite. + self.securitySuite = SecuritySuite.AES_GCM_128 + # Block cipher key. + self.blockCipherKey = blockCipherKey + # Authentication key. + self.authenticationKey = authenticationKey + # System title. + self.systemTitle = systemTitle + # Recipient system title. + self.recipientSystemTitle = None + # Count type. + self.type_ = CountType.PACKET + # Date time. + self.dateTime = None + # Other information. + self.otherInformation = None + # Counted tag. + self.countTag = None + # Key parameters. + self.keyParameters = None + # Key ciphered data. + self.keyCipheredData = None + # Ciphered content. + self.cipheredContent = None + # Shared secret is generated when connection is made. + self.sharedSecret = None + # xml settings. This is used only on xml parser. + self.xml = None + # System title is not send on pre-established connecions. + self.ignoreSystemTitle = False diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ConfirmedServiceError.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ConfirmedServiceError.py new file mode 100644 index 0000000..9a28a9d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ConfirmedServiceError.py @@ -0,0 +1,45 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from .GXIntEnum import GXIntEnum + +class ConfirmedServiceError(GXIntEnum): + """ + Confirmed service error tells when error has occurred. + """ + #pylint: disable=too-few-public-methods + + INITIATE_ERROR = 1 + READ = 5 + WRITE = 6 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ConnectionState.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ConnectionState.py new file mode 100644 index 0000000..35b979a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ConnectionState.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntFlag import GXIntFlag + +class ConnectionState(GXIntFlag): + """ + Enumerates connection state types. + """ + + # Connection is not made for the meter. + NONE = 0 + # Connection is made for HDLC level. + HDLC = 1 + # Connection is made for DLMS level. + DLMS = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/CountType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/CountType.py new file mode 100644 index 0000000..e1ef8ff --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/CountType.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXIntEnum import GXIntEnum + +class CountType(GXIntEnum): + """ + Enumerate values that are add to counted GMAC. + """ + + # Total packet is created. + PACKET = -1 + + # Counted Tag is added. + TAG = 0x1 + + # Data is added. + DATA = 0x2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXArray.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXArray.py new file mode 100644 index 0000000..2df5238 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXArray.py @@ -0,0 +1,36 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXArray(list): + def __init__(self): + list.__init__(self) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXBitString.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXBitString.py new file mode 100644 index 0000000..aa64fe2 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXBitString.py @@ -0,0 +1,101 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# +# -------------------------------------------------------------------------- +# +from .GXUInt8 import GXUInt8 + +# BitString class is used with Bit strings. +class GXBitString: + + # + # Constructor. + # + # @param val + # Bit string value. + # + def __init__(self, val, count=None): + if isinstance(val, (GXUInt8)): + val = GXBitString.___toBitString(val, 8) + if count: + val = val[0:count] + self.value = val + + @classmethod + def ___toBitString(cls, value, count): + count = min(count, 8) + pos = 0 + sb = "" + while pos != count: + if (value & (1 << pos)) != 0: + sb += "1" + else: + sb += "0" + pos = 1 + pos + return sb + + # Convert integer value to BitString. + # value: Value to convert. + # count: Amount of bits. + # Returns: Bitstring. + @classmethod + def toBitString(cls, value, count): + sb = cls.___toBitString(value & 0xFF, count) + if count > 8: + sb += cls.___toBitString((value >> 8) & 0xFF, count - 8) + if count > 16: + sb += cls.___toBitString((value >> 16) & 0xFF, count - 16) + if count > 24: + sb += cls.___toBitString((value >> 24) & 0xFF, count - 24) + if len(sb) > count: + return sb[0, count] + return sb + + def __str__(self): + return self.value + + # + # Bit string value as byte. + # + def toInteger(self): + val = 0 + if self.value: + pos = 0 + for it in self.value: + if it == '1': + val |= (1 << pos) + elif it != '0': + raise ValueError("Invalid parameter.") + pos = 1 + pos + return val diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXByteBuffer.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXByteBuffer.py new file mode 100644 index 0000000..a7e5eb2 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXByteBuffer.py @@ -0,0 +1,627 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import sys +import struct + +#pylint: disable=import-error, no-name-in-module +if sys.version_info < (3, 0): + __base = object +else: + from collections.abc import Sequence + __base = Sequence + +# pylint: disable=too-many-public-methods,useless-object-inheritance +class GXByteBuffer(__base): + """ + Byte array class is used to save received bytes. + """ + + __HEX_ARRAY = "0123456789ABCDEFGH" + __NIBBLE = 4 + __LOW_BYTE_PART = 0x0F + __ARRAY_CAPACITY = 10 + + def __init__(self, value=None): + """ + Constructor. + value: Buffer or capacity. + """ + self._data = bytearray() + self.__size = 0 + self.__position = 0 + if isinstance(value, (bytearray, bytes)): + self.setCapacity(len(value)) + self.set(value) + elif isinstance(value, GXByteBuffer): + self.setCapacity(value.size - value.position) + self.set(value) + elif isinstance(value, int): + self.setCapacity(value) + elif isinstance(value, str): + self.setHexString(value) + else: + self.setCapacity(0) + + def clear(self): + """ + Clear buffer but do not release memory. + """ + self.position = 0 + self.size = 0 + + # + # Buffer capacity. + # + # Buffer capacity. + # + def getCapacity(self): + if not self._data: + return 0 + return len(self._data) + + # + # Allocate new size for the array in bytes. + # + # @param capacity + # Buffer capacity. + # + def setCapacity(self, capacity): + if capacity == 0: + self._data = bytearray() + self.__size = 0 + self.__position = 0 + else: + if not self._data: + self._data = bytearray(capacity) + else: + tmp = self._data + self._data = bytearray(capacity) + if self.size < capacity: + self._data[0:self.size] = tmp + else: + self._data[0:capacity] = self._data + self.__size = capacity + + capacity = property(getCapacity, setCapacity) + """Buffer capacity.""" + + def getPosition(self): + return self.__position + + def setPosition(self, value): + if value < 0 or value > len(self): + raise ValueError("position") + self.__position = value + + position = property(getPosition, setPosition) + """Buffer position.""" + + def getSize(self): + return self.__size + + def setsize(self, value): + if value < 0 or value > self.capacity: + raise ValueError("size") + self.__size = value + if self.__position > self.__size: + self.__position = self.__size + + size = property(getSize, setsize) + """Buffer size.""" + + def __len__(self): + """Buffer size.""" + return self.__size + + def __getitem__(self, i): + return self._data[i] + + def available(self): + """Amount of non read bytes in the buffer.""" + return self.size - self.position + + # + # Get buffer data as byte array. + # + def array(self): + return self.subArray(0, self.size) + + # + # Returns sub array from byte buffer. + # + # @param index + # Start index. + # @param count + # Byte count. + # Sub array. + # + def subArray(self, index, count): + if count != 0: + tmp = bytearray(count) + tmp[0:count] = self._data[index:index + count] + return tmp + return bytearray(0) + + # + # Move content from source to destination. + # + # @param srcPos + # Source position. + # @param destPos + # Destination position. + # @param count + # Item count. + # + def move(self, srcPos, destPos, count): + if count < 0: + raise ValueError("count") + if count != 0: + self._data[destPos:destPos + count] = self._data[srcPos:srcPos + count] + self.size = destPos + count + if self.position > self.size: + self.position = self.size + + # + # Remove handled bytes. This can be used in debugging to remove + # handled + # bytes. + # + def trim(self): + if self.size == self.position: + self.size = 0 + else: + self.move(self.position, 0, self.size - self.position) + self.position = 0 + + # + # Push the given byte into this buffer at the current position, and + # then + # increments the position. + # + # @param item + # The byte to be added. + # + def setUInt8(self, item, index=None): + if index is None: + self.setUInt8(item, self.size) + self.size += 1 + else: + if index >= self.capacity: + self.capacity = index + self.__ARRAY_CAPACITY + self._data[index] = item + + # + # Push the given byte into this buffer at the current position, and + # then + # increments the position. + # + # @param item + # The byte to be added. + # + def setInt8(self, item, index=None): + self.setUInt8(item & 0xFF, index) + + def setUInt16(self, item, index=None): + if index is None: + self.setUInt16(item, self.size) + self.size += 2 + else: + if index + 2 >= self.capacity: + self.capacity = (index + self.__ARRAY_CAPACITY) + self._data[index] = int(((item >> 8) & 0xFF)) + self._data[index + 1] = int((item & 0xFF)) + + def setUInt32(self, item, index=None): + if index is None: + self.setUInt32(item, self.size) + self.size += 4 + else: + if index + 4 >= self.capacity: + self.capacity = index + self.__ARRAY_CAPACITY + self._data[index] = int(((item >> 24) & 0xFF)) + self._data[index + 1] = int(((item >> 16) & 0xFF)) + self._data[index + 2] = int(((item >> 8) & 0xFF)) + self._data[index + 3] = int((item & 0xFF)) + + def setUInt64(self, item, index=None): + if index is None: + self.setUInt64(item, self.size) + self.size += 8 + else: + if index + 8 >= self.capacity: + self.capacity = (index + self.__ARRAY_CAPACITY) + self._data[self.size] = int(((item >> 56) & 0xFF)) + self._data[self.size + 1] = int(((item >> 48) & 0xFF)) + self._data[self.size + 2] = int(((item >> 40) & 0xFF)) + self._data[self.size + 3] = int(((item >> 32) & 0xFF)) + self._data[self.size + 4] = int(((item >> 24) & 0xFF)) + self._data[self.size + 5] = int(((item >> 16) & 0xFF)) + self._data[self.size + 6] = int(((item >> 8) & 0xFF)) + self._data[self.size + 7] = int((item & 0xFF)) + + def setFloat(self, value, index=None): + if index is None: + self.setFloat(value, self.size) + self.size += 4 + else: + if index + 4 >= self.capacity: + self.capacity = (index + self.__ARRAY_CAPACITY) + tmp = struct.pack("f", value) + self._data[self.size] = tmp[3] + self._data[self.size + 1] = tmp[2] + self._data[self.size + 2] = tmp[1] + self._data[self.size + 3] = tmp[0] + + def setDouble(self, value, index=None): + if index is None: + self.setDouble(value, self.size) + self.size += 8 + else: + if index + 8 >= self.capacity: + self.capacity = (index + self.__ARRAY_CAPACITY) + tmp = struct.pack("d", value) + # Swap bytes. + self._data[self.size] = tmp[7] + self._data[self.size + 1] = tmp[6] + self._data[self.size + 2] = tmp[5] + self._data[self.size + 3] = tmp[4] + self._data[self.size + 4] = tmp[3] + self._data[self.size + 5] = tmp[2] + self._data[self.size + 6] = tmp[1] + self._data[self.size + 7] = tmp[0] + + def getUInt8(self, index=None): + if index is None: + index = self.position + value = self._data[index] & 0xFF + value = value % 2 ** 8 + self.position += 1 + return value + if index >= self.size: + raise ValueError("getUInt8") + value = self._data[index] & 0xFF + value = value % 2 ** 8 + return value + + def getInt8(self, index=None): + if index is None: + index = self.position + value = self._data[index] + value = (value + 2 ** 7) % 2 ** 8 - 2 ** 7 + self.position += 1 + return value + if index >= self.size: + raise ValueError("getInt8") + value = self._data[index] + value = (value + 2 ** 7) % 2 ** 8 - 2 ** 7 + return value + + def getUInt16(self, index=None): + if index is None: + index = self.position + value = ((self._data[index] & 0xFF) << 8) | (self._data[index + 1] & 0xFF) + value = value % 2 ** 16 + self.position += 2 + return value + if index + 2 > self.size: + raise ValueError("getUInt16") + value = ((self._data[index] & 0xFF) << 8) | (self._data[index + 1] & 0xFF) + value = value % 2 ** 16 + return value + + def getInt16(self): + return (self.getUInt16() + 2 ** 15) % 2 ** 16 - 2 ** 15 + + def getInt32(self, index=None): + if index is None: + index = self.position + if index + 4 > self.size: + raise ValueError("getInt32") + value = (self._data[index] & 0xFF) << 24 | (self._data[index + 1] & 0xFF) << 16 | (self._data[index + 2] & 0xFF) << 8 | (self._data[index + 3] & 0xFF) + value = (value + 2 ** 31) % 2 ** 32 - 2 ** 31 + self.position += 4 + return value + + if index + 4 > self.size: + raise ValueError("getInt32") + value = (self._data[index] & 0xFF) << 24 | (self._data[index + 1] & 0xFF) << 16 | (self._data[index + 2] & 0xFF) << 8 | (self._data[index + 3] & 0xFF) + value = (value + 2 ** 31) % 2 ** 32 - 2 ** 31 + return value + + def getUInt32(self, index=None): + if index is None: + index = self.position + self.position += 4 + if index + 4 > self.size: + raise ValueError("getUInt32") + value = self._data[index] & 0xFF + value = value << 24 + value |= (self._data[index + 1] & 0xFF) << 16 + value |= (self._data[index + 2] & 0xFF) << 8 + value |= (self._data[index + 3] & 0xFF) + value = value % 2 ** 32 + return value + + def getFloat(self): + tmp = bytearray(4) + self.get(tmp) + # Swap bytes. + tmp2 = tmp[0] + tmp[0] = tmp[3] + tmp[3] = tmp2 + tmp2 = tmp[1] + tmp[1] = tmp[2] + tmp[2] = tmp2 + return struct.unpack("f", tmp)[0] + + def getDouble(self): + tmp = bytearray(8) + self.get(tmp) + # Swap bytes. + tmp2 = tmp[0] + tmp[0] = tmp[7] + tmp[7] = tmp2 + tmp2 = tmp[1] + tmp[1] = tmp[6] + tmp[6] = tmp2 + tmp2 = tmp[2] + tmp[2] = tmp[5] + tmp[5] = tmp2 + tmp2 = tmp[3] + tmp[3] = tmp[4] + tmp[4] = tmp2 + return struct.unpack("d", tmp)[0] + + def getInt64(self, index=None): + if index is None: + index = self.position + self.position += 8 + value = ((self._data[index] & 0xFF)) << 56 + value |= ((self._data[index + 1] & 0xFF)) << 48 + value |= ((self._data[index + 2] & 0xFF)) << 40 + value |= ((self._data[index + 3] & 0xFF)) << 32 + value |= ((self._data[index + 4] & 0xFF)) << 24 + value |= (self._data[index + 5] & 0xFF) << 16 + value |= (self._data[index + 6] & 0xFF) << 8 + value |= (self._data[index + 7] & 0xFF) + value = (value + 2 ** 63) % 2 ** 64 - 2 ** 63 + return value + + def getUInt64(self, index=None): + value = self.getInt64(index) + return value % 2 ** 64 + + # + # Check is byte buffer ASCII string. + # + # @param value + # Byte array. + # Is ASCII string. + # + @classmethod + def isAsciiString(cls, value): + # pylint: disable=too-many-boolean-expressions + if value: + for it in value: + if (it < 32 or it > 127) and it != '\r' and it != '\n' and it != '\t' and it != 0: + return False + return True + + def getString(self, index, count): + if index is None and count is None: + tmp = self._data[0:self.size] + if self.isAsciiString(tmp): + str_ = tmp.decode("utf-8").rstrip('\x00') + else: + str_ = self.hex(tmp) + self.position += count + return str_ + + if index + count > self.size: + raise ValueError("getString") + tmp = self._data[index:index + count] + if self.isAsciiString(tmp): + return tmp.decode("utf-8").rstrip('\x00') + return self.hex(tmp) + + def set(self, value, index=None, count=None): + # pylint: disable=protected-access + if isinstance(value, str): + value = value.encode() + if value: + if index is None: + if isinstance(value, GXByteBuffer): + index = value.position + else: + index = 0 + if count is None: + count = len(value) - index + if isinstance(value, GXByteBuffer): + self.set(value._data, index, count) + value.position = index + count + elif value and count != 0: + if self.size + count > self.capacity: + self.capacity = self.size + count + self.__ARRAY_CAPACITY + self._data[self.size:self.size + count] = value[index:index + count] + self.size += count + + def get(self, target): + len1 = len(target) + if self.size - self.position < len1: + raise ValueError("get") + index = 0 + for index in range(0, len1): + target[index] = self._data[self.position] + self.position = self.position + 1 + + # + # Compares, whether two given arrays are similar starting from current + # position. + # + # @param arr + # Array to compare. + # True, if arrays are similar. False, if the arrays differ. + # + def compare(self, arr): + len1 = len(arr) + if not arr or (self.size - self.position < len1): + return False + bytes_ = bytearray(len1) + self.get(bytes_) + ret = arr == bytes_ + if not ret: + self.position -= len1 + return ret + + # + # Reverses the order of the given array. + # + def reverse(self): + first = self.position + last = self.size - 1 + tmp = int() + while last > first: + tmp = self._data[last] + self._data[last] = self._data[first] + self._data[first] = tmp + last -= 1 + first += 1 + + # + # Push the given hex string as byte array into this buffer at the + # current + # position, and then increments the position. + # + # @param value + # Byte array to add. + # @param index + # Byte index. + # @param count + # Byte count. + # + def setHexString(self, value, index=0, count=None): + tmp = self.hexToBytes(value) + if count is None: + count = len(tmp) + self.set(tmp, index, count) + + def __str__(self): + return self.hex(self._data, True, 0, self.size) + + # + # Get remaining data. + # + # Remaining data as byte array. + # + def remaining(self): + return self.subArray(self.position, self.size - self.position) + + # + # Get remaining data as a hex string. + # + # @param addSpace + # Add space between bytes. + # Remaining data as a hex string. + # + def remainingHexString(self, addSpace=True): + return self.hex(self._data, addSpace, self.position, self.size - self.position) + + # + # Get data as hex string. + # + # @param addSpace + # Add space between bytes. + # @param index + # Byte index. + # @param count + # Byte count. + # Data as hex string. + # + def toHex(self, addSpace=True, index=0, count=None): + if count is None: + count = len(self) - index + return self.hex(self._data, addSpace, index, count) + + #Convert char hex value to byte value. + @classmethod + def ___getValue(cls, c): + #Id char. + if c.islower(): + c = c.upper() + pos = GXByteBuffer.__HEX_ARRAY.find(c) + if pos == -1: + raise Exception("Invalid hex string") + return pos + + @classmethod + def hexToBytes(cls, value): + """Convert string to byte array. + value: Hex string. + Returns byte array. + """ + buff = bytearray() + if value: + lastValue = -1 + for ch in value: + if ch != ' ': + if lastValue == -1: + lastValue = GXByteBuffer.___getValue(ch) + elif lastValue != -1: + buff.append(lastValue << GXByteBuffer.__NIBBLE | GXByteBuffer.___getValue(ch)) + lastValue = -1 + elif lastValue != -1: + buff.append(GXByteBuffer.___getValue(ch)) + lastValue = -1 + return buff + + @classmethod + def hex(cls, value, addSpace=True, index=0, count=None): + """ + Convert byte array to hex string. + """ + #Return empty string if array is empty. + if not value: + return "" + hexChars = "" + #Python 2.7 handles bytes as a string array. It's changed to bytearray. + if sys.version_info < (3, 0) and not isinstance(value, bytearray): + value = bytearray(value) + if count is None: + count = len(value) + for it in value[index:index+count]: + hexChars += GXByteBuffer.__HEX_ARRAY[it >> GXByteBuffer.__NIBBLE] + hexChars += GXByteBuffer.__HEX_ARRAY[it & GXByteBuffer.__LOW_BYTE_PART] + if addSpace: + hexChars += ' ' + return hexChars.strip() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXCiphering.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXCiphering.py new file mode 100644 index 0000000..ce1c42d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXCiphering.py @@ -0,0 +1,228 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import Security +from .GXICipher import GXICipher +from .objects.enums import SecuritySuite +from .GXDLMSChippering import GXDLMSChippering +from .AesGcmParameter import AesGcmParameter +from .GXByteBuffer import GXByteBuffer + +# pylint: disable=too-many-instance-attributes, too-many-function-args, too-many-public-methods +class GXCiphering(GXICipher): + """ + Gurux DLMS/COSEM Transport security (Ciphering) settings. + """ + def __init__(self, title): + """ + # Constructor. Default values are from the Green Book. + title: Used system title. + """ + self.publicKeys = list() + self.certificates = list() + self.security = Security.NONE + # System title. + self.systemTitle = title + # Block cipher key. + self.blockCipherKey = bytearray((0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F)) + self.authenticationKey = bytearray((0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF)) + # Dedicated key. + self.dedicatedKey = None + # Certificates. + self.certificates = None + # Ephemeral key pair. + self.ephemeralKeyPair = None + # recipient system title. + self.recipientSystemTitle = None + # Invocation Counter. + self.invocationCounter = 0 + # Used security suite. + self.securitySuite = SecuritySuite.AES_GCM_128 + # Signing key pair. + self.signingKeyPair = None + # Client key agreement key pair. + self.keyAgreementKeyPair = None + # Target (Server or client) Public key. + self.publicKeys = None + # Shared secret is generated when connection is made. + self.sharedSecret = None + + @classmethod + def decrypt(cls, c, p, data): + tmp = [] + p.sharedSecret = c.sharedSecret + tmp = GXDLMSChippering.decryptAesGcm(p, data) + c.sharedSecret = p.sharedSecret + return tmp + + # + # Cipher PDU. + # * + # @param p + # Aes GCM Parameter. + # @param data + # Plain text. + # @return Secured data. + # + @classmethod + def encrypt(cls, p, data): + if p.security != Security.NONE: + tmp = GXDLMSChippering.encryptAesGcm(p, data) + return tmp + return data + + def reset(self): + """ + Reset encrypt settings. + """ + self.security = Security.NONE + self.invocationCounter = 0 + + def isCiphered(self): + """ + Is ciphering used. + """ + return self.security != Security.NONE + + # + # Generate GMAC password from given challenge. + # * + # @param challenge + # Client to Server or Server to Client challenge. + # @return Generated challenge. + # + def generateGmacPassword(self, challenge): + p = AesGcmParameter(0x10, self.systemTitle, self.blockCipherKey, self.authenticationKey) + p.security = Security.AUTHENTICATION + p.invocationCounter = self.invocationCounter + bb = GXByteBuffer() + GXDLMSChippering.encryptAesGcm(p, challenge) + bb.setUInt8(0x10) + bb.setUInt32(self.invocationCounter) + bb.set(p.countTag) + return bb.array() + + def getSecurity(self): + """ + Used security. + """ + return self.security + + def setSecurity(self, value): + """ + Used security. + """ + self.security = value + + def getSystemTitle(self): + """ + System title. + """ + return self.systemTitle + + # + # Recipient system Title. + # + def getRecipientSystemTitle(self): + """ + Recipient system Title. + """ + return self.recipientSystemTitle + + def getBlockCipherKey(self): + # Block cipher key. + return self.blockCipherKey + + def getAuthenticationKey(self): + # Authentication key. + return self.authenticationKey + + def setAuthenticationKey(self, value): + # Authentication key. + self.authenticationKey = value + + def getInvocationCounter(self): + # Invocation counter. + return self.invocationCounter + + def getSecuritySuite(self): + # Used security suite. + return self.securitySuite + + def getEphemeralKeyPair(self): + # Ephemeral key pair. + return self.ephemeralKeyPair + + def setEphemeralKeyPair(self, value): + # Ephemeral key pair. + self.ephemeralKeyPair = value + + def getKeyAgreementKeyPair(self): + # Client's key agreement key pair. + return self.keyAgreementKeyPair + + def setKeyAgreementKeyPair(self, value): + # Client's key agreement key pair. + self.keyAgreementKeyPair = value + + def getPublicKeys(self): + # Target (Server or client) Public key. + return self.publicKeys + + def getCertificates(self): + # Available certificates. + return self.certificates + + def getSigningKeyPair(self): + # Signing key pair. + return self.signingKeyPair + + def setSigningKeyPair(self, value): + # Signing key pair. + self.signingKeyPair = value + + def getSharedSecret(self): + # Shared secret is generated when connection is made. + return self.sharedSecret + + def setSharedSecret(self, value): + # Shared secret is generated when connection is made. + self.sharedSecret = value + + def getDedicatedKey(self): + # Dedicated key. + return self.dedicatedKey + + def setDedicatedKey(self, value): + # Dedicated key. + self.dedicatedKey = value diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMS.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMS.py new file mode 100644 index 0000000..d665390 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMS.py @@ -0,0 +1,2432 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +from .GXByteBuffer import GXByteBuffer +from .internal._GXCommon import _GXCommon +from .internal._GXDataInfo import _GXDataInfo +from ._GXFCS16 import _GXFCS16 +from ._HDLCInfo import _HDLCInfo +from .enums import Conformance, InterfaceType, RequestTypes, HdlcFrameType, Command, ErrorCode, Priority, ServiceClass, ObjectType +from .enums import ExceptionServiceError, Security +from .TranslatorSimpleTags import TranslatorSimpleTags +from .GetCommandType import GetCommandType +from .GXDLMSLNParameters import GXDLMSLNParameters +from .GXDLMSSNParameters import GXDLMSSNParameters +from .GXReplyData import GXReplyData +from .ConfirmedServiceError import ConfirmedServiceError +from .SetResponseType import SetResponseType +from .HdlcControlFrame import HdlcControlFrame +from .TranslatorOutputType import TranslatorOutputType +from .TranslatorTags import TranslatorTags +from .TranslatorStandardTags import TranslatorStandardTags +from .SingleReadResponse import SingleReadResponse +from .objects.enums import SecuritySuite +from .ConnectionState import ConnectionState +from .GXCiphering import GXCiphering +from .enums.DataType import DataType +from .VariableAccessSpecification import VariableAccessSpecification +from .AesGcmParameter import AesGcmParameter +from .GXDLMSConfirmedServiceError import GXDLMSConfirmedServiceError +from .MBusEncryptionMode import MBusEncryptionMode +from .MBusCommand import MBusCommand +from .enums.Standard import Standard +from .GXDLMSExceptionResponse import GXDLMSExceptionResponse +from .ActionRequestType import ActionRequestType +from .ActionResponseType import ActionResponseType +from .SetRequestType import SetRequestType +from .plc.enums import PlcDataLinkData, PlcHdlcSourceAddress, PlcMacSubframes, PlcSourceAddress, PlcDestinationAddress +# pylint: disable=too-many-public-methods,too-many-function-args +class GXDLMS: + """ + GXDLMS implements methods to communicate with DLMS/COSEM metering devices. + """ + + @classmethod + def useHdlc(cls, tp): + return tp in (InterfaceType.HDLC, InterfaceType.HDLC_WITH_MODE_E, InterfaceType.PLC_HDLC) + + __CIPHERING_HEADER_SIZE = 7 + 12 + 3 + _data_TYPE_OFFSET = 0xFF0000 + + @classmethod + def getInvokeIDPriority(cls, settings): + value = 0 + if settings.priority == Priority.HIGH: + value |= 0x80 + if settings.serviceClass == ServiceClass.CONFIRMED: + value |= 0x40 + value |= settings.invokeId + return value + + @classmethod + def getLongInvokeIDPriority(cls, settings): + """ + Generates Invoke ID and priority. + @param settings + DLMS settings. + Invoke ID and priority. + """ + + value = 0 + if settings.priority == Priority.HIGH: + value = 0x80000000 + if settings.serviceClass == ServiceClass.CONFIRMED: + value |= 0x40000000 + value |= int((settings.getLongInvokeID() & 0xFFFFFF)) + settings.setLongInvokeID(settings.getLongInvokeID() + 1) + return value + + # + # Generates an acknowledgment message, with which the server is + # informed to + # send next packets. + # + # @param type + # Frame type. + # Acknowledgment message as byte array. + # + @classmethod + def receiverReady(cls, settings, reply): + if isinstance(reply, RequestTypes): + tmp = reply + reply = GXReplyData() + reply.moreData = tmp + if reply.moreData == RequestTypes.NONE: + raise ValueError("Invalid receiverReady RequestTypes parameter.") + # Get next frame. + if (reply.moreData & RequestTypes.FRAME) != 0: + id_ = settings.getReceiverReady() + return GXDLMS.getHdlcFrame(settings, id_, None) + cmd = settings.command + if reply.moreData == RequestTypes.GBT: + p = GXDLMSLNParameters(settings, 0, Command.GENERAL_BLOCK_TRANSFER, 0, None, None, 0xff) + p.gbtWindowSize = reply.gbtWindowSize + p.blockNumberAck = reply.blockNumber + p.blockIndex = settings.blockNumber + reply = GXDLMS.getLnMessages(p) + else: + # Get next block. + bb = GXByteBuffer(4) + if settings.getUseLogicalNameReferencing(): + bb.setUInt32(settings.blockIndex) + else: + bb.setUInt16(settings.blockIndex) + settings.increaseBlockIndex() + if settings.getUseLogicalNameReferencing(): + p = GXDLMSLNParameters(settings, 0, cmd, GetCommandType.NEXT_DATA_BLOCK, bb, None, 0xff) + reply = GXDLMS.getLnMessages(p) + else: + p = GXDLMSSNParameters(settings, cmd, 1, VariableAccessSpecification.BLOCK_NUMBER_ACCESS, bb, None) + reply = GXDLMS.getSnMessages(p) + return reply[0] + + # + # Reserved for internal use. + # + @classmethod + def checkInit(cls, settings): + if settings.clientAddress == 0: + raise ValueError("Invalid Client Address.") + if settings.serverAddress == 0: + raise ValueError("Invalid Server Address.") + + @classmethod + #pylint: disable=too-many-arguments + def appendData(cls, settings, obj, index, bb, value): + tp = obj.getDataType(index) + if tp == DataType.ARRAY: + if isinstance(value, bytes): + if tp != DataType.OCTET_STRING: + bb.set(int(value)) + return + else: + if tp == DataType.NONE and value: + raise Exception("Invalid parameter. In python value type must give.") + if isinstance(value, str) and tp == DataType.OCTET_STRING: + ui = obj.getUIDataType(index) + if ui == DataType.STRING: + _GXCommon.setData(settings, bb, tp, value.encode()) + return + _GXCommon.setData(settings, bb, tp, value) + + # + # Get used glo message. + # + # @param command + # Executed command. + # Integer value of glo message. + # + @classmethod + def getGloMessage(cls, command): + cmd = int() + if command == Command.READ_REQUEST: + cmd = Command.GLO_READ_REQUEST + elif command == Command.GET_REQUEST: + cmd = Command.GLO_GET_REQUEST + elif command == Command.WRITE_REQUEST: + cmd = Command.GLO_WRITE_REQUEST + elif command == Command.SET_REQUEST: + cmd = Command.GLO_SET_REQUEST + elif command == Command.METHOD_REQUEST: + cmd = Command.GLO_METHOD_REQUEST + elif command == Command.READ_RESPONSE: + cmd = Command.GLO_READ_RESPONSE + elif command == Command.GET_RESPONSE: + cmd = Command.GLO_GET_RESPONSE + elif command == Command.WRITE_RESPONSE: + cmd = Command.GLO_WRITE_RESPONSE + elif command == Command.SET_RESPONSE: + cmd = Command.GLO_SET_RESPONSE + elif command == Command.METHOD_RESPONSE: + cmd = Command.GLO_METHOD_RESPONSE + elif command == Command.DATA_NOTIFICATION: + cmd = Command.GENERAL_GLO_CIPHERING + elif command == Command.RELEASE_REQUEST: + cmd = Command.RELEASE_REQUEST + elif command == Command.RELEASE_RESPONSE: + cmd = Command.RELEASE_RESPONSE + else: + raise Exception("Invalid GLO command.") + return cmd + + # + # Get used ded message. + # + # @param cmd + # Executed command. + # Integer value of ded message. + # + @classmethod + def getDedMessage(cls, command): + cmd = int() + if command == Command.GET_REQUEST: + cmd = Command.DED_GET_REQUEST + elif command == Command.SET_REQUEST: + cmd = Command.DED_SET_REQUEST + elif command == Command.METHOD_REQUEST: + cmd = Command.DED_METHOD_REQUEST + elif command == Command.GET_RESPONSE: + cmd = Command.DED_GET_RESPONSE + elif command == Command.SET_RESPONSE: + cmd = Command.DED_SET_RESPONSE + elif command == Command.METHOD_RESPONSE: + cmd = Command.DED_METHOD_RESPONSE + elif command == Command.DATA_NOTIFICATION: + cmd = Command.GENERAL_DED_CIPHERING + elif command == Command.RELEASE_REQUEST: + cmd = Command.RELEASE_REQUEST + elif command == Command.RELEASE_RESPONSE: + cmd = Command.RELEASE_RESPONSE + else: + raise Exception("Invalid DED command.") + return cmd + + # + # Add LLC bytes to generated message. + # + # @param settings + # DLMS settings. + # @param data + # Data where bytes are added. + # + @classmethod + def addLLCBytes(cls, settings, data): + tmp = data.array() + data.clear() + if settings.isServer: + data.set(_GXCommon.LLC_REPLY_BYTES) + else: + data.set(_GXCommon.LLC_SEND_BYTES) + data.set(tmp) + + @classmethod + def multipleBlocks(cls, p, reply, ciphering): + """ + Check is all_ data fit to one data block. + + @param p + LN parameters. + @param reply + Generated reply. + """ + + # Check is all_ data fit to one message if data is given. + len_ = 0 + if p.data: + len_ = len(p.data) - p.data.position + if p.attributeDescriptor: + len_ += p.attributeDescriptor.size + if ciphering: + len_ += GXDLMS.__CIPHERING_HEADER_SIZE + if not p.multipleBlocks: + # Add command type and invoke and priority. + p.multipleBlocks = 2 + len(reply) + len_ > p.settings.maxPduSize + if p.multipleBlocks: + # Add command type and invoke and priority. + p.lastBlock = not 8 + len(reply) + len_ > p.settings.maxPduSize + if p.lastBlock: + # Add command type and invoke and priority. + p.lastBlock = not 8 + len(reply) + len_ > p.settings.maxPduSize + + # + # Get next logical name PDU. + # + # @param p + # LN parameters. + # @param reply + # Generated message. + # + @classmethod + def getLNPdu(cls, p, reply): + # pylint: disable=too-many-nested-blocks + ciphering = p.command != Command.AARQ and p.command != Command.AARE and p.settings.cipher and p.settings.cipher.security != Security.NONE + len_ = 0 + if p.command == Command.AARQ: + if p.settings.gateway and p.settings.gateway.physicalDeviceAddress: + reply.setUInt8(Command.GATEWAY_REQUEST) + reply.setUInt8(p.settings.gateway.networkId) + reply.setUInt8(len(p.settings.gateway.physicalDeviceAddress)) + reply.set(p.settings.gateway.physicalDeviceAddress) + reply.set(p.attributeDescriptor) + else: + if p.command != Command.GENERAL_BLOCK_TRANSFER: + reply.setUInt8(p.command) + if p.command == Command.EVENT_NOTIFICATION or p.command == Command.DATA_NOTIFICATION or p.command == Command.ACCESS_REQUEST or p.command == Command.ACCESS_RESPONSE: + if p.command != Command.EVENT_NOTIFICATION: + if p.invokeId != 0: + reply.setUInt32(p.invokeId) + else: + reply.setUInt32(GXDLMS.getLongInvokeIDPriority(p.settings)) + if p.time is None: + reply.setUInt8(DataType.NONE) + else: + pos = len(reply) + _GXCommon.setData(p.settings, reply, DataType.OCTET_STRING, p.getTime()) + if p.command != Command.EVENT_NOTIFICATION: + reply.move(pos + 1, pos, len(reply) - pos - 1) + GXDLMS.multipleBlocks(p, reply, ciphering) + elif p.command != Command.RELEASE_REQUEST and p.command != Command.EXCEPTION_RESPONSE: + if p.command != Command.GET_REQUEST and p.data and reply: + GXDLMS.multipleBlocks(p, reply, ciphering) + if p.command == Command.SET_REQUEST: + if p.multipleBlocks and (p.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER) == 0: + if p.requestType == SetRequestType.NORMAL: + p.requestType = SetRequestType.FIRST_DATA_BLOCK + elif p.requestType == SetRequestType.FIRST_DATA_BLOCK: + p.requestType = SetRequestType.WITH_DATA_BLOCK + #Change Request type if action request and multiple + #blocks is needed. + elif p.command == Command.METHOD_REQUEST: + if p.multipleBlocks and (p.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER) == 0: + if p.requestType == ActionRequestType.NORMAL: + #Remove Method Invocation Parameters tag. + p.attributeDescriptor.size = p.attributeDescriptor.size - 1 + p.requestType = ActionRequestType.WITH_FIRST_BLOCK + elif p.requestType == ActionRequestType.WITH_FIRST_BLOCK: + p.requestType = ActionRequestType.WITH_BLOCK + #Change Request type if action request and multiple blocks is + #needed. + elif p.command == Command.METHOD_RESPONSE: + if p.multipleBlocks and (p.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER) == 0: + # There is no status fiel in action resonse. + p.status = 0xFF + if p.RequestType == ActionResponseType.NORMAL: + # Remove Method Invocation Parameters tag. + p.data.position = p.data.position + 2 + p.requestType = ActionResponseType.WITH_BLOCK + elif p.requestType == ActionResponseType.WITH_BLOCK and p.data.available == 0: + # If server asks next part of PDU. + p.requestType = ActionResponseType.NEXT_BLOCK + elif p.command == Command.GET_RESPONSE: + if p.multipleBlocks and (p.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER) == 0: + if p.requestType == 1: + p.requestType = 2 + if p.command != Command.GENERAL_BLOCK_TRANSFER: + reply.setUInt8(p.requestType) + if p.invokeId != 0: + reply.setUInt8(p.invokeId) + else: + reply.setUInt8(GXDLMS.getInvokeIDPriority(p.settings)) + reply.set(p.attributeDescriptor) + if p.multipleBlocks and (p.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER) == 0: + if p.command != Command.SET_RESPONSE: + if p.lastBlock: + reply.setUInt8(1) + p.settings.setCount(0) + p.settings.setIndex(0) + else: + reply.setUInt8(0) + reply.setUInt32(p.blockIndex) + p.blockIndex = p.blockIndex + 1 + if p.status != 0xFF: + if p.status != 0 and p.command == Command.GET_RESPONSE: + reply.setUInt8(1) + reply.setUInt8(p.status) + if p.data: + len_ = p.data.size - p.data.position + else: + len_ = 0 + totalLength = len_ + len(reply) + if ciphering: + totalLength += GXDLMS.__CIPHERING_HEADER_SIZE + if totalLength > p.settings.maxPduSize: + len_ = p.settings.maxPduSize - len(reply) + if ciphering: + len_ -= GXDLMS.__CIPHERING_HEADER_SIZE + len_ -= _GXCommon.getObjectCountSizeInBytes(len_) + _GXCommon.setObjectCount(len_, reply) + reply.set(p.data, p.data.position, len_) + if len_ == 0: + if p.status != 0xFF and p.command != Command.GENERAL_BLOCK_TRANSFER: + if p.status != 0 and p.command == Command.GET_RESPONSE: + reply.setUInt8(1) + reply.setUInt8(p.status) + if p.data: + len_ = p.data.size - p.data.position + if p.settings.gateway and p.settings.gateway.physicalDeviceAddress: + if 3 + len_ + len(p.settings.gateway.physicalDeviceAddress) > p.settings.maxPduSize: + len_ -= (3 + len(p.settings.gateway.physicalDeviceAddress)) + tmp = GXByteBuffer(reply) + reply.size = 0 + reply.setUInt8(Command.GATEWAY_REQUEST) + reply.setUInt8(p.settings.gateway.networkId) + reply.setUInt8(len(p.settings.gateway.physicalDeviceAddress)) + reply.set(p.settings.gateway.physicalDeviceAddress) + reply.set(tmp) + if p.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER != Conformance.NONE: + if 7 + len_ + len(reply) > p.settings.maxPduSize: + len_ = p.settings.maxPduSize - len(reply) - 7 + if ciphering and p.command != Command.GENERAL_BLOCK_TRANSFER: + reply.set(p.data) + tmp = [] + if p.settings.cipher.securitySuite == SecuritySuite.AES_GCM_128: + tmp = GXDLMS.cipher0(p, reply) + p.data.size = 0 + p.data.set(tmp) + reply.size = 0 + len_ = p.data.size + if 7 + len_ > p.settings.maxPduSize: + len_ = p.settings.maxPduSize - 7 + ciphering = False + elif p.command != Command.GET_REQUEST and len_ + len(reply) > p.settings.maxPduSize: + len_ = p.settings.maxPduSize - len(reply) + reply.set(p.data, p.data.position, len_) + elif (p.settings.gateway and p.settings.gateway.physicalDeviceAddress) and not (p.command == Command.GENERAL_BLOCK_TRANSFER or (p.multipleBlocks and (p.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER != Conformance.NONE))): + if 3 + len_ + len(p.settings.gateway.physicalDeviceAddress) > p.settings.maxPduSize: + len_ -= (3 + len(p.settings.gateway.physicalDeviceAddress)) + tmp = GXByteBuffer(reply) + reply.size = 0 + reply.setUInt8(Command.GATEWAY_REQUEST) + reply.setUInt8(p.settings.gateway.networkId) + reply.setUInt8(len(p.settings.gateway.physicalDeviceAddress)) + reply.set(p.settings.gateway.physicalDeviceAddress) + reply.set(tmp) + if ciphering and reply and p.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER == Conformance.NONE and p.command != Command.RELEASE_REQUEST: + tmp = [] + if p.settings.cipher.securitySuite == SecuritySuite.AES_GCM_128: + tmp = GXDLMS.cipher0(p, reply.array()) + reply.size = 0 + reply.set(tmp) + if p.command == Command.GENERAL_BLOCK_TRANSFER or (p.multipleBlocks and p.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER != Conformance.NONE): + bb = GXByteBuffer() + bb.set(reply) + reply.clear() + reply.setUInt8(Command.GENERAL_BLOCK_TRANSFER) + value = 0 + if p.lastBlock: + value = 0x80 + elif p.streaming: + value |= 0x40 + value |= p.gbtWindowSize + reply.setUInt8(value) + reply.setUInt16(p.blockIndex) + p.blockIndex += 1 + if p.command != Command.DATA_NOTIFICATION and p.blockNumberAck != 0: + reply.setUInt16(p.blockNumberAck) + p.blockNumberAck += 1 + else: + p.blockNumberAck = -1 + reply.setUInt16(0) + _GXCommon.setObjectCount(len(bb), reply) + reply.set(bb) + if p.command != Command.GENERAL_BLOCK_TRANSFER: + p.command = Command.GENERAL_BLOCK_TRANSFER + p.blockNumberAck += 1 + if p.settings.gateway and p.settings.gateway.physicalDeviceAddress: + if 3 + len_ + len(p.settings.gateway.physicalDeviceAddress) > p.settings.maxPduSize: + len_ -= (3 + len(p.settings.gateway.physicalDeviceAddress)) + tmp = GXByteBuffer(reply) + reply.size = 0 + reply.setUInt8(Command.GATEWAY_REQUEST) + reply.setUInt8(p.settings.gateway.networkId) + reply.setUInt8(len(p.settings.gateway.physicalDeviceAddress)) + reply.set(p.settings.gateway.physicalDeviceAddress) + reply.set(tmp) + if GXDLMS.useHdlc(p.settings.interfaceType): + GXDLMS.addLLCBytes(p.settings, reply) + + @classmethod + def cipher0(cls, p, data): + cmd = 0 + key = None + cipher = p.settings.cipher + if ((p.settings.connected & ConnectionState.DLMS) == 0 or (p.settings.negotiatedConformance & Conformance.GENERAL_PROTECTION) == 0) and \ + (not p.settings.preEstablishedSystemTitle or (p.settings.proposedConformance & Conformance.GENERAL_PROTECTION) == Conformance.NONE): + if cipher.dedicatedKey and (p.settings.connected & ConnectionState.DLMS) != 0: + cmd = GXDLMS.getDedMessage(p.command) + key = cipher.dedicatedKey + else: + cmd = GXDLMS.getGloMessage(p.command) + key = cipher.blockCipherKey + else: + if cipher.dedicatedKey: + cmd = Command.GENERAL_DED_CIPHERING + key = cipher.dedicatedKey + else: + cmd = Command.GENERAL_GLO_CIPHERING + key = cipher.blockCipherKey + s = AesGcmParameter(cmd, cipher.systemTitle, key, cipher.authenticationKey) + s.ignoreSystemTitle = p.settings.standard == Standard.ITALY + s.security = cipher.security + s.invocationCounter = cipher.invocationCounter + tmp = GXCiphering.encrypt(s, data) + cipher.invocationCounter = cipher.invocationCounter + 1 + return tmp + + @classmethod + def getLnMessages(cls, p): + reply = GXByteBuffer() + messages = list() + frame_ = 0 + if p.command == Command.DATA_NOTIFICATION or p.command == Command.EVENT_NOTIFICATION: + frame_ = 0x13 + while True: + GXDLMS.getLNPdu(p, reply) + p.lastBlock = True + if p.attributeDescriptor is None: + p.settings.increaseBlockIndex() + if p.command == Command.AARQ and p.command == Command.GET_REQUEST: + assert not p.settings.maxPduSize < len(reply) + while reply.position != len(reply): + if p.settings.interfaceType == InterfaceType.WRAPPER: + messages.append(GXDLMS.getWrapperFrame(p.settings, p.command, reply)) + elif p.settings.interfaceType == InterfaceType.HDLC or p.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + messages.append(GXDLMS.getHdlcFrame(p.settings, frame_, reply)) + if reply.position != len(reply): + frame_ = p.settings.getNextSend(False) + elif p.settings.interfaceType == InterfaceType.PDU: + messages.append(reply.array()) + break + else: + raise ValueError("InterfaceType") + reply.clear() + frame_ = 0 + if not p.data or p.data.position == p.data.size: + break + return messages + + @classmethod + def getSnMessages(cls, p): + reply = GXByteBuffer() + messages = list() + frame_ = 0x0 + if p.command == Command.INFORMATION_REPORT or p.command == Command.DATA_NOTIFICATION: + frame_ = 0x13 + while True: + GXDLMS.getSNPdu(p, reply) + if p.command != Command.AARQ and p.command != Command.AARE: + assert not p.settings.maxPduSize < len(reply) + while reply.position != len(reply): + if p.settings.interfaceType == InterfaceType.WRAPPER: + messages.append(GXDLMS.getWrapperFrame(p.settings, p.command, reply)) + elif p.settings.interfaceType == InterfaceType.HDLC or p.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + messages.append(GXDLMS.getHdlcFrame(p.settings, frame_, reply)) + if reply.position != len(reply): + frame_ = p.settings.getNextSend(False) + elif p.settings.interfaceType == InterfaceType.PDU: + messages.append(reply.array()) + break + else: + raise ValueError("InterfaceType") + reply.clear() + frame_ = 0 + if not p.data or p.data.position == p.data.size: + break + return messages + + @classmethod + def appendMultipleSNBlocks(cls, p, reply): + ciphering = p.settings.cipher and p.settings.cipher.security != Security.NONE + hSize = len(reply) + 3 + if p.command == Command.WRITE_REQUEST or p.command == Command.READ_REQUEST: + hSize += 1 + _GXCommon.getObjectCountSizeInBytes(p.count) + maxSize = p.settings.maxPduSize - hSize + if ciphering: + maxSize -= GXDLMS.__CIPHERING_HEADER_SIZE + if p.settings.interfaceType == InterfaceType.HDLC or p.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + maxSize -= 3 + maxSize -= _GXCommon.getObjectCountSizeInBytes(maxSize) + if reply.available() > maxSize: + reply.setUInt8(0) + else: + reply.setUInt8(1) + maxSize = reply.available() + reply.setUInt16(p.blockIndex) + if p.command == Command.WRITE_REQUEST: + p.blockIndex = p.blockIndex + 1 + _GXCommon.setObjectCount(p.count, reply) + reply.setUInt8(DataType.OCTET_STRING) + elif p.command == Command.READ_REQUEST: + p.blockIndex = p.blockIndex + 1 + _GXCommon.setObjectCount(maxSize, reply) + return maxSize + + @classmethod + def getSNPdu(cls, p, reply): + ciphering = p.settings.cipher and p.settings.cipher.security != Security.NONE and p.command != Command.AARQ and p.command != Command.AARE + if not ciphering and (p.settings.interfaceType == InterfaceType.HDLC or p.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E): + if p.settings.isServer: + reply.set(_GXCommon.LLC_REPLY_BYTES) + elif not reply: + reply.set(_GXCommon.LLC_SEND_BYTES) + cnt = 0 + cipherSize = 0 + if ciphering: + cipherSize = GXDLMS.__CIPHERING_HEADER_SIZE + if p.data: + cnt = p.data.size - p.data.position + if p.command == Command.INFORMATION_REPORT: + reply.setUInt8(p.command) + if not p.time: + reply.setUInt8(DataType.NONE) + else: + pos = len(reply) + _GXCommon.setData(p.settings, reply, DataType.OCTET_STRING, p.time) + reply.move(pos + 1, pos, len(reply) - pos - 1) + _GXCommon.setObjectCount(p.count, reply) + reply.set(p.attributeDescriptor) + elif p.command != Command.AARQ and p.command != Command.AARE: + reply.setUInt8(p.command) + if p.count != 0xFF: + _GXCommon.setObjectCount(p.count, reply) + if p.requestType != 0xFF: + reply.setUInt8(p.requestType) + reply.set(p.attributeDescriptor) + if not p.multipleBlocks: + p.multipleBlocks = len(reply) + cipherSize + cnt > p.settings.maxPduSize + if p.multipleBlocks: + reply.size = 0 + if not ciphering and (p.settings.interfaceType == InterfaceType.HDLC or p.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E): + if p.settings.isServer: + reply.set(_GXCommon.LLC_REPLY_BYTES) + elif not reply: + reply.set(_GXCommon.LLC_SEND_BYTES) + if p.command == Command.WRITE_REQUEST: + p.requestType = VariableAccessSpecification.WRITE_DATA_BLOCK_ACCESS + elif p.command == Command.READ_REQUEST: + p.requestType = VariableAccessSpecification.READ_DATA_BLOCK_ACCESS + elif p.command == Command.READ_RESPONSE: + p.requestType = SingleReadResponse.DATA_BLOCK_RESULT + else: + raise ValueError("Invalid command.") + reply.setUInt8(p.command) + reply.setUInt8(1) + if p.requestType != 0xFF: + reply.setUInt8(p.requestType) + cnt = GXDLMS.appendMultipleSNBlocks(p, reply) + else: + cnt = GXDLMS.appendMultipleSNBlocks(p, reply) + if p.data: + reply.set(p.data, p.data.position, cnt) + if p.data and p.data.position == p.data.size: + p.settings.index = 0 + p.settings.count = 0 + if ciphering and p.command != Command.AARQ and p.command != Command.AARE: + cipher = p.settings.cipher + s = AesGcmParameter(GXDLMS.getGloMessage(p.command), cipher.systemTitle, cipher.blockCipherKey, cipher.authenticationKey) + s.security = cipher.security + s.invocationCounter = cipher.invocationCounter + tmp = GXCiphering.encrypt(s, reply.array()) + assert not tmp + reply.size = 0 + if p.settings.interfaceType == InterfaceType.HDLC or p.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + if p.settings.isServer: + reply.set(_GXCommon.LLC_REPLY_BYTES) + elif not reply: + reply.set(_GXCommon.LLC_SEND_BYTES) + reply.set(tmp) + + @classmethod + def getAddress(cls, value, size): + if size < 2 and value < 0x80: + return int((value << 1 | 1)) + if size < 4 and value < 0x4000: + return int(((value & 0x3F80) << 2 | (value & 0x7F) << 1 | 1)) + if value < 0x10000000: + return int(((value & 0xFE00000) << 4 | (value & 0x1FC000) << 3 | (value & 0x3F80) << 2 | (value & 0x7F) << 1 | 1)) + raise ValueError("Invalid address.") + + @classmethod + def getAddressBytes(cls, value, size): + tmp = GXDLMS.getAddress(value, size) + bb = GXByteBuffer() + if size == 1 or tmp < 0x100: + bb.setUInt8(tmp) + elif size == 2 or tmp < 0x10000: + bb.setUInt16(tmp) + elif size == 4 or tmp < 0x100000000: + bb.setUInt32(tmp) + else: + raise ValueError("Invalid address type.") + return bb.array() + + @classmethod + def getWrapperFrame(cls, settings, command, data): + bb = GXByteBuffer() + bb.setUInt16(1) + if settings.isServer: + bb.setUInt16(settings.serverAddress) + if settings.pushClientAddress != 0 and command in (Command.DATA_NOTIFICATION, Command.EVENT_NOTIFICATION): + bb.setUInt16(settings.pushClientAddress) + else: + bb.setUInt16(settings.clientAddress) + else: + bb.setUInt16(settings.clientAddress) + bb.setUInt16(settings.serverAddress) + if data is None: + bb.setUInt16(0) + else: + bb.setUInt16(len(data)) + bb.set(data) + if settings.isServer: + if len(data) == data.position: + data.clear() + else: + data.move(data.position, 0, len(data) - data.position) + data.position = 0 + return bb.array() + + @classmethod + def getHdlcFrame(cls, settings, frame_, data): + # pylint: disable=protected-access + bb = GXByteBuffer() + frameSize = 0 + len1 = 0 + primaryAddress = None + secondaryAddress = None + if settings.isServer: + if frame_ == 0x13 and settings.pushClientAddress != 0: + primaryAddress = GXDLMS.getAddressBytes(settings.pushClientAddress, 1) + else: + primaryAddress = GXDLMS.getAddressBytes(settings.clientAddress, 1) + secondaryAddress = GXDLMS.getAddressBytes(settings.serverAddress, settings.serverAddressSize) + else: + primaryAddress = GXDLMS.getAddressBytes(settings.serverAddress, settings.serverAddressSize) + secondaryAddress = GXDLMS.getAddressBytes(settings.clientAddress, 1) + bb.setUInt8(_GXCommon.HDLC_FRAME_START_END) + frameSize = settings.hdlc.maxInfoTX + if data and data.position == 0: + frameSize -= 3 + if not data: + len1 = 0 + bb.setUInt8(0xA0) + elif len(data) - data.position <= frameSize: + len1 = len(data) - data.position + bb.setUInt8(0xA0 | (((len(secondaryAddress) + len(primaryAddress) + len1) >> 8) & 0x7)) + else: + len1 = frameSize + bb.setUInt8(0xA8 | (((len(secondaryAddress) + len(primaryAddress) + len1) >> 8) & 0x7)) + if len1 == 0: + bb.setUInt8(5 + len(secondaryAddress) + len(primaryAddress) + len1) + else: + bb.setUInt8(7 + len(secondaryAddress) + len(primaryAddress) + len1) + bb.set(primaryAddress) + bb.set(secondaryAddress) + if frame_ == 0: + bb.setUInt8(settings.getNextSend(True)) + else: + bb.setUInt8(frame_) + crc = _GXFCS16.countFCS16(bb._data, 1, bb.size - 1) + bb.setUInt16(crc) + if len1 != 0: + bb.set(data, data.position, len1) + crc = _GXFCS16.countFCS16(bb._data, 1, len(bb) - 1) + bb.setUInt16(crc) + bb.setUInt8(_GXCommon.HDLC_FRAME_START_END) + if settings.isServer: + if data: + if len(data) == data.position: + data.clear() + else: + data.move(data.position, 0, len(data) - data.position) + data.position = 0 + return bb.array() + + @classmethod + def getLLCBytes(cls, server, data): + if server: + return data.compare(_GXCommon.LLC_SEND_BYTES) + return data.compare(_GXCommon.LLC_REPLY_BYTES) + + @classmethod + def getHdlcData(cls, server, settings, reply, data, notify): + # pylint:disable=too-many-arguments,too-many-locals,too-many-return-statements, + # protected-access,broad-except + ch = 0 + pos = reply.position + packetStartID = reply.position + frameLen = 0 + crc = 0 + crcRead = 0 + if reply.size - reply.position < 9: + data.complete = False + if notify: + notify.complete = False + return 0 + data.complete = True + if notify: + notify.complete = True + isNotify = False + while pos < len(reply): + ch = reply.getUInt8() + if ch == _GXCommon.HDLC_FRAME_START_END: + packetStartID = pos + break + pos += 1 + if reply.position == len(reply): + data.complete = False + if notify: + notify.complete = False + return 0 + frame_ = reply.getUInt8() + if (frame_ & 0xF0) != 0xA0: + reply.position = reply.position - 1 + return GXDLMS.getHdlcData(server, settings, reply, data, notify) + if (frame_ & 0x7) != 0: + frameLen = ((frame_ & 0x7) << 8) + ch = reply.getUInt8() + frameLen += ch + if len(reply) - reply.position + 1 < frameLen: + data.complete = False + if notify: + notify.complete = False + reply.position = packetStartID + return 0 + eopPos = frameLen + packetStartID + 1 + ch = reply.getUInt8(eopPos) + if ch != _GXCommon.HDLC_FRAME_START_END: + reply.position = reply.position - 2 + return GXDLMS.getHdlcData(server, settings, reply, data, notify) + addresses = [0, 0] + try: + #pylint: disable=broad-except + ret = GXDLMS.checkHdlcAddress(server, settings, reply, eopPos, addresses) + except Exception: + ret = False + if not ret: + #If not notify. + if not (reply.position < len(reply) and (reply.getUInt8(reply.position) == 0x13 or reply.getUInt8(reply.position) == 0x3)): + reply.position = 1 + eopPos + return GXDLMS.getHdlcData(server, settings, reply, data, notify) + if notify: + isNotify = True + notify.targetAddress = addresses[1] + notify.sourceAddress = addresses[0] + # HDLC control fields + cf = reply.getUInt8() + if data.xml is None and not settings.checkFrame(cf, data.xml): + reply.position = eopPos + 1 + return GXDLMS.getHdlcData(server, settings, reply, data, notify) + if not isNotify and notify and cf in (0x13, 0x3): + isNotify = True + notify.clientAddress = addresses[1] + notify.serverAddress = addresses[0] + if (frame_ & 0x8) != 0: + if isNotify: + notify.moreData = notify.moreData | RequestTypes.FRAME + else: + data.moreData = data.moreData | RequestTypes.FRAME + else: + if isNotify: + notify.moreData = notify.moreData & ~RequestTypes.FRAME + else: + data.moreData = data.moreData & ~RequestTypes.FRAME + crc = _GXFCS16.countFCS16(reply, packetStartID + 1, reply.position - packetStartID - 1) + crcRead = reply.getUInt16() + if crc != crcRead: + if len(reply) - reply.position > 8: + return GXDLMS.getHdlcData(server, settings, reply, data, notify) + raise Exception("Wrong CRC.") + if reply.position != packetStartID + frameLen + 1: + crc = _GXFCS16.countFCS16(reply, packetStartID + 1, frameLen - 2) + crcRead = reply.getUInt16(packetStartID + frameLen - 1) + if crc != crcRead: + raise Exception("Wrong CRC.") + if isNotify: + notify.packetLength = (eopPos - 2) + else: + data.packetLength = (eopPos - 2) + else: + if isNotify: + notify.packetLength = (reply.position + 1) + else: + data.packetLength = (reply.position + 1) + if cf != 0x13 and cf != 0x3 and (cf & HdlcFrameType.U_FRAME) == HdlcFrameType.U_FRAME: + if reply.position == packetStartID + frameLen + 1: + reply.getUInt8() + if cf == 0x97: + data.error = ErrorCode.UNACCEPTABLE_FRAME + elif cf == 0x1f: + data.error = ErrorCode.DISCONNECT_MODE + elif cf == 0x17: + data.error = ErrorCode.DISCONNECT_MODE + data.command = cf + elif cf != 0x13 and cf != 0x3 and (cf & HdlcFrameType.S_FRAME) == HdlcFrameType.S_FRAME: + tmp = (cf >> 2) & 0x3 + if tmp == HdlcControlFrame.REJECT: + data.error = ErrorCode.REJECTED + elif tmp == HdlcControlFrame.RECEIVE_NOT_READY: + data.error = ErrorCode.RECEIVE_NOT_READY + elif tmp == HdlcControlFrame.RECEIVE_READY: + data.error = ErrorCode.OK + if reply.position == packetStartID + frameLen + 1: + reply.getUInt8() + else: + if reply.position == packetStartID + frameLen + 1: + reply.getUInt8() + if (cf & 0x1) == 0x1: + data.moreData = (RequestTypes.FRAME) + else: + if not GXDLMS.getLLCBytes(server, reply) and data.xml: + GXDLMS.getLLCBytes(not server, reply) + return cf + + @classmethod + def getServerAddress(cls, address, logical, physical): + if address < 0x4000: + logical[0] = address >> 7 + physical[0] = address & 0x7F + else: + logical[0] = address >> 14 + physical[0] = address & 0x3FFF + + @classmethod + def checkHdlcAddress(cls, server, settings, reply, index, addresses): + # pylint: disable=too-many-arguments + target = _GXCommon.getHDLCAddress(reply) + source = _GXCommon.getHDLCAddress(reply) + addresses[0] = source + addresses[1] = target + if server: + if settings.serverAddress != 0 and settings.serverAddress != target: + if reply.getUInt8(reply.position) == Command.SNRM: + settings.serverAddress = target + else: + raise Exception("Server addresses do not match. It is " + str(target) + ". It should be " + str(settings.serverAddress) + ".") + else: + settings.serverAddress = target + if settings.clientAddress != 0 and settings.clientAddress != source: + if reply.getUInt8(reply.position) == Command.SNRM: + settings.clientAddress = source + else: + raise Exception("Client addresses do not match. It is " + str(source) + ". It should be " + str(settings.clientAddress) + ".") + else: + settings.clientAddress = source + else: + if settings.clientAddress != target: + if settings.clientAddress == source and settings.serverAddress == target: + reply.position = index + 1 + return False + #If All-station (Broadcast). + if settings.serverAddress != source and (settings.serverAddress and 0x7F) != 0x7F and (settings.serverAddress and 0x3FFF) != 0x3FFF: + readLogical = [0] + readPhysical = [0] + logical = [0] + physical = [0] + GXDLMS.getServerAddress(source, readLogical, readPhysical) + GXDLMS.getServerAddress(settings.serverAddress, logical, physical) + if readLogical[0] != logical[0] or readPhysical[0] != physical[0]: + return False + return True + + @classmethod + def __getTcpData(cls, settings, buff, data, notify): + target = data + if len(buff) - buff.position < 8: + target.complete = (False) + return True + isData = True + pos = buff.position + value = int() + while buff.position < len(buff) - 1: + value = buff.getUInt16() + # pylint: disable=no-else-break + if value == 1: + if not GXDLMS.checkWrapperAddress(settings, buff, data, notify): + target = notify + isData = False + value = buff.getUInt16() + compleate = not (len(buff) - buff.position) < value + if compleate and (len(buff) - buff.position) != value: + print("Data length is " + str(value) + "and there are " + str(len(buff) - buff.position) + " bytes.") + target.complete = (compleate) + if not compleate: + buff.position = pos + else: + target.packetLength = (buff.position + value) + break + else: + buff.position = buff.position - 1 + return isData + + # Validate M-Bus checksum + @classmethod + def __validateCheckSum(cls, bb, count): + value = 0 + pos = 0 + while pos < count: + value += bb.getUInt8(bb.position + pos) + pos = pos + 1 + return value & 0xFF == bb.getUInt8(bb.position + count) + + # Get data from wired M-Bus frame. + @classmethod + def __getWiredMBusData(cls, settings, buff, data): + packetStartID = buff.position + if buff.getUInt8() != 0x68 or buff.available() < 5: + data.complete = False + buff.position = buff.position - 1 + else: + #L-field. + len_ = buff.getUInt8() + #L-field. + if buff.getUInt8() != len_ or buff.available() < 3 + len_ or buff.getUInt8() != 0x68: + data.complete = False + buff.position = packetStartID + else: + crc = GXDLMS.__validateCheckSum(buff, len_) + if not crc and not data.xml: + data.complete = False + buff.position = packetStartID + else: + if not crc: + data.xml.appendComment("Invalid checksum.") + #Check EOP. + if buff.getUInt8(buff.position + len_ + 1) != 0x16: + data.complete = False + buff.position = packetStartID + return + data.packetLength = buff.position + len_ + data.complete = True + #index = data.data.position + #Control field (C-Field) + tmp = buff.getUInt8() + cmd_ = MBusCommand(tmp & 0xF) + #Address (A-field) + id_ = buff.getUInt8() + #The Control Information Field (CI-field) + ci = buff.getUInt8() + if ci == 0x0: + data.moreData = data.moreData or RequestTypes.FRAME + elif (ci >> 4) == (ci & 0xf): + #If this is the last telegram. + data.moreData &= ~RequestTypes.FRAME + #If M-Bus data header is present + if (tmp and 0x40) != 0: + #Message from primary(initiating) station + #Destination Transport Service Access Point + settings.clientAddress = buff.getUInt8() + #Source Transport Service Access Point + settings.serverAddress = buff.getUInt8() + else: + #Message from secondary (responding) station. + #Source Transport Service Access Point + settings.serverAddress = buff.getUInt8() + #Destination Transport Service Access Point + settings.clientAddress = buff.getUInt8() + if data.xml and data.xml.comments: + data.xml.appendComment("Command: " + str(cmd_)) + data.xml.appendComment("A-Field: " + str(id_)) + data.xml.appendComment("CI-Field: " + str(ci)) + if (tmp & 0x40) != 0: + data.xml.appendComment("Primary station: " + str(settings.serverAddress)) + data.xml.appendComment("Secondary station: " + str(settings.clientAddress)) + else: + data.xml.appendComment("Primary station: " + str(settings.clientAddress)) + data.xml.appendComment("Secondary station: " + str(settings.serverAddress)) + + @classmethod + def __getWirelessMBusData(cls, settings, buff, data): + len_ = buff.getUInt8() + #Some meters are counting length to frame size. + if len(buff) < len_ - 1: + data.complete = (False) + buff.position = buff.position - 1 + else: + #Some meters are counting length to frame size. + if len(buff) < len_: + len_ -= 1 + data.packetLength = len_ + data.complete = True + cmd = buff.getUInt8() + manufacturerID = buff.getUInt16() + man = _GXCommon.decryptManufacturer(manufacturerID) + #id = + buff.getUInt32() + meterVersion = buff.getUInt8() + type_ = buff.getUInt8() + ci = buff.getUInt8() + #frameId = + buff.getUInt8() + #state = + buff.getUInt8() + configurationWord = buff.getUInt16() + encryption = MBusEncryptionMode(configurationWord & 7) + settings.clientAddress = buff.getUInt8() + settings.serverAddress = buff.getUInt8() + if data.xml and data.xml.comments: + data.xml.appendComment("Command: " + cmd) + data.xml.appendComment("Manufacturer: " + man) + data.xml.appendComment("Meter Version: " + meterVersion) + data.xml.appendComment("Meter Type: " + type_) + data.xml.appendComment("Control Info: " + ci) + data.xml.appendComment("Encryption: " + encryption) + elif settings.mBus: + settings.mBus.manufacturerId = _GXCommon.decryptManufacturer(manufacturerID) + settings.mBus.version = meterVersion + settings.mBus.meterType = type_ + + # Get data from S-FSK PLC frame. + @classmethod + def __getPlcData(cls, settings, buff, data): + # pylint: disable=too-many-locals + if buff.available() < 9: + data.complete = False + return + packetStartID = buff.position + #Find STX. + pos = buff.position + while pos < buff.size: + stx = buff.getUInt8() + if stx == 2: + packetStartID = pos + break + pos = pos + 1 + #Not a PLC frame. + if buff.position == buff.size: + #Not enough data to parse; + data.complete = False + buff.position = packetStartID + return + len_ = buff.getUInt8() + index = buff.position + if buff.available < len_: + data.complete = False + buff.position = buff.position - 2 + else: + buff.GetUInt8() + #Credit fields. IC, CC, DC + #credit = + buff.getUInt8() + #MAC Addresses. + mac = buff.getUInt24() + #SA. + macSa = (mac >> 12) + #DA. + macDa = (mac and 0xFFF) + #PAD length. + padLen = buff.getUInt8() + if buff.size < len_ + padLen + 2: + data.complete = False + buff.position = buff.position - (index + 6) + else: + #DL.Data.request + ch = buff.getUInt8() + if ch != PlcDataLinkData.REQUEST: + raise Exception("Parsing MAC LLC data failed. Invalid DataLink data request.") + #da = + buff.getUInt8() + #sa = + buff.getUInt8() + if settings.IsServer: + data.complete = data.xml or \ + (macDa in (PlcDestinationAddress.ALL_PHYSICAL, settings.plc.macSourceAddress) and macSa in (PlcSourceAddress.INITIATOR, settings.plc.macDestinationAddress)) + data.sourceAddress = macDa + data.targetAddress = macSa + else: + data.complete = data.xml or \ + (macDa in (PlcDestinationAddress.ALL_PHYSICAL, PlcSourceAddress.INITIATOR, settings.plc.macDestinationAddress)) + data.targetAddress = macDa + data.sourceAddress = macSa + #Skip padding. + if data.complete: + crcCount = _GXFCS16.countFCS16(buff.data, 0, len_ + padLen) + crc = buff.getUInt16(len_ + padLen) + #Check CRC. + if crc != crcCount: + if not data.xml: + raise Exception("Invalid data checksum.") + data.xml.appendComment("Invalid data checksum.") + data.packetLength = len_ + else: + buff.position = packetStartID + + #Get data from S-FSK PLC Hdlc frame. + @classmethod + def __getPlcHdlcData(cls, settings, buff, data): + if buff.available() < 2: + data.complete = False + return 0 + frame = 0 + #SN field. + frameLen = GXDLMS.getPlcSfskFrameSize(buff) + if frameLen == 0: + raise Exception("Invalid PLC frame size.") + if buff.Available < frameLen: + data.complete = False + else: + buff.position = buff.position + 2 + index = buff.position + #Credit fields. IC, CC, DC + #credit = + buff.getUInt8() + #MAC Addresses. + mac = buff.getUInt24() + #SA. + sa = (mac >> 12) + #DA. + da = (mac & 0xFFF) + if settings.isServer: + data.complete = data.xml or\ + (da in (PlcDestinationAddress.ALL_PHYSICAL, settings.plc.macSourceAddress) and\ + sa in (PlcHdlcSourceAddress.INITIATOR, settings.plc.macDestinationAddress)) + data.sourceAddress = da + data.targetAddress = sa + else: + data.complete = data.xml or\ + da in (PlcHdlcSourceAddress.INITIATOR, settings.plc.macDestinationAddress) + data.targetAddress = sa + data.sourceAddress = da + if data.IsComplete: + #PAD length. + padLen = buff.getUInt8() + frame = GXDLMS.getHdlcData(settings.isServer, settings, buff, data, None) + GXDLMS.getDataFromFrame(buff, data, True) + buff.position = buff.position + padLen + crcCount = _GXFCS16.countFCS24(buff.data, index, buff.position - index) + crc = buff.getUInt24(buff.Position) + #Check CRC. + if crc != crcCount: + if not data.xml: + raise Exception("Invalid data checksum.") + data.xml.appendComment("Invalid data checksum.") + data.packetLength = 2 + buff.position - index + else: + buff.position = buff.position + (frameLen - index - 4) + return frame + + #Check is this wireless M-Bus message. + @classmethod + def isWirelessMBusData(cls, buff): + if len(buff) - buff.position < 2: + return False + cmd = buff.getUInt8(buff.position + 1) + return cmd in (MBusCommand.SND_NR, MBusCommand.SND_UD, MBusCommand.RSP_UD) + + #Check is this wired M-Bus message. + @classmethod + def isWiredMBusData(cls, buff): + if len(buff) - buff.position < 1: + return False + return buff.getUInt8(buff.position) == 0x68 + + # Check is this PLC S-FSK message. + # buff: Received data. + # Returns S-FSK frame size in bytes. + @classmethod + def getPlcSfskFrameSize(cls, buff): + ret = 0 + if not buff.size - buff.position < 2: + len_ = buff.getUInt16(buff.position) + if len_ == int(PlcMacSubframes.ONE): + ret = 36 + elif len_ == int(PlcMacSubframes.TWO): + ret = 2 * 36 + elif len_ == int(PlcMacSubframes.THREE): + ret = 3 * 36 + elif len_ == int(PlcMacSubframes.FOUR): + ret = 4 * 36 + elif len_ == int(PlcMacSubframes.FIVE): + ret = 5 * 36 + elif len_ == int(PlcMacSubframes.SIX): + ret = 6 * 36 + elif len_ == int(PlcMacSubframes.SEVEN): + ret = 7 * 36 + return ret + + @classmethod + def checkWrapperAddress(cls, settings, buff, data, notify): + ret = True + value = 0 + if settings.isServer: + value = buff.getUInt16() + data.sourceAddress = value + #Check that client addresses match. + if not data.xml and settings.clientAddress != 0 and settings.clientAddress != value: + raise Exception("Source addresses do not match. It is " + str(value) + ". It should be " + str(settings.clientAddress) + ".") + settings.clientAddress = value + value = buff.getUInt16() + data.targetAddress = value + if not data.xml and settings.serverAddress != 0 and settings.serverAddress != value: + raise Exception("Destination addresses do not match. It is " + str(value) + ". It should be " + str(settings.serverAddress) + ".") + settings.serverAddress = value + else: + value = buff.getUInt16() + data.targetAddress = value + if settings.clientAddress != 0 and settings.serverAddress != value: + if notify is None: + raise Exception("Source addresses do not match. It is " + str(value) + ". It should be " + str(settings.serverAddress) + ".") + notify.sourceAddress = value + ret = False + else: + settings.serverAddress = value + value = buff.getUInt16() + data.sourceAddress = value + if not data.xml and settings.clientAddress != 0 and settings.clientAddress != value: + if notify is None: + raise Exception("Destination addresses do not match. It is " + str(value) + ". It should be " + str(settings.clientAddress) + ".") + ret = False + notify.targetAddress = value + else: + settings.clientAddress = value + return ret + + @classmethod + def readResponseDataBlockResult(cls, settings, reply, index): + reply.error = 0 + data = reply.data + lastBlock = data.getUInt8() + number = data.getUInt16() + blockLength = _GXCommon.getObjectCount(data) + if lastBlock == 0: + reply.moreData = (RequestTypes(reply.moreData | RequestTypes.DATABLOCK)) + else: + reply.moreData = (RequestTypes(reply.moreData & ~RequestTypes.DATABLOCK)) + if number != 1 and settings.blockIndex == 1: + settings.setBlockIndex(number) + expectedIndex = settings.blockIndex + if number != expectedIndex: + raise ValueError("Invalid Block number. It is " + str(number) + " and it should be " + str(expectedIndex) + ".") + if (reply.moreData & RequestTypes.FRAME) != 0: + GXDLMS.getDataFromBlock(data, index) + return False + if blockLength != data.size - data.position: + raise ValueError("Invalid block length.") + reply.command = Command.NONE + if reply.xml: + data.strip() + reply.xml.appendStartTag(Command.READ_RESPONSE, SingleReadResponse.DATA_BLOCK_RESULT) + reply.xml.appendLine(TranslatorTags.LAST_BLOCK, None, reply.xml.integerToHex(lastBlock, 2)) + reply.xml.appendLine(TranslatorTags.BLOCK_NUMBER, None, reply.xml.integerToHex(number, 4)) + reply.xml.appendLine(TranslatorTags.RAW_DATA, None, data.toHex(False, 0, data.size)) + reply.xml.appendEndTag(Command.READ_RESPONSE, SingleReadResponse.DATA_BLOCK_RESULT) + return False + GXDLMS.getDataFromBlock(reply.data, index) + reply.setTotalCount(0) + if reply.getMoreData() == RequestTypes.NONE: + settings.resetBlockIndex() + return True + + @classmethod + def handleReadResponse(cls, settings, reply, index): + data = reply.data + pos = 0 + cnt = reply.getTotalCount() + first = cnt == 0 or reply.commandType == SingleReadResponse.DATA_BLOCK_RESULT + if first: + cnt = _GXCommon.getObjectCount(reply.data) + reply.totalCount = cnt + type_ = 0 + values = None + if cnt != 1: + #Parse data after all data is received when readlist is used. + if reply.isMoreData(): + GXDLMS.getDataFromBlock(reply.data, 0) + return False + if not first: + reply.data.position = 0 + first = True + values = list() + if isinstance(reply.value, list): + values.append(reply.value) + reply.value = None + if reply.xml: + reply.xml.appendStartTag(Command.READ_RESPONSE, "Qty", reply.xml.integerToHex(cnt, 2)) + while pos != cnt: + if first: + type_ = data.getUInt8() + reply.commandType = type_ + else: + type_ = reply.commandType + standardXml = reply.xml and reply.xml.outputType == TranslatorOutputType.STANDARD_XML + if type_ == SingleReadResponse.DATA: + reply.error = 0 + if reply.xml: + if standardXml: + reply.xml.appendStartTag(TranslatorTags.CHOICE) + reply.xml.appendStartTag(Command.READ_RESPONSE, SingleReadResponse.DATA) + di = _GXDataInfo() + di.xml = (reply.xml) + _GXCommon.getData(settings, reply.data, di) + reply.xml.appendEndTag(Command.READ_RESPONSE, SingleReadResponse.DATA) + if standardXml: + reply.xml.appendEndTag(TranslatorTags.CHOICE) + elif cnt == 1: + GXDLMS.getDataFromBlock(reply.data, 0) + else: + reply.readPosition = data.position + GXDLMS.getValueFromData(settings, reply) + data.position = reply.readPosition + values.append(reply.value) + reply.value = None + elif type_ == SingleReadResponse.DATA_ACCESS_ERROR: + reply.error = data.getUInt8() + if reply.xml: + if standardXml: + reply.xml.appendStartTag(TranslatorTags.CHOICE) + reply.xml.appendLine(Command.READ_RESPONSE << 8 | SingleReadResponse.DATA_ACCESS_ERROR, None, GXDLMS.errorCodeToString(reply.xml.outputType, reply.error)) + if standardXml: + reply.xml.appendEndTag(TranslatorTags.CHOICE) + elif type_ == SingleReadResponse.DATA_BLOCK_RESULT: + if not GXDLMS.readResponseDataBlockResult(settings, reply, index): + if reply.xml: + reply.xml.appendEndTag(Command.READ_RESPONSE) + return False + elif type_ == SingleReadResponse.BLOCK_NUMBER: + number = data.getUInt16() + if number != settings.blockIndex: + raise ValueError("Invalid Block number. It is " + str(number) + " and it should be " + str(settings.blockIndex) + ".") + settings.increaseBlockIndex() + reply.moreData = (RequestTypes(reply.moreData | RequestTypes.DATABLOCK)) + else: + raise Exception("HandleReadResponse failed. Invalid tag.") + pos += 1 + if reply.xml: + reply.xml.appendEndTag(Command.READ_RESPONSE) + return True + if values: + reply.value = values + return cnt == 1 + + @classmethod + def errorCodeToString(cls, type_, value): + if type_ == TranslatorOutputType.STANDARD_XML: + return TranslatorStandardTags.errorCodeToString(value) + return TranslatorSimpleTags.errorCodeToString(value) + + @classmethod + def handleActionResponseNormal(cls, settings, data): + ret = data.data.getUInt8() + if ret != 0: + data.error = ret + if data.xml: + if data.xml.outputType == TranslatorOutputType.STANDARD_XML: + data.xml.appendStartTag(TranslatorTags.SINGLE_RESPONSE) + data.xml.appendLine(TranslatorTags.RESULT, None, GXDLMS.errorCodeToString(data.xml.outputType, data.error)) + settings.resetBlockIndex() + if data.data.position < data.data.size: + ret = data.data.getUInt8() + if ret == 0: + GXDLMS.getDataFromBlock(data.data, 0) + elif ret == 1: + ret = int(data.data.getUInt8()) + if ret != 0: + data.error = data.data.getUInt8() + if ret == 9 and data.getError() == 16: + data.data.position = data.data.position - 2 + GXDLMS.getDataFromBlock(data.data, 0) + data.error = 0 + ret = 0 + else: + GXDLMS.getDataFromBlock(data.data, 0) + else: + raise Exception("HandleActionResponseNormal failed. " + "Invalid tag.") + if data.xml and (ret != 0 or data.data.position < data.data.size): + data.xml.appendStartTag(TranslatorTags.RETURN_PARAMETERS) + if ret != 0: + data.xml.appendLine(TranslatorTags.DATA_ACCESS_ERROR, None, GXDLMS.errorCodeToString(data.xml.outputType, data.error)) + else: + data.xml.appendStartTag(Command.READ_RESPONSE, SingleReadResponse.DATA) + di = _GXDataInfo() + di.xml = (data.xml) + _GXCommon.getData(settings, data.data, di) + data.xml.appendEndTag(Command.READ_RESPONSE, SingleReadResponse.DATA) + data.xml.appendEndTag(TranslatorTags.RETURN_PARAMETERS) + if data.xml.outputType == TranslatorOutputType.STANDARD_XML: + data.xml.appendEndTag(TranslatorTags.SINGLE_RESPONSE) + + @classmethod + def handleActionResponseWithBlock(cls, settings, reply, index): + ret = True + ch = reply.data.getUInt8() + if reply.xml: + # Result start tag. + reply.xml.appendStartTag(TranslatorTags.P_BLOCK) + # LastBlock + reply.xml.appendLine(TranslatorTags.LAST_BLOCK, None, reply.xml.integerToHex(ch, 2)) + if ch == 0: + reply.moreData = reply.moreData | RequestTypes.DATABLOCK + else: + reply.moreData = reply.moreData & ~RequestTypes.DATABLOCK + # Get Block number. + number = reply.data.getUInt32() + if reply.xml: + # BlockNumber + reply.xml.AppendLine(TranslatorTags.BLOCK_NUMBER, None, reply.xml.integerToHex(number, 8)) + else: + # Update initial block index. This is critical if message is send + # and received in multiple blocks. + if number == 1: + settings.resetBlockIndex() + if number != settings.blockIndex: + raise ValueError("Invalid Block number. It is " + str(number) + " and it should be " + str(settings.BlockIndex) + ".") + #Note! There is no status!! + if reply.xml: + if reply.data.available() != 0: + # Get data size. + blockLength = _GXCommon.getObjectCount(reply.data) + # if whole block is read. + if (reply.moreData & RequestTypes.FRAME) == 0: + #Check Block length. + if blockLength > reply.data.available(): + reply.xml.appendComment("Block is not complete." + str(reply.data.available()) + "/" + str(blockLength) + ".") + reply.xml.appendLine(TranslatorTags.RAW_DATA, None, GXByteBuffer.toHex(reply.data.data, False, reply.data.position, reply.data.available)) + reply.xml.AppendEndTag(TranslatorTags.P_BLOCK) + elif reply.data.available != 0: + # Get data size. + blockLength = _GXCommon.getObjectCount(reply.data) + # if whole block is read. + if (reply.moreData & RequestTypes.FRAME) == 0: + # Check Block length. + if blockLength > reply.data.available(): + raise ValueError("Not enought data.") + # Keep command if this is last block for XML Client. + if (reply.moreData & RequestTypes.DATABLOCK) != 0: + reply.command = Command.NONE + if blockLength == 0: + #If meter sends empty data block. + reply.data.size = index + else: + GXDLMS.getDataFromBlock(reply.data, index) + # If last packet and data is not try to peek. + if reply.moreData == RequestTypes.NONE: + reply.data.position = 0 + settings.resetBlockIndex() + elif reply.data.available() == 0: + # Empty block. Conformance tests uses this. + reply.EmptyResponses |= RequestTypes.DATABLOCK + if reply.moreData == RequestTypes.NONE and settings and \ + settings.command == Command.METHOD_REQUEST and settings.commandType == ActionResponseType.WITH_LIST: + raise ValueError("Action with list is not implemented.") + return ret + + @classmethod + def handleMethodResponse(cls, settings, data, index): + type_ = int(data.data.getUInt8()) + data.invokeId = data.data.getUInt8() + if data.xml: + data.xml.appendStartTag(Command.METHOD_RESPONSE) + data.xml.appendStartTag(Command.METHOD_RESPONSE, type_) + data.xml.appendLine(TranslatorTags.INVOKE_ID, None, data.xml.integerToHex(data.invokeId, 2)) + if type_ == ActionResponseType.NORMAL: + GXDLMS.handleActionResponseNormal(settings, data) + elif type_ == 2: + GXDLMS.handleActionResponseWithBlock(settings, data, index) + elif type_ == 3: + raise ValueError("Invalid Command.") + elif type_ == 4: + number = data.data.getUInt32() + if data.xml: + data.xml.appendLine(TranslatorTags.BLOCK_NUMBER, None, data.xml.integerToHex(number, 8)) + elif number != settings.blockIndex: + raise ValueError("Invalid Block number. It is " + str(number) + " and it should be " + str(settings.BlockIndex) + ".") + settings.increaseBlockIndex() + else: + raise ValueError("Invalid Command.") + if data.xml: + data.xml.appendEndTag(Command.METHOD_RESPONSE, type_) + data.xml.appendEndTag(Command.METHOD_RESPONSE) + + @classmethod + def handlePush(cls, reply): + data = reply.data + index = data.position - 1 + last = data.getUInt8() + if (last & 0x80) == 0: + reply.moreData = (RequestTypes(reply.moreData | RequestTypes.DATABLOCK)) + else: + reply.moreData = (RequestTypes(reply.moreData & ~RequestTypes.DATABLOCK)) + data.getUInt8() + data.getUInt8() + data.getUInt8() + data.getUInt8() + if (data.getUInt8() & 0x0F) == 0: + raise ValueError("Invalid data.") + reply.data.getUInt32() + len_ = reply.data.getUInt8() + if len_ != 0: + reply.data.position = reply.data.position + len_ + GXDLMS.getDataFromBlock(reply.data, index) + + @classmethod + def handleAccessResponse(cls, settings, reply): + data = reply.data + reply.invokeId = reply.data.getUInt32() + len_ = reply.data.getUInt8() + tmp = None + if len_ != 0: + tmp = bytearray(len_) + data.get(tmp) + reply.time = _GXCommon.changeType(settings, tmp, DataType.DATETIME) + if reply.xml: + reply.xml.appendStartTag(Command.ACCESS_RESPONSE) + reply.xml.appendLine(TranslatorTags.LONG_INVOKE_ID, None, reply.xml.integerToHex(reply.invokeId, 8)) + if reply.time: + reply.xml.appendComment(str(reply.time)) + reply.xml.appendLine(TranslatorTags.DATE_TIME, None, GXByteBuffer.hex(tmp, False)) + reply.data.getUInt8() + len_ = _GXCommon.getObjectCount(reply.data) + reply.xml.appendStartTag(TranslatorTags.ACCESS_RESPONSE_BODY) + reply.xml.appendStartTag(TranslatorTags.ACCESS_RESPONSE_LIST_OF_DATA, "Qty", reply.xml.integerToHex(len_, 2)) + pos = 0 + while pos != len_: + if reply.xml.outputType == TranslatorOutputType.STANDARD_XML: + reply.xml.appendStartTag(Command.WRITE_REQUEST, SingleReadResponse.DATA) + di = _GXDataInfo() + di.xml = (reply.xml) + _GXCommon.getData(settings, reply.data, di) + if reply.xml.outputType == TranslatorOutputType.STANDARD_XML: + reply.xml.appendEndTag(Command.WRITE_REQUEST, SingleReadResponse.DATA) + pos += 1 + reply.xml.appendEndTag(TranslatorTags.ACCESS_RESPONSE_LIST_OF_DATA) + err = int() + len_ = _GXCommon.getObjectCount(reply.data) + reply.xml.appendStartTag(TranslatorTags.LIST_OF_ACCESS_RESPONSE_SPECIFICATION, "Qty", reply.xml.integerToHex(len_, 2)) + pos = 0 + while pos != len_: + type_ = int(data.getUInt8()) + err = data.getUInt8() + if err != 0: + err = data.getUInt8() + reply.xml.appendStartTag(TranslatorTags.ACCESS_RESPONSE_SPECIFICATION) + reply.xml.appendStartTag(Command.ACCESS_RESPONSE, type_) + reply.xml.appendLine(TranslatorTags.RESULT, None, GXDLMS.errorCodeToString(reply.xml, err)) + reply.xml.appendEndTag(Command.ACCESS_RESPONSE, type_) + reply.xml.appendEndTag(TranslatorTags.ACCESS_RESPONSE_SPECIFICATION) + pos += 1 + reply.xml.appendEndTag(TranslatorTags.LIST_OF_ACCESS_RESPONSE_SPECIFICATION) + reply.xml.appendEndTag(TranslatorTags.ACCESS_RESPONSE_BODY) + reply.xml.appendEndTag(Command.ACCESS_RESPONSE) + else: + data.getUInt8() + + @classmethod + def handleDataNotification(cls, settings, reply): + data = reply.data + start = data.position - 1 + reply.invokeId = data.getUInt32() + reply.time = None + len_ = data.getUInt8() + tmp = None + if len_ != 0: + tmp = bytearray(len_) + data.get(tmp) + dt = DataType.DATETIME + if len_ == 4: + dt = DataType.TIME + elif len_ == 5: + dt = DataType.DATE + info = _GXDataInfo() + info.type_ = dt + reply.time = _GXCommon.getData(settings, GXByteBuffer(tmp), info) + if reply.xml: + reply.xml.appendStartTag(Command.DATA_NOTIFICATION) + reply.xml.appendLine(TranslatorTags.LONG_INVOKE_ID, None, reply.xml.integerToHex(reply.invokeId, 8)) + if reply.time: + reply.xml.appendComment(str(reply.time)) + reply.xml.appendLine(TranslatorTags.DATE_TIME, None, GXByteBuffer.hex(tmp, False)) + reply.xml.appendStartTag(TranslatorTags.NOTIFICATION_BODY) + reply.xml.appendStartTag(TranslatorTags.DATA_VALUE) + di = _GXDataInfo() + di.xml = (reply.xml) + _GXCommon.getData(settings, reply.data, di) + reply.xml.appendEndTag(TranslatorTags.DATA_VALUE) + reply.xml.appendEndTag(TranslatorTags.NOTIFICATION_BODY) + reply.xml.appendEndTag(Command.DATA_NOTIFICATION) + else: + GXDLMS.getDataFromBlock(reply.data, start) + GXDLMS.getValueFromData(settings, reply) + + @classmethod + def handleSetResponse(cls, data): + type_ = SetResponseType(data.data.getUInt8()) + data.invokeId = data.data.getUInt8() + if data.xml: + data.xml.appendStartTag(Command.SET_RESPONSE) + data.xml.appendStartTag(Command.SET_RESPONSE, type_) + data.xml.appendLine(TranslatorTags.INVOKE_ID, None, data.xml.integerToHex(data.invokeId, 2)) + if type_ == SetResponseType.NORMAL: + data.error = data.data.getUInt8() + if data.xml: + data.xml.appendLine(TranslatorTags.RESULT, None, GXDLMS.errorCodeToString(data.xml.outputType, data.error)) + elif type_ == SetResponseType.DATA_BLOCK: + number = data.data.getUInt32() + if data.xml: + data.xml.appendLine(TranslatorTags.BLOCK_NUMBER, None, data.xml.integerToHex(number, 8)) + elif type_ == SetResponseType.LAST_DATA_BLOCK: + data.error = data.data.getUInt8() + number = data.data.getUInt32() + if data.xml: + data.xml.appendLine(TranslatorTags.RESULT, None, GXDLMS.errorCodeToString(data.xml.outputType, data.error)) + data.xml.appendLine(TranslatorTags.BLOCK_NUMBER, None, data.xml.integerToHex(number, 8)) + elif type_ == SetResponseType.WITH_LIST: + cnt = _GXCommon.getObjectCount(data.data) + if data.xml: + data.xml.appendStartTag(TranslatorTags.RESULT, "Qty", str(cnt)) + pos = 0 + while pos != cnt: + err = data.data.getUInt8() + data.xml.appendLine(TranslatorTags.DATA_ACCESS_RESULT, None, GXDLMS.errorCodeToString(data.xml.outputType, err)) + pos += 1 + data.xml.appendEndTag(TranslatorTags.RESULT) + else: + pos = 0 + while pos != cnt: + err = data.data.getUInt8() + if data.getError() == 0 and err != 0: + data.error = err + pos += 1 + else: + raise ValueError("Invalid data type.") + if data.xml: + data.xml.appendEndTag(Command.SET_RESPONSE, type_) + data.xml.appendEndTag(Command.SET_RESPONSE) + + @classmethod + def handleWriteResponse(cls, data): + cnt = _GXCommon.getObjectCount(data.data) + ret = int() + if data.xml: + data.xml.appendStartTag(Command.WRITE_RESPONSE, "Qty", data.xml.integerToHex(cnt, 2)) + pos = 0 + while pos != cnt: + ret = data.data.getUInt8() + if ret != 0: + data.error = data.data.getUInt8() + if data.xml: + if ret == 0: + data.xml.appendLine("<" + GXDLMS.errorCodeToString(data.xml.outputType, ret) + " />") + else: + data.xml.appendLine(TranslatorTags.DATA_ACCESS_ERROR, None, GXDLMS.errorCodeToString(data.xml.outputType, data.error)) + pos += 1 + if data.xml: + data.xml.appendEndTag(Command.WRITE_RESPONSE) + + @classmethod + def handleGetResponseWithList(cls, settings, reply): + cnt = _GXCommon.getObjectCount(reply.data) + values = list([None] * cnt) + if reply.xml: + reply.xml.appendStartTag(TranslatorTags.RESULT, "Qty", reply.xml.integerToHex(cnt, 2)) + pos = 0 + while pos != cnt: + ch = reply.data.getUInt8() + if ch != 0: + reply.error = reply.data.getUInt8() + else: + if reply.xml: + di = _GXDataInfo() + di.xml = (reply.xml) + reply.xml.appendStartTag(Command.READ_RESPONSE, SingleReadResponse.DATA) + _GXCommon.getData(settings, reply.data, di) + reply.xml.appendEndTag(Command.READ_RESPONSE, SingleReadResponse.DATA) + else: + reply.readPosition = reply.data.position + GXDLMS.getValueFromData(settings, reply) + if reply.value is None: + #Increase read position if data is null. This is a special case. + reply.readPosition = 1 + reply.readPosition + reply.data.position = reply.readPosition + if values: + values[pos] = reply.value + reply.value = None + pos += 1 + reply.value = values + + @classmethod + def handleGetResponseNormal(cls, settings, reply): + if reply.data.available() == 0: + empty = True + GXDLMS.getDataFromBlock(reply.data, 0) + else: + empty = False + # Result + ch = reply.data.getUInt8() + if ch != 0: + reply.error = reply.data.getUInt8() + if reply.xml: + # Result start tag. + reply.xml.appendStartTag(TranslatorTags.RESULT) + if reply.error: + reply.xml.appendLine(TranslatorTags.DATA_ACCESS_ERROR, None,\ + GXDLMS.errorCodeToString(reply.xml.outputType, reply.error)) + else: + reply.xml.appendStartTag(TranslatorTags.DATA) + di = _GXDataInfo() + di.xml = reply.xml + _GXCommon.getData(settings, reply.data, di) + reply.xml.appendEndTag(TranslatorTags.DATA) + else: + GXDLMS.getDataFromBlock(reply.data, 0) + return empty + + @classmethod + def handleGetResponseNextDataBlock(cls, settings, reply, index): + ret = True + ch = reply.data.getUInt8() + if reply.xml: + reply.xml.appendStartTag(TranslatorTags.RESULT) + reply.xml.appendLine(TranslatorTags.LAST_BLOCK, None, reply.xml.integerToHex(ch, 2)) + if ch == 0: + reply.moreData = reply.moreData | RequestTypes.DATABLOCK + else: + reply.moreData = reply.moreData & ~RequestTypes.DATABLOCK + number = reply.data.getUInt32() + if reply.xml: + reply.xml.appendLine(TranslatorTags.BLOCK_NUMBER, None, reply.xml.integerToHex(number, 8)) + else: + # If meter's block index is zero based. + if number == 0 and settings.blockIndex == 1: + settings.setBlockIndex(0) + expectedIndex = settings.blockIndex + if number != expectedIndex: + raise ValueError("Invalid Block number. It is " + str(number) + " and it should be " + str(expectedIndex) + ".") + # Get status. + ch = reply.data.getUInt8() + if ch != 0: + reply.error = reply.data.getUInt8() + if reply.xml: + reply.xml.appendStartTag(TranslatorTags.RESULT) + if reply.getError() != 0: + reply.xml.appendLine(TranslatorTags.DATA_ACCESS_RESULT, None, GXDLMS.errorCodeToString(reply.xml.outputType, reply.error)) + elif reply.data.available() != 0: + blockLength = _GXCommon.getObjectCount(reply.data) + if (reply.moreData & RequestTypes.FRAME) == 0: + if blockLength > len(reply.data) - reply.data.position: + reply.xml.appendComment("Block is not complete." + str(len(reply.data) - reply.data.position) + "/" + str(blockLength) + ".") + reply.xml.appendLine(TranslatorTags.RAW_DATA, None, reply.data.toHex(False, reply.data.position, reply.data.available())) + reply.xml.appendEndTag(TranslatorTags.RESULT) + elif reply.data.available != 0: + # Get data size. + blockLength = _GXCommon.getObjectCount(reply.data) + # if whole block is read. + if (reply.moreData & RequestTypes.FRAME) == 0: + # Check Block length. + if blockLength > reply.data.available(): + raise ValueError("Not enought data.") + # Keep command if this is last block for XML Client. + if (reply.moreData & RequestTypes.DATABLOCK) != 0: + reply.command = Command.NONE + if blockLength == 0: + # If meter sends empty data block. + reply.data.Size = index + else: + GXDLMS.getDataFromBlock(reply.data, index) + # If last packet and data is not try to peek. + if reply.moreData == RequestTypes.NONE: + reply.data.position = 0 + settings.resetBlockIndex() + + if reply.moreData == RequestTypes.NONE and settings and \ + settings.command == Command.GET_REQUEST and settings.commandType == GetCommandType.WITH_LIST: + GXDLMS.handleGetResponseWithList(settings, reply) + ret = False + return ret + + @classmethod + def handleGetResponse(cls, settings, reply, index): + # pylint: disable=too-many-locals + ret = True + type_ = reply.data.getUInt8() + reply.invokeId = reply.data.getUInt8() + if reply.xml: + reply.xml.appendStartTag(Command.GET_RESPONSE) + reply.xml.appendStartTag(Command.GET_RESPONSE, type_) + reply.xml.appendLine(TranslatorTags.INVOKE_ID, None, reply.xml.integerToHex(reply.invokeId, 2)) + if type_ == GetCommandType.NORMAL: + empty = GXDLMS.handleGetResponseNormal(settings, reply) + elif type_ == GetCommandType.NEXT_DATA_BLOCK: + GXDLMS.handleGetResponseNextDataBlock(settings, reply, index) + elif type_ == GetCommandType.WITH_LIST: + GXDLMS.handleGetResponseWithList(settings, reply) + ret = False + else: + raise ValueError("Invalid Get response.") + if reply.xml: + if not empty: + reply.xml.appendEndTag(TranslatorTags.RESULT) + reply.xml.appendEndTag(Command.GET_RESPONSE, type_) + reply.xml.appendEndTag(Command.GET_RESPONSE) + return ret + + @classmethod + def handleGbt(cls, settings, data): + # pylint: disable=broad-except + index = data.data.position - 1 + data.gbtWindowSize = settings.gbtWindowSize + bc = data.data.getUInt8() + data.streaming = (bc & 0x40) != 0 + gbtWindowSize = int(bc & 0x3F) + bn = data.data.getUInt16() + bna = data.data.getUInt16() + + if not data.xml: + #Remove existing data when first block is received. + if bn == 1: + index = 0 + elif bna != settings.blockIndex - 1: + #If this block is already received. + data.data.size = index + data.command = Command.NONE + return + + data.blockNumber = bn + data.blockNumberAck = bna + settings.blockNumberAck = data.blockNumber + data.command = Command.NONE + len_ = _GXCommon.getObjectCount(data.data) + if len_ > data.data.size - data.data.position: + data.complete = False + return + if data.xml: + if (data.data.size - data.data.position) != len_: + data.xml.appendComment("Data length is " + str(len_) + "and there are " + str(data.data.size - data.data.position) + " bytes.") + data.xml.appendStartTag(Command.GENERAL_BLOCK_TRANSFER) + if data.xml.comments: + data.xml.appendComment("Last block: " + (bc & 0x80) != 0) + data.xml.appendComment("Streaming: " + data.streaming) + data.xml.appendComment("Window size: " + gbtWindowSize) + data.xml.appendLine(TranslatorTags.BLOCK_CONTROL, None, data.xml.integerToHex(bc, 2)) + data.xml.appendLine(TranslatorTags.BLOCK_NUMBER, None, data.xml.integerToHex(data.blockNumber, 4)) + data.xml.appendLine(TranslatorTags.BLOCK_NUMBER_ACK, None, data.xml.integerToHex(data.blockNumberAck, 4)) + if (bc & 0x80) != 0 and data.xml.comments: + pos = data.data.position + len2 = data.xml.getXmlLength() + try: + reply = GXReplyData() + reply.data = data.data + reply.xml = data.xml + reply.xml.startComment("") + GXDLMS.getPdu(settings, reply) + reply.xml.endComment() + except Exception: + data.xml.setXmlLength = len2 + data.data.position = pos + data.xml.appendLine(TranslatorTags.BLOCK_DATA, None, data.data.toHex(False, data.data.position, len_)) + data.xml.appendEndTag(Command.GENERAL_BLOCK_TRANSFER) + return + GXDLMS.getDataFromBlock(data.data, index) + if (bc & 0x80) == 0: + data.moreData = (RequestTypes(data.moreData | RequestTypes.GBT)) + else: + data.moreData = (RequestTypes(data.moreData & ~RequestTypes.GBT)) + if data.data.size != 0: + data.data.position = 0 + GXDLMS.getPdu(settings, data) + if data.data.position != data.data.size and (data.command == Command.READ_RESPONSE or data.command == Command.GET_RESPONSE) and (data.moreData == RequestTypes.NONE or data.peek): + data.data.position = 0 + GXDLMS.getValueFromData(settings, data) + + @classmethod + def getPdu(cls, settings, data): + cmd = data.command + if data.command == Command.NONE: + if data.data.size - data.data.position == 0: + raise ValueError("Invalid PDU.") + index = data.data.position + cmd = data.data.getUInt8() + data.command = cmd + if cmd == Command.READ_RESPONSE: + if not GXDLMS.handleReadResponse(settings, data, index): + return + elif cmd == Command.GET_RESPONSE: + if not GXDLMS.handleGetResponse(settings, data, index): + return + elif cmd == Command.SET_RESPONSE: + GXDLMS.handleSetResponse(data) + elif cmd == Command.WRITE_RESPONSE: + GXDLMS.handleWriteResponse(data) + elif cmd == Command.METHOD_RESPONSE: + GXDLMS.handleMethodResponse(settings, data, index) + elif cmd == Command.ACCESS_RESPONSE: + GXDLMS.handleAccessResponse(settings, data) + elif cmd == Command.GENERAL_BLOCK_TRANSFER: + if data.xml or (not settings.isServer and (data.moreData & RequestTypes.FRAME) == 0): + GXDLMS.handleGbt(settings, data) + elif cmd in (Command.AARQ, Command.AARE): + # This is parsed later. + data.data.position = data.data.position - 1 + elif cmd == Command.RELEASE_RESPONSE: + pass + elif cmd == Command.CONFIRMED_SERVICE_ERROR: + GXDLMS.handleConfirmedServiceError(data) + elif cmd == Command.EXCEPTION_RESPONSE: + GXDLMS.handleExceptionResponse(data) + elif cmd in (Command.GET_REQUEST, Command.READ_REQUEST, Command.WRITE_REQUEST, Command.SET_REQUEST, Command.METHOD_REQUEST, Command.RELEASE_REQUEST): + pass + elif cmd in (Command.GLO_READ_REQUEST, Command.GLO_WRITE_REQUEST, Command.GLO_GET_REQUEST, Command.GLO_SET_REQUEST, \ + Command.GLO_METHOD_REQUEST, Command.DED_GET_REQUEST, Command.DED_SET_REQUEST, Command.DED_METHOD_REQUEST): + GXDLMS.handleGloDedRequest(settings, data) + elif cmd in (Command.GLO_READ_RESPONSE, Command.GLO_WRITE_RESPONSE, Command.GLO_GET_RESPONSE, Command.GLO_SET_RESPONSE, \ + Command.GLO_METHOD_RESPONSE, Command.GENERAL_GLO_CIPHERING, Command.GLO_EVENT_NOTIFICATION, \ + Command.DED_GET_RESPONSE, Command.DED_SET_RESPONSE, Command.DED_METHOD_RESPONSE, Command.GENERAL_DED_CIPHERING, Command.DED_EVENT_NOTIFICATION, \ + Command.GLO_CONFIRMED_SERVICE_ERROR, Command.DED_CONFIRMED_SERVICE_ERROR): + GXDLMS.handleGloDedResponse(settings, data, index) + elif cmd == Command.DATA_NOTIFICATION: + GXDLMS.handleDataNotification(settings, data) + elif cmd == Command.EVENT_NOTIFICATION: + pass + elif cmd == Command.INFORMATION_REPORT: + pass + elif cmd == Command.GENERAL_CIPHERING: + GXDLMS.handleGeneralCiphering(settings, data) + elif cmd == Command.GATEWAY_REQUEST: + pass + elif cmd == Command.GATEWAY_RESPONSE: + data.data.getUInt8() + pda = bytearray(_GXCommon.getObjectCount(data.data)) + data.data.get(pda) + GXDLMS.getDataFromBlock(data.data, index) + data.command = Command.NONE + GXDLMS.getPdu(settings, data) + else: + raise ValueError("Invalid Command.") + elif (data.moreData & RequestTypes.FRAME) == 0: + if not data.peek and data.moreData == RequestTypes.NONE: + if data.command == Command.AARE or data.command == Command.AARQ: + data.data.position = 0 + else: + data.data.position = 1 + if cmd == Command.GENERAL_BLOCK_TRANSFER: + data.data.position = data.cipherIndex + 1 + GXDLMS.handleGbt(settings, data) + data.cipherIndex = data.data.size + data.command = Command.NONE + elif settings.isServer: + if cmd in (Command.GLO_READ_REQUEST, Command.GLO_WRITE_REQUEST, Command.GLO_GET_REQUEST, Command.GLO_SET_REQUEST, \ + Command.GLO_METHOD_REQUEST, Command.GLO_EVENT_NOTIFICATION, Command.DED_GET_REQUEST, Command.DED_SET_REQUEST, \ + Command.DED_METHOD_REQUEST, Command.DED_EVENT_NOTIFICATION): + data.command = Command.NONE + data.data.position = data.getCipherIndex() + GXDLMS.getPdu(settings, data) + else: + # Client do not need a command any more. + if data.isMoreData(): + data.command = Command.NONE + if cmd in (Command.GLO_READ_RESPONSE, Command.GLO_WRITE_RESPONSE, Command.GLO_GET_RESPONSE, Command.GLO_SET_RESPONSE,\ + Command.GLO_METHOD_RESPONSE, Command.DED_GET_RESPONSE, Command.DED_SET_RESPONSE, Command.DED_METHOD_RESPONSE,\ + Command.GENERAL_GLO_CIPHERING, Command.GENERAL_DED_CIPHERING): + data.command = Command.NONE + data.data.position = data.cipherIndex + GXDLMS.getPdu(settings, data) + if cmd == Command.READ_RESPONSE and data.totalCount > 1: + if not GXDLMS.handleReadResponse(settings, data, 0): + return + + if cmd == Command.READ_RESPONSE and data.commandType == SingleReadResponse.DATA_BLOCK_RESULT and \ + (data.moreData & RequestTypes.FRAME) != 0: + return + if data.xml is None and data.data.position != data.data.size and \ + cmd in (Command.READ_RESPONSE, Command.GET_RESPONSE, Command.METHOD_RESPONSE, Command.DATA_NOTIFICATION) and (data.moreData == RequestTypes.NONE or data.peek): + GXDLMS.getValueFromData(settings, data) + + @classmethod + def handleConfirmedServiceError(cls, data): + if data.xml: + data.xml.appendStartTag(Command.CONFIRMED_SERVICE_ERROR) + if data.xml.outputType == TranslatorOutputType.STANDARD_XML: + data.data.getUInt8() + data.xml.appendStartTag(TranslatorTags.INITIATE_ERROR) + type_ = data.data.getUInt8() + tag = TranslatorStandardTags.serviceErrorToString(type_) + value = TranslatorStandardTags.getServiceErrorValue(type_, data.data.getUInt8()) + data.xml.appendLine("x:" + tag, None, value) + data.xml.appendEndTag(TranslatorTags.INITIATE_ERROR) + else: + data.xml.appendLine(TranslatorTags.SERVICE, None, data.xml.integerToHex(data.data.getUInt8(), 2)) + type_ = data.data.getUInt8() + data.xml.appendStartTag(TranslatorTags.SERVICE_ERROR) + data.xml.appendLine(TranslatorSimpleTags.serviceErrorToString(type_), None, TranslatorSimpleTags.getServiceErrorValue(type_, data.data.getUInt8())) + data.xml.appendEndTag(TranslatorTags.SERVICE_ERROR) + data.xml.appendEndTag(Command.CONFIRMED_SERVICE_ERROR) + else: + service = ConfirmedServiceError(data.data.getUInt8()) + type_ = data.data.getUInt8() + raise GXDLMSConfirmedServiceError(service, type_, data.data.getUInt8()) + + @classmethod + def handleExceptionResponse(cls, data): + state = data.data.getUInt8() + error = ExceptionServiceError(data.data.getUInt8()) + value = None + if error == ExceptionServiceError.INVOCATION_COUNTER_ERROR and data.data.available() > 3: + value = data.data.getUInt32() + if data.xml: + data.xml.appendStartTag(Command.EXCEPTION_RESPONSE) + if data.xml.outputType == TranslatorOutputType.STANDARD_XML: + data.xml.appendLine(TranslatorTags.STATE_ERROR, None, TranslatorStandardTags.stateErrorToString(state)) + data.xml.appendLine(TranslatorTags.SERVICE_ERROR, None, TranslatorStandardTags.exceptionServiceErrorToString(error)) + else: + data.xml.appendLine(TranslatorTags.STATE_ERROR, None, TranslatorSimpleTags.stateErrorToString(state)) + data.xml.appendLine(TranslatorTags.SERVICE_ERROR, None, TranslatorSimpleTags.exceptionServiceErrorToString(error)) + data.xml.appendEndTag(Command.EXCEPTION_RESPONSE) + else: + raise GXDLMSExceptionResponse(state, error, value) + + @classmethod + def handleGloDedRequest(cls, settings, data): + if settings.cipher is None: + raise ValueError("Secure connection is not supported.") + if (data.moreData & RequestTypes.FRAME) == 0: + data.data.position = data.data.position - 1 + p = None + if settings.cipher.dedicatedKey and (settings.connected & ConnectionState.DLMS) != 0: + p = AesGcmParameter(settings.sourceSystemTitle, settings.cipher.dedicatedKey, settings.cipher.authenticationKey) + else: + p = AesGcmParameter(settings.sourceSystemTitle, settings.cipher.blockCipherKey, settings.cipher.authenticationKey) + tmp = GXCiphering.decrypt(settings.cipher, p, data.data) + data.data.clear() + data.data.set(tmp) + data.command = Command(data.data.getUInt8()) + if data.command == Command.DATA_NOTIFICATION or data.command == Command.INFORMATION_REPORT: + data.command = Command.NONE + data.data.position = data.data.position - 1 + GXDLMS.getPdu(cls, settings, data) + else: + data.data.position = data.data.position - 1 + + @classmethod + def handleGloDedResponse(cls, settings, data, index): + if data.xml and not data.xml.comments: + data.data.position = data.data.position - 1 + else: + if settings.cipher is None: + raise ValueError("Secure connection is not supported.") + if (data.moreData & RequestTypes.FRAME) == 0: + data.data.position = data.data.position - 1 + bb = GXByteBuffer(data.data) + data.data.size = data.data.position = index + p = None + if settings.cipher.dedicatedKey and (settings.connected & ConnectionState.DLMS) != 0: + p = AesGcmParameter(0, settings.sourceSystemTitle, settings.cipher.dedicatedKey, settings.cipher.authenticationKey) + else: + p = AesGcmParameter(0, settings.sourceSystemTitle, settings.cipher.blockCipherKey, settings.cipher.authenticationKey) + data.data.set(GXCiphering.decrypt(settings.cipher, p, bb)) + data.cipheredCommand = data.command + data.command = Command.NONE + GXDLMS.getPdu(settings, data) + data.cipherIndex = data.data.size + + @classmethod + def handleGeneralCiphering(cls, settings, data): + # pylint: disable=broad-except + if settings.cipher is None: + raise ValueError("Secure connection is not supported.") + if (data.moreData & RequestTypes.FRAME) == 0: + data.data.position = data.data.position - 1 + p = AesGcmParameter(0, settings.sourceSystemTitle, settings.cipher.blockCipherKey, settings.cipher.authenticationKey) + tmp = GXCiphering.decrypt(settings.cipher, p, data.data) + data.data.clear() + data.data.set(tmp) + data.command = Command.NONE + if p.security: + try: + GXDLMS.getPdu(settings, data) + except Exception as ex: + if data.xml is None: + raise ex + if data.xml: + data.xml.appendStartTag(Command.GENERAL_CIPHERING) + data.xml.appendLine(TranslatorTags.TRANSACTION_ID, None, data.xml.integerToHex(p.invocationCounter, 16, True)) + data.xml.appendLine(TranslatorTags.ORIGINATOR_SYSTEM_TITLE, None, GXByteBuffer.hex(p.systemTitle, False)) + data.xml.appendLine(TranslatorTags.RECIPIENT_SYSTEM_TITLE, None, GXByteBuffer.hex(p.recipientSystemTitle, False)) + data.xml.appendLine(TranslatorTags.DATE_TIME, None, GXByteBuffer.hex(p.dateTime, False)) + data.xml.appendLine(TranslatorTags.OTHER_INFORMATION, None, GXByteBuffer.hex(p.otherInformation, False)) + data.xml.appendStartTag(TranslatorTags.KEY_INFO) + data.xml.appendStartTag(TranslatorTags.AGREED_KEY) + data.xml.appendLine(TranslatorTags.KEY_PARAMETERS, None, data.xml.integerToHex(p.keyParameters, 2, True)) + data.xml.appendLine(TranslatorTags.KEY_CIPHERED_DATA, None, GXByteBuffer.hex(p.keyCipheredData, False)) + data.xml.appendEndTag(TranslatorTags.AGREED_KEY) + data.xml.appendEndTag(TranslatorTags.KEY_INFO) + data.xml.appendLine(TranslatorTags.CIPHERED_CONTENT, None, GXByteBuffer.hex(p.cipheredContent, False)) + data.xml.appendEndTag(Command.GENERAL_CIPHERING) + + @classmethod + def getValueFromData(cls, settings, reply): + data = reply.data + info = _GXDataInfo() + if isinstance(reply.value, list): + info.type_ = DataType.ARRAY + info.count = reply.totalCount + info.index = reply.getCount() + index = data.position + data.position = reply.readPosition + try: + value = _GXCommon.getData(settings, data, info) + if value is not None: + if not isinstance(value, list): + reply.valueType = info.type_ + reply.value = value + reply.totalCount = 0 + reply.readPosition = data.position + else: + if value: + if reply.value is None: + reply.value = value + else: + list_ = list() + list_ += reply.value + list_ += value + reply.value = list_ + reply.readPosition = data.position + reply.totalCount = info.count + elif info.complete and reply.command == Command.DATA_NOTIFICATION: + reply.readPosition = data.position + finally: + data.position = index + if reply.command != Command.DATA_NOTIFICATION and info.complete and reply.moreData == RequestTypes.NONE: + if settings: + settings.resetBlockIndex() + data.position = 0 + + @classmethod + def getData(cls, settings, reply, data, notify): + frame_ = 0 + isLast = True + isNotify = False + target = data + index = reply.position + if settings.interfaceType == InterfaceType.HDLC or settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + frame_ = GXDLMS.getHdlcData(settings.isServer, settings, reply, target, notify) + isLast = (frame_ & 0x10) != 0 + if notify and frame_ in (0x13, 0x3): + target = notify + isNotify = True + target.frameId = frame_ + elif settings.interfaceType == InterfaceType.WRAPPER: + if not GXDLMS.__getTcpData(settings, reply, target, notify): + target = notify + isNotify = True + elif settings.interfaceType == InterfaceType.WIRELESS_MBUS: + GXDLMS.__getWirelessMBusData(settings, reply, target) + elif settings.interfaceType == InterfaceType.PDU: + target.packetLength = len(reply) + target.complete = target.packetLength != 0 + elif settings.interfaceType == InterfaceType.PLC: + GXDLMS.__getPlcData(settings, reply, data) + elif settings.interfaceType == InterfaceType.PLC_HDLC: + frame_ = GXDLMS.__getPlcHdlcData(settings, reply, data) + elif settings.interfaceType == InterfaceType.WIRED_MBUS: + GXDLMS.__getWiredMBusData(settings, reply, data) + else: + raise ValueError("Invalid Interface type.") + if not target.complete: + reply.position = index + return False + if settings.interfaceType != InterfaceType.PLC_HDLC: + GXDLMS.getDataFromFrame(reply, target, GXDLMS.useHdlc(settings.interfaceType)) + #If keepalive or get next frame request. + if data.xml or ((frame_ not in (0x13, 0x3) or data.isMoreData()) and (frame_ & 0x1) != 0): + if frame_ == 0x3 and target.isMoreData(): + tmp = GXDLMS.getData(settings, reply, data, notify) + target.data.position = 0 + return tmp + return True + + if frame_ == 0x13 and not target.isMoreData(): + target.data.position = 0 + + GXDLMS.getPdu(settings, target) + if notify and not isNotify: + #Check command to make sure it's not notify message. + if data.command in (Command.DATA_NOTIFICATION, + Command.GLO_EVENT_NOTIFICATION, + Command.INFORMATION_REPORT, + Command.EVENT_NOTIFICATION, + Command.DED_INFORMATION_REPORT_REQUEST, + Command.DED_EVENT_NOTIFICATION): + isNotify = True + notify.complete = data.complete + notify.moreData = data.moreData + notify.command = data.command + data.command = Command.NONE + notify.time = data.time + data.time = None + notify.data.set(data.data) + data.data.trim() + notify.value = data.value + data.value = None + if not isLast or (data.moreData == RequestTypes.GBT and reply.available() != 0): + return GXDLMS.getData(settings, reply, data, notify) + if isNotify: + return False + return True + + @classmethod + def getDataFromFrame(cls, reply, info, hdlc): + data = info.data + offset = len(data) + cnt = info.packetLength - reply.position + if cnt != 0: + data.capacity = offset + cnt + data.set(reply, reply.position, cnt) + if hdlc: + reply.position = reply.position + 3 + data.position = offset + + @classmethod + def getDataFromBlock(cls, data, index): + # pylint: disable=protected-access + if len(data) == data.position: + data.clear() + return 0 + len_ = data.position - index + data._data[data.position - len_:data.position] = data._data[data.position : len(data)] + data.position = data.position - len_ + data.size = len(data) - len_ + return len + + @classmethod + def getActionInfo(cls, objectType, value, count): + if objectType == ObjectType.IMAGE_TRANSFER: + value[0] = 0x40 + count[0] = 4 + elif objectType == ObjectType.ACTIVITY_CALENDAR: + value[0] = 0x50 + count[0] = 1 + elif objectType == ObjectType.ASSOCIATION_LOGICAL_NAME: + value[0] = 0x60 + count[0] = 4 + elif objectType == ObjectType.ASSOCIATION_SHORT_NAME: + value[0] = 0x20 + count[0] = 8 + elif objectType == ObjectType.CLOCK: + value[0] = 0x60 + count[0] = 6 + value[0] = 0x48 + count[0] = 2 + value[0] = 0x38 + count[0] = 1 + elif objectType == ObjectType.IP4_SETUP: + value[0] = 0x60 + count[0] = 3 + elif objectType == ObjectType.MBUS_SLAVE_PORT_SETUP: + value[0] = 0x60 + count[0] = 8 + elif objectType == ObjectType.PROFILE_GENERIC: + value[0] = 0x58 + count[0] = 4 + value[0] = 0x28 + count[0] = 1 + value[0] = 0x30 + count[0] = 3 + value[0] = 0x28 + count[0] = 2 + elif objectType == ObjectType.SAP_ASSIGNMENT: + pass + elif objectType == ObjectType.SCRIPT_TABLE: + value[0] = 0x20 + count[0] = 1 + elif objectType == ObjectType.SPECIAL_DAYS_TABLE: + value[0] = 0x10 + count[0] = 2 + elif objectType == ObjectType.SECURITY_SETUP: + value[0] = 0x30 + count[0] = 8 + elif objectType == ObjectType.DISCONNECT_CONTROL: + value[0] = 0x20 + count[0] = 2 + elif objectType == ObjectType.PUSH_SETUP: + value[0] = 0x38 + count[0] = 1 + else: + count[0] = 0 + value[0] = 0 + + @classmethod + def parseSnrmUaResponse(cls, data, settings): + if data.available() != 0: + data.getUInt8() + data.getUInt8() + data.getUInt8() + while data.position < len(data): + id_ = data.getUInt8() + len2 = data.getUInt8() + if len2 == 1: + val = data.getUInt8() + elif len2 == 2: + val = data.getUInt16() + elif len2 == 4: + val = data.getUInt32() + else: + raise Exception("Invalid Exception.") + if id_ == _HDLCInfo.MAX_INFO_RX: + settings.maxInfoTX = val + elif id_ == _HDLCInfo.MAX_INFO_TX: + settings.maxInfoRX = val + elif id_ == _HDLCInfo.WINDOW_SIZE_RX: + settings.windowSizeTX = val + elif id_ == _HDLCInfo.WINDOW_SIZE_TX: + settings.windowSizeRX = val + else: + raise Exception("Invalid UA response.") + + @classmethod + def appendHdlcParameter(cls, data, value): + if value < 0x100: + data.setUInt8(1) + data.setUInt8(value) + else: + data.setUInt8(2) + data.setUInt16(value) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSAccessItem.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSAccessItem.py new file mode 100644 index 0000000..25232c3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSAccessItem.py @@ -0,0 +1,57 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums.AccessServiceCommandType import AccessServiceCommandType +# +# Access item is used to generate Access Service message. +# +class GXDLMSAccessItem: + # + # Constructor. + # + # @param commandType + # Command to execute. + # @param targetObject + # COSEM target object. + # @param attributeIndex + # Attribute index. + # + def __init__(self, commandType, targetObject, attributeIndex): + if commandType == AccessServiceCommandType.GET: + self.command = 1 + elif commandType == AccessServiceCommandType.SET: + self.command = 2 + elif commandType == AccessServiceCommandType.ACTION: + self.command = 3 + self.target = targetObject + self.index = attributeIndex diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSChippering.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSChippering.py new file mode 100644 index 0000000..e03fbc8 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSChippering.py @@ -0,0 +1,276 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums.Security import Security +from .GXByteBuffer import GXByteBuffer +from .CountType import CountType +from .objects.enums.SecuritySuite import SecuritySuite +from .GXDLMSChipperingStream import GXDLMSChipperingStream +from .internal._GXCommon import _GXCommon +from .enums.Command import Command + +#pylint: disable=too-many-instance-attributes,too-many-public-methods +class GXDLMSChippering: + + # + # * Get nonse from frame counter and system title. + # * @param invocationCounter Invocation counter. + # * @param systemTitle System title. + # * @return Generated nonse. + # + @classmethod + def getNonse(cls, invocationCounter, systemTitle): + nonce = bytearray(12) + nonce[0:8] = systemTitle + nonce[8] = ((invocationCounter >> 24) & 0xFF) + nonce[9] = ((invocationCounter >> 16) & 0xFF) + nonce[10] = ((invocationCounter >> 8) & 0xFF) + nonce[11] = (invocationCounter & 0xFF) + return nonce + + @classmethod + def encryptAesGcm(cls, p, plainText): + p.countTag = None + data = GXByteBuffer() + if p.type_ == CountType.PACKET: + data.setUInt8(p.security) + tmp = bytearray(4) + invocationCounter = 0 + if p.securitySuite == SecuritySuite.AES_GCM_128: + invocationCounter = p.invocationCounter + tmp[0] = ((invocationCounter >> 24) & 0xFF) + tmp[1] = ((invocationCounter >> 16) & 0xFF) + tmp[2] = ((invocationCounter >> 8) & 0xFF) + tmp[3] = (invocationCounter & 0xFF) + aad = cls.getAuthenticatedData(p, plainText) + iv = cls.getNonse(invocationCounter, p.systemTitle) + gcm = GXDLMSChipperingStream(p.security, True, p.blockCipherKey, aad, iv, None) + # Encrypt the secret message + if p.security != Security.AUTHENTICATION: + gcm.write(plainText) + ciphertext = gcm.flushFinalBlock() + if p.security == Security.AUTHENTICATION: + if p.type_ == CountType.PACKET: + data.set(tmp) + if (p.type_ & CountType.DATA) != 0: + data.set(plainText) + if (p.type_ & CountType.TAG) != 0: + p.countTag = gcm.tag + data.set(p.countTag) + elif p.security == Security.ENCRYPTION: + if p.type_ == CountType.PACKET: + data.set(tmp) + data.set(ciphertext) + elif p.security == Security.AUTHENTICATION_ENCRYPTION: + if p.type_ == CountType.PACKET: + data.set(tmp) + if (p.type_ & CountType.DATA) != 0: + data.set(ciphertext) + if (p.type_ & CountType.TAG) != 0: + p.countTag = gcm.tag + data.set(p.countTag) + else: + raise ValueError("security") + if p.type_ == CountType.PACKET: + tmp2 = GXByteBuffer(10 + len(data)) + tmp2.setUInt8(p.tag) + if p.tag == Command.GENERAL_GLO_CIPHERING or p.tag == Command.GENERAL_DED_CIPHERING or p.tag == Command.DATA_NOTIFICATION: + if not p.ignoreSystemTitle: + _GXCommon.setObjectCount(len(p.systemTitle), tmp2) + tmp2.set(p.systemTitle) + else: + tmp2.setUInt8(0) + _GXCommon.setObjectCount(len(data), tmp2) + tmp2.set(data, 0, len(data)) + data = tmp2 + crypted = data.array() + return crypted + + @classmethod + def getAuthenticatedData(cls, p, plainText): + data = GXByteBuffer() + sc = p.security | p.securitySuite + if p.security == Security.AUTHENTICATION: + data.setUInt8(sc) + data.set(p.authenticationKey) + data.set(plainText) + elif p.security == Security.AUTHENTICATION_ENCRYPTION: + data.setUInt8(sc) + data.set(p.authenticationKey) + if p.securitySuite != SecuritySuite.AES_GCM_128: + # transaction-id + transactionId = GXByteBuffer() + transactionId.setUInt64(p.invocationCounter) + data.setUInt8(8) + data.set(transactionId) + # originator-system-title + _GXCommon.setObjectCount(len(p.systemTitle), data) + data.set(p.systemTitle) + # recipient-system-title + _GXCommon.setObjectCount(len(p.recipientSystemTitle), data) + data.set(p.recipientSystemTitle) + # date-time not present + data.setUInt8(0) + # other-information not present + data.setUInt8(0) + elif p.security == Security.ENCRYPTION: + data.set(p.authenticationKey) + return data.array() + + # + # * Decrypt data. + # * + # * @param c + # * Cipher settings. + # * @param p + # * GMAC Parameter. + # * @return Encrypted data. + # + @classmethod + def decryptAesGcm(cls, p, data): + # pylint: disable=too-many-locals + if not data or len(data) - data.position < 2: + raise ValueError("cryptedData") + tmp = [] + len_ = 0 + cmd = data.getUInt8() + if cmd in (Command.GENERAL_GLO_CIPHERING, Command.GENERAL_DED_CIPHERING): + len_ = _GXCommon.getObjectCount(data) + if len_ != 0: + title = bytearray(len_) + data.get(title) + p.systemTitle = title + if p.xml and p.xml.comments: + p.xml.appendComment(_GXCommon.systemTitleToString(0, p.systemTitle)) + elif cmd in (Command.GENERAL_CIPHERING, Command.GLO_INITIATE_REQUEST, Command.GLO_INITIATE_RESPONSE, Command.GLO_READ_REQUEST, Command.GLO_READ_RESPONSE, \ + Command.GLO_WRITE_REQUEST, Command.GLO_WRITE_RESPONSE, Command.GLO_GET_REQUEST, Command.GLO_GET_RESPONSE, Command.GLO_SET_REQUEST, \ + Command.GLO_SET_RESPONSE, Command.GLO_METHOD_REQUEST, Command.GLO_METHOD_RESPONSE, Command.GLO_EVENT_NOTIFICATION,\ + Command.DED_GET_REQUEST, Command.DED_GET_RESPONSE, Command.DED_SET_REQUEST, Command.DED_SET_RESPONSE, Command.DED_METHOD_REQUEST,\ + Command.DED_METHOD_RESPONSE, Command.DED_EVENT_NOTIFICATION, Command.DED_READ_REQUEST, Command.DED_READ_RESPONSE, Command.DED_WRITE_REQUEST, \ + Command.DED_WRITE_RESPONSE, Command.GLO_CONFIRMED_SERVICE_ERROR, Command.DED_CONFIRMED_SERVICE_ERROR): + pass + else: + raise ValueError("cryptedData") + value = 0 + transactionId = 0 + if cmd == Command.GENERAL_CIPHERING: + len_ = _GXCommon.getObjectCount(data) + tmp = bytearray(len_) + data.get(tmp) + t = GXByteBuffer(tmp) + transactionId = t.getInt64() + len_ = _GXCommon.getObjectCount(data) + tmp = bytearray(len_) + data.get(tmp) + p.setSystemTitle(tmp) + len_ = _GXCommon.getObjectCount(data) + tmp = bytearray(len_) + data.get(tmp) + p.setRecipientSystemTitle(tmp) + # Get date time. + len_ = _GXCommon.getObjectCount(data) + if len_ != 0: + tmp = bytearray(len_) + data.get(tmp) + p.dateTime = tmp + # other-information + len_ = data.getUInt8() + if len_ != 0: + tmp = bytearray(len_) + data.get(tmp) + p.otherInformation = tmp + # KeyInfo OPTIONAL + len_ = data.getUInt8() + # AgreedKey CHOICE tag. + data.getUInt8() + # key-parameters + len_ = data.getUInt8() + value = data.getUInt8() + p.setKeyParameters(value) + if value == 1: + # KeyAgreement.ONE_PASS_DIFFIE_HELLMAN + # key-ciphered-data + len_ = _GXCommon.getObjectCount(data) + tmp = bytearray(len_) + data.get(tmp) + p.keyCipheredData = tmp + elif value == 2: + # KeyAgreement.STATIC_UNIFIED_MODEL + len_ = _GXCommon.getObjectCount(data) + if len_ != 0: + raise ValueError("Invalid key parameters") + else: + raise ValueError("key-parameters") + len_ = _GXCommon.getObjectCount(data) + p.cipheredContent = data.remaining() + sc = data.getUInt8() + security = sc & 0x30 + ss = sc & 0x3 + if ss != SecuritySuite.AES_GCM_128: + raise ValueError("Decrypt failed. Invalid security suite.") + p.security = security + invocationCounter = data.getUInt32() + p.invocationCounter = invocationCounter + tag = bytearray(12) + encryptedData = None + if security == Security.AUTHENTICATION: + len_ = len(data) - data.position - 12 + encryptedData = bytearray(len_) + data.get(encryptedData) + data.get(tag) + # Check tag. + cls.encryptAesGcm(p, encryptedData) + if not GXDLMSChipperingStream.tagsEquals(tag, p.countTag): + if transactionId != 0: + p.setInvocationCounter(transactionId) + if not p.xml: + raise ValueError("Decrypt failed. Invalid tag.") + p.xml.appendComment("Decrypt failed. Invalid tag.") + return encryptedData + ciphertext = None + if security == Security.ENCRYPTION: + len_ = len(data) - data.position + ciphertext = bytearray(len_) + data.get(ciphertext) + elif security == Security.AUTHENTICATION_ENCRYPTION: + len_ = len(data) - data.position - 12 + ciphertext = bytearray(len_) + data.get(ciphertext) + data.get(tag) + aad = cls.getAuthenticatedData(p, ciphertext) + iv = cls.getNonse(invocationCounter, p.systemTitle) + gcm = GXDLMSChipperingStream(security, True, p.blockCipherKey, aad, iv, tag) + gcm.write(ciphertext) + if transactionId != 0: + p.setInvocationCounter(transactionId) + return gcm.flushFinalBlock() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSChipperingStream.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSChipperingStream.py new file mode 100644 index 0000000..2a9a77d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSChipperingStream.py @@ -0,0 +1,953 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +from .GXByteBuffer import GXByteBuffer +from .enums.Security import Security + +# pylint: disable=too-many-public-methods,too-many-instance-attributes,too-many-arguments +class GXDLMSChipperingStream: + """ + Implements GMAC. This class is based to this doc: + http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf + """ + + # Consts. + BLOCK_SIZE = 16 + TAG_SIZE = 0x10 + IV = [0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6] + + #schedule Vector (powers of x). + R_CON = (0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,\ + 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc,\ + 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,\ + 0xc5, 0x91) + + #S box + S_BOX = (0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5,\ + 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82,\ + 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF,\ + 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F,\ + 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,\ + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12,\ + 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A,\ + 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3,\ + 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B,\ + 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF,\ + 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F,\ + 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D,\ + 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,\ + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7,\ + 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC,\ + 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E,\ + 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C,\ + 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8,\ + 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA,\ + 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6,\ + 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,\ + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35,\ + 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11,\ + 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55,\ + 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68,\ + 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16) + + #Inverse sbox + S_BOX_REVERSED = (0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5,\ + 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c,\ + 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43,\ + 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6,\ + 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3,\ + 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76,\ + 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6,\ + 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d,\ + 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9,\ + 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90,\ + 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58,\ + 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca,\ + 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a,\ + 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97,\ + 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74,\ + 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c,\ + 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5,\ + 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc,\ + 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0,\ + 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88,\ + 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec,\ + 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d,\ + 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b,\ + 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83,\ + 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6,\ + 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d) + + #Rijndael (AES) Encryption fast table. + AES = (0xa56363c6, 0x847c7cf8, 0x997777ee,\ + 0x8d7b7bf6, 0x0df2f2ff, 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591,\ + 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, 0x19fefee7,\ + 0x62d7d7b5, 0xe6abab4d, 0x9a7676ec, 0x45caca8f, 0x9d82821f,\ + 0x40c9c989, 0x877d7dfa, 0x15fafaef, 0xeb5959b2, 0xc947478e,\ + 0x0bf0f0fb, 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45,\ + 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, 0xc2b7b775,\ + 0x1cfdfde1, 0xae93933d, 0x6a26264c, 0x5a36366c, 0x413f3f7e,\ + 0x02f7f7f5, 0x4fcccc83, 0x5c343468, 0xf4a5a551, 0x34e5e5d1,\ + 0x08f1f1f9, 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a,\ + 0x0c040408, 0x52c7c795, 0x65232346, 0x5ec3c39d, 0x28181830,\ + 0xa1969637, 0x0f05050a, 0xb59a9a2f, 0x0907070e, 0x36121224,\ + 0x9b80801b, 0x3de2e2df, 0x26ebebcd, 0x6927274e, 0xcdb2b27f,\ + 0x9f7575ea, 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34,\ + 0x2d1b1b36, 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, 0xf65252a4,\ + 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, 0x7b292952, 0x3ee3e3dd,\ + 0x712f2f5e, 0x97848413, 0xf55353a6, 0x68d1d1b9, 0x00000000,\ + 0x2cededc1, 0x60202040, 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6,\ + 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, 0xde4a4a94,\ + 0xd44c4c98, 0xe85858b0, 0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5,\ + 0xe5aaaa4f, 0x16fbfbed, 0xc5434386, 0xd74d4d9a, 0x55333366,\ + 0x94858511, 0xcf45458a, 0x10f9f9e9, 0x06020204, 0x817f7ffe,\ + 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, 0xf35151a2,\ + 0xfea3a35d, 0xc0404080, 0x8a8f8f05, 0xad92923f, 0xbc9d9d21,\ + 0x48383870, 0x04f5f5f1, 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf,\ + 0x63212142, 0x30101020, 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf,\ + 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, 0xe15f5fbe,\ + 0xa2979735, 0xcc444488, 0x3917172e, 0x57c4c493, 0xf2a7a755,\ + 0x827e7efc, 0x473d3d7a, 0xac6464c8, 0xe75d5dba, 0x2b191932,\ + 0x957373e6, 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3,\ + 0x66222244, 0x7e2a2a54, 0xab90903b, 0x8388880b, 0xca46468c,\ + 0x29eeeec7, 0xd3b8b86b, 0x3c141428, 0x79dedea7, 0xe25e5ebc,\ + 0x1d0b0b16, 0x76dbdbad, 0x3be0e0db, 0x56323264, 0x4e3a3a74,\ + 0x1e0a0a14, 0xdb494992, 0x0a06060c, 0x6c242448, 0xe45c5cb8,\ + 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, 0xa8919139,\ + 0xa4959531, 0x37e4e4d3, 0x8b7979f2, 0x32e7e7d5, 0x43c8c88b,\ + 0x5937376e, 0xb76d6dda, 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c,\ + 0xe0a9a949, 0xb46c6cd8, 0xfa5656ac, 0x07f4f4f3, 0x25eaeacf,\ + 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, 0xd5baba6f,\ + 0x887878f0, 0x6f25254a, 0x722e2e5c, 0x241c1c38, 0xf1a6a657,\ + 0xc7b4b473, 0x51c6c697, 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8,\ + 0x211f1f3e, 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f,\ + 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, 0xd8484890,\ + 0x05030306, 0x01f6f6f7, 0x120e0e1c, 0xa36161c2, 0x5f35356a,\ + 0xf95757ae, 0xd0b9b969, 0x91868617, 0x58c1c199, 0x271d1d3a,\ + 0xb99e9e27, 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122,\ + 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, 0xa7949433, 0xb69b9b2d,\ + 0x221e1e3c, 0x92878715, 0x20e9e9c9, 0x49cece87, 0xff5555aa,\ + 0x78282850, 0x7adfdfa5, 0x8f8c8c03, 0xf8a1a159, 0x80898909,\ + 0x170d0d1a, 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0,\ + 0xc3414182, 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b,\ + 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c) + + AES1_REVERSED = (0x50a7f451, 0x5365417e,\ + 0xc3a4171a, 0x965e273a, 0xcb6bab3b, 0xf1459d1f, 0xab58faac,\ + 0x9303e34b, 0x55fa3020, 0xf66d76ad, 0x9176cc88, 0x254c02f5,\ + 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, 0x8fa362b5, 0x495ab1de,\ + 0x671bba25, 0x980eea45, 0xe1c0fe5d, 0x02752fc3, 0x12f04c81,\ + 0xa397468d, 0xc6f9d36b, 0xe75f8f03, 0x959c9215, 0xeb7a6dbf,\ + 0xda595295, 0x2d83bed4, 0xd3217458, 0x2969e049, 0x44c8c98e,\ + 0x6a89c275, 0x78798ef4, 0x6b3e5899, 0xdd71b927, 0xb64fe1be,\ + 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, 0x184adf63, 0x82311ae5,\ + 0x60335197, 0x457f5362, 0xe07764b1, 0x84ae6bbb, 0x1ca081fe,\ + 0x942b08f9, 0x58684870, 0x19fd458f, 0x876cde94, 0xb7f87b52,\ + 0x23d373ab, 0xe2024b72, 0x578f1fe3, 0x2aab5566, 0x0728ebb2,\ + 0x03c2b52f, 0x9a7bc586, 0xa50837d3, 0xf2872830, 0xb2a5bf23,\ + 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, 0x92b479a7, 0xf0f207f3,\ + 0xa1e2694e, 0xcdf4da65, 0xd5be0506, 0x1f6234d1, 0x8afea6c4,\ + 0x9d532e34, 0xa055f3a2, 0x32e18a05, 0x75ebf6a4, 0x39ec830b,\ + 0xaaef6040, 0x069f715e, 0x51106ebd, 0xf98a213e, 0x3d06dd96,\ + 0xae053edd, 0x46bde64d, 0xb58d5491, 0x055dc471, 0x6fd40604,\ + 0xff155060, 0x24fb9819, 0x97e9bdd6, 0xcc434089, 0x779ed967,\ + 0xbd42e8b0, 0x888b8907, 0x385b19e7, 0xdbeec879, 0x470a7ca1,\ + 0xe90f427c, 0xc91e84f8, 0x00000000, 0x83868009, 0x48ed2b32,\ + 0xac70111e, 0x4e725a6c, 0xfbff0efd, 0x5638850f, 0x1ed5ae3d,\ + 0x27392d36, 0x64d90f0a, 0x21a65c68, 0xd1545b9b, 0x3a2e3624,\ + 0xb1670a0c, 0x0fe75793, 0xd296eeb4, 0x9e919b1b, 0x4fc5c080,\ + 0xa220dc61, 0x694b775a, 0x161a121c, 0x0aba93e2, 0xe52aa0c0,\ + 0x43e0223c, 0x1d171b12, 0x0b0d090e, 0xadc78bf2, 0xb9a8b62d,\ + 0xc8a91e14, 0x8519f157, 0x4c0775af, 0xbbdd99ee, 0xfd607fa3,\ + 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 0x347efb5b, 0x7629438b,\ + 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, 0xcadc31d7, 0x10856342,\ + 0x40229713, 0x2011c684, 0x7d244a85, 0xf83dbbd2, 0x1132f9ae,\ + 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, 0xec52860d, 0xd0e3c177,\ + 0x6c16b32b, 0x99b970a9, 0xfa489411, 0x2264e947, 0xc48cfca8,\ + 0x1a3ff0a0, 0xd82c7d56, 0xef903322, 0xc74e4987, 0xc1d138d9,\ + 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, 0x28de7aa5, 0x268eb7da,\ + 0xa4bfad3f, 0xe49d3a2c, 0x0d927850, 0x9bcc5f6a, 0x62467e54,\ + 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 0xf5afc382, 0xbe805d9f,\ + 0x7c93d069, 0xa92dd56f, 0xb31225cf, 0x3b99acc8, 0xa77d1810,\ + 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, 0xf418596e, 0x01b79aec,\ + 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, 0x08cfbc21, 0xe6e815ef,\ + 0xd99be7ba, 0xce366f4a, 0xd4099fea, 0xd67cb029, 0xafb2a431,\ + 0x31233f2a, 0x3094a5c6, 0xc066a235, 0x37bc4e74, 0xa6ca82fc,\ + 0xb0d090e0, 0x15d8a733, 0x4a9804f1, 0xf7daec41, 0x0e50cd7f,\ + 0x2ff69117, 0x8dd64d76, 0x4db0ef43, 0x544daacc, 0xdf0496e4,\ + 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1, 0x7f516546, 0x04ea5e9d,\ + 0x5d358c01, 0x737487fa, 0x2e410bfb, 0x5a1d67b3, 0x52d2db92,\ + 0x335610e9, 0x1347d66d, 0x8c61d79a, 0x7a0ca137, 0x8e14f859,\ + 0x893c13eb, 0xee27a9ce, 0x35c961b7, 0xede51ce1, 0x3cb1477a,\ + 0x59dfd29c, 0x3f73f255, 0x79ce1418, 0xbf37c773, 0xeacdf753,\ + 0x5baafd5f, 0x146f3ddf, 0x86db4478, 0x81f3afca, 0x3ec468b9,\ + 0x2c342438, 0x5f40a3c2, 0x72c31d16, 0x0c25e2bc, 0x8b493c28,\ + 0x41950dff, 0x7101a839, 0xdeb30c08, 0x9ce4b4d8, 0x90c15664,\ + 0x6184cb7b, 0x70b632d5, 0x745c6c48, 0x4257b8d0) + + blockSize = 16 + + # + # * Constructor. + # * + # * @param forSecurity + # * Used security level. + # * @param forEncrypt + # * @param blockCipherKey + # * @param forAad + # * @param iv + # * @param forTag + # + def __init__(self, forSecurity, forEncrypt, blockCipherKey, forAad, iv, forTag): + self.security = forSecurity + self.tag = forTag + if not self.tag: + # Tag size is 12 bytes. + self.tag = bytearray(12) + elif len(self.tag) != 12: + raise ValueError("Invalid tag.") + self.encrypt = forEncrypt + self.workingKey = self.generateKey(forEncrypt, blockCipherKey) + if self.encrypt: + bufLength = GXDLMSChipperingStream.BLOCK_SIZE + else: + bufLength = (GXDLMSChipperingStream.BLOCK_SIZE + GXDLMSChipperingStream.TAG_SIZE) + self.bufBlock = bytearray(bufLength) + self.aad = forAad + self.h = bytearray(GXDLMSChipperingStream.BLOCK_SIZE) + if iv: + self.processBlock(self.h, 0, self.h, 0) + self.mArray = [[None] * 32] * 32 + self.init(self.h) + self.j0 = bytearray(16) + self.j0[0:len(iv)] = iv[0:] + self.j0[15] = 0x01 + if self.aad: + self.s = self.getGHash(self.aad) + if iv: + self.counter = self.clone(self.j0) + self.bytesRemaining = 0 + self.totalLength = 0 + self.output = GXByteBuffer() + self.c0 = self.c1 = self.c2 = self.c3 = 0 + self.blockSize = 16 + + @classmethod + def clone(cls, value): + """ + Clone byte array. + """ + tmp = value[0:] + return tmp + + # + # * Convert byte array to Little Endian. + # * + # * @param data + # * @param offset + # * @return + # + @classmethod + def toUInt32(cls, value, offset): + tmp = value[offset] & 0xFF + tmp |= (value[offset + 1] << 8) & 0xFF00 + tmp |= (value[offset + 2] << 16) & 0xFF0000 + tmp |= (value[offset + 3] << 24) & 0xFF000000 + return tmp + + @classmethod + def subWord(cls, value): + tmp = cls.S_BOX[value & 0xFF] & 0xFF + tmp |= (((cls.S_BOX[(value >> 8) & 0xFF]) & 0xFF) << 8) & 0xFF00 + tmp |= (((cls.S_BOX[(value >> 16) & 0xFF]) & 0xFF) << 16) & 0xFF0000 + tmp |= (((cls.S_BOX[(value >> 24) & 0xFF]) & 0xFF) << 24) & 0xFF000000 + return tmp + + @classmethod + def shift(cls, value, shift): + """ + Shift value. + """ + return (value >> shift) | (value << (32 - shift)) & 0xFFFFFFFF + + # + # * Initialise the key schedule from the user supplied key. + # * + # * @return + # + @classmethod + def starX(cls, value): + m1 = 0x80808080 + m2 = 0x7f7f7f7f + m3 = 0x0000001b + return ((value & m2) << 1) ^ (((value & m1) >> 7) * m3) + + def imixCol(self, x): + f2 = self.starX(x) + f4 = self.starX(f2) + f8 = self.starX(f4) + f9 = x ^ f8 + return f2 ^ f4 ^ f8 ^ self.shift(f2 ^ f9, 8) ^ self.shift(f4 ^ f9, 16) ^ self.shift(f9, 24) + + # + # * Get bytes from UIn32. + # * + # * @param value + # * @param data + # * @param offset + # + @classmethod + def getUInt32(cls, value, data, offset): + data[offset] = value & 0xFF + data[offset + 1] = (value >> 8) & 0xFF + data[offset + 2] = (value >> 16) & 0xFF + data[offset + 3] = (value >> 24) & 0xFF + + def unPackBlock(self, bytes_, offset): + self.c0 = self.toUInt32(bytes_, offset) + self.c1 = self.toUInt32(bytes_, offset + 4) + self.c2 = self.toUInt32(bytes_, offset + 8) + self.c3 = self.toUInt32(bytes_, offset + 12) + + def packBlock(self, bytes_, offset): + self.getUInt32(self.c0, bytes_, offset) + self.getUInt32(self.c1, bytes_, offset + 4) + self.getUInt32(self.c2, bytes_, offset + 8) + self.getUInt32(self.c3, bytes_, offset + 12) + + # + # * Encrypt data block. + # * + # * @param key + # + def encryptBlock(self, key): + r = 1 + self.c0 ^= key[0][0] + self.c1 ^= key[0][1] + self.c2 ^= key[0][2] + self.c3 ^= key[0][3] + while r < self.rounds - 1: + r0 = (self.AES[self.c0 & 0xFF] & 0xFFFFFFFF) + r0 ^= (self.shift(self.AES[(self.c1 >> 8) & 0xFF], 24) & 0xFFFFFFFF) + r0 ^= (self.shift(self.AES[(self.c2 >> 16) & 0xFF], 16) & 0xFFFFFFFF) + r0 ^= (self.shift(self.AES[(self.c3 >> 24) & 0xFF], 8) & 0xFFFFFFFF) + r0 ^= (key[r][0] & 0xFFFFFFFF) + r1 = (self.AES[self.c1 & 0xFF] & 0xFFFFFFFF) + r1 ^= self.shift(self.AES[(self.c2 >> 8) & 0xFF], 24) & 0xFFFFFFFF + r1 ^= self.shift(self.AES[(self.c3 >> 16) & 0xFF], 16) & 0xFFFFFFFF + r1 ^= self.shift(self.AES[(self.c0 >> 24) & 0xFF], 8) & 0xFFFFFFFF + r1 ^= key[r][1] & 0xFFFFFFFF + r2 = self.AES[self.c2 & 0xFF] & 0xFFFFFFFF + r2 ^= self.shift(self.AES[(self.c3 >> 8) & 0xFF], 24) & 0xFFFFFFFF + r2 ^= self.shift(self.AES[(self.c0 >> 16) & 0xFF], 16) & 0xFFFFFFFF + r2 ^= self.shift(self.AES[(self.c1 >> 24) & 0xFF], 8) & 0xFFFFFFFF + r2 ^= key[r][2] & 0xFFFFFFFF + r3 = self.AES[self.c3 & 0xFF] & 0xFFFFFFFF + r3 ^= self.shift(self.AES[(self.c0 >> 8) & 0xFF], 24) & 0xFFFFFFFF + r3 ^= self.shift(self.AES[(self.c1 >> 16) & 0xFF], 16) & 0xFFFFFFFF + r3 ^= self.shift(self.AES[(self.c2 >> 24) & 0xFF], 8) & 0xFFFFFFFF + r3 ^= key[r][3] & 0xFFFFFFFF + r = r + 1 + self.c0 = self.AES[r0 & 0xFF] & 0xFFFFFFFF + self.c0 ^= self.shift(self.AES[(r1 >> 8) & 0xFF], 24) & 0xFFFFFFFF + self.c0 ^= self.shift(self.AES[(r2 >> 16) & 0xFF], 16) & 0xFFFFFFFF + self.c0 ^= self.shift(self.AES[(r3 >> 24) & 0xFF], 8) & 0xFFFFFFFF + self.c0 ^= key[r][0] & 0xFFFFFFFF + self.c1 = self.AES[r1 & 0xFF] & 0xFFFFFFFF + self.c1 ^= self.shift(self.AES[(r2 >> 8) & 0xFF], 24) & 0xFFFFFFFF + self.c1 ^= self.shift(self.AES[(r3 >> 16) & 0xFF], 16) & 0xFFFFFFFF + self.c1 ^= self.shift(self.AES[(r0 >> 24) & 0xFF], 8) & 0xFFFFFFFF + self.c1 ^= key[r][1] & 0xFFFFFFFF + self.c2 = self.AES[r2 & 0xFF] & 0xFFFFFFFF + self.c2 ^= self.shift(self.AES[(r3 >> 8) & 0xFF], 24) & 0xFFFFFFFF + self.c2 ^= self.shift(self.AES[(r0 >> 16) & 0xFF], 16) & 0xFFFFFFFF + self.c2 ^= self.shift(self.AES[(r1 >> 24) & 0xFF], 8) & 0xFFFFFFFF + self.c2 ^= key[r][2] & 0xFFFFFFFF + self.c3 = self.AES[r3 & 0xFF] & 0xFFFFFFFF + self.c3 ^= self.shift(self.AES[(r0 >> 8) & 0xFF], 24) & 0xFFFFFFFF + self.c3 ^= self.shift(self.AES[(r1 >> 16) & 0xFF], 16) & 0xFFFFFFFF + self.c3 ^= self.shift(self.AES[(r2 >> 24) & 0xFF], 8) & 0xFFFFFFFF + self.c3 ^= key[r][3] & 0xFFFFFFFF + r = r + 1 + + r0 = self.AES[self.c0 & 0xFF] & 0xFFFFFFFF + r0 ^= self.shift(self.AES[(self.c1 >> 8) & 0xFF], 24) & 0xFFFFFFFF + r0 ^= self.shift(self.AES[(self.c2 >> 16) & 0xFF], 16) & 0xFFFFFFFF + r0 ^= self.shift(self.AES[self.c3 >> 24], 8) & 0xFFFFFFFF + r0 ^= key[r][0] & 0xFFFFFFFF + r1 = self.AES[self.c1 & 0xFF] & 0xFFFFFFFF + r1 ^= self.shift(self.AES[(self.c2 >> 8) & 0xFF], 24) & 0xFFFFFFFF + r1 ^= self.shift(self.AES[(self.c3 >> 16) & 0xFF], 16) & 0xFFFFFFFF + r1 ^= self.shift(self.AES[self.c0 >> 24], 8) & 0xFFFFFFFF + r1 ^= key[r][1] & 0xFFFFFFFF + r2 = self.AES[self.c2 & 0xFF] & 0xFFFFFFFF + r2 ^= self.shift(self.AES[(self.c3 >> 8) & 0xFF], 24) & 0xFFFFFFFF + r2 ^= self.shift(self.AES[(self.c0 >> 16) & 0xFF], 16) & 0xFFFFFFFF + r2 ^= self.shift(self.AES[self.c1 >> 24], 8) & 0xFFFFFFFF + r2 ^= key[r][2] & 0xFFFFFFFF + r3 = self.AES[self.c3 & 0xFF] & 0xFFFFFFFF + r3 ^= self.shift(self.AES[(self.c0 >> 8) & 0xFF], 24) & 0xFFFFFFFF + r3 ^= self.shift(self.AES[(self.c1 >> 16) & 0xFF], 16) & 0xFFFFFFFF + r3 ^= self.shift(self.AES[self.c2 >> 24], 8) & 0xFFFFFFFF + r3 ^= key[r][3] & 0xFFFFFFFF + r += 1 + self.c0 = (self.S_BOX[r0 & 0xFF] & 0xFF) & 0xFFFFFFFF + self.c0 ^= ((self.S_BOX[(r1 >> 8) & 0xFF] & 0xFF) << 8) & 0xFFFFFFFF + self.c0 ^= ((self.S_BOX[(r2 >> 16) & 0xFF] & 0xFF) << 16) & 0xFFFFFFFF + self.c0 ^= ((self.S_BOX[r3 >> 24] & 0xFF) << 24) & 0xFFFFFFFF + self.c0 ^= key[r][0] & 0xFFFFFFFF + self.c1 = (self.S_BOX[r1 & 0xFF] & 0xFF) & 0xFFFFFFFF + self.c1 ^= ((self.S_BOX[(r2 >> 8) & 0xFF] & 0xFF) << 8) & 0xFFFFFFFF + self.c1 ^= ((self.S_BOX[(r3 >> 16) & 0xFF] & 0xFF) << 16) & 0xFFFFFFFF + self.c1 ^= ((self.S_BOX[r0 >> 24] & 0xFF) << 24) & 0xFFFFFFFF + self.c1 ^= key[r][1] & 0xFFFFFFFF + self.c2 = (self.S_BOX[r2 & 0xFF] & 0xFF) & 0xFFFFFFFF + self.c2 ^= ((self.S_BOX[(r3 >> 8) & 0xFF] & 0xFF) << 8) & 0xFFFFFFFF + self.c2 ^= ((self.S_BOX[(r0 >> 16) & 0xFF] & 0xFF) << 16) & 0xFFFFFFFF + self.c2 ^= ((self.S_BOX[r1 >> 24] & 0xFF) << 24) & 0xFFFFFFFF + self.c2 ^= key[r][2] & 0xFFFFFFFF + self.c3 = (self.S_BOX[r3 & 0xFF] & 0xFF) & 0xFFFFFFFF + self.c3 ^= ((self.S_BOX[(r0 >> 8) & 0xFF] & 0xFF) << 8) & 0xFFFFFFFF + self.c3 ^= ((self.S_BOX[(r1 >> 16) & 0xFF] & 0xFF) << 16) & 0xFFFFFFFF + self.c3 ^= ((self.S_BOX[r2 >> 24] & 0xFF) << 24) & 0xFFFFFFFF + self.c3 ^= key[r][3] & 0xFFFFFFFF + + def decryptBlock(self, key): + t0 = self.c0 ^ key[self.rounds][0] + t1 = self.c1 ^ key[self.rounds][1] + t2 = self.c2 ^ key[self.rounds][2] + r0 = r1 = r2 = 0 + r3 = self.c3 ^ key[self.rounds][3] + r = self.rounds - 1 + while r > 1: + r0 = (self.AES1_REVERSED[t0 & 255] & 0xFFFFFFFF) ^ self.shift(self.AES1_REVERSED[(r3 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(t2 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(t1 >> 24) & 255], 8) ^ key[r][0] + r1 = self.AES1_REVERSED[t1 & 255] ^ self.shift(self.AES1_REVERSED[(t0 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(r3 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(t2 >> 24) & 255], 8) ^ key[r][1] + r2 = self.AES1_REVERSED[t2 & 255] ^ self.shift(self.AES1_REVERSED[(t1 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(t0 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(r3 >> 24) & 255], 8) ^ key[r][2] + r3 = self.AES1_REVERSED[r3 & 255] ^ self.shift(self.AES1_REVERSED[(t2 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(t1 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(t0 >> 24) & 255], 8) ^ key[r][3] + r -= 1 + t0 = self.AES1_REVERSED[r0 & 255] ^ self.shift(self.AES1_REVERSED[(r3 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(r2 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(r1 >> 24) & 255], 8) ^ key[r][0] + t1 = self.AES1_REVERSED[r1 & 255] ^ self.shift(self.AES1_REVERSED[(r0 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(r3 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(r2 >> 24) & 255], 8) ^ key[r][1] + t2 = self.AES1_REVERSED[r2 & 255] ^ self.shift(self.AES1_REVERSED[(r1 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(r0 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(r3 >> 24) & 255], 8) ^ key[r][2] + r3 = self.AES1_REVERSED[r3 & 255] ^ self.shift(self.AES1_REVERSED[(r2 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(r1 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(r0 >> 24) & 255], 8) ^ key[r][3] + r -= 1 + r = 1 + r0 = self.AES1_REVERSED[t0 & 255] ^ self.shift(self.AES1_REVERSED[(r3 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(t2 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(t1 >> 24) & 255], 8) ^ key[r][0] + r1 = self.AES1_REVERSED[t1 & 255] ^ self.shift(self.AES1_REVERSED[(t0 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(r3 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(t2 >> 24) & 255], 8) ^ key[r][1] + r2 = self.AES1_REVERSED[t2 & 255] ^ self.shift(self.AES1_REVERSED[(t1 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(t0 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(r3 >> 24) & 255], 8) ^ key[r][2] + r3 = self.AES1_REVERSED[r3 & 255] ^ self.shift(self.AES1_REVERSED[(t2 >> 8) & 255], 24) ^ self.shift(self.AES1_REVERSED[(t1 >> 16) & 255], 16) ^ self.shift(self.AES1_REVERSED[(t0 >> 24) & 255], 8) ^ key[r][3] + r = 0 + self.c0 = (self.S_BOX_REVERSED[r0 & 255] & 0xFF) ^ ((((self.S_BOX_REVERSED[(r3 >> 8) & 255]) & 0xFF) << 8)) ^ ((((self.S_BOX_REVERSED[(r2 >> 16) & 255]) & 0xFF) << 16)) ^ ((((self.S_BOX_REVERSED[(r1 >> 24) & 255]) & 0xFF) << 24)) ^ key[r][0] + self.c1 = (self.S_BOX_REVERSED[r1 & 255] & 0xFF) ^ (((self.S_BOX_REVERSED[(r0 >> 8) & 255]) & 0xFF) << 8) ^ (((self.S_BOX_REVERSED[(r3 >> 16) & 255]) & 0xFF) << 16) ^ (((self.S_BOX_REVERSED[(r2 >> 24) & 255]) & 0xFF) << 24) ^ key[r][1] + self.c2 = (self.S_BOX_REVERSED[r2 & 255] & 0xFF) ^ (((self.S_BOX_REVERSED[(r1 >> 8) & 255]) & 0xFF) << 8) ^ (((self.S_BOX_REVERSED[(r0 >> 16) & 255]) & 0xFF) << 16) ^ (((self.S_BOX_REVERSED[(r3 >> 24) & 255]) & 0xFF) << 24) ^ key[r][2] + self.c3 = (self.S_BOX_REVERSED[r3 & 255] & 0xFF) ^ (((self.S_BOX_REVERSED[(r2 >> 8) & 255]) & 0xFF) << 8) ^ (((self.S_BOX_REVERSED[(r1 >> 16) & 255]) & 0xFF) << 16) ^ (((self.S_BOX_REVERSED[(r0 >> 24) & 255]) & 0xFF) << 24) ^ key[r][3] + + def processBlock(self, input_, inOffset, forOutput, outOffset): + if inOffset + (32 / 2) > len(input_): + raise ValueError("input buffer too short") + if outOffset + (32 / 2) > len(forOutput): + raise ValueError("output buffer too short") + self.unPackBlock(input_, inOffset) + if self.encrypt: + self.encryptBlock(self.workingKey) + else: + self.decryptBlock(self.workingKey) + self.packBlock(forOutput, outOffset) + return self.BLOCK_SIZE + + @classmethod + def bEToUInt32(cls, buff, offset): + value = (buff[offset] << 24) + value |= (buff[offset + 1] << 16) & 0xFF0000 + value |= (buff[offset + 2] << 8) & 0xFF00 + value |= buff[offset + 3] & 0xFF + return value + + @classmethod + def shiftRight(cls, block, count): + bit = 0 + i = 0 + while i < 4: + b = block[i] + block[i] = (b >> count) | bit + bit = (b << (32 - count)) & 0xFFFFFFFF + i += 1 + + @classmethod + def multiplyP(cls, x): + lsb = (x[3] & 1) != 0 + cls.shiftRight(x, 1) + if lsb: + x[0] ^= 0xe1000000 + + @classmethod + def getUint128(cls, buff): + us = [None] * 4 + us[0] = cls.bEToUInt32(buff, 0) + us[1] = cls.bEToUInt32(buff, 4) + us[2] = cls.bEToUInt32(buff, 8) + us[3] = cls.bEToUInt32(buff, 12) + return us + + @classmethod + def xor(cls, block, value): + pos = 0 + while pos != 16: + block[pos] ^= value[pos] + pos += 1 + + @classmethod + def xor128(cls, block, value): + pos = 0 + while pos != 4: + block[pos] ^= value[pos] + pos += 1 + + @classmethod + def multiplyP8(cls, x): + lsw = x[3] + cls.shiftRight(x, 8) + pos = 0 + while pos != 8: + if (lsw & (1 << pos)) != 0: + x[0] ^= ((0xe1000000 >> (7 - pos)) & 0xFFFFFFFF) + pos += 1 + + def getGHash(self, b): + y = bytearray(16) + pos = 0 + while pos < len(b): + x = bytearray(16) + cnt = min(len(b) - pos, 16) + x[0:cnt] = b[pos:pos + cnt] + self.xor(y, x) + self.multiplyH(y) + pos += 16 + return y + + @classmethod + def uInt32ToBE(cls, value, buff, offset): + buff[offset] = (value >> 24) & 0xFF + buff[offset + 1] = (value >> 16) & 0xFF + buff[offset + 2] = (value >> 8) & 0xFF + buff[offset + 3] = value & 0xFF + + def multiplyH(self, value): + tmp = [0] * 4 + pos = 0 + while pos != 16: + m = self.mArray[pos + pos][value[pos] & 0x0f] + tmp[0] ^= m[0] + tmp[1] ^= m[1] + tmp[2] ^= m[2] + tmp[3] ^= m[3] + m = self.mArray[pos + pos + 1][(value[pos] & 0xf0) >> 4] + tmp[0] ^= m[0] + tmp[1] ^= m[1] + tmp[2] ^= m[2] + tmp[3] ^= m[3] + pos += 1 + self.uInt32ToBE(tmp[0], value, 0) + self.uInt32ToBE(tmp[1], value, 4) + self.uInt32ToBE(tmp[2], value, 8) + self.uInt32ToBE(tmp[3], value, 12) + + def init(self, value): + self.mArray[0] = [0] * 16 + self.mArray[1] = [0] * 16 + self.mArray[0][0] = [0] * 4 + self.mArray[1][0] = [0] * 4 + self.mArray[1][8] = self.getUint128(value) + tmp = [] + pos = 4 + while pos >= 1: + tmp = self.clone(self.mArray[1][pos + pos]) + self.multiplyP(tmp) + self.mArray[1][pos] = tmp + pos >>= 1 + tmp = self.clone(self.mArray[1][1]) + self.multiplyP(tmp) + self.mArray[0][8] = tmp + pos = 4 + while pos >= 1: + tmp = self.clone(self.mArray[0][pos + pos]) + self.multiplyP(tmp) + self.mArray[0][pos] = tmp + pos >>= 1 + pos1 = 0 + while True: + pos2 = 2 + while pos2 < 16: + k = 1 + while k < pos2: + tmp = self.clone(self.mArray[pos1][pos2]) + self.xor128(tmp, self.mArray[pos1][k]) + self.mArray[pos1][pos2 + k] = tmp + k += 1 + pos2 += pos2 + pos1 += 1 + if pos1 == 32: + return + if pos1 > 1: + self.mArray[pos1] = [0] * 16 + self.mArray[pos1][0] = [0] * 4 + pos = 8 + while pos > 0: + tmp = self.clone(self.mArray[pos1 - 2][pos]) + self.multiplyP8(tmp) + self.mArray[pos1][pos] = tmp + pos >>= 1 + + + def gCTRBlock(self, buf, bufCount): + i = 15 + while i >= 12: + self.counter[i] += 1 + if self.counter[i] != 0: + break + i -= 1 + tmp = bytearray(self.BLOCK_SIZE) + self.processBlock(self.counter, 0, tmp, 0) + if self.encrypt: + zeroes = bytearray(self.BLOCK_SIZE) + tmp[bufCount:self.BLOCK_SIZE] = zeroes[bufCount:self.BLOCK_SIZE] + hashBytes = tmp + else: + hashBytes = buf + pos = 0 + while pos != bufCount: + tmp[pos] ^= buf[pos] + self.output.setUInt8(tmp[pos]) + pos += 1 + self.xor(self.s, hashBytes) + self.multiplyH(self.s) + self.totalLength += bufCount + + @classmethod + def setPackLength(cls, length, buff, offset): + cls.uInt32ToBE(int((length >> 32)), buff, offset) + cls.uInt32ToBE(int(length), buff, offset + 4) + + def reset(self): + self.s = self.getGHash(self.aad) + self.counter = self.clone(self.j0) + self.bytesRemaining = 0 + self.totalLength = 0 + + @classmethod + def tagsEquals(cls, tag1, tag2): + pos = 0 + while pos != 12: + if tag1[pos] != tag2[pos]: + return False + pos += 1 + return True + + def write(self, input_): + for it in input_: + self.bufBlock[self.bytesRemaining] = it + self.bytesRemaining += 1 + if self.bytesRemaining == self.BLOCK_SIZE: + self.gCTRBlock(self.bufBlock, self.BLOCK_SIZE) + if not self.encrypt: + #System.arraycopy(self.bufBlock, self.BLOCK_SIZE, + #self.bufBlock, 0,) + self.bufBlock[0:len(self.tag)] = self.bufBlock[self.blockSize:self.blockSize + len(self.tag)] + self.bytesRemaining = 0 + + def flushFinalBlock(self): + if self.bytesRemaining > 0: + tmp = self.bufBlock[0:self.bytesRemaining] + self.gCTRBlock(tmp, self.bytesRemaining) + if self.security == Security.ENCRYPTION: + self.reset() + return self.output.array() + x = bytearray(16) + self.setPackLength(8 * len(self.aad), x, 0) + self.setPackLength(self.totalLength * 8, x, 8) + self.xor(self.s, x) + self.multiplyH(self.s) + generatedTag = bytearray(self.BLOCK_SIZE) + self.processBlock(self.j0, 0, generatedTag, 0) + self.xor(generatedTag, self.s) + if not self.encrypt: + if not self.tagsEquals(self.tag, generatedTag): + print(GXByteBuffer.hex(self.tag, False) + "-" + GXByteBuffer.hex(generatedTag, False)) + raise ValueError("Decrypt failed. Invalid tag.") + else: + #Tag size is 12 bytes. + self.tag = generatedTag[0:12] + self.reset() + return self.output.array() + + def generateKey(self, isEncrypt, key): + #Key length in words. + keyLen = int(len(key) / 4) + self.rounds = keyLen + 6 + w = [[0 for x in range(4)] for y in range(self.rounds + 1)] + t = 0 + i = 0 + while i < len(key): + w[t >> 2][t & 3] = self.toUInt32(key, i) + i += 4 + t += 1 + k = (self.rounds + 1) << 2 + i = keyLen + while i < k: + temp = w[(i - 1) >> 2][(i - 1) & 3] + if (i % keyLen) == 0: + temp = self.subWord(self.shift(temp, 8)) ^ (self.R_CON[int(i / keyLen) - 1] & 0xFF) + elif (keyLen > 6) and ((i % keyLen) == 4): + temp = self.subWord(temp) + w[i >> 2][i & 3] = w[(i - keyLen) >> 2][(i - keyLen) & 3] ^ temp + i += 1 + if not isEncrypt: + j = 1 + while j < self.rounds: + i = 0 + while i < 4: + w[j][i] = self.imixCol(w[j][i]) + i += 1 + j += 1 + return w + + @classmethod + def galoisMultiply(cls, value): + if value >> 7 != 0: + value = value << 1 + return (value ^ 0x1b) & 0xFF + + return (value << 1) & 0xFF +# temp = (value >> 7) & 0xFF +# temp = temp & 0x1b +# return ((value << 1) ^ temp) & 0xFF + + @classmethod + def aes1Encrypt(cls, data, offset, secret): + round_ = 0 + i = 0 + key = secret[0:] + while round_ < 10: + i = 0 + while i < 16: + data[i + offset] = cls.S_BOX[(data[i + offset] ^ key[i]) & 0xFF] + i += 1 + buf1 = data[1 + offset] + data[1 + offset] = data[5 + offset] + data[5 + offset] = data[9 + offset] + data[9 + offset] = data[13 + offset] + data[13 + offset] = buf1 + buf1 = data[2 + offset] + buf2 = data[6 + offset] + data[2 + offset] = data[10 + offset] + data[6 + offset] = data[14 + offset] + data[10 + offset] = buf1 + data[14 + offset] = buf2 + buf1 = data[15 + offset] + data[15 + offset] = data[11 + offset] + data[11 + offset] = data[7 + offset] + data[7 + offset] = data[3 + offset] + data[3 + offset] = buf1 + if round_ < 9: + i = 0 + while i < 4: + buf4 = (i << 2) & 0xFF + buf1 = (data[buf4 + offset] ^ data[buf4 + 1 + offset] ^ data[buf4 + 2 + offset] ^ data[buf4 + 3 + offset]) & 0xFF + buf2 = data[buf4 + offset] & 0xFF + buf3 = (data[buf4 + offset] ^ data[buf4 + 1 + offset]) & 0xFF + buf3 = cls.galoisMultiply(buf3) & 0xFF + data[buf4 + offset] = (data[buf4 + offset] ^ buf3 ^ buf1) & 0xFF + buf3 = (data[buf4 + 1 + offset] ^ data[buf4 + 2 + offset]) & 0xFF + buf3 = cls.galoisMultiply(buf3) & 0xFF + data[buf4 + 1 + offset] = (data[buf4 + 1 + offset] ^ buf3 ^ buf1) & 0xFF + buf3 = (data[buf4 + 2 + offset] ^ data[buf4 + 3 + offset]) & 0xFF + buf3 = cls.galoisMultiply(buf3) & 0xFF + data[buf4 + 2 + offset] = (data[buf4 + 2 + offset] ^ buf3 ^ buf1) & 0xFF + buf3 = (data[buf4 + 3 + offset] ^ buf2) & 0xFF + buf3 = cls.galoisMultiply(buf3) & 0xFF + data[buf4 + 3 + offset] = (data[buf4 + 3 + offset] ^ buf3 ^ buf1) & 0xFF + i += 1 + key[0] = (cls.S_BOX[key[13] & 0xFF] ^ key[0] ^ cls.R_CON[round_]) & 0xFF + key[1] = (cls.S_BOX[key[14] & 0xFF] ^ key[1]) & 0xFF + key[2] = (cls.S_BOX[key[15] & 0xFF] ^ key[2]) & 0xFF + key[3] = (cls.S_BOX[key[12] & 0xFF] ^ key[3]) & 0xFF + i = 4 + while i < 16: + key[i] = (key[i] ^ key[i - 4]) & 0xFF + i += 1 + round_ += 1 + i = 0 + while i < 16: + data[i + offset] = (data[i + offset] ^ key[i]) + i += 1 + + def encryptAes(self, data): + n = len(data) / 8 + if (n * 8) != len(data): + raise ValueError("Invalid data.") + iv = bytearray([0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6]) + block = bytearray(len(data) + len(iv)) + buf = bytearray(8 + len(iv)) + block[0:len(self.IV)] = self.IV[0:len(self.IV)] + block[len(self.IV):len(self.IV) + len(data)] = data[0:len(data)] + j = 0 + while j != 6: + i = 1 + while i <= n: + buf[0:len(self.IV)] = block[0: len(self.IV)] + buf[len(self.IV):8 + len(self.IV)] = block[8 * i:8 * i + 8] + self.processBlock(buf, 0, buf, 0) + t = int(n * j + i) + k = 1 + while t != 0: + v = int(t) + buf[len(self.IV) - k] ^= v + t = int(t >> 8) + k += 1 + block[0:8] = buf[0:8] + block[8 * i:8 * i + 8] = buf[8:16] + i += 1 + j += 1 + return block + + def decryptAes(self, input_): + n = len(input_) / 8 + if (n * 8) != len(input_): + raise ValueError("Invalid data.") + iv = bytearray([0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6]) + if len(input_) > len(iv): + block = bytearray(len(input_) - len(iv)) + else: + block = bytearray(len(iv)) + a = bytearray(len(iv)) + buf = bytearray(8 + len(iv)) + a[0:] = input_[0:len(iv)] + block[0:] = input_[len(iv):len(input_)] + n = n - 1 + if n == 0: + n = 1 + j = 5 + while j >= 0: + i = n + while i >= 1: + buf[0:len(iv)] = a[0:len(iv)] + buf[len(iv):8 + len(iv)] = block[8 * int(i - 1):8 * int(i)] + t = n * j + i + k = 1 + while t != 0: + v = int(t) + buf[len(self.IV)] ^= v + t = int((int(t) >> 8)) + k += 1 + self.processBlock(buf, 0, buf, 0) + a[0:] = buf[0:8] + block[8:16] = buf[8 * (i - 1):8*i] + i -= 1 + j -= 1 + if a != self.IV: + raise ValueError("Invalid data") + return block + + @classmethod + def aes1Decrypt(cls, data, secret): + buf1 = 0 + buf2 = 0 + buf3 = 0 + round_ = 0 + i = 0 + buf4 = 0 + key = secret[0:] + while round_ < 10: + key[0] = int((cls.S_BOX[key[13] & 0xFF] ^ key[0] ^ cls.R_CON[round_])) + key[1] = int((cls.S_BOX[key[14] & 0xFF] ^ key[1])) + key[2] = int((cls.S_BOX[key[15] & 0xFF] ^ key[2])) + key[3] = int((cls.S_BOX[key[12] & 0xFF] ^ key[3])) + while i < 16: + key[i] = int((key[i] ^ key[i - 4])) + i += 1 + round_ += 1 + while i < 16: + data[i] = int((data[i] ^ key[i])) + i += 1 + while round_ < 10: + while i > 3: + key[i] = int((key[i] ^ key[i - 4])) + i -= 1 + key[0] = int((cls.S_BOX[key[13] & 0xFF] ^ key[0] ^ cls.R_CON[9 - round_])) + key[1] = int((cls.S_BOX[key[14] & 0xFF] ^ key[1])) + key[2] = int((cls.S_BOX[key[15] & 0xFF] ^ key[2])) + key[3] = int((cls.S_BOX[key[12] & 0xFF] ^ key[3])) + if round_ > 0: + while i < 4: + buf4 = (i << 2) & 0xFF + buf1 = cls.galoisMultiply(cls.galoisMultiply(data[buf4] ^ data[buf4 + 2])) + buf2 = cls.galoisMultiply(cls.galoisMultiply(data[buf4 + 1] ^ data[buf4 + 3])) + data[buf4] ^= buf1 + data[buf4 + 1] ^= buf2 + data[buf4 + 2] ^= buf1 + data[buf4 + 3] ^= buf2 + buf1 = int((data[buf4] ^ data[buf4 + 1] ^ data[buf4 + 2] ^ data[buf4 + 3])) + buf2 = data[buf4] + buf3 = int((data[buf4] ^ data[buf4 + 1])) + buf3 = cls.galoisMultiply(buf3) + data[buf4] = int((data[buf4] ^ buf3 ^ buf1)) + buf3 = int((data[buf4 + 1] ^ data[buf4 + 2])) + buf3 = cls.galoisMultiply(buf3) + data[buf4 + 1] = int((data[buf4 + 1] ^ buf3 ^ buf1)) + buf3 = int((data[buf4 + 2] ^ data[buf4 + 3])) + buf3 = cls.galoisMultiply(buf3) + data[buf4 + 2] = int((data[buf4 + 2] ^ buf3 ^ buf1)) + buf3 = int((data[buf4 + 3] ^ buf2)) + buf3 = cls.galoisMultiply(buf3) + data[buf4 + 3] = int((data[buf4 + 3] ^ buf3 ^ buf1)) + i += 1 + buf1 = data[13] + data[13] = data[9] + data[9] = data[5] + data[5] = data[1] + data[1] = buf1 + buf1 = data[10] + buf2 = data[14] + data[10] = data[2] + data[14] = data[6] + data[2] = buf1 + data[6] = buf2 + buf1 = data[3] + data[3] = data[7] + data[7] = data[11] + data[11] = data[15] + data[15] = buf1 + while i < 16: + data[i] = int((cls.S_BOX_REVERSED[data[i] & 0xFF] ^ key[i])) + i += 1 + round_ += 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSClient.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSClient.py new file mode 100644 index 0000000..590b1be --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSClient.py @@ -0,0 +1,1356 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +from .GXDLMSSettings import GXDLMSSettings +from .enums import Authentication, InterfaceType, SourceDiagnostic, DataType, Conformance +from .ConnectionState import ConnectionState +from .GXByteBuffer import GXByteBuffer +from .GXHdlcSettings import GXHdlcSettings +from .enums import Command, ObjectType +from .GXDLMS import GXDLMS +from ._GXAPDU import _GXAPDU +from ._HDLCInfo import _HDLCInfo +from .GXDLMSLNParameters import GXDLMSLNParameters +from .ActionRequestType import ActionRequestType +from .internal._GXCommon import _GXCommon +from .GetCommandType import GetCommandType +from .objects import GXDLMSObject, GXDLMSObjectCollection, GXDLMSData +from .internal._GXDataInfo import _GXDataInfo +from .ValueEventArgs import ValueEventArgs +from .GXDateTime import GXDateTime +from .SetRequestType import SetRequestType +from .GXDLMSSNParameters import GXDLMSSNParameters +from .VariableAccessSpecification import VariableAccessSpecification +from .GXSecure import GXSecure +from .GXDLMSConverter import GXDLMSConverter +from .GXDLMSLNCommandHandler import GXDLMSLNCommandHandler +from .GXDLMSSNCommandHandler import GXDLMSSNCommandHandler +from .enums.AccessServiceCommandType import AccessServiceCommandType +from .enums.ErrorCode import ErrorCode +from .GXDLMSTranslatorStructure import GXDLMSTranslatorStructure +from .enums.RequestTypes import RequestTypes +from .SerialnumberCounter import SerialNumberCounter +from ._GXObjectFactory import _GXObjectFactory + +#pylint:disable=bad-option-value,too-many-instance-attributes,too-many-arguments,too-many-public-methods,useless-object-inheritance +class GXDLMSClient(object): + """ + GXDLMS implements methods to communicate with DLMS/COSEM metering devices. + """ + + # + # Constructor. + # + # useLogicalNameReferencing: Is Logical Name referencing used. + # clientAddress: Server address. + # serverAddress: Client address. + # forAuthentication: Authentication type. + # password: Password if authentication is used. + # interfaceType: Interface type. + def __init__(self, useLogicalNameReferencing=True, clientAddress=16, serverAddress=1, forAuthentication=Authentication.NONE, password=None, interfaceType=InterfaceType.HDLC): + # DLMS settings. + self.settings = GXDLMSSettings(False) + self.manufacturerId = None + self.settings.setUseLogicalNameReferencing(useLogicalNameReferencing) + self.clientAddress = clientAddress + self.serverAddress = serverAddress + self.authentication = forAuthentication + if not password: + self.password = None + elif isinstance(password, str): + self.password = _GXCommon.getBytes(password) + elif isinstance(password, (bytes, bytearray)): + self.password = password + self.interfaceType = interfaceType + self.translator = None + self.throwExceptions = True + self.obisCodes = None + # Is authentication required. + self.isAuthenticationRequired = False + # Auto increase Invoke ID. + self.autoIncreaseInvokeID = False + #If protected release is used release is including a ciphered xDLMS + #Initiate request. + self.useProtectedRelease = False + + #Initialize challenge that is restored after the connection is closed. + self.initializeChallenge = None + #Initialize PDU size that is restored after the connection is closed. + self.initializePduSize = 0 + #Initialize Max HDLC transmission size that is restored after the connection is closed. + self.initializeMaxInfoTX = 0 + # Initialize Max HDLC receive size that is restored after the connection is closed. + self.initializeMaxInfoRX = 0 + #Initialize max HDLC window size in transmission that is restored after the connection is closed. + self.initializeWindowSizeTX = 0 + #Initialize max HDLC window size in receive that is restored after the connection is closed. + self.initializeWindowSizeRX = 0 + + def getObjects(self): + return self.settings.objects + + objects = property(getObjects) + + # + # Set starting packet index. Default is One based, but some meters + # use Zero + # based value. Usually this is not used. + # + # value + # Zero based starting index. + # + def setStartingPacketIndex(self, value): + self.settings.setStartingPacketIndex(value) + + def getUserId(self): + return self.settings.userId + + def setUserId(self, value): + if value < -1 or value > 255: + raise ValueError("Invalid user Id.") + self.settings.userId = value + + # + # User id is the identifier of the user. This value is used if user + # list on Association LN is used. + userId = property(getUserId, setUserId) + + def getClientAddress(self): + return self.settings.clientAddress + + def setClientAddress(self, value): + self.settings.clientAddress = value + + # + # Client address. + # + clientAddress = property(getClientAddress, setClientAddress) + + def __getServerAddress(self): + # pylint: disable=unused-private-member + return self.settings.serverAddress + + def __setServerAddress(self, value): + # pylint: disable=unused-private-member + self.settings.serverAddress = value + # + # Server Address. + # + serverAddress = property(__getServerAddress, __setServerAddress) + + def getServerAddressSize(self): + return self.settings.serverAddressSize + + def setServerAddressSize(self, value): + self.settings.serverAddressSize = value + + # + # Server address size in bytes. If it is Zero it is counted + # automatically. + # + serverAddressSize = property(getServerAddressSize, setServerAddressSize) + + def getSourceSystemTitle(self): + return self.settings.sourceSystemTitle + + # + # Source system title. + # Meter returns system title when ciphered connection is made or GMAC + # authentication is used. + # + sourceSystemTitle = property(getSourceSystemTitle) + + def getGbtWindowSize(self): + return self.settings.gbtWindowSize + + def setGbtWindowSize(self, value): + self.settings.gbtWindowSize = value + + # + # GBT window size. + # + gbtWndowSize = property(getGbtWindowSize, setGbtWindowSize) + + def getMaxReceivePDUSize(self): + return self.settings.maxPduSize + + def setMaxReceivePDUSize(self, value): + self.settings.maxPduSize = value + + # + # Retrieves the maximum size of received PDU. PDU size tells + # maximum size + # of PDU packet. Value can be from 0 to 0xFFFF. By default the + # value is + # 0xFFFF. + # + # @see GXDLMSClient.clientAddress + # @see GXDLMSClient.serverAddress + # @see GXDLMSClient.useLogicalNameReferencing + # Maximum size of received PDU. + # + maxReceivePDUSize = property(getMaxReceivePDUSize, setMaxReceivePDUSize) + + def getUseLogicalNameReferencing(self): + return self.settings.getUseLogicalNameReferencing() + + def setUseLogicalNameReferencing(self, value): + self.settings.setUseLogicalNameReferencing(value) + + # + # Determines, whether Logical, or Short name, referencing is used. + # Referencing depends on the device to communicate with. Normally, + # a device + # supports only either Logical or Short name referencing. The + # referencing + # is defined by the device manufacturer. If the referencing is + # wrong, the + # SNMR message will fail. + # + # Is Logical Name referencing used. + # + useLogicalNameReferencing = property(getUseLogicalNameReferencing, setUseLogicalNameReferencing) + + def getCtoSChallenge(self): + return self.settings.ctoSChallenge + + def setCtoSChallenge(self, value): + self.settings.useCustomChallenge = value is not None + self.settings.ctoSChallenge = value + + # + # Client to Server custom challenge. + # This is for debugging purposes. Reset custom challenge settings + # CtoSChallenge to null. + # + # Client to Server custom challenge. + # + # Client to Server custom challenge. + # + ctoSChallenge = property(getCtoSChallenge, setCtoSChallenge) + + def getUseUtc2NormalTime(self): + return self.settings.useUtc2NormalTime + + def setUseUtc2NormalTime(self, value): + self.settings.useUtc2NormalTime = value + + # + # Standard says that Time zone is from normal time to UTC in minutes. + # If meter is configured to use UTC time (UTC to normal time) set this to + # true. + # + # True, if UTC time is used. + # + useUtc2NormalTime = property(getUseUtc2NormalTime, setUseUtc2NormalTime) + + def getIncreaseInvocationCounterForGMacAuthentication(self): + return self.settings.increaseInvocationCounterForGMacAuthentication + + def setIncreaseInvocationCounterForGMacAuthentication(self, value): + self.settings.increaseInvocationCounterForGMacAuthentication = value + + increaseInvocationCounterForGMacAuthentication = property(getIncreaseInvocationCounterForGMacAuthentication, setIncreaseInvocationCounterForGMacAuthentication) + """Some meters expect that Invocation Counter is increased for GMAC Authentication when connection is established.""" + + def getDateTimeSkips(self): + return self.settings.dateTimeSkips + + def setDateTimeSkips(self, value): + self.settings.dateTimeSkips = value + + dateTimeSkips = property(getDateTimeSkips, setDateTimeSkips) + """Skipped date time fields. This value can be used if meter can't handle deviation or status.""" + + + def getStandard(self): + return self.settings.standard + + def setStandard(self, value): + self.settings.standard = value + + # + # Used standard. + # + standard = property(getStandard, setStandard) + + def getPassword(self): + return self.settings.password + + def setPassword(self, value): + self.settings.password = value + + # + # Retrieves the password that is used in communication. If + # authentication + # is set to none, password is not used. + # + # @see GXDLMSClient#getAuthentication + # Used password. + # + password = property(getPassword, setPassword) + + def getNegotiatedConformance(self): + return self.settings.negotiatedConformance + + def setNegotiatedConformance(self, value): + self.settings.negotiatedConformance = value + + # + # Functionality what server offers. + # + negotiatedConformance = property(getNegotiatedConformance, setNegotiatedConformance) + + + def getProposedConformance(self): + return self.settings.proposedConformance + + def setProposedConformance(self, value): + self.settings.proposedConformance = value + # + # When connection is made client tells what kind of services + # it want's to use. + # + proposedConformance = property(getProposedConformance, setProposedConformance) + + def getAuthentication(self): + return self.settings.authentication + + def setAuthentication(self, value): + self.settings.authentication = value + + # + # Retrieves the authentication used in communicating with the + # device. By + # default authentication is not used. If authentication is used, + # set the + # password with the Password property. + # + # @see GXDLMSClient#getPassword + # @see GXDLMSClient#getClientAddress + # Used authentication. + # + authentication = property(getAuthentication, setAuthentication) + + def getPriority(self): + return self.settings.priority + + def setPriority(self, value): + self.settings.priority = value + + # + # Used Priority. + # + priority = property(getPriority, setPriority) + + def getServiceClass(self): + return self.settings.serviceClass + + def setServiceClass(self, value): + self.settings.serviceClass = value + # + # Used service class. + # + serviceClass = property(getServiceClass, setServiceClass) + + def getInvokeID(self): + return self.settings.invokeId + + def setInvokeID(self, value): + self.settings.invokeID = value + + # + # Invoke ID. + # + invokeID = property(getInvokeID, setInvokeID) + + def getInterfaceType(self): + return self.settings.interfaceType + + def setInterfaceType(self, value): + self.settings.interfaceType = value + + # + # Interface type. + # + interfaceType = property(getInterfaceType, setInterfaceType) + """Interface type.""" + + # + # Information from the connection size that server can + # handle. + # + @property + def limits(self): + """Obsolete. Use hdlcSettings instead.""" + return self.settings.hdlc + + # + # HDLC framing settings. + # + @property + def hdlcSettings(self): + return self.settings.hdlc + + def getGateway(self): + return self.settings.gateway + + def setGateway(self, value): + self.settings.gateway = value + + # + # Gateway settings. + # + gateway = property(getGateway, setGateway) + + def getProtocolVersion(self): + return self.settings.protocolVersion + + def setProtocolVersion(self, value): + self.settings.protocolVersion = value + + # + # Protocol version. + # + protocolVersion = property(getProtocolVersion, setProtocolVersion) + + # + # Generates SNRM request. his method is used to generate send + # SNRMRequest. + # Before the SNRM request can be generated, at least the following + # properties must be set: + #
    + #
  • ClientAddress
  • + #
  • ServerAddress
  • + #
+ # Note! According to IEC 62056-47: when communicating using + # TCP/IP, + # the SNRM request is not send. + # + # @see GXDLMSClient#getClientAddress + # @see GXDLMSClient#getServerAddress + # @see GXDLMSClient#parseUAResponse + # SNRM request as byte array. + # + def snrmRequest(self): + #Save default values. + self.initializeMaxInfoTX = self.hdlcSettings.maxInfoTX + self.initializeMaxInfoRX = self.hdlcSettings.maxInfoRX + self.initializeWindowSizeTX = self.hdlcSettings.windowSizeTX + self.initializeWindowSizeRX = self.hdlcSettings.windowSizeRX + self.settings.connected = ConnectionState.NONE + self.isAuthenticationRequired = False + # SNRM request is not used in network connections. + if self.interfaceType == InterfaceType.WRAPPER: + return None + data = GXByteBuffer(25) + data.setUInt8(0x81) + # FromatID + data.setUInt8(0x80) + # GroupID + data.setUInt8(0) + # Length. + # If custom HDLC parameters are used. + if GXHdlcSettings.DEFAULT_MAX_INFO_TX != self.hdlcSettings.maxInfoTX: + data.setUInt8(_HDLCInfo.MAX_INFO_TX) + GXDLMS.appendHdlcParameter(data, self.hdlcSettings.maxInfoTX) + if GXHdlcSettings.DEFAULT_MAX_INFO_RX != self.hdlcSettings.maxInfoRX: + data.setUInt8(_HDLCInfo.MAX_INFO_RX) + GXDLMS.appendHdlcParameter(data, self.hdlcSettings.maxInfoRX) + if GXHdlcSettings.DEFAULT_WINDOWS_SIZE_TX != self.hdlcSettings.windowSizeTX: + data.setUInt8(_HDLCInfo.WINDOW_SIZE_TX) + data.setUInt8(4) + data.setUInt32(self.hdlcSettings.windowSizeTX) + if GXHdlcSettings.DEFAULT_WINDOWS_SIZE_RX != self.hdlcSettings.windowSizeRX: + data.setUInt8(_HDLCInfo.WINDOW_SIZE_RX) + data.setUInt8(4) + data.setUInt32(self.hdlcSettings.windowSizeRX) + # If default HDLC parameters are not used. + if data.size != 3: + data.setUInt8(len(data) - 3, 2) + else: + data = None + return GXDLMS.getHdlcFrame(self.settings, Command.SNRM, data) + + # + # Parses UAResponse from byte array. + # + # data: # Received message from the server. + # @see GXDLMSClient#snrmRequest + # + def parseUAResponse(self, data): + if not isinstance(data, GXByteBuffer): + data = GXByteBuffer(data) + + GXDLMS.parseSnrmUaResponse(data, self.settings.hdlc) + self.settings.connected = ConnectionState.HDLC + + # + # Generate AARQ request. Because all_ meters can't read all_ data in + # one + # packet, the packet must be split first, by using + # SplitDataToPackets + # method. + # + # AARQ request as byte array. + # @see GXDLMSClient#parseAareResponse + # + def aarqRequest(self): + #pylint: disable=bad-option-value,redefined-variable-type + #Save default values. + self.initializePduSize = self.maxReceivePDUSize + self.initializeChallenge = self.settings.getStoCChallenge() + self.settings.connected = self.settings.connected & ~ConnectionState.DLMS + buff = GXByteBuffer(20) + self.settings.resetBlockIndex() + GXDLMS.checkInit(self.settings) + self.settings.setStoCChallenge(None) + if self.autoIncreaseInvokeID: + self.settings.setInvokeID(0) + else: + self.settings.setInvokeID(1) + # If authentication or ciphering is used. + if self.authentication > Authentication.LOW: + if not self.settings.useCustomChallenge: + self.settings.ctoSChallenge = GXSecure.generateChallenge() + else: + self.settings.setCtoSChallenge(None) + _GXAPDU.generateAarq(self.settings, self.settings.cipher, None, buff) + reply = None + if self.settings.getUseLogicalNameReferencing(): + p = GXDLMSLNParameters(self.settings, 0, Command.AARQ, 0, buff, None, 0xff) + reply = GXDLMS.getLnMessages(p) + else: + p = GXDLMSSNParameters(self.settings, Command.AARQ, 0, 0, None, buff) + reply = GXDLMS.getSnMessages(p) + return reply + + # + # Parses the AARE response. Parse method will update the following + # data: + #
    + #
  • DLMSVersion
  • + #
  • MaxReceivePDUSize
  • + #
  • UseLogicalNameReferencing
  • + #
  • LNSettings or SNSettings
  • + #
+ # LNSettings or SNSettings will be updated, depending on the + # referencing, + # Logical name or Short name. + # + # reply + # Received data. + # @see GXDLMSClient#aarqRequest + # @see GXDLMSClient#useLogicalNameReferencing + # @see GXDLMSClient#negotiatedConformance + # @see GXDLMSClient#proposedConformance + # + def parseAareResponse(self, reply): + self.isAuthenticationRequired = _GXAPDU.parsePDU(self.settings, self.settings.cipher, reply, None) == SourceDiagnostic.AUTHENTICATION_REQUIRED + if self.settings.dlmsVersion != 6: + raise ValueError("Invalid DLMS version number.") + if not self.isAuthenticationRequired: + self.settings.connected = self.settings.connected | ConnectionState.DLMS + + # + # Is authentication Required. + # + def getIsAuthenticationRequired(self): + return self.isAuthenticationRequired + + # + # Get challenge request if HLS authentication is used. + # + def getApplicationAssociationRequest(self): + if self.settings.authentication != Authentication.HIGH_ECDSA and self.settings.authentication != Authentication.HIGH_GMAC and not self.settings.password: + raise ValueError("Password is invalid.") + self.settings.resetBlockIndex() + #Count challenge for Landis+Gyr. L+G is using custom way to count the + #challenge. + if self.manufacturerId == "LGZ" and self.settings.authentication == Authentication.HIGH: + challenge = self.encryptLandisGyrHighLevelAuthentication(self.settings.password, self.settings.stoCChallenge) + if self.useLogicalNameReferencing: + return self.__method("0.0.40.0.0.255", ObjectType.ASSOCIATION_LOGICAL_NAME, 1, challenge, DataType.OCTET_STRING) + return self.__method(0xFA00, ObjectType.ASSOCIATION_SHORT_NAME, 8, challenge, DataType.OCTET_STRING) + + if self.settings.authentication == Authentication.HIGH_GMAC: + pw = self.settings.cipher.systemTitle + elif self.settings.authentication == Authentication.HIGH_SHA256: + tmp = GXByteBuffer() + tmp.set(self.settings.password) + tmp.set(self.settings.cipher.systemTitle) + tmp.set(self.settings.sourceSystemTitle) + tmp.set(self.settings.stoCChallenge) + tmp.set(self.settings.ctoSChallenge) + pw = tmp.array() + else: + pw = self.settings.password + if self.settings.cipher and self.settings.increaseInvocationCounterForGMacAuthentication: + self.settings.cipher.invocationCounter += 1 + challenge = GXSecure.secure(self.settings, self.settings.cipher, self.settings.cipher.invocationCounter, self.settings.getStoCChallenge(), pw) + if self.useLogicalNameReferencing: + return self.__method("0.0.40.0.0.255", ObjectType.ASSOCIATION_LOGICAL_NAME, 1, challenge, DataType.OCTET_STRING) + return self.__method(0xFA00, ObjectType.ASSOCIATION_SHORT_NAME, 8, challenge, DataType.OCTET_STRING) + + # + # Parse server's challenge if HLS authentication is used. + # + # reply + # Received reply from the server. + # + def parseApplicationAssociationResponse(self, reply): + # Landis+Gyr is not returning StoC. + if self.manufacturerId == "LGZ" and self.settings.authentication == Authentication.HIGH: + self.settings.connected |= ConnectionState.DLMS + else: + info = _GXDataInfo() + equals = False + ic = 0 + value = _GXCommon.getData(self.settings, reply, info) + if value: + if self.settings.authentication == Authentication.HIGH_ECDSA: + raise ValueError("ECDSA is not supported.") + if self.settings.authentication == Authentication.HIGH_GMAC: + secret = self.settings.sourceSystemTitle + bb = GXByteBuffer(value) + bb.getUInt8() + ic = bb.getUInt32() + elif self.settings.authentication == Authentication.HIGH_SHA256: + tmp2 = GXByteBuffer() + tmp2.set(self.settings.password) + tmp2.set(self.settings.sourceSystemTitle) + tmp2.set(self.settings.cipher.systemTitle) + tmp2.set(self.settings.ctoSChallenge) + tmp2.set(self.settings.stoCChallenge) + secret = tmp2.array() + else: + secret = self.settings.password + tmp = GXSecure.secure(self.settings, self.settings.cipher, ic, self.settings.getCtoSChallenge(), secret) + challenge = GXByteBuffer(tmp) + equals = challenge.compare(value) + if not equals: + print("Invalid StoC:" + GXByteBuffer.hex(value, True) + "-" + GXByteBuffer.hex(tmp, True)) + else: + print("Server did not accept CtoS.") + if not equals: + raise Exception("parseApplicationAssociationResponse failed. " + " Server to Client do not match.") + self.settings.connected |= ConnectionState.DLMS + + def releaseRequest(self): + if (self.settings.connected & ConnectionState.DLMS) == 0: + return None + buff = GXByteBuffer() + buff.setUInt8(3) + buff.setUInt8(0x80) + buff.setUInt8(1) + buff.setUInt8(00) + #Restore default values. + self.maxReceivePDUSize = self.initializePduSize + self.settings.setCtoSChallenge(self.initializeChallenge) + if self.useProtectedRelease: + #Increase IC. + if self.settings.cipher and self.settings.cipher.isCiphered: + self.settings.cipher.invocationCounter = self.settings.cipher.invocationCounter + 1 + _GXAPDU.generateUserInformation(self.settings, self.settings.cipher, None, buff) + buff.setUInt8(len(buff) - 1, 0) + else: + buff.setUInt8(3) + buff.setUInt8(0x80) + buff.setUInt8(1) + buff.setUInt8(0) + + if self.useLogicalNameReferencing: + p = GXDLMSLNParameters(self.settings, 0, Command.RELEASE_REQUEST, 0, buff, None, 0xff) + reply = GXDLMS.getLnMessages(p) + else: + reply = GXDLMS.getSnMessages(GXDLMSSNParameters(self.settings, Command.RELEASE_REQUEST, 0xFF, 0xFF, None, buff)) + self.settings.connected = self.settings.connected & ~ConnectionState.DLMS + return reply + + def disconnectRequest(self, force=False): + if not force and self.settings.connected == ConnectionState.NONE: + return None + if GXDLMS.useHdlc(self.interfaceType): + self.settings.connected = ConnectionState.NONE + reply = GXDLMS.getHdlcFrame(self.settings, Command.DISCONNECT_REQUEST, None) + elif force or self.settings.connected == ConnectionState.DLMS: + reply = self.releaseRequest() + if reply: + reply = reply[0] + #Restore default HDLC values. + self.hdlcSettings.maxInfoTX = self.initializeMaxInfoTX + self.hdlcSettings.maxInfoRX = self.initializeMaxInfoRX + self.hdlcSettings.windowSizeTX = self.initializeWindowSizeTX + self.hdlcSettings.windowSizeRX = self.initializeWindowSizeRX + self.settings.connected = ConnectionState.NONE + self.settings.resetFrameSequence() + return reply + + @classmethod + def __createDLMSObject(cls, classID, version, baseName, ln, accessRights): + type_ = classID + obj = cls.createObject(type_) + GXDLMSClient.__updateObjectData(obj, type_, version, baseName, ln, accessRights) + return obj + + def parseSNObjects(self, buff, onlyKnownObjects, ignoreInactiveObjects): + # pylint: disable=unidiomatic-typecheck + buff.position = 0 + size = buff.getUInt8() + if size != 0x01: + raise Exception("Invalid response.") + items = GXDLMSObjectCollection(self) + cnt = _GXCommon.getObjectCount(buff) + info = _GXDataInfo() + objPos = 0 + while objPos != cnt: + if buff.position == len(buff): + break + info.count = 0 + info.index = 0 + info.type_ = DataType.NONE + objects = _GXCommon.getData(self.settings, buff, info) + if len(objects) != 4: + raise Exception("Invalid structure format.") + classID = objects[1] + baseName = int(objects[0]) & 0xFFFF + comp = GXDLMSClient.__createDLMSObject(classID, objects[2], baseName, objects[3], None) + if (not onlyKnownObjects or type(comp) != GXDLMSObject): + if not ignoreInactiveObjects or comp.logicalName != "0.0.127.0.0.0": + items.append(comp) + else: + print("Unknown object : " + str(classID) + " " + str(baseName)) + objPos += 1 + return items + + @classmethod + def __updateObjectData(cls, obj, objectType, version, baseName, logicalName, accessRights): + obj.objectType = objectType + if accessRights: + for attributeAccess in accessRights[0]: + id_ = attributeAccess[0] + if id_ > 0: + mode = attributeAccess[1] + obj.setAccess(id_, mode) + for methodAccess in accessRights[1]: + id_ = methodAccess[0] + tmp = 0 + if isinstance(methodAccess[1], bool): + if bool((methodAccess)[1]): + tmp = 1 + else: + tmp = 0 + else: + tmp = methodAccess[1] + obj.setMethodAccess(id_, tmp) + if baseName is not None: + obj.shortName = int(baseName) + if version is not None: + obj.version = int(version) + obj.logicalName = _GXCommon.toLogicalName(logicalName) + + def parseObjects(self, data, onlyKnownObjects=True, ignoreInactiveObjects=True): + if not data: + raise Exception("Invalid parameter.") + objects = None + if self.useLogicalNameReferencing: + objects = self.parseLNObjects(data, onlyKnownObjects, ignoreInactiveObjects) + else: + objects = self.parseSNObjects(data, onlyKnownObjects, ignoreInactiveObjects) + self.settings.objects = objects + + c = GXDLMSConverter(self.standard) + c.updateOBISCodeInformation(objects) + return objects + + def parseLNObjects(self, buff, onlyKnownObjects, ignoreInactiveObjects): + # pylint: disable=unidiomatic-typecheck + size = buff.getInt8() + if size != 0x01: + raise Exception("Invalid response.") + items = GXDLMSObjectCollection(self) + info = _GXDataInfo() + cnt = _GXCommon.getObjectCount(buff) + objPos = 0 + while objPos != cnt: + if buff.position == len(buff): + break + info.type_ = DataType.NONE + info.index = 0 + info.count = 0 + objects = _GXCommon.getData(self.settings, buff, info) + if len(objects) != 4: + raise Exception("Invalid structure format.") + classID = objects[0] + if classID > 0: + comp = GXDLMSClient.__createDLMSObject(classID, objects[1], 0, objects[2], objects[3]) + if (not onlyKnownObjects or type(comp) != GXDLMSObject): + if not ignoreInactiveObjects or comp.logicalName != "0.0.127.0.0.0": + items.append(comp) + else: + print("Unknown object : " + str(classID) + " " + _GXCommon.toLogicalName(objects[2])) + objPos += 1 + return items + + def updateValue(self, target, attributeIndex, value, parameters=None): + if isinstance(value, (bytes, bytearray)): + type_ = target.getUIDataType(attributeIndex) + if type_ == DataType.DATETIME and len(value) == 5: + type_ = DataType.DATE + target.setUIDataType(attributeIndex, type_) + if type_ != DataType.NONE: + value = self.changeType(value, type_, self.settings.useUtc2NormalTime) + e = ValueEventArgs(self.settings, target, attributeIndex, 0, parameters) + e.value = value + target.setValue(self.settings, e) + return target.getValues()[attributeIndex - 1] + + @classmethod + def getValue(cls, data, useUtc): + settings = GXDLMSSettings(False) + settings.useUtc2NormalTime = useUtc + info = _GXDataInfo() + return _GXCommon.getData(settings, data, info) + + + def updateValues(self, list_, values): + pos = 0 + for k, v in list_: + e = ValueEventArgs(self.settings, k, v, 0, None) + e.value = values[pos] + k.setValue(self.settings, e) + pos += 1 + + @classmethod + def changeType(cls, value, type_, useUtc=False): + settings = GXDLMSSettings(False) + settings.useUtc2NormalTime = useUtc + return _GXCommon.changeType(settings, value, type_) + + def getObjectsRequest(self): + #pylint: disable=bad-option-value,redefined-variable-type + self.settings.resetBlockIndex() + if self.useLogicalNameReferencing: + name = "0.0.40.0.0.255" + else: + name = int(0xFA00) + return self._read(name, ObjectType.ASSOCIATION_LOGICAL_NAME, 2)[0] + + + def method(self, item, index, data, type_): + return self.__method(item.name, item.objectType, index, data, type_) + + def __method(self, name, objectType, methodIndex, value, dataType=DataType.NONE): + #pylint: + #disable=bad-option-value,redefined-variable-type,too-many-locals + if not name or methodIndex < 1: + raise ValueError("Invalid parameter") + self.settings.resetBlockIndex() + if self.autoIncreaseInvokeID: + self.settings.setInvokeID(int(((self.settings.invokeId + 1) & 0xF))) + index = methodIndex + type_ = dataType + if type_ == DataType.NONE: + raise Exception("Invalid parameter. In python value type must give.") + reply = None + data = GXByteBuffer() + attributeDescriptor = GXByteBuffer() + _GXCommon.setData(self.settings, data, type_, value) + if self.useLogicalNameReferencing: + attributeDescriptor.setUInt16(objectType) + attributeDescriptor.set(_GXCommon.logicalNameToBytes(str(name))) + attributeDescriptor.setUInt8(int(methodIndex)) + if type_ == DataType.NONE: + attributeDescriptor.setUInt8(0) + else: + attributeDescriptor.setUInt8(1) + p = GXDLMSLNParameters(self.settings, 0, Command.METHOD_REQUEST, ActionRequestType.NORMAL, attributeDescriptor, data, 0xff) + reply = GXDLMS.getLnMessages(p) + else: + ind = [0] + count = [0] + GXDLMS.getActionInfo(objectType, ind, count) + if index > count[0]: + raise ValueError("methodIndex") + sn = name + index = (ind[0] + (index - 1) * 0x8) + sn += index + attributeDescriptor.setUInt16(sn) + if type_ != DataType.NONE: + attributeDescriptor.setUInt8(1) + else: + attributeDescriptor.setUInt8(0) + p = GXDLMSSNParameters(self.settings, Command.WRITE_REQUEST, 1, VariableAccessSpecification.VARIABLE_NAME, attributeDescriptor, data) + reply = GXDLMS.getSnMessages(p) + return reply + + + def write(self, item, index): + e = ValueEventArgs(self.settings, item, index, 0, None) + value = item.getValue(self.settings, e) + type_ = item.getDataType(index) + if type_ == DataType.OCTET_STRING and isinstance(value, (str,)): + ui = item.getUIDataType(index) + if ui == DataType.STRING: + return self.__write(item.name, str(value).encode(), type_, item.objectType, index) + return self.__write(item.name, value, type_, item.objectType, index) + + def __write(self, name, value, dataType, objectType, index): + #pylint: disable=bad-option-value,redefined-variable-type + if index < 1: + raise Exception("Invalid parameter") + self.settings.resetBlockIndex() + if self.autoIncreaseInvokeID: + self.settings.setInvokeID(int(((self.settings.invokeId + 1) & 0xF))) + type_ = dataType + if value is not None and type_ == DataType.NONE: + raise Exception("Invalid parameter. In python value type must give.") + reply = None + data = GXByteBuffer() + attributeDescriptor = GXByteBuffer() + _GXCommon.setData(self.settings, data, type_, value) + if self.useLogicalNameReferencing: + attributeDescriptor.setUInt16(objectType) + attributeDescriptor.set(_GXCommon.logicalNameToBytes(str(name))) + attributeDescriptor.setUInt8(index) + attributeDescriptor.setUInt8(0) + p = GXDLMSLNParameters(self.settings, 0, Command.SET_REQUEST, SetRequestType.NORMAL, attributeDescriptor, data, 0xff) + p.blockIndex = self.settings.blockIndex + p.blockNumberAck = self.settings.blockNumberAck + p.streaming = False + reply = GXDLMS.getLnMessages(p) + else: + sn = name + sn += (index - 1) * 8 + attributeDescriptor.setUInt16(sn) + attributeDescriptor.setUInt8(1) + p = GXDLMSSNParameters(self.settings, Command.WRITE_REQUEST, 1, VariableAccessSpecification.VARIABLE_NAME, attributeDescriptor, data) + reply = GXDLMS.getSnMessages(p) + return reply + + def writeList(self, list_): + if not list_: + raise ValueError("Invalid parameter.") + value = None + reply = None + self.settings.resetBlockIndex() + data = GXByteBuffer() + bb = GXByteBuffer() + if self.useLogicalNameReferencing: + bb.setUInt8(len(list_)) + for it in list_: + bb.setUInt16(it.target.objectType) + bb.set(_GXCommon.logicalNameToBytes(it.target.logicalName)) + bb.setUInt8(it.index) + bb.setUInt8(0) + else: + for it in list_: + bb.setUInt8(2) + sn = it.target.shortName + sn += (it.index - 1) * 8 + bb.setUInt16(sn) + _GXCommon.setObjectCount(len(list_), bb) + for it in list_: + e = ValueEventArgs(self.settings, it.target, it.index, it.selector, it.parameters) + value = it.target.getValue(self.settings, e) + type_ = it.getDataType() + if (type_ is None or type_ == DataType.NONE) and value: + type_ = it.target.getDataType(it.index) + if type_ == DataType.NONE: + raise Exception("Invalid parameter. In python value type must give.") + _GXCommon.setData(self.settings, data, type_, value) + if self.useLogicalNameReferencing: + p = GXDLMSLNParameters(self.settings, 0, Command.SET_REQUEST, SetRequestType.WITH_LIST, bb, data, 0xff) + reply = GXDLMS.getLnMessages(p) + else: + p2 = GXDLMSSNParameters(self.settings, Command.WRITE_REQUEST, len(list_), 4, bb, data) + reply = GXDLMS.getSnMessages(p2) + return reply + + + def _read(self, name, objectType, attributeOrdinal, data=None): + if attributeOrdinal < 1: + raise ValueError("Invalid parameter") + attributeDescriptor = GXByteBuffer() + reply = None + self.settings.resetBlockIndex() + if self.autoIncreaseInvokeID: + self.settings.setInvokeID(int(((self.settings.invokeId + 1) & 0xF))) + if self.useLogicalNameReferencing: + attributeDescriptor.setUInt16(int(objectType)) + attributeDescriptor.set(_GXCommon.logicalNameToBytes(str(name))) + attributeDescriptor.setUInt8(attributeOrdinal) + if not data: + attributeDescriptor.setUInt8(0) + else: + attributeDescriptor.setUInt8(1) + p = GXDLMSLNParameters(self.settings, 0, Command.GET_REQUEST, GetCommandType.NORMAL, attributeDescriptor, data, 0xFF) + reply = GXDLMS.getLnMessages(p) + else: + #pylint: disable=bad-option-value,redefined-variable-type + sn = name + sn += (attributeOrdinal - 1) * 8 + attributeDescriptor.setUInt16(sn) + if data: + requestType = VariableAccessSpecification.PARAMETERISED_ACCESS + else: + requestType = VariableAccessSpecification.VARIABLE_NAME + p = GXDLMSSNParameters(self.settings, Command.READ_REQUEST, 1, requestType, attributeDescriptor, data) + reply = GXDLMS.getSnMessages(p) + return reply + + def read(self, item, attributeOrdinal): + return self._read(item.name, item.objectType, attributeOrdinal) + + def readList(self, list_): + if not list_: + raise ValueError("Invalid parameter.") + if self.negotiatedConformance & Conformance.MULTIPLE_REFERENCES == 0: + raise ValueError("Meter doesn't support multiple objects reading with one request.") + + messages = list() + data = GXByteBuffer() + self.settings.resetBlockIndex() + if self.useLogicalNameReferencing: + p = GXDLMSLNParameters(self.settings, 0, Command.GET_REQUEST, GetCommandType.WITH_LIST, data, None, 0xff) + pos = 0 + count = (self.settings.maxPduSize - 12) / 10 + if len(list_) < count: + count = len(list_) + # pylint: disable=consider-using-min-builtin + if count > 10: + count = 10 + _GXCommon.setObjectCount(count, data) + for k, v in list_: + data.setUInt16(k.objectType) + data.set(_GXCommon.logicalNameToBytes(k.logicalName)) + data.setUInt8(v) + data.setUInt8(0) + pos += 1 + if pos % count == 0 and len(list_) != pos: + messages.append(GXDLMS.getLnMessages(p)) + data.clear() + if len(list_) - pos < count: + _GXCommon.setObjectCount(len(list_) - pos, data) + else: + _GXCommon.setObjectCount(count, data) + messages.append(GXDLMS.getLnMessages(p)) + else: + p2 = GXDLMSSNParameters(self.settings, Command.READ_REQUEST, len(list_), 0xFF, data, None) + for k, v in list_: + data.setUInt8(VariableAccessSpecification.VARIABLE_NAME) + sn = k.shortName + sn += (v - 1) * 8 + data.setUInt16(sn) + messages.append(GXDLMS.getSnMessages(p2)) + return messages + + def keepAlive(self): + if self.interfaceType == InterfaceType.WRAPPER: + return None + return GXDLMS.getHdlcFrame(self.settings, self.settings.getReceiverReady(), None) + + + def readRowsByEntry(self, pg, index, count, columns=None): + if index < 0: + raise ValueError("index") + if count < 0: + raise ValueError("count") + pg.buffer = list() + buff = GXByteBuffer(19) + buff.setUInt8(0x02) + buff.setUInt8(DataType.STRUCTURE) + buff.setUInt8(0x04) + _GXCommon.setData(self.settings, buff, DataType.UINT32, index) + if count == 0: + _GXCommon.setData(self.settings, buff, DataType.UINT32, count) + else: + _GXCommon.setData(self.settings, buff, DataType.UINT32, index + count - 1) + columnIndex = 1 + columnCount = 0 + pos = 0 + if columns: + if not pg.captureObjects: + raise ValueError("Read capture objects first.") + columnIndex = len(pg.captureObjects) + columnCount = 1 + for c in columns: + pos = 0 + found = False + for k, v in pg.captureObjects: + pos += 1 + if k.objectType == k.objectType and k.logicalName == c[0].logicalName and v.attributeIndex == c[1].attributeIndex and v.dataIndex == c[1].dataIndex: + found = True + if pos < columnIndex: + columnIndex = pos + columnCount = pos - columnIndex + 1 + break + if not found: + raise ValueError("Invalid column: " + c.logicalName) + _GXCommon.setData(self.settings, buff, DataType.UINT16, columnIndex) + _GXCommon.setData(self.settings, buff, DataType.UINT16, columnCount) + return self._read(pg.name, ObjectType.PROFILE_GENERIC, 2, buff) + + def readRowsByRange(self, pg, start, end, columns=None): + pg.buffer = list() + self.settings.resetBlockIndex() + if not isinstance(start, GXDateTime): + start = GXDateTime(start) + if not isinstance(end, GXDateTime): + end = GXDateTime(end) + sort = pg.sortObject + if not sort and pg.captureObjects: + sort = pg.captureObjects[0][0] + buff = GXByteBuffer(51) + buff.setUInt8(0x01) + buff.setUInt8(DataType.STRUCTURE) + buff.setUInt8(0x04) + buff.setUInt8(DataType.STRUCTURE) + buff.setUInt8(0x04) + if sort: + ot = sort.objectType + ln = sort.logicalName + else: + ot = ObjectType.CLOCK + ln = "0.0.1.0.0.255" + _GXCommon.setData(self.settings, buff, DataType.UINT16, ot) + _GXCommon.setData(self.settings, buff, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(ln)) + _GXCommon.setData(self.settings, buff, DataType.INT8, 2) + _GXCommon.setData(self.settings, buff, DataType.UINT16, 0) + if sort and isinstance(sort, GXDLMSData) and sort.logicalName == "0.0.1.1.0.255": + _GXCommon.setData(self.settings, buff, DataType.UINT32, GXDateTime.toUnixTime(start)) + _GXCommon.setData(self.settings, buff, DataType.UINT32, GXDateTime.toUnixTime(end)) + else: + _GXCommon.setData(self.settings, buff, DataType.OCTET_STRING, start) + _GXCommon.setData(self.settings, buff, DataType.OCTET_STRING, end) + buff.setUInt8(DataType.ARRAY) + if not columns: + buff.setUInt8(0x00) + else: + _GXCommon.setObjectCount(len(columns), buff) + for it in columns: + buff.setUInt8(DataType.STRUCTURE) + buff.setUInt8(4) + _GXCommon.setData(self.settings, buff, DataType.UINT16, it[0].objectType) + _GXCommon.setData(self.settings, buff, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(it[0].logicalName)) + _GXCommon.setData(self.settings, buff, DataType.INT8, it[1].attributeIndex) + _GXCommon.setData(self.settings, buff, DataType.UINT16, it[1].dataIndex) + return self._read(pg.name, ObjectType.PROFILE_GENERIC, 2, buff) + + @classmethod + def createObject(cls, type_): + return _GXObjectFactory.createObject(type_) + + def receiverReady(self, type_): + return GXDLMS.receiverReady(self.settings, type_) + + def getData(self, reply, data, notify=None): + # pylint: disable=broad-except + data.xml = None + ret = False + if isinstance(reply, bytearray): + reply = GXByteBuffer(reply) + elif isinstance(reply, bytes): + reply = GXByteBuffer(reply) + try: + ret = GXDLMS.getData(self.settings, reply, data, notify) + except Exception as ex: + if self.translator is None or self.throwExceptions: + raise ex + ret = True + if ret and self.translator and data.moreData == RequestTypes.NONE: + if data.xml is None: + data.xml = (GXDLMSTranslatorStructure(self.translator.outputType, self.translator.omitXmlNameSpace, self.translator.hex, self.translator.showStringAsHex, self.translator.comments, self.translator.tags)) + pos = data.data.position + try: + data2 = data.data + if data.command == Command.GET_RESPONSE: + tmp = GXByteBuffer((4 + data.data.size)) + tmp.setUInt8(data.command) + tmp.setUInt8(GetCommandType.NORMAL) + tmp.setUInt8(int(data.invokeId)) + tmp.setUInt8(0) + tmp.set(data.data) + data.data = tmp + elif data.command == Command.METHOD_RESPONSE: + tmp = GXByteBuffer((6 + data.data.size)) + tmp.setUInt8(data.command) + tmp.setUInt8(GetCommandType.NORMAL) + tmp.setUInt8(int(data.invokeId)) + tmp.setUInt8(0) + tmp.setUInt8(1) + tmp.setUInt8(0) + tmp.set(data.data) + data.data = tmp + elif data.command == Command.READ_RESPONSE: + tmp = GXByteBuffer(3 + data.data.size) + tmp.setUInt8(data.command) + tmp.setUInt8(VariableAccessSpecification.VARIABLE_NAME) + tmp.setUInt8(int(data.invokeId)) + tmp.setUInt8(0) + tmp.set(data.data) + data.data = tmp + data.data.position = 0 + if data.command == Command.SNRM or data.command == Command.UA: + data.xml.appendStartTag(data.command) + if data.data.size != 0: + #pylint: disable=protected-access + self.translator._pduToXml2(data.xml, data.data, self.translator.omitXmlDeclaration, self.translator.omitXmlNameSpace, True) + data.xml.appendEndTag(data.command) + else: + if data.data.size != 0: + #pylint: disable=protected-access + self.translator._pduToXml2(data.xml, data.data, self.translator.omitXmlDeclaration, self.translator.omitXmlNameSpace, True) + data.data = data2 + finally: + data.data.position = pos + return ret + + @classmethod + def getServerAddressFromSerialNumber(cls, serialNumber, logicalAddress=1, formula=None): + if not formula: + ret = SerialNumberCounter.count(serialNumber, "SN % 10000 + 1000") + else: + ret = SerialNumberCounter.count(serialNumber, formula) + if logicalAddress: + ret |= logicalAddress << 14 + return ret + + # Encrypt Landis+Gyr High level password. + @classmethod + def encryptLandisGyrHighLevelAuthentication(cls, password, seed): + crypted = bytearray(len(seed)) + crypted[0: len(seed)] = seed + for pos in range(0, len(password) - 1): + if password[pos] != 0x30: + crypted[pos] += (int(password[pos]) - 0x30) + #Convert to upper case letter. + if crypted[pos] > '9' and crypted[pos] < 'A': + crypted[pos] += 7 + return crypted + + @classmethod + def getServerAddress(cls, logicalAddress, physicalAddress, addressSize=0): + if addressSize < 4 and physicalAddress < 0x80 and logicalAddress < 0x80: + return logicalAddress << 7 | physicalAddress + if physicalAddress < 0x4000 and logicalAddress < 0x4000: + return logicalAddress << 14 | physicalAddress + raise ValueError("Invalid logical or physical address.") + + def accessRequest(self, time, list_): + bb = GXByteBuffer() + _GXCommon.setObjectCount(len(list_), bb) + for it in list_: + bb.setUInt8(it.command) + bb.setUInt16(it.target.objectType) + bb.set(_GXCommon.logicalNameToBytes(it.target.logicalName)) + bb.setUInt8(it.index) + _GXCommon.setObjectCount(len(list_), bb) + for it in list_: + if it.command == AccessServiceCommandType.GET: + bb.setUInt8(0) + elif it.command in (AccessServiceCommandType.SET, AccessServiceCommandType.ACTION): + value = (it.target).getValue(self.settings, ValueEventArgs(it.target, it.index, 0, None)) + type_ = it.target.getDataType(it.index) + if type_ == DataType.NONE: + raise Exception("Invalid parameter. In python value type must give.") + _GXCommon.setData(self.settings, bb, type_, value) + else: + raise ValueError("Invalid command.") + + p = GXDLMSLNParameters(self.settings, 0, Command.ACCESS_REQUEST, 0xFF, None, bb, 0xff) + if time: + p.time = GXDateTime(time) + return GXDLMS.getLnMessages(p) + + def parseAccessResponse(self, list_, data): + info = _GXDataInfo() + cnt = _GXCommon.getObjectCount(data) + if len(list_) != cnt: + raise ValueError("List size and values size do not match.") + for it in list_: + info.clear() + it.value = _GXCommon.getData(self.settings, data, info) + cnt = _GXCommon.getObjectCount(data) + if len(list_) != cnt: + raise ValueError("List size and values size do not match.") + for it in list_: + #Get access type. + data.getUInt8() + #Get status. + it.error = ErrorCode(data.getUInt8()) + if it.command == AccessServiceCommandType.GET and it.error == ErrorCode.OK: + self.updateValue(it.target, it.index, it.value) + + @classmethod + def getInitialConformance(cls, useLogicalNameReferencing): + return GXDLMSSettings.getInitialConformance(useLogicalNameReferencing) + + def parseReport(self, reply, list_): + if reply.command == Command.EVENT_NOTIFICATION: + GXDLMSLNCommandHandler.handleEventNotification(self.settings, reply, list_) + return None + if reply.command == Command.INFORMATION_REPORT: + GXDLMSSNCommandHandler.handleInformationReport(self.settings, reply, list_) + return None + if reply.command == Command.DATA_NOTIFICATION: + return reply.value + raise ValueError("Invalid command. " + reply.command) + + def parsePushObjects(self, data): + objects = list() + if data: + c = GXDLMSConverter(self.standard) + for it in data: + tmp = it + classID = tmp[0] + if classID > 0: + comp = None + comp = self.objects.findByLN(classID, _GXCommon.toLogicalName(tmp[1])) + if comp is None: + comp = GXDLMSClient.__createDLMSObject(classID, 0, 0, tmp[1], None) + self.settings.objects.append(comp) + c.updateOBISCodeInformation(comp) + if comp.__class__ != GXDLMSObject.__class__: + objects.append((comp, tmp[2])) + else: + print("Unknown object: " + str(classID) + " " + _GXCommon.toLogicalName(tmp[1])) + return objects + + def getFrameSize(self, data): + if self.interfaceType == InterfaceType.WRAPPER: + if data.available() < 8 or data.getUInt16(data.position) != 1: + return 8 - data.available() + return data.getUInt16(data.position + 6) + return 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSConfirmedServiceError.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSConfirmedServiceError.py new file mode 100644 index 0000000..b209e46 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSConfirmedServiceError.py @@ -0,0 +1,123 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .ServiceError import ServiceError +from .ConfirmedServiceError import ConfirmedServiceError +from .enums.ApplicationReference import ApplicationReference +from .enums.VdeStateError import VdeStateError +from .enums.HardwareResource import HardwareResource +from .enums.Definition import Definition +from .enums.Access import Access +from .enums.Service import Service +from .enums.Initiate import Initiate +from .enums.LoadDataSet import LoadDataSet +from .enums.Task import Task + +class GXDLMSConfirmedServiceError(Exception): + """ + DLMS specific exception class that has error description available from getDescription method. + """ + + # + # Constructor for Confirmed ServiceError. + # + # @param service + # @param type + # @param value + # + def __init__(self, service=None, type_=None, value=0): + Exception.__init__(self, "ServiceError " + self.__getConfirmedServiceError(service) + " exception. " + self.__getServiceError(type_) + " " + self.__getServiceErrorValue(type_, value)) + self.confirmedServiceError = service + self.serviceError = type_ + self.serviceErrorValue = value + + @classmethod + def __getConfirmedServiceError(cls, stateError): + str_ = "" + if stateError == ConfirmedServiceError.INITIATE_ERROR: + str_ = "Initiate Error" + elif stateError == ConfirmedServiceError.READ: + str_ = "Read" + elif stateError == ConfirmedServiceError.WRITE: + str_ = "Write" + return str_ + + @classmethod + def __getServiceError(cls, error): + str_ = "" + if error == ServiceError.APPLICATION_REFERENCE: + str_ = "Application reference" + elif error == ServiceError.HARDWARE_RESOURCE: + str_ = "Hardware resource" + elif error == ServiceError.VDE_STATE_ERROR: + str_ = "Vde state error" + elif error == ServiceError.SERVICE: + str_ = "Service" + elif error == ServiceError.DEFINITION: + str_ = "Definition" + elif error == ServiceError.ACCESS: + str_ = "Access" + elif error == ServiceError.INITIATE: + str_ = "Initiate" + elif error == ServiceError.LOAD_DATASET: + str_ = "Load dataset" + elif error == ServiceError.TASK: + str_ = "Task" + elif error == ServiceError.OTHER_ERROR: + str_ = "Other Error" + return str_ + + @classmethod + def __getServiceErrorValue(cls, error, value): + str_ = "" + if error == ServiceError.APPLICATION_REFERENCE: + str_ = str(ApplicationReference(value)) + elif error == ServiceError.HARDWARE_RESOURCE: + str_ = str(HardwareResource(value)) + elif error == ServiceError.VDE_STATE_ERROR: + str_ = str(VdeStateError(value)) + elif error == ServiceError.SERVICE: + str_ = str(Service(value)) + elif error == ServiceError.DEFINITION: + str_ = str(Definition(value)) + elif error == ServiceError.ACCESS: + str_ = str(Access(value)) + elif error == ServiceError.INITIATE: + str_ = str(Initiate(value)) + elif error == ServiceError.LOAD_DATASET: + str_ = str(LoadDataSet(value)) + elif error == ServiceError.TASK: + str_ = str(Task(value)) + elif error == ServiceError.OTHER_ERROR: + str_ = str(value) + return str_ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSConnectionEventArgs.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSConnectionEventArgs.py new file mode 100644 index 0000000..9447335 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSConnectionEventArgs.py @@ -0,0 +1,38 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXDLMSConnectionEventArgs: + # + # Server ID that client try to use to make connection. + # + serverID = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSConverter.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSConverter.py new file mode 100644 index 0000000..709ba68 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSConverter.py @@ -0,0 +1,505 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +import pkg_resources +from .enums import Standard, ObjectType, DataType +from .GXStandardObisCodeCollection import GXStandardObisCodeCollection +from .GXStandardObisCode import GXStandardObisCode +from .internal._GXCommon import _GXCommon +from .GXEnum import GXEnum +from .GXInt8 import GXInt8 +from .GXInt16 import GXInt16 +from .GXInt32 import GXInt32 +from .GXInt64 import GXInt64 +from .GXUInt8 import GXUInt8 +from .GXUInt16 import GXUInt16 +from .GXUInt32 import GXUInt32 +from .GXUInt64 import GXUInt64 +from .GXDate import GXDate +from .GXDateTime import GXDateTime +from .GXTime import GXTime +from .GXByteBuffer import GXByteBuffer +from .manufacturersettings.GXObisCode import GXObisCode + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class GXDLMSConverter: + # + # Constructor. + # + # value: Used standard. + # + def __init__(self, value=Standard.DLMS): + self.standard = value + # Collection of standard OBIS codes. + self.codes = GXStandardObisCodeCollection() + + # + # Get OBIS code description. + # + # @param logicalName + # Logical name (OBIS code). + # @param type + # Object type. + # @param description + # Description filter. + # Array of descriptions that match given OBIS code. + # + def getDescription(self, logicalName, type_=ObjectType.NONE, description=None): + if not self.codes: + self.__readStandardObisInfo(self.standard, self.codes) + list_ = list() + all_ = not logicalName + for it in self.codes.find(logicalName, type_): + if description and not it.description.lower().contains(description.lower()): + continue + if all_: + list_.append("A=" + it.getOBIS()[0] + ", B=" + it.getOBIS()[1] + ", C=" + it.getOBIS()[2] + ", D=" + it.getOBIS()[3] + ", E=" + it.getOBIS()[4] + ", F=" + it.getOBIS()[5] + "\r\n" + it.description) + else: + list_.append(it.description) + return list_ + + #pylint: disable=too-many-boolean-expressions + @classmethod + def __updateOBISCodeInfo(cls, codes, it, standard): + ln = it.logicalName + list_ = codes.find(ln, it.objectType) + code_ = list_[0] + if code_: + if not it.description: + it.description = code_.description + #Update data type from DLMS standard. + if standard != Standard.DLMS: + d = list_[len(list_) - 1] + code_.dataType = d.dataType + if not code_.uiDataType: + if "10" in code_.dataType: + code_.uiDataType = "10" + elif "25" in code_.dataType or "26" in code_.dataType: + code_.uiDataType = "25" + elif "9" in code_.dataType: + if (GXStandardObisCodeCollection.equalsMask2("0.0-64.96.7.10-14.255", ln) or GXStandardObisCodeCollection.equalsMask2("0.0-64.0.1.5.0-99,255", ln) or GXStandardObisCodeCollection.equalsMask2("0.0-64.0.1.2.0-99,255", ln) or GXStandardObisCodeCollection.equalsMask2("1.0-64.0.1.2.0-99,255", ln) or GXStandardObisCodeCollection.equalsMask2("1.0-64.0.1.5.0-99,255", ln) or GXStandardObisCodeCollection.equalsMask2("1.0-64.0.9.0.255", ln) or GXStandardObisCodeCollection.equalsMask2("1.0-64.0.9.6.255", ln) or GXStandardObisCodeCollection.equalsMask2("1.0-64.0.9.7.255", ln) or GXStandardObisCodeCollection.equalsMask2("1.0-64.0.9.13.255", ln) or GXStandardObisCodeCollection.equalsMask2("1.0-64.0.9.14.255", ln) or GXStandardObisCodeCollection.equalsMask2("1.0-64.0.9.15.255", ln)): + code_.uiDataType = "25" + #Local time + elif GXStandardObisCodeCollection.equalsMask2("1.0-64.0.9.1.255", ln): + code_.uiDataType = "27" + #Local date + elif GXStandardObisCodeCollection.equalsMask2("1.0-64.0.9.2.255", ln): + code_.uiDataType = "26" + #Active firmware identifier + elif GXStandardObisCodeCollection.equalsMask2("1.0.0.2.0.255", ln): + code_.uiDataType = "10" + #Unix time + elif it.objectType == ObjectType.DATA and GXStandardObisCodeCollection.equalsMask2("0.0.1.1.0.255", it.logicalName): + code_.uiDataType = "25" + + if not code_.dataType == "*" and not code_.dataType == "" and "," not in code_.dataType: + tp = int(code_.dataType) + if it.objectType in (ObjectType.DATA, ObjectType.REGISTER, ObjectType.REGISTER_ACTIVATION, ObjectType.EXTENDED_REGISTER): + it.setDataType(2, tp) + if code_.uiDataType: + tp = int(code_.uiDataType) + if it.objectType in (ObjectType.DATA, ObjectType.REGISTER, ObjectType.REGISTER_ACTIVATION, ObjectType.EXTENDED_REGISTER): + it.setUIDataType(2, tp) + else: + print("Unknown OBIS Code: " + it.logicalName + " Type: " + it.objectType) + + def updateOBISCodeInformation(self, objects): + if not self.codes: + self.__readStandardObisInfo(self.standard, self.codes) + if isinstance(objects, list): + for it in objects: + self.__updateOBISCodeInfo(self.codes, it, self.standard) + else: + self.__updateOBISCodeInfo(self.codes, objects, self.standard) + + @classmethod + def __getObjects(cls, standard): + codes = list() + if standard == Standard.ITALY: + str_ = pkg_resources.resource_string(__name__, "Italy.txt").decode("utf-8") + elif standard == Standard.INDIA: + str_ = pkg_resources.resource_string(__name__, "India.txt").decode("utf-8") + elif standard == Standard.SAUDI_ARABIA: + str_ = pkg_resources.resource_string(__name__, "SaudiArabia.txt").decode("utf-8") + elif standard == Standard.SPAIN: + str_ = pkg_resources.resource_string(__name__, "Spain.txt").decode("utf-8") + if not str_: + return None + str_ = str_.replace("\n", "\r") + rows = str_.split('\r') + for it in rows: + if it and not it.startswith("#"): + items = it.split(';') + if len(items) > 1: + ot = int(items[0]) + ln = _GXCommon.toLogicalName(_GXCommon.logicalNameToBytes(items[1])) + version = int(items[2]) + desc = items[3] + code_ = GXObisCode(ln, ot, 0, desc) + code_.version = version + if len(items) > 4: + code_.uiDataType = items[4] + codes.append(code_) + return codes + + @classmethod + def __readStandardObisInfo(cls, standard, codes): + if standard != Standard.DLMS: + for it in GXDLMSConverter.__getObjects(standard): + tmp = GXStandardObisCode(it.logicalName.split('.')) + tmp.interfaces = str(it.objectType) + tmp.description = it.description + tmp.uiDataType = it.uiDataType + codes.append(tmp) + + str_ = pkg_resources.resource_string(__name__, "OBISCodes.txt").decode("utf-8") + str_ = str_.replace("\n", "\r") + rows = str_.split('\r') + for it in rows: + if it and not it.startswith("#"): + items = it.split(';') + obis = items[0].split('.') + try: + code_ = GXStandardObisCode(obis, str(items[3]) + "; " + str(items[4]) + "; " + str(items[5]) + "; " + str(items[6]) + "; " + str(items[7]), str(items[1]), str(items[2])) + codes.append(code_) + except UnicodeEncodeError: + pass + + @classmethod + def changeType(cls, value, type_): + if _GXCommon.getDLMSDataType(value) == type_: + return value + if type_ == DataType.ARRAY: + raise ValueError("Can't change array types.") + if type_ == DataType.BCD: + ret = int(value) + elif type_ == DataType.BOOLEAN: + ret = bool(int(value)) + elif type_ == DataType.COMPACT_ARRAY: + raise ValueError("Can't change compact array types.") + elif type_ == DataType.DATE: + ret = GXDate(value) + elif type_ == DataType.DATETIME: + ret = GXDateTime(value) + elif type_ == DataType.ENUM: + ret = GXEnum(value) + elif type_ == DataType.FLOAT32: + ret = float(value) + elif type_ == DataType.FLOAT64: + ret = float(value) + elif type_ == DataType.INT16: + ret = GXInt16(value) + elif type_ == DataType.INT32: + ret = GXInt32(value) + elif type_ == DataType.INT64: + ret = GXInt64(value) + elif type_ == DataType.INT8: + ret = GXInt8(value) + elif type_ == DataType.NONE: + ret = None + elif type_ == DataType.OCTET_STRING: + if isinstance(value, str): + ret = GXByteBuffer.hexToBytes(str(value)) + else: + raise ValueError("Can't change octect string type.") + elif type_ == DataType.STRING: + ret = str(value) + elif type_ == DataType.BITSTRING: + ret = str(value) + elif type_ == DataType.STRING_UTF8: + ret = str(value) + elif type_ == DataType.STRUCTURE: + raise ValueError("Can't change structure types.") + elif type_ == DataType.TIME: + ret = GXTime(value) + elif type_ == DataType.UINT16: + ret = GXUInt16(value) + elif type_ == DataType.UINT32: + ret = GXUInt32(value) + elif type_ == DataType.UINT64: + ret = GXUInt64(value) + elif type_ == DataType.UINT8: + ret = GXUInt8(value) + else: + raise ValueError('Invalid data type.') + return ret + + @classmethod + def objectTypeToString(cls, ot): + if ot == ObjectType.ACTION_SCHEDULE: + ret = "ActionSchedule" + elif ot == ObjectType.ACTIVITY_CALENDAR: + ret = "ActivityCalendar" + elif ot == ObjectType.ASSOCIATION_LOGICAL_NAME: + ret = "AssociationLogicalName" + elif ot == ObjectType.ASSOCIATION_SHORT_NAME: + ret = "AssociationShortName" + elif ot == ObjectType.AUTO_ANSWER: + ret = "AutoAnswer" + elif ot == ObjectType.AUTO_CONNECT: + ret = "AutoConnect" + elif ot == ObjectType.CLOCK: + ret = "Clock" + elif ot == ObjectType.DATA: + ret = "Data" + elif ot == ObjectType.DEMAND_REGISTER: + ret = "DemandRegister" + elif ot == ObjectType.MAC_ADDRESS_SETUP: + ret = "MacAddressSetup" + elif ot == ObjectType.EXTENDED_REGISTER: + ret = "ExtendedRegister" + elif ot == ObjectType.GPRS_SETUP: + ret = "GprsSetup" + elif ot == ObjectType.SECURITY_SETUP: + ret = "SecuritySetup" + elif ot == ObjectType.IEC_HDLC_SETUP: + ret = "IecHdlcSetup" + elif ot == ObjectType.IEC_LOCAL_PORT_SETUP: + ret = "IecLocalPortSetup" + elif ot == ObjectType.IEC_TWISTED_PAIR_SETUP: + ret = "IEC_TWISTED_PAIR_SETUP" + elif ot == ObjectType.IP4_SETUP: + ret = "Ip4Setup" + elif ot == ObjectType.MBUS_SLAVE_PORT_SETUP: + ret = "MBusSlavePortSetup" + elif ot == ObjectType.IMAGE_TRANSFER: + ret = "ImageTransfer" + elif ot == ObjectType.DISCONNECT_CONTROL: + ret = "DisconnectControl" + elif ot == ObjectType.LIMITER: + ret = "Limiter" + elif ot == ObjectType.MBUS_CLIENT: + ret = "MBusClient" + elif ot == ObjectType.MODEM_CONFIGURATION: + ret = "ModemConfiguration" + elif ot == ObjectType.PPP_SETUP: + ret = "PppSetup" + elif ot == ObjectType.PROFILE_GENERIC: + ret = "ProfileGeneric" + elif ot == ObjectType.REGISTER: + ret = "Register" + elif ot == ObjectType.REGISTER_ACTIVATION: + ret = "RegisterActivation" + elif ot == ObjectType.REGISTER_MONITOR: + ret = "RegisterMonitor" + elif ot == ObjectType.REGISTER_TABLE: + ret = "RegisterTable" + elif ot == ObjectType.ZIG_BEE_SAS_STARTUP: + ret = "ZigBeeSasStartup" + elif ot == ObjectType.ZIG_BEE_SAS_JOIN: + ret = "ZigBeeSasJoin" + elif ot == ObjectType.ZIG_BEE_SAS_APS_FRAGMENTATION: + ret = "ZigBeeSasApsFragmentation" + elif ot == ObjectType.ZIG_BEE_NETWORK_CONTROL: + ret = "ZigBeeNetworkControl" + elif ot == ObjectType.SAP_ASSIGNMENT: + ret = "SapAssignment" + elif ot == ObjectType.SCHEDULE: + ret = "Schedule" + elif ot == ObjectType.SCRIPT_TABLE: + ret = "ScriptTable" + elif ot == ObjectType.SMTP_SETUP: + ret = "SMTPSetup" + elif ot == ObjectType.SPECIAL_DAYS_TABLE: + ret = "SpecialDaysTable" + elif ot == ObjectType.STATUS_MAPPING: + ret = "StatusMapping" + elif ot == ObjectType.TCP_UDP_SETUP: + ret = "TcpUdpSetup" + elif ot == ObjectType.UTILITY_TABLES: + ret = "UtilityTables" + elif ot == ObjectType.MBUS_MASTER_PORT_SETUP: + ret = "MBusMasterPortSetup" + elif ot == ObjectType.PUSH_SETUP: + ret = "PushSetup" + elif ot == ObjectType.ACCOUNT: + ret = "Account" + elif ot == ObjectType.CREDIT: + ret = "Credit" + elif ot == ObjectType.CHARGE: + ret = "Charge" + elif ot == ObjectType.PARAMETER_MONITOR: + ret = "ParameterMonitor" + elif ot == ObjectType.TOKEN_GATEWAY: + ret = "Token" + elif ot == ObjectType.GSM_DIAGNOSTIC: + ret = "GSMDiagnostic" + elif ot == ObjectType.COMPACT_DATA: + ret = "CompactData" + elif ot == ObjectType.IP6_SETUP: + ret = "Ip6Setup" + elif ot == ObjectType.LLC_SSCS_SETUP: + ret = "LlcSscsSetup" + elif ot == ObjectType.PRIME_NB_OFDM_PLC_PHYSICAL_LAYER_COUNTERS: + ret = "PrimeNbOfdmPlcPhysicalLayerCounters" + elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_SETUP: + ret = "PrimeNbOfdmPlcMacSetup" + elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_FUNCTIONAL_PARAMETERS: + ret = "PrimeNbOfdmPlcMacFunctionalParameters" + elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_COUNTERS: + ret = "PrimeNbOfdmPlcMacCounters" + elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_NETWORK_ADMINISTRATION_DATA: + ret = "PrimeNbOfdmPlcMacNetworkAdministrationData" + elif ot == ObjectType.PRIME_NB_OFDM_PLC_APPLICATIONS_IDENTIFICATION: + ret = "PrimeNbOfdmPlcApplicationsIdentification" + elif ot == ObjectType.NTP_SETUP: + ret = "NtpSetup" + else: + ret = "Manufacture spesific." + return ret + + @classmethod + def valueOfObjectType(cls, value): + if value == "ActionSchedule": + ot = ObjectType.ACTION_SCHEDULE + elif value == "ActivityCalendar": + ot = ObjectType.ACTIVITY_CALENDAR + elif value == "AssociationLogicalName": + ot = ObjectType.ASSOCIATION_LOGICAL_NAME + elif value == "AssociationShortName": + ot = ObjectType.ASSOCIATION_SHORT_NAME + elif value == "AutoAnswer": + ot = ObjectType.AUTO_ANSWER + elif value == "AutoConnect": + ot = ObjectType.AUTO_CONNECT + elif value == "Clock": + ot = ObjectType.CLOCK + elif value == "Data": + ot = ObjectType.DATA + elif value == "DemandRegister": + ot = ObjectType.DEMAND_REGISTER + elif value == "MacAddressSetup": + ot = ObjectType.MAC_ADDRESS_SETUP + elif value == "ExtendedRegister": + ot = ObjectType.EXTENDED_REGISTER + elif value == "GprsSetup": + ot = ObjectType.GPRS_SETUP + elif value == "SecuritySetup": + ot = ObjectType.SECURITY_SETUP + elif value in ("IecHdlcSetup", "HdlcSetup"): + ot = ObjectType.IEC_HDLC_SETUP + elif value in ("IecLocalPortSetup", "IecOpticalPortSetup"): + ot = ObjectType.IEC_LOCAL_PORT_SETUP + elif value == "IEC_TWISTED_PAIR_SETUP": + ot = ObjectType.IEC_TWISTED_PAIR_SETUP + elif value == "Ip4Setup": + ot = ObjectType.IP4_SETUP + elif value == "MBusSlavePortSetup": + ot = ObjectType.MBUS_SLAVE_PORT_SETUP + elif value == "ImageTransfer": + ot = ObjectType.IMAGE_TRANSFER + elif value == "DisconnectControl": + ot = ObjectType.DISCONNECT_CONTROL + elif value == "Limiter": + ot = ObjectType.LIMITER + elif value == "MBusClient": + ot = ObjectType.MBUS_CLIENT + elif value == "ModemConfiguration": + ot = ObjectType.MODEM_CONFIGURATION + elif value == "PppSetup": + ot = ObjectType.PPP_SETUP + elif value == "ProfileGeneric": + ot = ObjectType.PROFILE_GENERIC + elif value == "Register": + ot = ObjectType.REGISTER + elif value == "RegisterActivation": + ot = ObjectType.REGISTER_ACTIVATION + elif value == "RegisterMonitor": + ot = ObjectType.REGISTER_MONITOR + elif value == "RegisterTable": + ot = ObjectType.REGISTER_TABLE + elif value == "ZigBeeSasStartup": + ot = ObjectType.ZIG_BEE_SAS_STARTUP + elif value == "ZigBeeSasJoin": + ot = ObjectType.ZIG_BEE_SAS_JOIN + elif value == "ZigBeeSasApsFragmentation": + ot = ObjectType.ZIG_BEE_SAS_APS_FRAGMENTATION + elif value == "ZigBeeNetworkControl": + ot = ObjectType.ZIG_BEE_NETWORK_CONTROL + elif value == "SapAssignment": + ot = ObjectType.SAP_ASSIGNMENT + elif value == "Schedule": + ot = ObjectType.SCHEDULE + elif value == "ScriptTable": + ot = ObjectType.SCRIPT_TABLE + elif value == "SMTPSetup": + ot = ObjectType.SMTP_SETUP + elif value == "SpecialDaysTable": + ot = ObjectType.SPECIAL_DAYS_TABLE + elif value == "StatusMapping": + ot = ObjectType.STATUS_MAPPING + elif value == "TcpUdpSetup": + ot = ObjectType.TCP_UDP_SETUP + elif value == "UtilityTables": + ot = ObjectType.UTILITY_TABLES + elif value == "MBusMasterPortSetup": + ot = ObjectType.MBUS_MASTER_PORT_SETUP + elif value == "PushSetup": + ot = ObjectType.PUSH_SETUP + elif value == "Account": + ot = ObjectType.ACCOUNT + elif value == "Credit": + ot = ObjectType.CREDIT + elif value == "Charge": + ot = ObjectType.CHARGE + elif value == "ParameterMonitor": + ot = ObjectType.PARAMETER_MONITOR + elif value in ("Token", "TokenGateway"): + ot = ObjectType.TOKEN_GATEWAY + elif value == "GSMDiagnostic": + ot = ObjectType.GSM_DIAGNOSTIC + elif value == "CompactData": + ot = ObjectType.COMPACT_DATA + elif value == "Ip6Setup": + ot = ObjectType.IP6_SETUP + elif value == "LlcSscsSetup": + ot = ObjectType.LLC_SSCS_SETUP + elif value == "PrimeNbOfdmPlcPhysicalLayerCounters": + ot = ObjectType.PRIME_NB_OFDM_PLC_PHYSICAL_LAYER_COUNTERS + elif value == "PrimeNbOfdmPlcMacSetup": + ot = ObjectType.PRIME_NB_OFDM_PLC_MAC_SETUP + elif value == "PrimeNbOfdmPlcMacFunctionalParameters": + ot = ObjectType.PRIME_NB_OFDM_PLC_MAC_FUNCTIONAL_PARAMETERS + elif value == "PrimeNbOfdmPlcMacCounters": + ot = ObjectType.PRIME_NB_OFDM_PLC_MAC_COUNTERS + elif value == "PrimeNbOfdmPlcMacNetworkAdministrationData": + ot = ObjectType.PRIME_NB_OFDM_PLC_MAC_NETWORK_ADMINISTRATION_DATA + elif value == "PrimeNbOfdmPlcApplicationsIdentification": + ot = ObjectType.PRIME_NB_OFDM_PLC_APPLICATIONS_IDENTIFICATION + elif value == "NtpSetup": + ot = ObjectType.NTP_SETUP + else: + ot = ObjectType.NONE + return ot diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSException.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSException.py new file mode 100644 index 0000000..0a28ab9 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSException.py @@ -0,0 +1,130 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import AssociationResult, ErrorCode, SourceDiagnostic + +class GXDLMSException(Exception): + """ + DLMS specific exception class that has error description available from + GetDescription method. + """ + def __init__(self, errCode, serviceErr=None): + if isinstance(errCode, AssociationResult): + Exception.__init__(self, "Connection is " + self.getResult(errCode) + "\r\n" + self.getDiagnostic(serviceErr)) + self.result = errCode + self.diagnostic = serviceErr + else: + Exception.__init__(self, self.getDescription(errCode)) + self.errorCode = errCode + + # + # Get result as a string. + # + # @param result + # Enumeration value of AssociationResult. + # String description of AssociationResult. + # + @classmethod + def getResult(cls, result): + if result == AssociationResult.PERMANENT_REJECTED: + ret = "permanently rejected" + elif result == AssociationResult.TRANSIENT_REJECTED: + ret = "transient rejected" + else: + ret = "Invalid error code." + return ret + + # + # Get diagnostic as a string. + # + # @param value + # Enumeration value of SourceDiagnostic. + # String description of SourceDiagnostic. + # + @classmethod + def getDiagnostic(cls, value): + if value == SourceDiagnostic.NO_REASON_GIVEN: + ret = "No reason is given." + elif value == SourceDiagnostic.NOT_SUPPORTED: + ret = "The application context name is not supported." + elif value == SourceDiagnostic.NOT_RECOGNISED: + ret = "The authentication mechanism name is not recognized." + elif value == SourceDiagnostic.MECHANISM_NAME_REGUIRED: + ret = "Authentication mechanism name is required." + elif value == SourceDiagnostic.AUTHENTICATION_FAILURE: + ret = "Authentication failure." + elif value == SourceDiagnostic.AUTHENTICATION_REQUIRED: + ret = "Authentication is required." + else: + ret = "Invalid error code." + return ret + + @classmethod + def getDescription(cls, errCode): + if errCode == ErrorCode.REJECTED: + str_ = "Rejected" + elif errCode == ErrorCode.OK: + str_ = "" + elif errCode == ErrorCode.HARDWARE_FAULT: + str_ = "Access Error : Device reports a hardware fault." + elif errCode == ErrorCode.TEMPORARY_FAILURE: + str_ = "Access Error : Device reports a temporary failure." + elif errCode == ErrorCode.READ_WRITE_DENIED: + str_ = "Access Error : Device reports Read-Write denied." + elif errCode == ErrorCode.UNDEFINED_OBJECT: + str_ = "Access Error : Device reports a undefined object." + elif errCode == ErrorCode.INCONSISTENT_CLASS: + str_ = "Access Error : " + "Device reports a inconsistent Class or object." + elif errCode == ErrorCode.UNAVAILABLE_OBJECT: + str_ = "Access Error : Device reports a unavailable object." + elif errCode == ErrorCode.UNMATCHED_TYPE: + str_ = "Access Error : Device reports a unmatched type." + elif errCode == ErrorCode.ACCESS_VIOLATED: + str_ = "Access Error : Device reports scope of access violated." + elif errCode == ErrorCode.DATA_BLOCK_UNAVAILABLE: + str_ = "Access Error : Data Block Unavailable." + elif errCode == ErrorCode.LONG_GET_OR_READ_ABORTED: + str_ = "Access Error : Long Get Or Read Aborted." + elif errCode == ErrorCode.NO_LONG_GET_OR_READ_IN_PROGRESS: + str_ = "Access Error : No Long Get Or Read In Progress." + elif errCode == ErrorCode.LONG_SET_OR_WRITE_ABORTED: + str_ = "Access Error : Long Set Or Write Aborted." + elif errCode == ErrorCode.NO_LONG_SET_OR_WRITE_IN_PROGRESS: + str_ = "Access Error : No Long Set Or Write In Progress." + elif errCode == ErrorCode.DATA_BLOCK_NUMBER_INVALID: + str_ = "Access Error : Data Block Number Invalid." + elif errCode == ErrorCode.OTHER_REASON: + str_ = "Access Error : Other Reason." + else: + str_ = "Access Error : Unknown error." + return str_ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSExceptionResponse.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSExceptionResponse.py new file mode 100644 index 0000000..f440191 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSExceptionResponse.py @@ -0,0 +1,79 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums.StateError import StateError +from .enums.ExceptionServiceError import ExceptionServiceError + +class GXDLMSExceptionResponse(Exception): + """ + DLMS specific exception class that has error description available from getDescription method. + """ + + # + # Constructor for Confirmed ServiceError. + # + # @param service + # @param type + # @param value + # + def __init__(self, error=None, type_=None, value=0): + Exception.__init__(self, self.__getStateError(error) + ". " + self.__getServiceError(type_)) + self.exceptionStateError = error + self.exceptionServiceError = type_ + self.serviceErrorValue = value + + @classmethod + def __getStateError(cls, stateError): + str_ = "" + if stateError == StateError.SERVICE_NOT_ALLOWED: + str_ = "Service not allowed" + elif stateError == StateError.SERVICE_UNKNOWN: + str_ = "Service unknown" + return str_ + + @classmethod + def __getServiceError(cls, error): + str_ = "" + if error == ExceptionServiceError.OPERATION_NOT_POSSIBLE: + str_ = "Operation not possible" + elif error == ExceptionServiceError.SERVICE_NOT_SUPPORTED: + str_ = "Service not supported" + elif error == ExceptionServiceError.OTHER_REASON: + str_ = "Other reason" + elif error == ExceptionServiceError.PDU_TOO_LONG: + str_ = "PDU is too long" + elif error == ExceptionServiceError.DECIPHERING_ERROR: + str_ = "Ciphering failed" + elif error == ExceptionServiceError.INVOCATION_COUNTER_ERROR: + str_ = "Invocation counter is invalid." + return str_ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSGateway.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSGateway.py new file mode 100644 index 0000000..9652253 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSGateway.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +# +# GXDLMSGateway contains information that is needed if gateway is used +# between the client and the meter. +# +class GXDLMSGateway: + # + # Constructor. + # + def __init__(self): + # Gateway network ID. + self.networkId = 0 + # Physical device address. + self.physicalDeviceAddress = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLNCommandHandler.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLNCommandHandler.py new file mode 100644 index 0000000..602e2cc --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLNCommandHandler.py @@ -0,0 +1,777 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +from .ActionRequestType import ActionRequestType +from .internal._GXCommon import _GXCommon +from .enums import Command, ErrorCode, ObjectType +from .ServiceError import ServiceError +from .enums.Service import Service +from .ConfirmedServiceError import ConfirmedServiceError +from .TranslatorTags import TranslatorTags +from .GXByteBuffer import GXByteBuffer +from .enums.AccessServiceCommandType import AccessServiceCommandType +from .internal._GXDataInfo import _GXDataInfo +from .TranslatorOutputType import TranslatorOutputType +from .SingleReadResponse import SingleReadResponse +from .GetCommandType import GetCommandType +from .GXDLMSLNParameters import GXDLMSLNParameters +from .SetRequestType import SetRequestType +from .ValueEventArgs import ValueEventArgs +from .enums.DataType import DataType +from .objects.enums.AssociationStatus import AssociationStatus +from .ConnectionState import ConnectionState +from .objects.GXDLMSAssociationLogicalName import GXDLMSAssociationLogicalName +from .objects.GXDLMSSecuritySetup import GXDLMSSecuritySetup +from .GXDLMS import GXDLMS +from .GXDLMSLongTransaction import GXDLMSLongTransaction +from .enums.AccessMode import AccessMode +from .enums.MethodAccessMode import MethodAccessMode +from .objects.GXDLMSProfileGeneric import GXDLMSProfileGeneric + +# pylint: disable=bad-option-value,too-many-locals,too-many-arguments,broad-except,too-many-nested-blocks,old-style-class +class GXDLMSLNCommandHandler: + #Constructor. + def __init__(self): + pass + + @classmethod + def handleGetRequest(cls, settings, server, data, replyData, xml): + # Return error if connection is not established. + if xml is None and not settings.acceptConnection(): + replyData.set(server.generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE, Service.UNSUPPORTED)) + return + # Get type. + type_ = int(data.getUInt8()) + # Get invoke ID and priority. + invokeID = data.getUInt8() + settings.updateInvokeId(invokeID) + if xml: + xml.appendStartTag(Command.GET_REQUEST) + xml.appendStartTag(Command.GET_REQUEST, type_) + xml.appendLine(TranslatorTags.INVOKE_ID, "Value", xml.integerToHex(invokeID, 2)) + # GetRequest normal + if type_ == GetCommandType.NORMAL: + cls.getRequestNormal(settings, invokeID, server, data, replyData, xml) + elif type_ == GetCommandType.NEXT_DATA_BLOCK: + # Get request for next data block + cls.getRequestNextDataBlock(settings, invokeID, server, data, replyData, xml, False) + elif type_ == GetCommandType.WITH_LIST: + # Get request with a list. + cls.getRequestWithList(settings, invokeID, server, data, replyData, xml) + else: + bb = GXByteBuffer() + settings.resetBlockIndex() + # Access Error : Device reports a hardware fault. + bb.setUInt8(ErrorCode.HARDWARE_FAULT) + GXDLMS.getLNPdu(GXDLMSLNParameters(settings, invokeID, Command.GET_RESPONSE, type_, None, bb, ErrorCode.OK), replyData) + if xml: + xml.appendEndTag(Command.GET_REQUEST, type_) + xml.appendEndTag(Command.GET_REQUEST) + + # + # Handle set request. + # + # Reply to the client. + # + @classmethod + def handleSetRequest(cls, settings, server, data, replyData, xml): + # Return error if connection is not established. + if xml is None and not settings.acceptConnection(): + replyData.set(server.generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE, Service.UNSUPPORTED)) + return + # Get type. + type_ = int(data.getUInt8()) + # Get invoke ID and priority. + invoke = data.getUInt8() + settings.updateInvokeId(invoke) + # SetRequest normal or Set Request With First Data Block + p = GXDLMSLNParameters(settings, invoke, Command.SET_RESPONSE, type_, None, None, 0) + if xml: + xml.appendStartTag(Command.SET_REQUEST) + xml.appendStartTag(Command.SET_REQUEST, type_) + # InvokeIdAndPriority + xml.appendLine(TranslatorTags.INVOKE_ID, "Value", xml.integerToHex(invoke, 2)) + if type_ in (SetRequestType.NORMAL, SetRequestType.FIRST_DATA_BLOCK): + cls.handleSetRequestNormal(settings, server, data, type_, p, xml) + elif type_ == SetRequestType.WITH_DATA_BLOCK: + cls.hanleSetRequestWithDataBlock(settings, server, data, p, xml) + elif type_ == SetRequestType.WITH_LIST: + cls.hanleSetRequestWithList(settings, invoke, server, data, xml) + else: + settings.resetBlockIndex() + p.status = ErrorCode.HARDWARE_FAULT + if xml: + xml.appendEndTag(Command.SET_REQUEST, type_) + xml.appendEndTag(Command.SET_REQUEST) + return + GXDLMS.getLNPdu(p, replyData) + + @classmethod + def appendAttributeDescriptor(cls, xml, ci, ln, attributeIndex): + xml.appendStartTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR) + if xml.comments: + ot = ObjectType(ci) + if ot: + xml.appendComment(str(ot)) + xml.appendLine(TranslatorTags.CLASS_ID, "Value", xml.integerToHex(ci, 4)) + xml.appendComment(_GXCommon.toLogicalName(ln)) + xml.appendLine(TranslatorTags.INSTANCE_ID, "Value", GXByteBuffer.hex(ln, False)) + xml.appendLine(TranslatorTags.ATTRIBUTE_ID, "Value", xml.integerToHex(attributeIndex, 2)) + xml.appendEndTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR) + + @classmethod + def appendMethodDescriptor(cls, xml, ci, ln, attributeIndex): + xml.appendStartTag(TranslatorTags.METHOD_DESCRIPTOR) + if xml.comments: + ot = ObjectType(ci) + if ot: + xml.appendComment(str(ot)) + xml.appendLine(TranslatorTags.CLASS_ID, "Value", xml.integerToHex(ci, 4)) + xml.appendComment(_GXCommon.toLogicalName(ln)) + xml.appendLine(TranslatorTags.INSTANCE_ID, "Value", GXByteBuffer.hex(ln, False)) + xml.appendLine(TranslatorTags.METHOD_ID, "Value", xml.integerToHex(attributeIndex, 2)) + xml.appendEndTag(TranslatorTags.METHOD_DESCRIPTOR) + + # + # Handle get request normal command. + # + # @param data + # Received data. + # + @classmethod + def getRequestNormal(cls, settings, invokeID, server, data, replyData, xml): + bb = GXByteBuffer() + e = None + status = ErrorCode.OK + settings.setCount(0) + settings.setIndex(0) + settings.resetBlockIndex() + # CI + ci = data.getUInt16() + ln = bytearray(6) + data.get(ln) + # Attribute Id + attributeIndex = data.getUInt8() + # AccessSelection + selection = data.getUInt8() + selector = 0 + parameters = None + info = _GXDataInfo() + if selection != 0: + selector = data.getUInt8() + if xml: + cls.appendAttributeDescriptor(xml, ci, ln, attributeIndex) + if selection != 0: + info.xml = (xml) + xml.appendStartTag(TranslatorTags.ACCESS_SELECTION) + xml.appendLine(TranslatorTags.ACCESS_SELECTOR, "Value", xml.integerToHex(selector, 2)) + xml.appendStartTag(TranslatorTags.ACCESS_PARAMETERS) + _GXCommon.getData(settings, data, info) + xml.appendEndTag(TranslatorTags.ACCESS_PARAMETERS) + xml.appendEndTag(TranslatorTags.ACCESS_SELECTION) + return + if selection != 0: + parameters = _GXCommon.getData(settings, data, info) + ot = ObjectType(ci) + obj = settings.objects.findByLN(ot, _GXCommon.toLogicalName(ln)) + if obj is None: + obj = server.onFindObject(ot, 0, _GXCommon.toLogicalName(ln)) + e = ValueEventArgs(server, obj, attributeIndex, selector, parameters) + if obj is None: + # "Access Error : Device reports a undefined object." + status = ErrorCode.UNDEFINED_OBJECT + else: + e.invokeId = (invokeID) + if server.onGetAttributeAccess(e) == AccessMode.NO_ACCESS: + # Read Write denied. + status = ErrorCode.READ_WRITE_DENIED + else: + if isinstance(obj, (GXDLMSProfileGeneric,)) and attributeIndex == 2: + dt = None + rowsize = 0 + pg = e.target + # Count how many rows we can fit to one PDU. + for k, v in pg.captureObjects: + dt = k.getDataType(v.attributeIndex) + if dt == DataType.OCTET_STRING: + dt = k.getUIDataType(v.attributeIndex) + if dt == DataType.DATETIME: + rowsize += _GXCommon.getDataTypeSize(DataType.DATETIME) + elif dt == DataType.DATE: + rowsize += _GXCommon.getDataTypeSize(DataType.DATE) + elif dt == DataType.TIME: + rowsize += _GXCommon.getDataTypeSize(DataType.TIME) + elif dt == DataType.NONE: + rowsize += 2 + else: + rowsize += _GXCommon.getDataTypeSize(dt) + if rowsize != 0: + e.rowToPdu = int(settings.maxPduSize / rowsize) + server.notifyRead() + value = None + if e.handled: + value = e.value + else: + settings.setCount(e.rowEndIndex - e.rowBeginIndex) + value = obj.getValue(settings, e) + server.onPostRead() + if e.byteArray: + bb.set(value) + else: + GXDLMS.appendData(settings, obj, attributeIndex, bb, value) + status = e.error + GXDLMS.getLNPdu(GXDLMSLNParameters(settings, e.invokeId, Command.GET_RESPONSE, 1, None, bb, status), replyData) + if settings.count != settings.index or len(bb) != bb.position: + server.setTransaction(GXDLMSLongTransaction(e, Command.GET_REQUEST, bb)) + + # + # Handle get request next data block command. + # + # @param data + # Received data. + # + @classmethod + def getRequestNextDataBlock(cls, settings, invokeID, server, data, replyData, xml, streaming): + bb = GXByteBuffer() + if not streaming: + index = int(data.getUInt32()) + # Get block index. + if xml: + xml.appendLine(TranslatorTags.BLOCK_NUMBER, None, xml.integerToHex(index, 8)) + return + if index != settings.blockIndex: + GXDLMS.getLNPdu(GXDLMSLNParameters(settings, invokeID, Command.GET_RESPONSE, 2, None, bb, ErrorCode.DATA_BLOCK_NUMBER_INVALID), replyData) + return + settings.increaseBlockIndex() + p = GXDLMSLNParameters(settings, invokeID, Command.GENERAL_BLOCK_TRANSFER if streaming else Command.GET_RESPONSE, 2, None, bb, ErrorCode.OK) + p.streaming = streaming + p.gbtWindowSize = settings.gbtWndowSize + # If transaction is not in progress. + if server.getTransaction() is None: + p.status = int(ErrorCode.NO_LONG_GET_OR_READ_IN_PROGRESS) + else: + bb.set(server.getTransaction().data) + moreData = settings.index != settings.getCount() + if moreData: + # If there is multiple blocks on the buffer. + # This might happen when Max PDU size is very small. + if len(bb) < settings.getMaxPduSize(): + value = None + for arg in server.transaction.targets: + arg.invokeId = (p.invokeId) + server.onPreRead([arg]) + if arg.handled: + value = arg.value + else: + value = arg.target.getValue(settings, arg) + p.invokeId = (arg.invokeId) + # Add data. + if arg.byteArray: + bb.set(int(value)) + else: + GXDLMS.appendData(settings, arg.target, arg.index, bb, value) + moreData = settings.index != settings.getCount() + p.multipleBlocks = True + GXDLMS.getLNPdu(p, replyData) + if moreData or len(bb) - bb.position != 0: + server.getTransaction().setData(bb) + else: + server.setTransaction(None) + settings.resetBlockIndex() + + # + # Handle get request with list command. + # + # @param data + # Received data. + # + @classmethod + def getRequestWithList(cls, settings, invokeID, server, data, replyData, xml): + bb = GXByteBuffer() + pos = int() + cnt = _GXCommon.getObjectCount(data) + _GXCommon.setObjectCount(cnt, bb) + list_ = list() + if xml: + xml.appendStartTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR_LIST, "Qty", xml.integerToHex(cnt, 2)) + while pos != cnt: + ci = ObjectType(data.getUInt16()) + ln = bytearray(6) + data.get(ln) + attributeIndex = data.getUInt8() + # AccessSelection + selection = data.getUInt8() + selector = 0 + parameters = None + if selection != 0: + selector = data.getUInt8() + i = _GXDataInfo() + parameters = _GXCommon.getData(settings, data, i) + if xml: + xml.appendStartTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR_WITH_SELECTION) + xml.appendStartTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR) + if xml.comments: + xml.appendComment(ci.__str__()) + xml.appendLine(TranslatorTags.CLASS_ID, "Value", xml.integerToHex(ci.value, 4)) + xml.appendComment(_GXCommon.toLogicalName(ln)) + xml.appendLine(TranslatorTags.INSTANCE_ID, "Value", GXByteBuffer.hex(ln, False)) + xml.appendLine(TranslatorTags.ATTRIBUTE_ID, "Value", xml.integerToHex(attributeIndex, 2)) + xml.appendEndTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR) + xml.appendEndTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR_WITH_SELECTION) + else: + obj = settings.objects.findByLN(ci, _GXCommon.toLogicalName(ln)) + if obj is None: + obj = server.onFindObject(ci, 0, _GXCommon.toLogicalName(ln)) + arg = ValueEventArgs(server, obj, attributeIndex, selector, parameters) + arg.invokeId = (invokeID) + if obj is None: + arg.error = ErrorCode.UNDEFINED_OBJECT + list_.append(arg) + else: + if server.onGetAttributeAccess(arg) == AccessMode.NO_ACCESS: + # Read Write denied. + arg.error = ErrorCode.READ_WRITE_DENIED + list_.append(arg) + else: + list_.append(arg) + pos += 1 + if xml: + xml.appendEndTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR_LIST) + return + server.onPreRead(list_) + value = None + pos = 0 + p = GXDLMSLNParameters(settings, invokeID, Command.GET_RESPONSE, 3, None, bb, 0xFF) + for it in list_: + try: + if it.handled: + value = it.value + else: + value = it.target.getValue(settings, it) + bb.setUInt8(it.error) + if it.byteArray: + bb.set(value) + else: + GXDLMS.appendData(settings, it.target, it.index, bb, value) + p.invokeId = it.invokeId + except Exception: + bb.setUInt8(ErrorCode.HARDWARE_FAULT) + if settings.index != settings.count: + server.setTransaction(GXDLMSLongTransaction(list_, Command.GET_REQUEST, None)) + pos += 1 + server.onPostRead(list_) + GXDLMS.getLNPdu(p, replyData) + + @classmethod + def handleSetRequestNormal(cls, settings, server, data, type_, p, xml): + value = None + reply = _GXDataInfo() + # CI + ci = data.getInt16() + ot = ObjectType(ci & 0xFFFF) + ln = bytearray(6) + data.get(ln) + # Attribute index. + index = data.getUInt8() + # Get Access Selection. + data.getUInt8() + if type_ == 2: + lastBlock = data.getUInt8() + p.multipleBlocks = lastBlock == 0 + blockNumber = data.getUInt32() + if blockNumber != settings.blockIndex: + p.status = ErrorCode.DATA_BLOCK_NUMBER_INVALID + return + settings.increaseBlockIndex() + size = _GXCommon.getObjectCount(data) + realSize = len(data) - data.position + if size != realSize: + p.status = ErrorCode.DATA_BLOCK_UNAVAILABLE + return + if xml: + cls.appendAttributeDescriptor(xml, ci, ln, index) + xml.appendStartTag(TranslatorTags.DATA_BLOCK) + xml.appendLine(TranslatorTags.LAST_BLOCK, "Value", xml.integerToHex(lastBlock, 2)) + xml.appendLine(TranslatorTags.BLOCK_NUMBER, "Value", xml.integerToHex(blockNumber, 8)) + xml.appendLine(TranslatorTags.RAW_DATA, "Value", data.remainingHexString(False)) + xml.appendEndTag(TranslatorTags.DATA_BLOCK) + return + if xml: + cls.appendAttributeDescriptor(xml, ci, ln, index) + xml.appendStartTag(TranslatorTags.VALUE) + di = _GXDataInfo() + di.xml = (xml) + value = _GXCommon.getData(settings, data, di) + if not di.complete: + value = GXByteBuffer.hex(data.data, False, data.position, len(data) - data.position) + elif isinstance(value, bytearray): + value = GXByteBuffer.hex(value, False) + xml.appendEndTag(TranslatorTags.VALUE) + return + if not p.isMultipleBlocks(): + settings.resetBlockIndex() + value = _GXCommon.getData(settings, data, reply) + obj = settings.objects.findByLN(ot, _GXCommon.toLogicalName(ln)) + if obj is None: + obj = server.onFindObject(ot, 0, _GXCommon.toLogicalName(ln)) + # If target is unknown. + if obj is None: + # Device reports a undefined object. + p.setStatus(ErrorCode.UNDEFINED_OBJECT) + else: + e = ValueEventArgs(server, obj, index, 0, None) + e.invokeId = (p.invokeId) + am = server.onGetAttributeAccess(e) + # If write is denied. + if am not in (AccessMode.WRITE, AccessMode.READ_WRITE): + # Read Write denied. + p.setStatus(ErrorCode.READ_WRITE_DENIED) + else: + try: + if isinstance(value, bytearray): + dt = obj.getDataType(index) + if dt not in (DataType.NONE, DataType.OCTET_STRING): + value = _GXCommon.changeType(settings, value, dt) + e.value = value + list_ = list(e) + if p.isMultipleBlocks(): + server.setTransaction(GXDLMSLongTransaction(list_, Command.GET_REQUEST, data)) + server.onPreWrite(list_) + if e.error != ErrorCode.OK: + p.status = e.error + elif not e.handled and not p.multipleBlocks: + obj.setValue(settings, e) + server.onPostWrite(list_) + p.invokeId = e.invokeId + except Exception: + p.setStatus(ErrorCode.HARDWARE_FAULT) + + @classmethod + def hanleSetRequestWithDataBlock(cls, settings, server, data, p, xml): + reply = _GXDataInfo() + lastBlock = data.getUInt8() + p.multipleBlocks = lastBlock == 0 + blockNumber = data.getUInt32() + if xml is None and blockNumber != settings.blockIndex: + p.status = ErrorCode.DATA_BLOCK_NUMBER_INVALID + else: + settings.increaseBlockIndex() + size = _GXCommon.getObjectCount(data) + realSize = len(data) - data.position + if size != realSize: + p.status = ErrorCode.DATA_BLOCK_UNAVAILABLE + if xml: + xml.appendStartTag(TranslatorTags.DATA_BLOCK) + xml.appendLine(TranslatorTags.LAST_BLOCK, "Value", xml.integerToHex(lastBlock, 2)) + xml.appendLine(TranslatorTags.BLOCK_NUMBER, "Value", xml.integerToHex(blockNumber, 8)) + xml.appendLine(TranslatorTags.RAW_DATA, "Value", data.remainingHexString(False)) + xml.appendEndTag(TranslatorTags.DATA_BLOCK) + return + server.getTransaction().data.set(data) + if not p.isMultipleBlocks(): + try: + value = _GXCommon.getData(settings, server.getTransaction().data, reply) + if isinstance(value, bytearray): + dt = server.transaction.targets[0].target.getDataType(server.transaction.targets[0].index) + if dt not in (DataType.NONE, DataType.OCTET_STRING): + value = _GXCommon.changeType(settings, value, dt) + server.transaction.targets[0].setValue(value) + server.onPreWrite(server.transaction.targets) + if not server.transaction.targets[0].handled and not p.isMultipleBlocks(): + server.transaction.targets[0].target.setValue(settings, server.transaction.targets[0]) + server.onPostWrite(server.transaction.targets) + except Exception: + p.setStatus(ErrorCode.HARDWARE_FAULT) + finally: + server.setTransaction(None) + settings.resetBlockIndex() + p.multipleBlocks = True + + @classmethod + def hanleSetRequestWithList(cls, settings, invokeID, server, data, xml): + e = None + cnt = _GXCommon.getObjectCount(data) + list_ = list() + if xml: + xml.appendStartTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR_LIST, "Qty", xml.integerToHex(cnt, 2)) + try: + pos = 0 + while pos != cnt: + ci = ObjectType(data.getUInt16()) + ln = bytearray(6) + data.get(ln) + attributeIndex = data.getUInt8() + selection = data.getUInt8() + selector = 0 + parameters = None + if selection != 0: + selector = data.getUInt8() + info = _GXDataInfo() + parameters = _GXCommon.getData(settings, data, info) + if xml: + xml.appendStartTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR_WITH_SELECTION) + xml.appendStartTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR) + xml.appendComment(ci.__str__()) + xml.appendLine(TranslatorTags.CLASS_ID, "Value", xml.integerToHex(ci.value, 4)) + xml.appendComment(_GXCommon.toLogicalName(ln)) + xml.appendLine(TranslatorTags.INSTANCE_ID, "Value", GXByteBuffer.hex(ln, False)) + xml.appendLine(TranslatorTags.ATTRIBUTE_ID, "Value", xml.integerToHex(attributeIndex, 2)) + xml.appendEndTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR) + xml.appendEndTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR_WITH_SELECTION) + else: + obj = settings.objects.findByLN(ci, _GXCommon.toLogicalName(ln)) + if obj is None: + obj = server.onFindObject(ci, 0, _GXCommon.toLogicalName(ln)) + if obj is None: + e = ValueEventArgs(server, obj, attributeIndex, 0, 0) + e.error = ErrorCode.UNDEFINED_OBJECT + list_.append(e) + else: + arg = ValueEventArgs(server, obj, attributeIndex, selector, parameters) + arg.invokeId = (invokeID) + if server.onGetAttributeAccess(arg) == AccessMode.NO_ACCESS: + arg.error = ErrorCode.READ_WRITE_DENIED + list_.append(arg) + else: + list_.append(arg) + pos += 1 + cnt = _GXCommon.getObjectCount(data) + if xml: + xml.appendEndTag(TranslatorTags.ATTRIBUTE_DESCRIPTOR_LIST) + xml.appendStartTag(TranslatorTags.VALUE_LIST, "Qty", xml.integerToHex(cnt, 2)) + pos = 0 + while pos != cnt: + di = _GXDataInfo() + di.xml = xml + if xml and xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendStartTag(Command.WRITE_REQUEST, SingleReadResponse.DATA) + value = _GXCommon.getData(settings, data, di) + if not di.complete: + value = GXByteBuffer.hex(data.data, False, data.position, len(data) - data.position) + elif isinstance(value, bytearray): + value = GXByteBuffer.hex(value, False) + if xml and xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendEndTag(Command.WRITE_REQUEST, SingleReadResponse.DATA) + pos += 1 + if xml: + xml.appendEndTag(TranslatorTags.VALUE_LIST) + except Exception as ex: + if xml is None: + raise ex + + @classmethod + def handleMethodRequest(cls, settings, server, data, connectionInfo, replyData, xml): + error = ErrorCode.OK + bb = GXByteBuffer() + type_ = data.getUInt8() + invokeId = data.getUInt8() + settings.updateInvokeId(invokeId) + ci = data.getUInt16() + ot = ObjectType(ci) + ln = bytearray(6) + data.get(ln) + id_ = data.getUInt8() + parameters = None + selection = data.getUInt8() + if xml: + xml.appendStartTag(Command.METHOD_REQUEST) + if type_ == ActionRequestType.NORMAL: + xml.appendStartTag(Command.METHOD_REQUEST, ActionRequestType.NORMAL) + xml.appendLine(TranslatorTags.INVOKE_ID, "Value", xml.integerToHex(invokeId, 2)) + cls.appendMethodDescriptor(xml, ci, ln, id_) + if selection != 0: + xml.appendStartTag(TranslatorTags.METHOD_INVOCATION_PARAMETERS) + di = _GXDataInfo() + di.xml = (xml) + _GXCommon.getData(settings, data, di) + xml.appendEndTag(TranslatorTags.METHOD_INVOCATION_PARAMETERS) + xml.appendEndTag(Command.METHOD_REQUEST, ActionRequestType.NORMAL) + xml.appendEndTag(Command.METHOD_REQUEST) + return + if selection != 0: + info = _GXDataInfo() + parameters = _GXCommon.getData(settings, data, info) + obj = settings.objects.findByLN(ot, _GXCommon.toLogicalName(ln)) + if not settings.acceptConnection() and (ci != ObjectType.ASSOCIATION_LOGICAL_NAME or id_ != 1): + replyData.set(server.generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE, Service.UNSUPPORTED)) + return + if obj is None: + obj = server.onFindObject(ot, 0, _GXCommon.toLogicalName(ln)) + if obj is None: + error = ErrorCode.UNDEFINED_OBJECT + else: + e = ValueEventArgs(server, obj, id_, 0, parameters) + e.invokeId = (invokeId) + if server.onGetMethodAccess(e) == MethodAccessMode.NO_ACCESS: + error = ErrorCode.READ_WRITE_DENIED + else: + server.onPreAction(list(e)) + if e.handled: + actionReply = int(e.value) + else: + actionReply = obj.invoke(settings, e) + server.onPostAction(list(e)) + if actionReply and e.error == ErrorCode.OK: + bb.setUInt8(1) + bb.setUInt8(0) + if e.byteArray: + bb.set(actionReply) + else: + _GXCommon.setData(settings, bb, _GXCommon.getDLMSDataType(actionReply), actionReply) + else: + error = e.error + bb.setUInt8(0) + invokeId = int(e.invokeId) + p = GXDLMSLNParameters(settings, invokeId, Command.METHOD_RESPONSE, 1, None, bb, error) + GXDLMS.getLNPdu(p, replyData) + if isinstance(obj, (GXDLMSAssociationLogicalName,)) and id_ == 1: + if obj.getAssociationStatus() == AssociationStatus.ASSOCIATED: + server.notifyConnected(connectionInfo) + settings.setConnected(settings.connected | ConnectionState.DLMS) + else: + server.onInvalidConnection(connectionInfo) + settings.setConnected(settings.connected & ~ConnectionState.DLMS) + + #Start to use new keys. + if e is not None and error == 0 and isinstance(obj, GXDLMSSecuritySetup) and id_ == 2: + obj.applyKeys(settings, e) + + @classmethod + def handleAccessRequest(cls, settings, server, data, reply, xml): + #pylint: disable=bad-option-value,redefined-variable-type + if xml is None and not settings.acceptConnection(): + reply.set(server.generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE, Service.UNSUPPORTED)) + return + invokeId = data.getUInt32() + settings.setLongInvokeID(invokeId) + len_ = _GXCommon.getObjectCount(data) + tmp = None + if len_ != 0: + tmp = bytearray(len_) + data.get(tmp) + if xml is None: + dt = DataType.DATETIME + if len_ == 4: + dt = DataType.TIME + elif len_ == 5: + dt = DataType.DATE + info = _GXDataInfo() + info.type = dt + _GXCommon.getData(settings, GXByteBuffer(tmp), info) + cnt = _GXCommon.getObjectCount(data) + if xml: + xml.appendStartTag(Command.ACCESS_REQUEST) + xml.appendLine(TranslatorTags.LONG_INVOKE_ID, "Value", xml.integerToHex(invokeId, 8)) + xml.appendLine(TranslatorTags.DATE_TIME, "Value", GXByteBuffer.hex(tmp, False)) + xml.appendStartTag(TranslatorTags.ACCESS_REQUEST_BODY) + xml.appendStartTag(TranslatorTags.LIST_OF_ACCESS_REQUEST_SPECIFICATION, "Qty", xml.integerToHex(cnt, 2)) + type_ = 0 + pos = 0 + while pos != cnt: + type_ = AccessServiceCommandType(data.getUInt8()) + if type_ not in (AccessServiceCommandType.GET, AccessServiceCommandType.SET, AccessServiceCommandType.ACTION): + raise ValueError("Invalid access service command type.") + ci = data.getUInt16() + ln = bytearray(6) + data.get(ln) + attributeIndex = data.getUInt8() + if xml: + xml.appendStartTag(TranslatorTags.ACCESS_REQUEST_SPECIFICATION) + xml.appendStartTag(Command.ACCESS_REQUEST, type_) + cls.appendAttributeDescriptor(xml, ci, ln, attributeIndex) + xml.appendEndTag(Command.ACCESS_REQUEST, type_) + xml.appendEndTag(TranslatorTags.ACCESS_REQUEST_SPECIFICATION) + pos += 1 + if xml: + xml.appendEndTag(TranslatorTags.LIST_OF_ACCESS_REQUEST_SPECIFICATION) + xml.appendStartTag(TranslatorTags.ACCESS_REQUEST_LIST_OF_DATA, "Qty", xml.integerToHex(cnt, 2)) + cnt = _GXCommon.getObjectCount(data) + pos = 0 + while pos != cnt: + di = _GXDataInfo() + di.xml = xml + if xml and xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendStartTag(Command.WRITE_REQUEST, SingleReadResponse.DATA) + value = _GXCommon.getData(settings, data, di) + if not di.complete: + value = GXByteBuffer.hex(data.data, False, data.position, len(data) - data.position) + elif isinstance(value, bytearray): + value = GXByteBuffer.hex(value, False) + if xml and xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendEndTag(Command.WRITE_REQUEST, SingleReadResponse.DATA) + pos += 1 + if xml: + xml.appendEndTag(TranslatorTags.ACCESS_REQUEST_LIST_OF_DATA) + xml.appendEndTag(TranslatorTags.ACCESS_REQUEST_BODY) + xml.appendEndTag(Command.ACCESS_REQUEST) + + @classmethod + def handleEventNotification(cls, settings, reply, list_): + data = reply.data + reply.time = None + #Check is date-time available. + len_ = data.getUInt8() + tmp = None + if len_ != 0: + len_ = data.getUInt8() + tmp = bytearray(len_) + data.get(tmp) + reply.time = _GXCommon.changeType(settings, tmp, DataType.DATETIME) + if reply.xml: + reply.xml.appendStartTag(Command.EVENT_NOTIFICATION) + if reply.time: + reply.xml.appendComment(str(reply.time)) + reply.xml.appendLine(TranslatorTags.TIME, None, GXByteBuffer.hex(tmp, False)) + ci = data.getUInt16() + ln = bytearray(6) + data.get(ln) + index = data.getUInt8() + if reply.xml: + cls.appendAttributeDescriptor(reply.xml, ci, ln, index) + reply.xml.appendStartTag(TranslatorTags.ATTRIBUTE_VALUE) + di = _GXDataInfo() + di.xml = reply.xml + value = _GXCommon.getData(settings, reply.data, di) + if reply.xml: + reply.xml.appendEndTag(TranslatorTags.ATTRIBUTE_VALUE) + reply.xml.appendEndTag(Command.EVENT_NOTIFICATION) + else: + obj = settings.objects.findByLN(ObjectType(ci), _GXCommon.toLogicalName(ln)) + if obj: + v = ValueEventArgs(obj, index, 0, None) + v.value = value + obj.setValue(settings, v) + list_.append((obj, int(index))) + else: + print("InformationReport message. Unknown object : " + str(ObjectType(ci)) + " " + _GXCommon.toLogicalName(ln)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLNParameters.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLNParameters.py new file mode 100644 index 0000000..214270a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLNParameters.py @@ -0,0 +1,78 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from .enums.Command import Command +from .GetCommandType import GetCommandType +# +# LN Parameters +#pylint: disable=too-many-instance-attributes, too-many-arguments +class GXDLMSLNParameters: + # + # Constructor. + # + # forSettings: DLMS settings. + # forInvokeId: Invoke ID. + # forCommand: Command. + # forCommandType: Command type. + # forAttributeDescriptor: Attribute descriptor. + # forData: Data. + def __init__(self, forSettings, forInvokeId, forCommand, forCommandType, forAttributeDescriptor, forData, forStatus): + # DLMS settings. + self.settings = forSettings + self.invokeId = forInvokeId + self.blockIndex = self.settings.blockIndex + self.blockNumberAck = self.settings.blockNumberAck + # DLMS Command. + self.command = forCommand + # Request type. + self.requestType = forCommandType + # Attribute descriptor. + self.attributeDescriptor = forAttributeDescriptor + # Data. + self.data = forData + # Send date and time. This is used in Data notification messages. + self.time = None + # Reply status. + self.status = forStatus + # Are there more data to send or more data to receive. + self.multipleBlocks = forSettings.count != forSettings.index + # Is this last block in send. + self.lastBlock = forSettings.count == forSettings.index + self.gbtWindowSize = 1 + # Is GBT streaming used. + self.streaming = False + if self.settings: + self.settings.command = forCommand + if forCommand == Command.GET_REQUEST and forCommandType != GetCommandType.NEXT_DATA_BLOCK: + self.settings.commandType = forCommandType diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLimits.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLimits.py new file mode 100644 index 0000000..77dacae --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLimits.py @@ -0,0 +1,45 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# +# GXDLMSLimits contains commands for retrieving and setting the limits of +# field ength and window size, when communicating with the server. +# +from .GXHdlcSettings import GXHdlcSettings + +class GXDLMSLimits(GXHdlcSettings): + # + # Constructor. + # + def __init__(self): + GXHdlcSettings.__init__(self) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLongTransaction.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLongTransaction.py new file mode 100644 index 0000000..81390a7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSLongTransaction.py @@ -0,0 +1,56 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXByteBuffer import GXByteBuffer +# Long get or set information is saved here. +# +class GXDLMSLongTransaction: + # + # Constructor. + # + # @param forTargets + # Targets. + # @param forCommand + # Command. + # @param forData + # Data. + # + def __init__(self, forTargets, forCommand, forData=None): + # Targets. + self.targets = forTargets + # Executed command. + self.command = forCommand + # Extra data from PDU. + self.data = GXByteBuffer() + if forData: + self.data.set(forData) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSNotify.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSNotify.py new file mode 100644 index 0000000..8e9541b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSNotify.py @@ -0,0 +1,288 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSSettings import GXDLMSSettings +from .ValueEventArgs import ValueEventArgs +from .GXDateTime import GXDateTime +from .internal._GXCommon import _GXCommon +from .enums.Command import Command +from .GXDLMS import GXDLMS +from .GXDLMSLNParameters import GXDLMSLNParameters +from .GXByteBuffer import GXByteBuffer +from .GXDLMSSNParameters import GXDLMSSNParameters +from .VariableAccessSpecification import VariableAccessSpecification +from .enums.DataType import DataType +from .enums.Conformance import Conformance + +#pylint: disable=bad-option-value,useless-object-inheritance,too-many-public-methods +class GXDLMSNotify(object): + """This class is used to send data notify and push messages to the clients.""" + + # + # Constructor. + # + # @param useLogicalNameReferencing + # Is Logical Name referencing used. + # @param clientAddress + # Server address. + # @param serverAddress + # Client address. + # @param interfaceType + # Object type. + # + def __init__(self, useLogicalNameReferencing, clientAddress, serverAddress, interfaceType): + # DLMS settings. + self.settings = GXDLMSSettings(True) + self.useLogicalNameReferencing = useLogicalNameReferencing + self.settings.clientAddress = clientAddress + self.settings.serverAddress = serverAddress + self.settings.interfaceType = interfaceType + + def getConformance(self): + """What kind of services are used.""" + return self.settings.negotiatedConformance + + def setConformance(self, value): + """What kind of services are used.""" + self.settings.negotiatedConformance = value + + # What kind of services are used. + conformance = property(getConformance, setConformance) + + # + # @param value + # Cipher interface that is used to cipher PDU. + # + def setCipher(self, value): + self.settings.cipher = value + + def getObjects(self): + return self.settings.objects + + # Get list of meter's objects. + objects = property(getObjects) + + # + # Information from the connection size that server can + # handle. + # + @property + def limits(self): + """Obsolete. Use hdlcSettings instead.""" + return self.settings.hdlc + + # + # HDLC framing settings. + # + @property + def hdlcSettings(self): + return self.settings.hdlc + + def getMaxReceivePDUSize(self): + return self.settings.maxPduSize + + def setMaxReceivePDUSize(self, value): + self.settings.maxPduSize = value + + # + # Retrieves the maximum size of received PDU. PDU size tells maximum size + # of PDU packet. Value can be from 0 to 0xFFFF. By default the value is + # 0xFFFF. + # + # @see GXDLMSClient#clientAddress + # @see GXDLMSClient#serverAddress + # @see GXDLMSClient#useLogicalNameReferencing + # Maximum size of received PDU. + # + maxReceivePDUSize = property(getMaxReceivePDUSize, setMaxReceivePDUSize) + + def getUseLogicalNameReferencing(self): + return self.settings.getUseLogicalNameReferencing() + + def setUseLogicalNameReferencing(self, value): + self.settings.setUseLogicalNameReferencing(value) + + # + # Determines, whether Logical, or Short name, referencing is used. + # Referencing depends on the device to communicate with. Normally, a + # device + # supports only either Logical or Short name referencing. The referencing + # is defined by the device manufacturer. If the referencing is wrong, the + # SNMR message will fail. + # + # Is Logical Name referencing used. + # + useLogicalNameReferencing = property(getUseLogicalNameReferencing, setUseLogicalNameReferencing) + + def getPriority(self): + return self.settings.priority + + def setPriority(self, value): + self.settings.priority = value + + # + # Used Priority. + # + priority = property(getPriority, setPriority) + + def getServiceClass(self): + return self.settings.serviceClass + + def setServiceClass(self, value): + self.settings.serviceClass = value + + # + # Used service class. + # + serviceClass = property(getServiceClass, setServiceClass) + + def getInvokeID(self): + return self.settings.invokeId + + def setInvokeID(self, value): + self.settings.invokeID = value + + # + # Invoke ID. + # + invokeID = property(getInvokeID, setInvokeID) + + # + # Removes the HDLC frame from the packet, and returns COSEM data only. + # + # @param reply + # The received data from the device. + # @param data + # Information from the received data. + # Is frame complete. + # + def getData(self, reply, data): + return GXDLMS.getData(self.settings, reply, data, None) + + # + # Add value of COSEM object to byte buffer. AddData method can be used + # with + # GetDataNotificationMessage -method. DLMS specification do not specify + # the + # structure of Data-Notification body. So each manufacture can sent + # different data. + # + # @param obj + # COSEM object. + # @param index + # Attribute index. + # @param buff + # Byte buffer. + # + def addData(self, obj, index, buff): + dt = None + e = ValueEventArgs(self.settings, obj, index, 0, None) + value = obj.getValue(self.settings, e) + dt = obj.getDataType(index) + if dt == DataType.NONE and value: + dt = _GXCommon.getDLMSDataType(value) + _GXCommon.setData(self.settings, buff, dt, value) + + # + # Generates data notification message. + # + # @param time + # Date time. Set Date(0) if not added. + # @param data + # Notification body. + # Generated data notification message(s). + # + def generateDataNotificationMessages(self, time, data): + reply = None + if self.useLogicalNameReferencing: + p = GXDLMSLNParameters(self.settings, 0, Command.DATA_NOTIFICATION, 0, None, data, 0xff) + if time is None: + p.time = None + else: + p.time = GXDateTime(time) + reply = GXDLMS.getLnMessages(p) + else: + p2 = GXDLMSSNParameters(self.settings, Command.DATA_NOTIFICATION, 1, 0, data, None) + reply = GXDLMS.getSnMessages(p2) + if self.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER == 0 and len(reply) != 1: + raise ValueError("Data is not fit to one PDU. Use general block transfer.") + return reply + + # + # Generates push setup message. + # + # @param date + # Date time. Set to null or Date(0) if not used. + # @param push + # Target Push object. + # Generated data notification message(s). + # + def generatePushSetupMessages(self, date, push): + if push is None: + raise ValueError("push") + buff = GXByteBuffer() + buff.setUInt8(DataType.STRUCTURE) + _GXCommon.setObjectCount(len(push.pushObjectList), buff) + for k, v in push.pushObjectList: + self.addData(k, v.attributeIndex, buff) + return self.generateDataNotificationMessages(date, buff) + + def generateReport(self, time, list_): + #pylint: disable=bad-option-value,redefined-variable-type + if not list_: + raise ValueError("list") + if self.useLogicalNameReferencing and len(list_) != 1: + raise ValueError("Only one object can send with Event Notification request.") + buff = GXByteBuffer() + reply = None + if self.useLogicalNameReferencing: + for k, v in list_: + buff.setUInt16(k.objectType) + buff.set(_GXCommon.logicalNameToBytes(k.logicalName)) + buff.setUInt8(v) + self.addData(k, v, buff) + p = GXDLMSLNParameters(self.settings, 0, Command.EVENT_NOTIFICATION, 0, None, buff, 0xff) + p.time = time + reply = GXDLMS.getLnMessages(p) + else: + p = GXDLMSSNParameters(self.settings, Command.INFORMATION_REPORT, len(list_), 0xFF, None, buff) + for k, v in list_: + buff.setUInt8(VariableAccessSpecification.VARIABLE_NAME) + sn = k.shortName + sn += (v - 1) * 8 + buff.setUInt16(sn) + _GXCommon.setObjectCount(len(list_), buff) + for k, v in list_: + self.addData(k, v, buff) + reply = GXDLMS.getSnMessages(p) + return reply diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSSNCommandHandler.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSSNCommandHandler.py new file mode 100644 index 0000000..debae1a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSSNCommandHandler.py @@ -0,0 +1,482 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +from .TranslatorOutputType import TranslatorOutputType +from .TranslatorTags import TranslatorTags +from .ConfirmedServiceError import ConfirmedServiceError +from .ServiceError import ServiceError +from .enums import Command, ErrorCode, DataType, AccessMode, Service, ObjectType, MethodAccessMode +from .VariableAccessSpecification import VariableAccessSpecification +from .internal._GXCommon import _GXCommon +from .internal._GXDataInfo import _GXDataInfo +from .GXByteBuffer import GXByteBuffer +from .GXDLMS import GXDLMS +from .GXDLMSSNParameters import GXDLMSSNParameters +from .SingleReadResponse import SingleReadResponse +from .GXSNInfo import GXSNInfo +from .ValueEventArgs import ValueEventArgs +from .GXDLMSLongTransaction import GXDLMSLongTransaction +from .SingleWriteResponse import SingleWriteResponse + +# pylint: disable=bad-option-value,too-many-locals,too-many-arguments,old-style-class +class GXDLMSSNCommandHandler: + #Constructor. + def __init__(self): + pass + + @classmethod + def handleRead(cls, settings, server, type_, data, list_, reads, actions, replyData, xml): + # GetRequest normal + sn = data.getInt16() + if xml: + if xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendStartTag(TranslatorTags.VARIABLE_ACCESS_SPECIFICATION) + else: + sn &= 0xFFFF + if type_ == VariableAccessSpecification.PARAMETERISED_ACCESS: + xml.appendStartTag(Command.READ_REQUEST, VariableAccessSpecification.PARAMETERISED_ACCESS) + xml.appendLine(Command.READ_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME, "Value", xml.integerToHex(sn, 4)) + xml.appendLine(TranslatorTags.SELECTOR, "Value", xml.integerToHex(data.getUInt8(), 2)) + di = _GXDataInfo() + di.xml = xml + xml.appendStartTag(TranslatorTags.PARAMETER) + _GXCommon.getData(settings, data, di) + xml.appendEndTag(TranslatorTags.PARAMETER) + xml.appendEndTag(Command.READ_REQUEST, VariableAccessSpecification.PARAMETERISED_ACCESS) + else: + xml.appendLine(Command.READ_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME, "Value", xml.integerToHex(sn, 4)) + if xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendEndTag(TranslatorTags.VARIABLE_ACCESS_SPECIFICATION) + return + sn = sn & 0xFFFF + i = cls.findSNObject(server, server.settings, sn) + e = ValueEventArgs(server, i.item, i.index, 0, None) + e.action = i.action + if type_ == VariableAccessSpecification.PARAMETERISED_ACCESS: + e.selector = data.getUInt8() + di = _GXDataInfo() + e.parameters = _GXCommon.getData(settings, data, di) + # Return error if connection is not established. + if not settings.acceptConnection() and (not e.action or e.target.shortName != 0xFA00 or e.index != 8): + replyData.set(server.generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE, Service.UNSUPPORTED)) + return + list_.append(e) + if not e.action and server.onGetAttributeAccess(e) == AccessMode.NO_ACCESS: + e.error = ErrorCode.READ_WRITE_DENIED + elif e.action and server.onGetMethodAccess(e) == MethodAccessMode.NO_ACCESS: + e.error = ErrorCode.READ_WRITE_DENIED + else: + if e.action: + actions.append(e) + else: + reads.append(e) + + # + # Handle read Block in blocks. + # + # @param data + # Received data. + # + @classmethod + def handleReadBlockNumberAccess(cls, settings, server, data, replyData, xml): + blockNumber = data.getUInt16() + if xml: + xml.appendStartTag(Command.READ_REQUEST, VariableAccessSpecification.BLOCK_NUMBER_ACCESS) + xml.appendLine(TranslatorTags.BLOCK_NUMBER, "Value", xml.integerToHex(blockNumber, 4)) + xml.appendEndTag(Command.READ_REQUEST, VariableAccessSpecification.BLOCK_NUMBER_ACCESS) + return + bb = GXByteBuffer() + if blockNumber != settings.blockIndex: + bb.setUInt8(ErrorCode.DATA_BLOCK_NUMBER_INVALID) + GXDLMS.getSNPdu(GXDLMSSNParameters(settings, Command.READ_RESPONSE, 1, SingleReadResponse.DATA_ACCESS_ERROR, bb, None), replyData) + settings.resetBlockIndex() + return + if settings.index != settings.count and server.transaction.data.size < settings.maxPduSize: + reads = list() + actions = list() + for it in server.transaction.targets: + if it.action: + actions.append(it) + else: + reads.append(it) + if reads: + server.onPreRead(reads) + if actions: + server.onPreAction(actions) + cls.getReadData(settings, server.transaction.targets, server.transaction.data) + if reads: + server.onPostRead(reads) + if actions: + server.onPostAction(actions) + settings.increaseBlockIndex() + p = GXDLMSSNParameters(settings, Command.READ_RESPONSE, 1, SingleReadResponse.DATA_BLOCK_RESULT, bb, server.transaction.data) + p.multipleBlocks = True + GXDLMS.getSNPdu(p, replyData) + if server.transaction.data.size == server.transaction.data.position: + server.transaction = None + settings.resetBlockIndex() + else: + server.transaction.data.strip() + + @classmethod + def getReadData(cls, settings, list_, data): + value = None + first = True + type_ = SingleReadResponse.DATA + for e in list_: + if e.handled: + value = e.value + else: + if e.action: + value = e.target.invoke(settings, e) + else: + value = e.target.getValue(settings, e) + if e.error == ErrorCode.OK: + if not first and list_: + data.setUInt8(SingleReadResponse.DATA) + if e.action: + _GXCommon.setData(settings, data, _GXCommon.getDLMSDataType(value), value) + else: + GXDLMS.appendData(settings, e.target, e.index, data, value) + else: + if not first and list_: + data.setUInt8(SingleReadResponse.DATA_ACCESS_ERROR) + data.setUInt8(e.error) + type_ = SingleReadResponse.DATA_ACCESS_ERROR + first = False + return type_ + + @classmethod + def handleReadDataBlockAccess(cls, settings, server, command, data, cnt, replyData, xml): + bb = GXByteBuffer() + lastBlock = data.getUInt8() + blockNumber = data.getUInt16() + if xml: + if command == Command.WRITE_RESPONSE: + xml.appendStartTag(TranslatorTags.WRITE_DATA_BLOCK_ACCESS) + else: + xml.appendStartTag(TranslatorTags.READ_DATA_BLOCK_ACCESS) + xml.appendLine("") + xml.appendLine("") + if command == Command.WRITE_RESPONSE: + xml.appendEndTag(TranslatorTags.WRITE_DATA_BLOCK_ACCESS) + else: + xml.appendEndTag(TranslatorTags.READ_DATA_BLOCK_ACCESS) + return + if blockNumber != settings.blockIndex: + bb.setUInt8(ErrorCode.DATA_BLOCK_NUMBER_INVALID) + GXDLMS.getSNPdu(GXDLMSSNParameters(settings, command, 1, SingleReadResponse.DATA_ACCESS_ERROR, bb, None), replyData) + settings.resetBlockIndex() + return + count = 1 + type1 = DataType.OCTET_STRING + if command == Command.WRITE_RESPONSE: + count = data.getUInt8() + type1 = data.getUInt8() + size = _GXCommon.getObjectCount(data) + realSize = len(data) - data.position + if count != 1 or type1 != DataType.OCTET_STRING or size != realSize: + bb.setUInt8(ErrorCode.DATA_BLOCK_UNAVAILABLE) + GXDLMS.getSNPdu(GXDLMSSNParameters(settings, command, cnt, SingleReadResponse.DATA_ACCESS_ERROR, bb, None), replyData) + settings.resetBlockIndex() + return + if server.transaction is None: + server.setTransaction(GXDLMSLongTransaction(None, command, data)) + else: + server.transaction.data.set(data) + if lastBlock == 0: + #pylint: disable=bad-option-value,redefined-variable-type + bb.setUInt16(blockNumber) + settings.increaseBlockIndex() + if command == Command.READ_RESPONSE: + type2 = SingleReadResponse.BLOCK_NUMBER + else: + type2 = SingleWriteResponse.BLOCK_NUMBER + GXDLMS.getSNPdu(GXDLMSSNParameters(settings, command, cnt, type2, None, bb), replyData) + return + if server.transaction: + data.size(0) + data.set(server.transaction.data) + server.setTransaction(None) + if command == Command.READ_RESPONSE: + cls.handleReadRequest(settings, server, data, replyData, xml) + else: + cls.handleWriteRequest(settings, server, data, replyData, xml) + settings.resetBlockIndex() + + @classmethod + def handleReadRequest(cls, settings, server, data, replyData, xml): + bb = GXByteBuffer() + cnt = 0xFF + list_ = list() + if xml is None and not data: + if server.transaction: + return + bb.set(replyData) + replyData.clear() + for it in server.transaction.targets: + list_.append(it) + else: + cnt = _GXCommon.getObjectCount(data) + reads = list() + actions = list() + if xml: + xml.appendStartTag(Command.READ_REQUEST, "Qty", xml.integerToHex(cnt, 2)) + pos = 0 + while pos != cnt: + type_ = data.getUInt8() + if type_ in (VariableAccessSpecification.VARIABLE_NAME, VariableAccessSpecification.PARAMETERISED_ACCESS): + cls.handleRead(settings, server, type_, data, list_, reads, actions, replyData, xml) + elif type_ == VariableAccessSpecification.BLOCK_NUMBER_ACCESS: + cls.handleReadBlockNumberAccess(settings, server, data, replyData, xml) + if xml: + xml.appendEndTag(Command.READ_REQUEST) + return + elif type_ == VariableAccessSpecification.READ_DATA_BLOCK_ACCESS: + cls.handleReadDataBlockAccess(settings, server, Command.READ_RESPONSE, data, cnt, replyData, xml) + if xml: + xml.appendEndTag(Command.READ_REQUEST) + return + else: + cls.returnSNError(settings, Command.READ_RESPONSE, ErrorCode.READ_WRITE_DENIED, replyData) + return + pos += 1 + if reads: + server.onPreRead(reads) + if actions: + server.onPreAction(actions) + if xml: + xml.appendEndTag(Command.READ_REQUEST) + return + requestType = cls.getReadData(settings, list_, bb) + p = GXDLMSSNParameters(settings, Command.READ_RESPONSE, cnt, requestType, None, bb) + GXDLMS.getSNPdu(p, replyData) + if server.transaction() is None and (len(bb) != bb.position or settings.count != settings.index): + reads = list() + for it in list_: + reads.append(it) + if reads: + server.onPostRead(reads) + server.setTransaction(GXDLMSLongTransaction(reads, Command.READ_REQUEST, bb)) + elif server.transaction: + replyData.set(bb) + return + + @classmethod + def returnSNError(cls, settings, cmd, error, replyData): + bb = GXByteBuffer() + bb.setUInt8(error) + GXDLMS.getSNPdu(GXDLMSSNParameters(settings, cmd, 1, SingleReadResponse.DATA_ACCESS_ERROR, bb, None), replyData) + settings.resetBlockIndex() + + @classmethod + def findSNObject(cls, server, settings, sn): + i = GXSNInfo() + offset = [0] + count = [0] + for it in settings.objects: + if sn >= it.shortName: + if sn < it.shortName + it.getAttributeCount() * 8: + i.action = False + i.item = it + i.index = ((sn - it.shortName) / 8) + 1 + break + GXDLMS.getActionInfo(it.objectType, offset, count) + if sn < it.getShortName() + offset[0] + (8 * count[0]): + i.item = it + i.action = True + i.index = (sn - it.shortName - offset[0]) / 8 + 1 + break + if i.item is None and server: + i.item = server.onFindObject(ObjectType.NONE, sn, None) + return i + + @classmethod + def handleWriteRequest(cls, settings, server, data, replyData, xml): + if xml is None and not settings.acceptConnection: + replyData.set(server.generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE, Service.UNSUPPORTED)) + return + type_ = int() + value = None + targets = list() + cnt = _GXCommon.getObjectCount(data) + if xml: + xml.appendStartTag(Command.WRITE_REQUEST) + xml.appendStartTag(TranslatorTags.LIST_OF_VARIABLE_ACCESS_SPECIFICATION, "Qty", xml.integerToHex(cnt, 2)) + if xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendStartTag(TranslatorTags.VARIABLE_ACCESS_SPECIFICATION) + results = GXByteBuffer(cnt) + pos = 0 + while pos != cnt: + type_ = data.getUInt8() + if type_ == VariableAccessSpecification.VARIABLE_NAME: + sn = data.getUInt16() + if xml: + xml.appendLine(Command.WRITE_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME, "Value", xml.integerToHex(sn, 4)) + else: + i = cls.findSNObject(server, server.settings, sn) + targets.append(i) + if i is None: + results.setUInt8(ErrorCode.UNDEFINED_OBJECT) + else: + results.setUInt8(ErrorCode.OK) + elif type_ == VariableAccessSpecification.WRITE_DATA_BLOCK_ACCESS: + cls.handleReadDataBlockAccess(settings, server, Command.WRITE_RESPONSE, data, cnt, replyData, xml) + if xml is None: + return + else: + results.setUInt8(ErrorCode.HARDWARE_FAULT) + pos += 1 + if xml: + if xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendEndTag(TranslatorTags.VARIABLE_ACCESS_SPECIFICATION) + xml.appendEndTag(TranslatorTags.LIST_OF_VARIABLE_ACCESS_SPECIFICATION) + cnt = _GXCommon.getObjectCount(data) + di = _GXDataInfo() + if xml: + di.xml = xml + xml.appendStartTag(TranslatorTags.LIST_OF_DATA, "Qty", xml.integerToHex(cnt, 2)) + pos = 0 + while pos != cnt: + di.clear() + if xml: + if xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendStartTag(Command.WRITE_REQUEST << 8 | SingleReadResponse.DATA) + value = _GXCommon.getData(settings, data, di) + if not di.complete: + value = GXByteBuffer.hex(data.data, False, data.position, len(data) - data.position) + xml.appendLine(_GXCommon.DATA_TYPE_OFFSET + di.type, "Value", str(value)) + if xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendEndTag(Command.WRITE_REQUEST << 8 | SingleReadResponse.DATA) + elif results.getUInt8(pos) == 0: + target = targets[pos] + value = _GXCommon.getData(settings, data, di) + if isinstance(value, bytearray): + dt = target.getItem().getDataType(target.index) + if dt not in (DataType.NONE, DataType.OCTET_STRING): + value = _GXCommon.changeType(settings, value, dt) + e = ValueEventArgs(server, target.getItem(), target.index, 0, None) + am = server.onGetAttributeAccess(e) + if am not in (AccessMode.WRITE, AccessMode.READ_WRITE): + results.setUInt8(pos, ErrorCode.READ_WRITE_DENIED) + else: + e.value = value + server.onPreWrite(list(e)) + if e.error != ErrorCode.OK: + results.setUInt8(pos, e.error) + elif not e.handled: + target.item.setValue(settings, e) + server.onPostWrite((e)) + pos += 1 + if xml: + xml.appendEndTag(TranslatorTags.LIST_OF_DATA) + xml.appendEndTag(Command.WRITE_REQUEST) + return + bb = GXByteBuffer((2 * cnt)) + ret = int() + pos = 0 + while pos != cnt: + ret = results.getUInt8(pos) + if ret != 0: + bb.setUInt8(1) + bb.setUInt8(ret) + pos += 1 + p = GXDLMSSNParameters(settings, Command.WRITE_RESPONSE, cnt, 0xFF, None, bb) + GXDLMS.getSNPdu(p, replyData) + + @classmethod + def handleInformationReport(cls, settings, reply, list_): + data = reply.data + reply.time = None + len_ = data.getUInt8() + tmp = None + if len_ != 0: + tmp = bytearray(len_) + data.get(tmp) + reply.t = _GXCommon.changeType(settings, tmp, DataType.DATETIME) + type_ = 0 + ot = TranslatorOutputType.SIMPLE_XML + if reply.xml: + ot = reply.xml.outputType + count = _GXCommon.getObjectCount(reply.data) + if reply.xml: + reply.xml.appendStartTag(Command.INFORMATION_REPORT) + if reply.time: + reply.xml.appendComment(str(reply.time)) + if ot == TranslatorOutputType.SIMPLE_XML: + reply.xml.appendLine(TranslatorTags.CURRENT_TIME, None, GXByteBuffer.hex(tmp, False)) + else: + reply.xml.appendLine(TranslatorTags.CURRENT_TIME, None, _GXCommon.generalizedTime(reply.time)) + reply.xml.appendStartTag(TranslatorTags.LIST_OF_VARIABLE_ACCESS_SPECIFICATION, "Qty", reply.xml.integerToHex(count, 2)) + pos = 0 + while pos != count: + type_ = data.getUInt8() + if type_ == VariableAccessSpecification.VARIABLE_NAME: + sn = data.getUInt16() + if reply.xml: + if ot == TranslatorOutputType.STANDARD_XML: + reply.xml.appendStartTag(TranslatorTags.VARIABLE_ACCESS_SPECIFICATION) + reply.xml.appendLine(Command.WRITE_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME, "Value", reply.xml.integerToHex(sn, 4)) + if ot == TranslatorOutputType.STANDARD_XML: + reply.xml.appendEndTag(TranslatorTags.VARIABLE_ACCESS_SPECIFICATION) + else: + info = cls.findSNObject(None, settings, sn) + if info.item: + list_.append((info.item, info.index)) + else: + print("InformationReport message. Unknown object : " + str(sn)) + pos += 1 + if reply.xml: + reply.xml.appendEndTag(TranslatorTags.LIST_OF_VARIABLE_ACCESS_SPECIFICATION) + reply.xml.appendStartTag(TranslatorTags.LIST_OF_DATA, "Qty", reply.xml.integerToHex(count, 2)) + count = _GXCommon.getObjectCount(reply.data) + di = _GXDataInfo() + di.xml = (reply.xml) + pos = 0 + while pos != count: + di.clear() + if reply.xml: + if ot == TranslatorOutputType.STANDARD_XML: + reply.xml.appendStartTag(Command.WRITE_REQUEST << 8 | SingleReadResponse.DATA) + _GXCommon.getData(settings, reply.data, di) + if ot == TranslatorOutputType.STANDARD_XML: + reply.xml.appendEndTag(Command.WRITE_REQUEST << 8 | SingleReadResponse.DATA) + else: + v = ValueEventArgs(list_[pos].key, list_[pos].value, 0, None) + v.value = _GXCommon.getData(settings, reply.data, di) + list_.get(pos).getKey().setValue(settings, v) + pos += 1 + if reply.xml: + reply.xml.appendEndTag(TranslatorTags.LIST_OF_DATA) + reply.xml.appendEndTag(Command.INFORMATION_REPORT) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSSNParameters.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSSNParameters.py new file mode 100644 index 0000000..e26547b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSSNParameters.py @@ -0,0 +1,71 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +# SN Parameters +# +# pylint: disable=too-many-instance-attributes,too-many-arguments +class GXDLMSSNParameters: + # + # Constructor. + # + # @param forSettings + # DLMS settings. + # @param forCommand + # Command. + # @param forCommandType + # Command type. + # @param forAttributeDescriptor + # @param forData + # Attribute descriptor + # Generated messages. + # + def __init__(self, forSettings, forCommand, forCount, forCommandType, forAttributeDescriptor, forData): + # DLMS settings. + self.settings = forSettings + # Block index. + self.blockIndex = self.settings.blockIndex + # DLMS Command. + self.command = forCommand + # Item Count. + self.count = forCount + # Request type. + self.requestType = forCommandType + # Attribute descriptor. + self.attributeDescriptor = forAttributeDescriptor + # Data. + self.data = forData + # Are there more data to send or more data to receive. + self.multipleBlocks = False + # Send date and time. This is used in Data notification messages. + self.time = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSServer.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSServer.py new file mode 100644 index 0000000..042e252 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSServer.py @@ -0,0 +1,950 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from abc import ABCMeta, abstractmethod +import datetime +from .GXReplyData import GXReplyData +from .GXDLMSSettings import GXDLMSSettings +from .enums.Command import Command +from .GXDLMSLongTransaction import GXDLMSLongTransaction +from .ServiceError import ServiceError +from .enums.Service import Service +from .ConfirmedServiceError import ConfirmedServiceError +from .internal._GXCommon import _GXCommon +from .enums.InterfaceType import InterfaceType +from .GXDLMS import GXDLMS +from .GXDLMSSNCommandHandler import GXDLMSSNCommandHandler +from .ConnectionState import ConnectionState +from .GXDLMSLNParameters import GXDLMSLNParameters +from .GXDLMSSNParameters import GXDLMSSNParameters +from .GXByteBuffer import GXByteBuffer +from .enums.ErrorCode import ErrorCode +from .GXDLMSConfirmedServiceError import GXDLMSConfirmedServiceError +from .enums.RequestTypes import RequestTypes +from .enums.Authentication import Authentication +from ._HDLCInfo import _HDLCInfo +from .GXSecure import GXSecure +from ._GXAPDU import _GXAPDU +from .GXDLMSException import GXDLMSException +from .enums.AssociationResult import AssociationResult +from .objects.enums.AssociationStatus import AssociationStatus +from .enums.SourceDiagnostic import SourceDiagnostic +from .objects.enums.ApplicationContextName import ApplicationContextName +from .enums.ObjectType import ObjectType +from .ValueEventArgs import ValueEventArgs +from .enums.DataType import DataType +from .objects.GXDLMSAssociationLogicalName import GXDLMSAssociationLogicalName +from .objects.GXDLMSAssociationShortName import GXDLMSAssociationShortName +from .GXDateTime import GXDateTime +from .enums.Conformance import Conformance +from .objects.IGXDLMSBase import IGXDLMSBase +from .enums.Initiate import Initiate +from .enums.Security import Security +from .GXDLMSLNCommandHandler import GXDLMSLNCommandHandler +from .GXHdlcSettings import GXHdlcSettings + +# pylint:disable=too-many-public-methods,too-many-instance-attributes,useless-object-inheritance +class GXDLMSServer(object): + __metaclass__ = ABCMeta + # + # Constructor. + # logicalNameReferencing: Is logical name referencing used. + # interfaceType: Interface type. + # + def __init__(self, logicalNameReferencing, interfaceType): + self.info = GXReplyData() + # Received data. + self.receivedData = GXByteBuffer() + # Reply data. + self.replyData = GXByteBuffer() + # Long get or read transaction information. + self.transaction = None + # Server settings. + self.settings = GXDLMSSettings(True) + # Is server initialized. + self.initialized = False + # When data was received last time. + self.dataReceived = 0 + self.settings.setUseLogicalNameReferencing(logicalNameReferencing) + self.settings.interfaceType = interfaceType + self.hdlc = None + self.wrapper = None + self.reset() + + def getItems(self): + return self.settings.objects + + # List of objects that meter supports. + items = property(getItems) + + def getGbtWindowSize(self): + return self.settings.gbtWindowSize + + def setGbtWindowSize(self, value): + self.settings.gbtWindowSize = value + + #GBT Window size. + gbtWindowSize = property(getGbtWindowSize, setGbtWindowSize) + + def getPushClientAddress(self): + return self.settings.pushClientAddress + + def setPushClientAddress(self, value): + self.settings.pushClientAddress = value + + #client address for push messages. + pushClientAddress = property(getPushClientAddress, setPushClientAddress) + # + # Information from the connection size that server can + # handle. + # + @property + def limits(self): + """Obsolete. Use hdlcSettings instead.""" + return self.settings.hdlc + + # + # HDLC framing settings. + # + @property + def hdlcSettings(self): + return self.settings.hdlc + + def getMaxReceivePDUSize(self): + return self.maxReceivePDUSize + + def setMaxReceivePDUSize(self, value): + self.maxReceivePDUSize = value + + # + # Retrieves the maximum size of received PDU. PDU size tells maximum size + # of PDU packet. Value can be from 0 to 0xFFFF. By default the value is + # 0xFFFF. + # + # Maximum size of received PDU. + # + maxReceivePDUSize = property(getMaxReceivePDUSize, setMaxReceivePDUSize) + + def getUseLogicalNameReferencing(self): + return self.settings.getUseLogicalNameReferencing() + + def setUseLogicalNameReferencing(self, value): + self.settings.setUseLogicalNameReferencing(value) + + # + # Determines, whether Logical, or Short name, referencing is used. + # Referencing depends on the device to communicate with. Normally, a + # device + # supports only either Logical or Short name referencing. The referencing + # is defined by the device manufacturer. If the referencing is wrong, the + # SNMR message will fail. + # + # @see #getMaxReceivePDUSize + # Is logical name referencing used. + # + useLogicalNameReferencing = property(getUseLogicalNameReferencing, setUseLogicalNameReferencing) + + def getConformance(self): + return self.settings.proposedConformance + + def setConformance(self, value): + self.settings.proposedConformance = value + + # + # What kind of services server is offering. + # + conformance = property(getConformance, setConformance) + + # + # Check is data sent to this server. + # + # @param serverAddress + # Server address. + # @param clientAddress + # Client address. + # True, if data is sent to this server. + # + @abstractmethod + def isTarget(self, serverAddress, clientAddress): + raise ValueError("isTarget is called.") + + # + # Check whether the authentication and password are correct. + # + # @param authentication + # Authentication level. + # @param password + # Password. + # Source diagnostic. + # + @abstractmethod + def onValidateAuthentication(self, authentication, password): + raise ValueError("isTarget is called.") + + # + # Get selected value(s). This is called when example profile generic + # request current value. + # + # @param args + # Value event arguments. + # + @abstractmethod + def onPreGet(self, args): + raise ValueError("isTarget is called.") + + # + # Get selected value(s). This is called when example profile generic + # request current value. + # + # @param args + # Value event arguments. + # + @abstractmethod + def onPostGet(self, args): + raise ValueError("isTarget is called.") + + # + # Find object. + # + # @param objectType + # Object type. + # @param sn + # Short Name. In Logical name referencing this is not used. + # @param ln + # Logical Name. In Short Name referencing this is not used. + # Found object or null if object is not found. + # + @abstractmethod + def onFindObject(self, objectType, sn, ln): + raise ValueError("isTarget is called.") + + # + # Called before read is executed. + # + # @param args + # Handled read requests. + # + @abstractmethod + def onPreRead(self, args): + raise ValueError("isTarget is called.") + + # + # Called after read is executed. + # + # @param args + # Handled read requests. + # + @abstractmethod + def onPostRead(self, args): + raise ValueError("isTarget is called.") + + # + # Called before write is executed.. + # + # @param args + # Handled write requests. + # + @abstractmethod + def onPreWrite(self, args): + raise ValueError("isTarget is called.") + + # + # Called after write is executed. + # + # @param args + # Handled write requests. + # + @abstractmethod + def onPostWrite(self, args): + raise ValueError("isTarget is called.") + + # + # Accepted connection is made for the server. All initialization is done + # here. + # + # @param connectionInfo + # Connection info. + # + @abstractmethod + def onConnected(self, connectionInfo): + raise ValueError("isTarget is called.") + + # + # Client has try to made invalid connection. Password is incorrect. + # + # @param connectionInfo + # Connection info. + # + @abstractmethod + def onInvalidConnection(self, connectionInfo): + raise ValueError("isTarget is called.") + + # + # Server has close the connection. All clean up is made here. + # + # @param connectionInfo + # Connection info. + # + @abstractmethod + def onDisconnected(self, connectionInfo): + raise ValueError("isTarget is called.") + + # + # Get attribute access mode. + # + # @param arg + # Value event argument. + # Access mode. + # + @abstractmethod + def onGetAttributeAccess(self, arg): + raise ValueError("isTarget is called.") + + # + # Get method access mode. + # + # @param arg + # Value event argument. + # Method access mode. + # + @abstractmethod + def onGetMethodAccess(self, arg): + raise ValueError("onGetMethodAccess is called.") + + # + # Called before action is executed. + # + # @param args + # Handled action requests. + # + @abstractmethod + def onPreAction(self, args): + raise ValueError("onPreAction is called.") + + # + # Called after action is executed. + # + # @param args + # Handled action requests. + # + @abstractmethod + def onPostAction(self, args): + raise ValueError("onPostAction is called.") + + # + # Add value of COSEM object to byte buffer. AddData method can be used + # with + # GetDataNotificationMessage -method. DLMS specification do not specify + # the + # structure of Data-Notification body. So each manufacture can sent + # different data. + # + # @param obj + # COSEM object. + # @param index + # Attribute index. + # @param buff + # Byte buffer. + # + def addData(self, obj, index, buff): + dt = None + e = ValueEventArgs(self.settings, obj, index, 0, None) + value = obj.getValue(self.settings, e) + dt = obj.getDataType(index) + if dt == DataType.NONE and value: + dt = _GXCommon.getDLMSDataType(value) + _GXCommon.setData(self.settings, buff, dt, value) + + # + # Generates data notification message. + # + # @param time + # Date time. Set Date(0) if not added. + # @param data + # Notification body. + # Generated data notification message(s). + # + def generateDataNotificationMessages(self, time, data): + if self.useLogicalNameReferencing: + p = GXDLMSLNParameters(self.settings, 0, Command.DATA_NOTIFICATION, 0, None, data, 0xff) + if time is None: + p.time = None + else: + p.time = GXDateTime(time) + reply = GXDLMS.getLnMessages(p) + else: + p2 = GXDLMSSNParameters(self.settings, Command.DATA_NOTIFICATION, 1, 0, data, None) + reply = GXDLMS.getSnMessages(p2) + if (self.settings.negotiatedConformance & Conformance.GENERAL_BLOCK_TRANSFER) == 0 and len(reply) != 1: + raise ValueError("Data is not fit to one PDU. Use general block transfer.") + return reply + + def generatePushSetupMessages(self, date, push): + if push is None: + raise ValueError("push") + buff = GXByteBuffer() + buff.setUInt8(DataType.STRUCTURE) + _GXCommon.setObjectCount(len(push.pushObjectList), buff) + for k, v in push.pushObjectList: + self.addData(k, v.attributeIndex, buff) + return self.generateDataNotificationMessages(date, buff) + + def getStoCChallenge(self): + return self.settings.stoCChallenge + + def setStoCChallenge(self, value): + self.settings.useCustomChallenge = value is not None + self.settings.stoCChallenge = value + + # + # Server to Client custom challenge. This is for debugging + # purposes. Reset + # custom challenge settings StoCChallenge to null. + # @param value Server to Client challenge. + # + stoCChallenge = property(getStoCChallenge, setStoCChallenge) + + # + # Interface type. + # + def getInterfaceType(self): + return self.settings.interfaceType + + + def getStartingPacketIndex(self): + return self.settings.blockIndex + + def setStartingPacketIndex(self, value): + self.settings.blockIndex = value + + # + # Set starting packet index. Default is One based, but some meters + # use Zero + # based value. Usually this is not used. + # @param value Zero based starting index. + # + startingPacketIndex = property(getStartingPacketIndex, setStartingPacketIndex) + + def getInvokeID(self): + return self.settings.invokeId + + def setInvokeID(self, value): + self.settings.invokeID = value + + # Invoke ID. + invokeID = property(getInvokeID, setInvokeID) + + def getServiceClass(self): + return self.settings.serviceClass + + def setServiceClass(self, value): + self.settings.serviceClass = value + + # Used service class. + serviceClass = property(getServiceClass, setServiceClass) + + def getPriority(self): + return self.settings.priority + + def setPriority(self, value): + self.settings.priority = value + + # Used priority. + priority = property(getPriority, setPriority) + + # + # Initialize server. This must call after server objects are set. + # + def initialize(self): + associationObject = None + self.initialized = True + pos = 0 + while pos != len(self.settings.objects): + it = self.settings.objects[pos] + if not it.logicalName: + raise ValueError("Invalid Logical Name.") + it.start = self + if isinstance(it, (GXDLMSAssociationShortName,)) and not self.useLogicalNameReferencing: + if len(it.objectList) == 0: + it.objectList.append(self.items) + associationObject = it + elif isinstance(it, (GXDLMSAssociationLogicalName,)) and self.useLogicalNameReferencing: + ln = it + if len(ln.objectList) == 0: + ln.objectList.append(self.items) + associationObject = it + ln.xDLMSContextInfo.maxReceivePduSize = self.settings.maxServerPDUSize + ln.xDLMSContextInfo.maxSendPduSize = self.settings.maxServerPDUSize + elif not isinstance(it, IGXDLMSBase): + self.settings.objects.remove(pos) + pos -= 1 + pos += 1 + if not associationObject: + if self.useLogicalNameReferencing: + it = GXDLMSAssociationLogicalName() + it.xDLMSContextInfo.maxReceivePduSize = self.settings.maxServerPDUSize + it.xDLMSContextInfo.maxSendPduSize = self.settings.maxServerPDUSize + self.items.append(it) + it.objectList.append(self.items) + else: + it2 = GXDLMSAssociationShortName() + self.items.append(it2) + it2.objectList.append(self.items) + if not self.useLogicalNameReferencing: + self.updateShortNames(False) + + def updateShortNames(self, force): + sn = 0xA0 + offset = [0] + count = [0] + for it in self.settings.objects: + if not isinstance(it, (GXDLMSAssociationShortName, GXDLMSAssociationLogicalName)): + if force or it.shortName == 0: + it.setShortName(sn) + GXDLMS.getActionInfo(it.objectType, offset, count) + if count[0] != 0: + sn += offset[0] + (8 * count[0]) + else: + sn += 8 * it.getAttributeCount() + else: + sn = it.shortName + + def handleAarqRequest(self, data, connectionInfo): + # pylint: disable=too-many-nested-blocks + result = AssociationResult.ACCEPTED + error = None + self.settings.setCtoSChallenge(None) + if self.settings.cipher: + self.settings.cipher.setDedicatedKey(None) + if self.settings.interfaceType == InterfaceType.WRAPPER: + self.reset(True) + diagnostic = SourceDiagnostic.NO_REASON_GIVEN + try: + diagnostic = _GXAPDU.parsePDU(self.settings, self.settings.cipher, data, None) + if self.settings.negotiatedConformance == Conformance.NONE: + result = AssociationResult.PERMANENT_REJECTED + diagnostic = SourceDiagnostic.NO_REASON_GIVEN + error = GXByteBuffer() + error.setUInt8(0xE) + error.setUInt8(ConfirmedServiceError.INITIATE_ERROR) + error.setUInt8(ServiceError.INITIATE) + error.setUInt8(Initiate.INCOMPATIBLE_CONFORMANCE) + elif self.settings.maxPduSize < 64: + result = AssociationResult.PERMANENT_REJECTED + diagnostic = SourceDiagnostic.NO_REASON_GIVEN + error = GXByteBuffer() + error.setUInt8(0xE) + error.setUInt8(ConfirmedServiceError.INITIATE_ERROR) + error.setUInt8(ServiceError.INITIATE) + error.setUInt8(Initiate.PDU_SIZE_TOO_SHORT) + elif self.settings.dlmsVersion != 6: + self.settings.dlmsVersion = 6 + result = AssociationResult.PERMANENT_REJECTED + diagnostic = SourceDiagnostic.NO_REASON_GIVEN + error = GXByteBuffer() + error.setUInt8(0xE) + error.setUInt8(ConfirmedServiceError.INITIATE_ERROR) + error.setUInt8(ServiceError.INITIATE) + error.setUInt8(Initiate.DLMS_VERSION_TOO_LOW) + elif diagnostic != SourceDiagnostic.NONE: + result = AssociationResult.PERMANENT_REJECTED + diagnostic = SourceDiagnostic.NOT_SUPPORTED + self.onInvalidConnection(connectionInfo) + else: + diagnostic = self.onValidateAuthentication(self.settings.authentication, self.settings.password) + if diagnostic != SourceDiagnostic.NONE: + result = AssociationResult.PERMANENT_REJECTED + elif self.settings.authentication > Authentication.LOW: + result = AssociationResult.ACCEPTED + diagnostic = SourceDiagnostic.AUTHENTICATION_REQUIRED + if self.useLogicalNameReferencing: + ln = self.items.findByLN(ObjectType.ASSOCIATION_LOGICAL_NAME, "0.0.40.0.0.255") + if ln: + if self.settings.cipher is None or self.settings.cipher.security == Security.NONE: + ln.applicationContextName.contextId = ApplicationContextName.LOGICAL_NAME + else: + ln.applicationContextName.contextId = ApplicationContextName.LOGICAL_NAME_WITH_CIPHERING + ln.authenticationMechanismName.mechanismId = self.settings.authentication + ln.associationStatus = AssociationStatus.ASSOCIATION_PENDING + else: + if self.useLogicalNameReferencing: + ln = self.items.findByLN(ObjectType.ASSOCIATION_LOGICAL_NAME, "0.0.40.0.0.255") + if ln: + if self.settings.cipher is None or self.settings.cipher.security == Security.NONE: + ln.applicationContextName.contextId = ApplicationContextName.LOGICAL_NAME + else: + ln.applicationContextName.contextId = ApplicationContextName.LOGICAL_NAME_WITH_CIPHERING + ln.authenticationMechanismName.mechanismId = self.settings.authentication + ln.associationStatus = AssociationStatus.ASSOCIATED + self.settings.connected = self.settings.connected | ConnectionState.DLMS + except GXDLMSConfirmedServiceError as e: + result = AssociationResult.PERMANENT_REJECTED + diagnostic = SourceDiagnostic.NO_REASON_GIVEN + error = GXByteBuffer() + error.setUInt8(0xE) + error.setUInt8(e.confirmedServiceError) + error.setUInt8(e.serviceError) + error.setUInt8(e.serviceErrorValue) + except GXDLMSException as e: + result = e.result + diagnostic = e.diagnostic + if self.settings.interfaceType == InterfaceType.HDLC or self.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + self.replyData.set(_GXCommon.LLC_REPLY_BYTES) + if self.settings.authentication > Authentication.LOW: + self.settings.setStoCChallenge(GXSecure.generateChallenge()) + _GXAPDU.generateAARE(self.settings, self.replyData, result, diagnostic, self.settings.cipher, error, None) + + def handleReleaseRequest(self, data): + # pylint: disable=unused-argument + if self.settings.interfaceType == InterfaceType.HDLC or self.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + self.replyData.set(0, _GXCommon.LLC_REPLY_BYTES) + tmp = _GXAPDU.getUserInformation(self.settings, self.settings.cipher) + self.replyData.setUInt8(0x63) + self.replyData.setUInt8(int((len(tmp)))) + self.replyData.setUInt8(0x80) + self.replyData.setUInt8(0x01) + self.replyData.setUInt8(0x00) + self.replyData.setUInt8(0xBE) + self.replyData.setUInt8(1 + len(tmp)) + self.replyData.setUInt8(4) + self.replyData.setUInt8(len(tmp)) + self.replyData.set(tmp) + + def handleSnrmRequest(self, data): + if self.hdlc: + self.settings.hdlc.update(self.hdlcSettings) + else: + self.settings.hdlc.maxInfoRX = GXHdlcSettings.DEFAULT_MAX_INFO_RX + self.settings.hdlc.maxInfoTX = GXHdlcSettings.DEFAULT_MAX_INFO_TX + self.settings.hdlc.windowSizeRX = GXHdlcSettings.DEFAULT_WINDOWS_SIZE_RX + self.settings.hdlc.windowSizeTX = GXHdlcSettings.DEFAULT_WINDOWS_SIZE_TX + GXDLMS.parseSnrmUaResponse(data, self.settings.hdlc) + self.reset(True) + self.replyData.setUInt8(0x81) + self.replyData.setUInt8(0x80) + self.replyData.setUInt8(0) + if self.hdlc: + #If client wants send larger HDLC frames what meter accepts. + if self.settings.hdlc.maxInfoTX > self.hdlc.maximumInfoLengthReceive: + self.settings.hdlc.maxInfoTX = self.hdlc.maximumInfoLengthReceive + if self.settings.hdlc.maxInfoRX > self.hdlc.maximumInfoLengthTransmit: + self.settings.hdlc.maxInfoRX = self.hdlc.maximumInfoLengthTransmit + if self.settings.hdlc.maxInfoRX > self.hdlc.maximumInfoLengthTransmit: + self.settings.hdlc.windowSizeTX = self.hdlc.windowSizeReceive + if self.settings.hdlc.maxInfoRX > self.hdlc.maximumInfoLengthTransmit: + self.settings.hdlc.windowSizeRX = self.hdlc.windowSizeTransmit + self.replyData.setUInt8(_HDLCInfo.MAX_INFO_TX) + GXDLMS.appendHdlcParameter(self.replyData, self.hdlc.maxInfoTX) + self.replyData.setUInt8(_HDLCInfo.MAX_INFO_RX) + GXDLMS.appendHdlcParameter(self.replyData, self.hdlc.maxInfoRX) + self.replyData.setUInt8(_HDLCInfo.WINDOW_SIZE_TX) + self.replyData.setUInt8(4) + self.replyData.setUInt32(self.hdlc.windowSizeTX) + self.replyData.setUInt8(_HDLCInfo.WINDOW_SIZE_RX) + self.replyData.setUInt8(4) + self.replyData.setUInt32(self.hdlc.windowSizeRX) + self.replyData.setUInt8(2, len(self.replyData) - 3) + self.settings.connected = ConnectionState.HDLC + + def generateDisconnectRequest(self): + self.replyData.setUInt8(0x81) + self.replyData.setUInt8(0x80) + self.replyData.setUInt8(0) + self.replyData.setUInt8(_HDLCInfo.MAX_INFO_TX) + self.replyData.setUInt8(1) + self.replyData.setUInt8(self.hdlc.maxInfoTX) + self.replyData.setUInt8(_HDLCInfo.MAX_INFO_RX) + self.replyData.setUInt8(1) + self.replyData.setUInt8(self.hdlc.maxInfoRX) + self.replyData.setUInt8(_HDLCInfo.WINDOW_SIZE_TX) + self.replyData.setUInt8(4) + self.replyData.setUInt32(self.hdlc.windowSizeTX) + self.replyData.setUInt8(_HDLCInfo.WINDOW_SIZE_RX) + self.replyData.setUInt8(4) + self.replyData.setUInt32(self.hdlc.windowSizeRX) + self.replyData.setUInt8(2, len(self.replyData) - 3) + + + def reset(self, connect=False): + if not connect: + self.info.clear() + self.settings.serverAddress = 0 + self.settings.clientAddress = 0 + self.settings.protocolVersion = None + self.settings.ctoSChallenge = None + self.settings.stoCChallenge = None + self.receivedData.clear() + self.transaction = None + self.settings.count = 0 + self.settings.index = 0 + self.settings.connected = ConnectionState.NONE + self.replyData.clear() + self.settings.authentication = Authentication.NONE + if self.settings.cipher: + self.settings.cipher.reset() + + def reset_0(self): + self.reset(False) + + def handleRequest(self, sr): + #pylint: disable=too-many-return-statements,broad-except + """ + Handles client request. + + buff: Received data from the client. + Returns Response to the request. Response is null if request packet is not complete. + """ + if not sr.isStreaming() and not sr.data: + return + if not self.initialized: + raise ValueError("Server not Initialized.") + try: + if not sr.isStreaming(): + self.receivedData.set(sr.data) + first = self.settings.serverAddress == 0 and self.settings.clientAddress == 0 + try: + GXDLMS.getData(self.settings, self.receivedData, self.info, None) + except Exception: + self.dataReceived = datetime.datetime.now() + self.receivedData.size = 0 + sr.setReply(GXDLMS.getHdlcFrame(self.settings, Command.UNACCEPTABLE_FRAME, self.replyData)) + return + if not self.info.complete: + return + self.receivedData.clear() + if self.info.command == Command.DISCONNECT_REQUEST and (self.settings.connected == ConnectionState.NONE): + sr.setReply(GXDLMS.getHdlcFrame(self.settings, Command.DISCONNECT_MODE, self.replyData)) + self.info.clear() + return + if first or self.info.command == Command.SNRM or (self.settings.interfaceType == InterfaceType.WRAPPER and self.info.command == Command.AARQ): + if not self.isTarget(self.settings.serverAddress, self.settings.clientAddress): + self.info.clear() + return + if (self.info.moreData & RequestTypes.FRAME) == RequestTypes.FRAME: + self.dataReceived = datetime.datetime.now() + sr.setReply(GXDLMS.getHdlcFrame(self.settings, self.settings.getReceiverReady(), self.replyData)) + return + if self.info.command == Command.NONE: + if self.transaction: + self.info.command = (self.transaction.command) + elif not self.replyData: + sr.setReply(GXDLMS.getHdlcFrame(self.settings, self.settings.getReceiverReady(), self.replyData)) + return + if self.hdlc and self.hdlc.inactivityTimeout != 0: + if self.info.command != Command.SNRM: + elapsed = int((datetime.datetime.now() - self.dataReceived)) / 1000 + if elapsed >= self.hdlc.inactivityTimeout: + self.reset() + self.dataReceived = 0 + return + elif self.wrapper and self.wrapper.inactivityTimeout != 0: + if self.info.command != Command.AARQ: + elapsed = int((datetime.datetime.now() - self.dataReceived)) / 1000 + if elapsed >= self.wrapper.inactivityTimeout: + self.reset() + self.dataReceived = 0 + return + else: + self.info.command = (Command.GENERAL_BLOCK_TRANSFER) + try: + sr.setReply(self.handleCommand(self.info.command, self.info.data, sr)) + except Exception: + self.receivedData.size(0) + sr.setReply(GXDLMS.getHdlcFrame(self.settings, Command.UNACCEPTABLE_FRAME, self.replyData)) + self.dataReceived = datetime.datetime.now() + self.info.clear() + except Exception as e: + if isinstance(e, (GXDLMSConfirmedServiceError,)): + sr.setReply(self.reportConfirmedServiceError(e)) + self.transaction = None + self.settings.setCount(0) + self.settings.setIndex(0) + self.info.clear() + self.receivedData.clear() + elif self.info.command != Command.NONE: + sr.setReply(self.reportError(self.info.command, ErrorCode.HARDWARE_FAULT)) + self.transaction = None + self.settings.setCount(0) + self.settings.setIndex(0) + self.info.clear() + self.receivedData.clear() + else: + self.reset() + if (self.settings.connected & ConnectionState.DLMS) != 0: + self.settings.connected = self.settings.connected & ~ConnectionState.DLMS + self.onDisconnected(sr.connectionInfo) + + def reportConfirmedServiceError(self, e): + self.replyData.clear() + if self.settings.interfaceType == InterfaceType.HDLC or self.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + GXDLMS.addLLCBytes(self.settings, self.replyData) + self.replyData.setUInt8(Command.CONFIRMED_SERVICE_ERROR) + self.replyData.setUInt8(e.confirmedServiceError) + self.replyData.setUInt8(e.serviceError) + self.replyData.setUInt8(e.serviceErrorValue) + if self.settings.interfaceType == InterfaceType.WRAPPER: + return GXDLMS.getWrapperFrame(self.settings, Command.CONFIRMED_SERVICE_ERROR, self.replyData) + return GXDLMS.getHdlcFrame(self.settings, int(0), self.replyData) + + def reportError(self, command, error): + cmd = 0 + if command == Command.READ_REQUEST: + cmd = Command.READ_RESPONSE + elif command == Command.WRITE_REQUEST: + cmd = Command.WRITE_RESPONSE + elif command == Command.GET_REQUEST: + cmd = Command.GET_RESPONSE + elif command == Command.SET_REQUEST: + cmd = Command.SET_RESPONSE + elif command == Command.METHOD_REQUEST: + cmd = Command.METHOD_RESPONSE + else: + cmd = Command.NONE + if self.settings.getUseLogicalNameReferencing(): + p = GXDLMSLNParameters(self.settings, 0, cmd, 1, None, None, error) + GXDLMS.getLNPdu(p, self.replyData) + else: + bb = GXByteBuffer() + bb.setUInt8(error) + p2 = GXDLMSSNParameters(self.settings, cmd, 1, 1, None, bb) + GXDLMS.getSNPdu(p2, self.replyData) + if self.settings.interfaceType == InterfaceType.WRAPPER: + return GXDLMS.getWrapperFrame(self.settings, command, self.replyData) + return GXDLMS.getHdlcFrame(self.settings, int(0), self.replyData) + + def handleCommand(self, cmd, data, sr): + frame_ = 0 + if self.replyData: + frame_ = self.settings.getNextSend(False) + if cmd == Command.ACCESS_REQUEST: + GXDLMSLNCommandHandler.handleAccessRequest(self.settings, self, data, self.replyData, None) + elif cmd == Command.SET_REQUEST: + GXDLMSLNCommandHandler.handleSetRequest(self.settings, self, data, self.replyData, None) + elif cmd == Command.WRITE_REQUEST: + GXDLMSSNCommandHandler.handleWriteRequest(self.settings, self, data, self.replyData, None) + elif cmd == Command.GET_REQUEST: + if data: + GXDLMSLNCommandHandler.handleGetRequest(self.settings, self, data, self.replyData, None) + elif cmd == Command.READ_REQUEST: + GXDLMSSNCommandHandler.handleReadRequest(self.settings, self, data, self.replyData, None) + elif cmd == Command.METHOD_REQUEST: + GXDLMSLNCommandHandler.handleMethodRequest(self.settings, self, data, sr.getConnectionInfo(), self.replyData, None) + elif cmd == Command.SNRM: + self.handleSnrmRequest(data) + frame_ = int(Command.UA) + elif cmd == Command.AARQ: + self.handleAarqRequest(data, sr.getConnectionInfo()) + if (self.settings.connected & ConnectionState.DLMS) != 0: + self.onConnected(sr.getConnectionInfo()) + elif cmd == Command.RELEASE_REQUEST: + self.handleReleaseRequest(data) + if (self.settings.connected & ConnectionState.DLMS) != 0: + self.settings.connected = self.settings.connected & ~ConnectionState.DLMS + self.onDisconnected(sr.getConnectionInfo()) + elif cmd == Command.DISCONNECT_REQUEST: + self.generateDisconnectRequest() + if (self.settings.connected & ConnectionState.DLMS) != 0: + self.onDisconnected(sr.getConnectionInfo()) + self.settings.connected = ConnectionState.HDLC + frame_ = Command.UA + elif cmd == Command.GENERAL_BLOCK_TRANSFER: + if not self.handleGeneralBlockTransfer(data, sr): + return None + elif cmd == Command.NONE: + pass + else: + raise Exception("Invalid command: " + str(cmd)) + if self.settings.interfaceType == InterfaceType.WRAPPER: + reply = GXDLMS.getWrapperFrame(self.settings, cmd, self.replyData) + else: + reply = GXDLMS.getHdlcFrame(self.settings, frame_, self.replyData) + if cmd == Command.DISCONNECT_REQUEST or (self.settings.interfaceType == InterfaceType.WRAPPER and cmd == Command.RELEASE_REQUEST): + self.reset() + return reply + + def handleGeneralBlockTransfer(self, data, sr): + if self.transaction: + if self.transaction.command == Command.GET_REQUEST: + if sr.count == 0: + self.settings.setBlockNumberAck(self.settings.blockNumberAck + 1) + sr.setCount(self.settings.gbtWindowSize) + GXDLMSLNCommandHandler.getRequestNextDataBlock(self.settings, 0, self, data, self.replyData, None, True) + if sr.count != 0: + sr.setCount(sr.getCount() - 1) + if not self.transaction: + sr.setCount(0) + else: + bc = data.getUInt8() + blockNumber = data.getUInt16() + blockNumberAck = data.getUInt16() + len_ = _GXCommon.getObjectCount(data) + if len_ > len(data) - data.position: + self.replyData.set(self.generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE, Service.UNSUPPORTED)) + else: + self.transaction.data.set(data) + igonoreAck = (bc & 0x40) != 0 and (blockNumberAck * self.settings.gbtWindowSize) + 1 > blockNumber + gbtWindowSize = self.settings.gbtWindowSize + bn = self.settings.blockIndex + if (bc & 0x80) != 0: + self.handleCommand(self.transaction.command, self.transaction.data, sr) + self.transaction = None + igonoreAck = False + gbtWindowSize = 1 + if igonoreAck: + return False + self.replyData.setUInt8(Command.GENERAL_BLOCK_TRANSFER) + self.replyData.setUInt8(int((0x80 | gbtWindowSize))) + self.settings.blockIndex = self.settings.blockIndex + 1 + self.replyData.setUInt16(bn) + self.replyData.setUInt16(blockNumber) + self.replyData.setUInt8(0) + else: + bc = data.getUInt8() + blockNumber = data.getUInt16() + blockNumberAck = data.getUInt16() + len_ = _GXCommon.getObjectCount(data) + if len_ > len(data) - data.position: + self.replyData.set(self.generateConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE, Service.UNSUPPORTED)) + else: + self.transaction = GXDLMSLongTransaction(None, data.getUInt8(), data) + self.replyData.setUInt8(Command.GENERAL_BLOCK_TRANSFER) + self.replyData.setUInt8((0x80 | self.settings.gbtWindowSize)) + self.replyData.setUInt16(blockNumber) + blockNumberAck += 1 + self.replyData.setUInt16(blockNumberAck) + self.replyData.setUInt8(0) + return True + + @classmethod + def generateConfirmedServiceError(cls, service, type_, code_): + return [Command.CONFIRMED_SERVICE_ERROR, service, type_, code_] diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSSettings.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSSettings.py new file mode 100644 index 0000000..d1982b0 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSSettings.py @@ -0,0 +1,452 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +from .enums import Priority, ServiceClass, InterfaceType, Authentication, Standard, HdlcFrameType, Conformance +from .enums.DateTimeSkips import DateTimeSkips +from .ConnectionState import ConnectionState +from .objects.GXDLMSObjectCollection import GXDLMSObjectCollection +from .GXHdlcSettings import GXHdlcSettings + +# This class includes DLMS communication settings. +# pylint: disable=bad-option-value,too-many-public-methods,too-many-instance-attributes,old-style-class +class GXDLMSSettings: + # + # Server sender frame sequence starting number. + # + __SERVER_START_SENDER_FRAME_SEQUENCE = 0x1E + + # + # Server receiver frame sequence starting number. + # + __SERVER_START_RECEIVER_FRAME_SEQUENCE = 0xFE + + # + # Client sender frame sequence starting number. + # + __CLIENT_START_SENDER_FRAME_SEQUENCE = 0xFE + + # + # Client receiver frame sequence starting number. + # + __CLIENT_START_RCEIVER_FRAME_SEQUENCE = 0xE + + # + # DLMS version number. + # + __DLMS_VERSION = 6 + __MAX_RECEIVE_PDU_SIZE = 0xFFFF + + @classmethod + def getInitialConformance(cls, useLN): + if useLN: + return Conformance.BLOCK_TRANSFER_WITH_ACTION | Conformance.BLOCK_TRANSFER_WITH_SET_OR_WRITE | Conformance.BLOCK_TRANSFER_WITH_GET_OR_READ | Conformance.SET | Conformance.SELECTIVE_ACCESS | Conformance.ACTION | Conformance.MULTIPLE_REFERENCES | Conformance.GET | Conformance.ACCESS + return Conformance.INFORMATION_REPORT | Conformance.READ | Conformance.UN_CONFIRMED_WRITE | Conformance.WRITE | Conformance.PARAMETERIZED_ACCESS | Conformance.MULTIPLE_REFERENCES + + # + # Constructor. + # + def __init__(self, isServer): + self.customChallenges = False + self.ctoSChallenge = None + self.stoCChallenge = None + self.sourceSystemTitle = None + self.invokeId = 0x1 + self.longInvokeID = 0x1 + self.priority = Priority.HIGH + self.serviceClass = ServiceClass.CONFIRMED + self.clientAddress = 0 + self.serverAddress = 0 + self.pushClientAddress = 0 + self.serverAddressSize = 0 + self.__useLogicalNameReferencing = True + self.interfaceType = InterfaceType.HDLC + self.authentication = Authentication.NONE + self.password = None + self.kek = None + self.count = 0 + self.index = 0 + self.targetEphemeralKey = None + self.dlmsVersion = self.__DLMS_VERSION + self.connected = ConnectionState.NONE + self.allowAnonymousAccess = False + self.maxPduSize = self.__MAX_RECEIVE_PDU_SIZE + self.maxServerPDUSize = self.__MAX_RECEIVE_PDU_SIZE + self.startingPacketIndex = 1 + # Gets current block index. + self.blockIndex = 1 + self.cipher = None + self.blockNumberAck = 0 + self.protocolVersion = None + self.isServer = isServer + self.objects = GXDLMSObjectCollection() + self.hdlc = GXHdlcSettings() + self.gateway = None + self.proposedConformance = GXDLMSSettings.getInitialConformance(self.__useLogicalNameReferencing) + self.receiverFrame = 0 + self.senderFrame = 0 + self.resetFrameSequence() + self.gbtWindowSize = 1 + self.userId = -1 + #Quality of service. + self.qualityOfService = 0 + self.useUtc2NormalTime = False + self.increaseInvocationCounterForGMacAuthentication = False + self.dateTimeSkips = DateTimeSkips.NONE + self.standard = Standard.DLMS + self.negotiatedConformance = Conformance.NONE + self.invokeID = 0 + self.command = 0 + self.commandType = 0 + self.useCustomChallenge = False + self.preEstablishedSystemTitle = None + + # + # Client to Server challenge. + # + def getCtoSChallenge(self): + return self.ctoSChallenge + + # + # @param value + # Client to Server challenge. + # + def setCtoSChallenge(self, value): + if not self.customChallenges or self.ctoSChallenge is None: + self.ctoSChallenge = value + + # + # Server to Client challenge. + # + def getStoCChallenge(self): + return self.stoCChallenge + + # + # @param value + # Server to Client challenge. + # + def setStoCChallenge(self, value): + if not self.customChallenges or self.stoCChallenge is None: + self.stoCChallenge = value + + # + # Is connection accepted. + # + def acceptConnection(self): + return self.connected != ConnectionState.NONE or self.allowAnonymousAccess or (self.cipher and self.cipher.sharedSecret) + + # + # Reset frame sequence. + # + def resetFrameSequence(self): + if self.isServer: + self.senderFrame = self.__SERVER_START_SENDER_FRAME_SEQUENCE + self.receiverFrame = self.__SERVER_START_RECEIVER_FRAME_SEQUENCE + else: + self.senderFrame = self.__CLIENT_START_SENDER_FRAME_SEQUENCE + self.receiverFrame = self.__CLIENT_START_RCEIVER_FRAME_SEQUENCE + + #pylint: disable=too-many-return-statements + def checkFrame(self, frame_, xml): + # If notify + if frame_ == 0x13: + return True + + # If U frame. + if (frame_ & HdlcFrameType.U_FRAME) == HdlcFrameType.U_FRAME: + if frame_ == 0x93: + isEcho = not self.isServer and frame_ == 0x93 and (self.senderFrame == 0x10 or self.senderFrame == 0xfe) and self.receiverFrame == 0xE + self.resetFrameSequence() + return not isEcho + if frame_ == 0x73 and not self.isServer: + return self.senderFrame == 0xFE and self.receiverFrame == 0xE + return True + # If S -frame. + if (frame_ & HdlcFrameType.S_FRAME) == HdlcFrameType.S_FRAME: + #If echo. + if frame_ == (self.senderFrame & 0xF1): + return False + self.receiverFrame = self.increaseReceiverSequence(self.receiverFrame) + return True + # Handle I-frame. + if (self.senderFrame & 0x1) == 0: + expected = GXDLMSSettings.increaseReceiverSequence(GXDLMSSettings.increaseSendSequence(self.receiverFrame)) + if frame_ == expected: + self.receiverFrame = frame_ + return True + #If the final bit is not set. + if frame_ == (expected & ~0x10) and self.hdlc.windowSizeRX != 1: + self.receiverFrame = frame_ + return True + # If Final bit is not set for the previous message. + if (self.receiverFrame & 0x10) == 0 and self.hdlc.windowSizeRX != 1: + expected = (0x10 | self.increaseSendSequence(self.receiverFrame)) + if frame_ == expected: + self.receiverFrame = frame_ + return True + # If the final bit is not set. + if frame_ == (expected & ~0x10): + ReceiverFrame = frame_ + return True + else: + expected = self.increaseSendSequence(self.receiverFrame) & 0xFF + # If answer for RR. + if frame_ == expected: + self.receiverFrame = frame_ + return True + if frame_ == (expected & ~0x10): + self.receiverFrame = frame_ + return True + if self.hdlc.windowSizeRX != 1: + #If HDLC window size is bigger than one. + expected = self.increaseReceiverSequence(self.increaseSendSequence(ReceiverFrame)) + if frame_ == expected: + ReceiverFrame = frame_ + return True + + # If try to find data from bytestream and not real communicating. + if xml and ((not self.isServer and self.receiverFrame == 0xE) or\ + (self.isServer and self.receiverFrame == 0xEE)): + ReceiverFrame = frame_ + return True + print("Invalid HDLC Frame: " + hex(frame_) + " Expected: " + hex(expected)) + return False + + # + # Increase receiver sequence. + # + # @param value + # Frame value. + # Increased receiver frame sequence. + # + @classmethod + def increaseReceiverSequence(cls, value): + return ((value & 0xFF) + 0x20 | 0x10 | value & 0xE) & 0xFF + + # + # Increase sender sequence. + # + # @param value + # Frame value. + # Increased sender frame sequence. + # + @classmethod + def increaseSendSequence(cls, value): + return ((value & 0xF0 | (value + 0x2) & 0xE) & 0xFF) & 0xFF + + # + # Generates I-frame. + # @param first + # Is this first packet. + # Generated I-frame + # + def getNextSend(self, first): + if first: + self.senderFrame = self.increaseReceiverSequence(self.increaseSendSequence(int(self.senderFrame))) + else: + self.senderFrame = self.increaseSendSequence(int(self.senderFrame)) + return self.senderFrame & 0xFF + + # + # Generates Receiver Ready S-frame. + # + def getReceiverReady(self): + self.senderFrame = self.increaseReceiverSequence((self.senderFrame | 1)) + return self.senderFrame & 0xF1 + + # + # Generates Keep Alive S-frame. + # + def getKeepAlive(self): + self.senderFrame = (self.senderFrame | 1) + return self.senderFrame & 0xF1 + + # + # Gets starting block index in HDLC framing. Default is One based, + # but some + # meters use Zero based value. Usually this is not used. + # + # Current block index. + # + def getStartingPacketIndex(self): + return self.startingPacketIndex + + # + # Set starting block index in HDLC framing. Default is One based, + # but some + # meters use Zero based value. Usually this is not used. + # + # @param value + # Zero based starting index. + # + def setStartingPacketIndex(self, value): + self.startingPacketIndex = value + self.resetBlockIndex() + + # + # Sets current block index. + # + # @param value + # Block index. + # + def setBlockIndex(self, value): + self.blockIndex = value + + # + # Block number acknowledged in GBT. + # + def getBlockNumberAck(self): + return self.blockNumberAck + + # + # @param value + # Block number acknowledged in GBT. + # + def setBlockNumberAck(self, value): + self.blockNumberAck = value + + # + # Resets block index to default value. + # + def resetBlockIndex(self): + self.blockIndex = self.startingPacketIndex + self.blockNumberAck = 0 + + # + # Increases block index. + # + def increaseBlockIndex(self): + self.blockIndex += 1 + + # Is Logical Name Referencing used. + # Don't use property. For some reason Python 2.7 doesn't call it in Rasbperry PI. + def getUseLogicalNameReferencing(self): + return self.__useLogicalNameReferencing + + # Is Logical Name Referencing used. + # Don't use property. For some reason Python 2.7 doesn't call it in Rasbperry PI. + def setUseLogicalNameReferencing(self, value): + if self.__useLogicalNameReferencing != value: + self.__useLogicalNameReferencing = value + self.proposedConformance = GXDLMSSettings.getInitialConformance(self.__useLogicalNameReferencing) + + # + # Invoke ID. + # + def getInvokeID(self): + return self.invokeID + + # + # @param value + # update invoke ID. + # + def updateInvokeId(self, value): + #pylint: disable=bad-option-value,redefined-variable-type + if (value & 0x80) != 0: + self.priority = Priority.HIGH + else: + self.priority = Priority.NORMAL + if (value & 0x40) != 0: + self.serviceClass = ServiceClass.CONFIRMED + else: + self.serviceClass = ServiceClass.UN_CONFIRMED + self.invokeID = int((value & 0xF)) + + # + # @param value + # Invoke ID. + # + def setInvokeID(self, value): + if value > 0xF: + raise ValueError("Invalid InvokeID") + self.invokeID = int(value) + + # + # Invoke ID. + # + def getLongInvokeID(self): + return self.longInvokeID + + # + # @param value + # Invoke ID. + # + def setLongInvokeID(self, value): + if value > 0xFFFFFFFF: + raise ValueError("Invalid InvokeID") + self.longInvokeID = value + + # + # Source system title. + # + def getSourceSystemTitle(self): + return self.sourceSystemTitle + + # + # @param value + # Source system title. + # + def setSourceSystemTitle(self, value): + if not value or len(value) != 8: + raise ValueError("Invalid client system title.") + self.sourceSystemTitle = value + + # + # Long data count. + # + def getCount(self): + return self.count + + # + # @param value + # Data count. + # + def setCount(self, value): + if value < 0: + raise ValueError("Invalid count.") + self.count = value + + # + # Long data index. + # + def getIndex(self): + return self.index + + # + # @param value + # Long data index + # + def setIndex(self, value): + if value < 0: + raise ValueError("Invalid Index.") + self.index = value diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSTranslator.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSTranslator.py new file mode 100644 index 0000000..fa2755a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSTranslator.py @@ -0,0 +1,1912 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +import xml.etree.cElementTree as ET +from .internal._GXCommon import _GXCommon +from .GXByteBuffer import GXByteBuffer +from .ActionRequestType import ActionRequestType +from .TranslatorOutputType import TranslatorOutputType +from .TranslatorSimpleTags import TranslatorSimpleTags +from .TranslatorStandardTags import TranslatorStandardTags +from .GXDLMSTranslatorStructure import GXDLMSTranslatorStructure +from .GXDLMSSettings import GXDLMSSettings +from .enums import Command, Security +from .GXCiphering import GXCiphering +from .GXReplyData import GXReplyData +from .GXDLMSLNCommandHandler import GXDLMSLNCommandHandler +from .GXDLMSSNCommandHandler import GXDLMSSNCommandHandler +from ._GXAPDU import _GXAPDU +from .GXDLMS import GXDLMS +from .TranslatorTags import TranslatorTags +from .internal._GXDataInfo import _GXDataInfo +from .GXDLMSXmlSettings import GXDLMSXmlSettings +from .enums.InterfaceType import InterfaceType +from .enums.DataType import DataType +from .GXDLMSLNParameters import GXDLMSLNParameters +from .enums.HdlcFrameType import HdlcFrameType +from .GXDLMSSNParameters import GXDLMSSNParameters +from .enums.BerType import BerType +from .enums.RequestTypes import RequestTypes +from .GXDLMSConverter import GXDLMSConverter +from ._HDLCInfo import _HDLCInfo +from .TranslatorGeneralTags import TranslatorGeneralTags +from .SingleReadResponse import SingleReadResponse +from .VariableAccessSpecification import VariableAccessSpecification +from .enums.AccessServiceCommandType import AccessServiceCommandType +from .enums.Service import Service +from .ServiceError import ServiceError +from .enums.Priority import Priority +from .enums.ServiceClass import ServiceClass +from .GXDateTime import GXDateTime +from .SetResponseType import SetResponseType +from .GetCommandType import GetCommandType +from .SetRequestType import SetRequestType +from .enums.ErrorCode import ErrorCode +from .ActionResponseType import ActionResponseType +from .enums.Authentication import Authentication +from .enums.AssociationResult import AssociationResult +from .enums.SourceDiagnostic import SourceDiagnostic +from .AesGcmParameter import AesGcmParameter +from .GXDLMSException import GXDLMSException +from .enums.Standard import Standard +from .GXDLMSTranslatorMessage import GXDLMSTranslatorMessage +from .plc.enums import PlcSourceAddress, PlcDestinationAddress + +# pylint:disable=bad-option-value,too-many-instance-attributes,too-many-function-args,too-many-public-methods,too-many-public-methods,too-many-function-args,too-many-instance-attributes,old-style-class,raise-missing-from +class GXDLMSTranslator: + """ + This class is used to translate DLMS frame or PDU to xml. + """ + def __init__(self, type_=TranslatorOutputType.SIMPLE_XML): + """ + Constructor. + type_: Translator output type. + """ + self.tags = dict() + self.tagsByName = dict() + # Are numeric values shows as hex. + self.hex = True + # Is string serialized as hex. {@link messageToXml} {@link PduOnly} + self.showStringAsHex = False + # Sending data in multiple frames. + self.multipleFrames = False + # If only PDUs are shown and PDU is received on parts. + self.pduFrames = GXByteBuffer() + # Is only PDU shown when data is parsed with messageToXml. + # {@link messageToXml} {@link CompleatePdu} + self.pduOnly = False + self.outputType = None + # Is XML declaration skipped. + self.omitXmlDeclaration = False + # Is XML name space skipped. + self.omitXmlNameSpace = False + # Add comments. + self.comments = False + # Used security. + self.security = Security.NONE + # System title. + self.systemTitle = "ABCDEFGH".encode() + # Block cipher key. + self.blockCipherKey = bytearray((0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F)) + # Authentication key. + self.authenticationKey = bytearray((0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF)) + # Invocation Counter. + self.invocationCounter = 0 + # Dedicated key. + self.dedicatedKey = None + # Server system title. + self.serverSystemTitle = None + self.outputType = type_ + # Is only complete PDU parsed and shown. + self.completePdu = False + self.__getTags(self.outputType, self.tags, self.tagsByName) + self.standard = Standard.DLMS + + # + # Find next frame from the string. Position of data is set to the begin of + # new frame. If PDU is None it is not updated. + # + # @param data + # Data where frame is search. + # @param pdu + # PDU of received frame is set here. + # Is new frame found. + def findNextFrame(self, msg, pdu): + if not isinstance(msg, (GXDLMSTranslatorMessage)): + if not isinstance(msg, (GXByteBuffer)): + data = GXByteBuffer(msg) + else: + data = msg + msg = GXDLMSTranslatorMessage() + msg.message = data + msg.sourceAddress = msg.targetAddress = 0 + original = msg.message.position + msg.exception = None + data = msg.message + settings = GXDLMSSettings(True) + reply = GXReplyData() + reply.moreData = msg.moreData + reply.xml = (GXDLMSTranslatorStructure(self.outputType, self.omitXmlNameSpace, self.hex, self.showStringAsHex, self.comments, self.tags)) + pos = 0 + found = False + while data.position < len(data): + if msg.interfaceType in (None, InterfaceType.HDLC, InterfaceType.HDLC_WITH_MODE_E) and data.getUInt8(data.position) == 0x7e: + pos = data.position + settings.interfaceType = InterfaceType.HDLC + found = GXDLMS.getData(settings, data, reply, None) + data.position = pos + if found: + break + elif msg.interfaceType in (None, InterfaceType.WRAPPER) and data.getUInt16(data.position) == 0x1: + pos = data.position + settings.interfaceType = InterfaceType.WRAPPER + found = GXDLMS.getData(settings, data, reply, None) + data.position = pos + if found: + break + elif msg.interfaceType in (None, InterfaceType.PLC) and msg.message.getUInt8(msg.message.position) == 2: + pos = data.position + settings.interfaceType = InterfaceType.PLC + found = GXDLMS.getData(settings, data, reply, None) + data.position = pos + if found: + break + elif msg.interfaceType in (None, InterfaceType.WIRED_MBUS) and GXDLMS.isWiredMBusData(msg.message): + pos = data.position + settings.interfaceType = InterfaceType.WIRED_MBUS + found = GXDLMS.getData(settings, data, reply, None) + data.position = pos + if found: + break + elif msg.interfaceType in (None, InterfaceType.WIRELESS_MBUS) and GXDLMS.isWirelessMBusData(msg.message): + pos = data.position + settings.interfaceType = InterfaceType.WIRELESS_MBUS + found = GXDLMS.getData(settings, data, reply, None) + data.position = pos + if found: + break + data.position = data.position + 1 + msg.moreData = reply.moreData + msg.sourceAddress = reply.sourceAddress + msg.targetAddress = reply.targetAddress + if pdu: + pdu.clear() + pdu.set(data.data, 0, len(data)) + r = data.position != len(data) + if not found: + data.position = original + return r + # + # Find next frame from the string. Position of data is set to the begin of + # new frame. If PDU is None it is not updated. + # + # @param data + # Data where frame is search. + # @param pdu + # PDU of received frame is set here. + # @param type + # Interface type. + # Is new frame found. + # + def findNextFrame_0(self, data, pdu, type_): + settings = GXDLMSSettings(True) + settings.iInterfaceType = type_ + reply = GXReplyData() + reply.xml = (GXDLMSTranslatorStructure(self.outputType, self.omitXmlNameSpace, self.hex, self.showStringAsHex, self.comments, self.tags)) + pos = int() + found = bool() + try: + while data.position < len(data): + if type_ in (InterfaceType.HDLC, InterfaceType.HDLC_WITH_MODE_E) and data.getUInt8(data.position) == 0x7e: + pos = data.position + found = GXDLMS.getData(settings, data, reply, None) + data.position = pos + if found: + break + elif data.available() > 1 and type_ == InterfaceType.WRAPPER and data.getUInt16(data.position) == 0x1: + pos = data.position + found = GXDLMS.getData(settings, data, reply, None) + data.position = pos + if found: + break + elif type_ == InterfaceType.WIRELESS_MBUS: + pos = data.position + settings.interfaceType = InterfaceType.WIRELESS_MBUS + found = GXDLMS.getData(settings, data, reply, None) + data.position = pos + if found: + break + data.position = data.position + 1 + except Exception: + raise ValueError("Invalid DLMS frame.") + if pdu: + pdu.clear() + pdu.set(data, 0, data.size) + return data.position != len(data) + + # + # Get all tags. + # + # @param type + # Output type. + # @param list + # List of tags by ID. + # @param tagsByName + # List of tags by name. + # + @classmethod + def __getTags(cls, type_, list_, tagsByName): + if type_ == TranslatorOutputType.SIMPLE_XML: + TranslatorSimpleTags.getGeneralTags(list_) + TranslatorSimpleTags.getSnTags(list_) + TranslatorSimpleTags.getLnTags(list_) + TranslatorSimpleTags.getGloTags(list_) + TranslatorSimpleTags.getDedTags(list_) + TranslatorSimpleTags.getTranslatorTags(list_) + TranslatorSimpleTags.getDataTypeTags(list_) + else: + TranslatorStandardTags.getGeneralTags(list_) + TranslatorStandardTags.getSnTags(list_) + TranslatorStandardTags.getLnTags(list_) + TranslatorStandardTags.getGloTags(list_) + TranslatorStandardTags.getDedTags(list_) + TranslatorStandardTags.getTranslatorTags(list_) + TranslatorStandardTags.getDataTypeTags(list_) + # Simple is not case sensitive. + lowercase = type_ == TranslatorOutputType.SIMPLE_XML + for it in list_: + str_ = list_[it] + if lowercase: + str_ = str_.lower() + if str_ not in tagsByName: + tagsByName[str_] = it + + def getPdu(self, value): + return self.getPdu(GXByteBuffer(value)) + + # + # Identify used DLMS framing type. + # + # @param value + # Input data. + # Interface type. + # + @classmethod + def getDlmsFraming(cls, value): + pos = value.position + while pos != len(value): + if value.getUInt8(pos) == 0x7e: + return InterfaceType.HDLC + if value.available() > 1 and value.getUInt16(pos) == 1: + return InterfaceType.WRAPPER + if GXDLMS.isWirelessMBusData(value): + return InterfaceType.WIRELESS_MBUS + pos += 1 + raise ValueError("Invalid DLMS framing.") + + def getPdu_0(self, value): + framing = self.getDlmsFraming(value) + data = GXReplyData() + data.xml = (GXDLMSTranslatorStructure(self.outputType, self.omitXmlNameSpace, self.hex, self.showStringAsHex, self.comments, self.tags)) + settings = GXDLMSSettings(True) + settings.interfaceType = framing + GXDLMS.getData(settings, value, data, None) + return data.data + + def getCiphering(self, settings, force): + if force or self.security != Security.NONE: + c = GXCiphering(self.systemTitle) + c.security = self.security + c.systemTitle = self.systemTitle + c.blockCipherKey = self.blockCipherKey + c.authenticationKey = self.authenticationKey + c.invocationCounter = self.invocationCounter + c.dedicatedKey = self.dedicatedKey + settings.sourceSystemTitle = self.serverSystemTitle + settings.cipher = c + else: + settings.cipher = None + + # + # Clear {@link messageToXml} internal settings. + # + def clear(self): + self.multipleFrames = False + self.pduFrames.clear() + + @classmethod + def checkFrame(cls, frame_, xml): + if frame_ == 0x93: + xml.appendComment("SNRM frame.") + elif frame_ == 0x73: + xml.appendComment("UA frame.") + elif (frame_ & HdlcFrameType.S_FRAME) == HdlcFrameType.S_FRAME: + # If S -frame. + xml.appendComment("S frame.") + elif (frame_ & 1) == HdlcFrameType.U_FRAME: + # Handle U-frame. + xml.appendComment("U frame.") + else: + # I-frame. + if frame_ == 0x10: + xml.appendComment("AARQ frame.") + elif frame_ == 0x30: + xml.appendComment("AARE frame.") + else: + xml.appendComment("I frame.") + + @classmethod + def __updateAddress(cls, settings, msg): + reply = True + if msg.command in (Command.READ_REQUEST, Command.WRITE_REQUEST, Command.GET_REQUEST,\ + Command.SET_REQUEST,Command.METHOD_REQUEST, Command.SNRM, Command.AARQ,\ + Command.DISCONNECT_REQUEST, Command.RELEASE_REQUEST, Command.ACCESS_REQUEST,\ + Command.GLO_GET_REQUEST, Command.GLO_SET_REQUEST, Command.GLO_METHOD_REQUEST,\ + Command.GLO_INITIATE_REQUEST, Command.GLO_READ_REQUEST, Command.GLO_WRITE_REQUEST,\ + Command.DED_INITIATE_REQUEST,Command.DED_READ_REQUEST, Command.DED_WRITE_REQUEST,\ + Command.DED_GET_REQUEST, Command.DED_SET_REQUEST, Command.DED_METHOD_REQUEST,\ + Command.GATEWAY_REQUEST,Command.DISCOVER_REQUEST, Command.REGISTER_REQUEST,Command.PING_REQUEST): + reply = False + if reply: + msg.targetAddress = settings.clientAddress + msg.sourceAddress = settings.serverAddress + else: + msg.sourceAddress = settings.clientAddress + msg.targetAddress = settings.serverAddress + + # + # + # + # pylint:disable=broad-except + def messageToXml(self, msg): + """ + Convert message to XML. + msg : Translator message data. + Returns Converted xml. + """ + # pylint: disable=too-many-nested-blocks + if not isinstance(msg, (GXDLMSTranslatorMessage)): + if not isinstance(msg, GXByteBuffer): + data = GXByteBuffer(msg) + else: + data = msg + msg = GXDLMSTranslatorMessage() + msg.message = data + if not msg: + raise ValueError("msg") + msg.exception = None + xml = GXDLMSTranslatorStructure(self.outputType, self.omitXmlNameSpace, self.hex, self.showStringAsHex, self.comments, self.tags) + data = GXReplyData() + settings = GXDLMSSettings(True) + self.getCiphering(settings, True) + data.xml = xml + try: + offset = msg.message.position + # If HDLC framing. + if msg.interfaceType in (None, InterfaceType.HDLC, InterfaceType.HDLC_WITH_MODE_E) and msg.message.getUInt8(msg.message.position) == 0x7e: + msg.interfaceType = settings.interfaceType = InterfaceType.HDLC + if GXDLMS.getData(settings, msg.message, data, None): + msg.moreData = data.moreData + msg.sourceAddress = data.sourceAddress + msg.targetAddress = data.targetAddress + if not self.pduOnly: + xml.appendLine("") + xml.appendLine("") + xml.appendLine("") + # Check frame. + if self.comments: + self.checkFrame(data.frameId, xml) + xml.appendLine("") + if not data.data: + if (data.frameId & 1) != 0 and data.command == Command.NONE: + if not self.completePdu: + xml.appendLine("") + self.multipleFrames = True + else: + xml.appendStartTag(data.command) + xml.appendEndTag(data.command) + else: + if self.multipleFrames or data.isMoreData(): + if self.completePdu: + self.pduFrames.set(data.data) + if data.moreData == RequestTypes.NONE: + xml.appendLine(self.__pduToXml(self.pduFrames, True, True)) + self.pduFrames.clear() + else: + xml.appendLine("") + if data.moreData != RequestTypes.DATABLOCK: + self.multipleFrames = False + else: + if not self.pduOnly: + xml.appendLine("") + if self.pduFrames: + self.pduFrames.set(data.data.data) + xml.appendLine(self.__pduToXml(self.pduFrames, True, True)) + self.pduFrames.clear() + else: + if data.command == Command.SNRM or data.command == Command.UA: + xml.appendStartTag(data.command) + self._pduToXml2(xml, data.data, True, True, True) + xml.appendEndTag(data.command) + xml.setXmlLength(xml.getXmlLength() + 2) + else: + xml.appendLine(self.__pduToXml(data.data, True, True)) + # Remove \r\n. + xml.trim() + if not self.pduOnly: + xml.appendLine("") + if not self.pduOnly: + xml.appendLine("") + self.__updateAddress(settings, msg) + msg.xml = str(xml) + return msg.xml + # If wrapper. + if msg.interfaceType in (None, InterfaceType.WRAPPER) and (msg.message.available() > 1) and msg.message.getUInt16(msg.message.position) == 1: + msg.interfaceType = settings.interfaceType = InterfaceType.WRAPPER + GXDLMS.getData(settings, msg.message, data, None) + msg.moreData = data.moreData + msg.sourceAddress = data.sourceAddress + msg.targetAddress = data.targetAddress + pdu = self.__pduToXml(data.data, self.omitXmlDeclaration, self.omitXmlNameSpace) + if not self.pduOnly: + xml.appendLine("") + xml.appendLine("") + xml.appendLine("") + if not self.pduOnly: + xml.appendLine("") + xml.appendLine(pdu) + # Remove \r\n. + xml.trim() + if not self.pduOnly: + xml.appendLine("") + if not self.pduOnly: + xml.appendLine("") + self.__updateAddress(settings, msg) + msg.xml = str(xml) + return msg.xml + #If PLC. + if msg.interfaceType in (None, InterfaceType.PLC) and msg.message.getUInt8(msg.message.position) == 2: + msg.interfaceType = settings.interfaceType = InterfaceType.PLC + GXDLMS.getData(settings, msg.message, data, None) + msg.moreData = data.moreData + msg.sourceAddress = data.sourceAddress + msg.targetAddress = data.targetAddress + if not self.pduOnly: + xml.appendLine("") + if self.comments: + if data.targetAddress == PlcSourceAddress.INITIATOR: + xml.appendComment("Initiator") + elif data.targetAddress == PlcSourceAddress.NEW: + xml.appendComment("New") + xml.appendLine("") + if self.comments and data.sourceAddress == PlcDestinationAddress.ALL_PHYSICAL: + xml.appendComment("AllPhysical") + xml.appendLine("") + if data.data.size == 0: + xml.appendLine("") + else: + if not self.pduOnly: + xml.appendLine("") + xml.appendLine(self.__pduToXml(data.data, self.omitXmlDeclaration, self.omitXmlNameSpace, msg)) + #Remove \r\n. + xml.trim() + if not self.pduOnly: + xml.appendLine("") + if not self.pduOnly: + xml.appendLine("") + self.__updateAddress(settings, msg) + msg.xml = str(xml) + return msg.xml + #If Wired M-Bus. + if msg.interfaceType in (None, InterfaceType.WIRED_MBUS) and GXDLMS.isWiredMBusData(msg.message): + msg.interfaceType = settings.interfaceType = InterfaceType.WIRED_MBUS + len_ = xml.getXmlLength() + GXDLMS.getData(settings, msg.message, data, None) + msg.moreData = data.moreData + msg.sourceAddress = data.sourceAddress + msg.targetAddress = data.targetAddress + tmp = str(xml)[0:len_] + xml.setXmlLength(len_) + if not self.pduOnly: + xml.appendLine("") + xml.appendLine("") + xml.appendLine("") + xml.append(tmp) + if data.data.size == 0: + xml.appendLine("") + else: + if self.multipleFrames or (data.moreData & RequestTypes.FRAME) != 0: + if self.completePdu: + self.pduFrames.set(data.data) + if data.moreData == RequestTypes.NONE: + xml.appendLine(self.__pduToXml(self.pduFrames, True, True)) + self.pduFrames.clear() + else: + xml.appendLine("") + if data.moreData & RequestTypes.FRAME != 0: + self.multipleFrames = True + if data.moreData == RequestTypes.DATABLOCK: + self.multipleFrames = False + else: + if not self.pduOnly: + xml.appendLine("") + if self.pduFrames.size != 0: + self.pduFrames.set(data.data) + data.data.clear() + data.data.set(self.pduFrames) + xml.appendLine(self.__pduToXml(data.data, self.omitXmlDeclaration, self.omitXmlNameSpace)) + #Remove \r\n. + xml.trim() + if not self.pduOnly: + xml.appendLine("") + if not self.pduOnly: + xml.appendLine("") + self.__updateAddress(settings, msg) + msg.xml = str(xml) + return msg.xml + #If Wireless M-Bus. + if msg.interfaceType in (None, InterfaceType.WIRELESS_MBUS) and GXDLMS.isWirelessMBusData(msg.message): + msg.interfaceType = settings.interfaceType = InterfaceType.WIRELESS_MBUS + len_ = xml.getXmlLength() + GXDLMS.getData(settings, msg.message, data, None) + msg.moreData = data.moreData + msg.sourceAddress = data.sourceAddress + msg.targetAddress = data.targetAddress + tmp = str(xml)[0:len_] + xml.setXmlLength(len_) + if not self.pduOnly: + xml.appendLine("") + xml.appendLine("") + xml.appendLine("") + xml.append(tmp) + if data.data.size == 0: + xml.appendLine("") + else: + if not self.pduOnly: + xml.appendLine("") + xml.appendLine(self.__pduToXml(data.data, self.omitXmlDeclaration, self.omitXmlNameSpace, msg)) + #Remove \r\n. + xml.trim() + if not self.pduOnly: + xml.appendLine("") + if not self.pduOnly: + xml.appendLine("") + self.__updateAddress(settings, msg) + msg.xml = str(xml) + return msg.xml + except Exception as ex: + print(ex) + raise ValueError("Invalid DLMS framing.") + + # + # Convert PDU in hex string to XML. + # + # @param pdu + # Converted hex string or GXByteBuffer. + # Converted XML. + # + def pduToXml(self, pdu): + if not isinstance(pdu, GXByteBuffer): + pdu = GXByteBuffer(pdu) + return self.__pduToXml(pdu, self.omitXmlDeclaration, self.omitXmlNameSpace) + + @classmethod + def getUa(cls, data, xml): + data.getUInt8() + # Skip FromatID + data.getUInt8() + # Skip Group ID. + data.getUInt8() + # Skip Group length. + val = None + while data.position < len(data): + id_ = data.getUInt8() + len_ = data.getUInt8() + if len_ == 1: + val = data.getUInt8() + elif len_ == 2: + val = data.getUInt16() + elif len_ == 4: + val = data.getUInt32() + else: + raise GXDLMSException("Invalid Exception.") + if id_ == _HDLCInfo.MAX_INFO_TX: + xml.appendLine("") + elif id_ == _HDLCInfo.MAX_INFO_RX: + xml.appendLine("") + elif id_ == _HDLCInfo.WINDOW_SIZE_TX: + xml.appendLine("") + elif id_ == _HDLCInfo.WINDOW_SIZE_RX: + xml.appendLine("") + else: + raise GXDLMSException("Invalid UA response.") + + # + # Convert bytes to XML. + # + # @param value + # Bytes to convert. + # Converted XML. + # + def __pduToXml(self, value, omitDeclaration, omitNameSpace): + xml = GXDLMSTranslatorStructure(self.outputType, self.omitXmlNameSpace, self.hex, self.showStringAsHex, self.comments, self.tags) + return self._pduToXml2(xml, value, omitDeclaration, omitNameSpace, True) + + @classmethod + def isCiphered(cls, cmd): + return cmd in (Command.GLO_READ_REQUEST, Command.GLO_WRITE_REQUEST, Command.GLO_GET_REQUEST,\ + Command.GLO_SET_REQUEST, Command.GLO_READ_RESPONSE, Command.GLO_WRITE_RESPONSE,\ + Command.GLO_GET_RESPONSE, Command.GLO_SET_RESPONSE, Command.GLO_METHOD_REQUEST,\ + Command.GLO_METHOD_RESPONSE, Command.DED_GET_REQUEST, Command.DED_SET_REQUEST,\ + Command.DED_READ_RESPONSE, Command.DED_GET_RESPONSE, Command.DED_SET_RESPONSE,\ + Command.DED_METHOD_REQUEST, Command.DED_METHOD_RESPONSE, Command.GENERAL_GLO_CIPHERING,\ + Command.GENERAL_DED_CIPHERING, Command.AARQ, Command.AARE, Command.GLO_CONFIRMED_SERVICE_ERROR,\ + Command.DED_CONFIRMED_SERVICE_ERROR, Command.GENERAL_CIPHERING, Command.RELEASE_REQUEST) + + # + # Convert bytes to XML. + # + # @param value + # Bytes to convert. + # Converted XML. + # + def _pduToXml2(self, xml, value, omitDeclaration, omitNameSpace, allowUnknownCommand=True): + #pylint: disable=bad-option-value,too-many-arguments,too-many-locals, + #too-many-nested-blocks,redefined-variable-type + if not value: + raise ValueError("value") + settings = GXDLMSSettings(True) + settings.standard = self.standard + cmd = value.getUInt8() + self.getCiphering(settings, self.isCiphered(cmd)) + data = GXReplyData() + str_ = None + if cmd == Command.AARQ: + value.position = 0 + _GXAPDU.parsePDU(settings, settings.cipher, value, xml) + elif cmd == Command.INITIATE_REQUEST: + value.position = 0 + settings = GXDLMSSettings(True) + _GXAPDU.parseInitiate(True, settings, settings.cipher, value, xml) + elif cmd == Command.INITIATE_RESPONSE: + value.position = 0 + settings = GXDLMSSettings(False) + self.getCiphering(settings, True) + _GXAPDU.parseInitiate(True, settings, settings.cipher, value, xml) + elif cmd == 0x81: + # Ua + value.position = 0 + self.getUa(value, xml) + elif cmd == Command.AARE: + value.position = 0 + settings = GXDLMSSettings(False) + self.getCiphering(settings, True) + _GXAPDU.parsePDU(settings, settings.cipher, value, xml) + elif cmd == Command.GET_REQUEST: + GXDLMSLNCommandHandler.handleGetRequest(settings, None, value, None, xml) + elif cmd == Command.SET_REQUEST: + GXDLMSLNCommandHandler.handleSetRequest(settings, None, value, None, xml) + elif cmd == Command.READ_REQUEST: + GXDLMSSNCommandHandler.handleReadRequest(settings, None, value, None, xml) + elif cmd == Command.METHOD_REQUEST: + GXDLMSLNCommandHandler.handleMethodRequest(settings, None, value, None, None, xml) + elif cmd == Command.WRITE_REQUEST: + GXDLMSSNCommandHandler.handleWriteRequest(settings, None, value, None, xml) + elif cmd == Command.ACCESS_REQUEST: + GXDLMSLNCommandHandler.handleAccessRequest(settings, None, value, None, xml) + elif cmd == Command.DATA_NOTIFICATION: + data.xml = (xml) + data.data = value + value.position = 0 + GXDLMS.getPdu(settings, data) + elif cmd == Command.INFORMATION_REPORT: + data.xml = (xml) + data.data = value + GXDLMSSNCommandHandler.handleInformationReport(settings, data, None) + elif cmd == Command.EVENT_NOTIFICATION: + data.xml = (xml) + data.data = value + GXDLMSLNCommandHandler.handleEventNotification(settings, data, None) + elif cmd in (Command.READ_RESPONSE, Command.WRITE_RESPONSE, Command.GET_RESPONSE, + Command.SET_RESPONSE, Command.METHOD_RESPONSE, Command.ACCESS_RESPONSE, + Command.GENERAL_BLOCK_TRANSFER): + data.xml = xml + data.data = value + value.position = 0 + GXDLMS.getPdu(settings, data) + elif cmd == Command.GENERAL_CIPHERING: + settings.cipher = GXCiphering("ABCDEFGH".encode()) + data.xml = (xml) + data.data = value + value.position = 0 + GXDLMS.getPdu(settings, data) + elif cmd == Command.RELEASE_REQUEST: + xml.appendStartTag(cmd) + value.getUInt8() + # Len. + if value.available() != 0: + # BerType + value.getUInt8() + # Len. + value.getUInt8() + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + str_ = TranslatorSimpleTags.releaseRequestReasonToString(value.getUInt8()) + else: + str_ = TranslatorStandardTags.releaseRequestReasonToString(value.getUInt8()) + xml.appendLine(TranslatorTags.REASON, "Value", str_) + if value.available() != 0: + _GXAPDU.parsePDU2(settings, settings.cipher, value, xml) + xml.appendEndTag(cmd) + elif cmd == Command.RELEASE_RESPONSE: + xml.appendStartTag(cmd) + value.getUInt8() + # Len. + if value.available() != 0: + # BerType + value.getUInt8() + # Len. + value.getUInt8() + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + str_ = TranslatorSimpleTags.releaseResponseReasonToString(value.getUInt8()) + else: + str_ = TranslatorStandardTags.releaseResponseReasonToString(value.getUInt8()) + xml.appendLine(TranslatorTags.REASON, "Value", str_) + if value.available() != 0: + _GXAPDU.parsePDU2(settings, settings.cipher, value, xml) + xml.appendEndTag(cmd) + elif cmd in (Command.GLO_READ_REQUEST, Command.GLO_WRITE_REQUEST, Command.GLO_GET_REQUEST, + Command.GLO_SET_REQUEST, Command.GLO_READ_RESPONSE, Command.GLO_WRITE_RESPONSE, + Command.GLO_GET_RESPONSE, Command.GLO_SET_RESPONSE, Command.GLO_METHOD_REQUEST, + Command.GLO_METHOD_RESPONSE, Command.DED_GET_REQUEST, Command.DED_SET_REQUEST, + Command.DED_READ_RESPONSE, Command.DED_GET_RESPONSE, Command.DED_SET_RESPONSE, + Command.DED_METHOD_REQUEST, Command.DED_METHOD_RESPONSE, + Command.GLO_CONFIRMED_SERVICE_ERROR, Command.DED_CONFIRMED_SERVICE_ERROR): + if settings.cipher and self.comments: + originalPosition = value.position + len_ = xml.getXmlLength() + try: + value.position = value.position - 1 + if cmd in (Command.GLO_READ_REQUEST, Command.GLO_WRITE_REQUEST, Command.GLO_GET_REQUEST, + Command.GLO_SET_REQUEST, Command.GLO_METHOD_REQUEST, Command.DED_GET_REQUEST, + Command.DED_SET_REQUEST, Command.DED_METHOD_REQUEST): + st = settings.cipher.getSystemTitle() + else: + st = settings.sourceSystemTitle + if st or cmd in (Command.GENERAL_GLO_CIPHERING, Command.GENERAL_DED_CIPHERING): + p = None + if cmd in (Command.DED_GET_REQUEST, Command.DED_SET_REQUEST, Command.DED_METHOD_REQUEST): + p = AesGcmParameter(0, st, settings.cipher.dedicatedKey, settings.cipher.authenticationKey) + else: + p = AesGcmParameter(0, st, settings.cipher.blockCipherKey, settings.cipher.authenticationKey) + if p.blockCipherKey: + data2 = GXByteBuffer(GXCiphering.decrypt(settings.cipher, p, value)) + xml.startComment("Decrypt data: " + str(data2)) + self._pduToXml2(xml, data2, omitDeclaration, omitNameSpace, False) + xml.endComment() + except Exception: + # It's OK if this fails. Ciphering settings are not correct. + xml.xml.setXmlLength(len_) + value.position = originalPosition + cnt = _GXCommon.getObjectCount(value) + if cnt != len(value) - value.position: + xml.appendComment("Invalid length: " + str(cnt) + ". It should be: " + str(len(value) - value.position)) + xml.appendLine(cmd, "Value", value.toHex(False, value.position, len(value) - value.position)) + elif cmd in (Command.GENERAL_GLO_CIPHERING, Command.GENERAL_DED_CIPHERING): + if settings.cipher and self.comments: + len_ = xml.getXmlLength() + originalPosition = value.position + try: + tmp = GXByteBuffer() + tmp.set(value, value.position - 1, len(value) - value.position + 1) + p = AesGcmParameter(0, settings.cipher.systemTitle, settings.cipher.blockCipherKey, settings.cipher.authenticationKey) + p.xml = xml + tmp = GXByteBuffer(GXCiphering.decrypt(settings.cipher, p, tmp)) + len_ = xml.getXmlLength() + xml.startComment("Decrypt data: " + str(tmp)) + self._pduToXml2(xml, tmp, omitDeclaration, omitNameSpace, False) + xml.endComment() + except Exception: + # It's OK if this fails. Ciphering settings are not + # correct. + xml.setXmlLength(len_) + value.position = originalPosition + len_ = _GXCommon.getObjectCount(value) + tmp = bytearray(len_) + value.get(tmp) + xml.appendStartTag(Command.GENERAL_GLO_CIPHERING) + xml.appendLine(TranslatorTags.SYSTEM_TITLE, None, GXByteBuffer.hex(tmp, False, 0, len_)) + len_ = _GXCommon.getObjectCount(value) + tmp = bytearray(len_) + value.get(tmp) + xml.appendLine(TranslatorTags.CIPHERED_SERVICE, None, GXByteBuffer.hex(tmp, False, 0, len_)) + xml.appendEndTag(Command.GENERAL_GLO_CIPHERING) + elif cmd == Command.CONFIRMED_SERVICE_ERROR: + data.xml = xml + data.data = value + GXDLMS.handleConfirmedServiceError(data) + elif cmd == Command.GATEWAY_REQUEST: + pass + elif cmd == Command.GATEWAY_RESPONSE: + data.xml = xml + data.data = value + # Get Network ID. + id_ = value.getUInt8() + # Get Physical device address. + len_ = _GXCommon.getObjectCount(value) + tmp = bytearray(len_) + value.get(tmp) + xml.appendStartTag(cmd) + xml.appendLine(TranslatorTags.NETWORK_ID, None, str(id_)) + xml.appendLine(TranslatorTags.PHYSICAL_DEVICE_ADDRESS, None, GXByteBuffer.hex(tmp, False, 0, len_)) + self._pduToXml2(xml, GXByteBuffer(value.remaining()), omitDeclaration, omitNameSpace, allowUnknownCommand) + xml.appendEndTag(cmd) + elif cmd == Command.EXCEPTION_RESPONSE: + data.xml = xml + data.data = value + GXDLMS.handleExceptionResponse(data) + else: + if not allowUnknownCommand: + raise Exception("Invalid command.") + value.position -= 1 + xml.appendLine("") + if self.outputType == TranslatorOutputType.STANDARD_XML: + sb = "" + if not omitDeclaration: + sb += "\r\n" + if not omitNameSpace: + if not cmd in (Command.AARE, Command.AARQ, Command.RELEASE_REQUEST, Command.RELEASE_RESPONSE): + sb += "\r\n" + else: + sb += "\r\n" + + sb += str(xml) + if not omitNameSpace: + if not cmd in (Command.AARE, Command.AARQ, Command.RELEASE_REQUEST, Command.RELEASE_RESPONSE): + sb += "\r\n" + else: + sb += "\r\n" + return sb + return str(xml) + + # + # Get command from XML. + # + # @param node + # XML node. + # @param s + # XML settings. + # @param tag + # Tag. + # + @classmethod + def getCommand(cls, node, s, tag): + s.command = tag + if tag in (Command.SNRM, Command.AARQ, Command.READ_REQUEST, Command.WRITE_REQUEST,\ + Command.GET_REQUEST, Command.SET_REQUEST, Command.RELEASE_REQUEST, Command.METHOD_REQUEST,\ + Command.ACCESS_REQUEST, Command.INITIATE_REQUEST, Command.CONFIRMED_SERVICE_ERROR, Command.EXCEPTION_RESPONSE): + s.settings.server = False + elif tag in (Command.GLO_INITIATE_REQUEST, Command.GLO_GET_REQUEST, Command.GLO_SET_REQUEST,\ + Command.GLO_METHOD_REQUEST, Command.GLO_READ_REQUEST, Command.GLO_WRITE_REQUEST,\ + Command.DED_GET_REQUEST, Command.DED_SET_REQUEST, Command.DED_METHOD_REQUEST): + s.settings.server = False + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + s.settings.getCipher().setSecurity(Security(tmp[0])) + s.data.set(tmp) + elif tag in (Command.UA, Command.AARE, Command.GET_RESPONSE, Command.SET_RESPONSE,\ + Command.READ_RESPONSE, Command.WRITE_RESPONSE, Command.METHOD_RESPONSE,\ + Command.RELEASE_RESPONSE, Command.DATA_NOTIFICATION, Command.ACCESS_RESPONSE,\ + Command.INITIATE_RESPONSE, Command.INFORMATION_REPORT, Command.EVENT_NOTIFICATION,\ + Command.DISCONNECT_REQUEST): + pass + elif tag in (Command.GLO_INITIATE_RESPONSE, Command.GLO_GET_RESPONSE, Command.GLO_SET_RESPONSE,\ + Command.GLO_METHOD_RESPONSE, Command.GLO_READ_RESPONSE, Command.GLO_WRITE_RESPONSE,\ + Command.GLO_EVENT_NOTIFICATION, Command.DED_GET_RESPONSE, Command.DED_SET_RESPONSE,\ + Command.DED_METHOD_RESPONSE, Command.DED_EVENT_NOTIFICATION): + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + s.settings.getCipher().setSecurity(Security(tmp[0])) + s.data.set(tmp) + elif tag == Command.GENERAL_GLO_CIPHERING: + pass + elif tag == Command.GENERAL_CIPHERING: + pass + elif tag == TranslatorTags.FRAME_TYPE: + s.command = 0 + elif tag == Command.GATEWAY_REQUEST: + s.gwCommand = Command.GATEWAY_REQUEST + s.settings.server = False + elif tag == Command.GATEWAY_RESPONSE: + s.gwCommand = Command.GATEWAY_RESPONSE + else: + raise ValueError("Invalid Command: " + node.tag) + + @classmethod + def getFrame(cls, node, s, tag): + found = True + if tag == TranslatorTags.WRAPPER: + s.settings.interfaceType = InterfaceType.WRAPPER + elif tag == TranslatorTags.HDLC: + s.settings.interfaceType = InterfaceType.HDLC + elif tag == TranslatorTags.TARGET_ADDRESS: + s.settings.serverAddress = s.parseInt(cls.getValue(node, s)) + elif tag == TranslatorTags.SOURCE_ADDRESS: + s.settings.clientAddress = s.parseInt(cls.getValue(node, s)) + else: + found = False + return found + + @classmethod + def getNodeCount(cls, node): + cnt = 0 + for _ in node: + cnt += 1 + return cnt + + @classmethod + def handleAarqAare(cls, node, s, tag): + #pylint: disable=bad-option-value,redefined-variable-type + tmp = [] + list_ = None + value = int() + if tag == TranslatorGeneralTags.APPLICATION_CONTEXT_NAME: + if s.outputType == TranslatorOutputType.STANDARD_XML: + value = int(node.getFirstChild().getNodeValue()) + if value == 1: + s.settings.setUseLogicalNameReferencing(True) + elif value == 2: + s.settings.setUseLogicalNameReferencing(False) + elif value == 3: + s.settings.setUseLogicalNameReferencing(True) + elif value == 4: + s.settings.setUseLogicalNameReferencing(False) + else: + raise ValueError("Invalid application context name.") + else: + if node.attrib["Value"] == "SN" or node.attrib["Value"] == "SN_WITH_CIPHERING": + s.settings.setUseLogicalNameReferencing(False) + elif node.attrib["Value"] == "LN" or node.attrib["Value"] == "LN_WITH_CIPHERING": + s.settings.setUseLogicalNameReferencing(True) + else: + raise ValueError("Invalid Reference type name.") + elif tag == Command.GLO_INITIATE_REQUEST: + s.settings.setServer(False) + s.command = (tag) + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + s.settings.getCipher().setSecurity(Security(tmp[0])) + s.data.set(tmp) + elif tag == Command.GLO_INITIATE_RESPONSE: + s.command = (tag) + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + s.settings.getCipher().setSecurity(Security(tmp[0])) + s.data.set(tmp) + elif tag == Command.INITIATE_REQUEST: + pass + elif tag == Command.INITIATE_RESPONSE: + pass + elif tag == TranslatorGeneralTags.USER_INFORMATION: + if s.outputType == TranslatorOutputType.STANDARD_XML: + bb = GXByteBuffer() + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + bb.set(tmp) + if s.settings.isServer: + s.settings.setProposedConformance(0xFFFFFF) + _GXAPDU.parseInitiate(False, s.settings, s.settings.getCipher(), bb, None) + if not s.settings.isServer: + s.settings.proposedConformance = s.settings.negotiatedConformance + elif tag == 0xBE00: + pass + elif tag == 0xBE06: + pass + elif tag == 0xBE01: + pass + elif tag == 0x8A: + pass + elif tag == 0xBE04: + str_ = cls.getValue(node, s) + if int(str_, 16) == 7: + s.settings.setUseLogicalNameReferencing(True) + else: + s.settings.setUseLogicalNameReferencing(False) + elif tag == 0x8B: + pass + elif tag == 0x89: + if s.outputType == TranslatorOutputType.SIMPLE_XML: + s.settings.authentication = Authentication.valueofString(cls.getValue(node, s)) + else: + s.settings.authentication = Authentication(int(cls.getValue(node, s))) + elif tag == 0xAC: + if s.settings.authentication == Authentication.LOW: + s.settings.password = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + else: + s.settings.setCtoSChallenge(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + elif tag == TranslatorGeneralTags.DEDICATED_KEY: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + s.settings.getCipher().setDedicatedKey(tmp) + elif tag == TranslatorGeneralTags.CALLING_AP_TITLE: + s.settings.setCtoSChallenge(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + elif tag == int(TranslatorGeneralTags.CALLING_AE_INVOCATION_ID): + s.settings.setUserId(s.parseInt(cls.getValue(node, s))) + elif tag == int(TranslatorGeneralTags.CALLED_AE_INVOCATION_ID): + s.settings.setUserId(s.parseInt(cls.getValue(node, s))) + elif tag == TranslatorGeneralTags.RESPONDING_AE_INVOCATION_ID: + s.settings.setUserId(s.parseInt(cls.getValue(node, s))) + elif tag == 0xA4: + s.settings.setStoCChallenge(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + elif tag == 0xBE03: + pass + elif tag == 0xBE05: + if s.settings.isServer: + list_ = s.settings.negotiatedConformance + else: + list_ = s.settings.proposedConformance + list_ = [] + if s.outputType == TranslatorOutputType.STANDARD_XML: + nodes = node.getFirstChild().getNodeValue() + for it in nodes.split(" "): + if it.strip(): + list_.append(TranslatorStandardTags.value_ofConformance(it.strip())) + elif tag == 0xBE08: + if s.settings.isServer: + list_ = s.settings.negotiatedConformance + else: + list_ = s.settings.proposedConformance + list_ |= TranslatorSimpleTags.value_ofConformance(node.attrib["Name"]) + elif tag == 0xA2: + s.result = AssociationResult(s.parseInt(cls.getValue(node, s))) + elif tag == 0xBE02: + pass + elif tag == 0xBE07: + s.settings.maxPduSize = s.parseInt(cls.getValue(node, s)) + elif tag == 0xA3: + s.diagnostic = SourceDiagnostic.NONE + elif tag == 0xA301: + s.diagnostic = SourceDiagnostic(s.parseInt(cls.getValue(node, s))) + elif tag == 0xBE09: + pass + elif tag == TranslatorGeneralTags.CHAR_STRING: + if s.settings.authentication == Authentication.LOW: + s.settings.password = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + else: + if s.command == Command.AARQ: + s.settings.setCtoSChallenge(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + else: + s.settings.setStoCChallenge(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + elif tag == TranslatorGeneralTags.RESPONDER_ACSE_REQUIREMENT: + pass + elif tag == TranslatorGeneralTags.RESPONDING_AUTHENTICATION: + s.settings.setStoCChallenge(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + elif tag == TranslatorTags.RESULT: + s.setResult(AssociationResult(int(cls.getValue(node, s)))) + elif tag == Command.CONFIRMED_SERVICE_ERROR: + if s.command == Command.NONE: + s.settings.setServer(False) + s.command = (tag) + elif tag == TranslatorTags.REASON: + if s.command == Command.RELEASE_REQUEST: + if s.OutputType == TranslatorOutputType.SIMPLE_XML: + s.reason = TranslatorSimpleTags.value_ofReleaseRequestReason(cls.getValue(node, s)) + else: + s.reason = TranslatorStandardTags.value_ofReleaseRequestReason(cls.getValue(node, s)) + else: + if s.OutputType == TranslatorOutputType.SIMPLE_XML: + s.reason = TranslatorSimpleTags.value_ofReleaseResponseReason(cls.getValue(node, s)) + else: + s.reason = TranslatorStandardTags.value_ofReleaseResponseReason(cls.getValue(node, s)) + elif tag == TranslatorTags.SERVICE: + s.attributeDescriptor.setUInt8(0xE) + s.attributeDescriptor.setUInt8(s.parseInt(cls.getValue(node, s))) + elif tag == TranslatorTags.SERVICE_ERROR: + if s.command == Command.AARE: + s.attributeDescriptor.setUInt8(6) + for childNode in node: + s.attributeDescriptor.setUInt8(TranslatorSimpleTags.getInitiateByValue(cls.getValue(childNode, s))) + return False + elif tag == TranslatorTags.PROTOCOL_VERSION: + str_ = cls.getValue(node, s) + pv = GXByteBuffer() + pv.setUInt8(int((8 - len(str_)))) + _GXCommon.setBitString(pv, str_, False) + s.settings.setProtocolVersion(str_) + elif tag == TranslatorTags.CALLED_AP_TITLE: + pass + elif tag == TranslatorTags.CALLED_AE_QUALIFIER: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + s.attributeDescriptor.setUInt8(int((0xA2 if tag == int(TranslatorTags.CALLED_AP_TITLE) else 0xA3))) + s.attributeDescriptor.setUInt8(3) + s.attributeDescriptor.setUInt8(BerType.OCTET_STRING) + s.attributeDescriptor.setUInt8(len(tmp)) + s.attributeDescriptor.set(tmp) + elif tag == int(TranslatorTags.CALLED_AP_INVOCATION_ID): + s.attributeDescriptor.setUInt8(0xA6) + s.attributeDescriptor.setUInt8(3) + s.attributeDescriptor.setUInt8(BerType.INTEGER) + s.attributeDescriptor.setUInt8(1) + s.attributeDescriptor.setUInt8(int(s.parseInt(cls.getValue(node, s)))) + elif tag == int(TranslatorTags.CALLED_AE_INVOCATION_ID): + s.attributeDescriptor.setUInt8(0xA5) + s.attributeDescriptor.setUInt8(3) + s.attributeDescriptor.setUInt8(BerType.INTEGER) + s.attributeDescriptor.setUInt8(1) + s.attributeDescriptor.setUInt8(int(s.parseInt(cls.getValue(node, s)))) + elif tag == int(TranslatorTags.CALLING_AP_INVOCATION_ID): + s.attributeDescriptor.setUInt8(0xA4) + s.attributeDescriptor.setUInt8(3) + s.attributeDescriptor.setUInt8(BerType.INTEGER) + s.attributeDescriptor.setUInt8(1) + s.attributeDescriptor.setUInt8(int(s.parseInt(cls.getValue(node, s)))) + else: + raise ValueError("Invalid AARQ node: " + node.tag) + return True + + @classmethod + def getValue(cls, node, s): + str_ = "" + if s.outputType == TranslatorOutputType.STANDARD_XML: + if node.text: + str_ = node.text.strip() + else: + #Get first element. + for it in node.attrib: + str_ = node.attrib[it] + break + return str_ + + @classmethod + def value_ofErrorCode(cls, type_, value): + if type_ == TranslatorOutputType.STANDARD_XML: + return TranslatorStandardTags.value_ofErrorCode(value) + return TranslatorSimpleTags.value_ofErrorCode(value) + + @classmethod + def errorCodeToString(cls, type_, value): + if type_ == TranslatorOutputType.STANDARD_XML: + return TranslatorStandardTags.errorCodeToString(value) + return TranslatorSimpleTags.errorCodeToString(value) + + @classmethod + def readNode(cls, node, s): + #pylint: disable=bad-option-value,redefined-variable-type + value = 0 + tmp = [] + preData = None + str_ = None + if s.outputType == TranslatorOutputType.SIMPLE_XML: + str_ = node.tag.lower() + else: + pos = node.tag.find('}') + if pos != -1: + str_ = node.tag[1 + pos:] + else: + str_ = node.tag + tag = 0 + if s.command != Command.CONFIRMED_SERVICE_ERROR or s.tags.containsKey(str_): + tag = s.tags.get(str_) + if s.command == Command.NONE: + if not ((s.settings.clientAddress == 0 or s.settings.serverAddress == 0) and cls.getFrame(node, s, tag) or tag in (TranslatorTags.PDU_DLMS, TranslatorTags.PDU_CSE)): + cls.getCommand(node, s, tag) + elif s.command in (Command.AARQ, Command.AARE, Command.INITIATE_REQUEST, Command.INITIATE_RESPONSE, Command.RELEASE_REQUEST, Command.RELEASE_RESPONSE): + if not cls.handleAarqAare(node, s, tag): + return + elif tag >= _GXCommon.DATA_TYPE_OFFSET: + if tag == DataType.DATETIME + _GXCommon.DATA_TYPE_OFFSET or (s.command == Command.EVENT_NOTIFICATION and not s.attributeDescriptor): + preData = cls.updateDateTime(node, s, preData) + if preData is None and s.command == Command.GENERAL_CIPHERING: + s.data.setUInt8(0) + else: + preData = cls.updateDataType(node, s, tag) + elif s.command == Command.CONFIRMED_SERVICE_ERROR: + if s.outputType == TranslatorOutputType.STANDARD_XML: + if tag == TranslatorTags.INITIATE_ERROR: + s.attributeDescriptor.setUInt8(1) + else: + se = TranslatorStandardTags.getServiceError(str_[2:]) + s.attributeDescriptor.setUInt8(se) + s.attributeDescriptor.setUInt8(TranslatorStandardTags.getError(se, cls.getValue(node, s))) + else: + if tag != TranslatorTags.SERVICE_ERROR: + if s.attributeDescriptor.size == 0: + s.attributeDescriptor.setUInt8(s.parseShort(cls.getValue(node, s))) + else: + se = TranslatorSimpleTags.getServiceError(str_) + s.attributeDescriptor.setUInt8(se) + s.attributeDescriptor.setUInt8(TranslatorSimpleTags.getError(se, cls.getValue(node, s))) + else: + if tag in (Command.GET_REQUEST << 8 | GetCommandType.NORMAL, + Command.GET_REQUEST << 8 | GetCommandType.NEXT_DATA_BLOCK, + Command.GET_REQUEST << 8 | GetCommandType.WITH_LIST, + Command.SET_REQUEST << 8 | SetRequestType.NORMAL, + Command.SET_REQUEST << 8 | SetRequestType.FIRST_DATA_BLOCK, + Command.SET_REQUEST << 8 | SetRequestType.WITH_DATA_BLOCK, + Command.SET_REQUEST << 8 | SetRequestType.WITH_LIST): + s.requestType = tag & 0xF + elif tag in (Command.GET_RESPONSE << 8 | GetCommandType.NORMAL, + Command.GET_RESPONSE << 8 | GetCommandType.NEXT_DATA_BLOCK, + Command.GET_RESPONSE << 8 | GetCommandType.WITH_LIST, + Command.SET_RESPONSE << 8 | SetResponseType.NORMAL, + Command.SET_RESPONSE << 8 | SetResponseType.DATA_BLOCK, + Command.SET_RESPONSE << 8 | SetResponseType.LAST_DATA_BLOCK, + Command.SET_RESPONSE << 8 | SetResponseType.WITH_LIST, + Command.SET_RESPONSE << 8 | SetResponseType.LAST_DATA_BLOCK_WITH_LIST): + s.requestType = tag & 0xF + elif tag == Command.READ_RESPONSE << 8 | SingleReadResponse.DATA_BLOCK_RESULT: + s.count = s.count + 1 + s.requestType = tag & 0xF + elif tag == Command.READ_REQUEST << 8 | VariableAccessSpecification.PARAMETERISED_ACCESS: + s.requestType = VariableAccessSpecification.PARAMETERISED_ACCESS + elif tag == Command.READ_REQUEST << 8 | VariableAccessSpecification.BLOCK_NUMBER_ACCESS: + s.requestType = VariableAccessSpecification.BLOCK_NUMBER_ACCESS + s.count = s.count + 1 + elif tag == Command.METHOD_REQUEST << 8 | ActionRequestType.NORMAL: + s.requestType = tag & 0xF + elif tag == Command.METHOD_REQUEST << 8 | ActionRequestType.NEXT_BLOCK: + s.requestType = tag & 0xF + elif tag == Command.METHOD_REQUEST << 8 | ActionRequestType.WITH_LIST: + s.requestType = tag & 0xF + elif tag == Command.METHOD_RESPONSE << 8 | ActionResponseType.NORMAL: + s.requestType = tag & 0xF + elif tag in (Command.READ_RESPONSE << 8 | SingleReadResponse.DATA, + TranslatorTags.DATA): + if s.command == Command.READ_REQUEST or s.command == Command.READ_RESPONSE or s.command == Command.GET_REQUEST: + s.count = s.count + 1 + s.requestType = 0 + elif s.command == Command.GET_RESPONSE or s.command == Command.METHOD_RESPONSE: + s.data.setUInt8(0) + elif tag == TranslatorTags.SUCCESS: + s.count = s.count + 1 + s.attributeDescriptor.setUInt8(ErrorCode.OK) + elif tag == TranslatorTags.DATA_ACCESS_ERROR: + s.count = s.count + 1 + s.attributeDescriptor.setUInt8(1) + s.attributeDescriptor.setUInt8(cls.value_ofErrorCode(s.outputType, cls.getValue(node, s))) + elif tag == TranslatorTags.LIST_OF_VARIABLE_ACCESS_SPECIFICATION: + if s.command == Command.WRITE_REQUEST: + _GXCommon.setObjectCount(cls.getNodeCount(node), s.data) + elif tag == TranslatorTags.VARIABLE_ACCESS_SPECIFICATION: + pass + elif tag == TranslatorTags.LIST_OF_DATA: + if s.command == Command.ACCESS_RESPONSE and not s.data: + s.data.setUInt8(0) + if s.outputType == TranslatorOutputType.SIMPLE_XML or s.command != Command.WRITE_REQUEST: + _GXCommon.setObjectCount(cls.getNodeCount(node), s.data) + elif tag in (Command.ACCESS_RESPONSE << 8 | AccessServiceCommandType.GET, + Command.ACCESS_RESPONSE << 8 | AccessServiceCommandType.SET, + Command.ACCESS_RESPONSE << 8 | AccessServiceCommandType.ACTION): + s.requestType = tag & 0xF + elif tag == TranslatorTags.DATE_TIME: + preData = cls.updateDateTime(node, s, preData) + if preData is None and s.command == Command.GENERAL_CIPHERING: + s.data.setUInt8(0) + elif tag == TranslatorTags.CURRENT_TIME: + if s.outputType == TranslatorOutputType.SIMPLE_XML: + cls.updateDateTime(node, s, preData) + else: + str_ = cls.getValue(node, s) + s.setTime(GXDateTime(_GXCommon.getGeneralizedTime(str_))) + elif tag == TranslatorTags.TIME: + preData = cls.updateDateTime(node, s, preData) + elif tag == TranslatorTags.INVOKE_ID: + value = s.parseShort(cls.getValue(node, s)) + s.settings.updateInvokeId(value) + elif tag == TranslatorTags.LONG_INVOKE_ID: + value = s.parseLong(cls.getValue(node, s)) + if (value & 0x80000000) != 0: + s.settings.priority = Priority.HIGH + else: + s.settings.priority = Priority.NORMAL + if (value & 0x40000000) != 0: + s.settings.serviceClass = ServiceClass.CONFIRMED + else: + s.settings.serviceClass = ServiceClass.UN_CONFIRMED + s.settings.longInvokeID = value & 0xFFFFFFF + elif tag == 0x88: + # ResponderACSERequirement + pass + elif tag == 0x80: + s.settings.stoCChallenge = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + elif tag == TranslatorTags.ATTRIBUTE_DESCRIPTOR: + pass + elif tag == TranslatorTags.CLASS_ID: + s.attributeDescriptor.setUInt16(s.parseInt(cls.getValue(node, s))) + elif tag == TranslatorTags.INSTANCE_ID: + s.attributeDescriptor.set(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + elif tag == TranslatorTags.ATTRIBUTE_ID: + s.attributeDescriptor.setUInt8(s.parseShort(cls.getValue(node, s))) + if s.command not in (Command.ACCESS_REQUEST, Command.EVENT_NOTIFICATION): + s.attributeDescriptor.setUInt8(0) + elif tag == TranslatorTags.METHOD_INVOCATION_PARAMETERS: + s.attributeDescriptor.setUInt8(1, len(s.attributeDescriptor) - 1) + elif tag == TranslatorTags.SELECTOR: + s.attributeDescriptor.set(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + elif tag == TranslatorTags.PARAMETER: + pass + elif tag == TranslatorTags.LAST_BLOCK: + s.data.setUInt8(s.parseShort(cls.getValue(node, s))) + elif tag == TranslatorTags.BLOCK_NUMBER: + if s.command in (Command.GET_REQUEST, Command.GET_RESPONSE, Command.SET_REQUEST, Command.SET_RESPONSE, Command.METHOD_REQUEST, Command.METHOD_RESPONSE): + s.data.setUInt32(s.parseLong(cls.getValue(node, s))) + else: + s.data.setUInt16(s.parseInt(cls.getValue(node, s))) + elif tag == TranslatorTags.RAW_DATA: + if s.command == Command.GET_RESPONSE: + s.data.setUInt8(0) + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + _GXCommon.setObjectCount(len(tmp), s.data) + s.data.set(tmp) + elif tag == TranslatorTags.METHOD_DESCRIPTOR: + pass + elif tag == TranslatorTags.METHOD_ID: + s.attributeDescriptor.setUInt8(s.parseShort(cls.getValue(node, s))) + s.attributeDescriptor.setUInt8(0) + elif tag in (TranslatorTags.RESULT, TranslatorGeneralTags.ASSOCIATION_RESULT): + if s.command == Command.GET_REQUEST or s.requestType == 3: + _GXCommon.setObjectCount(node.getChildNodes().getLength(), s.attributeDescriptor) + elif s.command == Command.METHOD_RESPONSE or s.command == Command.SET_RESPONSE: + str_ = cls.getValue(node, s) + if str_: + s.attributeDescriptor.setUInt8(cls.value_ofErrorCode(s.outputType, str_)) + elif s.command == Command.ACCESS_RESPONSE: + str_ = cls.getValue(node, s) + if str_: + s.data.setUInt8(cls.value_ofErrorCode(s.outputType, str_)) + elif tag == TranslatorTags.REASON: + if s.command == Command.RELEASE_REQUEST: + if s.outputType == TranslatorOutputType.SIMPLE_XML: + s.reason = int(TranslatorSimpleTags.value_ofReleaseRequestReason(cls.getValue(node, s))) + else: + s.reason = int(TranslatorStandardTags.value_ofReleaseRequestReason(cls.getValue(node, s))) + else: + if s.outputType == TranslatorOutputType.SIMPLE_XML: + s.reason = int(TranslatorSimpleTags.value_ofReleaseResponseReason(cls.getValue(node, s))) + else: + s.reason = int(TranslatorStandardTags.value_ofReleaseResponseReason(cls.getValue(node, s))) + elif tag == TranslatorTags.RETURN_PARAMETERS: + s.attributeDescriptor.setUInt8(1) + elif tag == TranslatorTags.ACCESS_SELECTION: + s.attributeDescriptor.setUInt8(1, s.attributeDescriptor.size - 1) + elif tag == TranslatorTags.VALUE: + pass + elif tag == TranslatorTags.SERVICE: + if s.attributeDescriptor.size == 0: + s.attributeDescriptor.setUInt8(s.parseShort(cls.getValue(node, s))) + else: + s.attributeDescriptor.setUInt8(ServiceError.SERVICE) + s.attributeDescriptor.setUInt8(Service.valueofString(cls.getValue(node, s)).value) + elif tag == TranslatorTags.ACCESS_SELECTOR: + s.data.setUInt8(s.parseShort(cls.getValue(node, s))) + elif tag == TranslatorTags.ACCESS_PARAMETERS: + pass + elif tag == TranslatorTags.ATTRIBUTE_DESCRIPTOR_LIST: + _GXCommon.setObjectCount(cls.getNodeCount(node), s.attributeDescriptor) + elif tag in (TranslatorTags.ATTRIBUTE_DESCRIPTOR_WITH_SELECTION, + Command.ACCESS_REQUEST << 8 | AccessServiceCommandType.GET, + Command.ACCESS_REQUEST << 8 | AccessServiceCommandType.SET, + Command.ACCESS_REQUEST << 8 | AccessServiceCommandType.ACTION): + if s.command != Command.SET_REQUEST: + s.attributeDescriptor.setUInt8(tag & 0xFF) + elif tag in (Command.READ_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME, + Command.WRITE_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME, + Command.WRITE_REQUEST << 8 | SingleReadResponse.DATA): + if s.command not in (Command.ACCESS_REQUEST, Command.ACCESS_RESPONSE): + if not (s.outputType == TranslatorOutputType.STANDARD_XML and tag == (Command.WRITE_REQUEST << 8 | SingleReadResponse.DATA)): + if s.requestType == 0xFF: + s.attributeDescriptor.setUInt8(VariableAccessSpecification.VARIABLE_NAME) + else: + s.attributeDescriptor.setUInt8(s.requestType) + s.requestType = 0xFF + s.count = s.count + 1 + elif s.command != Command.INFORMATION_REPORT: + s.attributeDescriptor.setUInt8(s.count) + if s.outputType == TranslatorOutputType.SIMPLE_XML: + s.attributeDescriptor.setUInt16(int(cls.getValue(node, s), 16)) + else: + str_ = cls.getValue(node, s) + if str_: + s.attributeDescriptor.setUInt16(int(str_)) + elif tag == TranslatorTags.CHOICE: + pass + elif tag == Command.READ_RESPONSE << 8 | SingleReadResponse.DATA_ACCESS_ERROR: + err = cls.value_ofErrorCode(s.outputType, cls.getValue(node, s)) + s.count = s.count + 1 + s.data.setUInt8(1) + s.data.setUInt8(err) + elif tag == TranslatorTags.NOTIFICATION_BODY: + pass + elif tag == TranslatorTags.DATA_VALUE: + pass + elif tag == TranslatorTags.ACCESS_REQUEST_BODY: + pass + elif tag == TranslatorTags.LIST_OF_ACCESS_REQUEST_SPECIFICATION: + s.attributeDescriptor.setUInt8(cls.getNodeCount(node)) + elif tag == TranslatorTags.ACCESS_REQUEST_SPECIFICATION: + pass + elif tag == TranslatorTags.ACCESS_REQUEST_LIST_OF_DATA: + s.attributeDescriptor.setUInt8(cls.getNodeCount(node)) + elif tag == TranslatorTags.ACCESS_RESPONSE_BODY: + pass + elif tag == TranslatorTags.LIST_OF_ACCESS_RESPONSE_SPECIFICATION: + s.data.setUInt8(cls.getNodeCount(node)) + elif tag == TranslatorTags.ACCESS_RESPONSE_SPECIFICATION: + pass + elif tag == TranslatorTags.ACCESS_RESPONSE_LIST_OF_DATA: + s.data.setUInt8(0) + s.data.setUInt8(cls.getNodeCount(node)) + elif tag == TranslatorTags.SINGLE_RESPONSE: + pass + elif tag == TranslatorTags.SYSTEM_TITLE: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + s.settings.sourceSystemTitle = tmp + elif tag == TranslatorTags.CIPHERED_SERVICE: + pass + elif tag == Command.GLO_INITIATE_REQUEST: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + if s.command == Command.GENERAL_CIPHERING: + _GXCommon.setObjectCount(len(tmp), s.data) + elif s.command == Command.RELEASE_REQUEST: + s.data.setUInt8(0xBE) + _GXCommon.setObjectCount(4 + len(tmp), s.data) + s.data.setUInt8(4) + _GXCommon.setObjectCount(2 + len(tmp), s.data) + s.data.setUInt8(0x21) + _GXCommon.setObjectCount(len(tmp), s.data) + s.data.set(tmp) + elif tag == TranslatorTags.DATA_BLOCK: + pass + elif tag == TranslatorGeneralTags.USER_INFORMATION: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + s.data.setUInt8(0xBE) + s.data.setUInt8(len(tmp)) + s.data.setUInt8(0x4) + s.data.setUInt8() + s.data.set(tmp) + elif tag == TranslatorTags.TRANSACTION_ID: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + _GXCommon.setObjectCount(len(tmp), s.data) + s.data.set(tmp) + elif tag == TranslatorTags.ORIGINATOR_SYSTEM_TITLE: + pass + elif tag == TranslatorTags.RECIPIENT_SYSTEM_TITLE: + pass + elif tag == TranslatorTags.OTHER_INFORMATION: + pass + elif tag == TranslatorTags.KEY_CIPHERED_DATA: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + _GXCommon.setObjectCount(len(tmp), s.data) + s.data.set(tmp) + elif tag == TranslatorTags.CIPHERED_CONTENT: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + _GXCommon.setObjectCount(len(tmp), s.data) + s.data.set(tmp) + elif tag == TranslatorTags.KEY_INFO: + s.data.setUInt8(1) + elif tag == TranslatorTags.AGREED_KEY: + s.data.setUInt8(2) + elif tag == TranslatorTags.KEY_PARAMETERS: + s.data.setUInt8(1) + s.data.setUInt8(int(cls.getValue(node, s))) + elif tag == TranslatorTags.ATTRIBUTE_VALUE: + pass + elif tag == TranslatorTags.MAX_INFO_TX: + value = int(cls.getValue(node, s)) + if (s.command == Command.SNRM and not s.settings.isServer) or (s.command == Command.UA and s.settings.isServer): + s.settings.hdlc.setMaxInfoRX(int(value)) + s.data.setUInt8(_HDLCInfo.MAX_INFO_RX) + s.data.setUInt8(1) + s.data.setUInt8(int(value)) + elif tag == TranslatorTags.MAX_INFO_RX: + value = int(cls.getValue(node, s)) + if (s.command == Command.SNRM and not s.settings.isServer) or (s.command == Command.UA and s.settings.isServer): + s.settings.hdlc.setMaxInfoTX(int(value)) + s.data.setUInt8(_HDLCInfo.MAX_INFO_TX) + s.data.setUInt8(1) + s.data.setUInt8(int(value)) + elif tag == TranslatorTags.WINDOW_SIZE_TX: + value = int(cls.getValue(node, s)) + if (s.command == Command.SNRM and not s.settings.isServer) or (s.command == Command.UA and s.settings.isServer): + s.settings.hdlc.setWindowSizeRX(int(value)) + s.data.setUInt8(int(_HDLCInfo.WINDOW_SIZE_RX)) + s.data.setUInt8(4) + s.data.setUInt32(value) + elif tag == TranslatorTags.WINDOW_SIZE_RX: + value = int(cls.getValue(node, s)) + if (s.command == Command.SNRM and not s.settings.isServer) or (s.command == Command.UA and s.settings.isServer): + s.settings.hdlc.setWindowSizeTX(int(value)) + s.data.setUInt8(_HDLCInfo.WINDOW_SIZE_TX) + s.data.setUInt8(4) + s.data.setUInt32(value) + elif tag == Command.INITIATE_REQUEST: + pass + elif tag == TranslatorTags.VALUE_LIST: + _GXCommon.setObjectCount(cls.getNodeCount(node), s.data) + elif tag == TranslatorTags.DATA_ACCESS_RESULT: + s.data.setUInt8(cls.value_ofErrorCode(s.outputType, cls.getValue(node, s))) + elif tag == TranslatorTags.WRITE_DATA_BLOCK_ACCESS: + pass + elif tag == TranslatorTags.FRAME_TYPE: + pass + elif tag == TranslatorTags.BLOCK_CONTROL: + s.attributeDescriptor.setUInt8(s.parseShort(cls.getValue(node, s))) + elif tag == TranslatorTags.BLOCK_NUMBER_ACK: + s.attributeDescriptor.setUInt16(s.parseShort(cls.getValue(node, s))) + elif tag == TranslatorTags.BLOCK_DATA: + s.data.set(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + elif tag == TranslatorTags.CONTENTS_DESCRIPTION: + cls.getNodeTypes(s, node) + return + elif tag == TranslatorTags.ARRAY_CONTENTS: + if s.outputType == TranslatorOutputType.SIMPLE_XML: + cls.getNodeValues(s, node) + else: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + _GXCommon.setObjectCount(len(tmp), s.data) + s.data.set(tmp) + elif tag == TranslatorTags.NETWORK_ID: + s.setNetworkId(s.parseShort(cls.getValue(node, s))) + elif tag == TranslatorTags.PHYSICAL_DEVICE_ADDRESS: + s.setPhysicalDeviceAddress(GXByteBuffer.hexToBytes(cls.getValue(node, s))) + s.command = (Command.NONE) + elif tag == TranslatorTags.STATE_ERROR: + if s.outputType == TranslatorOutputType.SIMPLE_XML: + s.attributeDescriptor.setUInt8(TranslatorSimpleTags.valueofStateError(cls.getValue(node, s))) + else: + s.attributeDescriptor.setUInt8(TranslatorStandardTags.valueofStateError(cls.getValue(node, s))) + elif tag == TranslatorTags.SERVICE_ERROR: + if s.outputType == TranslatorOutputType.SIMPLE_XML: + s.attributeDescriptor.setUInt8(TranslatorSimpleTags.valueOfExceptionServiceError(cls.getValue(node, s))) + else: + s.attributeDescriptor.setUInt8(TranslatorStandardTags.valueOfExceptionServiceError(cls.getValue(node, s))) + else: + raise ValueError("Invalid node: " + node.tag) + cnt = 0 + for node2 in node: + cls.readNode(node2, s) + cnt += 1 + if preData: + _GXCommon.setObjectCount(cnt, preData) + preData.set(s.data) + s.data.size = 0 + s.data.set(preData) + + @classmethod + def getNodeValues(cls, s, node): + cnt = 1 + offset = 2 + if DataType(s.data.getUInt8(2)) == DataType.STRUCTURE: + cnt = s.data.getUInt8(3) + offset = 4 + types = bytearray(cnt) + pos = 0 + while pos != cnt: + types[pos] = DataType(s.data.getUInt8(offset + pos)) + pos += 1 + tmp = GXByteBuffer() + tmp2 = GXByteBuffer() + row = 0 + while row != node.getChildNodes().getLength(): + str_ = node.getChildNodes().item(row).getNodeValue() + for r in str_.split("\n"): + if r.strip() and not r == "\r": + col = 0 + for it in r.strip().split(";"): + tmp.clear() + _GXCommon.setData(None, tmp, types[len(types)], GXDLMSConverter.changeType(it, types[len(types)])) + if len(tmp) == 1: + s.data.setUInt8(0) + else: + tmp2.set(tmp.subArray(1, len(tmp) - 1)) + col += 1 + row += 1 + _GXCommon.setObjectCount(len(tmp2), s.data) + s.data.set(tmp2) + + @classmethod + def getNodeTypes(cls, s, node): + len1 = cls.getNodeCount(node) + if len1 > 1: + s.data.setUInt8(DataType.STRUCTURE) + _GXCommon.setObjectCount(len, s.data) + for node2 in node: + if s.outputType == TranslatorOutputType.SIMPLE_XML: + str_ = node2.tag.lower() + else: + str_ = node2.tag + tag = s.tags.get(str_) + s.data.setUInt8(tag - _GXCommon.DATA_TYPE_OFFSET) + + @classmethod + def updateDateTime(cls, node, s, preData): + bb = preData + if s.requestType != 0xFF: + bb = cls.updateDataType(node, s, DataType.DATETIME + _GXCommon.DATA_TYPE_OFFSET) + else: + dt = DataType.DATETIME + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + if tmp: + if len(tmp) == 5: + dt = DataType.DATE + elif len(tmp) == 4: + dt = DataType.TIME + s.setTime(_GXCommon.changeType(None, tmp, dt)) + return bb + + @classmethod + def updateDataType(cls, node, s, tag): + #pylint: disable=bad-option-value,redefined-variable-type + preData = None + v = cls.getValue(node, s) + if s.template or v == "*": + s.template = True + return preData + dt = DataType(tag - _GXCommon.DATA_TYPE_OFFSET) + if dt == DataType.ARRAY: + s.data.setUInt8(DataType.ARRAY) + preData = GXByteBuffer(s.data) + s.data.size = 0 + elif dt == DataType.BCD: + _GXCommon.setData(None, s.data, DataType.BCD, s.parseShort(cls.getValue(node, s))) + elif dt == DataType.BITSTRING: + _GXCommon.setData(None, s.data, DataType.BITSTRING, cls.getValue(node, s)) + elif dt == DataType.BOOLEAN: + _GXCommon.setData(None, s.data, DataType.BOOLEAN, s.parseShort(cls.getValue(node, s))) + elif dt == DataType.DATE: + _GXCommon.setData(None, s.data, DataType.DATE, _GXCommon.changeType(None, GXByteBuffer.hexToBytes(cls.getValue(node, s)), DataType.DATE)) + elif dt == DataType.DATETIME: + tmp = GXByteBuffer.hexToBytes(cls.getValue(node, s)) + if len(tmp) == 5: + dt = DataType.DATE + elif len(tmp) == 4: + dt = DataType.TIME + _GXCommon.setData(None, s.data, dt, _GXCommon.changeType(None, tmp, dt)) + elif dt == DataType.ENUM: + _GXCommon.setData(None, s.data, DataType.ENUM, s.parseShort(cls.getValue(node, s))) + elif dt == DataType.FLOAT32: + cls.getFloat32(node, s) + elif dt == DataType.FLOAT64: + cls.getFloat64(node, s) + elif dt == DataType.INT16: + _GXCommon.setData(None, s.data, DataType.INT16, s.parseShort(cls.getValue(node, s))) + elif dt == DataType.INT32: + _GXCommon.setData(None, s.data, DataType.INT32, s.parseInt(cls.getValue(node, s))) + elif dt == DataType.INT64: + _GXCommon.setData(None, s.data, DataType.INT64, s.parseLong(cls.getValue(node, s))) + elif dt == DataType.INT8: + _GXCommon.setData(None, s.data, DataType.INT8, s.parseShort(cls.getValue(node, s))) + elif dt == DataType.NONE: + _GXCommon.setData(None, s.data, DataType.NONE, None) + elif dt == DataType.OCTET_STRING: + cls.getOctetString(node, s) + elif dt == DataType.STRING: + if s.showStringAsHex: + _GXCommon.setData(None, s.data, DataType.STRING, GXByteBuffer.hexToBytes(cls.getValue(node, s))) + else: + _GXCommon.setData(None, s.data, DataType.STRING, cls.getValue(node, s)) + elif dt == DataType.STRING_UTF8: + if s.showStringAsHex: + _GXCommon.setData(None, s.data, DataType.STRING_UTF8, GXByteBuffer.hexToBytes(cls.getValue(node, s))) + else: + _GXCommon.setData(None, s.data, DataType.STRING_UTF8, cls.getValue(node, s)) + elif dt == DataType.STRUCTURE: + s.data.setUInt8(DataType.STRUCTURE) + preData = GXByteBuffer(s.data) + s.data.size = 0 + elif dt == DataType.TIME: + _GXCommon.setData(None, s.data, DataType.TIME, _GXCommon.changeType(None, GXByteBuffer.hexToBytes(cls.getValue(node, s)), DataType.TIME)) + elif dt == DataType.UINT16: + _GXCommon.setData(None, s.data, DataType.UINT16, s.parseShort(cls.getValue(node, s))) + elif dt == DataType.UINT32: + _GXCommon.setData(None, s.data, DataType.UINT32, s.parseLong(cls.getValue(node, s))) + elif dt == DataType.UINT64: + _GXCommon.setData(None, s.data, DataType.UINT64, s.parseLong(cls.getValue(node, s))) + elif dt == DataType.UINT8: + _GXCommon.setData(None, s.data, DataType.UINT8, s.parseShort(cls.getValue(node, s))) + elif dt == DataType.COMPACT_ARRAY: + s.data.setUInt8(dt.value) + else: + raise ValueError("Invalid node: " + node.tag) + return preData + + @classmethod + def getOctetString(cls, node, s): + bb = GXByteBuffer() + bb.setHexString(cls.getValue(node, s)) + _GXCommon.setData(None, s.data, DataType.OCTET_STRING, bb.array()) + + @classmethod + def getFloat32(cls, node, s): + bb = GXByteBuffer() + bb.setHexString(cls.getValue(node, s)) + _GXCommon.setData(None, s.data, DataType.FLOAT32, bb.getFloat()) + + @classmethod + def getFloat64(cls, node, s): + bb = GXByteBuffer() + bb.setHexString(cls.getValue(node, s)) + _GXCommon.setData(None, s.data, DataType.FLOAT64, bb.getDouble()) + + def xmlToHexPdu(self, xml, addSpace=False): + return GXByteBuffer.hex(self.xmlToPdu(xml, None), addSpace) + + def xmlToPdu(self, xml, settings=None): + s = settings + if s is None: + s = GXDLMSXmlSettings(self.outputType, self.hex, self.showStringAsHex, self.tagsByName) + + tmp = ET.fromstring(xml) + if tmp.tag == '{http://www.dlms.com/COSEMpdu}xDLMS-APDU': + for it in tmp: + tmp = it + break + self.readNode(tmp, s) + bb = GXByteBuffer() + ln = None + sn = None + if s.command == Command.INITIATE_REQUEST: + _GXAPDU.getInitiateRequest(s.settings, bb) + elif s.command == Command.INITIATE_RESPONSE: + bb.set(_GXAPDU.getUserInformation(s.settings, s.settings.cipher)) + elif s.command in (Command.READ_REQUEST, Command.WRITE_REQUEST, Command.READ_RESPONSE, Command.WRITE_RESPONSE): + sn = GXDLMSSNParameters(s.settings, s.command, s.count, s.requestType, s.attributeDescriptor, s.data) + GXDLMS.getSNPdu(sn, bb) + elif s.command in (Command.GET_REQUEST, Command.GET_RESPONSE, Command.SET_REQUEST, Command.SET_RESPONSE, Command.METHOD_REQUEST, Command.METHOD_RESPONSE): + ln = GXDLMSLNParameters(s.settings, 0, s.command, s.requestType, s.attributeDescriptor, s.data, 0xff) + GXDLMS.getLNPdu(ln, bb) + elif s.command in (Command.GLO_GET_REQUEST, Command.GLO_GET_RESPONSE, Command.GLO_SET_REQUEST, Command.GLO_SET_RESPONSE, \ + Command.GLO_METHOD_REQUEST, Command.GLO_METHOD_RESPONSE, Command.GLO_READ_REQUEST, Command.GLO_WRITE_REQUEST, Command.GLO_READ_RESPONSE, \ + Command.GLO_WRITE_RESPONSE, Command.DED_GET_REQUEST, Command.DED_GET_RESPONSE, Command.DED_SET_REQUEST, Command.DED_SET_RESPONSE, \ + Command.DED_METHOD_REQUEST, Command.DED_METHOD_RESPONSE): + bb.setUInt8(s.command) + _GXCommon.setObjectCount(len(s.data), bb) + bb.set(s.data) + elif s.command == Command.UNACCEPTABLE_FRAME: + pass + elif s.command == Command.SNRM: + s.settings.server = False + if s.data: + bb.setUInt8(0x81) + bb.setUInt8(0x80) + bb.setUInt8(len(s.data)) + bb.set(s.data) + s.data.clear() + s.data.set(bb) + bb.clear() + bb.set(GXDLMS.getHdlcFrame(s.settings, int(Command.SNRM), s.data)) + elif s.command == Command.UA: + if s.data: + bb.setUInt8(0x81) + bb.setUInt8(0x80) + bb.setUInt8(len(s.data)) + bb.set(s.data) + s.data.clear() + s.data.set(bb) + bb.clear() + bb.set(GXDLMS.getHdlcFrame(s.settings, s.command, s.data)) + elif s.command in (Command.AARQ, Command.GLO_INITIATE_REQUEST): + _GXAPDU.generateAarq(s.settings, s.settings.cipher, s.data, bb) + elif s.command in (Command.AARE, Command.GLO_INITIATE_RESPONSE): + _GXAPDU.generateAARE(s.settings, bb, s.result, s.diagnostic, s.settings.cipher, s.attributeDescriptor, s.data) + elif s.command == Command.DISCONNECT_REQUEST: + pass + elif s.command == Command.RELEASE_REQUEST: + bb.setUInt8(s.command) + bb.setUInt8(3 + len(s.data)) + bb.setUInt8(BerType.CONTEXT) + bb.setUInt8(1) + bb.setUInt8(s.reason) + if s.data.size == 0: + bb.setUInt8(0) + else: + bb.set(s.data) + elif s.command == Command.RELEASE_RESPONSE: + bb.setUInt8(s.command) + bb.setUInt8(3) + bb.setUInt8(BerType.CONTEXT) + bb.setUInt8(1) + bb.setUInt8(s.reason) + elif s.command in (Command.CONFIRMED_SERVICE_ERROR, Command.EXCEPTION_RESPONSE): + bb.setUInt8(s.command) + bb.set(s.attributeDescriptor) + elif s.command == Command.EXCEPTION_RESPONSE: + pass + elif s.command == Command.GENERAL_BLOCK_TRANSFER: + ln = GXDLMSLNParameters(s.settings, 0, s.command, s.requestType, s.attributeDescriptor, s.data, 0xff, Command.NONE) + GXDLMS.getLNPdu(ln, bb) + elif s.command == Command.ACCESS_REQUEST: + ln = GXDLMSLNParameters(s.settings, 0, s.command, s.requestType, s.attributeDescriptor, s.data, 0xff) + GXDLMS.getLNPdu(ln, bb) + elif s.command == Command.ACCESS_RESPONSE: + ln = GXDLMSLNParameters(s.settings, 0, s.command, s.requestType, s.attributeDescriptor, s.data, 0xff) + GXDLMS.getLNPdu(ln, bb) + elif s.command == Command.DATA_NOTIFICATION: + ln = GXDLMSLNParameters(s.settings, 0, s.command, s.requestType, s.attributeDescriptor, s.data, 0xff) + ln.time = s.time + GXDLMS.getLNPdu(ln, bb) + elif s.command == Command.INFORMATION_REPORT: + sn = GXDLMSSNParameters(s.settings, s.command, s.count, s.requestType, s.attributeDescriptor, s.data) + sn.time = s.time + GXDLMS.getSNPdu(sn, bb) + elif s.command == Command.EVENT_NOTIFICATION: + ln = GXDLMSLNParameters(s.settings, 0, s.command, s.requestType, s.attributeDescriptor, s.data, 0xff) + ln.time = s.time + GXDLMS.getLNPdu(ln, bb) + elif s.command == Command.GENERAL_GLO_CIPHERING: + bb.setUInt8(s.command) + _GXCommon.setObjectCount(len(s.settings.sourceSystemTitle), bb) + bb.set(s.settings.sourceSystemTitle) + _GXCommon.setObjectCount(len(s.data), bb) + bb.set(s.data) + elif s.command == Command.GENERAL_CIPHERING: + bb.setUInt8(s.command) + bb.set(s.data) + elif s.command == Command.GLO_EVENT_NOTIFICATION: + pass + else: + raise ValueError("Invalid command.") + if s.physicalDeviceAddress: + bb2 = GXByteBuffer() + bb2.setUInt8(s.gwCommand) + bb2.setUInt8(s.networkId) + _GXCommon.setObjectCount(len(s.physicalDeviceAddress), bb2) + bb2.set(s.physicalDeviceAddress) + bb2.set(bb) + return bb2 + return bb + + def dataToXml(self, data): + di = _GXDataInfo() + xml = GXDLMSTranslatorStructure(self.outputType, self.omitXmlNameSpace, self.hex, self.showStringAsHex, self.comments, self.tags) + di.xml = xml + _GXCommon.getData(None, data, di) + return str(di.xml) + + def __getAllDataNodes(self, nodes, s): + preData = None + tag = int() + for it in nodes: + if s.outputType == TranslatorOutputType.SIMPLE_XML: + tag = s.tags.get(it.tag.tolower()) + else: + tag = s.tags.get(it.tag) + if tag == TranslatorTags.RAW_DATA: + s.data.setHexString(it.value) + else: + preData = self.updateDataType(it, s, tag) + if preData: + _GXCommon.setObjectCount(it.getChildNodes().getLength(), preData) + preData.set(s.data) + s.data.size(0) + s.data.set(preData) + self.__getAllDataNodes(it.getChildNodes(), s) + + def XmlToData(self, xml): + s = GXDLMSXmlSettings(self.outputType, self.hex, self.showStringAsHex, self.tagsByName) + self.__getAllDataNodes(ET.fromstring(xml).iter(), s) + return s.data diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSTranslatorMessage.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSTranslatorMessage.py new file mode 100644 index 0000000..3ad31b5 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSTranslatorMessage.py @@ -0,0 +1,59 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXDLMSTranslatorMessage(): + ##pylint: disable=too-many-instance-attributes + def __init__(self): + """ + Constructor. + """ + self.message = None + """Message to convert to XML.""" + #Converted XML. + self.xml = None + #Executed Command. + self.command = None + #System title from AARQ or AARE messages. + self.systemTitle = None + # Dedicated key from AARQ messages. + self.dedicatedKey = None + # Interface type. + self.interfaceType = None + #Client address. + self.sourceAddress = 0 + # Server address. + self.targetAddress = 0 + #Is more data available. Return None if more data is not available or Frame or Block type. + self.moreData = 0 + #Occurred exception. + self.exception = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSTranslatorStructure.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSTranslatorStructure.py new file mode 100644 index 0000000..0c6f7ed --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSTranslatorStructure.py @@ -0,0 +1,258 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# +from .TranslatorOutputType import TranslatorOutputType +from .internal._GXCommon import _GXCommon +# pylint: disable=too-many-arguments, too-many-instance-attributes +class GXDLMSTranslatorStructure: + """ + This class is used internally in GXDLMSTranslator to save generated XML. + """ + + def setOffset(self, value): + """ + Amount of spaces. + """ + if value < 0: + raise ValueError("offset") + self.offset = value + + # + # Constructor. + # + # @param list + # List of tags. + # + def __init__(self, type_, numericsAshex, numericshex, isHex, addComments, list_): + self.sb = "" + self.tags = None + self.outputType = None + # Are numeric values shows as hex. + self.showNumericsAsHex = False + # Amount of spaces. + self.offset = 0 + self.outputType = type_ + numericsAshex = numericshex + self.showNumericsAsHex = numericsAshex + self.showStringAsHex = isHex + self.tags = list_ + # Are comments added. + self.comments = addComments + self.omitNameSpace = False + + def __str__(self): + return str(self.sb) + + def getDataType(self, type_): + return self.__getTag(_GXCommon.DATA_TYPE_OFFSET + type_) + + def __getTag(self, tag): + if self.outputType == TranslatorOutputType.SIMPLE_XML or self.omitNameSpace: + return self.tags.get(tag) + return "x:" + self.tags.get(tag) + + # + # Append spaces to the buffer. + # + # @param count + # Amount of spaces. + # + def appendSpaces(self, count): + pos = 0 + while pos != count: + self.sb += ' ' + pos += 1 + + def appendLine(self, tag, name=None, value=None): + self.appendSpaces(2 * self.offset) + if name is None and value is None: + self.sb += tag + else: + self.sb += '<' + if isinstance(tag, int): + tag = self.__getTag(tag) + self.sb += tag + if self.outputType == TranslatorOutputType.SIMPLE_XML: + self.sb += ' ' + if name is None: + self.sb += "Value" + else: + self.sb += name + self.sb += "=\"" + else: + self.sb += '>' + self.sb += value + if self.outputType == TranslatorOutputType.SIMPLE_XML: + self.sb += "\" />" + else: + self.sb += "' + self.sb += '\r' + self.sb += '\n' + + # + # Append comment. + # + # @param comment + # Comment to add. + # + def appendComment(self, comment): + if self.comments: + self.appendSpaces(2 * self.offset) + self.sb += "" + self.sb += '\r' + self.sb += '\n' + + # + # Start comment section. + # + # @param comment + # Comment to add. + # + def startComment(self, comment): + if self.comments: + self.appendSpaces(2 * self.offset) + self.sb += "" + self.sb += '\r' + self.sb += '\n' + + def append(self, tag, start=None): + if isinstance(tag, str): + self.sb += tag + return + if start: + self.appendSpaces(2 * self.offset) + self.sb += '<' + else: + self.sb += "' + + def appendStartTag(self, tag, name=None, value=None, plain=False): + if isinstance(name, int): + tag = self.__getTag(tag << 8 | name) + name = None + elif isinstance(tag, int): + tag = self.__getTag(tag) + self.appendSpaces(2 * self.offset) + self.sb += '<' + self.sb += tag + if self.outputType == TranslatorOutputType.SIMPLE_XML and name: + self.sb += ' ' + self.sb += name + self.sb += "=\"" + self.sb += value + self.sb += "\" >" + else: + self.sb += '>' + if not plain: + self.sb += '\r' + self.sb += '\n' + self.offset += 1 + + def appendEndTag(self, tag, plain=False): + if isinstance(tag, int): + if not isinstance(plain, bool) and (isinstance(plain, int)): + tag = self.__getTag(tag << 8 | plain) + else: + tag = self.__getTag(tag) + self.setOffset(self.offset - 1) + if not isinstance(plain, bool) or not plain: + self.appendSpaces(2 * self.offset) + self.sb += "' + self.sb += '\r' + self.sb += '\n' + + def appendEmptyTag(self, tag): + if isinstance(tag, int): + tag = self.__getTag(tag) + self.appendSpaces(2 * self.offset) + self.sb += "<" + self.sb += tag + self.sb += '/' + self.sb += '>' + self.sb += '\r' + self.sb += '\n' + + def trim(self): + """ + Remove \r\n. + """ + self.sb = self.sb[:-2] + + def getXmlLength(self): + """ + XML Length. + """ + return len(self.sb) + + def setXmlLength(self, value): + """ + Set XML Length. + """ + self.sb = self.sb[0:value] + + # + # Convert integer to string. + # + # @param value + # Converted value. + # @param desimals + # Amount of decimals. + # @param forceHex + # Force value as hex. + # Integer as string. + # + def integerToHex(self, value, desimals, forceHex=False): + if forceHex or (self.showNumericsAsHex and self.outputType == TranslatorOutputType.SIMPLE_XML): + return _GXCommon.integerToHex(value, desimals) + return str(value) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSXmlClient.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSXmlClient.py new file mode 100644 index 0000000..14bf9af --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSXmlClient.py @@ -0,0 +1,203 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import xml.etree.cElementTree as ET +from .secure.GXDLMSSecureClient import GXDLMSSecureClient +from .enums import InterfaceType, Command, DataType +from .GXDLMS import GXDLMS +from .internal._GXCommon import _GXCommon +from .GXByteBuffer import GXByteBuffer +from .GXDLMSLNParameters import GXDLMSLNParameters +from .GXDLMSXmlSettings import GXDLMSXmlSettings +from .GXDLMSTranslator import GXDLMSTranslator +from .enums.Security import Security +from .GXDLMSXmlPdu import GXDLMSXmlPdu +from .TranslatorOutputType import TranslatorOutputType + +class GXDLMSXmlClient(GXDLMSSecureClient): + """ + GXDLMS Xml client implements methods to communicate with DLMS/COSEM metering + devices using XML. + """ + + # + # Constructor + # + # @param type + # XML type. + # + def __init__(self, type_ = TranslatorOutputType.SIMPLE_XML): + GXDLMSSecureClient.__init__(self) + # XML client don't throw exceptions. It serializes them as a default. Set + # value to true, if exceptions are thrown. + # + self.throwExceptions = False + self.translator = GXDLMSTranslator(type_) + self.translator.hex = False + self.useLogicalNameReferencing = True + + + @classmethod + def removeRecursively(cls, node, nodeType, name): + if node.getNodeType() == nodeType and (name is None or node.tag == name): + node.getParentNode().removeChild(node) + else: + # check the children recursively + list_ = node.getChildNodes() + i = 0 + while i < list_.getLength(): + cls.removeRecursively(list_.item(i), nodeType, name) + i += 1 + + # + # Load XML commands from the string or file. + # + # @param filename + # XML file name. + # @param settings + # Load settings. + # Loaded XML objects. + # pylint: disable=too-many-locals,too-many-nested-blocks + def load(self, filename, loadSettings = None): + tree = ET.parse(filename) + root = tree.getroot() + actions = list() + description = None + error = None + errorUrl = None + sleep = None + for node in root: + if node.tag == "Description": + description = node.text + continue + if node.tag == "Error": + error = node.text + continue + if node.tag == "ErrorUrl": + errorUrl = node.text + continue + if node.tag == "Sleep": + sleep = node.text + continue + if loadSettings and node.tag == "GetRequest": + structure = node.find("./GetRequestNormal/AccessSelection/AccessSelector/AccessParameters/Structure") + if structure: + start = False + for node2 in structure: + if start: + bb = GXByteBuffer() + if start: + _GXCommon.setData(self.settings, bb, DataType.OCTET_STRING, loadSettings.start) + node2.attrib["Value"] = bb.toHex(False, 2) + start = False + else: + _GXCommon.setData(self.settings, bb, DataType.OCTET_STRING, loadSettings.end) + node2.attrib["Value"] = bb.toHex(False, 2) + + s = GXDLMSXmlSettings(self.translator.outputType, self.translator.hex, self.translator.showStringAsHex, self.translator.tagsByName) + s.settings.clientAddress = self.settings.clientAddress + s.settings.serverAddress = self.settings.serverAddress + reply = [] + reply = self.translator.xmlToPdu(GXDLMSXmlPdu.getOuterXml(node), s) + if s.command == Command.SNRM and not s.settings.isServer: + self.settings.hdlc.maxInfoTX = s.settings.hdlc.maxInfoTX + self.settings.hdlc.maxInfoRX = s.settings.hdlc.maxInfoRX + self.settings.hdlc.windowSizeRX = s.settings.hdlc.windowSizeRX + self.settings.hdlc.windowSizeTX = s.settings.hdlc.windowSizeTX + elif s.command == Command.UA and s.settings.isServer: + self.settings.hdlc.maxInfoTX = s.settings.hdlc.maxInfoTX + self.settings.hdlc.maxInfoRX = s.settings.hdlc.maxInfoRX + self.settings.hdlc.windowSizeRX = s.settings.hdlc.windowSizeRX + self.settings.hdlc.windowSizeTX = s.settings.hdlc.windowSizeTX + if s.template: + reply = None + p = GXDLMSXmlPdu(s.command, node, reply) + if description: + p.description = description + if error: + p.error = error + if errorUrl: + p.errorUrl = errorUrl + if sleep: + p.sleep = int(sleep) + actions.append(p) + return actions + + def pduToMessages(self, pdu): + self.settings.command = pdu.command + messages = list() + if pdu.command == Command.SNRM: + messages.append(pdu.data) + elif pdu.command == Command.UA: + messages.append(pdu.data) + elif pdu.command == Command.DISCONNECT_REQUEST: + messages.append(GXDLMS.getHdlcFrame(self.settings, int(Command.DISCONNECT_REQUEST), GXByteBuffer(pdu.data))) + else: + reply = None + if self.settings.interfaceType == InterfaceType.WRAPPER: + if self.ciphering.security != Security.NONE: + p = GXDLMSLNParameters(self.settings, 0, pdu.command, 0x0, None, None, 0xff) + reply = GXByteBuffer(GXDLMS.cipher0(p, pdu.data)) + else: + reply = GXByteBuffer(pdu.data) + else: + if self.ciphering.security != Security.NONE: + p = GXDLMSLNParameters(self.settings, 0, pdu.command, 0x0, None, None, 0xff) + tmp = GXDLMS.cipher0(p, pdu.data) + reply = GXByteBuffer(len(tmp)) + reply.set(_GXCommon.LLC_SEND_BYTES) + reply.set(tmp) + else: + reply = GXByteBuffer(len(pdu.data)) + reply.set(_GXCommon.LLC_SEND_BYTES) + reply.set(pdu.data) + frame_ = 0 + while reply.position != len(reply): + if self.settings.interfaceType == InterfaceType.WRAPPER: + messages.append(GXDLMS.getWrapperFrame(self.settings, pdu.command, reply)) + elif self.settings.interfaceType == InterfaceType.HDLC or self.settings.interfaceType == InterfaceType.HDLC_WITH_MODE_E: + if pdu.command == Command.AARQ: + frame_ = 0x10 + elif pdu.command == Command.AARE: + frame_ = 0x30 + elif pdu.command == Command.EVENT_NOTIFICATION: + frame_ = 0x13 + messages.append(GXDLMS.getHdlcFrame(self.settings, frame_, reply)) + if reply.position != len(reply): + frame_ = self.settings.getNextSend(False) + elif self.settings.interfaceType == InterfaceType.PDU: + messages.append(reply.array()) + break + else: + raise ValueError("InterfaceType") + return messages diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSXmlPdu.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSXmlPdu.py new file mode 100644 index 0000000..b85aabe --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSXmlPdu.py @@ -0,0 +1,107 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import xml.etree.cElementTree as ET +from .enums.Command import Command + +# pylint: disable=bad-option-value,old-style-class +class GXDLMSXmlPdu: + #pylint: disable=too-many-instance-attributes + def __init__(self, command=0, xml=None, pdu=None): + # XML Node. + self.xmlNode = xml + # Command. + self.command = command + # Description of the test. + self.description = None + # Shown error if this test fails. + self.error = None + # Error url if test fails. + self.errorUrl = None + # Generated Pdu. + self.data = pdu + # Sleep time in milliseconds. + self.privateSleep = 0 + + @classmethod + def getOuterXml(cls, node): + return ET.tostring(node) + + # + # Return PDU as XML string. + # + def getPduAsXml(self): + if self.xmlNode is None: + return None + return self.getOuterXml(self.xmlNode).decode("utf-8") + + @classmethod + def compare(cls, expectedNode, actualNode, list_): + cnt = expectedNode.getChildNodes().getLength() + if expectedNode.getNodeName().compareTo(actualNode.getNodeName()) != 0: + a = expectedNode.getAttributes().getNamedItem("Value") + if expectedNode.getNodeName().compareTo("None") == 0 and a and a.getNodeValue().compareTo("*") == 0: + return + list_.append(expectedNode.getNodeName() + "-" + actualNode.getNodeName()) + return + if cnt != actualNode.getChildNodes().getLength(): + # If we are reading array items count might vary. + if expectedNode.getNodeName() == "Array" or expectedNode.getNodeName() == "Structure": + # Check only first If meter is returning more nodes what + # wehave in template. + if not cnt < actualNode.getChildNodes().getLength(): + cnt = actualNode.getChildNodes().getLength() + else: + list_.append("Different amount: " + expectedNode.getNodeName() + "-" + actualNode.getNodeName()) + return + pos = 0 + while pos != cnt: + if actualNode.getChildNodes().item(pos) is None: + list_.append("Different values. Expected: '" + expectedNode.getChildNodes().item(pos).getNodeValue() + "'. Actual: 'null'.") + elif actualNode.getChildNodes().item(pos).getChildNodes().getLength() != 0: + cls.compare(expectedNode.getChildNodes().item(pos), actualNode.getChildNodes().item(pos), list_) + elif expectedNode.getChildNodes().item(pos).getNodeValue().compareTo(actualNode.getChildNodes().item(pos).getNodeValue()) != 0: + a = expectedNode.getChildNodes().item(pos).getAttributes().getNamedItem("Value") + if a is None or (expectedNode.getChildNodes().item(pos).getNodeName().compareTo("None") != 0 and expectedNode.getChildNodes().item(pos).getNodeName().compareTo(actualNode.getChildNodes().item(pos).getNodeName()) != 0) or a.getNodeValue().compareTo("*") != 0: + if not expectedNode.getFirstChild().getNodeName() == "Structure" and not expectedNode.getFirstChild().getNodeName() == "Array" and not expectedNode.getParentNode().getNodeName() == "Array": + list_.append("Different values. Expected: '" + expectedNode.getChildNodes().item(pos).getNodeValue() + "'. Actual: '" + actualNode.getChildNodes().item(pos).getNodeValue() + "'.") + pos += 1 + + def isRequest(self): + return self.command in (Command.SNRM, Command.AARQ, Command.READ_REQUEST, Command.GLO_READ_REQUEST,\ + Command.WRITE_REQUEST, Command.GLO_WRITE_REQUEST, Command.GET_REQUEST, Command.GLO_GET_REQUEST,\ + Command.SET_REQUEST, Command.GLO_SET_REQUEST, Command.METHOD_REQUEST, Command.GLO_METHOD_REQUEST,\ + Command.DISCONNECT_REQUEST, Command.RELEASE_REQUEST) + + def __str__(self): + return self.getPduAsXml() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSXmlSettings.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSXmlSettings.py new file mode 100644 index 0000000..6986f83 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDLMSXmlSettings.py @@ -0,0 +1,110 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .TranslatorOutputType import TranslatorOutputType +from .enums.InterfaceType import InterfaceType +from .GXCiphering import GXCiphering +from .enums.AssociationResult import AssociationResult +from .enums.SourceDiagnostic import SourceDiagnostic +from .GXByteBuffer import GXByteBuffer +from .GXDLMSSettings import GXDLMSSettings +# pylint:disable=bad-option-value,old-style-class +class GXDLMSXmlSettings: + # + # Constructor. + # + # pylint: disable=too-many-instance-attributes + # + def __init__(self, type_, numericsAsHex, isHex, list_): + self.settings = GXDLMSSettings(True) + self.outputType = type_ + # Are numeric values shows as hex. + self.showNumericsAsHex = self.outputType != TranslatorOutputType.STANDARD_XML and numericsAsHex + self.showStringAsHex = isHex + self.settings.interfaceType = InterfaceType.PDU + self.settings.cipher = GXCiphering("ABCDEFGH".encode()) + self.tags = list_ + self.result = AssociationResult.ACCEPTED + self.diagnostic = SourceDiagnostic.NONE + self.reason = 0 + self.command = 0 + self.gwCommand = 0 + # GW newtork ID. + self.networkId = 0 + self.physicalDeviceAddress = [] + self.count = 0 + self.requestType = 0xFF + self.attributeDescriptor = GXByteBuffer() + self.data = GXByteBuffer() + self.time = None + # Is xml used as a reply template. + self.template = False + + def parseInt(self, value): + if not value: + return 0 + if self.showNumericsAsHex: + return int(value, 16) + return int(value) + + def parseShort(self, value): + if not value: + return 0 + if self.showNumericsAsHex: + return int(value, 16) + return int(value) + + def parseLong(self, value): + if not value: + return 0 + if self.showNumericsAsHex: + return int(value, 16) + return int(value) + + # + # the outputType + # + def getOutputType(self): + return self.outputType + + def getNetworkId(self): + return self.networkId + + def setNetworkId(self, value): + self.networkId = value + + def getPhysicalDeviceAddress(self): + return self.physicalDeviceAddress + + def setPhysicalDeviceAddress(self, value): + self.physicalDeviceAddress = value diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDate.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDate.py new file mode 100644 index 0000000..4d781ab --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDate.py @@ -0,0 +1,63 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import datetime +from .GXDateTime import GXDateTime +from .enums import DateTimeSkips + +class GXDate(GXDateTime): + def __init__(self, value=None, pattern=None): + """ + Constructor. + + pattern: Date-time pattern that is used when value is a string. + value: Date value. + """ + GXDateTime.__init__(self, value, pattern) + self.skip |= DateTimeSkips.HOUR + self.skip |= DateTimeSkips.MINUTE + self.skip |= DateTimeSkips.SECOND + self.skip |= DateTimeSkips.MILLISECOND + + def _remove(self, format_): + format_ = GXDateTime._remove_(format_, "%H", True) + format_ = GXDateTime._remove_(format_, "%-H", True) + format_ = GXDateTime._remove_(format_, "%I", True) + format_ = GXDateTime._remove_(format_, "%-I", True) + format_ = GXDateTime._remove_(format_, "%M", True) + format_ = GXDateTime._remove_(format_, "%-M", True) + format_ = GXDateTime._remove_(format_, "%S", True) + format_ = GXDateTime._remove_(format_, "%-S", True) + str_ = datetime.datetime.now().strftime('%p') + format_ = GXDateTime._remove_(format_, str_, True) + return format_ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDateTime.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDateTime.py new file mode 100644 index 0000000..e7baf17 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXDateTime.py @@ -0,0 +1,583 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import datetime +import calendar +import time +from .enums import DateTimeSkips, ClockStatus, DateTimeExtraInfo +from .GXTimeZone import GXTimeZone + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class GXDateTime: + def __init__(self, value=None, pattern=None): + """ + Constructor. + + value: Date-time value. + pattern: Date-time pattern that is used when value is a string. + """ + self.extra = DateTimeExtraInfo.NONE + self.skip = DateTimeSkips.NONE + self.status = ClockStatus.OK + self.dayOfWeek = 0 + if isinstance(value, datetime.datetime): + if value.tzinfo is None: + if time.localtime().tm_isdst: + #If DST is used. + tz = GXTimeZone(int(-time.altzone / 60)) + else: + tz = GXTimeZone(int(-time.timezone / 60)) + self.value = datetime.datetime(value.year, value.month, value.day, value.hour, value.minute, value.second, 0, tzinfo=tz) + else: + self.value = value + elif isinstance(value, str): + self.value = self.fromString(value, pattern) + elif isinstance(value, GXDateTime): + self.value = value.value + self.skip = value.skip + self.extra = value.extra + elif not value: + self.value = None + else: + raise ValueError("Invalid datetime value.") + + @classmethod + def __isNumeric(cls, value): + return '0' <= value <= '9' + + @classmethod + def __get_pattern(cls, loading): + tm = datetime.datetime(1901, 2, 3, 13, 14, 15) + pm = tm.strftime('%p') + date = tm.strftime('%x') + for s in date: + if s < '0' or s > '9': + sep = s + break + dp = "" + tp = "" + appendPM = "" + date = date.replace(" ", sep).replace(" ", "") + for d in date.split(sep): + if not cls.__isNumeric(d): + if d == '' and sep != '.': + d = '.' + appendPM += d + continue + dp += sep + tmp = int(d) + if tmp in (1901, 1): + if loading: + dp += "%Y" + elif len(d) == 2: + dp += "%y" + elif len(d) == 1: + dp += "%-y" + else: + dp += "%Y" + elif tmp == 2: + if len(d) == 2 or loading: + dp += "%m" + else: + dp += "%-m" + elif tmp == 3: + if len(d) == 2 or loading: + dp += "%d" + else: + dp += "%-d" + + date = tm.strftime('%X').replace(pm, "").replace(" ", "") + for s in date: + if s < '0' or s > '9': + sep = s + break + for t in date.split(sep): + tp += sep + tmp = int(t) + if tmp in (13, 1): + if len(t) == 2 or loading: + tp += "%H" + else: + tp += "%-H" + elif tmp == 14: + if len(t) == 2 or loading: + tp += "%M" + else: + tp += "%-M" + elif tmp == 15: + if len(t) == 2 or loading: + tp += "%S" + else: + tp += "%-S" + if not pm or tm.strftime('%c').find(pm) == -1: + return dp[1:] + appendPM + " " + tp[1:] + tp = tp.replace("H", "I") + if not appendPM: + return dp[1:] + " " + tp[1:] + " " + pm + return dp[1:] + " " + appendPM + " " + tp[1:] + + #Check is time zone included and return index of time zone. + @classmethod + def __timeZonePosition(cls, value): + if len(value) > 5: + pos = len(value) - 6 + sep = value[pos] + if sep in('-', '+'): + return pos + if value[len(value) - 1] == 'Z': + return len(value) - 1 + return -1 + + # + # Constructor + # + # @param value + # Date time value as a string. + #pylint: disable=too-many-nested-blocks + def fromString(self, value, pattern=None): + if self.skip is None: + self.skip = DateTimeSkips.NONE + if self.status is None: + self.status = ClockStatus.OK + if self.extra is None: + self.extra = DateTimeExtraInfo.NONE + if value: + if not pattern: + pattern = self.__get_pattern(True) + pattern = self._remove(pattern) + if value.find('BEGIN') != -1: + self.extra |= DateTimeExtraInfo.DST_BEGIN + value = value.replace("BEGIN", "01") + if value.find("END") != -1: + self.extra |= DateTimeExtraInfo.DST_END + value = value.replace("END", "01") + if value.find("LASTDAY2") != -1: + self.extra |= DateTimeExtraInfo.LAST_DAY2 + value = value.replace("LASTDAY2", "01") + if value.find("LASTDAY") != -1: + self.extra |= DateTimeExtraInfo.LAST_DAY + value = value.replace("LASTDAY", "01") + v = value + #Time zone is not added to time or date objects. + addTimeZone = type(self).__name__ == GXDateTime.__name__ + if value.find('*') != -1: + lastFormatIndex = -1 + pos = 0 + while pos < len(value): + c = value[pos] + if not self.__isNumeric(c): + if c == '*': + end = lastFormatIndex + 1 + c = pattern[end] + while end + 1 < len(pattern) and pattern[end] == c: + end += 1 + if pattern[end] == 'Y': + v = str(v[0:pos]) + "Y" + str(v[pos + 1:]) + elif pattern[end] == '%-Y': + v = str(v[0:pos]) + "Y" + str(v[pos + 1:]) + elif pattern[end] == '%Y': + v = str(v[0:pos]) + "Y" + str(v[pos + 1:]) + else: + v = str(v[0:pos]) + "1" + str(v[pos + 1:]) + tmp = pattern[lastFormatIndex + 1: end + 1].strip() + if tmp in ("%y", "%-y", "%Y", "%-Y"): + addTimeZone = False + self.skip |= DateTimeSkips.YEAR + elif tmp in ("%m", "%-m"): + addTimeZone = False + self.skip |= DateTimeSkips.MONTH + elif tmp in ("%d", "%-d"): + addTimeZone = False + self.skip |= DateTimeSkips.DAY + elif tmp in ("%H", "%-H"): + addTimeZone = False + self.skip |= DateTimeSkips.HOUR + pos2 = pattern.find("%p") + if pos2 != -1: + pattern.replace(pos2, pos2 + 1, "") + elif tmp in ("%M", "%-M"): + addTimeZone = False + self.skip |= DateTimeSkips.MINUTE + elif tmp in ("%S", "%-S"): + self.skip |= DateTimeSkips.SECOND + elif tmp and not tmp == "G": + raise ValueError("Invalid date time format.") + else: + lastFormatIndex = pattern.find(str(c), lastFormatIndex + 1) + pos += 1 + v = v.replace("Y", "2000") + self.skip |= DateTimeSkips.DAY_OF_WEEK | DateTimeSkips.MILLISECOND + #If time zone is used. + if addTimeZone: + pos = self.__timeZonePosition(v) + tz = None + if pos != -1: + if v[pos] != 'Z': + tz = 60 * int(v[pos + 1:pos + 3]) + int(v[pos + 4:]) + else: + tz = 0 + v = v[0:pos] + tmp = datetime.datetime.strptime(v, pattern) + if tz is None: + tmp = datetime.datetime(tmp.year, tmp.month, tmp.day, tmp.hour, tmp.minute, tmp.second, 0) + else: + tmp = datetime.datetime(tmp.year, tmp.month, tmp.day, tmp.hour, tmp.minute, tmp.second, 0, tzinfo=GXTimeZone(tz)) + return tmp + return datetime.datetime.strptime(v.strip(), pattern) + return None + + #pylint: disable=no-self-use + def _remove(self, format_): + #Do nothing. + return format_ + + def toFormatString(self, pattern=None): + return self.__toFormatString(True, pattern) + + def toFormatMeterString(self, pattern=None): + return self.__toFormatString(False, pattern) + + @classmethod + def __toLocal(cls, value): + #Convert current time to local. + if value.tzinfo is None: + #If meter is not use time zone. + return value + timestamp = calendar.timegm(value.utctimetuple()) + local_dt = datetime.datetime.fromtimestamp(timestamp) + assert value.resolution >= datetime.timedelta(microseconds=1) + return local_dt.replace(microsecond=value.microsecond) + + def __getTimeZone(self): + if self.value.tzinfo: + return self.value.tzname() + return "" +# if self.value.utcoffset() is None: +# str_ = "" +# else: +# +# offset = int(self.value.utcoffset().seconds / 60) +# if offset == 0: +# str_ = "Z" +# else: +# if offset > 0: +# str_ = "+" +# else: +# str_ = "-" +# str_ += str(int(offset / 60)).zfill(2) +# str_ += ":" +# str_ += str(offset % 60).zfill(2)# +# return str_ + + def __toFormatString(self, useLocalTime, pattern=None): + if not self.value: + return "" + + if self.skip != DateTimeSkips.NONE: + # Separate date and time parts. + if not pattern: + pattern = self.__get_pattern(True) + pattern = self._remove(pattern) + + if self.extra & DateTimeExtraInfo.DST_BEGIN != 0: + pattern = self._replace(pattern, "%m", "BEGIN") + pattern = self._replace(pattern, "%-m", "BEGIN") + elif self.extra & DateTimeExtraInfo.DST_END != 0: + pattern = self._replace(pattern, "%m", "END") + pattern = self._replace(pattern, "%-m", "END") + elif self.extra & DateTimeExtraInfo.LAST_DAY != 0: + pattern = self._replace(pattern, "%d", "LASTDAY") + pattern = self._replace(pattern, "%-d", "LASTDAY") + elif self.extra & DateTimeExtraInfo.LAST_DAY2 != 0: + pattern = self._replace(pattern, "%d", "LASTDAY2") + pattern = self._replace(pattern, "%-d", "LASTDAY2") + + if self.skip & DateTimeSkips.YEAR != DateTimeSkips.NONE: + pattern = self._replace(pattern, "%y", "*") + pattern = self._replace(pattern, "%-y", "*") + pattern = self._replace(pattern, "%-Y", "*") + pattern = self._replace(pattern, "%Y", "*") + if self.skip & DateTimeSkips.MONTH != DateTimeSkips.NONE: + pattern = self._replace(pattern, "%m", "*") + pattern = self._replace(pattern, "%-m", "*") + if self.skip & DateTimeSkips.DAY != DateTimeSkips.NONE: + pattern = self._replace(pattern, "%d", "*") + pattern = self._replace(pattern, "%-d", "*") + if self.skip & DateTimeSkips.HOUR != DateTimeSkips.NONE: + pattern = self._replace(pattern, "%H", "*") + pattern = self._replace(pattern, "%-H", "*") + pattern = self._replace(pattern, "%I", "*") + pattern = self._replace(pattern, "%-I", "*") + pattern = self._remove_(pattern, "p", False) + if self.skip & DateTimeSkips.MILLISECOND != DateTimeSkips.NONE: + pattern = self._replace(pattern, "%f", "*") + else: + index = pattern.find("%S") + if index != -1: + sep = pattern[index - 1] + pattern.replace("%S", "%S" + sep + "%f") + else: + index = pattern.find("%-S") + if index != -1: + sep = pattern[index - 1] + pattern.replace("%-S", "%-S" + sep + "%f") + if self.skip & DateTimeSkips.SECOND != DateTimeSkips.NONE: + pattern = self._replace(pattern, "%S", "*") + pattern = self._replace(pattern, "%-S", "*") + else: + index = pattern.find("%M") + if index != -1: + sep = pattern[index - 1] + pattern.replace("%M", "%M" + sep + "%S") + if self.skip & DateTimeSkips.MINUTE != DateTimeSkips.NONE: + pattern = self._replace(pattern, "%M", "*") + pattern = self._replace(pattern, "%-M", "*") + + if useLocalTime: + return self.__toLocal(self.value).strftime(pattern) + return self.value.strftime(pattern) + self.__getTimeZone() + if useLocalTime: + return self.__toLocal(self.value).strftime("%x %X") + return self.value.strftime("%x %X") + self.__getTimeZone() + + @classmethod + def _remove_(cls, value, tag, removeSeparator): + pos = value.find(tag) + if pos != -1: + len_ = pos + len(tag) + if pos != 0 and removeSeparator: + pos -= 1 + value = value[0:pos] + value[len_:] + return value + + @classmethod + def _replace(cls, value, tag, replacement): + pos = value.find(tag) + if pos != -1: + value = value.replace(tag, replacement) + return value + + def __str__(self): + #Returns date-time of the meter using local time zone. + return self.__toString(True) + + def toMeterString(self, pattern=None): + #Returns date-time of the meter using meter time zone. + return self.__toString(False, pattern) + + def __toString(self, useLocalTime, pattern=None): + if not self.value: + return "" + if self.skip != DateTimeSkips.NONE: + # Separate date and time parts. + if not pattern: + pattern = self.__get_pattern(False) + pattern = self._remove(pattern) + if self.skip & DateTimeSkips.YEAR != DateTimeSkips.NONE: + pattern = self._remove_(pattern, "%Y", True) + pattern = self._remove_(pattern, "%y", True) + pattern = self._remove_(pattern, "%-y", True) + pattern = self._remove_(pattern, "%-Y", True) + if self.skip & DateTimeSkips.MONTH != DateTimeSkips.NONE: + pattern = self._remove_(pattern, "%m", True) + pattern = self._remove_(pattern, "%-m", True) + if self.skip & DateTimeSkips.DAY != DateTimeSkips.NONE: + pattern = self._remove_(pattern, "%d", True) + pattern = self._remove_(pattern, "%-d", True) + if self.skip & DateTimeSkips.HOUR != DateTimeSkips.NONE: + pattern = self._remove_(pattern, "%H", True) + pattern = self._remove_(pattern, "%-H", True) + pattern = self._remove_(pattern, "%p", True) + if self.skip & DateTimeSkips.MILLISECOND != DateTimeSkips.NONE: + pattern = self._remove_(pattern, "%f", True) + else: + index = pattern.find("%S") + if index != -1: + sep = pattern[index - 1] + pattern = pattern.replace("%S", "%S" + sep + "%f") + else: + index = pattern.find("%-S") + if index != -1: + sep = pattern[index - 1] + pattern = pattern.replace("%-S", "%-S" + sep + "%f") + if self.skip & DateTimeSkips.SECOND != DateTimeSkips.NONE: + pattern = self._remove_(pattern, "%S", True) + pattern = self._remove_(pattern, "%-S", True) + else: + index = pattern.find("%M") + if index == -1: + index = pattern.find("%-M") + if index != -1: + sep = pattern[index - 1] + pattern.replace("%-M", "%-M" + sep + "-S") + else: + sep = pattern[index - 1] + pattern.replace("%M", "%M" + sep + "S") + if self.skip & DateTimeSkips.MINUTE != DateTimeSkips.NONE: + pattern = self._remove_(pattern, "%M", True) + pattern = self._remove_(pattern, "%-M", True) + if useLocalTime: + return self.__toLocal(self.value).strftime(pattern.strip()) + return self.value.strftime(pattern.strip()) + self.__getTimeZone() + if useLocalTime: + return self.__toLocal(self.value).strftime("%x %X") + return self.value.strftime("%x %X") + self.__getTimeZone() + + # + # Get difference between given time and run time in ms. + # + # @param start + # Start date time. + # @param to + # Compared time. + # Difference in milliseconds. + # + @classmethod + def getDifference(cls, start, to): + diff = 0 + cal = to.getLocalCalendar() + # Compare seconds. + if not to.skip & DateTimeSkips.SECOND != DateTimeSkips.NONE: + if start.second < cal.second: + diff += (cal.second - start.second) * 1000 + else: + diff -= (start.second - cal.second) * 1000 + elif diff < 0: + diff = 60000 + diff + # Compare minutes. + if not to.skip & DateTimeSkips.MINUTE != DateTimeSkips.NONE: + if start.minute < cal.minute: + diff += (cal.minute - start.minute) * 60000 + else: + diff -= (start.minute - cal.minute) * 60000 + elif diff < 0: + diff = 60 * 60000 + diff + # Compare hours. + if not to.skip & DateTimeSkips.HOUR != DateTimeSkips.NONE: + if start.hour < cal.hour: + diff += (cal.hour - start.hour) * 60 * 60000 + else: + diff -= (start.hour - cal.hour) * 60 * 60000 + elif diff < 0: + diff = 60 * 60000 + diff + # Compare days. + if not to.skip & DateTimeSkips.DAY != DateTimeSkips.NONE: + if start.day < cal.day: + diff += (cal.month - start.month) * 24 * 60 * 60000 + elif start.month != cal.month: + if not to.skip & DateTimeSkips.DAY != DateTimeSkips.NONE: + diff += (cal.month - start.month) * 24 * 60 * 60000 + else: + diff = ((cls.daysInMonth(start.year, start.month) - start.day + cal.day) * 24 * 60 * 60000) + diff + elif diff < 0: + diff = 24 * 60 * 60000 + diff + # Compare months. + if not to.skip & DateTimeSkips.MONTH != DateTimeSkips.NONE: + if start.month < cal.month: + m = start.day + while m != cal.day: + diff += cls.daysInMonth(start.year, m) * 24 * 60 * 60000 + m += 1 + else: + m = cal.day + while m != start.day: + diff -= cls.daysInMonth(start.year, m) * 24 * 60 * 60000 + m += 1 + elif diff < 0: + diff = cls.daysInMonth(start.year, start.month) * 24 * 60 * 60000 + diff + return diff + + # + # Get the number of days in that month. + # + # year + # Year. + # month + # Month. + # Number of days in month. + # + @classmethod + def daysInMonth(cls, year, month): + return calendar.monthrange(year, month) + + # + # Get date time from Epoch time. + # + # @param unixTime + # Unix time. + # Date and time. + # + @classmethod + def fromUnixTime(cls, unixTime): + return GXDateTime(datetime.datetime(unixTime * 1000)) + + # + # Convert date time to Epoch time. + # + # @param value + # Date and time. + # Returns unix time. + # + @classmethod + def toUnixTime(cls, value): + if isinstance(value, (datetime.datetime)): + return value.utctimetuple() + if isinstance(value, (GXDateTime)): + return value.value.utctimetuple() + return int(value.value / 1000) + + # + # Get date time from high resolution clock time. + # + # highResolution: High resolution clock time is milliseconds since 1970-01-01 00:00:00. + # Returns Date and time. + # + @classmethod + def fromHighResolutionTime(cls, unixTime): + return GXDateTime(datetime.datetime(unixTime)) + + # + # Convert date time to high resolution time. + # + # @param value + # Date and time. + # Returns high resolution time. + # + @classmethod + def toHighResolutionTime(cls, value): + if isinstance(value, datetime.datetime): + return value.utctimetuple() * 1000.0 + if isinstance(value, GXDateTime): + return value.value.utctimetuple() * 1000.0 + return int(value.value) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXEnum.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXEnum.py new file mode 100644 index 0000000..c5c9ce6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXEnum.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXEnum(int): + """Enum class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXFloat32.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXFloat32.py new file mode 100644 index 0000000..b0c1170 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXFloat32.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXFloat32(float): + """Float32 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXFloat64.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXFloat64.py new file mode 100644 index 0000000..d360f4b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXFloat64.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXFloat64(float): + """GXFloat64 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXHdlcSettings.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXHdlcSettings.py new file mode 100644 index 0000000..e1378f7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXHdlcSettings.py @@ -0,0 +1,59 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# +# GXHdlcSettings contains commands for retrieving and setting the limits of +# field ength and window size, when communicating with the server. +# +class GXHdlcSettings: + DEFAULT_MAX_INFO_TX = 128 + DEFAULT_MAX_INFO_RX = 128 + DEFAULT_WINDOWS_SIZE_TX = 1 + DEFAULT_WINDOWS_SIZE_RX = 1 + + # + # Constructor. + # + def __init__(self): + self.maxInfoTX = self.DEFAULT_MAX_INFO_TX + self.maxInfoRX = self.DEFAULT_MAX_INFO_RX + self.windowSizeTX = self.DEFAULT_WINDOWS_SIZE_TX + self.windowSizeRX = self.DEFAULT_WINDOWS_SIZE_RX + + # Update HDLC settings from HdlcSetup COSEM object. + def _update(self, hdlc): + if hdlc: + self.maxInfoRX = hdlc.maximumInfoLengthReceive + self.maxInfoTX = hdlc.maximumInfoLengthTransmit + self.windowSizeRX = hdlc.windowSizeReceive + self.windowSizeTX = hdlc.windowSizeTransmit diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXICipher.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXICipher.py new file mode 100644 index 0000000..52bb9ae --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXICipher.py @@ -0,0 +1,204 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from abc import ABCMeta, abstractmethod + +ABC = ABCMeta('ABC', (object,), {'__slots__': ()}) + +# pylint: disable=too-many-public-methods +class GXICipher(ABC): + # + # Reset encrypt settings. + # + @abstractmethod + def reset(self): + raise ValueError("abstract method is called.") + + # Is ciphering used. + @abstractmethod + def isCiphered(self): + raise ValueError("abstract method is called.") + + # + # Used security. + # + @abstractmethod + def getSecurity(self): + raise ValueError("abstract method is called.") + + # + # @param value + # Used security. + # + @abstractmethod + def setSecurity(self, value): + raise ValueError("abstract method is called.") + + # + # System title. + # + @abstractmethod + def getSystemTitle(self): + raise ValueError("abstract method is called.") + + # + # Recipient system Title. + # + @abstractmethod + def getRecipientSystemTitle(self): + raise ValueError("abstract method is called.") + + # + # Block cipher key. + # + @abstractmethod + def getBlockCipherKey(self): + raise ValueError("abstract method is called.") + + # + # Authentication key. + # + @abstractmethod + def getAuthenticationKey(self): + raise ValueError("abstract method is called.") + + # + # @param value + # Authentication key. + # + @abstractmethod + def setAuthenticationKey(self, value): + raise ValueError("abstract method is called.") + + # + # Invocation counter. + # + @abstractmethod + def getInvocationCounter(self): + raise ValueError("abstract method is called.") + + # + # Used security suite. + # + @abstractmethod + def getSecuritySuite(self): + raise ValueError("abstract method is called.") + + # + # Ephemeral key pair. + # + @abstractmethod + def getEphemeralKeyPair(self): + raise ValueError("abstract method is called.") + + # + # @param value + # Ephemeral key pair. + # + @abstractmethod + def setEphemeralKeyPair(self, value): + raise ValueError("abstract method is called.") + + # + # Client's key agreement key pair. + # + @abstractmethod + def getKeyAgreementKeyPair(self): + raise ValueError("abstract method is called.") + + # + # @param value + # Client's key agreement key pair. + # + @abstractmethod + def setKeyAgreementKeyPair(self, value): + raise ValueError("abstract method is called.") + + # + # Target (Server or client) Public key. + # + @abstractmethod + def getPublicKeys(self): + raise ValueError("abstract method is called.") + + # + # Available certificates. + # + @abstractmethod + def getCertificates(self): + raise ValueError("abstract method is called.") + + # + # Signing key pair. + # + @abstractmethod + def getSigningKeyPair(self): + raise ValueError("abstract method is called.") + + # + # @param value + # Signing key pair. + # + @abstractmethod + def setSigningKeyPair(self, value): + raise ValueError("abstract method is called.") + + # + # Shared secret is generated when connection is made. + # + @abstractmethod + def getSharedSecret(self): + raise ValueError("abstract method is called.") + + # + # @param value + # Shared secret is generated when connection is made. + # + @abstractmethod + def setSharedSecret(self, value): + raise ValueError("abstract method is called.") + + # + # Dedicated key. + # + @abstractmethod + def getDedicatedKey(self): + raise ValueError("abstract method is called.") + + # + # @param value + # Dedicated key. + # + @abstractmethod + def setDedicatedKey(self, value): + raise ValueError("abstract method is called.") diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt16.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt16.py new file mode 100644 index 0000000..35f1056 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt16.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXInt16(int): + """Int16 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt32.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt32.py new file mode 100644 index 0000000..d086158 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt32.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXInt32(int): + """Int32 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt64.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt64.py new file mode 100644 index 0000000..67d9cef --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt64.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXInt64(int): + """Int64 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt8.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt8.py new file mode 100644 index 0000000..37c833c --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXInt8.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXInt8(int): + """Int8 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXIntEnum.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXIntEnum.py new file mode 100644 index 0000000..0e36fb4 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXIntEnum.py @@ -0,0 +1,41 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +#pylint: disable=broad-except +try: + from enum import IntEnum + class GXIntEnum(IntEnum): + """Enum class.""" +except Exception: + class GXIntEnum(int): + """Enum class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXIntFlag.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXIntFlag.py new file mode 100644 index 0000000..7868322 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXIntFlag.py @@ -0,0 +1,41 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +#pylint: disable=broad-except +try: + from enum import IntFlag + class GXIntFlag(IntFlag): + """Flag enum class.""" +except Exception: + class GXIntFlag(int): + """Flag enum class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXReplyData.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXReplyData.py new file mode 100644 index 0000000..8f74a75 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXReplyData.py @@ -0,0 +1,206 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXByteBuffer import GXByteBuffer +from .enums import DataType, RequestTypes, Command +from .GXDLMSException import GXDLMSException + +# pylint: disable=bad-option-value,old-style-class,too-few-public-methods,too-many-instance-attributes +class GXReplyData: + # + # Constructor. + # + # @param more + # Is more data available. + # @param cmd + # Received command. + # @param buff + # Received data. + # @param forComplete + # Is frame complete. + # @param err + # Received error ID. + # pylint: disable=too-many-arguments + def __init__(self, more=RequestTypes.NONE, cmd=Command.NONE, buff=None, forComplete=False, err=0): + # Is more data available. + self.moreData = more + # Received command. + self.command = cmd + # Received data. + self.data = buff + if not self.data: + self.data = GXByteBuffer() + # Is frame complete. + self.complete = forComplete + # Received error. + self.error = err + self.value = None + # Is received frame echo. + self.echo = False + # Received command type. + self.commandType = 0 + # HDLC frame ID. + self.frameId = 0 + # Read value. + self.dataValue = None + # Expected count of element in the array. + self.totalCount = 0 + # Last read position. This is used in peek to solve how far data + # is read. + self.readPosition = 0 + # Packet length. + self.packetLength = 0 + # Try get value. + self.peek = False + # Cipher index is position where data is decrypted. + self.cipherIndex = 0 + # Data notification date time. + self.time = None + # XML settings. + self.xml = None + # Invoke ID. + self.invokeId = 0 + # GBT block number. + self.blockNumber = 0 + # GBT block number ACK. + self.blockNumberAck = 0 + # Is GBT streaming in use. + self.streaming = False + # GBT Window size. This is for internal use. + self.gbtWindowSize = 0 + # Client address of the notification message. Notification + # message sets + # this. + self.targetAddress = 0 + # Server address of the notification message. Notification + # message sets + # this. + self.sourceAddress = 0 + # Gateway information. + self.gateway = None + # Data type. + self.valueType = DataType.NONE + self.cipheredCommand = Command.NONE + + def clear(self): + """" + Reset data values to default. + """ + self.moreData = RequestTypes.NONE + self.command = Command.NONE + self.commandType = 0 + self.data.capacity = 0 + self.complete = False + self.error = 0 + self.totalCount = 0 + self.dataValue = None + self.readPosition = 0 + self.packetLength = 0 + self.valueType = DataType.NONE + self.cipherIndex = 0 + self.cipheredCommand = 0 + self.time = None + if self.xml: + self.xml.xml = "" + self.invokeId = 0 + self.value = None + + def isMoreData(self): + """ + Is more data available. + """ + return self.moreData != RequestTypes.NONE and self.error == 0 + + # + # Is notify message. + # + def isNotify(self): + return self.command == Command.EVENT_NOTIFICATION or self.command == Command.DATA_NOTIFICATION or self.command == Command.INFORMATION_REPORT + + # + # Is frame complete. + # + # Returns true if frame is complete or false if bytes is + # missing. + # + def isComplete(self): + return self.complete + + # + # Get Received error. Value is zero if no error has occurred. + # + # Received error. + # + def getError(self): + return self.error + + def getErrorMessage(self): + return GXDLMSException.getDescription(self.error) + + # + # Get total count of element in the array. If this method is used + # peek must + # be set true. + # + # Count of element in the array. + # @see #setPeek + # @see #getCount + # + def getTotalCount(self): + return self.totalCount + + # + # Get count of read elements. If this method is used peek must be set + # true. + # + # Count of read elements. + # @see #setPeek + # @see #getTotalCount + # + def getCount(self): + if isinstance(self.dataValue, list): + return len(self.dataValue) + return 0 + + # + # Is GBT streaming. + # + def isStreaming(self): + return (self.moreData & RequestTypes.FRAME) == 0 and self.streaming and (self.blockNumberAck * self.gbtWindowSize) + 1 > self.blockNumber + + def __str__(self): + if self.xml: + return str(self.xml) + if self.data is None: + return "" + return str(self.data) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXSNInfo.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXSNInfo.py new file mode 100644 index 0000000..9ad0e54 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXSNInfo.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXSNInfo: + #Server uses this class to find Short Name object and attribute index. + #This class is reserved for internal use. + + # Constructor. + def __init__(self): + # Is attribute index or action index + self.action = False + # Attribute index. + self.index = 0 + # COSEM object. + self.item = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXSecure.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXSecure.py new file mode 100644 index 0000000..ae798d9 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXSecure.py @@ -0,0 +1,140 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import random +import hashlib +from .enums import Authentication, Security +from .GXDLMSChipperingStream import GXDLMSChipperingStream +from .GXByteBuffer import GXByteBuffer +from .CountType import CountType +from .GXDLMSChippering import GXDLMSChippering +# +class GXSecure: + # + # * Chipher text. + # * + # * @param settings + # * DLMS settings. + # * @param cipher + # * Chipher settings. + # * @param ic + # * IC + # * @param data + # * Text to chipher. + # * @param secret + # * Secret. + # * @return Chiphered text. + #pylint: disable=too-many-arguments + @classmethod + def secure(cls, settings, cipher, ic, data, secret): + #pylint: disable=import-outside-toplevel + from .AesGcmParameter import AesGcmParameter + if isinstance(secret, str): + secret = secret.encode() + d = [] + s = [] + if settings.authentication == Authentication.HIGH: + len_ = len(data) + if (len_ % 16) != 0: + len_ += (16 - (len(data) % 16)) + + if len(secret) > len(data): + len_ = len(secret) + if len_ % 16 != 0: + len_ += 16 - (len(secret) % 16) + s = bytearray(len_) + d = bytearray(len_) + s[0:len(secret)] = secret[0:] + d[0:len(data)] = data[0:] + pos = 0 + while pos < len(d) / 16: + GXDLMSChipperingStream.aes1Encrypt(d, pos * 16, s) + pos += 1 + return d + # Get server Challenge. + challenge = GXByteBuffer() + # Get shared secret + if settings.authentication == Authentication.HIGH_GMAC: + challenge.set(data) + elif settings.authentication == Authentication.HIGH_SHA256: + challenge.set(secret) + else: + challenge.set(data) + challenge.set(secret) + d = challenge.array() + if settings.authentication == Authentication.HIGH_MD5: + md = hashlib.md5() + md.update(d) + d = md.digest() + elif settings.authentication == Authentication.HIGH_SHA1: + md = hashlib.sha1() + md.update(d) + d = md.digest() + elif settings.authentication == Authentication.HIGH_SHA256: + md = hashlib.sha256() + md.update(d) + d = md.digest() + elif settings.authentication == Authentication.HIGH_GMAC: + # SC is always Security.Authentication. + p = AesGcmParameter(0, secret, cipher.blockCipherKey, cipher.authenticationKey) + p.security = Security.AUTHENTICATION + p.invocationCounter = ic + p.type_ = CountType.TAG + challenge.clear() + challenge.setUInt8(Security.AUTHENTICATION) + challenge.setUInt32(p.invocationCounter) + challenge.set(GXDLMSChippering.encryptAesGcm(p, d)) + d = challenge.array() + elif settings.authentication == Authentication.HIGH_ECDSA: + raise Exception("ECDSA is not supported.") + return d + + # + # * Generates challenge. + # * + # * @param authentication + # * Used authentication. + # * @return Generated challenge. + # + @classmethod + def generateChallenge(cls): + # Random challenge is 8 to 64 bytes. + # Texas Instruments accepts only 16 byte long challenge. + # For this reason challenge size is 16 bytes at the moment. + len_ = 16 + result = [None] * len_ + pos = 0 + while pos != len_: + result[pos] = random.randint(0, 255) + pos += 1 + return result diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXServerReply.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXServerReply.py new file mode 100644 index 0000000..0ee097f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXServerReply.py @@ -0,0 +1,56 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXServerReply: + # + # Constructor. + # + # @param value + # Received data. + # + def __init__(self, value): + # Connection info. + self.connectionInfo = None + # Server reply message. + self.reply = [] + # Message count to send. + self.count = 0 + # Server received data. + self.data = value + + # + # Is GBT streaming in progress. + # + def isStreaming(self): + return self.count != 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXStandardObisCode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXStandardObisCode.py new file mode 100644 index 0000000..7b9fd7f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXStandardObisCode.py @@ -0,0 +1,67 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# +# * OBIS Code class is used to find out default description to OBIS Code. +# +class GXStandardObisCode: + # + # Constructor. + # + def __init__(self, forObis, desc=None, forInterfaces=None, forDataType=""): + # OBIS code. + if forObis is None: + self.obis = [0, 0, 0, 0, 0, 0] + else: + self.obis = forObis + # OBIS code description. + self.description = desc + # Interfaces that are using this OBIS code. + self.interfaces = forInterfaces + # Standard data types. + self.dataType = forDataType + #Standard UI data types. + self.uiDataType = None + + # + # Convert to string. + # + # @return + # + def __str__(self): + str_ = "" + for s in self.obis: + if str_: + str_ += '.' + str_ += s + return str_ + " " + self.description diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXStandardObisCodeCollection.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXStandardObisCodeCollection.py new file mode 100644 index 0000000..680f425 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXStandardObisCodeCollection.py @@ -0,0 +1,435 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXStandardObisCode import GXStandardObisCode +from .GXByteBuffer import GXByteBuffer + +class GXStandardObisCodeCollection(list): + """ + Standard OBIS code collection is used to save all default OBIS Codes. + """ + + @classmethod + def getBytes(cls, ln): + if not ln: + return None + tmp = ln.split('.') + if len(tmp) != 6: + # If value is give as hex. + tmp = GXByteBuffer.hexToBytes(ln) + if len(tmp) != 6: + raise ValueError("Invalid OBIS Code.") + code_ = bytearray(6) + code_[0] = int(tmp[0]) + code_[1] = int(tmp[1]) + code_[2] = int(tmp[2]) + code_[3] = int(tmp[3]) + code_[4] = int(tmp[4]) + code_[5] = int(tmp[5]) + return code_ + + def find(self, ln, objectType): + return self.find2(self.getBytes(ln), objectType) + + @classmethod + def equalsInterface(cls, it, ic): + """Check is interface included to standard.""" + + # If all interfaces are allowed. + if int(ic) == 0 or it.interfaces == "*": + return True + return str(int(ic)) in it.interfaces.split(',') + + @classmethod + def equalsMask(cls, obis, ic): + """Check OBIS codes.""" + ret = False + number = True + if obis.find(',') != -1: + tmp = obis.split(',') + for it in tmp: + if it.find('-') != -1: + if cls.equalsMask(it, ic): + return True + elif int(it) == ic: + return True + ret = False + elif obis.find('-') != -1: + number = False + tmp = obis.split('-') + ret = int(ic) >= int(tmp[0]) and ic <= int(tmp[1]) + elif number: + if obis == "&": + return ic in (0, 1, 7) + ret = int(obis) == ic + return ret + + @classmethod + def equalsMask2(cls, obisMask, ln): + return cls.equalsObisCode(obisMask.split('.'), cls.getBytes(ln)) + + # + # Check OBIS code. + # + @classmethod + def equalsObisCode(cls, obisMask, ic): + ret = True + if not ic: + ret = True + elif not cls.equalsMask(obisMask[0], ic[0]): + ret = False + elif not cls.equalsMask(obisMask[1], ic[1]): + ret = False + elif not cls.equalsMask(obisMask[2], ic[2]): + ret = False + elif not cls.equalsMask(obisMask[3], ic[3]): + ret = False + elif not cls.equalsMask(obisMask[4], ic[4]): + ret = False + elif not cls.equalsMask(obisMask[5], ic[5]): + ret = False + return ret + + # + # Get N1C description. + # + @classmethod + def getN1CDescription(cls, str_): + if not str_ or str_[0] != '$': + return "" + value = int(str_[1:]) + tmp = "" + if value == 41: + tmp = "Absolute temperature" + elif value == 42: + tmp = "Absolute pressure" + elif value == 44: + tmp = "Velocity of sound" + elif value == 45: + tmp = "Density(of gas)" + elif value == 46: + tmp = "Relative density" + elif value == 47: + tmp = "Gauge pressure" + elif value == 48: + tmp = "Differential pressure" + elif value == 49: + tmp = "Density of air" + return tmp + + @classmethod + def getDescription(cls, str_): + """Get description.""" + if not str_ or str_[0] != '$': + return "" + + value = int(str_[1:]) + if value == 1: + ret = "Sum Li Active power+ (QI+QIV)" + elif value == 2: + ret = "Sum Li Active power- (QII+QIII)" + elif value == 3: + ret = "Sum Li Reactive power+ (QI+QII)" + elif value == 4: + ret = "Sum Li Reactive power- (QIII+QIV)" + elif value == 5: + ret = "Sum Li Reactive power QI" + elif value == 6: + ret = "Sum Li Reactive power QII" + elif value == 7: + ret = "Sum Li Reactive power QIII" + elif value == 8: + ret = "Sum Li Reactive power QIV" + elif value == 9: + ret = "Sum Li Apparent power+ (QI+QIV)" + elif value == 10: + ret = "Sum Li Apparent power- (QII+QIII)" + elif value == 11: + ret = "Current: any phase" + elif value == 12: + ret = "Voltage: any phase" + elif value == 13: + ret = "Sum Li Power factor" + elif value == 14: + ret = "Supply frequency" + elif value == 15: + ret = "Sum Li Active power (abs(QI+QIV)+abs(QII+QIII))" + elif value == 16: + ret = "Sum Li Active power (abs(QI+QIV)-abs(QII+QIII))" + elif value == 17: + ret = "Sum Li Active power QI" + elif value == 18: + ret = "Sum Li Active power QII" + elif value == 19: + ret = "Sum Li Active power QIII" + elif value == 20: + ret = "Sum Li Active power QIV" + elif value == 21: + ret = "L1 Active power+ (QI+QIV)" + elif value == 22: + ret = "L1 Active power- (QII+QIII)" + elif value == 23: + ret = "L1 Reactive power+ (QI+QII)" + elif value == 24: + ret = "L1 Reactive power- (QIII+QIV)" + elif value == 25: + ret = "L1 Reactive power QI" + elif value == 26: + ret = "L1 Reactive power QII" + elif value == 27: + ret = "L1 Reactive power QIII" + elif value == 28: + ret = "L1 Reactive power QIV" + elif value == 29: + ret = "L1 Apparent power+ (QI+QIV)" + elif value == 30: + ret = "L1 Apparent power- (QII+QIII)" + elif value == 31: + ret = "L1 Current" + elif value == 32: + ret = "L1 Voltage" + elif value == 33: + ret = "L1 Power factor" + elif value == 34: + ret = "L1 Supply frequency" + elif value == 35: + ret = "L1 Active power (abs(QI+QIV)+abs(QII+QIII))" + elif value == 36: + ret = "L1 Active power (abs(QI+QIV)-abs(QII+QIII))" + elif value == 37: + ret = "L1 Active power QI" + elif value == 38: + ret = "L1 Active power QII" + elif value == 39: + ret = "L1 Active power QIII" + elif value == 40: + ret = "L1 Active power QIV" + elif value == 41: + ret = "L2 Active power+ (QI+QIV)" + elif value == 42: + ret = "L2 Active power- (QII+QIII)" + elif value == 43: + ret = "L2 Reactive power+ (QI+QII)" + elif value == 44: + ret = "L2 Reactive power- (QIII+QIV)" + elif value == 45: + ret = "L2 Reactive power QI" + elif value == 46: + ret = "L2 Reactive power QII" + elif value == 47: + ret = "L2 Reactive power QIII" + elif value == 48: + ret = "L2 Reactive power QIV" + elif value == 49: + ret = "L2 Apparent power+ (QI+QIV)" + elif value == 50: + ret = "L2 Apparent power- (QII+QIII)" + elif value == 51: + ret = "L2 Current" + elif value == 52: + ret = "L2 Voltage" + elif value == 53: + ret = "L2 Power factor" + elif value == 54: + ret = "L2 Supply frequency" + elif value == 55: + ret = "L2 Active power (abs(QI+QIV)+abs(QII+QIII))" + elif value == 56: + ret = "L2 Active power (abs(QI+QIV)-abs(QI+QIII))" + elif value == 57: + ret = "L2 Active power QI" + elif value == 58: + ret = "L2 Active power QII" + elif value == 59: + ret = "L2 Active power QIII" + elif value == 60: + ret = "L2 Active power QIV" + elif value == 61: + ret = "L3 Active power+ (QI+QIV)" + elif value == 62: + ret = "L3 Active power- (QII+QIII)" + elif value == 63: + ret = "L3 Reactive power+ (QI+QII)" + elif value == 64: + ret = "L3 Reactive power- (QIII+QIV)" + elif value == 65: + ret = "L3 Reactive power QI" + elif value == 66: + ret = "L3 Reactive power QII" + elif value == 67: + ret = "L3 Reactive power QIII" + elif value == 68: + ret = "L3 Reactive power QIV" + elif value == 69: + ret = "L3 Apparent power+ (QI+QIV)" + elif value == 70: + ret = "L3 Apparent power- (QII+QIII)" + elif value == 71: + ret = "L3 Current" + elif value == 72: + ret = "L3 Voltage" + elif value == 73: + ret = "L3 Power factor" + elif value == 74: + ret = "L3 Supply frequency" + elif value == 75: + ret = "L3 Active power (abs(QI+QIV)+abs(QII+QIII))" + elif value == 76: + ret = "L3 Active power (abs(QI+QIV)-abs(QI+QIII))" + elif value == 77: + ret = "L3 Active power QI" + elif value == 78: + ret = "L3 Active power QII" + elif value == 79: + ret = "L3 Active power QIII" + elif value == 80: + ret = "L3 Active power QIV" + elif value == 82: + ret = "Unitless quantities (pulses or pieces)" + elif value == 84: + ret = "Sum Li Power factor-" + elif value == 85: + ret = "L1 Power factor-" + elif value == 86: + ret = "L2 Power factor-" + elif value == 87: + ret = "L3 Power factor-" + elif value == 88: + ret = "Sum Li A2h QI+QII+QIII+QIV" + elif value == 89: + ret = "Sum Li V2h QI+QII+QIII+QIV" + elif value == 90: + ret = "SLi current (algebraic sum of the - unsigned - value of the currents in all phases)" + elif value == 91: + ret = "Lo Current (neutral)" + elif value == 92: + ret = "Lo Voltage (neutral)" + else: + ret = "" + return ret + + # + # Get OBIS value. + # + # @param formula + # OBIS formula. + # @param value + # OBIS value. + # @return OBIS value as integer. + # + @classmethod + def getObisValue(cls, formula, value): + if len(formula) == 1: + return str(value) + return str(value + int(formula[1:])) + + # + # Find Standard OBIS Code description. + # pylint: disable=too-many-nested-blocks + def find2(self, obisCode, ic): + if isinstance(obisCode, str): + obisCode = self.getBytes(obisCode) + tmp = None + list_ = list() + for it in self: + # Interface is tested first because it's faster. + if self.equalsInterface(it, ic) and self.equalsObisCode(it.obis, obisCode): + tmp = GXStandardObisCode(it.obis[0:], it.description, it, it.dataType) + tmp.uiDataType = it.uiDataType + list_.append(tmp) + tmp2 = it.description.split(';') + if len(tmp2) > 1: + desc = "" + if obisCode and tmp2[1].strip() == "$1": + if obisCode[0] == 7: + desc = self.getN1CDescription("$" + str(obisCode[2])) + else: + desc = self.getDescription("$" + str(obisCode[2])) + if desc: + tmp2[1] = desc + tmp.description = "" + for s in tmp2: + if tmp.description: + tmp.description += ";" + tmp.description += s + if obisCode: + obis = tmp.obis + obis[0] = str(obisCode[0]) + obis[1] = str(obisCode[1]) + obis[2] = str(obisCode[2]) + obis[3] = str(obisCode[3]) + obis[4] = str(obisCode[4]) + obis[5] = str(obisCode[5]) + tmp.obis = obis + desc = tmp.description + desc = desc.replace("$A", str(obisCode[0])) + desc = desc.replace("$B", str(obisCode[1])) + desc = desc.replace("$C", str(obisCode[2])) + desc = desc.replace("$D", str(obisCode[3])) + desc = desc.replace("$E", str(obisCode[4])) + desc = desc.replace("$F", str(obisCode[5])) + # Increase value + begin = desc.find("$(") + if begin != -1: + arr = desc[begin + 2:].replace('(', '$').replace(')', '$').split('$') + desc = desc[0:begin] + for v in arr: + if not v: + pass + elif v[0] == 'A': + desc += self.getObisValue(v, obisCode[0]) + elif v[0] == 'B': + desc += self.getObisValue(v, obisCode[1]) + elif v[0] == 'C': + desc += self.getObisValue(v, obisCode[2]) + elif v[0] == 'D': + desc += self.getObisValue(v, obisCode[3]) + elif v[0] == 'E': + desc += self.getObisValue(v, obisCode[4]) + elif v[0] == 'F': + desc += self.getObisValue(v, obisCode[5]) + else: + desc += v + tmp.description = desc.replace(';', ' ').replace(" ", " ").strip() + if not list_: + tmp = GXStandardObisCode(None, "Invalid", str(int(ic)), "") + obis = tmp.obis + obis[0] = str(obisCode[0]) + obis[1] = str(obisCode[1]) + obis[2] = str(obisCode[2]) + obis[3] = str(obisCode[3]) + obis[4] = str(obisCode[4]) + obis[5] = str(obisCode[5]) + tmp.obis = obis + list_.append(tmp) + return list_ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXStructure.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXStructure.py new file mode 100644 index 0000000..7d7fdd5 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXStructure.py @@ -0,0 +1,36 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXStructure(list): + def __init__(self): + list.__init__(self) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXTime.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXTime.py new file mode 100644 index 0000000..fff5395 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXTime.py @@ -0,0 +1,59 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDateTime import GXDateTime +from .enums import DateTimeSkips + +class GXTime(GXDateTime): + def __init__(self, value=None, pattern=None): + """ + Constructor. + + value: Time value. + pattern: Date-time pattern that is used when value is a string. + """ + GXDateTime.__init__(self, value, pattern) + self.skip |= DateTimeSkips.YEAR + self.skip |= DateTimeSkips.MONTH + self.skip |= DateTimeSkips.DAY + self.skip |= DateTimeSkips.DAY_OF_WEEK + + def _remove(self, format_): + format_ = GXDateTime._remove_(format_, "%Y", True) + format_ = GXDateTime._remove_(format_, "%-y", True) + format_ = GXDateTime._remove_(format_, "%y", True) + format_ = GXDateTime._remove_(format_, "%m", True) + format_ = GXDateTime._remove_(format_, "%-m", True) + format_ = GXDateTime._remove_(format_, "%d", True) + format_ = GXDateTime._remove_(format_, "%-d", True) + return format_ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXTimeZone.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXTimeZone.py new file mode 100644 index 0000000..94cf87d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXTimeZone.py @@ -0,0 +1,66 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import datetime + +# Reserved for internal use. +#pylint222: disable=bad-option-value,old-style-class,too-few-public-methods +class GXTimeZone(datetime.tzinfo): + """ + UTC offset from UTC. + + :param offset: + UTC time zone offset in minutes. + """ + def __init__(self, offset): + self._offset = datetime.timedelta(seconds=offset * 60) + if offset == 0: + self._name = "Z" + else: + if offset > 0: + self._name = "+" + else: + self._name = "-" + self._name += str(int(offset / 60)).zfill(2) + self._name += ":" + self._name += str(offset % 60).zfill(2) + + def utcoffset(self, dt): + ###UTC offset in seconds. + return self._offset + + def dst(self, dt): + return datetime.timedelta(0) + + def tzname(self, dt): + return self._name diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt16.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt16.py new file mode 100644 index 0000000..018e29d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt16.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXUInt16(int): + """UInt16 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt32.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt32.py new file mode 100644 index 0000000..9285403 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt32.py @@ -0,0 +1,43 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import sys + +if sys.version_info < (3, 0): + # pylint: disable=undefined-variable + __base = long +else: + __base = int + +class GXUInt32(__base): + """UInt32 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt64.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt64.py new file mode 100644 index 0000000..3c57757 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt64.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXUInt64(int): + """UInt64 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt8.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt8.py new file mode 100644 index 0000000..bc6c755 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXUInt8.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXUInt8(int): + """UInt8 class.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXWriteItem.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXWriteItem.py new file mode 100644 index 0000000..3b1970a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXWriteItem.py @@ -0,0 +1,58 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import DataType + +# pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXWriteItem: + # + # Constructor. + # + # @param object + # Object to write. + # @param attributeIndex + # Attribute index. + # + def __init__(self, object_=None, attributeIndex=0): + self.dataType = DataType.NONE + self.target = object_ + # Attribute index to write. + self.index = attributeIndex + # Written object. + self.target = None + # Data type to write. + self.dataType = None + # Parameter selector. + self.selector = 0 + # Optional parameters. + self.parameters = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXXmlLoadSettings.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXXmlLoadSettings.py new file mode 100644 index 0000000..ebcc9ee --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GXXmlLoadSettings.py @@ -0,0 +1,42 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXXmlLoadSettings: + # + # Constructor. + # + def __init__(self): + # Start date of profile Generic. + self.start = None + # End date of profile Generic. + self.end = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GetCommandType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GetCommandType.py new file mode 100644 index 0000000..7903a36 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/GetCommandType.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXIntEnum import GXIntEnum + +class GetCommandType(GXIntEnum): + """ + Enumerates Get request and response types. + """ + + # Normal Get. + NORMAL = 1 + + # Next data block. + NEXT_DATA_BLOCK = 2 + + # Get request with list. + WITH_LIST = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/HdlcControlFrame.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/HdlcControlFrame.py new file mode 100644 index 0000000..d0bb012 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/HdlcControlFrame.py @@ -0,0 +1,41 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXIntEnum import GXIntEnum + +class HdlcControlFrame(GXIntEnum): + """HDLC control frame types.""" + RECEIVE_READY = 0 + RECEIVE_NOT_READY = 1 + REJECT = 2 + SELECTIVE_REJECT = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/India.txt b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/India.txt new file mode 100644 index 0000000..4044eb2 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/India.txt @@ -0,0 +1,220 @@ +# IC | OBIS | VERSION | DESCRIPTION | UI Type +1;0.0.0.1.0.255;0;Cum. Billing Count +1;0.0.0.1.1.255;0;Available Billing Periods +1;0.0.42.0.0.255;0;COSEM Logical Device Name;10 +1;0.0.43.1.1.255;0;Invocation Counter #1 (PC) +1;0.0.43.1.2.255;0;Invocation Counter #2 (MR) +1;0.0.43.1.3.255;0;Invocation Counter #3 (US) +1;0.0.43.1.4.255;0;Invocation Counter #4 (Push) +1;0.0.43.1.5.255;0;Invocation Counter #5 (FW) +1;0.0.43.1.6.255;0;Invocation Counter #6 (IHD) +1;0.0.94.91.0.255;0;Cumulative Tamper Count +1;0.0.94.91.9.255;0;Meter Type +1;0.0.94.91.11.255;0;Meter Category;10 +1;0.0.94.91.12.255;0;Current Rating;10 +1;0.0.94.91.18.255;0;Event Status Word 1 (ESW-1) +1;0.0.94.91.26.255;0;ESWF +1;0.0.94.96.19.255;0;Metering Mode;10 +1;0.0.94.96.20.255;0;Payment Mode +1;0.0.94.96.21.255;0;Last Token Recharge Amount +1;0.0.94.96.22.255;0;Last Token Recharge Time;25 +1;0.0.94.96.23.255;0;Total Amount At Last Recharge +1;0.0.94.96.24.255;0;Current Balance Amount +1;0.0.94.96.25.255;0;Current Balance Time;25 +1;0.0.96.1.0.255;0;Meter Serial Number;10 +1;0.0.96.1.1.255;0;Manufacturer Name;10 +1;0.0.96.1.2.255;0;Device ID;10 +1;0.0.96.1.4.255;0;Year Of Manufacture +1;0.0.96.2.0.255;0;Cum. Programming Count +1;0.0.96.7.0.255;0;No of Power Failures +1;0.0.96.10.1.255;0;Meter Health Indicator +1;0.0.96.11.0.255;0;Event:Voltage Related +1;0.0.96.11.1.255;0;Event:Current Related +1;0.0.96.11.2.255;0;Event:Power Related +1;0.0.96.11.3.255;0;Event:Transaction Related +1;0.0.96.11.4.255;0;Event:Others +1;0.0.96.11.5.255;0;Event:Non-Roll Over +1;0.0.96.11.6.255;0;Event:Control +1;0.0.96.15.0.255;0;EventLog Seq:Voltage +1;0.0.96.15.1.255;0;EventLog Seq:Current +1;0.0.96.15.2.255;0;EventLog Seq:Power +1;0.0.96.15.3.255;0;EventLog Seq:Transaction +1;0.0.96.15.4.255;0;EventLog Seq:Others +1;0.0.96.15.5.255;0;EventLog Seq:Non-Roll Over +1;0.0.96.15.6.255;0;EventLog Seq:Control +1;0.0.96.15.128.255;0;Tamper Count for Billing Period +1;0.0.97.98.0.255;0;Alarm register object? +1;1.0.0.2.0.255;0;Firmware Version For Meter;10 +1;1.0.0.4.2.255;0;CTR +1;1.0.0.4.3.255;0;PTR +1;1.0.0.8.0.255;0;Demand Integration Period +1;1.0.0.8.4.255;0;Profile Capture Period +1;1.0.96.128.25.255;0;Active Relay Time +1;1.0.96.128.30.255;0;Passive Relay Time +3;0.0.0.1.2.255;0;Billing Date;25 +3;0.0.94.91.8.255;0;Cum. Power Failure Duration +3;0.0.94.91.13.255;0;Billing Power On Duration +3;0.0.94.91.14.255;0;Cum. Power On Duration +3;0.0.96.9.0.255;0;Ambient Temperature +3;1.0.1.7.0.255;0;Active Power-W +3;1.0.1.8.0.255;0;Cum. Energy-Wh(Imp) +3;1.0.1.8.1.255;0;Cum. Energy-Wh(Imp)-TZ1 +3;1.0.1.8.2.255;0;Cum. Energy-Wh(Imp)-TZ2 +3;1.0.1.8.3.255;0;Cum. Energy-Wh(Imp)-TZ3 +3;1.0.1.8.4.255;0;Cum. Energy-Wh(Imp)-TZ4 +3;1.0.1.8.5.255;0;Cum. Energy-Wh(Imp)-TZ5 +3;1.0.1.8.6.255;0;Cum. Energy-Wh(Imp)-TZ6 +3;1.0.1.8.7.255;0;Cum. Energy-Wh(Imp)-TZ7 +3;1.0.1.8.8.255;0;Cum. Energy-Wh(Imp)-TZ8 +3;1.0.1.9.0.255;0;Cum. Energy-Wh Total? +3;1.0.1.29.0.255;0;Block Energy-Wh(Imp) +3;1.0.2.8.0.255;0;Cum. Energy-Wh(Exp) +3;1.0.2.8.1.255;0;Cum. Energy-Wh(Exp)-TZ1 +3;1.0.2.8.2.255;0;Cum. Energy-Wh(Exp)-TZ2 +3;1.0.2.8.3.255;0;Cum. Energy-Wh(Exp)-TZ3 +3;1.0.2.8.4.255;0;Cum. Energy-Wh(Exp)-TZ4 +3;1.0.2.8.5.255;0;Cum. Energy-Wh(Exp)-TZ5 +3;1.0.2.8.6.255;0;Cum. Energy-Wh(Exp)-TZ6 +3;1.0.2.8.7.255;0;Cum. Energy-Wh(Exp)-TZ7 +3;1.0.2.8.8.255;0;Cum. Energy-Wh(Exp)-TZ8 +3;1.0.2.29.0.255;0;Block Energy-Wh(Exp) +3;1.0.3.7.0.255;0;Signed Reactive Power-VAr +3;1.0.5.8.0.255;0;Cum. Energy-VArh, Q1 +3;1.0.5.29.0.255;0;Block Energy-VArh, Q1 +3;1.0.6.8.0.255;0;Cum. Energy-VArh, Q2 +3;1.0.6.29.0.255;0;Block Energy-VArh, Q2 +3;1.0.7.8.0.255;0;Cum. Energy-VArh, Q3 +3;1.0.7.29.0.255;0;Block Energy-VArh, Q3 +3;1.0.8.8.0.255;0;Cum. Energy-VArh, Q4 +3;1.0.8.29.0.255;0;Block Energy-VArh, Q4 +3;1.0.9.7.0.255;0;Apparent Power-VA +3;1.0.9.8.0.255;0;Cum. Energy-VAh(Imp) +3;1.0.9.8.1.255;0;Cum. Energy-VAh(Imp)-TZ1 +3;1.0.9.8.2.255;0;Cum. Energy-VAh(Imp)-TZ2 +3;1.0.9.8.3.255;0;Cum. Energy-VAh(Imp)-TZ3 +3;1.0.9.8.4.255;0;Cum. Energy-VAh(Imp)-TZ4 +3;1.0.9.8.5.255;0;Cum. Energy-VAh(Imp)-TZ5 +3;1.0.9.8.6.255;0;Cum. Energy-VAh(Imp)-TZ6 +3;1.0.9.8.7.255;0;Cum. Energy-VAh(Imp)-TZ7 +3;1.0.9.8.8.255;0;Cum. Energy-VAh(Imp)-TZ8 +3;1.0.9.29.0.255;0;Block Energy-VAh(Imp) +3;1.0.10.8.0.255;0;Cum. Energy-VAh(Exp) +3;1.0.10.8.1.255;0;Cum. Energy-VAh(Exp)-TZ1 +3;1.0.10.8.2.255;0;Cum. Energy-VAh(Exp)-TZ2 +3;1.0.10.8.3.255;0;Cum. Energy-VAh(Exp)-TZ3 +3;1.0.10.8.4.255;0;Cum. Energy-VAh(Exp)-TZ4 +3;1.0.10.8.5.255;0;Cum. Energy-VAh(Exp)-TZ5 +3;1.0.10.8.6.255;0;Cum. Energy-VAh(Exp)-TZ6 +3;1.0.10.8.7.255;0;Cum. Energy-VAh(Exp)-TZ7 +3;1.0.10.8.8.255;0;Cum. Energy-VAh(Exp)-TZ8 +3;1.0.10.29.0.255;0;Block Energy-VAh(Exp) +3;1.0.11.7.0.255;0;Phase Current +3;1.0.11.27.0.255;0;Average Current +3;1.0.12.7.0.255;0;Voltage +3;1.0.12.27.0.255;0;Average Voltage +3;1.0.13.0.0.255;0;Average Power Factor for Billing Period +3;1.0.13.7.0.255;0;Signed Power Factor +3;1.0.14.7.0.255;0;Frequency-Hz +3;1.0.31.7.0.255;0;L1 Current +3;1.0.31.27.0.255;0;L1 Current Avg +3;1.0.32.7.0.255;0;L1 Voltage +3;1.0.32.27.0.255;0;L1 Voltage Avg +3;1.0.33.7.0.255;0;L1 Power Factor +3;1.0.51.7.0.255;0;L2 Current +3;1.0.51.27.0.255;0;L2 Current Avg +3;1.0.52.7.0.255;0;L2 Voltage +3;1.0.52.27.0.255;0;L2 Voltage Avg +3;1.0.53.7.0.255;0;L2 Power Factor +3;1.0.71.7.0.255;0;L3 Current +3;1.0.71.27.0.255;0;L3 Current Avg +3;1.0.72.7.0.255;0;L3 Voltage +3;1.0.72.27.0.255;0;L3 Voltage Avg +3;1.0.73.7.0.255;0;L3 Power factor +3;1.0.81.7.1.255;0;Angle Phase (A, B) volt. +3;1.0.81.7.12.255;0;Angle Phase (B, C) volt. +3;1.0.81.7.2.255;0;Angle Phase (A, C) volt. +3;1.0.91.7.0.255;0;Neutral Current +3;1.0.94.91.14.255;0;Current +4;1.0.1.6.0.255;0;MD-W(Imp) +4;1.0.1.6.1.255;0;MD-W(Imp)-TZ1 +4;1.0.1.6.2.255;0;MD-W(Imp)-TZ2 +4;1.0.1.6.3.255;0;MD-W(Imp)-TZ3 +4;1.0.1.6.4.255;0;MD-W(Imp)-TZ4 +4;1.0.1.6.5.255;0;MD-W(Imp)-TZ5 +4;1.0.1.6.6.255;0;MD-W(Imp)-TZ6 +4;1.0.1.6.7.255;0;MD-W(Imp)-TZ7 +4;1.0.1.6.8.255;0;MD-W(Imp)-TZ8 +4;1.0.2.6.0.255;0;MD-W(Exp) +4;1.0.2.6.1.255;0;MD-W(Exp)-TZ1 +4;1.0.2.6.2.255;0;MD-W(Exp)-TZ2 +4;1.0.2.6.3.255;0;MD-W(Exp)-TZ3 +4;1.0.2.6.4.255;0;MD-W(Exp)-TZ4 +4;1.0.2.6.5.255;0;MD-W(Exp)-TZ5 +4;1.0.2.6.6.255;0;MD-W(Exp)-TZ6 +4;1.0.2.6.7.255;0;MD-W(Exp)-TZ7 +4;1.0.2.6.8.255;0;MD-W(Exp)-TZ8 +4;1.0.9.6.0.255;0;MD-VA(Imp) +4;1.0.9.6.1.255;0;MD-VA(Imp)-TZ1 +4;1.0.9.6.2.255;0;MD-VA(Imp)-TZ2 +4;1.0.9.6.3.255;0;MD-VA(Imp)-TZ3 +4;1.0.9.6.4.255;0;MD-VA(Imp)-TZ4 +4;1.0.9.6.5.255;0;MD-VA(Imp)-TZ5 +4;1.0.9.6.6.255;0;MD-VA(Imp)-TZ6 +4;1.0.9.6.7.255;0;MD-VA(Imp)-TZ7 +4;1.0.9.6.8.255;0;MD-VA(Imp)-TZ8 +4;1.0.10.6.0.255;0;MD-VA(Exp) +4;1.0.10.6.1.255;0;MD-VA(Exp)-TZ1 +4;1.0.10.6.2.255;0;MD-VA(Exp)-TZ2 +4;1.0.10.6.3.255;0;MD-VA(Exp)-TZ3 +4;1.0.10.6.4.255;0;MD-VA(Exp)-TZ4 +4;1.0.10.6.5.255;0;MD-VA(Exp)-TZ5 +4;1.0.10.6.6.255;0;MD-VA(Exp)-TZ6 +4;1.0.10.6.7.255;0;MD-VA(Exp)-TZ7 +4;1.0.10.6.8.255;0;MD-VA(Exp)-TZ8 +4;0.1.96.12.5.255;0;Average Signal Strength +7;0.0.94.91.10.255;1;Nameplate Profile +7;0.0.99.98.0.255;1;Voltage Related Events Profile +7;0.0.99.98.1.255;1;Current Related Events Profile +7;0.0.99.98.2.255;1;Power Related Events Profile +7;0.0.99.98.3.255;1;Transaction Events Profile +7;0.0.99.98.4.255;1;Other Tamper Events Profile +7;0.0.99.98.5.255;1;Non Roll Over Events Profile +7;0.0.99.98.6.255;1;Control Events Profile +7;1.0.94.91.0.255;1;Instantaneous Profile +7;1.0.94.91.3.255;0;Scaler: Instantaneous Profile +7;1.0.94.91.4.255;0;Scaler: Block Load Profile +7;1.0.94.91.5.255;0;Scaler: Daily Load Profile +7;1.0.94.91.6.255;0;Scaler: Billing Profile +7;1.0.94.91.7.255;0;Scaler: Events Profile +7;1.0.94.91.10.255;0;Name Plate Details +7;1.0.98.1.0.255;1;Billing Profile +7;1.0.99.1.0.255;1;BlockLoad Profile +7;1.0.99.2.0.255;1;Daily Load Profile +8;0.0.1.0.0.255;0;Clock +9;0.0.10.0.1.255;0;MD Reset Action +15;0.0.40.0.0.255;0;Current Association +15;0.0.40.0.1.255;1;PC Association +15;0.0.40.0.2.255;1;MR Association (LLS) +15;0.0.40.0.3.255;1;US Association (HLS) +15;0.0.40.0.4.255;1;Push Association (HLS) +15;0.0.40.0.5.255;1;Firmware Upgrade Association (HLS) +15;0.0.40.0.6.255;1;IHD Association (Pre-established, LLS) +18;0.0.44.0.0.255;0;Image Activation Info +20;0.0.13.0.0.255;0;Activity Calendar Active Time +20;0.0.13.0.0.255;0;Activity Calendar For Time Zones +22;0.0.15.0.0.255;0;Single Action Schedule for Billing Dates +22;0.0.15.0.2.255;0;Single Action Schedule for Image Activation +22;0.0.15.0.4.255;0;Single Action Schedule for PUSH +40;0.0.25.9.0.255;1;Instant Push Setup +40;0.4.25.9.0.255;1;Alert Push Setup +64;0.0.25.9.1.255;0;Periodic Push (SM to HES) +64;0.0.25.9.2.255;0;Utility Message (HES to IHD) +64;0.0.25.9.3.255;0;Consumer Message (IHD to HES) +64;0.0.25.9.4.255;0;Periodic Push (SM to IHD) +64;0.0.25.9.5.255;0;EventPush (SM to HES) +64;0.0.43.0.2.255;0;Global Key Change #2 (LLS) +64;0.0.43.0.3.255;0;Global Key Change #3 (HLS) +64;0.0.43.0.4.255;0;Global Key Change #4 (Push) +64;0.0.43.0.5.255;0;Global Key Change #5 (Firmware Upgrade) +70;0.0.96.3.10.255;0;Load Limit Function Status +71;0.0.17.0.0.255;0;Load Limit Value (kW) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/Italy.txt b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/Italy.txt new file mode 100644 index 0000000..023f893 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/Italy.txt @@ -0,0 +1,175 @@ +1;0.0.96.1.0.255;0;Manufacturing number +1;0.0.96.1.10.255;0;Metering Point Identifier +1;0.0.96.1.3.255;0;Equipment Class Identifier +1;0.0.96.1.5.255;0;UNI/TS Reference version +17;0.0.41.0.0.255;0;SAP assigment +1;0.0.42.0.0.255;0;Logical Device Name +15;0.0.40.0.1.255;2;Management Association +15;0.0.40.0.16.255;2;Public Association +15;0.0.40.0.3.255;2;Installer/Maintainer Association +15;0.0.40.0.48.255;2;Guarantor Authority Association +15;0.0.40.0.64.255;2;Gateway Association +15;0.0.40.0.32.255;2;Broadcasting Association +1;0.0.94.39.33.255;0;Global Frame Counter Thresholds +1;0.0.43.1.1.255;0;Management Frame Counter (On-line) +1;0.1.43.1.1.255;0;Management Frame Counter (Off-line) +1;0.0.43.1.48.255;0;Guarantor Authority Frame Counter +1;0.0.43.1.3.255;0;Installer/Maintainer Frame Counter +1;0.0.43.1.64.255;0;Gateway Frame Counter +1;0.0.43.1.32.255;0;Broadcast Frame Counter +8;0.0.1.0.0.255;0;Clock +1;0.0.1.1.0.255;0;UNIX Time +1;0.0.1.1.0.101;0;UNIX Time, Daily historical value, previous gas day +1;0.0.94.39.44.255;0;Synchronization Algorithm +1;0.0.94.39.20.255;0;Synchronization Registers +1;0.0.96.14.0.255;0;Currently Active Tariff +8192;0.0.94.39.21.255;0;Active UNI/TS Tariff Plan +8192;0.0.94.39.22.255;0;Passive UNI/TS Tariff Plan +9;0.0.10.0.0.255;0;Global Script +1;7.0.96.5.0.255;0;UNI/TS Device Status +3;0.0.94.39.31.255;0;Installer/Maintainer Remaining Time +21;0.0.94.39.32.255;0;Installer/Maintainer Monitor +1;0.0.94.39.30.255;0;Installer/Maintainer Setup +70;0.0.96.3.10.255;0;Disconnect Control +9;0.0.10.0.106.255;0;Disconnect Control Script Table +22;0.0.15.0.1.255;0;Disconnect control Single action schedule +1;0.0.94.39.3.255;0;Valve Configuration PGV +1;0.0.94.39.1.255;0;Valve Enable Password +1;0.0.94.39.2.255;0;Maximum Password Attempts +1;0.0.94.39.6.255;0;Opening Command Duration Validity +21;0.0.94.39.5.255;0;Number of days without Comms Threshold +21;0.0.94.39.25.255;0;Tampering Attempts Threshold +1;0.1.96.20.25.255;0;Internal tampering attempt counter +1;0.0.94.39.26.255;0;Leakage Test Parameters +1;0.0.94.39.7.255;0;Valve Closure Cause +1;0.0.94.39.46.255;0;User Message +3;7.0.96.5.1.255;0;Current Diagnostic +1;7.1.96.5.1.255;0;Daily diagnostic +1;7.2.96.5.1.255;0;Snapshot Period Diagnostic +1;0.0.96.20.25.255;0;Anti-fraud totalizer +1;0.0.96.20.30.255;0;Communication tamper event counter +1;0.0.96.11.2.255;0;Event Code +1;0.0.96.15.2.255;0;Event Counter +7;7.0.99.98.0.255;0;Event Logbook +7;7.0.96.11.1.255;0;Metrological Event Code +7;7.0.96.98.1.255;0;Metrological Logbook +1;0.0.96.15.1.255;0;Metrological Event Counter +7;7.0.99.16.0.255;0;Parameter Monitor Logbook +65;0.0.16.2.0.255;0;Parameter Monitor +1;0.0.94.39.42.255;0;Session Error Reason Code +1;0.0.94.39.43.255;0;Invoke Id of Erroneous APDU +3;7.0.13.2.0.255;0;Current Index of Converted Volume +3;7.0.13.2.1.255;0;Current Index of Converted Volume in F1 rate +3;7.0.13.2.2.255;0;Current Index of Converted Volume in F2 rate +3;7.0.13.2.3.255;0;Current Index of Converted Volume in F3 rate +3;7.0.12.2.0.255;0;Current Index of Converted Volume Under Alarm +4;7.0.43.45.0.255;0;Maximum Convertional Converted Gas Flow +3;0.0.96.8.0.255;0;Total operating time +9;0.0.10.0.1.255;0;Snapshot Script +9;0.0.10.0.8.255;0;Push Script +3;7.0.13.26.0.101;0;Index Converted Volume, Daily historical value, previous gas day +3;7.0.12.26.0.101;0;Index Converted Volume under Alarm, Daily historical value, previous gas day +1;7.0.0.9.3.255;0;Start of Conventional Gas Day +1;7.1.96.5.1.101;0;Daily diagnostic, Daily historical value, previous gas day +1;0.0.1.1.0.255;0;UNIX Time, Daily historical value, previous gas day +22;0.1.15.0.4.255;0;Push Scheduler 1 +40;0.1.25.9.0.255;0;Push Setup 1 +1;7.0.0.8.23.255;0;EOB Snapshot Period +1;0.0.94.39.11.255;0;EOB Snapshot Starting Date +1;0.0.94.39.8.255;0;On Demand Snapshot Time +1;0.0.96.10.2.255;0;Snapshot Reason Code +1;7.0.0.1.0.255;0;Billing/Snapshot Period Counter +7;7.0.98.11.0.255;0;Snapshot Period Data +22;0.2.15.0.4.255;0;Push Scheduler 2 +40;0.2.25.9.0.255;0;Push Setup 2 +22;0.3.15.0.4.255;0;Push Scheduler 3 +40;0.3.25.9.0.255;0;Push Setup 3 +22;0.4.15.0.4.255;0;Push Scheduler 4 +40;0.4.25.9.0.255;0;Push Setup 4 +7;7.0.99.99.3.255;0;Daily Load Profile +18;0.0.44.0.0.255;0;Image transfer +1;7.0.0.2.1.255;0;Metrological firmware version +1;7.0.0.2.8.255;0;Metrological firmware signature +1;7.1.0.2.1.255;0;Non Metrological firmware version +1;7.1.0.2.8.255;0;Non Metrological firmware signature +1;0.0.94.39.41.255;0;Block transfer status +3;0.0.96.6.0.255;0;Battery Use Time Counter 0 +3;0.1.96.6.0.255;0;Battery Use Time Counter 1 +3;0.0.96.6.6.255;0;Battery estimated remaining use time 0 +3;0.1.96.6.6.255;0;Battery estimated remaining use time 1 +3;0.0.96.6.4.255;0;Battery Initial Capacity 0 +3;0.1.96.6.4.255;0;Battery Initial Capacity 1 +3;0.0.94.39.14.255;0;Battery Change Authorization +3;7.0.41.0.0.255;0;Current Temperature +3;7.0.41.2.0.255;0;Base Temperature +3;7.0.41.3.0.255;0;Temperature, backup value +43;0.0.25.2.0.255;0;RF module MAC address +73;0.0.31.0.0.255;0;RF module Secondary Address +1;0.0.94.39.13.255;0;PhyTransmittingERPower +1;0.0.94.39.15.255;0;Physical channel Information Plan +1;0.0.94.39.16.255;0;PIB +1;0.0.94.39.34.255;0;Detected Transmission Frequency Offset +1;0.0.94.39.18.255;0;MIB +1;0.0.94.39.18.254;0;MIB +1;0.0.94.39.9.255;0;Synchronous Access +9;0.0.94.39.17.255;0;Power Level Selection Script +1;0.0.94.39.12.255;0;Maintenance Window +1;0.1.94.39.12.255;0;Synchronization Window +3;0.0.94.39.4.255;0;Number of days without Comms +21;0.0.94.39.10.255;0;PM1 Orphaned Threshold +1;0.0.94.39.19.255;0;PM1 Affiliation Parameters +1;0.0.94.39.23.255;0;PM1 Datalink Frame Information +1;0.0.94.39.24.255;0;PM1 Received RSSI +1;0.1.25.2.0.255;0;PM1 Peer MAC Address +1;0.0.96.5.4.255;0;PM1 Network Status +7;7.0.99.98.6.255;0;PM1 Logbook +1;0.0.96.15.3.255;0;PM1 Event Counter +19;0.0.20.0.0.255;0;IEC Local Port Setup +23;0.0.22.0.0.255;1;IEC HDLC Setup +1;0.0.94.39.40.255;0;Spare Object +62;0.0.66.0.1.255;0;CF1 +62;0.0.66.0.2.255;0;CF2 +62;0.0.66.0.10.255;0;CF10 +62;0.0.66.0.11.255;0;CF11 +62;0.0.66.0.4.255;0;CF4 +62;0.0.66.0.6.255;0;CF6 +62;0.0.66.0.7.255;0;CF7 +62;0.0.66.0.9.255;0;CF9 +1;0.0.96.15.4.255;0;Directory event counter +1;0.0.96.11.4.255;0;Directory event code +7;7.0.99.98.1.255;0;Metrological logbook +8192;0.0.94.39.22.255;0;Passive UNI/TS Tariff Plan +28;0.0.2.2.0.255;0;Auto Answer +29;0.0.2.1.0.255;0;Auto Connect +1;7.0.96.5.1.101;0;Current Diagnostic +1;0.0.96.10.2.101;0;Snapshot Reason Code (end-of-billing-period) +1;7.0.0.1.0.101;0;Snapshot/Billing Period Counter (end-of-billing-period) +3;7.0.13.2.0.101;0;Current Index of Converted Volume (end-of-billing-period) +3;7.0.13.83.1.101;0;Current Index of Converted Volume in F1 rate (end-of-billing-period) +1;7.2.96.5.1.101;0;Snapshot Period Diagnostic (end-of-billing-period) +43;0.1.25.2.0.255;0;PM1 Peer MAC address +7;0.0.21.0.1.255;1;Not Enrolled Detected List +7;0.1.21.0.1.255;1;Enrolled Detected List +1;0.0.94.39.36.255;0;PM1 first detected UNIX Time +1;0.0.94.39.35.255;0;PM1 RSSI Statistic +1;0.0.94.39.38.255;0;Detected Entry Status +1;0.0.94.39.48.255;0;White Listed Devices +1;0.0.94.39.47.255;0;Black Listed Devices +7;0.0.21.0.2.255;1;White List +7;0.0.21.0.3.255;1;Black List +7;7.0.99.98.3.255;0;Directory Event Logbook +7;7.0.21.0.10.255;0;Command Queue +7;7.0.21.0.11.255;0;Response Queue +7;7.0.21.0.12.255;0;Broadcast Command Queue +1;0.0.94.39.44.255;0;Synchronization Algorithm +1;0.0.21.0.14.255;0;Command Buffer +1;0.0.94.39.39.255;0;White List Control Information +1;0.0.94.39.45.255;0;PM1 Request Priority +1;0.0.94.39.27.255;0;PM1 Request Identifier +1;0.0.94.39.29.255;0;PM1 Response Status + +1;0.0.94.39.50.255;0;PM1 DataLink Payload +1;0.0.94.39.28.255;0;PM1 Request Time-To-Live +7;0.0.21.0.10.255;0;Command Queue +7;0.0.98.1.0.255;0;Push Data Queue +7;0.0.21.0.11.255;0;Response Queue \ No newline at end of file diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusCommand.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusCommand.py new file mode 100644 index 0000000..ff16eea --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusCommand.py @@ -0,0 +1,44 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class MBusCommand(GXIntEnum): + """M-Bus command.""" + #Access demand from Meter to Other Device. This message requests an access to + #the Meter (contains no application data). + RSP_UD = 0x8 + #Send unsolicited/periodical application data without request (Send/No Reply) + SND_NR = 0x4 + # Send a command (Send User Data). + SND_UD = 0x3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusControlInfo.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusControlInfo.py new file mode 100644 index 0000000..3b06f39 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusControlInfo.py @@ -0,0 +1,56 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class MBusControlInfo(GXIntEnum): + """M-Bus control info.""" + #pylint: disable=too-few-public-methods + + # Long M-Bus data header present, direction master to slave + LONG_HEADER_MASTER = 0x60 + + # Short M-Bus data header present, direction master to slave + SHORT_HEADER_MASTER = 0x61 + + # Long M-Bus data header present, direction slave to master + LONG_HEADER_SLAVE = 0x7C + + # Short M-Bus data header present, direction slave to master + SHORT_HEADER_SLAVE = 0x7D + + # M-Bus short Header. + SHORT_HEADER = 0x7A + + # M-Bus long Header. + LONG_HEADER = 0x72 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusEncryptionMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusEncryptionMode.py new file mode 100644 index 0000000..f36f12e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusEncryptionMode.py @@ -0,0 +1,59 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class MBusEncryptionMode(GXIntEnum): + """Encryption modes.""" + + # Encryption is not used. + NONE = 0 + + # AES with Counter Mode = CTR) noPadding and IV. + AES_128 = 1 + + # DES with Cipher Block Chaining Mode = CBC). + DES_CBC = 2 + + # DES with Cipher Block Chaining Mode = CBC) and Initial Vector. + DES_CBC_IV = 3 + + # AES with Cipher Block Chaining Mode = CBC) and Initial Vector. + AES_CBC_IV = 5 + + # AES 128 with Cipher Block Chaining Mode = CBC) and dynamic key and + # Initial Vector with 0. + AES_CBC_IV0 = 7 + + # TLS + Tls = 13 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusMeterType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusMeterType.py new file mode 100644 index 0000000..903e4b6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/MBusMeterType.py @@ -0,0 +1,52 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class MBusMeterType(GXIntEnum): + """M-Bus meter type.""" + + # Oil meter. + OIL = 1 + + # Energy meter. + ENERGY = 2 + + # Gas meter. + GAS = 3 + + # Water meter. + WATER = 7 + + # Unknown meter type. + UNKNOWN = 0x0F diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/OBISCodes.txt b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/OBISCodes.txt new file mode 100644 index 0000000..72ad9a8 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/OBISCodes.txt @@ -0,0 +1,1371 @@ +0.0-64.0.1.0.0-99,255;1,3,4;6,17,18;Ch. $B;Billing period counter (1);;;#$F +0.0-64.0.1.1.255;1,3,4;6,17,18;Ch. $B;No. of available billing periods (1);;; +0.0-64.0.1.2.0-99;1,3,4;6,9,21,25;Ch. $B;Time stamp of the billing period (1);;;#$F +0.0-64.0.1.2.255;1,3,4;6,9,21,25;Ch. $B;Time stamp of the most recent billing period (1) closed;;; +0.0-64.0.1.3.0-99,255;1,3,4;6,17,18;Ch. $B;Billing period counter (2), VZ;;;#$F +0.0-64.0.1.4.255;1,3,4;6,17,18;Ch. $B;No. of available billing periods (2);;; +0.0-64.0.1.5.0-99;1,3,4;6,9,21,25;Ch. $B;Time stamp of the billing period (2);;;#$F +0.0-64.0.1.5.255;1,3,4;6,9,21,25;Ch. $B;Time stamp of the most recent billing period (2) closed;;; +0.0-64.0.2.0.255;1,3,4;9,10,17,18;Ch. $B;Active firmware identifier;;; +0.0-64.0.2.1.255;1,3,4;9,10,17,18;Ch. $B;Active firmware version;;; +0.0-64.0.2.8.255;1,3,4;9,10,17,18;Ch. $B;Active firmware signature;;; +0.0-64.0.9.1.255;1,3,4;9,27;Ch. $B;Local time;;; +0.0-64.0.9.2.255;1,3,4;9,26;Ch. $B;Local date;;; +0.0-64.1.0.0.255;8;;Ch. $B;Clock object;;#$(E+1); +0.0-64.1.0.1-127.255;8;;Ch. $B;Clock object, nth instance;;#$(E+1); +0.0-64.1.1.0-127.255;1,3,4;6;Ch. $B;UNIX clock;;#$(E+1); +0.0-64.1.2.0-127.255;1,3,4;21;Ch. $B;High resolution clock;;#$(E+1); +0.0-64.2.0.0.255;27;;Ch. $B;Modem configuration;;; +0.0-64.2.1.0.255;29;;Ch. $B;Auto connect;;; +0.0-64.2.2.0.255;28;;Ch. $B;Auto answer;;; +0.0-64.10.0.0.255;9;;Ch. $B;Global meter reset;;; +0.0-64.10.1-127.0.255;9;;Ch. $B;Global meter reset, implementation specific;#$(D+1);; +0.0-64.10.0.1.255;9;;Ch. $B;MDI reset / End of billing period;;; +0.0-64.10.1-127.1.255;9;;Ch. $B;MDI reset / End of billing period, implementation specific;#$(D+1);; +0.0-64.10.0.100.255;9;;Ch. $B;Tariffication script table;;; +0.0-64.10.1-127.100.255;9;;Ch. $B;Tariffication script table, implementation specific;#$(D+1);; +0.0-64.10.0.101.255;9;;Ch. $B;Activate test mode;;; +0.0-64.10.1-127.101.255;9;;Ch. $B;Activate test mode, implementation specific;#$(D+1);; +0.0-64.10.0.102.255;9;;Ch. $B;Activate normal mode;;; +0.0-64.10.1-127.102.255;9;;Ch. $B;Activate normal mode, implementation specific;#$(D+1);; +0.0-64.10.0.103.255;9;;Ch. $B;Set output signals;;; +0.0-64.10.1-127.103.255;9;;Ch. $B;Set output signals, implementation specific;#$(D+1);; +0.0-64.10.0.104.255;9;;Ch. $B;Switch optical test output;;; +0.0-64.10.1-127.104.255;9;;Ch. $B;Switch optical test output, implementation specific;#$(D+1);; +0.0-64.10.0.105.255;9;;Ch. $B;Power quality measurement management;;; +0.0-64.10.1-127.105.255;9;;Ch. $B;Power quality measurement management, implementation specific;#$(D+1);; +0.0-64.10.0.106.255;9;;Ch. $B;Disconnect control;;; +0.0-64.10.1-127.106.255;9;;Ch. $B;Disconnect control, implementation specific;#$(D+1);; +0.0-64.10.0.107.255;9;;Ch. $B;Image activation;;; +0.0-64.10.1-127.107.255;9;;Ch. $B;Image activation, implementation specific;#$(D+1);; +0.0-64.10.0.108.255;9;;Ch. $B;Push script table;;; +0.0-64.10.1-127.108.255;9;;Ch. $B;Push script table, implementation specific;#$(D+1);; +0.0-64.10.0.109.255;9;;Ch. $B;Load profile control script table;;; +0.0-64.10.1-127.109.255;9;;Ch. $B;Load profile control script table, implementation specific;#$(D+1);; +0.0-64.10.0.110.255;9;;Ch. $B;M-Bus profile control script table,;;; +0.0-64.10.1-127.110.255;9;;Ch. $B;M-Bus profile control script table, implementation specific;#$(D+1);; +0.0-64.10.0.111.255;9;;Ch. $B;Function control script table;;; +0.0-64.10.1-127.111.255;9;;Ch. $B;Function control script table, implementation specific;#$(D+1);; +0.0-64.10.0.125.255;9;;Ch. $B;Broadcast script table;;; +0.0-64.10.1-127.125.255;9;;Ch. $B;Broadcast script table, implementation specific;#$(D+1);; +0.0-64.11.0.0-127.255;11;;Ch. $B;Special days table;;#$(E+1); +0.0-64.12.0.0.255;10;;Ch. $B;Schedule;;#$(E+1); +0.0-64.12.0.1-127.255;10;;Ch. $B;Schedule;;#$(E+1); +0.0-64.13.0.0-127.255;20;;Ch. $B;Activity calendar;;#$(E+1); +0.0-64.14.0.0.255;6;;Ch. $B;Register activation;;#$(E+1); +0.0-64.14.0.1-127.255;6;;Ch. $B;Register activation;;#$(E+1); +0.0-64.15.0.0.255;22;;Ch. $B;End of billing period;;; +0.0-64.15.1-127.0.255;22;;Ch. $B;End of billing period, implementation specific;#$(D+1);; +0.0-64.15.0.1.255;22;;Ch. $B;Disconnect control;;; +0.0-64.15.1-127.1.255;22;;Ch. $B;Disconnect control, implementation specific;#$(D+1);; +0.0-64.15.0.2.255;22;;Ch. $B;Image activation;;; +0.0-64.15.1-127.2.255;22;;Ch. $B;Image activation, implementation specific;#$(D+1);; +0.0-64.15.0.3.255;22;;Ch. $B;Output control;;; +0.0-64.15.1-127.3.255;22;;Ch. $B;Output control, implementation specific;#$(D+1);; +0.0-64.15.0.4.255;22;;Ch. $B;Push;;; +0.0-64.15.1-127.4.255;22;;Ch. $B;Push, implementation specific;#$(D+1);; +0.0-64.15.0.5.255;22;;Ch. $B;Load profile control;;; +0.0-64.15.1-127.5.255;22;;Ch. $B;Load profile control, implementation specific;#$(D+1);; +0.0-64.15.0.6.255;22;;Ch. $B;M-Bus profile control;;; +0.0-64.15.1-127.6.255;22;;Ch. $B;M-Bus profile control, implementation specific;#$(D+1);; +0.0-64.15.0.7.255;22;;Ch. $B;Function control scheduler;;; +0.0-64.15.1-127.7.255;22;;Ch. $B;Function control scheduler, implementation specific;#$(D+1);; +0.0-64.16.0.0.255;21;;Ch. $B;Register monitor;;#$(E+1); +0.0-64.16.0.1-127.255;21;;Ch. $B;Register monitor;;#$(E+1); +0.0-64.16.1.0-9.255;21;;Ch. $B;Alarm monitor;;#$(E+1); +0.0-64.16.2.0-127.255;65;;Ch. $B;Parameter monitor;;#$(E+1); +0.0-64.17.0.0.255;71;;Ch. $B;Limiter;;#$(E+1); +0.0-64.17.0.1-127.255;71;;Ch. $B;Limiter;;#$(E+1); +0.0-64.18.0.0.255;123;;Ch. $B;Array manager;;#$(E+1); +0.0-64.18.0.1-127.255;123;;Ch. $B;Array manager;;#$(E+1); +0.0-64.19.0-9.0.255;111;;Ch. $B;Account;;; +0.0-64.19.10-19.0-127.255;112;;Ch. $B;Credit;#$(D-10);#$(E); +0.0-64.19.20-29.0-127.255;113;;Ch. $B;Charge;#$(D-20);#$(E); +0.0-64.19.40-49.0-127.255;115;;Ch. $B;Token gateway;#$(D-40);#$(E); +0.0-64.19.50-59.1.255;1,3,4;5,6,17,18,20,21;Ch. $B;Max credit limit;#$(D-50);; +0.0-64.19.50-59.2.255;1,3.4;5,6,17,18,20,21;Ch. $B;Max vend limit;#$(D-50);; +0.0-64.20.0.0.255;19;;Ch. $B;IEC optical port setup;;; +0.0-64.20.0.1.255;19;;Ch. $B;IEC electrical port setup;;; +0.0-64.21.0.0.255;7;;Ch. $B;General local port readout;;; +0.0-64.21.0.1.255;7;;Ch. $B;General display readout;;; +0.0-64.21.0.2.255;7;;Ch. $B;Alternate display readout;;; +0.0-64.21.0.3.255;7;;Ch. $B;Service display readout;;; +0.0-64.21.0.4.255;7;;Ch. $B;List of configurable meter data;;; +0.0-64.21.0.5.255;7;;Ch. $B;Additional readout profile;;#$(E-4); +0.0-64.21.0.6-127.255;7;;Ch. $B;Additional readout profiles;;#$(E-4); +0.0-64.21.0.0.255;1;1;Ch. $B;Standard readout parametrization;;#$(E+1); +0.0-64.21.0.1-127.255;1;1;Ch. $B;Standard readout parametrization;;#$(E+1); +0.0-64.22.0.0.255;23;;Ch. $B;IEC HDLC setup;;; +0.0-64.23.0.0.255;24;;Ch. $B;IEC twisted pair (1) setup;;; +0.0-64.23.1.0.255;43;;Ch. $B;IEC twisted pair MAC address setup;;; +0.0-64.23.2.0.255;1,3,4;4,6,9,17,18;Ch. $B;IEC twisted pair fatal error register;;; +0.0-64.23.3.0.255;7;;Ch. $B;IEC 62056-3-1 Short readout;;; +0.0-64.23.3.1.255;7;;Ch. $B;IEC 62056-3-1 Long readout;;; +0.0-64.23.3.2-9.255;7;;Ch. $B;IEC 62056-3-1 Alternate readout profile;;#$(E-2); +0.0-64.23.3.0-127.255;1;;Ch. $B;IEC 62056-3-1 readout parametrization;;; +0.0-64.24.0.0.255;25;;Ch. $B;M-Bus slave port setup;;; +0.0-64.24.1.0.255;72;;Ch. $B;M-Bus client;;; +0.0-64.24.2.0-127.255;4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;M-Bus value;;#$(E+1); +0.0-64.24.3.0-127.255;7;;Ch. $B;M-Bus profile generic;;#$(E+1); +0.0-64.24.4.0.255;70;;Ch. $B;M-Bus disconnect control;;#$(E+1); +0.0-64.24.5.0.255;7;;Ch. $B;M-Bus control log;;#$(E+1); +0.0-64.24.6.0.255;74;;Ch. $B;M-Bus master port setup;;#$(E+1); +0.0-64.24.8.0-127.255;76;;Ch. $B;DLMS/COSEM server M-Bus port setup;;; +0.0-64.24.9.0-127.255;77;;Ch. $B;M-Bus diagnostic;;; +0.0-64.25.0.0.255;41;;Ch. $B;TCP-UDP setup;;; +0.0-64.25.1.0.255;42;;Ch. $B;IPv4 setup;;; +0.0-64.25.2.0.255;43;;Ch. $B;MAC address setup;;; +0.0-64.25.3.0.255;44;;Ch. $B;PPP setup;;; +0.0-64.25.4.0.255;45;;Ch. $B;GPRS modem setup;;; +0.0-64.25.5.0.255;46;;Ch. $B;SMTP setup;;; +0.0-64.25.6.0.255;47;;Ch. $B;GSM diagnostic;;; +0.0-64.25.7.0.255;48;;Ch. $B;IPv6 setup;;; +0.0-64.25.9.0.255;40;;Ch. $B;Push setup;;; +0.0-64.25.10.0.255;100;;Ch. $B;NTP setup;;; +0.0-64.25.11.0.255;151;;Ch. $B;LTE monitoring;;; +0.0-64.26.0.0.255;50;;Ch. $B;S-FSK Phy&MAC setup;;; +0.0-64.26.1.0.255;51;;Ch. $B;S-FSK Active initiator;;; +0.0-64.26.2.0.255;52;;Ch. $B;S-FSK MAC synchronization timeouts;;; +0.0-64.26.3.0.255;53;;Ch. $B;S-FSK MAC counters;;; +0.0-64.26.5.0.255;55;;Ch. $B;IEC 61334-4-32 LLC setup;;; +0.0-64.26.6.0.255;56;;Ch. $B;S-FSK Reporting system list;;; +0.0-64.27.0.0.255;57;;Ch. $B;ISO/IEC 8802-2 LLC Type 1 setup;;; +0.0-64.27.1.0.255;58;;Ch. $B;ISO/IEC 8802-2 LLC Type 2 setup;;; +0.0-64.27.2.0.255;59;;Ch. $B;ISO/IEC 8802-2 LLC Type 3 setup;;; +0.0-64.28.0.0.255;80;;Ch. $B;61334-4-32 LLC SSCS setup;;; +0.0-64.28.1.0.255;81;;Ch. $B;PRIME NB OFDM PLC Physical layer counters;;; +0.0-64.28.2.0.255;82;;Ch. $B;PRIME NB OFDM PLC MAC setup;;; +0.0-64.28.3.0.255;83;;Ch. $B;PRIME NB OFDM PLC MAC functional parameters;;; +0.0-64.28.4.0.255;84;;Ch. $B;PRIME NB OFDM PLC MAC counters;;; +0.0-64.28.5.0.255;85;;Ch. $B;PRIME NB OFDM PLC MAC network administration data;;; +0.0-64.28.6.0.255;43;;Ch. $B;PRIME NB OFDM PLC MAC address setup;;; +0.0-64.28.7.0.255;86;;Ch. $B;PRIME NB OFDM PLC Application identification;;; +0.0-64.29.0.0.255;90;;Ch. $B;G3-PLC MAC layer counters;;; +0.0-64.29.1.0.255;91;;Ch. $B;G3-PLC MAC setup;;; +0.0-64.29.2.0.255;92;;Ch. $B;G3-PLC 6LoWPAN adaptation layer setup;;; +0.0-64.30.0.0-127.255;101;;Ch. $B;ZigBee® SAS startup;;#$(E+1); +0.0-64.30.1.0-127.255;102;;Ch. $B;ZigBee® SAS join;;#$(E+1); +0.0-64.30.2.0-127.255;103;;Ch. $B;ZigBee® SAS APS fragmentation;;#$(E+1); +0.0-64.30.3.0-127.255;104;;Ch. $B;ZigBee® network control;;#$(E+1); +0.0-64.30.4.0-127.255;105;;Ch. $B;ZigBee® tunnel setup;;#$(E+1); +0.0-64.31.0.0.255;73;;Ch. $B;Wireless Mode Q channel;;; +0.0-64.33.0.0.255;140;;Ch. $B;HS-PLC ISO/IEC 12139-1 MAC setup;;; +0.0-64.33.1.0.255;141;;Ch. $B;HS-PLC ISO/IEC 12139-1 CPAS setup;;; +0.0-64.33.2.0.255;142;;Ch. $B;HS-PLC ISO/IEC 12139-1 IP SSAS setup;;; +0.0-64.33.3.0.255;143;;Ch. $B;HS-PLC ISO/IEC 12139-1 HDLC SSAS setup;;; +0.0.40.0.0.255;12,15;;Ch. $B;Current association;;; +0.0.40.0.1.255;12,15;;Ch. $B;Association;;#$E; +0.0.40.0.2-127.255;12,15;;Ch. $B;Association;;#$E; +0.0.41.0.0.255;17;;Ch. $B;SAP Assignment;;; +0.0.42.0.0.255;1,3;9,10;Ch. $B;COSEM Logical device name;;; +0.0.43.0.0-127.255;64;;Ch. $B;Security setup;;#$E; +0.0-64.43.1.0-127.255;1,3,4;6,17,18,21;Ch. $B;Invocation counter;;#$E; +0.0-64.43.2.0-127.255;30;;Ch. $B;Data protection;;#$E; +0.0.44.0.0-127.255;18;;Ch. $B;Image transfer;;#$E; +0.0.44.1.0-127.255;122;;;Function control;;; +0.0-64.65.0.0-127.255;26;;Ch. $B;Standard tables (D*128+E);$D;#$E; +0.0-64.65.1-15.0-127.255;26;;Ch. $B;Standard tables (D*128+E);$D;#$E; +0.0-64.65.16.0-127.255;26;;Ch. $B;Manufacturer tables ((D-16)*128+E);$D;#$E; +0.0-64.65.17-31.0-127.255;26;;Ch. $B;Manufacturer tables ((D-16)*128+E);$D;#$E; +0.0-64.65.32.0-127.255;26;;Ch. $B;Std pending tables ((D-32)*128+E);$D;#$E; +0.0-64.65.33-47.0-127.255;26;;Ch. $B;Std pending tables ((D-32)*128+E);$D;#$E; +0.0-64.65.48.0-127.255;26;;Ch. $B;Mfg pending tables ((D-48)*128+E);$D;#$E; +0.0-64.65.49-63.0-127.255;26;;Ch. $B;Mfg pending tables ((D-48)*128+E);$D;#$E; +0.0-64.66.0.0-127.255;62;;Ch. $B;Compact data;;#$E; +0,1,7.0-64.94.0.0-255.0-255;*;*;Ch. $B;Identifiers for Finland;;; +0,1,7.0-64.94.1.0-255.0-255;*;*;Ch. $B;Identifiers for the USA;;; +0,1,7.0-64.94.2.0-255.0-255;*;*;Ch. $B;Identifiers for Canada;;; +0,1,7.0-64.94.3.0-255.0-255;*;*;Ch. $B;Identifiers for Serbia;;; +0,1,7.0-64.94.7.0-255.0-255;*;*;Ch. $B;Identifiers for Russia;;; +0,1,7.0-64.94.10.0-255.0-255;*;*;Ch. $B;Identifiers for Czech;;; +0,1,7.0-64.94.11.0-255.0-255;*;*;Ch. $B;Identifiers for Bulgaria;;; +0,1,7.0-64.94.12.0-255.0-255;*;*;Ch. $B;Identifiers for Croatia;;; +0,1,7.0-64.94.13.0-255.0-255;*;*;Ch. $B;Identifiers for Ireland;;; +0,1,7.0-64.94.14.0-255.0-255;*;*;Ch. $B;Identifiers for Israel;;; +0,1,7.0-64.94.15.0-255.0-255;*;*;Ch. $B;Identifiers for Ukraine;;; +0,1,7.0-64.94.16.0-255.0-255;*;*;Ch. $B;Identifiers for Yugoslavia;;; +0,1,7.0-64.94.20.0-255.0-255;*;*;Ch. $B;Identifiers for Egyipt;;; +0,1,7.0-64.94.27.0-255.0-255;*;*;Ch. $B;Identifiers for South Africa;;; +0,1,7.0-64.94.30.0-255.0-255;*;*;Ch. $B;Identifiers for Greece;;; +0,1,7.0-64.94.31.0-255.0-255;*;*;Ch. $B;Identifiers for the Netherlands;;; +0,1,7.0-64.94.32.0-255.0-255;*;*;Ch. $B;Identifiers for Belgium;;; +0,1,7.0-64.94.33.0-255.0-255;*;*;Ch. $B;Identifiers for France;;; +0,1,7.0-64.94.34.0-255.0-255;*;*;Ch. $B;Identifiers for Spain;;; +0,1,7.0-64.94.35.0-255.0-255;*;*;Ch. $B;Identifiers for Portugal;;; +0,1,7.0-64.94.36.0-255.0-255;*;*;Ch. $B;Identifiers for Hungary;;; +0,1,7.0-64.94.37.0-255.0-255;*;*;Ch. $B;Identifiers for Lithuania;;; +0,1,7.0-64.94.38.0-255.0-255;*;*;Ch. $B;Identifiers for Slovenia;;; +0,1,7.0-64.94.39.0-255.0-255;*;*;Ch. $B;Identifiers for Italy;;; +0,1,7.0-64.94.40.0-255.0-255;*;*;Ch. $B;Identifiers for Romania;;; +0,1,7.0-64.94.41.0-255.0-255;*;*;Ch. $B;Identifiers for Switzerland;;; +0,1,7.0-64.94.42.0-255.0-255;*;*;Ch. $B;Identifiers for Slovakia;;; +0,1,7.0-64.94.43.0-255.0-255;*;*;Ch. $B;Identifiers for Austria;;; +0,1,7.0-64.94.44.0-255.0-255;*;*;Ch. $B;Identifiers for the UK;;; +0,1,7.0-64.94.45.0-255.0-255;*;*;Ch. $B;Identifiers for Denmark;;; +0,1,7.0-64.94.46.0-255.0-255;*;*;Ch. $B;Identifiers for Sweden;;; +0,1,7.0-64.94.47.0-255.0-255;*;*;Ch. $B;Identifiers for Norway;;; +0,1,7.0-64.94.48.0-255.0-255;*;*;Ch. $B;Identifiers for Poland;;; +0,1,7.0-64.94.49.0-255.0-255;*;*;Ch. $B;Identifiers for Germany;;; +0,1,7.0-64.94.51.0-255.0-255;*;*;Ch. $B;Identifiers for Peru;;; +0,1,7.0-64.94.52.0-255.0-255;*;*;Ch. $B;Identifiers for South Korea;;; +0,1,7.0-64.94.53.0-255.0-255;*;*;Ch. $B;Identifiers for Cuba;;; +0,1,7.0-64.94.54.0-255.0-255;*;*;Ch. $B;Identifiers for Argentina;;; +0,1,7.0-64.94.55.0-255.0-255;*;*;Ch. $B;Identifiers for Brazil;;; +0,1,7.0-64.94.56.0-255.0-255;*;*;Ch. $B;Identifiers for Chile;;; +0,1,7.0-64.94.57.0-255.0-255;*;*;Ch. $B;Identifiers for Colombia;;; +0,1,7.0-64.94.58.0-255.0-255;*;*;Ch. $B;Identifiers for Venezuela;;; +0,1,7.0-64.94.60.0-255.0-255;*;*;Ch. $B;Identifiers for Malaysia;;; +0,1,7.0-64.94.61.0-255.0-255;*;*;Ch. $B;Identifiers for Australia;;; +0,1,7.0-64.94.62.0-255.0-255;*;*;Ch. $B;Identifiers for Indonesia;;; +0,1,7.0-64.94.63.0-255.0-255;*;*;Ch. $B;Identifiers for the Philippines;;; +0,1,7.0-64.94.64.0-255.0-255;*;*;Ch. $B;Identifiers for New Zealand;;; +0,1,7.0-64.94.65.0-255.0-255;*;*;Ch. $B;Identifiers for Singapore;;; +0,1,7.0-64.94.66.0-255.0-255;*;*;Ch. $B;Identifiers for Thailand;;; +0,1,7.0-64.94.71.0-255.0-255;*;*;Ch. $B;Identifiers for Latvia;;; +0,1,7.0-64.94.73.0-255.0-255;*;*;Ch. $B;Identifiers for Moldova;;; +0,1,7.0-64.94.75.0-255.0-255;*;*;Ch. $B;Identifiers for Belarus;;; +0,1,7.0-64.94.81.0-255.0-255;*;*;Ch. $B;Identifiers for Japan;;; +0,1,7.0-64.94.85.0-255.0-255;*;*;Ch. $B;Identifiers for Hong Kong;;; +0,1,7.0-64.94.86.0-255.0-255;*;*;Ch. $B;Identifiers for China;;; +0,1,7.0-64.94.87.0-255.0-255;*;*;Ch. $B;Identifiers for Bosnia and Herzegovina;;; +0,1,7.0-64.94.90.0-255.0-255;*;*;Ch. $B;Identifiers for Turkey;;; +0,1,7.0-64.94.91.0-255.0-255;*;*;Ch. $B;Identifiers for India;;; +0,1,7.0-64.94.92.0-255.0-255;*;*;Ch. $B;Identifiers for Pakistan;;; +0,1,7.0-64.94.96.0-255.0-255;*;*;Ch. $B;Identifiers for Saudi Arabia;;; +0,1,7.0-64.94.97.0-255.0-255;*;*;Ch. $B;Identifiers for the United Arab Emirates;;; +0,1,7.0-64.94.98.0-255.0-255;*;*;Ch. $B;Identifiers for Iran;;; +0.0-64.96.1.0.255;1,3,4;6,9,10,12,17,18;Ch. $B;Device ID 1, manufacturing number;;; +0.0-64.96.1.1-9.255;1,3,4;6,9,10,12,17,18;Ch. $B;Device ID;;#$(E+1); +0.0-64.96.1.255.255;7,61;;Ch. $B;Complete device ID;;; +0.0-64.96.1.10.255;1,3,4;6,9,10,12,17,18;Ch. $B;Metering point ID;;; +0.0-64.96.2.0.255;1,3,4;6,17,18,21;Ch. $B;No. of configuration program changes;;; +0.0-64.96.2.1.255;1,3,4;9,25,26;Ch. $B;Date of last configuration program change;;; +0.0-64.96.2.2.255;1,3,4;9,25,26;Ch. $B;Date of last time switch program change;;; +0.0-64.96.2.3.255;1,3,4;9,25,26;Ch. $B;Date of last ripple control program change;;; +0.0-64.96.2.4.255;1,3,4;4,6,9,17,18,21;Ch. $B;Status of security switches;;; +0.0-64.96.2.5.255;1,3,4;9,25,26;Ch. $B;Date of last calibration;;; +0.0-64.96.2.6.255;1,3,4;9,25,26;Ch. $B;Date of next configuration program change;;; +0.0-64.96.2.7.255;1,3,4;9,25,26;Ch. $B;Date of activation of the passive calendar;;; +0.0-64.96.2.10.255;1,3,4;6,17,18,21;Ch. $B;No. of protected configuration program changes;;; +0.0-64.96.2.11.255;1,3,4;9,25,26;Ch. $B;Date of last protected configuration program change;;; +0.0-64.96.2.12.255;1,3,4;9,25,26;Ch. $B;Date (corrected) of last clock synchronisation / setting;;; +0.0-64.96.2.13.255;1,3,4;9,25,26;Ch. $B;Date of last firmware activation;;; +0.0-64.96.3.0.255;1,3,4,63,7,61;3,4,6,9,17,18,21;Ch. $B;State of input/output control signals, global;;; +0.0-64.96.3.1.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;State of the input control signals;;(status word #$E); +0.0-64.96.3.2.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;State of the output control signals;;(status word #$E); +0.0-64.96.3.3.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;State of input/output control signals;;(status word #$E); +0.0-64.96.3.4.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;State of input/output control signals;;(status word #$E); +0.0-64.96.3.10.255;70;;Ch. $B;Disconnect control;;; +0.0-64.96.3.20-29.255;68;;Ch. $B;Arbitrator;;#$(E-20); +0.0-64.96.4.0.255;1,3,4,63,7,61;3,4,6,9,17,18,21;Ch. $B;State of the internal control signals, global;;; +0.0-64.96.4.1.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;State of the internal control signals;;(status word #$E); +0.0-64.96.4.2.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;State of the internal control signals;;(status word #$E); +0.0-64.96.4.3.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;State of the internal control signals;;(status word #$E); +0.0-64.96.4.4.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;State of the internal control signals;;(status word #$E); +0.0-64.96.5.0.255;1,3,4,63,7,61;3,4,6,9,17,18,21;Ch. $B;Internal operating status, global;;; +0.0-64.96.5.1.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +0.0-64.96.5.2.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +0.0-64.96.5.3.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +0.0-64.96.5.4.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +0.0-64.96.6.0.255;3,1,4;6,17,18,21;Ch. $B;Battery use time counter;;; +0.0-64.96.6.1.255;3,1,4;6,17,18,21;Ch. $B;Battery charge display;;; +0.0-64.96.6.2.255;3,1,4;9,25,26;Ch. $B;Date of next battery change;;; +0.0-64.96.6.3.255;3,1,4;5,6,15,16,17,18,20,21,23,24;Ch. $B;Battery voltage;;; +0.0-64.96.6.4.255;3,1,4;5,6,15,16,17,18,20,21,23,24;Ch. $B;Battery initial capacity;;; +0.0-64.96.6.5.255;3,1,4;9,25,26;Ch. $B;Battery installation date and time;;; +0.0-64.96.6.6.255;3,1,4;6,17,18,21;Ch. $B;Battery estimated remaining use time;;; +0.0-64.96.6.10.255;3,1,4;6,17,18,21;Ch. $B;Aux. supply use time counter;;; +0.0-64.96.6.11.255;3,1,4;5,6,15,16,17,18,20,21,23,24;Ch. $B;Aux. voltage (measured);;; +0.0-64.96.7.0.255;1,3,4;6,17,18,21;Ch. $B;No. of power failures;in all three phases;; +0.0-64.96.7.1.255;1,3,4;6,17,18,21;Ch. $B;No. of power failures;in phase L1;; +0.0-64.96.7.2.255;1,3,4;6,17,18,21;Ch. $B;No. of power failures;in phase L2;; +0.0-64.96.7.3.255;1,3,4;6,17,18,21;Ch. $B;No. of power failures;in phase L3;; +0.0-64.96.7.4.255;1,3,4;6,17,18,21;Ch. $B;No. of power failures;Auxiliary power supply;; +0.0-64.96.7.21.255;1,3,4;6,17,18,21;Ch. $B;No. of power failures;in any phase;; +0.0-64.96.7.5.255;1,3,4;6,17,18,21;Ch. $B;No. of long power failures;in all three phases;; +0.0-64.96.7.6.255;1,3,4;6,17,18,21;Ch. $B;No. of long power failures;in phase L1;; +0.0-64.96.7.7.255;1,3,4;6,17,18,21;Ch. $B;No. of long power failures;in phase L2;; +0.0-64.96.7.8.255;1,3,4;6,17,18,21;Ch. $B;No. of long power failures;in phase L3;; +0.0-64.96.7.9.255;1,3,4;6,17,18,21;Ch. $B;No. of long power failures;in any phase;; +0.0-64.96.7.10.255;1,3,4;6,9,21,25,;Ch. $B;Time of power failure;in all three phases;; +0.0-64.96.7.11.255;1,3,4;6,9,21,25,;Ch. $B;Time of power failure;in phase L1;; +0.0-64.96.7.12.255;1,3,4;6,9,21,25,;Ch. $B;Time of power failure;in phase L2;; +0.0-64.96.7.13.255;1,3,4;6,9,21,25,;Ch. $B;Time of power failure;in phase L3;; +0.0-64.96.7.14.255;1,3,4;6,9,21,25,;Ch. $B;Time of power failure;in any phase;; +0.0-64.96.7.15.255;1,3,4;6,17,18,21;Ch. $B;Duration of long power failure;in all three phases;; +0.0-64.96.7.16.255;1,3,4;6,17,18,21;Ch. $B;Duration of long power failure;in phase L1;; +0.0-64.96.7.17.255;1,3,4;6,17,18,21;Ch. $B;Duration of long power failure;in phase L2;; +0.0-64.96.7.18.255;1,3,4;6,17,18,21;Ch. $B;Duration of long power failure;in phase L3;; +0.0-64.96.7.19.255;1,3,4;6,17,18,21;Ch. $B;Duration of long power failure;in any phase;; +0.0-64.96.7.20.255;1,3,4;6,17,18,21;Ch. $B;Time threshold for long of power failure;;; +0.0-64.96.8.0.255;3,4,1;6,17,18,21;Ch. $B;Time of operation, total;;; +0.0-64.96.8.1-63.255;3,4,1;6,17,18,21;Ch. $B;Time of operation;;Rate $E; +0.0-64.96.9.0.255;3,4;5,6,15,16,17,18,20,21,23,24;Ch. $B;Ambient temperature;;; +0.0-64.96.9.1.255;3,4;5,6,15,16,17,18,20,21,23,24;Ch. $B;Ambient pressure;;; +0.0-64.96.9.2.255;3,4;5,6,15,16,17,18,20,21,23,24;Ch. $B;Relative humidity;;; +0.0-64.96.10.1.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.10.2.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.10.3.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.10.4.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.10.5.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.10.6.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.10.7.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.10.8.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.10.9.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.10.10.255;1,3,4,63;4,6,9,17,18,21;Ch. $B;Status register;;#$E; +0.0-64.96.11.0-99.255;1,3,4;5,6,15,16,17,18,22;Ch. $B;Event code;;#$(E+1); +0.0-64.96.12.1.255;1,3,4;6,17,18,21;Ch. $B;Comm. port parameters;No. of connections;; +0.0-64.96.12.4.255;1,3,4;9,10;Ch. $B;Comm. port parameters;Parameter 1;; +0.0-64.96.12.5.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Comm. port parameters;GSM field strength;; +0.0-64.96.12.6.255;1,3,4;9;Ch. $B;Comm. port parameters;Telephone number / Communication address of the physical device;; +0.0-64.96.13.0.255;1,3,4;9,10;Ch. $B;Consumer message via local consumer information port;;; +0.0-64.96.13.1.255;1,3,4;9,10;Ch. $B;Consumer message via the meter display and/or via local consumer information port;;; +0.0-64.96.14.0-15.255;1,3,4;6,9,15,17,18,21;Ch. $B;Currently active tariff;;#$(E+1); +0.0-64.96.15.0-99.255;1,3,4;6,17,18,21;Ch. $B;Event counter;;#$(E+1); +0.0-64.96.16.0-9.255;1,3,4;9;Ch. $B;Profile entry digital signature;;#$(E+1); +0.0-64.96.20.0.255;1,3,4;6,17,18;Ch. $B;Meter open event counter;;; +0.0-64.96.20.1.255;1,3,4;6,9,25;Ch. $B;Meter open event, time stamp of current event occurrence;;; +0.0-64.96.20.2.255;1,3,4;6,17,18;Ch. $B;Meter open event, duration of current event;;; +0.0-64.96.20.3.255;1,3,4;6,17,18;Ch. $B;Meter open event, cumulative duration;;; +0.0-64.96.20.5.255;1,3,4;6,17,18;Ch. $B;Terminal cover open event counter;;; +0.0-64.96.20.6.255;1,3,4;6,9,25;Ch. $B;Terminal cover open event, time stamp of current event occurrence;;; +0.0-64.96.20.7.255;1,3,4;6,17,18;Ch. $B;Terminal cover open event, duration of current event;;; +0.0-64.96.20.8.255;1,3,4;6,17,18;Ch. $B;Terminal cover open event, cumulative duration;;; +0.0-64.96.20.10.255;1,3,4;6,17,18;Ch. $B;Tilt event counter;;; +0.0-64.96.20.11.255;1,3,4;6,9,25;Ch. $B;Tilt event, time stamp of current event occurrence;;; +0.0-64.96.20.12.255;1,3,4;6,17,18;Ch. $B;Tilt event, duration of current event;;; +0.0-64.96.20.13.255;1,3,4;6,17,18;Ch. $B;Tilt event, cumulative duration;;; +0.0-64.96.20.15.255;1,3,4;6,17,18;Ch. $B;Strong DC magnetic field event counter;;; +0.0-64.96.20.16.255;1,3,4;6,9,25;Ch. $B;Strong DC magnetic field event, time stamp of current event occurrence;;; +0.0-64.96.20.17.255;1,3,4;6,17,18;Ch. $B;Strong DC magnetic field event, duration of current event;;; +0.0-64.96.20.18.255;1,3,4;6,17,18;Ch. $B;Strong DC magnetic field event, cumulative duration;;; +0.0-64.96.20.20.255;1,3,4;6,17,18;Ch. $B;Supply control switch / valve tamper event counter;;; +0.0-64.96.20.21.255;1,3,4;6,9,25;Ch. $B;Supply control switch / valve tamper event, time stamp of current event occurrence;;; +0.0-64.96.20.22.255;1,3,4;6,17,18;Ch. $B;Supply control switch / valve tamper event, duration of current event;;; +0.0-64.96.20.23.255;1,3,4;6,17,18;Ch. $B;Supply control switch / valve tamper event, cumulative duration;;; +0.0-64.96.20.25.255;1,3,4;6,17,18;Ch. $B;Metrology tamper event counter;;; +0.0-64.96.20.26.255;1,3,4;6,9,25;Ch. $B;Metrology tamper event, time stamp of current event occurrence;;; +0.0-64.96.20.27.255;1,3,4;6,17,18;Ch. $B;Metrology tamper event, duration of current event;;; +0.0-64.96.20.28.255;1,3,4;6,17,18;Ch. $B;Metrology tamper event, cumulative duration;;; +0.0-64.96.20.29.255;1,3,4;;Ch. $B;Reserved;;; +0.0-64.96.20.30.255;1,3,4;6,17,18;Ch. $B;Communication tamper event counter;;; +0.0-64.96.20.31.255;1,3,4;6,9,25;Ch. $B;Communication tamper event, time stamp of current event occurrence;;; +0.0-64.96.20.32.255;1,3,4;6,17,18;Ch. $B;Communication tamper event, duration of current event;;; +0.0-64.96.20.33.255;1,3,4;6,17,18;Ch. $B;Communication tamper event, cumulative duration;;; +0.0-64.96.50-99.0-255.0-255;1,3,4,7,61,63;*;Ch. $B;Man. spec. abstract object;;; +0,1,7.0-64.97.97.0.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.1.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.2.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.3.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.4.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.5.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.6.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.7.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.8.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.9.255;1,3,4;4,6,9,17,18;Ch. $B;Error object;;#$(E+1); +0,1,7.0-64.97.97.255.255;7,61;;Ch. $B;Error profile object;;; +0.0-64.97.98.0-9.255;1,3,4;4,6,9,17,18;Ch. $B;Alarm register object;;#$(E+1); +0.0-64.97.98.255.255;7,61;;Ch. $B;Alarm register profile object;;; +0.0-64.97.98.10-19.255;1,3,4;4,6,9,17,18;Ch. $B;Alarm filter object;;#$(E-9); +0.0-64.97.98.20-29.255;1,3,4;4,6,9,17,18;Ch. $B;Alarm descriptor object;;#$(E-9); +0.0-64.98.1.0-127,255.0-99;7;;Ch. $B;Data of billing period;Scheme 1;#$(E+1);Billing period $F +0.0-64.98.1.0-127,255.101-125;7;;Ch. $B;Data of billing period;Scheme 1;#$(E+1);$(F-100) last billing periods +0.0-64.98.1.0-127,255.126,255;7;;Ch. $B;Data of billing period;Scheme 1;#$(E+1);Unspecified number of most recent billing periods +0.0-64.98.2.0-127,255.0-99;7;;Ch. $B;Data of billing period;Scheme 2;#$(E+1);Billing period $F +0.0-64.98.2.0-127,255.101-125;7;;Ch. $B;Data of billing period;Scheme 2;#$(E+1);$(F-100) last billing periods +0.0-64.98.2.0-127,255.126,255;7;;Ch. $B;Data of billing period;Scheme 2;#$(E+1);Unspecified number of most recent billing periods +0.0-64.98.10.0-127.0-99;61;;Ch. $B;Register table objects, general use;;#$(E+1); +0.0-64.98.10.0-127.101-125;61;;Ch. $B;Register table objects, general use;;#$(E+1); +0.0-64.98.10.0-127.126,255;61;;Ch. $B;Register table objects, general use;;#$(E+1); +0.0-64.99.1.0-127.255;7;;Ch. $B;Load profile with recording period 1;;#$(E+1); +0.0-64.99.2.0-127.255;7;;Ch. $B;Load profile with recording period 2;;#$(E+1); +0.0-64.99.3.0.255;7;;Ch. $B;Load profile during test;;; +0.0-64.99.12.0-127.255;7;;Ch. $B;Connection profile;;#$(E+1); +0.0-64.99.13.0-127.255;7;;Ch. $B;GSM diagnostic profile;;#$(E+1); +0.0-64.99.14.0-127.255;7;;Ch. $B;Charge collection history (Payment metering);;#$(E+1); +0.0-64.99.15.0-127.255;7;;Ch. $B;Token credit history (Payment metering);;#$(E+1); +0.0-64.99.16.0-127.255;7;;Ch. $B;Parameter monitor log;;#$(E+1); +0.0-64.99.17.0-127.255;7;;Ch. $B;Token transfer log (Payment metering);;#$(E+1); +0.0-64.99.18.0-127.255;7;;Ch. $B;LTE monitoring profile;;#$(E+1); +0,1,7.0-64.99.98.0-127.255;7;;Ch. $B;Event log;;#$(E+1); +0,1,7.0-64.127.0-255.0-255.0-255;*;*;Ch. $B;Inactive object;;; +0,1,7.65-127.0-255.0-255.0-255.0-255;*;*;Ch. $B;Utility specific objects;;; +0.128-199.0-199,255.0-255.0-255.0-255;*;*;Manufacturer specific;;;; +0.0-199,255.128-199,240.0-255.0-255.0-255;*;*;Manufacturer specific;;;; +0.0-199,255.0-199,255.128-254.0-255.0-255;*;*;Manufacturer specific;;;; +0.0-199,255.0-199,255.0-255.128-254.0-255;*;*;Manufacturer specific;;;; +0.0-199,255.0-199,255.0-255.0-255.128-254;*;*;Manufacturer specific;;;; +1.0-64.0.0.0-9.255;1,3,4;6,9,10,17,18;Ch. $B;Electricity ID;;#$(E+1); +1.0-64.0.0.255.255;7,61;;Ch. $B;Complete combined electricity ID;;; +1.0-64.0.1.0.0-99,255;1,3,4;6,17,18;Ch. $B;Billing period counter (1);;;#$F +1.0-64.0.1.1.255;1,3,4;6,17,18;Ch. $B;No. of available billing periods (1);;; +1.0-64.0.1.2.0-99;1,3,4;6,9,21,25;Ch. $B;Time stamp of the billing period (1);;;#$F +1.0-64.0.1.2.255;1,3,4;6,9,21,25;Ch. $B;Time stamp of the most recent billing period closed (1);;; +1.0-64.0.1.3.0-99,255;1,3,4;6,17,18;Ch. $B;Billing period counter (2);;;$F +1.0-64.0.1.4.255;1,3,4;6,17,18;Ch. $B;No. of available billing periods (2);;; +1.0-64.0.1.5.0-99;1,3,4;6,9,21,25;Ch. $B;Time stamp of the billing period (2);;;#$F +1.0-64.0.1.5.255;1,3,4;6,9,21,25;Ch. $B;Time stamp of the most recent billing period closed (2);;; +1.0-64.0.2.0.255;1,3,4;9,10,17,18;Ch. $B;Active firmware identifier;;; +1.0-64.0.2.1.255;1,3,4;9,10,17,18;Ch. $B;Parameter record number;;; +1.0-64.0.2.1.1;1,3,4;9,10,17,18;Ch. $B;Parameter number line 1;;; +1.0-64.0.2.2.255;1,3,4;9,10,17,18;Ch. $B;Time switch program number;;; +1.0-64.0.2.3.255;1,3,4;9,10,17,18;Ch. $B;RCR program number;;; +1.0-64.0.2.4.255;1,3,4;9,10,17,18,22;Ch. $B;Meter connection diagram ID;;; +1.0-64.0.2.7.255;1,3,4;9,10,17,18;Ch. $B;Passive calendar name;;; +1.0-64.0.2.8.255;1,3,4;9,10,17,18;Ch. $B;Active firmware signature;;; +1.0-64.0.3.0.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Active energy, metrological LED;; +1.0-64.0.3.1.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Reactive energy, metrological LED;; +1.0-64.0.3.2.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Apparent energy, metrological LED;; +1.0-64.0.3.3.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Active energy, output pulse;; +1.0-64.0.3.4.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Reactive energy, output pulse;; +1.0-64.0.3.5.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Apparent energy, output pulse;; +1.0-64.0.3.6.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Volt-squared hours, metrological LED;; +1.0-64.0.3.7.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Ampere-squared hours, metrological LED;; +1.0-64.0.3.8.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Volt-squared hours, output pulse;; +1.0-64.0.3.9.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Meter constant;Ampere-squared hours, output pulse;; +1.0-64.0.4.0.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Reading factor for power;;; +1.0-64.0.4.1.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Reading factor for energy;;; +1.0-64.0.4.2.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Transformer ratio - current (numerator);;; +1.0-64.0.4.3.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Transformer ratio - voltage (numerator);;; +1.0-64.0.4.4.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Overall transformer ratio (numerator);;; +1.0-64.0.4.5.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Transformer ratio - current (denominator);;; +1.0-64.0.4.6.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Transformer ratio - voltage (denominator);;; +1.0-64.0.4.7.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Overall transformer ratio - (denominator);;; +1.0-64.0.5.1.1-4;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Demand limit;;;#$F +1.0-64.0.5.2.1-9;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Demand limit;;;Rate #$F +1.0-64.0.6.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Nominal voltage;;; +1.0-64.0.6.1.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Basic/nominal current;;; +1.0-64.0.6.2.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Nominal frequency;;; +1.0-64.0.6.3.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Maximum current;;; +1.0-64.0.6.4.0-99,255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Reference voltage for power quality measurement;;; +1.0-64.0.6.5.0-99,255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Reference voltage of aux. power supply;;; +1.0-64.0.7.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse values or constant;Active energy;; +1.0-64.0.7.1.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse values or constant;Reactive energy;; +1.0-64.0.7.2.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse values or constant;Apparent energy;; +1.0-64.0.7.3.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse values or constant;Volt-squared hours;; +1.0-64.0.7.4.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse values or constant;Ampere-squared hours;; +1.0-64.0.7.5.255;1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse values or constant;Unitless quantities;; +1.0-64.0.7.10.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse values or constant;Active energy, export;; +1.0-64.0.7.11.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse values or constant;Reactive energy, export;; +1.0-64.0.7.12.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse values or constant;Apparent energy, export;; +1.0-64.0.8.0.255;3,1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Measurement period 1, for averaging scheme 1;;; +1.0-64.0.8.1.255;3,1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Measurement period 2, for averaging scheme 2;;; +1.0-64.0.8.2.255;3,1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Measurement period 3, for instantaneous value;;; +1.0-64.0.8.3.255;3,1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Measurement period 4 for test value;;; +1.0-64.0.8.4.255;3,1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Recording interval 1, for load profile;;; +1.0-64.0.8.5.255;3,1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Recording interval 2, for load profile;;; +1.0-64.0.8.6.255;3,1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Billing period (Billing period 1 if there are two billing periods);;; +1.0-64.0.8.7.255;3,1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Billing period 2;;; +1.0-64.0.8.8.255;3,1,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Measurement period 4, for harmonics measurement;;; +1.0-64.0.9.0.255;3,1,4;6,17,18;Ch. $B;Time expired since last end of billing period;;; +1.0-64.0.9.1.255;1,3,4;9,27;Ch. $B;Local time;;; +1.0-64.0.9.2.255;1,3,4;9,26;Ch. $B;Local date;;; +1.0-64.0.9.3.255;1,3,4;;Ch. $B;Reserved for Germany;;; +1.0-64.0.9.4.255;1,3,4;;Ch. $B;Reserved for Germany;;; +1.0-64.0.9.5.255;1,3,4;6,15,17,18;Ch. $B;Week day (0...7);;; +1.0-64.0.9.6.255;1,3,4;9;Ch. $B;Time of last reset;;; +1.0-64.0.9.7.255;1,3,4;9;Ch. $B;Date of last reset;;; +1.0-64.0.9.8.255;3,1,4;6,15,17,18;Ch. $B;Output pulse duration;;; +1.0-64.0.9.9.255;3,4,1;6,15,17,18;Ch. $B;Clock synchronisation window;;; +1.0-64.0.9.10.255;3,4,1;22;Ch. $B;Clock synchronisation method;;; +1.0-64.0.9.11.255;3,4,1;6,15,17,18;Ch. $B;Clock time shift limit;;; +1.0-64.0.9.12.255;3,1,4;6,15,17,18;Ch. $B;Billing period reset lockout time (First billing period scheme if there are more than one);;; +1.0-64.0.9.13.255;3,1,4;6,17,18;Ch. $B;Time expired since last end of billing period (Second billing period scheme);;; +1.0-64.0.9.14.255;1,3,4;9;Ch. $B;Time of last reset (Second billing period scheme);;; +1.0-64.0.9.15.255;1,3,4;9;Ch. $B;Date of last reset (Second billing period scheme);;; +1.0-64.0.9.16.255;3,1,4;6,15,17,18;Ch. $B;Billing period reset lockout time (Second billing period scheme);;; +1.0-64.0.10.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Transformer magnetic losses Xm;;; +1.0-64.0.10.1.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Transformer iron losses RFe;;; +1.0-64.0.10.2.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Line resistance losses RCu;;; +1.0-64.0.10.3.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Line reactance losses Xs;;; +1.0-64.0.11.1.255;1,3;22;Ch. $B;Measurement algorithm;Active power;; +1.0-64.0.11.2.255;1,3;22;Ch. $B;Measurement algorithm;Active energy;; +1.0-64.0.11.3.255;1,3;22;Ch. $B;Measurement algorithm;Reactive power;; +1.0-64.0.11.4.255;1,3;22;Ch. $B;Measurement algorithm;Reactive energy;; +1.0-64.0.11.5.255;1,3;22;Ch. $B;Measurement algorithm;Apparent power;; +1.0-64.0.11.6.255;1,3;22;Ch. $B;Measurement algorithm;Apparent energy;; +1.0-64.0.11.7.255;1,3;22;Ch. $B;Measurement algorithm;Power factor calculation;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.0.0.255;3,4,5;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Billing period avg. (since last reset);Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.0.1-63.255;3,4,5;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Billing period avg. (since last reset);Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.0.0-63.0-99;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Billing period avg. (since last reset);Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.0.0-63.101-125;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Billing period avg. (since last reset);Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.0.0-63.126,255;,7;;Ch. $B;$1;Billing period avg. (since last reset);Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.1.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.1.1-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.1.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 1;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.1.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 1;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.1.0-63.126,255;,7;;Ch. $B;$1;Cum. min. 1;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.2.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.2.1-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.2.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 1;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.2.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 1;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.2.0-63.126,255;,7;;Ch. $B;$1;Cum. max. 1;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.3.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.3.1-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.3.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 1;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.3.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 1;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.3.0-63.126;,7;;Ch. $B;$1;Min. 1;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.4.0-63.255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.5.0-63.255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Last avg. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.6.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.6.1-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.6.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 1;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.6.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 1;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.6.0-63.126;,7;;Ch. $B;$1;Max. 1;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-13,15-20,21-33,35-40,41-53,55-60,61-73,75-80,84-92,100-107,124-126.7.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Inst. value;; +1.0-64.14,34,54,74.7.0,255.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Inst. value;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.8.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.8.1-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 1;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.8.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 1;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.8.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 1;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.8.0-63.126,255;,7;;Ch. $B;$1;Time integral 1;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.9.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.9.1-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.9.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 2;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.9.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 2;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.9.0-63.126,255;,7;;Ch. $B;$1;Time integral 2;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.10.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 3;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.10.1-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 3;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.10.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 3;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.10.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 3;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.10.0-63.126,255;,7;;Ch. $B;$1;Time integral 3;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.11.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.11.1-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.11.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 2;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.11.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 2;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.11.0-63.126,255;,7;;Ch. $B;$1;Cum. min. 2;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.12.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.12.1-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.12.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 2;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.12.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 2;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.12.0-63.126,255;,7;;Ch. $B;$1;Cum. max. 2;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.13.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.13.1-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.13.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 2;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.13.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 2;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.13.0-63.126;,7;;Ch. $B;$1;Min. 2;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.14.0-63.255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.15.0-63.255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Last avg. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.16.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.16.1-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 2;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.16.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 2;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.16.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 2;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.16.0-63.126;,7;;Ch. $B;$1;Max. 2;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.17.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 7;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.18.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 8;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.19.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 9;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.20.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 10;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.21.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 3;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.21.1-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 3;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.21.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 3;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.21.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. min. 3;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.21.0-63.126,255;,7;;Ch. $B;$1;Cum. min. 3;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.22.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 3;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.22.1-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 3;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.22.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 3;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.22.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Cum. max. 3;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.22.0-63.126,255;,7;;Ch. $B;$1;Cum. max. 3;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.23.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 3;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.23.1-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 3;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.23.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 3;Rate $E (0 is total);Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.23.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. 3;Rate $E (0 is total);$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.23.0-63.126;,7;;Ch. $B;$1;Min. 3;Rate $E (0 is total);Unspecified number of most recent billing periods +1.0-64.1-13,15-20,21-33,35-40,41-53,55-60,61-73,75-80,84-92,100-107,124-126.24.0.255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 3;; +1.0-64.14,34,54,74.24.0,255.255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 3;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.25.0-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Last avg. 3;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.26.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 3;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.26.1-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 3;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.26.0-63.0-99;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 3;;Billing period $F +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.26.0-63.101-125;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. 3;;$(F-100). last / $(F-100) last billing period(s) +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.26.0-63.126;,7;;Ch. $B;$1;Max. 3;;Unspecified number of most recent billing periods +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.27.0-63,255.255;5,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 5;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.28.0-63,255.255;5,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 6;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.29.0-63,255.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 5;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.30.0-63,255.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 6;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.49.0-63,255.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average for recording interval 1;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.50.0-63,255.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average for recording interval 2;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.51.0-63,255.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. for recording interval 1;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.52.0-63,255.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Min. for recording interval 2;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.53.0-63,255.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. for recording interval 1;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.54.0-63,255.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Max. for recording interval 2;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.46.0-63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Contracted value;Rate $E (0 is total); +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.55.0,255.255;3,5;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Test avg.;; +1.0-64.1-20,21-40,41-60,61-80,82,84-92,100-107,124-126.58.0,255.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Time integral 4;; +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.31.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Under limit threshold;Rate $E (0 is total);Threshold $F +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.32.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Under limit occurrence counter;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.33.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Under limit duration;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.34.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Under limit magnitude;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.35.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Over limit threshold;Rate $E (0 is total);Threshold $F +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.36.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Over limit occurrence counter;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.37.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Over limit duration;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.38.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Over limit magnitude;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.39.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Missing threshold;Rate $E (0 is total);Threshold $F +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.40.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Missing occurrence counter;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.41.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Missing duration;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.42.0-63.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Missing magnitude;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.43.0-63.0-99,255;1,3,4;6,15,17,18;Ch. $B;$1;Time threshold for under limit;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.44.0-63.0-99,255;1,3,4;6,15,17,18;Ch. $B;$1;Time threshold for over limit;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.45.0-63.0-99,255;1,3,4;6,15,17,18;Ch. $B;$1;Time threshold of missing magnitude;Rate $E (0 is total);Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.31.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Under limit threshold;Harmonic / Distortion factor $E;Threshold $F +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.32.0-120,124-127.0-99;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Under limit occurrence counter;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.33.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Under limit duration;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.34.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Under limit magnitude;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.35.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Over limit threshold;Harmonic / Distortion factor $E;Threshold $F +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.36.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Over limit occurrence counter;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.37.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Over limit duration;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.38.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Over limit magnitude;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.39.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Missing threshold;Harmonic / Distortion factor $E;Threshold $F +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.40.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Missing occurrence counter;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.41.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Missing duration;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.42.0-120,124-127.0-99,255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Missing magnitude;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.43.0-120,124-127.0-99,255;1,3,4;6,15,17,18;Ch. $B;$1;Time threshold for under limit;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.44.0-120,124-127.0-99,255;1,3,4;6,15,17,18;Ch. $B;$1;Time threshold for over limit;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.45.0-120,124-127.0-99,255;1,3,4;6,15,17,18;Ch. $B;$1;Time threshold of missing magnitude;Harmonic / Distortion factor $E;Billing period $F (255 is current) +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.31.0-63.0-99,255;21;;Ch. $B;$1;Inst. value, under limit threshold;Rate $E (0 is total); +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.35.0-63.0-99,255;21;;Ch. $B;$1;Inst. value, over limit threshold;Rate $E (0 is total); +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.39.0-63.0-99,255;21;;Ch. $B;$1;Inst. value, missing threshold;Rate $E (0 is total); +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.31.0-120,124-127.0-99,255;21;;Ch. $B;$1;Inst. value, under limit threshold;Harmonic / Distortion factor $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.35.0-120,124-127.0-99,255;21;;Ch. $B;$1;Inst. value, over limit threshold;Harmonic / Distortion factor $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.39.0-120,124-127.0-99,255;21;;Ch. $B;$1;Inst. value, missing threshold;Harmonic / Distortion factor $E; +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.4.0-63.0-99,255;21;;Ch. $B;$1;Current avg. 1;Rate $E (0 is total); +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.5.0-63.0-99,255;21;;;$1;Last avg. 1;Rate $E (0 is total); +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.14.0-63.0-99,255;21;;;$1;Current avg. 2;Rate $E (0 is total); +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.15.0-63.0-99,255;21;;;$1;Last avg. 2;Rate $E (0 is total); +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.24.0-63.0-99,255;21;;;$1;Current avg. 3;Rate $E (0 is total); +1.0-64.1-10,13,14,16-20,21-30,33,34,36-40,41-50,53,54,56-60,61-70,73,74,76-80,82,84-89,100-107.25.0-63.0-99,255;21;;Ch. $B;$1;Last avg. 3;Rate $E (0 is total); +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.4.0-120,124-127.0-99,255;21;;Ch. $B;$1;Current avg. 1;Harmonic / Distortion factor $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.5.0-120,124-127.0-99,255;21;;;$1;Last avg. 1;Harmonic / Distortion factor $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.14.0-120,124-127.0-99,255;21;;;$1;Current avg. 2;Harmonic / Distortion factor $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.15.0-120,124-127.0-99,255;21;;;$1;Last avg. 2;Harmonic / Distortion factor $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.24.0-120,124-127.0-99,255;21;;;$1;Current avg. 3;Harmonic / Distortion factor $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.25.0-120,124-127.0-99,255;21;;Ch. $B;$1;Last avg. 3;Harmonic / Distortion factor $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.7.1-120.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Inst. value;Harmonic $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.7.124.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Inst. value;Total Harmonic Distortion (THD); +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.7.125.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Inst. value;Total Demand Distortion (TDD); +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.7.126.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Inst. value;All harmonics; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.7.127.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Inst. value;All harmonics to nominal value ratio; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.24.1-120.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 3;Harmonic $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.24.124.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 3;Total Harmonic Distortion (THD); +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.24.125.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 3;Total Demand Distortion (TDD); +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.24.126.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 3;All harmonics; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.24.127.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 4;All harmonics to nominal value ratio; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.56.0.255;3,4,5;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 4;Fundamental + all harmonics; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.56.1-120.255;3,4,5;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 4;Harmonic $E; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.56.124.255;3,4,5;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 4;Total Harmonic Distortion (THD); +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.56.125.255;3,4,5;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 4;Total Demand Distortion (TDD); +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.56.126.255;3,4,5;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 4;All harmonics; +1.0-64.11,12,15,31,32,35,51,52,55,71,72,75,90-92,124-126.56.127.255;3,4,5;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Current avg. 4;All harmonics to nominal value ratio; +1.0-64.81.7.0.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L1) - U(L1);;; +1.0-64.81.7.1.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L2) - U(L1);;; +1.0-64.81.7.2.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L3) - U(L1);;; +1.0-64.81.7.4.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L1) - U(L1);;; +1.0-64.81.7.5.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L2) - U(L1);;; +1.0-64.81.7.6.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L3) - U(L1);;; +1.0-64.81.7.7.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L0) - U(L1);;; +1.0-64.81.7.10.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L1) - U(L2);;; +1.0-64.81.7.11.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L2) - U(L2);;; +1.0-64.81.7.12.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L3) - U(L2);;; +1.0-64.81.7.14.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L1) - U(L2);;; +1.0-64.81.7.15.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L2) - U(L2);;; +1.0-64.81.7.16.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L3) - U(L2);;; +1.0-64.81.7.17.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L0) - U(L2);;; +1.0-64.81.7.20.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L1) - U(L3);;; +1.0-64.81.7.21.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L2) - U(L3);;; +1.0-64.81.7.22.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L3) - U(L3);;; +1.0-64.81.7.24.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L1) - U(L3);;; +1.0-64.81.7.25.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L2) - U(L3);;; +1.0-64.81.7.26.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L3) - U(L3);;; +1.0-64.81.7.27.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L0) - U(L3);;; +1.0-64.81.7.40.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L1) - I(L1);;; +1.0-64.81.7.41.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L2) - I(L1);;; +1.0-64.81.7.42.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L3) - I(L1);;; +1.0-64.81.7.44.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L1) - I(L1);;; +1.0-64.81.7.45.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L2) - I(L1);;; +1.0-64.81.7.46.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L3) - I(L1);;; +1.0-64.81.7.47.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L0) - I(L1);;; +1.0-64.81.7.50.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L1) - I(L2);;; +1.0-64.81.7.51.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L2) - I(L2);;; +1.0-64.81.7.52.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L3) - I(L2);;; +1.0-64.81.7.54.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L1) - I(L2);;; +1.0-64.81.7.55.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L2) - I(L2);;; +1.0-64.81.7.56.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L3) - I(L2);;; +1.0-64.81.7.57.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L0) - I(L2);;; +1.0-64.81.7.60.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L1) - I(L3);;; +1.0-64.81.7.61.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L2) - I(L3);;; +1.0-64.81.7.62.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L3) - I(L3);;; +1.0-64.81.7.64.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L1) - I(L3);;; +1.0-64.81.7.65.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L2) - I(L3);;; +1.0-64.81.7.66.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L3) - I(L3);;; +1.0-64.81.7.67.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L0) - I(L3);;; +1.0-64.81.7.70.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L1) - I(L0);;; +1.0-64.81.7.71.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L2) - I(L0);;; +1.0-64.81.7.72.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of U(L3) - I(L0);;; +1.0-64.81.7.74.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L1) - I(L0);;; +1.0-64.81.7.75.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L2) - I(L0);;; +1.0-64.81.7.76.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L3) - I(L0);;; +1.0-64.81.7.77.255;3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Angle of I(L0) - I(L0);;; +1.0-64.81.7.255.255;61;;Ch. $B;Summary of phase angles;;; +1.0-64.12,32,52,72,124-126.32.0.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 10...<= 15%, 10ms...<= 100ms; +1.0-64.12,32,52,72,124-126.32.1.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 10...<= 15%, 100ms...<= 500ms; +1.0-64.12,32,52,72,124-126.32.2.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 10...<= 15%, 500ms...<= 1000ms; +1.0-64.12,32,52,72,124-126.32.3.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 10...<= 15%, 1s...<= 3s; +1.0-64.12,32,52,72,124-126.32.4.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 10...<= 15%, 3s...<= 20s; +1.0-64.12,32,52,72,124-126.32.5.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 10...<= 15%, 20s...<= 60s; +1.0-64.12,32,52,72,124-126.32.10.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 15...<= 30%, 10...<= 100ms; +1.0-64.12,32,52,72,124-126.32.11.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 15...<= 30%, 100...<= 500ms; +1.0-64.12,32,52,72,124-126.32.12.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 15...<= 30%, 500...<= 1000ms; +1.0-64.12,32,52,72,124-126.32.13.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 15...<= 30%, 1s...<= 3s; +1.0-64.12,32,52,72,124-126.32.14.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 15...<= 30%, 3s...<= 20s; +1.0-64.12,32,52,72,124-126.32.15.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 15...<= 30%, 20s...<= 60s; +1.0-64.12,32,52,72,124-126.32.20.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 30...<= 60%, 10...<= 100ms; +1.0-64.12,32,52,72,124-126.32.21.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 30...<= 60%, 100...<= 500ms; +1.0-64.12,32,52,72,124-126.32.22.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 30...<= 60%, 500...<= 1000ms; +1.0-64.12,32,52,72,124-126.32.23.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 30...<= 60%, 1s...<= 3s; +1.0-64.12,32,52,72,124-126.32.24.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 30...<= 60%, 3s...<= 20s; +1.0-64.12,32,52,72,124-126.32.25.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 30...<= 60%, 20s...<= 60s; +1.0-64.12,32,52,72,124-126.32.30.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 60...<= 90%, 10...<= 100ms; +1.0-64.12,32,52,72,124-126.32.31.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 60...<= 90%, 100...<= 500ms; +1.0-64.12,32,52,72,124-126.32.32.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 60...<= 90%, 500...<= 1000ms; +1.0-64.12,32,52,72,124-126.32.33.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 60...<= 90%, 1s...<= 3s; +1.0-64.12,32,52,72,124-126.32.34.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 60...<= 90%, 3s...<= 20s; +1.0-64.12,32,52,72,124-126.32.35.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 60...<= 90%, 20s...<= 60s; +1.0-64.12,32,52,72,124-126.32.40.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 90...<= 99%, 10...<= 100ms; +1.0-64.12,32,52,72,124-126.32.41.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 90...<= 99%, 100...<= 500ms; +1.0-64.12,32,52,72,124-126.32.42.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 90...<= 99%, 500...<= 1000ms; +1.0-64.12,32,52,72,124-126.32.43.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 90...<= 99%, 1s...<= 3s; +1.0-64.12,32,52,72,124-126.32.44.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 90...<= 99%, 3s...<= 20s; +1.0-64.12,32,52,72,124-126.32.45.255;1,3;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Unipede voltage dip;Class 90...<= 99%, 20s...<= 90s; +1.0-64.12,32,52,72,124-126.32.255.255;61;;Ch. $B;$1;Unipede voltage dip;Summary; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.1.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Active line losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.2.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Active line losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.3.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Active line losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.4.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Active transformer losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.5.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Active transformer losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.6.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Active transformer losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.7.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Active losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.8.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Active losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.9.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Active losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.10.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Reactive line losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.11.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Reactive line losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.12.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Reactive line losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.13.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Reactive transformer losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.14.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Reactive transformer losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.15.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Reactive transformer losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.16.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Reactive losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.17.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Reactive losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.18.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Sum Li Reactive losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.19.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Total transformer losses with normalised RFE = 1 MOhm;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.20.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Total line losses with normalised RCU = 10 Ohm;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.21.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Compensated active gross +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.22.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Compensated active net +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.23.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Compensated active gross -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.24.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Compensated active net -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.25.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Compensated reactive gross +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.26.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Compensated reactive net +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.27.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Compensated reactive gross -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.28.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Compensated reactive net -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.31.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Active line losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.32.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Active line losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.33.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Active line losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.34.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Active transformer losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.35.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Active transformer losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.36.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Active transformer losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.37.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Active losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.38.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Active losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.39.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Active losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.40.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Reactive line losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.41.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Reactive line losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.42.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Reactive line losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.43.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Reactive transformer losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.44.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Reactive transformer losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.45.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Reactive transformer losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.46.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Reactive losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.47.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Reactive losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.48.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 Reactive losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.49.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 A2h;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.50.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L1 V2h;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.51.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Active line losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.52.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Active line losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.53.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Active line losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.54.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Active transformer losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.55.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Active transformer losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.56.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Active transformer losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.57.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Active losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.58.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Active losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.59.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Active losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.60.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Reactive line losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.61.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Reactive line losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.62.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Reactive line losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.63.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Reactive transformer losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.64.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Reactive transformer losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.65.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Reactive transformer losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.66.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Reactive losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.67.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Reactive losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.68.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 Reactive losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.69.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 A2h;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.70.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L2 V2h;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.71.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Active line losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.72.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Active line losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.73.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Active line losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.74.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Active transformer losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.75.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Active transformer losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.76.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Active transformer losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.77.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Active losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.78.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Active losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.79.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Active losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.80.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Reactive line losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.81.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Reactive line losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.82.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Reactive line losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.83.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Reactive transformer losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.84.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Reactive transformer losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.85.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Reactive transformer losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.86.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Reactive losses +;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.87.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Reactive losses -;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.88.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 Reactive losses;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.89.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 A2h;$2;; +1.0-64.83.0-10,11-16,17-20,21-26,27-30,55,58.90.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;L3 V2h;$2;; +1.0-64.96.1.0-9.255;1,3;6,9,10,17,18;Ch. $B;Metering point ID;;#$(E+1); +1.0-64.96.1.255.255;7,61;;Ch. $B;Metering point ID;combined;; +1.0-64.96.5.0.255;1,3,4,63,7,61;3,4,6,9,17,18,21;Ch. $B;Internal operating status, global;;; +1.0-64.96.5.1.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +1.0-64.96.5.2.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +1.0-64.96.5.3.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +1.0-64.96.5.4.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +1.0-64.96.5.5.255;1,3,4;3,4,6,9,17,18,21;Ch. $B;Meter started status flag;;; +1.0-64.96.10.0.255;1,3,4;3,4,6,9,17,18,21;Ch. $B;Status information missing voltage;;; +1.0-64.96.10.1.255;1,3,4;3,4,6,9,17,18,21;Ch. $B;Status information missing current;;; +1.0-64.96.10.2.255;1,3,4;3,4,6,9,17,18,21;Ch. $B;Status information current without voltage;;; +1.0-64.96.10.3.255;1,3,4;3,4,6,9,17,18,21;Ch. $B;Status information aux. power supply;;; +1.0-64.96.50-99.0-255.0-255;1,3,4,7,61,63;*;Ch. $B;Manufacturer specific;;; +1.0-64.98.1.0-127,255.0-99;7;;Ch. $B;Data of billing period;Scheme 1;#$(E+1);Billing period $F +1.0-64.98.1.0-127,255.101-125;7;;Ch. $B;Data of billing period;Scheme 1;#$(E+1);$(F-100) last billing periods +1.0-64.98.1.0-127,255.126-255;7;;Ch. $B;Data of billing period;Scheme 1;#$(E+1);Unspecified number of most recent billing periods +1.0-64.98.2.0-127,255.0-99;7;;Ch. $B;Data of billing period;Scheme 2;#$(E+1);Billing period $F +1.0-64.98.2.0-127,255.101-125;7;;Ch. $B;Data of billing period;Scheme 2;#$(E+1);$(F-100) last billing periods +1.0-64.98.2.0-127,255.126-255;7;;Ch. $B;Data of billing period;Scheme 2;#$(E+1);Unspecified number of most recent billing periods +1.0-64.98.10.0-127.0-99;61;;Ch. $B;Register table object;General use;#$(E+1);Billing period $F +1.0-64.98.10.0-127.101-125;61;;Ch. $B;Register table object;General use;#$(E+1);$(F-100) last billing periods +1.0-64.98.10.0-127.126,255;61;;Ch. $B;Register table object;General use;#$(E+1);Unspecified number of most recent billing periods +1.0-64.99.1.0-127.255;7;;Ch. $B;Load profile with recording period 1;;#$(E+1); +1.0-64.99.2.0-127.255;7;;Ch. $B;Load profile with recording period 2;;#$(E+1); +1.0-64.99.3.0.255;7;;Ch. $B;Load profile during test;;; +1.0-64.99.10.1.255;7;;Ch. $B;Dips voltage profile;;; +1.0-64.99.10.2.255;7;;Ch. $B;Swells voltage profile;;; +1.0-64.99.10.3.255;7;;Ch. $B;Cuts voltage profile;;; +1.0-64.99.11.1-120,124-127.255;7;;Ch. $B;Voltage harmonic profile;;# $E; +1.0-64.99.12.1-120,124-127.255;7;;Ch. $B;Current harmonic profile;;# $E; +1.0-64.99.13.0.255;7;;Ch. $B;Voltage unbalance profile;;; +1.0-64.99.14.0-127.255;7;;Ch. $B;Power quality profile;;#$(E+1); +1.0-64.99.97.0-127.255;7;;Ch. $B;Power failure event log;;#$(E+1); +1.0-64.99.98.0-127.255;7;;Ch. $B;Event log;;#$(E+1); +1.0-64.99.99.0-127.255;7;;Ch. $B;Certification data log;;#$(E+1); +1.128-199.0-199,255.0-255.0-255.0-255;*;*;Man. specific;;;; +1.0-199,255.128-199,240.0-255.0-255.0-255;*;*;Man. specific;;;; +1.0-199,255.0-199,255.128-254.0-255.0-255;*;*;Man. specific;;;; +1.0-199,255.0-199,255.0-255.128-254.0-255;*;*;Man. specific;;;; +1.0-199,255.0-199,255.0-255.0-255.128-254;*;*;Man. specific;;;; +7.0-64.0.0.0-9.255;1,3,4;6,9,10,17,18;Ch. $B;Complete combined gas ID;;#$(E+1); +7.0-64.0.0.255.255;7,61;;Ch. $B;Gas ID;;; +7.0-64.0.1.0.0-99,255;1,3,4;6,17,18;Ch. $B;Billing period counter (1);;;#$F +7.0-64.0.1.1.255;1,3;6,17,18;Ch. $B;No. of available billing periods (1);;; +7.0-64.0.1.2.255;1,3,4;6,9,21,25;Ch. $B;Time stamp of the most recent billing period (1);;; +7.0-64.0.1.2.0-99;1,3,4;6,9,21,25;Ch. $B;Time stamp of the billing period (1);;;#$F +7.0-64.0.1.3.0-99,255;1,3,4;6,17,18;Ch. $B;Billing period counter (2);;;#$F +7.0-64.0.1.4.255;1,3;6,17,18;Ch. $B;No. of available billing periods (2);;; +7.0-64.0.1.5.255;1,3,4;6,9,21,25;Ch. $B;Time stamp of the most recent billing period (2);;; +7.0-64.0.1.5.0-99;1,3,4;6,9,21,25;Ch. $B;Time stamp of the billing period (2) VZ (last reset);;;#$F +7.0-64.0.1.6.0-99,255;1,3,4;6,17,18;Ch. $B;Billing period counter (3);;;#$F +7.0-64.0.1.7.255;1,3;6,17,18;Ch. $B;No. of available billing periods (3);;; +7.0-64.0.1.8.255;1,3,4;6,9,21,25;Ch. $B;Time stamp of the most recent billing period (3);;; +7.0-64.0.1.8.0-99;1,3,4;6,9,21,25;Ch. $B;Time stamp of the billing period (3) VZ (last reset);;;#$F +7.0-64.0.1.9.0-99,255;1,3,4;6,17,18;Ch. $B;Billing period counter (4);;;#$F +7.0-64.0.1.10.255;1,3;6,17,18;Ch. $B;No. of available billing periods (4);;; +7.0-64.0.1.11.255;1,3,4;6,9,21,25;Ch. $B;Time stamp of the most recent billing period (4);;; +7.0-64.0.1.11.0-99;1,3,4;6,9,21,25;Ch. $B;Time stamp of the billing period (4) VZ (last reset);;;#$F +7.0-64.0.2.0.255;1,3,4;6,9,10,17,18;Ch. $B;Program version;;; +7.0-64.0.2.1.255;1,3,4;6,9,10,17,18;Ch. $B;Firmware version;;; +7.0-64.0.2.2.255;1,3,4;6,9,10,17,18;Ch. $B;Software version;;; +7.0-64.0.2.3.255;1,3,4;6,9,10,17,18;Ch. $B;Device version;;; +7.0-64.0.2.8.255;1,3,4;6,9,10,17,18;Ch. $B;Active firmware signature;;; +7.0-64.0.2.10.255;1,3,4;6,9,10,17,18;Ch. $B;Number of device channels;;; +7.0-64.0.2.11.255;1,3,4;6,9,10,17,18;Ch. $B;Pressure sensor, serial no.;;; +7.0-64.0.2.12.255;1,3,4;6,9,10,17,18;Ch. $B;Temperature sensor, serial no.;;; +7.0-64.0.2.13.255;1,3,4;6,9,10,17,18;Ch. $B;Calculator, serial no.;;; +7.0-64.0.2.14.255;1,3,4;6,9,10,17,18;Ch. $B;Volume sensor, serial no.;;; +7.0-64.0.2.15.255;1,3,4;6,9,10,17,18;Ch. $B;Density sensor, serial no.;;; +7.0-64.0.2.16.255;1,3,4;6,9,10,17,18;Ch. $B;Sensor (medium irrespective), serial no.;;; +7.0-64.0.2.17.255;1,3,4;6,9,10,17,18;Ch. $B;Digital output configuration;;; +7.0-64.0.2.18.255;1,3,4;6,9,10,17,18;Ch. $B;Analogue output configuration;;; +7.0-64.0.3.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Output pulse constant;Volume forward at metering conditions;; +7.0-64.0.3.1.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Output pulse constant;Volume reverse at metering conditions;; +7.0-64.0.3.2.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Output pulse constant;Volume absolute at metering conditions;; +7.0-64.0.3.3.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Output pulse constant;Volume forward at base conditions;; +7.0-64.0.3.4.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Output pulse constant;Volume reverse at base conditions;; +7.0-64.0.3.5.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Output pulse constant;Volume absolute at base conditions;; +7.0-64.0.4.0.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Conversion factor;;#$(E+1); +7.0-64.0.4.1.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Conversion factor;;$#(E+1); +7.0-64.0.4.2.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Conversion factor;;$#(E+1); +7.0-64.0.4.3.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Conversion factor;;$(E+1); +7.0-64.0.4.4.255;1,3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Conversion factor;;#$(E+1); +7.0-64.0.5.1.1-4;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Threshold power for over-consumption relative to measurement period 2 for indexes and index differences;;Limit $#E; +7.0-64.0.5.1.11-14;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Threshold power for over-consumption relative to measurement period 3 for indexes and index differences;;Limit #($E-10); +7.0-64.0.5.2.1-9;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Threshold limit for over-consumption relative to measurement period 2 for indexes and index differences;;Rate #($E-10); +7.0-64.0.5.2.11-19;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Threshold limit for over-consumption relative to measurement period 3 for indexes and index differences;;Rate #($E-10); +7.0-64.0.5.3.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Max. contracted consumption for recording interval 1;;; +7.0-64.0.5.4.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Max. contracted consumption for recording interval 2;;; +7.0-64.0.5.11.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Absolute temperature, min. limit setting, Tmin;;; +7.0-64.0.5.12.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Absolute temperature, max. limit setting, Tmax;;; +7.0-64.0.5.13.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Absolute pressure, min. limit setting, Pmin;;; +7.0-64.0.5.14.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Absolute pressure, max. limit setting, Pmax;;; +7.0-64.0.6.1.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Nominal values volume sensor;Pressure;; +7.0-64.0.6.2.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Nominal values volume sensor;Temperature;; +7.0-64.0.6.3.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Nominal values volume sensor;Qmin;; +7.0-64.0.6.4.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Nominal values volume sensor;Qmax;; +7.0-64.0.7.0.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse constant;Volume forward at metering conditions;; +7.0-64.0.7.1.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse constant;Volume reverse metering conditions;; +7.0-64.0.7.2.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse constant;Volume absolute at metering conditions;; +7.0-64.0.7.3.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse constant;Volume forward at base conditions;; +7.0-64.0.7.4.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse constant;Volume reverse at base conditions;; +7.0-64.0.7.5.255;3,4;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Input pulse constant;Volume absolute at base conditions;; +7.0-64.0.8.1.255;1,3;6,17,18;Ch. $B;Recording interval 1, for profile;;; +7.0-64.0.8.2.255;1,3;6,17,18;Ch. $B;Recording interval 2, for profile;;; +7.0-64.0.8.3.255;1,3;6,17,18;Ch. $B;Measurement period 1, for average value 1;;; +7.0-64.0.8.4.255;1,3;6,17,18;Ch. $B;Measurement period 2, for average value 2;;; +7.0-64.0.8.5.255;1,3;6,17,18;Ch. $B;Measurement period 3, for instantaneous value;;; +7.0-64.0.8.6.255;1,3;6,17,18;Ch. $B;Measurement period 4, for test value;;; +7.0-64.0.8.10.255;1,3;6,17,18;Ch. $B;Billing period;;; +7.0-64.0.8.11.255;1,3;6,17,18;Ch. $B;Process interval 1, default value 15 minutes;;; +7.0-64.0.8.12.255;1,3;6,17,18;Ch. $B;Process interval 2, default value 1 hour;;; +7.0-64.0.8.13.255;1,3;6,17,18;Ch. $B;Process interval 3, default value 1 day;;; +7.0-64.0.8.14.255;1,3;6,17,18;Ch. $B;Process interval 4, default value 1 month;;; +7.0-64.0.8.15.255;1,3;6,17,18;Ch. $B;Process interval 5, for process value, since last event;;; +7.0-64.0.8.16.255;1,3;6,17,18;Ch. $B;Process interval 6, between last two events;;; +7.0-64.0.8.17.255;1,3;6,17,18;Ch. $B;Measurement period 1, for indexes and index differences, default value 15 minutes;;; +7.0-64.0.8.18.255;1,3;6,17,18;Ch. $B;Measurement period 2, for indexes and index differences, default value 1 hour;;; +7.0-64.0.8.19.255;1,3;6,17,18;Ch. $B;Measurement period 3, for indexes and index differences, no default value;;; +7.0-64.0.8.20.255;1,3;6,17,18;Ch. $B;Billing period 1, for indexes and index differences, default value 1 day;;; +7.0-64.0.8.21.255;1,3;6,17,18;Ch. $B;Billing period 2, for indexes and index differences, default value 1 month;;; +7.0-64.0.8.22.255;1,3;6,17,18;Ch. $B;Billing period 3, for indexes and index differences, default value 1 year;;; +7.0-64.0.8.23.255;1,3;6,17,18;Ch. $B;Billing period 4, for indexes and index differences, no default value;;; +7.0-64.0.8.25.255;1,3;6,17,18;Ch. $B;Averaging period 1, default value 5 minutes;;; +7.0-64.0.8.26.255;1,3;6,17,18;Ch. $B;Averaging period 2, default value 15 minutes;;; +7.0-64.0.8.27.255;1,3;6,17,18;Ch. $B;Averaging period 3, default value 1 hour;;; +7.0-64.0.8.28.255;1,3;6,17,18;Ch. $B;Averaging period 4, no default value;;; +7.0-64.0.8.29.255;1,3;6,17,18;Ch. $B;Averaging period 5, default value 1 day;;; +7.0-64.0.8.30.255;1,3;6,17,18;Ch. $B;Averaging period 6, default value 1 month;;; +7.0-64.0.8.31.255;1,3;6,17,18;Ch. $B;Averaging period 7, default value 1 year;;; +7.0-64.0.8.32.255;1,3;6,17,18;Ch. $B;Averaging period 8, no default value;;; +7.0-64.0.8.33.255;1,3;6,17,18;Ch. $B;Averaging period 9, since last event;;; +7.0-64.0.8.34.255;1,3;6,17,18;Ch. $B;Averaging period 10, between two last events;;; +7.0-64.0.8.35.255;1,3;6,17,18;Ch. $B;Number of sub-periods for averaging period 2;;; +7.0-64.0.9.0.255;1,3;6,17,18;Ch. $B;No. of days since last reset;;; +7.0-64.0.9.1.255;1,3,4;9,27;Ch. $B;Local time;;; +7.0-64.0.9.2.255;1,3,4;9,26;Ch. $B;Local date;;; +7.0-64.0.9.3.255;1,3,4;9,25;Ch. $B;Start of conventional gas day;;; +7.0-64.0.9.4.255;1,3,4;5,6,15,16,17,18;Ch. $B;Residual time shift;;; +7.0-64.0.9.6.255;1,3,4;9,25;Ch. $B;Time of last reset (First billing period scheme if there are more than one);;; +7.0-64.0.9.7.255;1,3,4;9,25;Ch. $B;Date of last reset (First billing period scheme if there are more than one);;; +7.0-64.0.9.11.255;1,3,4;6,17,18;Ch. $B;Clock time shift limit;;; +7.0-64.0.9.12.255;1,3,4;6,17,18;Ch. $B;Billing period reset lockout time (First billing period scheme if there are more than one);;; +7.0-64.0.9.13.255;1,3,4;6,17,18;Ch. $B;Time expired since last end of billing period (Second billing period scheme);;; +7.0-64.0.9.14.255;1,3,4;9,25;Ch. $B;Time of last reset (Second billing period scheme);;; +7.0-64.0.9.15.255;1,3,4;9,25;Ch. $B;Date of last reset (Second billing period scheme);;; +7.0-64.0.9.16.255;1,3,4;6,17,18;Ch. $B;Billing period reset lockout time (Second billing period scheme);;; +7.0-64.0.9.17.255;1,3,4;6,17,18;Ch. $B;Time expired since last end of billing period (Third billing period scheme);;; +7.0-64.0.9.18.255;1,3,4;9,25;Ch. $B;Time of last reset (Third billing period scheme);;; +7.0-64.0.9.19.255;1,3,4;9,25;Ch. $B;Date of last reset (Third billing period scheme);;; +7.0-64.0.9.20.255;1,3,4;6,17,18;Ch. $B;Billing period reset lockout time (Third billing period scheme);;; +7.0-64.0.9.21.255;1,3,4;6,17,18;Ch. $B;Time expired since last end of billing period (Fourth billing period scheme);;; +7.0-64.0.9.22.255;1,3,4;9,25;Ch. $B;Time of last reset (Fourth billing period scheme);;; +7.0-64.0.9.23.255;1,3,4;9,25;Ch. $B;Date of last reset (Fourth billing period scheme);;; +7.0-64.0.9.24.255;1,3,4;6,17,18;Ch. $B;Billing period reset lockout time (Fourth billing period scheme);;; +7.0-64.0.10.0.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Heating temperature f, current value;;; +7.0-64.0.10.1.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Heating temperature, average 15 minutes;;; +7.0-64.0.10.11.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Heating temperature, average 60 minutes;;; +7.0-64.0.10.21.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Heating temperature, average day;;; +7.0-64.0.10.31.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Heating temperature, average month;;; +7.0-64.0.11.0.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Ambient device temperature g, current value;;; +7.0-64.0.11.1.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Ambient device temperature, average 15 minutes;;; +7.0-64.0.11.11.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Ambient device temperature, average 60 minutes;;; +7.0-64.0.11.21.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Ambient device temperature, average day;;; +7.0-64.0.11.31.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Ambient device temperature, average month;;; +7.0-64.0.12.8.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Reference pressure of gas analysis;;; +7.0-64.0.12.9.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Reference temperature of gas analysis;;; +7.0-64.0.12.10.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Superior Wobbe number 0 °C;;; +7.0-64.0.12.11.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Inferior Wobbe number 0 °C;;; +7.0-64.0.12.12.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Methane number;;; +7.0-64.0.12.13.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Total sulphur;;; +7.0-64.0.12.14.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Hydrogen sulphide H2S;;; +7.0-64.0.12.15.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Mercaptans;;; +7.0-64.0.12.16.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Water dew point (DP H2O);;; +7.0-64.0.12.17.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Water (H2O) dew point outlet / normalised;;; +7.0-64.0.12.18.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Hydrocarbon dew point (DP CXHY);;; +7.0-64.0.12.19.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Inferior calorific value Hi,n;;; +7.0-64.0.12.20.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Water H2O;;; +7.0-64.0.12.45.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Density (of gas), base conditions;;; +7.0-64.0.12.46.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Relative density;;; +7.0-64.0.12.54.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Superior calorific value Hs,n;;; +7.0-64.0.12.60.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Nitrogen N2;;; +7.0-64.0.12.61.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Hydrogen H2;;; +7.0-64.0.12.62.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Oxygen O2;;; +7.0-64.0.12.63.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Helium He;;; +7.0-64.0.12.64.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Argon Ar;;; +7.0-64.0.12.65.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Carbon monoxide CO;;; +7.0-64.0.12.66.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Carbon dioxide CO2;;; +7.0-64.0.12.67.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Methane CH4;;; +7.0-64.0.12.68.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Ethene C2H4;;; +7.0-64.0.12.69.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Ethane C2H6;;; +7.0-64.0.12.70.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Propene C3H6;;; +7.0-64.0.12.71.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Propane C3H8;;; +7.0-64.0.12.72.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;i-butane i-C4H10;;; +7.0-64.0.12.73.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;n-butane n-C4H10;;; +7.0-64.0.12.74.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;neo-pentane neo-C5H12;;; +7.0-64.0.12.75.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;i-pentane i-C5H12;;; +7.0-64.0.12.76.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;n-pentane n-C5H12;;; +7.0-64.0.12.77.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Hexane C6H14;;; +7.0-64.0.12.78.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Hexane share higher hydrocarbons C6H14 %;;; +7.0-64.0.12.79.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Hexane+ C6 H14+;;; +7.0-64.0.12.80.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Heptane C7H16;;; +7.0-64.0.12.81.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Octane C8H18;;; +7.0-64.0.12.82.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Nonane C9H20;;; +7.0-64.0.12.83.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Decane C10H22;;; +7.0-64.0.12.84.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Tetrahydrothiophene;;; +7.0-64.0.13.1.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Internal pipe diameter;;; +7.0-64.0.13.2.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Orifice diameter;;; +7.0-64.0.13.3.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Pressure type (orifice fitting);;; +7.0-64.0.13.4.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Flow coefficient (alfa);;; +7.0-64.0.13.5.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Expansion coefficient (epsilon);;; +7.0-64.0.13.6.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Reflux coefficient;;; +7.0-64.0.13.7.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Isoentropic coefficient;;; +7.0-64.0.13.8.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Dynamic viscosity;;; +7.0-64.0.13.9.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;Differential pressure dp for cut off;;; +7.0-64.0.13.10.255;1,3,4;5,6,15,16,17,18;Ch. $B;Reynold number;;; +7.0-64.0.14.1.255;1,3,4;5,6,15,16,17,18,23,24;Ch. $B;K0 Densimeter Coefficient;;; +7.0-64.0.14.2.255;1,3,4;5,6,15,16,17,18;Ch. $B;K2 Densimeter Coefficient;;; +7.0-64.0.14.10.255;1,3,4;5,6,15,16,17,18,23,25;Ch. $B;Densimeter period for instanteneous measurement;;; +7.0-64.0.14.11.255;1,3,4;5,6,15,16,17,19;Ch. $B;Densimeter period for measurement period 15 minutes;;; +7.0-64.0.15.0-127.255;67;;Ch. $B;Sensor manager objects;;#$(E+1); +7.0-64.1-8,11-16,21-26,31-36,61-66.0.0-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Index, Value at metering conditions, Current;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.1.0-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Index, Corrected value, Current;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.2.0-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Index, Value at base conditions / “Converted value”, Current;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.3.0-63.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Index, Current redundant value at metering conditions, Current;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.6.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 1, Index difference, Value at metering conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.7.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 1, Index difference, Corrected value, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.8.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 1, Index difference, Value at base conditions, Current, period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.9.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 1, Index difference, Value at metering conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.10.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 1, Index difference, Corrected value, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.11.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 1, Index difference, Value at base conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.12.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 2, Index difference, Value at metering conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.13.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 2, Index difference, Corrected value, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.14.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 2, Index difference, Value at base conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.15.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 2, Index difference, Value at metering conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.16.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 2, Index difference, Corrected value, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.17.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 2, Index difference, Value at base conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.18.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 3, Index difference, Value at metering conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.19.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 3, Index difference, Corrected value, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.20.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 3, Index difference, Value at base conditions, Current, period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.21.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 3, Index difference, Value at metering conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.22.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 3, Index difference, Corrected value, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.23.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to measurement period 3, Index difference, Value at base conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.24.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Index, Value at metering conditions, Historical;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.25.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Index, Corrected value, Historical;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.26.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Index, Value at base conditions, Historical;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.27.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 1, Index difference, Value at metering conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.28.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 1, Index difference, Corrected value, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.29.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 1, Index difference, Value at base conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.30.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 1, Index difference, Value at metering conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.31.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 1, Index difference, Corrected value, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.32.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 1, Index difference, Value at base conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.33.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Maximum of Index differences over measurement period 1, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.34.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Maximum of Index differences over measurement period 1, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.35.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Maximum of Index differences over measurement period 1, Value at base conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.36.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Maximum of Index differences over measurement period 2, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.37.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Maximum of Index differences over measurement period 2, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.38.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Maximum of Index differences over measurement period 2, Value at base conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.39.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Maximum of Index differences over measurement period 3, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.40.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Maximum of Index differences over measurement period 3, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.41.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 1, Maximum of Index differences over measurement period 3, Value at base conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.42.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Index, Value at metering conditions, Historical;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.43.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Index, Corrected value, Historical;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.44.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Index, Value at base conditions, Historical;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.45.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 2, Index difference, Value at metering conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.46.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 2, Index difference, Corrected value, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.47.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 2, Index difference, Value at base conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.48.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 2, Index difference, Value at metering conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.49.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 2, Index difference, Corrected value, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.50.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 2, Index difference, Value at base conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.51.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over measurement period 1, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.52.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over measurement period 1, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.53.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over measurement period 1, Value at base conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.54.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over measurement period 2, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.55.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over measurement period 2, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.56.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over measurement period 2, Value at base conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.57.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over measurement period 3, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.58.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over measurement period 3, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.59.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over measurement period 3, Value at base conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.60.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over billing period 1, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.61.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over billing period 1, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.62.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 2, Maximum of Index differences over billing period 1, Value at base conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.63.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Index, Value at metering conditions, Historical;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.64.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Index, Corrected value, Historical;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.65.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Index, Value at base conditions, Historical;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.66.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 3, Index difference, Value at metering conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.67.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 3, Index difference, Corrected value, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.68.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 3, Index difference, Value at base conditions, Current period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.69.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 3, Index difference, Value at metering conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.70.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 3, Index difference, Corrected value, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.71.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 3, Index difference, Value at base conditions, Last period;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.72.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Maximum of Index differences over measurement period 1, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.73.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Maximum of Index differences over measurement period 1, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.74.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Maximum of Index differences over measurement period 1, Value at base conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.75.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Maximum of Index differences over measurement period 2, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.76.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Maximum of Index differences over measurement period 2, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.77.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Maximum of Index differences over measurement period 2, Value at base conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.78.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Maximum of Index differences over measurement period 3, Value at metering conditions;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.79.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Maximum of Index differences over measurement period 3, Corrected value;Rate $E (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.80.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 3, Maximum of Index differences over measurement period 3, Value at base conditions;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.81.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Index, Value at metering conditions, Historical;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.82.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Index, Corrected value, Historical;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.83.0-63.0-99,101-126;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Index, Value at base conditions, Historical;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.84.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 4, Index difference, Value at metering conditions, Current period;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.85.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 4, Index difference, Corrected value, Current period;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.86.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 4, Index difference, Value at base conditions, Current period;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.87.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 4, Index difference, Value at metering conditions, Last period;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.88.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 4, Index difference, Corrected value, Last period;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.89.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Value relative to billing period 4, Index difference, Value at base conditions, Last period;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.90.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Maximum of Index differences over measurement period 1, Value at metering conditions;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.91.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Maximum of Index differences over measurement period 1, Corrected value;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.92.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Maximum of Index differences over measurement period 1, Value at base conditions;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.93.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Maximum of Index differences over measurement period 2, Value at metering conditions;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.94.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Maximum of Index differences over measurement period 2, Corrected value;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.95.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Maximum of Index differences over measurement period 2, Value at base conditions;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.96.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Maximum of Index differences over measurement period 3, Value at metering conditions;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.97.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Maximum of Index differences over measurement period 3, Corrected value;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.1-8,11-16,21-26,31-36,61-66.98.0-63.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$0;Values relative to billing period 4, Maximum of Index differences over measurement period 3, Value at base conditions;Rate $E (0 is total) (0 is total);Billing period $F (255 is current) +7.0-64.43.0.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Instantaneous, Current value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.1.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Instantaneous, Corrected value;;Billing period $F (255 is current) +7.0-64.43.2.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Instantaneous, Value at base conditions / “Converted value”;;Billing period $F (255 is current) +7.0-64.43.13.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Instantaneous, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.15.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.16.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 1, Corrected value;;Billing period $F (255 is current) +7.0-64.43.17.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.18.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.19.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.20.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 1, Corrected value;;Billing period $F (255 is current) +7.0-64.43.21.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.22.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.23.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to measurement period 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.24.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to measurement period 2, Corrected value;;Billing period $F (255 is current) +7.0-64.43.25.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to measurement period 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.26.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to measurement period 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.27.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to measurement period 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.28.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to measurement period 3, Corrected value;;Billing period $F (255 is current) +7.0-64.43.29.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to measurement period 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.30.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to measurement period 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.31.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to billing period 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.32.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to billing period 1, Corrected value;;Billing period $F (255 is current) +7.0-64.43.33.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to billing period 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.34.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 1 relative to billing period 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.35.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.36.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 2, Corrected value;;Billing period $F (255 is current) +7.0-64.43.37.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.38.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.39.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.40.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 2, Corrected value;;Billing period $F (255 is current) +7.0-64.43.41.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.42.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.43.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to measurement period 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.44.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to measurement period 2, Corrected value;;Billing period $F (255 is current) +7.0-64.43.45.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to measurement period 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.46.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to measurement period 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.47.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to measurement period 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.48.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to measurement period 3, Corrected value;;Billing period $F (255 is current) +7.0-64.43.49.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to measurement period 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.50.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to measurement period 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.51.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to billing period 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.52.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to billing period 1, Corrected value;;Billing period $F (255 is current) +7.0-64.43.53.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to billing period 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.54.0.0-99,101-126,255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Maximum of last averages for averaging period 2 relative to billing period 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.55.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.56.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 3, Corrected value;;Billing period $F (255 is current) +7.0-64.43.57.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.58.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.59.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.60.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 3, Corrected value;;Billing period $F (255 is current) +7.0-64.43.61.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.62.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.63.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 4, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.64.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 4, Corrected value;;Billing period $F (255 is current) +7.0-64.43.65.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 4, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.66.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Current average for averaging period 4, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.43.67.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 4, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.43.68.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 4, Corrected value;;Billing period $F (255 is current) +7.0-64.43.69.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 4, Value at base conditions;;Billing period $F (255 is current) +7.0-64.43.70.0.0-99,101-126,255;3,4,5,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Flow rate;Last average for averaging period 4, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.0.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Instantaneous, Current value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.2.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Instantaneous Value at base conditions / “Converted value”;;Billing period $F (255 is current) +7.0-64.41,42,44-49.3.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Instantaneous, Backup value;;Billing period $F (255 is current) +7.0-64.41,42,44-49.10.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Instantaneous, Actual value;;Billing period $F (255 is current) +7.0-64.41,42,44-49.11.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Instantaneous, Preset value;;Billing period $F (255 is current) +7.0-64.41,42,44-49.13.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Instantaneous, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.15.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.16.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.17.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.18.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.19.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.20.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.21.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.22.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.23.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.24.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.25.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.26.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.27.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.28.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.29.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.30.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 1, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.31.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 1, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.32.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 1, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.33.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.34.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.35.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.36.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.37.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.38.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.39.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.40.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.41.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.42.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.43.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.44.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.45.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.46.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.47.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.48.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 2, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.49.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 2, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.50.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 2, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.51.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.52.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.53.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.54.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.55.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.56.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.57.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.58.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.59.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.60.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.61.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.62.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.63.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.64.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.65.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.66.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 3, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.67.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 3, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.68.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 3, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.69.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 4, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.70.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 4, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.71.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, current interval, process interval 4, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.72.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 4, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.73.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 4, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.74.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, current interval, process interval 4, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.75.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 4, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.76.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 4, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.77.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, current interval, process interval 4, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.78.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 4, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.79.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 4, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.80.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, last interval, process interval 4, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.81.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 4, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.82.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 4, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.83.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Minimum, last interval, process interval 4, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.84.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 4, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.85.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 4, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.86.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Maximum, last interval, process interval 4, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.87.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, process interval 5, interval since last event, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.88.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, process interval 5, interval since last event, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.89.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, process interval 5, interval since last event, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.90.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, process interval 6, interval between last two events, Value at metering conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.91.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, process interval 6, interval between last two events, Value at base conditions;;Billing period $F (255 is current) +7.0-64.41,42,44-49.92.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$1;Average, process interval 6, interval between last two events, Value at standard conditions;;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Process independent current value;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.1.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Weighted value (e.g. Superior calorific value);Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.11.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, current interval, averaging period 1;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.12.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, last interval, averaging period 1;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.13.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, current interval, averaging period 2;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.14.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, last interval, averaging period 2;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.15.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, current interval, averaging period 3;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.16.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, last interval, averaging period 3;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.17.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, current interval, averaging period 4;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.18.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, last interval, averaging period 4;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.19.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, current interval, averaging period 5;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.20.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, last interval, averaging period 5;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.21.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, current interval, averaging period 6;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.22.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, last interval, averaging period 6;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.23.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, current interval, averaging period 7;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.24.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, last interval, averaging period 7;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.25.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, current interval, averaging period 8;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.26.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, last interval, averaging period 8;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.27.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, averaging period 9, interval since last event;Billing period $F (255 is current) +7.0-64.51-55.0,2,3,10,11.28.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;$2;$3;Average, averaging period 10, interval between last two events;Billing period $F (255 is current) +7.0-64.51-55.12.0.255;3;;;Calculation method in use;;; +7.0-64.51-55.12.1-20.255;3;;;Calculation method supported;;$E; +7.0-64.70.8-20,60-84.0.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Process independent current value;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.1.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Weighted value (e.g. CO2 in [GJ / t]);Billing period $F (255 is current) +7.0-64.70.8-20,60-84.11.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, current interval, averaging period 1;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.12.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, last interval, averaging period 1;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.13.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, current interval, averaging period 2;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.14.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, last interval, averaging period 2;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.15.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, current interval, averaging period 3;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.16.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, last interval, averaging period 3;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.17.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, current interval, averaging period 4;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.18.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, last interval, averaging period 4;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.19.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, current interval, averaging period 5;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.20.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, last interval, averaging period 5;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.21.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, current interval, averaging period 6;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.22.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, last interval, averaging period 6;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.23.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, current interval, averaging period 7;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.24.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, last interval, averaging period 7;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.25.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, current interval, averaging period 8;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.26.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, last interval, averaging period 8;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.27.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, averaging period 9, interval since last event;Billing period $F (255 is current) +7.0-64.70.8-20,60-84.28.255;3,4,7;5,6,9,10,13,15,16,17,18,20,21,23,24;Ch. $B;Natural gas analysis;$4;Average, averaging period 10, interval between last two events;Billing period $F (255 is current) +7.0-64.96.5.0.255;1,3,4,63,7,61;3,4,6,9,17,18,21;Ch. $B;Internal operating status, global;;; +7.0-64.96.5.1.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +7.0-64.96.5.2.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +7.0-64.96.5.3.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +7.0-64.96.5.4.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +7.0-64.96.5.5.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +7.0-64.96.5.6.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +7.0-64.96.5.7.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +7.0-64.96.5.8.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +7.0-64.96.5.9.255;1,3,4,63;3,4,6,9,17,18,21;Ch. $B;Internal operating status;;(status word #$E); +7.0-64.96.50-99.0-255.0-255;1,3,4,7,61,63;*;Ch. $B;Manufacturer specific;;; +7.0-64.98.1.0-127.255;7;;Ch. $B;Gas related data of billing period (with billing period scheme 1 if there are more than one schemes available);;; +7.0-64.98.2.0-127.255;7;;Ch. $B;Gas related data of billing period (with billing period scheme 2);;; +7.0-64.98.3.0-127.255;7;;Ch. $B;Gas related data of billing period (with billing period scheme 3);;; +7.0-64.98.4.0-127.255;7;;Ch. $B;Gas related data of billing period (with billing period scheme 4);;; +7.0-64.98.11.0-127.255;7;;Ch. $B;Gas related data of event triggered billing profile;;; +7.0-64.99.1.4.255;7;;Ch. $B;Load profile with recording interval 1;;#$(E+1); +7.0-64.99.2.4.255;7;;Ch. $B;Load profile with recording interval 2;;#$(E+1); +7.0-64.99.3.4.255;7;;Ch. $B;Profile of maxima with recording interval 1;;; +7.0-64.99.4.4.255;7;;Ch. $B;Profile of maxima with recording interval 2;;; +7.0-64.99.1-8,11-16,21-26,31-36,61-66.0-3,6-98.0-99,101-126,255;7;;;Load profile for indexes and index differences of volume, mass and energy;$5;$6;Billing period $F (255 is current) +7.0-64.99.41.255.255;;;;;Absolute temperature;; +7.0-64.99.42.255.255;;;;;Absolute pressure;; +7.0-64.99.44.255.255;;;;;Velocity of sound;; +7.0-64.99.45.255.255;;;;;Density (of gas);; +7.0-64.99.46.255.255;;;;;Relative density;; +7.0-64.99.47.255.255;;;;;Gauge pressure;; +7.0-64.99.48.255.255;;;;;Differential pressure;; +7.0-64.99.49.255.255;;;;;Density of air;; +7.0-64.99.41,42,44-49.0,2,13,24-32,42-50,60-68,78-86,90-92.0-99,101-126,255;7;;Ch. $B;Load profile for process values;$7;$8;Billing period $F (255 is current) +7.0-64.99.43.0-2,13,19-22,39-42,59-62,67-70.0-99,101-126,255;7;;Ch. $B;Load profile for;Flow rate;$9;Billing period $F (255 is current) +7.0-64.99.99.0.255;7;;Ch. $B;Certification data log;;#$(E+1);Billing period $F (255 is current) +7.0-64.99.99.1.255;7;;Ch. $B;Load profile with recording interval 15 minutes;;;Billing period $F (255 is current) +7.0-64.99.99.2.255;7;;Ch. $B;Load profile with recording interval 60 minutes;;;Billing period $F (255 is current) +7.0-64.99.99.3.255;7;;Ch. $B;Load profile with recording interval day;;;Billing period $F (255 is current) +7.0-64.99.99.4.255;7;;Ch. $B;Load profile with recording interval month;;;Billing period $F (255 is current) +7.128-254.0-255.0-255.0-255.0-255;*;*;Ch. $B;Manufacturer specific;;; +7.0-64.128-254.0-255.0-255.0-255;*;*;Ch. $B;Manufacturer specific;;; +7.0-64.0-254.128-254.0-255.0-255;*;*;Ch. $B;Manufacturer specific;;; +7.0-64.0-254.0-254.128-254.0-255;*;*;Ch. $B;Manufacturer specific;;; +7.0-64.0-254.0-254.0-254.128-254;*;*;Ch. $B;Manufacturer specific;;; diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ReleaseRequestReason.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ReleaseRequestReason.py new file mode 100644 index 0000000..5cf5647 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ReleaseRequestReason.py @@ -0,0 +1,49 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ReleaseRequestReason(GXIntEnum): + """ + RequestTypes enumerates the replies of the server to a client's request, + indicating the request type. + """ + + #Client closes connection as normal. + NORMAL = 0 + + # Client closes connection as urgent. + URGENT = 1 + + #Client closes connection user defined reason. + USER_DEFINED = 30 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ReleaseResponseReason.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ReleaseResponseReason.py new file mode 100644 index 0000000..b399b87 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ReleaseResponseReason.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ReleaseResponseReason(GXIntEnum): + """ + RequestTypes enumerates the replies of the server to a client's request, + indicating the request type. + """ + # Meter closes connection as normal. + NORMAL = 0 + # Client closes connection as urgent. + NOT_FINISHED = 1 + + # Client closes connection user defined reason. + USER_DEFINED = 30 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SaudiArabia.txt b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SaudiArabia.txt new file mode 100644 index 0000000..b0f348f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SaudiArabia.txt @@ -0,0 +1,96 @@ +1;0.1.94.96.2.255;0;Storage Device Failure Date/Time +1;0.1.94.96.22.255;0;LCD time-to-return to default screen +1;0.1.94.96.23.255;0;LCD auto scroll time +1;0.1.94.96.24.255;0;LCD night off option +1;0.1.94.96.25.255;0;Start-stop time setting of LCD night off +9;0.1.94.96.10.255;0;Meter Program Configuration +9;0.1.94.96.11.255;0;Meter Program Configuration Status +1;0.96.94.96.8.255;0;Remote output relay control Selection of the input control signals +1;0.96.94.96.9.255;0;Auto recovery operation times of current over limit +1;0.96.94.96.10.255;0;Auto recovery times setting of current over limit +1;0.96.94.96.11.255;0;Release times of current over limit setting +1;0.96.94.96.12.255;0;Data Transport Security Mode +1;0.96.94.96.15.255;0;Number of Global Meter Reset +1;0.96.94.96.16.255;0;Number of wrong cable connection +1;1.96.12.33.0.255;0;Sag setting value +1;0.1.94.96.3.255;0;Number of power restore (in all three phases) +1;0.1.94.96.4.255;0;Number of power restore (phases L1) +1;0.1.94.96.5.255;0;Number of power restore (phases L2) +1;0.1.94.96.6.255;0;Number of power restore (phases L3) +3;1.0.32.128.0.255;0;R-Y Phase average voltage +3;1.0.52.128.0.255;0;Y-B Phase average voltage +3;1.0.72.128.0.255;0;B-R Phase average voltage +3;1.0.130.7.0.255;0;L1-L2 Instantaneous voltage +3;1.0.131.7.0.255;0;L2-L3 Instantaneous voltage +3;1.0.132.7.0.255;0;L3-L1 Instantaneous voltage +1;0.0.94.96.22.255;0;LCD time-to-return to default screen +1;0.0.94.96.23.255;0;LCD auto scroll time +1;0.0.94.96.24.255;0;LCD night off option +1;0.0.94.96.25.255;0;Start-stop time setting of LCD night off +1;0.1.94.96.3.255;0;Number of power restore (in all three phases) +1;0.1.94.96.4.255;0;Number of power restore (Phase L1) +1;0.1.94.96.5.255;0;Number of power restore (Phase L2) +1;0.1.94.96.6.255;0;Number of power restore (Phase L3) +1;0.0.96.11.1.255;0;Number of clock change +1;0.0.96.11.2.255;0;Number of EOB reset +1;0.0.96.11.3.255;0;Number of manual reset +1;0.0.96.11.4.255;0;Number of auto-reset +1;0.0.96.11.5.255;0;Number of Current Missing +1;0.0.96.11.6.255;0;Number of top cover open +1;0.0.96.11.7.255;0;Number of terminal cover open +1;0.0.96.11.8.255;0;Number of phase-neutral swap +1;0.0.96.11.9.255;0;Number of reverse current +1;0.0.96.11.10.255;0;Number of magnetic interference tampering +1;0.0.96.15.0.255;0;Number of phase-neutral bypassing +1;0.0.96.15.1.255;0;Number of phase sequence reversal +1;1.0.91.40.0.255;0;Number of neutral line missing +1;1.0.12.40.0.255;0;Number of voltage cut +1;0.0.96.15.2.255;0;Number of password changes +1;0.96.96.4.1.255;0;Number of state change of output relay control signals +1;0.96.96.4.2.255;0;Number of selection of the input control signals +1;0.96.96.4.3.255;0;Number of auto recovery operation times of current over limit +1;0.96.96.4.4.255;0;Number of auto recovery times setting of current over limit +1;0.96.96.4.5.255;0;Number of current over limit release +1;0.96.96.4.6.255;0;Number of abnormal temperature +1;0.96.96.4.7.255;0;Number of maximum current +1;0.96.96.4.8.255;0;Number of harmonics (THD) limit +1;0.96.12.32.0.255;0;Number of Sag limit +1;0.96.12.36.0.255;0;Number of Swell limit +1;0.96.94.96.15.255;0;Number of Global Meter Reset +1;0.96.94.96.16.255;0;Number of wrong cable connection +1;0.96.99.98.19.255;0;Security association Event +1;0.96.15.128.0.255;0;Meter reading parameter +7;1.0.99.97.0.255;1;Event log (Power failure) +7;1.0.99.98.2.255;1;Event log (Power restore) +7;1.0.99.98.3.255;1;Event log (Time change: from time) +7;1.0.99.98.4.255;1;Event log (Time change: to time) +7;1.0.99.98.5.255;1;Event log (EOB reset) +7;1.0.99.98.6.255;1;Event log (Manual reset) +7;1.0.99.98.7.255;1;Event log (Auto-reset) +7;1.0.99.98.8.255;1;Event log (Meter configuration change) +7;1.0.99.98.9.255;1;Event Log (Communication port log) +7;1.0.99.98.10.255;1;Event log (Storage Device /Memory) +7;1.0.99.98.11.255;1;Event log (Power line cut) +7;1.0.99.98.12.255;1;Event log (Tamper 1) +7;1.0.99.98.13.255;1;Event log (Tamper 2) +7;1.0.99.98.14.255;1;Event log (Current/voltage limit violation) +7;1.0.99.98.15.255;1;Event Log (Output relay control) +7;1.0.99.98.16.255;1;Event Log (Password changes) +7;1.96.99.98.12.255;1;State of output relay control signals (ON/OFF) +7;1.96.99.98.2.255;1;Selection of the input control signals +7;1.96.99.98.3.255;1;Auto recovery operation times of current over limit +7;1.96.99.98.4.255;1;Auto recovery times setting of current over limit +7;1.96.99.98.13.255;1;Current over limit release +7;1.96.99.98.6.255;1;Temperature over limit +7;1.96.99.98.7.255;1;Maximum current +7;1.96.99.98.8.255;1;Harmonics (THD) limit +7;1.96.99.10.1.255;1;Sag limit +7;1.96.99.10.2.255;1;Swell limit +7;1.96.99.98.19.255;1;Security association Event +7;1.96.99.98.20.255;1;Display Roll-Over to Zero Event +1;0.1.94.96.11.255;0;Meter Program Configuration Status +1;0.1.94.96.12.255;0;Data Transport Security Mode +1;0.1.94.96.22.255;0;LCD time-to-return to default screen +1;0.1.94.96.23.255;0;LCD auto scroll time +1;0.1.94.96.24.255;0;LCD night off option +1;0.1.94.96.25.255;0;Start-stop time setting of LCD night off \ No newline at end of file diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SerialnumberCounter.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SerialnumberCounter.py new file mode 100644 index 0000000..14456e0 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SerialnumberCounter.py @@ -0,0 +1,105 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class SerialNumberCounter: + @classmethod + def __getValues(cls, expressions): + """ + Get values to count together. + """ + values = list() + last = 0 + index = 0 + for ch in expressions: + if ch in ('%', '+', '-', '*', '/'): + values.append(expressions[last: index]) + values.append(str(ch)) + last = index + 1 + index += 1 + if index != last: + values.append(expressions[last:]) + return values + + @classmethod + def __getValue(cls, value, sn): + if value == "sn": + return sn + return int(value) + + # Count serial number using formula. + # sn: Serial number + # formula: Formula to used. + @classmethod + def count(cls, sn, formula): + values = SerialNumberCounter.__getValues(SerialNumberCounter.__formatString(formula)) + if len(values) % 2 == 0: + raise ValueError("Invalid serial number formula.") + str_ = None + value = SerialNumberCounter.__getValue(values[0], sn) + index = 1 + while index != len(values): + str_ = values[index] + if str_ == "%": + value = value % SerialNumberCounter.__getValue(values[index + 1], sn) + elif str_ == "+": + value = value + SerialNumberCounter.__getValue(values[index + 1], sn) + elif str_ == "-": + value = value - SerialNumberCounter.__getValue(values[index + 1], sn) + elif str_ == "*": + value = value * SerialNumberCounter.__getValue(values[index + 1], sn) + elif str_ == "/": + value = value / SerialNumberCounter.__getValue(values[index + 1], sn) + else: + raise ValueError("Invalid serial number formula.") + index += 2 + return value + + # + # Produce formatted String by the given math expression. + # + # @param expression + # Unformatted math expression. + # Formatted math expression. + # + @classmethod + def __formatString(cls, expression): + if not expression: + raise ValueError("Expression is null or empty") + sb = "" + for ch in expression.lower(): + if ch in ('(', ')'): + raise ValueError("Invalid serial number formula.") + # Is not white space. + if ch != ' ': + sb += ch + return sb diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ServiceError.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ServiceError.py new file mode 100644 index 0000000..1a3729e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ServiceError.py @@ -0,0 +1,69 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ServiceError(GXIntEnum): + """ + ServiceError enumerates service errors. + """ + + # Application error. + APPLICATION_REFERENCE = 0 + + # Hardware error. + HARDWARE_RESOURCE = 1 + + # Vde state error. + VDE_STATE_ERROR = 2 + + # Service error. + SERVICE = 3 + + # Definition error. + DEFINITION = 4 + + # Access error. + ACCESS = 5 + + # Initiate error. + INITIATE = 6 + + # LoadDataSet error. + LOAD_DATASET = 7 + + # Task error. + TASK = 8 + + # Other error describes manufacturer specific error code. + OTHER_ERROR = 9 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SetRequestType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SetRequestType.py new file mode 100644 index 0000000..3552e5e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SetRequestType.py @@ -0,0 +1,52 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class SetRequestType(GXIntEnum): + """Enumerates Set request types.""" + + # Normal Set. + NORMAL = 1 + + # Set with first data block. + FIRST_DATA_BLOCK = 2 + + # Set with data block. + WITH_DATA_BLOCK = 3 + + # Set with list . + WITH_LIST = 4 + + # Set with list and first data block. + WITH_LIST_AND_WITH_FIRST_DATABLOCK = 5 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SetResponseType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SetResponseType.py new file mode 100644 index 0000000..bb35adb --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SetResponseType.py @@ -0,0 +1,61 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class SetResponseType(GXIntEnum): + """Enumerates Set response types.""" + # + # Normal set response. + # + NORMAL = 1 + + # + # Set response in data blocks. + # + DATA_BLOCK = 2 + + # + # Set response in last data block. + # + LAST_DATA_BLOCK = 3 + + # + # Set response in last data block with list. + # + LAST_DATA_BLOCK_WITH_LIST = 4 + + # + # Set with list response. + # + WITH_LIST = 5 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SingleReadResponse.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SingleReadResponse.py new file mode 100644 index 0000000..8428f58 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SingleReadResponse.py @@ -0,0 +1,56 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class SingleReadResponse(GXIntEnum): + """Enumerates single read response types.""" + # + # Normal data. + # + DATA = 0 + + # + # Error has occurred on read. + # + DATA_ACCESS_ERROR = 1 + + # + # Return data as blocks. + # + DATA_BLOCK_RESULT = 2 + + # + # Return block number. + # + BLOCK_NUMBER = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SingleWriteResponse.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SingleWriteResponse.py new file mode 100644 index 0000000..40666ce --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/SingleWriteResponse.py @@ -0,0 +1,54 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class SingleWriteResponse(GXIntEnum): + """Enumerates single write response types.""" + + #pylint: disable=too-few-public-methods + + # + # Write succeeded. + # + SUCCESS = 0 + + # + # Write error has occurred. + # + DATA_ACCESS_ERROR = 1 + + # + # Get next block. + # + BLOCK_NUMBER = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/Spain.txt b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/Spain.txt new file mode 100644 index 0000000..de0f243 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/Spain.txt @@ -0,0 +1,62 @@ +# IC | OBIS | VERSION | DESCRIPTION | UI Type +3;1.1.94.34.1.255;0;Clock Time Shift Invalid Limit +3;0.1.94.34.1.255;0;Active Demand Control Threshold Rate 1 Contract 1 +3;0.1.94.34.2.255;0;Active Demand Control Threshold Rate 2 Contract 1 +3;0.1.94.34.3.255;0;Active Demand Control Threshold Rate 3 Contract 1 +3;0.1.94.34.4.255;0;Active Demand Control Threshold Rate 4 Contract 1 +3;0.1.94.34.21.255;0;Active Demand Control Threshold Rate 1 Contract 3 +3;0.1.94.34.22.255;0;Active Demand Control Threshold Rate 2 Contract 3 +3;0.1.94.34.23.255;0;Active Demand Control Threshold Rate 3 Contract 3 +3;0.1.94.34.24.255;0;Active Demand Control Threshold Rate 4 Contract 3 +3;0.1.94.34.11.255;0;Passive Demand Control Threshold Rate 1 Contract 1 +3;0.1.94.34.12.255;0;Passive Demand Control Threshold Rate 2 Contract 1 +3;0.1.94.34.13.255;0;Passive Demand Control Threshold Rate 3 Contract 1 +3;0.1.94.34.14.255;0;Passive Demand Control Threshold Rate 4 Contract 1 +3;0.1.94.34.41.255;0;Passive Demand Control Threshold Rate 1 Contract 3 +3;0.1.94.34.42.255;0;Passive Demand Control Threshold Rate 2 Contract 3 +3;0.1.94.34.43.255;0;Passive Demand Control Threshold Rate 3 Contract 3 +3;0.1.94.34.44.255;0;Passive Demand Control Threshold Rate 4 Contract 3 +1;0.0.94.34.41.255;0;Passive end of billing period 1 contract 1;25 +1;0.0.94.34.42.255;0;Passive end of billing period 1 contract 2;25 +1;0.0.94.34.43.255;0;Passive end of billing period 1 contract 3;25 +1;0.0.94.34.11.255;0;Time stamp of billing period 1 last reset contract 1;25 +1;0.0.94.34.12.255;0;Time stamp of billing period 1 last reset contract 2;25 +1;0.0.94.34.13.255;0;Time stamp of billing period 1 last reset contract 3;25 +3;0.0.94.34.60.255;0;Threshold for long power failure +3;0.0.94.34.100.255;0;Duration of current long power failures in any phases +3;0.0.94.34.101.255;0;Duration of current long power failures in phase L1 +3;0.0.94.34.102.255;0;Duration of current long power failures in phase L2 +3;0.0.94.34.103.255;0;Duration of current long power failures in phase L3 +1;0.0.94.34.80.255;Time trigger for voltage sags, for voltage swell or long power failure;25 +1;0.1.94.34.105.255;0;Standard Event Log Filter +1;0.1.94.34.106.255;0;Fraud Detection Log Filter +1;0.1.94.34.107.255;0;Power Quality Non-finished Event Log Filter +1;0.1.94.34.108.255;0;Power Quality finished Event Log Filter +1;0.1.94.34.111.255;0;Import power Contract Event Log Filter +1;0.1.94.34.112.255;0;Firmware Event Log Filter +1;0.1.94.34.113.255;0;Synchronization Event Log Filter +1;0.1.94.34.114.255;0;Disconnect Control log Filter +1;0.1.94.34.115.255;0;Export power Contract Event Log Filter +70;0.1.94.34.20.255;0;Previous Disconnect Control +1;0.1.94.34.30.255;0;Import Active Power over threshold status +3;0.1.94.34.31.255;0;Currently Import Active Power Threshold +1;0.1.94.34.32.255;0;Export Active Power over threshold status +3;0.1.94.34.33.255;0;Currently Export Active Power Threshold +3;0.0.94.34.70.255;0;Threshold for Demand close to contract power +3;0.0.94.34.110.255;0;Time for Scroll Display +3;0.0.94.34.51.255;0;Timeout open session for reading client +3;0.0.94.34.52.255;0;Timeout open session for Management client +1;1.0.94.34.130.255;0;Time stamp for new calendar activation +1;1.0.94.34.90.255;0;Number of voltage sags for average voltage in all 3 phases +3;1.0.94.34.91.255;0;Duration of voltage sags for average voltage in all 3 phases +1;1.0.94.34.92.255;0;Number of voltage swells for average voltage in all 3 phases +3;1.0.94.34.93.255;0;Duration of voltage swells for average voltage in all 3 phases +1;1.1.94.34.100.255;0;Active Quadrant +1;1.1.94.34.101.255;0;Active Quadrant L1 +1;1.1.94.34.102.255;0;Active Quadrant L2 +1;1.1.94.34.103.255;0;Active Quadrant L3 +1;1.1.94.34.104.255;0;Phase presence +3;0.0.94.34.54.255;0;Timeout open session for security client through PLC channel +3;0.0.94.34.55.255;0;Timeout open session for security client through PLC channel for current association +1;0.1.94.34.116.255;0;Correct security operations Event log Filter +1;0.1.94.34.117.255;0;Failed security operations Event log filter diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorGeneralTags.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorGeneralTags.py new file mode 100644 index 0000000..001a46a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorGeneralTags.py @@ -0,0 +1,64 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class TranslatorGeneralTags(GXIntEnum): + APPLICATION_CONTEXT_NAME = 0xA1 + NEGOTIATED_QUALITY_OF_SERVICE = 0xBE00 + PROPOSED_DLMS_VERSION_NUMBER = 0xBE01 + PROPOSED_MAX_PDU_SIZE = 0xBE02 + PROPOSED_CONFORMANCE = 0xBE03 + VAA_NAME = 0xBE04 + NEGOTIATED_CONFORMANCE = 0xBE05 + NEGOTIATED_DLMS_VERSION_NUMBER = 0xBE06 + NEGOTIATED_MAX_PDU_SIZE = 0xBE07 + CONFORMANCE_BIT = 0xBE08 + PROPOSED_QUALITY_OF_SERVICE = 0xBE09 + SENDER_ACSE_REQUIREMENTS = 0x8A + RESPONDER_ACSE_REQUIREMENT = 0x88 + RESPONDING_MECHANISM_NAME = 0x89 + CALLING_MECHANISM_NAME = 0x8B + CALLING_AUTHENTICATION = 0xAC + RESPONDING_AUTHENTICATION = 0x80 + ASSOCIATION_RESULT = 0xA2 + RESULT_SOURCE_DIAGNOSTIC = 0xA3 + ACSE_SERVICE_USER = 0xA301 + RESPONDING_AP_TITLE = 0xA4 + DEDICATED_KEY = 0xA8 + CALLING_AP_TITLE = 0xA6 + CALLING_AE_INVOCATION_ID = 0xA9 + CALLED_AE_INVOCATION_ID = 0xA5 + RESPONDING_AE_INVOCATION_ID = 0xA7 + CHAR_STRING = 0xAA + USER_INFORMATION = 0xAB diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorOutputType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorOutputType.py new file mode 100644 index 0000000..20c8ad8 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorOutputType.py @@ -0,0 +1,40 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class TranslatorOutputType(GXIntEnum): + """Enumerates Translator output types.""" + #pylint: disable=too-few-public-methods + SIMPLE_XML = 0 + STANDARD_XML = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorSimpleTags.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorSimpleTags.py new file mode 100644 index 0000000..40d95fb --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorSimpleTags.py @@ -0,0 +1,867 @@ +from .ActionRequestType import ActionRequestType +from .enums import Command, DataType, Initiate, LoadDataSet, Task, Access, Definition, ApplicationReference, HardwareResource, VdeStateError, StateError, ExceptionServiceError +from .TranslatorGeneralTags import TranslatorGeneralTags +from .TranslatorTags import TranslatorTags +from .SingleReadResponse import SingleReadResponse +from .VariableAccessSpecification import VariableAccessSpecification +from .GetCommandType import GetCommandType +from .SetRequestType import SetRequestType +from .ActionResponseType import ActionResponseType +from .SetResponseType import SetResponseType +from .enums import AccessServiceCommandType, Conformance, ErrorCode +from .internal._GXCommon import _GXCommon +from .ServiceError import ServiceError +from .enums.Service import Service +from .ReleaseResponseReason import ReleaseResponseReason +from .ReleaseRequestReason import ReleaseRequestReason + +#pylint: disable=bad-option-value,old-style-class,too-many-public-methods +class TranslatorSimpleTags: + # Constructor. + def __init__(self): + pass + + # + # Get general tags. + # + # @param type + # @param list + @classmethod + def getGeneralTags(cls, list_): + list_[Command.SNRM] = "Snrm" + list_[Command.UNACCEPTABLE_FRAME] = "UnacceptableFrame" + list_[Command.DISCONNECT_MODE] = "DisconnectMode" + list_[Command.UA] = "Ua" + list_[Command.AARQ] = "AssociationRequest" + list_[Command.AARE] = "AssociationResponse" + list_[TranslatorGeneralTags.APPLICATION_CONTEXT_NAME] = "ApplicationContextName" + list_[Command.INITIATE_RESPONSE] = "InitiateResponse" + list_[Command.INITIATE_REQUEST] = "InitiateRequest" + list_[TranslatorGeneralTags.NEGOTIATED_QUALITY_OF_SERVICE] = "NegotiatedQualityOfService" + list_[TranslatorGeneralTags.PROPOSED_QUALITY_OF_SERVICE] = "ProposedQualityOfService" + list_[TranslatorGeneralTags.PROPOSED_DLMS_VERSION_NUMBER] = "ProposedDlmsVersionNumber" + list_[TranslatorGeneralTags.PROPOSED_MAX_PDU_SIZE] = "ProposedMaxPduSize" + list_[TranslatorGeneralTags.PROPOSED_CONFORMANCE] = "ProposedConformance" + list_[TranslatorGeneralTags.VAA_NAME] = "VaaName" + list_[TranslatorGeneralTags.NEGOTIATED_CONFORMANCE] = "NegotiatedConformance" + list_[TranslatorGeneralTags.NEGOTIATED_DLMS_VERSION_NUMBER] = "NegotiatedDlmsVersionNumber" + list_[TranslatorGeneralTags.NEGOTIATED_MAX_PDU_SIZE] = "NegotiatedMaxPduSize" + list_[TranslatorGeneralTags.CONFORMANCE_BIT] = "ConformanceBit" + list_[TranslatorGeneralTags.SENDER_ACSE_REQUIREMENTS] = "SenderACSERequirements" + list_[TranslatorGeneralTags.RESPONDER_ACSE_REQUIREMENT] = "ResponderACSERequirement" + list_[TranslatorGeneralTags.RESPONDING_MECHANISM_NAME] = "MechanismName" + list_[TranslatorGeneralTags.CALLING_MECHANISM_NAME] = "MechanismName" + list_[TranslatorGeneralTags.CALLING_AUTHENTICATION] = "CallingAuthentication" + list_[TranslatorGeneralTags.RESPONDING_AUTHENTICATION] = "RespondingAuthentication" + list_[Command.RELEASE_REQUEST] = "ReleaseRequest" + list_[Command.RELEASE_RESPONSE] = "ReleaseResponse" + list_[Command.DISCONNECT_REQUEST] = "DisconnectRequest" + list_[TranslatorGeneralTags.ASSOCIATION_RESULT] = "AssociationResult" + list_[TranslatorGeneralTags.RESULT_SOURCE_DIAGNOSTIC] = "ResultSourceDiagnostic" + list_[TranslatorGeneralTags.ACSE_SERVICE_USER] = "ACSEServiceUser" + list_[TranslatorGeneralTags.CALLING_AP_TITLE] = "CallingAPTitle" + list_[TranslatorGeneralTags.RESPONDING_AP_TITLE] = "RespondingAPTitle" + list_[TranslatorGeneralTags.DEDICATED_KEY] = "DedicatedKey" + list_[Command.CONFIRMED_SERVICE_ERROR] = "ConfirmedServiceError" + list_[Command.INFORMATION_REPORT] = "InformationReportRequest" + list_[Command.EVENT_NOTIFICATION] = "EventNotificationRequest" + list_[Command.EXCEPTION_RESPONSE] = "ExceptionResponse" + list_[TranslatorTags.STATE_ERROR] = "StateError" + list_[TranslatorTags.SERVICE_ERROR] = "ServiceError" + + # + # Get SN tags. + # + # @param type + # @param list + # + @classmethod + def getSnTags(cls, list_): + list_[Command.READ_REQUEST] = "ReadRequest" + list_[Command.WRITE_REQUEST] = "WriteRequest" + list_[Command.WRITE_REQUEST << 8 | SingleReadResponse.DATA] = "VariableName" + list_[Command.WRITE_RESPONSE] = "WriteResponse" + list_[Command.READ_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME] = "VariableName" + list_[Command.READ_REQUEST << 8 | VariableAccessSpecification.PARAMETERISED_ACCESS] = "ParameterisedAccess" + list_[Command.READ_REQUEST << 8 | VariableAccessSpecification.BLOCK_NUMBER_ACCESS] = "BlockNumberAccess" + list_[Command.WRITE_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME] = "VariableName" + list_[Command.READ_RESPONSE] = "ReadResponse" + list_[Command.READ_RESPONSE << 8 | SingleReadResponse.DATA_BLOCK_RESULT] = "DataBlockResult" + list_[Command.READ_RESPONSE << 8 | SingleReadResponse.DATA] = "Data" + list_[Command.READ_RESPONSE << 8 | SingleReadResponse.DATA_ACCESS_ERROR] = "DataAccessError" + + # + # Get LN tags. + # + # @param type + # @param list + # + @classmethod + def getLnTags(cls, list_): + list_[Command.GET_REQUEST] = "GetRequest" + list_[Command.GET_REQUEST << 8 | GetCommandType.NORMAL] = "GetRequestNormal" + list_[Command.GET_REQUEST << 8 | GetCommandType.NEXT_DATA_BLOCK] = "GetRequestForNextDataBlock" + list_[Command.GET_REQUEST << 8 | GetCommandType.WITH_LIST] = "GetRequestWithList" + list_[Command.SET_REQUEST] = "SetRequest" + list_[Command.SET_REQUEST << 8 | SetRequestType.NORMAL] = "SetRequestNormal" + list_[Command.SET_REQUEST << 8 | SetRequestType.FIRST_DATA_BLOCK] = "SetRequestFirstDataBlock" + list_[Command.SET_REQUEST << 8 | SetRequestType.WITH_DATA_BLOCK] = "SetRequestWithDataBlock" + list_[Command.SET_REQUEST << 8 | SetRequestType.WITH_LIST] = "SetRequestWithList" + list_[Command.METHOD_REQUEST] = "ActionRequest" + list_[Command.METHOD_REQUEST << 8 | ActionRequestType.NORMAL] = "ActionRequestNormal" + list_[Command.METHOD_REQUEST << 8 | ActionRequestType.NEXT_BLOCK] = "ActionRequestForNextDataBlock" + list_[Command.METHOD_REQUEST << 8 | ActionRequestType.WITH_LIST] = "ActionRequestWithList" + list_[Command.METHOD_RESPONSE] = "ActionResponse" + list_[Command.METHOD_RESPONSE << 8 | ActionResponseType.NORMAL] = "ActionResponseNormal" + list_[Command.METHOD_RESPONSE << 8 | ActionResponseType.WITH_BLOCK] = "ActionResponseWithPBlock" + list_[Command.METHOD_RESPONSE << 8 | ActionResponseType.WITH_LIST] = "ActionResponseWithList" + list_[Command.METHOD_RESPONSE << 8 | ActionResponseType.NEXT_BLOCK] = "ActionResponseNextBlock" + list_[int(Command.DATA_NOTIFICATION)] = "DataNotification" + list_[Command.GET_RESPONSE] = "GetResponse" + list_[Command.GET_RESPONSE << 8 | GetCommandType.NORMAL] = "GetResponseNormal" + list_[Command.GET_RESPONSE << 8 | GetCommandType.NEXT_DATA_BLOCK] = "GetResponsewithDataBlock" + list_[Command.GET_RESPONSE << 8 | GetCommandType.WITH_LIST] = "GetResponseWithList" + list_[Command.SET_RESPONSE] = "SetResponse" + list_[Command.SET_RESPONSE << 8 | SetResponseType.NORMAL] = "SetResponseNormal" + list_[Command.SET_RESPONSE << 8 | SetResponseType.DATA_BLOCK] = "SetResponseDataBlock" + list_[Command.SET_RESPONSE << 8 | SetResponseType.LAST_DATA_BLOCK] = "SetResponseWithLastDataBlock" + list_[Command.SET_RESPONSE << 8 | SetResponseType.WITH_LIST] = "SetResponseWithList" + list_[Command.ACCESS_REQUEST] = "AccessRequest" + list_[(Command.ACCESS_REQUEST) << 8 | AccessServiceCommandType.GET] = "AccessRequestGet" + list_[(Command.ACCESS_REQUEST) << 8 | AccessServiceCommandType.SET] = "AccessRequestSet" + list_[(Command.ACCESS_REQUEST) << 8 | AccessServiceCommandType.ACTION] = "AccessRequestAction" + list_[Command.ACCESS_RESPONSE] = "AccessResponse" + list_[(Command.ACCESS_RESPONSE) << 8 | AccessServiceCommandType.GET] = "AccessResponseGet" + list_[(Command.ACCESS_RESPONSE) << 8 | AccessServiceCommandType.SET] = "AccessResponseSet" + list_[(Command.ACCESS_RESPONSE) << 8 | AccessServiceCommandType.ACTION] = "AccessResponseAction" + list_[TranslatorTags.ACCESS_REQUEST_BODY] = "AccessRequestBody" + list_[TranslatorTags.LIST_OF_ACCESS_REQUEST_SPECIFICATION] = "AccessRequestSpecification" + list_[TranslatorTags.ACCESS_REQUEST_SPECIFICATION] = "_AccessRequestSpecification" + list_[TranslatorTags.ACCESS_REQUEST_LIST_OF_DATA] = "AccessRequestListOfData" + list_[TranslatorTags.ACCESS_RESPONSE_BODY] = "AccessResponseBody" + list_[TranslatorTags.LIST_OF_ACCESS_RESPONSE_SPECIFICATION] = "AccessResponseSpecification" + list_[TranslatorTags.ACCESS_RESPONSE_SPECIFICATION] = "_AccessResponseSpecification" + list_[TranslatorTags.ACCESS_RESPONSE_LIST_OF_DATA] = "AccessResponseListOfData" + list_[TranslatorTags.SERVICE] = "Service" + list_[TranslatorTags.SERVICE_ERROR] = "ServiceError" + list_[Command.GENERAL_BLOCK_TRANSFER] = "GeneralBlockTransfer" + list_[TranslatorGeneralTags.CALLING_AE_INVOCATION_ID] = "CallingAEInvocationId" + list_[TranslatorGeneralTags.CALLED_AE_INVOCATION_ID] = "CalledAEInvocationId" + list_[TranslatorGeneralTags.RESPONDING_AE_INVOCATION_ID] = "RespondingAEInvocationId" + list_[Command.GATEWAY_REQUEST] = "GatewayRequest" + list_[Command.GATEWAY_RESPONSE] = "GatewayResponse" + + # + # Get glo tags. + # + # @param type + # @param list + # + @classmethod + def getGloTags(cls, list_): + list_[Command.GLO_INITIATE_REQUEST] = "glo_InitiateRequest" + list_[Command.GLO_INITIATE_RESPONSE] = "glo_InitiateResponse" + list_[Command.GLO_GET_REQUEST] = "glo_GetRequest" + list_[Command.GLO_GET_RESPONSE] = "glo_GetResponse" + list_[Command.GLO_SET_REQUEST] = "glo_SetRequest" + list_[Command.GLO_SET_RESPONSE] = "glo_SetResponse" + list_[Command.GLO_METHOD_REQUEST] = "glo_ActionRequest" + list_[Command.GLO_METHOD_RESPONSE] = "glo_ActionResponse" + list_[Command.GLO_READ_REQUEST] = "glo_ReadRequest" + list_[Command.GLO_READ_RESPONSE] = "glo_ReadResponse" + list_[Command.GLO_WRITE_REQUEST] = "glo_WriteRequest" + list_[Command.GLO_WRITE_RESPONSE] = "glo_WriteResponse" + list_[Command.GENERAL_GLO_CIPHERING] = "GeneralGloCiphering" + list_[Command.GENERAL_CIPHERING] = "GeneralCiphering" + list_[Command.GLO_CONFIRMED_SERVICE_ERROR] = "glo_GloConfirmedServiceError" + + # + # Get ded tags. + # + # @param type + # @param list + # + @classmethod + def getDedTags(cls, list_): + list_[Command.DED_GET_REQUEST] = "ded_GetRequest" + list_[Command.DED_GET_RESPONSE] = "ded_GetResponse" + list_[Command.DED_SET_REQUEST] = "ded_SetRequest" + list_[Command.DED_SET_RESPONSE] = "ded_SetResponse" + list_[Command.DED_METHOD_REQUEST] = "ded_ActionRequest" + list_[Command.DED_METHOD_RESPONSE] = "ded_ActionResponse" + list_[Command.GENERAL_DED_CIPHERING] = "GeneralDedCiphering" + list_[Command.DED_CONFIRMED_SERVICE_ERROR] = "ded_GloConfirmedServiceError" + # + # Get translator tags. + # + # @param type + # @param list + # + @classmethod + def getTranslatorTags(cls, list_): + list_[TranslatorTags.WRAPPER] = "Wrapper" + list_[TranslatorTags.HDLC] = "Hdlc" + list_[TranslatorTags.PDU_DLMS] = "Pdu" + list_[TranslatorTags.TARGET_ADDRESS] = "TargetAddress" + list_[TranslatorTags.SOURCE_ADDRESS] = "SourceAddress" + list_[TranslatorTags.FRAME_TYPE] = "FrameType" + list_[TranslatorTags.LIST_OF_VARIABLE_ACCESS_SPECIFICATION] = "ListOfVariableAccessSpecification" + list_[TranslatorTags.LIST_OF_DATA] = "ListOfData" + list_[TranslatorTags.SUCCESS] = "Success" + list_[TranslatorTags.DATA_ACCESS_ERROR] = "DataAccessError" + list_[TranslatorTags.ATTRIBUTE_DESCRIPTOR] = "AttributeDescriptor" + list_[TranslatorTags.CLASS_ID] = "ClassId" + list_[TranslatorTags.INSTANCE_ID] = "InstanceId" + list_[TranslatorTags.ATTRIBUTE_ID] = "AttributeId" + list_[TranslatorTags.METHOD_INVOCATION_PARAMETERS] = "MethodInvocationParameters" + list_[TranslatorTags.SELECTOR] = "Selector" + list_[TranslatorTags.PARAMETER] = "Parameter" + list_[TranslatorTags.LAST_BLOCK] = "LastBlock" + list_[TranslatorTags.BLOCK_NUMBER] = "BlockNumber" + list_[TranslatorTags.RAW_DATA] = "RawData" + list_[TranslatorTags.METHOD_DESCRIPTOR] = "MethodDescriptor" + list_[TranslatorTags.METHOD_ID] = "MethodId" + list_[TranslatorTags.RESULT] = "Result" + list_[TranslatorTags.RETURN_PARAMETERS] = "ReturnParameters" + list_[TranslatorTags.ACCESS_SELECTION] = "AccessSelection" + list_[TranslatorTags.VALUE] = "Value" + list_[TranslatorTags.ACCESS_SELECTOR] = "AccessSelector" + list_[TranslatorTags.ACCESS_PARAMETERS] = "AccessParameters" + list_[TranslatorTags.ATTRIBUTE_DESCRIPTOR_LIST] = "AttributeDescriptorList" + list_[TranslatorTags.ATTRIBUTE_DESCRIPTOR_WITH_SELECTION] = "AttributeDescriptorWithSelection" + list_[TranslatorTags.READ_DATA_BLOCK_ACCESS] = "ReadDataBlockAccess" + list_[TranslatorTags.WRITE_DATA_BLOCK_ACCESS] = "WriteDataBlockAccess" + list_[TranslatorTags.DATA] = "Data" + list_[TranslatorTags.INVOKE_ID] = "InvokeIdAndPriority" + list_[TranslatorTags.LONG_INVOKE_ID] = "LongInvokeIdAndPriority" + list_[TranslatorTags.DATE_TIME] = "DateTime" + list_[TranslatorTags.CURRENT_TIME] = "CurrentTime" + list_[TranslatorTags.TIME] = "Time" + list_[TranslatorTags.REASON] = "Reason" + list_[TranslatorTags.NOTIFICATION_BODY] = "NotificationBody" + list_[TranslatorTags.DATA_VALUE] = "DataValue" + list_[TranslatorTags.CIPHERED_SERVICE] = "CipheredService" + list_[TranslatorTags.SYSTEM_TITLE] = "SystemTitle" + list_[TranslatorTags.DATA_BLOCK] = "DataBlock" + list_[TranslatorTags.TRANSACTION_ID] = "TransactionId" + list_[TranslatorTags.ORIGINATOR_SYSTEM_TITLE] = "OriginatorSystemTitle" + list_[TranslatorTags.RECIPIENT_SYSTEM_TITLE] = "RecipientSystemTitle" + list_[TranslatorTags.OTHER_INFORMATION] = "OtherInformation" + list_[TranslatorTags.KEY_INFO] = "KeyInfo" + list_[TranslatorTags.CIPHERED_CONTENT] = "CipheredContent" + list_[TranslatorTags.AGREED_KEY] = "AgreedKey" + list_[TranslatorTags.KEY_PARAMETERS] = "KeyParameters" + list_[TranslatorTags.KEY_CIPHERED_DATA] = "KeyCipheredData" + list_[TranslatorTags.ATTRIBUTE_VALUE] = "AttributeValue" + list_[TranslatorTags.MAX_INFO_RX] = "MaxInfoRX" + list_[TranslatorTags.MAX_INFO_TX] = "MaxInfoTX" + list_[TranslatorTags.WINDOW_SIZE_RX] = "WindowSizeRX" + list_[TranslatorTags.WINDOW_SIZE_TX] = "WindowSizeTX" + list_[TranslatorTags.VALUE_LIST] = "ValueList" + list_[TranslatorTags.DATA_ACCESS_RESULT] = "DataAccessResult" + list_[TranslatorTags.BLOCK_CONTROL] = "BlockControl" + list_[TranslatorTags.BLOCK_NUMBER_ACK] = "BlockNumberAck" + list_[TranslatorTags.BLOCK_DATA] = "BlockData" + list_[TranslatorTags.CONTENTS_DESCRIPTION] = "ContentsDescription" + list_[TranslatorTags.ARRAY_CONTENTS] = "ArrayContents" + list_[TranslatorTags.NETWORK_ID] = "NetworkId" + list_[TranslatorTags.PHYSICAL_DEVICE_ADDRESS] = "PhysicalDeviceAddress" + list_[TranslatorTags.PROTOCOL_VERSION] = "ProtocolVersion" + list_[TranslatorTags.CALLED_AP_TITLE] = "CalledAPTitle" + list_[TranslatorTags.CALLED_AP_INVOCATION_ID] = "CalledAPInvocationId" + list_[TranslatorTags.CALLED_AE_INVOCATION_ID] = "CalledAEInvocationId" + list_[TranslatorTags.CALLING_AP_INVOCATION_ID] = "CallingApInvocationId" + list_[TranslatorTags.CALLED_AE_QUALIFIER] = "CalledAEQualifier" + + @classmethod + def getDataTypeTags(cls, list_): + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.NONE] = "None" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.ARRAY] = "Array" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.BCD] = "BCD" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.BITSTRING] = "BitString" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.BOOLEAN] = "Boolean" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.COMPACT_ARRAY] = "CompactArray" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.DATE] = "Date" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.DATETIME] = "DateTime" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.ENUM] = "Enum" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.FLOAT32] = "Float32" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.FLOAT64] = "Float64" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.INT16] = "Int16" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.INT32] = "Int32" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.INT64] = "Int64" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.INT8] = "Int8" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.OCTET_STRING] = "OctetString" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.STRING] = "String" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.STRING_UTF8] = "StringUTF8" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.STRUCTURE] = "Structure" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.TIME] = "Time" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.UINT16] = "UInt16" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.UINT32] = "UInt32" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.UINT64] = "UInt64" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.UINT8] = "UInt8" + + @classmethod + def errorCodeToString(cls, value): + value = ErrorCode(value) + str_ = None + if value == ErrorCode.ACCESS_VIOLATED: + str_ = "AccessViolated" + elif value == ErrorCode.DATA_BLOCK_NUMBER_INVALID: + str_ = "DataBlockNumberInvalid" + elif value == ErrorCode.DATA_BLOCK_UNAVAILABLE: + str_ = "DataBlockUnavailable" + elif value == ErrorCode.HARDWARE_FAULT: + str_ = "HardwareFault" + elif value == ErrorCode.INCONSISTENT_CLASS: + str_ = "InconsistentClass" + elif value == ErrorCode.LONG_GET_OR_READ_ABORTED: + str_ = "LongGetOrReadAborted" + elif value == ErrorCode.LONG_SET_OR_WRITE_ABORTED: + str_ = "LongSetOrWriteAborted" + elif value == ErrorCode.NO_LONG_GET_OR_READ_IN_PROGRESS: + str_ = "NoLongGetOrReadInProgress" + elif value == ErrorCode.NO_LONG_SET_OR_WRITE_IN_PROGRESS: + str_ = "NoLongSetOrWriteInProgress" + elif value == ErrorCode.OK: + str_ = "Success" + elif value == ErrorCode.OTHER_REASON: + str_ = "OtherReason" + elif value == ErrorCode.READ_WRITE_DENIED: + str_ = "ReadWriteDenied" + elif value == ErrorCode.TEMPORARY_FAILURE: + str_ = "TemporaryFailure" + elif value == ErrorCode.UNAVAILABLE_OBJECT: + str_ = "UnavailableObject" + elif value == ErrorCode.UNDEFINED_OBJECT: + str_ = "UndefinedObject" + elif value == ErrorCode.UNMATCHED_TYPE: + str_ = "UnmatchedType" + else: + raise ValueError("Error code: " + str(value)) + return str_ + + @classmethod + def value_ofErrorCode(cls, value): + v = None + if "AccessViolated".lower() == value.lower(): + v = ErrorCode.ACCESS_VIOLATED + elif "DataBlockNumberInvalid".lower() == value.lower(): + v = ErrorCode.DATA_BLOCK_NUMBER_INVALID + elif "DataBlockUnavailable".lower() == value.lower(): + v = ErrorCode.DATA_BLOCK_UNAVAILABLE + elif "HardwareFault".lower() == value.lower(): + v = ErrorCode.HARDWARE_FAULT + elif "InconsistentClass".lower() == value.lower(): + v = ErrorCode.INCONSISTENT_CLASS + elif "LongGetOrReadAborted".lower() == value.lower(): + v = ErrorCode.LONG_GET_OR_READ_ABORTED + elif "LongSetOrWriteAborted".lower() == value.lower(): + v = ErrorCode.LONG_SET_OR_WRITE_ABORTED + elif "NoLongGetOrReadInProgress".lower() == value.lower(): + v = ErrorCode.NO_LONG_GET_OR_READ_IN_PROGRESS + elif "NoLongSetOrWriteInProgress".lower() == value.lower(): + v = ErrorCode.NO_LONG_SET_OR_WRITE_IN_PROGRESS + elif "Success".lower() == value.lower(): + v = ErrorCode.OK + elif "OtherReason".lower() == value.lower(): + v = ErrorCode.OTHER_REASON + elif "ReadWriteDenied".lower() == value.lower(): + v = ErrorCode.READ_WRITE_DENIED + elif "TemporaryFailure".lower() == value.lower(): + v = ErrorCode.TEMPORARY_FAILURE + elif "UnavailableObject".lower() == value.lower(): + v = ErrorCode.UNAVAILABLE_OBJECT + elif "UndefinedObject".lower() == value.lower(): + v = ErrorCode.UNDEFINED_OBJECT + elif "UnmatchedType".lower() == value.lower(): + v = ErrorCode.UNMATCHED_TYPE + else: + raise ValueError("Error code: " + value) + return v + + @classmethod + def __getServiceErrors(cls): + list_ = dict() + list_[ServiceError.APPLICATION_REFERENCE] = "ApplicationReference" + list_[ServiceError.HARDWARE_RESOURCE] = "HardwareResource" + list_[ServiceError.VDE_STATE_ERROR] = "VdeStateError" + list_[ServiceError.SERVICE] = "Service" + list_[ServiceError.DEFINITION] = "Definition" + list_[ServiceError.ACCESS] = "Access" + list_[ServiceError.INITIATE] = "Initiate" + list_[ServiceError.LOAD_DATASET] = "LoadDataSet" + list_[ServiceError.TASK] = "Task" + return list_ + + @classmethod + def __getApplicationReference(cls): + list_ = dict() + list_[ApplicationReference.APPLICATION_CONTEXT_UNSUPPORTED] = "ApplicationContextUnsupported" + list_[ApplicationReference.APPLICATION_REFERENCE_INVALID] = "ApplicationReferenceInvalid" + list_[ApplicationReference.APPLICATION_UNREACHABLE] = "ApplicationUnreachable" + list_[ApplicationReference.DECIPHERING_ERROR] = "DecipheringError" + list_[ApplicationReference.OTHER] = "Other" + list_[ApplicationReference.PROVIDER_COMMUNICATION_ERROR] = "ProviderCommunicationError" + list_[ApplicationReference.TIME_ELAPSED] = "TimeElapsed" + return list_ + + @classmethod + def __getHardwareResource(cls): + list_ = dict() + list_[HardwareResource.MASS_STORAGE_UNAVAILABLE] = "MassStorageUnavailable" + list_[HardwareResource.MEMORY_UNAVAILABLE] = "MemoryUnavailable" + list_[HardwareResource.OTHER] = "Other" + list_[HardwareResource.OTHER_RESOURCE_UNAVAILABLE] = "OtherResourceUnavailable" + list_[HardwareResource.PROCESSOR_RESOURCE_UNAVAILABLE] = "ProcessorResourceUnavailable" + return list_ + + @classmethod + def __getVdeStateError(cls): + list_ = dict() + list_[VdeStateError.LOADING_DATASET] = "LoadingDataSet" + list_[VdeStateError.NO_DLMS_CONTEXT] = "NoDlmsContext" + list_[VdeStateError.OTHER] = "Other" + list_[VdeStateError.STATUS_INOPERABLE] = "StatusInoperable" + list_[VdeStateError.STATUS_NO_CHANGE] = "StatusNochange" + return list_ + + @classmethod + def __getService(cls): + list_ = dict() + list_[Service.OTHER] = "Other" + list_[Service.PDU_SIZE] = "PduSize" + list_[Service.UNSUPPORTED] = "ServiceUnsupported" + return list_ + + @classmethod + def __getDefinition(cls): + list_ = dict() + list_[Definition.OBJECT_ATTRIBUTE_INCONSISTENT] = "ObjectAttributeInconsistent" + list_[Definition.OBJECT_CLASS_INCONSISTENT] = "ObjectClassInconsistent" + list_[Definition.OBJECT_UNDEFINED] = "ObjectUndefined" + list_[Definition.OTHER] = "Other" + return list_ + + @classmethod + def __getAccess(cls): + list_ = dict() + list_[Access.HARDWARE_FAULT] = "HardwareFault" + list_[Access.OBJECT_ACCESS_INVALID] = "ObjectAccessInvalid" + list_[Access.OBJECT_UNAVAILABLE] = "ObjectUnavailable" + list_[Access.OTHER] = "Other" + list_[Access.SCOPE_OF_ACCESS_VIOLATED] = "ScopeOfAccessViolated" + return list_ + + @classmethod + def __getInitiate(cls): + list_ = dict() + list_[Initiate.DLMS_VERSION_TOO_LOW] = "DlmsVersionTooLow" + list_[Initiate.INCOMPATIBLE_CONFORMANCE] = "IncompatibleConformance" + list_[Initiate.OTHER] = "Other" + list_[Initiate.PDU_SIZE_TOO_SHORT] = "PduSizeTooShort" + list_[Initiate.REFUSED_BY_THE_VDE_HANDLER] = "RefusedByTheVDEHandler" + return list_ + + @classmethod + def __getLoadDataSet(cls): + list_ = dict() + list_[LoadDataSet.DATASET_NOT_READY] = "DataSetNotReady" + list_[LoadDataSet.DATASET_SIZE_TOO_LARGE] = "DatasetSizeTooLarge" + list_[LoadDataSet.INTERPRETATION_FAILURE] = "InterpretationFailure" + list_[LoadDataSet.NOT_AWAITED_SEGMENT] = "NotAwaitedSegment" + list_[LoadDataSet.NOT_LOADABLE] = "NotLoadable" + list_[LoadDataSet.OTHER] = "Other" + list_[LoadDataSet.PRIMITIVE_OUT_OF_SEQUENCE] = "PrimitiveOutOfSequence" + list_[LoadDataSet.STORAGE_FAILURE] = "StorageFailure" + return list_ + + @classmethod + def __getTask(cls): + list_ = dict() + list_[Task.NO_REMOTE_CONTROL] = "NoRemoteControl" + list_[Task.OTHER] = "Other" + list_[Task.TI_RUNNING] = "tiRunning" + list_[Task.TI_STOPPED] = "tiStopped" + list_[Task.TI_UNUSABLE] = "tiUnusable" + return list_ + + @classmethod + def getServiceErrorValue(cls, error, value): + if error == ServiceError.APPLICATION_REFERENCE: + str_ = TranslatorSimpleTags.__getApplicationReference().get(ApplicationReference(value)) + elif error == ServiceError.HARDWARE_RESOURCE: + str_ = TranslatorSimpleTags.__getHardwareResource().get(HardwareResource(value)) + elif error == ServiceError.VDE_STATE_ERROR: + str_ = TranslatorSimpleTags.__getVdeStateError().get(VdeStateError(value)) + elif error == ServiceError.SERVICE: + str_ = TranslatorSimpleTags.__getService().get(Service(value)) + elif error == ServiceError.DEFINITION: + str_ = TranslatorSimpleTags.__getDefinition().get(Definition(value)) + elif error == ServiceError.ACCESS: + str_ = TranslatorSimpleTags.__getAccess().get(Access(value)) + elif error == ServiceError.INITIATE: + str_ = TranslatorSimpleTags.__getInitiate().get(Initiate(value)) + elif error == ServiceError.LOAD_DATASET: + str_ = TranslatorSimpleTags.__getLoadDataSet().get(LoadDataSet(value)) + elif error == ServiceError.TASK: + str_ = TranslatorSimpleTags.__getTask().get(Task(value)) + elif error == ServiceError.OTHER_ERROR: + str_ = str(value) + else: + str_ = "" + return str_ + + # + # @param error + # Service error enumeration value. + # Service error simple XML tag. + # + @classmethod + def serviceErrorToString(cls, error): + return TranslatorSimpleTags.__getServiceErrors().get(error) + + @classmethod + def __getApplicationReferenceByValue(cls, value): + ret = None + for k, v in TranslatorSimpleTags.__getApplicationReference().items(): + if value == v: + ret = k + break + if ret is None: + raise ValueError() + return ret + + # + # @param value + # Service error simple XML tag. + # Service error enumeration value. + # + @classmethod + def getServiceError(cls, value): + error = None + for k, v in TranslatorSimpleTags.__getServiceErrors().items(): + if value == v: + error = k + break + if error is None: + raise ValueError() + return error + + @classmethod + def __getHardwareResourceByValue(cls, value): + ret = None + for k, v in TranslatorSimpleTags.__getHardwareResource().items(): + if value == v: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getVdeStateErrorByValue(cls, value): + ret = None + for k, v in TranslatorSimpleTags.__getVdeStateError().items(): + if value == v: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getServiceByValue(cls, value): + ret = None + for k, v in TranslatorSimpleTags.__getService().items(): + if value == v: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getDefinitionByValue(cls, value): + ret = None + for k, v in TranslatorSimpleTags.__getDefinition().items(): + if value == v: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getAccessByValue(cls, value): + ret = None + for k, v in TranslatorSimpleTags.__getAccess().items(): + if value == v: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def getInitiateByValue(cls, value): + ret = None + for k, v in TranslatorSimpleTags.__getInitiate().items(): + if value == v: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getLoadDataSetByValue(cls, value): + ret = None + for k, v in TranslatorSimpleTags.__getLoadDataSet().items(): + if value == v: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getTaskByValue(cls, value): + ret = None + for k, v in TranslatorSimpleTags.__getTask().items(): + if value == v: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def getError(cls, serviceError, value): + ret = 0 + if serviceError == ServiceError.APPLICATION_REFERENCE: + ret = TranslatorSimpleTags.__getApplicationReferenceByValue(value) + elif serviceError == ServiceError.HARDWARE_RESOURCE: + ret = TranslatorSimpleTags.__getHardwareResourceByValue(value) + elif serviceError == ServiceError.VDE_STATE_ERROR: + ret = TranslatorSimpleTags.__getVdeStateErrorByValue(value) + elif serviceError == ServiceError.SERVICE: + ret = TranslatorSimpleTags.__getServiceByValue(value) + elif serviceError == ServiceError.DEFINITION: + ret = TranslatorSimpleTags.__getDefinitionByValue(value) + elif serviceError == ServiceError.ACCESS: + ret = TranslatorSimpleTags.__getAccessByValue(value) + elif serviceError == ServiceError.INITIATE: + ret = TranslatorSimpleTags.getInitiateByValue(value) + elif serviceError == ServiceError.LOAD_DATASET: + ret = TranslatorSimpleTags.__getLoadDataSetByValue(value) + elif serviceError == ServiceError.TASK: + ret = TranslatorSimpleTags.__getTaskByValue(value) + elif serviceError == ServiceError.OTHER_ERROR: + ret = int(value) + return ret + + @classmethod + def conformancetoString(cls, value): + str_ = None + if value == Conformance.ACCESS: + str_ = "Access" + elif value == Conformance.ACTION: + str_ = "Action" + elif value == Conformance.ATTRIBUTE_0_SUPPORTED_WITH_GET: + str_ = "Attribute0SupportedWithGet" + elif value == Conformance.ATTRIBUTE_0_SUPPORTED_WITH_SET: + str_ = "Attribute0SupportedWithSet" + elif value == Conformance.BLOCK_TRANSFER_WITH_ACTION: + str_ = "BlockTransferWithAction" + elif value == Conformance.BLOCK_TRANSFER_WITH_GET_OR_READ: + str_ = "BlockTransferWithGetOrRead" + elif value == Conformance.BLOCK_TRANSFER_WITH_SET_OR_WRITE: + str_ = "BlockTransferWithSetOrWrite" + elif value == Conformance.DATA_NOTIFICATION: + str_ = "DataNotification" + elif value == Conformance.EVENT_NOTIFICATION: + str_ = "EventNotification" + elif value == Conformance.GENERAL_BLOCK_TRANSFER: + str_ = "GeneralBlockTransfer" + elif value == Conformance.GENERAL_PROTECTION: + str_ = "GeneralProtection" + elif value == Conformance.GET: + str_ = "Get" + elif value == Conformance.INFORMATION_REPORT: + str_ = "InformationReport" + elif value == Conformance.MULTIPLE_REFERENCES: + str_ = "MultipleReferences" + elif value == Conformance.PARAMETERIZED_ACCESS: + str_ = "ParameterizedAccess" + elif value == Conformance.PRIORITY_MGMT_SUPPORTED: + str_ = "PriorityMgmtSupported" + elif value == Conformance.READ: + str_ = "Read" + elif value == Conformance.RESERVED_SEVEN: + str_ = "ReservedSeven" + elif value == Conformance.DELTA_VALUE_ENCODING: + str_ = "DeltaValueEncoding" + elif value == Conformance.RESERVED_ZERO: + str_ = "ReservedZero" + elif value == Conformance.SELECTIVE_ACCESS: + str_ = "SelectiveAccess" + elif value == Conformance.SET: + str_ = "Set" + elif value == Conformance.UN_CONFIRMED_WRITE: + str_ = "UnconfirmedWrite" + elif value == Conformance.WRITE: + str_ = "Write" + else: + raise ValueError(str(value)) + return str_ + + @classmethod + def value_ofConformance(cls, value): + ret = None + if "Access".lower() == value.lower(): + ret = Conformance.ACCESS + elif "Action".lower() == value.lower(): + ret = Conformance.ACTION + elif "Attribute0SupportedWithGet".lower() == value.lower(): + ret = Conformance.ATTRIBUTE_0_SUPPORTED_WITH_GET + elif "Attribute0SupportedWithSet".lower() == value.lower(): + ret = Conformance.ATTRIBUTE_0_SUPPORTED_WITH_SET + elif "BlockTransferWithAction".lower() == value.lower(): + ret = Conformance.BLOCK_TRANSFER_WITH_ACTION + elif "BlockTransferWithGetOrRead".lower() == value.lower(): + ret = Conformance.BLOCK_TRANSFER_WITH_GET_OR_READ + elif "BlockTransferWithSetOrWrite".lower() == value.lower(): + ret = Conformance.BLOCK_TRANSFER_WITH_SET_OR_WRITE + elif "DataNotification".lower() == value.lower(): + ret = Conformance.DATA_NOTIFICATION + elif "EventNotification".lower() == value.lower(): + ret = Conformance.EVENT_NOTIFICATION + elif "GeneralBlockTransfer".lower() == value.lower(): + ret = Conformance.GENERAL_BLOCK_TRANSFER + elif "GeneralProtection".lower() == value.lower(): + ret = Conformance.GENERAL_PROTECTION + elif "Get".lower() == value.lower(): + ret = Conformance.GET + elif "InformationReport".lower() == value.lower(): + ret = Conformance.INFORMATION_REPORT + elif "MultipleReferences".lower() == value.lower(): + ret = Conformance.MULTIPLE_REFERENCES + elif "ParameterizedAccess".lower() == value.lower(): + ret = Conformance.PARAMETERIZED_ACCESS + elif "PriorityMgmtSupported".lower() == value.lower(): + ret = Conformance.PRIORITY_MGMT_SUPPORTED + elif "Read".lower() == value.lower(): + ret = Conformance.READ + elif "ReservedSeven".lower() == value.lower(): + ret = Conformance.RESERVED_SEVEN + elif "DeltaValueEncoding".lower() == value.lower(): + ret = Conformance.DELTA_VALUE_ENCODING + elif "ReservedZero".lower() == value.lower(): + ret = Conformance.RESERVED_ZERO + elif "SelectiveAccess".lower() == value.lower(): + ret = Conformance.SELECTIVE_ACCESS + elif "Set".lower() == value.lower(): + ret = Conformance.SET + elif "UnconfirmedWrite".lower() == value.lower(): + ret = Conformance.UN_CONFIRMED_WRITE + elif "Write".lower() == value.lower(): + ret = Conformance.WRITE + else: + raise ValueError(value) + return ret + + @classmethod + def releaseResponseReasonToString(cls, value): + str_ = None + if value == ReleaseResponseReason.NORMAL: + str_ = "Normal" + elif value == ReleaseResponseReason.NOT_FINISHED: + str_ = "NotFinished" + elif value == ReleaseResponseReason.USER_DEFINED: + str_ = "UserDefined" + else: + raise ValueError(str(value)) + return str_ + + @classmethod + def value_ofReleaseResponseReason(cls, value): + ret = None + if "Normal".lower() == value.lower(): + ret = ReleaseResponseReason.NORMAL + elif "NotFinished".lower() == value.lower(): + ret = ReleaseResponseReason.NOT_FINISHED + elif "UserDefined".lower() == value.lower(): + ret = ReleaseResponseReason.USER_DEFINED + else: + raise ValueError(value) + return ret + + @classmethod + def releaseRequestReasonToString(cls, value): + str_ = None + if value == ReleaseRequestReason.NORMAL: + str_ = "Normal" + elif value == ReleaseRequestReason.URGENT: + str_ = "Urgent" + elif value == ReleaseRequestReason.USER_DEFINED: + str_ = "UserDefined" + else: + raise ValueError(str(value)) + return str_ + + @classmethod + def value_ofReleaseRequestReason(cls, value): + ret = None + if "Normal".lower() == value.lower(): + ret = ReleaseRequestReason.NORMAL + elif "Urgent".lower() == value.lower(): + ret = ReleaseRequestReason.URGENT + elif "UserDefined".lower() == value.lower(): + ret = ReleaseRequestReason.USER_DEFINED + else: + raise ValueError(value) + return ret + + @classmethod + def stateErrorToString(cls, value): + if value == StateError.SERVICE_NOT_ALLOWED: + ret = "ServiceNotAllowed" + elif value == StateError.SERVICE_UNKNOWN: + ret = "ServiceUnknown" + else: + raise ValueError(value) + return ret + + @classmethod + def exceptionServiceErrorToString(cls, value): + if value == ExceptionServiceError.OPERATION_NOT_POSSIBLE: + ret = "OperationNotPossible" + elif value == ExceptionServiceError.SERVICE_NOT_SUPPORTED: + ret = "ServiceNotSupported" + elif value == ExceptionServiceError.OTHER_REASON: + ret = "OtherReason" + else: + raise ValueError(value) + return ret + + @classmethod + def valueofStateError(cls, value): + if "ServiceNotAllowed".lower() == value.lower(): + ret = StateError.SERVICE_NOT_ALLOWED + elif "ServiceUnknown".lower() == value.lower(): + ret = StateError.SERVICE_UNKNOWN + else: + raise ValueError(value) + return ret + + @classmethod + def valueOfExceptionServiceError(cls, value): + if "OperationNotPossible".lower() == value.lower(): + ret = ExceptionServiceError.OPERATION_NOT_POSSIBLE + elif "ServiceNotSupported".lower() == value.lower(): + ret = ExceptionServiceError.SERVICE_NOT_SUPPORTED + elif "OtherReason".lower() == value.lower(): + ret = ExceptionServiceError.OTHER_REASON + else: + raise ValueError(value) + return ret diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorStandardTags.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorStandardTags.py new file mode 100644 index 0000000..f5637a6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorStandardTags.py @@ -0,0 +1,878 @@ +from .ActionRequestType import ActionRequestType +from .enums import Command, DataType, Initiate, LoadDataSet, Task, Access, Definition, ApplicationReference, HardwareResource, VdeStateError, StateError, ExceptionServiceError +from .TranslatorGeneralTags import TranslatorGeneralTags +from .TranslatorTags import TranslatorTags +from .SingleReadResponse import SingleReadResponse +from .VariableAccessSpecification import VariableAccessSpecification +from .GetCommandType import GetCommandType +from .SetRequestType import SetRequestType +from .ActionResponseType import ActionResponseType +from .SetResponseType import SetResponseType +from .enums import AccessServiceCommandType, Conformance, ErrorCode +from .internal._GXCommon import _GXCommon +from .ServiceError import ServiceError +from .enums.Service import Service +from .ReleaseResponseReason import ReleaseResponseReason +from .ReleaseRequestReason import ReleaseRequestReason + +#pylint: disable=bad-option-value,old-style-class,too-many-public-methods,unused-private-member +class TranslatorStandardTags: + # Constructor. + def __init__(self): + pass + + # + # Get general tags. + # + # @param type + # @param list + # + @classmethod + def getGeneralTags(cls, list_): + list_[Command.SNRM] = "Snrm" + list_[Command.UNACCEPTABLE_FRAME] = "UnacceptableFrame" + list_[Command.DISCONNECT_MODE] = "DisconnectMode" + list_[Command.UA] = "Ua" + list_[Command.AARQ] = "aarq" + list_[Command.AARE] = "aare" + list_[TranslatorGeneralTags.APPLICATION_CONTEXT_NAME] = "application-context-name" + list_[Command.INITIATE_RESPONSE] = "initiateResponse" + list_[Command.INITIATE_REQUEST] = "initiateRequest" + list_[TranslatorGeneralTags.NEGOTIATED_QUALITY_OF_SERVICE] = "negotiated-quality-of-service" + list_[TranslatorGeneralTags.PROPOSED_QUALITY_OF_SERVICE] = "proposed-quality-of-service" + list_[TranslatorGeneralTags.PROPOSED_DLMS_VERSION_NUMBER] = "proposed-dlms-version-number" + list_[TranslatorGeneralTags.PROPOSED_MAX_PDU_SIZE] = "client-max-receive-pdu-size" + list_[TranslatorGeneralTags.PROPOSED_CONFORMANCE] = "proposed-conformance" + list_[TranslatorGeneralTags.VAA_NAME] = "vaa-name" + list_[TranslatorGeneralTags.NEGOTIATED_CONFORMANCE] = "negotiated-conformance" + list_[TranslatorGeneralTags.NEGOTIATED_DLMS_VERSION_NUMBER] = "negotiated-dlms-version-number" + list_[TranslatorGeneralTags.NEGOTIATED_MAX_PDU_SIZE] = "server-max-receive-pdu-size" + list_[TranslatorGeneralTags.CONFORMANCE_BIT] = "ConformanceBit" + list_[TranslatorGeneralTags.SENDER_ACSE_REQUIREMENTS] = "sender-acse-requirements" + list_[TranslatorGeneralTags.RESPONDER_ACSE_REQUIREMENT] = "responder-acse-requirements" + list_[TranslatorGeneralTags.RESPONDING_MECHANISM_NAME] = "mechanism-name" + list_[TranslatorGeneralTags.CALLING_MECHANISM_NAME] = "mechanism-name" + list_[TranslatorGeneralTags.CALLING_AUTHENTICATION] = "calling-authentication-value" + list_[TranslatorGeneralTags.RESPONDING_AUTHENTICATION] = "responding-authentication-value" + list_[Command.RELEASE_REQUEST] = "rlrq" + list_[Command.RELEASE_RESPONSE] = "rlre" + list_[Command.DISCONNECT_REQUEST] = "Disc" + list_[TranslatorGeneralTags.ASSOCIATION_RESULT] = "result" + list_[TranslatorGeneralTags.RESULT_SOURCE_DIAGNOSTIC] = "result-source-diagnostic" + list_[TranslatorGeneralTags.ACSE_SERVICE_USER] = "acse-service-user" + list_[TranslatorGeneralTags.CALLING_AP_TITLE] = "CallingAPTitle" + list_[TranslatorGeneralTags.RESPONDING_AP_TITLE] = "RespondingAPTitle" + list_[TranslatorGeneralTags.CHAR_STRING] = "charstring" + list_[TranslatorGeneralTags.DEDICATED_KEY] = "dedicated-key" + list_[TranslatorTags.RESPONSE_ALLOWED] = "response-allowed" + list_[TranslatorGeneralTags.USER_INFORMATION] = "user-information" + list_[Command.CONFIRMED_SERVICE_ERROR] = "confirmedServiceError" + list_[Command.INFORMATION_REPORT] = "informationReportRequest" + list_[Command.EVENT_NOTIFICATION] = "event-notification-request" + list_[TranslatorGeneralTags.CALLING_AE_INVOCATION_ID] = "calling-AE-invocation-id" + list_[TranslatorGeneralTags.CALLED_AE_INVOCATION_ID] = "called-AE-invocation-id" + list_[TranslatorGeneralTags.RESPONDING_AE_INVOCATION_ID] = "responding-AE-invocation-id" + list_[Command.EXCEPTION_RESPONSE] = "exception-response" + list_[TranslatorTags.STATE_ERROR] = "state-error" + list_[TranslatorTags.SERVICE_ERROR] = "service-error" + + # + # Get SN tags. + # + # @param type + # @param list + # + @classmethod + def getSnTags(cls, list_): + list_[Command.READ_REQUEST] = "readRequest" + list_[Command.WRITE_REQUEST] = "writeRequest" + list_[Command.WRITE_RESPONSE] = "writeResponse" + list_[Command.WRITE_REQUEST << 8 | SingleReadResponse.DATA] = "Data" + list_[Command.READ_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME] = "variable-name" + list_[Command.READ_REQUEST << 8 | VariableAccessSpecification.PARAMETERISED_ACCESS] = "parameterized-access" + list_[Command.READ_REQUEST << 8 | VariableAccessSpecification.BLOCK_NUMBER_ACCESS] = "BlockNumberAccess" + list_[Command.WRITE_REQUEST << 8 | VariableAccessSpecification.VARIABLE_NAME] = "variable-name" + list_[Command.READ_RESPONSE] = "readResponse" + list_[Command.READ_RESPONSE << 8 | SingleReadResponse.DATA_BLOCK_RESULT] = "DataBlockResult" + list_[Command.READ_RESPONSE << 8 | SingleReadResponse.DATA] = "data" + list_[Command.WRITE_RESPONSE << 8 | SingleReadResponse.DATA] = "data" + list_[Command.READ_RESPONSE << 8 | SingleReadResponse.DATA_ACCESS_ERROR] = "data-access-error" + + # + # Get LN tags. + # + # @param type + # @param list + # + @classmethod + def getLnTags(cls, list_): + list_[Command.GET_REQUEST] = "get-request" + list_[Command.GET_REQUEST << 8 | GetCommandType.NORMAL] = "get-request-normal" + list_[Command.GET_REQUEST << 8 | GetCommandType.NEXT_DATA_BLOCK] = "get-request-next" + list_[Command.GET_REQUEST << 8 | GetCommandType.WITH_LIST] = "get-request-with-list" + list_[Command.SET_REQUEST] = "set-request" + list_[Command.SET_REQUEST << 8 | SetRequestType.NORMAL] = "set-request-normal" + list_[Command.SET_REQUEST << 8 | SetRequestType.FIRST_DATA_BLOCK] = "set-request-first-data-block" + list_[Command.SET_REQUEST << 8 | SetRequestType.WITH_DATA_BLOCK] = "set-request-with-datablock" + list_[Command.SET_REQUEST << 8 | SetRequestType.WITH_LIST] = "set-request-with-list" + list_[Command.METHOD_REQUEST] = "action-request" + list_[Command.METHOD_REQUEST << 8 | ActionRequestType.NORMAL] = "action-request-normal" + list_[Command.METHOD_REQUEST << 8 | ActionRequestType.NEXT_BLOCK] = "ActionRequestForNextDataBlock" + list_[Command.METHOD_REQUEST << 8 | ActionRequestType.WITH_LIST] = "action-request-with-list" + list_[Command.METHOD_RESPONSE] = "action-response" + list_[Command.METHOD_RESPONSE << 8 | ActionResponseType.NORMAL] = "action-response-normal" + list_[Command.METHOD_RESPONSE << 8 | ActionResponseType.WITH_BLOCK] = "action-response-with-pblock" + list_[Command.METHOD_RESPONSE << 8 | ActionResponseType.WITH_LIST] = "action-response-with-list" + list_[Command.METHOD_RESPONSE << 8 | ActionResponseType.NEXT_BLOCK] = "action-response-next-pblock" + list_[TranslatorTags.SINGLE_RESPONSE] = "single-response" + list_[int(Command.DATA_NOTIFICATION)] = "data-notification" + list_[Command.GET_RESPONSE] = "get-response" + list_[Command.GET_RESPONSE << 8 | GetCommandType.NORMAL] = "get-response-normal" + list_[Command.GET_RESPONSE << 8 | GetCommandType.NEXT_DATA_BLOCK] = "get-response-with-data-block" + list_[Command.GET_RESPONSE << 8 | GetCommandType.WITH_LIST] = "get-response-with-list" + list_[Command.SET_RESPONSE] = "set-response" + list_[Command.SET_RESPONSE << 8 | SetResponseType.NORMAL] = "set-response-normal" + list_[Command.SET_RESPONSE << 8 | SetResponseType.DATA_BLOCK] = "set-response-data-block" + list_[Command.SET_RESPONSE << 8 | SetResponseType.LAST_DATA_BLOCK] = "set-response-with-last-data-block" + list_[Command.SET_RESPONSE << 8 | SetResponseType.WITH_LIST] = "set-response-with-list" + list_[Command.ACCESS_REQUEST] = "access-request" + list_[(Command.ACCESS_REQUEST) << 8 | AccessServiceCommandType.GET] = "access-request-get" + list_[(Command.ACCESS_REQUEST) << 8 | AccessServiceCommandType.SET] = "access-request-set" + list_[(Command.ACCESS_REQUEST) << 8 | AccessServiceCommandType.ACTION] = "access-request-action" + list_[Command.ACCESS_RESPONSE] = "access-response" + list_[(Command.ACCESS_RESPONSE) << 8 | AccessServiceCommandType.GET] = "access-response-get" + list_[(Command.ACCESS_RESPONSE) << 8 | AccessServiceCommandType.SET] = "access-response-set" + list_[(Command.ACCESS_RESPONSE) << 8 | AccessServiceCommandType.ACTION] = "access-response-action" + list_[TranslatorTags.ACCESS_REQUEST_BODY] = "access-request-body" + list_[TranslatorTags.LIST_OF_ACCESS_REQUEST_SPECIFICATION] = "access-request-specification" + list_[TranslatorTags.ACCESS_REQUEST_SPECIFICATION] = "Access-Request-Specification" + list_[TranslatorTags.ACCESS_REQUEST_LIST_OF_DATA] = "access-request-list-of-data" + list_[TranslatorTags.ACCESS_RESPONSE_BODY] = "access-response-body" + list_[TranslatorTags.LIST_OF_ACCESS_RESPONSE_SPECIFICATION] = "access-response-specification" + list_[TranslatorTags.ACCESS_RESPONSE_SPECIFICATION] = "Access-Response-Specification" + list_[TranslatorTags.ACCESS_RESPONSE_LIST_OF_DATA] = "access-response-list-of-data" + list_[TranslatorTags.SERVICE] = "service" + list_[TranslatorTags.SERVICE_ERROR] = "service-error" + list_[Command.GENERAL_BLOCK_TRANSFER] = "general-block-transfer" + list_[Command.GATEWAY_REQUEST] = "gateway-request" + list_[Command.GATEWAY_RESPONSE] = "gateway-response" + + # + # Get glo tags. + # + # @param type + # @param list + # + @classmethod + def getGloTags(cls, list_): + list_[Command.GLO_INITIATE_REQUEST] = "glo-initiate-request" + list_[Command.GLO_INITIATE_RESPONSE] = "glo-initiate-response" + list_[Command.GLO_GET_REQUEST] = "glo-get-request" + list_[Command.GLO_GET_RESPONSE] = "glo-get-response" + list_[Command.GLO_SET_REQUEST] = "glo-set-request" + list_[Command.GLO_SET_RESPONSE] = "glo-set-response" + list_[Command.GLO_METHOD_REQUEST] = "glo-action-request" + list_[Command.GLO_METHOD_RESPONSE] = "glo-action-response" + list_[Command.GLO_READ_REQUEST] = "glo-read-request" + list_[Command.GLO_READ_RESPONSE] = "glo-read-response" + list_[Command.GLO_WRITE_REQUEST] = "glo-write-request" + list_[Command.GLO_WRITE_RESPONSE] = "glo-write-response" + list_[Command.GENERAL_GLO_CIPHERING] = "general-glo-ciphering" + list_[Command.GENERAL_CIPHERING] = "general-ciphering" + list_[Command.GLO_CONFIRMED_SERVICE_ERROR] = "glo-confirmed-service-error" + + # + # Get ded tags. + # + # @param type + # @param list + # + @classmethod + def getDedTags(cls, list_): + list_[Command.DED_GET_REQUEST] = "ded-get-request" + list_[Command.DED_GET_RESPONSE] = "ded-get-response" + list_[Command.DED_SET_REQUEST] = "ded-set-request" + list_[Command.DED_SET_RESPONSE] = "ded-set-response" + list_[Command.DED_METHOD_REQUEST] = "ded-action-request" + list_[Command.DED_METHOD_RESPONSE] = "ded-action-response" + list_[Command.GENERAL_DED_CIPHERING] = "general-ded-ciphering" + list_[Command.DED_CONFIRMED_SERVICE_ERROR] = "ded-confirmed-service-error" + + # + # Get translator tags. + # + # @param type + # @param list + # + @classmethod + def getTranslatorTags(cls, list_): + list_[TranslatorTags.WRAPPER] = "Wrapper" + list_[TranslatorTags.HDLC] = "Hdlc" + list_[TranslatorTags.PDU_DLMS] = "xDLMS-APDU" + list_[TranslatorTags.PDU_CSE] = "aCSE-APDU" + list_[TranslatorTags.TARGET_ADDRESS] = "TargetAddress" + list_[TranslatorTags.SOURCE_ADDRESS] = "SourceAddress" + list_[TranslatorTags.FRAME_TYPE] = "FrameType" + list_[TranslatorTags.LIST_OF_VARIABLE_ACCESS_SPECIFICATION] = "variable-access-specification" + list_[TranslatorTags.LIST_OF_DATA] = "list-of-data" + list_[TranslatorTags.SUCCESS] = "Success" + list_[TranslatorTags.DATA_ACCESS_ERROR] = "data-access-result" + list_[TranslatorTags.ATTRIBUTE_DESCRIPTOR] = "cosem-attribute-descriptor" + list_[TranslatorTags.CLASS_ID] = "class-id" + list_[TranslatorTags.INSTANCE_ID] = "instance-id" + list_[TranslatorTags.ATTRIBUTE_ID] = "attribute-id" + list_[TranslatorTags.METHOD_INVOCATION_PARAMETERS] = "method-invocation-parameters" + list_[TranslatorTags.SELECTOR] = "selector" + list_[TranslatorTags.PARAMETER] = "parameter" + list_[TranslatorTags.LAST_BLOCK] = "last-block" + list_[TranslatorTags.BLOCK_NUMBER] = "block-number" + list_[TranslatorTags.RAW_DATA] = "raw-data" + list_[TranslatorTags.METHOD_DESCRIPTOR] = "cosem-method-descriptor" + list_[TranslatorTags.METHOD_ID] = "method-id" + list_[TranslatorTags.RESULT] = "result" + list_[TranslatorTags.RETURN_PARAMETERS] = "return-parameters" + list_[TranslatorTags.ACCESS_SELECTION] = "access-selection" + list_[TranslatorTags.VALUE] = "value" + list_[TranslatorTags.ACCESS_SELECTOR] = "access-selector" + list_[TranslatorTags.ACCESS_PARAMETERS] = "access-parameters" + list_[TranslatorTags.ATTRIBUTE_DESCRIPTOR_LIST] = "attribute-descriptor-list" + list_[TranslatorTags.ATTRIBUTE_DESCRIPTOR_WITH_SELECTION] = "Cosem-Attribute-Descriptor-With-Selection" + list_[TranslatorTags.READ_DATA_BLOCK_ACCESS] = "ReadDataBlockAccess" + list_[TranslatorTags.WRITE_DATA_BLOCK_ACCESS] = "WriteDataBlockAccess" + list_[TranslatorTags.DATA] = "data" + list_[TranslatorTags.INVOKE_ID] = "invoke-id-and-priority" + list_[TranslatorTags.LONG_INVOKE_ID] = "long-invoke-id-and-priority" + list_[TranslatorTags.DATE_TIME] = "date-time" + list_[TranslatorTags.CURRENT_TIME] = "current-time" + list_[TranslatorTags.TIME] = "time" + list_[TranslatorTags.REASON] = "reason" + list_[TranslatorTags.VARIABLE_ACCESS_SPECIFICATION] = "Variable-Access-Specification" + list_[TranslatorTags.CHOICE] = "CHOICE" + list_[TranslatorTags.NOTIFICATION_BODY] = "notification-body" + list_[TranslatorTags.DATA_VALUE] = "data-value" + list_[TranslatorTags.INITIATE_ERROR] = "initiateError" + list_[TranslatorTags.CIPHERED_SERVICE] = "ciphered-content" + list_[TranslatorTags.SYSTEM_TITLE] = "system-title" + list_[TranslatorTags.DATA_BLOCK] = "datablock" + list_[TranslatorTags.TRANSACTION_ID] = "transaction-id" + list_[TranslatorTags.ORIGINATOR_SYSTEM_TITLE] = "originator-system-title" + list_[TranslatorTags.RECIPIENT_SYSTEM_TITLE] = "recipient-system-title" + list_[TranslatorTags.OTHER_INFORMATION] = "other-information" + list_[TranslatorTags.KEY_INFO] = "key-info" + list_[TranslatorTags.CIPHERED_CONTENT] = "ciphered-content" + list_[TranslatorTags.AGREED_KEY] = "agreed-key" + list_[TranslatorTags.KEY_PARAMETERS] = "key-parameters" + list_[TranslatorTags.KEY_CIPHERED_DATA] = "key-ciphered-data" + list_[TranslatorTags.ATTRIBUTE_VALUE] = "attribute-value" + list_[TranslatorTags.MAX_INFO_RX] = "MaxInfoRX" + list_[TranslatorTags.MAX_INFO_TX] = "MaxInfoTX" + list_[TranslatorTags.WINDOW_SIZE_RX] = "WindowSizeRX" + list_[TranslatorTags.WINDOW_SIZE_TX] = "WindowSizeTX" + list_[TranslatorTags.VALUE_LIST] = "value-list" + list_[TranslatorTags.DATA_ACCESS_RESULT] = "data-access-result" + list_[TranslatorTags.BLOCK_CONTROL] = "block-control" + list_[TranslatorTags.BLOCK_NUMBER_ACK] = "block-number-ack" + list_[TranslatorTags.BLOCK_DATA] = "block-data" + list_[TranslatorTags.CONTENTS_DESCRIPTION] = "contents-description" + list_[TranslatorTags.ARRAY_CONTENTS] = "array-contents" + list_[TranslatorTags.NETWORK_ID] = "network-id" + list_[TranslatorTags.PHYSICAL_DEVICE_ADDRESS] = "physical-device-address" + list_[TranslatorTags.PROTOCOL_VERSION] = "protocol-version" + list_[TranslatorTags.CALLED_AP_TITLE] = "called-ap-title" + list_[TranslatorTags.CALLED_AP_INVOCATION_ID] = "called-ap-invocation-id" + list_[TranslatorTags.CALLED_AE_INVOCATION_ID] = "called-ae-invocation-id" + list_[TranslatorTags.CALLING_AP_INVOCATION_ID] = "calling-ap-invocation-id" + list_[TranslatorTags.CALLED_AE_QUALIFIER] = "called-ae-qualifier" + + @classmethod + def getDataTypeTags(cls, list_): + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.NONE] = "null-data" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.ARRAY] = "array" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.BCD] = "bcd" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.BITSTRING] = "bit-string" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.BOOLEAN] = "boolean" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.COMPACT_ARRAY] = "compact-array" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.DATE] = "date" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.DATETIME] = "date-time" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.ENUM] = "enum" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.FLOAT32] = "float32" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.FLOAT64] = "float64," + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.INT16] = "long" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.INT32] = "double-long" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.INT64] = "long64" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.INT8] = "integer" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.OCTET_STRING] = "octet-string" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.STRING] = "visible-string" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.STRING_UTF8] = "utf8-string" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.STRUCTURE] = "structure" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.TIME] = "time" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.UINT16] = "long-unsigned" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.UINT32] = "double-long-unsigned" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.UINT64] = "long64-unsigned" + list_[_GXCommon.DATA_TYPE_OFFSET + DataType.UINT8] = "unsigned" + + @classmethod + def errorCodeToString(cls, value): + value = ErrorCode(value) + str_ = None + if value == ErrorCode.ACCESS_VIOLATED: + str_ = "scope-of-access-violated" + elif value == ErrorCode.DATA_BLOCK_NUMBER_INVALID: + str_ = "data-block-number-invalid" + elif value == ErrorCode.DATA_BLOCK_UNAVAILABLE: + str_ = "data-block-unavailable" + elif value == ErrorCode.HARDWARE_FAULT: + str_ = "hardware-fault" + elif value == ErrorCode.INCONSISTENT_CLASS: + str_ = "object-class-inconsistent" + elif value == ErrorCode.LONG_GET_OR_READ_ABORTED: + str_ = "long-get-aborted" + elif value == ErrorCode.LONG_SET_OR_WRITE_ABORTED: + str_ = "long-set-aborted" + elif value == ErrorCode.NO_LONG_GET_OR_READ_IN_PROGRESS: + str_ = "no-long-get-in-progress" + elif value == ErrorCode.NO_LONG_SET_OR_WRITE_IN_PROGRESS: + str_ = "no-long-set-in-progress" + elif value == ErrorCode.OK: + str_ = "success" + elif value == ErrorCode.OTHER_REASON: + str_ = "other-reason" + elif value == ErrorCode.READ_WRITE_DENIED: + str_ = "read-write-denied" + elif value == ErrorCode.TEMPORARY_FAILURE: + str_ = "temporary-failure" + elif value == ErrorCode.UNAVAILABLE_OBJECT: + str_ = "object-unavailable" + elif value == ErrorCode.UNDEFINED_OBJECT: + str_ = "object-undefined" + elif value == ErrorCode.UNMATCHED_TYPE: + str_ = "type-unmatched" + else: + raise ValueError("Error code: " + str(value)) + return str_ + + @classmethod + def value_ofErrorCode(cls, value): + v = None + if "scope-of-access-violated".lower() == value.lower(): + v = ErrorCode.ACCESS_VIOLATED + elif "data-block-number-invalid".lower() == value.lower(): + v = ErrorCode.DATA_BLOCK_NUMBER_INVALID + elif "data-block-unavailable".lower() == value.lower(): + v = ErrorCode.DATA_BLOCK_UNAVAILABLE + elif "hardware-fault".lower() == value.lower(): + v = ErrorCode.HARDWARE_FAULT + elif "object-class-inconsistent".lower() == value.lower(): + v = ErrorCode.INCONSISTENT_CLASS + elif "long-get-aborted".lower() == value.lower(): + v = ErrorCode.LONG_GET_OR_READ_ABORTED + elif "long-set-aborted".lower() == value.lower(): + v = ErrorCode.LONG_SET_OR_WRITE_ABORTED + elif "no-long-get-in-progress".lower() == value.lower(): + v = ErrorCode.NO_LONG_GET_OR_READ_IN_PROGRESS + elif "no-long-set-in-progress".lower() == value.lower(): + v = ErrorCode.NO_LONG_SET_OR_WRITE_IN_PROGRESS + elif "success".lower() == value.lower(): + v = ErrorCode.OK + elif "other-reason".lower() == value.lower(): + v = ErrorCode.OTHER_REASON + elif "read-write-denied".lower() == value.lower(): + v = ErrorCode.READ_WRITE_DENIED + elif "temporary-failure".lower() == value.lower(): + v = ErrorCode.TEMPORARY_FAILURE + elif "object-unavailable".lower() == value.lower(): + v = ErrorCode.UNAVAILABLE_OBJECT + elif "object-undefined".lower() == value.lower(): + v = ErrorCode.UNDEFINED_OBJECT + elif "type-unmatched".lower() == value.lower(): + v = ErrorCode.UNMATCHED_TYPE + else: + raise ValueError("Error code: " + value) + return v + + @classmethod + def __getServiceErrors(cls): + list_ = dict() + list_[ServiceError.APPLICATION_REFERENCE] = "application-reference" + list_[ServiceError.HARDWARE_RESOURCE] = "hardware-resource" + list_[ServiceError.VDE_STATE_ERROR] = "vde-state-error" + list_[ServiceError.SERVICE] = "service" + list_[ServiceError.DEFINITION] = "definition" + list_[ServiceError.ACCESS] = "access" + list_[ServiceError.INITIATE] = "initiate" + list_[ServiceError.LOAD_DATASET] = "load-data-set" + list_[ServiceError.TASK] = "task" + return list_ + + @classmethod + + def __getApplicationReference(cls): + list_ = dict() + list_[ApplicationReference.APPLICATION_CONTEXT_UNSUPPORTED] = "application-context-unsupported" + list_[ApplicationReference.APPLICATION_REFERENCE_INVALID] = "application-reference-invalid" + list_[ApplicationReference.APPLICATION_UNREACHABLE] = "application-unreachable" + list_[ApplicationReference.DECIPHERING_ERROR] = "deciphering-error" + list_[ApplicationReference.OTHER] = "other" + list_[ApplicationReference.PROVIDER_COMMUNICATION_ERROR] = "provider-communication-error" + list_[ApplicationReference.TIME_ELAPSED] = "time-elapsed" + return list_ + + @classmethod + def __getHardwareResource(cls): + list_ = dict() + list_[HardwareResource.MASS_STORAGE_UNAVAILABLE] = "mass-storage-unavailable" + list_[HardwareResource.MEMORY_UNAVAILABLE] = "memory-unavailable" + list_[HardwareResource.OTHER] = "other" + list_[HardwareResource.OTHER_RESOURCE_UNAVAILABLE] = "other-resource-unavailable" + list_[HardwareResource.PROCESSOR_RESOURCE_UNAVAILABLE] = "processor-resource-unavailable" + return list_ + + @classmethod + def __getVdeStateError(cls): + list_ = dict() + list_[VdeStateError.LOADING_DATASET] = "loading-data-set" + list_[VdeStateError.NO_DLMS_CONTEXT] = "no-dlms-context" + list_[VdeStateError.OTHER] = "other" + list_[VdeStateError.STATUS_INOPERABLE] = "status-inoperable" + list_[VdeStateError.STATUS_NO_CHANGE] = "status-nochange" + return list_ + + @classmethod + def __getService(cls): + list_ = dict() + list_[Service.OTHER] = "other" + list_[Service.PDU_SIZE] = "pdu-size" + list_[Service.UNSUPPORTED] = "service-unsupported" + return list_ + + @classmethod + def __getDefinition(cls): + list_ = dict() + list_[Definition.OBJECT_ATTRIBUTE_INCONSISTENT] = "object-attribute-inconsistent" + list_[Definition.OBJECT_CLASS_INCONSISTENT] = "object-class-inconsistent" + list_[Definition.OBJECT_UNDEFINED] = "object-undefined" + list_[Definition.OTHER] = "other" + return list_ + + @classmethod + def __getAccess(cls): + list_ = dict() + list_[Access.HARDWARE_FAULT] = "hardware-fault" + list_[Access.OBJECT_ACCESS_INVALID] = "object-access-violated" + list_[Access.OBJECT_UNAVAILABLE] = "object-unavailable" + list_[Access.OTHER] = "other" + list_[Access.SCOPE_OF_ACCESS_VIOLATED] = "scope-of-access-violated" + return list_ + + @classmethod + def __getInitiate(cls): + list_ = dict() + list_[Initiate.DLMS_VERSION_TOO_LOW] = "dlms-version-too-low" + list_[Initiate.INCOMPATIBLE_CONFORMANCE] = "incompatible-conformance" + list_[Initiate.OTHER] = "other" + list_[Initiate.PDU_SIZE_TOO_SHORT] = "pdu-size-too-short" + list_[Initiate.REFUSED_BY_THE_VDE_HANDLER] = "refused-by-the-VDE-Handler" + return list_ + + @classmethod + def __getLoadDataSet(cls): + list_ = dict() + list_[LoadDataSet.DATASET_NOT_READY] = "data-set-not-ready" + list_[LoadDataSet.DATASET_SIZE_TOO_LARGE] = "dataset-size-too-large" + list_[LoadDataSet.INTERPRETATION_FAILURE] = "interpretation-failure" + list_[LoadDataSet.NOT_AWAITED_SEGMENT] = "not-awaited-segment" + list_[LoadDataSet.NOT_LOADABLE] = "not-loadable" + list_[LoadDataSet.OTHER] = "other" + list_[LoadDataSet.PRIMITIVE_OUT_OF_SEQUENCE] = "primitive-out-of-sequence" + list_[LoadDataSet.STORAGE_FAILURE] = "storage-failure" + return list_ + + @classmethod + def __getTask(cls): + list_ = dict() + list_[Task.NO_REMOTE_CONTROL] = "no-remote-control" + list_[Task.OTHER] = "other" + list_[Task.TI_RUNNING] = "ti-running" + list_[Task.TI_STOPPED] = "ti-stopped" + list_[Task.TI_UNUSABLE] = "ti-unusable" + return list_ + + @classmethod + def getServiceErrorValue(cls, error, value): + ret = "" + if error == ServiceError.APPLICATION_REFERENCE: + ret = cls.__getApplicationReference().get(ApplicationReference(value)) + elif error == ServiceError.HARDWARE_RESOURCE: + ret = cls.__getHardwareResource().get(HardwareResource(value)) + elif error == ServiceError.VDE_STATE_ERROR: + ret = cls.__getVdeStateError().get(VdeStateError(value)) + elif error == ServiceError.SERVICE: + ret = cls.__getService().get(Service(value)) + elif error == ServiceError.DEFINITION: + ret = cls.__getDefinition().get(Definition(value)) + elif error == ServiceError.ACCESS: + ret = cls.__getAccess().get(Access(value)) + elif error == ServiceError.INITIATE: + ret = cls.__getInitiate().get(Initiate(value)) + elif error == ServiceError.LOAD_DATASET: + ret = cls.__getLoadDataSet().get(LoadDataSet(value)) + elif error == ServiceError.TASK: + ret = cls.__getTask().get(Task(value)) + elif error == ServiceError.OTHER_ERROR: + ret = str(value) + return ret + + # + # @param error + # Service error enumeration value. + # Service error standard XML tag. + # + @classmethod + def serviceErrorToString(cls, error): + return cls.__getServiceErrors().get(error) + + # + # @param value + # Service error standard XML tag. + # Service error enumeration value. + # + @classmethod + def getServiceError(cls, value): + error = None + for k, v in cls.__getServiceErrors(): + if value.compareTo(v) == 0: + error = k + break + if error is None: + raise ValueError() + return error + + @classmethod + def __getApplicationReferenceByValue(cls, value): + ret = None + for k, v in cls.__getApplicationReference(): + if value.compareTo(v) == 0: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getHardwareResourceByValue(cls, value): + ret = None + for k, v in cls.__getHardwareResource(): + if value.compareTo(v) == 0: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getVdeStateErrorByValue(cls, value): + ret = None + for k, v in cls.__getVdeStateError(): + if value.compareTo(v) == 0: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getServiceByValue(cls, value): + ret = None + for k, v in cls.__getService(): + if value.compareTo(v) == 0: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getDefinitionByValue(cls, value): + ret = None + for k, v in cls.__getDefinition(): + if value.compareTo(v) == 0: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getAccessByValue(cls, value): + ret = None + for k, v in cls.__getAccess(): + if value.compareTo(v) == 0: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getInitiateByValue(cls, value): + ret = None + for k, v in cls.__getInitiate(): + if value.compareTo(v) == 0: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getLoadDataSetByValue(cls, value): + ret = None + for k, v in cls.__getLoadDataSet(): + if value.compareTo(v) == 0: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def __getTaskByValue(cls, value): + ret = None + for k, v in cls.__getTask(): + if value.compareTo(v) == 0: + ret = k + break + if ret is None: + raise ValueError() + return ret + + @classmethod + def getError(cls, serviceError, value): + ret = 0 + if serviceError == ServiceError.APPLICATION_REFERENCE: + ret = cls.__getApplicationReferenceByValue(value) + elif serviceError == ServiceError.HARDWARE_RESOURCE: + ret = cls.__getHardwareResourceByValue(value) + elif serviceError == ServiceError.VDE_STATE_ERROR: + ret = cls.__getVdeStateErrorByValue(value) + elif serviceError == ServiceError.SERVICE: + ret = cls.__getServiceByValue(value) + elif serviceError == ServiceError.DEFINITION: + ret = cls.__getDefinitionByValue(value) + elif serviceError == ServiceError.ACCESS: + ret = cls.__getAccessByValue(value) + elif serviceError == ServiceError.INITIATE: + ret = cls.__getInitiateByValue(value) + elif serviceError == ServiceError.LOAD_DATASET: + ret = cls.__getLoadDataSetByValue(value) + elif serviceError == ServiceError.TASK: + ret = cls.__getTaskByValue(value) + elif serviceError == ServiceError.OTHER_ERROR: + ret = int(value) + return int(ret) + + @classmethod + def conformancetoString(cls, value): + str_ = None + if value == Conformance.ACCESS: + str_ = "access" + elif value == Conformance.ACTION: + str_ = "action" + elif value == Conformance.ATTRIBUTE_0_SUPPORTED_WITH_GET: + str_ = "attribute0-supported-with-get" + elif value == Conformance.ATTRIBUTE_0_SUPPORTED_WITH_SET: + str_ = "attribute0-supported-with-set" + elif value == Conformance.BLOCK_TRANSFER_WITH_ACTION: + str_ = "block-transfer-with-action" + elif value == Conformance.BLOCK_TRANSFER_WITH_GET_OR_READ: + str_ = "block-transfer-with-get-or-read" + elif value == Conformance.BLOCK_TRANSFER_WITH_SET_OR_WRITE: + str_ = "block-transfer-with-set-or-write" + elif value == Conformance.DATA_NOTIFICATION: + str_ = "data-notification" + elif value == Conformance.EVENT_NOTIFICATION: + str_ = "event-notification" + elif value == Conformance.GENERAL_BLOCK_TRANSFER: + str_ = "general-block-transfer" + elif value == Conformance.GENERAL_PROTECTION: + str_ = "general-protection" + elif value == Conformance.GET: + str_ = "get" + elif value == Conformance.INFORMATION_REPORT: + str_ = "information-report" + elif value == Conformance.MULTIPLE_REFERENCES: + str_ = "multiple-references" + elif value == Conformance.PARAMETERIZED_ACCESS: + str_ = "parameterized-access" + elif value == Conformance.PRIORITY_MGMT_SUPPORTED: + str_ = "priority-mgmt-supported" + elif value == Conformance.READ: + str_ = "read" + elif value == Conformance.RESERVED_SEVEN: + str_ = "reserved-seven" + elif value == Conformance.DELTA_VALUE_ENCODING: + str_ = "delta-value-encoding" + elif value == Conformance.RESERVED_ZERO: + str_ = "reserved-zero" + elif value == Conformance.SELECTIVE_ACCESS: + str_ = "selective-access" + elif value == Conformance.SET: + str_ = "set" + elif value == Conformance.UN_CONFIRMED_WRITE: + str_ = "unconfirmed-write" + elif value == Conformance.WRITE: + str_ = "write" + else: + raise ValueError(str(value)) + return str_ + + @classmethod + def value_ofConformance(cls, value): + ret = None + if "access".lower() == value.lower(): + ret = Conformance.ACCESS + elif "action".lower() == value.lower(): + ret = Conformance.ACTION + elif "attribute0-supported-with-get".lower() == value.lower(): + ret = Conformance.ATTRIBUTE_0_SUPPORTED_WITH_GET + elif "attribute0-supported-with-set".lower() == value.lower(): + ret = Conformance.ATTRIBUTE_0_SUPPORTED_WITH_SET + elif "block-transfer-with-action".lower() == value.lower(): + ret = Conformance.BLOCK_TRANSFER_WITH_ACTION + elif "block-transfer-with-get-or-read".lower() == value.lower(): + ret = Conformance.BLOCK_TRANSFER_WITH_GET_OR_READ + elif "block-transfer-with-set-or-write".lower() == value.lower(): + ret = Conformance.BLOCK_TRANSFER_WITH_SET_OR_WRITE + elif "data-notification".lower() == value.lower(): + ret = Conformance.DATA_NOTIFICATION + elif "event-notification".lower() == value.lower(): + ret = Conformance.EVENT_NOTIFICATION + elif "general-block-transfer".lower() == value.lower(): + ret = Conformance.GENERAL_BLOCK_TRANSFER + elif "general-protection".lower() == value.lower(): + ret = Conformance.GENERAL_PROTECTION + elif "get".lower() == value.lower(): + ret = Conformance.GET + elif "information-report".lower() == value.lower(): + ret = Conformance.INFORMATION_REPORT + elif "multiple-references".lower() == value.lower(): + ret = Conformance.MULTIPLE_REFERENCES + elif "parameterized-access".lower() == value.lower(): + ret = Conformance.PARAMETERIZED_ACCESS + elif "priority-mgmt-supported".lower() == value.lower(): + ret = Conformance.PRIORITY_MGMT_SUPPORTED + elif "read".lower() == value.lower(): + ret = Conformance.READ + elif "reserved-seven".lower() == value.lower(): + ret = Conformance.RESERVED_SEVEN + elif "delta-value-encoding".lower() == value.lower(): + ret = Conformance.DELTA_VALUE_ENCODING + elif "reserved-zero".lower() == value.lower(): + ret = Conformance.RESERVED_ZERO + elif "selective-access".lower() == value.lower(): + ret = Conformance.SELECTIVE_ACCESS + elif "set".lower() == value.lower(): + ret = Conformance.SET + elif "unconfirmed-write".lower() == value.lower(): + ret = Conformance.UN_CONFIRMED_WRITE + elif "write".lower() == value.lower(): + ret = Conformance.WRITE + else: + raise ValueError(value) + return ret + + @classmethod + def releaseResponseReasonToString(cls, value): + str_ = None + if value == ReleaseResponseReason.NORMAL: + str_ = "normal" + elif value == ReleaseResponseReason.NOT_FINISHED: + str_ = "not-finished" + elif value == ReleaseResponseReason.USER_DEFINED: + str_ = "user-defined" + else: + raise ValueError(str(value)) + return str_ + + @classmethod + def value_ofReleaseResponseReason(cls, value): + ret = None + if "normal".lower() == value.lower(): + ret = ReleaseResponseReason.NORMAL + elif "not-finished".lower() == value.lower(): + ret = ReleaseResponseReason.NOT_FINISHED + elif "user-defined".lower() == value.lower(): + ret = ReleaseResponseReason.USER_DEFINED + else: + raise ValueError(value) + return ret + + @classmethod + def releaseRequestReasonToString(cls, value): + str_ = None + if value == ReleaseRequestReason.NORMAL: + str_ = "normal" + elif value == ReleaseRequestReason.URGENT: + str_ = "not-finished" + elif value == ReleaseRequestReason.USER_DEFINED: + str_ = "user-defined" + else: + raise ValueError(str(value)) + return str_ + + @classmethod + def value_ofReleaseRequestReason(cls, value): + ret = None + if "normal".lower() == value.lower(): + ret = ReleaseRequestReason.NORMAL + elif "not-finished".lower() == value.lower(): + ret = ReleaseRequestReason.URGENT + elif "user-defined".lower() == value.lower(): + ret = ReleaseRequestReason.USER_DEFINED + else: + raise ValueError(value) + return ret + + @classmethod + def stateErrorToString(cls, value): + if value == StateError.SERVICE_NOT_ALLOWED: + ret = "service-not-allowed" + elif value == StateError.SERVICE_UNKNOWN: + ret = "service-unknown" + else: + raise ValueError(value) + return ret + + @classmethod + def exceptionServiceErrorToString(cls, value): + if value == ExceptionServiceError.OPERATION_NOT_POSSIBLE: + ret = "operation-not-possible" + elif value == ExceptionServiceError.SERVICE_NOT_SUPPORTED: + ret = "service-not-supported" + elif value == ExceptionServiceError.OTHER_REASON: + ret = "other-reason" + else: + raise ValueError(value) + return ret + + @classmethod + def valueofStateError(cls, value): + if "service-not-allowed".lower() == value.lower(): + ret = StateError.SERVICE_NOT_ALLOWED + elif "service-unknown".lower() == value.lower(): + ret = StateError.SERVICE_UNKNOWN + else: + raise ValueError(value) + return ret + + @classmethod + def valueOfExceptionServiceError(cls, value): + if "operation-not-possible".lower() == value.lower(): + ret = ExceptionServiceError.OPERATION_NOT_POSSIBLE + elif "service-not-supported".lower() == value.lower(): + ret = ExceptionServiceError.SERVICE_NOT_SUPPORTED + elif "other-reason".lower() == value.lower(): + ret = ExceptionServiceError.OTHER_REASON + else: + raise ValueError(value) + return ret diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorTags.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorTags.py new file mode 100644 index 0000000..7f9f075 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/TranslatorTags.py @@ -0,0 +1,130 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class TranslatorTags(GXIntEnum): + WRAPPER = 0xFF01 + HDLC = 0xFF02 + PDU_DLMS = 0xFF03 + TARGET_ADDRESS = 0xFF04 + SOURCE_ADDRESS = 0xFF05 + LIST_OF_VARIABLE_ACCESS_SPECIFICATION = 0xFF06 + LIST_OF_DATA = 0xFF07 + SUCCESS = 0xFF08 + DATA_ACCESS_ERROR = 0xFF09 + ATTRIBUTE_DESCRIPTOR = 0xFF0A + CLASS_ID = 0xFF0B + INSTANCE_ID = 0xFF0C + ATTRIBUTE_ID = 0xFF0D + METHOD_INVOCATION_PARAMETERS = 0xFF0E + SELECTOR = 0xFF0F + PARAMETER = 0xFF10 + LAST_BLOCK = 0xFF11 + BLOCK_NUMBER = 0xFF12 + RAW_DATA = 0xFF13 + METHOD_DESCRIPTOR = 0xFF14 + METHOD_ID = 0xFF15 + RESULT = 0xFF16 + RETURN_PARAMETERS = 0xFF17 + ACCESS_SELECTION = 0xFF18 + VALUE = 0xFF19 + ACCESS_SELECTOR = 0xFF1A + ACCESS_PARAMETERS = 0xFF1B + ATTRIBUTE_DESCRIPTOR_LIST = 0xFF1C + ATTRIBUTE_DESCRIPTOR_WITH_SELECTION = 0xFF1D + READ_DATA_BLOCK_ACCESS = 0xFF1E + WRITE_DATA_BLOCK_ACCESS = 0xFF1F + DATA = 0xFF20 + INVOKE_ID = 0xFF21 + DATE_TIME = 0xFF22 + REASON = 0xFF23 + VARIABLE_ACCESS_SPECIFICATION = 0xFF24 + PDU_CSE = 0xFF26 + CHOICE = 0xFF27 + LONG_INVOKE_ID = 0xFF28 + NOTIFICATION_BODY = 0xFF29 + DATA_VALUE = 0xFF30 + ACCESS_REQUEST_BODY = 0xFF31 + LIST_OF_ACCESS_REQUEST_SPECIFICATION = 0xFF32 + ACCESS_REQUEST_SPECIFICATION = 0xFF33 + ACCESS_REQUEST_LIST_OF_DATA = 0xFF34 + ACCESS_RESPONSE_BODY = 0xFF35 + LIST_OF_ACCESS_RESPONSE_SPECIFICATION = 0xFF36 + ACCESS_RESPONSE_SPECIFICATION = 0xFF37 + ACCESS_RESPONSE_LIST_OF_DATA = 0xFF38 + SINGLE_RESPONSE = 0xFF39 + SERVICE = 0xFF40 + SERVICE_ERROR = 0xFF41 + INITIATE_ERROR = 0xFF42 + CIPHERED_SERVICE = 0xFF43 + SYSTEM_TITLE = 0xFF44 + DATA_BLOCK = 0xFF45 + TRANSACTION_ID = 0xFF46 + ORIGINATOR_SYSTEM_TITLE = 0xFF47 + RECIPIENT_SYSTEM_TITLE = 0xFF48 + OTHER_INFORMATION = 0xFF49 + KEY_INFO = 0xFF50 + AGREED_KEY = 0xFF51 + KEY_PARAMETERS = 0xFF52 + KEY_CIPHERED_DATA = 0xFF53 + CIPHERED_CONTENT = 0xFF54 + ATTRIBUTE_VALUE = 0xFF55 + CURRENT_TIME = 0xFF56 + TIME = 0xFF57 + MAX_INFO_RX = 0xFF58 + MAX_INFO_TX = 0xFF59 + WINDOW_SIZE_RX = 0xFF60 + WINDOW_SIZE_TX = 0xFF61 + VALUE_LIST = 0xFF62 + DATA_ACCESS_RESULT = 0xFF63 + FRAME_TYPE = 0xFF64 + BLOCK_CONTROL = 0xFF65 + BLOCK_NUMBER_ACK = 0xFF66 + BLOCK_DATA = 0xFF67 + CONTENTS_DESCRIPTION = 0xFF68 + ARRAY_CONTENTS = 0xFF69 + NETWORK_ID = 0xFF7A + PHYSICAL_DEVICE_ADDRESS = 0xFF7B + PROTOCOL_VERSION = 0xFF7C + CALLED_AP_TITLE = 0xFF7D + CALLED_AP_INVOCATION_ID = 0xFF7E + CALLED_AE_INVOCATION_ID = 0xFF7F + CALLING_AP_INVOCATION_ID = 0xFF80 + CALLED_AE_QUALIFIER = 0xFF81 + RESPONSE_ALLOWED = 0xFF82 + EXCEPTION_RESPONSE = 0xFF83 + STATE_ERROR = 0xFF84 + P_BLOCK = 0xFF85 + CONTENT = 0xFF86 + SIGNATURE = 0xFF87 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ValueEventArgs.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ValueEventArgs.py new file mode 100644 index 0000000..700d465 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/ValueEventArgs.py @@ -0,0 +1,93 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import ErrorCode, DataType +from .GXDLMSSettings import GXDLMSSettings + +# pylint: disable=bad-option-value,old-style-class,too-few-public-methods,too-many-instance-attributes +class ValueEventArgs: + # + # Constructor. + # + # @param s + # DLMS settings. + # @param eventTarget + # Event target. + # @param eventIndex + # Event index. + # @param readSelector + # Optional read event selector. + # @param forParameters + # Optional parameters. + # pylint: disable=too-many-arguments + def __init__(self, s, eventTarget, eventIndex, readSelector=0, forParameters=None): + if isinstance(s, GXDLMSSettings): + self.settings = s + else: + self.settings = s.settings + # Data type of the value. + self.dataType = DataType.NONE + # Target DLMS object + self.target = eventTarget + # Attribute index. + self.index = eventIndex + # Optional selector. + self.selector = readSelector + # Optional parameters. + self.parameters = forParameters + # Object value. + self.eventValue = None + # Is request handled. + self.handled = False + # Occurred error. + self.error = ErrorCode.OK + # Is action. This is reserved for internal use. + self.action = False + # Is value max PDU size skipped when converting data to bytes. + self.skipMaxPduSize = False + # Is reply handled as byte array or octect string. + self.byteArray = False + # Row to PDU is used with Profile Generic to tell how many rows are fit + # to + # one PDU. + self.rowToPdu = 0 + # Rows begin index. + self.rowBeginIndex = 0 + # Rows end index. + self.rowEndIndex = 0 + # DLMS server. + self.server = None + # Invoke ID. + self.invokeId = 0 + self.rowEndIndex = 0 + self.rowBeginIndex = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/VariableAccessSpecification.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/VariableAccessSpecification.py new file mode 100644 index 0000000..2f745fb --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/VariableAccessSpecification.py @@ -0,0 +1,62 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class VariableAccessSpecification(GXIntEnum): + """Enumerates how data is access on read or write.""" + + # + # Read data using SN. + # + VARIABLE_NAME = 2 + + # + # Get data using parameterized access. + # + PARAMETERISED_ACCESS = 4 + + # + # Get next block. + # + BLOCK_NUMBER_ACCESS = 5 + + # + # Read data as blocks. + # + READ_DATA_BLOCK_ACCESS = 6 + + # + # Write data as blocks. + # + WRITE_DATA_BLOCK_ACCESS = 7 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_GXAPDU.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_GXAPDU.py new file mode 100644 index 0000000..eea999b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_GXAPDU.py @@ -0,0 +1,1111 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +from .enums import BerType, PduType, Authentication, Command, AssociationResult, SourceDiagnostic, Service, AcseServiceProvider +from .ConfirmedServiceError import ConfirmedServiceError +from .GXDLMSConfirmedServiceError import GXDLMSConfirmedServiceError +from .internal._GXCommon import _GXCommon +from .GXByteBuffer import GXByteBuffer +from .GXDLMSException import GXDLMSException +from .TranslatorTags import TranslatorTags +from .TranslatorOutputType import TranslatorOutputType +from .TranslatorSimpleTags import TranslatorSimpleTags +from .TranslatorGeneralTags import TranslatorGeneralTags +from .ServiceError import ServiceError +from .TranslatorStandardTags import TranslatorStandardTags +from .enums.Security import Security +from .AesGcmParameter import AesGcmParameter +from .GXCiphering import GXCiphering + +# +# The services to access the attributes and methods of COSEM objects are +# determined on DLMS/COSEM Application layer. The services are carried by +# Application Protocol Data Units (APDUs). +#

+# In DLMS/COSEM the meter is primarily a server, and the controlling system +# is +# a client. Also unsolicited (received without a request) messages are +# available. +# pylint: disable=too-many-public-methods +class _GXAPDU: + # + # Retrieves the string that indicates the level of authentication, if any. + # + @classmethod + def getAuthenticationString(cls, settings, data, ignoreAcse): + if settings.authentication != Authentication.NONE or \ + (not ignoreAcse and settings.cipher and settings.cipher.security != Security.NONE): + # Add sender ACSE-requirements field component. + data.setUInt8(BerType.CONTEXT | PduType.SENDER_ACSE_REQUIREMENTS) + data.setUInt8(2) + data.setUInt8(BerType.BIT_STRING | BerType.OCTET_STRING) + data.setUInt8(0x80) + data.setUInt8(BerType.CONTEXT | PduType.MECHANISM_NAME) + # Len + data.setUInt8(7) + # OBJECT IDENTIFIER + p = [int(0x60), int(0x85), int(0x74), 0x05, 0x08, 0x02, settings.authentication] + data.set(p) + # If authentication is used. + if settings.authentication != Authentication.NONE: + # Add Calling authentication information. + len_ = 0 + callingAuthenticationValue = None + if settings.authentication == Authentication.LOW: + if settings.password: + callingAuthenticationValue = settings.password + else: + callingAuthenticationValue = settings.ctoSChallenge + # 0xAC + data.setUInt8(BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLING_AUTHENTICATION_VALUE) + # Len + if callingAuthenticationValue: + len_ = len(callingAuthenticationValue) + data.setUInt8((2 + len_)) + # Add authentication information. + data.setUInt8(BerType.CONTEXT) + # Len. + data.setUInt8(len_) + if len_ != 0: + data.set(callingAuthenticationValue) + + # + # Code application context name. + # + # @param settings + # DLMS settings. + # @param data + # Byte buffer where data is saved. + # @param cipher + # Is ciphering settings. + # + @classmethod + def generateApplicationContextName(cls, settings, data, cipher): + # ProtocolVersion + if settings.protocolVersion: + len_ = len(settings.protocolVersion) + data.setUInt8(BerType.CONTEXT | PduType.PROTOCOL_VERSION) + data.setUInt8(2) + data.setUInt8(8 - len_) + _GXCommon.setBitString(data, settings.protocolVersion, False) + # Application context name tag + data.setUInt8((BerType.CONTEXT | BerType.CONSTRUCTED | PduType.APPLICATION_CONTEXT_NAME)) + # Len + data.setUInt8(0x09) + data.setUInt8(BerType.OBJECT_IDENTIFIER) + # Len + data.setUInt8(0x07) + ciphered = cipher and cipher.isCiphered() + data.setUInt8(0x60) + data.setUInt8(0x85) + data.setUInt8(0x74) + data.setUInt8(0x5) + data.setUInt8(0x8) + data.setUInt8(0x1) + if settings.getUseLogicalNameReferencing(): + if ciphered: + data.setUInt8(3) + else: + data.setUInt8(1) + else: + if ciphered: + data.setUInt8(4) + else: + data.setUInt8(2) + + # Add system title. + if not settings.isServer and (ciphered or settings.authentication == Authentication.HIGH_GMAC) or settings.authentication == Authentication.HIGH_ECDSA: + if len(cipher.systemTitle) != 8: + raise ValueError("SystemTitle") + # Add calling-AP-title + data.setUInt8((BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLING_AP_TITLE)) + # LEN + data.setUInt8(2 + len(cipher.systemTitle)) + data.setUInt8(BerType.OCTET_STRING) + # LEN + data.setUInt8(len(cipher.systemTitle)) + data.set(cipher.systemTitle) + # Add CallingAEInvocationId. + if not settings.isServer and settings.userId != -1: + data.setUInt8(BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLING_AE_INVOCATION_ID) + # LEN + data.setUInt8(3) + data.setUInt8(BerType.INTEGER) + # LEN + data.setUInt8(1) + data.setUInt8(settings.userId) + + # Reserved for internal use. + @classmethod + def getConformanceToArray(cls, data): + ret = _GXCommon.swapBits(data.getUInt8()) + ret |= _GXCommon.swapBits(data.getUInt8()) << 8 + ret |= _GXCommon.swapBits(data.getUInt8()) << 16 + return ret + + # Reserved for internal use. + @classmethod + def setConformanceToArray(cls, value, data): + data.setUInt8(_GXCommon.swapBits(int(value) & 0xFF)) + data.setUInt8(_GXCommon.swapBits((int(value) >> 8) & 0xFF)) + data.setUInt8(_GXCommon.swapBits((int(value) >> 16) & 0xFF)) + + # + # Generate User information initiate request. + # + # @param settings + # DLMS settings. + # @param cipher + # @param data + # + @classmethod + def getInitiateRequest(cls, settings, data): + # Tag for xDLMS-Initiate request + data.setUInt8(Command.INITIATE_REQUEST) + # Usage field for the response allowed component. + # Usage field for dedicated-key component. + if not settings.cipher or settings.cipher.security == Security.NONE or not settings.cipher.dedicatedKey: + # Not used + data.setUInt8(0x00) + else: + data.setUInt8(1) + _GXCommon.setObjectCount(len(settings.cipher.dedicatedKey), data) + data.set(settings.cipher.dedicatedKey) + # encoding of the response-allowed component (BOOLEAN DEFAULT TRUE) + # usage flag (FALSE, default value TRUE conveyed) + data.setUInt8(0) + # Usage field of the proposed-quality-of-service component. Not used + if settings.qualityOfService == 0: + data.setUInt8(0x00) + else: + data.setUInt8(0x01) + data.setUInt8(settings.qualityOfService) + + data.setUInt8(settings.dlmsVersion) + # Tag for conformance block + data.setUInt8(0x5F) + data.setUInt8(0x1F) + # length of the conformance block + data.setUInt8(0x04) + # encoding the number of unused bits in the bit string + data.setUInt8(0x00) + cls.setConformanceToArray(settings.proposedConformance, data) + data.setUInt16(settings.maxPduSize) + + # + # Generate user information. + # + # @param settings + # DLMS settings. + # @param cipher + # @param data + # Generated user information. + # + @classmethod + def generateUserInformation(cls, settings, cipher, encryptedData, data): + data.setUInt8(BerType.CONTEXT | BerType.CONSTRUCTED | PduType.USER_INFORMATION) + if not cipher or not cipher.isCiphered(): + # Length for AARQ user field + data.setUInt8(0x10) + # Coding the choice for user-information (Octet STRING, universal) + data.setUInt8(BerType.OCTET_STRING) + # Length + data.setUInt8(0) + offset = len(data) + cls.getInitiateRequest(settings, data) + data.setUInt8(len(data) - offset, offset - 1) + else: + if encryptedData: + # Length for AARQ user field + data.setUInt8(int((4 + len(encryptedData)))) + # Tag + data.setUInt8(BerType.OCTET_STRING) + data.setUInt8(int((2 + len(encryptedData)))) + # Coding the choice for user-information (Octet STRING, + # universal) + data.setUInt8(int(Command.GLO_INITIATE_REQUEST)) + data.setUInt8(len(encryptedData)) + data.set(encryptedData) + else: + tmp = GXByteBuffer() + cls.getInitiateRequest(settings, tmp) + p = AesGcmParameter(Command.GLO_INITIATE_REQUEST, cipher.systemTitle, cipher.blockCipherKey, cipher.authenticationKey) + p.security = cipher.security + p.invocationCounter = cipher.invocationCounter + crypted = GXCiphering.encrypt(p, tmp.array()) + # Length for AARQ user field + data.setUInt8(2 + len(crypted)) + # Coding the choice for user-information (Octet string, + # universal) + data.setUInt8(BerType.OCTET_STRING) + data.setUInt8(len(crypted)) + data.set(crypted) + + # + # Generates Aarq. + # + @classmethod + def generateAarq(cls, settings, cipher, encryptedData, data): + # AARQ APDU Tag + data.setUInt8(BerType.APPLICATION | BerType.CONSTRUCTED) + # Length is updated later. + offset = len(data) + data.setUInt8(0) + # ///////////////////////////////////////// + # Add Application context name. + cls.generateApplicationContextName(settings, data, cipher) + cls.getAuthenticationString(settings, data, encryptedData) + cls.generateUserInformation(settings, cipher, encryptedData, data) + data.setUInt8((len(data) - offset - 1), offset) + + # pylint: disable=unused-variable + @classmethod + def getConformance(cls, value, xml): + tmp = 1 + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + for it in range(0, 24): + if (tmp & value) != 0: + xml.appendLine(TranslatorGeneralTags.CONFORMANCE_BIT, "Name", TranslatorSimpleTags.conformancetoString(tmp)) + tmp = tmp << 1 + else: + for it in range(0, 24): + if (tmp & value) != 0: + xml.append(TranslatorStandardTags.conformancetoString(tmp) + " ") + tmp = tmp << 1 + + # + # Parse User Information from PDU. + # + @classmethod + def parseUserInformation(cls, settings, cipher, data, xml): + len_ = data.getUInt8() + if len(data) - data.position < len_: + if xml is None: + raise ValueError("Not enough data.") + xml.appendComment("Error: Invalid data size.") + # Encoding the choice for user information + tag = data.getUInt8() + if tag != 0x4: + raise ValueError("Invalid tag.") + len_ = data.getUInt8() + if len(data) - data.position < len_: + if not xml: + raise ValueError("Not enough data.") + xml.appendComment("Error: Invalid data size.") + if xml and xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendLine(TranslatorGeneralTags.USER_INFORMATION, None, data.toHex(False, data.position, len_)) + data.position = data.position + len_ + return + _GXAPDU.parseInitiate(False, settings, cipher, data, xml) + + # pylint: disable=too-many-arguments,too-many-locals,unused-argument + @classmethod + def parse(cls, initiateRequest, settings, cipher, data, xml, tag2): + len_ = 0 + tmp2 = GXByteBuffer() + tmp2.setUInt8(0) + response = tag2 == Command.INITIATE_RESPONSE + if response: + if xml: + # + xml.appendStartTag(Command.INITIATE_RESPONSE) + # Optional usage field of the negotiated quality of service + # component + tag = data.getUInt8() + if tag != 0: + len_ = data.getUInt8() + data.position = data.position + len_ + if len_ == 0 and xml and xml.outputType == TranslatorOutputType.SIMPLE_XML: + # NegotiatedQualityOfService + xml.appendLine(TranslatorGeneralTags.NEGOTIATED_QUALITY_OF_SERVICE, "Value", "00") + elif tag2 == Command.INITIATE_REQUEST: + if xml: + xml.appendStartTag(Command.INITIATE_REQUEST) + # Optional usage field of the negotiated quality of service + # component + tag = data.getUInt8() + if tag != 0: + len_ = data.getUInt8() + tmp = bytearray(len_) + data.get(tmp) + if settings.cipher: + settings.cipher.setDedicatedKey(tmp) + if xml: + xml.appendLine(TranslatorGeneralTags.DEDICATED_KEY, None, GXByteBuffer.hex(tmp, False)) + elif settings.cipher: + settings.cipher.dedicatedKey = None + # Optional usage field of the negotiated quality of service + # component + tag = data.getUInt8() + if tag != 0: + len_ = data.getUInt8() + if xml and (initiateRequest or xml.outputType == TranslatorOutputType.SIMPLE_XML): + xml.appendLine(TranslatorGeneralTags.PROPOSED_QUALITY_OF_SERVICE, None, str(len_)) + else: + if xml and xml.outputType == TranslatorOutputType.STANDARD_XML: + xml.appendLine(TranslatorTags.RESPONSE_ALLOWED, None, "true") + # Optional usage field of the proposed quality of service + # component + tag = data.getUInt8() + # Skip if used. + if tag != 0: + len_ = data.getUInt8() + data.position = data.position + len_ + elif tag2 == Command.CONFIRMED_SERVICE_ERROR: + if xml: + xml.appendStartTag(Command.CONFIRMED_SERVICE_ERROR) + if xml.outputType == TranslatorOutputType.STANDARD_XML: + data.getUInt8() + xml.appendStartTag(TranslatorTags.INITIATE_ERROR) + type_ = ServiceError(data.getUInt8()) + str_ = TranslatorStandardTags.serviceErrorToString(type_) + value = TranslatorStandardTags.getServiceErrorValue(type_, int(data.getUInt8())) + xml.appendLine("x:" + str_, None, value) + xml.appendEndTag(TranslatorTags.INITIATE_ERROR) + else: + xml.appendLine(TranslatorTags.SERVICE, "Value", xml.integerToHex(data.getUInt8(), 2)) + type_ = ServiceError(data.getUInt8()) + xml.appendStartTag(TranslatorTags.SERVICE_ERROR) + xml.appendLine(TranslatorSimpleTags.serviceErrorToString(type_), "Value", TranslatorSimpleTags.getServiceErrorValue(type_, int(data.getUInt8()))) + xml.appendEndTag(TranslatorTags.SERVICE_ERROR) + xml.appendEndTag(Command.CONFIRMED_SERVICE_ERROR) + return + raise GXDLMSConfirmedServiceError(ConfirmedServiceError(data.getUInt8()), ServiceError(data.getUInt8()), data.getUInt8()) + else: + if xml: + xml.appendComment("Error: Failed to descypt data.") + data.position = len(data) + return + raise ValueError("Invalid tag.") + # Get DLMS version number. + if not response: + settings.dlmsVersion = data.getUInt8() + if settings.dlmsVersion != 6: + if not settings.isServer: + raise ValueError("Invalid DLMS version number.") + # ProposedDlmsVersionNumber + if xml and (initiateRequest or xml.outputType == TranslatorOutputType.SIMPLE_XML): + xml.appendLine(TranslatorGeneralTags.PROPOSED_DLMS_VERSION_NUMBER, "Value", xml.integerToHex(settings.dlmsVersion, 2)) + else: + if data.getUInt8() != 6: + raise ValueError("Invalid DLMS version number.") + if xml and (initiateRequest or xml.outputType == TranslatorOutputType.SIMPLE_XML): + xml.appendLine(TranslatorGeneralTags.NEGOTIATED_DLMS_VERSION_NUMBER, "Value", xml.integerToHex(settings.dlmsVersion, 2)) + # Tag for conformance block + tag = data.getUInt8() + if tag != 0x5F: + raise ValueError("Invalid tag.") + # Old Way... + if data.getUInt8(data.position) == 0x1F: + data.getUInt8() + len_ = data.getUInt8() + # The number of unused bits in the bit string. + tag = data.getUInt8() + v = cls.getConformanceToArray(data) + if settings.isServer: + settings.negotiatedConformance = v & settings.proposedConformance + if xml: + xml.appendStartTag(TranslatorGeneralTags.PROPOSED_CONFORMANCE) + cls.getConformance(v, xml) + else: + if xml: + xml.appendStartTag(TranslatorGeneralTags.NEGOTIATED_CONFORMANCE) + cls.getConformance(v, xml) + settings.negotiatedConformance = v + if not response: + # Proposed max PDU size. + pdu = data.getUInt16() + settings.maxPduSize = pdu + if xml: + # ProposedConformance closing + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + xml.appendEndTag(TranslatorGeneralTags.PROPOSED_CONFORMANCE) + elif initiateRequest: + xml.append(TranslatorGeneralTags.PROPOSED_CONFORMANCE, False) + # ProposedMaxPduSize + xml.appendLine(TranslatorGeneralTags.PROPOSED_MAX_PDU_SIZE, "Value", xml.integerToHex(pdu, 4)) + # If client asks too high PDU. + if pdu > settings.maxServerPDUSize: + settings.setMaxPduSize = settings.maxServerPDUSize + else: + pdu = data.getUInt16() + if xml is None and pdu < 64: + raise GXDLMSConfirmedServiceError(ConfirmedServiceError.INITIATE_ERROR, ServiceError.SERVICE, Service.PDU_SIZE) + # Max PDU size. + settings.maxPduSize = pdu + if xml: + # NegotiatedConformance closing + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + xml.appendEndTag(TranslatorGeneralTags.NEGOTIATED_CONFORMANCE) + elif initiateRequest: + xml.append(TranslatorGeneralTags.NEGOTIATED_CONFORMANCE, False) + # NegotiatedMaxPduSize + xml.appendLine(TranslatorGeneralTags.NEGOTIATED_MAX_PDU_SIZE, "Value", xml.integerToHex(settings.maxPduSize, 4)) + if response: + # VAA Name + tag = data.getUInt16() + if xml: + if initiateRequest or xml.outputType == TranslatorOutputType.SIMPLE_XML: + xml.appendLine(TranslatorGeneralTags.VAA_NAME, "Value", xml.integerToHex(tag, 4)) + if tag == 0x0007: + if initiateRequest: + settings.setUseLogicalNameReferencing(True) + else: + # If LN + if not settings.getUseLogicalNameReferencing() and xml is None: + raise ValueError("Invalid VAA.") + elif tag == 0xFA00: + # If SN + if initiateRequest: + settings.setUseLogicalNameReferencing(False) + else: + if settings.getUseLogicalNameReferencing(): + raise ValueError("Invalid VAA.") + else: + # Unknown VAA. + raise ValueError("Invalid VAA.") + if xml: + # + xml.appendEndTag(Command.INITIATE_RESPONSE) + elif xml: + xml.appendEndTag(Command.INITIATE_REQUEST) + + #pylint: disable=too-many-function-args, broad-except, too-many-arguments, + #too-many-locals + @classmethod + def parseInitiate(cls, initiateRequest, settings, cipher, data, xml): + # Tag for xDLMS-Initate.response + tag = data.getUInt8() + originalPos = 0 + if tag in (Command.GLO_INITIATE_RESPONSE, Command.GLO_INITIATE_REQUEST, + Command.DED_INITIATE_RESPONSE, Command.DED_INITIATE_REQUEST, + Command.GENERAL_GLO_CIPHERING, Command.GENERAL_DED_CIPHERING): + if xml: + originalPos = data.position + if tag in (Command.GENERAL_GLO_CIPHERING, Command.GENERAL_DED_CIPHERING): + cnt = _GXCommon.getObjectCount(data) + st = bytearray(cnt) + data.get(st) + else: + if tag in (Command.GLO_INITIATE_REQUEST, Command.DED_INITIATE_REQUEST): + st = settings.cipher.systemTitle + else: + st = settings.sourceSystemTitle + cnt = _GXCommon.getObjectCount(data) + encrypted = bytearray(cnt) + data.get(encrypted) + if st and cipher and cipher.blockCipherKey and cipher.authenticationKey and xml.comments: + pos = xml.getXmlLength() + try: + data.position = originalPos - 1 + p = AesGcmParameter(0, st, settings.cipher.blockCipherKey, settings.cipher.authenticationKey) + p.xml = (xml) + tmp = GXCiphering.decrypt(settings.cipher, p, data) + data.clear() + data.set(tmp) + cipher.setSecurity(p.security) + tag1 = data.getUInt8() + xml.startComment("Decrypted data:") + xml.appendLine("Security: " + str(Security(p.security))) + xml.appendLine("Invocation Counter: " + str(p.invocationCounter)) + cls.parse(initiateRequest, settings, cipher, data, xml, tag1) + xml.endComment() + except Exception: + # It's OK if this fails. + xml.setXmlLength(pos) + xml.appendLine(tag, None, GXByteBuffer.hex(encrypted, False)) + return + data.position = data.position - 1 + p = AesGcmParameter(0, settings.sourceSystemTitle, settings.cipher.blockCipherKey, settings.cipher.authenticationKey) + tmp = GXCiphering.decrypt(settings.cipher, p, data) + data.size = 0 + data.set(tmp) + cipher.security = p.security + cipher.securitySuite = p.securitySuite + tag = data.getUInt8() + _GXAPDU.parse(initiateRequest, settings, cipher, data, xml, tag) + + # + # Parse application context name. + # + # @param settings + # DLMS settings. + # @param buff + # Received data. + # + #pylint: disable=too-many-boolean-expressions, too-many-return-statements + @classmethod + def parseApplicationContextName(cls, settings, buff, xml): + #Get length. + len_ = buff.getUInt8() + if len(buff) - buff.position < len_: + raise ValueError("Encoding failed. Not enough data.") + if buff.getUInt8() != 0x6: + raise ValueError("Encoding failed. Not an Object ID.") + if settings.isServer and settings.cipher: + settings.cipher.setSecurity(Security.NONE) + # Object ID length. + len_ = buff.getUInt8() + tmp = bytearray(len_) + buff.get(tmp) + if tmp[0] != 0x60 or tmp[1] != 0x85 or tmp[2] != 0x74 or tmp[3] != 0x5 or tmp[4] != 0x8 or tmp[5] != 0x1: + if xml: + xml.appendLine(TranslatorGeneralTags.APPLICATION_CONTEXT_NAME, "Value", "UNKNOWN") + return True + raise Exception("Encoding failed. Invalid Application context name.") + name = tmp[6] + if xml: + if name == 1: + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + xml.appendLine(TranslatorGeneralTags.APPLICATION_CONTEXT_NAME, "Value", "LN") + else: + xml.appendLine(TranslatorGeneralTags.APPLICATION_CONTEXT_NAME, None, "1") + settings.setUseLogicalNameReferencing(True) + elif name == 3: + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + xml.appendLine(TranslatorGeneralTags.APPLICATION_CONTEXT_NAME, "Value", "LN_WITH_CIPHERING") + else: + xml.appendLine(TranslatorGeneralTags.APPLICATION_CONTEXT_NAME, None, "3") + settings.setUseLogicalNameReferencing(True) + elif name == 2: + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + xml.appendLine(TranslatorGeneralTags.APPLICATION_CONTEXT_NAME, "Value", "SN") + else: + xml.appendLine(TranslatorGeneralTags.APPLICATION_CONTEXT_NAME, None, "2") + settings.setUseLogicalNameReferencing(False) + elif name == 4: + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + xml.appendLine(TranslatorGeneralTags.APPLICATION_CONTEXT_NAME, "Value", "SN_WITH_CIPHERING") + else: + xml.appendLine(TranslatorGeneralTags.APPLICATION_CONTEXT_NAME, None, "4") + settings.setUseLogicalNameReferencing(False) + else: + return False + return True + if settings.getUseLogicalNameReferencing(): + if name == 1: + return True + # If ciphering is used. + return name == 3 + if name == 2: + return True + # If ciphering is used. + return name == 4 + + @classmethod + def validateAare(cls, settings, buff): + tag = buff.getUInt8() + if settings.isServer: + if tag != (BerType.APPLICATION | BerType.CONSTRUCTED | PduType.PROTOCOL_VERSION): + raise ValueError("Invalid tag.") + else: + if tag != (BerType.APPLICATION | BerType.CONSTRUCTED | PduType.APPLICATION_CONTEXT_NAME): + raise ValueError("Invalid tag.") + + # + # Parse APDU. + # + @classmethod + def parsePDU(cls, settings, cipher, buff, xml): + # Get AARE tag and length + cls.validateAare(settings, buff) + len_ = _GXCommon.getObjectCount(buff) + size = len(buff) - buff.position + if len_ > size: + if xml is None: + raise ValueError("Not enough data.") + xml.appendComment("Error: Invalid data size.") + # Opening tags + if xml: + if settings.isServer: + xml.appendStartTag(Command.AARQ) + else: + xml.appendStartTag(Command.AARE) + ret = _GXAPDU.parsePDU2(settings, cipher, buff, xml) + # Closing tags + if xml: + if settings.isServer: + xml.appendEndTag(Command.AARQ) + else: + xml.appendEndTag(Command.AARE) + return ret + + @classmethod + def parseProtocolVersion(cls, settings, buff, xml): + # Get count. + buff.getUInt8() + unusedBits = buff.getUInt8() + value = buff.getUInt8() + sb = _GXCommon.toBitString(value, 8 - unusedBits) + settings.protocolVersion = sb + if xml: + xml.appendLine(TranslatorTags.PROTOCOL_VERSION, "Value", settings.protocolVersion) + + # + # Parse APDU. + #pylint: disable=broad-except + @classmethod + def parsePDU2(cls, settings, cipher, buff, xml): + resultComponent = AssociationResult.ACCEPTED + resultDiagnosticValue = SourceDiagnostic.NONE + len_ = 0 + tag = 0 + while buff.position < len(buff): + tag = buff.getUInt8() + if tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.APPLICATION_CONTEXT_NAME: + if not cls.parseApplicationContextName(settings, buff, xml): + raise GXDLMSException(AssociationResult.PERMANENT_REJECTED, SourceDiagnostic.NOT_SUPPORTED) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLED_AP_TITLE: + # 0xA2 + # Get length. + if buff.getUInt8() != 3: + raise ValueError("Invalid tag.") + if settings.isServer: + # Choice for result (INTEGER, universal) + if buff.getUInt8() != BerType.OCTET_STRING: + raise ValueError("Invalid tag.") + len_ = buff.getUInt8() + tmp = bytearray(len_) + buff.get(tmp) + try: + settings.sourceSystemTitle = tmp + except Exception as ex: + if xml is None: + raise ex + if xml: + # RespondingAPTitle + if xml.comments: + xml.appendComment(_GXCommon.systemTitleToString(settings.standard, settings.sourceSystemTitle)) + xml.appendLine(TranslatorTags.CALLED_AP_TITLE, "Value", GXByteBuffer.hex(tmp, False)) + else: + # Choice for result (INTEGER, universal) + if buff.getUInt8() != BerType.INTEGER: + raise ValueError("Invalid tag.") + # Get length. + if buff.getUInt8() != 1: + raise ValueError("Invalid tag.") + resultComponent = AssociationResult(buff.getUInt8()) + if xml: + if resultComponent != AssociationResult.ACCEPTED: + xml.appendComment(resultComponent.__str__()) + xml.appendLine(TranslatorGeneralTags.ASSOCIATION_RESULT, "Value", xml.integerToHex(resultComponent, 2)) + xml.appendStartTag(TranslatorGeneralTags.RESULT_SOURCE_DIAGNOSTIC) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLED_AE_QUALIFIER: + # 0xA3 + resultDiagnosticValue = _GXAPDU.parseSourceDiagnostic(settings, buff, xml) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLED_AP_INVOCATION_ID: + # 0xA4 + _GXAPDU.parseResult(settings, buff, xml) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLING_AP_TITLE: + # 0xA6 + len_ = buff.getUInt8() + tag = buff.getUInt8() + len_ = buff.getUInt8() + tmp = bytearray(len_) + buff.get(tmp) + try: + settings.setSourceSystemTitle(tmp) + except Exception as ex: + if xml is None: + raise ex + _GXAPDU.appendClientSystemTitleToXml(settings, xml) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.SENDER_ACSE_REQUIREMENTS: + # 0xAA + len_ = buff.getUInt8() + tag = buff.getUInt8() + len_ = buff.getUInt8() + tmp = bytearray(len_) + buff.get(tmp) + settings.setStoCChallenge(tmp) + _GXAPDU.appendServerSystemTitleToXml(settings, xml, tag) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLING_AE_INVOCATION_ID: + # 0xA9 + len_ = buff.getUInt8() + tag = buff.getUInt8() + len_ = buff.getUInt8() + settings.userId = buff.getUInt8() + if xml: + # CallingAPTitle + xml.appendLine(TranslatorGeneralTags.CALLING_AE_INVOCATION_ID, "Value", xml.integerToHex(settings.userId, 2)) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLED_AE_INVOCATION_ID: + # 0xA5 + len_ = buff.getUInt8() + tag = buff.getUInt8() + len_ = buff.getUInt8() + settings.userId = buff.getUInt8() + if xml: + # CallingAPTitle + xml.appendLine(TranslatorGeneralTags.CALLED_AE_INVOCATION_ID, "Value", xml.integerToHex(settings.getUserId(), 2)) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | 7: + # 0xA7 + len_ = buff.getUInt8() + tag = buff.getUInt8() + len_ = buff.getUInt8() + settings.userId = buff.getUInt8() + if xml: + # CallingAPTitle + xml.appendLine(TranslatorGeneralTags.RESPONDING_AE_INVOCATION_ID, "Value", xml.integerToHex(settings.userId, 2)) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLING_AP_INVOCATION_ID: + # 0xA8 + if buff.getUInt8() != 3: + raise ValueError("Invalid tag.") + if buff.getUInt8() != 2: + raise ValueError("Invalid length.") + if buff.getUInt8() != 1: + raise ValueError("Invalid tag length.") + # Get value. + len_ = buff.getUInt8() + if xml: + # CallingApInvocationId + xml.appendLine(TranslatorTags.CALLING_AP_INVOCATION_ID, "Value", xml.integerToHex(len_, 2)) + elif tag in (BerType.CONTEXT | PduType.SENDER_ACSE_REQUIREMENTS, BerType.CONTEXT | PduType.CALLING_AP_INVOCATION_ID): + # 0x88 + # Get sender ACSE-requirements field component. + if buff.getUInt8() != 2: + raise ValueError("Invalid tag.") + if buff.getUInt8() != BerType.OBJECT_DESCRIPTOR: + raise ValueError("Invalid tag.") + # Get only value because client application is + # sending system title with LOW authentication. + buff.getUInt8() + if xml: + xml.appendLine(tag, "Value", "1") + elif tag in (BerType.CONTEXT | PduType.MECHANISM_NAME, BerType.CONTEXT | PduType.CALLING_AE_INVOCATION_ID): + # 0x89 + _GXAPDU.updateAuthentication(settings, buff) + if xml: + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + str_ = Authentication.toString(settings.authentication) + xml.appendLine(tag, "Value", str_) + else: + xml.appendLine(tag, "Value", str(settings.authentication)) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLING_AUTHENTICATION_VALUE: + # 0xAC + _GXAPDU.updatePassword(settings, buff, xml) + elif tag == BerType.CONTEXT | BerType.CONSTRUCTED | PduType.USER_INFORMATION: + # 0xBE + try: + _GXAPDU.parseUserInformation(settings, cipher, buff, xml) + except Exception: + if xml is None: + # pylint: disable=raise-missing-from + # Check result component. Some meters are returning invalid + # user-information if connection failed. + if xml is None and resultComponent != AssociationResult.ACCEPTED and resultDiagnosticValue != SourceDiagnostic.NONE: + raise GXDLMSException(resultComponent, resultDiagnosticValue) + if xml is None and resultComponent != AssociationResult.ACCEPTED and resultDiagnosticValue != AcseServiceProvider.NONE: + raise GXDLMSException(resultComponent, resultDiagnosticValue) + raise GXDLMSException(AssociationResult.PERMANENT_REJECTED, SourceDiagnostic.NO_REASON_GIVEN) + elif tag == BerType.CONTEXT: + # 0x80 + cls.parseProtocolVersion(settings, buff, xml) + else: + # Unknown tags. + print("Unknown tag: " + str(tag) + ".") + if buff.position < len(buff): + len_ = buff.getUInt8() + buff.position = buff.position + len_ + # All meters don't send user-information if connection is failed. + # For this reason result component is check again. + if xml is None and resultComponent != AssociationResult.ACCEPTED and isinstance(resultDiagnosticValue, (SourceDiagnostic)) and resultDiagnosticValue != SourceDiagnostic.NONE: + raise GXDLMSException(resultComponent, resultDiagnosticValue) + return resultDiagnosticValue + + @classmethod + def parseResult(cls, settings, buff, xml): + if settings.isServer: + # Get len. + if buff.getUInt8() != 3: + raise ValueError("Invalid tag.") + # Choice for result (Universal, Octetstring type) + if buff.getUInt8() != BerType.INTEGER: + raise ValueError("Invalid tag.") + if buff.getUInt8() != 1: + raise ValueError("Invalid tag length.") + # Get value. + len_ = buff.getUInt8() + if xml: + # RespondingAPTitle + xml.appendLine(TranslatorTags.CALLED_AP_INVOCATION_ID, "Value", xml.integerToHex(len_, 2)) + else: + # Get length. + if buff.getUInt8() != 0xA: + raise ValueError("Invalid tag.") + # Choice for result (Universal, Octet string type) + if buff.getUInt8() != BerType.OCTET_STRING: + raise ValueError("Invalid tag.") + # responding-AP-title-field + # Get length. + len_ = buff.getUInt8() + tmp = bytearray(len_) + buff.get(tmp) + settings.setSourceSystemTitle(tmp) + cls.appendResultToXml(settings, xml) + + @classmethod + def parseSourceDiagnostic(cls, settings, buff, xml): + tag = int() + resultDiagnosticValue = SourceDiagnostic.NONE + len_ = buff.getUInt8() + # ACSE service user tag. + tag = buff.getUInt8() + len_ = buff.getUInt8() + if settings.isServer: + calledAEQualifier = bytearray(len_) + buff.get(calledAEQualifier) + if xml: + xml.appendLine(TranslatorTags.CALLED_AE_QUALIFIER, "Value", GXByteBuffer.hex(calledAEQualifier, False)) + else: + # Result source diagnostic component. + tag = buff.getUInt8() + if tag != BerType.INTEGER: + raise ValueError("Invalid tag.") + len_ = buff.getUInt8() + if len_ != 1: + raise ValueError("Invalid tag.") + resultDiagnosticValue = SourceDiagnostic(buff.getUInt8()) + if xml: + if resultDiagnosticValue != SourceDiagnostic.NONE: + xml.appendComment(resultDiagnosticValue.__str__()) + xml.appendLine(TranslatorGeneralTags.ACSE_SERVICE_USER, "Value", xml.integerToHex(resultDiagnosticValue, 2)) + xml.appendEndTag(TranslatorGeneralTags.RESULT_SOURCE_DIAGNOSTIC) + return resultDiagnosticValue + + @classmethod + def appendServerSystemTitleToXml(cls, settings, xml, tag): + if xml: + # RespondingAuthentication + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + xml.appendLine(tag, None, GXByteBuffer.hex(settings.getStoCChallenge(), False)) + else: + xml.append(tag, True) + xml.append(TranslatorGeneralTags.CHAR_STRING, True) + xml.append(GXByteBuffer.hex(settings.getStoCChallenge(), False)) + xml.append(TranslatorGeneralTags.CHAR_STRING, False) + xml.append(tag, False) + xml.append("\n") + + @classmethod + def appendClientSystemTitleToXml(cls, settings, xml): + if xml: + # CallingAPTitle + xml.appendLine(TranslatorGeneralTags.CALLING_AP_TITLE, "Value", GXByteBuffer.hex(settings.sourceSystemTitle, False)) + + @classmethod + def appendResultToXml(cls, settings, xml): + if xml: + # RespondingAPTitle + xml.appendLine(TranslatorGeneralTags.RESPONDING_AP_TITLE, "Value", GXByteBuffer.hex(settings.sourceSystemTitle, False)) + + @classmethod + def updatePassword(cls, settings, buff, xml): + tmp = [] + len_ = buff.getUInt8() + # Get authentication information. + if buff.getUInt8() != 0x80: + raise ValueError("Invalid tag.") + len_ = buff.getUInt8() + tmp = bytearray(len_) + buff.get(tmp) + if settings.authentication == Authentication.LOW: + settings.password = tmp + else: + settings.ctoSChallenge = tmp + if xml: + if xml.outputType == TranslatorOutputType.SIMPLE_XML: + if GXByteBuffer.isAsciiString(tmp): + xml.appendComment(tmp.decode("utf-8")) + xml.appendLine(TranslatorGeneralTags.CALLING_AUTHENTICATION, "Value", GXByteBuffer.hex(tmp, False)) + else: + xml.appendStartTag(TranslatorGeneralTags.CALLING_AUTHENTICATION) + xml.appendStartTag(TranslatorGeneralTags.CHAR_STRING) + if settings.authentication == Authentication.LOW: + xml.append(GXByteBuffer.hex(settings.password, False)) + else: + xml.append(GXByteBuffer.hex(settings.ctoSChallenge, False)) + xml.appendEndTag(TranslatorGeneralTags.CHAR_STRING) + xml.appendEndTag(TranslatorGeneralTags.CALLING_AUTHENTICATION) + + @classmethod + def updateAuthentication(cls, settings, buff): + ch = buff.getUInt8() + if buff.getUInt8() != 0x60: + raise ValueError("Invalid tag.") + if buff.getUInt8() != 0x85: + raise ValueError("Invalid tag.") + if buff.getUInt8() != 0x74: + raise ValueError("Invalid tag.") + if buff.getUInt8() != 0x05: + raise ValueError("Invalid tag.") + if buff.getUInt8() != 0x08: + raise ValueError("Invalid tag.") + if buff.getUInt8() != 0x02: + raise ValueError("Invalid tag.") + ch = buff.getUInt8() + if ch < 0 or ch > 7: + raise ValueError("Invalid tag.") + settings.authentication = ch + + #pylint: disable=too-many-function-args + @classmethod + def getUserInformation(cls, settings, cipher): + data = GXByteBuffer() + # Tag for xDLMS-Initiate response + data.setUInt8(Command.INITIATE_RESPONSE) + # Usage field for the response allowed component (not used) + data.setUInt8(0x00) + # DLMS Version Number + data.setUInt8(6) + data.setUInt8(0x5F) + data.setUInt8(0x1F) + # length of the conformance block + data.setUInt8(0x04) + # encoding the number of unused bits in the bit string + data.setUInt8(0x00) + cls.setConformanceToArray(settings.negotiatedConformance, data) + data.setUInt16(settings.maxPduSize) + # VAA Name VAA name (0x0007 for LN referencing and 0xFA00 for SN) + if settings.getUseLogicalNameReferencing(): + data.setUInt16(0x0007) + else: + data.setUInt16(0xFA00) + if cipher and cipher.isCiphered(): + p = AesGcmParameter(Command.GLO_INITIATE_RESPONSE, cipher.systemTitle, cipher.blockCipherKey, cipher.authenticationKey) + p.security = cipher.security + p.invocationCounter = cipher.invocationCounter + return GXCiphering.encrypt(p, data.array()) + + if settings.increaseInvocationCounterForGMacAuthentication: + cipher.InvocationCounter += 1 + return data.array() + + # + # Server generates AARE message. + # pylint: disable=too-many-arguments + @classmethod + def generateAARE(cls, settings, data, result, diagnostic, cipher, errorData, encryptedData): + offset = len(data) + # Set AARE tag and length 0x61 + data.setUInt8(BerType.APPLICATION | BerType.CONSTRUCTED | PduType.APPLICATION_CONTEXT_NAME) + # Length is updated later. + data.setUInt8(0) + cls.generateApplicationContextName(settings, data, cipher) + # Result 0xA2 + data.setUInt8(BerType.CONTEXT | BerType.CONSTRUCTED | BerType.INTEGER) + data.setUInt8(3) + # len + data.setUInt8(BerType.INTEGER) + # Tag + # Choice for result (INTEGER, universal) + data.setUInt8(1) + # Len + data.setUInt8(result) + # ResultValue + # SourceDiagnostic + data.setUInt8(0xA3) + data.setUInt8(5) + # len + data.setUInt8(0xA1) + # Tag + data.setUInt8(3) + # len + data.setUInt8(2) + # Tag + # Choice for result (INTEGER, universal) + data.setUInt8(1) + # Len + # diagnostic + data.setUInt8(diagnostic) + # SystemTitle + if cipher and (settings.authentication == Authentication.HIGH_GMAC or cipher.isCiphered()): + data.setUInt8(BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLED_AP_INVOCATION_ID) + data.setUInt8((len(cipher.systemTitle))) + data.setUInt8(BerType.OCTET_STRING) + data.setUInt8() + data.set(cipher.systemTitle) + # Add CalledAEInvocationId. + if settings.userId != -1: + data.setUInt8(BerType.CONTEXT | BerType.CONSTRUCTED | PduType.CALLED_AE_INVOCATION_ID) + # LEN + data.setUInt8(3) + data.setUInt8(BerType.INTEGER) + # LEN + data.setUInt8(1) + data.setUInt8(settings.userId) + if settings.authentication > Authentication.LOW: + # Add server ACSE-requirenents field component. + data.setUInt8(0x88) + data.setUInt8(0x02) + # Len. + data.setUInt16(0x0780) + # Add tag. + data.setUInt8(0x89) + data.setUInt8(0x07) + # Len + data.setUInt8(0x60) + data.setUInt8(0x85) + data.setUInt8(0x74) + data.setUInt8(0x05) + data.setUInt8(0x08) + data.setUInt8(0x02) + data.setUInt8(settings.authentication) + # Add tag. + data.setUInt8(0xAA) + data.setUInt8((len(settings.stoCChallenge))) + # Len + data.setUInt8(BerType.CONTEXT) + data.setUInt8(len(settings.stoCChallenge)) + data.set(settings.stoCChallenge) + if result == AssociationResult.ACCEPTED or not cipher or cipher.security == Security.NONE: + # Add User Information + # Tag 0xBE + data.setUInt8(BerType.CONTEXT | BerType.CONSTRUCTED | PduType.USER_INFORMATION) + if encryptedData: + tmp2 = GXByteBuffer(2 + len(encryptedData)) + tmp2.setUInt8(Command.GLO_INITIATE_RESPONSE) + _GXCommon.setObjectCount(len(encryptedData), tmp2) + tmp2.set(encryptedData) + tmp = tmp2.array() + else: + if errorData: + tmp = errorData + else: + tmp = cls.getUserInformation(settings, cipher) + data.setUInt8(2 + (len(tmp))) + # Coding the choice for user-information (Octet STRING, universal) + data.setUInt8(BerType.OCTET_STRING) + # Length + data.setUInt8(len(tmp)) + data.set(tmp) + data.setUInt8((len(data) - offset - 2), (offset + 1)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_GXFCS16.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_GXFCS16.py new file mode 100644 index 0000000..746ab58 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_GXFCS16.py @@ -0,0 +1,110 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# +# * Reserved for internal use. +# +class _GXFCS16: + + __fcs16Table = (0x0000, 0x1189, 0x2312, 0x329B, 0x4624, + 0x57AD, 0x6536, 0x74BF, 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, + 0xDBE5, 0xE97E, 0xF8F7, 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, + 0x472C, 0x75B7, 0x643E, 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, + 0xCB64, 0xF9FF, 0xE876, 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, + 0x76AF, 0x4434, 0x55BD, 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, + 0xFAE7, 0xC87C, 0xD9F5, 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, + 0x662E, 0x54B5, 0x453C, 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, + 0xEA66, 0xD8FD, 0xC974, 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, + 0x15A9, 0x2732, 0x36BB, 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, + 0x99E1, 0xAB7A, 0xBAF3, 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, + 0x0528, 0x37B3, 0x263A, 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, + 0x8960, 0xBBFB, 0xAA72, 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, + 0x34AB, 0x0630, 0x17B9, 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, + 0xB8E3, 0x8A78, 0x9BF1, 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, + 0x242A, 0x16B1, 0x0738, 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, + 0xA862, 0x9AF9, 0x8B70, 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, + 0xD3A5, 0xE13E, 0xF0B7, 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, + 0x5FED, 0x6D76, 0x7CFF, 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, + 0xC324, 0xF1BF, 0xE036, 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, + 0x4F6C, 0x7DF7, 0x6C7E, 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, + 0xF2A7, 0xC03C, 0xD1B5, 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, + 0x7EEF, 0x4C74, 0x5DFD, 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, + 0xE226, 0xD0BD, 0xC134, 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, + 0x6E6E, 0x5CF5, 0x4D7C, 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, + 0x91A1, 0xA33A, 0xB2B3, 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, + 0x1DE9, 0x2F72, 0x3EFB, 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, + 0x8120, 0xB3BB, 0xA232, 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, + 0x0D68, 0x3FF3, 0x2E7A, 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, + 0xB0A3, 0x8238, 0x93B1, 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, + 0x3CEB, 0x0E70, 0x1FF9, 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, + 0xA022, 0x92B9, 0x8330, 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, + 0x2C6A, 0x1EF1, 0x0F78) + + # + # * Reserved for internal use. + # * + # * @param buff + # * @param offset + # * @param count + # * @return + # + @classmethod + def countFCS16(cls, buff, offset, count): + fcs16 = 0xFFFF + pos = offset + while pos < offset + count: + fcs16 = int((((fcs16 >> 8) ^ _GXFCS16.__fcs16Table[(fcs16 ^ buff[pos]) & 0xFF]) & 0xFFFF)) + pos += 1 + fcs16 = ~fcs16 + fcs16 = ((fcs16 >> 8) & 0xFF) | (fcs16 << 8) + return fcs16 & 0xFFFF + + ___CRCPOLY = 0xD3B6BA00 + #Reserved for internal use. + @classmethod + def countFCS24(cls, buff, index, count): + crcreg = 0 + j = 0 + while j < count: + b = buff[index + j] + i = 0 + while i < 8: + crcreg >>= 1 + if (b and 0x80) != 0: + crcreg = crcreg or 0x80000000 + if (crcreg and 0x80) != 0: + crcreg = crcreg ^ cls.___CRCPOLY + b <<= 1 + i = i + 1 + j = j + 1 + return crcreg >> 8 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_GXObjectFactory.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_GXObjectFactory.py new file mode 100644 index 0000000..00939ce --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_GXObjectFactory.py @@ -0,0 +1,220 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import ObjectType +#pylint: disable=bad-option-value,too-many-locals, +#cyclic-import,old-style-class,too-few-public-methods +from .objects.GXDLMSAssociationLogicalName import GXDLMSAssociationLogicalName +from .objects.GXDLMSObject import GXDLMSObject +from .objects.GXDLMSActionSchedule import GXDLMSActionSchedule +from .objects.GXDLMSActivityCalendar import GXDLMSActivityCalendar +from .objects.GXDLMSAssociationShortName import GXDLMSAssociationShortName +from .objects.GXDLMSAutoAnswer import GXDLMSAutoAnswer +from .objects.GXDLMSAutoConnect import GXDLMSAutoConnect +from .objects.GXDLMSClock import GXDLMSClock +from .objects.GXDLMSData import GXDLMSData +from .objects.GXDLMSDemandRegister import GXDLMSDemandRegister +from .objects.GXDLMSMacAddressSetup import GXDLMSMacAddressSetup +from .objects.GXDLMSRegister import GXDLMSRegister +from .objects.GXDLMSExtendedRegister import GXDLMSExtendedRegister +from .objects.GXDLMSGprsSetup import GXDLMSGprsSetup +from .objects.GXDLMSHdlcSetup import GXDLMSHdlcSetup +from .objects.GXDLMSIECLocalPortSetup import GXDLMSIECLocalPortSetup +from .objects.GXDLMSIecTwistedPairSetup import GXDLMSIecTwistedPairSetup +from .objects.GXDLMSIp4Setup import GXDLMSIp4Setup +from .objects.GXDLMSIp6Setup import GXDLMSIp6Setup +from .objects.GXDLMSMBusSlavePortSetup import GXDLMSMBusSlavePortSetup +from .objects.GXDLMSImageTransfer import GXDLMSImageTransfer +from .objects.GXDLMSSecuritySetup import GXDLMSSecuritySetup +from .objects.GXDLMSDisconnectControl import GXDLMSDisconnectControl +from .objects.GXDLMSLimiter import GXDLMSLimiter + +from .objects.GXDLMSMBusClient import GXDLMSMBusClient +from .objects.GXDLMSModemConfiguration import GXDLMSModemConfiguration +from .objects.GXDLMSPppSetup import GXDLMSPppSetup +from .objects.GXDLMSProfileGeneric import GXDLMSProfileGeneric +from .objects.GXDLMSRegisterMonitor import GXDLMSRegisterMonitor +from .objects.GXDLMSRegisterActivation import GXDLMSRegisterActivation +from .objects.GXDLMSSapAssignment import GXDLMSSapAssignment +from .objects.GXDLMSSchedule import GXDLMSSchedule +from .objects.GXDLMSScriptTable import GXDLMSScriptTable +from .objects.GXDLMSSpecialDaysTable import GXDLMSSpecialDaysTable +from .objects.GXDLMSTcpUdpSetup import GXDLMSTcpUdpSetup +from .objects.GXDLMSPushSetup import GXDLMSPushSetup +from .objects.GXDLMSMBusMasterPortSetup import GXDLMSMBusMasterPortSetup +from .objects.GXDLMSGSMDiagnostic import GXDLMSGSMDiagnostic +from .objects.GXDLMSAccount import GXDLMSAccount +from .objects.GXDLMSCredit import GXDLMSCredit +from .objects.GXDLMSCharge import GXDLMSCharge +from .objects.GXDLMSTokenGateway import GXDLMSTokenGateway +from .objects.GXDLMSParameterMonitor import GXDLMSParameterMonitor +from .objects.GXDLMSUtilityTables import GXDLMSUtilityTables +from .objects.GXDLMSLlcSscsSetup import GXDLMSLlcSscsSetup +from .objects.GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters import GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters +from .objects.GXDLMSPrimeNbOfdmPlcMacSetup import GXDLMSPrimeNbOfdmPlcMacSetup +from .objects.GXDLMSPrimeNbOfdmPlcMacFunctionalParameters import GXDLMSPrimeNbOfdmPlcMacFunctionalParameters +from .objects.GXDLMSPrimeNbOfdmPlcMacCounters import GXDLMSPrimeNbOfdmPlcMacCounters +from .objects.GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData import GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData +from .objects.GXDLMSPrimeNbOfdmPlcApplicationsIdentification import GXDLMSPrimeNbOfdmPlcApplicationsIdentification +from .objects.GXDLMSNtpSetup import GXDLMSNtpSetup + +class _GXObjectFactory: + #Reserved for internal use. + + # + # Constructor. + def __init__(self): + pass + + @classmethod + def createObject(cls, ot): + #pylint: disable=bad-option-value,redefined-variable-type + # If IC is manufacturer specific or unknown. + if ot is None: + raise ValueError("Invalid object type.") + + if ot == ObjectType.ACTION_SCHEDULE: + ret = GXDLMSActionSchedule() + elif ot == ObjectType.ACTIVITY_CALENDAR: + ret = GXDLMSActivityCalendar() + elif ot == ObjectType.ASSOCIATION_LOGICAL_NAME: + ret = GXDLMSAssociationLogicalName() + elif ot == ObjectType.ASSOCIATION_SHORT_NAME: + ret = GXDLMSAssociationShortName() + elif ot == ObjectType.AUTO_ANSWER: + ret = GXDLMSAutoAnswer() + elif ot == ObjectType.AUTO_CONNECT: + ret = GXDLMSAutoConnect() + elif ot == ObjectType.CLOCK: + ret = GXDLMSClock() + elif ot == ObjectType.DATA: + ret = GXDLMSData() + elif ot == ObjectType.DEMAND_REGISTER: + ret = GXDLMSDemandRegister() + elif ot == ObjectType.MAC_ADDRESS_SETUP: + ret = GXDLMSMacAddressSetup() + elif ot == ObjectType.REGISTER: + ret = GXDLMSRegister() + elif ot == ObjectType.EXTENDED_REGISTER: + ret = GXDLMSExtendedRegister() + elif ot == ObjectType.GPRS_SETUP: + ret = GXDLMSGprsSetup() + elif ot == ObjectType.IEC_HDLC_SETUP: + ret = GXDLMSHdlcSetup() + elif ot == ObjectType.IEC_LOCAL_PORT_SETUP: + ret = GXDLMSIECLocalPortSetup() + elif ot == ObjectType.IEC_TWISTED_PAIR_SETUP: + ret = GXDLMSIecTwistedPairSetup() + elif ot == ObjectType.IP4_SETUP: + ret = GXDLMSIp4Setup() + elif ot == ObjectType.IP6_SETUP: + ret = GXDLMSIp6Setup() + elif ot == ObjectType.MBUS_SLAVE_PORT_SETUP: + ret = GXDLMSMBusSlavePortSetup() + elif ot == ObjectType.IMAGE_TRANSFER: + ret = GXDLMSImageTransfer() + elif ot == ObjectType.SECURITY_SETUP: + ret = GXDLMSSecuritySetup() + elif ot == ObjectType.DISCONNECT_CONTROL: + ret = GXDLMSDisconnectControl() + elif ot == ObjectType.LIMITER: + ret = GXDLMSLimiter() + elif ot == ObjectType.MBUS_CLIENT: + ret = GXDLMSMBusClient() + elif ot == ObjectType.MODEM_CONFIGURATION: + ret = GXDLMSModemConfiguration() + elif ot == ObjectType.PPP_SETUP: + ret = GXDLMSPppSetup() + elif ot == ObjectType.PROFILE_GENERIC: + ret = GXDLMSProfileGeneric() + elif ot == ObjectType.REGISTER_MONITOR: + ret = GXDLMSRegisterMonitor() + elif ot == ObjectType.REGISTER_ACTIVATION: + ret = GXDLMSRegisterActivation() + elif ot == ObjectType.REGISTER_TABLE: + ret = GXDLMSObject(ot) + elif ot == ObjectType.ZIG_BEE_SAS_STARTUP: + ret = GXDLMSObject(ot) + elif ot == ObjectType.ZIG_BEE_SAS_JOIN: + ret = GXDLMSObject(ot) + elif ot == ObjectType.SAP_ASSIGNMENT: + ret = GXDLMSSapAssignment() + elif ot == ObjectType.SCHEDULE: + ret = GXDLMSSchedule() + elif ot == ObjectType.SCRIPT_TABLE: + ret = GXDLMSScriptTable() + elif ot == ObjectType.SPECIAL_DAYS_TABLE: + ret = GXDLMSSpecialDaysTable() + elif ot == ObjectType.STATUS_MAPPING: + ret = GXDLMSObject(ot) + elif ot == ObjectType.TCP_UDP_SETUP: + ret = GXDLMSTcpUdpSetup() + elif ot == ObjectType.ZIG_BEE_SAS_APS_FRAGMENTATION: + ret = GXDLMSObject(ot) + elif ot == ObjectType.UTILITY_TABLES: + ret = GXDLMSUtilityTables() + elif ot == ObjectType.PUSH_SETUP: + ret = GXDLMSPushSetup() + elif ot == ObjectType.MBUS_MASTER_PORT_SETUP: + ret = GXDLMSMBusMasterPortSetup() + elif ot == ObjectType.GSM_DIAGNOSTIC: + ret = GXDLMSGSMDiagnostic() + elif ot == ObjectType.ACCOUNT: + ret = GXDLMSAccount() + elif ot == ObjectType.CREDIT: + ret = GXDLMSCredit() + elif ot == ObjectType.CHARGE: + ret = GXDLMSCharge() + elif ot == ObjectType.TOKEN_GATEWAY: + ret = GXDLMSTokenGateway() + elif ot == ObjectType.PARAMETER_MONITOR: + ret = GXDLMSParameterMonitor() + elif ot == ObjectType.LLC_SSCS_SETUP: + ret = GXDLMSLlcSscsSetup() + elif ot == ObjectType.PRIME_NB_OFDM_PLC_PHYSICAL_LAYER_COUNTERS: + ret = GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters() + elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_SETUP: + ret = GXDLMSPrimeNbOfdmPlcMacSetup() + elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_FUNCTIONAL_PARAMETERS: + ret = GXDLMSPrimeNbOfdmPlcMacFunctionalParameters() + elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_COUNTERS: + ret = GXDLMSPrimeNbOfdmPlcMacCounters() + elif ot == ObjectType.PRIME_NB_OFDM_PLC_MAC_NETWORK_ADMINISTRATION_DATA: + ret = GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData() + elif ot == ObjectType.PRIME_NB_OFDM_PLC_APPLICATIONS_IDENTIFICATION: + ret = GXDLMSPrimeNbOfdmPlcApplicationsIdentification() + elif ot == ObjectType.NTP_SETUP: + ret = GXDLMSNtpSetup() + else: + ret = GXDLMSObject(ot) + return ret diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_HDLCInfo.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_HDLCInfo.py new file mode 100644 index 0000000..ab9e102 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/_HDLCInfo.py @@ -0,0 +1,45 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +# Reserved for internal use. +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class _HDLCInfo: + # Constructor. + def __init__(self): + pass + + MAX_INFO_TX = 0x5 + MAX_INFO_RX = 0x6 + WINDOW_SIZE_TX = 0x7 + WINDOW_SIZE_RX = 0x8 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/__init__.py new file mode 100644 index 0000000..8862c41 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/__init__.py @@ -0,0 +1,122 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXArray import GXArray +from .GXStructure import GXStructure +from .ActionRequestType import ActionRequestType +from .ActionResponseType import ActionResponseType +from .ConfirmedServiceError import ConfirmedServiceError +from .ConnectionState import ConnectionState +from .GetCommandType import GetCommandType +from ._GXAPDU import _GXAPDU +from .GXBitString import GXBitString +from .GXByteBuffer import GXByteBuffer +from .GXTimeZone import GXTimeZone +from .GXDate import GXDate +from .GXDateTime import GXDateTime +from .GXDLMS import GXDLMS +from .GXDLMSAccessItem import GXDLMSAccessItem +from .GXDLMSClient import GXDLMSClient +from .GXDLMSConfirmedServiceError import GXDLMSConfirmedServiceError +from .GXDLMSExceptionResponse import GXDLMSExceptionResponse +from .GXDLMSConnectionEventArgs import GXDLMSConnectionEventArgs +from .GXDLMSConverter import GXDLMSConverter +from .GXDLMSException import GXDLMSException +from .GXDLMSGateway import GXDLMSGateway +from .GXDLMSLimits import GXDLMSLimits +from .GXHdlcSettings import GXHdlcSettings +from .GXDLMSLNCommandHandler import GXDLMSLNCommandHandler +from .GXDLMSLNParameters import GXDLMSLNParameters +from .GXDLMSLongTransaction import GXDLMSLongTransaction +from .GXDLMSNotify import GXDLMSNotify +from .GXDLMSServer import GXDLMSServer +from .GXDLMSSettings import GXDLMSSettings +from .GXDLMSSNCommandHandler import GXDLMSSNCommandHandler +from .GXDLMSSNParameters import GXDLMSSNParameters +from .GXDLMSTranslator import GXDLMSTranslator +from .GXDLMSTranslatorStructure import GXDLMSTranslatorStructure +from .GXDLMSXmlClient import GXDLMSXmlClient +from .GXDLMSXmlPdu import GXDLMSXmlPdu +from .GXDLMSXmlSettings import GXDLMSXmlSettings +from .GXICipher import GXICipher +from .GXReplyData import GXReplyData +from .GXServerReply import GXServerReply +from .GXSNInfo import GXSNInfo +from .GXStandardObisCode import GXStandardObisCode +from .GXStandardObisCodeCollection import GXStandardObisCodeCollection +from .GXTime import GXTime +from .GXWriteItem import GXWriteItem +from .GXXmlLoadSettings import GXXmlLoadSettings +from .HdlcControlFrame import HdlcControlFrame +from ._HDLCInfo import _HDLCInfo +from .MBusCommand import MBusCommand +from .MBusControlInfo import MBusControlInfo +from .MBusEncryptionMode import MBusEncryptionMode +from .MBusMeterType import MBusMeterType +from .ReleaseRequestReason import ReleaseRequestReason +from .ReleaseResponseReason import ReleaseResponseReason +from .SerialnumberCounter import SerialNumberCounter +from .ServiceError import ServiceError +from .SetRequestType import SetRequestType +from .SetResponseType import SetResponseType +from .SingleReadResponse import SingleReadResponse +from .SingleWriteResponse import SingleWriteResponse +from .TranslatorGeneralTags import TranslatorGeneralTags +from .TranslatorOutputType import TranslatorOutputType +from .TranslatorSimpleTags import TranslatorSimpleTags +from .TranslatorStandardTags import TranslatorStandardTags +from .TranslatorTags import TranslatorTags +from .ValueEventArgs import ValueEventArgs +from .VariableAccessSpecification import VariableAccessSpecification +from ._GXObjectFactory import _GXObjectFactory +from ._GXFCS16 import _GXFCS16 +from .AesGcmParameter import AesGcmParameter +from .CountType import CountType +from .GXCiphering import GXCiphering +from .GXDLMSChippering import GXDLMSChippering +from .GXDLMSChipperingStream import GXDLMSChipperingStream +from .GXEnum import GXEnum +from .GXInt8 import GXInt8 +from .GXInt16 import GXInt16 +from .GXInt32 import GXInt32 +from .GXInt64 import GXInt64 +from .GXUInt8 import GXUInt8 +from .GXUInt16 import GXUInt16 +from .GXUInt32 import GXUInt32 +from .GXUInt64 import GXUInt64 +from .GXFloat32 import GXFloat32 +from .GXFloat64 import GXFloat64 +from .GXIntEnum import GXIntEnum +from .GXIntFlag import GXIntFlag +from .GXDLMSTranslatorMessage import GXDLMSTranslatorMessage +name = "gurux_dlms" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Access.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Access.py new file mode 100644 index 0000000..95f3861 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Access.py @@ -0,0 +1,51 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from ..GXIntEnum import GXIntEnum + +class Access(GXIntEnum): + """ + Access describes access errors. + """ + #pylint: disable=too-few-public-methods + + OTHER = 0 + + SCOPE_OF_ACCESS_VIOLATED = 1 + + OBJECT_ACCESS_INVALID = 2 + + HARDWARE_FAULT = 3 + + OBJECT_UNAVAILABLE = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AccessMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AccessMode.py new file mode 100644 index 0000000..f0baeca --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AccessMode.py @@ -0,0 +1,58 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class AccessMode(GXIntEnum): + """ + Enumerates access modes. + """ + #pylint: disable=too-few-public-methods + + #No access. + NO_ACCESS = 0 + + #The client is allowed only reading from the server. + READ = 1 + + #The client is allowed only writing to the server. + WRITE = 2 + + #The client is allowed both reading from the server and writing to it. + READ_WRITE = 3 + + AUTHENTICATED_READ = 4 + + AUTHENTICATED_WRITE = 5 + + AUTHENTICATED_READ_WRITE = 6 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AccessServiceCommandType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AccessServiceCommandType.py new file mode 100644 index 0000000..268344b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AccessServiceCommandType.py @@ -0,0 +1,54 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class AccessServiceCommandType(GXIntEnum): + """ + Enumerates Access Service types. + """ + + # + # Get request or response. + # + GET = 1 + + # + # Set request or response. + # + SET = 2 + + # + # Action request or response. + # + ACTION = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AcseServiceProvider.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AcseServiceProvider.py new file mode 100644 index 0000000..af5e2d1 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AcseServiceProvider.py @@ -0,0 +1,44 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class AcseServiceProvider(GXIntEnum): + """ACSE service provider.""" + #pylint: disable=too-few-public-methods + + NONE = 0 + + NO_REASON_GIVEN = 1 + + NO_COMMON_ACSE_VERSION = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ApplicationReference.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ApplicationReference.py new file mode 100644 index 0000000..408a72d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ApplicationReference.py @@ -0,0 +1,61 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class ApplicationReference(GXIntEnum): + """ + Application reference describes application errors. + """ + #pylint: disable=too-few-public-methods + + # Other error is occurred. + OTHER = 0 + + # Time elapsed. + TIME_ELAPSED = 1 + + # Application unreachable. + APPLICATION_UNREACHABLE = 2 + + # Application reference is invalid. + APPLICATION_REFERENCE_INVALID = 3 + + # Application context unsupported. + APPLICATION_CONTEXT_UNSUPPORTED = 4 + + # Provider communication error. + PROVIDER_COMMUNICATION_ERROR = 5 + + # Deciphering error. + DECIPHERING_ERROR = 6 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AssociationResult.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AssociationResult.py new file mode 100644 index 0000000..f785c6f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/AssociationResult.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class AssociationResult(GXIntEnum): + """ + The AssociationResult enumerates the answers, which the server can give to + client's association request. + """ + #pylint: disable=too-few-public-methods + + # Association request is accepted. + ACCEPTED = 0 + + # Association request is permanently rejected. + PERMANENT_REJECTED = 1 + + # Association request is transiently rejected. + TRANSIENT_REJECTED = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Authentication.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Authentication.py new file mode 100644 index 0000000..ae4d2a1 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Authentication.py @@ -0,0 +1,90 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class Authentication(GXIntEnum): + """ + Authentication enumerates the authentication levels. + """ + #pylint: disable=too-few-public-methods + + #No authentication is used. + NONE = 0 + + #Low authentication is used. + LOW = 1 + + #High authentication is used. + HIGH = 2 + + #High authentication is used. Password is hashed with MD5. + HIGH_MD5 = 3 + + #High authentication is used. Password is hashed with SHA1. + HIGH_SHA1 = 4 + + #High authentication is used. Password is hashed with GMAC. + HIGH_GMAC = 5 + + #High authentication is used. Password is hashed with SHA-256. + HIGH_SHA256 = 6 + + #High authentication is used. Password is hashed with ECDSA. + HIGH_ECDSA = 7 + + @classmethod + def valueofString(cls, value): + return Authentication[value.upper()] + + @classmethod + def toString(cls, value): + if value == 0: + tmp = "None" + elif value == 1: + tmp = "Low" + elif value == 2: + tmp = "High" + elif value == 3: + tmp = "HighMd5" + elif value == 4: + tmp = "HighSha1" + elif value == 5: + tmp = "HighGmac" + elif value == 6: + tmp = "HighSha256" + elif value == 7: + tmp = "HighEcdsa" + else: + raise ValueError("Invalid Authentication value.") + return tmp diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/BerType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/BerType.py new file mode 100644 index 0000000..092f94d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/BerType.py @@ -0,0 +1,129 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class BerType(GXIntEnum): + """ + BER encoding enumeration values. + """ + #pylint: disable=too-few-public-methods + + # End of Content. + EOC = 0x00 + + # Boolean. + BOOLEAN = 0x1 + + # Integer. + INTEGER = 0x2 + + # Bit String. + BIT_STRING = 0x3 + + # Octet string. + OCTET_STRING = 0x4 + + # Null value. + NULL = 0x5 + + # Object identifier. + OBJECT_IDENTIFIER = 0x6 + + # Object Descriptor. + OBJECT_DESCRIPTOR = 7 + + # External + EXTERNAL = 8 + + # Real = float). + REAL = 9 + + # Enumerated. + ENUMERATED = 10 + + # Sequence. + SEQUENCE = 0x10 + + SET = 0x11 + + # Utf8 String. + UTF8STRING = 12 + + # Numeric string. + NUMERIC_STRING = 18 + + # Printable string. + PRINTABLE_STRING = 19 + + # Teletex string. + TELETEX_STRING = 20 + + # Videotex string. + VIDEOTEX_STRING = 21 + + # Ia5 string + IA5_STRING = 22 + + # Utc time. + UTC_TIME = 23 + + # Generalized time. + GENERALIZED_TIME = 24 + + # Graphic string. + GRAPHIC_STRING = 25 + + # Visible string. + VISIBLE_STRING = 26 + + # General string. + GENERAL_STRING = 27 + + # Universal string. + UNIVERSAL_STRING = 28 + + # Bmp string. + BMP_STRING = 30 + + # Application class. + APPLICATION = 0x40 + + # Context class. + CONTEXT = 0x80 + + # Private class. + PRIVATE = 0xc0 + + # Constructed. + CONSTRUCTED = 0x20 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ClockStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ClockStatus.py new file mode 100644 index 0000000..6b161b3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ClockStatus.py @@ -0,0 +1,68 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright =c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntFlag import GXIntFlag + +class ClockStatus(GXIntFlag): + """Defines Clock status.""" + #pylint: disable=too-few-public-methods + + # OK. + OK = 0 + + # Invalid value. + INVALID_VALUE = 0x1 + + # Doubtful b value. + DOUBTFUL_VALUE = 0x2 + + # Different clock base c. + DIFFERENT_CLOCK_BASE = 0X4 + + # Invalid clock status d. + INVALID_CLOCK_STATUS = 0x8 + + # Reserved. + RESERVED2 = 0x10 + + # Reserved. + RESERVED3 = 0x20 + + # Reserved. + RESERVED4 = 0x40 + + # Daylight saving active. + DAYLIGHT_SAVE_ACTIVE = 0x80 + + # Clock status is skipped. + SKIPPED = 0xFF diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Command.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Command.py new file mode 100644 index 0000000..76ad12c --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Command.py @@ -0,0 +1,619 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class Command(GXIntEnum): + """DLMS commands.""" + + # + # No command to execute. + # + NONE = 0 + + # + # Initiate request. + # + INITIATE_REQUEST = 0x1 + + # + # Initiate response. + # + INITIATE_RESPONSE = 0x8 + + # + # Read request. + # + READ_REQUEST = 0x5 + + # + # Read response. + # + READ_RESPONSE = 0xC + + # + # Write request. + # + WRITE_REQUEST = 0x6 + + # + # Write response. + # + WRITE_RESPONSE = 0xD + + # + # Get request. + # + GET_REQUEST = 0xC0 + + # + # Get response. + # + GET_RESPONSE = 0xC4 + + # + # Set request. + # + SET_REQUEST = 0xC1 + + # + # Set response. + # + SET_RESPONSE = 0xC5 + + # + # Action request. + # + METHOD_REQUEST = 0xC3 + + # + # Action response. + # + METHOD_RESPONSE = 0xC7 + + # + # HDLC Disconnect Mode. + # + DISCONNECT_MODE = 0x1F + + # + # HDLC Unacceptable frame. + # + UNACCEPTABLE_FRAME = 0x97 + + # + # HDLC SNRM request. + # + SNRM = 0x93 + + # + # HDLC UA request. + # + UA = 0x73 + + # + # AARQ request. + # + AARQ = 0x60 + + # + # AARE request. + # + AARE = 0x61 + + # + # Disconnect request for HDLC framing. + # + DISCONNECT_REQUEST = 0x53 + + # + # Release request. + # + RELEASE_REQUEST = 0x62 + + # + # Disconnect response. + # + RELEASE_RESPONSE = 0x63 + + # + # Confirmed Service Error. + # + CONFIRMED_SERVICE_ERROR = 0x0E + + # + # Exception Response. + # + EXCEPTION_RESPONSE = 0xD8 + + # + # General Block Transfer. + # + GENERAL_BLOCK_TRANSFER = 0xE0 + + # + # Access Request. + # + ACCESS_REQUEST = 0xD9 + + # + # Access Response. + # + ACCESS_RESPONSE = 0xDA + + # + # Data Notification request. + # + DATA_NOTIFICATION = 0x0F + + # + # Glo get request. + # + GLO_GET_REQUEST = 0xC8 + + # + # Glo get response. + # + GLO_GET_RESPONSE = 0xCC + + # + # Glo set request. + # + GLO_SET_REQUEST = 0xC9 + + # + # Glo set response. + # + GLO_SET_RESPONSE = 0xCD + + # + # Glo event notification. + # + GLO_EVENT_NOTIFICATION = 0xCA + + # + # Glo method request. + # + GLO_METHOD_REQUEST = 0xCB + + # + # Glo method response. + # + GLO_METHOD_RESPONSE = 0xCF + + # + # Glo Initiate request. + # + GLO_INITIATE_REQUEST = 0x21 + + # + # Glo read request. + # + GLO_READ_REQUEST = 37 + + # + # Glo write request. + # + GLO_WRITE_REQUEST = 38 + + # + # Glo Initiate response. + # + GLO_INITIATE_RESPONSE = 40 + + # + # Glo read response. + # + GLO_READ_RESPONSE = 44 + + # + # Glo write response. + # + GLO_WRITE_RESPONSE = 45 + + # + # Ded confirmed service error. + # + GLO_CONFIRMED_SERVICE_ERROR = 46 + + # + # General GLO ciphering. + # + GENERAL_GLO_CIPHERING = 0xDB + + # + # General DED ciphering. + # + GENERAL_DED_CIPHERING = 0xDC + + # + # General ciphering. + # + GENERAL_CIPHERING = 0xDD + + # + # Information Report request. + # + INFORMATION_REPORT = 0x18 + + # + # Event Notification request. + # + EVENT_NOTIFICATION = 0xC2 + + # + # Ded initiate request. + # + DED_INITIATE_REQUEST = 65 + + # + # Ded read request. + # + DED_READ_REQUEST = 69 + + # + # Ded write request. + # + DED_WRITE_REQUEST = 70 + + # + # Ded initiate response. + # + DED_INITIATE_RESPONSE = 72 + + # + # Ded read response. + # + DED_READ_RESPONSE = 76 + + # + # Ded write response. + # + DED_WRITE_RESPONSE = 77 + + # + # Ded confirmed service error. + # + DED_CONFIRMED_SERVICE_ERROR = 78 + + # + # Ded confirmed write request. + # + DED_UNCONFIRMED_WRITE_REQUEST = 86 + + # + # Ded information report request. + # + DED_INFORMATION_REPORT_REQUEST = 88 + + # + # Ded get request. + # + DED_GET_REQUEST = 0xD0 + + # + # Ded get response. + # + DED_GET_RESPONSE = 0xD4 + + # + # Ded set request. + # + DED_SET_REQUEST = 0xD1 + + # + # Ded set response. + # + DED_SET_RESPONSE = 0xD5 + + # + # Ded event notification request. + # + DED_EVENT_NOTIFICATION = 0xD2 + + # + # Ded method request. + # + DED_METHOD_REQUEST = 0xD3 + + # + # Ded method response. + # + DED_METHOD_RESPONSE = 0xD7 + + # + # Request message from client to gateway. + # + GATEWAY_REQUEST = 0xE6 + + # + # Response message from gateway to client. + # + GATEWAY_RESPONSE = 0xE7 + + # PLC discover request. + DISCOVER_REQUEST = 0x1D + + # PLC discover report. + DISCOVER_REPORT = 0x1E + + # PLC register request. + REGISTER_REQUEST = 0x1C + + # PLC ping request. + PING_REQUEST = 0x19 + + # PLC ping response. + PING_RESPONSE = 0x1A + + #PLC repeat call request. + REPEAT_CALL_REQUEST = 0x1F + + @classmethod + def toString(cls, value): + str_ = None + if value == Command.NONE: + str_ = "None" + elif value == Command.INITIATE_REQUEST: + str_ = "InitiateRequest" + elif value == Command.INITIATE_RESPONSE: + str_ = "InitiateResponse" + elif value == Command.READ_REQUEST: + str_ = "ReadRequest" + elif value == Command.READ_RESPONSE: + str_ = "ReadResponse" + elif value == Command.WRITE_REQUEST: + str_ = "WriteRequest" + elif value == Command.WRITE_RESPONSE: + str_ = "WriteResponse" + elif value == Command.GET_REQUEST: + str_ = "GetRequest" + elif value == Command.GET_RESPONSE: + str_ = "GetResponse" + elif value == Command.SET_REQUEST: + str_ = "SetRequest" + elif value == Command.SET_RESPONSE: + str_ = "SetResponse" + elif value == Command.METHOD_REQUEST: + str_ = "MethodRequest" + elif value == Command.METHOD_RESPONSE: + str_ = "MethodResponse" + elif value == Command.UNACCEPTABLE_FRAME: + str_ = "UnacceptableFrame" + elif value == Command.SNRM: + str_ = "Snrm" + elif value == Command.UA: + str_ = "Ua" + elif value == Command.AARQ: + str_ = "Aarq" + elif value == Command.AARE: + str_ = "Aare" + elif value == Command.DISCONNECT_REQUEST: + str_ = "Disc" + elif value == Command.RELEASE_REQUEST: + str_ = "DisconnectRequest" + elif value == Command.RELEASE_RESPONSE: + str_ = "DisconnectResponse" + elif value == Command.CONFIRMED_SERVICE_ERROR: + str_ = "ConfirmedServiceError" + elif value == Command.EXCEPTION_RESPONSE: + str_ = "ExceptionResponse" + elif value == Command.GENERAL_BLOCK_TRANSFER: + str_ = "GeneralBlockTransfer" + elif value == Command.ACCESS_REQUEST: + str_ = "AccessRequest" + elif value == Command.ACCESS_RESPONSE: + str_ = "AccessResponse" + elif value == Command.DATA_NOTIFICATION: + str_ = "DataNotification" + elif value == Command.GLO_GET_REQUEST: + str_ = "GloGetRequest" + elif value == Command.GLO_GET_RESPONSE: + str_ = "GloGetResponse" + elif value == Command.GLO_SET_REQUEST: + str_ = "GloSetRequest" + elif value == Command.GLO_SET_RESPONSE: + str_ = "GloSetResponse" + elif value == Command.GLO_EVENT_NOTIFICATION: + str_ = "GloEventNotification" + elif value == Command.GLO_METHOD_REQUEST: + str_ = "GloMethodRequest" + elif value == Command.GLO_METHOD_RESPONSE: + str_ = "GloMethodResponse" + elif value == Command.GLO_INITIATE_REQUEST: + str_ = "GloInitiateRequest" + elif value == Command.GLO_READ_REQUEST: + str_ = "GloReadRequest" + elif value == Command.GLO_WRITE_REQUEST: + str_ = "GloWriteRequest" + elif value == Command.GLO_INITIATE_RESPONSE: + str_ = "GloInitiateResponse" + elif value == Command.GLO_READ_RESPONSE: + str_ = "GloReadResponse" + elif value == Command.GLO_WRITE_RESPONSE: + str_ = "GloWriteResponse" + elif value == Command.GENERAL_GLO_CIPHERING: + str_ = "GeneralGloCiphering" + elif value == Command.GENERAL_DED_CIPHERING: + str_ = "GeneralDedCiphering" + elif value == Command.GENERAL_CIPHERING: + str_ = "GeneralCiphering" + elif value == Command.INFORMATION_REPORT: + str_ = "InformationReport" + elif value == Command.EVENT_NOTIFICATION: + str_ = "EventNotification" + elif value == Command.DED_GET_REQUEST: + str_ = "DedGetRequest" + elif value == Command.DED_GET_RESPONSE: + str_ = "DedGetResponse" + elif value == Command.DED_SET_REQUEST: + str_ = "DedSetRequest" + elif value == Command.DED_SET_RESPONSE: + str_ = "DedSetResponse" + elif value == Command.DED_EVENT_NOTIFICATION: + str_ = "DedEventNotification" + elif value == Command.DED_METHOD_REQUEST: + str_ = "DedMethodRequest" + elif value == Command.GATEWAY_REQUEST: + str_ = "GatewayRequest" + elif value == Command.GATEWAY_RESPONSE: + str_ = "GatewayResponse" + elif value == Command.DISCOVER_REQUEST: + str_ = "DiscoverRequest" + elif value == Command.DISCOVER_REPORT: + str_ = "DiscoverReport" + elif value == Command.REGISTER_REQUEST: + str_ = "RegisterRequest" + elif value == Command.PING_REQUEST: + str_ = "PingRequest" + elif value == Command.PING_RESPONSE: + str_ = "PingResponse" + elif value == Command.REPEAT_CALL_REQUEST: + str_ = "RepeatCallRequest" + else: + raise ValueError(str(value)) + return str_ + + @classmethod + def value_of(cls, value): + if "None".lower() == value.lower(): + ret = Command.NONE + elif "InitiateRequest".lower() == value.lower(): + ret = Command.INITIATE_REQUEST + elif "InitiateResponse".lower() == value.lower(): + ret = Command.INITIATE_RESPONSE + elif "ReadRequest".lower() == value.lower(): + ret = Command.READ_REQUEST + elif "ReadResponse".lower() == value.lower(): + ret = Command.READ_RESPONSE + elif "WriteRequest".lower() == value.lower(): + ret = Command.WRITE_REQUEST + elif "WriteRequest".lower() == value.lower(): + ret = Command.WRITE_RESPONSE + elif "WriteResponse".lower() == value.lower(): + ret = Command.WRITE_RESPONSE + elif "GetRequest".lower() == value.lower(): + ret = Command.GET_REQUEST + elif "GetResponse".lower() == value.lower(): + ret = Command.GET_RESPONSE + elif "SetRequest".lower() == value.lower(): + ret = Command.SET_REQUEST + elif "SetResponse".lower() == value.lower(): + ret = Command.SET_RESPONSE + elif "MethodRequest".lower() == value.lower(): + ret = Command.METHOD_REQUEST + elif "MethodResponse".lower() == value.lower(): + ret = Command.METHOD_RESPONSE + elif "UnacceptableFrame".lower() == value.lower(): + ret = Command.UNACCEPTABLE_FRAME + elif "Snrm".lower() == value.lower(): + ret = Command.SNRM + elif "Ua".lower() == value.lower(): + ret = Command.UA + elif "Aarq".lower() == value.lower(): + ret = Command.AARQ + elif "Aare".lower() == value.lower(): + ret = Command.AARE + elif "Disc".lower() == value.lower(): + ret = Command.DISCONNECT_REQUEST + elif "DisconnectRequest".lower() == value.lower(): + ret = Command.RELEASE_REQUEST + elif "DisconnectResponse".lower() == value.lower(): + ret = Command.RELEASE_RESPONSE + elif "ConfirmedServiceError".lower() == value.lower(): + ret = Command.CONFIRMED_SERVICE_ERROR + elif "ExceptionResponse".lower() == value.lower(): + ret = Command.EXCEPTION_RESPONSE + elif "GeneralBlockTransfer".lower() == value.lower(): + ret = Command.GENERAL_BLOCK_TRANSFER + elif "AccessRequest".lower() == value.lower(): + ret = Command.ACCESS_REQUEST + elif "AccessResponse".lower() == value.lower(): + ret = Command.ACCESS_RESPONSE + elif "DataNotification".lower() == value.lower(): + ret = Command.DATA_NOTIFICATION + elif "GloGetRequest".lower() == value.lower(): + ret = Command.GLO_GET_REQUEST + elif "GloGetResponse".lower() == value.lower(): + ret = Command.GLO_GET_RESPONSE + elif "GloSetRequest".lower() == value.lower(): + ret = Command.GLO_SET_REQUEST + elif "GloSetResponse".lower() == value.lower(): + ret = Command.GLO_SET_RESPONSE + elif "GloEventNotification".lower() == value.lower(): + ret = Command.GLO_EVENT_NOTIFICATION + elif "GloMethodRequest".lower() == value.lower(): + ret = Command.GLO_METHOD_REQUEST + elif "GloMethodResponse".lower() == value.lower(): + ret = Command.GLO_METHOD_RESPONSE + elif "GloInitiateRequest".lower() == value.lower(): + ret = Command.GLO_INITIATE_REQUEST + elif "GloReadRequest".lower() == value.lower(): + ret = Command.GLO_READ_REQUEST + elif "GloWriteRequest".lower() == value.lower(): + ret = Command.GLO_WRITE_REQUEST + elif "GloInitiateResponse".lower() == value.lower(): + ret = Command.GLO_INITIATE_RESPONSE + elif "GloReadResponse".lower() == value.lower(): + ret = Command.GLO_READ_RESPONSE + elif "GloWriteResponse".lower() == value.lower(): + ret = Command.GLO_WRITE_RESPONSE + elif "GeneralGloCiphering".lower() == value.lower(): + ret = Command.GENERAL_GLO_CIPHERING + elif "GeneralDedCiphering".lower() == value.lower(): + ret = Command.GENERAL_DED_CIPHERING + elif "GeneralCiphering".lower() == value.lower(): + ret = Command.GENERAL_CIPHERING + elif "InformationReport".lower() == value.lower(): + ret = Command.INFORMATION_REPORT + elif "EventNotification".lower() == value.lower(): + ret = Command.EVENT_NOTIFICATION + elif "GatewayRequest".lower() == value.lower(): + ret = Command.GATEWAY_REQUEST + elif "GatewayResponse".lower() == value.lower(): + ret = Command.GATEWAY_RESPONSE + elif "DiscoverRequest".lower() == value.lower(): + ret = Command.DISCOVER_REQUEST + elif "DiscoverReport".lower() == value.lower(): + ret = Command.DISCOVER_REPORT + elif "RegisterRequest".lower() == value.lower(): + ret = Command.REGISTER_REQUEST + elif "PingRequest".lower() == value.lower(): + ret = Command.PING_REQUEST + elif "PingResponse".lower() == value.lower(): + ret = Command.PING_RESPONSE + elif "RepeatCallRequest".lower() == value.lower(): + ret = Command.REPEAT_CALL_REQUEST + else: + raise ValueError(value) + return ret diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Conformance.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Conformance.py new file mode 100644 index 0000000..f8c6e26 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Conformance.py @@ -0,0 +1,186 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntFlag import GXIntFlag + +class Conformance(GXIntFlag): + """Enumerates all conformance bits. + Client is using conformance to tell what kind of services it's interested + and server is using conformance to tell what kind of services it can offer. + + All services enumerated here are not necessary supported by the client or the + server. + https://www.gurux.fi/Gurux.DLMS.Conformance + """ + #pylint: disable=too-few-public-methods + + #None conformace is reserved for internal use. + NONE = 0x0 + + #Reserved zero conformance bit. + #Not used at the moment. + RESERVED_ZERO = 0x1 + + #Is General Protection supported by the meter or is client interested from + #General Protection. + #General Protection is used to secure connection between client and the + #meter using Symmetric or ASymmetric ciphering or General Signing. + GENERAL_PROTECTION = 0x2 + + #General Block Transfer mechanism is used to transport data that size is + #longer than PDU size. + GENERAL_BLOCK_TRANSFER = 0x4 + + #Is Read supported by the meter or is client interested from Read. + #Read is used with Short Name Referencing to read value from the meter. + READ = 0x8 + + #Is Write supported by the meter or is client interested from write data + #to the meter. + #Write is used with Short Name Referencing to write value to the meter. + WRITE = 0x10 + + #Is Unconfirmed Write supported by the meter or is client interested from + #write unconfirmed data to the meter. + #Unconfirmed Write is used with Short Name Referencing to write value + #without confirmation from success to the meter. + UN_CONFIRMED_WRITE = 0x20 + + #Delta value encoding. + DELTA_VALUE_ENCODING = 0x40 + + #Reserved seven conformance bit. + #Not used at the moment. + RESERVED_SEVEN = 0x80 + + #Is Attribute Set supported by the meter or is client interested from + #write data with Attribute 0 to the meter. + #Attribute 0 Set means that all COSEM object's attributes can be write + #with one message. + ATTRIBUTE_0_SUPPORTED_WITH_SET = 0x100 + + #Is Priority Management supported by the meter or is client interested + #from Priority Management. + #Priority Management is used by Logical Name referencing to handle urgent + #messages. + PRIORITY_MGMT_SUPPORTED = 0x200 + + #Is Attribute get supported by the meter or is client interested from read + #data with Attribute 0 from the meter. + #Attribute 0 Get means that all COSEM object's attributes can be read with + #one message. + ATTRIBUTE_0_SUPPORTED_WITH_GET = 0x400 + + #Is Block transfer supported by the meter or is client interested from + #read data with blocks from the meter. + #Read Block transfer is used to read data from the meter that is not fit + #to one PDU. Example reading historical data might take more than one PDU + #and then block transfer is used. + BLOCK_TRANSFER_WITH_GET_OR_READ = 0x800 + + #Is Block transfer supported by the meter or is client interested from + #writing data with blocks to the meter. + #Write Block transfer is used to write data from the meter that is not fit + #to one PDU. Example updating image =Firmware) might take more than one + #PDU and then block transfer is used. + BLOCK_TRANSFER_WITH_SET_OR_WRITE = 0x1000 + + #Is Block with action transfer supported by the meter or is client + #interested from sending action data with blocks to the meter. + #Action Block transfer is used to send action data from the meter that is + #not fit to one PDU. Example Public Key transfer might take more than one + #PDU and then block transfer with action is used. + BLOCK_TRANSFER_WITH_ACTION = 0x2000 + + #Is multiple references supported by the meter or is client interested + #from multiple references. + #Multiple references is used when reading or writing several object with + #one message. Example ReadByList is using it. + MULTIPLE_REFERENCES = 0x4000 + + #Is Information reports supported by the meter. + #Information reports is used with Short Name Referencing + # + #@see DATA_NOTIFICATION + #@see EVENT_NOTIFICATION + INFORMATION_REPORT = 0x8000 + + #Is Data Notification supported by the meter. + # + #@see INFORMATION_REPORT + #@see EVENT_NOTIFICATION + DATA_NOTIFICATION = 0x10000 + + #Is Access service supported by the meter or is client interested from + #Access service. + #Using Access service client can make several Read, Write or Action + #requests with one command. + ACCESS = 0x20000 + + #Is parameterized access supported by the meter or is client interested + #from parameterized access. + #Parameterized access is used with Short Name Referencing example if + #Profile Generic is read by range or entry. + # + #@see SELECTIVE_ACCESS + PARAMETERIZED_ACCESS = 0x40000 + + #Is Get supported by the meter or is client interested from Get. + #Get is used with Logical Name Referencing to read value from the meter. + GET = 0x80000 + + #Is Set supported by the meter or is client interested from Set. + #Set is used with Logical Name Referencing to write value to the meter. + SET = 0x100000 + + #Is selective access supported by the meter or is client interested from + #selective access. + #Selective access is used with Logical Name Referencing example if Profile + #Generic is read by range or entry. + # + #@see PARAMETERIZED_ACCESS + SELECTIVE_ACCESS = 0x200000 + + #Is Event Notification supported by the meter. + #Meter is using Event Notifications with Logical Name referencing to send + #events for given target. Example if power down occurred. Note! Client + #do + #not need to have connection to the server when event notification is + #send. + # + EVENT_NOTIFICATION = 0x400000 + + #Is actions supported by the meter or is client interested from actions. + # + #Actions are used example to reset historical data. + ACTION = 0x800000 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/DataType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/DataType.py new file mode 100644 index 0000000..b75cd3d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/DataType.py @@ -0,0 +1,112 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright = c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class DataType(GXIntEnum): + """ + DataType enumerates usable types of data in GuruxDLMS. + """ + #pylint: disable=too-few-public-methods + + # Data type is Array. + ARRAY = 1 + + # Data type is Binary coded decimal. + BCD = 13 + + # Data type is Bit string. + BITSTRING = 4 + + # Data type is Boolean. + BOOLEAN = 3 + + # Data type is Compact array. + COMPACT_ARRAY = 0x13 + + # Data type is Date. + DATE = 0x1a + + # Data type is DateTime. + DATETIME = 0x19 + + # Data type is enumerator. + ENUM = 0x16 + + # Data type is Float32. + FLOAT32 = 0x17 + + # Data type is Float64. + FLOAT64 = 0x18 + + # Data type is Int16. + INT16 = 0x10 + + # Data type is Int32. + INT32 = 5 + + # Data type is Int64. + INT64 = 20 + + # Data type is Int8. + INT8 = 15 + + # By default, no data type is set. + NONE = 0 + + # Data type is Octet string. + OCTET_STRING = 9 + + # Data type is String. + STRING = 10 + + # Data type is UTF8 String. + STRING_UTF8 = 12 + + # Data type is Structure. + STRUCTURE = 2 + + # Data type is Time. + TIME = 0x1b + + # Data type is UInt16. + UINT16 = 0x12 + + # Data type is UInt32. + UINT32 = 6 + + # Data type is UInt64. + UINT64 = 0x15 + + # Data type is UInt8. + UINT8 = 0x11 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/DateTimeExtraInfo.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/DateTimeExtraInfo.py new file mode 100644 index 0000000..89bc759 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/DateTimeExtraInfo.py @@ -0,0 +1,49 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright =c Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntFlag import GXIntFlag + +class DateTimeExtraInfo(GXIntFlag): + """Date time extra info.""" + #pylint: disable=too-few-public-methods + + # No extra info. + NONE = 0 + # Daylight savings begin. + DST_BEGIN = 0x1 + # Daylight savings end. + DST_END = 0x2 + # Last day of month. + LAST_DAY = 0x4 + # 2nd last day of month + LAST_DAY2 = 0x8 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/DateTimeSkips.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/DateTimeSkips.py new file mode 100644 index 0000000..90aa349 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/DateTimeSkips.py @@ -0,0 +1,71 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright =c Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntFlag import GXIntFlag + +class DateTimeSkips(GXIntFlag): + """Enumerated date time skipped fields.""" + #pylint: disable=too-few-public-methods + + # Nothing is skipped from date time. + NONE = 0 + + # Year part of date time is skipped. + YEAR = 1 + + # Month part of date time is skipped. + MONTH = 2 + + # Day part is skipped. + DAY = 4 + + # Day of week part of date time is skipped. + DAY_OF_WEEK = 8 + + # Hours part of date time is skipped. + HOUR = 0x10 + + # Minute part of date time is skipped. + MINUTE = 0x20 + + # Second part of date time is skipped. + SECOND = 0x40 + + # Hundreds of seconds part of date time is skipped. + MILLISECOND = 0x80 + + # Devitation is not used. + DEVITATION = 0x100 + + # Status is not used. + STATUS = 0x200 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Definition.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Definition.py new file mode 100644 index 0000000..bc35938 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Definition.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class Definition(GXIntEnum): + """Definition describes definition errors.""" + #pylint: disable=too-few-public-methods + + OTHER = 0 + + OBJECT_UNDEFINED = 1 + + OBJECT_CLASS_INCONSISTENT = 2 + + OBJECT_ATTRIBUTE_INCONSISTENT = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ErrorCode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ErrorCode.py new file mode 100644 index 0000000..5096ef0 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ErrorCode.py @@ -0,0 +1,80 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class ErrorCode(GXIntEnum): + """Enumerates all DLMS error codes. + https://www.gurux.fi/Gurux.DLMS.ErrorCodes + """ + #pylint: disable=too-few-public-methods + + DISCONNECT_MODE = -4 + + RECEIVE_NOT_READY = -3 + + REJECTED = -2 + + UNACCEPTABLE_FRAME = -1 + + OK = 0 + + HARDWARE_FAULT = 1 + + TEMPORARY_FAILURE = 2 + + READ_WRITE_DENIED = 3 + + UNDEFINED_OBJECT = 4 + + INCONSISTENT_CLASS = 9 + + UNAVAILABLE_OBJECT = 11 + + UNMATCHED_TYPE = 12 + + ACCESS_VIOLATED = 13 + + DATA_BLOCK_UNAVAILABLE = 14 + + LONG_GET_OR_READ_ABORTED = 15 + + NO_LONG_GET_OR_READ_IN_PROGRESS = 16 + + LONG_SET_OR_WRITE_ABORTED = 17 + + NO_LONG_SET_OR_WRITE_IN_PROGRESS = 18 + + DATA_BLOCK_NUMBER_INVALID = 19 + + OTHER_REASON = 250 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ExceptionServiceError.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ExceptionServiceError.py new file mode 100644 index 0000000..10fa0b3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ExceptionServiceError.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class ExceptionServiceError(GXIntEnum): + """DLMS service errors.""" + #pylint: disable=too-few-public-methods + # Operation is not possible. + OPERATION_NOT_POSSIBLE = 1 + # Service is not supported. + SERVICE_NOT_SUPPORTED = 2 + # Other reason. + OTHER_REASON = 3 + # PDU is too long. + PDU_TOO_LONG = 4 + # Ciphering failed. + DECIPHERING_ERROR = 5 + # Invocation counter is invalid. + INVOCATION_COUNTER_ERROR = 6 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/HardwareResource.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/HardwareResource.py new file mode 100644 index 0000000..ec41be7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/HardwareResource.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class HardwareResource(GXIntEnum): + """Hardware resource describes hardware errors.""" + #pylint: disable=too-few-public-methods + + OTHER = 0 + + MEMORY_UNAVAILABLE = 1 + + PROCESSOR_RESOURCE_UNAVAILABLE = 2 + + MASS_STORAGE_UNAVAILABLE = 3 + + OTHER_RESOURCE_UNAVAILABLE = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/HdlcFrameType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/HdlcFrameType.py new file mode 100644 index 0000000..59bb703 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/HdlcFrameType.py @@ -0,0 +1,42 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class HdlcFrameType(GXIntEnum): + """HDLC frame types.""" + #pylint: disable=too-few-public-methods + + I_FRAME = 0 + S_FRAME = 1 + U_FRAME = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Initiate.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Initiate.py new file mode 100644 index 0000000..ad561a4 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Initiate.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class Initiate(GXIntEnum): + """Initiate describes onitiate errors.""" + #pylint: disable=too-few-public-methods + + OTHER = 0 + + DLMS_VERSION_TOO_LOW = 1 + + INCOMPATIBLE_CONFORMANCE = 2 + + PDU_SIZE_TOO_SHORT = 3 + + REFUSED_BY_THE_VDE_HANDLER = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/InterfaceType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/InterfaceType.py new file mode 100644 index 0000000..d1d5043 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/InterfaceType.py @@ -0,0 +1,61 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class InterfaceType(GXIntEnum): + """InterfaceType enumerates the usable types of connection in GuruxDLMS.""" + #pylint: disable=too-few-public-methods + + # General interface type is used for meters that support IEC 62056-46 Data link layer using HDLC protocol. + HDLC = 0 + # Network interface type is used for meters that support IEC 62056-47 COSEM transport layers for IPv4 networks. + WRAPPER = 1 + # Plain PDU is returned. + PDU = 2 + # EN 13757-4/-5 Wireless M-Bus profile is used. + WIRELESS_MBUS = 3 + # IEC 62056-21 E-Mode is used to initialize communication before moving to HDLC protocol. + HDLC_WITH_MODE_E = 4 + #PLC Logical link control (LLC) profile is used with IEC 61334-4-32 connectionless LLC sublayer. + PLC = 5 + # PLC Logical link control (LLC) profile is used with HDLC. + PLC_HDLC = 6 + # LowPower Wide Area Networks (LPWAN) profile is used. + LPWAN = 7 + # Wi-SUN FAN mesh network is used. + WI_SUN = 8 + # OFDM PLC PRIME is defined in IEC 62056-8-4. + PLC_PRIME = 9 + # EN 13757-2 wired (twisted pair based) M-Bus scheme is used. + WIRED_MBUS = 10 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/KeyAgreement.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/KeyAgreement.py new file mode 100644 index 0000000..fda1634 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/KeyAgreement.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class KeyAgreement(GXIntEnum): + """Key agreement.""" + #pylint: disable=too-few-public-methods + + # The Ephemeral Unified Model C(2e, 0s, ECC CDH) scheme. + EPHEMERAL_UNIFIED_MODEL = 0 + + # The One-Pass Diffie-Hellman C(1e, 1s, ECC CDH) scheme. + ONE_PASS_DIFFIE_HELLMAN = 1 + + # The Static Unified Model C(0e, 2s, ECC CDH) scheme. + STATIC_UNIFIED_MODEL = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/LoadDataSet.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/LoadDataSet.py new file mode 100644 index 0000000..edbb926 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/LoadDataSet.py @@ -0,0 +1,62 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class LoadDataSet(GXIntEnum): + """LoadDataSet describes load dataset errors.""" + #pylint: disable=too-few-public-methods + + #Other error. + OTHER = 0 + + #Primitive out of sequence. + PRIMITIVE_OUT_OF_SEQUENCE = 1 + + #Not loadable. + NOT_LOADABLE = 2 + + #Dataset size is too large. + DATASET_SIZE_TOO_LARGE = 3 + + #Not awaited segment. + NOT_AWAITED_SEGMENT = 4 + + #Interpretation failure. + INTERPRETATION_FAILURE = 5 + + #Storage failure. + STORAGE_FAILURE = 6 + + #Dataset not ready. + DATASET_NOT_READY = 7 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/MethodAccessMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/MethodAccessMode.py new file mode 100644 index 0000000..7aeae84 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/MethodAccessMode.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class MethodAccessMode(GXIntEnum): + """Enumerated method access types.""" + #pylint: disable=too-few-public-methods + + #No access. + NO_ACCESS = 0 + + #Access. + ACCESS = 1 + + #Authenticated access. + AUTHENTICATED_ACCESS = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ObjectType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ObjectType.py new file mode 100644 index 0000000..86053b6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ObjectType.py @@ -0,0 +1,341 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class ObjectType(GXIntEnum): + """ObjectType enumerates the usable types of DLMS objects in GuruxDLMS.""" + #pylint: disable=too-few-public-methods + + # When communicating with a meter, the application may demand periodical + # actions. If these actions are not linked to tariffication = + # ActivityCalendar or Schedule, use an object of type ActionSchedule = + # 0x16. + ACTION_SCHEDULE = 22 + + # When handling tariffication structures, you can use an object of type + # ActivityCalendar. It determines, when to activate specified scripts to + # perform certain activities in the meter. The activities, simply said, + # scheduled actions, are operations that are carried out on a specified + # day, at a specified time. ActivityCalendar can be used together with a + # more general object type, Schedule, and they can even overlap. If + # multiple actions are timed to the same moment, the actions determined in + # the Schedule are executed first, and then the ones determined in the + # ActivityCalendar. If using object type SpecialDaysTable, with + # ActivityCalendar, simultaneous actions determined in SpecialDaysTable are + # executed over the ones determined in ActivityCalendar.

Note: + # To make sure that tariffication is correct after a power failure, + # only the latest missed action from ActivityCalendar is executed, with a + # delay. In a case of power failure, if a Schedule object coexists, the + # latest missed action from ActivityCalendar has to be executed at the + # correct time, sequentially with actions determined in the Schedule. + ACTIVITY_CALENDAR = 20 + + # All object types are used. + NONE = 0 + + # AssociationLogicalName object type is used with meters that utilize + # Logical Name associations within a COSEM. + ASSOCIATION_LOGICAL_NAME = 15 + + # AssociationShortName object type is used with meters that utilize Short + # Name associations within a COSEM. + ASSOCIATION_SHORT_NAME = 12 + + # To determine auto answering settings = for data transfer between device + # and modem = s to answer incoming calls, use AutoAnswer object. + AUTO_ANSWER = 28 + + # To determine auto connecting settings = for data transfer from the meter + # to defined destinations, use AutoConnect = previously known as AutoDial + # object. + AUTO_CONNECT = 29 + + # An object of type Clock is used to handle the information of a date = + # day, month and year and/or a time = hundredths of a second, seconds, + # minutes and hours. + CLOCK = 8 + + # An object of type Data typically stores manufacturer specific information + # of the meter, for example configuration data and logical name. + DATA = 1 + + # An object of type DemandRegister stores a value, information of the item, + # which the value belongs to, the status of the item, and the time of the + # value. DemandRegister object type enables both current, and last + # average, + # it supports both block, and sliding demand calculation, and it also + # provides resetting the value average, and periodic averages. + DEMAND_REGISTER = 5 + + # MAC address of the physical device. The name and the use of this + # interface class has been changed from Ethernet setup to MAC address setup + # to allow a more general use. + MAC_ADDRESS_SETUP = 43 + + + # ExtendedRegister stores a value, and understands the type of the value. + # Refer to an object of this type by its logical name, using the OBIS + # identification code. + EXTENDED_REGISTER = 4 + + # To determine the GPRS settings, use GprsSetup object. + GPRS_SETUP = 45 + + # To determine the HDLC = High-level Data Link Control settings, use the + # IecHdlcSetup object. + IEC_HDLC_SETUP = 23 + + # To determine the Local Port settings, use the IecLocalPortSetup object. + IEC_LOCAL_PORT_SETUP = 19 + + # To determine the Twisted Pair settings, use the IecTwistedPairSetup + # object. + IEC_TWISTED_PAIR_SETUP = 24 + + # To determine the IP 4 settings, use the Ip4Setup object. + IP4_SETUP = 42 + + GSM_DIAGNOSTIC = 47 + + # To determine the IP 6 settings, use the Ip6Setup object. + IP6_SETUP = 48 + + # To determine the M-BUS settings, use the MbusSetup object. + MBUS_SLAVE_PORT_SETUP = 25 + + # To determine modem settings, use ModemConfiguration object. + MODEM_CONFIGURATION = 27 + + # To determine PPP = Point-to-Point Protocol settings, use the PppSetup + # object. + PPP_SETUP = 44 + + # ProfileGeneric determines a general way of gathering values from a + # profile. The data is retrieved either by a period of time, or by an + # occuring event. When gathering values from a profile, you need to + # understand the concept of the profile buffer, in which the profile data + # is stored. The buffer may be sorted by a register, or by a clock, within + # the profile, or the data can be just piled in it, in order: last in, + # first out. You can retrieve a part of the buffer, within a certain range + # of values, or by a range of entry numbers. You can also determine + # objects, whose values are to be retained. To determine, what to + # retrieve, + # and what to retain, you need to assign the objects to the profile. You + # can use static assignments, as all entries in a buffer are alike = same + # size, same structure.

Note: When you modify any assignment, + # the buffer of the corresponding profile is cleared, and all other + # profiles, using the modified one, will be cleared too. This is to make + # sure that their entries stay alike by size and structure. + PROFILE_GENERIC = 7 + + # Register stores a value, and understands the type of the value. Refer to + # an object of this type by its logical name, using the OBIS identification + # code. + REGISTER = 3 + + # When handling tariffication structures, you can use RegisterActivation to + # determine, what objects to enable, when activating a certain activation + # mask. The objects, assigned to the register, but not determined in the + # mask, are disabled.

Note: If an object is not assigned to + # any register, it is, by default, enabled. + REGISTER_ACTIVATION = 6 + + # RegisterMonitor allows you to determine scripts to execute, when a + # register value crosses a specified threshold. To use RegisterMonitor, + # also ScriptTable needs to be instantiated in the same logical device. + REGISTER_MONITOR = 21 + + # Instances of the Disconnect control IC manage an internal or external + # disconnect unit of the meter. + DISCONNECT_CONTROL = 70 + + LIMITER = 71 + + MBUS_CLIENT = 72 + + PUSH_SETUP = 40 + + COMPACT_DATA = 62 + + PARAMETER_MONITOR = 65 + WIRELESS_MODE_Q_CHANNEL = 73 + MBUS_MASTER_PORT_SETUP = 74 + + # + # Addresses that are provided by the base node during the opening of the + # convergence layer. + # + LLC_SSCS_SETUP = 80 + + # + # Counters related to the physical layers exchanges. + # + PRIME_NB_OFDM_PLC_PHYSICAL_LAYER_COUNTERS = 81 + + # + # A necessary parameters to set up and manage the PRIME NB OFDM PLC MAC + # layer. + # + PRIME_NB_OFDM_PLC_MAC_SETUP = 82 + + # + # Functional behaviour of MAC. + # + PRIME_NB_OFDM_PLC_MAC_FUNCTIONAL_PARAMETERS = 83 + + # + # Statistical information on the operation of the MAC layer for management + # purposes. + # + PRIME_NB_OFDM_PLC_MAC_COUNTERS = 84 + + # + # Parameters related to the management of the devices connected to the + # network. + # + PRIME_NB_OFDM_PLC_MAC_NETWORK_ADMINISTRATION_DATA = 85 + + # + # Identification information related to administration and maintenance of + # PRIME NB OFDM PLC devices. + # + PRIME_NB_OFDM_PLC_APPLICATIONS_IDENTIFICATION = 86 + + # RegisterTable stores identical attributes of objects, in a selected + # collection of objects. All the objects in the collection need to be of + # the same type. Also, the value in value groups A to D and F in their + # logical name = OBIS identification code needs to be identical.

+ # Clause 5 determines the possible values in value group E, as a table, + # where header = the common part, and each cell = a possible E value, of + # the OBIS code. + + REGISTER_TABLE = 61 + # NTP Setup is used for time synchronisation. + NTP_SETUP = 100 + + # + # Configure a ZigBee PRO device with information necessary to create or + # join the network. + ZIG_BEE_SAS_STARTUP = 101 + + # + # Configure the behavior of a ZigBee PRO device on joining or loss of + # connection to the network. + ZIG_BEE_SAS_JOIN = 102 + + # + # Configure the fragmentation feature of ZigBee PRO transport layer. + ZIG_BEE_SAS_APS_FRAGMENTATION = 103 + ZIG_BEE_NETWORK_CONTROL = 104 + + DATA_PROTECTION = 30 + + ACCOUNT = 111 + + CREDIT = 112 + + CHARGE = 113 + + TOKEN_GATEWAY = 115 + + # SapAssigment stores information of assignment of the logical devices to + # their SAP = Service Access Points. + SAP_ASSIGNMENT = 17 + + # Instances of the Image transfer IC model the mechanism of transferring + # binary files, called firmware Images to COSEM servers. + IMAGE_TRANSFER = 18 + + # To handle time and date driven actions, use Schedule, with an object of + # type SpecialDaysTable. + SCHEDULE = 10 + + # To trigger a set of actions with an execute method, use object type + # ScriptTable. Each table entry = script includes a unique identifier, and + # a set of action specifications, which either execute a method, or modify + # the object attributes, within the logical device. The script can be + # triggered by other objects = within the same logical device, or from the + # outside. + SCRIPT_TABLE = 9 + + # To determine the SMTP protocol settings, use the SmtpSetup object. + SMTP_SETUP = 2 + + # With SpecialDaysTable you can determine dates to override a preset + # behaviour, for specific days = data item day_id. SpecialDaysTable works + # together with objects of Schedule, or Activity Calendar. + SPECIAL_DAYS_TABLE = 11 + + # StatusMapping object stores status words with mapping. Each bit in the + # status word is mapped to position = s in referencing status table. + STATUS_MAPPING = 63 + + SECURITY_SETUP = 64 + + # To determine Internet TCP/UDP protocol settings, use the TcpUdpSetup + # object. + TCP_UDP_SETUP = 41 + + # In an object of type UtilityTables each "Table" = ANSI C12.19:1997 table + # data is represented as an instance, and identified by its logical name. + UTILITY_TABLES = 26 + + # + # S-FSK Phy MAC Setup + SFSK_PHY_MAC_SETUP = 50 + + + # S-FSK Active initiator. + SFSK_ACTIVE_INITIATOR = 51 + + # S-FSK MAC synchronization timeouts + SFSK_MAC_SYNCHRONIZATION_TIMEOUTS = 52 + + # S-FSK MAC Counters. + SFSK_MAC_COUNTERS = 53 + + # G3-PLC MAC layer counters + G3_PLC_MAC_LAYER_COUNTERS = 90 + + + # G3-PLC MAC setup. + G3_PLC_MAC_SETUP = 91 + + # G3-PLC 6LoWPAN. + G3_PLC6_LO_WPAN = 92 + + # + # Tariff Plan =Piano Tariffario) is used in Italian standard UNI/TS + # 11291-11. + TARIFF_PLAN = 8192 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/PduType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/PduType.py new file mode 100644 index 0000000..c796543 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/PduType.py @@ -0,0 +1,85 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class PduType(GXIntEnum): + """APDU types.""" + #pylint: disable=too-few-public-methods + + # IMPLICIT BIT STRING {version1 = 0)} DEFAULT {version1} + PROTOCOL_VERSION = 0 + + # Application-context-name + APPLICATION_CONTEXT_NAME = 1 + + # AP-title OPTIONAL + CALLED_AP_TITLE = 2 + + # AE-qualifier OPTIONAL. + CALLED_AE_QUALIFIER = 3 + + # AP-invocation-identifier OPTIONAL. + CALLED_AP_INVOCATION_ID = 4 + + # AE-invocation-identifier OPTIONAL + CALLED_AE_INVOCATION_ID = 5 + + # AP-title OPTIONAL + CALLING_AP_TITLE = 6 + + # AE-qualifier OPTIONAL + CALLING_AE_QUALIFIER = 7 + + # AP-invocation-identifier OPTIONAL + CALLING_AP_INVOCATION_ID = 8 + + # AE-invocation-identifier OPTIONAL + CALLING_AE_INVOCATION_ID = 9 + + # The following field shall not be present if only the kernel is used. + SENDER_ACSE_REQUIREMENTS = 10 + + # The following field shall only be present if the authentication + # functional unit is selected. + MECHANISM_NAME = 11 + + # The following field shall only be present if the authentication + # functional unit is selected. + CALLING_AUTHENTICATION_VALUE = 12 + + # Implementation-data. + IMPLEMENTATION_INFORMATION = 29 + + # Association-information OPTIONAL. + USER_INFORMATION = 30 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Priority.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Priority.py new file mode 100644 index 0000000..e47bafc --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Priority.py @@ -0,0 +1,41 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class Priority(GXIntEnum): + """Used priority.""" + #pylint: disable=too-few-public-methods + + NORMAL = 0 + HIGH = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/RequestTypes.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/RequestTypes.py new file mode 100644 index 0000000..d2df836 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/RequestTypes.py @@ -0,0 +1,45 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntFlag import GXIntFlag + +class RequestTypes(GXIntFlag): + """ + RequestTypes enumerates the replies of the server to a client's request, + indicating the request type. + """ + + NONE = 0 + DATABLOCK = 1 + FRAME = 2 + GBT = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Security.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Security.py new file mode 100644 index 0000000..5709b7d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Security.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class Security(GXIntEnum): + """Used security model.""" + #pylint: disable=too-few-public-methods + # Transport security is not used. + NONE = 0 + # Authentication security is used. + AUTHENTICATION = 0x10 + # Encryption security is used. + ENCRYPTION = 0x20 + # Authentication and Encryption security are used. + AUTHENTICATION_ENCRYPTION = 0x30 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Service.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Service.py new file mode 100644 index 0000000..14596b3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Service.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class Service(GXIntEnum): + """Service describes service errors.""" + #pylint: disable=too-few-public-methods + # Other error. + OTHER = 0 + # PDU size is wrong. + PDU_SIZE = 1 + # Service is unsupported. + UNSUPPORTED = 2 + + @classmethod + def valueofString(cls, value): + return Service[value.upper()] diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ServiceClass.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ServiceClass.py new file mode 100644 index 0000000..dc19a65 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ServiceClass.py @@ -0,0 +1,41 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class ServiceClass(GXIntEnum): + """Used service.""" + #pylint: disable=too-few-public-methods + + UN_CONFIRMED = 0 + CONFIRMED = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ServiceError.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ServiceError.py new file mode 100644 index 0000000..17098d3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/ServiceError.py @@ -0,0 +1,44 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class ServiceError(GXIntEnum): + """DLMS service errors.""" + #pylint: disable=too-few-public-methods + + OPERATION_NOT_POSSIBLE = 1 + + SERVICE_NOT_SUPPORTED = 2 + + OTHER_REASON = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/SourceDiagnostic.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/SourceDiagnostic.py new file mode 100644 index 0000000..7828f8f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/SourceDiagnostic.py @@ -0,0 +1,69 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class SourceDiagnostic(GXIntEnum): + """SourceDiagnostic enumerates the error codes for reasons that can + cause the server to reject the client.""" + #pylint: disable=too-few-public-methods + + NONE = 0 + + NO_REASON_GIVEN = 1 + + NOT_SUPPORTED = 2 + + CALLING_AP_TITLE_NOT_RECOGNIZED = 3 + + CALLING_AP_INVOCATION_IDENTIFIER_NOT_RECOGNIZED = 4 + + CALLING_AE_QUALIFIER_NOT_RECOGNIZED = 5 + + CALLING_AE_INVOCATION_IDENTIFIER_NOT_RECOGNIZED = 6 + + CALLED_AP_TITLE_NOT_RECOGNIZED = 7 + + CALLED_AP_INVOCATION_IDENTIFIER_NOT_RECOGNIZED = 8 + + CALLED_AE_QUALIFIER_NOT_RECOGNIZED = 9 + + CALLED_AE_INVOCATION_IDENTIFIER_NOT_RECOGNIZED = 10 + + NOT_RECOGNISED = 11 + + MECHANISM_NAME_REGUIRED = 12 + + AUTHENTICATION_FAILURE = 13 + + AUTHENTICATION_REQUIRED = 14 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Standard.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Standard.py new file mode 100644 index 0000000..d844212 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Standard.py @@ -0,0 +1,51 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class Standard(GXIntEnum): + """Used DLMS standard.""" + #pylint: disable=too-few-public-methods + + #Meter uses default DLMS IEC 62056 standard. https://dlms.com + DLMS = 0 + #Meter uses India DLMS standard IS 15959-2. https://www.standardsbis.in + INDIA = 1 + #Meter uses Italy DLMS standard UNI/TS 11291-11-2. https://uni.com + ITALY = 2 + #Meter uses Saudi Arabia DLMS standard. + SAUDI_ARABIA = 3 + #Meter uses IDIS DLMS standard. https://www.idis-association.com/ + IDIS = 4 + #Meter uses Spain DLMS standard. + SPAIN = 5 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/StateError.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/StateError.py new file mode 100644 index 0000000..7a3240e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/StateError.py @@ -0,0 +1,42 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class StateError(GXIntEnum): + """DLMS state errors.""" + #pylint: disable=too-few-public-methods + + SERVICE_NOT_ALLOWED = 1 + + SERVICE_UNKNOWN = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Task.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Task.py new file mode 100644 index 0000000..540a2eb --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Task.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class Task(GXIntEnum): + """Task describes load task errors.""" + #pylint: disable=too-few-public-methods + + OTHER = 0 + + NO_REMOTE_CONTROL = 1 + + TI_STOPPED = 2 + + TI_RUNNING = 3 + + TI_UNUSABLE = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Unit.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Unit.py new file mode 100644 index 0000000..2769172 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/Unit.py @@ -0,0 +1,253 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from ..GXIntEnum import GXIntEnum + +class Unit(GXIntEnum): + """Enumerated units.""" + #pylint: disable=too-few-public-methods + + #No Unit. + NONE = 0 + #Year. + YEAR = 1 + #Month. + MONTH = 2 + #Week. + WEEK = 3 + #Day. + DAY = 4 + #Hour. + HOUR = 5 + #Minute. + MINUTE = 6 + #Second. + SECOND = 7 + #Phase angle degree. + PHASE_ANGLE_DEGREE = 8 + #Temperature + TEMPERATURE = 9 + #Local currency. + LOCAL_CURRENCY = 10 + #Length l meter m. + LENGTH = 11 + #Speed. + SPEED = 12 + #Volume V m3. + VOLUME_CUBIC_METER = 13 + #Corrected volume m3. + CORRECTED_VOLUME = 14 + VOLUME_FLUX_HOUR = 15 + CORRECTED_VOLUME_FLUX_HOUR = 16 + VOLUME_FLUXDAY = 17 + CORRECTE_VOLUME_FLUX_DAY = 18 + #Volume 10-3 m3. + VOLUME_LITER = 19 + #Mass m kilogram kg. + MASS_KG = 20 + #return "Force F newton N. + FORCE = 21 + #Energy newtonmeter J = Nm = Ws. + ENERGY = 22 + PRESSURE_PASCAL = 23 + PRESSURE_BAR = 24 + #Energy joule J = Nm = Ws. + ENERGY_JOULE = 25 + THERMAL_POWER = 26 + #Active power. + ACTIVE_POWER = 27 + #Apparent power S. + APPARENT_POWER = 28 + #Reactive power Q. + REACTIVE_POWER = 29 + #Active energy W6060s. + ACTIVE_ENERGY = 30 + #Apparent energy VA6060s. + APPARENT_ENERGY = 31 + #Reactive energy var6060s. + REACTIVE_ENERGY = 32 + #Current I ampere A. + CURRENT = 33 + #Electrical charge Q coulomb C = As. + ELECTRICAL_CHARGE = 34 + #Voltage. + VOLTAGE = 35 + ELECTRICAL_FIELD_STRENGTH = 36 + CAPACITY = 37 + RESISTANCE = 38 + #Resistivity. + RESISTIVITY = 39 + #Magnetic flux F weber Wb = Vs. + MAGNETIC_FLUX = 40 + INDUCTION = 41 + MAGNETIC = 42 + INDUCTIVITY = 43 + #Frequency f. + FREQUENCY = 44 + ACTIVE = 45 + #Reactive energy meter constant. + REACTIVE = 46 + #Apparent energy meter constant. + APPARENT = 47 + #V26060s. + V260 = 48 + #A26060s. + A260 = 49 + MASS_KG_PER_SECOND = 50 + CONDUCTANCE = 51 + #Temperature in Kelvin. + KELVIN = 52 + V2H = 53 + A2H = 54 + CUBIC_METER_RV = 55 + #Percentage. + PERCENTAGE = 56 + #Ah ampere hours. + AMPERE_HOURS = 57 + ENERGY_PER_VOLUME = 60 + WOBBE = 61 + #Mol % molar fraction of gas composition mole percent (Basic gas composition unit). + MOLE_PERCENT = 62 + MASS_DENSITY = 63 + #Dynamic viscosity pascal second =Characteristic of gas stream). + PASCAL_SECOND = 64 + JOULE_KILOGRAM = 65 + # Pressure, gram per square centimeter. + PRESSURE_GRAM_PER_SQUARE_CENTIMETER = 66 + # Pressure, atmosphere. + PRESSURE_ATMOSPHERE = 67 + # dBm Signal strength =e.g. of GSM radio systems). + SIGNAL_STRENGTH = 70 + # Signal strength dB microvolt. + SIGNAL_STRENGTH_MICRO_VOLT = 71 + # Logarithmic unit that expresses the ratio between two values of a physical quantity. + DB = 72 + # Length in inches. + INCH = 128 + # Foot (Length). + FOOT = 129 + # Pound (mass). + POUND = 130 + # Fahrenheit. + FAHRENHEIT = 131 + # Rankine. + RANKINE = 132 + # Square inch. + SQUARE_INCH = 133 + # Square foot. + SQUARE_FOOT = 134 + # Acre. + ACRE = 135 + # Cubic inch. + CUBIC_INCH = 136 + # Cubic foot. + CUBIC_FOOT = 137 + # Acre foot. + ACRE_FOOT = 138 + # Gallon (imperial). + GALLON_IMPERIAL = 139 + # Gallon (US). + GALLON_US = 140 + # Pound force. + POUND_FORCE = 141 + # Pound force per square inch. + POUND_FORCE_PER_SQUARE_INCH = 142 + # Pound per cubic foot. + POUND_PER_CUBIC_FOOT = 143 + # Pound per (foot second). + POUND_PER_FOOT_SECOND = 144 + # Square foot per second. + SQUARE_FOOT_PER_SECOND = 145 + # British thermal unit. + BRITISH_THERMAL_UNIT = 146 + # Therm EU. + THERM_EU = 147 + # Therm US. + THERM_US = 148 + # British thermal unit per pound. + BRITISH_THERMAL_UNIT_PER_POUND = 149 + # British thermal unit per cubic foot. + BRITISH_THERMAL_UNIT_PER_CUBIC_FOOT = 150 + # Cubic feet. + CUBIC_FEET = 151 + # Foot per second. + FOOT_PER_SECOND = 152 + # Cubic foot per second. + CUBIC_FOOT_PER_SECOND = 153 + # Cubic foot per min. + CUBIC_FOOT_PER_MIN = 154 + # Cubic foot per hour. + CUBIC_FOOT_PER_HOUR = 155 + # Cubic foot per day + CUBIC_FOOT_PER_DAY = 156 + # Acre foot per second. + ACRE_FOOT_PER_SECOND = 157 + # Acre foot per min. + ACRE_FOOT_PER_MIN = 158 + # Acre foot per hour. + ACRE_FOOT_PER_HOUR = 159 + # Acre foot per day. + ACRE_FOOT_PER_DAY = 160 + # Imperial gallon. + IMPERIAL_GALLON = 161 + # Imperial gallon per second. + IMPERIAL_GALLON_PER_SECOND = 162 + # Imperial gallon per min. + IMPERIAL_GALLON_PER_MIN = 163 + # Imperial gallon per hour. + IMPERIAL_GALLON_PER_HOUR = 164 + # Imperial gallon per day. + IMPERIAL_GALLON_PER_DAY = 165 + # US gallon. + US_GALLON = 166 + # US gallon per second. + US_GALLON_PER_SECOND = 167 + # US gallon per min. + US_GALLON_PER_MIN = 168 + # US gallon per hour. + US_GALLON_PER_HOUR = 169 + # US gallon per day. + US_GALLON_PER_DAY = 170 + # British thermal unit per second. + BRITISH_THERMAL_UNIT_PER_SECOND = 171 + # British thermal unit per minute. + BRITISH_THERMAL_UNIT_PER_MIN = 172 + # British thermal unit per hour. + BRITISH_THERMAL_UNIT_PER_HOUR = 173 + # British thermal unit per day. + BRITISH_THERMAL_UNIT_PER_DAY = 174 + #Other Unit. + OTHER_UNIT = 254 + #No Unit. + NO_UNIT = 255 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/VdeStateError.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/VdeStateError.py new file mode 100644 index 0000000..ee7f59c --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/VdeStateError.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class VdeStateError(GXIntEnum): + """VdeState error describes Vde state errors.""" + #pylint: disable=too-few-public-methods + + OTHER = 0 + + NO_DLMS_CONTEXT = 1 + + LOADING_DATASET = 2 + + STATUS_NO_CHANGE = 3 + + STATUS_INOPERABLE = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/__init__.py new file mode 100644 index 0000000..14384ff --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/enums/__init__.py @@ -0,0 +1,71 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .Access import Access +from .AccessMode import AccessMode +from .AccessServiceCommandType import AccessServiceCommandType +from .ApplicationReference import ApplicationReference +from .AssociationResult import AssociationResult +from .Authentication import Authentication +from .BerType import BerType +from .ClockStatus import ClockStatus +from .Command import Command +from .Conformance import Conformance +from .DataType import DataType +from .DateTimeSkips import DateTimeSkips +from .DateTimeExtraInfo import DateTimeExtraInfo +from .Definition import Definition +from .ErrorCode import ErrorCode +from .ExceptionServiceError import ExceptionServiceError +from .HardwareResource import HardwareResource +from .Initiate import Initiate +from .InterfaceType import InterfaceType +from .KeyAgreement import KeyAgreement +from .LoadDataSet import LoadDataSet +from .MethodAccessMode import MethodAccessMode +from .ObjectType import ObjectType +from .Priority import Priority +from .RequestTypes import RequestTypes +from .Security import Security +from .ServiceClass import ServiceClass +from .ServiceError import ServiceError +from .SourceDiagnostic import SourceDiagnostic +from .Standard import Standard +from .StateError import StateError +from .Task import Task +from .Unit import Unit +from .VdeStateError import VdeStateError +from .HdlcFrameType import HdlcFrameType +from .PduType import PduType +from .Service import Service +from .AcseServiceProvider import AcseServiceProvider diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/internal/_GXCommon.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/internal/_GXCommon.py new file mode 100644 index 0000000..fc119dc --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/internal/_GXCommon.py @@ -0,0 +1,1895 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +#pylint: disable=broad-except,no-name-in-module +from datetime import datetime +from ..GXTimeZone import GXTimeZone +from ._GXDataInfo import _GXDataInfo +from ..GXByteBuffer import GXByteBuffer +from ..GXBitString import GXBitString +from ..enums import DataType +from ..enums import DateTimeSkips, DateTimeExtraInfo, ClockStatus +from ..TranslatorTags import TranslatorTags +from ..TranslatorOutputType import TranslatorOutputType +from ..GXArray import GXArray +from ..GXStructure import GXStructure +from ..enums.Standard import Standard +from ..GXEnum import GXEnum +from ..GXInt8 import GXInt8 +from ..GXInt16 import GXInt16 +from ..GXInt32 import GXInt32 +from ..GXInt64 import GXInt64 +from ..GXUInt8 import GXUInt8 +from ..GXUInt16 import GXUInt16 +from ..GXUInt32 import GXUInt32 +from ..GXUInt64 import GXUInt64 +from ..GXDateTime import GXDateTime +from ..GXDate import GXDate +from ..GXTime import GXTime +from ..GXFloat32 import GXFloat32 +from ..GXFloat64 import GXFloat64 + +# pylint: disable=too-many-public-methods +class _GXCommon: + """This class is for internal use only and is subject to changes or removal + in future versions of the API. Don't use it.""" + + # HDLC frame start and end character. + HDLC_FRAME_START_END = 0x7E + LLC_SEND_BYTES = bytearray([0xE6, 0xE6, 0x00]) + LLC_REPLY_BYTES = bytearray([0xE6, 0xE7, 0x00]) + DATA_TYPE_OFFSET = 0xFF0000 + zeroes = "00000000000000000000000000000000" + + + @classmethod + def getBytes(cls, value): + """ + Convert string to byte array. + value: String value. + returns String as bytes. + """ + if not value: + return None + return value.encode() + + # + # Is string hex string. + # + # value: String value. + # Return true, if string is hex string. + # + @classmethod + def isHexString(cls, value): + # pylint: disable=chained-comparison + if not value: + return False + ch = str() + pos = 0 + while pos != len(value): + ch = value.charAt(pos) + if ch != ' ': + if not ((ch > 0x40 and ch < 'G') or (ch > 0x60 and ch < 'g') or (ch > '/' and ch < ':')): + return False + pos += 1 + return True + + # + # Get object count. If first byte is 0x80 or higger it will tell + # bytes + # count. + # data received data. + # Object count. + # + @classmethod + def getObjectCount(cls, data): + cnt = data.getUInt8() + if cnt > 0x80: + if cnt == 0x81: + cnt = data.getUInt8() + elif cnt == 0x82: + cnt = data.getUInt16() + elif cnt == 0x84: + cnt = int(data.getUInt32()) + else: + raise ValueError("Invalid count.") + return cnt + + # + # Return how many bytes object count takes. + # + # count + # Value + # Value size in bytes. + # + @classmethod + def getObjectCountSizeInBytes(cls, count): + if count < 0x80: + ret = 1 + elif count < 0x100: + ret = 2 + elif count < 0x10000: + ret = 3 + else: + ret = 5 + return ret + + # + # Add string to byte buffer. + # + # value + # String to add. + # bb + # Byte buffer where string is added. + # + @classmethod + def addString(cls, value, bb): + bb.setUInt8(DataType.OCTET_STRING) + if not value: + _GXCommon.setObjectCount(0, bb) + else: + _GXCommon.setObjectCount(len(value), bb) + bb.set(value.encode()) + + # + # Set item count. + # count + # buff + # + @classmethod + def setObjectCount(cls, count, buff): + if count < 0x80: + buff.setUInt8(count) + ret = 1 + elif count < 0x100: + buff.setUInt8(0x81) + buff.setUInt8(count) + ret = 2 + elif count < 0x10000: + buff.setUInt8(0x82) + buff.setUInt16(count) + ret = 3 + else: + buff.setUInt8(0x84) + buff.setUInt32(count) + ret = 5 + return ret + + # + # Reserved for internal use. + # + @classmethod + def toBitString(cls, value, count): + count2 = count + sb = "" + if count2 > 0: + count2 = min(count2, 8) + pos = 7 + while pos != 8 - count2 - 1: + if (value & (1 << pos)) != 0: + sb += '1' + else: + sb += '0' + pos -= 1 + return sb + + @classmethod + def changeType(cls, settings, value, type_): + #pylint: disable=import-outside-toplevel + if value is None: + ret = None + elif type_ == DataType.NONE: + ret = GXByteBuffer.hex(value, True) + elif type_ in (DataType.STRING, DataType.OCTET_STRING) and not value: + ret = "" + elif type_ == DataType.OCTET_STRING: + ret = GXByteBuffer(value) + elif type_ == DataType.STRING and not GXByteBuffer.isAsciiString(value): + ret = GXByteBuffer(value) + elif type_ == DataType.DATETIME and not value: + ret = GXDateTime(None) + elif type_ == DataType.DATE and not value: + ret = GXDate(None) + elif type_ == DataType.TIME and not value: + ret = GXTime(None) + else: + info = _GXDataInfo() + info.type_ = type_ + ret = _GXCommon.getData(settings, GXByteBuffer(value), info) + if not info.complete: + raise ValueError("Change type failed. Not enought data.") + if type_ == DataType.OCTET_STRING and isinstance(ret, bytes): + ret = GXByteBuffer.hex(ret) + return ret + + # + # Get data from DLMS frame. + # + # data + # received data. + # info + # Data info. + # Received data. + # + @classmethod + def getData(cls, settings, data, info): + value = None + startIndex = data.position + if data.position == len(data): + info.complete = False + return None + info.complete = True + knownType = info.type_ != DataType.NONE + # Get data type if it is unknown. + if not knownType: + info.type_ = data.getUInt8() + if info.type_ == DataType.NONE: + if info.xml: + info.xml.appendLine("<" + info.xml.getDataType(info.type_) + " />") + return value + if data.position == len(data): + info.complete = False + return None + if info.type_ == DataType.ARRAY or info.type_ == DataType.STRUCTURE: + value = cls.getArray(settings, data, info, startIndex) + elif info.type_ == DataType.BOOLEAN: + value = cls.getBoolean(data, info) + elif info.type_ == DataType.BITSTRING: + value = cls.getBitString(data, info) + elif info.type_ == DataType.INT32: + value = cls.getInt32(data, info) + elif info.type_ == DataType.UINT32: + value = cls.getUInt32(data, info) + elif info.type_ == DataType.STRING: + value = cls.getString(data, info, knownType) + elif info.type_ == DataType.STRING_UTF8: + value = cls.getUtfString(data, info, knownType) + elif info.type_ == DataType.OCTET_STRING: + value = cls.getOctetString(settings, data, info, knownType) + elif info.type_ == DataType.BCD: + value = cls.getBcd(data, info) + elif info.type_ == DataType.INT8: + value = cls.getInt8(data, info) + elif info.type_ == DataType.INT16: + value = cls.getInt16(data, info) + elif info.type_ == DataType.UINT8: + value = cls.getUInt8(data, info) + elif info.type_ == DataType.UINT16: + value = cls.getUInt16(data, info) + elif info.type_ == DataType.COMPACT_ARRAY: + value = cls.getCompactArray(settings, data, info) + elif info.type_ == DataType.INT64: + value = cls.getInt64(data, info) + elif info.type_ == DataType.UINT64: + value = cls.getUInt64(data, info) + elif info.type_ == DataType.ENUM: + value = cls.getEnum(data, info) + elif info.type_ == DataType.FLOAT32: + value = cls.getFloat(settings, data, info) + elif info.type_ == DataType.FLOAT64: + value = cls.getDouble(settings, data, info) + elif info.type_ == DataType.DATETIME: + value = cls.getDateTime(settings, data, info) + elif info.type_ == DataType.DATE: + value = cls.getDate(data, info) + elif info.type_ == DataType.TIME: + value = cls.getTime(data, info) + else: + raise ValueError("Invalid data type.") + return value + + # + # Convert value to hex string. + # value value to convert. + # desimals Amount of decimals. + # @return + # + @classmethod + def integerToHex(cls, value, desimals): + if desimals: + nbits = desimals * 4 + str_ = hex((value + (1 << nbits)) % (1 << nbits))[2:].upper() + else: + str_ = hex(value)[2:].upper() + if not desimals or desimals == len(str_): + return str_ + return _GXCommon.zeroes[0: desimals - len(str_)] + str_.upper() + + # + # Convert value to hex string. + # value value to convert. + # desimals Amount of decimals. + # @return + # + @classmethod + def integerString(cls, value, desimals): + str_ = str(value) + if desimals == 0 or len(_GXCommon.zeroes) == len(str_): + return str_ + return _GXCommon.zeroes[0: desimals - len(str_)] + str_ + + # + # Get array from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # index + # starting index. + # Object array. + # + @classmethod + def getArray(cls, settings, buff, info, index): + value = None + if info.count == 0: + info.count = _GXCommon.getObjectCount(buff) + if info.xml: + info.xml.appendStartTag(info.xml.getDataType(info.type_), "Qty", info.xml.integerToHex(info.count, 2)) + size = len(buff) - buff.position + if info.count != 0 and size < 1: + info.complete = False + return None + startIndex = index + if info.type_ == DataType.ARRAY: + value = GXArray() + else: + value = GXStructure() + # Position where last row was found. Cache uses this info. + pos = info.index + while pos != info.count: + info2 = _GXDataInfo() + info2.xml = info.xml + tmp = cls.getData(settings, buff, info2) + if not info2.complete: + buff.position = startIndex + info.complete = False + break + if info2.count == info2.index: + startIndex = buff.position + value.append(tmp) + pos += 1 + if info.xml: + info.xml.appendEndTag(info.xml.getDataType(info.type_)) + info.index = pos + return value + + # + # Get time from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # Parsed time. + # + @classmethod + def getTime(cls, buff, info): + # pylint: disable=broad-except + value = None + if len(buff) - buff.position < 4: + # If there is not enough data available. + info.complete = False + return None + str_ = None + if info.xml: + str_ = buff.toHex(False, buff.position, 4) + try: + value = GXTime(None) + # Get time. + hour = buff.getUInt8() + if hour == 0xFF: + hour = 0 + value.skip |= DateTimeSkips.HOUR + minute = buff.getUInt8() + if minute == 0xFF: + minute = 0 + value.skip |= DateTimeSkips.MINUTE + second = buff.getUInt8() + if second == 0xFF: + second = 0 + value.skip |= DateTimeSkips.SECOND + ms = buff.getUInt8() + if ms != 0xFF: + ms *= 10 + else: + ms = 0 + value.skip |= DateTimeSkips.MILLISECOND + value.value = datetime(1900, 1, 1, hour, minute, second, ms) + except Exception as ex: + if info.xml is None: + raise ex + if info.xml: + if value: + info.xml.appendComment(str(value)) + info.xml.appendLine(info.xml.getDataType(info.type_), None, str_) + return value + + # + # Get date from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # Parsed date. + # + @classmethod + def getDate(cls, buff, info): + # pylint: disable=broad-except + value = None + if len(buff) - buff.position < 5: + # If there is not enough data available. + info.complete = False + return None + str_ = None + if info.xml: + str_ = buff.integerToHex(False, buff.position, 5) + try: + dt = GXDate() + # Get year. + year = buff.getUInt16() + if year < 1900 or year == 0xFFFF: + dt.skip |= DateTimeSkips.YEAR + year = 2000 + # Get month + month = buff.getUInt8() + if month == 0xFE: + dt.extra |= DateTimeExtraInfo.DST_BEGIN + month = 1 + elif month == 0xFD: + dt.extra |= DateTimeExtraInfo.DST_END + month = 1 + else: + if month < 1 or month > 12: + dt.skip |= DateTimeSkips.MONTH + month = 1 + # Get day + day = buff.getUInt8() + if day == 0xFE: + dt.extra |= DateTimeExtraInfo.LAST_DAY + day = 1 + elif day == 0xFD: + dt.extra |= DateTimeExtraInfo.LAST_DAY2 + day = 1 + else: + if day < 1 or day > 31: + dt.skip |= DateTimeSkips.DAY + day = 1 + dt.value = datetime(year, month, day, 0, 0, 0, 0) + value = dt + # Skip week day + if buff.getUInt8() == 0xFF: + dt.skip |= DateTimeSkips.DAY_OF_WEEK + except Exception as ex: + if info.xml is None: + raise ex + if info.xml: + if value: + info.xml.appendComment(str(value)) + info.xml.appendLine(info.xml.getDataType(info.type_), None, str_) + return value + + # + # Get date and time from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # Parsed date and time. + # + @classmethod + def getDateTime(cls, settings, buff, info): + # pylint: disable=too-many-locals, broad-except + value = None + skip = DateTimeSkips.NONE + extra = DateTimeExtraInfo.NONE + # If there is not enough data available. + if len(buff) - buff.position < 12: + info.complete = False + return None + str_ = None + if info.xml: + str_ = buff.toHex(False, buff.position, 12) + dt = GXDateTime() + try: + # Get year. + year = buff.getUInt16() + # Get month + month = buff.getUInt8() + # Get day + day = buff.getUInt8() + # Skip week day + dayOfWeek = buff.getUInt8() + if dayOfWeek == 0xFF: + skip |= DateTimeSkips.DAY_OF_WEEK + else: + dt.dayOfWeek = dayOfWeek + # Get time. + hour = buff.getUInt8() + minute = buff.getUInt8() + second = buff.getUInt8() + ms = buff.getUInt8() & 0xFF + if ms != 0xFF: + ms *= 10 + else: + ms = -1 + deviation = buff.getInt16() + if deviation == -32768: + deviation = 0x8000 + skip |= DateTimeSkips.DEVITATION + status = buff.getUInt8() + dt.status = status + if year < 1900 or year == 0xFFFF: + skip |= DateTimeSkips.YEAR + year = 2000 + if month == 0xFE: + extra |= DateTimeExtraInfo.DST_BEGIN + month = 1 + elif month == 0xFD: + extra |= DateTimeExtraInfo.DST_END + month = 1 + else: + if month < 1 or month > 12: + skip |= DateTimeSkips.MONTH + month = 1 + + if day == 0xFE: + extra |= DateTimeExtraInfo.LAST_DAY + day = 1 + elif day == 0xFD: + extra |= DateTimeExtraInfo.LAST_DAY2 + day = 1 + else: + if day == -1 or day == 0 or day > 31: + skip |= DateTimeSkips.DAY + day = 1 + + if hour < 0 or hour > 24: + skip |= DateTimeSkips.HOUR + hour = 0 + if minute < 0 or minute > 60: + skip |= DateTimeSkips.MINUTE + minute = 0 + if second < 0 or second > 60: + skip |= DateTimeSkips.SECOND + second = 0 + # If ms is Zero it's skipped. + if ms < 0 or ms > 100: + skip |= DateTimeSkips.MILLISECOND + ms = 0 + tz = None + if deviation != 0x8000: + if settings.useUtc2NormalTime: + tz = deviation + else: + tz = -deviation + if tz is None: + dt.value = datetime(year, month, day, hour, minute, second, ms) + else: + dt.value = datetime(year, month, day, hour, minute, second, ms, tzinfo=GXTimeZone(tz)) + dt.skip = skip + value = dt + except Exception as ex: + if info.xml is None: + raise ex + if info.xml: + if dt.skip & DateTimeSkips.YEAR == 0 and value: + info.xml.appendComment(str(value)) + info.xml.appendLine(info.xml.getDataType(info.type_), None, str_) + return value + + # + # Get double value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # Parsed double value. + # + @classmethod + def getDouble(cls, settings, buff, info): + value = None + # If there is not enough data available. + if len(buff) - buff.position < 8: + info.complete = False + return None + value = buff.getDouble() + if info.xml: + if info.xml.comments: + info.xml.appendComment(('%f' % value).rstrip('0').rstrip('.')) + tmp = GXByteBuffer() + cls.setData(settings, tmp, DataType.FLOAT64, value) + info.xml.appendLine(info.xml.getDataType(info.type_), None, tmp.toHex(False, 1, len(tmp) - 1)) + return GXFloat64(value) + + # + # Get float value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # Parsed float value. + # + @classmethod + def getFloat(cls, settings, buff, info): + value = None + # If there is not enough data available. + if len(buff) - buff.position < 4: + info.complete = False + return None + value = buff.getFloat() + if info.xml: + if info.xml.comments: + info.xml.appendComment(('%f' % value).rstrip('0').rstrip('.')) + tmp = GXByteBuffer() + cls.setData(settings, tmp, DataType.FLOAT32, value) + info.xml.appendLine(info.xml.getDataType(info.type_), None, tmp.toHex(False, 1, len(tmp) - 1)) + return GXFloat32(value) + + # + # Get enumeration value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed enumeration value. + # + @classmethod + def getEnum(cls, buff, info): + value = None + # If there is not enough data available. + if len(buff) - buff.position < 1: + info.complete = False + return None + value = buff.getUInt8() + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 2)) + return GXEnum(value) + + # + # Get UInt64 value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed UInt64 value. + # + @classmethod + def getUInt64(cls, buff, info): + value = None + # If there is not enough data available. + if len(buff) - buff.position < 8: + info.complete = False + return None + value = buff.getUInt64() + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 16)) + return GXUInt64(value) + + # + # Get Int64 value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed Int64 value. + # + @classmethod + def getInt64(cls, buff, info): + value = None + # If there is not enough data available. + if len(buff) - buff.position < 8: + info.complete = False + return None + value = buff.getInt64() + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 16)) + return value + + # + # Get UInt16 value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed UInt16 value. + # + @classmethod + def getUInt16(cls, buff, info): + value = None + # If there is not enough data available. + if len(buff) - buff.position < 2: + info.complete = False + return None + value = buff.getUInt16() + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 4)) + return GXUInt16(value) + + #pylint: disable=too-many-arguments + @classmethod + def getCompactArrayItem(cls, settings, buff, dt, list_, len_): + if isinstance(dt, list): + tmp2 = list() + for it in dt: + if isinstance(it, DataType): + cls.getCompactArrayItem(settings, buff, it, tmp2, 1) + else: + cls.getCompactArrayItem(settings, buff, it, tmp2, 1) + list_.append(tmp2) + return + + tmp = _GXDataInfo() + tmp.type_ = dt + start = buff.position + if dt == DataType.STRING: + while buff.position - start < len_: + tmp.clear() + tmp.type = dt + list_.append(cls.getString(buff, tmp, False)) + if not tmp.complete: + break + elif dt == DataType.OCTET_STRING: + while buff.position - start < len_: + tmp.clear() + tmp.type = dt + list_.append(cls.getOctetString(settings, buff, tmp, False)) + if not tmp.complete: + break + else: + while buff.position - start < len_: + tmp.clear() + tmp.type_ = dt + list_.append(cls.getData(settings, buff, tmp)) + if not tmp.complete: + break + + @classmethod + def getDataTypes(cls, buff, cols, len_): + dt = None + pos = 0 + while pos != len_: + dt = buff.getUInt8() + if dt == DataType.ARRAY: + cnt = buff.getUInt16() + tmp = list() + tmp2 = list() + cls.getDataTypes(buff, tmp, 1) + i = 0 + while i != cnt: + tmp2.append(tmp) + i += 1 + cols.append(tmp2) + elif dt == DataType.STRUCTURE: + tmp = list() + cls.getDataTypes(buff, tmp, buff.getUInt8()) + cols.append(tmp) + else: + cols.append(dt) + pos += 1 + + @classmethod + def appendDataTypeAsXml(cls, cols, info): + for it in cols: + if isinstance(it, (DataType,)): + info.xml.appendEmptyTag(info.xml.getDataType(it)) + elif isinstance(it, GXStructure): + info.xml.appendStartTag(cls.DATA_TYPE_OFFSET + DataType.STRUCTURE, None, None) + cls.appendDataTypeAsXml(it, info) + info.xml.appendEndTag(cls.DATA_TYPE_OFFSET + DataType.STRUCTURE) + elif isinstance(it, GXArray): + info.xml.appendStartTag(cls.DATA_TYPE_OFFSET + DataType.ARRAY, None, None) + cls.appendDataTypeAsXml(it, info) + info.xml.appendEndTag(cls.DATA_TYPE_OFFSET + DataType.ARRAY) + + # + # Get compact array value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed UInt16 value. + # + @classmethod + def getCompactArray(cls, settings, buff, info): + # pylint: disable=too-many-nested-blocks + + # If there is not enough data available. + if len(buff) - buff.position < 2: + info.complete = False + return None + dt = buff.getUInt8() + if dt == DataType.ARRAY: + raise ValueError("Invalid compact array data.") + len_ = _GXCommon.getObjectCount(buff) + list_ = list() + if dt == DataType.STRUCTURE: + # Get data types. + cols = list() + cls.getDataTypes(buff, cols, len_) + len_ = _GXCommon.getObjectCount(buff) + if info.xml: + info.xml.appendStartTag(info.xml.getDataType(DataType.COMPACT_ARRAY), None, None) + info.xml.appendStartTag(TranslatorTags.CONTENTS_DESCRIPTION) + cls.appendDataTypeAsXml(cols, info) + info.xml.appendEndTag(TranslatorTags.CONTENTS_DESCRIPTION) + if info.xml.outputType == TranslatorOutputType.STANDARD_XML: + info.xml.appendStartTag(TranslatorTags.ARRAY_CONTENTS, None, None, True) + info.xml.append(buff.remainingHexString(True)) + info.xml.appendEndTag(TranslatorTags.ARRAY_CONTENTS, True) + info.xml.appendEndTag(info.xml.getDataType(DataType.COMPACT_ARRAY)) + else: + info.xml.appendStartTag(TranslatorTags.ARRAY_CONTENTS) + start = buff.position + while buff.position - start < len_: + row = list() + pos = 0 + while pos != len(cols): + if isinstance(cols[pos], GXArray): + cls.getCompactArrayItem(settings, buff, cols[pos], row, 1) + elif isinstance(cols[pos], GXStructure): + tmp2 = list() + cls.getCompactArrayItem(settings, buff, cols[pos], tmp2, 1) + row.append(tmp2[0]) + else: + cls.getCompactArrayItem(settings, buff, cols[pos], row, 1) + if buff.position == len(buff): + break + pos += 1 + # If all columns are read. + if len(row) >= len(cols): + list_.append(row) + else: + break + if info.xml and info.xml.outputType == TranslatorOutputType.SIMPLE_XML: + sb = "" + for row in list_: + for it in row: + if isinstance(it, bytearray): + sb += GXByteBuffer.hex(it) + elif isinstance(it, list): + start = len(sb) + for it2 in it: + if isinstance(it2, bytearray): + sb += GXByteBuffer.hex(it2) + else: + sb += str(it2) + sb += ";" + if start != len(sb): + sb = sb[0:len(sb) - 1] + else: + sb += str(it) + sb += ";" + if sb: + sb = sb[0:len(sb) - 1] + info.xml.appendLine(sb) + sb = "" + if info.xml and info.xml.outputType == TranslatorOutputType.SIMPLE_XML: + info.xml.appendEndTag(TranslatorTags.ARRAY_CONTENTS) + info.xml.appendEndTag(info.xml.getDataType(DataType.COMPACT_ARRAY)) + else: + if info.xml: + info.xml.appendStartTag(info.xml.getDataType(DataType.COMPACT_ARRAY), None, None) + info.xml.appendStartTag(TranslatorTags.CONTENTS_DESCRIPTION) + info.xml.appendEmptyTag(info.xml.getDataType(dt)) + info.xml.appendEndTag(TranslatorTags.CONTENTS_DESCRIPTION) + info.xml.appendStartTag(TranslatorTags.ARRAY_CONTENTS, None, None, True) + if info.xml.outputType == TranslatorOutputType.STANDARD_XML: + info.xml.append(buff.remainingHexStringFalse) + info.xml.appendEndTag(TranslatorTags.ARRAY_CONTENTS, True) + info.xml.appendEndTag(info.xml.getDataType(DataType.COMPACT_ARRAY)) + cls.getCompactArrayItem(settings, buff, dt, list_, len_) + if info.xml and info.xml.outputType == TranslatorOutputType.SIMPLE_XML: + for it in list_: + if isinstance(it, bytearray): + info.xml.append(GXByteBuffer.hex(it)) + else: + info.xml.append(str(it)) + info.xml.append(";") + if list_: + info.xml.setXmlLength(info.xml.getXmlLength() - 1) + info.xml.appendEndTag(TranslatorTags.ARRAY_CONTENTS, True) + info.xml.appendEndTag(info.xml.getDataType(DataType.COMPACT_ARRAY)) + return list_ + + # + # Get UInt8 value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed UInt8 value. + # + @classmethod + def getUInt8(cls, buff, info): + value = None + # If there is not enough data available. + if len(buff) - buff.position < 1: + info.complete = False + return None + value = buff.getUInt8() & 0xFF + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 2)) + return GXUInt8(value) + + # + # Get Int16 value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed Int16 value. + # + @classmethod + def getInt16(cls, buff, info): + value = None + # If there is not enough data available. + if len(buff) - buff.position < 2: + info.complete = False + return None + value = int(buff.getInt16()) + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 4)) + return value + + # + # Get Int8 value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed Int8 value. + # + @classmethod + def getInt8(cls, buff, info): + value = None + # If there is not enough data available. + if len(buff) - buff.position < 1: + info.complete = False + return None + value = int(buff.getInt8()) + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 2)) + return GXInt8(value) + + # + # Get BCD value from DLMS data. + # + # buff: Received DLMS data. + # info: Data info. + # Returns parsed BCD value. + # + @classmethod + def getBcd(cls, buff, info): + # If there is not enough data available. + if len(buff) - buff.position < 1: + info.complete = False + return None + value = buff.getUInt8() + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 2)) + return value + + # + # Get UTF string value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed UTF string value. + # + @classmethod + def getUtfString(cls, buff, info, knownType): + if knownType: + len_ = len(buff) + else: + len_ = _GXCommon.getObjectCount(buff) + # If there is not enough data available. + if len(buff) - buff.position < len_: + info.complete = False + return None + if len_ > 0: + value = buff.getString(buff.position, len_) + buff.position += len_ + else: + value = "" + if info.xml: + if info.xml.getShowStringAsHex: + info.xml.appendLine(info.xml.getDataType(info.type_), None, buff.toHex(False, buff.position - len, len)) + else: + info.xml.appendLine(info.xml.getDataType(info.type_), None, value) + return value + + # + # Get octet string value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed octet string value. + # + @classmethod + def getOctetString(cls, settings, buff, info, knownType): + # pylint: disable=too-many-nested-blocks,broad-except + value = None + if knownType: + len_ = len(buff) + else: + len_ = _GXCommon.getObjectCount(buff) + # If there is not enough data available. + if len(buff) - buff.position < len_: + info.complete = False + return None + tmp = bytearray(len_) + buff.get(tmp) + value = tmp + if info.xml: + if info.xml.comments and tmp: + # This might be a logical name. + if len(tmp) == 6 and tmp[5] == 255: + info.xml.appendComment(cls.toLogicalName(tmp)) + else: + isString = True + # Try to move octect string to DateTime, Date or time. + if len(tmp) == 12 or len(tmp) == 5 or len(tmp) == 4: + try: + type_ = None + if len(tmp) == 12: + type_ = DataType.DATETIME + elif len(tmp) == 5: + type_ = DataType.DATE + else: + type_ = DataType.TIME + dt = _GXCommon.changeType(settings, tmp, type_) + year = dt.value.year + if 1970 < year < 2100: + info.xml.appendComment(str(dt)) + isString = False + except Exception: + isString = True + if isString: + for it in tmp: + if it < 32 or it > 126: + isString = False + break + if isString: + info.xml.appendComment(str(tmp)) + info.xml.appendLine(info.xml.getDataType(info.type_), None, GXByteBuffer.hex(tmp, False)) + return value + + # + # Get string value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed string value. + # + @classmethod + def getString(cls, buff, info, knownType): + value = None + if knownType: + len_ = len(buff) + else: + len_ = _GXCommon.getObjectCount(buff) + # If there is not enough data available. + if len(buff) - buff.position < len_: + info.complete = False + return None + if len_ > 0: + value = buff.getString(buff.position, len_) + buff.position += len_ + else: + value = "" + if info.xml: + if info.xml.showStringAsHex: + info.xml.appendLine(info.xml.getDataType(info.type_), None, buff.toHex(False, buff.position - len_, len_)) + else: + info.xml.appendLine(info.xml.getDataType(info.type_), None, value) + return value + + # + # Get UInt32 value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed UInt32 value. + # + @classmethod + def getUInt32(cls, buff, info): + # If there is not enough data available. + if len(buff) - buff.position < 4: + info.complete = False + return None + value = buff.getUInt32() + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 8)) + return GXUInt32(value) + + # + # Get Int32 value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed Int32 value. + # + @classmethod + def getInt32(cls, buff, info): + # If there is not enough data available. + if len(buff) - buff.position < 4: + info.complete = False + return None + value = int(buff.getInt32()) + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, info.xml.integerToHex(value, 8)) + return value + + # + # Get bit string value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed bit string value. + # + @classmethod + def getBitString(cls, buff, info): + cnt = cls.getObjectCount(buff) + t = cnt + t /= 8 + if cnt % 8 != 0: + t += 1 + byteCnt = int(t) + # If there is not enough data available. + if len(buff) - buff.position < byteCnt: + info.complete = False + return None + sb = "" + while cnt > 0: + sb += cls.toBitString(buff.getInt8(), cnt) + cnt -= 8 + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, sb) + return GXBitString(sb) + + # + # Get boolean value from DLMS data. + # + # buff + # Received DLMS data. + # info + # Data info. + # parsed boolean value. + # + @classmethod + def getBoolean(cls, buff, info): + # If there is not enough data available. + if len(buff) - buff.position < 1: + info.complete = False + return None + value = bool(buff.getUInt8() != 0) + if info.xml: + info.xml.appendLine(info.xml.getDataType(info.type_), None, value.__str__()) + return value + + # + # Get HDLC address from byte array. + # + # buff + # byte array. + # HDLC address. + # + @classmethod + def getHDLCAddress(cls, buff): + size = 0 + pos = buff.position + while pos != len(buff): + size += 1 + if buff.getUInt8(pos) & 0x1 == 1: + break + pos += 1 + if size == 1: + ret = (buff.getUInt8() & 0xFE) >> 1 + elif size == 2: + ret = buff.getUInt16() + ret = ((ret & 0xFE) >> 1) | ((ret & 0xFE00) >> 2) + elif size == 4: + ret = buff.getUInt32() + ret = ((ret & 0xFE) >> 1) | ((ret & 0xFE00) >> 2) | ((ret & 0xFE0000) >> 3) | ((ret & 0xFE000000) >> 4) + else: + raise ValueError("Wrong size.") + return ret + + # + # Convert object to DLMS bytes. + # + # settings: DLMS settings. + # buff: Byte buffer where data is write. + # dataType: Data type. + # value: Added Value. + # + @classmethod + def setData(cls, settings, buff, dataType, value): + if dataType in (DataType.ARRAY, DataType.STRUCTURE) and isinstance(value, (GXByteBuffer, bytearray, bytes)): + # If byte array is added do not add type. + buff.set(value) + return + + buff.setUInt8(dataType) + if dataType == DataType.NONE: + pass + elif dataType == DataType.BOOLEAN: + if value: + buff.setUInt8(1) + else: + buff.setUInt8(0) + elif dataType == DataType.UINT8: + buff.setUInt8(value) + elif dataType in (DataType.INT8, DataType.ENUM): + buff.setInt8(value) + elif dataType in (DataType.UINT16, DataType.INT16): + buff.setUInt16(value) + elif dataType in (DataType.UINT32, DataType.INT32): + buff.setUInt32(value) + elif dataType in (DataType.UINT64, DataType.INT64): + buff.setUInt64(value) + elif dataType == DataType.FLOAT32: + buff.setFloat(value) + elif dataType == DataType.FLOAT64: + buff.setDouble(value) + elif dataType == DataType.BITSTRING: + cls.setBitString(buff, value, True) + elif dataType == DataType.STRING: + cls.setString(buff, value) + elif dataType == DataType.STRING_UTF8: + cls.setUtfString(buff, value) + elif dataType == DataType.OCTET_STRING: + if isinstance(value, GXDate): + # Add size + buff.setUInt8(5) + cls.setDate(buff, value) + elif isinstance(value, GXTime): + # Add size + buff.setUInt8(4) + cls.setTime(buff, value) + elif isinstance(value, (GXDateTime, datetime)): + buff.setUInt8(12) + cls.setDateTime(settings, buff, value) + else: + cls.setOctetString(buff, value) + elif dataType in (DataType.ARRAY, DataType.STRUCTURE): + cls.setArray(settings, buff, value) + elif dataType == DataType.BCD: + cls.setBcd(buff, value) + elif dataType == DataType.COMPACT_ARRAY: + # Compact array is not work with python because we don't know data + # types of each element. + raise ValueError("Invalid data type.") + elif dataType == DataType.DATETIME: + cls.setDateTime(settings, buff, value) + elif dataType == DataType.DATE: + cls.setDate(buff, value) + elif dataType == DataType.TIME: + cls.setTime(buff, value) + else: + raise ValueError("Invalid data type.") + + # + # Convert time to DLMS bytes. + # + # buff + # Byte buffer where data is write. + # value + # Added value. + # + @classmethod + def setTime(cls, buff, value): + dt = _GXCommon.__getDateTime(value) + # Add time. + if dt.skip & DateTimeSkips.HOUR != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.hour) + if dt.skip & DateTimeSkips.MINUTE != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.minute) + if dt.skip & DateTimeSkips.SECOND != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.second) + if dt.skip & DateTimeSkips.MILLISECOND != DateTimeSkips.NONE: + # Hundredth of seconds is not used. + buff.setUInt8(0xFF) + else: + ms = dt.value.microsecond + if ms != 0: + ms /= 10000 + buff.setUInt8(int(ms)) + + # + # Convert date to DLMS bytes. + # + # buff + # Byte buffer where data is write. + # value + # Added value. + # + @classmethod + def setDate(cls, buff, value): + dt = _GXCommon.__getDateTime(value) + # Add year. + if dt.skip & DateTimeSkips.YEAR != DateTimeSkips.NONE: + buff.setUInt16(0xFFFF) + else: + buff.setUInt16(dt.value.year) + # Add month + if dt.extra & DateTimeExtraInfo.DST_BEGIN != 0: + buff.setUInt8(0xFE) + elif dt.extra & DateTimeExtraInfo.DST_END != 0: + buff.setUInt8(0xFD) + elif dt.skip & DateTimeSkips.MONTH != 0: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.month) + # Add day + if dt.extra & DateTimeExtraInfo.LAST_DAY2 != DateTimeSkips.NONE: + buff.setUInt8(0xFD) + elif dt.extra & DateTimeExtraInfo.LAST_DAY != DateTimeSkips.NONE: + buff.setUInt8(0xFE) + elif dt.skip & DateTimeSkips.DAY != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.day) + + # Day of week. + if dt.skip & DateTimeSkips.DAY_OF_WEEK != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + if dt.dayOfWeek == 0: + buff.setUInt8(dt.value.weekday() + 1) + else: + buff.setUInt8(dt.dayOfWeek) + + @classmethod + def __getDateTime(cls, value): + dt = None + if isinstance(value, (GXDateTime)): + dt = value + elif isinstance(value, (datetime, str)): + dt = GXDateTime(value) + dt.skip |= DateTimeSkips.MILLISECOND + else: + raise ValueError("Invalid date format.") + return dt + + # + # Convert date time to DLMS bytes. + # + # buff + # Byte buffer where data is write. + # value + # Added value. + # + @classmethod + def setDateTime(cls, settings, buff, value): + dt = cls.__getDateTime(value) + skip = dt.skip + if settings and settings.dateTimeSkips: + skip = skip or settings.dateTimeSkips + + # Add year. + if skip & DateTimeSkips.YEAR != DateTimeSkips.NONE: + buff.setUInt16(0xFFFF) + else: + buff.setUInt16(dt.value.year) + # Add month + if dt.extra & DateTimeExtraInfo.DST_BEGIN != 0: + buff.setUInt8(0xFD) + elif dt.extra & DateTimeExtraInfo.DST_END != 0: + buff.setUInt8(0xFE) + elif skip & DateTimeSkips.MONTH != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.month) + + # Add day + if dt.extra & DateTimeExtraInfo.LAST_DAY2 != DateTimeSkips.NONE: + buff.setUInt8(0xFD) + elif dt.extra & DateTimeExtraInfo.LAST_DAY != DateTimeSkips.NONE: + buff.setUInt8(0xFE) + elif skip & DateTimeSkips.DAY != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.day) + # Day of week. + if skip & DateTimeSkips.DAY_OF_WEEK != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + if dt.dayOfWeek == 0: + buff.setUInt8(dt.value.weekday() + 1) + else: + buff.setUInt8(dt.dayOfWeek) + # Add time. + if skip & DateTimeSkips.HOUR != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.hour) + if skip & DateTimeSkips.MINUTE != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.minute) + if skip & DateTimeSkips.SECOND != DateTimeSkips.NONE: + buff.setUInt8(0xFF) + else: + buff.setUInt8(dt.value.second) + if skip & DateTimeSkips.MILLISECOND != DateTimeSkips.NONE: + # Hundredth of seconds is not used. + buff.setUInt8(0xFF) + else: + ms = dt.value.microsecond + if ms != 0: + ms /= 10000 + buff.setUInt8(int(ms)) + # devitation not used. + if skip & DateTimeSkips.DEVITATION != DateTimeSkips.NONE: + buff.setUInt16(0x8000) + else: + # Add devitation. + d = int(dt.value.utcoffset().seconds / 60) + if not (settings and settings.useUtc2NormalTime): + d = -d + buff.setUInt16(d) + # Add clock_status + if skip & DateTimeSkips.STATUS == DateTimeSkips.NONE: + if dt.value.dst() or dt.status & ClockStatus.DAYLIGHT_SAVE_ACTIVE != ClockStatus.OK: + buff.setUInt8(dt.status | ClockStatus.DAYLIGHT_SAVE_ACTIVE) + else: + buff.setUInt8(dt.status) + else: + buff.setUInt8(0xFF) + + @classmethod + def setBcd(cls, buff, value): + buff.setUInt8(value) + + @classmethod + def setArray(cls, settings, buff, value): + if value: + _GXCommon.setObjectCount(len(value), buff) + for it in value: + cls.setData(settings, buff, cls.getDLMSDataType(it), it) + else: + _GXCommon.setObjectCount(0, buff) + + @classmethod + def setOctetString(cls, buff, value): + if isinstance(value, str): + tmp = GXByteBuffer.hexToBytes(value) + _GXCommon.setObjectCount(len(tmp), buff) + buff.set(tmp) + elif isinstance(value, GXByteBuffer): + cls.setObjectCount(len(value), buff) + buff.set(value) + elif isinstance(value, (bytearray, bytes)): + cls.setObjectCount(len(value), buff) + buff.set(value) + elif value is None: + cls.setObjectCount(0, buff) + else: + raise ValueError("Invalid data type.") + + @classmethod + def setUtfString(cls, buff, value): + if value: + tmp = value.encode() + _GXCommon.setObjectCount(len(tmp), buff) + buff.set(tmp) + else: + buff.setUInt8(0) + + @classmethod + def setString(cls, buff, value): + if value: + _GXCommon.setObjectCount(len(value), buff) + buff.set(_GXCommon.getBytes(value)) + else: + buff.setUInt8(0) + + @classmethod + def setBitString(cls, buff, val1, addCount): + value = val1 + if isinstance(value, GXBitString): + value = value.value + if isinstance(value, str): + val = 0 + str_ = str(value) + if addCount: + _GXCommon.setObjectCount(len(str_), buff) + index = 7 + pos = 0 + while pos != len(str_): + it = str_[pos] + if it == '1': + val |= (1 << index) + elif it != '0': + raise ValueError("Not a bit string.") + index -= 1 + if index == -1: + index = 7 + buff.setUInt8(val) + val = 0 + pos += 1 + if index != 7: + buff.setUInt8(val) + elif isinstance(value, (bytearray, bytes)): + _GXCommon.setObjectCount(8 * len(value), buff) + buff.set(value) + elif isinstance(value, int): + _GXCommon.setObjectCount(8, buff) + buff.setUInt8(value) + elif value is None: + buff.setUInt8(0) + else: + raise ValueError("BitString must give as string.") + + @classmethod + def getDataType(cls, value): + if value == DataType.NONE: + ret = None + elif value == DataType.OCTET_STRING: + ret = bytes.__class__ + elif value == DataType.ENUM: + ret = GXEnum.__class__ + elif value == DataType.INT8: + ret = int.__class__ + elif value == DataType.INT16: + ret = int.__class__ + elif value == DataType.INT32: + ret = int.__class__ + elif value == DataType.INT64: + ret = int.__class__ + elif value == DataType.UINT8: + ret = GXUInt8.__class__ + elif value == DataType.UINT16: + ret = GXUInt16.__class__ + elif value == DataType.UINT32: + ret = GXUInt32.__class__ + elif value == DataType.UINT64: + ret = GXUInt64.__class__ + elif value == DataType.TIME: + ret = GXTime.__class__ + elif value == DataType.DATE: + ret = GXDate.__class__ + elif value == DataType.DATETIME: + ret = GXDateTime.__class__ + elif value == DataType.ARRAY: + ret = list.__class__ + elif value == DataType.STRING: + ret = str.__class__ + elif value == DataType.BOOLEAN: + ret = bool.__class__ + elif value == DataType.FLOAT32: + ret = GXFloat32.__class__ + elif value == DataType.FLOAT64: + ret = GXFloat64.__class__ + elif value == DataType.BITSTRING: + ret = GXBitString.__class__ + else: + raise ValueError("Invalid value.") + return ret + + @classmethod + def getDLMSDataType(cls, value): + # pylint: disable=undefined-variable + if value is None: + ret = DataType.NONE + elif isinstance(value, (bytes, bytearray, GXByteBuffer)): + ret = DataType.OCTET_STRING + elif isinstance(value, (GXEnum)): + ret = DataType.ENUM + elif isinstance(value, (GXInt8)): + ret = DataType.INT8 + elif isinstance(value, (GXInt16)): + ret = DataType.INT16 + elif isinstance(value, (GXInt64)): + ret = DataType.INT64 + elif isinstance(value, (GXUInt8)): + ret = DataType.UINT8 + elif isinstance(value, (GXUInt16)): + ret = DataType.UINT16 + elif isinstance(value, (GXUInt32)): + ret = DataType.UINT32 + elif isinstance(value, (GXUInt64)): + ret = DataType.UINT64 + elif isinstance(value, (bool)): + ret = DataType.BOOLEAN + elif isinstance(value, (GXInt32, int)): + ret = DataType.INT32 + elif isinstance(value, (GXTime)): + ret = DataType.TIME + elif isinstance(value, (GXDate)): + ret = DataType.DATE + elif isinstance(value, (datetime, GXDateTime)): + ret = DataType.DATETIME + elif isinstance(value, (GXStructure)): + ret = DataType.STRUCTURE + elif isinstance(value, (GXArray, list)): + ret = DataType.ARRAY + elif isinstance(value, (str)): + ret = DataType.STRING + elif isinstance(value, (GXFloat64)): + ret = DataType.FLOAT64 + elif isinstance(value, (GXFloat32, complex, float)): + ret = DataType.FLOAT32 + elif isinstance(value, (GXBitString)): + ret = DataType.BITSTRING + else: + ret = None + if ret is None: + #Python 2.7 uses unicode. + try: + if isinstance(value, (unicode)): + ret = DataType.STRING + except Exception: + ret = None + if ret is None: + raise ValueError("Invalid datatype " + type(value) + ".") + return ret + + @classmethod + def getDataTypeSize(cls, type_): + size = -1 + if type_ == DataType.BCD: + size = 1 + elif type_ == DataType.BOOLEAN: + size = 1 + elif type_ == DataType.DATE: + size = 5 + elif type_ == DataType.DATETIME: + size = 12 + elif type_ == DataType.ENUM: + size = 1 + elif type_ == DataType.FLOAT32: + size = 4 + elif type_ == DataType.FLOAT64: + size = 8 + elif type_ == DataType.INT16: + size = 2 + elif type_ == DataType.INT32: + size = 4 + elif type_ == DataType.INT64: + size = 8 + elif type_ == DataType.INT8: + size = 1 + elif type_ == DataType.NONE: + size = 0 + elif type_ == DataType.TIME: + size = 4 + elif type_ == DataType.UINT16: + size = 2 + elif type_ == DataType.UINT32: + size = 4 + elif type_ == DataType.UINT64: + size = 8 + elif type_ == DataType.UINT8: + size = 1 + return size + + @classmethod + def toLogicalName(cls, value): + if isinstance(value, bytearray): + if not value: + value = bytearray(6) + if len(value) == 6: + return str(value[0]) + "." + str(value[1]) + "." + str(value[2]) + "." + str(value[3]) + "." + str(value[4]) + "." + str(value[5]) + raise ValueError("Invalid Logical name.") + return str(value) + + @classmethod + def logicalNameToBytes(cls, value): + if not value: + return bytearray(6) + items = value.split('.') + if len(items) != 6: + raise ValueError("Invalid Logical name.") + buff = bytearray(6) + pos = 0 + for it in items: + v = int(it) + if v < 0 or v > 255: + raise ValueError("Invalid Logical name.") + buff[pos] = int(v) + pos += 1 + return buff + + @classmethod + def getGeneralizedTime(cls, dateString): + year = int(dateString[0:4]) + month = int(dateString[4:6]) + day = int(dateString[6:8]) + hour = int(dateString[8:10]) + minute = int(dateString[10:12]) + #If UTC time. + if dateString.endsWith("Z"): + if len(dateString) > 13: + second = int(dateString[12:14]) + return datetime(year, month, day, hour, minute, second, 0, tzinfo=GXTimeZone(0)) + + if len(dateString) > 17: + second = int(dateString.substring(12, 14)) + tz = dateString[dateString.length() - 4:] + return datetime(year, month, day, hour, minute, second, 0, tzinfo=GXTimeZone(tz)) + + @classmethod + def generalizedTime(cls, value): + #Convert to UTC time. + if isinstance(value, (GXDateTime)): + value = value.value + value = value.utctimetuple() + sb = cls.integerString(value.tm_year, 4) + sb += cls.integerString(value.tm_mon, 2) + sb += cls.integerString(value.tm_mday, 2) + sb += cls.integerString(value.tm_hour, 2) + sb += cls.integerString(value.tm_min, 2) + sb += cls.integerString(value.tm_sec, 2) + #UTC time. + sb += "Z" + return sb + + @classmethod + def encryptManufacturer(cls, flagName): + if len(flagName) != 3: + raise ValueError("Invalid Flag name.") + value = ((flagName.charAt(0) - 0x40) & 0x1f) + value <<= 5 + value += ((flagName.charAt(0) - 0x40) & 0x1f) + value <<= 5 + value += ((flagName.charAt(0) - 0x40) & 0x1f) + return value + + @classmethod + def decryptManufacturer(cls, value): + tmp = (value >> 8 | value << 8) + c = chr(((tmp & 0x1f) + 0x40)) + tmp = (tmp >> 5) + c1 = chr(((tmp & 0x1f) + 0x40)) + tmp = (tmp >> 5) + c2 = chr(((tmp & 0x1f) + 0x40)) + return "".join([c2, c1, c]) + + @classmethod + def idisSystemTitleToString(cls, st): + sb = '\n' + sb += "IDIS system title:\n" + sb += "Manufacturer Code: " + sb += _GXCommon.__getChar(st[0]) + _GXCommon.__getChar(st[1]) + _GXCommon.__getChar(st[2]) + sb += "\nFunction type: " + ft = st[4] >> 4 + add = False + if (ft & 0x1) != 0: + sb += "Disconnector extension" + add = True + if (ft & 0x2) != 0: + if add: + sb += ", " + add = True + sb += "Load Management extension" + + if (ft & 0x4) != 0: + if add: + sb += ", " + sb += "Multi Utility extension" + #Serial number + sn = (st[4] & 0xF) << 24 + sn |= st[5] << 16 + sn |= st[6] << 8 + sn |= st[7] + sb += '\n' + sb += "Serial number: " + sb += str(sn) + '\n' + return sb + + @classmethod + def dlmsSystemTitleToString(cls, st): + sb = '\n' + sb += "IDIS system title:\n" + sb += "Manufacturer Code: " + sb += _GXCommon.__getChar(st[0]) + _GXCommon.__getChar(st[1]) + _GXCommon.__getChar(st[2]) + sb += "Serial number: " + sb += cls.__getChar(st[3]) + cls.__getChar(st[4]) + cls.__getChar(st[5]) + cls.__getChar(st[6]) + cls.__getChar(st[7]) + return sb + + @classmethod + def uniSystemTitleToString(cls, st): + sb = '\n' + sb += "UNI/TS system title:\n" + sb += "Manufacturer: " + m = st[0] << 8 | st[1] + sb += cls.decryptManufacturer(m) + sb += "\nSerial number: " + sb += GXByteBuffer.hex((st[7], st[6], st[5], st[4], st[3], st[2]), False) + return sb + + @classmethod + def __getChar(cls, ch): + try: + return str(chr(ch)) + except Exception: + #If python 2.7 is used. + #pylint: disable=undefined-variable + return str(unichr(ch)) + + @classmethod + def systemTitleToString(cls, standard, st): + ###Conver system title to string. + #pylint: disable=too-many-boolean-expressions + if standard == Standard.ITALY or not _GXCommon.__getChar(st[0]).isalpha() or \ + not cls.__getChar(st[1]).isalpha() or not cls.__getChar(st[2]).isalpha(): + return cls.uniSystemTitleToString(st) + if standard == Standard.IDIS or not _GXCommon.__getChar(st[3]).isdigit() or \ + not _GXCommon.__getChar(st[4]).isdigit() or not _GXCommon.__getChar(st[5]).isdigit() or \ + not _GXCommon.__getChar(st[6]).isdigit() or not _GXCommon.__getChar(st[7]).isdigit(): + return cls.idisSystemTitleToString(st) + return cls.dlmsSystemTitleToString(st) + + #Reserved for internal use. + @classmethod + def swapBits(cls, value): + ret = 0 + pos = 0 + while pos != 8: + ret = ret << 1 | value & 0x01 + value = value >> 1 + pos = pos + 1 + return ret diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/internal/_GXDataInfo.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/internal/_GXDataInfo.py new file mode 100644 index 0000000..c65fb6f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/internal/_GXDataInfo.py @@ -0,0 +1,56 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..enums import DataType +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class _GXDataInfo: + """This class is used in DLMS data parsing.""" + + def __init__(self): + """Constructor.""" + # Last array index. + self.index = 0 + # Items count in array. + self.count = 0 + # Object data type. + self.type_ = DataType.NONE + # Is data parsed to the end. + self.complete = True + self.xml = None + + def clear(self): + self.index = 0 + self.count = 0 + self.type_ = DataType.NONE + self.complete = True diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/internal/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/internal/__init__.py new file mode 100644 index 0000000..74f5267 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/internal/__init__.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ._GXCommon import * +from ._GXDataInfo import * diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXAttributeCollection.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXAttributeCollection.py new file mode 100644 index 0000000..3f347ce --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXAttributeCollection.py @@ -0,0 +1,58 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSAttributeSettings import GXDLMSAttributeSettings + +class GXAttributeCollection(list): + # + # Constructor. + # + # forParent: Parent object. + # + def __init__(self, forParent=None): + list.__init__(self) + # Parent object. + self.parent = forParent + + def append(self, item): + if not isinstance(item, GXDLMSAttributeSettings): + raise TypeError('item is not of type GXDLMSAttributeSettings') + #pylint: disable=super-with-arguments + super(GXAttributeCollection, self).append(item) + item.parent = self + + def find(self, index): + for it in self: + if it.index == index: + return it + return None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXAuthentication.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXAuthentication.py new file mode 100644 index 0000000..8202d38 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXAuthentication.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..enums import Authentication + +class GXAuthentication: + """ + Authentication class is used to give authentication information to the server. + """ + + def __init__(self, forType=Authentication.NONE, pw=None, forClientAddress=0): + """ + Constructor. + forType: Authentication type + pw: Used password. + forClientAddress: Client Id. + """ + + self.type_ = forType + if pw: + self.password = pw[0:] + self.clientAddress = forClientAddress + + def __str__(self): + return str(self.type_) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXDLMSAttribute.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXDLMSAttribute.py new file mode 100644 index 0000000..8f9852e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXDLMSAttribute.py @@ -0,0 +1,52 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSAttributeSettings import GXDLMSAttributeSettings +from ..enums import DataType + +class GXDLMSAttribute(GXDLMSAttributeSettings): + #pylint: disable=too-few-public-methods + def __init__(self, index=0, type_=DataType.NONE, uiType=DataType.NONE, order=0): + """ + Constructor. + + index : Attribute index. + type : Data type. + uiType : UI data type. + order : Order. + """ + GXDLMSAttributeSettings.__init__(self) + self.index = index + self.type_ = type_ + self.UIType = uiType + self.order = order diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXDLMSAttributeSettings.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXDLMSAttributeSettings.py new file mode 100644 index 0000000..e121d91 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXDLMSAttributeSettings.py @@ -0,0 +1,67 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..enums import DataType + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class GXDLMSAttributeSettings: + # pylint: disable=too-many-instance-attributes + + def __init__(self, index=0): + # + #Constructor. + #index: Attribute index. + # + self.name = "" + self.index = index + self.type_ = DataType.NONE + self.uiType = DataType.NONE + self.access = None + self.static = False + self.values = None + self.order = 0 + self.minimumVersion = 0 + self.xml = "" + + def copyTo(self, target): + target.name = self.name + target.index = self.index + target.type_ = self.type_ + target.uiType = self.uiType + target.access = self.access + target.static = self.static + target.values = self.values + target.order = self.order + target.minimumVersion = self.minimumVersion + target.xml = self.xml diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXManufacturer.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXManufacturer.py new file mode 100644 index 0000000..1786d8f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXManufacturer.py @@ -0,0 +1,76 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .InactivityMode import InactivityMode +from .StartProtocolType import StartProtocolType +from .GXObisCodeCollection import GXObisCodeCollection +# +class GXManufacturer: + # pylint: disable=too-many-instance-attributes + def __init__(self): + """ + Constructor. + """ + self.inactivityMode = InactivityMode.KEEPALIVE + self.useIEC47 = False + self.forceInactivity = False + self.useLogicalNameReferencing = False + self.identification = None + self.obisCodes = GXObisCodeCollection() + self.name = None + self.settings = list() + self.serverSettings = list() + self.keepAliveInterval = 40000 + self.startProtocol = StartProtocolType.IEC + self.webAddress = None + self.info = None + + + def getServer(self, address): + for it in self.serverSettings: + if it.HDLCAddress == address: + return it + return None + + # + # Get authentication settings. + # + # @param authentication + # Authentication type. + # Authentication settings. + # + def getAuthentication(self, authentication): + for it in self.settings: + if it.type_ == authentication: + return it + return None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXManufacturerCollection.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXManufacturerCollection.py new file mode 100644 index 0000000..4a605bd --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXManufacturerCollection.py @@ -0,0 +1,243 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +import xml.etree.cElementTree as ET +import datetime +import sys +from os import listdir +from os.path import isfile, join +import os.path +from tempfile import NamedTemporaryFile +from .GXManufacturer import GXManufacturer +from .GXObisCode import GXObisCode +from .GXAuthentication import GXAuthentication +from .GXServerAddress import GXServerAddress +from .GXDLMSAttribute import GXDLMSAttribute +from .InactivityMode import InactivityMode +from .StartProtocolType import StartProtocolType +from .HDLCAddressType import HDLCAddressType +from ..enums import Authentication, DataType, ObjectType +if sys.version_info >= (3, 0): + import urllib.request + +# +class GXManufacturerCollection(list): + # + # * Find manufacturer settings by manufacturer id. + # + # @param id + # Manufacturer id. + # found manufacturer or null. + # + def findByIdentification(self, id_): + for it in self: + if it.identification == id_: + return it + return None + + # + # @param path + # Is this first run. + # Settings directory. + # + @classmethod + def isFirstRun(cls, path): + if not os.path.isdir(path): + os.mkdir(path) + return True + if not os.path.isfile(os.path.join(path, "files.xml")): + return True + return False + + # + # Check if there are any updates available in Gurux www server. + # + # @param path + # Settings directory. + # Returns true if there are any updates available. + # + @classmethod + def isUpdatesAvailable(cls, path): + if sys.version_info < (3, 0): + return False + # pylint: disable=broad-except + if not os.path.isfile(os.path.join(path, "files.xml")): + return True + try: + available = dict() + for it in ET.parse(os.path.join(path, "files.xml")).iter(): + if it.tag == "File": + available[it.text] = datetime.datetime.strptime(it.attrib["Modified"], "%d-%m-%Y") + + path = NamedTemporaryFile() + path.close() + urllib.request.urlretrieve("https://www.gurux.fi/obis/files.xml", path.name) + for it in ET.parse(path.name).iter(): + if it.tag == "File": + tmp = datetime.datetime.strptime(it.attrib["Modified"], "%d-%m-%Y") + if not it.text in available or available[it.text] != tmp: + return True + except Exception as e: + print(e) + return True + return False + + @classmethod + def updateManufactureSettings(cls, directory): + # + # Update manufacturer settings from the Gurux www server. + # + # directory: Target directory. + # + if sys.version_info >= (3, 0): + return + if not os.path.isdir(directory): + os.mkdir(directory) + if not os.path.isdir(directory): + return + path = os.path.join(directory, "files.xml") + urllib.request.urlretrieve("https://www.gurux.fi/obis/files.xml", path) + for it in ET.parse(path).iter(): + if it.tag == "File": + path = os.path.join(directory, it.text) + urllib.request.urlretrieve("https://www.gurux.fi/obis/" + it.text, path) + + @classmethod + def readManufacturerSettings(cls, manufacturers, path): + # pylint: disable=broad-except + manufacturers = [] + files = [f for f in listdir(path) if isfile(join(path, f))] + if files: + for it in files: + if it.endswith(".obx"): + try: + manufacturers.append(GXManufacturerCollection.__parse(os.path.join(path, it))) + except Exception as e: + print(e) + continue + + # + # Serialize manufacturer from the xml. + # + # @param in + # Input stream. + # Serialized manufacturer. + # + @classmethod + def __parse(cls, file): + man = None + authentication = None + for it in ET.parse(file).iter(): + if it.tag == "GXManufacturer": + man = GXManufacturer() + elif it.tag == "GXObisCode": + obisCode = GXObisCode() + man.obisCodes.append(obisCode) + elif it.tag == "GXAuthentication": + authentication = GXAuthentication() + man.settings.append(authentication) + elif it.tag == "GXServerAddress": + serveraddress = GXServerAddress() + man.serverSettings.append(serveraddress) + elif it.tag == "GXDLMSAttributeSettings": + att = GXDLMSAttribute() + obisCode.attributes.append(att) + elif it.tag == "Identification": + man.identification = it.text + elif it.tag == "Name": + man.name = it.text + elif it.tag == "UseLN": + man.useLogicalNameReferencing = bool(it.text) + elif it.tag == "UseIEC47": + man.useIEC47 = bool(it.text) + elif it.tag == "ClientAddress": + if authentication: + authentication.clientAddress = int(it.text) + elif it.tag == "PhysicalAddress": + if serveraddress: + serveraddress.physicalAddress = int(it.text) + elif it.tag == "LogicalAddress": + if serveraddress: + serveraddress.logicalAddress = int(it.text) + elif it.tag == "Formula": + if serveraddress: + serveraddress.formula = it.text + elif it.tag == "HDLCAddress": + if serveraddress: + serveraddress.HDLCAddress = (HDLCAddressType(int(it.text))) + elif it.tag == "Selected": + # Old functionality. + continue + elif it.tag == "InactivityMode": + man.inactivityMode = InactivityMode[it.text.upper()] + elif it.tag == "Type": + if authentication: + str_ = it.text.upper().replace("HIGH", "HIGH_") + if str_ == "HIGH_": + str_ = "HIGH" + authentication.type_ = Authentication[str_] + else: + if it.text == "BinaryCodedDesimal": + att.type_ = DataType.BCD + elif it.text == "OctetString": + att.type_ = DataType.OCTET_STRING + else: + att.type_ = DataType[it.text.upper()] + elif it.tag == "UIType": + if it.text == "BinaryCodedDesimal": + att.uiType = DataType.BCD + elif it.text == "OctetString": + att.uiType = DataType.OCTET_STRING + else: + att.uiType = DataType[it.text.upper()] + elif it.tag == "GXAuthentication": + authentication = None + elif it.tag == "LogicalName": + obisCode.logicalName = it.text + elif it.tag == "Description": + obisCode.description = it.text + elif it.tag == "ObjectType": + obisCode.objectType = ObjectType(int(it.text)) + elif it.tag == "Interface": + # Old way. ObjectType is used now. + continue + elif it.tag == "Index": + att.index = int(it.text) + elif it.tag == "StartProtocol": + man.startProtocol = StartProtocolType[it.text] + elif it.tag == "WebAddress": + man.webAddress = it.text + elif it.tag == "Info": + man.info = it.text + return man diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisCode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisCode.py new file mode 100644 index 0000000..e2318a7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisCode.py @@ -0,0 +1,58 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..enums import ObjectType +from .GXAttributeCollection import GXAttributeCollection + +class GXObisCode: + + # + # Constructor. + # + # ln: Logical name. + # ot: Object type. + # index: Attribute index. + # + def __init__(self, ln=None, ot=ObjectType.NONE, index=0, desc=None): + self.version = 0 + self.attributeIndex = index + self.logicalName = ln + self.description = desc + self.objectType = ot + self.attributes = GXAttributeCollection(self) + self.attributes.parent = self + #UI data types. + self.uiDataType = None + + def __str__(self): + return str(self.objectType) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisCodeCollection.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisCodeCollection.py new file mode 100644 index 0000000..cbef8ee --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisCodeCollection.py @@ -0,0 +1,40 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..enums import ObjectType +class GXObisCodeCollection(list): + def findByLN(self, ot, ln, skipItem): + for it in self: + if ot in (it.objectType, ObjectType.NONE) and it.logicalName == ln and it != skipItem: + return it + return None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisValueItem.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisValueItem.py new file mode 100644 index 0000000..2951590 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisValueItem.py @@ -0,0 +1,44 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXObisValueItem: + # pylint: disable=too-few-public-methods + def __init__(self, devValue=None, userValue=None): + """ + Constructor. + + devValue: Device value. + userValue: Value that is shown to the user. + """ + self.value = devValue + self.uiValue = userValue diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisValueItemCollection.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisValueItemCollection.py new file mode 100644 index 0000000..a5bb6a7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXObisValueItemCollection.py @@ -0,0 +1,58 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXObisValueItem import GXObisValueItem + +class GXObisValueItemCollection(list): + # + # Constructor. + # + # forParent: Parent object. + # + def __init__(self, forParent=None): + list.__init__(self) + # Parent object. + self.parent = forParent + + def append(self, item): + if not isinstance(item, GXObisValueItem): + raise TypeError('item is not of type GXObisValueItem') + if not self.contains(item): + #pylint: disable=super-with-arguments + super(GXObisValueItemCollection, self).append(item) + + def contains(self, item): + for it in self: + if it.value == item.value: + return True + return False diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXServerAddress.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXServerAddress.py new file mode 100644 index 0000000..73d05a1 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/GXServerAddress.py @@ -0,0 +1,49 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .HDLCAddressType import HDLCAddressType +# +class GXServerAddress: + # pylint: disable=too-few-public-methods + def __init__(self, address=HDLCAddressType.DEFAULT, value=0): + """ + Constructor. + + address: HDLC address type. + value: Physical address. + """ + self.HDLCAddress = address + self.physicalAddress = value + self.formula = None + self.logicalAddress = 0 + self.selected = False diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/HDLCAddressType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/HDLCAddressType.py new file mode 100644 index 0000000..63afdc9 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/HDLCAddressType.py @@ -0,0 +1,40 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class HDLCAddressType(GXIntEnum): + #pylint: disable=too-few-public-methods + DEFAULT = 0 + SERIAL_NUMBER = 1 + CUSTOM = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/InactivityMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/InactivityMode.py new file mode 100644 index 0000000..16d17a4 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/InactivityMode.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class InactivityMode(GXIntEnum): + """ + Enumerates inactivity modes that are used, when communicating with IEC + using serial port connection. + """ + #pylint: disable=too-few-public-methods + + NONE = 0 + KEEPALIVE = 1 + REOPEN = 2 + REOPENACTIVE = 3 + DISCONNECT = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/StartProtocolType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/StartProtocolType.py new file mode 100644 index 0000000..c317ba6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/StartProtocolType.py @@ -0,0 +1,45 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXIntEnum import GXIntEnum + +class StartProtocolType(GXIntEnum): + """ + What protocol meter uses when connection is started. + """ + #pylint: disable=too-few-public-methods + + # IEC protocol is start protocol. + IEC = 0 + # DLMS is start protocol. + DLMS = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/__init__.py new file mode 100644 index 0000000..5252e9d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/manufacturersettings/__init__.py @@ -0,0 +1,14 @@ +from .GXAttributeCollection import * +from .GXAuthentication import * +from .GXDLMSAttributeSettings import * +from .GXDLMSAttribute import * +from .GXManufacturer import * +from .GXManufacturerCollection import * +from .GXObisCode import * +from .GXObisCodeCollection import * +from .GXObisValueItem import * +from .GXObisValueItemCollection import * +from .GXServerAddress import * +from .HDLCAddressType import * +from .InactivityMode import * +from .StartProtocolType import * diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXAdjacentCell.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXAdjacentCell.py new file mode 100644 index 0000000..a5388b1 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXAdjacentCell.py @@ -0,0 +1,45 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXAdjacentCell: + # + # Constructor. + # + def __init__(self): + # Four-byte cell ID. + self.cellId = 0 + # Signal quality. + self.signalQuality = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXApplicationContextName.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXApplicationContextName.py new file mode 100644 index 0000000..90a39cf --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXApplicationContextName.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import ApplicationContextName + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXApplicationContextName: + def __init__(self): + """ + Constructor. + """ + self.jointIsoCtt = 2 + self.country = 16 + self.countryName = 756 + self.identifiedOrganization = 5 + self.dlmsUA = 8 + self.applicationContext = 1 + self.contextId = ApplicationContextName.LOGICAL_NAME + + def __str__(self): + str_ = str(self.jointIsoCtt) + " " + str(self.country) + " " + str_ += str(self.countryName) + " " + str(self.identifiedOrganization) + " " + str_ += str(self.dlmsUA) + " " + str(self.applicationContext) + " " + str(self.contextId) + return str_ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXAuthenticationMechanismName.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXAuthenticationMechanismName.py new file mode 100644 index 0000000..4b7bd72 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXAuthenticationMechanismName.py @@ -0,0 +1,54 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..enums import Authentication + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class GXAuthenticationMechanismName: + # pylint: disable=too-many-instance-attributes,too-few-public-methods + + def __init__(self): + # Constructor. + self.mechanismId = Authentication.NONE + self.jointIsoCtt = 2 + self.country = 16 + self.countryName = 756 + self.identifiedOrganization = 5 + self.dlmsUA = 8 + self.authenticationMechanismName = 2 + + def __str__(self): + return str(self.jointIsoCtt) + " " + str(self.country) + " " + str(self.countryName) + " " + \ + str(self.identifiedOrganization) + " " + str(self.dlmsUA) + " " + \ + str(self.authenticationMechanismName) + " " + str(self.mechanismId) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXChargePerUnitScaling.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXChargePerUnitScaling.py new file mode 100644 index 0000000..5130db0 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXChargePerUnitScaling.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +class GXChargePerUnitScaling: + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSCharge + """ + #pylint: disable=bad-option-value,old-style-class,too-few-public-methods + + # + # Constructor. + # + def __init__(self): + self.commodityScale = 0 + self.priceScale = 0 + + def __str__(self): + return str(self.commodityScale) + ", " + str(self.priceScale) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXChargeTable.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXChargeTable.py new file mode 100644 index 0000000..c8726fb --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXChargeTable.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXChargeTable: + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSCharge + """ + + # + # Constructor. + # + def __init__(self): + self.index = None + self.chargePerUnit = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXCommodity.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXCommodity.py new file mode 100644 index 0000000..38bd414 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXCommodity.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +class GXCommodity: + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSCharge + """ + #pylint: disable=bad-option-value,old-style-class,too-few-public-methods + + # + # Constructor. + # + def __init__(self): + self.target = None + self.index = 0 + + def __str__(self): + return str(self.target) + ", " + str(self.index) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXCreditChargeConfiguration.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXCreditChargeConfiguration.py new file mode 100644 index 0000000..d795f2a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXCreditChargeConfiguration.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import CreditCollectionConfiguration +class GXCreditChargeConfiguration: + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAccount + """ + #pylint: disable=bad-option-value,old-style-class,too-few-public-methods + # + # Constructor. + # + def __init__(self): + self.creditReference = None + self.chargeReference = None + self.collectionConfiguration = CreditCollectionConfiguration.NONE diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXCurrency.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXCurrency.py new file mode 100644 index 0000000..659377f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXCurrency.py @@ -0,0 +1,56 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import Currency + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class, too-few-public-methods +class GXCurrency: + """ + Used currency. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAccount + """ + def __init__(self): + """ + Constructor. + """ + # Currency unit. + self.unit = Currency.TIME + # Currency name. + self.name = None + # Currency scale. + self.scale = 0 + + def __str__(self): + return str(self.unit) + " " + str(self.name) + " " + str(self.scale) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAccount.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAccount.py new file mode 100644 index 0000000..1f082c0 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAccount.py @@ -0,0 +1,540 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..enums import ObjectType, DataType +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..GXDateTime import GXDateTime +from .enums import PaymentMode, AccountStatus, AccountCreditStatus, CreditCollectionConfiguration +from .GXCurrency import GXCurrency +from .GXTokenGatewayConfiguration import GXTokenGatewayConfiguration +from .GXCreditChargeConfiguration import GXCreditChargeConfiguration +from ..GXBitString import GXBitString + +# pylint: disable=too-many-instance-attributes +class GXDLMSAccount(GXDLMSObject, IGXDLMSBase): + + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAccount + """ + def __init__(self, ln="0.0.19.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.ACCOUNT, ln, sn) + self.paymentMode = PaymentMode.CREDIT + self.accountStatus = AccountStatus.NEW_INACTIVE_ACCOUNT + self.creditReferences = list() + self.chargeReferences = list() + self.creditChargeConfigurations = list() + self.tokenGatewayConfigurations = list() + self.currency = GXCurrency() + self.currentCreditInUse = 0 + self.currentCreditStatus = AccountCreditStatus.IN_CREDIT + self.availableCredit = 0 + self.amountToClear = 0 + self.clearanceThreshold = 0 + self.aggregatedDebt = 0 + self.accountActivationTime = None + self.accountClosureTime = None + self.lowCreditThreshold = 0 + self.nextCreditAvailableThreshold = 0 + self.maxProvision = 0 + self.maxProvisionPeriod = 0 + + def getValues(self): + return [self.logicalName, + [self.paymentMode, self.accountStatus], + self.currentCreditInUse, + self.currentCreditStatus, + self.availableCredit, + self.amountToClear, + self.clearanceThreshold, + self.aggregatedDebt, + self.creditReferences, + self.chargeReferences, + self.creditChargeConfigurations, + self.tokenGatewayConfigurations, + self.accountActivationTime, + self.accountClosureTime, + self.currency, + self.lowCreditThreshold, + self.nextCreditAvailableThreshold, + self.maxProvision, + self.maxProvisionPeriod] + + def activate(self, client): + """Activate account.""" + return client.method(self, 1, 0, DataType.INT8) + + def close(self, client): + """Close account.""" + return client.method(self, 2, 0, DataType.INT8) + + def reset(self, client): + """Reset account.""" + return client.method(self, 3, 0, DataType.INT8) + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # PaymentMode, AccountStatus + if all_ or self.canRead(2): + attributes.append(2) + # CurrentCreditInUse + if all_ or self.canRead(3): + attributes.append(3) + # CurrentCreditStatus + if all_ or self.canRead(4): + attributes.append(4) + # AvailableCredit + if all_ or self.canRead(5): + attributes.append(5) + # AmountToClear + if all_ or self.canRead(6): + attributes.append(6) + # ClearanceThreshold + if all_ or self.canRead(7): + attributes.append(7) + # AggregatedDebt + if all_ or self.canRead(8): + attributes.append(8) + # CreditReferences + if all_ or self.canRead(9): + attributes.append(9) + # ChargeReferences + if all_ or self.canRead(10): + attributes.append(10) + # CreditChargeConfigurations + if all_ or self.canRead(11): + attributes.append(11) + # TokenGatewayConfigurations + if all_ or self.canRead(12): + attributes.append(12) + # AccountActivationTime + if all_ or self.canRead(13): + attributes.append(13) + # AccountClosureTime + if all_ or self.canRead(14): + attributes.append(14) + # Currency + if all_ or self.canRead(15): + attributes.append(15) + # LowCreditThreshold + if all_ or self.canRead(16): + attributes.append(16) + # NextCreditAvailableThreshold + if all_ or self.canRead(17): + attributes.append(17) + # MaxProvision + if all_ or self.canRead(18): + attributes.append(18) + # MaxProvisionPeriod + if all_ or self.canRead(19): + attributes.append(19) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 19 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 3 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.STRUCTURE + elif index == 3: + ret = DataType.UINT8 + elif index == 4: + ret = DataType.BITSTRING + elif index == 5: + ret = DataType.INT32 + elif index == 6: + ret = DataType.INT32 + elif index == 7: + ret = DataType.INT32 + elif index == 8: + ret = DataType.INT32 + elif index == 9: + ret = DataType.ARRAY + elif index == 10: + ret = DataType.ARRAY + elif index == 11: + ret = DataType.ARRAY + elif index == 12: + ret = DataType.ARRAY + elif index == 13: + ret = DataType.OCTET_STRING + elif index == 14: + ret = DataType.OCTET_STRING + elif index == 15: + ret = DataType.STRUCTURE + elif index == 16: + ret = DataType.INT32 + elif index == 17: + ret = DataType.INT32 + elif index == 18: + ret = DataType.UINT16 + elif index == 19: + ret = DataType.INT32 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(self.paymentMode) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(self.accountStatus) + ret = bb.array() + elif e.index == 3: + ret = self.currentCreditInUse + elif e.index == 4: + ret = GXBitString.toBitString(self.currentCreditStatus, 8) + elif e.index == 5: + ret = self.availableCredit + elif e.index == 6: + ret = self.amountToClear + elif e.index == 7: + ret = self.clearanceThreshold + elif e.index == 8: + ret = self.aggregatedDebt + elif e.index == 9: + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.creditReferences: + bb.setUInt8(0) + else: + _GXCommon.setObjectCount(len(self.creditReferences), bb) + for it in self.creditReferences: + bb.setUInt8(DataType.OCTET_STRING) + bb.setUInt8(6) + bb.set(_GXCommon.logicalNameToBytes(it)) + ret = bb.array() + elif e.index == 10: + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.chargeReferences: + bb.setUInt8(0) + else: + _GXCommon.setObjectCount(len(self.chargeReferences), bb) + for it in self.chargeReferences: + bb.setUInt8(DataType.OCTET_STRING) + bb.setUInt8(6) + bb.set(_GXCommon.logicalNameToBytes(it)) + ret = bb.array() + elif e.index == 11: + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.creditChargeConfigurations: + bb.setUInt8(0) + else: + _GXCommon.setObjectCount(len(self.creditChargeConfigurations), bb) + for it in self.creditChargeConfigurations: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + bb.setUInt8(DataType.OCTET_STRING) + bb.setUInt8(6) + bb.set(_GXCommon.logicalNameToBytes(it.creditReference)) + bb.setUInt8(DataType.OCTET_STRING) + bb.setUInt8(6) + bb.set(_GXCommon.logicalNameToBytes(it.chargeReference)) + _GXCommon.setData(settings, bb, DataType.BITSTRING, GXBitString.toBitString(it.collectionConfiguration, 3)) + ret = bb.array() + elif e.index == 12: + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.tokenGatewayConfigurations: + bb.setUInt8(0) + else: + _GXCommon.setObjectCount(len(self.tokenGatewayConfigurations), bb) + for it in self.tokenGatewayConfigurations: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + bb.setUInt8(DataType.OCTET_STRING) + bb.setUInt8(6) + bb.set(_GXCommon.logicalNameToBytes(it.creditReference)) + bb.setUInt8(DataType.UINT8) + bb.setUInt8(it.tokenProportion) + ret = bb.array() + elif e.index == 13: + ret = self.accountActivationTime + elif e.index == 14: + ret = self.accountClosureTime + elif e.index == 15: + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + _GXCommon.setData(settings, bb, DataType.STRING_UTF8, self.currency.name) + _GXCommon.setData(settings, bb, DataType.INT8, self.currency.scale) + _GXCommon.setData(settings, bb, DataType.ENUM, self.currency.unit) + ret = bb.array() + elif e.index == 16: + ret = self.lowCreditThreshold + elif e.index == 17: + ret = self.nextCreditAvailableThreshold + elif e.index == 18: + ret = self.maxProvision + elif e.index == 19: + ret = self.maxProvisionPeriod + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.paymentMode = e.value[0] + self.accountStatus = e.value[1] + elif e.index == 3: + self.currentCreditInUse = e.value + elif e.index == 4: + self.currentCreditStatus = AccountCreditStatus(e.value.toInteger()) + elif e.index == 5: + self.availableCredit = e.value + elif e.index == 6: + self.amountToClear = e.value + elif e.index == 7: + self.clearanceThreshold = e.value + elif e.index == 8: + self.aggregatedDebt = e.value + elif e.index == 9: + self.creditReferences = [] + if e.value: + for it in e.value: + self.creditReferences.append(_GXCommon.toLogicalName(it)) + elif e.index == 10: + self.chargeReferences = [] + if e.value: + for it in e.value: + self.chargeReferences.append(_GXCommon.toLogicalName(it)) + elif e.index == 11: + #pylint: disable=bad-option-value,redefined-variable-type + self.creditChargeConfigurations = [] + if e.value: + for it in e.value: + item = GXCreditChargeConfiguration() + item.creditReference = _GXCommon.toLogicalName(it[0]) + item.chargeReference = _GXCommon.toLogicalName(it[1]) + item.collectionConfiguration = CreditCollectionConfiguration(it[2].toInteger()) + self.creditChargeConfigurations.append(item) + elif e.index == 12: + #pylint: disable=bad-option-value,redefined-variable-type + self.tokenGatewayConfigurations = [] + if e.value: + for it in e.value: + item = GXTokenGatewayConfiguration() + item.creditReference = _GXCommon.toLogicalName(it[0]) + item.tokenProportion = it[1] + self.tokenGatewayConfigurations.append(item) + elif e.index == 13: + if not e.value: + self.accountActivationTime = GXDateTime() + else: + tmp = None + if isinstance(e.value, bytearray): + tmp = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + tmp = e.value + self.accountActivationTime = tmp + elif e.index == 14: + if not e.value: + self.accountClosureTime = GXDateTime() + else: + tmp = None + if isinstance(e.value, bytearray): + tmp = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + tmp = e.value + self.accountClosureTime = tmp + elif e.index == 15: + tmp = e.value + self.currency.name = str(tmp[0]) + self.currency.scale = tmp[1] + self.currency.unit = tmp[2] + elif e.index == 16: + self.lowCreditThreshold = e.value + elif e.index == 17: + self.nextCreditAvailableThreshold = e.value + elif e.index == 18: + self.maxProvision = e.value + elif e.index == 19: + self.maxProvisionPeriod = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + @classmethod + def loadReferences(cls, reader, name, list_): + list_ = [] + if reader.isStartElement(name, True): + while reader.isStartElement("Item", True): + list_.append(reader.readElementContentAsString("Name")) + reader.readEndElement(name) + + @classmethod + def loadCreditChargeConfigurations(cls, reader, list_): + list_ = [] + if reader.isStartElement("CreditChargeConfigurations", True): + while reader.isStartElement("Item", True): + it = GXCreditChargeConfiguration() + it.creditReference = reader.readElementContentAsString("Credit") + it.chargeReference = reader.readElementContentAsString("Charge") + it.collectionConfiguration = CreditCollectionConfiguration(reader.readElementContentAsInt("Configuration")) + list_.append(it) + reader.readEndElement("CreditChargeConfigurations") + + @classmethod + def loadTokenGatewayConfigurations(cls, reader, list_): + list_ = [] + if reader.isStartElement("TokenGatewayConfigurations", True): + while reader.isStartElement("Item", True): + it = GXTokenGatewayConfiguration() + it.creditReference = reader.readElementContentAsString("Credit") + it.tokenProportion = reader.readElementContentAsInt("Token") + list_.append(it) + reader.readEndElement("TokenGatewayConfigurations") + + def load(self, reader): + self.paymentMode = reader.readElementContentAsInt("PaymentMode") + self.accountStatus = reader.readElementContentAsInt("AccountStatus") + self.currentCreditInUse = reader.readElementContentAsInt("CurrentCreditInUse") + self.currentCreditStatus = AccountCreditStatus(reader.readElementContentAsInt("CurrentCreditStatus")) + self.availableCredit = reader.readElementContentAsInt("AvailableCredit") + self.amountToClear = reader.readElementContentAsInt("AmountToClear") + self.clearanceThreshold = reader.readElementContentAsInt("ClearanceThreshold") + self.aggregatedDebt = reader.readElementContentAsInt("AggregatedDebt") + self.loadReferences(reader, "CreditReferences", self.creditReferences) + self.loadReferences(reader, "ChargeReferences", self.chargeReferences) + self.loadCreditChargeConfigurations(reader, self.creditChargeConfigurations) + self.loadTokenGatewayConfigurations(reader, self.tokenGatewayConfigurations) + self.accountActivationTime = reader.readElementContentAsDateTime("AccountActivationTime") + self.accountClosureTime = reader.readElementContentAsDateTime("AccountClosureTime") + self.currency.name = reader.readElementContentAsString("CurrencyName") + self.currency.scale = reader.readElementContentAsInt("CurrencyScale") + self.currency.unit = reader.readElementContentAsInt("CurrencyUnit") + self.lowCreditThreshold = reader.readElementContentAsInt("LowCreditThreshold") + self.nextCreditAvailableThreshold = reader.readElementContentAsInt("NextCreditAvailableThreshold") + self.maxProvision = reader.readElementContentAsInt("MaxProvision") + self.maxProvisionPeriod = reader.readElementContentAsInt("MaxProvisionPeriod") + + @classmethod + def saveReferences(cls, writer, list_, name): + if list_: + writer.writeStartElement(name) + for it in list_: + writer.writeStartElement("Item") + writer.writeElementString("Name", it) + writer.writeEndElement() + writer.writeEndElement() + + @classmethod + def saveCreditChargeConfigurations(cls, writer, list_): + if list_: + writer.writeStartElement("CreditChargeConfigurations") + for it in list_: + writer.writeStartElement("Item") + writer.writeElementString("Credit", it.creditReference) + writer.writeElementString("Charge", it.chargeReference) + writer.writeElementString("Configuration", int(it.collectionConfiguration)) + writer.writeEndElement() + writer.writeEndElement() + + @classmethod + def saveTokenGatewayConfigurations(cls, writer, list_): + if list_: + writer.writeStartElement("TokenGatewayConfigurations") + for it in list_: + writer.writeStartElement("Item") + writer.writeElementString("Credit", it.creditReference) + writer.writeElementString("Token", it.tokenProportion) + writer.writeEndElement() + writer.writeEndElement() + + def save(self, writer): + writer.writeElementString("PaymentMode", int(self.paymentMode)) + writer.writeElementString("AccountStatus", int(self.accountStatus)) + writer.writeElementString("CurrentCreditInUse", self.currentCreditInUse) + writer.writeElementString("CurrentCreditStatus", int(self.currentCreditStatus)) + writer.writeElementString("AvailableCredit", self.availableCredit) + writer.writeElementString("AmountToClear", self.amountToClear) + writer.writeElementString("ClearanceThreshold", self.clearanceThreshold) + writer.writeElementString("AggregatedDebt", self.aggregatedDebt) + self.saveReferences(writer, self.creditReferences, "CreditReferences") + self.saveReferences(writer, self.chargeReferences, "ChargeReferences") + self.saveCreditChargeConfigurations(writer, self.creditChargeConfigurations) + self.saveTokenGatewayConfigurations(writer, self.tokenGatewayConfigurations) + writer.writeElementString("AccountActivationTime", self.accountActivationTime) + writer.writeElementString("AccountClosureTime", self.accountClosureTime) + writer.writeElementString("CurrencyName", self.currency.name) + writer.writeElementString("CurrencyScale", self.currency.scale) + writer.writeElementString("CurrencyUnit", int(self.currency.unit)) + writer.writeElementString("LowCreditThreshold", self.lowCreditThreshold) + writer.writeElementString("NextCreditAvailableThreshold", self.nextCreditAvailableThreshold) + writer.writeElementString("MaxProvision", self.maxProvision) + writer.writeElementString("MaxProvisionPeriod", self.maxProvisionPeriod) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActionItem.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActionItem.py new file mode 100644 index 0000000..3bbae34 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActionItem.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSActionItem: + def __init__(self): + """ + Constructor. + """ + self.logicalName = None + self.scriptSelector = 0 + + def __str__(self): + return self.logicalName + " " + str(self.scriptSelector) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActionSchedule.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActionSchedule.py new file mode 100644 index 0000000..419b64c --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActionSchedule.py @@ -0,0 +1,222 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode, DateTimeSkips +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..GXDateTime import GXDateTime +from ..GXTime import GXTime +from ..GXDate import GXDate +from ..enums import ObjectType, DataType +from .enums import SingleActionScheduleType +from .GXDLMSScriptTable import GXDLMSScriptTable + +# pylint: disable=too-many-instance-attributes +class GXDLMSActionSchedule(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSActionSchedule + """ + + def __init__(self, ln="0.0.15.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.ACTION_SCHEDULE, ln, sn) + self.type_ = SingleActionScheduleType.SingleActionScheduleType1 + # Script to execute. + self.target = None + self.executedScriptSelector = 0 + self.executionTime = list() + + def getValues(self): + if self.target: + ln = self.target.logicalName + else: + ln = "" + return [self.logicalName, + [ln, self.executedScriptSelector], + self.type_, + self.executionTime] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # ExecutedScriptLogicalName is static and read only once. + if all_ or not self.isRead(2): + attributes.append(2) + # Type is static and read only once. + if all_ or not self.isRead(3): + attributes.append(3) + # ExecutionTime is static and read only once. + if all_ or not self.isRead(4): + attributes.append(4) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 4 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ARRAY + elif index == 3: + return DataType.ENUM + elif index == 4: + return DataType.ARRAY + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(self.target.logicalName)) + _GXCommon.setData(settings, bb, DataType.UINT16, int(self.executedScriptSelector)) + return bb.array() + if e.index == 3: + return self.type_ + if e.index == 4: + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.executionTime: + _GXCommon.setObjectCount(0, bb) + else: + _GXCommon.setObjectCount(len(self.executionTime), bb) + for it in self.executionTime: + bb.setUInt8(DataType.STRUCTURE) + # Count + bb.setUInt8(2) + # Time + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, GXTime(it)) + # Date + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, GXDate(it)) + return bb.array() + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if e.value: + ln = _GXCommon.toLogicalName(e.value[0]) + self.target = settings.objects.findByLN(ObjectType.SCRIPT_TABLE, ln) + if not self.target: + self.target = GXDLMSScriptTable(ln) + self.executedScriptSelector = e.value[1] + else: + self.target = None + self.executedScriptSelector = 0 + elif e.index == 3: + self.type_ = e.value + elif e.index == 4: + self.executionTime = [] + if e.value: + for it in e.value: + time = GXDateTime(_GXCommon.changeType(settings, it[0], DataType.TIME)) + date = GXDateTime(_GXCommon.changeType(settings, it[1], DataType.DATE)) + tmp = GXDateTime(date) + tmp.value = tmp.value.replace(hour=time.value.hour, minute=time.value.minute, second=time.value.second) + tmp.skip = (date.skip & (DateTimeSkips.YEAR | DateTimeSkips.MONTH | DateTimeSkips.DAY | DateTimeSkips.DAY_OF_WEEK)) + tmp.skip |= (time.skip & (DateTimeSkips.HOUR | DateTimeSkips.MINUTE | DateTimeSkips.SECOND | DateTimeSkips.MILLISECOND)) + self.executionTime.append(tmp) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + ot = ObjectType(reader.readElementContentAsInt("ObjectType")) + ln = reader.readElementContentAsString("LN") + if ot != ObjectType.NONE and ln: + self.target = reader.objects.findByLN(ot, ln) + # if object is not load yet. + if not self.target: + self.target = GXDLMSScriptTable(ln) + self.executedScriptSelector = reader.readElementContentAsInt("ExecutedScriptSelector") + self.type_ = reader.readElementContentAsInt("Type") + self.executionTime = [] + if reader.isStartElement("ExecutionTime", True): + while reader.isStartElement("Time", False): + it = reader.readElementContentAsDateTime("Time") + self.executionTime.append(it) + reader.readEndElement("ExecutionTime") + + def save(self, writer): + if self.target: + writer.writeElementString("ObjectType", int(self.target.objectType)) + writer.writeElementString("LN", self.target.logicalName) + writer.writeElementString("ExecutedScriptSelector", self.executedScriptSelector) + writer.writeElementString("Type", int(self.type_)) + if self.executionTime: + writer.writeStartElement("ExecutionTime") + for it in self.executionTime: + writer.writeElementString("Time", it) + writer.writeEndElement() + + def postLoad(self, reader): + # Upload target after load. + if self.target: + t = reader.objects.findByLN(ObjectType.SCRIPT_TABLE, self.target.logicalName) + if t and self.target != t: + self.target = t diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActionSet.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActionSet.py new file mode 100644 index 0000000..acb7125 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActionSet.py @@ -0,0 +1,45 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from .GXDLMSActionItem import GXDLMSActionItem + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSActionSet: + # + # Constructor. + # + def __init__(self): + self.actionUp = GXDLMSActionItem() + self.actionDown = GXDLMSActionItem() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActivityCalendar.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActivityCalendar.py new file mode 100644 index 0000000..41edda1 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSActivityCalendar.py @@ -0,0 +1,477 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..GXDateTime import GXDateTime +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .GXDLMSSeasonProfile import GXDLMSSeasonProfile +from .GXDLMSWeekProfile import GXDLMSWeekProfile +from .GXDLMSDayProfile import GXDLMSDayProfile +from .GXDLMSDayProfileAction import GXDLMSDayProfileAction + +# pylint: disable=too-many-public-methods, too-many-instance-attributes +class GXDLMSActivityCalendar(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSActivityCalendar + """ + + def __init__(self, ln="0.0.13.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.ACTIVITY_CALENDAR, ln, sn) + self.calendarNameActive = None + self.seasonProfileActive = None + self.weekProfileTableActive = None + self.dayProfileTableActive = None + self.calendarNamePassive = None + self.seasonProfilePassive = None + self.weekProfileTablePassive = None + self.dayProfileTablePassive = None + self.time = None + self.isSec = False + + def getValues(self): + return [self.logicalName, + self.calendarNameActive, + self.seasonProfileActive, + self.weekProfileTableActive, + self.dayProfileTableActive, + self.calendarNamePassive, + self.seasonProfilePassive, + self.weekProfileTablePassive, + self.dayProfileTablePassive, + self.time] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # CalendarNameActive + if all_ or self.canRead(2): + attributes.append(2) + # SeasonProfileActive + if all_ or self.canRead(3): + attributes.append(3) + # WeekProfileTableActive + if all_ or self.canRead(4): + attributes.append(4) + # DayProfileTableActive + if all_ or self.canRead(5): + attributes.append(5) + # CalendarNamePassive + if all_ or self.canRead(6): + attributes.append(6) + # SeasonProfilePassive + if all_ or self.canRead(7): + attributes.append(7) + # WeekProfileTablePassive + if all_ or self.canRead(8): + attributes.append(8) + # DayProfileTablePassive + if all_ or self.canRead(9): + attributes.append(9) + # Time. + if all_ or self.canRead(10): + attributes.append(10) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 10 + + # + # This method copies all passive properties to the active. + # + def activatePassiveCalendar(self, client): + return client.method(self, 1, int(0), DataType.INT8) + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.OCTET_STRING + elif index == 3: + ret = DataType.ARRAY + elif index == 4: + ret = DataType.ARRAY + elif index == 5: + ret = DataType.ARRAY + elif index == 6: + ret = DataType.OCTET_STRING + elif index == 7: + ret = DataType.ARRAY + elif index == 8: + ret = DataType.ARRAY + elif index == 9: + ret = DataType.ARRAY + elif index == 10: + if self.isSec: + ret = DataType.DATETIME + else: + ret = DataType.OCTET_STRING + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + @classmethod + def __getSeasonProfile(cls, settings, target): + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if target is None: + # Add count + _GXCommon.setObjectCount(0, data) + else: + # Add count + _GXCommon.setObjectCount(len(target), data) + for it in target: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, it.name) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, it.start) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, it.weekName) + return data.array() + + @classmethod + def __getWeekProfileTable(cls, settings, target): + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if target is None: + # Add count + _GXCommon.setObjectCount(0, data) + else: + # Add count + _GXCommon.setObjectCount(len(target), data) + for it in target: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(8) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, it.name) + _GXCommon.setData(settings, data, DataType.UINT8, it.monday) + _GXCommon.setData(settings, data, DataType.UINT8, it.tuesday) + _GXCommon.setData(settings, data, DataType.UINT8, it.wednesday) + _GXCommon.setData(settings, data, DataType.UINT8, it.thursday) + _GXCommon.setData(settings, data, DataType.UINT8, it.friday) + _GXCommon.setData(settings, data, DataType.UINT8, it.saturday) + _GXCommon.setData(settings, data, DataType.UINT8, it.sunday) + return data.array() + + @classmethod + def __getDayProfileTable(cls, settings, target): + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if target is None: + # Add count + _GXCommon.setObjectCount(0, data) + else: + # Add count + _GXCommon.setObjectCount(len(target), data) + for it in target: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.UINT8, it.dayId) + data.setUInt8(DataType.ARRAY) + # Add count + _GXCommon.setObjectCount(len(it.daySchedules), data) + for action in it.daySchedules: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, action.startTime) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(action.scriptLogicalName)) + _GXCommon.setData(settings, data, DataType.UINT16, int(action.scriptSelector)) + return data.array() + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + #pylint: disable=bad-option-value,redefined-variable-type + if not self.calendarNameActive: + ret = None + elif self.isSec: + ret = GXByteBuffer.hexToBytes(self.calendarNameActive) + else: + ret = self.calendarNameActive.encode("utf-8") + elif e.index == 3: + ret = self.__getSeasonProfile(settings, self.seasonProfileActive) + elif e.index == 4: + ret = self.__getWeekProfileTable(settings, self.weekProfileTableActive) + elif e.index == 5: + ret = self.__getDayProfileTable(settings, self.dayProfileTableActive) + elif e.index == 6: + if self.calendarNamePassive is None: + ret = None + elif self.isSec: + ret = GXByteBuffer.hexToBytes(self.calendarNamePassive) + else: + ret = self.calendarNamePassive.encode() + elif e.index == 7: + ret = self.__getSeasonProfile(settings, self.seasonProfilePassive) + elif e.index == 8: + ret = self.__getWeekProfileTable(settings, self.weekProfileTablePassive) + elif e.index == 9: + ret = self.__getDayProfileTable(settings, self.dayProfileTablePassive) + elif e.index == 10: + ret = self.time + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + @classmethod + def __setSeasonProfile(cls, settings, value): + items = list() + if value: + for item in value: + it = GXDLMSSeasonProfile() + it.name = item[0] + it.start = _GXCommon.changeType(settings, item[1], DataType.DATETIME) + it.weekName = item[2] + items.append(it) + return items + + @classmethod + def __setWeekProfileTable(cls, value): + items = list() + if value: + for item in value: + it = GXDLMSWeekProfile() + it.name = item[0] + it.monday = item[1] + it.tuesday = item[2] + it.wednesday = item[3] + it.thursday = item[4] + it.friday = item[5] + it.saturday = item[6] + it.sunday = item[7] + items.append(it) + return items + + @classmethod + def __setDayProfileTable(cls, settings, value): + items = list() + if value: + for item in value: + it = GXDLMSDayProfile() + it.dayId = item[0] + it.daySchedules = list() + for it2 in item[1]: + ac = GXDLMSDayProfileAction() + ac.startTime = _GXCommon.changeType(settings, it2[0], DataType.TIME) + ac.scriptLogicalName = _GXCommon.toLogicalName(it2[1]) + ac.scriptSelector = it2[2] + it.daySchedules.append(ac) + items.append(it) + return items + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if self.isSec or not GXByteBuffer.isAsciiString(e.value): + self.calendarNameActive = GXByteBuffer.hex(e.value) + else: + self.calendarNameActive = e.value.decode("utf-8").strip('\x00') + elif e.index == 3: + self.seasonProfileActive = self.__setSeasonProfile(settings, e.value) + elif e.index == 4: + self.weekProfileTableActive = self.__setWeekProfileTable(e.value) + elif e.index == 5: + self.dayProfileTableActive = self.__setDayProfileTable(settings, e.value) + elif e.index == 6: + if self.isSec or not GXByteBuffer.isAsciiString(e.value): + self.calendarNamePassive = GXByteBuffer.hex(e.value) + else: + self.calendarNamePassive = e.value.decode("utf-8").strip('\x00') + elif e.index == 7: + self.seasonProfilePassive = self.__setSeasonProfile(settings, e.value) + elif e.index == 8: + self.weekProfileTablePassive = self.__setWeekProfileTable(e.value) + elif e.index == 9: + self.dayProfileTablePassive = self.__setDayProfileTable(settings, e.value) + elif e.index == 10: + if isinstance(e.value, GXDateTime): + self.time = e.value + else: + self.time = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + @classmethod + def loadSeasonProfile(cls, reader, name): + list_ = list() + if reader.isStartElement(name, True): + while reader.isStartElement("Item", True): + it = GXDLMSSeasonProfile() + it.name = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Name")) + it.start = reader.readElementContentAsDateTime("Start") + it.weekName = GXByteBuffer.hexToBytes(reader.readElementContentAsString("WeekName")) + list_.append(it) + reader.readEndElement(name) + return list_ + + @classmethod + def loadWeekProfileTable(cls, reader, name): + list_ = list() + if reader.isStartElement(name, True): + while reader.isStartElement("Item", True): + it = GXDLMSWeekProfile() + it.name = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Name")) + it.monday = reader.readElementContentAsInt("Monday") + it.tuesday = reader.readElementContentAsInt("Tuesday") + it.wednesday = reader.readElementContentAsInt("Wednesday") + it.thursday = reader.readElementContentAsInt("Thursday") + it.friday = reader.readElementContentAsInt("Friday") + it.saturday = reader.readElementContentAsInt("Saturday") + it.sunday = reader.readElementContentAsInt("Sunday") + list_.append(it) + reader.readEndElement(name) + return list_ + + @classmethod + def loadDayProfileTable(cls, reader, name): + list_ = list() + if reader.isStartElement(name, True): + while reader.isStartElement("Item", True): + it = GXDLMSDayProfile() + it.dayId = reader.readElementContentAsInt("DayId") + list_.append(it) + it.daySchedules = list() + if reader.isStartElement("Actions", True): + while reader.isStartElement("Action", True): + d = GXDLMSDayProfileAction() + it.daySchedules.append(d) + d.startTime = reader.readElementContentAsTime("Start") + d.scriptLogicalName = reader.readElementContentAsString("LN") + d.scriptSelector = reader.readElementContentAsInt("Selector") + reader.readEndElement("Actions") + reader.readEndElement(name) + return list_ + + def load(self, reader): + self.calendarNameActive = reader.readElementContentAsString("CalendarNameActive") + self.seasonProfileActive = self.loadSeasonProfile(reader, "SeasonProfileActive") + self.weekProfileTableActive = self.loadWeekProfileTable(reader, "WeekProfileTableActive") + self.dayProfileTableActive = self.loadDayProfileTable(reader, "DayProfileTableActive") + self.calendarNamePassive = reader.readElementContentAsString("CalendarNamePassive") + self.seasonProfilePassive = self.loadSeasonProfile(reader, "SeasonProfilePassive") + self.weekProfileTablePassive = self.loadWeekProfileTable(reader, "WeekProfileTablePassive") + self.dayProfileTablePassive = self.loadDayProfileTable(reader, "DayProfileTablePassive") + self.time = reader.readElementContentAsDateTime("Time") + + @classmethod + def saveSeasonProfile(cls, writer, list_, name): + if list_: + writer.writeStartElement(name) + for it in list_: + writer.writeStartElement("Item") + str_ = it.name + if isinstance(str_, (bytearray, bytes)): + str_ = GXByteBuffer.hex(str_) + writer.writeElementString("Name", str_) + writer.writeElementString("Start", it.start) + writer.writeElementString("WeekName", GXByteBuffer.hex(it.weekName)) + writer.writeEndElement() + writer.writeEndElement() + + @classmethod + def saveWeekProfileTable(cls, writer, list_, name): + if list_: + writer.writeStartElement(name) + for it in list_: + writer.writeStartElement("Item") + writer.writeElementString("Name", GXByteBuffer.hex(it.name)) + writer.writeElementString("Monday", it.monday) + writer.writeElementString("Tuesday", it.tuesday) + writer.writeElementString("Wednesday", it.wednesday) + writer.writeElementString("Thursday", it.thursday) + writer.writeElementString("Friday", it.friday) + writer.writeElementString("Saturday", it.saturday) + writer.writeElementString("Sunday", it.sunday) + writer.writeEndElement() + writer.writeEndElement() + + @classmethod + def saveDayProfileTable(cls, writer, list_, name): + if list_: + writer.writeStartElement(name) + for it in list_: + writer.writeStartElement("Item") + writer.writeElementString("DayId", it.dayId) + writer.writeStartElement("Actions") + for d in it.daySchedules: + writer.writeStartElement("Action") + writer.writeElementString("Start", d.startTime) + writer.writeElementString("LN", d.scriptLogicalName) + writer.writeElementString("Selector", d.scriptSelector) + writer.writeEndElement() + writer.writeEndElement() + writer.writeEndElement() + writer.writeEndElement() + + def save(self, writer): + writer.writeElementString("CalendarNameActive", self.calendarNameActive) + self.saveSeasonProfile(writer, self.seasonProfileActive, "SeasonProfileActive") + self.saveWeekProfileTable(writer, self.weekProfileTableActive, "WeekProfileTableActive") + self.saveDayProfileTable(writer, self.dayProfileTableActive, "DayProfileTableActive") + writer.writeElementString("CalendarNamePassive", self.calendarNamePassive) + self.saveSeasonProfile(writer, self.seasonProfilePassive, "SeasonProfilePassive") + self.saveWeekProfileTable(writer, self.weekProfileTablePassive, "WeekProfileTablePassive") + self.saveDayProfileTable(writer, self.dayProfileTablePassive, "DayProfileTablePassive") + writer.writeElementString("Time", self.time) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAssociationLogicalName.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAssociationLogicalName.py new file mode 100644 index 0000000..3a5d0d1 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAssociationLogicalName.py @@ -0,0 +1,671 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import DataType, ObjectType, Authentication +from .enums import AssociationStatus +from .GXDLMSObjectCollection import GXDLMSObjectCollection +from .GXApplicationContextName import GXApplicationContextName +from .GXxDLMSContextType import GXxDLMSContextType +from .GXAuthenticationMechanismName import GXAuthenticationMechanismName +from ..ValueEventArgs import ValueEventArgs +from ..GXSecure import GXSecure +from ..enums.Conformance import Conformance +from ..GXBitString import GXBitString + +# pylint: disable=too-many-instance-attributes +class GXDLMSAssociationLogicalName(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAssociationLogicalName + """ + + def __init__(self, ln="0.0.40.0.0.255"): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.ASSOCIATION_LOGICAL_NAME, ln) + self.objectList = GXDLMSObjectCollection(self) + self.applicationContextName = GXApplicationContextName() + self.xDLMSContextInfo = GXxDLMSContextType() + self.authenticationMechanismName = GXAuthenticationMechanismName() + self.userList = list() + self.version = 2 + self.associationStatus = AssociationStatus.NON_ASSOCIATED + self.clientSAP = None + self.serverSAP = None + self.secret = None + self.securitySetupReference = None + self.userList = list() + self.currentUser = None + + # + # Updates secret. + # + # @param client + # DLMS client. + # Action bytes. + # + def updateSecret(self, client): + if self.authenticationMechanismName.mechanismId == Authentication.NONE: + raise ValueError("Invalid authentication level in MechanismId.") + if self.authenticationMechanismName.mechanismId == Authentication.HIGH_GMAC: + raise ValueError("HighGMAC secret is updated using Security setup.") + if self.authenticationMechanismName.mechanismId == Authentication.LOW: + return client.write(self, 7) + # Action is used to update High authentication password. + return client.method(self, 2, self.secret, DataType.OCTET_STRING) + + # + # Add user to user list. + # + # @param client + # DLMS client. + # @param id + # User ID. + # @param name + # User name. + # Action bytes. + # + def addUser(self, client, id_, name): + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + # Add structure size. + data.setUInt8(2) + _GXCommon.setData(None, data, DataType.UINT8, id_) + _GXCommon.setData(None, data, DataType.STRING, name) + return client.method(self, 5, data.array(), DataType.STRUCTURE) + + # + # Remove user from user list. + # + # @param client + # DLMS client. + # @param id + # User ID. + # @param name + # User name. + # Action bytes. + # + def removeUser(self, client, id_, name): + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + # Add structure size. + data.setUInt8(2) + _GXCommon.setData(None, data, DataType.UINT8, id_) + _GXCommon.setData(None, data, DataType.STRING, name) + return client.method(self, 6, data.array(), DataType.STRUCTURE) + + def getValues(self): + return [self.logicalName, + self.objectList, + [self.clientSAP, self.serverSAP], + self.applicationContextName, + self.xDLMSContextInfo, + self.authenticationMechanismName, + self.secret, + self.associationStatus, + self.securitySetupReference, + self.userList, + self.currentUser] + + def invoke(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + # Check reply_to_HLS_authentication + if e.index == 1: + serverChallenge = None + clientChallenge = None + ic = 0 + readSecret = [] + accept = True + if settings.authentication == Authentication.HIGH_ECDSA: + raise ValueError("ECDSA is not implemented.") + if settings.authentication == Authentication.HIGH_GMAC: + readSecret = settings.sourceSystemTitle + bb = GXByteBuffer(int(e.parameters)) + bb.getUInt8() + ic = bb.getUInt32() + else: + readSecret = self.secret + serverChallenge = GXSecure.secure(settings, settings.cipher, ic, settings.stoCChallenge, readSecret) + clientChallenge = int(e.parameters) + accept = serverChallenge == clientChallenge + if accept: + if settings.authentication == Authentication.HIGH_GMAC or settings.authentication == Authentication.HIGH_ECDSA: + readSecret = settings.cipher.getSystemTitle() + ic = settings.cipher.invocationCounter + else: + readSecret = self.secret + tmp = GXSecure.secure(settings, settings.cipher, ic, settings.getCtoSChallenge(), readSecret) + self.associationStatus = AssociationStatus.ASSOCIATED + return tmp + self.associationStatus = AssociationStatus.NON_ASSOCIATED + elif e.index == 2: + if not e.parameters: + e.error = ErrorCode.READ_WRITE_DENIED + else: + self.secret = e.parameters + elif e.index == 5: + if not e.parameters: + e.error = ErrorCode.READ_WRITE_DENIED + else: + self.userList.append((e.parameters[0], str(e.parameters[1]))) + elif e.index == 6: + tmp = e.parameters + if not tmp: + e.error = ErrorCode.READ_WRITE_DENIED + else: + id_ = tmp[0] + name = str(tmp[1]) + for k, v in self.userList: + if k == id_ and v == name: + self.userList.remove(k) + break + else: + e.error = ErrorCode.READ_WRITE_DENIED + return None + + def getAttributeIndexToRead(self, all_): + attributes = list() + if all_ or not self.logicalName: + attributes.append(1) + if all_ or not self.isRead(2): + attributes.append(2) + if all_ or not self.isRead(3): + attributes.append(3) + if all_ or not self.isRead(4): + attributes.append(4) + if all_ or not self.isRead(5): + attributes.append(5) + if all_ or not self.isRead(6): + attributes.append(6) + if all_ or not self.isRead(7): + attributes.append(7) + if all_ or not self.isRead(8): + attributes.append(8) + if self.version > 0 and (all_ or not self.isRead(9)): + attributes.append(9) + if self.version > 1: + if all_ or not self.isRead(10): + attributes.append(10) + if all_ or not self.isRead(11): + attributes.append(11) + return attributes + + def getAttributeCount(self): + if self.version > 1: + return 11 + if self.version > 0: + return 9 + return 8 + + def getMethodCount(self): + if self.version > 1: + return 6 + return 4 + + def getObjects(self, settings, e): + data = GXByteBuffer() + if settings.index == 0: + settings.setCount(len(self.objectList)) + data.setUInt8(DataType.ARRAY) + _GXCommon.setObjectCount(len(self.objectList), data) + pos = 0 + for it in self.objectList: + pos += 1 + if not pos <= settings.index: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(4) + _GXCommon.setData(settings, data, DataType.UINT16, it.objectType) + _GXCommon.setData(settings, data, DataType.UINT8, it.version) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(it.logicalName)) + self.__getAccessRights(settings, it, e.server, data) + settings.index = settings.index + 1 + if settings.isServer: + if not e.isSkipMaxPduSize and len(data) >= settings.maxPduSize: + break + return data + + def __getAccessRights(self, settings, item, server, data): + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + if server is None: + data.setUInt8(DataType.ARRAY) + data.setUInt8(0) + data.setUInt8(DataType.ARRAY) + data.setUInt8(0) + else: + data.setUInt8(DataType.ARRAY) + cnt = item.getAttributeCount() + data.setUInt8(cnt) + e = ValueEventArgs(server, item, 0, 0, None) + pos = 0 + while pos != cnt: + e.index = pos + 1 + m = server.onGetAttributeAccess(e) + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.INT8, pos + 1) + _GXCommon.setData(settings, data, DataType.ENUM, m) + _GXCommon.setData(settings, data, DataType.NONE, None) + pos += 1 + data.setUInt8(DataType.ARRAY) + cnt = item.getMethodCount() + data.setUInt8(cnt) + pos = 0 + while pos != cnt: + e.index = pos + 1 + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.INT8, pos + 1) + m = server.onGetMethodAccess(e) + if self.version == 0: + _GXCommon.setData(settings, data, DataType.BOOLEAN, m != 0) + else: + _GXCommon.setData(settings, data, DataType.ENUM, m) + pos += 1 + + @classmethod + def updateAccessRights(cls, obj, buff): + if buff: + for attributeAccess in buff[0]: + id_ = attributeAccess[0] + tmp = attributeAccess[1] + obj.setAccess(id_, tmp) + for methodAccess in buff[1]: + id_ = methodAccess[0] + tmp = 0 + if isinstance(methodAccess[1], bool): + if methodAccess[1]: + tmp = 1 + else: + tmp = 0 + else: + tmp = methodAccess[1] + obj.setMethodAccess(id_, tmp) + + def getUserList(self, settings): + data = GXByteBuffer() + if settings.index == 0: + settings.setCount(len(self.userList)) + data.setUInt8(DataType.ARRAY) + _GXCommon.setObjectCount(len(self.userList), data) + pos = 0 + for k, v in self.userList: + pos += 1 + if not pos <= settings.index: + settings.index = settings.index + 1 + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.UINT8, k) + _GXCommon.setData(settings, data, DataType.STRING, v) + return data + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ARRAY + elif index == 3: + ret = DataType.STRUCTURE + elif index == 4: + ret = DataType.STRUCTURE + elif index == 5: + ret = DataType.STRUCTURE + elif index == 6: + ret = DataType.STRUCTURE + elif index == 7: + ret = DataType.OCTET_STRING + elif index == 8: + ret = DataType.ENUM + elif self.version > 0 and index == 9: + ret = DataType.OCTET_STRING + elif self.version > 1: + if index == 10: + ret = DataType.ARRAY + if index == 11: + ret = DataType.STRUCTURE + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.getObjects(settings, e) + elif e.index == 3: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + data.setUInt8(DataType.INT8) + data.setUInt8(self.clientSAP) + data.setUInt8(DataType.UINT16) + data.setUInt16(self.serverSAP) + ret = data + elif e.index == 4: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(0x7) + _GXCommon.setData(settings, data, DataType.UINT8, self.applicationContextName.jointIsoCtt) + _GXCommon.setData(settings, data, DataType.UINT8, self.applicationContextName.country) + _GXCommon.setData(settings, data, DataType.UINT16, self.applicationContextName.countryName) + _GXCommon.setData(settings, data, DataType.UINT8, self.applicationContextName.identifiedOrganization) + _GXCommon.setData(settings, data, DataType.UINT8, self.applicationContextName.dlmsUA) + _GXCommon.setData(settings, data, DataType.UINT8, self.applicationContextName.applicationContext) + _GXCommon.setData(settings, data, DataType.UINT8, self.applicationContextName.contextId) + ret = data + elif e.index == 5: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(6) + _GXCommon.setData(settings, data, DataType.BITSTRING, GXBitString.toBitString(self.xDLMSContextInfo.conformance, 24)) + _GXCommon.setData(settings, data, DataType.UINT16, self.xDLMSContextInfo.maxReceivePduSize) + _GXCommon.setData(settings, data, DataType.UINT16, self.xDLMSContextInfo.maxSendPduSize) + _GXCommon.setData(settings, data, DataType.UINT8, self.xDLMSContextInfo.dlmsVersionNumber) + _GXCommon.setData(settings, data, DataType.INT8, self.xDLMSContextInfo.qualityOfService) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, self.xDLMSContextInfo.cypheringInfo) + ret = data + elif e.index == 6: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(0x7) + _GXCommon.setData(settings, data, DataType.UINT8, self.authenticationMechanismName.jointIsoCtt) + _GXCommon.setData(settings, data, DataType.UINT8, self.authenticationMechanismName.country) + _GXCommon.setData(settings, data, DataType.UINT16, self.authenticationMechanismName.countryName) + _GXCommon.setData(settings, data, DataType.UINT8, self.authenticationMechanismName.identifiedOrganization) + _GXCommon.setData(settings, data, DataType.UINT8, self.authenticationMechanismName.dlmsUA) + _GXCommon.setData(settings, data, DataType.UINT8, self.authenticationMechanismName.authenticationMechanismName) + _GXCommon.setData(settings, data, DataType.UINT8, self.authenticationMechanismName.mechanismId) + ret = data + elif e.index == 7: + ret = self.secret + elif e.index == 8: + ret = self.associationStatus + elif e.index == 9: + ret = _GXCommon.logicalNameToBytes(self.securitySetupReference) + elif e.index == 10: + ret = self.getUserList(settings) + elif e.index == 11: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + if self.currentUser is None: + _GXCommon.setData(settings, data, DataType.UINT8, 0) + _GXCommon.setData(settings, data, DataType.STRING, None) + else: + _GXCommon.setData(settings, data, DataType.UINT8, self.currentUser[0]) + _GXCommon.setData(settings, data, DataType.STRING, self.currentUser[1]) + ret = data + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + @classmethod + def updateObjectList(cls, settings, target, value): + #pylint: disable=import-outside-toplevel,unidiomatic-typecheck + from .._GXObjectFactory import _GXObjectFactory + target.clear() + if value: + for item in value: + type_ = item[0] + version = item[1] + ln = _GXCommon.toLogicalName(item[2]) + obj = settings.objects.findByLN(type_, ln) + if obj is None: + obj = _GXObjectFactory.createObject(type_) + obj.logicalName = ln + obj.version = version + if type(obj) != GXDLMSObject: + cls.updateAccessRights(obj, item[3]) + target.append(obj) + + def updateApplicationContextName(self, value): + if isinstance(value, bytearray): + buff = GXByteBuffer(value) + if buff.getUInt8(0) == 0x60: + self.applicationContextName.jointIsoCtt = 0 + self.applicationContextName.country = 0 + self.applicationContextName.countryName = 0 + buff.position = buff.position + 3 + self.applicationContextName.identifiedOrganization = buff.getUInt8() + self.applicationContextName.dlmsUA = buff.getUInt8() + self.applicationContextName.applicationContext = buff.getUInt8() + self.applicationContextName.contextId = buff.getUInt8() + else: + if buff.getUInt8() != 2 and buff.getUInt8() != 7: + raise ValueError() + if buff.getUInt8() != 0x11: + raise ValueError() + self.applicationContextName.jointIsoCtt = buff.getUInt8() + if buff.getUInt8() != 0x11: + raise ValueError() + self.applicationContextName.country = buff.getUInt8() + if buff.getUInt8() != 0x12: + raise ValueError() + self.applicationContextName.countryName = buff.getUInt16() + if buff.getUInt8() != 0x11: + raise ValueError() + self.applicationContextName.identifiedOrganization = buff.getUInt8() + if buff.getUInt8() != 0x11: + raise ValueError() + self.applicationContextName.dlmsUA = buff.getUInt8() + if buff.getUInt8() != 0x11: + raise ValueError() + self.applicationContextName.applicationContext = buff.getUInt8() + if buff.getUInt8() != 0x11: + raise ValueError() + self.applicationContextName.contextId = buff.getUInt8() + else: + if value: + self.applicationContextName.jointIsoCtt = value[0] + self.applicationContextName.country = value[1] + self.applicationContextName.countryName = value[2] + self.applicationContextName.identifiedOrganization = value[3] + self.applicationContextName.dlmsUA = value[4] + self.applicationContextName.applicationContext = value[5] + self.applicationContextName.contextId = value[6] + + def updateAuthenticationMechanismName(self, value): + if value: + if isinstance(value, bytearray): + buff = GXByteBuffer(value) + if buff.getUInt8(0) == 0x60: + self.authenticationMechanismName.jointIsoCtt = 0 + self.authenticationMechanismName.country = 0 + self.authenticationMechanismName.countryName = 0 + buff.position = buff.position + 3 + self.authenticationMechanismName.identifiedOrganization = buff.getUInt8() + self.authenticationMechanismName.dlmsUA = buff.getUInt8() + self.authenticationMechanismName.authenticationMechanismName = buff.getUInt8() + self.authenticationMechanismName.mechanismId = buff.getUInt8() + else: + if buff.getUInt8() != 2 and buff.getUInt8() != 7: + raise ValueError() + if buff.getUInt8() != 0x11: + raise ValueError() + self.authenticationMechanismName.jointIsoCtt = buff.getUInt8() + if buff.getUInt8() != 0x11: + raise ValueError() + self.authenticationMechanismName.country = buff.getUInt8() + if buff.getUInt8() != 0x12: + raise ValueError() + self.authenticationMechanismName.countryName = buff.getUInt16() + if buff.getUInt8() != 0x11: + raise ValueError() + self.authenticationMechanismName.identifiedOrganization = buff.getUInt8() + if buff.getUInt8() != 0x11: + raise ValueError() + self.authenticationMechanismName.dlmsUA = buff.getUInt8() + if buff.getUInt8() != 0x11: + raise ValueError() + self.authenticationMechanismName.authenticationMechanismName = buff.getUInt8() + if buff.getUInt8() != 0x11: + raise ValueError() + self.authenticationMechanismName.mechanismId = buff.getUInt8() + else: + if value: + self.authenticationMechanismName.jointIsoCtt = value[0] + self.authenticationMechanismName.country = value[1] + self.authenticationMechanismName.countryName = value[2] + self.authenticationMechanismName.identifiedOrganization = value[3] + self.authenticationMechanismName.dlmsUA = value[4] + self.authenticationMechanismName.authenticationMechanismName = value[5] + self.authenticationMechanismName.mechanismId = value[6] + + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.updateObjectList(settings, self.objectList, e.value) + elif e.index == 3: + if e.value: + self.clientSAP = e.value[0] + self.serverSAP = e.value[1] + elif e.index == 4: + self.updateApplicationContextName(e.value) + elif e.index == 5: + if e.value: + self.xDLMSContextInfo.conformance = Conformance(e.value[0].toInteger()) + self.xDLMSContextInfo.maxReceivePduSize = e.value[1] + self.xDLMSContextInfo.maxSendPduSize = e.value[2] + self.xDLMSContextInfo.dlmsVersionNumber = e.value[3] + self.xDLMSContextInfo.qualityOfService = e.value[4] + self.xDLMSContextInfo.cypheringInfo = e.value[5] + elif e.index == 6: + self.updateAuthenticationMechanismName(e.value) + elif e.index == 7: + self.secret = e.value + elif e.index == 8: + #pylint: disable=bad-option-value,redefined-variable-type + if e.value is None: + self.associationStatus = AssociationStatus.NON_ASSOCIATED + else: + self.associationStatus = e.value + elif e.index == 9: + self.securitySetupReference = _GXCommon.toLogicalName(e.value) + elif e.index == 10: + self.userList = [] + if e.value: + for tmp in e.value: + item = tmp + self.userList.append((int(item[0]), str(item[1]))) + elif e.index == 11: + if e.value: + tmp = e.value + self.currentUser = (tmp[0], str(tmp[1])) + else: + self.currentUser = None + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.clientSAP = reader.readElementContentAsInt("ClientSAP") + self.serverSAP = reader.readElementContentAsInt("ServerSAP") + if reader.isStartElement("ApplicationContextName", True): + self.applicationContextName.jointIsoCtt = reader.readElementContentAsInt("JointIsoCtt") + self.applicationContextName.country = reader.readElementContentAsInt("Country") + self.applicationContextName.countryName = reader.readElementContentAsInt("CountryName") + self.applicationContextName.identifiedOrganization = reader.readElementContentAsInt("IdentifiedOrganization") + self.applicationContextName.dlmsUA = reader.readElementContentAsInt("DlmsUA") + self.applicationContextName.applicationContext = reader.readElementContentAsInt("ApplicationContext") + self.applicationContextName.contextId = reader.readElementContentAsInt("ContextId") + reader.readEndElement("ApplicationContextName") + if reader.isStartElement("XDLMSContextInfo", True): + self.xDLMSContextInfo.conformance = Conformance(reader.readElementContentAsInt("Conformance")) + self.xDLMSContextInfo.maxReceivePduSize = reader.readElementContentAsInt("MaxReceivePduSize") + self.xDLMSContextInfo.maxSendPduSize = reader.readElementContentAsInt("MaxSendPduSize") + self.xDLMSContextInfo.dlmsVersionNumber = reader.readElementContentAsInt("DlmsVersionNumber") + self.xDLMSContextInfo.qualityOfService = reader.readElementContentAsInt("QualityOfService") + self.xDLMSContextInfo.cypheringInfo = GXByteBuffer.hexToBytes(reader.readElementContentAsString("CypheringInfo")) + reader.readEndElement("XDLMSContextInfo") + if reader.isStartElement("AuthenticationMechanismName", True): + self.authenticationMechanismName.JointIsoCtt = reader.readElementContentAsInt("JointIsoCtt") + self.authenticationMechanismName.country = reader.readElementContentAsInt("Country") + self.authenticationMechanismName.countryName = reader.readElementContentAsInt("CountryName") + self.authenticationMechanismName.identifiedOrganization = reader.readElementContentAsInt("IdentifiedOrganization") + self.authenticationMechanismName.dlmsUA = reader.readElementContentAsInt("DlmsUA") + self.authenticationMechanismName.authenticationMechanismName = reader.readElementContentAsInt("AuthenticationMechanismName") + self.authenticationMechanismName.mechanismId = reader.readElementContentAsInt("MechanismId") + reader.readEndElement("AuthenticationMechanismName") + str_ = reader.readElementContentAsString("Secret") + if str_ is None: + self.secret = None + else: + self.secret = GXByteBuffer.hexToBytes(str_) + self.associationStatus = reader.readElementContentAsInt("AssociationStatus") + self.securitySetupReference = reader.readElementContentAsString("SecuritySetupReference") + + def save(self, writer): + writer.writeElementString("ClientSAP", self.clientSAP) + writer.writeElementString("ServerSAP", self.serverSAP) + if self.applicationContextName: + writer.writeStartElement("ApplicationContextName") + writer.writeElementString("JointIsoCtt", self.applicationContextName.jointIsoCtt) + writer.writeElementString("Country", self.applicationContextName.country) + writer.writeElementString("CountryName", self.applicationContextName.countryName) + writer.writeElementString("IdentifiedOrganization", self.applicationContextName.identifiedOrganization) + writer.writeElementString("DlmsUA", self.applicationContextName.dlmsUA) + writer.writeElementString("ApplicationContext", self.applicationContextName.applicationContext) + writer.writeElementString("ContextId", int(self.applicationContextName.contextId)) + writer.writeEndElement() + if self.xDLMSContextInfo: + writer.writeStartElement("XDLMSContextInfo") + writer.writeElementString("Conformance", int(self.xDLMSContextInfo.conformance)) + writer.writeElementString("MaxReceivePduSize", self.xDLMSContextInfo.maxReceivePduSize) + writer.writeElementString("MaxSendPduSize", self.xDLMSContextInfo.maxSendPduSize) + writer.writeElementString("DlmsVersionNumber", self.xDLMSContextInfo.dlmsVersionNumber) + writer.writeElementString("QualityOfService", self.xDLMSContextInfo.qualityOfService) + writer.writeElementString("CypheringInfo", GXByteBuffer.hex(self.xDLMSContextInfo.cypheringInfo)) + writer.writeEndElement() + if self.authenticationMechanismName: + writer.writeStartElement("AuthenticationMechanismName") + writer.writeElementString("JointIsoCtt", self.authenticationMechanismName.jointIsoCtt) + writer.writeElementString("Country", self.authenticationMechanismName.country) + writer.writeElementString("CountryName", self.authenticationMechanismName.countryName) + writer.writeElementString("IdentifiedOrganization", self.authenticationMechanismName.identifiedOrganization) + writer.writeElementString("DlmsUA", self.authenticationMechanismName.dlmsUA) + writer.writeElementString("AuthenticationMechanismName", self.authenticationMechanismName.authenticationMechanismName) + writer.writeElementString("MechanismId", int(self.authenticationMechanismName.mechanismId)) + writer.writeEndElement() + writer.writeElementString("Secret", GXByteBuffer.hex(self.secret)) + writer.writeElementString("AssociationStatus", int(self.associationStatus)) + writer.writeElementString("SecuritySetupReference", self.securitySetupReference) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAssociationShortName.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAssociationShortName.py new file mode 100644 index 0000000..7ec238f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAssociationShortName.py @@ -0,0 +1,267 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType, AccessMode, MethodAccessMode, Authentication +from .GXDLMSObjectCollection import GXDLMSObjectCollection +from ..GXSecure import GXSecure +from ..ConnectionState import ConnectionState + +# pylint: disable=too-many-instance-attributes +class GXDLMSAssociationShortName(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAssociationShortName + """ + + def __init__(self, ln="0.0.40.0.0.255", sn=0xFA00): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.ASSOCIATION_SHORT_NAME, ln, sn) + self.secret = bytearray() + self.objectList = GXDLMSObjectCollection(self) + self.version = 2 + self.securitySetupReference = None + + def getValues(self): + return [self.logicalName, + self.objectList, + None, + self.securitySetupReference] + + # + # Invokes method. + # @param index Method index. + # + def invoke(self, settings, e): + # Check reply_to_HLS_authentication + if e.index == 8: + serverChallenge = None + clientChallenge = None + ic = 0 + readSecret = [] + accept = False + if settings.authentication == Authentication.HIGH_ECDSA: + accept = False + else: + if settings.authentication == Authentication.HIGH_GMAC: + readSecret = settings.sourceSystemTitle + bb = GXByteBuffer(e.parameters) + bb.getUInt8() + ic = bb.getUInt32() + else: + readSecret = self.secret + serverChallenge = GXSecure.secure(settings, settings.cipher, ic, settings.getStoCChallenge(), readSecret) + clientChallenge = int(e.parameters) + accept = serverChallenge == clientChallenge + if accept: + if settings.authentication == Authentication.HIGH_GMAC: + readSecret = settings.cipher.getSystemTitle() + ic = settings.cipher.invocationCounter + else: + readSecret = self.secret + settings.setConnected(settings.connected | ConnectionState.DLMS) + return GXSecure.secure(settings, settings.cipher, ic, settings.getCtoSChallenge(), readSecret) + else: + settings.setConnected(settings.connected & ~ConnectionState.DLMS) + e.error = ErrorCode.READ_WRITE_DENIED + return None + + def getAttributeIndexToRead(self, all_): + attributes = list() + if all_ or not self.logicalName: + attributes.append(1) + if all_ or not self.isRead(2): + attributes.append(2) + if self.version > 1: + if all_ or not self.isRead(3): + attributes.append(3) + if all_ or not self.isRead(4): + attributes.append(4) + return attributes + + def getAttributeCount(self): + if self.version < 2: + return 2 + return 4 + + def getMethodCount(self): + return 8 + + @classmethod + def __getAccessRights_(cls, settings, item, data): + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.UINT16, item.shortName) + data.setUInt8(DataType.ARRAY) + data.setUInt8(len(item.attributes)) + for att in item.attributes: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.INT8, att.index) + _GXCommon.setData(settings, data, DataType.ENUM, att.access.value) + _GXCommon.setData(settings, data, DataType.NONE, None) + data.setUInt8(DataType.ARRAY) + data.setUInt8(len(item.methodAttributes)) + for it in item.methodAttributes: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.INT8, it.index) + _GXCommon.setData(settings, data, DataType.ENUM, it.getMethodAccess().value) + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ARRAY + elif index == 3: + ret = DataType.ARRAY + elif index == 4: + return DataType.OCTET_STRING + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + def getObjects(self, settings, e): + bb = GXByteBuffer() + cnt = len(self.objectList) + if settings.index == 0: + settings.setCount(cnt) + bb.setUInt8(DataType.ARRAY) + _GXCommon.setObjectCount(cnt, bb) + pos = 0 + if cnt != 0: + for it in self.objectList: + pos += 1 + if not pos <= settings.index: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(4) + _GXCommon.setData(settings, bb, DataType.INT16, it.shortName) + _GXCommon.setData(settings, bb, DataType.UINT16, it.objectType) + _GXCommon.setData(settings, bb, DataType.UINT8, 0) + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(it.logicalName)) + settings.index = settings.index + 1 + if settings.isServer: + if not e.isSkipMaxPduSize() and len(bb) >= settings.maxPduSize: + break + return bb.array() + + def getValue(self, settings, e): + ret = None + bb = GXByteBuffer() + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.getObjects(settings, e) + elif e.index == 3: + lnExists = self.objectList.findBySN(self.shortName) is not None + cnt = len(self.objectList) + if not lnExists: + cnt += 1 + bb.setUInt8(DataType.ARRAY) + _GXCommon.setObjectCount(cnt, bb) + for it in self.objectList: + self.__getAccessRights_(settings, it, bb) + if not lnExists: + self.__getAccessRights_(settings, self, bb) + ret = bb.array() + elif e.index == 4: + ret = _GXCommon.getBytes(self.securitySetupReference) + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + def updateAccessRights(self, buff): + for access in buff: + sn = access[0] + obj = self.objectList.findBySN(sn) + if obj: + for attributeAccess in access[1]: + id1 = attributeAccess[0] + mode1 = AccessMode(attributeAccess[1]) + obj.setAccess(id1, mode1) + for methodAccess in access[2]: + id2 = methodAccess[0] + mode2 = MethodAccessMode(methodAccess[1]) + obj.setMethodAccess(id2, mode2) + + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + self.objectList.clear() + if e.value: + for item in e.value: + sn = item[0] + ot = item[1] + version = item[2] + ln = _GXCommon.toLogicalName(item[3]) + obj = _GXObjectFactory.createObject(ot) + obj.logicalName = ln + obj.shortName = sn + obj.version = version + self.objectList.append(obj) + elif e.index == 3: + if e.value is None: + for it in self.objectList: + pos = 1 + while pos != it.getAttributeCount(): + it.setAccess(pos, AccessMode.NO_ACCESS) + pos += 1 + else: + self.updateAccessRights(e.value) + elif e.index == 4: + self.securitySetupReference = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + str_ = reader.readElementContentAsString("Secret") + if str_ is None: + self.secret = None + else: + self.secret = GXByteBuffer.hexToBytes(str_) + self.securitySetupReference = reader.readElementContentAsString("SecuritySetupReference") + + def save(self, writer): + writer.writeElementString("Secret", GXByteBuffer.hex(self.secret)) + writer.writeElementString("SecuritySetupReference", self.securitySetupReference) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAutoAnswer.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAutoAnswer.py new file mode 100644 index 0000000..6f7bbef --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAutoAnswer.py @@ -0,0 +1,222 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import AutoAnswerMode, AutoAnswerStatus + +# pylint: disable=too-many-instance-attributes +class GXDLMSAutoAnswer(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAutoAnswer + """ + + def __init__(self, ln="0.0.2.2.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.AUTO_ANSWER, ln, sn) + self.listeningWindow = list() + self.mode = AutoAnswerMode.NONE + self.status = AutoAnswerStatus.INACTIVE + self.numberOfCalls = 0 + self.numberOfRingsInListeningWindow = 0 + self.numberOfRingsOutListeningWindow = 0 + + def getValues(self): + return [self.logicalName, + self.mode, + self.listeningWindow, + self.status, + self.numberOfCalls, + [self.numberOfRingsInListeningWindow, + self.numberOfRingsOutListeningWindow]] + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Mode is static and read only once. + if all_ or not self.isRead(2): + attributes.append(2) + # ListeningWindow is static and read only once. + if all_ or not self.isRead(3): + attributes.append(3) + # Status is not static. + if all_ or self.canRead(4): + attributes.append(4) + # NumberOfCalls is static and read only once. + if all_ or not self.isRead(5): + attributes.append(5) + # NumberOfRingsInListeningWindow is static and read only once. + if all_ or not self.isRead(6): + attributes.append(6) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 6 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ENUM + elif index == 3: + ret = DataType.ARRAY + elif index == 4: + ret = DataType.ENUM + elif index == 5: + ret = DataType.UINT8 + elif index == 6: + ret = DataType.ARRAY + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.mode + elif e.index == 3: + cnt = len(self.listeningWindow) + buff = GXByteBuffer() + buff.setUInt8(DataType.ARRAY) + # Add count + _GXCommon.setObjectCount(cnt, buff) + if cnt != 0: + for it in self.listeningWindow: + buff.setUInt8(DataType.STRUCTURE) + # Count + buff.setUInt8(2) + # Start time + _GXCommon.setData(settings, buff, DataType.OCTET_STRING, it[0]) + # End time + _GXCommon.setData(settings, buff, DataType.OCTET_STRING, it[1]) + ret = buff.array() + elif e.index == 4: + ret = self.status + elif e.index == 5: + ret = self.numberOfCalls + elif e.index == 6: + buff = GXByteBuffer() + buff.setUInt8(DataType.STRUCTURE) + _GXCommon.setObjectCount(2, buff) + _GXCommon.setData(settings, buff, DataType.UINT8, self.numberOfRingsInListeningWindow) + _GXCommon.setData(settings, buff, DataType.UINT8, self.numberOfRingsOutListeningWindow) + ret = buff.array() + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.mode = e.value + elif e.index == 3: + self.listeningWindow = [] + if e.value: + for item in e.value: + start = _GXCommon.changeType(settings, item[0], DataType.DATETIME) + end = _GXCommon.changeType(settings, item[1], DataType.DATETIME) + self.listeningWindow.append((start, end)) + elif e.index == 4: + self.status = e.value + elif e.index == 5: + self.numberOfCalls = e.value + elif e.index == 6: + self.numberOfRingsInListeningWindow = 0 + self.numberOfRingsOutListeningWindow = 0 + if e.value: + self.numberOfRingsInListeningWindow = e.value[0] + self.numberOfRingsOutListeningWindow = e.value[1] + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.mode = reader.readElementContentAsInt("Mode") + self.listeningWindow = [] + if reader.isStartElement("ListeningWindow", True): + while reader.isStartElement("Item", True): + start = reader.readElementContentAsDateTime("Start") + end = reader.readElementContentAsDateTime("End") + self.listeningWindow.append((start, end)) + reader.readEndElement("ListeningWindow") + self.status = reader.readElementContentAsInt("Status") + self.numberOfCalls = reader.readElementContentAsInt("NumberOfCalls") + self.numberOfRingsInListeningWindow = reader.readElementContentAsInt("NumberOfRingsInListeningWindow") + self.numberOfRingsOutListeningWindow = reader.readElementContentAsInt("NumberOfRingsOutListeningWindow") + + def save(self, writer): + writer.writeElementString("Mode", int(self.mode)) + writer.writeStartElement("ListeningWindow") + if self.listeningWindow: + for k, v in self.listeningWindow: + writer.writeStartElement("Item") + writer.writeElementString("Start", k) + writer.writeElementString("End", v) + writer.writeEndElement() + writer.writeEndElement() + writer.writeElementString("Status", int(self.status)) + writer.writeElementString("NumberOfCalls", self.numberOfCalls) + writer.writeElementString("NumberOfRingsInListeningWindow", self.numberOfRingsInListeningWindow) + writer.writeElementString("NumberOfRingsOutListeningWindow", self.numberOfRingsOutListeningWindow) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAutoConnect.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAutoConnect.py new file mode 100644 index 0000000..84cb237 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSAutoConnect.py @@ -0,0 +1,240 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import AutoConnectMode + +# pylint: disable=too-many-instance-attributes +class GXDLMSAutoConnect(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAutoConnect + """ + + def __init__(self, ln="0.0.2.1.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.AUTO_CONNECT, ln, sn) + self.mode = AutoConnectMode.NO_AUTO_DIALLING + self.repetitions = 0 + self.repetitionDelay = 0 + self.callingWindow = list() + self.destinations = list() + self.version = 2 + + # + # Initiates the connection process. + # + # @param client + # DLMS client. + # Action bytes. + # + def connect(self, client): + return client.method(self.name(), self.objectType, 1, 0, DataType.INT8) + + def getValues(self): + return [self.logicalName, + self.mode, + self.repetitions, + self.repetitionDelay, + self.callingWindow, + self.destinations] + + def invoke(self, settings, e): + if e.index != 1: + e.error = ErrorCode.READ_WRITE_DENIED + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Mode + if all_ or self.canRead(2): + attributes.append(2) + # Repetitions + if all_ or self.canRead(3): + attributes.append(3) + # RepetitionDelay + if all_ or self.canRead(4): + attributes.append(4) + # CallingWindow + if all_ or self.canRead(5): + attributes.append(5) + # Destinations + if all_ or self.canRead(6): + attributes.append(6) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 6 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ENUM + elif index == 3: + ret = DataType.UINT8 + elif index == 4: + ret = DataType.UINT16 + elif index == 5: + ret = DataType.ARRAY + elif index == 6: + ret = DataType.ARRAY + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.mode + elif e.index == 3: + ret = self.repetitions + elif e.index == 4: + ret = self.repetitionDelay + elif e.index == 5: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + # Add count + _GXCommon.setObjectCount(len(self.callingWindow), data) + for k, v in self.callingWindow: + data.setUInt8(DataType.STRUCTURE) + # Count + data.setUInt8(2) + # Start time + _GXCommon.setData(settings, data, DataType.OCTET_STRING, k) + # End time + _GXCommon.setData(settings, data, DataType.OCTET_STRING, v) + ret = data.array() + elif e.index == 6: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.destinations: + # Add count + _GXCommon.setObjectCount(0, data) + else: + # Add count + _GXCommon.setObjectCount(len(self.destinations), data) + # destination + for it in self.destinations: + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.getBytes(it)) + ret = data.array() + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.mode = e.value + elif e.index == 3: + self.repetitions = e.value + elif e.index == 4: + self.repetitionDelay = e.value + elif e.index == 5: + self.callingWindow = [] + if e.value: + for item in e.value: + start = _GXCommon.changeType(settings, item[0], DataType.DATETIME) + end = _GXCommon.changeType(settings, item[1], DataType.DATETIME) + self.callingWindow.append((start, end)) + elif e.index == 6: + self.destinations = [] + if e.value: + for item in e.value: + it = _GXCommon.changeType(settings, item, DataType.STRING) + self.destinations.append(it) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.mode = reader.readElementContentAsInt("Mode") + self.repetitions = reader.readElementContentAsInt("Repetitions") + self.repetitionDelay = reader.readElementContentAsInt("RepetitionDelay") + self.callingWindow = [] + if reader.isStartElement("CallingWindow", True): + while reader.isStartElement("Item", True): + start = reader.readElementContentAsDateTime("Start") + end = reader.readElementContentAsDateTime("End") + self.callingWindow.append((start, end)) + reader.readEndElement("CallingWindow") + str_ = reader.readElementContentAsString("Destinations", "") + if str_: + self.destinations = str_.split(';') + + def save(self, writer): + writer.writeElementString("Mode", int(self.mode)) + writer.writeElementString("Repetitions", self.repetitions) + writer.writeElementString("RepetitionDelay", self.repetitionDelay) + if self.callingWindow: + writer.writeStartElement("CallingWindow") + for k, v in self.callingWindow: + writer.writeStartElement("Item") + writer.writeElementString("Start", k) + writer.writeElementString("End", v) + writer.writeEndElement() + writer.writeEndElement() + if self.destinations: + writer.writeElementString("Destinations", ';'.join(self.destinations)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCaptureObject.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCaptureObject.py new file mode 100644 index 0000000..a8c8ae6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCaptureObject.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSCaptureObject: + # + # Constructor. + # + # aindex: Attribute index. + # dIndex: Data index. + # + def __init__(self, aIndex=0, dIndex=0): + self.attributeIndex = aIndex + self.dataIndex = dIndex diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCertificateInfo.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCertificateInfo.py new file mode 100644 index 0000000..0cc5200 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCertificateInfo.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import CertificateEntity, CertificateType + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSCertificateInfo: + # + # Constructor. + # + def __init__(self): + self.entity = CertificateEntity.SERVER + self.type_ = CertificateType.DIGITAL_SIGNATURE + self.serialNumber = None + self.issuer = None + self.subject = None + self.subjectAltName = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCharge.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCharge.py new file mode 100644 index 0000000..206e4f7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCharge.py @@ -0,0 +1,345 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import ChargeType, ChargeConfiguration +from .GXUnitCharge import GXUnitCharge +from .GXChargeTable import GXChargeTable +from ..GXBitString import GXBitString + +# pylint: disable=too-many-instance-attributes,too-few-public-methods +class GXDLMSCharge(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSCharge + """ + + def __init__(self, ln="0.0.19.20.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.CHARGE, ln, sn) + self.totalAmountPaid = 0 + self.priority = 0 + self.unitChargeActivationTime = None + self.period = 0 + self.chargeConfiguration = ChargeConfiguration.NONE + self.lastCollectionTime = None + self.lastCollectionAmount = 0 + self.totalAmountRemaining = 0 + self.proportion = 0 + self.unitChargeActive = GXUnitCharge() + self.unitChargePassive = GXUnitCharge() + self.chargeType = ChargeType.CONSUMPTION_BASED_COLLECTION + + def getValues(self): + return [self.logicalName, + self.totalAmountPaid, + self.chargeType, + self.priority, + self.unitChargeActive, + self.unitChargePassive, + self.unitChargeActivationTime, + self.period, + self.chargeConfiguration, + self.lastCollectionTime, + self.lastCollectionAmount, + self.totalAmountRemaining, + self.proportion] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # TotalAmountPaid + if all_ or self.canRead(2): + attributes.append(2) + # ChargeType + if all_ or self.canRead(3): + attributes.append(3) + # Priority + if all_ or self.canRead(4): + attributes.append(4) + # UnitChargeActive + if all_ or self.canRead(5): + attributes.append(5) + # UnitChargePassive + if all_ or self.canRead(6): + attributes.append(6) + # UnitChargeActivationTime + if all_ or self.canRead(7): + attributes.append(7) + # Period + if all_ or self.canRead(8): + attributes.append(8) + # ChargeConfiguration + if all_ or self.canRead(9): + attributes.append(9) + # LastCollectionTime + if all_ or self.canRead(10): + attributes.append(10) + # LastCollectionAmount + if all_ or self.canRead(11): + attributes.append(11) + # TotalAmountRemaining + if all_ or self.canRead(12): + attributes.append(12) + # Proportion + if all_ or self.canRead(13): + attributes.append(13) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 13 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 5 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.INT32 + elif index == 3: + ret = DataType.ENUM + elif index == 4: + ret = DataType.UINT8 + elif index == 5: + ret = DataType.STRUCTURE + elif index == 6: + ret = DataType.STRUCTURE + elif index == 7: + ret = DataType.OCTET_STRING + elif index == 8: + ret = DataType.UINT32 + elif index == 9: + ret = DataType.BITSTRING + elif index == 10: + ret = DataType.DATETIME + elif index == 11: + ret = DataType.INT32 + elif index == 12: + ret = DataType.INT32 + elif index == 13: + ret = DataType.UINT16 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + def getUIDataType(self, index): + if index in (7, 10): + return DataType.DATETIME + #pylint: disable=super-with-arguments + return super(GXDLMSCharge, self).getUIDataType(index) + + @classmethod + def getUnitCharge(cls, settings, charge): + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + _GXCommon.setData(settings, bb, DataType.INT8, charge.chargePerUnitScaling.commodityScale) + _GXCommon.setData(settings, bb, DataType.INT8, charge.chargePerUnitScaling.priceScale) + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + if charge.commodity.target is None: + _GXCommon.setData(settings, bb, DataType.UINT16, 0) + bb.setUInt8(DataType.OCTET_STRING) + bb.setUInt8(6) + bb.setUInt8(0) + bb.setUInt8(0) + bb.setUInt8(0) + bb.setUInt8(0) + bb.setUInt8(0) + bb.setUInt8(0) + _GXCommon.setData(settings, bb, DataType.INT8, 0) + else: + _GXCommon.setData(settings, bb, DataType.UINT16, charge.commodity.target.objectType) + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(charge.commodity.target.logicalName)) + _GXCommon.setData(settings, bb, DataType.INT8, charge.commodity.index) + bb.setUInt8(DataType.ARRAY) + if charge.chargeTables is None: + bb.setUInt8(0) + else: + _GXCommon.setObjectCount(len(charge.chargeTables), bb) + for it in charge.chargeTables: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, it.index) + _GXCommon.setData(settings, bb, DataType.INT16, it.chargePerUnit) + return bb.array() + + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.totalAmountPaid + elif e.index == 3: + ret = int(self.chargeType) + elif e.index == 4: + ret = self.priority + elif e.index == 5: + ret = self.getUnitCharge(settings, self.unitChargeActive) + elif e.index == 6: + ret = self.getUnitCharge(settings, self.unitChargePassive) + elif e.index == 7: + ret = self.unitChargeActivationTime + elif e.index == 8: + ret = self.period + elif e.index == 9: + ret = GXBitString.toBitString(self.chargeConfiguration, 2) + elif e.index == 10: + ret = self.lastCollectionTime + elif e.index == 11: + ret = self.lastCollectionAmount + elif e.index == 12: + ret = self.totalAmountRemaining + elif e.index == 13: + ret = self.proportion + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + @classmethod + def setUnitCharge(cls, settings, charge, value): + tmp = value + tmp2 = tmp[0] + charge.chargePerUnitScaling.commodityScale = tmp2[0] + charge.chargePerUnitScaling.priceScale = tmp2[1] + tmp2 = tmp[1] + ot = tmp2[0] + ln = _GXCommon.toLogicalName(tmp2[1]) + charge.commodity.target = settings.objects.findByLN(ot, ln) + charge.commodity.index = tmp2[2] + charge.chargeTables = [] + tmp2 = tmp[2] + for tmp3 in tmp2: + it = tmp3 + item = GXChargeTable() + item.index = it[0] + item.chargePerUnit = it[1] + charge.chargeTables.append(item) + + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.totalAmountPaid = e.value + elif e.index == 3: + self.chargeType = ChargeType(e.value) + elif e.index == 4: + self.priority = e.value + elif e.index == 5: + self.setUnitCharge(settings, self.unitChargeActive, e.value) + elif e.index == 6: + self.setUnitCharge(settings, self.unitChargePassive, e.value) + elif e.index == 7: + if isinstance(e.value, bytearray): + self.unitChargeActivationTime = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + self.unitChargeActivationTime = e.value + elif e.index == 8: + self.period = e.value + elif e.index == 9: + self.chargeConfiguration = ChargeConfiguration(e.value.toInteger()) + elif e.index == 10: + if isinstance(e.value, bytearray): + self.lastCollectionTime = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + self.lastCollectionTime = e.value + elif e.index == 11: + self.lastCollectionAmount = e.value + elif e.index == 12: + self.totalAmountRemaining = e.value + elif e.index == 13: + self.proportion = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + @classmethod + def loadUnitChargeActive(cls, reader, name, charge): + pass + + def load(self, reader): + self.totalAmountPaid = reader.readElementContentAsInt("TotalAmountPaid") + self.chargeType = ChargeType(reader.readElementContentAsInt("ChargeType")) + self.priority = int(reader.readElementContentAsInt("Priority")) + self.loadUnitChargeActive(reader, "UnitChargeActive", self.unitChargeActive) + self.loadUnitChargeActive(reader, "UnitChargePassive", self.unitChargePassive) + self.unitChargeActivationTime = reader.readElementContentAsDateTime("UnitChargeActivationTime") + self.period = reader.readElementContentAsInt("Period") + self.chargeConfiguration = ChargeConfiguration(reader.readElementContentAsInt("ChargeConfiguration")) + self.lastCollectionTime = reader.readElementContentAsDateTime("LastCollectionTime") + self.lastCollectionAmount = reader.readElementContentAsInt("LastCollectionAmount") + self.totalAmountRemaining = reader.readElementContentAsInt("TotalAmountRemaining") + self.proportion = reader.readElementContentAsInt("Proportion") + + @classmethod + def saveUnitChargeActive(cls, writer, name, charge): + pass + + def save(self, writer): + writer.writeElementString("TotalAmountPaid", self.totalAmountPaid) + writer.writeElementString("ChargeType", int(self.chargeType)) + writer.writeElementString("Priority", self.priority) + self.saveUnitChargeActive(writer, "UnitChargeActive", self.unitChargeActive) + self.saveUnitChargeActive(writer, "UnitChargePassive", self.unitChargePassive) + writer.writeElementString("UnitChargeActivationTime", self.unitChargeActivationTime) + writer.writeElementString("Period", self.period) + writer.writeElementString("ChargeConfiguration", int(self.chargeConfiguration)) + writer.writeElementString("LastCollectionTime", self.lastCollectionTime) + writer.writeElementString("LastCollectionAmount", self.lastCollectionAmount) + writer.writeElementString("TotalAmountRemaining", self.totalAmountRemaining) + writer.writeElementString("Proportion", self.proportion) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSClock.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSClock.py new file mode 100644 index 0000000..221a40f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSClock.py @@ -0,0 +1,291 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..GXDateTime import GXDateTime +from ..enums import ObjectType, DataType, ClockStatus +from .enums import ClockBase + +# pylint: disable=too-many-instance-attributes +class GXDLMSClock(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSClock + """ + + def __init__(self, ln="0.0.1.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.CLOCK, ln, sn) + self.time = None + self.timeZone = 0 + self.status = ClockStatus.OK + self.deviation = 0 + self.begin = GXDateTime() + self.end = GXDateTime() + self.enabled = False + self.clockBase = ClockBase.NONE + + def getUIDataType(self, index): + if index in (2, 5, 6): + return DataType.DATETIME + #pylint: disable=super-with-arguments + return super(GXDLMSClock, self).getUIDataType(index) + + def getValues(self): + return [self.logicalName, + self.time, + self.timeZone, + self.status, + self.begin, + self.end, + self.deviation, + self.enabled, + self.clockBase] + + def invoke(self, settings, e): + e.error = ErrorCode.READ_WRITE_DENIED + + # + # Sets the meter's time to the nearest (+/-) quarter of an hour + # value + # (*:00, *:15, *:30, *:45). + # + def adjustToQuarter(self, client): + return client.method(self, 1, 0, DataType.INT8) + + def adjustToMeasuringPeriod(self, client): + return client.method(self, 2, 0, DataType.INT8) + + def adjustToMinute(self, client): + return client.method(self, 3, 0, DataType.INT8) + + def adjustToPresetTime(self, client): + return client.method(self, 4, 0, DataType.INT8) + + def presetAdjustingTime(self, client, presetTime, validityIntervalStart, validityIntervalEnd): + buff = GXByteBuffer(44) + buff.setUInt8(DataType.STRUCTURE) + buff.setUInt8(3) + _GXCommon.setData(None, buff, DataType.OCTET_STRING, presetTime) + _GXCommon.setData(None, buff, DataType.OCTET_STRING, validityIntervalStart) + _GXCommon.setData(None, buff, DataType.OCTET_STRING, validityIntervalEnd) + return client.method(self, 5, buff.array(), DataType.ARRAY) + + # + # Shifts the time by n (-900 <= n <= 900) s. + # + def shiftTime(self, client, forTime): + if forTime < -900 or forTime > 900: + raise ValueError("Invalid shift time.") + return client.method(self, 6, int(forTime), DataType.INT16) + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Time + if all_ or self.canRead(2): + attributes.append(2) + # TimeZone + if all_ or not self.isRead(3): + attributes.append(3) + # Status + if all_ or self.canRead(4): + attributes.append(4) + # Begin + if all_ or not self.isRead(5): + attributes.append(5) + # End + if all_ or not self.isRead(6): + attributes.append(6) + # Deviation + if all_ or not self.isRead(7): + attributes.append(7) + # Enabled + if all_ or not self.isRead(8): + attributes.append(8) + # ClockBase + if all_ or not self.isRead(9): + attributes.append(9) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 9 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 6 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.OCTET_STRING + elif index == 3: + ret = DataType.INT16 + elif index == 4: + ret = DataType.UINT8 + elif index == 5: + ret = DataType.OCTET_STRING + elif index == 6: + ret = DataType.OCTET_STRING + elif index == 7: + ret = DataType.INT8 + elif index == 8: + ret = DataType.BOOLEAN + elif index == 9: + ret = DataType.ENUM + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.time + elif e.index == 3: + ret = int(self.timeZone) + elif e.index == 4: + ret = self.status + elif e.index == 5: + ret = self.begin + elif e.index == 6: + ret = self.end + elif e.index == 7: + ret = self.deviation + elif e.index == 8: + ret = self.enabled + elif e.index == 9: + ret = self.clockBase + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if isinstance(e.value, bytearray): + self.time = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + self.time = e.value + elif e.index == 3: + if e.value is None: + self.timeZone = 0 + else: + self.timeZone = e.value + elif e.index == 4: + if e.value is None: + self.status = ClockStatus.OK + else: + self.status = e.value + elif e.index == 5: + if e.value is None: + self.begin = GXDateTime() + elif isinstance(e.value, bytearray): + self.begin = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + self.begin = e.value + elif e.index == 6: + if e.value is None: + self.end = GXDateTime() + elif isinstance(e.value, bytearray): + self.end = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + self.end = e.value + elif e.index == 7: + if e.value is None: + self.deviation = 0 + else: + self.deviation = e.value + elif e.index == 8: + if e.value is None: + self.enabled = False + else: + self.enabled = e.value + elif e.index == 9: + if e.value is None: + self.clockBase = ClockBase.NONE + else: + self.clockBase = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.time = reader.readElementContentAsDateTime("Time") + self.timeZone = reader.readElementContentAsInt("TimeZone") + self.status = ClockStatus(reader.readElementContentAsInt("Status")) + self.begin = reader.readElementContentAsDateTime("Begin") + self.end = reader.readElementContentAsDateTime("End") + self.deviation = reader.readElementContentAsInt("Deviation") + self.enabled = reader.readElementContentAsInt("Enabled") != 0 + self.clockBase = ClockBase(reader.readElementContentAsInt("ClockBase")) + + def save(self, writer): + writer.writeElementString("Time", self.time) + writer.writeElementString("TimeZone", self.timeZone) + writer.writeElementString("Status", int(self.status)) + writer.writeElementString("Begin", self.begin) + writer.writeElementString("End", self.end) + writer.writeElementString("Deviation", self.deviation) + writer.writeElementString("Enabled", self.enabled) + writer.writeElementString("ClockBase", int(self.clockBase)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCredit.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCredit.py new file mode 100644 index 0000000..502470c --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSCredit.py @@ -0,0 +1,263 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXDateTime import GXDateTime +from ..enums import ObjectType, DataType +from .enums import CreditConfiguration, CreditType, CreditStatus +from ..GXBitString import GXBitString + +# pylint: disable=too-many-instance-attributes +class GXDLMSCredit(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSCredit + """ + + def __init__(self, ln="0.0.19.10.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.CREDIT, ln, sn) + self.creditConfiguration = CreditConfiguration.NONE + self.type_ = CreditType.TOKEN + self.status = CreditStatus.ENABLED + self.currentCreditAmount = 0 + self.priority = 0 + self.warningThreshold = 0 + self.limit = 0 + self.presetCreditAmount = 0 + self.creditAvailableThreshold = 0 + self.period = None + + def getValues(self): + return [self.logicalName, + self.currentCreditAmount, + self.type_, + self.priority, + self.warningThreshold, + self.limit, + self.creditConfiguration, + self.status, + self.presetCreditAmount, + self.creditAvailableThreshold, + self.period] + + def updateAmount(self, client, value): + """Adjusts the value of the current credit amount attribute.""" + return client.method(self, 1, value, DataType.INT32) + + def setAmountToValue(self, client, value): + """Sets the value of the current credit amount attribute.""" + return client.method(self, 2, value, DataType.INT32) + + def invokeCredit(self, client, value): + """Adjusts the value of the current credit amount attribute.""" + return client.method(self, 3, value, DataType.UINT8) + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # CurrentCreditAmount + if all_ or self.canRead(2): + attributes.append(2) + # Type + if all_ or self.canRead(3): + attributes.append(3) + # Priority + if all_ or self.canRead(4): + attributes.append(4) + # WarningThreshold + if all_ or self.canRead(5): + attributes.append(5) + # Limit + if all_ or self.canRead(6): + attributes.append(6) + # creditConfiguration + if all_ or self.canRead(7): + attributes.append(7) + # Status + if all_ or self.canRead(8): + attributes.append(8) + # PresetCreditAmount + if all_ or self.canRead(9): + attributes.append(9) + # CreditAvailableThreshold + if all_ or self.canRead(10): + attributes.append(10) + # Period + if all_ or self.canRead(11): + attributes.append(11) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 11 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 3 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.INT32 + elif index == 3: + ret = DataType.ENUM + elif index == 4: + ret = DataType.UINT8 + elif index == 5: + ret = DataType.INT32 + elif index == 6: + ret = DataType.INT32 + elif index == 7: + ret = DataType.BITSTRING + elif index == 8: + ret = DataType.ENUM + elif index == 9: + ret = DataType.INT32 + elif index == 10: + ret = DataType.INT32 + elif index == 11: + ret = DataType.OCTET_STRING + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.currentCreditAmount + elif e.index == 3: + ret = self.type_ + elif e.index == 4: + ret = self.priority + elif e.index == 5: + ret = self.warningThreshold + elif e.index == 6: + ret = self.limit + elif e.index == 7: + ret = GXBitString.toBitString(self.creditConfiguration, 5) + elif e.index == 8: + ret = self.status + elif e.index == 9: + ret = self.presetCreditAmount + elif e.index == 10: + ret = self.creditAvailableThreshold + elif e.index == 11: + ret = self.period + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.currentCreditAmount = e.value + elif e.index == 3: + self.type_ = e.value + elif e.index == 4: + self.priority = e.value + elif e.index == 5: + self.warningThreshold = e.value + elif e.index == 6: + self.limit = e.value + elif e.index == 7: + self.creditConfiguration = CreditConfiguration(e.value.toInteger()) + elif e.index == 8: + self.status = e.value + elif e.index == 9: + self.presetCreditAmount = e.value + elif e.index == 10: + self.creditAvailableThreshold = e.value + elif e.index == 11: + if e.value is None: + self.period = GXDateTime() + else: + tmp = None + if isinstance(e.value, bytearray): + tmp = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + tmp = e.value + self.period = tmp + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.currentCreditAmount = reader.readElementContentAsInt("CurrentCreditAmount") + self.type_ = reader.readElementContentAsInt("Type") + self.priority = reader.readElementContentAsInt("Priority") + self.warningThreshold = reader.readElementContentAsInt("WarningThreshold") + self.limit = reader.readElementContentAsInt("Limit") + self.creditConfiguration = CreditConfiguration(reader.readElementContentAsInt("CreditConfiguration")) + self.status = reader.readElementContentAsInt("Status") + self.presetCreditAmount = reader.readElementContentAsInt("PresetCreditAmount") + self.creditAvailableThreshold = reader.readElementContentAsInt("CreditAvailableThreshold") + self.period = reader.readElementContentAsDateTime("Period") + + def save(self, writer): + writer.writeElementString("CurrentCreditAmount", self.currentCreditAmount) + writer.writeElementString("Type", int(self.type_)) + writer.writeElementString("Priority", self.priority) + writer.writeElementString("WarningThreshold", self.warningThreshold) + writer.writeElementString("Limit", self.limit) + writer.writeElementString("CreditConfiguration", int(self.creditConfiguration)) + writer.writeElementString("Status", int(self.status)) + writer.writeElementString("PresetCreditAmount", self.presetCreditAmount) + writer.writeElementString("CreditAvailableThreshold", self.creditAvailableThreshold) + writer.writeElementString("Period", self.period) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSData.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSData.py new file mode 100644 index 0000000..b7617a6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSData.py @@ -0,0 +1,121 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType + +# pylint: disable=too-many-instance-attributes +class GXDLMSData(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSData + """ + + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.DATA, ln, sn) + self.value = None + + def getValues(self): + return [self.logicalName, + self.value] + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Value + if all_ or self.canRead(2): + attributes.append(2) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 2 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + #pylint: disable=super-with-arguments + return super(GXDLMSData, self).getDataType(index) + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.value + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.value = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.value = reader.readElementContentAsObject("Value", None, self, 2) + + def save(self, writer): + writer.writeElementObject("Value", self.value, self.getDataType(2), self.getUIDataType(2)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDayProfile.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDayProfile.py new file mode 100644 index 0000000..0c94b4a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDayProfile.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXDLMSDayProfile: + """ + Activity Calendar's Day profile is defined on the standard. + """ + #pylint: disable=bad-option-value,old-style-class,too-few-public-methods + + def __init__(self, day=0, schedules=None): + """ + # Constructor. + + day: value of the day. + schedules: Collection of schedules. + """ + self.dayId = day + self.daySchedules = schedules + + def __str__(self): + str_ = str(self.dayId) + if self.daySchedules: + for it in self.daySchedules: + str_ += " " + it.__str__() + return str_ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDayProfileAction.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDayProfileAction.py new file mode 100644 index 0000000..58fba58 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDayProfileAction.py @@ -0,0 +1,52 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXDLMSDayProfileAction: + """ + Activity Calendar's Day Profile Action is defined on the standard. + """ + #pylint: disable=bad-option-value,old-style-class,too-few-public-methods + + def __init__(self, startTime=None, scriptLogicalName=None, scriptSelector=0): + """ + Constructor. + + startTime: Start time. + scriptLogicalName: Logical name. + scriptSelector: Script selector. + """ + self.startTime = startTime + self.scriptLogicalName = scriptLogicalName + self.scriptSelector = scriptSelector + + def __str__(self): + return str(self.startTime) + " " + self.scriptLogicalName diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDemandRegister.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDemandRegister.py new file mode 100644 index 0000000..c1cd399 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDemandRegister.py @@ -0,0 +1,307 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import math +import datetime +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType, Unit + +# pylint: disable=too-many-instance-attributes +class GXDLMSDemandRegister(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSDemandRegister + """ + + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.DEMAND_REGISTER, ln, sn) + self.currentAverageValue = None + self.lastAverageValue = None + self.scaler = 1 + self.unit = Unit.NONE + self.status = None + self.captureTime = None + self.startTimeCurrent = None + self.period = 0 + self.numberOfPeriods = 0 + + def getValues(self): + return [self.logicalName, + self.currentAverageValue, + self.lastAverageValue, + [self.scaler, self.unit], + self.status, + self.captureTime, + self.startTimeCurrent, + self.period, + self.numberOfPeriods] + + def reset(self, client): + """Reset value.""" + return client.method(self, 1, 0, DataType.INT8) + + def nextPeriod(self, client): + """Closes the current period and starts a new one.""" + return client.method(self, 2, 0, DataType.INT8) + + def invoke(self, settings, e): + # Resets the value to the default value. + # The default value is an instance specific constant. + if e.index == 1: + self.currentAverageValue = None + self.lastAverageValue = None + self.startTimeCurrent = self.captureTime = datetime.datetime.now() + elif e.index == 2: + self.lastAverageValue = self.currentAverageValue + self.currentAverageValue = None + self.startTimeCurrent = self.captureTime = datetime.datetime.now() + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def isRead(self, index): + if index == 4: + return self.unit != 0 + #pylint: disable=super-with-arguments + return super(GXDLMSDemandRegister, self).isRead(index) + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # ScalerUnit + if all_ or not self.isRead(4): + attributes.append(4) + # CurrentAvarageValue + if all_ or self.canRead(2): + attributes.append(2) + # LastAvarageValue + if all_ or self.canRead(3): + attributes.append(3) + # Status + if all_ or self.canRead(5): + attributes.append(5) + # CaptureTime + if all_ or self.canRead(6): + attributes.append(6) + # StartTimeCurrent + if all_ or self.canRead(7): + attributes.append(7) + # Period + if all_ or self.canRead(8): + attributes.append(8) + # NumberOfPeriods + if all_ or self.canRead(9): + attributes.append(9) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 9 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 2 + + def getDataType(self, index): + #pylint: disable=super-with-arguments + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = super(GXDLMSDemandRegister, self).getDataType(index) + elif index == 3: + ret = super(GXDLMSDemandRegister, self).getDataType(index) + elif index == 4: + ret = DataType.ARRAY + elif index == 5: + ret = super(GXDLMSDemandRegister, self).getDataType(index) + elif index == 6: + ret = DataType.OCTET_STRING + elif index == 7: + ret = DataType.OCTET_STRING + elif index == 8: + ret = DataType.UINT32 + elif index == 9: + ret = DataType.UINT16 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.currentAverageValue + elif e.index == 3: + ret = self.lastAverageValue + elif e.index == 4: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.INT8, math.floor(math.log(self.scaler, 10))) + _GXCommon.setData(settings, data, DataType.ENUM, int(self.unit)) + ret = data.array() + elif e.index == 5: + ret = self.status + elif e.index == 6: + ret = self.captureTime + elif e.index == 7: + ret = self.startTimeCurrent + elif e.index == 8: + ret = self.period + elif e.index == 9: + ret = self.numberOfPeriods + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # pylint: disable=broad-except + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if self.scaler != 1 and e.value: + try: + if settings.isServer: + self.currentAverageValue = e.value + else: + self.currentAverageValue = e.value * self.scaler + except Exception: + # Sometimes scaler is set for wrong Object type. + self.currentAverageValue = e.value + else: + self.currentAverageValue = e.value + elif e.index == 3: + if self.scaler != 1 and e.value: + try: + if settings.isServer: + self.lastAverageValue = e.value + else: + self.lastAverageValue = e.value * self.scaler + except Exception: + # Sometimes scaler is set for wrong Object type. + self.lastAverageValue = e.value + else: + self.lastAverageValue = e.value + elif e.index == 4: + # Set default values. + if e.value is None: + self.scaler = 1 + self.unit = Unit.NONE + else: + if len(e.value) != 2: + raise ValueError("setValue failed. Invalid scaler unit value.") + self.scaler = math.pow(10, e.value[0]) + self.unit = Unit(e.value[1]) + elif e.index == 5: + if e.value is None: + self.status = None + else: + self.status = e.value + elif e.index == 6: + if e.value is None: + self.captureTime = None + else: + tmp = None + if isinstance(e.value, bytearray): + tmp = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + tmp = e.value + self.captureTime = tmp + elif e.index == 7: + if e.value is None: + self.startTimeCurrent = None + else: + tmp = None + if isinstance(e.value, bytearray): + tmp = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + tmp = e.value + self.startTimeCurrent = tmp + elif e.index == 8: + if e.value is None: + self.period = 0 + else: + self.period = e.value + elif e.index == 9: + if e.value is None: + self.numberOfPeriods = 0 + else: + self.numberOfPeriods = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.currentAverageValue = reader.readElementContentAsObject("CurrentAverageValue", None, self, 2) + self.lastAverageValue = reader.readElementContentAsObject("LastAverageValue", None, self, 3) + self.scaler = reader.readElementContentAsDouble("Scaler", 1) + self.unit = Unit(reader.readElementContentAsInt("Unit")) + self.status = reader.readElementContentAsObject("Status", None, self, 5) + self.captureTime = reader.readElementContentAsDateTime("CaptureTime") + self.startTimeCurrent = reader.readElementContentAsDateTime("StartTimeCurrent") + self.period = reader.readElementContentAsInt("Period") + self.numberOfPeriods = reader.readElementContentAsInt("NumberOfPeriods") + + def save(self, writer): + writer.writeElementObject("CurrentAverageValue", self.currentAverageValue) + writer.writeElementObject("LastAverageValue", self.lastAverageValue) + writer.writeElementString("Scaler", self.scaler, 1) + writer.writeElementString("Unit", int(self.unit)) + writer.writeElementObject("Status", self.status) + writer.writeElementString("CaptureTime", self.captureTime) + writer.writeElementString("StartTimeCurrent", self.startTimeCurrent) + writer.writeElementString("Period", self.period) + writer.writeElementString("NumberOfPeriods", self.numberOfPeriods) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDisconnectControl.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDisconnectControl.py new file mode 100644 index 0000000..808f078 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSDisconnectControl.py @@ -0,0 +1,160 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType +from .enums import ControlState, ControlMode + +# pylint: disable=too-many-instance-attributes +class GXDLMSDisconnectControl(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSDisconnectControl + """ + + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.DISCONNECT_CONTROL, ln, sn) + self.controlState = ControlState.DISCONNECTED + self.controlMode = ControlMode.NONE + self.outputState = False + + # + # Forces the disconnect control object into 'disconnected' state if + # remote + # disconnection is enabled. + # + def remoteDisconnect(self, client): + return client.method(self, 1, int(0), DataType.INT8) + + # + # Forces the disconnect control object into the + # 'ready_for_reconnection' + # state if a direct remote reconnection is disabled. + # + def remoteReconnect(self, client): + return client.method(self, 2, int(0), DataType.INT8) + + def getValues(self): + return [self.logicalName, + self.outputState, + self.controlState, + self.controlMode] + + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # OutputState + if all_ or self.canRead(2): + attributes.append(2) + # ControlState + if all_ or self.canRead(3): + attributes.append(3) + # ControlMode + if all_ or self.canRead(4): + attributes.append(4) + return attributes + + def getAttributeCount(self): + return 4 + + def getMethodCount(self): + return 2 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.BOOLEAN + elif index == 3: + ret = DataType.ENUM + elif index == 4: + ret = DataType.ENUM + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.outputState + if e.index == 3: + return self.controlState + if e.index == 4: + return self.controlMode + e.error = ErrorCode.READ_WRITE_DENIED + return None + + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if e.value is None: + self.outputState = False + else: + self.outputState = e.value + elif e.index == 3: + #pylint: disable=bad-option-value,redefined-variable-type + if e.value is None: + self.controlState = ControlState.DISCONNECTED + else: + self.controlState = e.value + elif e.index == 4: + #pylint: disable=bad-option-value,redefined-variable-type + if e.value is None: + self.controlMode = ControlMode.NONE + else: + self.controlMode = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.outputState = reader.readElementContentAsInt("OutputState") != 0 + self.controlState = reader.readElementContentAsInt("ControlState") + self.controlMode = reader.readElementContentAsInt("ControlMode") + + def save(self, writer): + writer.writeElementString("OutputState", self.outputState) + writer.writeElementString("ControlState", int(self.controlState)) + writer.writeElementString("ControlMode", int(self.controlMode)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSEmergencyProfile.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSEmergencyProfile.py new file mode 100644 index 0000000..bc5d71e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSEmergencyProfile.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSEmergencyProfile: + def __init__(self): + """ + Constructor. + """ + self.id = 0 + self.activationTime = None + self.duration = 0 + + def __str__(self): + return str(self.id) + " " + self.activationTime.__str__() + " " + str(self.duration) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSExtendedRegister.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSExtendedRegister.py new file mode 100644 index 0000000..3ba827e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSExtendedRegister.py @@ -0,0 +1,217 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import math +import datetime +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..GXDateTime import GXDateTime +from ..enums import ObjectType, DataType, Unit + +# pylint: disable=too-many-instance-attributes +class GXDLMSExtendedRegister(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSExtendedRegister + """ + + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.EXTENDED_REGISTER, ln, sn) + self.value = None + self.scaler = 1 + self.unit = Unit.NONE + self.status = None + self.captureTime = None + + def getUIDataType(self, index): + if index == 5: + return DataType.DATETIME + #pylint: disable=super-with-arguments + return super(GXDLMSExtendedRegister, self).getUIDataType(index) + + def getValues(self): + return [self.logicalName, + self.value, + [self.scaler, self.unit], + self.status, + self.captureTime] + + def reset(self, client): + """Reset value.""" + return client.method(self, 1, 0, DataType.INT8) + + def invoke(self, settings, e): + # Resets the value to the default value. + # The default value is an instance specific constant. + if e.index == 1: + self.value = None + self.captureTime = datetime.datetime.now() + else: + e.error = ErrorCode.READ_WRITE_DENIED + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # ScalerUnit + if all_ or not self.isRead(3): + attributes.append(3) + # Value + if all_ or self.canRead(2): + attributes.append(2) + # Status + if all_ or self.canRead(4): + attributes.append(4) + # CaptureTime + if all_ or self.canRead(5): + attributes.append(5) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 5 + + def getMethodCount(self): + return 0 + + def getDataType(self, index): + #pylint: disable=super-with-arguments + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = super(GXDLMSExtendedRegister, self).getDataType(index) + elif index == 3: + ret = DataType.ARRAY + elif index == 4: + ret = super(GXDLMSExtendedRegister, self).getDataType(index) + elif index == 5: + ret = DataType.OCTET_STRING + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.value + if e.index == 3: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.INT8, math.floor(math.log(self.scaler, 10))) + _GXCommon.setData(settings, data, DataType.ENUM, int(self.unit)) + return data.array() + if e.index == 4: + return self.status + if e.index == 5: + return self.captureTime + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + #pylint: disable=broad-except + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if self.scaler != 1 and e.value: + try: + if settings.isServer: + self.value = e.value + else: + self.value = e.value * self.scaler + except Exception: + # Sometimes scaler is set for wrong Object type. + self.value = e.value + else: + self.value = e.value + elif e.index == 3: + # Set default values. + if not e.value: + self.scaler = 0 + self.unit = Unit.NONE + else: + if not e.value: + self.scaler = 0 + self.unit = Unit.NONE + else: + self.scaler = math.pow(10, e.value[0]) + self.unit = Unit(e.value[1]) + elif e.index == 4: + self.status = e.value + elif e.index == 5: + if e.value is None: + self.captureTime = GXDateTime() + else: + if isinstance(e.value, bytearray): + self.captureTime = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + self.captureTime = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.unit = Unit(reader.readElementContentAsInt("Unit", 0)) + self.scaler = reader.readElementContentAsDouble("Scaler", 1) + self.value = reader.readElementContentAsObject("Value", None, self, 2) + self.status = reader.readElementContentAsObject("Status", None, self, 4) + self.captureTime = reader.readElementContentAsDateTime("CaptureTime") + + def save(self, writer): + writer.writeElementString("Unit", int(self.unit)) + writer.writeElementString("Scaler", self.scaler, 1) + writer.writeElementObject("Value", self.value, self.getDataType(2), self.getUIDataType(2)) + writer.writeElementObject("Status", self.status, self.getDataType(4), self.getUIDataType(4)) + writer.writeElementString("CaptureTime", self.captureTime) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSGSMCellInfo.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSGSMCellInfo.py new file mode 100644 index 0000000..1e8ce90 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSGSMCellInfo.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSGSMCellInfo: + def __init__(self): + """ + Constructor. + """ + self.cellId = 0 + self.ber = 0 + self.locationId = 0 + self.signalQuality = 0 + self.mobileCountryCode = 0 + self.mobileNetworkCode = 0 + self.channelNumber = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSGSMDiagnostic.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSGSMDiagnostic.py new file mode 100644 index 0000000..7a363dc --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSGSMDiagnostic.py @@ -0,0 +1,291 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import GsmStatus, GsmCircuitSwitchStatus, GsmPacketSwitchStatus +from .GXDLMSGSMCellInfo import GXDLMSGSMCellInfo +from .GXAdjacentCell import GXAdjacentCell + +# pylint: disable=too-many-instance-attributes +class GXDLMSGSMDiagnostic(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSGSMDiagnostic + """ + + def __init__(self, ln="0.0.25.6.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.GSM_DIAGNOSTIC, ln, sn) + self.version = 1 + self.cellInfo = GXDLMSGSMCellInfo() + self.adjacentCells = list() + self.operator = "" + self.status = GsmStatus.NONE + self.circuitSwitchStatus = GsmCircuitSwitchStatus.INACTIVE + self.packetSwitchStatus = GsmPacketSwitchStatus.INACTIVE + self.captureTime = None + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Operator + if all_ or self.canRead(2): + attributes.append(2) + # Status + if all_ or self.canRead(3): + attributes.append(3) + # CircuitSwitchStatus + if all_ or self.canRead(4): + attributes.append(4) + # PacketSwitchStatus + if all_ or self.canRead(5): + attributes.append(5) + # CellInfo + if all_ or self.canRead(6): + attributes.append(6) + # AdjacentCells + if all_ or self.canRead(7): + attributes.append(7) + # CaptureTime + if all_ or self.canRead(8): + attributes.append(8) + return attributes + + def getValues(self): + return [self.logicalName, + self.operator, + self.status, + self.circuitSwitchStatus, + self.packetSwitchStatus, + self.cellInfo, + self.adjacentCells, + self.captureTime] + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 8 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.STRING + elif index == 3: + ret = DataType.ENUM + elif index == 4: + ret = DataType.ENUM + elif index == 5: + ret = DataType.ENUM + elif index == 6: + ret = DataType.STRUCTURE + elif index == 7: + ret = DataType.ARRAY + elif index == 8: + ret = DataType.DATETIME + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + bb = None + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + #pylint: disable=bad-option-value,redefined-variable-type + if self.operator is None: + ret = None + else: + ret = self.operator.encode() + elif e.index == 3: + if not self.status: + ret = 0 + else: + ret = self.status + elif e.index == 4: + ret = self.circuitSwitchStatus + elif e.index == 5: + ret = self.packetSwitchStatus + elif e.index == 6: + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + if self.version == 0: + bb.setUInt8(4) + _GXCommon.setData(settings, bb, DataType.UINT16, self.cellInfo.cellId) + else: + bb.setUInt8(7) + _GXCommon.setData(settings, bb, DataType.UINT32, self.cellInfo.cellId) + _GXCommon.setData(settings, bb, DataType.UINT16, self.cellInfo.locationId) + _GXCommon.setData(settings, bb, DataType.UINT8, self.cellInfo.signalQuality) + _GXCommon.setData(settings, bb, DataType.UINT8, self.cellInfo.ber) + if self.version > 0: + _GXCommon.setData(settings, bb, DataType.UINT16, self.cellInfo.mobileCountryCode) + _GXCommon.setData(settings, bb, DataType.UINT16, self.cellInfo.mobileNetworkCode) + _GXCommon.setData(settings, bb, DataType.UINT32, self.cellInfo.channelNumber) + ret = bb + elif e.index == 7: + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if self.adjacentCells is None: + bb.setUInt8(0) + else: + bb.setUInt8(len(self.adjacentCells)) + for it in self.adjacentCells: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + if self.version == 0: + _GXCommon.setData(settings, bb, DataType.UINT16, it.cellId) + else: + _GXCommon.setData(settings, bb, DataType.UINT32, it.cellId) + _GXCommon.setData(settings, bb, DataType.UINT8, it.signalQuality) + ret = bb + elif e.index == 8: + ret = self.captureTime + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if isinstance(e.value, bytearray): + self.operator = str(e.value) + elif isinstance(self.operator, (str,)): + self.operator = str(e.value) + elif self.operator is None: + self.operator = None + else: + e.error = ErrorCode.READ_WRITE_DENIED + elif e.index == 3: + self.status = GsmStatus(e.value) + elif e.index == 4: + self.circuitSwitchStatus = GsmCircuitSwitchStatus(e.value) + elif e.index == 5: + self.packetSwitchStatus = GsmPacketSwitchStatus(e.value) + elif e.index == 6: + if e.value: + self.cellInfo.cellId = e.value[0] + self.cellInfo.locationId = e.value[1] + self.cellInfo.signalQuality = e.value[2] + self.cellInfo.ber = e.value[3] + if self.version > 0: + self.cellInfo.mobileCountryCode = e.value[4] + self.cellInfo.mobileNetworkCode = e.value[5] + self.cellInfo.channelNumber = e.value[6] + elif e.index == 7: + self.adjacentCells = [] + if e.value: + for it in e.value: + ac = GXAdjacentCell() + ac.cellId = it[0] + ac.signalQuality = it[1] + self.adjacentCells.append(ac) + elif e.index == 8: + if isinstance(e.value, bytearray): + self.captureTime = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + self.captureTime = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.operator = reader.readElementContentAsString("Operator") + self.status = reader.readElementContentAsInt("Status") + self.circuitSwitchStatus = reader.readElementContentAsInt("CircuitSwitchStatus") + self.packetSwitchStatus = reader.readElementContentAsInt("PacketSwitchStatus") + if reader.isStartElement("CellInfo", True): + self.cellInfo.cellId = reader.readElementContentAsLong("CellId") + self.cellInfo.locationId = reader.readElementContentAsInt("LocationId") + self.cellInfo.signalQuality = reader.readElementContentAsInt("SignalQuality") + self.cellInfo.ber = reader.readElementContentAsInt("Ber") + reader.readEndElement("CellInfo") + self.adjacentCells = [] + if reader.isStartElement("AdjacentCells", True): + while reader.isStartElement("Item", True): + it = GXAdjacentCell() + it.cellId = reader.readElementContentAsLong("CellId") + it.signalQuality = reader.readElementContentAsInt("SignalQuality") + self.adjacentCells.append(it) + reader.readEndElement("AdjacentCells") + self.captureTime = reader.readElementContentAsDateTime("CaptureTime") + + def save(self, writer): + writer.writeElementObject("Operator", self.operator) + writer.writeElementString("Status", int(self.status)) + writer.writeElementString("CircuitSwitchStatus", int(self.circuitSwitchStatus)) + writer.writeElementString("PacketSwitchStatus", int(self.packetSwitchStatus)) + if self.cellInfo: + writer.writeStartElement("CellInfo") + writer.writeElementString("CellId", self.cellInfo.cellId) + writer.writeElementString("LocationId", self.cellInfo.locationId) + writer.writeElementString("SignalQuality", self.cellInfo.signalQuality) + writer.writeElementString("Ber", self.cellInfo.ber) + writer.writeEndElement() + if self.adjacentCells: + writer.writeStartElement("AdjacentCells") + for it in self.adjacentCells: + writer.writeStartElement("Item") + writer.writeElementString("CellId", it.cellId) + writer.writeElementString("SignalQuality", it.signalQuality) + writer.writeEndElement() + writer.writeEndElement() + writer.writeElementString("CaptureTime", self.captureTime) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSGprsSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSGprsSetup.py new file mode 100644 index 0000000..fb75c19 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSGprsSetup.py @@ -0,0 +1,217 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .GXDLMSQualityOfService import GXDLMSQualityOfService + +# pylint: disable=too-many-instance-attributes +class GXDLMSGprsSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSGprsSetup + """ + + def __init__(self, ln="0.0.25.4.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.GPRS_SETUP, ln, sn) + self.apn = "" + self.pinCode = 0 + self.defaultQualityOfService = GXDLMSQualityOfService() + self.requestedQualityOfService = GXDLMSQualityOfService() + + def getValues(self): + return [self.logicalName, + self.apn, + self.pinCode, + self.defaultQualityOfService, + self.requestedQualityOfService] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # APN + if all_ or not self.isRead(2): + attributes.append(2) + # PINCode + if all_ or not self.isRead(3): + attributes.append(3) + # DefaultQualityOfService + RequestedQualityOfService + if all_ or not self.isRead(4): + attributes.append(4) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 4 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.OCTET_STRING + elif index == 3: + ret = DataType.UINT16 + elif index == 4: + ret = DataType.ARRAY + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + if not self.apn: + ret = None + else: + ret = self.apn.encode() + elif e.index == 3: + ret = self.pinCode + elif e.index == 4: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(5) + _GXCommon.setData(settings, data, DataType.UINT8, self.defaultQualityOfService.precedence) + _GXCommon.setData(settings, data, DataType.UINT8, self.defaultQualityOfService.delay) + _GXCommon.setData(settings, data, DataType.UINT8, self.defaultQualityOfService.reliability) + _GXCommon.setData(settings, data, DataType.UINT8, self.defaultQualityOfService.peakThroughput) + _GXCommon.setData(settings, data, DataType.UINT8, self.defaultQualityOfService.meanThroughput) + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(5) + _GXCommon.setData(settings, data, DataType.UINT8, self.requestedQualityOfService.precedence) + _GXCommon.setData(settings, data, DataType.UINT8, self.requestedQualityOfService.delay) + _GXCommon.setData(settings, data, DataType.UINT8, self.requestedQualityOfService.reliability) + _GXCommon.setData(settings, data, DataType.UINT8, self.requestedQualityOfService.peakThroughput) + _GXCommon.setData(settings, data, DataType.UINT8, self.requestedQualityOfService.meanThroughput) + ret = data.array() + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if isinstance(e.value, str): + self.apn = e.value + else: + self.apn = _GXCommon.changeType(settings, e.value, DataType.STRING) + elif e.index == 3: + self.pinCode = e.value + elif e.index == 4: + arr = (e.value)[0] + self.defaultQualityOfService.precedence = arr[0] + self.defaultQualityOfService.delay = arr[1] + self.defaultQualityOfService.reliability = arr[2] + self.defaultQualityOfService.peakThroughput = arr[3] + self.defaultQualityOfService.meanThroughput = arr[4] + arr = (e.value)[1] + self.requestedQualityOfService.precedence = arr[0] + self.requestedQualityOfService.delay = arr[1] + self.requestedQualityOfService.reliability = arr[2] + self.requestedQualityOfService.peakThroughput = arr[3] + self.requestedQualityOfService.meanThroughput = arr[4] + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.apn = reader.readElementContentAsString("APN") + self.pinCode = reader.readElementContentAsInt("PINCode") + if reader.isStartElement("DefaultQualityOfService", True): + self.defaultQualityOfService.precedence = reader.readElementContentAsInt("Precedence") + self.defaultQualityOfService.delay = reader.readElementContentAsInt("Delay") + self.defaultQualityOfService.reliability = reader.readElementContentAsInt("Reliability") + self.defaultQualityOfService.peakThroughput = reader.readElementContentAsInt("PeakThroughput") + self.defaultQualityOfService.meanThroughput = reader.readElementContentAsInt("MeanThroughput") + reader.readEndElement("DefaultQualityOfService") + if reader.isStartElement("RequestedQualityOfService", True): + self.requestedQualityOfService.precedence = reader.readElementContentAsInt("Precedence") + self.requestedQualityOfService.delay = reader.readElementContentAsInt("Delay") + self.requestedQualityOfService.reliability = reader.readElementContentAsInt("Reliability") + self.requestedQualityOfService.peakThroughput = reader.readElementContentAsInt("PeakThroughput") + self.requestedQualityOfService.meanThroughput = reader.readElementContentAsInt("MeanThroughput") + reader.readEndElement("DefaultQualityOfService") + + def save(self, writer): + writer.writeElementString("APN", self.apn) + writer.writeElementString("PINCode", self.pinCode) + if self.defaultQualityOfService: + writer.writeStartElement("DefaultQualityOfService") + writer.writeElementString("Precedence", self.defaultQualityOfService.precedence) + writer.writeElementString("Delay", self.defaultQualityOfService.delay) + writer.writeElementString("Reliability", self.defaultQualityOfService.reliability) + writer.writeElementString("PeakThroughput", self.defaultQualityOfService.peakThroughput) + writer.writeElementString("MeanThroughput", self.defaultQualityOfService.meanThroughput) + writer.writeEndElement() + if self.requestedQualityOfService: + writer.writeStartElement("RequestedQualityOfService") + writer.writeElementString("Precedence", self.requestedQualityOfService.precedence) + writer.writeElementString("Delay", self.requestedQualityOfService.delay) + writer.writeElementString("Reliability", self.requestedQualityOfService.reliability) + writer.writeElementString("PeakThroughput", self.requestedQualityOfService.peakThroughput) + writer.writeElementString("MeanThroughput", self.requestedQualityOfService.meanThroughput) + writer.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSHdlcSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSHdlcSetup.py new file mode 100644 index 0000000..6cc5047 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSHdlcSetup.py @@ -0,0 +1,220 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode, ObjectType, DataType +from ..internal._GXCommon import _GXCommon +from .enums import BaudRate + +# pylint: disable=too-many-instance-attributes +class GXDLMSHdlcSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSHdlcSetup + """ + + def __init__(self, ln="0.0.22.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.IEC_HDLC_SETUP, ln, sn) + self.communicationSpeed = BaudRate.BAUDRATE_9600 + self.windowSizeTransmit = 1 + self.windowSizeReceive = 1 + self.maximumInfoLengthReceive = 128 + self.maximumInfoLengthTransmit = 128 + self.inactivityTimeout = 120 + self.version = 1 + self.interCharachterTimeout = 0 + self.deviceAddress = 0 + + def getValues(self): + return [self.logicalName, + self.communicationSpeed, + self.windowSizeTransmit, + self.windowSizeReceive, + self.maximumInfoLengthTransmit, + self.maximumInfoLengthReceive, + self.interCharachterTimeout, + self.inactivityTimeout, + self.deviceAddress] + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Communication speed + if all_ or not self.isRead(2): + attributes.append(2) + # Window size transmit + if all_ or not self.isRead(3): + attributes.append(3) + # Window size receive + if all_ or not self.isRead(4): + attributes.append(4) + # maximum info length transmit + if all_ or not self.isRead(5): + attributes.append(5) + # MaximumInfoLengthReceive + if all_ or not self.isRead(6): + attributes.append(6) + # InterCharachterTimeout + if all_ or not self.isRead(7): + attributes.append(7) + # Inactivity timeout + if all_ or not self.isRead(8): + attributes.append(8) + # Device address + if all_ or not self.isRead(9): + attributes.append(9) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 9 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ENUM + elif index == 3: + ret = DataType.UINT8 + elif index == 4: + ret = DataType.UINT8 + elif index == 5: + if self.version == 0: + ret = DataType.UINT8 + else: + ret = DataType.UINT16 + elif index == 6: + if self.version == 0: + ret = DataType.UINT8 + else: + ret = DataType.UINT16 + elif index == 7: + ret = DataType.UINT16 + elif index == 8: + ret = DataType.UINT16 + elif index == 9: + ret = DataType.UINT16 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.communicationSpeed + elif e.index == 3: + ret = self.windowSizeTransmit + elif e.index == 4: + ret = self.windowSizeReceive + elif e.index == 5: + ret = self.maximumInfoLengthTransmit + elif e.index == 6: + ret = self.maximumInfoLengthReceive + elif e.index == 7: + ret = self.interCharachterTimeout + elif e.index == 8: + ret = self.inactivityTimeout + elif e.index == 9: + ret = self.deviceAddress + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.communicationSpeed = e.value + elif e.index == 3: + self.windowSizeTransmit = e.value + elif e.index == 4: + self.windowSizeReceive = e.value + elif e.index == 5: + self.maximumInfoLengthTransmit = e.value + elif e.index == 6: + self.maximumInfoLengthReceive = e.value + elif e.index == 7: + self.interCharachterTimeout = e.value + elif e.index == 8: + self.inactivityTimeout = e.value + elif e.index == 9: + self.deviceAddress = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.communicationSpeed = reader.readElementContentAsInt("Speed") + self.windowSizeTransmit = reader.readElementContentAsInt("WindowSizeTx") + self.windowSizeReceive = reader.readElementContentAsInt("WindowSizeRx") + self.maximumInfoLengthTransmit = reader.readElementContentAsInt("MaximumInfoLengthTx") + self.maximumInfoLengthReceive = reader.readElementContentAsInt("MaximumInfoLengthRx") + self.interCharachterTimeout = reader.readElementContentAsInt("InterCharachterTimeout") + self.inactivityTimeout = reader.readElementContentAsInt("InactivityTimeout") + self.deviceAddress = reader.readElementContentAsInt("DeviceAddress") + + def save(self, writer): + writer.writeElementString("Speed", int(self.communicationSpeed)) + writer.writeElementString("WindowSizeTx", self.windowSizeTransmit) + writer.writeElementString("WindowSizeRx", self.windowSizeReceive) + writer.writeElementString("MaximumInfoLengthTx", self.maximumInfoLengthTransmit) + writer.writeElementString("MaximumInfoLengthRx", self.maximumInfoLengthReceive) + writer.writeElementString("InterCharachterTimeout", self.interCharachterTimeout) + writer.writeElementString("InactivityTimeout", self.inactivityTimeout) + writer.writeElementString("DeviceAddress", self.deviceAddress) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIECLocalPortSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIECLocalPortSetup.py new file mode 100644 index 0000000..b067650 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIECLocalPortSetup.py @@ -0,0 +1,214 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType +from .enums import OpticalProtocolMode, BaudRate, LocalPortResponseTime + +# pylint: disable=too-many-instance-attributes +class GXDLMSIECLocalPortSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSIECLocalPortSetup + """ + + def __init__(self, ln="0.0.20.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.IEC_LOCAL_PORT_SETUP, ln, sn) + self.defaultMode = OpticalProtocolMode.DEFAULT + self.defaultBaudrate = BaudRate.BAUDRATE_300 + self.proposedBaudrate = BaudRate.BAUDRATE_300 + self.responseTime = LocalPortResponseTime.ms20 + self.password1 = None + self.password2 = None + self.password5 = None + self.deviceAddress = None + self.version = 1 + + def getValues(self): + return [self.logicalName, + self.defaultMode, + self.defaultBaudrate, + self.proposedBaudrate, + self.responseTime, + self.deviceAddress, + self.password1, + self.password2, + self.password5] + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # DefaultMode + if all_ or not self.isRead(2): + attributes.append(2) + # DefaultBaudrate + if all_ or not self.isRead(3): + attributes.append(3) + # ProposedBaudrate + if all_ or not self.isRead(4): + attributes.append(4) + # ResponseTime + if all_ or not self.isRead(5): + attributes.append(5) + # DeviceAddress + if all_ or not self.isRead(6): + attributes.append(6) + # Password1 + if all_ or not self.isRead(7): + attributes.append(7) + # Password2 + if all_ or not self.isRead(8): + attributes.append(8) + # Password5 + if all_ or not self.isRead(9): + attributes.append(9) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 9 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ENUM + elif index == 3: + ret = DataType.ENUM + elif index == 4: + ret = DataType.ENUM + elif index == 5: + ret = DataType.ENUM + elif index == 6: + ret = DataType.OCTET_STRING + elif index == 7: + ret = DataType.OCTET_STRING + elif index == 8: + ret = DataType.OCTET_STRING + elif index == 9: + ret = DataType.OCTET_STRING + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.defaultMode + elif e.index == 3: + ret = self.defaultBaudrate + elif e.index == 4: + ret = self.proposedBaudrate + elif e.index == 5: + ret = self.responseTime + elif e.index == 6: + ret = _GXCommon.getBytes(self.deviceAddress) + elif e.index == 7: + ret = _GXCommon.getBytes(self.password1) + elif e.index == 8: + ret = _GXCommon.getBytes(self.password2) + elif e.index == 9: + ret = _GXCommon.getBytes(self.password5) + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.defaultMode = e.value + elif e.index == 3: + self.defaultBaudrate = e.value + elif e.index == 4: + self.proposedBaudrate = e.value + elif e.index == 5: + self.responseTime = e.value + elif e.index == 6: + self.deviceAddress = _GXCommon.changeType(settings, e.value, DataType.STRING) + elif e.index == 7: + self.password1 = _GXCommon.changeType(settings, e.value, DataType.STRING) + elif e.index == 8: + self.password2 = _GXCommon.changeType(settings, e.value, DataType.STRING) + elif e.index == 9: + self.password5 = _GXCommon.changeType(settings, e.value, DataType.STRING) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.defaultMode = reader.readElementContentAsInt("DefaultMode") + self.defaultBaudrate = reader.readElementContentAsInt("DefaultBaudrate") + self.proposedBaudrate = reader.readElementContentAsInt("ProposedBaudrate") + self.responseTime = reader.readElementContentAsInt("ResponseTime") + self.deviceAddress = reader.readElementContentAsString("DeviceAddress") + self.password1 = reader.readElementContentAsString("Password1") + self.password2 = reader.readElementContentAsString("Password2") + self.password5 = reader.readElementContentAsString("Password5") + + def save(self, writer): + writer.writeElementString("DefaultMode", int(self.defaultMode)) + writer.writeElementString("DefaultBaudrate", int(self.defaultBaudrate)) + writer.writeElementString("ProposedBaudrate", int(self.proposedBaudrate)) + writer.writeElementString("ResponseTime", int(self.responseTime)) + writer.writeElementString("DeviceAddress", self.deviceAddress) + writer.writeElementString("Password1", self.password1) + writer.writeElementString("Password2", self.password2) + writer.writeElementString("Password5", self.password5) + +class GXDLMSIECOpticalPortSetup(GXDLMSIECLocalPortSetup): + """Obsolete. Use GXDLMSIECLocalPortSetup instead.""" diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIecTwistedPairSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIecTwistedPairSetup.py new file mode 100644 index 0000000..c469031 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIecTwistedPairSetup.py @@ -0,0 +1,164 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import IecTwistedPairSetupMode, BaudRate + +# pylint: disable=too-many-instance-attributes +class GXDLMSIecTwistedPairSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSIecTwistedPairSetup + """ + + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.IEC_HDLC_SETUP, ln, sn) + # Working mode. + self.mode = IecTwistedPairSetupMode.INACTIVE + # Communication speed. + self.speed = BaudRate.BAUDRATE_9600 + # List of Primary Station Addresses. + self.primaryAddresses = list() + # List of the TAB(i) for which the real equipment has been programmed + # in the case of forgotten station call. + self.tabis = list() + + def getValues(self): + return [self.logicalName, + self.mode, + self.speed, + self.primaryAddresses, + self.tabis] + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Mode + if all_ or self.canRead(2): + attributes.append(2) + # Speed + if all_ or self.canRead(3): + attributes.append(3) + # PrimaryAddresses + if all_ or self.canRead(4): + attributes.append(4) + # Tabis + if all_ or self.canRead(5): + attributes.append(5) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 5 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.ENUM + if index == 3: + return DataType.ENUM + if index == 4: + return DataType.ARRAY + if index == 5: + return DataType.ARRAY + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.mode = IecTwistedPairSetupMode(e.value) + elif e.index == 3: + self.speed = BaudRate(e.value) + elif e.index == 4: + self.primaryAddresses = [] + for it in e.value: + self.primaryAddresses.append(it) + elif e.index == 5: + self.tabis = [] + for it in e.value: + self.tabis.append(it) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.mode = IecTwistedPairSetupMode(reader.readElementContentAsInt("Mode")) + self.speed = BaudRate(reader.readElementContentAsInt("Speed")) + self.primaryAddresses = GXByteBuffer.hexToBytes(reader.readElementContentAsString("PrimaryAddresses")) + self.tabis = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Tabis")) + + def save(self, writer): + writer.writeElementString("Mode", self.mode) + writer.writeElementString("Speed", self.speed) + writer.writeElementString("LN", GXByteBuffer.hex(self.primaryAddresses)) + if self.tabis: + writer.writeElementString("LN", GXByteBuffer.hex(self.tabis)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSImageActivateInfo.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSImageActivateInfo.py new file mode 100644 index 0000000..59d28c7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSImageActivateInfo.py @@ -0,0 +1,64 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXByteBuffer import GXByteBuffer + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSImageActivateInfo: + def __init__(self, size=0, identification=None, signature=None): + """ + Constructor. + + size: Size. + identification: Identification. + signature: Signature. + """ + self.size = size + self.identification = identification + self.signature = signature + + def __str__(self): + sb = "" + if GXByteBuffer.isAsciiString(self.identification): + sb += str(self.identification) + else: + sb += GXByteBuffer.hex(self.identification, True) + sb += " " + if GXByteBuffer.isAsciiString(self.signature): + sb += str(self.signature) + else: + sb += GXByteBuffer.hex(self.signature, True) + sb += " " + sb += str(self.size) + return sb diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSImageTransfer.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSImageTransfer.py new file mode 100644 index 0000000..32d3731 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSImageTransfer.py @@ -0,0 +1,324 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import ImageTransferStatus +from .GXDLMSImageActivateInfo import GXDLMSImageActivateInfo + +# pylint: disable=too-many-instance-attributes +class GXDLMSImageTransfer(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSImageTransfer + """ + + def __init__(self, ln="0.0.44.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.IMAGE_TRANSFER, ln, sn) + self.imageBlockSize = 200 + self.imageTransferredBlocksStatus = "" + self.imageFirstNotTransferredBlockNumber = 0 + self.imageTransferEnabled = True + self.imageActivateInfo = list() + self.imageTransferStatus = ImageTransferStatus.IMAGE_TRANSFER_NOT_INITIATED + self.imageSize = 0 + self.imageData = None + + def getValues(self): + return [self.logicalName, + self.imageBlockSize, + self.imageTransferredBlocksStatus, + self.imageFirstNotTransferredBlockNumber, + self.imageTransferEnabled, + self.imageTransferStatus, + self.imageActivateInfo] + + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # ImageBlockSize + if all_ or not self.isRead(2): + attributes.append(2) + # ImageTransferredBlocksStatus + if all_ or not self.isRead(3): + attributes.append(3) + # ImageFirstNotTransferredBlockNumber + if all_ or not self.isRead(4): + attributes.append(4) + # ImageTransferEnabled + if all_ or not self.isRead(5): + attributes.append(5) + # ImageTransferStatus + if all_ or not self.isRead(6): + attributes.append(6) + # ImageActivateInfo + if all_ or not self.isRead(7): + attributes.append(7) + return attributes + + def getAttributeCount(self): + return 7 + + def getMethodCount(self): + return 4 + + def invoke(self, settings, e): + # Image transfer initiate + if e.index == 1: + self.imageFirstNotTransferredBlockNumber = 0 + self.imageTransferredBlocksStatus = "" + value = e.parameters + imageIdentifier = value[0] + self.imageSize = value[1] + self.imageTransferStatus = ImageTransferStatus.IMAGE_TRANSFER_INITIATED + list_ = list() + list_.append(self.imageActivateInfo) + item = None + for it in self.imageActivateInfo: + if it.identification == imageIdentifier: + item = it + break + if item is None: + item = GXDLMSImageActivateInfo() + list_.append(item) + item.size = self.imageSize + item.identification = imageIdentifier + self.imageActivateInfo = list_ + cnt = int((self.imageSize / self.imageBlockSize)) + if self.imageSize % self.imageBlockSize != 0: + cnt += 1 + sb = "" + pos = 0 + while pos < cnt: + sb += '0' + pos += 1 + self.imageTransferredBlocksStatus = sb + return None + if e.index == 2: + # Image block transfer + value = e.parameters + imageIndex = (value[0]).longValue() + tmp = self.imageTransferredBlocksStatus.encode() + tmp[int(imageIndex)] = '1' + self.imageTransferredBlocksStatus = str(tmp) + self.imageFirstNotTransferredBlockNumber = imageIndex + 1 + self.imageData.put(imageIndex, value[1]) + self.imageTransferStatus = ImageTransferStatus.IMAGE_TRANSFER_INITIATED + return None + if e.index == 3: + # Image verify. + return None + if e.index == 4: + return None + e.error = ErrorCode.READ_WRITE_DENIED + return None + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.UINT32 + elif index == 3: + ret = DataType.BITSTRING + elif index == 4: + ret = DataType.UINT32 + elif index == 5: + ret = DataType.BOOLEAN + elif index == 6: + ret = DataType.ENUM + elif index == 7: + ret = DataType.ARRAY + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.imageBlockSize + elif e.index == 3: + ret = self.imageTransferredBlocksStatus + elif e.index == 4: + ret = self.imageFirstNotTransferredBlockNumber + elif e.index == 5: + ret = self.imageTransferEnabled + elif e.index == 6: + ret = self.imageTransferStatus + elif e.index == 7: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + # Count + data.setUInt8(len(self.imageActivateInfo)) + for it in self.imageActivateInfo: + data.setUInt8(DataType.STRUCTURE) + # Item count. + data.setUInt8(int(3)) + _GXCommon.setData(settings, data, DataType.UINT32, it.size) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, it.identification) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, it.signature) + ret = data + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if e.value is None: + self.imageBlockSize = 0 + else: + self.imageBlockSize = e.value + elif e.index == 3: + self.imageTransferredBlocksStatus = str(e.value) + elif e.index == 4: + if e.value is None: + self.imageFirstNotTransferredBlockNumber = 0 + else: + self.imageFirstNotTransferredBlockNumber = e.value + elif e.index == 5: + if e.value is None: + self.imageTransferEnabled = False + else: + self.imageTransferEnabled = e.value + elif e.index == 6: + #pylint: disable=bad-option-value,redefined-variable-type + if e.value is None: + self.imageTransferStatus = ImageTransferStatus.IMAGE_TRANSFER_NOT_INITIATED + else: + self.imageTransferStatus = e.value + elif e.index == 7: + self.imageActivateInfo = [] + if e.value: + for it in e.value: + item = GXDLMSImageActivateInfo() + item.size = it[0] + item.identification = it[1] + item.signature = it[2] + self.imageActivateInfo.append(item) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def imageTransferInitiate(self, client, imageIdentifier, imageSize): + if self.imageBlockSize == 0: + raise ValueError("Invalid image block size.") + if self.imageBlockSize > client.maxReceivePDUSize: + raise ValueError("Image block size is bigger than max PDU size.") + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(None, data, DataType.OCTET_STRING, _GXCommon.getBytes(imageIdentifier)) + _GXCommon.setData(None, data, DataType.UINT32, imageSize) + return client.method(self, 1, data, DataType.ARRAY) + + def imageBlockTransfer(self, client, imageBlockValue, imageBlockCount): + cnt = int(len(imageBlockValue) / self.imageBlockSize) + if (len(imageBlockValue) % self.imageBlockSize) != 0: + cnt += 1 + if imageBlockCount: + imageBlockCount[0] = cnt + packets = list() + pos = 0 + while pos != cnt: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(None, data, DataType.UINT32, pos) + tmp = None + len_ = len(imageBlockValue) - ((pos + 1) * self.imageBlockSize) + # If last packet + if len_ < 0: + tmp = imageBlockValue[pos * self.imageBlockSize:] + else: + tmp = imageBlockValue[pos * self.imageBlockSize: pos * self.imageBlockSize + self.imageBlockSize] + _GXCommon.setData(None, data, DataType.OCTET_STRING, tmp) + packets.append(client.method(self, 2, data, DataType.ARRAY)) + pos += 1 + return packets + + def imageVerify(self, client): + return client.method(self, 3, int(0), DataType.INT8) + + def imageActivate(self, client): + return client.method(self, 4, int(0), DataType.INT8) + + def load(self, reader): + self.imageBlockSize = reader.readElementContentAsInt("ImageBlockSize") + self.imageTransferredBlocksStatus = reader.readElementContentAsString("ImageTransferredBlocksStatus") + self.imageFirstNotTransferredBlockNumber = reader.readElementContentAsLong("ImageFirstNotTransferredBlockNumber") + self.imageTransferEnabled = reader.readElementContentAsInt("ImageTransferEnabled") != 0 + self.imageTransferStatus = reader.readElementContentAsInt("ImageTransferStatus") + self.imageActivateInfo = [] + if reader.isStartElement("ImageActivateInfo", True): + while reader.isStartElement("Item", True): + it = GXDLMSImageActivateInfo() + it.size = reader.readElementContentAsULong("Size") + it.identification = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Identification")) + it.signature = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Signature")) + self.imageActivateInfo.append(it) + reader.readEndElement("ImageActivateInfo") + + def save(self, writer): + writer.writeElementString("ImageBlockSize", self.imageBlockSize) + writer.writeElementString("ImageTransferredBlocksStatus", self.imageTransferredBlocksStatus) + writer.writeElementString("ImageFirstNotTransferredBlockNumber", self.imageFirstNotTransferredBlockNumber) + writer.writeElementString("ImageTransferEnabled", self.imageTransferEnabled) + writer.writeElementString("ImageTransferStatus", int(self.imageTransferStatus)) + writer.writeStartElement("ImageActivateInfo") + if self.imageActivateInfo: + for it in self.imageActivateInfo: + writer.writeStartElement("Item") + writer.writeElementString("Size", it.size) + writer.writeElementString("Identification", GXByteBuffer.hex(it.identification, False)) + writer.writeElementString("Signature", GXByteBuffer.hex(it.signature, False)) + writer.writeEndElement() + writer.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIp4Setup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIp4Setup.py new file mode 100644 index 0000000..de46f46 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIp4Setup.py @@ -0,0 +1,296 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import socket +import struct +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .GXDLMSIp4SetupIpOption import GXDLMSIp4SetupIpOption + +# pylint: disable=too-many-instance-attributes +class GXDLMSIp4Setup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSIp4Setup + """ + def __init__(self, ln="0.0.25.1.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSIp4Setup, self).__init__(ObjectType.IP4_SETUP, ln, sn) + self.dataLinkLayerReference = None + self.ipAddress = None + self.multicastIPAddress = list() + self.ipOptions = list() + self.subnetMask = "" + self.gatewayIPAddress = "" + self.useDHCP = False + self.primaryDNSAddress = "" + self.secondaryDNSAddress = "" + + def getValues(self): + return [self.logicalName, + self.dataLinkLayerReference, + self.ipAddress, + self.multicastIPAddress, + self.ipOptions, + self.subnetMask, + self.gatewayIPAddress, + self.useDHCP, + self.primaryDNSAddress, + self.secondaryDNSAddress] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # DataLinkLayerReference + if all_ or not self.isRead(2): + attributes.append(2) + # IPAddress + if all_ or self.canRead(3): + attributes.append(3) + # MulticastIPAddress + if all_ or self.canRead(4): + attributes.append(4) + # IPOptions + if all_ or self.canRead(5): + attributes.append(5) + # SubnetMask + if all_ or self.canRead(6): + attributes.append(6) + # GatewayIPAddress + if all_ or self.canRead(7): + attributes.append(7) + # UseDHCP + if all_ or not self.isRead(8): + attributes.append(8) + # PrimaryDNSAddress + if all_ or self.canRead(9): + attributes.append(9) + # SecondaryDNSAddress + if all_ or self.canRead(10): + attributes.append(10) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 10 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 3 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.OCTET_STRING + elif index == 3: + ret = DataType.UINT32 + elif index == 4: + ret = DataType.ARRAY + elif index == 5: + ret = DataType.ARRAY + elif index == 6: + ret = DataType.UINT32 + elif index == 7: + ret = DataType.UINT32 + elif index == 8: + ret = DataType.BOOLEAN + elif index == 9: + ret = DataType.UINT32 + elif index == 10: + ret = DataType.UINT32 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = _GXCommon.logicalNameToBytes(self.dataLinkLayerReference) + elif e.index == 3: + ret = struct.unpack("!I", socket.inet_aton(self.ipAddress))[0] + elif e.index == 4: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.multicastIPAddress: + _GXCommon.setObjectCount(0, data) + else: + _GXCommon.setObjectCount(len(self.multicastIPAddress), data) + for it in self.multicastIPAddress: + _GXCommon.setData(settings, data, DataType.UINT16, struct.unpack("!I", socket.inet_aton(it))[0]) + ret = data + elif e.index == 5: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.ipOptions: + data.setUInt8(0) + else: + _GXCommon.setObjectCount(len(self.ipOptions), data) + for it in self.ipOptions: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.UINT8, it.type_) + _GXCommon.setData(settings, data, DataType.UINT8, it.length) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, it.data) + ret = data.array() + elif e.index == 6: + ret = struct.unpack("!I", socket.inet_aton(self.subnetMask))[0] + elif e.index == 7: + ret = struct.unpack("!I", socket.inet_aton(self.gatewayIPAddress))[0] + elif e.index == 8: + ret = self.useDHCP + elif e.index == 9: + ret = struct.unpack("!I", socket.inet_aton(self.primaryDNSAddress))[0] + elif e.index == 10: + ret = struct.unpack("!I", socket.inet_aton(self.secondaryDNSAddress))[0] + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if isinstance(e.value, str): + self.dataLinkLayerReference = e.value + else: + self.dataLinkLayerReference = _GXCommon.toLogicalName(e.value) + elif e.index == 3: + self.ipAddress = socket.inet_ntoa(struct.pack("!I", e.value)) + elif e.index == 4: + self.multicastIPAddress = [] + if e.value: + for it in e.value: + self.multicastIPAddress.append(socket.inet_ntoa(struct.pack("!I", it))) + elif e.index == 5: + self.ipOptions = [] + if e.value: + for it in e.value: + item = GXDLMSIp4SetupIpOption() + item.type_ = it[0] + item.length = it[1] + item.data = it[2] + self.ipOptions.append(item) + elif e.index == 6: + self.subnetMask = socket.inet_ntoa(struct.pack("!I", e.value)) + elif e.index == 7: + self.gatewayIPAddress = socket.inet_ntoa(struct.pack("!I", e.value)) + elif e.index == 8: + self.useDHCP = e.value + elif e.index == 9: + self.primaryDNSAddress = socket.inet_ntoa(struct.pack("!I", e.value)) + elif e.index == 10: + self.secondaryDNSAddress = socket.inet_ntoa(struct.pack("!I", e.value)) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.dataLinkLayerReference = reader.readElementContentAsString("DataLinkLayerReference") + self.ipAddress = reader.readElementContentAsString("IPAddress") + self.multicastIPAddress = [] + if reader.isStartElement("MulticastIPAddress", True): + while reader.isStartElement("Value", False): + self.multicastIPAddress.append(reader.readElementContentAsString("Value")) + reader.readEndElement("MulticastIPAddress") + self.ipOptions = [] + if reader.isStartElement("IPOptions", True): + while reader.isStartElement("IPOption", True): + it = GXDLMSIp4SetupIpOption() + it.type_ = reader.readElementContentAsInt("Type") + it.length = reader.readElementContentAsInt("Length") + it.data = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Data")) + self.ipOptions.append(it) + #Old. This can be removed in some point. + while reader.isStartElement("IPOptions", True): + it = GXDLMSIp4SetupIpOption() + it.type_ = reader.readElementContentAsInt("Type") + it.length = reader.readElementContentAsInt("Length") + it.data = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Data")) + self.ipOptions.append(it) + reader.readEndElement("IPOptions") + self.subnetMask = reader.readElementContentAsString("SubnetMask") + self.gatewayIPAddress = reader.readElementContentAsString("GatewayIPAddress") + self.useDHCP = reader.readElementContentAsInt("UseDHCP") != 0 + self.primaryDNSAddress = reader.readElementContentAsString("PrimaryDNSAddress") + self.secondaryDNSAddress = reader.readElementContentAsString("SecondaryDNSAddress") + + def save(self, writer): + writer.writeElementString("DataLinkLayerReference", self.dataLinkLayerReference) + writer.writeElementString("IPAddress", self.ipAddress) + writer.writeStartElement("MulticastIPAddress") + if self.multicastIPAddress: + for it in self.multicastIPAddress: + writer.writeElementString("Value", it) + writer.writeEndElement() + writer.writeStartElement("IPOptions") + if self.ipOptions: + for it in self.ipOptions: + writer.writeStartElement("IPOption") + writer.writeElementString("Type", it.type_) + writer.writeElementString("Length", it.length) + writer.writeElementString("Data", GXByteBuffer.hex(it.data)) + writer.writeEndElement() + writer.writeEndElement() + writer.writeElementString("SubnetMask", self.subnetMask) + writer.writeElementString("GatewayIPAddress", self.gatewayIPAddress) + writer.writeElementString("UseDHCP", self.useDHCP) + writer.writeElementString("PrimaryDNSAddress", self.primaryDNSAddress) + writer.writeElementString("SecondaryDNSAddress", self.secondaryDNSAddress) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIp4SetupIpOption.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIp4SetupIpOption.py new file mode 100644 index 0000000..9340ce3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIp4SetupIpOption.py @@ -0,0 +1,42 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSIp4SetupIpOption: + #Constructor. + def __init__(self): + self.type_ = None + self.length = 0 + self.data = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIp6Setup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIp6Setup.py new file mode 100644 index 0000000..c1ce4d3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSIp6Setup.py @@ -0,0 +1,351 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import socket +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import AddressConfigMode +from .GXNeighborDiscoverySetup import GXNeighborDiscoverySetup + +# pylint: disable=too-many-instance-attributes +class GXDLMSIp6Setup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSIp6Setup + """ + def __init__(self, ln="0.0.25.7.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSIp6Setup, self).__init__(ObjectType.IP6_SETUP, ln, sn) + self.addressConfigMode = AddressConfigMode.AUTO + self.dataLinkLayerReference = None + self.unicastIPAddress = list() + self.multicastIPAddress = list() + self.gatewayIPAddress = list() + self.primaryDNSAddress = "" + self.secondaryDNSAddress = "" + self.trafficClass = 0 + self.neighborDiscoverySetup = list() + + def getValues(self): + return [self.logicalName, + self.dataLinkLayerReference, + self.addressConfigMode, + self.unicastIPAddress, + self.multicastIPAddress, + self.gatewayIPAddress, + self.primaryDNSAddress, + self.secondaryDNSAddress, + self.trafficClass, + self.neighborDiscoverySetup] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # DataLinkLayerReference + if all_ or not self.isRead(2): + attributes.append(2) + # AddressConfigMode + if all_ or self.canRead(3): + attributes.append(3) + # UnicastIPAddress + if all_ or self.canRead(4): + attributes.append(4) + # MulticastIPAddress + if all_ or self.canRead(5): + attributes.append(5) + # GatewayIPAddress + if all_ or self.canRead(6): + attributes.append(6) + # PrimaryDNSAddress + if all_ or self.canRead(7): + attributes.append(7) + # SecondaryDNSAddress + if all_ or not self.isRead(8): + attributes.append(8) + # TrafficClass + if all_ or self.canRead(9): + attributes.append(9) + # NeighborDiscoverySetup + if all_ or self.canRead(10): + attributes.append(10) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 10 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 2 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.OCTET_STRING + elif index == 3: + ret = DataType.ENUM + elif index == 4: + ret = DataType.ARRAY + elif index == 5: + ret = DataType.ARRAY + elif index == 6: + ret = DataType.ARRAY + elif index == 7: + ret = DataType.OCTET_STRING + elif index == 8: + ret = DataType.OCTET_STRING + elif index == 9: + ret = DataType.UINT8 + elif index == 10: + ret = DataType.ARRAY + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = _GXCommon.logicalNameToBytes(self.dataLinkLayerReference) + elif e.index == 3: + ret = self.addressConfigMode + elif e.index == 4: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.unicastIPAddress: + _GXCommon.setObjectCount(0, data) + else: + _GXCommon.setObjectCount(len(self.unicastIPAddress), data) + for it in self.unicastIPAddress: + _GXCommon.setData(settings, data, DataType.OCTET_STRING, socket.inet_pton(socket.AF_INET6, it)) + ret = data + elif e.index == 5: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.multicastIPAddress: + _GXCommon.setObjectCount(0, data) + else: + _GXCommon.setObjectCount(len(self.multicastIPAddress), data) + for it in self.multicastIPAddress: + _GXCommon.setData(settings, data, DataType.OCTET_STRING, socket.inet_pton(socket.AF_INET6, it)) + ret = data + elif e.index == 6: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.gatewayIPAddress: + _GXCommon.setObjectCount(0, data) + else: + _GXCommon.setObjectCount(len(self.gatewayIPAddress), data) + for it in self.gatewayIPAddress: + _GXCommon.setData(settings, data, DataType.OCTET_STRING, socket.inet_pton(socket.AF_INET6, it)) + ret = data + elif e.index == 7: + if not self.primaryDNSAddress: + ret = None + else: + ret = socket.inet_pton(socket.AF_INET6, self.primaryDNSAddress) + elif e.index == 8: + if not self.secondaryDNSAddress: + ret = None + else: + ret = socket.inet_pton(socket.AF_INET6, self.secondaryDNSAddress) + elif e.index == 9: + ret = self.trafficClass + elif e.index == 10: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if self.neighborDiscoverySetup is None: + # Object count is zero. + data.setUInt8(0) + else: + _GXCommon.setObjectCount(len(self.neighborDiscoverySetup), data) + for it in self.neighborDiscoverySetup: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.UINT8, it.maxRetry) + _GXCommon.setData(settings, data, DataType.UINT16, it.retryWaitTime) + _GXCommon.setData(settings, data, DataType.UINT32, it.sendPeriod) + ret = data + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if isinstance(e.value, str): + self.dataLinkLayerReference = e.value.__str__() + else: + self.dataLinkLayerReference = _GXCommon.toLogicalName(e.value) + elif e.index == 3: + self.addressConfigMode = AddressConfigMode(e.value) + elif e.index == 4: + self.unicastIPAddress = [] + if e.value: + # This fails in Python 2.7.4. Update to 2.7.6 + # https://bugs.python.org/issue10212 + for it in e.value: + self.unicastIPAddress.append(socket.inet_ntop(socket.AF_INET6, it)) + elif e.index == 5: + self.multicastIPAddress = [] + if e.value: + # This fails in Python 2.7.4. Update to 2.7.6 + # https://bugs.python.org/issue10212 + for it in e.value: + self.multicastIPAddress.append(socket.inet_ntop(socket.AF_INET6, it)) + elif e.index == 6: + self.gatewayIPAddress = [] + if e.value: + # This fails in Python 2.7.4. Update to 2.7.6 + # https://bugs.python.org/issue10212 + for it in e.value: + self.gatewayIPAddress.append(socket.inet_ntop(socket.AF_INET6, it)) + elif e.index == 7: + if not e.value: + self.primaryDNSAddress = None + else: + self.primaryDNSAddress = socket.inet_ntop(socket.AF_INET6, e.value) + elif e.index == 8: + if not e.value: + self.secondaryDNSAddress = None + else: + # This fails in Python 2.7.4. Update to 2.7.6 + # https://bugs.python.org/issue10212 + self.secondaryDNSAddress = socket.inet_ntop(socket.AF_INET6, e.value) + elif e.index == 9: + self.trafficClass = e.value + elif e.index == 10: + self.neighborDiscoverySetup = [] + if e.value: + for it in e.value: + v = GXNeighborDiscoverySetup() + v.maxRetry = it[0] + v.retryWaitTime = it[1] + v.sendPeriod = it[2] + self.neighborDiscoverySetup.append(v) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + @classmethod + def loadIPAddress(cls, reader, name): + list_ = list() + if reader.isStartElement(name, True): + while reader.isStartElement("Value", False): + list_.append(reader.readElementContentAsString("Value")) + reader.readEndElement(name) + return list_ + + @classmethod + def loadNeighborDiscoverySetup(cls, reader, name): + list_ = list() + if reader.isStartElement(name, True): + while reader.isStartElement("Item", True): + it = GXNeighborDiscoverySetup() + list_.append(it) + it.maxRetry = reader.readElementContentAsInt("MaxRetry") + it.retryWaitTime = reader.readElementContentAsInt("RetryWaitTime") + it.sendPeriod = reader.readElementContentAsInt("SendPeriod") + reader.readEndElement(name) + return list_ + + def load(self, reader): + self.dataLinkLayerReference = reader.readElementContentAsString("DataLinkLayerReference") + self.addressConfigMode = reader.readElementContentAsInt("AddressConfigMode") + self.unicastIPAddress = self.loadIPAddress(reader, "UnicastIPAddress") + self.multicastIPAddress = self.loadIPAddress(reader, "MulticastIPAddress") + self.gatewayIPAddress = self.loadIPAddress(reader, "GatewayIPAddress") + self.primaryDNSAddress = reader.readElementContentAsString("PrimaryDNSAddress") + self.secondaryDNSAddress = reader.readElementContentAsString("SecondaryDNSAddress") + self.trafficClass = reader.readElementContentAsInt("TrafficClass") + self.neighborDiscoverySetup = self.loadNeighborDiscoverySetup(reader, "NeighborDiscoverySetup") + + @classmethod + def saveIPAddress(cls, writer, list_, name): + writer.writeStartElement(name) + if list_: + for it in list_: + writer.writeElementString("Value", it) + writer.writeEndElement() + + @classmethod + def saveNeighborDiscoverySetup(cls, writer, list_, name): + writer.writeStartElement(name) + if list_: + for it in list_: + writer.writeStartElement("Item") + writer.writeElementString("MaxRetry", it.maxRetry) + writer.writeElementString("RetryWaitTime", it.retryWaitTime) + writer.writeElementString("SendPeriod", it.sendPeriod) + writer.writeEndElement() + writer.writeEndElement() + + def save(self, writer): + writer.writeElementString("DataLinkLayerReference", self.dataLinkLayerReference) + writer.writeElementString("AddressConfigMode", int(self.addressConfigMode)) + self.saveIPAddress(writer, self.unicastIPAddress, "UnicastIPAddress") + self.saveIPAddress(writer, self.multicastIPAddress, "MulticastIPAddress") + self.saveIPAddress(writer, self.gatewayIPAddress, "GatewayIPAddress") + writer.writeElementString("PrimaryDNSAddress", self.primaryDNSAddress) + writer.writeElementString("SecondaryDNSAddress", self.secondaryDNSAddress) + writer.writeElementString("TrafficClass", self.trafficClass) + self.saveNeighborDiscoverySetup(writer, self.neighborDiscoverySetup, "NeighborDiscoverySetup") diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSLimiter.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSLimiter.py new file mode 100644 index 0000000..0de1428 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSLimiter.py @@ -0,0 +1,342 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .GXDLMSEmergencyProfile import GXDLMSEmergencyProfile +from .GXDLMSActionItem import GXDLMSActionItem + +# pylint: disable=too-many-instance-attributes +class GXDLMSLimiter(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSLimiter + + """ + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSLimiter, self).__init__(ObjectType.LIMITER, ln, sn) + self.monitoredValue = None + self.monitoredAttributeIndex = 0 + self.thresholdActive = None + self.thresholdNormal = None + self.thresholdEmergency = None + self.minOverThresholdDuration = None + self.minUnderThresholdDuration = None + self.emergencyProfileGroupIDs = list() + self.emergencyProfileActive = None + self.emergencyProfile = GXDLMSEmergencyProfile() + self.actionOverThreshold = GXDLMSActionItem() + self.actionUnderThreshold = GXDLMSActionItem() + + def getValues(self): + return [self.logicalName, + self.monitoredValue, + self.thresholdActive, + self.thresholdNormal, + self.thresholdEmergency, + self.minOverThresholdDuration, + self.minUnderThresholdDuration, + self.emergencyProfile, + self.emergencyProfileGroupIDs, + self.emergencyProfileActive, + [self.actionOverThreshold, self.actionUnderThreshold]] + + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # MonitoredValue + if all_ or self.canRead(2): + attributes.append(2) + # ThresholdActive + if all_ or self.canRead(3): + attributes.append(3) + # ThresholdNormal + if all_ or self.canRead(4): + attributes.append(4) + # ThresholdEmergency + if all_ or self.canRead(5): + attributes.append(5) + # MinOverThresholdDuration + if all_ or self.canRead(6): + attributes.append(6) + # MinUnderThresholdDuration + if all_ or self.canRead(7): + attributes.append(7) + # EmergencyProfile + if all_ or self.canRead(8): + attributes.append(8) + # EmergencyProfileGroup + if all_ or self.canRead(9): + attributes.append(9) + # EmergencyProfileActive + if all_ or self.canRead(10): + attributes.append(10) + # Actions + if all_ or self.canRead(11): + attributes.append(11) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 11 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + #pylint: disable=super-with-arguments + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.STRUCTURE + elif index == 3: + ret = super(GXDLMSLimiter, self).getDataType(index) + elif index == 4: + ret = super(GXDLMSLimiter, self).getDataType(index) + elif index == 5: + ret = super(GXDLMSLimiter, self).getDataType(index) + elif index == 6: + ret = DataType.UINT32 + elif index == 7: + ret = DataType.UINT32 + elif index == 8: + ret = DataType.STRUCTURE + elif index == 9: + ret = DataType.ARRAY + elif index == 10: + ret = DataType.BOOLEAN + elif index == 11: + ret = DataType.STRUCTURE + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + if self.monitoredValue is None: + _GXCommon.setData(settings, data, DataType.INT16, int(0)) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(None)) + _GXCommon.setData(settings, data, DataType.INT8, 0) + else: + _GXCommon.setData(settings, data, DataType.INT16, int(self.monitoredValue.objectType)) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(self.monitoredValue.logicalName)) + _GXCommon.setData(settings, data, DataType.INT8, self.monitoredAttributeIndex) + ret = data + elif e.index == 3: + ret = self.thresholdActive + elif e.index == 4: + ret = self.thresholdNormal + elif e.index == 5: + ret = self.thresholdEmergency + elif e.index == 6: + ret = self.minOverThresholdDuration + elif e.index == 7: + ret = self.minUnderThresholdDuration + elif e.index == 8: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.UINT16, self.emergencyProfile.id) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, self.emergencyProfile.activationTime) + _GXCommon.setData(settings, data, DataType.UINT32, self.emergencyProfile.duration) + ret = data + elif e.index == 9: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + data.setUInt8(len(self.emergencyProfileGroupIDs)) + for it in self.emergencyProfileGroupIDs: + _GXCommon.setData(settings, data, DataType.UINT16, it) + ret = data + elif e.index == 10: + ret = self.emergencyProfileActive + elif e.index == 11: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(self.actionOverThreshold.logicalName)) + _GXCommon.setData(settings, data, DataType.UINT16, self.actionOverThreshold.scriptSelector) + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(self.actionUnderThreshold.logicalName)) + _GXCommon.setData(settings, data, DataType.UINT16, self.actionUnderThreshold.scriptSelector) + ret = data + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + ot = e.value[0] + ln = _GXCommon.toLogicalName(e.value[1]) + self.monitoredAttributeIndex = e.value[2] + self.monitoredValue = settings.objects.findByLN(ot, ln) + elif e.index == 3: + self.thresholdActive = e.value + elif e.index == 4: + self.thresholdNormal = e.value + elif e.index == 5: + self.thresholdEmergency = e.value + elif e.index == 6: + self.minOverThresholdDuration = e.value + elif e.index == 7: + self.minUnderThresholdDuration = e.value + elif e.index == 8: + self.emergencyProfile.id = e.value[0] + self.emergencyProfile.activationTime = _GXCommon.changeType(settings, e.value[1], DataType.DATETIME) + self.emergencyProfile.duration = e.value[2] + elif e.index == 9: + self.emergencyProfileGroupIDs = [] + if e.value: + for it in e.value: + self.emergencyProfileGroupIDs.append(it) + elif e.index == 10: + self.emergencyProfileActive = e.value + elif e.index == 11: + self.actionOverThreshold.logicalName = _GXCommon.toLogicalName(e.value[0][0]) + self.actionOverThreshold.scriptSelector = e.value[0][1] + self.actionUnderThreshold.logicalName = _GXCommon.toLogicalName(e.value[1][0]) + self.actionUnderThreshold.scriptSelector = e.value[1][1] + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + if reader.isStartElement("MonitoredValue", True): + ot = reader.readElementContentAsInt("ObjectType") + ln = reader.readElementContentAsString("LN") + if ot != ObjectType.NONE and ln: + self.monitoredValue = reader.objects.findByLN(ot, ln) + # If item is not serialized yet. + if self.monitoredValue is None: + self.monitoredValue = _GXObjectFactory.createObject(ot) + self.monitoredValue.logicalName = ln + reader.readEndElement("MonitoredValue") + self.thresholdActive = reader.readElementContentAsObject("ThresholdActive", None, self, 3) + self.thresholdNormal = reader.readElementContentAsObject("ThresholdNormal", None, self, 4) + self.thresholdEmergency = reader.readElementContentAsObject("ThresholdEmergency", None, self, 5) + self.minOverThresholdDuration = reader.readElementContentAsInt("MinOverThresholdDuration") + self.minUnderThresholdDuration = reader.readElementContentAsInt("MinUnderThresholdDuration") + if reader.isStartElement("EmergencyProfile", True): + self.emergencyProfile.id = reader.readElementContentAsInt("ID") + self.emergencyProfile.activationTime = reader.readElementContentAsDateTime("Time") + self.emergencyProfile.duration = reader.readElementContentAsInt("Duration") + reader.readEndElement("EmergencyProfile") + self.emergencyProfileGroupIDs = [] + if reader.isStartElement("EmergencyProfileGroupIDs", True): + while reader.isStartElement("Value", False): + self.emergencyProfileGroupIDs.append(reader.readElementContentAsInt("Value")) + reader.readEndElement("EmergencyProfileGroupIDs") + self.emergencyProfileActive = reader.readElementContentAsInt("Active") != 0 + if reader.isStartElement("ActionOverThreshold", True): + self.actionOverThreshold.logicalName = reader.readElementContentAsString("LN") + self.actionOverThreshold.scriptSelector = reader.readElementContentAsInt("ScriptSelector") + reader.readEndElement("ActionOverThreshold") + if reader.isStartElement("ActionUnderThreshold", True): + self.actionUnderThreshold.logicalName = reader.readElementContentAsString("LN") + self.actionUnderThreshold.scriptSelector = reader.readElementContentAsInt("ScriptSelector") + reader.readEndElement("ActionUnderThreshold") + + def save(self, writer): + if self.monitoredValue: + writer.writeStartElement("MonitoredValue") + writer.writeElementString("ObjectType", int(self.monitoredValue.objectType)) + writer.writeElementString("LN", self.monitoredValue.logicalName) + writer.writeEndElement() + writer.writeElementObject("ThresholdActive", self.thresholdActive, self.getDataType(3), self.getUIDataType(3)) + writer.writeElementObject("ThresholdNormal", self.thresholdNormal, self.getDataType(4), self.getUIDataType(4)) + writer.writeElementObject("ThresholdEmergency", self.thresholdEmergency, self.getDataType(5), self.getUIDataType(5)) + writer.writeElementString("MinOverThresholdDuration", self.minOverThresholdDuration) + writer.writeElementString("MinUnderThresholdDuration", self.minUnderThresholdDuration) + if self.emergencyProfile: + writer.writeStartElement("EmergencyProfile") + writer.writeElementString("ID", self.emergencyProfile.id) + writer.writeElementString("Time", self.emergencyProfile.activationTime) + writer.writeElementString("Duration", self.emergencyProfile.duration) + writer.writeEndElement() + writer.writeStartElement("EmergencyProfileGroupIDs") + if self.emergencyProfileGroupIDs: + for it in self.emergencyProfileGroupIDs: + writer.writeElementString("Value", it) + writer.writeEndElement() + writer.writeElementString("Active", self.emergencyProfileActive) + if self.actionOverThreshold: + writer.writeStartElement("ActionOverThreshold") + writer.writeElementString("LN", self.actionOverThreshold.logicalName) + writer.writeElementString("ScriptSelector", self.actionOverThreshold.scriptSelector) + writer.writeEndElement() + if self.actionUnderThreshold: + writer.writeStartElement("ActionUnderThreshold") + writer.writeElementString("LN", self.actionUnderThreshold.logicalName) + writer.writeElementString("ScriptSelector", self.actionUnderThreshold.scriptSelector) + writer.writeEndElement() + + def postLoad(self, reader): + # Upload Monitored Value after load. + if self.monitoredValue: + target = reader.objects.findByLN(self.monitoredValue.objectType, self.monitoredValue.logicalName) + if target != self.monitoredValue: + self.monitoredValue = target diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSLlcSscsSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSLlcSscsSetup.py new file mode 100644 index 0000000..6e70b2e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSLlcSscsSetup.py @@ -0,0 +1,143 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType + +# pylint: disable=too-many-instance-attributes +class GXDLMSLlcSscsSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSLlcSscsSetup + """ + + def __init__(self, ln="0.0.28.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.LLC_SSCS_SETUP, ln, sn) + self.serviceNodeAddress = 0 + self.baseNodeAddress = 0 + + def getValues(self): + return [self.logicalName, + self.serviceNodeAddress, + self.baseNodeAddress] + + def reset(self, client): + """Reset value.""" + return client.method(self, 1, 0, DataType.INT8) + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # ServiceNodeAddress + if all_ or self.canRead(2): + attributes.append(2) + # BaseNodeAddress + if all_ or self.canRead(3): + attributes.append(3) + return attributes + + def invoke(self, settings, e): + # Resets the value to the default value. + # The default value is an instance specific constant. + if e.index == 1: + self.serviceNodeAddress = 0xFFE + self.baseNodeAddress = 0 + else: + e.error = ErrorCode.READ_WRITE_DENIED + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 3 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index in (2, 3): + return DataType.UINT16 + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.serviceNodeAddress + if e.index == 3: + return self.baseNodeAddress + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.serviceNodeAddress = e.value + elif e.index == 3: + self.baseNodeAddress = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.serviceNodeAddress = reader.readElementContentAsInt("ServiceNodeAddress") + self.baseNodeAddress = reader.readElementContentAsInt("BaseNodeAddress") + + def save(self, writer): + writer.writeElementString("ServiceNodeAddress", self.serviceNodeAddress) + writer.writeElementString("BaseNodeAddress", self.baseNodeAddress) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMBusClient.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMBusClient.py new file mode 100644 index 0000000..e2338ad --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMBusClient.py @@ -0,0 +1,387 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import MBusEncryptionKeyStatus + +# pylint: disable=too-many-instance-attributes +class GXDLMSMBusClient(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSMBusClient + """ + + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSMBusClient, self).__init__(ObjectType.MBUS_CLIENT, ln, sn) + self.captureDefinition = list() + self.mBusPortReference = None + self.capturePeriod = None + self.primaryAddress = None + self.identificationNumber = None + self.manufacturerID = None + self.dataHeaderVersion = None + self.deviceType = None + self.accessNumber = None + self.status = None + self.alarm = None + self.configuration = 0 + self.encryptionKeyStatus = MBusEncryptionKeyStatus.NO_ENCRYPTION_KEY + self.version = 1 + + def slaveInstall(self, client): + """Installs a slave device.""" + return client.method(self, 1, 0, DataType.INT8) + + def slaveDeInstall(self, client): + """De-installs a slave device.""" + return client.method(self, 2, 0, DataType.INT8) + + def capture(self, client): + """Captures values.""" + return client.method(self, 3, 0, DataType.INT8) + + def resetAlarm(self, client): + """Resets alarm state of the M-Bus slave device.""" + return client.method(self, 4, 0, DataType.INT8) + + def synchronizeClock(self, client): + """Synchronize the clock.""" + return client.method(self, 5, 0, DataType.INT8) + + def sendData(self, client, data): + """Sends data to the M-Bus slave device.""" + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + bb.setUInt8(DataType.STRUCTURE) + _GXCommon.setObjectCount(len(data), bb) + for it in data: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + bb.setUInt8(DataType.OCTET_STRING) + _GXCommon.setObjectCount(it.DataInformation.Length, bb) + bb.set(it.DataInformation) + bb.setUInt8(DataType.OCTET_STRING) + _GXCommon.setObjectCount(it.ValueInformation.Length, bb) + bb.set(it.ValueInformation) + _GXCommon.setData(None, bb, _GXCommon.getDLMSDataType(it.Data), it.Data) + return client.method(self, 6, bb, DataType.ARRAY) + + def setEncryptionKey(self, client, encryptionKey): + """Sets the encryption key in the M-Bus client and enables encrypted communication with the M-Bus slave device.""" + bb = GXByteBuffer() + bb.setUInt8(DataType.OCTET_STRING) + if not encryptionKey: + bb.setUInt8(0) + else: + _GXCommon.setObjectCount(encryptionKey.Length, bb) + bb.set(encryptionKey) + return client.method(self, 7, bb, DataType.ARRAY) + + def transferKey(self, client, encryptionKey): + """Transfers an encryption key to the M-Bus slave device.""" + bb = GXByteBuffer() + bb.setUInt8(DataType.OCTET_STRING) + if not encryptionKey: + bb.setUInt8(0) + else: + _GXCommon.setObjectCount(encryptionKey.Length, bb) + bb.set(encryptionKey) + return client.method(self, 8, bb, DataType.ARRAY) + + def getValues(self): + if self.version == 0: + return [self.logicalName, + self.mBusPortReference, + self.captureDefinition, + self.capturePeriod, + self.primaryAddress, + self.identificationNumber, + self.manufacturerID, + self.dataHeaderVersion, + self.deviceType, + self.accessNumber, + self.status, + self.alarm] + return [self.logicalName, + self.mBusPortReference, + self.captureDefinition, + self.capturePeriod, + self.primaryAddress, + self.identificationNumber, + self.manufacturerID, + self.dataHeaderVersion, + self.deviceType, + self.accessNumber, + self.status, + self.alarm, + self.configuration, + self.encryptionKeyStatus] + + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # MBusPortReference + if all_ or self.canRead(2): + attributes.append(2) + # CaptureDefinition + if all_ or self.canRead(3): + attributes.append(3) + # CapturePeriod + if all_ or self.canRead(4): + attributes.append(4) + # PrimaryAddress + if all_ or self.canRead(5): + attributes.append(5) + # IdentificationNumber + if all_ or self.canRead(6): + attributes.append(6) + # ManufacturerID + if all_ or self.canRead(7): + attributes.append(7) + # Version + if all_ or self.canRead(8): + attributes.append(8) + # DeviceType + if all_ or self.canRead(9): + attributes.append(9) + # AccessNumber + if all_ or self.canRead(10): + attributes.append(10) + # Status + if all_ or self.canRead(11): + attributes.append(11) + # Alarm + if all_ or self.canRead(12): + attributes.append(12) + if self.version != 0: + # Alarm + if all_ or self.canRead(13): + attributes.append(13) + # Alarm + if all_ or self.canRead(14): + attributes.append(14) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + if self.version != 0: + return 14 + return 12 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 8 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.OCTET_STRING + elif index == 3: + ret = DataType.ARRAY + elif index == 4: + ret = DataType.UINT32 + elif index == 5: + ret = DataType.UINT8 + elif index == 6: + ret = DataType.UINT32 + elif index == 7: + ret = DataType.UINT16 + elif index == 8: + ret = DataType.UINT8 + elif index == 9: + ret = DataType.UINT8 + elif index == 10: + ret = DataType.UINT8 + elif index == 11: + ret = DataType.UINT8 + elif index == 12: + ret = DataType.UINT8 + elif index == 13: + ret = DataType.UINT16 + elif index == 14: + ret = DataType.ENUM + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = _GXCommon.logicalNameToBytes(self.mBusPortReference) + elif e.index == 3: + buff = GXByteBuffer() + buff.setUInt8(DataType.ARRAY) + _GXCommon.setObjectCount(len(self.captureDefinition), buff) + for k, v in self.captureDefinition: + buff.setUInt8(DataType.STRUCTURE) + buff.setUInt8(2) + _GXCommon.setData(settings, buff, DataType.UINT8, k) + if not v: + _GXCommon.setData(settings, buff, DataType.OCTET_STRING, None) + else: + _GXCommon.setData(settings, buff, DataType.OCTET_STRING, v.encode()) + ret = buff + elif e.index == 4: + ret = self.capturePeriod + elif e.index == 5: + ret = self.primaryAddress + elif e.index == 6: + ret = self.identificationNumber + elif e.index == 7: + ret = self.manufacturerID + elif e.index == 8: + ret = self.dataHeaderVersion + elif e.index == 9: + ret = self.deviceType + elif e.index == 10: + ret = self.accessNumber + elif e.index == 11: + ret = self.status + elif e.index == 12: + ret = self.alarm + elif e.index == 13: + ret = self.configuration + elif e.index == 14: + ret = self.encryptionKeyStatus + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.mBusPortReference = _GXCommon.toLogicalName(e.value) + elif e.index == 3: + self.captureDefinition = [] + if e.value: + for it in e.value: + self.captureDefinition.append((_GXCommon.changeType(settings, it[0], DataType.OCTET_STRING), _GXCommon.changeType(settings, it[1], DataType.OCTET_STRING))) + elif e.index == 4: + self.capturePeriod = e.value + elif e.index == 5: + self.primaryAddress = e.value + elif e.index == 6: + self.identificationNumber = e.value + elif e.index == 7: + self.manufacturerID = e.value + elif e.index == 8: + self.dataHeaderVersion = e.value + elif e.index == 9: + self.deviceType = e.value + elif e.index == 10: + self.accessNumber = e.value + elif e.index == 11: + self.status = e.value + elif e.index == 12: + self.alarm = e.value + elif e.index == 13: + self.configuration = e.value + elif e.index == 14: + self.encryptionKeyStatus = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.mBusPortReference = reader.readElementContentAsString("MBusPortReference") + self.captureDefinition = [] + if reader.isStartElement("CaptureDefinition", True): + while reader.isStartElement("Item", True): + d = reader.readElementContentAsString("Data") + v = reader.readElementContentAsString("Value") + self.captureDefinition.append((d, v)) + reader.readEndElement("CaptureDefinition") + self.capturePeriod = reader.readElementContentAsInt("CapturePeriod") + self.primaryAddress = reader.readElementContentAsInt("PrimaryAddress") + self.identificationNumber = reader.readElementContentAsInt("IdentificationNumber") + self.manufacturerID = reader.readElementContentAsInt("ManufacturerID") + self.dataHeaderVersion = reader.readElementContentAsInt("DataHeaderVersion") + self.deviceType = reader.readElementContentAsInt("DeviceType") + self.accessNumber = reader.readElementContentAsInt("AccessNumber") + self.status = reader.readElementContentAsInt("Status") + self.alarm = reader.readElementContentAsInt("Alarm") + if self.version != 0: + self.configuration = reader.readElementContentAsInt("Configuration") + self.encryptionKeyStatus = MBusEncryptionKeyStatus(reader.readElementContentAsInt("EncryptionKeyStatus")) + + def save(self, writer): + writer.writeElementString("MBusPortReference", self.mBusPortReference) + writer.writeStartElement("CaptureDefinition") + if self.captureDefinition: + for k, v in self.captureDefinition: + writer.writeStartElement("Item") + writer.writeElementString("Data", k) + writer.writeElementString("Value", v) + writer.writeEndElement() + writer.writeEndElement() + writer.writeElementString("CapturePeriod", self.capturePeriod) + writer.writeElementString("PrimaryAddress", self.primaryAddress) + writer.writeElementString("IdentificationNumber", self.identificationNumber) + writer.writeElementString("ManufacturerID", self.manufacturerID) + writer.writeElementString("DataHeaderVersion", self.dataHeaderVersion) + writer.writeElementString("DeviceType", self.deviceType) + writer.writeElementString("AccessNumber", self.accessNumber) + writer.writeElementString("Status", self.status) + writer.writeElementString("Alarm", self.alarm) + if self.version != 0: + writer.writeElementString("Configuration", self.configuration) + writer.writeElementString("EncryptionKeyStatus", self.encryptionKeyStatus) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMBusMasterPortSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMBusMasterPortSetup.py new file mode 100644 index 0000000..39fd1d2 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMBusMasterPortSetup.py @@ -0,0 +1,119 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType +from .enums import BaudRate + +# pylint: disable=too-many-instance-attributes +class GXDLMSMBusMasterPortSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSMBusMasterPortSetup + """ + + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSMBusMasterPortSetup, self).__init__(ObjectType.MBUS_MASTER_PORT_SETUP, ln, sn) + # The communication speed supported by the port. + self.commSpeed = BaudRate.BAUDRATE_2400 + + def getValues(self): + return [self.logicalName, + self.commSpeed] + + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # CommSpeed + if all_ or self.canRead(2): + attributes.append(2) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 2 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.ENUM + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return self.logicalName + if e.index == 2: + return self.commSpeed + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.commSpeed = BaudRate(e.value) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.commSpeed = BaudRate(reader.readElementContentAsInt("CommSpeed")) + + def save(self, writer): + writer.writeElementString("CommSpeed", int(self.commSpeed)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMBusSlavePortSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMBusSlavePortSetup.py new file mode 100644 index 0000000..f570d6a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMBusSlavePortSetup.py @@ -0,0 +1,179 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType +from .enums import BaudRate, AddressState + +# pylint: disable=too-many-instance-attributes +class GXDLMSMBusSlavePortSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSMBusSlavePortSetup + """ + + # + # Constructor. + # + # @param ln + # Logical Name of the object. + # @param sn + # Short Name of the object. + # + def __init__(self, ln=None, sn=0): + #pylint: disable=super-with-arguments + super(GXDLMSMBusSlavePortSetup, self).__init__(ObjectType.MBUS_SLAVE_PORT_SETUP, ln, sn) + # Defines the baud rate for the opening sequence. + self.defaultBaud = BaudRate.BAUDRATE_300 + # Defines the baud rate for the opening sequence. + self.availableBaud = BaudRate.BAUDRATE_300 + # Defines whether or not the device has been assigned an + # address since last power up of the device. + self.addressState = AddressState.NONE + # Defines the baud rate for the opening sequence. + self.busAddress = 0 + + def getValues(self): + return [self.logicalName, + self.defaultBaud, + self.availableBaud, + self.addressState, + self.busAddress] + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # DefaultBaud + if all_ or not self.isRead(2): + attributes.append(2) + # AvailableBaud + if all_ or not self.isRead(3): + attributes.append(3) + # AddressState + if all_ or not self.isRead(4): + attributes.append(4) + # BusAddress + if all_ or not self.isRead(5): + attributes.append(5) + return attributes + + def getAttributeCount(self): + return 5 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ENUM + elif index == 3: + ret = DataType.ENUM + elif index == 4: + ret = DataType.ENUM + elif index == 5: + ret = DataType.UINT8 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.defaultBaud + if e.index == 3: + return self.availableBaud + if e.index == 4: + return self.addressState + if e.index == 5: + return self.busAddress + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if e.value is None: + self.defaultBaud = BaudRate.BAUDRATE_300 + else: + self.defaultBaud = BaudRate(e.value) + elif e.index == 3: + if e.value is None: + self.availableBaud = BaudRate.BAUDRATE_300 + else: + self.availableBaud = BaudRate(e.value) + elif e.index == 4: + if e.value is None: + self.addressState = AddressState.NONE + else: + self.addressState = AddressState(e.value) + elif e.index == 5: + if e.value is None: + self.busAddress = 0 + else: + self.busAddress = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.defaultBaud = reader.readElementContentAsInt("DefaultBaud") + self.availableBaud = reader.readElementContentAsInt("AvailableBaud") + self.addressState = reader.readElementContentAsInt("AddressState") + self.busAddress = reader.readElementContentAsInt("BusAddress") + + def save(self, writer): + writer.writeElementString("DefaultBaud", int(self.defaultBaud)) + writer.writeElementString("AvailableBaud", int(self.availableBaud)) + writer.writeElementString("AddressState", int(self.addressState)) + writer.writeElementString("BusAddress", self.busAddress) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMacAddressSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMacAddressSetup.py new file mode 100644 index 0000000..e1dace0 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMacAddressSetup.py @@ -0,0 +1,125 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType + +# pylint: disable=too-many-instance-attributes +class GXDLMSMacAddressSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSMacAddressSetup + """ + def __init__(self, ln="0.0.25.2.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSMacAddressSetup, self).__init__(ObjectType.MAC_ADDRESS_SETUP, ln, sn) + self.macAddress = None + + def getValues(self): + return [self.logicalName, + self.macAddress] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # MacAddress + if all_ or not self.isRead(2): + attributes.append(2) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 2 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.OCTET_STRING + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + if not self.macAddress: + return self.macAddress + return GXByteBuffer.hexToBytes(self.macAddress.replace(":", " ")) + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + add = GXByteBuffer.hex(e.value) + self.macAddress = add.replace(" ", ":") + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.macAddress = reader.readElementContentAsString("MacAddress") + + def save(self, writer): + writer.writeElementString("MacAddress", self.macAddress) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSModemConfiguration.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSModemConfiguration.py new file mode 100644 index 0000000..f0d7200 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSModemConfiguration.py @@ -0,0 +1,208 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import BaudRate +from .GXDLMSModemInitialisation import GXDLMSModemInitialisation + +# pylint: disable=too-many-instance-attributes +class GXDLMSModemConfiguration(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSModemConfiguration + """ + + # + # Constructor. + # + # @param ln + # Logical Name of the object. + # @param sn + # Short Name of the object. + # + def __init__(self, ln=None, sn=0): + #pylint: disable=super-with-arguments + super(GXDLMSModemConfiguration, self).__init__(ObjectType.MODEM_CONFIGURATION, ln, sn) + self.initialisationStrings = list() + self.communicationSpeed = BaudRate.BAUDRATE_300 + self.modemProfile = self.defultProfiles() + + @classmethod + def defultProfiles(cls): + return ["OK", "CONNECT", "RING", "NO CARRIER", "ERROR", + "CONNECT 1200", "NO DIAL TONE", "BUSY", "NO ANSWER", + "CONNECT 600", "CONNECT 2400", "CONNECT 4800", "CONNECT 9600", + "CONNECT 14 400", "CONNECT 28 800", "CONNECT 33 600", + "CONNECT 56 000"] + + def getValues(self): + return [self.logicalName, + self.communicationSpeed, + self.initialisationStrings, + self.modemProfile] + + # + # Returns collection of attributes to read. If attribute is static + # and already read or device is returned HW error it is not returned. + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # CommunicationSpeed + if all_ or not self.isRead(2): + attributes.append(2) + # InitialisationStrings + if all_ or not self.isRead(3): + attributes.append(3) + # ModemProfile + if all_ or not self.isRead(4): + attributes.append(4) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 4 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.ENUM + if index == 3: + return DataType.ARRAY + if index == 4: + return DataType.ARRAY + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.communicationSpeed + if e.index == 3: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + # Add count + cnt = 0 + if self.initialisationStrings: + cnt = len(self.initialisationStrings) + _GXCommon.setObjectCount(cnt, data) + if cnt != 0: + for it in self.initialisationStrings: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + # Count + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.getBytes(it.request)) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.getBytes(it.response)) + _GXCommon.setData(settings, data, DataType.UINT16, it.delay) + return data + if e.index == 4: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + # Add count + cnt = len(self.modemProfile) + _GXCommon.setObjectCount(cnt, data) + if cnt != 0: + for it in self.modemProfile: + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.getBytes(it)) + return data + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.communicationSpeed = e.value + elif e.index == 3: + self.initialisationStrings = [] + if e.value: + for it in e.value: + item = GXDLMSModemInitialisation() + item.request = it[0].decode("utf-8") + item.response = it[1].decode("utf-8") + if len(it) == 3: + item.delay = it[2] + self.initialisationStrings.append(item) + elif e.index == 4: + self.modemProfile = [] + if e.value: + for it in e.value: + self.modemProfile.append(it.decode("utf-8")) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.communicationSpeed = BaudRate(reader.readElementContentAsInt("CommunicationSpeed")) + if reader.isStartElement("InitialisationStrings", True): + while reader.isStartElement("Initialisation", True): + it = GXDLMSModemInitialisation() + it.request = reader.readElementContentAsString("Request") + it.response = reader.readElementContentAsString("Response") + it.delay = reader.readElementContentAsInt("Delay") + reader.readEndElement("InitialisationStrings") + self.modemProfile = reader.readElementContentAsString("ModemProfile", "").split(';') + + def save(self, writer): + writer.writeElementString("CommunicationSpeed", int(self.communicationSpeed)) + writer.writeStartElement("InitialisationStrings") + if self.initialisationStrings: + for it in self.initialisationStrings: + writer.writeStartElement("Initialisation") + writer.writeElementString("Request", it.request) + writer.writeElementString("Response", it.response) + writer.writeElementString("Delay", it.delay) + writer.writeEndElement() + writer.writeEndElement() + if self.modemProfile: + writer.writeElementString("ModemProfile", ';'.join(self.modemProfile)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSModemInitialisation.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSModemInitialisation.py new file mode 100644 index 0000000..9038a8e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSModemInitialisation.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSModemInitialisation: + # + #Constructor. + # + def __init__(self): + self.request = None + self.response = None + self.delay = 0 + + def __str__(self): + return self.request + " " + self.response + " " + str(self.delay) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMonitoredValue.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMonitoredValue.py new file mode 100644 index 0000000..1181711 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSMonitoredValue.py @@ -0,0 +1,53 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..enums import ObjectType + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSMonitoredValue: + # + #Constructor. + # + def __init__(self): + self.objectType = ObjectType.NONE + self.logicalName = None + self.attributeIndex = 0 + + def update(self, value, index): + self.objectType = value.objectType + self.logicalName = value.logicalName + self.attributeIndex = index + + def __str__(self): + return str(self.objectType) + " " + self.logicalName + " " + str(self.attributeIndex) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSNtpSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSNtpSetup.py new file mode 100644 index 0000000..fe21def --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSNtpSetup.py @@ -0,0 +1,262 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType +from .enums.NtpAuthenticationMethod import NtpAuthenticationMethod +from ..GXByteBuffer import GXByteBuffer +# pylint: disable=too-many-instance-attributes +class GXDLMSNtpSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSNtpSetup + """ + + def __init__(self, ln="0.0.25.10.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.NTP_SETUP, ln, sn) + self.value = None + #Is NTP time synchronisation active. + self.activated = False + # NTP server address. + self.serverAddress = None + # UDP port related to this protocol. + self.port = 123 + # Authentication method. + self.authentication = NtpAuthenticationMethod.NO_SECURITY + # Symmetric keys for authentication. + self.keys = {} + #Client key (NTP server public key). + self.clientKey = None + + def getValues(self): + return [self.logicalName, + self.activated, self.serverAddress, self.port, + self.authentication, self.keys, self.clientKey] + + # Synchronizes the time of the DLMS server with the NTP server. + # client: DLMS client. + # Returns Action bytes. + def synchronize(self, client): + return client.method(self, 1, 0, DataType.INT8) + + # Adds a new symmetric authentication key to authentication key array. + # client: DLMS client. + # id_: Authentication key Id. + # authentication Key. + # Returns Action bytes. + def addAuthenticationKey(self, client, id_, key): + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + bb.setUInt8(DataType.UINT32) + bb.setUInt32(id_) + bb.setUInt8(DataType.OCTET_STRING) + _GXCommon.setObjectCount(key.length, bb) + bb.set(key) + return client.method(self, 2, bb.array(), DataType.STRUCTURE) + + # Remove symmetric authentication key. + # client: DLMS client. + # id_: Authentication key Id. + # Returns Action bytes. + def deleteAuthenticationKey(self, client, id_): + return client.method(self, 3, id_, DataType.INT8) + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Activated + if all_ or self.canRead(2): + attributes.append(2) + # ServerAddress + if all_ or self.canRead(3): + attributes.append(3) + # Port + if all_ or self.canRead(4): + attributes.append(4) + # Authentication + if all_ or self.canRead(5): + attributes.append(5) + # Keys + if all_ or self.canRead(6): + attributes.append(6) + # ClientKey + if all_ or self.canRead(7): + attributes.append(7) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 7 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 3 + + def getUIDataType(self, index): + if index == 3: + return DataType.STRING + #pylint:disable=super-with-arguments + return super(GXDLMSNtpSetup, self).getUIDataType(index) + + def getDataType(self, index): + if index == 1: + dt = DataType.OCTET_STRING + elif index == 2: + dt = DataType.BOOLEAN + elif index == 3: + dt = DataType.OCTET_STRING + elif index == 4: + dt = DataType.UINT16 + elif index == 5: + dt = DataType.ENUM + elif index == 6: + dt = DataType.ARRAY + elif index == 7: + dt = DataType.OCTET_STRING + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return dt + + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.activated + elif e.index == 3: + ret = self.serverAddress + elif e.index == 4: + ret = self.port + elif e.index == 5: + ret = self.authentication + elif e.index == 6: + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + #Add count + _GXCommon.setObjectCount(len(self.keys), bb) + for it in self.keys: + bb.setUInt8(DataType.STRUCTURE) + #Count + bb.setUInt8(2) + bb.setUInt8(DataType.UINT32) + bb.setUInt32(it.key) + bb.setUInt8(DataType.OCTET_STRING) + _GXCommon.setObjectCount(len(it.value), bb) + bb.set(it.value) + ret = bb.array() + elif e.index == 7: + ret = self.clientKey + else: + e.error = ErrorCode.READ_WRITE_DENIED + ret = None + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.activated = e.value + elif e.index == 3: + if isinstance(e.value, bytearray): + self.serverAddress = str(e.value) + elif isinstance(e.value, str): + self.serverAddress = e.value + else: + self.serverAddress = None + elif e.index == 4: + self.port = e.value + elif e.index == 5: + self.authentication = NtpAuthenticationMethod(e.value) + elif e.index == 6: + self.keys.clear() + if e.getValue() : + for it in e.value: + self.keys[it[0]] = it[1] + elif e.index == 7: + self.clientKey = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.activated = reader.readElementContentAsInt("Activated", 1) != 0 + self.serverAddress = reader.readElementContentAsString("ServerAddress", None) + self.port = reader.readElementContentAsInt("Port", 0) + self.authentication = NtpAuthenticationMethod(reader.readElementContentAsInt("Authentication", 0)) + self.keys.clear() + if reader.isStartElement("Keys", True): + while reader.isStartElement("Item", True): + id_ = reader.readElementContentAsLong("ID") + key = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Key")) + self.keys[id_] = key + self.clientKey = GXByteBuffer.hexToBytes(reader.readElementContentAsString("ClientKey", None)) + + def save(self, writer): + writer.writeElementString("Activated", self.activated) + writer.writeElementString("ServerAddress", self.serverAddress) + writer.writeElementString("Port", self.port) + writer.writeElementString("Authentication", int(self.authentication)) + writer.writeStartElement("Keys") + for it in self.keys: + writer.writeStartElement("Item") + writer.writeElementString("ID", str(it.key)) + writer.writeElementString("Key", GXByteBuffer.hex(it.value, False)) + writer.writeEndElement() + writer.writeEndElement() + #Keys + writer.writeElementString("ClientKey", GXByteBuffer.hex(self.clientKey, False)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSObject.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSObject.py new file mode 100644 index 0000000..e729faa --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSObject.py @@ -0,0 +1,308 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from abc import abstractmethod +from ..internal._GXCommon import _GXCommon +from ..manufacturersettings import GXAttributeCollection, GXDLMSAttributeSettings +from ..enums.DataType import DataType +from ..enums.AccessMode import AccessMode +from ..enums.MethodAccessMode import MethodAccessMode +# +# pylint: disable=too-many-public-methods,too-many-instance-attributes,useless-object-inheritance +class GXDLMSObject(object): + # + # Constructor, + # + def __init__(self, ot, ln=None, sn=0): + # DLMS version number. + self.version = 0 + self.objectType = ot + self.shortName = sn + # Description of COSEM object. + self.description = "" + self.attributes = GXAttributeCollection() + self.methodAttributes = GXAttributeCollection() + if ln: + items = ln.split('.') + if len(items) != 6: + raise Exception("Invalid Logical Name.") + # Logical Name of COSEM object. + self.logicalName = ln + self.readTimes = dict() + + # + # Validate logical name. + # value: Logical Name. + # + @classmethod + def validateLogicalName(cls, value): + _GXCommon.logicalNameToBytes(value) + + # + # Is attribute read. This can be used with static attributes to + # make meter + # reading faster. + # + def isRead(self, index): + if not self.canRead(index): + return True + return not self.getLastReadTime(index) + + def canRead(self, index): + return self.getAccess(index) and AccessMode.READ != 0 + + # + # Returns time when attribute was last time read. - + # + # @param attributeIndex + # Attribute index. + # Is attribute read only. + # + def getLastReadTime(self, attributeIndex): + for k, v in self.readTimes.items(): + if k == attributeIndex: + return v + return None + + # + # Set time when attribute was last time read. + # @param attributeIndex Attribute index. + # @param tm Read time. + # + def setLastReadTime(self, attributeIndex, tm): + self.readTimes[attributeIndex] = tm + + # + # Logical or Short Name of DLMS object. + # + def __str__(self): + str_ = None + if self.shortName != 0: + str_ = str(self.shortName) + else: + str_ = self.logicalName + if self.description: + str_ += " " + self.description + return str_ + + # + # Interface type of the COSEM object. + # + def getObjectType(self): + return self.objectType + + # + # The base name of the object, if using SN. When using SN + # referencing, + # retrieves the base name of the DLMS object. When using LN + # referencing, + # the value is 0. + # + # The base name of the object. + # + def getShortName(self): + return self.shortName + + def getName(self): + if self.shortName != 0: + return self.shortName + return self.logicalName + + # + # Logical or Short Name of DLMS object. + # + # Logical or Short Name of DLMS object + # + name = property(getName) + + # + # Object attribute collection. + # + def getAttributes(self): + return self.attributes + + # + # Object method attribute collection. + # + def getMethodAttributes(self): + return self.methodAttributes + + # + # Returns is attribute read only. - + # + # @param index + # Attribute index. + # Is attribute read only. + # + def getAccess(self, index): + if index == 1: + return AccessMode.READ + att = self.attributes.find(index) + if att is None: + return AccessMode.READ_WRITE + return att.access + + # + # Set attribute access. + # + # @param index + # Attribute index. + # @param access + # Attribute access. + # + def setAccess(self, index, access): + att = self.attributes.find(index) + if att is None: + att = GXDLMSAttributeSettings(index) + self.attributes.append(att) + att.access = access + + # + # Returns amount of methods. + # + @abstractmethod + def getMethodCount(self): + raise ValueError("getMethodCount") + + # + # Returns is Method attribute read only. - + # + # @param index + # Method Attribute index. + # Is attribute read only. + # + def getMethodAccess(self, index): + att = self.getMethodAttributes().find(index) + if att: + return att.methodAccess + return MethodAccessMode.ACCESS + + # + # Set Method attribute access. + # + # @param index + # Method index. + # @param access + # Method access mode. + # + def setMethodAccess(self, index, access): + att = self.getMethodAttributes().find(index) + if att is None: + att = GXDLMSAttributeSettings(index) + self.methodAttributes.append(att) + att.methodAccess = access + + + def getDataType(self, index): + att = self.attributes.find(index) + if not att: + return DataType.NONE + return att.type_ + + + + def getUIDataType(self, index): + att = self.attributes.find(index) + if not att: + return DataType.NONE + return att.uiType + + + # + # Amount of attributes. + # + @abstractmethod + def getAttributeCount(self): + raise ValueError("getAttributeCount") + + # + # Object values as an array. + # + @abstractmethod + def getValues(self): + raise ValueError("getValues") + + @abstractmethod + def getValue(self, settings, e): + # + # Get value. + # settings: DLMS settings. + # e: Value event parameters. + # + raise ValueError("getValue") + + @abstractmethod + def setValue(self, settings, e): + # + # Set value of given attribute. + # + # settings: DLMS settings. + # e: event parameters. + raise ValueError("setValue") + + # + # Server calls this invokes method. + # @param settings DLMS settings. + # @param e Value event parameters. + # pylint: disable=no-self-use + def invoke(self, settings, e): + #pylint: disable=unused-argument + raise ValueError("invoke") + + def setDataType(self, index, type_): + att = self.attributes.find(index) + if att is None: + att = GXDLMSAttributeSettings(index) + self.attributes.append(att) + att.type_ = type_ + + def setUIDataType(self, index, type_): + att = self.attributes.find(index) + if att is None: + att = GXDLMSAttributeSettings(index) + self.attributes.append(att) + att.uiType = type_ + + def setStatic(self, index, isStatic): + att = self.attributes.find(index) + if att is None: + att = GXDLMSAttributeSettings(index) + self.attributes.append(att) + att.static = isStatic + + def getStatic(self, index): + att = self.attributes.find(index) + if att is None: + att = GXDLMSAttributeSettings(index) + self.attributes.append(att) + return att.static diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSObjectCollection.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSObjectCollection.py new file mode 100644 index 0000000..c403611 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSObjectCollection.py @@ -0,0 +1,165 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import xml.etree.cElementTree as ET +from xml.dom import minidom +from .GXDLMSObject import GXDLMSObject +from ..enums import ObjectType +from .GXXmlWriter import GXXmlWriter +from .GXXmlReader import GXXmlReader +from ..GXDLMSConverter import GXDLMSConverter + +class GXDLMSObjectCollection(list): + """ + Collection of DLMS objects. + """ + + # + # Constructor. + # + # forParent: Parent object. + # + def __init__(self, forParent=None): + #pylint: disable=super-with-arguments + super(GXDLMSObjectCollection, self).__init__() + self.parent = forParent + + def append(self, item): + if not isinstance(item, GXDLMSObject): + raise TypeError('item is not of type GXDLMSObject') + #pylint: disable=super-with-arguments + super(GXDLMSObjectCollection, self).append(item) + item.parent = self + + def extend(self, items): + #pylint: disable=super-with-arguments + if not isinstance(items, GXDLMSObjectCollection): + raise TypeError('items is not of type GXDLMSObjectCollection') + for it in items: + super(GXDLMSObjectCollection, self).append(it) + it.parent = self + + def getObjects(self, type_): + if isinstance(type_, (int, ObjectType)): + type_ = [type_] + items = GXDLMSObjectCollection() + for it in self: + if it.objectType in type_: + items.append(it) + return items + + def findByLN(self, type_, ln): + for it in self: + if type_ in (ObjectType.NONE, it.objectType) and it.logicalName.strip() == ln: + return it + return None + + def findBySN(self, sn): + for it in self: + if it.shortName == sn: + return it + return None + + def __str__(self): + str_ = '[' + for it in self: + if str_: + str_ += ", " + str_ += it + str_ += ']' + return str_ + + @classmethod + def load(cls, file_): + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + obj = None + target = None + type_ = None + reader = GXXmlReader(file_, GXDLMSObjectCollection()) + reader.getNext() + while not reader.isEOF(): + if reader.isStartElement(): + target = reader.name() + if "Objects".lower() == target.lower(): + reader.getNext() + elif target.find("GXDLMS") == 0: + try: + type_ = GXDLMSConverter.valueOfObjectType(target[6:]) + except Exception: + # pylint:disable=raise-missing-from + raise ValueError("Invalid object type: " + target + ".") + obj = _GXObjectFactory.createObject(type_) + obj.version = 0 + reader.objects.append(obj) + reader.getNext() + elif "SN".lower() == target.lower(): + obj.shortName = reader.readElementContentAsInt("SN") + elif "LN".lower() == target.lower(): + obj.logicalName = reader.readElementContentAsString("LN") + elif "Description".lower() == target.lower(): + obj.description = reader.readElementContentAsString("Description") + elif "Version".lower() == target.lower(): + obj.version = reader.readElementContentAsInt("Version") + else: + # pylint:disable=broad-except + try: + obj.load(reader) + except Exception as ex: + print("Failed to load object " + str(obj) + " " +str(ex)) + obj = None + else: + reader.read() + for obj in reader.objects: + obj.postLoad(reader) + return reader.objects + + def save(self, name, settings=None): + writer = GXXmlWriter() + objects = ET.Element("Objects") + for it in self: + node = ET.SubElement(objects, "GXDLMS" + GXDLMSConverter.objectTypeToString(it.objectType)) + if it.shortName != 0: + ET.SubElement(node, "SN").text = str(it.shortName) + ET.SubElement(node, "LN").text = it.logicalName + if it.version != 0: + ET.SubElement(node, "Version").text = str(it.version) + if it.description: + ET.SubElement(node, "Description").text = it.description + if not settings or settings.values: + writer.objects = [] + writer.objects.append(node) + it.save(writer) + str_ = minidom.parseString(ET.tostring(objects, encoding='utf-8', method='xml')).toprettyxml(indent=" ") + with open(name, "w") as f: + f.write(str_) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSObjectDefinition.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSObjectDefinition.py new file mode 100644 index 0000000..9300a8d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSObjectDefinition.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSObjectDefinition: + + def __init__(self, ot=None, ln=None): + """ + Constructor + """ + self.objectType = ot + self.logicalName = ln + + def __str__(self): + return str(self.objectType) + " " + self.logicalName diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSParameterMonitor.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSParameterMonitor.py new file mode 100644 index 0000000..cf8204b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSParameterMonitor.py @@ -0,0 +1,289 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .GXDLMSTarget import GXDLMSTarget + +# pylint: disable=too-many-instance-attributes +class GXDLMSParameterMonitor(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSParameterMonitor + """ + def __init__(self, ln="0.0.16.2.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSParameterMonitor, self).__init__(ObjectType.PARAMETER_MONITOR, ln, sn) + self.parameters = list() + self.captureTime = None + self.changedParameter = GXDLMSTarget() + + def getValues(self): + return [self.logicalName, + self.changedParameter, + self.captureTime, + self.parameters] + + # + # Inserts a new entry in the table. + # + # @param client + # DLMS Client. + # @param entry + # Removed entry. + # If a special day with the same index or with the same date + # as an + # already defined day is inserted, the old entry will be + # overwritten. + # + def insert(self, client, entry): + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + _GXCommon.setData(None, bb, DataType.UINT16, entry.target.objectType) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(entry.target.logicalName)) + _GXCommon.setData(None, bb, DataType.INT8, entry.attributeIndex) + return client.method(self, 1, bb.array(), DataType.ARRAY) + + def delete(self, client, entry): + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + _GXCommon.setData(None, bb, DataType.UINT16, entry.target.objectType) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(entry.target.logicalName)) + _GXCommon.setData(None, bb, DataType.INT8, entry.attributeIndex) + return client.method(self, 2, bb.array(), DataType.ARRAY) + + def invoke(self, settings, e): + if e.index != 1 and e.index != 2: + e.error = ErrorCode.READ_WRITE_DENIED + else: + if e.index == 1: + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + tmp = e.parameters + type_ = ObjectType(tmp[0]) + ln = _GXCommon.toLogicalName(tmp[1]) + index = tmp[2] + for item in self.parameters: + if item.target.objectType == type_ and item.target.logicalName == ln and item.attributeIndex == index: + self.parameters.remove(item) + break + it = GXDLMSTarget() + it.target = settings.objects.findByLN(type_, ln) + if it.target is None: + it.target = _GXObjectFactory.createObject(type_) + it.target.logicalName = ln + it.attributeIndex = index + self.parameters.append(it) + elif e.index == 2: + tmp = e.parameters + type_ = ObjectType(tmp[0]) + ln = _GXCommon.toLogicalName(tmp[1]) + index = tmp[2] + for item in self.parameters: + if item.target.objectType == type_ and item.target.logicalName == ln and item.attributeIndex == index: + self.parameters.remove(item) + break + + def getAttributeIndexToRead(self, all_): + attributes = list() + if all_ or not self.logicalName: + attributes.append(1) + if all_ or self.canRead(2): + attributes.append(2) + if all_ or self.canRead(3): + attributes.append(3) + if all_ or self.canRead(4): + attributes.append(4) + return attributes + + def getAttributeCount(self): + + return 4 + + def getMethodCount(self): + + return 2 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.STRUCTURE + elif index == 3: + ret = DataType.OCTET_STRING + elif index == 4: + ret = DataType.ARRAY + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(4) + if self.changedParameter is None: + _GXCommon.setData(settings, data, DataType.UINT16, 0) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, [0, 0, 0, 0, 0, 0]) + _GXCommon.setData(settings, data, DataType.INT8, 1) + _GXCommon.setData(settings, data, DataType.NONE, None) + else: + _GXCommon.setData(settings, data, DataType.UINT16, self.changedParameter.target.objectType) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(self.changedParameter.target.logicalName)) + _GXCommon.setData(settings, data, DataType.INT8, self.changedParameter.attributeIndex) + _GXCommon.setData(settings, data, _GXCommon.getDLMSDataType(self.changedParameter.value), self.changedParameter.value) + ret = data + elif e.index == 3: + ret = self.captureTime + elif e.index == 4: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if self.parameters is None: + data.setUInt8(0) + else: + data.setUInt8(len(self.parameters)) + for it in self.parameters: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.UINT16, it.target.objectType) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(it.target.logicalName)) + _GXCommon.setData(settings, data, DataType.INT8, it.attributeIndex) + ret = data + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + def setValue(self, settings, e): + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.changedParameter = GXDLMSTarget() + if len(e.value) != 4: + raise Exception("Invalid structure format.") + type_ = ObjectType(e.value[0]) + ln = _GXCommon.toLogicalName(e.value[1]) + self.changedParameter.target = settings.objects.findByLN(type_, ln) + if self.changedParameter.target is None: + self.changedParameter.target = _GXObjectFactory.createObject(type_) + self.changedParameter.target.logicalName = ln + self.changedParameter.attributeIndex = e.value[2] + self.changedParameter.value = e.value[3] + elif e.index == 3: + if e.value is None: + self.captureTime = None + else: + if isinstance(e.value, bytearray): + self.captureTime = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + else: + self.captureTime = e.value + elif e.index == 4: + self.parameters = [] + if e.value: + for i in e.value: + if len(i) != 3: + raise Exception("Invalid structure format.") + obj = GXDLMSTarget() + type_ = ObjectType(i[0]) + ln = _GXCommon.toLogicalName(i[1]) + obj.target = settings.objects.findByLN(type_, ln) + if obj.target is None: + obj.target = _GXObjectFactory.createObject(type_) + obj.target.logicalName = ln + obj.attributeIndex = i[2] + self.parameters.append(obj) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + self.changedParameter = GXDLMSTarget() + if reader.isStartElement("ChangedParameter", True): + ot = ObjectType(reader.readElementContentAsInt("Type")) + ln = reader.readElementContentAsString("LN") + self.changedParameter.target = reader.objects.findByLN(ot, ln) + if self.changedParameter.target is None: + self.changedParameter.target = _GXObjectFactory.createObject(ot) + self.changedParameter.target.logicalName = ln + self.changedParameter.attributeIndex = reader.readElementContentAsInt("Index") + self.changedParameter.value = reader.readElementContentAsObject("Value", None) + reader.readEndElement("ChangedParameter") + self.captureTime = reader.readElementContentAsDateTime("Time") + self.parameters = [] + if reader.isStartElement("Parameters", True): + while reader.isStartElement("Item", True): + obj = GXDLMSTarget() + ot = ObjectType(reader.readElementContentAsInt("Type")) + ln = reader.readElementContentAsString("LN") + obj.target = reader.objects.findByLN(ot, ln) + if obj.target is None: + obj.target = _GXObjectFactory.createObject(ot) + obj.target.logicalName = ln + obj.attributeIndex = reader.readElementContentAsInt("Index") + self.parameters.append(obj) + reader.readEndElement("Parameters") + + def save(self, writer): + if self.changedParameter and self.changedParameter.target: + writer.writeStartElement("ChangedParameter") + writer.writeElementString("Type", int(self.changedParameter.target.objectType)) + writer.writeElementString("LN", self.changedParameter.target.logicalName) + writer.writeElementString("Index", self.changedParameter.attributeIndex) + writer.writeElementObject("Value", self.changedParameter.value) + writer.writeEndElement() + writer.writeElementString("Time", self.captureTime) + writer.writeStartElement("Parameters") + for it in self.parameters: + writer.writeStartElement("Item") + writer.writeElementString("Type", int(it.target.objectType)) + writer.writeElementString("LN", it.target.logicalName) + writer.writeElementString("Index", it.attributeIndex) + writer.writeEndElement() + writer.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPppSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPppSetup.py new file mode 100644 index 0000000..2288764 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPppSetup.py @@ -0,0 +1,260 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from .enums import PppSetupLcpOptionType +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ErrorCode, ObjectType, DataType +from .GXDLMSPppSetupLcpOption import GXDLMSPppSetupLcpOption +from .GXDLMSPppSetupIPCPOption import GXDLMSPppSetupIPCPOption +from .enums import PppSetupIPCPOptionType + +# pylint: disable=too-many-instance-attributes +class GXDLMSPppSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSPppSetup + """ + + # + # Constructor. + # + # @param ln + # Logical Name of the object. + # @param sn + # Short Name of the object. + # + def __init__(self, ln=None, sn=0): + #pylint: disable=super-with-arguments + super(GXDLMSPppSetup, self).__init__(ObjectType.PPP_SETUP, ln, sn) + self.ipcpOptions = list() + self.phyReference = None + self.lcpOptions = list() + # PPP authentication procedure user name. + self.userName = None + # PPP authentication procedure password. + self.password = None + self.authentication = None + + def getValues(self): + return [self.logicalName, + self.phyReference, + self.lcpOptions, + self.ipcpOptions, + [self.userName, self.password]] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # PHYReference + if all_ or not self.isRead(2): + attributes.append(2) + # LCPOptions + if all_ or not self.isRead(3): + attributes.append(3) + # IPCPOptions + if all_ or not self.isRead(4): + attributes.append(4) + # PPPAuthentication + if all_ or not self.isRead(5): + attributes.append(5) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 5 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.OCTET_STRING + if index == 3: + return DataType.ARRAY + if index == 4: + return DataType.ARRAY + if index == 5: + if not self.userName: + return DataType.NONE + return DataType.STRUCTURE + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = _GXCommon.logicalNameToBytes(self.phyReference) + elif e.index == 3: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.lcpOptions: + data.setUInt8(0) + else: + data.setUInt8(len(self.lcpOptions)) + for it in self.lcpOptions: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.UINT8, it.type_.value) + _GXCommon.setData(settings, data, DataType.UINT8, it.length) + _GXCommon.setData(settings, data, _GXCommon.getDLMSDataType(it.data), it.data) + ret = data.array() + elif e.index == 4: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.ipcpOptions: + data.setUInt8(0) + else: + data.setUInt8(len(self.ipcpOptions)) + for it in self.ipcpOptions: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + _GXCommon.setData(settings, data, DataType.UINT8, it.type_.value) + _GXCommon.setData(settings, data, DataType.UINT8, it.length) + _GXCommon.setData(settings, data, _GXCommon.getDLMSDataType(it.data), it.data) + ret = data.array() + elif e.index == 5: + if self.userName: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, self.userName) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, self.password) + ret = data.array() + else: + ret = None + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.phyReference = _GXCommon.toLogicalName(e.value) + elif e.index == 3: + self.lcpOptions = [] + if e.value: + for item in e.value: + it1 = GXDLMSPppSetupLcpOption() + it1.type_ = PppSetupLcpOptionType(item[0]) + it1.length = item[1] + it1.data = item[2] + self.lcpOptions.append(it1) + elif e.index == 4: + self.ipcpOptions = [] + if e.value: + for item in e.value: + it2 = GXDLMSPppSetupIPCPOption() + it2.type_ = item[0] + it2.length = item[1] + it2.data = item[2] + self.ipcpOptions.append(it2) + elif e.index == 5: + if e.value: + self.userName = e.value[0] + self.password = e.value[1] + else: + self.userName = None + self.password = None + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.phyReference = reader.readElementContentAsString("PHYReference") + self.lcpOptions = [] + if reader.isStartElement("LCPOptions", True): + while reader.isStartElement("Item", True): + it1 = GXDLMSPppSetupLcpOption() + it1.type_ = PppSetupLcpOptionType(reader.readElementContentAsInt("Type")) + it1.length = reader.readElementContentAsInt("Length") + it1.data = reader.readElementContentAsObject("Data", None) + self.lcpOptions.append(it1) + reader.readEndElement("LCPOptions") + + self.ipcpOptions = [] + if reader.isStartElement("IPCPOptions", True): + while reader.isStartElement("Item", True): + it2 = GXDLMSPppSetupIPCPOption() + it2.type_ = PppSetupIPCPOptionType(reader.readElementContentAsInt("Type")) + it2.length = reader.readElementContentAsInt("Length") + it2.data = reader.readElementContentAsObject("Data", None) + self.ipcpOptions.append(it2) + reader.readEndElement("IPCPOptions") + self.userName = GXByteBuffer.hexToBytes(reader.readElementContentAsString("UserName")) + self.password = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Password")) + + def save(self, writer): + writer.writeElementString("PHYReference", self.phyReference) + writer.writeStartElement("LCPOptions") + if self.lcpOptions: + for it in self.lcpOptions: + writer.writeStartElement("Item") + writer.writeElementString("Type", it.type_) + writer.writeElementString("Length", it.length) + writer.writeElementObject("Data", it.data) + writer.writeEndElement() + writer.writeEndElement() + writer.writeStartElement("IPCPOptions") + if self.ipcpOptions: + for it in self.ipcpOptions: + writer.writeStartElement("Item") + writer.writeElementString("Type", it.type_) + writer.writeElementString("Length", it.length) + writer.writeElementObject("Data", it.data) + writer.writeEndElement() + writer.writeEndElement() + writer.writeElementString("UserName", GXByteBuffer.hex(self.userName)) + writer.writeElementString("Password", GXByteBuffer.hex(self.password)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPppSetupIPCPOption.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPppSetupIPCPOption.py new file mode 100644 index 0000000..0884765 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPppSetupIPCPOption.py @@ -0,0 +1,44 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSPppSetupIPCPOption: + # + #Constructor. + # + def __init__(self): + self.type_ = None + self.length = 0 + self.data = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPppSetupLcpOption.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPppSetupLcpOption.py new file mode 100644 index 0000000..de87658 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPppSetupLcpOption.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSPppSetupLcpOption: + # + #Constructor. + # + def __init__(self): + self.type_ = None + self.data = None + self.length = 0 + + def __str__(self): + return str(self.type_) + " " + str(self.data) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcApplicationsIdentification.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcApplicationsIdentification.py new file mode 100644 index 0000000..2498e00 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcApplicationsIdentification.py @@ -0,0 +1,153 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType + +# pylint: disable=too-many-instance-attributes +class GXDLMSPrimeNbOfdmPlcApplicationsIdentification(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSPrimeNbOfdmPlcApplicationsIdentification + """ + + def __init__(self, ln="0.0.28.7.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.PRIME_NB_OFDM_PLC_APPLICATIONS_IDENTIFICATION, ln, sn) + # Textual description of the firmware version running on the device. + self.firmwareVersion = None + # Unique vendor identifier assigned by PRIME Alliance. + self.vendorId = 0 + # Vendor assigned unique identifier for specific product. + self.productId = 0 + + def getValues(self): + return [self.logicalName, + self.firmwareVersion, + self.vendorId, + self.productId] + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # FirmwareVersion + if all_ or self.canRead(2): + attributes.append(2) + # VendorId + if all_ or self.canRead(3): + attributes.append(3) + # ProductId + if all_ or self.canRead(4): + attributes.append(4) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 4 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index in (1, 2): + return DataType.OCTET_STRING + if index in (3, 4): + return DataType.UINT16 + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + if isinstance(self.firmwareVersion, str): + ret = self.firmwareVersion.encode() + else: + ret = self.firmwareVersion + elif e.index == 3: + ret = self.vendorId + elif e.index == 4: + ret = self.productId + else: + ret = None + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if e.value is None: + self.firmwareVersion = None + else: + self.firmwareVersion = e.value.decode("utf-8").rstrip('\x00') + elif e.index == 3: + self.vendorId = e.value + elif e.index == 4: + self.productId = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.firmwareVersion = reader.readElementContentAsString("FirmwareVersion") + self.vendorId = reader.readElementContentAsInt("VendorId") + self.productId = reader.readElementContentAsInt("ProductId") + + def save(self, writer): + writer.writeElementString("FirmwareVersion", self.firmwareVersion) + writer.writeElementString("VendorId", self.vendorId) + writer.writeElementString("ProductId", self.productId) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacCounters.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacCounters.py new file mode 100644 index 0000000..428ccc8 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacCounters.py @@ -0,0 +1,198 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType + +# pylint: disable=too-many-instance-attributes +class GXDLMSPrimeNbOfdmPlcMacCounters(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSPrimeNbOfdmPlcMacCounters + """ + + def __init__(self, ln="0.0.28.4.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.PRIME_NB_OFDM_PLC_MAC_COUNTERS, ln, sn) + # Count of successfully transmitted MSDUs. + self.txDataPktCount = 0 + # Count of successfully received MSDUs whose destination address + # was this node. + self.rxDataPktCount = 0 + # Count of successfully transmitted MAC control packets. + self.txCtrlPktCount = 0 + # Count of successfully received MAC control packets whose + # destination was this node. + self.rxCtrlPktCount = 0 + # Count of failed CSMA transmit attempts. + self.csmaFailCount = 0 + # Count of number of times this node has to back off SCP + # transmission due to channel busy state. + self.csmaChBusyCount = 0 + + def getValues(self): + return [self.logicalName, + self.txDataPktCount, + self.rxDataPktCount, + self.txCtrlPktCount, + self.rxCtrlPktCount, + self.csmaFailCount, + self.csmaChBusyCount] + + def reset(self, client): + """Reset all counters.""" + return client.method(self, 1, 0, DataType.INT8) + + def invoke(self, settings, e): + # Resets the value to the default value. + # The default value is an instance specific constant. + if e.index == 1: + self.txDataPktCount = self.rxDataPktCount = self.txCtrlPktCount = self.rxCtrlPktCount = self.csmaFailCount = self.csmaChBusyCount = 0 + else: + e.error = ErrorCode.READ_WRITE_DENIED + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # TxDataPktCount + if all_ or self.canRead(2): + attributes.append(2) + # RxDataPktCount + if all_ or self.canRead(3): + attributes.append(3) + # TxCtrlPktCount + if all_ or self.canRead(4): + attributes.append(4) + # RxCtrlPktCount + if all_ or self.canRead(5): + attributes.append(5) + # CsmaFailCount + if all_ or self.canRead(6): + attributes.append(6) + # CsmaChBusyCount + if all_ or self.canRead(7): + attributes.append(7) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 7 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index in (2, 3, 4, 5, 6, 7): + return DataType.UINT32 + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.txDataPktCount + elif e.index == 3: + ret = self.rxDataPktCount + elif e.index == 4: + ret = self.txCtrlPktCount + elif e.index == 5: + ret = self.rxCtrlPktCount + elif e.index == 6: + ret = self.csmaFailCount + elif e.index == 7: + ret = self.csmaChBusyCount + else: + ret = None + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.txDataPktCount = e.value + elif e.index == 3: + self.rxDataPktCount = e.value + elif e.index == 4: + self.txCtrlPktCount = e.value + elif e.index == 5: + self.rxCtrlPktCount = e.value + elif e.index == 6: + self.csmaFailCount = e.value + elif e.index == 7: + self.csmaChBusyCount = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.txDataPktCount = reader.readElementContentAsLong("TxDataPktCount") + self.rxDataPktCount = reader.readElementContentAsLong("RxDataPktCount") + self.txCtrlPktCount = reader.readElementContentAsLong("TxCtrlPktCount") + self.rxCtrlPktCount = reader.readElementContentAsLong("RxCtrlPktCount") + self.csmaFailCount = reader.readElementContentAsLong("CsmaFailCount") + self.csmaChBusyCount = reader.readElementContentAsLong("CsmaChBusyCount") + + def save(self, writer): + writer.writeElementString("TxDataPktCount", self.txDataPktCount) + writer.writeElementString("RxDataPktCount", self.rxDataPktCount) + writer.writeElementString("TxCtrlPktCount", self.txCtrlPktCount) + writer.writeElementString("RxCtrlPktCount", self.rxCtrlPktCount) + writer.writeElementString("CsmaFailCount", self.csmaFailCount) + writer.writeElementString("CsmaChBusyCount", self.csmaChBusyCount) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacFunctionalParameters.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacFunctionalParameters.py new file mode 100644 index 0000000..5c53e27 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacFunctionalParameters.py @@ -0,0 +1,285 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums.MacState import MacState + +# pylint: disable=too-many-instance-attributes +class GXDLMSPrimeNbOfdmPlcMacFunctionalParameters(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSPrimeNbOfdmPlcMacFunctionalParameters + """ + + def __init__(self, ln="0.0.28.3.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.PRIME_NB_OFDM_PLC_MAC_FUNCTIONAL_PARAMETERS, ln, sn) + #LNID allocated to this node at time of its registration. + self.lnId = 0 + #LSID allocated to this node at the time of its promotion. + self.lsId = 0 + #SID of the switch node through which this node is connected to the + #subnetwork. + self.sId = 0 + #Subnetwork address to which this node is registered. + self.sna = None + #Present functional state of the node. + self.state = MacState.DISCONNECTED + #The SCP length, in symbols, in present frame. + self.scpLength = 0 + #Level of this node in subnetwork hierarchy. + self.nodeHierarchyLevel = 0 + #Number of beacon slots provisioned in present frame structure. + self.beaconSlotCount = 0 + #Beacon slot in which this device's switch node transmits its + #beacon. + self.beaconRxSlot = 0 + #Beacon slot in which this device transmits its beacon. + self.beaconTxSlot = 0 + #Number of frames between receptions of two successive beacons. + self.beaconRxFrequency = 0 + #Number of frames between transmissions of two successive beacons. + self.beaconTxFrequency = 0 + #This attribute defines the capabilities of the node. + self.capabilities = list() + + def getValues(self): + return [self.logicalName, + self.lnId, + self.lsId, + self.sId, + self.sna, + self.state, + self.scpLength, + self.nodeHierarchyLevel, + self.beaconSlotCount, + self.beaconRxSlot, + self.beaconTxSlot, + self.beaconRxFrequency, + self.beaconTxFrequency, + self.capabilities] + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # LnId + if all_ or self.canRead(2): + attributes.append(2) + # LsId + if all_ or self.canRead(3): + attributes.append(3) + # SId + if all_ or self.canRead(4): + attributes.append(4) + # SNa + if all_ or self.canRead(5): + attributes.append(5) + # State + if all_ or self.canRead(6): + attributes.append(6) + # ScpLength + if all_ or self.canRead(7): + attributes.append(7) + # NodeHierarchyLevel + if all_ or self.canRead(8): + attributes.append(8) + # BeaconSlotCount + if all_ or self.canRead(9): + attributes.append(9) + # BeaconRxSlot + if all_ or self.canRead(10): + attributes.append(10) + # BeaconTxSlot + if all_ or self.canRead(11): + attributes.append(11) + # BeaconRxFrequency + if all_ or self.canRead(12): + attributes.append(12) + # BeaconTxFrequency + if all_ or self.canRead(13): + attributes.append(13) + # Capabilities + if all_ or self.canRead(14): + attributes.append(14) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 14 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.INT16 + elif index in (3, 4): + ret = DataType.UINT8 + elif index == 5: + ret = DataType.OCTET_STRING + elif index == 6: + ret = DataType.ENUM + elif index == 7: + ret = DataType.INT16 + elif index in (8, 9, 10, 11, 12, 13): + ret = DataType.UINT8 + elif index == 14: + ret = DataType.UINT16 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.lnId + elif e.index == 3: + ret = self.lsId + elif e.index == 4: + ret = self.sId + elif e.index == 5: + ret = self.sna + elif e.index == 6: + ret = self.state + elif e.index == 7: + ret = self.scpLength + elif e.index == 8: + ret = self.nodeHierarchyLevel + elif e.index == 9: + ret = self.beaconSlotCount + elif e.index == 10: + ret = self.beaconRxSlot + elif e.index == 11: + ret = self.beaconTxSlot + elif e.index == 12: + ret = self.beaconRxFrequency + elif e.index == 13: + ret = self.beaconTxFrequency + elif e.index == 14: + ret = self.capabilities + else: + ret = None + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.lnId = e.value + elif e.index == 3: + self.lsId = e.value + elif e.index == 4: + self.sId = e.value + elif e.index == 5: + self.sna = e.value + elif e.index == 6: + self.state = e.value + elif e.index == 7: + self.scpLength = e.value + elif e.index == 8: + self.nodeHierarchyLevel = e.value + elif e.index == 9: + self.beaconSlotCount = e.value + elif e.index == 10: + self.beaconRxSlot = e.value + elif e.index == 11: + self.beaconTxSlot = e.value + elif e.index == 12: + self.beaconRxFrequency = e.value + elif e.index == 13: + self.beaconTxFrequency = e.value + elif e.index == 14: + self.capabilities = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.lnId = reader.readElementContentAsInt("LnId") + self.lsId = reader.readElementContentAsInt("LsId") + self.sId = reader.readElementContentAsInt("SId") + self.sna = GXByteBuffer.hexToBytes(reader.readElementContentAsString("SNa")) + self.state = reader.readElementContentAsInt("State") + self.scpLength = reader.readElementContentAsInt("ScpLength") + self.nodeHierarchyLevel = reader.readElementContentAsInt("NodeHierarchyLevel") + self.beaconSlotCount = reader.readElementContentAsInt("BeaconSlotCount") + self.beaconRxSlot = reader.readElementContentAsInt("BeaconRxSlot") + self.beaconTxSlot = reader.readElementContentAsInt("BeaconTxSlot") + self.beaconRxFrequency = reader.readElementContentAsInt("BeaconRxFrequency") + self.beaconTxFrequency = reader.readElementContentAsInt("BeaconTxFrequency") + self.capabilities = reader.readElementContentAsInt("Capabilities") + + def save(self, writer): + writer.writeElementString("LnId", self.lnId) + writer.writeElementString("LsId", self.lsId) + writer.writeElementString("SId", self.sId) + writer.writeElementString("SNa", self.sna) + writer.writeElementString("State", self.state) + writer.writeElementString("ScpLength", self.scpLength) + writer.writeElementString("NodeHierarchyLevel", self.nodeHierarchyLevel) + writer.writeElementString("BeaconSlotCount", self.beaconSlotCount) + writer.writeElementString("BeaconRxSlot", self.beaconRxSlot) + writer.writeElementString("BeaconTxSlot", self.beaconTxSlot) + writer.writeElementString("BeaconRxFrequency", self.beaconRxFrequency) + writer.writeElementString("BeaconTxFrequency", self.beaconTxFrequency) + writer.writeElementString("Capabilities", self.capabilities) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData.py new file mode 100644 index 0000000..8ccfc85 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData.py @@ -0,0 +1,474 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType +from ..GXByteBuffer import GXByteBuffer +from .GXMacMulticastEntry import GXMacMulticastEntry +from .GXMacDirectTable import GXMacDirectTable +from .GXMacAvailableSwitch import GXMacAvailableSwitch +from .GXMacPhyCommunication import GXMacPhyCommunication + +# pylint: disable=too-many-instance-attributes +class GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData + """ + + def __init__(self, ln="0.0.28.5.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.PRIME_NB_OFDM_PLC_MAC_NETWORK_ADMINISTRATION_DATA, ln, sn) + # List of entries in multicast switching table. + self.multicastEntries = list() + # Switch table. + self.switchTable = list() + # List of entries in multicast switching table. + self.directTable = list() + # List of available switches. + self.availableSwitches = list() + # List of PHY communication parameters. + self.communications = list() + + def getValues(self): + return [self.logicalName, + self.multicastEntries, + self.switchTable, + self.directTable, + self.availableSwitches, + self.communications] + + def reset(self, client): + """Reset the values.""" + return client.method(self, 1, 0, DataType.INT8) + + def invoke(self, settings, e): + # Resets the value to the default value. + # The default value is an instance specific constant. + if e.index == 1: + self.multicastEntries = list() + self.switchTable = list() + self.directTable = list() + self.availableSwitches = list() + self.communications = list() + else: + e.error = ErrorCode.READ_WRITE_DENIED + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # MulticastEntries + if all_ or self.canRead(2): + attributes.append(2) + # SwitchTable + if all_ or self.canRead(3): + attributes.append(3) + # DirectTable + if all_ or self.canRead(4): + attributes.append(4) + # AvailableSwitches + if all_ or self.canRead(5): + attributes.append(5) + # Communications + if all_ or self.canRead(6): + attributes.append(6) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 6 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index in (2, 3, 4, 5, 6): + return DataType.ARRAY + raise ValueError("getDataType failed. Invalid attribute index.") + + def __getMulticastEntries(self, settings): + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.multicastEntries: + _GXCommon.setObjectCount(0, bb) + else: + _GXCommon.setObjectCount(len(self.multicastEntries), bb) + for it in self.multicastEntries: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + _GXCommon.setData(settings, bb, DataType.INT8, it.id) + _GXCommon.setData(settings, bb, DataType.INT16, it.members) + return bb.array() + + def __getSwitchTable(self, settings): + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.switchTable: + _GXCommon.setObjectCount(0, bb) + else: + _GXCommon.setObjectCount(len(self.switchTable), bb) + for it in self.switchTable: + _GXCommon.setData(settings, bb, DataType.INT16, it) + return bb.array() + + def __getDirectTable(self, settings): + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.directTable: + _GXCommon.setObjectCount(0, bb) + else: + _GXCommon.setObjectCount(len(self.directTable), bb) + for it in self.directTable: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(7) + _GXCommon.setData(settings, bb, DataType.INT16, it.sourceSId) + _GXCommon.setData(settings, bb, DataType.INT16, it.sourceLnId) + _GXCommon.setData(settings, bb, DataType.INT16, it.sourceLcId) + _GXCommon.setData(settings, bb, DataType.INT16, it.destinationSId) + _GXCommon.setData(settings, bb, DataType.INT16, it.destinationLnId) + _GXCommon.setData(settings, bb, DataType.INT16, it.destinationLcId) + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, it.did) + return bb.array() + + def __getAvailableSwitches(self, settings): + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.availableSwitches: + _GXCommon.setObjectCount(0, bb) + else: + _GXCommon.setObjectCount(len(self.availableSwitches), bb) + for it in self.availableSwitches: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(5) + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, it.sna) + _GXCommon.setData(settings, bb, DataType.INT16, it.lsId) + _GXCommon.setData(settings, bb, DataType.INT8, it.level) + _GXCommon.setData(settings, bb, DataType.INT8, it.rxLevel) + _GXCommon.setData(settings, bb, DataType.INT8, it.rxSnr) + return bb.array() + + def __getCommunications(self, settings): + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.communications: + _GXCommon.setObjectCount(0, bb) + else: + _GXCommon.setObjectCount(len(self.communications), bb) + for it in self.communications: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(9) + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, it.eui) + _GXCommon.setData(settings, bb, DataType.INT8, it.txPower) + _GXCommon.setData(settings, bb, DataType.INT8, it.txCoding) + _GXCommon.setData(settings, bb, DataType.INT8, it.rxCoding) + _GXCommon.setData(settings, bb, DataType.INT8, it.rxLvl) + _GXCommon.setData(settings, bb, DataType.INT8, it.snr) + _GXCommon.setData(settings, bb, DataType.INT8, it.txPowerModified) + _GXCommon.setData(settings, bb, DataType.INT8, it.txCodingModified) + _GXCommon.setData(settings, bb, DataType.INT8, it.rxCodingModified) + return bb.array() + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.__getMulticastEntries(settings) + elif e.index == 3: + ret = self.__getSwitchTable(settings) + elif e.index == 4: + ret = self.__getDirectTable(settings) + elif e.index == 5: + ret = self.__getAvailableSwitches(settings) + elif e.index == 6: + ret = self.__getCommunications(settings) + else: + ret = None + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + @classmethod + def __setMulticastEntry(cls, value): + data = list() + if value: + for it in value: + v = GXMacMulticastEntry() + v.id = it[0] + v.members = it[1] + data.append(v) + return data + + @classmethod + def __setSwitchTable(cls, value): + data = list() + if value: + for it in value: + data.append(it) + return data + + @classmethod + def __setDirectTable(cls, value): + data = list() + if value: + for it in value: + v = GXMacDirectTable() + v.sourceSId = it[0] + v.sourceLnId = it[1] + v.sourceLcId = it[2] + v.destinationSId = it[3] + v.destinationLnId = it[4] + v.destinationLcId = it[5] + v.did = it[6] + data.append(v) + return data + + @classmethod + def __setAvailableSwitches(cls, value): + data = list() + if value: + for it in value: + v = GXMacAvailableSwitch() + v.sna = it[0] + v.lsId = it[1] + v.level = it[2] + v.rxLevel = it[3] + v.rxSnr = it[4] + data.append(v) + return data + + @classmethod + def __setCommunications(cls, value): + data = list() + if value: + for it in value: + v = GXMacPhyCommunication() + v.eui = it[0] + v.txPower = it[1] + v.txCoding = it[2] + v.rxCoding = it[3] + v.rxLvl = it[4] + v.snr = it[5] + v.txPowerModified = it[6] + v.txCodingModified = it[7] + v.rxCodingModified = it[8] + data.append(v) + return data + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.multicastEntries = self.__setMulticastEntry(e.value) + elif e.index == 3: + self.switchTable = self.__setSwitchTable(e.value) + elif e.index == 4: + self.directTable = self.__setDirectTable(e.value) + elif e.index == 5: + self.availableSwitches = self.__setAvailableSwitches(e.value) + elif e.index == 6: + self.communications = self.__setCommunications(e.value) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + @classmethod + def __loadMulticastEntries(cls, reader): + list_ = list() + if reader.isStartElement("MulticastEntries", True): + while reader.isStartElement("Item", True): + it = GXMacMulticastEntry() + list_.append(it) + it.id = reader.readElementContentAsInt("Id") + it.members = reader.readElementContentAsInt("Members") + reader.readEndElement("MulticastEntries") + return list_ + + @classmethod + def __loadSwitchTable(cls, reader): + list_ = list() + if reader.isStartElement("SwitchTable", True): + while reader.isStartElement("Item", False): + list_.append(reader.readElementContentAsInt("Item")) + reader.readEndElement("SwitchTable") + return list_ + + @classmethod + def __loadDirectTable(cls, reader): + list_ = list() + if reader.isStartElement("DirectTable", True): + while reader.isStartElement("Item", True): + it = GXMacDirectTable() + list_.append(it) + it.sourceSId = reader.readElementContentAsInt("SourceSId") + it.sourceLnId = reader.readElementContentAsInt("SourceLnId") + it.sourceLcId = reader.readElementContentAsInt("SourceLcId") + it.destinationSId = reader.readElementContentAsInt("DestinationSId") + it.destinationLnId = reader.readElementContentAsInt("DestinationLnId") + it.destinationLcId = reader.readElementContentAsInt("DestinationLcId") + it.did = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Did")) + reader.readEndElement("DirectTable") + return list_ + + @classmethod + def __loadAvailableSwitches(cls, reader): + list_ = list() + if reader.isStartElement("AvailableSwitches", True): + while reader.isStartElement("Item", True): + it = GXMacAvailableSwitch() + list_.append(it) + it.sna = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Sna")) + it.lsId = reader.readElementContentAsInt("LsId") + it.level = reader.readElementContentAsInt("Level") + it.rxLevel = reader.readElementContentAsInt("RxLevel") + it.rxSnr = reader.readElementContentAsInt("RxSnr") + reader.readEndElement("AvailableSwitches") + return list_ + + @classmethod + def __loadCommunications(cls, reader): + list_ = list() + if reader.isStartElement("Communications", True): + while reader.isStartElement("Item", True): + it = GXMacPhyCommunication() + list_.append(it) + it.eui = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Eui")) + it.txPower = reader.readElementContentAsInt("TxPower") + it.txCoding = reader.readElementContentAsInt("TxCoding") + it.rxCoding = reader.readElementContentAsInt("RxCoding") + it.rxLvl = reader.readElementContentAsInt("RxLvl") + it.snr = reader.readElementContentAsInt("Snr") + it.txPowerModified = reader.readElementContentAsInt("TxPowerModified") + it.txCodingModified = reader.readElementContentAsInt("TxCodingModified") + it.rxCodingModified = reader.readElementContentAsInt("RxCodingModified") + reader.readEndElement("Communications") + return list_ + + def load(self, reader): + self.multicastEntries = self.__loadMulticastEntries(reader) + self.switchTable = self.__loadSwitchTable(reader) + self.directTable = self.__loadDirectTable(reader) + self.availableSwitches = self.__loadAvailableSwitches(reader) + self.communications = self.__loadCommunications(reader) + + def __saveMulticastEntries(self, writer): + writer.writeStartElement("MulticastEntries") + if self.multicastEntries: + for it in self.multicastEntries: + writer.writeStartElement("Item") + writer.writeElementString("Id", it.id) + writer.writeElementString("Members", it.members) + writer.writeEndElement() + writer.writeEndElement() + + def __saveSwitchTable(self, writer): + writer.writeStartElement("SwitchTable") + if self.switchTable: + for it in self.switchTable: + writer.writeElementString("Item", it) + writer.writeEndElement() + + def __saveDirectTable(self, writer): + writer.writeStartElement("DirectTable") + if self.directTable: + for it in self.directTable: + writer.writeStartElement("Item") + writer.writeElementString("SourceSId", it.sourceSId) + writer.writeElementString("SourceLnId", it.sourceLnId) + writer.writeElementString("SourceLcId", it.sourceLcId) + writer.writeElementString("DestinationSId", it.destinationSId) + writer.writeElementString("DestinationLnId", it.destinationLnId) + writer.writeElementString("DestinationLcId", it.destinationLcId) + writer.writeElementString("Did", GXByteBuffer.hex(it.did, False)) + writer.writeEndElement() + writer.writeEndElement() + + def __saveAvailableSwitches(self, writer): + writer.writeStartElement("AvailableSwitches") + if self.availableSwitches: + for it in self.availableSwitches: + writer.writeStartElement("Item") + writer.writeElementString("Sna", GXByteBuffer.hex(it.sna, False)) + writer.writeElementString("LsId", it.lsId) + writer.writeElementString("Level", it.level) + writer.writeElementString("RxLevel", it.rxLevel) + writer.writeElementString("RxSnr", it.rxSnr) + writer.writeEndElement() + writer.writeEndElement() + + def __saveCommunications(self, writer): + writer.writeStartElement("Communications") + if self.communications: + for it in self.communications: + writer.writeStartElement("Item") + writer.writeElementString("Eui", GXByteBuffer.hex(it.eui, False)) + writer.writeElementString("TxPower", it.txPower) + writer.writeElementString("TxCoding", it.txCoding) + writer.writeElementString("RxCoding", it.rxCoding) + writer.writeElementString("RxLvl", it.rxLvl) + writer.writeElementString("Snr", it.snr) + writer.writeElementString("TxPowerModified", it.txPowerModified) + writer.writeElementString("TxCodingModified", it.txCodingModified) + writer.writeElementString("RxCodingModified", it.rxCodingModified) + writer.writeEndElement() + writer.writeEndElement() + + def save(self, writer): + self.__saveMulticastEntries(writer) + self.__saveSwitchTable(writer) + self.__saveDirectTable(writer) + self.__saveAvailableSwitches(writer) + self.__saveCommunications(writer) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacSetup.py new file mode 100644 index 0000000..b1ef373 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcMacSetup.py @@ -0,0 +1,195 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType + +# pylint: disable=too-many-instance-attributes +class GXDLMSPrimeNbOfdmPlcMacSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSPrimeNbOfdmPlcMacSetup + """ + + def __init__(self, ln="0.0.28.2.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.PRIME_NB_OFDM_PLC_MAC_SETUP, ln, sn) + # PIB attribute 0x0010. + self.macMinSwitchSearchTime = 0 + # PIB attribute 0x0011. + self.macMaxPromotionPdu = 0 + # PIB attribute 0x0012. + self.macPromotionPduTxPeriod = 0 + # PIB attribute 0x0013. + self.macBeaconsPerFrame = 0 + # PIB attribute 0x0014. + self.macScpMaxTxAttempts = 0 + # PIB attribute 0x0015. + self.macCtlReTxTimer = 0 + # PIB attribute 0x0018. + self.macMaxCtlReTx = 0 + + def getValues(self): + return [self.logicalName, + self.macMinSwitchSearchTime, + self.macMaxPromotionPdu, + self.macPromotionPduTxPeriod, + self.macBeaconsPerFrame, + self.macScpMaxTxAttempts, + self.macCtlReTxTimer, + self.macMaxCtlReTx] + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # MacMinSwitchSearchTime + if all_ or self.canRead(2): + attributes.append(2) + # MacMaxPromotionPdu + if all_ or self.canRead(3): + attributes.append(3) + # MacPromotionPduTxPeriod + if all_ or self.canRead(4): + attributes.append(4) + # MacBeaconsPerFrame + if all_ or self.canRead(5): + attributes.append(5) + # MacScpMaxTxAttempts + if all_ or self.canRead(6): + attributes.append(6) + # MacCtlReTxTimer + if all_ or self.canRead(7): + attributes.append(7) + # MacMaxCtlReTx + if all_ or self.canRead(8): + attributes.append(8) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 8 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index in (2, 3, 4, 5, 6, 7, 8): + return DataType.UINT8 + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.macMinSwitchSearchTime + elif e.index == 3: + ret = self.macMaxPromotionPdu + elif e.index == 4: + ret = self.macPromotionPduTxPeriod + elif e.index == 5: + ret = self.macBeaconsPerFrame + elif e.index == 6: + ret = self.macScpMaxTxAttempts + elif e.index == 7: + ret = self.macCtlReTxTimer + elif e.index == 8: + ret = self.macMaxCtlReTx + else: + ret = None + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.macMinSwitchSearchTime = e.value + elif e.index == 3: + self.macMaxPromotionPdu = e.value + elif e.index == 4: + self.macPromotionPduTxPeriod = e.value + elif e.index == 5: + self.macBeaconsPerFrame = e.value + elif e.index == 6: + self.macScpMaxTxAttempts = e.value + elif e.index == 7: + self.macCtlReTxTimer = e.value + elif e.index == 8: + self.macMaxCtlReTx = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.macMinSwitchSearchTime = reader.readElementContentAsInt("MacMinSwitchSearchTime") + self.macMaxPromotionPdu = reader.readElementContentAsInt("MacMaxPromotionPdu") + self.macPromotionPduTxPeriod = reader.readElementContentAsInt("MacPromotionPduTxPeriod") + self.macBeaconsPerFrame = reader.readElementContentAsInt("MacBeaconsPerFrame") + self.macScpMaxTxAttempts = reader.readElementContentAsInt("MacScpMaxTxAttempts") + self.macCtlReTxTimer = reader.readElementContentAsInt("MacCtlReTxTimer") + self.macMaxCtlReTx = reader.readElementContentAsInt("MacMaxCtlReTx") + + def save(self, writer): + writer.writeElementString("MacMinSwitchSearchTime", self.macMinSwitchSearchTime) + writer.writeElementString("MacMaxPromotionPdu", self.macMaxPromotionPdu) + writer.writeElementString("MacPromotionPduTxPeriod", self.macPromotionPduTxPeriod) + writer.writeElementString("MacBeaconsPerFrame", self.macBeaconsPerFrame) + writer.writeElementString("MacScpMaxTxAttempts", self.macScpMaxTxAttempts) + writer.writeElementString("MacCtlReTxTimer", self.macCtlReTxTimer) + writer.writeElementString("MacMaxCtlReTx", self.macMaxCtlReTx) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters.py new file mode 100644 index 0000000..0176af9 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters.py @@ -0,0 +1,173 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType + +# pylint: disable=too-many-instance-attributes +class GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters + """ + + def __init__(self, ln="0.0.28.1.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.PRIME_NB_OFDM_PLC_PHYSICAL_LAYER_COUNTERS, ln, sn) + # Number of bursts received on the physical layer for which the CRC was incorrect. + self.crcIncorrectCount = 0 + # Number of bursts received on the physical layer for which the CRC + # was + # correct, but the Protocol field of PHY header had invalid value. + self.crcFailedCount = 0 + + # Number of times when PHY layer received new data to transmit. + self.txDropCount = 0 + # Number of times when the PHY layer received new data on the + # channel. + self.rxDropCount = 0 + + def getValues(self): + return [self.logicalName, + self.crcIncorrectCount, + self.crcFailedCount, + self.txDropCount, + self.rxDropCount] + + def reset(self, client): + """Reset value.""" + return client.method(self, 1, 0, DataType.INT8) + + def invoke(self, settings, e): + # Resets the value to the default value. + # The default value is an instance specific constant. + if e.index == 1: + self.crcIncorrectCount = self.crcFailedCount = self.txDropCount = self.rxDropCount = 0 + else: + e.error = ErrorCode.READ_WRITE_DENIED + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # CrcIncorrectCount + if all_ or self.canRead(2): + attributes.append(2) + # CrcFailedCount + if all_ or self.canRead(3): + attributes.append(3) + # TxDropCount + if all_ or self.canRead(4): + attributes.append(4) + # RxDropCount + if all_ or self.canRead(5): + attributes.append(5) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 5 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index in (2, 3, 4, 5): + return DataType.UINT16 + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.crcIncorrectCount + if e.index == 3: + return self.crcFailedCount + if e.index == 4: + return self.txDropCount + if e.index == 5: + return self.rxDropCount + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.crcIncorrectCount = e.value + elif e.index == 3: + self.crcFailedCount = e.value + elif e.index == 4: + self.txDropCount = e.value + elif e.index == 5: + self.rxDropCount = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.crcIncorrectCount = reader.readElementContentAsInt("CrcIncorrectCount") + self.crcFailedCount = reader.readElementContentAsInt("CrcFailedCount") + self.txDropCount = reader.readElementContentAsInt("TxDropCount") + self.rxDropCount = reader.readElementContentAsInt("RxDropCount") + + def save(self, writer): + writer.writeElementString("CrcIncorrectCount", self.crcIncorrectCount) + writer.writeElementString("CrcFailedCount", self.crcFailedCount) + writer.writeElementString("TxDropCount", self.txDropCount) + writer.writeElementString("RxDropCount", self.rxDropCount) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSProfileGeneric.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSProfileGeneric.py new file mode 100644 index 0000000..a62fb27 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSProfileGeneric.py @@ -0,0 +1,592 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +from datetime import timedelta +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..GXDateTime import GXDateTime +from ..enums import ObjectType, DataType +from .enums import SortMethod +from .GXDLMSCaptureObject import GXDLMSCaptureObject +from .GXDLMSDemandRegister import GXDLMSDemandRegister +from .GXDLMSRegister import GXDLMSRegister +from ..ValueEventArgs import ValueEventArgs +from ..internal._GXDataInfo import _GXDataInfo + +# pylint: disable=too-many-instance-attributes +class GXDLMSProfileGeneric(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSProfileGeneric + """ + + # + # Constructor. + # + # @param ln + # Logical Name of the object. + # @param sn + # Short Name of the object. + def __init__(self, ln=None, sn=0): + #pylint: disable=super-with-arguments + super(GXDLMSProfileGeneric, self).__init__(ObjectType.PROFILE_GENERIC, ln, sn) + self.version = 1 + self.buffer = list() + self.captureObjects = list() + self.capturePeriod = 0 + self.sortMethod = SortMethod.FIFO + self.sortObject = None + self.entriesInUse = 0 + self.profileEntries = 0 + self.sortObjectAttributeIndex = 0 + self.sortObjectDataIndex = 0 + + # + # Clears the buffer. + # + # @param client + # DLMS client. + # Action bytes. + def reset(self, client): + return client.method(self, 1, 0, DataType.INT8) + + # Copies the values of the objects to capture into the buffer by + # reading + # each capture object. + # + # @param client + # DLMS client. + # Action bytes. + def capture(self, client): + return client.method(self, 2, 0, DataType.INT8) + + # + # Add new capture object (column) to the profile generic. + # + def addCaptureObject(self, item, attributeIndex, dataIndex): + if item is None: + raise ValueError("Invalid Object") + #Don't check attributeIndex. Some meters are using -1. + if dataIndex < 0: + raise ValueError("Invalid data index") + co = GXDLMSCaptureObject(attributeIndex, dataIndex) + self.captureObjects.append((item, co)) + + def getValues(self): + return [self.logicalName, + self.buffer, + self.captureObjects, + self.capturePeriod, + self.sortMethod, + self.sortObject, + self.entriesInUse, + self.profileEntries] + + def invoke(self, settings, e): + if e.index == 1: + # Reset. + self.__reset() + elif e.index == 2: + # Capture. + self.__capture(e.server) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + # + # Returns collection of attributes to read. + # If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Buffer + if all_ or not self.isRead(2): + attributes.append(2) + # CaptureObjects + if all_ or not self.isRead(3): + attributes.append(3) + # CapturePeriod + if all_ or not self.isRead(4): + attributes.append(4) + # SortMethod + if all_ or not self.isRead(5): + attributes.append(5) + # SortObject + if all_ or not self.isRead(6): + attributes.append(6) + # EntriesInUse + if all_ or not self.isRead(7): + attributes.append(7) + # ProfileEntries + if all_ or not self.isRead(8): + attributes.append(8) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 8 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 2 + + # + # Returns captured columns. + # + def __getColumns(self, settings): + cnt = len(self.captureObjects) + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + # Add count + _GXCommon.setObjectCount(cnt, data) + for k, v in self.captureObjects: + data.setUInt8(DataType.STRUCTURE) + # Count + data.setUInt8(4) + # ClassID + _GXCommon.setData(settings, data, DataType.UINT16, k.objectType) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(k.logicalName)) + _GXCommon.setData(settings, data, DataType.INT8, v.attributeIndex) + _GXCommon.setData(settings, data, DataType.UINT16, v.dataIndex) + return data + + def getData(self, settings, e, table, columns): + data = GXByteBuffer() + if settings.index == 0: + data.setUInt8(DataType.ARRAY) + if e.rowEndIndex != 0: + _GXCommon.setObjectCount(e.rowEndIndex - e.rowBeginIndex, data) + else: + _GXCommon.setObjectCount(len(table), data) + types = [None] * len(self.captureObjects) + pos = 0 + for k, v in self.captureObjects: + types[pos] = k.getDataType(v.attributeIndex) + pos += 1 + tp = None + for row in table: + items = row + data.setUInt8(DataType.STRUCTURE) + if not columns: + _GXCommon.setObjectCount(0, data) + else: + _GXCommon.setObjectCount(len(columns), data) + pos = 0 + for value in items: + if columns is None or self.captureObjects[pos] in columns: + tp = types[pos] + if tp == DataType.NONE: + tp = _GXCommon.getDLMSDataType(value) + types[pos] = tp + _GXCommon.setData(settings, data, tp, value) + pos += 1 + settings.setIndex(settings.index + 1) + if e.getRowEndIndex() != 0: + e.setRowBeginIndex(len(table)) + return data.array() + + def getColumns(self, cols): + columns = None + if cols: + columns = list() + for it in cols: + ot = ObjectType(it[0]) + ln = _GXCommon.toLogicalName(it[1]) + attributeIndex = it[2] + dataIndex = it[3] + for k, v in self.captureObjects: + if k.objectType == ot and v.attributeIndex == attributeIndex and v.dataIndex == dataIndex and k.logicalName == ln: + columns.append((k, v)) + break + else: + colums = list() + colums.append(self.captureObjects) + return colums + return columns + + def getSelectedColumns(self, selector, parameters): + if selector == 0: + colums = list() + colums.append(self.captureObjects) + ret = colums + elif selector == 1: + ret = self.getColumns((parameters)[3]) + elif selector == 2: + arr = parameters + colStart = 1 + colCount = 0 + if len(arr) > 2: + colStart = arr[2] + if len(arr) > 3: + colCount = arr[3] + elif colStart != 1: + colCount = len(self.captureObjects) + if colStart != 1 or colCount != 0: + return self.captureObjects[colStart - 1: colStart + colCount - 1] + colums = list() + colums.append(self.captureObjects) + ret = colums + else: + raise ValueError("Invalid selector.") + return ret + + def __getProfileGenericData(self, settings, e): + #pylint: disable=bad-option-value,chained-comparison + columns = None + if e.selector == 0 or e.parameters is None or e.getRowEndIndex() != 0: + return self.getData(settings, e, self.buffer, columns) + arr = e.parameters + columns = self.getSelectedColumns(e.selector, arr) + table = list() + if e.selector == 1: + info = _GXDataInfo() + info.type_ = DataType.DATETIME + start = _GXCommon.getData(settings, GXByteBuffer(arr[1]), info).value + info.clear() + info.type_ = DataType.DATETIME + end = _GXCommon.getData(settings, GXByteBuffer(arr[2]), info).value + for row in self.buffer: + tm = None + tmp = (row)[0] + if isinstance(tmp, GXDateTime): + tm = tmp.value + else: + tm = tmp + if tm >= start >= 0 and tm <= end: + table.append(row) + elif e.selector == 2: + start = arr[0] + if start == 0: + start = 1 + count = arr[1] + if count == 0: + count = len(self.buffer) + if start + count > len(self.buffer): + count = len(self.buffer) + pos = 0 + while pos < count: + if pos + start - 1 == len(self.buffer): + break + table.append(self.buffer[start + pos - 1]) + pos += 1 + else: + raise ValueError("Invalid selector.") + return self.getData(settings, e, table, columns) + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ARRAY + elif index == 3: + ret = DataType.ARRAY + elif index == 4: + ret = DataType.UINT32 + elif index == 5: + ret = DataType.ENUM + elif index == 6: + ret = DataType.ARRAY + elif index == 7: + ret = DataType.UINT32 + elif index == 8: + ret = DataType.UINT32 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.__getProfileGenericData(settings, e) + elif e.index == 3: + ret = self.__getColumns(settings) + elif e.index == 4: + ret = self.capturePeriod + elif e.index == 5: + ret = self.sortMethod + elif e.index == 6: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(int(4)) + if self.sortObject is None: + _GXCommon.setData(settings, data, DataType.UINT16, 0) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, bytearray(6)) + _GXCommon.setData(settings, data, DataType.INT8, 0) + _GXCommon.setData(settings, data, DataType.UINT16, 0) + else: + _GXCommon.setData(settings, data, DataType.UINT16, self.sortObject.objectType) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(self.sortObject.logicalName)) + _GXCommon.setData(settings, data, DataType.INT8, self.sortObjectAttributeIndex) + _GXCommon.setData(settings, data, DataType.UINT16, self.sortObjectDataIndex) + ret = data.array() + elif e.index == 7: + ret = self.entriesInUse + elif e.index == 8: + ret = self.profileEntries + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + def setValue(self, settings, e): + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.__setBuffer(settings, e) + elif e.index == 3: + self.captureObjects = [] + self.buffer = [] + self.entriesInUse = 0 + if e.value: + for it in e.value: + tmp = it + if len(tmp) != 4: + raise Exception("Invalid structure format.") + type_ = tmp[0] + ln = _GXCommon.toLogicalName(tmp[1]) + obj = None + if settings and settings.objects: + obj = settings.objects.findByLN(type_, ln) + if obj is None: + obj = _GXObjectFactory.createObject(type_) + obj.logicalName = ln + index = tmp[2] + self.addCaptureObject(obj, index, tmp[3]) + elif e.index == 4: + if settings and settings.isServer: + self.__reset() + if e.value is None: + self.capturePeriod = 0 + else: + self.capturePeriod = e.value + elif e.index == 5: + #pylint: disable=bad-option-value,redefined-variable-type + if settings and settings.isServer: + self.__reset() + if e.value is None: + self.sortMethod = SortMethod.FIFO + else: + self.sortMethod = SortMethod(e.value) + elif e.index == 6: + if settings and settings.isServer: + self.__reset() + if e.value is None: + self.sortObject = None + else: + tmp = e.value + if len(tmp) != 4: + raise ValueError("Invalid structure format.") + type_ = ObjectType(tmp[0]) + ln = _GXCommon.toLogicalName(tmp[1]) + attributeIndex = tmp[2] + dataIndex = tmp[3] + self.sortObject = settings.objects.findByLN(type_, ln) + if self.sortObject is None: + self.sortObject = _GXObjectFactory.createObject(type_) + self.sortObject.logicalName = ln + self.sortObjectAttributeIndex = attributeIndex + self.sortObjectDataIndex = dataIndex + elif e.index == 7: + if e.value is None: + self.entriesInUse = 0 + else: + self.entriesInUse = e.value + elif e.index == 8: + if settings and settings.isServer: + self.__reset() + if e.value is None: + self.profileEntries = 0 + else: + self.profileEntries = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def __setBuffer(self, settings, e): + #pylint: disable=broad-except,too-many-nested-blocks,consider-using-enumerate + cols = e.parameters + colIndex = 0 + if cols is None: + cols = self.captureObjects + if cols is None or not cols: + raise ValueError("Read capture objects first.") + if e.value: + colIndex += 1 + lastDate = None + types = list() + colIndex = 0 + for k, v in cols: + types.append(k.getUIDataType(v.attributeIndex)) + for it in e.value: + row = it + if len(row) != len(cols): + raise ValueError("Number of columns do not match.") + for colIndex in range(len(row)): + data = row[colIndex] + type_ = types[colIndex] + if type_ != DataType.NONE and isinstance(data, bytearray): + data = _GXCommon.changeType(settings, data, type_) + if isinstance(data, GXDateTime): + lastDate = data.value + row[colIndex] = data + elif type_ == DataType.DATETIME and data is None: + if not lastDate and self.buffer: + lastDate = self.buffer[len(self.buffer) - 1][colIndex].value + if lastDate: + if self.sortMethod in(SortMethod.FIFO, SortMethod.SMALLEST): + lastDate += timedelta(seconds=self.capturePeriod) + else: + lastDate -= timedelta(seconds=self.capturePeriod) + row[colIndex] = GXDateTime(lastDate) + elif type_ == DataType.DATETIME and not isinstance(row[colIndex], GXDateTime): + row[colIndex] = GXDateTime.fromUnixTime(row[colIndex]) + item = cols[colIndex] + if isinstance(item[0], GXDLMSRegister) and item[1].attributeIndex == 2: + scaler_ = item[0].scaler + if scaler_ != 1 and data: + try: + row[colIndex] = data * scaler_ + except Exception: + print("Scalar failed for: {}".format(item[0].logicalName)) + elif isinstance(item[0], GXDLMSDemandRegister) and (item[1].attributeIndex == 2 or item[1].attributeIndex == 3): + scaler_ = item[0].scaler + if scaler_ != 1 and data: + try: + row[colIndex] = data * scaler_ + except Exception: + print("Scalar failed for: {}".format(item[0].logicalName)) + colIndex += 1 + self.buffer.append(row) + if e.settings.isServer: + self.entriesInUse = len(self.buffer) + + def __reset(self): + self.buffer = [] + self.entriesInUse = 0 + + def __capture(self, server): + srv = server + values = [None] * len(self.captureObjects) + pos = 0 + args = [ValueEventArgs(srv, self, 2)] + srv.onPreGet(args) + if not args[0].handled: + for k, v in self.captureObjects: + values[pos] = k.values()[v.attributeIndex - 1] + pos += 1 + if self.profileEntries: + self.entriesInUse -= 1 + self.buffer.remove(0) + self.buffer.append(values) + self.entriesInUse += 1 + srv.onPostGet(args) + srv.onAction(args) + srv.onPostAction(args) + + def load(self, reader): + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + self.buffer = [] + if reader.isStartElement("Buffer", True): + while reader.isStartElement("Row", True): + row = list() + while reader.isStartElement("Cell", False): + row.append(reader.readElementContentAsObject("Cell", None)) + self.buffer.append(row) + reader.readEndElement("Buffer") + self.captureObjects = [] + if reader.isStartElement("CaptureObjects", True): + while reader.isStartElement("Item", True): + ot = reader.readElementContentAsInt("ObjectType") + ln = reader.readElementContentAsString("LN") + ai = reader.readElementContentAsInt("Attribute") + di = reader.readElementContentAsInt("Data") + co = GXDLMSCaptureObject(ai, di) + obj = reader.objects.findByLN(ot, ln) + if obj is None: + obj = _GXObjectFactory.createObject(ot) + obj.logicalName = ln + self.captureObjects.append((obj, co)) + reader.readEndElement("CaptureObjects") + self.capturePeriod = reader.readElementContentAsInt("CapturePeriod") + self.sortMethod = SortMethod(reader.readElementContentAsInt("SortMethod")) + if reader.isStartElement("SortObject", True): + self.capturePeriod = reader.readElementContentAsInt("CapturePeriod") + ot = reader.readElementContentAsInt("ObjectType") + ln = reader.readElementContentAsString("LN") + self.sortObject = reader.objects.findByLN(ot, ln) + reader.readEndElement("SortObject") + self.entriesInUse = reader.readElementContentAsInt("EntriesInUse") + self.profileEntries = reader.readElementContentAsInt("ProfileEntries") + + def save(self, writer): + writer.writeStartElement("Buffer") + if self.buffer: + for row in self.buffer: + writer.writeStartElement("Row") + for it in row: + writer.writeElementObject("Cell", it) + writer.writeEndElement() + writer.writeEndElement() + writer.writeStartElement("CaptureObjects") + if self.captureObjects: + for k, v in self.captureObjects: + writer.writeStartElement("Item") + writer.writeElementString("ObjectType", int(k.objectType)) + writer.writeElementString("LN", k.logicalName) + writer.writeElementString("Attribute", v.attributeIndex) + writer.writeElementString("Data", v.dataIndex) + writer.writeEndElement() + writer.writeEndElement() + writer.writeElementString("CapturePeriod", self.capturePeriod) + writer.writeElementString("SortMethod", int(self.sortMethod)) + if self.sortObject: + writer.writeStartElement("SortObject") + writer.writeElementString("ObjectType", int(self.sortObject.objectType)) + writer.writeElementString("LN", self.sortObject.logicalName) + writer.writeEndElement() + writer.writeElementString("EntriesInUse", self.entriesInUse) + writer.writeElementString("ProfileEntries", self.profileEntries) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPushSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPushSetup.py new file mode 100644 index 0000000..0131140 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSPushSetup.py @@ -0,0 +1,321 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import ServiceType, MessageType +from .GXDLMSCaptureObject import GXDLMSCaptureObject + +# pylint: disable=too-many-instance-attributes +class GXDLMSPushSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSPushSetup + """ + + def __init__(self, ln="0.7.25.9.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSPushSetup, self).__init__(ObjectType.PUSH_SETUP, ln, sn) + self.pushObjectList = [] + self.communicationWindow = [] + self.service = ServiceType.TCP + self.message = MessageType.COSEM_APDU + self.destination = None + self.randomisationStartInterval = 0 + self.numberOfRetries = 0 + self.repetitionDelay = 0 + + def getValues(self): + return [self.logicalName, + self.pushObjectList, + (self.service, self.destination, self.message), + self.communicationWindow, + self.randomisationStartInterval, + self.numberOfRetries, + self.repetitionDelay] + + def getPushValues(self, client, values): + """ + Get received objects from push message. + + values: Received values. + Returns clone of captured COSEM objects. + """ + if len(values) != len(self.pushObjectList): + raise ValueError("Size of the push object list is different than values.") + pos = 0 + objects = [] + for k, v in self.pushObjectList: + co = GXDLMSCaptureObject() + co.attributeIndex = v.attributeIndex + co.dataIndex = v.dataIndex + objects.append((k, co)) + if v.attributeIndex == 0: + tmp = values[pos] + index = 1 + while index <= k.getAttributeCount(): + client.updateValue(k, index, tmp[index - 1]) + index = 1 + index + else: + client.updateValue(k, v.attributeIndex, values[pos]) + pos = 1 + pos + + def invoke(self, settings, e): + if e.index != 1: + e.error = ErrorCode.READ_WRITE_DENIED + + # + # Activates the push process. + # + def activate(self, client): + return client.method(self, 1, 0, DataType.INT8) + + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # PushObjectList + if all_ or self.canRead(2): + attributes.append(2) + # SendDestinationAndMethod + if all_ or self.canRead(3): + attributes.append(3) + # CommunicationWindow + if all_ or self.canRead(4): + attributes.append(4) + # RandomisationStartInterval + if all_ or self.canRead(5): + attributes.append(5) + # NumberOfRetries + if all_ or self.canRead(6): + attributes.append(6) + # RepetitionDelay + if all_ or self.canRead(7): + attributes.append(7) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 7 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ARRAY + elif index == 3: + ret = DataType.STRUCTURE + elif index == 4: + ret = DataType.ARRAY + elif index == 5: + ret = DataType.UINT16 + elif index == 6: + ret = DataType.UINT8 + elif index == 7: + ret = DataType.UINT16 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + buff = GXByteBuffer() + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + buff.setUInt8(DataType.ARRAY) + _GXCommon.setObjectCount(len(self.pushObjectList), buff) + for k, v in self.pushObjectList: + buff.setUInt8(DataType.STRUCTURE) + buff.setUInt8(4) + _GXCommon.setData(settings, buff, DataType.UINT16, k.objectType) + _GXCommon.setData(settings, buff, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(k.logicalName)) + _GXCommon.setData(settings, buff, DataType.INT8, v.attributeIndex) + _GXCommon.setData(settings, buff, DataType.UINT16, v.dataIndex) + ret = buff + elif e.index == 3: + buff.setUInt8(DataType.STRUCTURE) + buff.setUInt8(3) + _GXCommon.setData(settings, buff, DataType.ENUM, self.service) + if self.destination: + _GXCommon.setData(settings, buff, DataType.OCTET_STRING, self.destination.encode()) + else: + _GXCommon.setData(settings, buff, DataType.OCTET_STRING, None) + _GXCommon.setData(settings, buff, DataType.ENUM, self.message) + ret = buff + elif e.index == 4: + buff.setUInt8(DataType.ARRAY) + _GXCommon.setObjectCount(len(self.communicationWindow), buff) + for k, v in self.communicationWindow: + buff.setUInt8(DataType.STRUCTURE) + buff.setUInt8(2) + _GXCommon.setData(settings, buff, DataType.OCTET_STRING, k) + _GXCommon.setData(settings, buff, DataType.OCTET_STRING, v) + return buff + elif e.index == 5: + ret = self.randomisationStartInterval + elif e.index == 6: + ret = self.numberOfRetries + elif e.index == 7: + ret = self.repetitionDelay + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.pushObjectList = [] + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + if e.value: + for it in e.value: + type_ = it[0] + ln = _GXCommon.toLogicalName(it[1]) + obj = settings.objects.findByLN(type_, ln) + if not obj: + obj = _GXObjectFactory.createObject(type_) + obj.logicalName = ln + co = GXDLMSCaptureObject() + co.attributeIndex = it[2] + co.dataIndex = it[3] + self.pushObjectList.append((obj, co)) + elif e.index == 3: + #pylint: disable=broad-except + if e.value: + self.service = ServiceType(e.value[0]) + try: + if self.service == ServiceType.HDLC: + self.destination = _GXCommon.toLogicalName(e.value[1]) + else: + self.destination = e.value[1].decode() + #If destination is not ASCII string. + if self.destination and not GXByteBuffer.isAsciiString(self.destination): + self.destination = GXByteBuffer.toHex(self.destination) + except Exception: + self.destination = GXByteBuffer.hex(e.value[1]) + self.message = e.value[2] + elif e.index == 4: + self.communicationWindow = [] + if e.value: + for it in e.value: + start = _GXCommon.changeType(settings, it[0], DataType.DATETIME) + end = _GXCommon.changeType(settings, it[1], DataType.DATETIME) + self.communicationWindow.append((start, end)) + elif e.index == 5: + self.randomisationStartInterval = e.value + elif e.index == 6: + self.numberOfRetries = e.value + elif e.index == 7: + self.repetitionDelay = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + from .._GXObjectFactory import _GXObjectFactory + self.pushObjectList = [] + if reader.isStartElement("ObjectList", True): + while reader.isStartElement("Item", True): + ot = ObjectType(reader.readElementContentAsInt("ObjectType")) + ln = reader.readElementContentAsString("LN") + ai = reader.readElementContentAsInt("AI") + di = reader.readElementContentAsInt("DI") + reader.readEndElement("ObjectList") + co = GXDLMSCaptureObject(ai, di) + obj = reader.objects.findByLN(ot, ln) + if not obj: + obj = _GXObjectFactory.createObject(ot) + obj.LogicalName = ln + self.pushObjectList.append((obj, co)) + reader.readEndElement("ObjectList") + self.service = ServiceType(reader.readElementContentAsInt("Service")) + self.destination = reader.readElementContentAsString("Destination") + self.message = MessageType(reader.readElementContentAsInt("Message")) + self.communicationWindow = [] + if reader.isStartElement("CommunicationWindow", True): + while reader.isStartElement("Item", True): + start = reader.readElementContentAsDateTime("Start") + end = reader.readElementContentAsDateTime("End") + self.communicationWindow.append((start, end)) + reader.readEndElement("CommunicationWindow") + self.randomisationStartInterval = reader.readElementContentAsInt("RandomisationStartInterval") + self.numberOfRetries = reader.readElementContentAsInt("NumberOfRetries") + self.repetitionDelay = reader.readElementContentAsInt("RepetitionDelay") + + def save(self, writer): + if self.pushObjectList: + writer.writeStartElement("ObjectList") + for k, v in self.pushObjectList: + writer.writeStartElement("Item") + writer.writeElementString("ObjectType", int(k.objectType)) + writer.writeElementString("LN", k.logicalName) + writer.writeElementString("AI", v.attributeIndex) + writer.writeElementString("DI", v.dataIndex) + writer.writeEndElement() + writer.writeEndElement() + writer.writeElementString("Service", int(self.service)) + writer.writeElementString("Destination", self.destination) + writer.writeElementString("Message", int(self.message)) + writer.writeStartElement("CommunicationWindow") + if self.communicationWindow: + for k, v in self.communicationWindow: + writer.writeStartElement("Item") + writer.writeElementString("Start", k) + writer.writeElementString("End", v) + writer.writeEndElement() + writer.writeEndElement() + writer.writeElementString("RandomisationStartInterval", self.randomisationStartInterval) + writer.writeElementString("NumberOfRetries", self.numberOfRetries) + writer.writeElementString("RepetitionDelay", self.repetitionDelay) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSQualityOfService.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSQualityOfService.py new file mode 100644 index 0000000..6a859ed --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSQualityOfService.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSQualityOfService: + # + #Constructor. + # + def __init__(self): + self.precedence = 0 + self.delay = 0 + self.reliability = 0 + self.peakThroughput = 0 + self.meanThroughput = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSRegister.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSRegister.py new file mode 100644 index 0000000..5efedc3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSRegister.py @@ -0,0 +1,183 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import math +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType, Unit + +# pylint: disable=too-many-instance-attributes +class GXDLMSRegister(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSRegister + """ + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSRegister, self).__init__(ObjectType.REGISTER, ln, sn) + self.value = None + self.scaler = 1 + self.unit = Unit.NONE + + def reset(self, client): + """Reset value.""" + return client.method(self, 1, 0, DataType.INT8) + + def getValues(self): + return [self.logicalName, + self.value, + [self.scaler, self.unit]] + + + def invoke(self, settings, e): + # Resets the value to the default value. + # The default value is an instance specific constant. + if e.index == 1: + self.value = None + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def isRead(self, index): + if index == 3: + return self.unit != 0 + #pylint: disable=super-with-arguments + return super(GXDLMSRegister, self).isRead(index) + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # ScalerUnit + if all_ or not self.isRead(3): + attributes.append(3) + # Value + if all_ or self.canRead(2): + attributes.append(2) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 3 + + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + + def getDataType(self, index): + #pylint: disable=super-with-arguments + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return super(GXDLMSRegister, self).getDataType(index) + if index == 3: + return DataType.STRUCTURE + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.value + if e.index == 3: + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.INT8, math.floor(math.log(self.scaler, 10))) + _GXCommon.setData(settings, data, DataType.ENUM, int(self.unit)) + return data.array() + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + #pylint: disable=broad-except + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if self.scaler != 1 and e.value is not None: + try: + if settings.isServer: + self.value = e.value + else: + self.value = e.value * self.scaler + except Exception: + # Sometimes scaler is set for wrong Object type. + self.value = e.value + else: + self.value = e.value + elif e.index == 3: + # Set default values. + if not e.value: + self.scaler = 1 + self.unit = Unit.NONE + else: + self.scaler = math.pow(10, e.value[0]) + self.unit = Unit(e.value[1]) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.unit = Unit(reader.readElementContentAsInt("Unit", 0)) + self.scaler = reader.readElementContentAsDouble("Scaler", 1) + self.value = reader.readElementContentAsObject("Value", None, self, 2) + + def save(self, writer): + writer.writeElementString("Unit", int(self.unit)) + writer.writeElementString("Scaler", self.scaler, 1) + writer.writeElementObject("Value", self.value, self.getDataType(2), self.getUIDataType(2)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSRegisterActivation.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSRegisterActivation.py new file mode 100644 index 0000000..6d580ff --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSRegisterActivation.py @@ -0,0 +1,216 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .GXDLMSObjectDefinition import GXDLMSObjectDefinition + +# pylint: disable=too-many-instance-attributes +class GXDLMSRegisterActivation(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSRegisterActivation + """ + + # + # Constructor. + # + # @param ln + # Logical Name of the object. + # @param sn + # Short Name of the object. + # + def __init__(self, ln=None, sn=0): + #pylint: disable=super-with-arguments + super(GXDLMSRegisterActivation, self).__init__(ObjectType.REGISTER_ACTIVATION, ln, sn) + self.registerAssignment = list() + self.maskList = list() + self.activeMask = bytearray() + + def getValues(self): + return [self.logicalName, + self.registerAssignment, + self.maskList, + self.activeMask] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + if all_ or not self.isRead(2): + attributes.append(2) + # MaskList + if all_ or not self.isRead(3): + attributes.append(3) + # ActiveMask + if all_ or not self.isRead(4): + attributes.append(4) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 4 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 3 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ARRAY + elif index == 3: + ret = DataType.ARRAY + elif index == 4: + ret = DataType.OCTET_STRING + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.registerAssignment: + data.setUInt8(0) + else: + data.setUInt8(len(self.registerAssignment)) + for it in self.registerAssignment: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.UINT16, it.objectType) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(it.logicalName)) + return data + if e.index == 3: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + data.setUInt8(len(self.maskList)) + for k, v in self.maskList: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, k) + data.setUInt8(DataType.ARRAY) + data.setUInt8(len(v)) + for b in v: + _GXCommon.setData(settings, data, DataType.UINT8, b) + return data + if e.index == 4: + return self.activeMask + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.registerAssignment = [] + if e.value: + for it in e.value: + item = GXDLMSObjectDefinition() + item.objectType = ObjectType(it[0]) + item.logicalName = _GXCommon.toLogicalName(it[1]) + self.registerAssignment.append(item) + elif e.index == 3: + self.maskList = [] + if e.value: + for it in e.value: + self.maskList.append((it[0], it[1])) + elif e.index == 4: + if not e.value: + self.activeMask = None + else: + self.activeMask = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.registerAssignment = [] + if reader.isStartElement("RegisterAssignment", True): + while reader.isStartElement("Item", True): + it = GXDLMSObjectDefinition() + it.objectType = ObjectType(reader.readElementContentAsInt("ObjectType")) + it.logicalName = reader.readElementContentAsString("LN") + self.registerAssignment.append(it) + self.maskList = [] + if reader.isStartElement("MaskList", True): + while reader.isStartElement("Item", True): + mask = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Mask")) + str_ = reader.readElementContentAsString("Index") + i = GXByteBuffer.hexToBytes(str_.replace(";", " ")) + self.maskList.append((mask, i)) + reader.readEndElement("MaskList") + self.activeMask = GXByteBuffer.hexToBytes(reader.readElementContentAsString("ActiveMask")) + + def save(self, writer): + if self.registerAssignment: + writer.writeStartElement("RegisterAssignment") + for it in self.registerAssignment: + writer.writeStartElement("Item") + writer.writeElementString("ObjectType", int(it.objectType)) + writer.writeElementString("LN", it.logicalName) + writer.writeEndElement() + writer.writeEndElement() + + if self.maskList: + writer.writeStartElement("MaskList") + for k, v in self.maskList: + writer.writeStartElement("Item") + writer.writeElementString("Mask", GXByteBuffer.hex(k)) + writer.writeElementString("Index", GXByteBuffer.hex(v).replace(" ", ";")) + writer.writeEndElement() + writer.writeEndElement() + if self.activeMask: + writer.writeElementString("ActiveMask", GXByteBuffer.hex(self.activeMask)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSRegisterMonitor.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSRegisterMonitor.py new file mode 100644 index 0000000..46e6b14 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSRegisterMonitor.py @@ -0,0 +1,238 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .GXDLMSMonitoredValue import GXDLMSMonitoredValue +from .GXDLMSActionSet import GXDLMSActionSet + +# pylint: disable=too-many-instance-attributes +class GXDLMSRegisterMonitor(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSRegisterMonitor + """ + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSRegisterMonitor, self).__init__(ObjectType.REGISTER_MONITOR, ln, sn) + self.thresholds = list() + self.monitoredValue = GXDLMSMonitoredValue() + self.actions = list() + + def getValues(self): + return [self.logicalName, + self.thresholds, + self.monitoredValue, + self.actions] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Thresholds + if all_ or not self.isRead(2): + attributes.append(2) + # MonitoredValue + if all_ or not self.isRead(3): + attributes.append(3) + # Actions + if all_ or not self.isRead(4): + attributes.append(4) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 4 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return super(GXDLMSRegisterMonitor, self).getDataType(index) + if index == 3: + return DataType.ARRAY + if index == 4: + return DataType.ARRAY + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.thresholds + if e.index == 3: + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + # ClassID + _GXCommon.setData(settings, bb, DataType.UINT16, self.monitoredValue.objectType) + # LN. + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(self.monitoredValue.logicalName)) + # Attribute index. + _GXCommon.setData(settings, bb, DataType.INT8, self.monitoredValue.attributeIndex) + return bb + if e.index == 4: + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + if not self.actions: + bb.setUInt8(0) + else: + bb.setUInt8(len(self.actions)) + for it in self.actions: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + # LN + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(it.actionUp.logicalName)) + # ScriptSelector + _GXCommon.setData(settings, bb, DataType.UINT16, it.actionUp.scriptSelector) + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + # LN + _GXCommon.setData(settings, bb, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(it.actionDown.logicalName)) + # ScriptSelector + _GXCommon.setData(settings, bb, DataType.UINT16, it.actionDown.scriptSelector) + return bb + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.thresholds = e.value + elif e.index == 3: + if self.monitoredValue is None: + self.monitoredValue = GXDLMSMonitoredValue() + self.monitoredValue.objectType = e.value[0] + self.monitoredValue.logicalName = _GXCommon.toLogicalName(e.value[1]) + self.monitoredValue.attributeIndex = e.value[2] + elif e.index == 4: + self.actions = [] + if e.value: + for as_ in e.value: + set_ = GXDLMSActionSet() + target = as_[0] + set_.actionUp.logicalName = _GXCommon.toLogicalName(target[0]) + set_.actionUp.scriptSelector = target[1] + target = as_[1] + set_.actionDown.logicalName = _GXCommon.toLogicalName(target[0]) + set_.actionDown.scriptSelector = target[1] + self.actions.append(set_) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.thresholds = [] + if reader.isStartElement("Thresholds", True): + while reader.isStartElement("Value", False): + it = reader.readElementContentAsObject("Value", None) + self.thresholds.append(it) + reader.readEndElement("Thresholds") + if reader.isStartElement("MonitoredValue", True): + self.monitoredValue.objectType = ObjectType(reader.readElementContentAsInt("ObjectType")) + self.monitoredValue.logicalName = reader.readElementContentAsString("LN") + self.monitoredValue.attributeIndex = reader.readElementContentAsInt("Index") + reader.readEndElement("MonitoredValue") + self.actions = [] + if reader.isStartElement("Actions", True): + while reader.isStartElement("Item", True): + it = GXDLMSActionSet() + self.actions.append(it) + if reader.isStartElement("Up", True): + it.actionUp.logicalName = reader.readElementContentAsString("LN", None) + it.actionUp.scriptSelector = reader.readElementContentAsInt("Selector") + reader.readEndElement("Up") + if reader.isStartElement("Down", True): + it.actionDown.logicalName = reader.readElementContentAsString("LN", None) + it.actionDown.scriptSelector = reader.readElementContentAsInt("Selector") + reader.readEndElement("Down") + reader.readEndElement("Actions") + + def save(self, writer): + if self.thresholds: + writer.writeStartElement("Thresholds") + for it in self.thresholds: + writer.writeElementObject("Value", it) + writer.writeEndElement() + if self.monitoredValue: + writer.writeStartElement("MonitoredValue") + writer.writeElementString("ObjectType", int(self.monitoredValue.objectType)) + writer.writeElementString("LN", self.monitoredValue.logicalName) + writer.writeElementString("Index", self.monitoredValue.attributeIndex) + writer.writeEndElement() + if self.actions: + writer.writeStartElement("Actions") + for it in self.actions: + writer.writeStartElement("Item") + writer.writeStartElement("Up") + writer.writeElementString("LN", it.actionUp.logicalName) + writer.writeElementString("Selector", it.actionUp.scriptSelector) + writer.writeEndElement() + writer.writeStartElement("Down") + writer.writeElementString("LN", it.actionDown.logicalName) + writer.writeElementString("Selector", it.actionDown.scriptSelector) + writer.writeEndElement() + writer.writeEndElement() + writer.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSapAssignment.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSapAssignment.py new file mode 100644 index 0000000..eea8b8f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSapAssignment.py @@ -0,0 +1,173 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType + +# pylint: disable=too-many-instance-attributes +class GXDLMSSapAssignment(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSSapAssignment + """ + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + super(GXDLMSSapAssignment, self).__init__(ObjectType.SAP_ASSIGNMENT, ln, sn) + self.sapAssignmentList = list() + + def getValues(self): + return [self.logicalName, + self.sapAssignmentList] + + def addSap(self, client, id_, name): + """Add new SAP item.""" + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + #Add structure size. + data.setUInt8(2) + _GXCommon.setData(client.settings, data, DataType.UINT16, id_) + _GXCommon.setData(client.settings, data, DataType.OCTET_STRING, name.encode()) + return client.method(self, 1, data.array(), DataType.STRUCTURE) + + def removeSap(self, client, name): + """Remove SAP item.""" + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + #Add structure size. + data.setUInt8(2) + _GXCommon.setData(client.settings, data, DataType.UINT16, 0) + _GXCommon.setData(client.settings, data, DataType.OCTET_STRING, name.encode()) + return client.method(self, 1, data.array(), DataType.STRUCTURE) + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # SapAssignmentList + if all_ or not self.isRead(2): + attributes.append(2) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 2 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.ARRAY + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + cnt = len(self.sapAssignmentList) + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + # Add count + _GXCommon.setObjectCount(cnt, data) + if cnt != 0: + for k, v in self.sapAssignmentList: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(2) + # Count + _GXCommon.setData(settings, data, DataType.UINT16, k) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.getBytes(v)) + return data + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.sapAssignmentList = [] + if e.value: + for item in e.value: + str_ = None + if isinstance(item[1], bytearray): + str_ = _GXCommon.changeType(settings, item[1], DataType.STRING) + else: + str_ = str(item[1]) + self.sapAssignmentList.append((item[0], str_)) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.sapAssignmentList = [] + if reader.isStartElement("SapAssignmentList", True): + while reader.isStartElement("Item", True): + sap = reader.readElementContentAsInt("SAP") + ldn = reader.readElementContentAsString("LDN") + self.sapAssignmentList.append((sap, ldn)) + reader.readEndElement("SapAssignmentList") + + def save(self, writer): + if self.sapAssignmentList: + writer.writeStartElement("SapAssignmentList") + for k, v in self.sapAssignmentList: + writer.writeStartElement("Item") + writer.writeElementString("SAP", k) + writer.writeElementString("LDN", v) + writer.writeEndElement() + writer.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSchedule.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSchedule.py new file mode 100644 index 0000000..2523368 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSchedule.py @@ -0,0 +1,281 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from .enums import Weekdays +from ..enums import ObjectType, DataType +from .GXDLMSScheduleEntry import GXDLMSScheduleEntry +from ..GXDate import GXDate +from ..GXTime import GXTime +from ..GXByteBuffer import GXByteBuffer +from ..GXBitString import GXBitString + +# pylint: disable=too-many-instance-attributes +class GXDLMSSchedule(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSSchedule + """ + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + #pylint: disable=super-with-arguments + super(GXDLMSSchedule, self).__init__(ObjectType.SCHEDULE, ln, sn) + # Specifies the scripts to be executed at given times. + self.entries = list() + + def getValues(self): + return [self.logicalName, + self.entries] + + # + # Add entry to entries list. + # + # client: DLMS client. + # entry: Schedule entry. + # Returns Action bytes. + def insert(self, client, entry): + data = GXByteBuffer() + self.addEntry(entry, data) + return client.method(self, 2, data.array(), DataType.STRUCTURE) + + # + # Remove entry from entries list. + # + # client: DLMS client. + # entry: Schedule entry. + # Returns Action bytes. + def delete(self, client, entry): + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + #Add structure size. + data.setUInt8(2) + #firstIndex + _GXCommon.setData(None, data, DataType.UINT16, entry.index) + #lastIndex + _GXCommon.setData(None, data, DataType.UINT16, entry.index) + return client.method(self, 3, data.array(), DataType.STRUCTURE) + + # + # Enable entry from entries list. + # + # client: DLMS client. + # entry: Schedule entries. + # Returns Action bytes. + def enable(self, client, entry): + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + #Add structure size. + data.setUInt8(4) + #firstIndex + _GXCommon.setData(None, data, DataType.UINT16, entry.index) + #lastIndex + _GXCommon.setData(None, data, DataType.UINT16, entry.index) + _GXCommon.setData(None, data, DataType.UINT16, 0) + _GXCommon.setData(None, data, DataType.UINT16, 0) + return client.method(self, 1, data.array(), DataType.STRUCTURE) + + # + # Disable entry from entries list. + # + # client: DLMS client. + # entry: Schedule entries. + # Returns Action bytes. + def disable(self, client, entry): + data = GXByteBuffer() + data.setUInt8(DataType.STRUCTURE) + #Add structure size. + data.setUInt8(4) + #firstIndex + _GXCommon.setData(None, data, DataType.UINT16, 0) + _GXCommon.setData(None, data, DataType.UINT16, 0) + _GXCommon.setData(None, data, DataType.UINT16, entry.index) + #lastIndex + _GXCommon.setData(None, data, DataType.UINT16, entry.index) + return client.method(self, 1, data.array(), DataType.STRUCTURE) + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Entries + if all_ or not self.isRead(2): + attributes.append(2) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 2 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 3 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.ARRAY + raise ValueError("getDataType failed. Invalid attribute index.") + + @classmethod + def addEntry(cls, it, data): + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(10) + #Add index. + data.setUInt8(DataType.UINT16) + data.setUInt16(it.index) + #Add enable. + data.setUInt8(DataType.BOOLEAN) + data.setUInt8(it.enable) + #Add logical Name. + data.setUInt8(DataType.OCTET_STRING) + data.setUInt8(6) + data.set(_GXCommon.logicalNameToBytes(it.logicalName)) + #Add script selector. + data.setUInt8(DataType.UINT16) + data.setUInt16(it.scriptSelector) + #Add switch time. + _GXCommon.setData(None, data, DataType.OCTET_STRING, GXTime(it.switchTime)) + #Add validity window. + data.setUInt8(DataType.UINT16) + data.setUInt16(it.validityWindow) + #Add exec week days. + _GXCommon.setData(None, data, DataType.BITSTRING, GXBitString.toBitString(it.execWeekdays, 7)) + #Add exec spec days. + _GXCommon.setData(None, data, DataType.BITSTRING, it.execSpecDays) + #Add begin date. + _GXCommon.setData(None, data, DataType.OCTET_STRING, GXDate(it.beginDate)) + #Add end date. + _GXCommon.setData(None, data, DataType.OCTET_STRING, GXDate(it.endDate)) + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + _GXCommon.setObjectCount(len(self.entries), data) + for it in self.entries: + self.addEntry(it, data) + return data.array() + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Create a new entry. + # + @classmethod + def createEntry(cls, settings, it): + item = GXDLMSScheduleEntry() + item.index = it[0] + item.enable = it[1] + item.logicalName = _GXCommon.toLogicalName(it[2]) + item.scriptSelector = it[3] + item.switchTime = _GXCommon.changeType(settings, it[4], DataType.TIME) + item.validityWindow = it[5] + item.execWeekdays = Weekdays(it[6].toInteger()) + item.execSpecDays = str(it[7]) + item.beginDate = _GXCommon.changeType(settings, it[8], DataType.DATE) + item.endDate = _GXCommon.changeType(settings, it[9], DataType.DATE) + return item + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.entries = [] + if e.value: + for it in e.value: + self.entries.append(self.createEntry(settings, it)) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.entries = [] + if reader.isStartElement("Entries", True): + while reader.isStartElement("Item", True): + it = GXDLMSScheduleEntry() + it.index = reader.readElementContentAsInt("Index") + it.enable = reader.readElementContentAsInt("Enable") != 0 + it.logicalName = reader.readElementContentAsString("LogicalName") + it.scriptSelector = reader.readElementContentAsInt("ScriptSelector") + it.switchTime = reader.readElementContentAsTime("SwitchTime") + it.validityWindow = reader.readElementContentAsInt("ValidityWindow") + it.execWeekdays = Weekdays(reader.readElementContentAsInt("ExecWeekdays")) + it.execSpecDays = reader.readElementContentAsString("ExecSpecDays") + it.beginDate = reader.readElementContentAsDate("BeginDate") + it.endDate = reader.readElementContentAsDate("EndDate") + self.entries.append(it) + reader.readEndElement("Entries") + + def save(self, writer): + if self.entries: + writer.writeStartElement("Entries") + for it in self.entries: + writer.writeStartElement("Item") + writer.writeElementString("Index", it.index) + writer.writeElementString("Enable", it.enable) + writer.writeElementString("LogicalName", it.logicalName) + writer.writeElementString("ScriptSelector", it.scriptSelector) + writer.writeElementString("SwitchTime", it.switchTime) + writer.writeElementString("ValidityWindow", it.validityWindow) + writer.writeElementString("ExecWeekdays", int(it.execWeekdays)) + writer.writeElementString("ExecSpecDays", it.execSpecDays) + writer.writeElementString("BeginDate", it.beginDate) + writer.writeElementString("EndDate", it.endDate) + writer.writeEndElement() + writer.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScheduleEntry.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScheduleEntry.py new file mode 100644 index 0000000..2290f8b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScheduleEntry.py @@ -0,0 +1,63 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class GXDLMSScheduleEntry: + """Executed scripts.""" + #pylint: disable=too-few-public-methods,too-many-instance-attributes + + def __init__(self): + """#Constructor.""" + # Schedule entry index. + self.index = 0 + # Is Schedule entry enabled. + self.enable = False + # Logical name of the Script table object. + self.logicalName = None + # Script identifier of the script to be executed. + self.scriptSelector = 0 + self.switchTime = None + # Defines a period in minutes, in which an entry shall be processed + # after power fail. + self.validityWindow = 0 + # Days of the week on which the entry is valid. + self.execWeekdays = None + # Perform the link to the IC Special days table, day_id. + self.execSpecDays = None + # Date starting period in which the entry is valid. + self.beginDate = None + # Date starting period in which the entry is valid. + self.endDate = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScript.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScript.py new file mode 100644 index 0000000..ec93d42 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScript.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSScript: + """Script of script table.""" + def __init__(self): + """ + Constructor. + """ + # Script identifier. + self.id = 0 + # Script actions. + self.actions = list() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScriptAction.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScriptAction.py new file mode 100644 index 0000000..7c63384 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScriptAction.py @@ -0,0 +1,62 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .enums import ScriptActionType +from ..enums import ObjectType +from ..enums import DataType +from ..GXByteBuffer import GXByteBuffer + +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods,too-many-instance-attributes +class GXDLMSScriptAction: + # + # Constructor. + # + def __init__(self): + self.type_ = ScriptActionType.NOTHING + self.objectType = ObjectType.NONE + self.parameterDataType = DataType.NONE + # Executed object. + self.target = None + self.logicalName = None + self.index = 0 + self.parameter = None + + def __str__(self): + tmp = None + if isinstance(self.parameter, bytearray): + tmp = GXByteBuffer.hex(self.parameter, True) + else: + tmp = str(self.parameter) + if self.target: + return self.type_.__str__() + " " + str(self.target.objectType) + " " + self.target.logicalName + " " + str(self.index) + " " + tmp + return self.type_.__str__() + " " + str(self.objectType) + " " + self.logicalName + " " + str(self.index) + " " + tmp diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScriptTable.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScriptTable.py new file mode 100644 index 0000000..5410c96 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSScriptTable.py @@ -0,0 +1,242 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .GXDLMSScript import GXDLMSScript +from .GXDLMSScriptAction import GXDLMSScriptAction +from .enums import ScriptActionType + +# pylint: disable=too-many-instance-attributes +class GXDLMSScriptTable(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSScriptTable + """ + def __init__(self, ln=None, sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + super(GXDLMSScriptTable, self).__init__(ObjectType.SCRIPT_TABLE, ln, sn) + self.scripts = list() + + def getValues(self): + return [self.logicalName, + self.scripts] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Scripts + if all_ or not self.isRead(2): + attributes.append(2) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 2 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 1 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.ARRAY + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + cnt = len(self.scripts) + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + # Add count + _GXCommon.setObjectCount(cnt, data) + for it in self.scripts: + data.setUInt8(DataType.STRUCTURE) + # Count + data.setUInt8(2) + # Script_identifier: + _GXCommon.setData(settings, data, DataType.UINT16, it.id) + data.setUInt8(DataType.ARRAY) + # Count + data.setUInt8(len(it.actions)) + for a in it.actions: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(5) + # service_id + _GXCommon.setData(settings, data, DataType.ENUM, a.type_) + if not a.target: + # class_id + _GXCommon.setData(settings, data, DataType.UINT16, a.objectType) + # logical_name + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(a.logicalName)) + else: + # class_id + _GXCommon.setData(settings, data, DataType.UINT16, a.target.objectType) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, _GXCommon.logicalNameToBytes(a.target.logicalName)) + _GXCommon.setData(settings, data, DataType.INT8, a.index) + _GXCommon.setData(settings, data, a.parameterDataType, a.parameter) + return data + e.error = ErrorCode.READ_WRITE_DENIED + return None + + def setValue(self, settings, e): + # pylint: disable=import-outside-toplevel,bad-option-value,too-many-nested-blocks,redefined-variable-type + from .._GXObjectFactory import _GXObjectFactory + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.scripts = [] + if isinstance(e.value, list) and e.value: + if isinstance(e.value[0], list): + for item in e.value: + script = GXDLMSScript() + script.id = item[0] + self.scripts.append(script) + for arr in item[1]: + it = GXDLMSScriptAction() + it.type_ = arr[0] + ot = arr[1] + ln = _GXCommon.toLogicalName(arr[2]) + t = settings.objects.findByLN(ot, ln) + if t is None: + t = _GXObjectFactory.createObject(ot) + t.logicalName = ln + it.target = t + it.index = arr[3] + it.parameter = arr[4] + it.parameterDataType = _GXCommon.getDLMSDataType(it.parameter) + script.actions.append(it) + else: + script = GXDLMSScript() + script.id = e.value[0] + arr = e.value[1] + it = GXDLMSScriptAction() + it.type_ = arr[0] + ot = arr[1] + ln = _GXCommon.toLogicalName(arr[2]) + t = settings.objects.findByLN(ot, ln) + if t is None: + t = _GXObjectFactory.createObject(ot) + t.logicalName = ln + it.target = t + it.index = arr[3] + it.parameter = arr[4] + script.actions.append(it) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def execute(self, client, script): + if isinstance(script, GXDLMSScript): + script = script.id + return client.method(self, 1, script, DataType.UINT16) + + def load(self, reader): + #pylint: disable=import-outside-toplevel + from .._GXObjectFactory import _GXObjectFactory + self.scripts = [] + if reader.isStartElement("Scripts", True): + while reader.isStartElement("Script", True): + it = GXDLMSScript() + self.scripts.append(it) + it.id = reader.readElementContentAsInt("ID") + if reader.isStartElement("Actions", True): + while reader.isStartElement("Action", True): + a = GXDLMSScriptAction() + a.type_ = ScriptActionType(reader.readElementContentAsInt("Type")) + ot = ObjectType(reader.readElementContentAsInt("ObjectType")) + ln = reader.readElementContentAsString("LN") + t = reader.objects.findByLN(ot, ln) + if t is None: + t = _GXObjectFactory.createObject(ot) + t.logicalName = ln + a.target = t + a.index = reader.readElementContentAsInt("Index") + a.parameterDataType = reader.readElementContentAsInt("ParameterDataType") + a.parameter = reader.readElementContentAsObject("Parameter", None) + it.actions.append(a) + reader.readEndElement("Actions") + reader.readEndElement("Scripts") + + def save(self, writer): + if self.scripts: + writer.writeStartElement("Scripts") + for it in self.scripts: + writer.writeStartElement("Script") + writer.writeElementString("ID", it.id) + writer.writeStartElement("Actions") + for a in it.actions: + writer.writeStartElement("Action") + writer.writeElementString("Type", int(a.type_)) + if a.target is None: + writer.writeElementString("ObjectType", int(ObjectType.NONE)) + writer.writeElementString("LN", "0.0.0.0.0.0") + writer.writeElementString("Index", "0") + writer.writeElementString("ParameterDataType", int(DataType.NONE)) + writer.writeElementObject("Parameter", "") + else: + writer.writeElementString("ObjectType", int(a.target.objectType)) + writer.writeElementString("LN", a.target.logicalName) + writer.writeElementString("Index", a.index) + writer.writeElementString("ParameterDataType", int(a.parameterDataType)) + writer.writeElementObject("Parameter", a.parameter) + writer.writeEndElement() + writer.writeEndElement() + writer.writeEndElement() + writer.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSeasonProfile.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSeasonProfile.py new file mode 100644 index 0000000..3510a21 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSeasonProfile.py @@ -0,0 +1,58 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from ..GXByteBuffer import GXByteBuffer + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSSeasonProfile: + # Constructor. + # + # @param forName + # name of season profile. + # @param forStart + # Start time. + # @param forWeekName + # Week name. + def __init__(self, forName=None, forStart=None, forWeekName=None): + self.name = forName + self.start = forStart + self.weekName = forWeekName + + def __str__(self): + if GXByteBuffer.isAsciiString(self.name): + tmp = self.name.decode("utf-8") + else: + tmp = GXByteBuffer.hex(self.name) + return tmp + " " + str(self.start) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSecuritySetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSecuritySetup.py new file mode 100644 index 0000000..4764bba --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSecuritySetup.py @@ -0,0 +1,479 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from __future__ import print_function +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ErrorCode, ObjectType, DataType +from .enums import SecuritySuite, CertificateEntity, CertificateType, SecurityPolicy0, SecurityPolicy +from .GXDLMSCertificateInfo import GXDLMSCertificateInfo +from ..enums.Security import Security +from .enums.GlobalKeyType import GlobalKeyType + +# pylint: disable=too-many-public-methods,too-many-instance-attributes +class GXDLMSSecuritySetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSSecuritySetup + """ + def __init__(self, ln="0.0.43.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + super(GXDLMSSecuritySetup, self).__init__(ObjectType.SECURITY_SETUP, ln, sn) + self.version = 1 + self.securityPolicy = SecurityPolicy0.NOTHING + # Security policy for version 1. + self.securitySuite = SecuritySuite.AES_GCM_128 + self.certificates = list() + # Client system title. + self.clientSystemTitle = None + # Server system title. + self.serverSystemTitle = None + # Available certificates. + self.certificates = list() + + def getValues(self): + return [self.logicalName, + self.securityPolicy, + self.securitySuite, + self.clientSystemTitle, + self.serverSystemTitle, + self.certificates] + + # + # Activates and strengthens the security policy. + # + # client: DLMS client that is used to generate action. + # security: New security level. + # Generated action. + def activate(self, client, security): + return client.method(self, 1, security, DataType.ENUM) + + # + # Updates one or more global keys. + # + # client: DLMS client that is used to generate action. + # kek: Master key, also known as Key Encrypting Key. + # list: List of Global key types and keys. + # Generated action. + # + def globalKeyTransfer(self, client, kek, list_): + # pylint: disable=import-outside-toplevel + from ..secure.GXDLMSSecureClient import GXDLMSSecureClient + if not list_: + raise ValueError("Invalid list. It is empty.") + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + bb.setUInt8(int(len(list_))) + for it in list_: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + _GXCommon.setData(None, bb, DataType.ENUM, it[0]) + tmp = GXDLMSSecureClient.encrypt(kek, it[1]) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, tmp) + return client.method(self, 2, bb.array(), DataType.ARRAY) + + # + # Agree on one or more symmetric keys using the key agreement + # algorithm. + # + # @param client + # DLMS client that is used to generate action. + # @param list + # List of keys. + # Generated action. + # + def __keyAgreement(self, client, list_): + if not list_: + raise ValueError("Invalid list. It is empty.") + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + bb.setUInt8(int(len(list_))) + for it in list_: + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + _GXCommon.setData(None, bb, DataType.ENUM, it.getKey()) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, it) + return client.method(self, 3, bb.array(), DataType.ARRAY) + + # + # Agree on one or more symmetric keys using the key agreement + # algorithm. + # + # @param client + # DLMS client that is used to generate action. + # @param type + # Global key type. + # Generated action. + def keyAgreement(self, client, type_): + bb = GXByteBuffer() + data = self.getEphemeralPublicKeyData(type_, client.ciphering.ephemeralKeyPair.public) + bb.set(data, 1, 64) + print("Signin public key: " + str(client.ciphering.signingKeyPair.public)) + # sign = GXASymmetric.getEphemeralPublicKeySignature(type_ , + # client.ciphering.ephemeralKeyPair.public, + # client.ciphering.signingKeyPair.private) + # bb.set(sign) + # print("Data: " + GXByteBuffer.hex(data)) + #print("Sign: " + GXByteBuffer.hex(sign)) + list_ = list() + list_.append((type_, bb.array())) + return self.__keyAgreement(client, list_) + + def generateKeyPair(self, client, type_): + return client.method(self, 4, type_, DataType.ENUM) + + def generateCertificate(self, client, type_): + return client.method(self, 5, type_, DataType.ENUM) + + + def importCertificate(self, client, certificate): + #If certificate is a string. + if isinstance(certificate, (str)): + certificate = certificate.getEncoded() + return client.method(self, 6, certificate, DataType.OCTET_STRING) + + def exportCertificateByEntity(self, client, entity, type_, systemTitle): + if not systemTitle: + raise ValueError("Invalid system title.") + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(0) + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(entity) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(type_) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, systemTitle) + return client.method(self, 7, bb.array(), DataType.STRUCTURE) + + def exportCertificateBySerial(self, client, serialNumber, issuer): + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(1) + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, serialNumber.encode()) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, issuer.encode()) + return client.method(self, 7, bb.array(), DataType.STRUCTURE) + + def removeCertificateByEntity(self, client, entity, type_, systemTitle): + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(0) + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(entity) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(type_) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, systemTitle) + return client.method(self, 8, bb.array(), DataType.STRUCTURE) + + def removeCertificateBySerial(self, client, serialNumber, issuer): + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(1) + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, serialNumber.encode()) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, issuer.encode()) + return client.method(self, 8, bb.array(), DataType.STRUCTURE) + + @classmethod + def systemTitleToSubject(cls, systemTitle): + bb = GXByteBuffer(systemTitle) + bb.setUInt8(0, 0) + bb.setUInt8(0, 1) + bb.setUInt8(0, 2) + subject = "CN=" + str(systemTitle, 0, 3) + subject += str(bb.getUInt64()) + return subject + + def invoke(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + if self.version == 0: + self.securityPolicy = SecurityPolicy0(e.parameters) + if self.securityPolicy == SecurityPolicy0.AUTHENTICATED: + settings.cipher.security = Security.AUTHENTICATION + elif self.securityPolicy == SecurityPolicy0.ENCRYPTED: + settings.cipher.security = Security.ENCRYPTION + elif self.securityPolicy == SecurityPolicy0.AUTHENTICATED_ENCRYPTED: + settings.cipher.security = Security.AUTHENTICATION_ENCRYPTION + elif self.version == 1: + self.securityPolicy = SecurityPolicy(e.parameters) + if self.securityPolicy & SecurityPolicy.AUTHENTICATED_RESPONSE != 0: + settings.cipher.security = Security(settings.cipher.security | Security.AUTHENTICATION) + if self.securityPolicy & SecurityPolicy.ENCRYPTED_RESPONSE != 0: + settings.cipher.security = Security(settings.cipher.security | Security.ENCRYPTION) + elif e.index == 2: + # pylint: disable=import-outside-toplevel + from ..secure.GXDLMSSecureClient import GXDLMSSecureClient + # if settings.Cipher is null non secure server is used. + # Keys are take in action after reply is generated. + for tmp in e.parameters: + item = tmp + type_ = GlobalKeyType(item[0]) + if type_ == GlobalKeyType.UNICAST_ENCRYPTION: + GXDLMSSecureClient.decrypt(settings.kek, item[1]) + elif type_ == GlobalKeyType.BROADCAST_ENCRYPTION: + e.error = ErrorCode.READ_WRITE_DENIED + elif type_ == GlobalKeyType.AUTHENTICATION: + GXDLMSSecureClient.decrypt(settings.kek, item[1]) + elif type_ == GlobalKeyType.KEK: + GXDLMSSecureClient.decrypt(settings.kek, item[1]) + else: + e.error = ErrorCode.READ_WRITE_DENIED + elif e.index == 3: + e.rror = ErrorCode.HARDWARE_FAULT + elif e.index == 4: + e.rror = ErrorCode.HARDWARE_FAULT + elif e.index == 5: + e.rror = ErrorCode.HARDWARE_FAULT + elif e.index == 6: + e.rror = ErrorCode.HARDWARE_FAULT + elif e.index == 8: + e.error = ErrorCode.READ_WRITE_DENIED + else: + e.error = ErrorCode.READ_WRITE_DENIED + + @classmethod + def applyKeys(cls, settings, e): + #pylint: disable=import-outside-toplevel + from ..secure.GXDLMSSecureClient import GXDLMSSecureClient + for tmp in e.parameters: + item = tmp + type_ = GlobalKeyType(item[0]) + data = GXDLMSSecureClient.decrypt(settings.kek, item[1]) + if type_ == GlobalKeyType.UNICAST_ENCRYPTION: + settings.cipher.blockCipherKey = data + elif type_ == GlobalKeyType.BROADCAST_ENCRYPTION: + e.error = ErrorCode.READ_WRITE_DENIED + elif type_ == GlobalKeyType.AUTHENTICATION: + settings.cipher.authenticationKey = data + elif type_ == GlobalKeyType.KEK: + settings.kek = data + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def getAttributeIndexToRead(self, all_): + attributes = list() + if all_ or not self.logicalName: + attributes.append(1) + if all_ or self.canRead(2): + attributes.append(2) + if all_ or self.canRead(3): + attributes.append(3) + if all_ or self.canRead(4): + attributes.append(4) + if all_ or self.canRead(5): + attributes.append(5) + if self.version != 0: + if all_ or self.canRead(6): + attributes.append(6) + return attributes + + def getAttributeCount(self): + if self.version == 0: + return 5 + return 6 + + def getMethodCount(self): + if self.version == 0: + return 2 + return 8 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.ENUM + elif index == 3: + ret = DataType.ENUM + elif index == 4: + ret = DataType.OCTET_STRING + elif index == 5: + ret = DataType.OCTET_STRING + elif self.version > 0: + if index == 6: + ret = DataType.ARRAY + else: + raise ValueError("getDataType failed. Invalid attribute index.") + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + @classmethod + def getCertificatesByteArray(cls, settings): + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if settings.cipher.certificates: + _GXCommon.setObjectCount(len(settings.cipher.certificates), bb) + for it in settings.cipher.certificates: + bb.setUInt8(DataType.STRUCTURE) + _GXCommon.setObjectCount(6, bb) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(CertificateEntity.SERVER) + bb.setUInt8(DataType.ENUM) + bb.setUInt8(CertificateType.DIGITAL_SIGNATURE) + _GXCommon.addString(it.serialNumber, bb) + _GXCommon.addString(it.issuer, bb) + _GXCommon.addString(it.subject, bb) + _GXCommon.addString("", bb) + else: + bb.setUInt8(0) + return bb.array() + + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.securityPolicy + elif e.index == 3: + ret = self.securitySuite + elif e.index == 4: + ret = self.clientSystemTitle + elif e.index == 5: + ret = self.serverSystemTitle + elif e.index == 6: + ret = self.getCertificatesByteArray(settings) + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + def updateSertificates(self, list_): + self.certificates = [] + if list_: + for it in list_: + info = GXDLMSCertificateInfo() + info.entity = CertificateEntity(it[0]) + info.type_ = CertificateType(it[1]) + info.serialNumber = it[2] + info.issuer = it[3] + info.subject = it[4] + info.subjectAltName = it[5] + self.certificates.append(info) + + def setValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if settings.isServer: + e.error = ErrorCode.READ_WRITE_DENIED + else: + if self.version == 0: + self.securityPolicy = SecurityPolicy0(e.value) + else: + self.securityPolicy = SecurityPolicy(e.value) + elif e.index == 3: + self.securitySuite = e.value + elif e.index == 4: + self.clientSystemTitle = e.value + elif e.index == 5: + self.serverSystemTitle = e.value + elif e.index == 6: + self.updateSertificates(e.value) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + @classmethod + def getEphemeralPublicKeyData(cls, keyId, ephemeralKey): + # pylint: disable=unused-argument + #tmp = + #(GXAsn1Converter.fromByteArray(ephemeralKey.getEncoded())).get(1) + #epk = GXByteBuffer(tmp.value) + #epk.setUInt8(int(keyId), 0) + #return epk + return None + + def load(self, reader): + self.securityPolicy = SecurityPolicy(reader.readElementContentAsInt("SecurityPolicy")) + self.securitySuite = SecuritySuite(reader.readElementContentAsInt("SecuritySuite")) + str_ = reader.readElementContentAsString("ClientSystemTitle") + if str_ is None: + self.clientSystemTitle = None + else: + self.clientSystemTitle = GXByteBuffer.hexToBytes(str_) + str_ = reader.readElementContentAsString("ServerSystemTitle") + if str_ is None: + self.serverSystemTitle = None + else: + self.serverSystemTitle = GXByteBuffer.hexToBytes(str_) + self.certificates = [] + if reader.isStartElement("Certificates", True): + while reader.isStartElement("Item", True): + it = GXDLMSCertificateInfo() + self.certificates.append(it) + it.entity = CertificateEntity(reader.readElementContentAsInt("Entity")) + it.type_ = CertificateType(reader.readElementContentAsInt("Type")) + it.serialNumber = reader.readElementContentAsString("SerialNumber") + it.issuer = reader.readElementContentAsString("Issuer") + it.subject = reader.readElementContentAsString("Subject") + it.subjectAltName = reader.readElementContentAsString("SubjectAltName") + reader.readEndElement("Certificates") + + def save(self, writer): + writer.writeElementString("SecurityPolicy", int(self.securityPolicy)) + writer.writeElementString("SecuritySuite", int(self.securitySuite)) + writer.writeElementString("ClientSystemTitle", GXByteBuffer.hex(self.clientSystemTitle)) + writer.writeElementString("ServerSystemTitle", GXByteBuffer.hex(self.serverSystemTitle)) + writer.writeStartElement("Certificates") + if self.certificates: + for it in self.certificates: + writer.writeStartElement("Item") + writer.writeElementString("Entity", it.entity) + writer.writeElementString("Type", it.type_) + writer.writeElementString("SerialNumber", it.serialNumber) + writer.writeElementString("Issuer", it.issuer) + writer.writeElementString("Subject", it.subject) + writer.writeElementString("SubjectAltName", it.subjectAltName) + writer.writeEndElement() + writer.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSpecialDay.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSpecialDay.py new file mode 100644 index 0000000..e5b6344 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSpecialDay.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +class GXDLMSSpecialDay: + #pylint: disable=bad-option-value,old-style-class,too-few-public-methods + + # + # Constructor. + # + def __init__(self): + self.index = 0 + self.date = None + self.dayId = 0 + + def __str__(self): + return str(self.index) + " " + str(self.date) + " " + str(self.dayId) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSpecialDaysTable.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSpecialDaysTable.py new file mode 100644 index 0000000..f801f4e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSSpecialDaysTable.py @@ -0,0 +1,214 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .GXDLMSSpecialDay import GXDLMSSpecialDay + +# pylint: disable=too-many-instance-attributes +class GXDLMSSpecialDaysTable(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSSpecialDaysTable + """ + + def __init__(self, ln="0.0.11.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + super(GXDLMSSpecialDaysTable, self).__init__(ObjectType.SPECIAL_DAYS_TABLE, ln, sn) + # Special day entries. + self.entries = list() + + # + # Inserts a new entry in the table + # If a special day with the same index or with the same date as an + # already + # defined day is inserted, the old entry will be overwritten. + # + # @param client + # DLMS Client. + # @param entries + # Inserted special day entry. + # Generated byte array. + def insert(self, client, entry): + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(3) + _GXCommon.setData(None, bb, DataType.UINT16, entry.index) + _GXCommon.setData(None, bb, DataType.OCTET_STRING, entry.date) + _GXCommon.setData(None, bb, DataType.UINT8, entry.dayId) + return client.method(self, 1, bb, DataType.STRUCTURE) + + # + # Deletes an entry in the table. + # + # @param client + # DLMS Client. + # @param entry + # Deleted special day item. + # Generated byte array. + def delete(self, client, entry): + return client.method(self, 2, entry.index, DataType.UINT16) + + def getValues(self): + return [self.logicalName, + self.entries] + + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Entries + if all_ or not self.isRead(2): + attributes.append(2) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 2 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 2 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.ARRAY + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + data = GXByteBuffer() + data.setUInt8(DataType.ARRAY) + if not self.entries: + _GXCommon.setObjectCount(0, data) + else: + cnt = len(self.entries) + # Add count + _GXCommon.setObjectCount(cnt, data) + for it in self.entries: + data.setUInt8(DataType.STRUCTURE) + data.setUInt8(3) + # Count + _GXCommon.setData(settings, data, DataType.UINT16, it.index) + _GXCommon.setData(settings, data, DataType.OCTET_STRING, it.date) + _GXCommon.setData(settings, data, DataType.UINT8, it.dayId) + return data + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.entries = [] + if e.value: + for item in e.value: + it = GXDLMSSpecialDay() + it.index = item[0] + it.date = _GXCommon.changeType(settings, item[1], DataType.DATE) + it.dayId = item[2] + self.entries.append(it) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def invoke(self, settings, e): + if e.index != 1 and e.index != 2: + e.error = ErrorCode.READ_WRITE_DENIED + else: + items = list() + if self.entries: + items.append(self.entries) + if e.index == 1: + item = e.parameters + it = GXDLMSSpecialDay() + it.index = item[0] + it.date = _GXCommon.changeType(settings, item[1], DataType.DATE) + it.dayId = item[2] + for item2 in items: + if item2.index == it.index: + items.remove(item2) + break + items.append(it) + elif e.index == 2: + index = e.parameters + for item in items: + if item.index == index: + items.remove(item) + break + self.entries = items + + def load(self, reader): + self.entries = [] + if reader.isStartElement("Entries", True): + while reader.isStartElement("Entry", True): + it = GXDLMSSpecialDay() + it.index = reader.readElementContentAsInt("Index") + it.date = reader.readElementContentAsDate("Date") + it.dayId = reader.readElementContentAsInt("DayId") + self.entries.append(it) + reader.readEndElement("Entries") + + def save(self, writer): + if self.entries: + writer.writeStartElement("Entries") + for it in self.entries: + writer.writeStartElement("Entry") + writer.writeElementString("Index", it.index) + writer.writeElementString("Date", it.date) + writer.writeElementString("DayId", it.dayId) + writer.writeEndElement() + writer.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSTarget.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSTarget.py new file mode 100644 index 0000000..f2044d5 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSTarget.py @@ -0,0 +1,47 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXDLMSTarget: + def __init__(self, target=None, attributeIndex=0, dataIndex=0): + """ + Constructor. + target: Target object. + attributeIndex: Attribute index. + dataIndex: Data Index. + """ + self.target = target + self.attributeIndex = attributeIndex + self.dataIndex = dataIndex diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSTcpUdpSetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSTcpUdpSetup.py new file mode 100644 index 0000000..8d0cd7d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSTcpUdpSetup.py @@ -0,0 +1,190 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType + +# pylint: disable=too-many-instance-attributes +class GXDLMSTcpUdpSetup(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSTcpUdpSetup + """ + def __init__(self, ln="0.0.25.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + super(GXDLMSTcpUdpSetup, self).__init__(ObjectType.TCP_UDP_SETUP, ln, sn) + self.port = 4059 + self.inactivityTimeout = 180 + self.maximumSegmentSize = 576 + self.ipReference = None + self.maximumSimultaneousConnections = 0 + + def getValues(self): + return [self.logicalName, + self.port, + self.ipReference, + self.maximumSegmentSize, + self.maximumSimultaneousConnections, + self.inactivityTimeout] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Port + if all_ or not self.isRead(2): + attributes.append(2) + # IPReference + if all_ or not self.isRead(3): + attributes.append(3) + # MaximumSegmentSize + if all_ or not self.isRead(4): + attributes.append(4) + # MaximumSimultaneousConnections + if all_ or not self.isRead(5): + attributes.append(5) + # InactivityTimeout + if all_ or not self.isRead(6): + attributes.append(6) + return attributes + + def getAttributeCount(self): + return 6 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.UINT16 + elif index == 3: + ret = DataType.OCTET_STRING + elif index == 4: + ret = DataType.UINT16 + elif index == 5: + ret = DataType.UINT8 + elif index == 6: + ret = DataType.UINT16 + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.port + elif e.index == 3: + ret = _GXCommon.logicalNameToBytes(self.ipReference) + elif e.index == 4: + ret = self.maximumSegmentSize + elif e.index == 5: + ret = self.maximumSimultaneousConnections + elif e.index == 6: + ret = self.inactivityTimeout + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + if e.value is None: + self.port = 4059 + else: + self.port = e.value + elif e.index == 3: + if e.value is None: + self.ipReference = None + else: + if isinstance(e.value, bytearray): + self.ipReference = _GXCommon.toLogicalName(e.value) + else: + self.ipReference(e.value) + elif e.index == 4: + if e.value is None: + self.maximumSegmentSize = 576 + else: + self.maximumSegmentSize = e.value + elif e.index == 5: + if e.value is None: + self.maximumSimultaneousConnections = 1 + else: + self.maximumSimultaneousConnections = e.value + elif e.index == 6: + if e.value is None: + self.inactivityTimeout = 180 + else: + self.inactivityTimeout = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.port = reader.readElementContentAsInt("Port") + self.ipReference = reader.readElementContentAsString("IPReference") + self.maximumSegmentSize = reader.readElementContentAsInt("MaximumSegmentSize") + self.maximumSimultaneousConnections = reader.readElementContentAsInt("MaximumSimultaneousConnections") + self.inactivityTimeout = reader.readElementContentAsInt("InactivityTimeout") + + def save(self, writer): + writer.writeElementString("Port", self.port) + writer.writeElementString("IPReference", self.ipReference) + writer.writeElementString("MaximumSegmentSize", self.maximumSegmentSize) + writer.writeElementString("MaximumSimultaneousConnections", self.maximumSimultaneousConnections) + writer.writeElementString("InactivityTimeout", self.inactivityTimeout) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSTokenGateway.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSTokenGateway.py new file mode 100644 index 0000000..2226973 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSTokenGateway.py @@ -0,0 +1,219 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..GXByteBuffer import GXByteBuffer +from ..enums import ObjectType, DataType +from .enums import TokenDelivery, TokenStatusCode + +# pylint: disable=too-many-instance-attributes +class GXDLMSTokenGateway(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSTokenGateway + """ + + def __init__(self, ln="0.0.19.40.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + super(GXDLMSTokenGateway, self).__init__(ObjectType.TOKEN_GATEWAY, ln, sn) + # Descriptions. + self.descriptions = list() + # Token Delivery method. + self.deliveryMethod = TokenDelivery.LOCAL + # Token status code. + self.statusCode = TokenStatusCode.FORMAT_OK + # Token. + self.token = list() + # Time. + self.time = None + # Token data value. + self.dataValue = None + + def getValues(self): + return [self.logicalName, + self.token, + self.time, + self.descriptions, + self.deliveryMethod, + [self.statusCode, self.dataValue]] + + # + # Returns collection of attributes to read. If attribute is static + # and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Token + if all_ or self.canRead(2): + attributes.append(2) + # Time + if all_ or self.canRead(3): + attributes.append(3) + # Description + if all_ or self.canRead(4): + attributes.append(4) + # DeliveryMethod + if all_ or self.canRead(5): + attributes.append(5) + # Status + if all_ or self.canRead(6): + attributes.append(6) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + + return 6 + + # + # Returns amount of methods. + # + def getMethodCount(self): + + return 1 + + def getDataType(self, index): + if index == 1: + ret = DataType.OCTET_STRING + elif index == 2: + ret = DataType.OCTET_STRING + elif index == 3: + ret = DataType.OCTET_STRING + elif index == 4: + ret = DataType.ARRAY + elif index == 5: + ret = DataType.ENUM + elif index == 6: + ret = DataType.STRUCTURE + else: + raise ValueError("getDataType failed. Invalid attribute index.") + return ret + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + #pylint: disable=bad-option-value,redefined-variable-type + bb = None + if e.index == 1: + ret = _GXCommon.logicalNameToBytes(self.logicalName) + elif e.index == 2: + ret = self.token + elif e.index == 3: + ret = self.time + elif e.index == 4: + bb = GXByteBuffer() + bb.setUInt8(DataType.ARRAY) + if not self.descriptions: + bb.setUInt8(0) + else: + bb.setUInt8(len(self.descriptions)) + for it in self.descriptions: + bb.setUInt8(DataType.OCTET_STRING) + bb.setUInt8(int(len(it))) + bb.set(it) + ret = bb + elif e.index == 5: + ret = self.deliveryMethod + elif e.index == 6: + bb = GXByteBuffer() + bb.setUInt8(DataType.STRUCTURE) + bb.setUInt8(2) + _GXCommon.setData(settings, bb, DataType.ENUM, self.statusCode) + _GXCommon.setData(settings, bb, DataType.BITSTRING, self.dataValue) + ret = bb + else: + e.error = ErrorCode.READ_WRITE_DENIED + return ret + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.token = e.value + elif e.index == 3: + self.time = _GXCommon.changeType(settings, e.value, DataType.DATETIME) + elif e.index == 4: + self.descriptions = [] + if e.value: + for it in e.value: + self.descriptions.append(it) + elif e.index == 5: + self.deliveryMethod = e.value + elif e.index == 6: + self.statusCode = e.value[0] + self.dataValue = str(e.value[1]) + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.token = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Token")) + self.time = reader.readElementContentAsDateTime("Time") + self.descriptions = [] + if reader.isStartElement("Descriptions", True): + while reader.isStartElement("Item", True): + self.descriptions.append(reader.readElementContentAsString("Name")) + reader.readEndElement("Descriptions") + self.deliveryMethod = reader.readElementContentAsInt("DeliveryMethod") + self.statusCode = reader.readElementContentAsInt("Status") + self.dataValue = reader.readElementContentAsString("Data") + + def save(self, writer): + writer.writeElementString("Token", GXByteBuffer.hex(self.token, False)) + writer.writeElementString("Time", self.time) + if self.descriptions: + writer.writeStartElement("Descriptions") + for it in self.descriptions: + writer.writeStartElement("Item") + writer.writeElementString("Name", it) + writer.writeEndElement() + writer.writeEndElement() + writer.writeElementString("DeliveryMethod", int(self.deliveryMethod)) + writer.writeElementString("Status", int(self.statusCode)) + writer.writeElementString("Data", self.dataValue) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSUtilityTables.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSUtilityTables.py new file mode 100644 index 0000000..361c2cd --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSUtilityTables.py @@ -0,0 +1,151 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSObject import GXDLMSObject +from .IGXDLMSBase import IGXDLMSBase +from ..enums import ErrorCode +from ..internal._GXCommon import _GXCommon +from ..enums import ObjectType, DataType +from ..GXByteBuffer import GXByteBuffer + +# pylint: disable=too-many-instance-attributes +class GXDLMSUtilityTables(GXDLMSObject, IGXDLMSBase): + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSUtilityTables + """ + + def __init__(self, ln="0.0.65.0.0.255", sn=0): + """ + Constructor. + + ln : Logical Name of the object. + sn : Short Name of the object. + """ + GXDLMSObject.__init__(self, ObjectType.UTILITY_TABLES, ln, sn) + # Table Id. + self.tableId = 0 + # Contents of the table. + self.buffer = None + + def getValues(self): + tmp = 0 + if self.buffer: + tmp = len(self.buffer) + return [self.logicalName, + self.tableId, + tmp, + self.buffer] + + # + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + def getAttributeIndexToRead(self, all_): + attributes = list() + # LN is static and read only once. + if all_ or not self.logicalName: + attributes.append(1) + # Table Id. + if all_ or self.canRead(2): + attributes.append(2) + #Length + if all_ or self.canRead(3): + attributes.append(3) + #Buffer + if all_ or self.canRead(4): + attributes.append(4) + return attributes + + # + # Returns amount of attributes. + # + def getAttributeCount(self): + return 4 + + # + # Returns amount of methods. + # + def getMethodCount(self): + return 0 + + def getDataType(self, index): + if index == 1: + return DataType.OCTET_STRING + if index == 2: + return DataType.UINT16 + if index == 3: + return DataType.UINT32 + if index == 4: + return DataType.OCTET_STRING + raise ValueError("getDataType failed. Invalid attribute index.") + + # + # Returns value of given attribute. + # + def getValue(self, settings, e): + if e.index == 1: + return _GXCommon.logicalNameToBytes(self.logicalName) + if e.index == 2: + return self.tableId + if e.index == 3: + if self.buffer: + return len(self.buffer) + return 0 + if e.index == 4: + return self.buffer + e.error = ErrorCode.READ_WRITE_DENIED + return None + + # + # Set value of given attribute. + # + def setValue(self, settings, e): + if e.index == 1: + self.logicalName = _GXCommon.toLogicalName(e.value) + elif e.index == 2: + self.tableId = e.value + elif e.index == 3: + pass + elif e.index == 4: + self.buffer = e.value + else: + e.error = ErrorCode.READ_WRITE_DENIED + + def load(self, reader): + self.tableId = reader.readElementContentAsInt("Id", None) + self.buffer = GXByteBuffer.hexToBytes(reader.readElementContentAsString("Buffer")) + + def save(self, writer): + writer.writeElementString("Id", self.tableId, False) + writer.writeElementString("Buffer", GXByteBuffer.hex(self.buffer)) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSWeekProfile.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSWeekProfile.py new file mode 100644 index 0000000..63f411e --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXDLMSWeekProfile.py @@ -0,0 +1,52 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..GXByteBuffer import GXByteBuffer + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class GXDLMSWeekProfile: + #pylint: disable=too-many-instance-attributes,too-few-public-methods + def __init__(self): + """Constructor.""" + self.name = None + self.monday = 0 + self.tuesday = 0 + self.wednesday = 0 + self.thursday = 0 + self.friday = 0 + self.saturday = 0 + self.sunday = 0 + + def __str__(self): + return GXByteBuffer.hex(self.name) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacAvailableSwitch.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacAvailableSwitch.py new file mode 100644 index 0000000..3f76b98 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacAvailableSwitch.py @@ -0,0 +1,51 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXMacAvailableSwitch: + # + # Constructor. + # + def __init__(self): + # EUI-48 of the subnetwork. + self.sna = None + # SID of this switch. + self.lsId = 0 + # Level of this switch in subnetwork hierarchy. + self.level = 0 + # The received signal level for this switch. + self.rxLevel = 0 + # The signal to noise ratio for this switch. + self.rxSnr = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacDirectTable.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacDirectTable.py new file mode 100644 index 0000000..946c1ca --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacDirectTable.py @@ -0,0 +1,56 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXMacDirectTable: + # + # Constructor. + # + def __init__(self): + # SID of switch through which the source service node is connected. + self.sourceSId = 0 + # NID allocated to the source service node. + self.sourceLnId = 0 + # LCID allocated to this connection at the source. + self.sourceLcId = 0 + # SID of the switch through which the destination service node is + # connected. + self.destinationSId = 0 + # NID allocated to the destination service node. + self.destinationLnId = 0 + # LCID allocated to this connection at the destination. + self.destinationLcId = 0 + # Entry DID is the EUI-48 of the direct switch. + self.did = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacMulticastEntry.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacMulticastEntry.py new file mode 100644 index 0000000..4d6c391 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacMulticastEntry.py @@ -0,0 +1,45 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXMacMulticastEntry: + # + # Constructor. + # + def __init__(self): + #LCID of multicast group + self.id = 0 + # Number of child nodes. + self.members = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacPhyCommunication.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacPhyCommunication.py new file mode 100644 index 0000000..9128c7f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXMacPhyCommunication.py @@ -0,0 +1,60 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXMacPhyCommunication: + # + # Constructor. + # + # pylint: disable=too-many-instance-attributes + def __init__(self): + # EUI is the EUI-48 of the other device. + self.eui = None + # The tx power of GPDU packets sent to the device. + self.txPower = 0 + # The Tx coding of GPDU packets sent to the device. + self.txCoding = 0 + # The Rx coding of GPDU packets received from the device. + self.rxCoding = 0 + # The Rx power level of GPDU packets received from the device. + self.rxLvl = 0 + # SNR of GPDU packets received from the device. + self.snr = 0 + # The number of times the Tx power was modified. + self.txPowerModified = 0 + # The number of times the Tx coding was modified. + self.txCodingModified = 0 + # The number of times the Rx coding was modified. + self.rxCodingModified = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXNeighborDiscoverySetup.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXNeighborDiscoverySetup.py new file mode 100644 index 0000000..2d348bb --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXNeighborDiscoverySetup.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXNeighborDiscoverySetup: + """ + Contains the configuration to be used for both routers and hosts to + support the Neighbor Discovery protocol for IPv6. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSIp6Setup + """ + # Constructor. + def __init__(self): + self.maxRetry = 3 + self.retryWaitTime = 10000 + self.sendPeriod = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXTokenGatewayConfiguration.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXTokenGatewayConfiguration.py new file mode 100644 index 0000000..5cec894 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXTokenGatewayConfiguration.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXTokenGatewayConfiguration: + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAccount + """ + + # Constructor. + def __init__(self): + # Credit reference. + self.creditReference = None + # Token proportion. + self.tokenProportion = 0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXUnitCharge.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXUnitCharge.py new file mode 100644 index 0000000..6186d5d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXUnitCharge.py @@ -0,0 +1,57 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXChargePerUnitScaling import GXChargePerUnitScaling +from .GXCommodity import GXCommodity + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class,too-few-public-methods +class GXUnitCharge: + """ + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSCharge + """ + + def __init__(self): + """ + Constructor. + """ + # Charge per unit scaling. + self.chargePerUnitScaling = GXChargePerUnitScaling() + # Commodity. + self.commodity = GXCommodity() + # Charge tables. + self.chargeTables = list() + + def __str__(self): + return str(self.chargePerUnitScaling) + str(self.commodity) + str(self.chargeTables) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXXmlReader.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXXmlReader.py new file mode 100644 index 0000000..1a79783 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXXmlReader.py @@ -0,0 +1,196 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import xml.etree.cElementTree as ET +from collections import deque +from ..enums import DataType +from ..GXByteBuffer import GXByteBuffer +from ..GXDateTime import GXDateTime +from ..GXDate import GXDate +from ..GXTime import GXTime +from ..GXDLMSConverter import GXDLMSConverter + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class GXXmlReader: + """Read serialized COSEM object from the file.""" + + def __init__(self, filename, objects): + """ + Constructor. + filename: File name. + """ + self.tree = ET.parse(filename) + self.root = self.tree.getroot() + self.iter = self.tree.iter() + self.currentElement = None + self.objects = objects + dd = deque(self.tree.iter(), maxlen=1) + self.last_element = dd.pop() + + def getNext(self): + if self.isEOF(): + return None + self.currentElement = next(self.iter) + return self.currentElement + + def isEOF(self): + return self.currentElement == self.last_element + + @classmethod + def read(cls): + return True + + def readEndElement(self, name): + if name == self.name(): + self.getNext() + + def name(self): + return self.currentElement.tag + + def isStartElement(self, name=None, getNext=False): + if name is None: + ret = isinstance(self.currentElement, (ET.Element,)) + else: + ret = isinstance(self.currentElement, (ET.Element,)) and self.currentElement.tag == name + if ret and getNext: + self.getNext() + return ret + + def getAttribute(self, index): + for it in self.currentElement.attrib: + if index == 0: + return self.currentElement.attrib[it] + index -= 1 + return None + + def readElementContentAsInt(self, name, defaultValue=0): + if name == self.name(): + str_ = self.currentElement.text + if not str_: + ret = 0 + else: + ret = int(str_) + self.getNext() + return ret + return defaultValue + + def readElementContentAsLong(self, name, defaultValue=0): + return self.readElementContentAsInt(name, defaultValue) + + def readElementContentAsULong(self, name, defaultValue=0): + return self.readElementContentAsInt(name, defaultValue) + + def readElementContentAsDouble(self, name, defaultValue=0): + if name == self.name(): + str_ = self.currentElement.text + ret = float(str_) + self.getNext() + return ret + return defaultValue + + def readArray(self): + list_ = list() + while self.isStartElement("Item", False): + list_.append(self.readElementContentAsObject("Item", None)) + return list_ + + def readElementContentAsObject(self, name, defaultValue, obj=None, index=0): + #pylint: disable=bad-option-value,redefined-variable-type + if name == self.name(): + ret = None + str_ = self.getAttribute(0) + if str_: + tp = int(str_) + else: + tp = 0 + str_ = self.getAttribute(1) + if str_: + uiType = int(str_) + else: + uiType = tp + if obj: + obj.setDataType(index, tp) + if uiType: + obj.setUIDataType(index, uiType) + if tp in (DataType.ARRAY, DataType.STRUCTURE): + self.getNext() + ret = self.readArray() + else: + str_ = self.currentElement.text + if uiType == DataType.OCTET_STRING: + ret = GXByteBuffer.hexToBytes(str_) + elif uiType == DataType.DATETIME: + ret = GXDateTime(str_, "%m/%d/%Y %H:%M:%S") + elif uiType == DataType.DATE: + ret = GXDate(str_, "%m/%d/%Y") + elif uiType == DataType.TIME: + ret = GXTime(str_, "%H:%M:%S") + elif uiType == DataType.NONE: + ret = str_ + else: + ret = GXDLMSConverter.changeType(str_, uiType) + self.getNext() + return ret + return defaultValue + + def readElementContentAsString(self, name, defaultValue=None): + if name == self.currentElement.tag: + str_ = self.currentElement.text + self.getNext() + return str_ + return defaultValue + + def readElementContentAsDateTime(self, name): + if name == self.currentElement.tag: + str_ = self.currentElement.text + self.getNext() + if str_: + return GXDateTime(str_, "%m/%d/%Y %H:%M:%S") + return None + + def readElementContentAsDate(self, name): + if name == self.currentElement.tag: + str_ = self.currentElement.text + self.getNext() + if str_: + return GXDate(str_, "%m/%d/%Y") + return None + + def readElementContentAsTime(self, name): + if name == self.currentElement.tag: + str_ = self.currentElement.text + self.getNext() + if str_: + return GXTime(str_, "%H:%M:%S") + return None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXXmlWriter.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXXmlWriter.py new file mode 100644 index 0000000..68927ce --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXXmlWriter.py @@ -0,0 +1,169 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import xml.etree.cElementTree as ET +#pylint: disable=broad-except,no-name-in-module +from ..GXByteBuffer import GXByteBuffer +from ..GXDateTime import GXDateTime +from ..GXDLMSConverter import GXDLMSConverter +from ..internal._GXCommon import _GXCommon +from ..enums import DataType +from ..GXArray import GXArray +from ..GXStructure import GXStructure +from ..GXIntEnum import GXIntEnum +from ..GXIntFlag import GXIntFlag + + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class GXXmlWriter: + """ + Save COSEM object to the file. + """ + + def __init__(self): + """ + Constructor. + """ + self.objects = list() + self.skipDefaults = False + + def getTarget(self): + return self.objects[len(self.objects) - 1] + + # pylint: disable=unused-argument + def writeStartElement(self, elementName, attributeName=None, value=None, newLine=True): + target = None + if value: + target = ET.SubElement(self.getTarget(), elementName) + else: + target = ET.SubElement(self.getTarget(), elementName) + + if attributeName: + target.set(attributeName, value) + self.objects.append(target) + return target + + def writeEndElement(self): + self.objects.pop() + + + def writeElementString(self, name, value, defaultValue=None): + if isinstance(value, (GXIntEnum, GXIntFlag)): + value = int(value) + + if not(value and self.skipDefaults) or value != defaultValue: + if value is None: + ET.SubElement(self.getTarget(), name) + elif isinstance(value, str): + ET.SubElement(self.getTarget(), name).text = value + elif isinstance(value, GXDateTime): + ET.SubElement(self.getTarget(), name).text = value.toFormatMeterString("%m/%d/%Y %H:%M:%S") + elif isinstance(value, bool): + if value: + ET.SubElement(self.getTarget(), name).text = "1" + else: + ET.SubElement(self.getTarget(), name).text = "0" + elif isinstance(value, int): + ET.SubElement(self.getTarget(), name).text = str(value) + elif isinstance(value, (bytearray, bytes)): + ET.SubElement(self.getTarget(), name).text = GXByteBuffer.hex(value) + elif isinstance(value, (float)): + ET.SubElement(self.getTarget(), name).text = str(value).replace(",", ".") + + def writeArray(self, data): + if isinstance(data, (list)): + arr = data + for tmp in arr: + if isinstance(tmp, bytearray): + self.writeElementObject("Item", tmp) + elif isinstance(tmp, (GXArray,)): + self.writeStartElement("Item", "Type", str(int(DataType.ARRAY)), True) + self.writeArray(tmp) + self.writeEndElement() + elif isinstance(tmp, (GXStructure,)): + self.writeStartElement("Item", "Type", str(int(DataType.STRUCTURE)), True) + self.writeArray(tmp) + self.writeEndElement() + else: + self.writeElementObject("Item", tmp) + + # + # Write object value to file. + # + # @param name + # Object name. + # @param value + # Object value. + # + # pylint: disable=too-many-arguments + def writeElementObject(self, name, value, dt=DataType.NONE, uiType=DataType.NONE): + if isinstance(value, (GXIntEnum, GXIntFlag)): + value = int(value) + + if value or not self.skipDefaults: + if value is None: + target = self.writeStartElement(name) + self.writeEndElement() + return + if dt == DataType.OCTET_STRING: + if uiType == DataType.STRING: + value = str(value) + elif uiType == DataType.OCTET_STRING: + value = GXByteBuffer.hex(value) + elif dt != DataType.NONE and not isinstance(value, (float, GXDateTime)): + value = GXDLMSConverter.changeType(value, dt) + + if dt == DataType.NONE: + dt = _GXCommon.getDLMSDataType(value) + + target = self.writeStartElement(name, "Type", str(int(dt)), False) + if uiType != DataType.NONE and uiType != dt and (uiType != DataType.STRING or dt == DataType.OCTET_STRING): + target.set("UIType", str(int(uiType))) + if dt in (DataType.ARRAY, DataType.STRUCTURE): + self.writeArray(value) + else: + if isinstance(value, (float)): + target.set("UIType", str(int(DataType.FLOAT64))) + if isinstance(value, GXDateTime): + target.text = value.toFormatMeterString("%m/%d/%Y %H:%M:%S") + elif isinstance(value, (bytearray, bytes)): + target.text = GXByteBuffer.hex(value) + elif isinstance(value, bool): + if value: + target.text = "1" + else: + target.text = "0" + else: + target.text = str(value) + self.writeEndElement() diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXXmlWriterSettings.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXXmlWriterSettings.py new file mode 100644 index 0000000..677fec8 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXXmlWriterSettings.py @@ -0,0 +1,42 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +# pylint: disable=bad-option-value,old-style-class +class GXXmlWriterSettings: + """XML write settings.""" + def __init__(self): + # Are attribute values also serialized. + self.values = True + # Are values saved in old way. + self.old = False diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXxDLMSContextType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXxDLMSContextType.py new file mode 100644 index 0000000..1894923 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/GXxDLMSContextType.py @@ -0,0 +1,111 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..enums import Conformance +from ..GXByteBuffer import GXByteBuffer + +#pylint: disable=too-many-instance-attributes,too-few-public-methods, useless-object-inheritance +class GXxDLMSContextType(object): + # + # Constructor. + # + def __init__(self): + # Server settings. + self.__settings = None + # Conformance. + self.__conformance = Conformance.NONE + # Maximum receive PDU size. + self.__maxReceivePduSize = 0 + # Maximum Send PDU size. + self.__maxSendPduSize = 0 + # DLMS Version Number. + self.__dlmsVersionNumber = 0 + # Quality Of Service. + self.qualityOfService = 0 + # CypheringInfo. + self.cypheringInfo = [] + + def getConformance(self): + if self.__settings: + return self.__settings.proposedConformance + return self.__conformance + + def setConformance(self, value): + if self.__settings: + self.__settings.proposedConformance = value + self.__conformance = value + + def getMaxReceivePduSize(self): + if self.__settings: + return self.__settings.maxServerPDUSize + return self.__maxReceivePduSize + + def setMaxReceivePduSize(self, value): + if self.__settings: + self.__settings.maxServerPDUSize = value + self.__maxReceivePduSize = value + + def getMaxSendPduSize(self): + if self.__settings: + return self.__settings.maxServerPDUSize + return self.__maxSendPduSize + + def setMaxSendPduSize(self, value): + if self.__settings: + self.__settings.maxServerPDUSize = value + self.__maxSendPduSize = value + + def getDlmsVersionNumber(self): + if self.__settings: + return self.__settings.dlmsVersionNumber + return self.__dlmsVersionNumber + + def setDlmsVersionNumber(self, value): + if self.__settings: + self.__settings.dlmsVersionNumber = value + self.__dlmsVersionNumber = value + + #Conformance + conformance = property(getConformance, setConformance) + + maxReceivePduSize = property(getMaxReceivePduSize, setMaxReceivePduSize) + + maxSendPduSize = property(getMaxSendPduSize, setMaxSendPduSize) + + dlmsVersionNumber = property(getDlmsVersionNumber, setDlmsVersionNumber) + + def __str__(self): + str_ = str(self.conformance) + " " + str(self.maxReceivePduSize) + " " + str_ += str(self.maxSendPduSize) + " " + str(self.dlmsVersionNumber) + " " + str_ += str(self.qualityOfService) + " " + GXByteBuffer.hex(self.cypheringInfo, True) + return str_ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/IGXDLMSBase.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/IGXDLMSBase.py new file mode 100644 index 0000000..9c25813 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/IGXDLMSBase.py @@ -0,0 +1,113 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from abc import ABCMeta, abstractmethod + +ABC = ABCMeta('ABC', (object,), {'__slots__': ()}) + +class IGXDLMSBase(ABC): + + # Returns collection of attributes to read. If attribute is static and + # already read or device is returned HW error it is not returned. + # + # @param all_ + # All are attributes get. + # @return Collection of attributes to read. + @abstractmethod + def getAttributeIndexToRead(self, all_): + """ This is interface method """ + + # @return Amount of attributes. + @abstractmethod + def getAttributeCount(self): + """ This is interface method """ + + # @return Amount of methods. + @abstractmethod + def getMethodCount(self): + """ This is interface method """ + + # Returns value of given attribute. + # + # @param settings + # DLMS settings. + # @param e + # Get parameter. + # @return Returned value. + @abstractmethod + def getValue(self, settings, e): + """ This is interface method """ + + # Set value of given attribute. + # + # @param settings + # DLMS settings. + # @param e + # Set parameter. + @abstractmethod + def setValue(self, settings, e): + """ This is interface method """ + + # Server calls invoke method. + # + # @param settings + # Server settings. + # @param e + # Invoke parameter. + # @return Reply for the client. + @abstractmethod + def invoke(self, settings, e): + """ This is interface method """ + + # Load object content from XML. + # + # @param reader + # XML reader. + @abstractmethod + def load(self, reader): + """ This is interface method """ + + # Save object content to XML. + # + # @param writer + # XML writer. + @abstractmethod + def save(self, writer): + """ This is interface method """ + + # Handle actions after Load. + # + # @param reader + # XML reader. + def postLoad(self, reader): + """ This is interface method """ diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/__init__.py new file mode 100644 index 0000000..afe5bcf --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/__init__.py @@ -0,0 +1,129 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXAdjacentCell import GXAdjacentCell +from .GXApplicationContextName import GXApplicationContextName +from .GXAuthenticationMechanismName import GXAuthenticationMechanismName +from .GXChargePerUnitScaling import GXChargePerUnitScaling +from .GXChargeTable import GXChargeTable +from .GXCommodity import GXCommodity +from .GXCreditChargeConfiguration import GXCreditChargeConfiguration +from .GXCurrency import GXCurrency +from .GXDLMSRegister import GXDLMSRegister +from .GXDLMSDemandRegister import GXDLMSDemandRegister +from .GXDLMSRegisterMonitor import GXDLMSRegisterMonitor +from .GXDLMSRegisterActivation import GXDLMSRegisterActivation +from .GXDLMSExtendedRegister import GXDLMSExtendedRegister +from .GXDLMSAccount import GXDLMSAccount +from .GXDLMSActionItem import GXDLMSActionItem +from .GXDLMSActionSchedule import GXDLMSActionSchedule +from .GXDLMSActionSet import GXDLMSActionSet +from .GXDLMSActivityCalendar import GXDLMSActivityCalendar +from .GXDLMSAssociationLogicalName import GXDLMSAssociationLogicalName +from .GXDLMSAssociationShortName import GXDLMSAssociationShortName +from .GXDLMSAutoAnswer import GXDLMSAutoAnswer +from .GXDLMSAutoConnect import GXDLMSAutoConnect +from .GXDLMSCaptureObject import GXDLMSCaptureObject +from .GXDLMSCertificateInfo import GXDLMSCertificateInfo +from .GXDLMSCharge import GXDLMSCharge +from .GXDLMSClock import GXDLMSClock +from .GXDLMSCredit import GXDLMSCredit +from .GXDLMSData import GXDLMSData +from .GXDLMSDayProfile import GXDLMSDayProfile +from .GXDLMSDayProfileAction import GXDLMSDayProfileAction +from .GXDLMSDisconnectControl import GXDLMSDisconnectControl +from .GXDLMSEmergencyProfile import GXDLMSEmergencyProfile +from .GXDLMSGprsSetup import GXDLMSGprsSetup +from .GXDLMSGSMCellInfo import GXDLMSGSMCellInfo +from .GXDLMSGSMDiagnostic import GXDLMSGSMDiagnostic +from .GXDLMSHdlcSetup import GXDLMSHdlcSetup +from .GXDLMSIECLocalPortSetup import GXDLMSIECLocalPortSetup +from .GXDLMSIecTwistedPairSetup import GXDLMSIecTwistedPairSetup +from .GXDLMSImageActivateInfo import GXDLMSImageActivateInfo +from .GXDLMSImageTransfer import GXDLMSImageTransfer +from .GXDLMSIp4Setup import GXDLMSIp4Setup +from .GXDLMSIp4SetupIpOption import GXDLMSIp4SetupIpOption +from .GXDLMSIp6Setup import GXDLMSIp6Setup +from .GXDLMSLimiter import GXDLMSLimiter +from .GXDLMSMacAddressSetup import GXDLMSMacAddressSetup +from .GXDLMSMBusClient import GXDLMSMBusClient +from .GXDLMSMBusMasterPortSetup import GXDLMSMBusMasterPortSetup +from .GXDLMSMBusSlavePortSetup import GXDLMSMBusSlavePortSetup +from .GXDLMSModemConfiguration import GXDLMSModemConfiguration +from .GXDLMSModemInitialisation import GXDLMSModemInitialisation +from .GXDLMSMonitoredValue import GXDLMSMonitoredValue +from .GXDLMSObject import GXDLMSObject +from .GXDLMSObjectCollection import GXDLMSObjectCollection +from .GXDLMSObjectDefinition import GXDLMSObjectDefinition +from .GXDLMSParameterMonitor import GXDLMSParameterMonitor +from .GXDLMSPppSetup import GXDLMSPppSetup +from .GXDLMSPppSetupIPCPOption import GXDLMSPppSetupIPCPOption +from .GXDLMSPppSetupLcpOption import GXDLMSPppSetupLcpOption +from .GXDLMSProfileGeneric import GXDLMSProfileGeneric +from .GXDLMSPushSetup import GXDLMSPushSetup +from .GXDLMSQualityOfService import GXDLMSQualityOfService +from .GXDLMSSapAssignment import GXDLMSSapAssignment +from .GXDLMSSchedule import GXDLMSSchedule +from .GXDLMSScheduleEntry import GXDLMSScheduleEntry +from .GXDLMSScript import GXDLMSScript +from .GXDLMSScriptAction import GXDLMSScriptAction +from .GXDLMSScriptTable import GXDLMSScriptTable +from .GXDLMSSeasonProfile import GXDLMSSeasonProfile +from .GXDLMSSecuritySetup import GXDLMSSecuritySetup +from .GXDLMSSpecialDay import GXDLMSSpecialDay +from .GXDLMSSpecialDaysTable import GXDLMSSpecialDaysTable +from .GXDLMSTarget import GXDLMSTarget +from .GXDLMSTcpUdpSetup import GXDLMSTcpUdpSetup +from .GXDLMSTokenGateway import GXDLMSTokenGateway +from .GXDLMSWeekProfile import GXDLMSWeekProfile +from .GXNeighborDiscoverySetup import GXNeighborDiscoverySetup +from .GXTokenGatewayConfiguration import GXTokenGatewayConfiguration +from .GXUnitCharge import GXUnitCharge +from .GXxDLMSContextType import GXxDLMSContextType +from .GXXmlReader import GXXmlReader +from .GXXmlWriter import GXXmlWriter +from .GXXmlWriterSettings import GXXmlWriterSettings +from .IGXDLMSBase import IGXDLMSBase +from .GXDLMSUtilityTables import GXDLMSUtilityTables +from .GXDLMSLlcSscsSetup import GXDLMSLlcSscsSetup +from .GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters import GXDLMSPrimeNbOfdmPlcPhysicalLayerCounters +from .GXDLMSPrimeNbOfdmPlcMacSetup import GXDLMSPrimeNbOfdmPlcMacSetup +from .GXDLMSPrimeNbOfdmPlcMacFunctionalParameters import GXDLMSPrimeNbOfdmPlcMacFunctionalParameters +from .GXDLMSPrimeNbOfdmPlcMacCounters import GXDLMSPrimeNbOfdmPlcMacCounters +from .GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData import GXDLMSPrimeNbOfdmPlcMacNetworkAdministrationData +from .GXDLMSPrimeNbOfdmPlcApplicationsIdentification import GXDLMSPrimeNbOfdmPlcApplicationsIdentification +from .GXMacMulticastEntry import GXMacMulticastEntry +from .GXMacDirectTable import GXMacDirectTable +from .GXMacAvailableSwitch import GXMacAvailableSwitch +from .GXMacPhyCommunication import GXMacPhyCommunication +from .GXDLMSNtpSetup import GXDLMSNtpSetup diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AccountCreditStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AccountCreditStatus.py new file mode 100644 index 0000000..abb3834 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AccountCreditStatus.py @@ -0,0 +1,81 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntFlag import GXIntFlag + +class AccountCreditStatus(GXIntFlag): + """Enumerates account credit status modes. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAccount + """ + #pylint: disable=too-few-public-methods + + # + # In credit. + # + IN_CREDIT = 0x1 + + # + # Low credit. + # + LOW_CREDIT = 0x2 + + # + # Next credit enabled. + # + NEXT_CREDIT_ENABLED = 0x4 + + # + # Next credit selectable. + # + NEXT_CREDIT_SELECTABLE = 0x8 + + # + # Credit reference list. + # + CREDIT_REFERENCE_LIST = 0x10 + + # + # Selectable credit in use. + # + SELECTABLE_CREDIT_IN_USE = 0x20 + + # + # Out of credit. + # + OUT_OF_CREDIT = 0x40 + + # + # Reserved. + # + RESERVED = 0x80 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AccountStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AccountStatus.py new file mode 100644 index 0000000..937a465 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AccountStatus.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class AccountStatus(GXIntEnum): + """ + Enumerates account status modes. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAccount + """ + #pylint: disable=too-few-public-methods + + # + # New =inactive) account. + # + NEW_INACTIVE_ACCOUNT = 1 + # + # Account active. + # + ACTIVE = 2 + # + # Account closed. + # + CLOSED = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AddressConfigMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AddressConfigMode.py new file mode 100644 index 0000000..3fe7ad7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AddressConfigMode.py @@ -0,0 +1,57 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class AddressConfigMode(GXIntEnum): + """ + Enumerated Address config modes. + """ + #pylint: disable=too-few-public-methods + + # + # Auto Configuration. + # + AUTO = 0 + # + # DHCP v6. + # + DHCP_V6 = 1 + # + # Manual + # + MANUAL = 2 + # + # Neighbour Discovery. + # + NEIGHBOUR_DISCOVERY = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AddressState.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AddressState.py new file mode 100644 index 0000000..f978d47 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AddressState.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class AddressState(GXIntEnum): + """ + Defines whether or not the device has been assigned an address + since last power up of the device. + """ + #pylint: disable=too-few-public-methods + + # + # Not assigned an address yet- + # + NONE = 0 + # + # Assigned an address either by manual setting, or by automated method. + # + ASSIGNED = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ApplicationContextName.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ApplicationContextName.py new file mode 100644 index 0000000..e2fd238 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ApplicationContextName.py @@ -0,0 +1,57 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ApplicationContextName(GXIntEnum): + """ + Enumerates application context name. + """ + #pylint: disable=too-few-public-methods + + # + # Logical name. + # + LOGICAL_NAME = 1 + # + # Short name. + # + SHORT_NAME = 2 + # + # Logical name with ciphering. + # + LOGICAL_NAME_WITH_CIPHERING = 3 + # + # Short name with ciphering. + # + SHORT_NAME_WITH_CIPHERING = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AssociationStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AssociationStatus.py new file mode 100644 index 0000000..ec035ff --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AssociationStatus.py @@ -0,0 +1,54 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class AssociationStatus(GXIntEnum): + """ + Association Status tells is association =connection) made to Association + object. + """ + #pylint: disable=too-few-public-methods + + # + # Association is not made. + # + NON_ASSOCIATED = 0 + # + # Association is pending. + # + ASSOCIATION_PENDING = 1 + # + # Association is made. + # + ASSOCIATED = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AutoAnswerMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AutoAnswerMode.py new file mode 100644 index 0000000..a3a9ba6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AutoAnswerMode.py @@ -0,0 +1,59 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class AutoAnswerMode(GXIntEnum): + #pylint: disable=too-few-public-methods + + # + # Line dedicated to the device. + # + DEVICE = 0 + # + # Shared line management with a limited number of calls allowed. Once the + # number of calls is reached, the window status becomes inactive until the + # next start date, whatever the result of the call, + # + CALL = 1 + # + # Shared line management with a limited number of successful calls + # allowed. + # Once the number of successful communications is reached, the window + # status becomes inactive until the next start date, + # + CONNECTED = 2 + # + # Currently no modem connected. + # + NONE = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AutoAnswerStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AutoAnswerStatus.py new file mode 100644 index 0000000..831eada --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AutoAnswerStatus.py @@ -0,0 +1,42 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class AutoAnswerStatus(GXIntEnum): + #pylint: disable=too-few-public-methods + INACTIVE = 0 + + ACTIVE = 1 + + LOCKED = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AutoConnectMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AutoConnectMode.py new file mode 100644 index 0000000..3b86053 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/AutoConnectMode.py @@ -0,0 +1,102 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class AutoConnectMode(GXIntEnum): + """ + Defines the mode controlling the auto dial functionality concerning the + timing. + """ + #pylint: disable=too-few-public-methods + + # + # No auto dialing, + # + NO_AUTO_DIALLING = 0 + + # + # Auto dialing allowed any time, + # + AUTO_DIALLING_ALLOWED_ANYTIME = 1 + + # + # Auto dialing allowed within the validity time of the calling window. + # + AUTO_DIALLING_ALLOWED_CALLING_WINDOW = 2 + + # + # Regular auto dialing allowed within the validity time of the calling + # window; alarm initiated auto dialing allowed any time, + # + REGULAR_AUTO_DIALLING_ALLOWED_CALLING_WINDOW = 3 + + # + # SMS sending via Public Land Mobile Network =PLMN + # + SMS_SENDING_PLMN = 4 + + # + # SMS sending via PSTN. + # + SMS_SENDING_PSTN = 5 + + # + # Email sending. + # + EMAIL_SENDING = 6 + + # + # The device is permanently connected to the communication network. + # + PERMANENTLY_CONNECT = 101 + # + # The device is permanently connected to the communication network. No + # connection possible outside the calling window. + # + CONNECT_WITH_CALLING_WINDOW = 102 + # + # The device is permanently connected to the communication network. + # Connection is possible as soon as the connect method is invoked. + # + CONNECT_INVOKED = 103 + # + # The device is usually disconnected. It connects to the communication + # network as soon as the connect method is invoked + # + DISCONNECT_CONNECT_INVOKED = 104 + + # + # =200..255) manufacturer specific modes + # + MANUFACTURE_SPESIFIC = 200 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/BaudRate.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/BaudRate.py new file mode 100644 index 0000000..69fc8e0 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/BaudRate.py @@ -0,0 +1,90 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class BaudRate(GXIntEnum): + """ + Defines the baud rates. + """ + #pylint: disable=too-few-public-methods + + # + # Baudrate is 300. + # + BAUDRATE_300 = 0 + + # + # Baudrate is 600. + # + BAUDRATE_600 = 1 + + # + # Baudrate is 1200. + # + BAUDRATE_1200 = 2 + + # + # Baudrate is 2400. + # + BAUDRATE_2400 = 3 + + # + # Baudrate is 4800. + # + BAUDRATE_4800 = 4 + + # + # Baudrate is 9600. + # + BAUDRATE_9600 = 5 + + # + # Baudrate is 19200. + # + BAUDRATE_19200 = 6 + + # + # Baudrate is 38400. + # + BAUDRATE_38400 = 7 + + # + # Baudrate is 57600. + # + BAUDRATE_57600 = 8 + + # + # Baudrate is 115200. + # + BAUDRATE_115200 = 9 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CertificateEntity.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CertificateEntity.py new file mode 100644 index 0000000..8c4e7a6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CertificateEntity.py @@ -0,0 +1,57 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class CertificateEntity(GXIntEnum): + """ + Certificate entity. + """ + #pylint: disable=too-few-public-methods + + # + # Certificate entity is server + # + SERVER = 0 + # + # Certificate entity is client + # + CLIENT = 1 + # + # Certificate entity is certification authority + # + CERTIFICATION_AUTHORITY = 2 + # + # Certificate entity is other. + # + OTHER = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CertificateIdentificationType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CertificateIdentificationType.py new file mode 100644 index 0000000..87bab62 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CertificateIdentificationType.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class CertificateIdentificationType(GXIntEnum): + """ + Certificate is identified with entity identification or the serial number + of the certificate. + """ + #pylint: disable=too-few-public-methods + + # + # Certificate is identified with entity identification. + # + ENTITY = 0 + # + # Certificate is identified with serial number of the certificate. + # + SERIAL = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CertificateType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CertificateType.py new file mode 100644 index 0000000..4d98f00 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CertificateType.py @@ -0,0 +1,57 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class CertificateType(GXIntEnum): + """ + Certificate type. + """ + #pylint: disable=too-few-public-methods + + # + # Certificate type is digital signature. + # + DIGITAL_SIGNATURE = 0 + # + # Certificate type is key agreement. + # + KEY_AGREEMENT = 1 + # + # Certificate type is TLS. + # + TLS = 2 + # + # Certificate type is other. + # + OTHER = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ChargeConfiguration.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ChargeConfiguration.py new file mode 100644 index 0000000..15ca294 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ChargeConfiguration.py @@ -0,0 +1,42 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntFlag import GXIntFlag + +class ChargeConfiguration(GXIntFlag): + NONE = 0 + #Charge configuration enumeration types. + #Percentage based collection. + PERCENTAGE_BASED_COLLECTION = 0x1 + #Continuous collection. + CONTINUOUS_COLLECTION = 0x2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ChargeType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ChargeType.py new file mode 100644 index 0000000..2ca4a76 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ChargeType.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ChargeType(GXIntEnum): + """ + Enumerates account credit status modes. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSCharge + """ + #pylint: disable=too-few-public-methods + + # + # Consumption based collection. + # + CONSUMPTION_BASED_COLLECTION = 0 + # + # Time based collection. + # + TIME_BASED_COLLECTION = 1 + # + # Payment based collection. + # + PAYMENT_EVENT_BASED_COLLECTION = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ClockBase.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ClockBase.py new file mode 100644 index 0000000..e760ee9 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ClockBase.py @@ -0,0 +1,70 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ClockBase(GXIntEnum): + """ + Clock base enumerated values. + """ + #pylint: disable=too-few-public-methods + + # + # Not defined + # + NONE = 0 + + # + # Internal Crystal + # + CRYSTAL = 1 + + # + # Mains frequency 50 Hz, + # + FREQUENCY50 = 2 + + # + # Mains frequency 60 Hz, + # + FREQUENCY60 = 3 + + # + # Global Positioning System. + # + GPSS = 4 + + # + # Radio controlled. + # + RADIO = 5 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ControlMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ControlMode.py new file mode 100644 index 0000000..c3cb75d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ControlMode.py @@ -0,0 +1,76 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ControlMode(GXIntEnum): + """ + Configures the behaviour of the disconnect control object for all + triggers, i.e. the possible state transitions. + """ + #pylint: disable=too-few-public-methods + + # + # The disconnect control object is always in 'connected' state, + # + NONE = 0 + # + # Disconnection: Remote =b, c manual =f local =g) Reconnection: Remote + # =d manual =e). + # + MODE_1 = 1 + # + # Disconnection: Remote =b, c manual =f local =g) Reconnection: Remote + # =a manual =e). + # + MODE_2 = 2 + # + # Disconnection: Remote =b, c manual =- local =g) Reconnection: Remote + # =d manual =e). + # + MODE_3 = 3 + # + # Disconnection: Remote =b, c manual =- local =g) Reconnection: Remote + # =a manual =e) + # + MODE_4 = 4 + # + # Disconnection: Remote =b, c manual =f local =g) Reconnection: Remote + # =d manual =e local =h + # + MODE_5 = 5 + # + # Disconnection: Remote =b, c manual =- local =g) Reconnection: Remote + # =d manual =e local =h) + # + MODE_6 = 6 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ControlState.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ControlState.py new file mode 100644 index 0000000..b891282 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ControlState.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ControlState(GXIntEnum): + """ + The internal states of the disconnect control object. + """ + #pylint: disable=too-few-public-methods + + # + # The output_state is set to false and the consumer is disconnected. + # + DISCONNECTED = 0 + + # + # The output_state is set to true and the consumer is connected. + # + CONNECTED = 1 + + # + # The output_state is set to false and the consumer is disconnected. + # + READY_FOR_RECONNECTION = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditCollectionConfiguration.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditCollectionConfiguration.py new file mode 100644 index 0000000..6708ab9 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditCollectionConfiguration.py @@ -0,0 +1,60 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntFlag import GXIntFlag + +class CreditCollectionConfiguration(GXIntFlag): + """ + Defines behavior under specific conditions. + """ + #pylint: disable=too-few-public-methods + + # + # None. + # + NONE = 0 + + # + # Collect when supply disconnected. + # + DISCONNECTED = 0x1 + + # + # Collect in load limiting periods. + # + LOAD_LIMITING = 0x2 + + # + # Collect in friendly credit periods. + # + FRIENDLY_CREDIT = 0x4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditConfiguration.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditConfiguration.py new file mode 100644 index 0000000..509da2b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditConfiguration.py @@ -0,0 +1,72 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntFlag import GXIntFlag + +class CreditConfiguration(GXIntFlag): + """ + Enumerated Credit configuration values. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAccount + """ + #pylint: disable=too-few-public-methods + + # + # None. + # + NONE = 0x0 + + # + # Requires visual indication. + # + VISUAL = 0x1 + + # + # Requires confirmation before it can be selected/invoked + # + CONFIRMATION = 0x2 + + # + # Requires the credit amount to be paid back. + # + PAID_BACK = 0x4 + + # + # Resettable. + # + RESETTABLE = 0x8 + + # + # Able to receive credit amounts from tokens. + # + TOKENS = 0x10 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditStatus.py new file mode 100644 index 0000000..ce5668b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditStatus.py @@ -0,0 +1,62 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class CreditStatus(GXIntEnum): + """Enumerates credit status values. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSCredit + """ + #pylint: disable=too-few-public-methods + + # + # Enabled state. + # + ENABLED = 0 + # + # Selectable state. + # + SELECTABLE = 1 + # + # Selected/Invoked state. + # + INVOKED = 2 + # + # In use state. + # + IN_USE = 3 + # + # Consumed state. + # + CONSUMED = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditType.py new file mode 100644 index 0000000..99d0836 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/CreditType.py @@ -0,0 +1,62 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class CreditType(GXIntEnum): + """Enumerates credit types. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSCredit + """ + #pylint: disable=too-few-public-methods + + # + # Token credit. + # + TOKEN = 0 + # + # Reserved credit. + # + RESERVED = 1 + # + # Emergency credit. + # + EMERGENCY = 2 + # + # TimeBased credit. + # + TIME_BASED = 3 + # + # Consumption based credit. + # + CONSUMPTION_BASED = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/Currency.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/Currency.py new file mode 100644 index 0000000..49ae1b7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/Currency.py @@ -0,0 +1,54 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class Currency(GXIntEnum): + """Enumerates payment Modes. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAccount + """ + #pylint: disable=too-few-public-methods + + # + # Time. + # + TIME = 0 + # + # Consumption. + # + CONSUMPTION = 1 + # + # Monetary. + # + MONETARY = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GlobalKeyType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GlobalKeyType.py new file mode 100644 index 0000000..3bfcddc --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GlobalKeyType.py @@ -0,0 +1,60 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class GlobalKeyType(GXIntEnum): + """ + Global key types. + """ + #pylint: disable=too-few-public-methods + + # + # Global unicast encryption key. + # Client and server uses this message to send Ephemeral Public Key to + # other + # party. + # + UNICAST_ENCRYPTION = 0 + # + # Global broadcast encryption key. + # + BROADCAST_ENCRYPTION = 1 + # + # Authentication key. + # + AUTHENTICATION = 2 + # + # Key Encrypting Key, also known as Master key. + # + KEK = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GsmCircuitSwitchStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GsmCircuitSwitchStatus.py new file mode 100644 index 0000000..6d7392f --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GsmCircuitSwitchStatus.py @@ -0,0 +1,53 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class GsmCircuitSwitchStatus(GXIntEnum): + """ + GSM circuit switced status. + """ + #pylint: disable=too-few-public-methods + + # + # Inactive. + # + INACTIVE = 0 + # + # Incoming call. + # + INCOMING_CALL = 1 + # + # Active. + # + ACTIVE = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GsmPacketSwitchStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GsmPacketSwitchStatus.py new file mode 100644 index 0000000..add84de --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GsmPacketSwitchStatus.py @@ -0,0 +1,69 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class GsmPacketSwitchStatus(GXIntEnum): + """ + Packet switched status of the modem. + """ + #pylint: disable=too-few-public-methods + + # + # Inactive + # + INACTIVE = 0 + # + # GPRS + # + GPRS = 1 + # + # EDGE + # + EDGE = 2 + # + # UMTS + # + UMTS = 3 + # + # HSDPA + # + HSDPA = 4 + # + # LTE + # + LTE = 5 + # + # CDMA + # + CDMA = 6 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GsmStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GsmStatus.py new file mode 100644 index 0000000..96833b9 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/GsmStatus.py @@ -0,0 +1,62 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class GsmStatus(GXIntEnum): + """ + Defines the GSM status. + """ + #pylint: disable=too-few-public-methods + + # + # + NONE = 0 + # + # + HOME_NETWORK = 1 + # + # to. + # + SEARCHING = 2 + # + # Registration denied. + # + DENIED = 3 + # + # Unknown. + # + UNKNOWN = 4 + # + # Roaming. + ROAMING = 5 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/IecTwistedPairSetupMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/IecTwistedPairSetupMode.py new file mode 100644 index 0000000..3603139 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/IecTwistedPairSetupMode.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class IecTwistedPairSetupMode(GXIntEnum): + """ + IEC Twisted pair setup working mode. + """ + #pylint: disable=too-few-public-methods + + # + # The interface ignores all received frames. + # + INACTIVE = 0 + + # + # Active. + # + ACTIVE = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ImageTransferStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ImageTransferStatus.py new file mode 100644 index 0000000..bb39a4c --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ImageTransferStatus.py @@ -0,0 +1,49 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ImageTransferStatus(GXIntEnum): + """ + Holds the status of the Image transfer process. + """ + #pylint: disable=too-few-public-methods + + IMAGE_TRANSFER_NOT_INITIATED = 0 + IMAGE_TRANSFER_INITIATED = 1 + IMAGE_VERIFICATION_INITIATED = 2 + IMAGE_VERIFICATION_SUCCESSFUL = 3 + IMAGE_VERIFICATION_FAILED = 4 + IMAGE_ACTIVATION_INITIATED = 5 + IMAGE_ACTIVATION_SUCCESSFUL = 6 + IMAGE_ACTIVATION_FAILED = 7 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ImageTransferredBlocksStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ImageTransferredBlocksStatus.py new file mode 100644 index 0000000..03261bd --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ImageTransferredBlocksStatus.py @@ -0,0 +1,44 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ImageTransferredBlocksStatus(GXIntEnum): + """ + Provides information about the transfer status of each ImageBlock. + Each bit in the bit-string provides information about one individual ImageBlock: + """ + #pylint: disable=too-few-public-methods + + NOT_TRANSFERRED = 0 + TRANSFERRED = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/Ip4SetupIpOptionType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/Ip4SetupIpOptionType.py new file mode 100644 index 0000000..c29b3bf --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/Ip4SetupIpOptionType.py @@ -0,0 +1,76 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class Ip4SetupIpOptionType(GXIntEnum): + #pylint: disable=too-few-public-methods + + # If this option is present, the device shall be allowed to send security, + # compartmentation, handling restrictions and TCC =closed user group) + # parameters within its IP Datagrams. The value of the IP-Option- Length + # Field must be 11, and the IP-Option-Data shall contain the value of the + # Security, Compartments, Handling Restrictions and Transmission Control + # code values, as specified in STD0005 / RFC791. + SECURITY = 0x82 + + # If this option is present, the device shall supply routing information + # to + # be used by the gateways in forwarding the datagram to the destination, + # and to record the route information. The IP-Option-length and + # IP-Option-Data values are specified in STD0005 / RFC 791. + LOOSE_SOURCE_AND_RECORD_ROUTE = 0x83 + + # If this option is present, the device shall supply routing information + # to + # be used by the gateways in forwarding the datagram to the destination, + # and to record the route information. The IP-Option-length and + # IP-Option-Data values are specified in STD0005 / RFC 791. + STRICT_SOURCE_AND_RECORD_ROUTE = 0x89 + + # If this option is present, the device shall as well: send originated IP + # Datagrams with that option, providing means to record the route of these + # Datagrams; as a router, send routed IP Datagrams with the route option + # adjusted according to this option. The IP-Option-length and + # IP-Option-Data values are specified in STD0005 / RFC 791. + # + RECORD_ROUTE = 0x07 + + # If this option is present, the device shall as well: send originated IP + # Datagrams with that option, providing means to time-stamp the datagram + # in + # the route to its destination; as a router, send routed IP Datagrams with + # the time-stamp option adjusted according to this option. The + # IP-Option-length and IP-Option-Data values are specified in STD0005 / + # RFC 791. + INTERNET_TIMESTAMP = 0x44 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/KeyUsage.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/KeyUsage.py new file mode 100644 index 0000000..c80e114 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/KeyUsage.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class KeyUsage(GXIntEnum): + """ + The internal states of the disconnect control object. + """ + #pylint: disable=too-few-public-methods + + # + # The output_state is set to false and the consumer is disconnected. + # + DISCONNECTED = 0 + + # + # The output_state is set to true and the consumer is connected. + # + CONNECTED = 1 + + # + # The output_state is set to false and the consumer is disconnected. + # + READY_FOR_RECONNECTION = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/LocalPortResponseTime.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/LocalPortResponseTime.py new file mode 100644 index 0000000..e642dca --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/LocalPortResponseTime.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class LocalPortResponseTime(GXIntEnum): + """ + Defines the minimum time between the reception of a request (end of request telegram) + and the transmission of the response =begin of response telegram). + """ + #pylint: disable=too-few-public-methods + + # + # Minimum time is 20 ms. + # + ms20 = 0 + # + # Minimum time is 200 ms. + # + ms200 = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/MBusEncryptionKeyStatus.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/MBusEncryptionKeyStatus.py new file mode 100644 index 0000000..7f6abc3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/MBusEncryptionKeyStatus.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class MBusEncryptionKeyStatus(GXIntEnum): + """ + M-Bus encryption key status enumerated values. + """ + #pylint: disable=too-few-public-methods + + NO_ENCRYPTION_KEY = 0 + ENCRYPTION_KEY_SET = 1 + ENCRYPTION_KEY_TRANSFERRED = 2 + ENCRYPTION_KEY_SET_AND_TRANSFERRED = 3 + ENCRYPTION_KEY_INUSE = 4 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/MacState.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/MacState.py new file mode 100644 index 0000000..2122667 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/MacState.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class MacState(GXIntEnum): + """ + Present functional state of the node. + """ + #pylint: disable=too-few-public-methods + # Disconnected. + DISCONNECTED = 0 + # Terminal. + TERMINAL = 1 + # Switch. + SWITCH = 2 + # Base. + BASE = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/MessageType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/MessageType.py new file mode 100644 index 0000000..6afd152 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/MessageType.py @@ -0,0 +1,44 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class MessageType(GXIntEnum): + """ + Enumerated message types. + """ + #pylint: disable=too-few-public-methods + + COSEM_APDU = 0 + COSEM_APDU_XML = 1 + MANUFACTURER_SPESIFIC = 128 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/NtpAuthenticationMethod.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/NtpAuthenticationMethod.py new file mode 100644 index 0000000..c1f100d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/NtpAuthenticationMethod.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class NtpAuthenticationMethod(GXIntEnum): + """ + Defines NTP authentication methods. + """ + #pylint: disable=too-few-public-methods + + # + # No security is used. + # + NO_SECURITY = 0 + + # + # Shared secrets are used. + # + SHARED_SECRETS = 1 + + # + # IFF auto key is used. + # + AUTO_KEY_IFF = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/OpticalProtocolMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/OpticalProtocolMode.py new file mode 100644 index 0000000..2f71254 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/OpticalProtocolMode.py @@ -0,0 +1,57 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class OpticalProtocolMode(GXIntEnum): + """ + Defines the protocol used by the meter on the port. + """ + #pylint: disable=too-few-public-methods + + # + # Protocol according to IEC 62056-21 (modes A-E) + # + DEFAULT = 0 + # + # Protocol according to IEC 62056-46. Using this enumeration value all + # other attributes of this IC are not applicable. + # + NET = 1 + # + # Protocol not specified. Using this enumeration value, ProposedBaudrate + # is + # used for setting the communication speed on the port. All other + # attributes are not applicable. + # + UNKNOWN = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PaymentMode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PaymentMode.py new file mode 100644 index 0000000..c1eee3b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PaymentMode.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class PaymentMode(GXIntEnum): + """Enumerates payment Modes. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSAccount + """ + #pylint: disable=too-few-public-methods + + # + # Credit mode. + # + CREDIT = 1 + # + # Prepayment mode. + # + PREPAYMENT = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PppAuthenticationType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PppAuthenticationType.py new file mode 100644 index 0000000..f62b6da --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PppAuthenticationType.py @@ -0,0 +1,53 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class PppAuthenticationType(GXIntEnum): + """ + PPP Authentication Type + """ + #pylint: disable=too-few-public-methods + + # + # No authentication. + # + NO_AUTHENTICATION = 0 + # + # PAP Login + # + PAP = 1 + # + # CHAP-algorithm + # + CHAP = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PppSetupIPCPOptionType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PppSetupIPCPOptionType.py new file mode 100644 index 0000000..00cad23 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PppSetupIPCPOptionType.py @@ -0,0 +1,43 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class PppSetupIPCPOptionType(GXIntEnum): + #pylint: disable=too-few-public-methods + + IP_COMPRESSION_PROTOCOL = 2 + PREF_LOCAL_IP = 3 + PREF_PEER_IP = 20 + GAO = 21 + USIP = 22 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PppSetupLcpOptionType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PppSetupLcpOptionType.py new file mode 100644 index 0000000..ce6bcbb --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/PppSetupLcpOptionType.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class PppSetupLcpOptionType(GXIntEnum): + #pylint: disable=too-few-public-methods + + MAX_REC_UNIT = 1 + ASYNC_CONTROL_CHAR_MAP = 2 + AUTH_PROTOCOL = 3 + MAGIC_NUMBER = 5 + PROTOCOL_FIELD_COMPRESSION = 7 + ADDRESS_AND_CTRL_COMPRESSION = 8 + FCS_ALTERNATIVES = 9 + CALLBACK = 13 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ScriptActionType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ScriptActionType.py new file mode 100644 index 0000000..ab6f0dc --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ScriptActionType.py @@ -0,0 +1,50 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ScriptActionType(GXIntEnum): + #pylint: disable=too-few-public-methods + + # + # Nothing is executed. + # + NOTHING = 0 + # + # Write attribute. + # + WRITE = 1 + # + # Execute specific method + # + EXECUTE = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SecurityPolicy.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SecurityPolicy.py new file mode 100644 index 0000000..c562cca --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SecurityPolicy.py @@ -0,0 +1,75 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntFlag import GXIntFlag + +class SecurityPolicy(GXIntFlag): + """ + Security policy Enforces authentication and/or encryption algorithm + provided with security suite. This enumeration is used for version 1. + """ + #pylint: disable=too-few-public-methods + + # + # Security is not used. + # + NOTHING = 0 + # + # Request is authenticated. + # + AUTHENTICATED_REQUEST = 0x4 + + # + # Request is encrypted. + # + ENCRYPTED_REQUEST = 0x8 + + # + # Request is digitally signed. + # + DIGITALLY_SIGNED_REQUEST = 0x10 + + # + # Response authenticated. + # + AUTHENTICATED_RESPONSE = 0x20 + + # + # Response encrypted. + # + ENCRYPTED_RESPONSE = 0x40 + + # + # Response is digitally signed. + # + DIGITALLY_SIGNED_RESPONSE = 0x80 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SecurityPolicy0.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SecurityPolicy0.py new file mode 100644 index 0000000..aca3a8b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SecurityPolicy0.py @@ -0,0 +1,61 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class SecurityPolicy0(GXIntEnum): + """ + Security policy Enforces authentication and/or encryption algorithm + provided with security suite. This enumeration is used for version 0. + """ + #pylint: disable=too-few-public-methods + + # + # Security is not used. + # + NOTHING = 0 + + # + # All messages are authenticated. + # + AUTHENTICATED = 1 + + # + # All messages are encrypted. + # + ENCRYPTED = 2 + + # + # All messages are authenticated and encrypted. + # + AUTHENTICATED_ENCRYPTED = 3 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SecuritySuite.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SecuritySuite.py new file mode 100644 index 0000000..f3e02f9 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SecuritySuite.py @@ -0,0 +1,59 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class SecuritySuite(GXIntEnum): + """ + Security suite Specifies authentication, encryption and key wrapping algorithm. + """ + #pylint: disable=too-few-public-methods + + # + # AES-GCM-128 for authenticated encryption and AES-128 for key wrapping. + # + # A.K.A Security Suite 0. + # + AES_GCM_128 = 0 + + # + # ECDH-ECDSAAES-GCM-128SHA-256. + # A.K.A Security Suite 1. + # + ECDH_ECDSA_AES_GCM_128_SHA_256 = 1 + + # + # ECDH-ECDSAAES-GCM-256SHA-384. + # A.K.A Security Suite 2. + # + ECDHE_CDSA_AES_GCM_256_SHA_384 = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ServiceType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ServiceType.py new file mode 100644 index 0000000..77995cd --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/ServiceType.py @@ -0,0 +1,73 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class ServiceType(GXIntEnum): + """ + Type of service used to push the data. + """ + #pylint: disable=too-few-public-methods + + # + # Transport service type is TCP/IP. + # + TCP = 0 + # + # Transport service type is UDP. + # + UDP = 1 + # + # Transport service type is FTP. + # + FTP = 2 + # + # Transport service type is SMTP. + # + SMTP = 3 + # + # Transport service type is SMS. + # + SMS = 4 + # + # Transport service type is HDLC. + # + HDLC = 5 + # + # Transport service type is MBUS. + # + M_BUS = 6 + # + # Transport service type is ZigBee. + # + ZIGBEE = 7 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SingleActionScheduleType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SingleActionScheduleType.py new file mode 100644 index 0000000..15f8a68 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SingleActionScheduleType.py @@ -0,0 +1,66 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class SingleActionScheduleType(GXIntEnum): + #pylint: disable=too-few-public-methods + + # + # Size of execution_time = 1. Wildcard in date allowed. + # + SingleActionScheduleType1 = 1 + + # + # Size of execution_time = n. All time values are the same, wildcards in + # date not allowed. + # + SingleActionScheduleType2 = 2 + + # + # Size of execution_time = n. All time values are the same, wildcards in + # date are allowed, + # + SingleActionScheduleType3 = 3 + + # + # Size of execution_time = n. Time values may be different, wildcards in + # date not allowed, + # + SingleActionScheduleType4 = 4 + + # + # Size of execution_time = n. Time values may be different, wildcards in + # date are allowed + # + SingleActionScheduleType5 = 5 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SortMethod.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SortMethod.py new file mode 100644 index 0000000..71b2a72 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/SortMethod.py @@ -0,0 +1,70 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class SortMethod(GXIntEnum): + """ + Sort methods. + """ + #pylint: disable=too-few-public-methods + + # + # First in first out When circle buffer is full first item is removed. + # + FIFO = 1 + + # + # Last in first out. When circle buffer is full last item is removed. + # + LIFO = 2 + + # + # Largest is first. + # + LARGEST = 3 + + # + # Smallest is first. + # + SMALLEST = 4 + + # + # Nearest to zero is first. + # + NEAREST_TO_ZERO = 5 + + # + # Farest from zero is first. + # + FAREST_FROM_ZERO = 6 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/TokenDelivery.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/TokenDelivery.py new file mode 100644 index 0000000..d2c5d14 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/TokenDelivery.py @@ -0,0 +1,55 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class TokenDelivery(GXIntEnum): + """ + Enumerates token delivery methods. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSTokenGateway + """ + #pylint: disable=too-few-public-methods + + # + # Via remote communications. + # + REMOTE = 0 + # + # Via local communications. + # + LOCAL = 1 + # + # Via manual entry. + # + MANUAL = 2 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/TokenStatusCode.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/TokenStatusCode.py new file mode 100644 index 0000000..1eeb856 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/TokenStatusCode.py @@ -0,0 +1,78 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntEnum import GXIntEnum + +class TokenStatusCode(GXIntEnum): + """Enumerates token status codes. + Online help: + http://www.gurux.fi/Gurux.DLMS.Objects.GXDLMSTokenGateway + """ + #pylint: disable=too-few-public-methods + + # + # Token format result OK. + # + FORMAT_OK = 0 + # + # Authentication result OK. + # + AUTHENTICATION_OK = 1 + # + # Validation result OK. + # + VALIDATION_OK = 2 + # + # Token execution result OK. + # + TOKEN_EXECUTION_OK = 3 + # + # Token format failure. + # + TOKEN_FORMAT_FAILURE = 4 + # + # Authentication failure. + # + AUTHENTICATION_FAILURE = 5 + # + # Validation result failure. + # + VALIDATION_RESULT_FAILURE = 6 + # + # Token execution result failure. + # + TOKEN_EXECUTION_RESULT_FAILURE = 7 + # + # Token received and not yet processed. + # + TOKEN_RECEIVED = 8 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/Weekdays.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/Weekdays.py new file mode 100644 index 0000000..3d67909 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/Weekdays.py @@ -0,0 +1,74 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_dlms.GXIntFlag import GXIntFlag + +class Weekdays(GXIntFlag): + """ + Defines the weekdays. + """ + #pylint: disable=too-few-public-methods + + # + # Indicates Monday. + # + NONE = 0x0 + + # + # Indicates Monday. + # + MONDAY = 0x1 + # + # Indicates Tuesday. + # + TUESDAY = 0x2 + # + # Indicates Wednesday. + # + WEDNESDAY = 0x4 + # + # Indicates Thursday. + # + THURSDAY = 0x8 + # + # Indicates Friday. + # + FRIDAY = 0x10 + # + # Indicates Saturday. + # + SATURDAY = 0x20 + # + # Indicates Sunday. + # + SUNDAY = 0x40 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/__init__.py new file mode 100644 index 0000000..b0050cb --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/objects/enums/__init__.py @@ -0,0 +1,84 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .AccountCreditStatus import AccountCreditStatus +from .AccountStatus import AccountStatus +from .AddressConfigMode import AddressConfigMode +from .AddressState import AddressState +from .ApplicationContextName import ApplicationContextName +from .AssociationStatus import AssociationStatus +from .AutoAnswerMode import AutoAnswerMode +from .AutoAnswerStatus import AutoAnswerStatus +from .AutoConnectMode import AutoConnectMode +from .BaudRate import BaudRate +from .CertificateEntity import CertificateEntity +from .CertificateIdentificationType import CertificateIdentificationType +from .CertificateType import CertificateType +from .ChargeType import ChargeType +from .ClockBase import ClockBase +from .ControlMode import ControlMode +from .ControlState import ControlState +from .CreditCollectionConfiguration import CreditCollectionConfiguration +from .CreditConfiguration import CreditConfiguration +from .CreditStatus import CreditStatus +from .CreditType import CreditType +from .Currency import Currency +from .GlobalKeyType import GlobalKeyType +from .GsmCircuitSwitchStatus import GsmCircuitSwitchStatus +from .GsmPacketSwitchStatus import GsmPacketSwitchStatus +from .GsmStatus import GsmStatus +from .IecTwistedPairSetupMode import IecTwistedPairSetupMode +from .ImageTransferredBlocksStatus import ImageTransferredBlocksStatus +from .ImageTransferStatus import ImageTransferStatus +from .Ip4SetupIpOptionType import Ip4SetupIpOptionType +from .LocalPortResponseTime import LocalPortResponseTime +from .MessageType import MessageType +from .OpticalProtocolMode import OpticalProtocolMode +from .PaymentMode import PaymentMode +from .PppAuthenticationType import PppAuthenticationType +from .PppSetupIPCPOptionType import PppSetupIPCPOptionType +from .PppSetupLcpOptionType import PppSetupLcpOptionType +from .ScriptActionType import ScriptActionType +from .SecurityPolicy import SecurityPolicy +from .SecurityPolicy0 import SecurityPolicy0 +from .SecuritySuite import SecuritySuite +from .ServiceType import ServiceType +from .SingleActionScheduleType import SingleActionScheduleType +from .SortMethod import SortMethod +from .TokenDelivery import TokenDelivery +from .TokenStatusCode import TokenStatusCode +from .MBusEncryptionKeyStatus import MBusEncryptionKeyStatus +from .Weekdays import Weekdays +from .MacState import MacState +from .ChargeConfiguration import ChargeConfiguration +from .NtpAuthenticationMethod import NtpAuthenticationMethod diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/GXDLMSPlcMeterInfo.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/GXDLMSPlcMeterInfo.py new file mode 100644 index 0000000..c194973 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/GXDLMSPlcMeterInfo.py @@ -0,0 +1,46 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +# Information from the discovered PLC meter. +class GXDLMSPlcMeterInfo: + # Constructor. + def __init__(self): + #Source Address. + self.sourceAddress = 0 + # Destination Address. + self.destinationAddress = 0 + #System title + self.systemTitle = None + #Alarm descriptor + self.alarmDescriptor = None diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/__init__.py new file mode 100644 index 0000000..7ce6ad2 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/__init__.py @@ -0,0 +1,34 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSPlcMeterInfo import GXDLMSPlcMeterInfo diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcDataLinkData.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcDataLinkData.py new file mode 100644 index 0000000..3b11303 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcDataLinkData.py @@ -0,0 +1,43 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from gurux_dlms.GXIntEnum import GXIntEnum + +class PlcDataLinkData(GXIntEnum): + """ + PLC data link data commands. + """ + #pylint: disable=too-few-public-methods + + REQUEST = 0x90 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcDestinationAddress.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcDestinationAddress.py new file mode 100644 index 0000000..aea09b6 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcDestinationAddress.py @@ -0,0 +1,43 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from gurux_dlms.GXIntEnum import GXIntEnum + +class PlcDestinationAddress(GXIntEnum): + """ + PLC Destination address enumerations. + """ + #pylint: disable=too-few-public-methods + + ALL_PHYSICAL = 0xFFF diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcHdlcSourceAddress.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcHdlcSourceAddress.py new file mode 100644 index 0000000..0dfde84 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcHdlcSourceAddress.py @@ -0,0 +1,43 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from gurux_dlms.GXIntEnum import GXIntEnum + +class PlcHdlcSourceAddress(GXIntEnum): + """ + PLC HDLC Source address enumerations. + """ + #pylint: disable=too-few-public-methods + + INITIATOR = 0xC01 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcMacSubframes.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcMacSubframes.py new file mode 100644 index 0000000..94cb28a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcMacSubframes.py @@ -0,0 +1,49 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from gurux_dlms.GXIntEnum import GXIntEnum + +class PlcMacSubframes(GXIntEnum): + """ + Sequence number of MAC sub frame. + """ + #pylint: disable=too-few-public-methods + + ONE = 0x6C6C + TWO = 0x3A3A + THREE = 0x5656 + FOUR = 0x7171 + FIVE = 0x1D1D + SIX = 0x4B4B + SEVEN = 0x2727 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcSourceAddress.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcSourceAddress.py new file mode 100644 index 0000000..ee1b598 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/PlcSourceAddress.py @@ -0,0 +1,44 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- + +from gurux_dlms.GXIntEnum import GXIntEnum + +class PlcSourceAddress(GXIntEnum): + """ + PLC Source address enumerations. + """ + #pylint: disable=too-few-public-methods + + INITIATOR = 0xC00 + NEW = 0xFFE diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/__init__.py new file mode 100644 index 0000000..ac59e79 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/plc/enums/__init__.py @@ -0,0 +1,38 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .PlcDataLinkData import PlcDataLinkData +from .PlcDestinationAddress import PlcDestinationAddress +from .PlcHdlcSourceAddress import PlcHdlcSourceAddress +from .PlcMacSubframes import PlcMacSubframes +from .PlcSourceAddress import PlcSourceAddress diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/requirements.txt b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/requirements.txt new file mode 100644 index 0000000..3e16d2b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/requirements.txt @@ -0,0 +1,2 @@ +pytz==2019.2 +python-dateutil==2.8.0 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/GXDLMSSecureClient.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/GXDLMSSecureClient.py new file mode 100644 index 0000000..12b4319 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/GXDLMSSecureClient.py @@ -0,0 +1,125 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ..enums import InterfaceType, Authentication +from ..GXDLMSClient import GXDLMSClient +from ..GXCiphering import GXCiphering +from ..GXDLMSChipperingStream import GXDLMSChipperingStream + +#pylint: disable=too-many-arguments +class GXDLMSSecureClient(GXDLMSClient): + """ + GXDLMSSecureClient implements secure client where all messages are secured + using transport security. + """ + + # + # Constructor. + # * + # @param useLogicalNameReferencing + # Is Logical Name referencing used. + # @param clientAddress + # Server address. + # @param serverAddress + # Client address. + # @param forAuthentication + # Authentication type. + # @param password + # Password if authentication is used. + # @param interfaceType + # Object type. + # + def __init__(self, useLogicalNameReferencing=False, clientAddress=16, serverAddress=1, forAuthentication=Authentication.NONE, password=None, interfaceType=InterfaceType.HDLC): + GXDLMSClient.__init__(self, useLogicalNameReferencing, clientAddress, serverAddress, forAuthentication, password, interfaceType) + # Ciphering settings. + self.settings.cipher = GXCiphering("ABCDEFGH".encode()) + + # + # Encrypt data using Key Encrypting Key. + # * + # @param kek + # Key Encrypting Key, also known as Master key. + # @param data + # Data to encrypt. + # @return Encrypt data. + # + @classmethod + def encrypt(cls, kek, data): + if len(kek) != 16: + raise ValueError("Key Encrypting Key") + if not data: + raise ValueError("data") + gcm = GXDLMSChipperingStream(None, True, kek, None, None, 0) + return gcm.encryptAes(data) + + # + # Decrypt data using Key Encrypting Key. + # * + # @param kek + # Key Encrypting Key, also known as Master key. + # @param data + # Data to decrypt. + # @return Decrypted data. + # + @classmethod + def decrypt(cls, kek, data): + if len(kek) != 16: + raise ValueError("Key Encrypting Key") + if not data: + raise ValueError("data") + gcm = GXDLMSChipperingStream(None, False, kek, None, None, 0) + return gcm.decryptAes(data) + + def getSecuritySuite(self): + return self.ciphering.securitySuite + + def setSecuritySuite(self, value): + self.ciphering.securitySuite = value + + # Used security suite. + securitySuite = property(getSecuritySuite, setSecuritySuite) + + def getServerSystemTitle(self): + return self.settings.preEstablishedSystemTitle + + def setServerSystemTitle(self, value): + self.settings.preEstablishedSystemTitle = value + + # Server system title. + serverSystemTitle = property(getServerSystemTitle, setServerSystemTitle) + + def getCiphering(self): + return self.settings.cipher + + # Ciphering. + ciphering = property(getCiphering) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/GXDLMSSecureNotify.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/GXDLMSSecureNotify.py new file mode 100644 index 0000000..1df81a7 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/GXDLMSSecureNotify.py @@ -0,0 +1,61 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +# +from ..GXDLMSNotify import GXDLMSNotify +from ..GXCiphering import GXCiphering +# This class is used to send data notify and push messages to the clients. +# +class GXDLMSSecureNotify(GXDLMSNotify): + # + # * Constructor. + # * + # * @param useLogicalNameReferencing + # * Is Logical Name referencing used. + # * @param clientAddress + # * Server address. + # * @param serverAddress + # * Client address. + # * @param interfaceType + # * Object type. + # + def __init__(self, useLogicalNameReferencing, clientAddress, serverAddress, interfaceType): + #pylint:disable=super-with-arguments + super(GXDLMSSecureNotify, self).__init__(useLogicalNameReferencing, clientAddress, serverAddress, interfaceType) + self.settings.cipher = GXCiphering("ABCDEFGH".encode()) + + # + # * @return Ciphering settings. + # + def getCiphering(self): + return self.settings.cipher diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/GXDLMSSecureServer.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/GXDLMSSecureServer.py new file mode 100644 index 0000000..64b5be9 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/GXDLMSSecureServer.py @@ -0,0 +1,241 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from abc import ABCMeta, abstractmethod +from ..GXDLMSServer import GXDLMSServer +from ..GXCiphering import GXCiphering + +class GXDLMSSecureServer(GXDLMSServer): + __metaclass__ = ABCMeta + """ + Implements secured DLMS server. + """ + + def __init__(self, association, interfaceType): + """ + # Constructor. + # + # association: Association logical name. + # type: Interface type. + """ + #pylint:disable=super-with-arguments + super(GXDLMSSecureServer, self).__init__(association, interfaceType) + self.settings.ciphering = GXCiphering("ABCDEFGH".encode()) + self.kek = None + + def getCiphering(self): + return self.settings.ciphering + + def getKek(self): + return self.settings.kek + + def setKek(self, value): + self.settings.kek = value + + kek = property(getKek, setKek, None, "Key Encrypting Key, also known as Master key.") + + # + # Check is data sent to this server. + # + # @param serverAddress + # Server address. + # @param clientAddress + # Client address. + # True, if data is sent to this server. + # + @abstractmethod + def isTarget(self, serverAddress, clientAddress): + raise ValueError("isTarget is called.") + + # + # Check whether the authentication and password are correct. + # + # @param authentication + # Authentication level. + # @param password + # Password. + # Source diagnostic. + # + @abstractmethod + def onValidateAuthentication(self, authentication, password): + raise ValueError("isTarget is called.") + + # + # Get selected value(s). This is called when example profile generic + # request current value. + # + # @param args + # Value event arguments. + # + @abstractmethod + def onPreGet(self, args): + raise ValueError("isTarget is called.") + + # + # Get selected value(s). This is called when example profile generic + # request current value. + # + # @param args + # Value event arguments. + # + @abstractmethod + def onPostGet(self, args): + raise ValueError("isTarget is called.") + + # + # Find object. + # + # @param objectType + # Object type. + # @param sn + # Short Name. In Logical name referencing this is not used. + # @param ln + # Logical Name. In Short Name referencing this is not used. + # Found object or null if object is not found. + # + @abstractmethod + def onFindObject(self, objectType, sn, ln): + raise ValueError("isTarget is called.") + + # + # Called before read is executed. + # + # @param args + # Handled read requests. + # + @abstractmethod + def onPreRead(self, args): + raise ValueError("isTarget is called.") + + # + # Called after read is executed. + # + # @param args + # Handled read requests. + # + @abstractmethod + def onPostRead(self, args): + raise ValueError("isTarget is called.") + + # + # Called before write is executed.. + # + # @param args + # Handled write requests. + # + @abstractmethod + def onPreWrite(self, args): + raise ValueError("isTarget is called.") + + # + # Called after write is executed. + # + # @param args + # Handled write requests. + # + @abstractmethod + def onPostWrite(self, args): + raise ValueError("isTarget is called.") + + # + # Accepted connection is made for the server. All initialization is done + # here. + # + # @param connectionInfo + # Connection info. + # + @abstractmethod + def onConnected(self, connectionInfo): + raise ValueError("isTarget is called.") + + # + # Client has try to made invalid connection. Password is incorrect. + # + # @param connectionInfo + # Connection info. + # + @abstractmethod + def onInvalidConnection(self, connectionInfo): + raise ValueError("isTarget is called.") + + # + # Server has close the connection. All clean up is made here. + # + # @param connectionInfo + # Connection info. + # + @abstractmethod + def onDisconnected(self, connectionInfo): + raise ValueError("isTarget is called.") + + # + # Get attribute access mode. + # + # @param arg + # Value event argument. + # Access mode. + # + @abstractmethod + def onGetAttributeAccess(self, arg): + raise ValueError("isTarget is called.") + + # + # Get method access mode. + # + # @param arg + # Value event argument. + # Method access mode. + # + @abstractmethod + def onGetMethodAccess(self, arg): + raise ValueError("onGetMethodAccess is called.") + + # + # Called before action is executed. + # + # @param args + # Handled action requests. + # + @abstractmethod + def onPreAction(self, args): + raise ValueError("onPreAction is called.") + + # + # Called after action is executed. + # + # @param args + # Handled action requests. + # + @abstractmethod + def onPostAction(self, args): + raise ValueError("onPostAction is called.") diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/__init__.py new file mode 100644 index 0000000..acbaf03 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_dlms/secure/__init__.py @@ -0,0 +1,35 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .GXDLMSSecureClient import GXDLMSSecureClient +from .GXDLMSSecureServer import GXDLMSSecureServer diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/GXNet.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/GXNet.py new file mode 100644 index 0000000..29fb950 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/GXNet.py @@ -0,0 +1,454 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import socket +import threading +import traceback +from gurux_common.enums import TraceLevel, MediaState, TraceTypes +from gurux_common.IGXMedia import IGXMedia +from gurux_common.MediaStateEventArgs import MediaStateEventArgs +from gurux_common.TraceEventArgs import TraceEventArgs +from gurux_common.PropertyChangedEventArgs import PropertyChangedEventArgs +from .enums.NetworkType import NetworkType +from ._GXSynchronousMediaBase import _GXSynchronousMediaBase +from ._NetReceiveEventArgs import _NetReceiveEventArgs + +# pylint: disable=too-many-public-methods, too-many-instance-attributes +class GXNet(IGXMedia): + + def __init__(self, networkType=NetworkType.TCP, name=None, portNo=0): + """ + Client Constructor. + + networkType : Used protocol. + name : Host name. + portNo : Client port number. + """ + self.__receiveDelay = 0 + self.__asyncWaitTime = 0 + ###Used protocol.### + self.__protocol = networkType + ###Host name.### + self.__host_name = name + ###Used port.### + self.__port = portNo + ###Is server or client.### + self.server = False + ###Created socket.### + self.__socket = None + ###Amount of sent bytes.### + self.__bytesSent = 0 + self.__bytesReceived = 0 + ###Trace level.### + self.__trace = TraceLevel.OFF + ###Used end of packet.### + self.__eop = None + ###Synchronous data handler.### + self.__syncBase = _GXSynchronousMediaBase(1024) + ###Event listeners. + self.__listeners = [] + self.__netListeners = [] + self.__lock = threading.Lock() + self.__thread = None + self.__aThread = None + #Is IPv6 used. Default is False (IPv4). + self.useIPv6 = False + + #pylint: disable=unused-private-member + def __getTrace(self): + return self.__trace + + #pylint: disable=unused-private-member + def __setTrace(self, value): + self.__trace = value + self.__syncBase.trace = value + + trace = property(__getTrace, __setTrace) + """Trace level.""" + + def addListener(self, listener): + self.__listeners.append(listener) + + def removeListener(self, listener): + self.__listeners.remove(listener) + + def __notifyPropertyChanged(self, info): + """Notify that property has changed.""" + for it in self.__listeners: + it.onPropertyChanged(self, PropertyChangedEventArgs(info)) + + def __notifyClientConnected(self, e): + """Notify that client has connected.""" + for it in self.__netListeners: + it.onClientConnected(self, e) + + if int(self.trace) >= int(TraceLevel.INFO): + for it in self.__listeners: + it.onTrace(self, TraceEventArgs(TraceTypes.INFO, "Client connected.")) + + def __notifyClientDisconnected(self, e): + """Notifies clients that client is disconnected.""" + for it in self.__netListeners: + it.onClientDisconnected(self, e) + + if int(self.__trace) >= int(TraceLevel.INFO): + for it in self.__listeners: + it.onTrace(self, TraceEventArgs(TraceTypes.INFO, "Client disconnected.")) + + def __notifyError(self, ex): + """Notify clients from error occurred.""" + for it in self.__listeners: + it.onError(self, ex) + if int(self.__trace) >= int(TraceLevel.ERROR): + it.onTrace(self, TraceEventArgs(TraceTypes.ERROR, ex)) + + def __notifyReceived(self, e): + """Notify clients from new data received.""" + for it in self.__listeners: + it.onReceived(self, e) + + def __notifyTrace(self, e): + """Notify clients from trace events.""" + for it in self.__listeners: + it.onTrace(self, e) + + def send(self, data, receiver=None): + if not self.__socket: + raise Exception("Invalid connection.") + + if self.__trace == TraceLevel.VERBOSE: + self.__notifyTrace(TraceEventArgs(TraceTypes.SENT, data)) + + #Reset last position if end of packet is used. + with self.__syncBase.getSync(): + self.__syncBase.resetLastPosition() + + if not isinstance(data, bytes): + data = bytes(_GXSynchronousMediaBase.toBytes(data)) + + if isinstance(receiver, _NetReceiveEventArgs): + receiver.socket.sendall(data) + else: + self.__socket.sendall(data) + self.__bytesSent += len(data) + + def __notifyMediaStateChange(self, state): + ###Notify client from media state change. + for it in self.__listeners: + if self.__trace >= TraceLevel.ERROR: + it.onTrace(self, TraceEventArgs(TraceTypes.INFO, state)) + it.onMediaStateChange(self, MediaStateEventArgs(state)) + + #Handle received data. + def __handleReceivedData(self, buff, s): + if not buff: + return + self.__bytesReceived += len(buff) + totalCount = 0 + if self.getIsSynchronous(): + arg = None + with self.__syncBase.getSync(): + self.__syncBase.appendData(buff, 0, len(buff)) + #Search end of packet if it is given. + if self.eop: + tmp = _GXSynchronousMediaBase.toBytes(self.eop) + totalCount = _GXSynchronousMediaBase.indexOf(buff, tmp, 0, len(buff)) + if totalCount != -1: + if self.trace == TraceLevel.VERBOSE: + arg = TraceEventArgs(TraceTypes.RECEIVED, buff, 0, totalCount + 1) + self.__syncBase.setReceived() + if arg: + self.__notifyTrace(arg) + else: + self.__syncBase.resetReceivedSize() + if self.trace == TraceLevel.VERBOSE: + self.__notifyTrace(TraceEventArgs(TraceTypes.RECEIVED, buff)) + info = s.getpeername() + e = _NetReceiveEventArgs(buff, str(info[0]) + ":" + str(info[1]), s) + self.__notifyReceived(e) + + #pylint: disable=broad-except + def __listenerThread(self, s): + while self.__socket and s: + try: + data = s.recv(1000) + if data: + #Convert data to bytearray because 2.7 handles bytes as a + #string. + #This is causing problems with non-ascii chars. + data = bytearray(data) + self.__handleReceivedData(data, s) + elif self.server: + self.__notifyClientDisconnected(s.getpeername()) + break + except ConnectionResetError: + if not self.server: + #Server has close the connection. + self.close() + break + except Exception: + if self.__socket: + traceback.print_exc() + else: + break + + #pylint: disable=broad-except + def __acceptThread(self): + while self.__socket: + try: + # accept connections from outside + (clientsocket, address) = self.__socket.accept() + self.__notifyClientConnected(address) + self.__thread = threading.Thread(target=self.__listenerThread, args=(clientsocket,)) + self.__thread.start() + except Exception: + if self.__socket: + traceback.print_exc() + + def __getInet(self, addr): + if not addr: + if self.useIPv6: + return socket.AF_INET6 + return socket.AF_INET + if addr.find(":") != -1: + return socket.AF_INET6 + return socket.AF_INET + + def open(self): + """Opens the connection. Protocol, Port and HostName must be set, before + calling the Open method.""" + self.close() + try: + with self.__syncBase.getSync(): + self.__syncBase.resetLastPosition() + + self.__notifyMediaStateChange(MediaState.OPENING) + if self.protocol == NetworkType.TCP or 1==1: # or condition is temporary solution to define why 2 objects with same value and not equal [, ] + if self.server: + self.__socket = socket.socket(self.__getInet(self.__host_name), socket.SOCK_STREAM) + if self.__host_name: + self.__socket.bind((self.__host_name, self.__port)) + else: + self.__socket.bind((socket.gethostname(), self.__port)) + self.__socket.listen(5) + self.__aThread = threading.Thread(target=self.__acceptThread) + self.__aThread.start() + else: + self.__socket = socket.socket(self.__getInet(self.__host_name), socket.SOCK_STREAM) + self.__socket.connect((self.__host_name, self.__port)) + else: + self.__socket = socket.socket(self.__getInet(self.__host_name), socket.SOCK_DGRAM) + self.__socket.connect((self.__host_name, self.__port)) + self.__notifyMediaStateChange(MediaState.OPEN) + if not self.server or self.protocol == NetworkType.UDP: + self.__thread = threading.Thread(target=self.__listenerThread, args=(self.__socket,)) + self.__thread.start() + except Exception as e: + self.close() + raise e + + def close(self): + if self.__socket: + self.__notifyMediaStateChange(MediaState.CLOSING) + try: + #This will fail if connection is closed on server side. + if self.__socket: + tmp = self.__socket + self.__socket = None + if not self.server: + tmp.shutdown(socket.SHUT_RDWR) + tmp.close() + except Exception: + pass + try: + if self.__thread: + self.__thread.join() + self.__thread = None + except Exception: + pass + try: + if self.__aThread: + self.__aThread.join() + self.__aThread = None + except Exception: + pass + self.__notifyMediaStateChange(MediaState.CLOSED) + self.__bytesSent = 0 + self.__syncBase.resetReceivedSize() + + def isOpen(self): + return self.__socket is not None + + def __getProtocol(self): + return self.__protocol + def __setProtocol(self, value): + if self.__protocol != value: + self.__protocol = value + self.__notifyPropertyChanged("protocol") + + protocol = property(__getProtocol, __setProtocol) + """Protocol.""" + + def __getHostName(self): + return self.__host_name + + def __setHostName(self, value): + if self.__host_name != value: + self.__host_name = value + self.__notifyPropertyChanged("hostName") + + hostName = property(__getHostName, __setHostName) + """Host name.""" + + def __getPort(self): + return self.__port + + def __setPort(self, value): + if self.__port != value: + self.__port = value + self.__notifyPropertyChanged("port") + + + port = property(__getPort, __setPort) + """Port number.""" + + def receive(self, args): + return self.__syncBase.receive(args) + + def getBytesSent(self): + """Sent byte count.""" + return self.__bytesSent + + def getBytesReceived(self): + """Received byte count.""" + return self.__bytesReceived + + def resetByteCounters(self): + """Resets BytesReceived and BytesSent counters.""" + self.__bytesSent = 0 + self.__bytesReceived = 0 + + def getSettings(self): + """Media settings as a XML string.""" + sb = "" + nl = "\r\n" + if self.__host_name: + sb += "" + sb += self.__host_name + sb += "" + sb += nl + if self.__port != 0: + sb += "" + sb += str(self.__port) + sb += "" + sb += nl + + if self.__protocol != NetworkType.TCP: + sb += "" + sb += str(int(self.__protocol)) + sb += "" + sb += nl + return sb + + def setSettings(self, value): + #Reset to default values. + self.__host_name = "" + self.__port = 0 + self.__protocol = NetworkType.TCP + + + def copy(self, target): + self.__port = target.port + self.__host_name = target.hostName + self.__protocol = target.protocol + + def getName(self): + tmp = self.__host_name + " " + self.__port + if self.__protocol == NetworkType.UDP: + tmp += "UDP" + else: + tmp += "TCP/IP" + return tmp + + def getMediaType(self): + return "Net" + + def getSynchronous(self): + return self.__lock + + #pylint: disable=no-member + def getIsSynchronous(self): + return self.__lock.locked() + + def resetSynchronousBuffer(self): + with self.__syncBase.getSync(): + self.__syncBase.resetReceivedSize() + + def validate(self): + if self.__port == 0: + raise Exception("Invalid port name.") + if not self.hostName: + raise Exception("Invalid host name.") + + def __getEop(self): + return self.__eop + + def __setEop(self, value): + self.__eop = value + + eop = property(__getEop, __setEop) + + def getReceiveDelay(self): + return self.__receiveDelay + + def setReceiveDelay(self, value): + self.__receiveDelay = value + + def getAsyncWaitTime(self): + return self.__asyncWaitTime + + def setAsyncWaitTime(self, value): + self.__asyncWaitTime = value + + def __str__(self): + if self.__protocol == NetworkType.TCP: + tmp = "TCP " + else: + tmp = "UDP " + if self.server: + tmp = tmp + "Server " + if self.__host_name: + tmp = tmp + self.__host_name + elif self.server: + tmp = tmp + socket.gethostname() + return tmp + ":" + str(self.__port) diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/_GXSynchronousMediaBase.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/_GXSynchronousMediaBase.py new file mode 100644 index 0000000..3ae64f4 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/_GXSynchronousMediaBase.py @@ -0,0 +1,298 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +import threading +import datetime +from gurux_common.enums.TraceLevel import TraceLevel + +###Python 2 requires this +#pylint: disable=bad-option-value,old-style-class +class _GXSynchronousMediaBase: + def __init__(self, length=0): + """Constructor. + + length : receive buffer size. + """ + #Trace level. + self.trace = TraceLevel.OFF + #Occurred exception. + self.exception = None + #Received bytes. + self.__receivedBuffer = bytearray(length) + #Received event. + self.__receivedEvent = threading.Event() + #Synchronous object. + self.__sync = threading.RLock() + #Amount of received bytes. + self.__receivedSize = 0 + #Position where end of packet was last time search. This is used to + #improve searching. + self.__lastPosition = 0 + + def resetLastPosition(self): + """Reset last position.""" + self.__lastPosition = 0 + + def resetReceivedSize(self): + """Reset received size.""" + self.__receivedSize = 0 + + def setReceived(self): + """Set received event.""" + self.__receivedEvent.set() + + def getSync(self): + """Get synchronous object.""" + return self.__sync + + def getReceivedSize(self): + """@return Amount of received bytes.""" + return self.__receivedSize + + def getReceivedData(self): + """@return Get received data.""" + return self.__receivedBuffer[0:self.__receivedSize] + + def appendData(self, data, index, count): + """Append new data. + + data : data to append. + index : Index where start. + count : Count of bytes to add. + """ + with self.__sync: + #Allocate new buffer. + if self.__receivedSize + count > len(self.__receivedBuffer): + tmp = self.__receivedBuffer + self.__receivedBuffer = bytearray(2 * len(self.__receivedBuffer)) + self.__receivedBuffer[0:self.__receivedSize] = tmp[0:self.__receivedSize] + + self.__receivedBuffer[self.__receivedSize:self.__receivedSize + count - index] = data[index: count - index] + self.__receivedSize += count - index + + @classmethod + def indexOf(cls, data, pattern, index, count): + """ + Finds the first occurrence of the pattern in the text. + data : Data where to find. + pattern : Search pattern. + index : Byte index to start. + count : Count of bytes to search. + Return True if pattern is found. + """ + failure = _GXSynchronousMediaBase.__computeFailure(pattern) + j = 0 + if not data or len(data) < index: + return -1 + + for i in range(index, count): + while j > 0 and pattern[j] != data[i]: + j = failure[j - 1] + if pattern[j] == data[i]: + j = j + 1 + + if j == len(pattern): + return i - len(pattern) + 1 + return -1 + + @classmethod + def __computeFailure(cls, pattern): + """Computes the failure function using a boot-strapping process, where the + pattern is matched against itself. + + pattern : Pattern to search. + Returns Failure pattern. + """ + failure = bytearray(len(pattern)) + j = 0 + for i in range(1, len(pattern)): + while j > 0 and pattern[j] != pattern[i]: + j = failure[j - 1] + if pattern[j] == pattern[i]: + j = j + 1 + + failure[i] = j + return failure + + def __findData(self, args): + """Find data from buffer. + args : Receive parameters. + isFound : Is data found in given time. + Returns : Position where end of packet was found. -1 Is returned if data + was not found in given time. + """ + nSize = 0 + foundPosition = -1 + lastBuffSize = 0 + startTime = datetime.datetime.now().time().microsecond + terminator = None + nMinSize = args.count + if nMinSize < nSize: + nMinSize = nSize + + waitTime = args.waitTime + self.exception = None + if waitTime <= 0: + waitTime = -1 + if args.eop: + terminator = self.toBytes(args.eop) + nSize = len(terminator) + #Wait until reply occurred. + while foundPosition == -1: + if waitTime == 0: + #If we want to read all data. + if args.allData: + foundPosition = self.__receivedSize + else: + foundPosition = -1 + break + + if waitTime != - 1: + waitTime = args.waitTime - ((datetime.datetime.now().time().microsecond - startTime) / 1000) + if waitTime < 0: + waitTime = 0 + + with self.__sync: + isReceived = not (lastBuffSize == self.__receivedSize or self.__receivedSize < nMinSize) + + # Do not wait if there is data on the buffer... + if not isReceived: + if waitTime == - 1: + isReceived = self.__receivedEvent.wait() + elif waitTime != 0: + isReceived = self.__receivedEvent.wait(waitTime / 1000) + self.__receivedEvent.clear() + if self.exception: + #pylint: disable=raising-bad-type + raise self.exception + #If timeout occurred. + if not isReceived: + #If we want to read all data. + if args.allData and self.__receivedSize != 0: + foundPosition = self.__receivedSize + else: + foundPosition = -1 + break + + with self.__sync: + lastBuffSize = self.__receivedSize + #Read more data, if not enough. + if self.__receivedSize < nMinSize: + continue + + #If only byte count matters. + if nSize == 0: + foundPosition = args.count + else: + if self.__lastPosition != 0 and self.__lastPosition < self.__receivedSize: + index = self.__lastPosition + else: + index = args.count + #If terminator found. + if isinstance(args.eop, (list)): + raise Exception("Only single byte terminator allowed.") + if len(terminator) != 1 and self.__receivedSize - index < len(terminator): + index = self.__receivedSize - len(terminator) + foundPosition = self.indexOf(self.__receivedBuffer, terminator, index, self.__receivedSize) + self.__lastPosition = self.__receivedSize + if foundPosition != -1: + foundPosition += len(terminator) + + #If terminator is not given read only bytes that are needed. + if nSize == 0 and foundPosition != -1: + foundPosition = args.count + return foundPosition + + def receive(self, args): + """ + Receive new data synchronously from the media. + args : Receive parameters. + Return True if new data is received. + """ + if args.eop is None and args.count == 0: + raise Exception("Either Count or Eop must be set.") + foundPosition = self.__findData(args) + data = None + if foundPosition != -1: + with self.__sync: + if args.allData: + #If all data is copied. + foundPosition = self.__receivedSize + + if foundPosition != 0: + #Convert bytes to object. + data = self.__receivedBuffer[0:foundPosition] + #Remove read data. + self.__receivedSize -= foundPosition + #Received size can go less than zero if we have received + #data and we try to read more. + if self.__receivedSize < 0: + self.__receivedSize = 0 + if self.__receivedSize != 0: + self.__receivedBuffer[0:self.__receivedSize] = self.__receivedBuffer[foundPosition:foundPosition + self.__receivedSize] + #Reset count after read. + args.count = 0 + #Append data. + oldReplySize = 0 + if args.reply is None: + args.reply = data + else: + oldArray = args.reply + newArray = data + if newArray: + oldReplySize = len(oldArray) + len_ = oldReplySize + len(newArray) + arr = bytearray(len_) + #Copy old values. + arr[0:len(oldArray)] = args.reply[0:len(oldArray)] + #Copy new values. + arr[len(oldArray): len(oldArray) + len(newArray)] = newArray[0:len(newArray)] + args.reply = arr + return foundPosition != -1 + + #Convert value to bytes. + @classmethod + def toBytes(cls, value): + if isinstance(value, bytearray): + pass + elif isinstance(value, str): + value = bytearray(value.encode()) + elif isinstance(value, (bytes, list)): + value = bytearray(value) + elif isinstance(value, memoryview): + value = bytearray(value.tobytes()) + elif isinstance(value, int): + value = bytearray([value]) + else: + raise ValueError("Invalid data value.") + return value diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/_NetReceiveEventArgs.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/_NetReceiveEventArgs.py new file mode 100644 index 0000000..a9138f2 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/_NetReceiveEventArgs.py @@ -0,0 +1,39 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from gurux_common.ReceiveEventArgs import ReceiveEventArgs + +class _NetReceiveEventArgs(ReceiveEventArgs): + def __init__(self, data=None, senderInfo=None, socket=None): + ReceiveEventArgs.__init__(self, data, senderInfo) + self.socket = socket diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/__init__.py new file mode 100644 index 0000000..a190617 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/__init__.py @@ -0,0 +1,36 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from ._GXSynchronousMediaBase import _GXSynchronousMediaBase +from ._NetReceiveEventArgs import _NetReceiveEventArgs +from .GXNet import GXNet diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/enums/NetworkType.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/enums/NetworkType.py new file mode 100644 index 0000000..6ed855a --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/enums/NetworkType.py @@ -0,0 +1,48 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +#pylint: disable=broad-except,no-name-in-module +try: + from enum import Enum + __base = Enum +except Exception: + __base = object + +class NetworkType(__base): + """Determines the valid network protocols to be used in data transfer.""" + #pylint: disable=too-few-public-methods + + #UDP protocol. + UDP = 0 + #TCP/IP protocol. + TCP = 1 diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/enums/__init__.py b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/enums/__init__.py new file mode 100644 index 0000000..9192bc2 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/gurux_net/enums/__init__.py @@ -0,0 +1,34 @@ +# +# -------------------------------------------------------------------------- +# Gurux Ltd +# +# +# +# Filename: $HeadURL$ +# +# Version: $Revision$, +# $Date$ +# $Author$ +# +# Copyright (c) Gurux Ltd +# +# --------------------------------------------------------------------------- +# +# DESCRIPTION +# +# This file is a part of Gurux Device Framework. +# +# Gurux Device Framework is Open Source software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 of the License. +# Gurux Device Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# More information of Gurux products: http://www.gurux.org +# +# This code is licensed under the GNU General Public License v2. +# Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt +# --------------------------------------------------------------------------- +from .NetworkType import NetworkType diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/meter_data.json b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/meter_data.json new file mode 100644 index 0000000..7a753fc --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/meter_data.json @@ -0,0 +1,251 @@ +[ + { + "text": "Supply frequency", + "meter_param": "1.0.14.7.0.255", + "meter_attr": 2, + "multiplier": 0.01, + "unit": "Hz", + "sensor": "frequency" + }, + { + "text": "Sum Li Reactive power+ (QI+QII)", + "meter_param": "1.0.3.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "VA", + "sensor": "power" + }, + { + "text": "Sum Li Reactive power- (QIII+QIV)", + "meter_param": "1.0.4.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "VA", + "sensor": "power" + }, + { + "text": "Sum Li Power factor", + "meter_param": "1.0.13.7.0.255", + "meter_attr": 2, + "multiplier": 0.001, + "sensor": "power_factor" + }, + { + "text": "Sum Li Active power (abs(QI+QIV)-abs(QII+QIII))", + "meter_param": "1.0.16.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "W", + "sensor": "power" + }, + { + "text": "L1 Active power (abs(QI+QIV)-abs(QII+QIII))", + "meter_param": "1.0.36.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "W", + "sensor": "power" + }, + { + "text": "L2 Active power (abs(QI+QIV)-abs(QI+QIII))", + "meter_param": "1.0.56.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "W", + "sensor": "power" + }, + { + "text": "L3 Active power (abs(QI+QIV)-abs(QI+QIII))", + "meter_param": "1.0.76.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "W", + "sensor": "power" + }, + { + "text": "L1 Reactive power+ (QI+QII)", + "meter_param": "1.0.23.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "VA", + "sensor": "power" + }, + { + "text": "L2 Reactive power+ (QI+QII)", + "meter_param": "1.0.43.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "VA", + "sensor": "power" + }, + { + "text": "L3 Reactive power+ (QI+QII)", + "meter_param": "1.0.63.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "VA", + "sensor": "power" + }, + { + "text": "L1 Reactive power- (QIII+QIV)", + "meter_param": "1.0.24.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "VA", + "sensor": "power" + }, + { + "text": "L2 Reactive power- (QIII+QIV)", + "meter_param": "1.0.44.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "VA", + "sensor": "power" + }, + { + "text": "L3 Reactive power- (QIII+QIV)", + "meter_param": "1.0.64.7.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "VA", + "sensor": "power" + }, + { + "text": "L1 Power factor", + "meter_param": "1.0.33.7.0.255", + "meter_attr": 2, + "multiplier": 0.001, + "sensor": "power_factor" + }, + { + "text": "L2 Power factor", + "meter_param": "1.0.53.7.0.255", + "meter_attr": 2, + "multiplier": 0.001, + "sensor": "power_factor" + }, + { + "text": "L3 Power factor", + "meter_param": "1.0.73.7.0.255", + "meter_attr": 2, + "multiplier": 0.001, + "sensor": "power_factor" + }, + { + "text": "L1 Current", + "meter_param": "1.0.31.7.0.255", + "meter_attr": 2, + "multiplier": 0.01, + "unit": "A", + "sensor": "current" + }, + { + "text": "L2 Current", + "meter_param": "1.0.51.7.0.255", + "meter_attr": 2, + "multiplier": 0.01, + "unit": "A", + "sensor": "current" + }, + { + "text": "L3 Current", + "meter_param": "1.0.71.7.0.255", + "meter_attr": 2, + "multiplier": 0.01, + "unit": "A", + "sensor": "current" + }, + { + "text": "L1 Voltage", + "meter_param": "1.0.32.7.0.255", + "meter_attr": 2, + "multiplier": 0.01, + "unit": "V", + "sensor": "voltage" + }, + { + "text": "L2 Voltage", + "meter_param": "1.0.52.7.0.255", + "meter_attr": 2, + "multiplier": 0.01, + "unit": "V", + "sensor": "voltage" + }, + { + "text": "L3 Voltage", + "meter_param": "1.0.72.7.0.255", + "meter_attr": 2, + "multiplier": 0.01, + "unit": "V", + "sensor": "voltage" + }, + { + "text": "Time of operation, total", + "meter_param": "0.0.96.8.0.255", + "meter_attr": 2, + "multiplier": 1, + "unit": "s", + "sensor": "time_duration" + }, + { + "text": "Time of operation Rate 1", + "meter_param": "0.0.96.8.1.255", + "meter_attr": 2, + "multiplier": 1, + "unit": "s", + "sensor": "time_duration" + }, + { + "text": "Time of operation Rate 2", + "meter_param": "0.0.96.8.2.255", + "meter_attr": 2, + "multiplier": 1, + "unit": "s", + "sensor": "time_duration" + }, + { + "text": "Time of operation Rate 3", + "meter_param": "0.0.96.8.3.255", + "meter_attr": 2, + "multiplier": 1, + "unit": "s", + "sensor": "time_duration" + }, + { + "text": "Time of operation Rate 4", + "meter_param": "0.0.96.8.4.255", + "meter_attr": 2, + "multiplier": 1, + "unit": "s", + "sensor": "time_duration" + }, + { + "text": "Time stamp of the most recent billing period closed", + "meter_param": "1.0.0.1.2.255", + "meter_attr": 2, + "sensor": "date" + }, + { + "text": "No. of long power failures in all three phases", + "meter_param": "0.0.96.7.5.255", + "meter_attr": 2, + "multiplier": 1, + "sensor": "counter" + }, + { + "text": "Sum Li Active power+ (QI+QIV) Time integral 1 Rate 0", + "meter_param": "1.0.1.8.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "Wh", + "sensor": "energy_total" + }, + { + "text": "Sum Li Active power+ (QI+QIV) Time integral 2 Rate 0", + "meter_param": "1.0.1.9.0.255", + "meter_attr": 2, + "multiplier": 0.1, + "unit": "Wh", + "sensor": "energy_total_increasing" + } +] diff --git a/custom_components/gama_300_dlms_meter_reader/dlms_read_data/meter_param.json b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/meter_param.json new file mode 100644 index 0000000..9a7249d --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/dlms_read_data/meter_param.json @@ -0,0 +1,38 @@ +[ + { + "text": "Meter Model", + "meter_param": "1.0.96.1.1.255", + "meter_attr": 2, + "sensor": "info" + }, + { + "text": "Model Config", + "meter_param": "1.0.96.1.2.255", + "meter_attr": 2, + "sensor": "info" + }, + { + "text": "Meter Serial", + "meter_param": "1.0.0.0.0.255", + "meter_attr": 2, + "sensor": "info" + }, + { + "text": "Meter Country ISO", + "meter_param": "1.0.0.0.1.255", + "meter_attr": 2, + "sensor": "info" + }, + { + "text": "Meter Country", + "meter_param": "1.0.0.0.2.255", + "meter_attr": 2, + "sensor": "info" + }, + { + "text": "Meter Firmware", + "meter_param": "1.0.0.2.0.255", + "meter_attr": 2, + "sensor": "info" + } +] \ No newline at end of file diff --git a/custom_components/gama_300_dlms_meter_reader/hub.py b/custom_components/gama_300_dlms_meter_reader/hub.py new file mode 100644 index 0000000..d5ed59b --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/hub.py @@ -0,0 +1,125 @@ +"""A demonstration 'hub' that connects several devices.""" +from __future__ import annotations +from .dlms_read_data.MeterClient import MeterClient + +# In a real implementation, this would be in an external library that's on PyPI. +# The PyPI package needs to be included in the `requirements` section of manifest.json +# See https://developers.home-assistant.io/docs/creating_integration_manifest +# for more information. +# This hub always returns 3 meters. +import asyncio +import random + +from homeassistant.core import HomeAssistant +import time +import logging +_LOGGER = logging.getLogger(__name__) + +class Hub: + manufacturer = "DLMS Meter" + + def __init__(self, hass: HomeAssistant, host: str, port: int, serial: int) -> None: + """Init hub.""" + self._host = host + self._port = port + self._serial = serial + self._hass = hass + self.meter_client=MeterClient(host,port,serial) + self._name = "DLMS Meter " + host + self._id = host.lower() + #self.manufacturer=self.meter_client.get_model2() + self.manufacturer='DLMS Meter Manufacturer' + self.meters = [ + DLMSMeter(f"{self._id}_1", f"{self._name}", self), + #DLMSMeter(f"{self._id}_2", f"{self._name} 2", self), + #DLMSMeter(f"{self._id}_3", f"{self._name} 3", self), + ] + self.online = True + + @property + def hub_id(self) -> str: + """ID for hub.""" + return self._id + + async def test_connection(self) -> bool: + """Test connectivity with dlmsmeter""" + #await asyncio.sleep(1) + return self.meter_client.online() + + +class DLMSMeter: + """dlmsmeter (device for HA).""" + + def __init__(self, dlmsmeterid: str, name: str, hub: Hub) -> None: + """Init dlmsmeter.""" + self._id = dlmsmeterid + self.hub = hub + self.name = name + self._callbacks = set() + self._loop = asyncio.get_event_loop() + self.hub.meter_client.connect() + self.model=self.hub.meter_client.get_model() + self.firmware_version=self.hub.meter_client.get_firmware_version() + + @property + def dlmsmeter_id(self) -> str: + """Return ID for dlmsmeter.""" + return self._id + + @property + def position(self): + """Return position for dlmsmeter.""" + return self._current_position + + async def set_position(self, position: int) -> None: + """ + Set cover to the given position. + + State is announced a random number of seconds later. + """ + self._target_position = position + + # Update the moving status, and broadcast the update + self.moving = position - 50 + await self.publish_updates() + + self._loop.create_task(self.delayed_update()) + + async def delayed_update(self) -> None: + """Publish updates, with a random delay to emulate interaction with device.""" + await asyncio.sleep(20) + self.moving = 0 + await self.publish_updates() + + def register_callback(self, callback: Callable[[], None]) -> None: + """Register callback, called when DLMSMeter changes state.""" + self._callbacks.add(callback) + + def remove_callback(self, callback: Callable[[], None]) -> None: + """Remove previously registered callback.""" + self._callbacks.discard(callback) + + # In a real implementation, this library would call it's call backs when it was + # notified of any state changeds for the relevant device. + async def publish_updates(self) -> None: + """Schedule call all registered callbacks.""" + self._current_position = self._target_position + for callback in self._callbacks: + callback() + + def update_data(self): + return self.hub.meter_client.ReadAllConfig() + + def get_available_params(self): + return self.hub.meter_client.GetDataList() + + def get_available_diag_params(self): + return self.hub.meter_client.GetMeterConfigList() + + @property + def online(self) -> float: + """DLMSMeter is online.""" + # Returns True if online, + # False if offline. + self.update_data() + return self.hub.meter_client.online() diff --git a/custom_components/gama_300_dlms_meter_reader/manifest.json b/custom_components/gama_300_dlms_meter_reader/manifest.json new file mode 100644 index 0000000..6bfbd05 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/manifest.json @@ -0,0 +1,14 @@ +{ + "domain": "gama_300_dlms_meter_reader", + "name": "GAMA 300 DLMS Meter", + "codeowners": ["@astraliens"], + "config_flow": true, + "dependencies": [], + "documentation": "https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter", + "homekit": {}, + "iot_class": "local_polling", + "requirements": [], + "ssdp": [], + "version": "0.1.0", + "zeroconf": [] +} diff --git a/custom_components/gama_300_dlms_meter_reader/sensor.py b/custom_components/gama_300_dlms_meter_reader/sensor.py new file mode 100644 index 0000000..d444019 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/sensor.py @@ -0,0 +1,331 @@ +"""Platform for sensor integration.""" +# This file shows the setup for the sensors associated with the cover. +# They are setup in the same way with the call to the async_setup_entry function +# via HA from the module __init__. Each sensor has a device_class, this tells HA how +# to display it in the UI (for know types). The unit_of_measurement property tells HA +# what the unit is, so it can display the correct range. For predefined types (such as +# battery), the unit_of_measurement should match what's expected. +import random + +from homeassistant.const import ( + ATTR_VOLTAGE, + DEVICE_CLASS_BATTERY, + PERCENTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + EntityCategory +) +from homeassistant.helpers.entity import Entity + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntityDescription, + SensorStateClass, + SensorEntity +) + +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) +import async_timeout +from homeassistant.core import callback +from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.components.light import LightEntity + + + + + +from .const import DOMAIN +from datetime import timedelta +import time + +from homeassistant.components.sensor import STATE_CLASS_TOTAL_INCREASING + +import logging +_LOGGER = logging.getLogger(__name__) +#SCAN_INTERVAL = timedelta(seconds=5) + + +# See cover.py for more details. +# Note how both entities for each dlmsmeter sensor (battry and illuminance) are added at +# the same time to the same list. This way only a single async_add_devices call is +# required. +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add sensors for passed config_entry in HA.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + + coordinator = DLMSMeterDataCoordinator(hass, hub) + await coordinator.async_config_entry_first_refresh() + + coordinator_diag = DLMSMeterConfigCoordinator(hass, hub) + await coordinator_diag.async_config_entry_first_refresh() + + + new_devices = [] + for dlmsmeter in hub.meters: + available_params=dlmsmeter.get_available_params() + for dlmsmeter_param in available_params: + match dlmsmeter_param["sensor"]: + case "voltage": + new_devices.append(SensorDLMSMeterVoltage(coordinator, dlmsmeter_param)) + case "current": + new_devices.append(SensorDLMSMeterCurrent(coordinator, dlmsmeter_param)) + case "frequency": + new_devices.append(SensorDLMSMeterFrequency(coordinator, dlmsmeter_param)) + case "power": + new_devices.append(SensorDLMSMeterPower(coordinator, dlmsmeter_param)) + case "power_factor": + new_devices.append(SensorDLMSMeterPowerFactor(coordinator, dlmsmeter_param)) + case "energy_total": + new_devices.append(SensorDLMSMeterEnergyTotal(coordinator, dlmsmeter_param)) + case "energy_total_increasing": + new_devices.append(SensorDLMSMeterEnergyTotalIncreasing(coordinator, dlmsmeter_param)) + case "counter": + new_devices.append(SensorDLMSMeterCounter(coordinator, dlmsmeter_param)) + case "info": + new_devices.append(SensorDLMSMeterInfo(coordinator, dlmsmeter_param)) + case "time_duration": + new_devices.append(SensorDLMSMeterTimeDuration(coordinator, dlmsmeter_param)) + + available_diag_params=dlmsmeter.get_available_diag_params() + for dlmsmeter_param in available_diag_params: + match dlmsmeter_param["sensor"]: + case "info": + new_devices.append(SensorDLMSMeterDiagInfo(coordinator_diag, dlmsmeter_param)) + + if new_devices: + async_add_entities(new_devices) + + +class DLMSMeterDataCoordinator(DataUpdateCoordinator): + """My custom coordinator.""" + + def __init__(self, hass, hub): + """Initialize my coordinator.""" + super().__init__( + hass, + _LOGGER, + # Name of the data. For logging purposes. + name="Meter Data Coordinator", + # Polling interval. Will only be polled if there are subscribers. + update_interval=timedelta(seconds=3), + ) + self.hub = hub + self.hass=hass + + async def _async_update_data(self): + """Fetch data from API endpoint. + + This is the place to pre-process the data to lookup tables + so entities can quickly look up their data. + """ + try: + # Note: asyncio.TimeoutError and aiohttp.ClientError are already + # handled by the data update coordinator. + async with async_timeout.timeout(30): + # Grab active context variables to limit data required to be fetched from API + # Note: using context is not required if there is no need or ability to limit + # data retrieved from API. + listening_idx = set(self.async_contexts()) + #return await self.hub.fetch_data(listening_idx) + + return await self.hass.async_add_executor_job(self.hub.meter_client.ReadAllDataSYNC) # nedd to avoid problem with UI freezing during data update process + #return await self.hub.meter_client.ReadAllData() + + except Exception as e: + s = str(e) + # Raising ConfigEntryAuthFailed will cancel future updates + # and start a config flow with SOURCE_REAUTH (async_step_reauth) + _LOGGER.error("-------------------- COORDINATOR DATA UPDATE FAILED ----------------" + s) + self.hub.meter_client.reconnect() + + +class DLMSMeterConfigCoordinator(DataUpdateCoordinator): + """My custom coordinator.""" + + def __init__(self, hass, hub): + """Initialize my coordinator.""" + super().__init__( + hass, + _LOGGER, + # Name of the data. For logging purposes. + name="Meter Config Coordinator", + # Polling interval. Will only be polled if there are subscribers. + update_interval=timedelta(seconds=600), + ) + self.hub = hub + + async def _async_update_data(self): + """Fetch data from API endpoint. + + This is the place to pre-process the data to lookup tables + so entities can quickly look up their data. + """ + try: + # Note: asyncio.TimeoutError and aiohttp.ClientError are already + # handled by the data update coordinator. + async with async_timeout.timeout(20): + # Grab active context variables to limit data required to be fetched from API + # Note: using context is not required if there is no need or ability to limit + # data retrieved from API. + listening_idx = set(self.async_contexts()) + #return await self.hub.fetch_data(listening_idx) + return await self.hub.meter_client.ReadAllConfig() + + except Exception as e: + s = str(e) + # Raising ConfigEntryAuthFailed will cancel future updates + # and start a config flow with SOURCE_REAUTH (async_step_reauth) + _LOGGER.error("-------------------- COORDINATOR CONFIG UPDATE FAILED ----------------" + s) + + +class SensorBase(CoordinatorEntity, SensorEntity): + """An entity using CoordinatorEntity. + + The CoordinatorEntity class provides: + should_poll + async_update + async_added_to_hass + available + + """ + + device_class = "" + state_class = "" + + def __init__(self, coordinator, dlmsmeter_param): + """Pass coordinator to CoordinatorEntity.""" + super().__init__(coordinator) + self.hub=coordinator.hub + self.dlmsmeter_param = dlmsmeter_param + self._attr_unique_id = f"dlms_meter_{self.hub._id}_{dlmsmeter_param['meter_param']}" + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + test =1 + """ + #self._attr_is_on = self.coordinator.data[self.idx]["text"] + self._attr_is_on = 1 + self.async_write_ha_state() + """ + +class SensorDLMSMeter(SensorBase): + def __init__(self, coordinator, dlmsmeter_param): + super().__init__(coordinator, dlmsmeter_param) + #self.sensor_name=dlmsmeter_param["text"] + self.meter_param=dlmsmeter_param["meter_param"] + self._attr_name = f"{dlmsmeter_param['text']}" + self._attr_unique_id = f"dlms_meter_{self.hub._id}_{dlmsmeter_param['meter_param']}" + #self.sensor_name=self._attr_unique_id + self._state = 0 + if "unit" in dlmsmeter_param : + self._attr_native_unit_of_measurement = dlmsmeter_param["unit"] + self.last_state='' + + @property + def device_info(self): + """Return information to link this entity with the correct device.""" + # return {"identifiers": {(DOMAIN, self.hub._id)}} + + return { + "identifiers": {(DOMAIN, self.hub._id)}, + # If desired, the name for the device could be different to the entity + "name": self.hub._name, + #"sw_version": self.hub.meter_client.get_firmware_version(), + #"model": self.hub.meter_client.get_model(), + "manufacturer": self.hub.meter_client.get_manufacturer() + } + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self.last_state=self.GtCurrentParamData()['value'] + self.async_write_ha_state() + + def GtCurrentParamData(self): + for data_item in self.coordinator.data: + if(data_item['meter_param']==self.meter_param): + return data_item + + @property + def state(self) : + return self.last_state + #self.coordinator.data + #return self.GtCurrentParamData()['value'] + + +class SensorDLMSMeterVoltage(SensorDLMSMeter): + def __init__(self, coordinator, dlmsmeter_param): + super().__init__(coordinator, dlmsmeter_param) + self.state_class = SensorStateClass.MEASUREMENT + self.device_class = DEVICE_CLASS_VOLTAGE + +class SensorDLMSMeterCurrent(SensorDLMSMeter): + def __init__(self, coordinator, dlmsmeter_param): + super().__init__(coordinator, dlmsmeter_param) + self.state_class = SensorStateClass.MEASUREMENT + self.device_class = DEVICE_CLASS_CURRENT + +class SensorDLMSMeterPowerFactor(SensorDLMSMeter): + def __init__(self, coordinator, dlmsmeter_param): + super().__init__(coordinator, dlmsmeter_param) + self.state_class = SensorStateClass.MEASUREMENT + self.device_class = DEVICE_CLASS_POWER_FACTOR + +class SensorDLMSMeterFrequency(SensorDLMSMeter): + def __init__(self, dlmsmeter, param_name): + super().__init__(dlmsmeter, param_name) + self.state_class = SensorStateClass.MEASUREMENT + self.device_class = DEVICE_CLASS_FREQUENCY + +class SensorDLMSMeterPower(SensorDLMSMeter): + def __init__(self, dlmsmeter, param_name): + super().__init__(dlmsmeter, param_name) + self.state_class = SensorStateClass.MEASUREMENT + self.device_class = DEVICE_CLASS_POWER + +class SensorDLMSMeterEnergyTotal(SensorDLMSMeter): + def __init__(self, dlmsmeter, param_name): + super().__init__(dlmsmeter, param_name) + self.device_class = SensorDeviceClass.ENERGY + self.state_class = SensorStateClass.TOTAL + +class SensorDLMSMeterEnergyTotalIncreasing(SensorDLMSMeter): + def __init__(self, dlmsmeter, param_name): + super().__init__(dlmsmeter, param_name) + self.device_class = SensorDeviceClass.ENERGY + self.state_class = SensorStateClass.TOTAL_INCREASING + +class SensorDLMSMeterInfo(SensorDLMSMeter): + def __init__(self, dlmsmeter, param_name): + super().__init__(dlmsmeter, param_name) + +class SensorDLMSMeterCounter(SensorDLMSMeter): + def __init__(self, dlmsmeter, param_name): + super().__init__(dlmsmeter, param_name) + self.state_class = SensorStateClass.MEASUREMENT + +class SensorDLMSMeterTimeDuration(SensorDLMSMeter): + def __init__(self, dlmsmeter, param_name): + super().__init__(dlmsmeter, param_name) + #self.state_class = SensorStateClass.MEASUREMENT + self.device_class = SensorDeviceClass.DURATION + +class SensorDLMSMeterDiag(SensorDLMSMeter): + entity_category = EntityCategory.DIAGNOSTIC + def __init__(self, dlmsmeter, param_name): + super().__init__(dlmsmeter, param_name) + +class SensorDLMSMeterDiagInfo(SensorDLMSMeterDiag): + def __init__(self, dlmsmeter, param_name): + super().__init__(dlmsmeter, param_name) + diff --git a/custom_components/gama_300_dlms_meter_reader/strings.json b/custom_components/gama_300_dlms_meter_reader/strings.json new file mode 100644 index 0000000..2cda440 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/strings.json @@ -0,0 +1,23 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]", + "serial": "[%key:common::config_flow::data::serial%]", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} diff --git a/custom_components/gama_300_dlms_meter_reader/translations/en.json b/custom_components/gama_300_dlms_meter_reader/translations/en.json new file mode 100644 index 0000000..59d86e3 --- /dev/null +++ b/custom_components/gama_300_dlms_meter_reader/translations/en.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "step": { + "user": { + "data": { + "host": "Meter IP", + "port": "Meter Port", + "serial": "Meter serial number", + "password": "[%key:common::config_flow::data::password%]", + "username": "[%key:common::config_flow::data::username%]" + } + } + } + } +} \ No newline at end of file diff --git a/hacs.json b/hacs.json new file mode 100644 index 0000000..b555272 --- /dev/null +++ b/hacs.json @@ -0,0 +1,6 @@ +{ + "name": "Gama 300 DLMS Meter", + "content_in_root": false, + "render_readme": true, + "iot_class": "Local Pull" +} diff --git a/images/HF5142B_TCP_Server.jpg b/images/HF5142B_TCP_Server.jpg new file mode 100644 index 0000000..ac71f1c Binary files /dev/null and b/images/HF5142B_TCP_Server.jpg differ diff --git a/images/HF5142B_connection.jpg b/images/HF5142B_connection.jpg new file mode 100644 index 0000000..0c22cec Binary files /dev/null and b/images/HF5142B_connection.jpg differ diff --git a/images/gama_300_rs485_pinout.jpg b/images/gama_300_rs485_pinout.jpg new file mode 100644 index 0000000..54e4416 Binary files /dev/null and b/images/gama_300_rs485_pinout.jpg differ