Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions custom_components/NBEConnect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ async def _async_update_data(self):
"""Fetch the latest data from the device."""
try:
operating_data = await self.hass.async_add_executor_job(self.proxy.get, "operating_data/")
consumption_data = await self.hass.async_add_executor_job(self.proxy.get, "consumption_data/counter")
logger.debug(operating_data)
logger.debug(consumption_data)
consumption_data = await self.hass.async_add_executor_job(self.proxy.get, "consumption_data/counter")
if operating_data is not None:
if consumption_data is not None:
operating_data = operating_data + consumption_data
Expand Down
66 changes: 42 additions & 24 deletions custom_components/NBEConnect/button.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,66 @@
from homeassistant.core import callback
from homeassistant.helpers.entity import Entity
from homeassistant.components.button import ButtonEntity

from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .protocol import Proxy
from logging import getLogger

_LOGGER = getLogger(__name__)

async def async_setup_entry(hass, config_entry, async_add_entities):
coordinator = hass.data[DOMAIN][config_entry.entry_id+'_coordinator']
proxy = hass.data[DOMAIN][config_entry.entry_id]
async def async_setup_entry(hass, entry, async_add_entities):
dc = hass.data[DOMAIN][entry.entry_id+'_coordinator']

serial = "Unknown"
if hasattr(dc.proxy, 'serial') and dc.proxy.serial:
serial = dc.proxy.serial
elif entry.data.get("serial"):
serial = entry.data.get("serial")

device_info = {
"identifiers": {(DOMAIN, serial)},
"name": "NBE Boiler",
"manufacturer": "NBE",
"model": "V7/V13 Controller"
}

async_add_entities([
RTBSignalButton(coordinator, proxy, "Start Boiler", "settings/misc/start", "nbestart", "1"),
RTBSignalButton(coordinator, proxy, "Stop Boiler", "settings/misc/stop", "nbestop", "1"),
RTBSignalButton(coordinator, proxy, "Reset Boiler Alarm", "settings/misc/reset_alarm", "nbereset", "1")
RTBSignalButton(
coordinator=dc,
name="Reset Boiler Alarm",
path="settings/misc/reset_alarm",
uid="nbereset",
value="1",
icon="mdi:alert-remove-outline",
dev_info=device_info
)
])

class RTBSignalButton(CoordinatorEntity, ButtonEntity):
"""Representation of a signal switch."""
"""Representation of a signal button."""

def __init__(self, coordinator, proxy, name, path, uid, value):
"""Initialize the switch."""
def __init__(self, coordinator, name, path, uid, value, icon, dev_info):
super().__init__(coordinator)
self._name = name
self._state = False
self.proxy = proxy
self._path = path
self._value = value
self.uid = uid
self._attr_icon = icon
self._dev_info = dev_info
self._attr_unique_id = f"{coordinator.entry_id}_{uid}"

@property
def name(self):
"""Return the name of the switch."""
return self._name

@property
def unique_id(self):
return self.uid
return f"NBE {self._name}"

def press(self) -> None:
"""Press the button."""
_LOGGER.debug(f"asynch press {self._name} - awaiting sendboilercmd..")
self.proxy.set(self._path, self._value)
_LOGGER.debug(f"asynch press {self._name} - Done!")
_LOGGER.debug(f"Async press {self._name}")
proxy = self.coordinator.proxy
if proxy:
try:
proxy.set(self._path, self._value)
except Exception as e:
_LOGGER.warning(f"Button press error (might be okay): {e}")
_LOGGER.debug(f"Async press {self._name} - Done!")

@property
def device_info(self):
return self._dev_info
2 changes: 1 addition & 1 deletion custom_components/NBEConnect/const.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
DOMAIN = "nbeconnect"
PLATFORMS = ['sensor', 'button']
PLATFORMS = ['sensor', 'button', "number", "switch"]
2 changes: 1 addition & 1 deletion custom_components/NBEConnect/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"issue_tracker": "n/a",
"iot_class": "local_polling",
"integration_type": "device",
"version": "0.1.4"
"version": "1.0.0"
}
112 changes: 112 additions & 0 deletions custom_components/NBEConnect/number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from homeassistant.components.number import (
NumberEntity,
NumberDeviceClass,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from logging import getLogger

_LOGGER = getLogger(__name__)

async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the NBE-RTB numbers."""
dc = hass.data[DOMAIN][entry.entry_id+'_coordinator']

serial = "Unknown"
if hasattr(dc.proxy, 'serial') and dc.proxy.serial:
serial = dc.proxy.serial
elif entry.data.get("serial"):
serial = entry.data.get("serial")

device_info = {
"identifiers": {(DOMAIN, serial)},
"name": "NBE Boiler",
"manufacturer": "NBE",
"model": "V7/V13 Controller"
}

async_add_entities([
RTBNumber(
coordinator=dc,
name='Boiler Target Temp',
read_key='operating_data/boiler_ref',
write_key='settings/boiler/temp',
unit="°C",
min_val=10,
max_val=85,
read_multiplier=1,
write_multiplier=1,
icon="mdi:thermometer-check",
dev_info=device_info
),

RTBNumber(
coordinator=dc,
name='Hopper Content',
read_key='operating_data/content',
write_key='settings/hopper/content',
unit="kg",
min_val=0,
max_val=9000,
read_multiplier=10,
write_multiplier=1,
icon="mdi:silo",
dev_info=device_info
),
])

class RTBNumber(CoordinatorEntity, NumberEntity):

def __init__(self, coordinator, name, read_key, write_key, unit, min_val, max_val, read_multiplier=1, write_multiplier=1, icon=None, dev_info=None):
super().__init__(coordinator)
self.read_key = read_key
self.write_key = write_key
self.sensorname = name
self._attr_native_unit_of_measurement = unit
self._attr_native_min_value = min_val
self._attr_native_max_value = max_val
self._attr_device_class = NumberDeviceClass.TEMPERATURE if "Temp" in name else NumberDeviceClass.WEIGHT
self._attr_unique_id = f"{coordinator.entry_id}_{write_key}"

self.read_multiplier = read_multiplier
self.write_multiplier = write_multiplier
self._attr_icon = icon
self._dev_info = dev_info

@property
def name(self):
return f"NBE {self.sensorname}"

@property
def native_value(self):
val = self.coordinator.rtbdata.get(self.read_key)
try:
if val:
return float(val) * self.read_multiplier
return None
except ValueError:
return None

@property
def device_info(self):
return self._dev_info

async def async_set_native_value(self, value: float) -> None:
_LOGGER.info(f"Setting NBE value for {self.write_key} to {value}")

proxy = self.coordinator.proxy

if proxy:
try:
val_to_send = int(value * self.write_multiplier)

await self.coordinator.hass.async_add_executor_job(proxy.set, self.write_key, str(val_to_send))

except (OSError, IOError) as e:
_LOGGER.warning(f"NBEConnect: Command sent via {self.write_key}, but response decode failed (expected). Error: {e}")
except Exception as e:
_LOGGER.error(f"NBEConnect: Unexpected error: {e}")

await self.coordinator.async_request_refresh()
else:
_LOGGER.error("NBEConnect: Cannot find 'proxy' in coordinator to send command!")
104 changes: 66 additions & 38 deletions custom_components/NBEConnect/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,105 +22,133 @@

async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the NBE-RTB sensors."""
_LOGGER.info("This is debugging from sensor.py")
_LOGGER.info("Setting up NBE sensors with Renamed Temperatures...")


#DataUpdateCoordinator
dc = hass.data[DOMAIN][entry.entry_id+'_coordinator']

async_add_entities([
RTBBinarySensor(dc, 'Boiler Running', 'operating_data/power_pct', 'boiler_power_pct', BinarySensorDeviceClass.HEAT),
RTBBinarySensor(dc, 'Boiler Alarm', 'operating_data/off_on_alarm', 'boiler_state_off_on_alarm', BinarySensorDeviceClass.PROBLEM),
RTBSensor(dc, 'Boiler Temperature', 'operating_data/boiler_temp', 'boiler_temp', "\u00b0C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT),
RTBSensor(dc, 'DWH Temperature', 'operating_data/sun_dhw_temp', 'dhw_temp', "\u00b0C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT),
RTBSensor(dc, 'External Temperature', 'operating_data/external_temp', 'external_temp', "\u00b0C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT),
RTBSensor(dc, 'Boiler Effect', 'operating_data/power_kw', 'power_kw', "kW", SensorDeviceClass.POWER, SensorStateClass.MEASUREMENT),
RTBSensor(dc, 'Boiler Power', 'operating_data/power_pct', 'power_pct', "%", SensorDeviceClass.POWER_FACTOR, SensorStateClass.MEASUREMENT),
RTBSensor(dc, 'Total Consumption', 'consumption_data/counter', 'pelletcounter', "kg", SensorDeviceClass.WEIGHT, SensorStateClass.TOTAL_INCREASING),
])
_LOGGER.info(f"Sensor.py, sensors where added!")
serial = "Unknown"
if hasattr(dc.proxy, 'serial') and dc.proxy.serial:
serial = dc.proxy.serial
elif entry.data.get("serial"):
serial = entry.data.get("serial")

device_info = {
"identifiers": {(DOMAIN, serial)},
"name": "NBE Boiler",
"manufacturer": "NBE",
"model": "V7/V13 Controller",
"configuration_url": f"http://{entry.data.get('ip_address')}" if entry.data.get('ip_address') else None
}

sensors = [
RTBBinarySensor(dc, 'Boiler Running', 'operating_data/power_pct', 'boiler_power_pct', BinarySensorDeviceClass.RUNNING, icon="mdi:fire", dev_info=device_info),
RTBBinarySensor(dc, 'Boiler Alarm', 'operating_data/off_on_alarm', 'boiler_state_off_on_alarm', BinarySensorDeviceClass.PROBLEM, icon="mdi:alert-circle", dev_info=device_info),
RTBBinarySensor(dc, 'Boiler Pump', 'operating_data/boiler_pump_state', 'house_pump_state', None, icon="mdi:pump", dev_info=device_info),
RTBSensor(dc, 'Temperature Boiler', 'operating_data/boiler_temp', 'boiler_temp', "°C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT, icon="mdi:thermometer", dev_info=device_info),
RTBSensor(dc, 'Temperature Target', 'operating_data/boiler_ref', 'boiler_ref', "°C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT, icon="mdi:thermometer-check", dev_info=device_info),
RTBSensor(dc, 'Temperature Smoke', 'operating_data/smoke_temp', 'smoke_temp', "°C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT, icon="mdi:thermometer-alert", dev_info=device_info),
RTBSensor(dc, 'Temperature Return', 'operating_data/return_temp', 'return_temp', "°C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT, icon="mdi:thermometer-water", dev_info=device_info),
RTBSensor(dc, 'Temperature Shaft', 'operating_data/shaft_temp', 'shaft_temp', "°C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT, icon="mdi:thermometer-lines", dev_info=device_info),
RTBSensor(dc, 'Temperature External', 'operating_data/external_temp', 'external_temp', "°C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT, icon="mdi:thermometer", dev_info=device_info),
RTBSensor(dc, 'Temperature DHW', 'operating_data/dhw_temp', 'dhw_temp', "°C", SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT, icon="mdi:water-thermometer", dev_info=device_info),
RTBSensor(dc, 'Boiler Power %', 'operating_data/power_pct', 'power_pct', "%", SensorDeviceClass.POWER_FACTOR, SensorStateClass.MEASUREMENT, icon="mdi:lightning-bolt-outline", dev_info=device_info),
RTBSensor(dc, 'Boiler Power kW', 'operating_data/power_kw', 'power_kw', "kW", SensorDeviceClass.POWER, SensorStateClass.MEASUREMENT, icon="mdi:lightning-bolt", dev_info=device_info),
RTBSensor(dc, 'Photo Sensor', 'operating_data/photo_level', 'photo_level', "lx", SensorDeviceClass.ILLUMINANCE, SensorStateClass.MEASUREMENT, icon="mdi:eye", dev_info=device_info),
RTBSensor(dc, 'Oxygen (O2)', 'operating_data/oxygen', 'oxygen_level', "%", None, SensorStateClass.MEASUREMENT, icon="mdi:gas-cylinder", dev_info=device_info),
RTBSensor(dc, 'Total Pellet Consumption', 'consumption_data/counter', 'pelletcounter', "kg", SensorDeviceClass.WEIGHT, SensorStateClass.TOTAL_INCREASING, icon="mdi:chart-line", dev_info=device_info),
RTBSensor(dc, 'Hopper Content', 'operating_data/content', 'hopper_content', "kg", SensorDeviceClass.WEIGHT, SensorStateClass.MEASUREMENT, multiplier=10, icon="mdi:silo", dev_info=device_info),
RTBSensor(dc, 'State Code', 'operating_data/state', 'boiler_state_code', None, None, SensorStateClass.MEASUREMENT, icon="mdi:information-outline", dev_info=device_info),
]

async_add_entities(sensors)
_LOGGER.info(f"NBE Sensors updated successfully!")


class RTBSensor(CoordinatorEntity, SensorEntity):
"""Representation of an RTB sensor."""

def __init__(self, coordinator, name, client_key, uid, unitofmeassurement, device_class, state_class):
"""Initialize the sensor."""
def __init__(self, coordinator, name, client_key, uid, unitofmeassurement, device_class, state_class, multiplier=None, icon=None, dev_info=None):
super().__init__(coordinator)
self.client_key = client_key
self._device_class = device_class
self.sensorname = name
self.uid = uid
self._unit_of_measurement = unitofmeassurement
self._state_class = state_class
self.multiplier = multiplier
self._attr_icon = icon
self._dev_info = dev_info

@property
def name(self):
"""Return the name of the sensor."""
#_LOGGER.info(f"sensor.py (name) returning \"NBE {self.sensorname}\"")
return f"NBE {self.sensorname}"

@property
def unit_of_measurement(self):
"""Return the unit of measurement of the sensor."""
def native_unit_of_measurement(self):
return self._unit_of_measurement

@property
def unique_id(self):
return self.uid

@property
def state(self):
"""Return the state of the sensor."""
def native_value(self):
state = self.coordinator.rtbdata.get(self.client_key)
#_LOGGER.info(f"Sensor.py RTBSensor (state) returning \"{state}\"")

if not state or "999.9" in str(state):
return None

if self.multiplier:
try:
val_float = float(state)
return val_float * self.multiplier
except ValueError:
return state

return state

@property
def device_class(self):
"""Return the device class of the sensor."""
return self._device_class

@property
def state_class(self):
"""Return the state class of the sensor."""
return self._state_class

@property
def device_info(self):
return self._dev_info

class RTBBinarySensor(CoordinatorEntity, BinarySensorEntity):
"""Representation of an RTB binary sensor."""

def __init__(self, coordinator, name, client_key, uid, device_class):
"""Initialize the binary sensor."""
def __init__(self, coordinator, name, client_key, uid, device_class, icon=None, dev_info=None):
super().__init__(coordinator)
self.client_key = client_key
self._device_class = device_class
self.sensorname = name
self.uid = uid
self._attr_icon = icon
self._dev_info = dev_info

@property
def name(self):
"""Return the name of the binary sensor."""
#_LOGGER.info(f"sensor.py RTBBinarySensor (name) returning \"NBE {self.sensorname}\"")
return f"NBE {self.sensorname}"

@property
def is_on(self):
_LOGGER.debug("is_on called")
"""Return the state of the binary sensor."""
s = self.coordinator.rtbdata.get(self.client_key)
_LOGGER.debug(f"sensor.py RTBBinarySensor (is_on) value \"{s}\"")
if "power_pct" in self.client_key:
return s != "0"
if "off_on_alarm" in self.client_key:
return s == "2"
return s == "0"
if not s: return None
return s != "0"

@property
def unique_id(self):
return self.uid

@property
def device_class(self):
"""Return the device class of the binary sensor."""
return self._device_class

@property
def device_info(self):
return self._dev_info
Loading