Skip to content

Commit

Permalink
working on #1
Browse files Browse the repository at this point in the history
  • Loading branch information
marq24 committed Oct 18, 2023
1 parent afc9353 commit 2cf7589
Showing 1 changed file with 109 additions and 32 deletions.
141 changes: 109 additions & 32 deletions custom_components/tibber_local/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import logging
import re

import voluptuous as vol

Expand All @@ -15,7 +16,6 @@
from homeassistant.helpers.entity import EntityDescription, Entity
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed


from .const import (
DOMAIN,
MANUFACTURE,
Expand Down Expand Up @@ -157,53 +157,130 @@ def should_poll(self) -> bool:
return False


class XObisCode():
def __init__(self, obis_src: list):
_a = int(obis_src[1])
_b = int(obis_src[2])
_c = int(obis_src[3])
_d = int(obis_src[4])
_e = int(obis_src[5])
_f = int(obis_src[6])
self.obis_code = f'{_a}-{_b}:{_c}.{_d}.{_e}*{_f}'
self.obis_short = f'{_c}.{_d}.{_e}'
self.obis_hex = f'{self.get_two_digit_hex(_a)}{self.get_two_digit_hex(_b)}{self.get_two_digit_hex(_c)}{self.get_two_digit_hex(_d)}{self.get_two_digit_hex(_e)}{self.get_two_digit_hex(_f)}'

def get_two_digit_hex(self, input: int) -> str:
out = f'{input:x}'
if len(out) == 1:
return '0' + out
else:
return out;


class TibberLocalBridge:

def __init__(self, host, pwd, websession, options: dict = None):
# _communication_mode '3' is the initial implemented mode (reading binary sml data)...
# 'all' other modes have to be implemented one by one
def __init__(self, host, pwd, websession, com_mode: int = 3, options: dict = None):
_LOGGER.info(f"restarting TibberLocalBridge integration... for host: '{host}' with options: {options}")
self.websession = websession
self.url = f"http://admin:{pwd}@{host}/data.json?node_id=1"
self.url_data = f"http://admin:{pwd}@{host}/data.json?node_id=1"
self.url_mode = f"http://admin:{pwd}@{host}/node_params.json?node_id=1"
self._com_mode = com_mode
self._obis_values = {}

async def detect_com_mode(self):
# {'param_id': 27, 'name': 'meter_mode', 'size': 1, 'type': 'uint8', 'help': '0:IEC 62056-21, 1:Count impressions', 'value': [3]}
self._com_mode = -1
async with self.websession.get(self.url_mode, ssl=False) as res:
res.raise_for_status()
if res.status == 200:
json_resp = await res.json()
for a_parm_obj in json_resp:
if 'param_id' in a_parm_obj and a_parm_obj['param_id'] == 27 or \
'name' in a_parm_obj and a_parm_obj['name'] == 'meter_mode':
if 'value' in a_parm_obj:
self._com_mode = a_parm_obj['value'][0]
break

async def update(self):
await self.read_tibber_local(retry=True)

async def read_tibber_local(self, retry: bool):
async with self.websession.get(self.url, ssl=False) as res:
async with self.websession.get(self.url_data, ssl=False) as res:
res.raise_for_status()
self._obis_values = {}
if res.status == 200:
payload = await res.read()
# for what ever reason the data that can be read from the TibberPulse Webserver is
# not always valid! [I guess there is a issue with an internal buffer in the webserver
# implementation] - in any case the bytes received contain sometimes invalid characters
# so the 'stream.get_frame()' method will not be able to parse the data...
stream = SmlStreamReader()
stream.add(payload)
try:
sml_frame = stream.get_frame()
if sml_frame is None:
_LOGGER.info(f"Bytes missing - payload: {payload}")
if retry:
await asyncio.sleep(1.5)
await self.read_tibber_local(retry=False)
else:
# Shortcut to extract all values without parsing the whole frame
for entry in sml_frame.get_obis():
self._obis_values[entry.obis] = entry
except CrcError as crc:
_LOGGER.info(f"CRC while parse data - payload: {payload}")
if retry:
await asyncio.sleep(1.5)
await self.read_tibber_local(retry=False)
except Exception as exc:
_LOGGER.warning(f"Exception {exc} while parse data - payload: {payload}")
if retry:
await asyncio.sleep(1.5)
await self.read_tibber_local(retry=False)
if self._com_mode == 3:
await self.read_sml(await res.read(), retry)
elif self._com_mode == -1:
await self.read_plaintext(await res.text(), retry)
else:
_LOGGER.warning(f"access to bridge failed with code {res.status}")

async def read_plaintext(self, plaintext: str, retry: bool):
try:
for a_line in plaintext.splitlines():
# obis pattern is 'a-b:c.d.e*f'
parts = re.split('(.*?)-(.*?):(.*?)\\.(.*?)\\.(.*?)\\*(.*?)\\((.*?)\\)', a_line)
if len(parts) == 9:
obc = XObisCode(parts)
value = parts[7]
if '*' in value:
val_with_unit = value.split("*")
if '.' in val_with_unit[0]:
value = float(val_with_unit[0])
# converting any "kilo" unit to base unit...
# so kWh will be converted to Wh - or kV will be V
if val_with_unit[1].lower()[0] == 'k':
value = value * 1000;

print(obc.obis_hex + ' ' + str(value))
else:
if parts[0] == '!':
break;
elif parts[0][0] != '/':
print('unknown:' + parts[0])
# else:
# print('ignore '+ parts[0])

except Exception as exc:
_LOGGER.warning(f"Exception {exc} while process data - plaintext: {plaintext}")
if retry:
await asyncio.sleep(1.5)
await self.read_tibber_local(retry=False)

async def read_sml(self, payload: bytes, retry: bool):
# for what ever reason the data that can be read from the TibberPulse Webserver is
# not always valid! [I guess there is a issue with an internal buffer in the webserver
# implementation] - in any case the bytes received contain sometimes invalid characters
# so the 'stream.get_frame()' method will not be able to parse the data...
stream = SmlStreamReader()
stream.add(payload)
try:
sml_frame = stream.get_frame()
if sml_frame is None:
_LOGGER.info(f"Bytes missing - payload: {payload}")
if retry:
await asyncio.sleep(1.5)
await self.read_tibber_local(retry=False)
else:
# Shortcut to extract all values without parsing the whole frame
for entry in sml_frame.get_obis():
self._obis_values[entry.obis] = entry

except CrcError as crc:
_LOGGER.info(f"CRC while parse data - payload: {payload}")
if retry:
await asyncio.sleep(1.5)
await self.read_tibber_local(retry=False)

except Exception as exc:
_LOGGER.warning(f"Exception {exc} while parse data - payload: {payload}")
if retry:
await asyncio.sleep(1.5)
await self.read_tibber_local(retry=False)

def _get_value_internal(self, key, divisor: int = 1):
if key in self._obis_values:
a_obis = self._obis_values.get(key)
Expand Down

0 comments on commit 2cf7589

Please sign in to comment.