From 929efdd12c877fb60dbf253cd285e45c2cb8a585 Mon Sep 17 00:00:00 2001 From: Enkhbaatar Tsogtbaatar Date: Fri, 10 Nov 2023 15:29:45 +0100 Subject: [PATCH] Include files --- __init__.py | 1 + manifest.json | 9 +++ sensor.py | 138 +++++++++++++++++++++++++++++++++++++++++++ strings.json | 43 ++++++++++++++ translations/cs.json | 43 ++++++++++++++ translations/en.json | 43 ++++++++++++++ 6 files changed, 277 insertions(+) create mode 100644 __init__.py create mode 100644 manifest.json create mode 100644 sensor.py create mode 100644 strings.json create mode 100644 translations/cs.json create mode 100644 translations/en.json diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..eb5c805 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +"""Solareco sensor integration""" \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..6a70ee4 --- /dev/null +++ b/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "solareco", + "name": "solareco", + "version": "1.0.0", + "dependencies": [], + "requirments": [], + "codeowners": [], + "iot_class": "cloud_polling" +} \ No newline at end of file diff --git a/sensor.py b/sensor.py new file mode 100644 index 0000000..eefc51b --- /dev/null +++ b/sensor.py @@ -0,0 +1,138 @@ +import logging +import telnetlib +from datetime import timedelta, datetime, time +from typing import Callable, Any +import re + +from homeassistant.components.sensor import SensorEntity, SensorStateClass, SensorDeviceClass +from homeassistant.const import UnitOfTemperature, UnitOfPower, UnitOfElectricPotential, UnitOfElectricCurrent, \ + UnitOfEnergy, UnitOfFrequency, UnitOfTime +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import async_track_time_interval + +SIGNAL = 'solareco' +DOMAIN = 'solareco' + +_LOGGER = logging.getLogger('solareco') + + +class SolarecoSensorConfig: + def __init__(self, name, + unit_of_measurement, + device_class, + data_transformation, + icon, + state_class=SensorStateClass.MEASUREMENT): + self.name = name + self.unit_of_measurement = unit_of_measurement + self.device_class = device_class + self.data_transformation = data_transformation + self.icon = icon + self.state_class = state_class + +SENSORS = [ + SolarecoSensorConfig('relay', None, None, lambda data: re.findall('AC+\d', data)[0][2:] if re.search('AC+\d',data) else re.findall('R:+\d', data)[0][2:],""), + SolarecoSensorConfig('fan', None, None, lambda data: re.findall('F:+\d', data)[0][2:] if re.search('F:+\d',data) else None,"mdi:fan"), + SolarecoSensorConfig('required_voltage', UnitOfElectricPotential.VOLT, None, lambda data: re.findall('\d+U', data)[0][:-1],"mdi:alpha-v-circle-outline"), + SolarecoSensorConfig('voltage', UnitOfElectricPotential.VOLT, SensorDeviceClass.VOLTAGE, lambda data: re.findall('\d+V', data)[0][:-1],"mdi:alpha-v-circle-outline"), + SolarecoSensorConfig('current', UnitOfElectricCurrent.MILLIAMPERE, SensorDeviceClass.CURRENT, lambda data: re.findall('\d+mA', data)[0][:-2],"mdi:current-dc"), + SolarecoSensorConfig('power', UnitOfPower.WATT, SensorDeviceClass.POWER, lambda data: re.findall('\d+W', data)[0][:-1],"mdi:alpha-w-circle-outline"), + SolarecoSensorConfig('frequency', UnitOfFrequency.HERTZ, SensorDeviceClass.FREQUENCY, lambda data: re.findall('\d+Hz', data)[0][:-2] if re.search('\d+Hz',data) else None,""), + SolarecoSensorConfig('cooler_temperature', UnitOfTemperature.CELSIUS, SensorDeviceClass.TEMPERATURE, lambda data: re.findall('\d+C', data)[0][:-1],""), + SolarecoSensorConfig('boiler_temperature', UnitOfTemperature.CELSIUS, SensorDeviceClass.TEMPERATURE, lambda data: re.findall('(\d+):\d+C',data)[0],""), + SolarecoSensorConfig('pulse_width', UnitOfTime.MICROSECONDS, None, lambda data: re.findall('\d+us', data)[0][:-2],""), + SolarecoSensorConfig('total_energy', UnitOfEnergy.WATT_HOUR, SensorDeviceClass.ENERGY, lambda data: re.findall('\d+kWh\s\d+Wh', data)[0].replace(' ','').replace('kWh','').replace('Wh','') if re.search('\d+kWh\s\d+Wh', data) else None,"mdi:alpha-w-circle-outline"), + SolarecoSensorConfig('day_energy', UnitOfEnergy.WATT_HOUR, SensorDeviceClass.ENERGY, lambda data: re.findall('\d+Wh', data)[0][:-2],"mdi:alpha-w-circle-outline" ,SensorStateClass.TOTAL_INCREASING), +] + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None) -> True: + _LOGGER.info(str(config)) + sensor_connector = SensorConnector(hass, config['host'], config['port']) + # Do first update + await hass.async_add_executor_job(sensor_connector.update) + # Poll for updates in the background + async_track_time_interval( + hass, + lambda now: sensor_connector.update(), + timedelta(seconds=int(config['poll_interval_seconds'])), + ) + + entities: list[SensorEntity] = [] + entities.extend([SolarecoSensor(sensor_connector, sensor_config) for sensor_config in SENSORS]) + async_add_entities(entities, True) + + hass.data.setdefault(DOMAIN, {}) + + +class SolarecoSensor(SensorEntity): + def __init__(self, sensor_connector, sensor_config: SolarecoSensorConfig): + super().__init__() + self.sensor_connector = sensor_connector + self.sensor_config = sensor_config + + self._attr_has_entity_name = True + self._attr_translation_key = self.sensor_config.name + self._state = None + self._state_attributes = None + self._attr_icon = self.sensor_config.icon + + async def async_added_to_hass(self) -> None: + self.async_on_remove( + async_dispatcher_connect(self.hass, SIGNAL, self._async_update_callback) + ) + + @callback + def _async_update_callback(self): + self._async_update_data() + self.async_write_ha_state() + + @property + def unique_id(self): + return f"{'solareco'} {self.sensor_config.name}" + + # @property + # def name(self): + # return f"{'solareco'} {self.sensor_config.name}" + + @property + def native_value(self): + return self._state + + @property + def state_class(self): + return self.sensor_config.state_class + + @property + def device_class(self): + return self.sensor_config.device_class + + @property + def native_unit_of_measurement(self): + return self.sensor_config.unit_of_measurement + + @callback + def _async_update_data(self): + self._state = self.sensor_connector.data[self.sensor_config.name] + + +class SensorConnector: + def __init__(self, hass, solareco_host, solareco_port): + self.hass = hass + self.solareco_host = solareco_host + self.solareco_port = solareco_port + self.data = {SENSORS[i]: None for i in range(0, len(SENSORS))} + + def update(self): + try: + with telnetlib.Telnet(self.solareco_host, self.solareco_port) as tn: + line = tn.read_until(b'\n').decode('ascii') + for i in range(0, len(SENSORS)): + sensor = SENSORS[i] + self.data[sensor.name] = sensor.data_transformation(line) + dispatcher_send(self.hass, SIGNAL) + except: + _LOGGER.error("Can't connect to SolarEco") + diff --git a/strings.json b/strings.json new file mode 100644 index 0000000..4054dac --- /dev/null +++ b/strings.json @@ -0,0 +1,43 @@ +{ + "entity": { + "sensor": { + "relay": { + "name": "relay" + }, + "fan": { + "name": "fan" + }, + "required_voltage": { + "name": "required_voltage" + }, + "voltage": { + "name": "voltage" + }, + "current": { + "name": "current" + }, + "power": { + "name": "power" + }, + "frequency": { + "name": "frequency" + }, + "cooler_temperature": { + "name": "cooler_temperature" + }, + "boiler_temperature": { + "name": "boiler_temperature" + }, + "pulse_width": { + "name": "day_energy" + }, + "total_energy": { + "name": "total_energy" + }, + "day_energy": { + "name": "day_energy" + } + + } + } +} \ No newline at end of file diff --git a/translations/cs.json b/translations/cs.json new file mode 100644 index 0000000..0f94866 --- /dev/null +++ b/translations/cs.json @@ -0,0 +1,43 @@ +{ + "entity": { + "sensor": { + "relay": { + "name": "relé" + }, + "fan": { + "name": "větrák" + }, + "required_voltage": { + "name": "požadované_napětí" + }, + "voltage": { + "name": "napětí" + }, + "current": { + "name": "proud" + }, + "power": { + "name": "výkon" + }, + "frequency": { + "name": "frekvence" + }, + "cooler_temperature": { + "name": "teplota_chladiče" + }, + "boiler_temperature": { + "name": "teplota_kotle" + }, + "pulse_width": { + "name": "šířka_pulzu" + }, + "total_energy": { + "name": "celková_energie" + }, + "day_energy": { + "name": "denní_energie" + } + + } + } +} \ No newline at end of file diff --git a/translations/en.json b/translations/en.json new file mode 100644 index 0000000..84666ed --- /dev/null +++ b/translations/en.json @@ -0,0 +1,43 @@ +{ + "entity": { + "sensor": { + "relay": { + "name": "relay" + }, + "fan": { + "name": "fan" + }, + "required_voltage": { + "name": "required_voltage" + }, + "voltage": { + "name": "voltage" + }, + "current": { + "name": "current" + }, + "power": { + "name": "power" + }, + "frequency": { + "name": "frequency" + }, + "cooler_temperature": { + "name": "cooler_temperature" + }, + "boiler_temperature": { + "name": "boiler_temperature" + }, + "pulse_width": { + "name": "day_energy" + }, + "total_energy": { + "name": "total_energy" + }, + "day_energy": { + "name": "day_energy" + } + + } + } +} \ No newline at end of file