spacemanspiff2007 / SmlLib

A library for the SML (Smart Message Language) protocol
GNU General Public License v3.0
28 stars 9 forks source link

OBIS Codes #6

Closed Froschie closed 2 years ago

Froschie commented 2 years ago

Hello,

Great SML module for python. I´ll be using it soon for one of my scripts.

One improvement I´ve added in my script is to decoded the OBIX Hex strings you output.

https://www.promotic.eu/en/pmdoc/Subsystems/Comm/PmDrivers/IEC62056_OBIS.htm If looking at the whole HEX string is contains: A-B:C.D.E*F

Personally I think C.D.E is sufficient.

In my testing today simply decoded it with: obis_C = int(value[4:6], 16) obis_D = int(value[6:8], 16) obis_E = int(value[8:10], 16) obis_string = "{}.{}.{}".format(obis_C, obis_D, obis_E)

Would be great if your library would decode it directly instead of giving the RAW Hex value.

Thanks.

niksauer commented 2 years ago

I've created the following utilities to deal with OBIS codes:

import re
from typing import List

OBIS_CODE_REGEX = r'^\d+-\d+:\d+.\d+.\d+\*\d+$'  # Format: A-B:C.D.E*F
OBIS_HEX_REGEX = r'^[\da-fA-F]{12}$'  # Format: twelve hex digits

def is_obis_code(str: str) -> bool:
    return bool(re.match(OBIS_CODE_REGEX, str))

def is_obis_hex(str: str) -> bool:
    return bool(re.match(OBIS_HEX_REGEX, str))

def obis_code_to_hex(obis: str) -> str:
    if not is_obis_code(obis):
        raise ValueError('Input does not represent a valid OBIS code')

    groups: List[str] = re.split(r'\D+', obis)
    return ''.join(['{:02x}'.format(int(group)) for group in groups])

def _obis_hex_to_groups(hex: str) -> List[int]:
    if not is_obis_hex(hex):
        raise ValueError('Input does not represent a valid OBIS hex')

    return [int(hex[i:i + 2], 16) for i in range(0, len(hex), 2)]

def obis_hex_to_code(hex: str, short: bool = False) -> str:
    groups = _obis_hex_to_groups(hex)

    if short:
        return '{0}.{1}.{2}'.format(*groups[2:-1])

    return '{0}-{1}:{2}.{3}.{4}*{5}'.format(*groups)

Feel free to include them in this library. This project has helped me a lot 🙂


Tests

class ObisUtilsTestCase(TestCase):
    def test_is_obis_code(self):
        """
        Should be able to identify valid OBIS codes.

        Format: A-B:C.D.E*F
        """
        self.assertTrue(is_obis_code('1-0:1.8.0*255'))

        # May not have empty groups
        self.assertFalse(is_obis_code(':..*'))

        # May not skip a group (F)
        self.assertFalse(is_obis_code('1-0:1.8.0'))

        # May not contain non-digit in a group (F)
        self.assertFalse(is_obis_code('1-0:1.8.0*25A'))

    def test_is_obis_hex(self):
        """
        Should be able to identify valid OBIS hex codes.

        Format: twelve hex digits
        """
        self.assertTrue(is_obis_hex('0100010800ff'))

        # May mix upper/lowercase hex digits (F)
        self.assertTrue(is_obis_hex('0100010800fF'))

        # May not contain non-hex digits (g)
        self.assertFalse(is_obis_hex('0100010800g'))

        # May not have less than 12 characters (11)
        self.assertFalse(is_obis_hex('0100010800f'))

        # May not have more than 12 characters (13)
        self.assertFalse(is_obis_hex('0100010800fff'))

    def test_obis_code_to_hex(self):
        """
        Should be able to convert an OBIS code to an OBIS hex code.
        """
        self.assertEqual(obis_code_to_hex('1-0:1.8.0*255'), '0100010800ff')

        # Fails if the input is not a valid OBIS code
        with self.assertRaises(ValueError):
            obis_code_to_hex('1-0:1.8.0*25A')

    def test_obis_hex_to_code(self):
        """
        Should be able to convert an OBIS hex code to an OBIS code.
        """
        self.assertEqual(obis_hex_to_code('0100010800ff'), '1-0:1.8.0*255')
        self.assertEqual(obis_hex_to_code('0100010800ff', short=True), '1.8.0')

        # Fails if the input is not a valid OBIS hex
        with self.assertRaises(ValueError):
            obis_hex_to_code('0100010800g')

        with self.assertRaises(ValueError):
            obis_hex_to_code('0100010800g', short=True)