diff --git a/README.md b/README.md index dfd6df0..369193e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ Supports the new energy dashboard in Home-Assistant. If it does not decode your data, please submit a ticket, and I will try to make a parser for your meter. - If your meter type shown "unknown", please submit a ticket, and I will add your meter to the module. diff --git a/custom_components/ams/__init__.py b/custom_components/ams/__init__.py index 18f0c3d..ace6f8f 100755 --- a/custom_components/ams/__init__.py +++ b/custom_components/ams/__init__.py @@ -92,8 +92,9 @@ async def async_setup(hass: HomeAssistant, config: Config): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up AMS as config entry.""" _setup(hass, entry.data) - hass.async_add_job(hass.config_entries.async_forward_entry_setup(entry, - "sensor")) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup( + entry, "sensor")) return True @@ -103,7 +104,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return True -async def async_remove_entry(hass, entry): +async def async_remove_entry(hass, entry): # pylint: disable=unused-argument """Handle removal of an entry.""" await hass.async_add_executor_job(hass.data[DOMAIN].stop_serial_read) return True @@ -167,15 +168,17 @@ def meter_type(self): return self._attrs[HAN_METER_TYPE] - def connect(self): + def connect(self): # pylint: disable=too-many-branches """Read the data from the port.""" parser = None - + detect_pkg = None # This is needed to push the package used for + # detecting the meter straight to the parser. If not, users will get + # unknown state class None for energy sensors at startup. if self.meter_manufacturer == "auto": while parser is None: _LOGGER.info("Autodetecting meter manufacturer") - pkg = self.read_bytes() - self.meter_manufacturer = self._find_parser(pkg) + detect_pkg = self.read_bytes() + self.meter_manufacturer = self._find_parser(detect_pkg) parser = self.meter_manufacturer if self.meter_manufacturer == "aidon": @@ -191,7 +194,10 @@ def connect(self): while self._running: try: - data = self.read_bytes() + if detect_pkg: + data = detect_pkg + else: + data = self.read_bytes() if parser.test_valid_data(data): _LOGGER.debug("data read from port=%s", data) self.sensor_data, _ = parser.parse_data(self.sensor_data, @@ -199,18 +205,22 @@ def connect(self): self._check_for_new_sensors_and_update(self.sensor_data) else: _LOGGER.debug("failed package: %s", data) + if detect_pkg: + detect_pkg = None except serial.serialutil.SerialException: pass - def _find_parser(self, pkg): + @classmethod + def _find_parser(cls, pkg): """Helper to detect meter manufacturer.""" - def _test_meter(pkg, meter): + def _test_meter(test_pkg, meter): """Meter tester.""" match = [] _LOGGER.debug("Testing for %s", meter) - for i in range(len(pkg)): - if pkg[i] == meter[0] and pkg[i:(i + len(meter))] == meter: + for i, _ in enumerate(test_pkg): + if test_pkg[i] == meter[0] and ( + test_pkg[i:(i + len(meter))] == meter): match.append(meter) return meter in match @@ -234,7 +244,7 @@ def _test_meter(pkg, meter): return "kamstrup" _LOGGER.warning("No parser detected") - _LOGGER.debug("Package dump: %s", pkg) + _LOGGER.debug("Meter detection package dump: %s", pkg) @property def data(self): @@ -253,14 +263,13 @@ def missing_attrs(self, data=None): cp_sensors_data = deepcopy(data) for check in miss_attrs: for value in cp_sensors_data.values(): - v = value.get(SENSOR_ATTR, {}).get(check) - if v: - self._attrs[check] = v + val = value.get(SENSOR_ATTR, {}).get(check) + if val: + self._attrs[check] = val break del cp_sensors_data return len([i for i in attrs_to_check if i not in self._attrs]) - else: - return False + return False def _check_for_new_sensors_and_update(self, sensor_data): """Compare sensor list and update.""" @@ -268,7 +277,7 @@ def _check_for_new_sensors_and_update(self, sensor_data): sensors_in_data = set(sensor_data.keys()) new_devices = sensors_in_data.difference(AMS_DEVICES) - if len(new_devices): + if new_devices: # Check that we have all the info we need before the sensors are # created, the most important one is the meter_serial as this is # use to create the unique_id diff --git a/custom_components/ams/config_flow.py b/custom_components/ams/config_flow.py index 49c58d6..3027947 100755 --- a/custom_components/ams/config_flow.py +++ b/custom_components/ams/config_flow.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant import config_entries -from custom_components.ams.const import ( +from custom_components.ams.const import ( # pylint: disable=unused-import CONF_BAUDRATE, CONF_METER_MANUFACTURER, CONF_PARITY, @@ -79,7 +79,7 @@ async def async_step_import(self, import_config): """Import a config flow from configuration.""" if self._async_current_entries(): _LOGGER.warning("Only one configuration of AMS Reader is allowed.") - return self.async_abort(reason="singel_instance_allowed") + return self.async_abort(reason="single_instance_allowed") return self.async_create_entry(title="configuration.yaml", data=import_config) diff --git a/custom_components/ams/const.py b/custom_components/ams/const.py index e20590c..3e84ec1 100755 --- a/custom_components/ams/const.py +++ b/custom_components/ams/const.py @@ -118,7 +118,7 @@ 6841131: "Omnipower 3 Phase 4-Wire Direct meter", 6851121: "Omnipower 3 Phase CT 3-Wire Direct meter", 6851131: "Omnipower 3 Phase CT 4-Wire Direct meter", - 6851128: "Omnipower 3 Phase Direct meter", + 6841128: "Omnipower 3 Phase Direct meter", 6841138: "Omnipower 3 Phase Direct meter", } UNKNOWN_METER = "Unknown" @@ -130,6 +130,16 @@ HAN_REACTIVE_ENERGY_EXPORT, ] +ACTIVE_ENERGY_SENSORS = [ + HAN_ACTIVE_ENERGY_IMPORT, + HAN_ACTIVE_ENERGY_EXPORT, +] + +ACTIVE_ENERGY_DEFAULT_ATTRS = { + ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, + ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, +} + CURRENT_SENSORS = [ HAN_CURRENT_L1, HAN_CURRENT_L2, @@ -155,7 +165,14 @@ HAN_CURRENT_L3, ] + HOURLY_SENSORS -MANUFACTURER_OPTIONS = ["auto", "aidon", "aidon_se", "kaifa", "kamstrup"] +MANUFACTURER_OPTIONS = [ + "auto", + "aidon", + "aidon_se", + "kaifa", + "kaifa_se,", + "kamstrup", +] SIGNAL_UPDATE_AMS = "ams_update" SIGNAL_NEW_AMS_SENSOR = "ams_new_sensor" diff --git a/custom_components/ams/manifest.json b/custom_components/ams/manifest.json index a43c4b8..2ee6c90 100755 --- a/custom_components/ams/manifest.json +++ b/custom_components/ams/manifest.json @@ -7,6 +7,6 @@ "codeowners": ["@turbokongen"], "requirements": ["pyserial==3.5", "crccheck==1.0"], "config_flow": true, - "version": "1.7.1", + "version": "1.7.2", "iot_class": "local_push" } diff --git a/custom_components/ams/parsers/aidon.py b/custom_components/ams/parsers/aidon.py index 3cb61e2..87b386e 100755 --- a/custom_components/ams/parsers/aidon.py +++ b/custom_components/ams/parsers/aidon.py @@ -8,6 +8,7 @@ from crccheck.crc import CrcX25 from custom_components.ams.parsers import byte_decode, field_type from custom_components.ams.const import ( + ACTIVE_ENERGY_SENSORS, ATTR_DEVICE_CLASS, ATTR_STATE_CLASS, DATA_FLAG, @@ -258,12 +259,13 @@ def parse_data(stored, data): sensor_data[key][SENSOR_ATTR][ HAN_METER_DATETIME] = han_data[ HAN_METER_DATETIME] - sensor_data[key][SENSOR_ATTR][ - ATTR_STATE_CLASS] = ( - STATE_CLASS_TOTAL_INCREASING) sensor_data[key][SENSOR_ATTR][ ATTR_DEVICE_CLASS] = ( DEVICE_CLASS_ENERGY) + if key in ACTIVE_ENERGY_SENSORS: + sensor_data[key][SENSOR_ATTR][ + ATTR_STATE_CLASS] = ( + STATE_CLASS_TOTAL_INCREASING) _LOGGER.debug( "Value double OBIS type 6: %s, Index:%s", han_data[key], (v_start, v_stop) diff --git a/custom_components/ams/parsers/aidon_se.py b/custom_components/ams/parsers/aidon_se.py index dfed958..19efd13 100644 --- a/custom_components/ams/parsers/aidon_se.py +++ b/custom_components/ams/parsers/aidon_se.py @@ -9,6 +9,7 @@ from crccheck.crc import CrcX25 from custom_components.ams.parsers import byte_decode from custom_components.ams.const import ( + ACTIVE_ENERGY_SENSORS, ATTR_DEVICE_CLASS, ATTR_STATE_CLASS, DATA_FLAG, @@ -150,12 +151,13 @@ def parse_data(stored, data): sensor_data[key][SENSOR_ATTR][ HAN_METER_DATETIME] = han_data[ HAN_METER_DATETIME] - sensor_data[key][SENSOR_ATTR][ - ATTR_STATE_CLASS] = ( - STATE_CLASS_TOTAL_INCREASING) sensor_data[key][SENSOR_ATTR][ ATTR_DEVICE_CLASS] = ( DEVICE_CLASS_ENERGY) + if key in ACTIVE_ENERGY_SENSORS: + sensor_data[key][SENSOR_ATTR][ + ATTR_STATE_CLASS] = ( + STATE_CLASS_TOTAL_INCREASING) _LOGGER.debug( "%s, OBIS:%s, Index:%s, Type:%s Double OBIS", key, item, (i, i + len(item)), diff --git a/custom_components/ams/parsers/kaifa_se.py b/custom_components/ams/parsers/kaifa_se.py index 38b851d..4081283 100644 --- a/custom_components/ams/parsers/kaifa_se.py +++ b/custom_components/ams/parsers/kaifa_se.py @@ -9,6 +9,7 @@ from crccheck.crc import CrcX25 from custom_components.ams.parsers import byte_decode, field_type from custom_components.ams.const import ( + ACTIVE_ENERGY_SENSORS, ATTR_DEVICE_CLASS, ATTR_STATE_CLASS, CURRENT_SENSORS, @@ -156,12 +157,13 @@ def parse_data(stored, data): sensor_data[key][SENSOR_ATTR][ HAN_METER_DATETIME] = han_data[ HAN_METER_DATETIME] - sensor_data[key][SENSOR_ATTR][ - ATTR_STATE_CLASS] = ( - STATE_CLASS_TOTAL_INCREASING) sensor_data[key][SENSOR_ATTR][ ATTR_DEVICE_CLASS] = ( DEVICE_CLASS_ENERGY) + if key in ACTIVE_ENERGY_SENSORS: + sensor_data[key][SENSOR_ATTR][ + ATTR_STATE_CLASS] = ( + STATE_CLASS_TOTAL_INCREASING) _LOGGER.debug( "%s, OBIS:%s, Index:%s, Type:%s Double OBIS", key, item, (i, i + len(item)), diff --git a/custom_components/ams/parsers/kamstrup.py b/custom_components/ams/parsers/kamstrup.py index 371e910..95794a2 100755 --- a/custom_components/ams/parsers/kamstrup.py +++ b/custom_components/ams/parsers/kamstrup.py @@ -7,6 +7,7 @@ from datetime import datetime from crccheck.crc import CrcX25 from custom_components.ams.const import ( + ACTIVE_ENERGY_SENSORS, ATTR_DEVICE_CLASS, ATTR_STATE_CLASS, DATA_FLAG, @@ -228,12 +229,13 @@ def parse_data(stored, data): sensor_data[key][SENSOR_ATTR][ HAN_METER_DATETIME] = han_data[ HAN_METER_DATETIME] - sensor_data[key][SENSOR_ATTR][ - ATTR_STATE_CLASS] = ( - STATE_CLASS_TOTAL_INCREASING) sensor_data[key][SENSOR_ATTR][ ATTR_DEVICE_CLASS] = ( DEVICE_CLASS_ENERGY) + if key in ACTIVE_ENERGY_SENSORS: + sensor_data[key][SENSOR_ATTR][ + ATTR_STATE_CLASS] = ( + STATE_CLASS_TOTAL_INCREASING) _LOGGER.debug( "%s, OBIS:%s, Index:%s, Type:%s Double OBIS", key, item, (i, i + len(item)), diff --git a/custom_components/ams/sensor.py b/custom_components/ams/sensor.py index 2033df8..e264bdc 100755 --- a/custom_components/ams/sensor.py +++ b/custom_components/ams/sensor.py @@ -9,6 +9,8 @@ from homeassistant.util import dt as dt_utils from custom_components.ams.const import ( + ACTIVE_ENERGY_DEFAULT_ATTRS, + ACTIVE_ENERGY_SENSORS, AMS_DEVICES, AMS_SENSOR_CREATED_BUT_NOT_READ, DOMAIN, @@ -21,6 +23,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices): + # pylint: disable=unused-argument """Setup sensor platform for the ui""" @callback @@ -59,10 +62,18 @@ def async_add_sensor(): "state": data.get(hourly, {}).get("state"), "attributes": data.get(hourly, {}).get("attributes"), } + if hourly in ACTIVE_ENERGY_SENSORS: + sensor_states = { + "name": hourly, + "state": data.get(hourly, {}).get("state"), + "attributes": data.get(hourly, {}).get("attributes", ( + ACTIVE_ENERGY_DEFAULT_ATTRS)), + } sensors.append(AmsSensor(hass, sensor_states)) - if len(sensors): - _LOGGER.debug("Trying to add %s sensors", len(sensors)) + if sensors: + _LOGGER.debug("Trying to add %s sensors: %s", len(sensors), + sensors) async_add_devices(sensors) async_dispatcher_connect(hass, SIGNAL_NEW_AMS_SENSOR, async_add_sensor) diff --git a/custom_components/ams/translations/se.json b/custom_components/ams/translations/se.json new file mode 100644 index 0000000..710a9c2 --- /dev/null +++ b/custom_components/ams/translations/se.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Endast en inträde av AMS läsare är tillåten" + }, + "title": "AMS Reader", + "step": { + "user": { + "title": "AMS läsare", + "description": "Installera sensorer för AMS läsare", + "data": { + "serial_port": "Serieport", + "parity": "Paritet", + "meter_manufacturer": "Mätar tilverkare", + "baudrate": "Baudrate" + } + } + }, + "error": { + "name_exists": "Namn finns" + } + } +} \ No newline at end of file