Skip to content

Commit

Permalink
Merge pull request #4 from Tom-Bom-badil/prepare-2024.12.6
Browse files Browse the repository at this point in the history
Prepare 2024.12.6
  • Loading branch information
Tom-Bom-badil authored Dec 23, 2024
2 parents 4153d4e + 258369c commit d6191ce
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 206 deletions.
119 changes: 52 additions & 67 deletions custom_components/helios_vallox_ventilation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,21 @@
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT
from .schema import CONFIG_SCHEMA, SERVICE_WRITE_VALUE_SCHEMA
from .const import DOMAIN


# preparations
_LOGGER = logging.getLogger(__name__)
DOMAIN = "helios_vallox_ventilation"


# shared class - fetch and cache data from the python script
class HeliosData:

def __init__(self, config):
def __init__(self, ip_address, port):
self.data = None
self.ip_address = config.get("ip_address", "192.168.178.38")
self.port = config.get("port", 8234)
self.ip_address = ip_address
self.port = port
_LOGGER.debug(f"Initialized HeliosData with IP: {self.ip_address} and Port: {self.port}")

# fetch and cache
# fetch and cache all
def update(self):
try:
result = subprocess.run(
Expand All @@ -35,7 +33,7 @@ def update(self):
],
capture_output=True,
text=True,
check=True
check=True,
)
self.data = json.loads(result.stdout)
except subprocess.CalledProcessError as e:
Expand All @@ -45,7 +43,7 @@ def update(self):
_LOGGER.error(f"Unexpected error reading Helios data: {e}")
self.data = None

# return value for a specific variable
# fetch and cache one specific
def get_value(self, variable):
if self.data:
return self.data.get(variable, None)
Expand Down Expand Up @@ -176,67 +174,54 @@ async def handle_write_service(call: ServiceCall):


# set up the integration
async def async_setup(hass, config):

conf = config[DOMAIN]
ip_address = conf[CONF_IP_ADDRESS]
port = conf[CONF_PORT]

hass.data[DOMAIN] = {"ip_address": ip_address, "port": port}
async def async_setup(hass, config):
# check for config
ventilation_config = config.get(DOMAIN, {})
if not ventilation_config:
_LOGGER.error("No configuration found for Helios Pro / Vallox SE.")
return False

_LOGGER.debug("Starting setup of Helios Pro / Vallox SE Integration")
# read IP and port from config
ip_address = ventilation_config.get("ip_address", "192.168.178.38")
port = ventilation_config.get("port", 8234)
_LOGGER.debug(f"Loaded configuration: IP={ip_address}, Port={port}")

# create HeliosData instance
global HELIOS_DATA
HELIOS_DATA = HeliosData(ip_address, port)

# load platforms for entities
hass.async_create_task(
async_load_platform(hass, "sensor", "helios_vallox_ventilation", {"sensors": ventilation_config.get("sensors", [])}, config)
)
hass.async_create_task(
async_load_platform(hass, "binary_sensor", "helios_vallox_ventilation", {"binary_sensors": ventilation_config.get("binary_sensors", [])}, config)
)
hass.async_create_task(
async_load_platform(hass, "switch", "helios_vallox_ventilation", {"switches": ventilation_config.get("switches", [])}, config)
)

# refresh in intervals
async def update_data(_):
_LOGGER.debug("Updating Helios data")
HELIOS_DATA.update()

# define the interval
async_track_time_interval(hass, update_data, timedelta(minutes=1))

# register service
try:
# ventilation_config = config.get("helios_vallox_ventilation")
ventilation_config = config.get("helios_vallox_ventilation", {})
if not ventilation_config:
_LOGGER.error("No configuration found for Helios Pro / Vallox SE.")
return False

global HELIOS_DATA
HELIOS_DATA = HeliosData(ventilation_config)

# IP adress and port for writing
ip_address = ventilation_config.get("ip_address", "192.168.178.38")
port = ventilation_config.get("port", 8234)

_LOGGER.debug(f"Loaded configuration: {ventilation_config}")

# load platforms for entities
hass.async_create_task(
async_load_platform(hass, "sensor", "helios_vallox_ventilation", {"sensors": ventilation_config.get("sensors", [])}, config)
)
hass.async_create_task(
async_load_platform(hass, "binary_sensor", "helios_vallox_ventilation", {"binary_sensors": ventilation_config.get("binary_sensors", [])}, config)
hass.services.async_register(
DOMAIN,
"write_value",
create_write_service_handler(hass),
schema=SERVICE_WRITE_VALUE_SCHEMA,
)
hass.async_create_task(
async_load_platform(hass, "switch", "helios_vallox_ventilation", {"switches": ventilation_config.get("switches", [])}, config)
)

# refresh entities in regular intervals
async def update_data(_):
_LOGGER.debug("Updating Helios data")
HELIOS_DATA.update()

# set the interval
async_track_time_interval(hass, update_data, timedelta(minutes=1))

# register write service
try:
hass.services.async_register(
"helios_vallox_ventilation",
"write_value",
create_write_service_handler(hass),
schema=SERVICE_WRITE_VALUE_SCHEMA
)
_LOGGER.debug("Service write_value registered successfully")

except Exception as e:
_LOGGER.error(f"Failed to register service write_value: {e}", exc_info=True)
return False

_LOGGER.debug("Helios Pro / Vallox SE Integration setup completed successfully")
return True

_LOGGER.debug("Service write_value registered successfully")
except Exception as e:
_LOGGER.error(f"Error during setup: {e}", exc_info=True)
_LOGGER.error(f"Failed to register service write_value: {e}", exc_info=True)
return False

_LOGGER.debug("Helios Pro / Vallox SE Integration setup completed successfully")
return True
125 changes: 125 additions & 0 deletions custom_components/helios_vallox_ventilation/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import array

# domain
DOMAIN = "helios_vallox_ventilation"

# default network address and port
DEFAULT_IP = "192.168.178.36"
DEFAULT_PORT = 502

# mapping for the four NTC5k temperature sensors
NTC5K_TEMPERATURES = array.array(
"i",
[
-74,-70,-66,-62,-59,-56,-54,-52,-50,-48,-47,-46,-44,-43,-42,-41,-40,-39,-38,-37,-36,
-35,-34,-33,-33,-32,-31,-30,-30,-29,-28,-28,-27,-27,-26,-25,-25,-24,-24,-23,-23,-22,
-22,-21,-21,-20,-20,-19,-19,-19,-18,-18,-17,-17,-16,-16,-16,-15,-15,-14,-14,-14,-13,
-13,-12,-12,-12,-11,-11,-11,-10,-10,-9,-9,-9,-8,-8,-8,-7,-7,-7,-6,-6,-6,-5,-5,-5,-4,
-4,-4,-3,-3,-3,-2,-2,-2,-1,-1,-1,-1,0,0,0,1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,5,6,6,6,7,7,
7,8,8,8,9,9,9,10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15,15,16,16,16,17,17,
18,18,18,19,19,19,20,20,21,21,21,22,22,22,23,23,24,24,24,25,25,26,26,27,27,27,28,28,
29,29,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,40,40,41,41,42,43,43,
44,45,45,46,47,48,48,49,50,51,52,53,53,54,55,56,57,59,60,61,62,63,65,66,68,69,71,73,
75,77,79,81,82,86,90,93,97,100,100,100,100,100,100,100,100,100
]
)

# mapping for valid senders / receivers
BUS_ADDRESSES = {
"MB*": 0x10, # all mainboards
"MB1": 0x11, # mainboard 1
"FB*": 0x20, # alle remote controls
"FB1": 0x21, # remote control 1
"LON": 0x28, # LON bus module (if any)
"_HA": 0x2E, # this HA Python script; we are simulating a remote
"_SH": 0x2F # SmartHomeNG Python script; also simulating a remote
}

# mapping bits to fan speeds
FANSPEEDS = {
1: 1,
3: 2,
7: 3,
15: 4,
31: 5,
63: 6,
127: 7,
255: 8
}

# mapping error messages / faults
COMPONENT_FAULTS = {
5: 'Inlet air sensor fault',
6: 'CO2 Alarm',
7: 'Outside air sensor fault',
8: 'Exhaust air sensor fault',
9: 'Water coil frost warning',
10: 'Outlet air sensor fault'
}

# mapping for registers and coils
REGISTERS_AND_COILS = {
# Current fanspeed (EC300Pro: 1..8)
"fanspeed" : {"varid" : 0x29, 'type': 'fanspeed', 'bitposition': -1, 'read': True, 'write': True },
# Fanspeed after switching on
"initial_fanspeed" : {"varid" : 0xA9, 'type': 'fanspeed', 'bitposition': -1, 'read': True, 'write': True },
# Maximum settable fanspeed
"max_fanspeed" : {"varid" : 0xA5, 'type': 'fanspeed', 'bitposition': -1, 'read': True, 'write': True },
# NTC5K sensors: outside air temperature
"outside_temp" : {"varid" : 0x32, 'type': 'temperature', 'bitposition': -1, 'read': True, 'write': False },
# NTC5K sensors: supply air temperature
"inlet_temp" : {"varid" : 0x35, 'type': 'temperature', 'bitposition': -1, 'read': True, 'write': False },
# NTC5K sensors: extract / inside air temperature
"outlet_temp" : {"varid" : 0x34, 'type': 'temperature', 'bitposition': -1, 'read': True, 'write': False },
# NTC5K sensors: exhaust air temperature
"exhaust_temp" : {"varid" : 0x33, 'type': 'temperature', 'bitposition': -1, 'read': True, 'write': False },
# various coils in register 0xA3 that are displayed on the remote controls (0..3 read/write, 4..7 readonly)
# FB LED1: on/off Caution: Remotes will not be switched back on automatically; initial_fanspeed set if done manually.
"powerstate" : {"varid" : 0xA3, 'type': 'bit', 'bitposition': 0, 'read': True, 'write': True },
# FB LED2: CO2 warning
"co2_indicator" : {"varid" : 0xA3, 'type': 'bit', 'bitposition': 1, 'read': True, 'write': False },
# FB LED3: Humidity warning
"rh_indicator" : {"varid" : 0xA3, 'type': 'bit', 'bitposition': 2, 'read': True, 'write': False },
# FB LED4: 0 = summer mode with bypass, 1 = wintermode with heat regeneration (LED is on in winter mode)
"summer_winter_mode": {"varid" : 0xA3, 'type': 'bit', 'bitposition': 3, 'read': True, 'write': False },
# FB icon 1: "Clean filter" warning
"clean_filter" : {"varid" : 0xA3, 'type': 'bit', 'bitposition': 4, 'read': True, 'write': False },
# FB icon 2 2: Pre-/Post heating active
"post_heating_on" : {"varid" : 0xA3, 'type': 'bit', 'bitposition': 5, 'read': True, 'write': False },
# FB icon 3: Error / fault
"fault_detected" : {"varid" : 0xA3, 'type': 'bit', 'bitposition': 6, 'read': True, 'write': False },
# FB icon 4: Service request
"service_requested" : {"varid" : 0xA3, 'type': 'bit', 'bitposition': 7, 'read': True, 'write': False },
# Summer mode: Activate bypass from this temperature onwards if fresh air °C (outside) < extract air °C (inside)
"bypass_setpoint" : {"varid" : 0xAF, 'type': 'temperature', 'bitposition': -1, 'read': True, 'write': True },
# Activation temperature for pre / post heating
"preheat_setpoint" : {"varid" : 0xA7, 'type': 'temperature', 'bitposition': -1, 'read': True, 'write': True },
# Pre / post heating is off (0) / on (1)
"preheat_status" : {"varid" : 0x70, 'type': 'bit', 'bitposition': 7, 'read': True, 'write': True },
# Frost protection - switch off fresh air ventilator and heating below this temperature; -6 ... +15°C
"defrost_setpoint" : {"varid" : 0xA8, 'type': 'temperature', 'bitposition': -1, 'read': True, 'write': True },
# Frost protection hysteresis - when to switch it on again (defrost_setpoint + (this_value/3)) --> 0x03 = 1°C
"defrost_hysteresis": {"varid" : 0xB2, 'type': 'dec_special', 'bitposition': -1, 'read': True, 'write': True },
# Boost mode: 0=fireplace (ignition - no exhaust air in the first 15 minutes of boost); 1=normal boost mode
"boost_mode" : {"varid" : 0xAA, 'type': 'bit', 'bitposition': 5, 'read': True, 'write': True },
# Switch boost on for 45 minutes (set to 1; will be reset automatically)
"boost_on_switch" : {"varid" : 0x71, 'type': 'bit', 'bitposition': 5, 'read': True, 'write': True },
# Current boost status (off/on)
"boost_status" : {"varid" : 0x71, 'type': 'bit', 'bitposition': 6, 'read': True, 'write': False },
# Remaining minutes of boost if on
"boost_remaining" : {"varid" : 0x79, 'type': 'dec', 'bitposition': -1, 'read': True, 'write': False },
# Fresh air vetilator off; set to 1 to switch off; requires to be set twice
"input_fan_off" : {"varid" : 0x08, 'type': 'bit', 'bitposition': 3, 'read': True, 'write': True },
# Exhaust air vetilator off; set to 1 to switch off; requires to be set twice
"output_fan_off" : {"varid" : 0x08, 'type': 'bit', 'bitposition': 5, 'read': True, 'write': True },
# rpm of fresh air ventilator (65...100% - pneumatic calibration; default=100)
"input_fan_percent" : {"varid" : 0xB0, 'type': 'dec', 'bitposition': -1, 'read': True, 'write': True },
# rpm of exhaust air ventilator (65...100% - pneumatic calibration; default=100)
"output_fan_percent": {"varid" : 0xB1, 'type': 'dec', 'bitposition': -1, 'read': True, 'write': True },
# Service reminder interval in months (used after reset f service reminder)
"service_interval" : {"varid" : 0xA6, 'type': 'dec', 'bitposition': -1, 'read': True, 'write': True },
# Remaining months for current service reminder
"service_due_months": {"varid" : 0xAB, 'type': 'dec', 'bitposition': -1, 'read': True, 'write': True },
# Error / fault register. 0 = no fault. see COMPONENT_FAULTS below
"fault_number" : {"varid" : 0x36, 'type': 'dec', 'bitposition': -1, 'read': True, 'write': False }
}
4 changes: 2 additions & 2 deletions custom_components/helios_vallox_ventilation/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"name": "Helios Pro / Vallox SE Ventilation",
"codeowners": ["@Tom-Bom-badil"],
"dependencies": [],
"documentation": "https://github.com/Tom-Bom-badil/helios/wiki",
"documentation": "https://github.com/Tom-Bom-badil/home-assistant_helios-vallox/wiki",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/Tom-Bom-badil/home-assistant_helios-vallox/issues",
"requirements": [],
"version": "2024.12.5"
"version": "2024.12.6"
}
3 changes: 1 addition & 2 deletions custom_components/helios_vallox_ventilation/schema.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import voluptuous as vol
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT
from homeassistant.helpers import config_validation as cv

DOMAIN = "helios_vallox_ventilation"
from .const import DOMAIN

# Configuration schema
CONFIG_SCHEMA = vol.Schema(
Expand Down
Loading

0 comments on commit d6191ce

Please sign in to comment.