Skip to content

Commit

Permalink
Added Mode selection and bug fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mimikyu committed Nov 23, 2024
1 parent 63618df commit 4a8d2a4
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 51 deletions.
62 changes: 51 additions & 11 deletions custom_components/madelon_ventilation/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .const import DOMAIN
from .fresh_air_controller import FreshAirSystem
import logging
from typing import Any

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback):
"""Set up the Fresh Air System fan."""
Expand All @@ -28,17 +29,26 @@ def __init__(self, entry: ConfigEntry, system: FreshAirSystem):
self._attr_unique_id = f"{DOMAIN}_fan_{system.unique_identifier}"
self._attr_percentage = 0

async def async_update(self):
async def async_update(self, now=None):
"""Fetch new state data for the fan."""
self._system._read_all_registers()
self._attr_is_on = self._system.power
self._attr_percentage = self._get_percentage(self._system.supply_speed)
try:
self._system._read_all_registers()
if self._system.available:
self._attr_is_on = self._system.power
self._attr_percentage = self._get_percentage(self._system.supply_speed)
self._attr_available = self._system.available
except Exception as e:
self.logger.error(f"Error updating fan state: {e}")
self._attr_available = False

self.async_write_ha_state()

def _update_state_from_system(self):
"""Update the fan's state from the FreshAirSystem."""
self._attr_is_on = self._system.power
self._attr_percentage = self._get_percentage(self._system.supply_speed)
if self._system.available:
self._attr_is_on = self._system.power
self._attr_percentage = self._get_percentage(self._system.supply_speed)
self._attr_available = self._system.available
self.async_write_ha_state()

@property
Expand Down Expand Up @@ -92,12 +102,42 @@ def _get_speed_value(self, percentage):
else:
return 3

async def async_turn_on(self, percentage=None, **kwargs) -> None:
"""Turn the fan on."""
async def async_turn_on(
self,
speed: str | None = None,
percentage: int | None = None,
preset_mode: str | None = None,
**kwargs: Any
) -> None:
"""Turn on the fan."""
self._system.power = True

# Handle percentage if provided (preferred method)
if percentage is not None:
await self.async_set_percentage(percentage)
else:
self._system.power = True
speed_value = self._get_speed_value(percentage)
self._system.supply_speed = speed_value
self._system.exhaust_speed = speed_value
self._attr_percentage = percentage
# Handle legacy speed if provided
elif speed is not None:
# Convert legacy speed string to percentage
if speed == "low":
self._attr_percentage = 33
elif speed == "medium":
self._attr_percentage = 66
elif speed == "high":
self._attr_percentage = 100
speed_value = self._get_speed_value(self._attr_percentage)
self._system.supply_speed = speed_value
self._system.exhaust_speed = speed_value

# Handle preset_mode if provided
if preset_mode is not None:
# Implement preset mode handling if your fan supports it
pass

self._attr_is_on = True
# Update the entity state by reading from the device
await self.async_update()

async def async_turn_off(self, **kwargs) -> None:
Expand Down
23 changes: 13 additions & 10 deletions custom_components/madelon_ventilation/fresh_air_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,26 @@ def __init__(self, host, port, unit_id):
self.unique_identifier = f"{host}:{port}:{unit_id}" # Use host, port and unit_id as a unique identifier
self.logger = logging.getLogger(__name__)
self.logger.debug(f"Initialized FreshAirSystem with host: {host}, port: {port}, unit_id: {unit_id}")
self.available = False # Add availability tracking
self.sensors = [] # List to hold sensor entities

def register_sensor(self, sensor):
"""Register a sensor entity with the system."""
self.sensors.append(sensor)

def _read_all_registers(self):
"""一次性读取所有相关寄存器"""
start_address = min(self.REGISTERS.values())
count = max(self.REGISTERS.values()) - start_address + 1
self.logger.debug(f"Reading all registers from {start_address} to {start_address + count - 1}")
self._registers_cache = self.modbus.read_registers(start_address, count)
self.logger.debug(f"Registers read: {self._registers_cache}")

# Update all registered sensors
for sensor in self.sensors:
sensor.async_schedule_update_ha_state(True)
"""Read all registers with error handling."""
try:
start_address = min(self.REGISTERS.values())
count = max(self.REGISTERS.values()) - start_address + 1
self.logger.debug(f"Reading all registers from {start_address} to {start_address + count - 1}")
self._registers_cache = self.modbus.read_registers(start_address, count)
self.available = True if self._registers_cache else False
self.logger.debug(f"Registers read: {self._registers_cache}")
except Exception as e:
self.logger.error(f"Failed to read registers: {e}")
self.available = False
self._registers_cache = None

def _get_register_value(self, name):
"""从缓存中获取寄存器值"""
Expand Down
44 changes: 26 additions & 18 deletions custom_components/madelon_ventilation/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .fresh_air_controller import FreshAirSystem
from .fresh_air_controller import FreshAirSystem, OperationMode
import logging
import asyncio

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback):
"""Set up the Fresh Air System mode select."""
Expand All @@ -20,37 +21,44 @@ def __init__(self, entry: ConfigEntry, system: FreshAirSystem):
self._attr_name = "Fresh Air Mode"
self._attr_unique_id = f"{DOMAIN}_mode_select_{system.unique_identifier}"
self._attr_options = [
"manual", "auto", "timer",
"manual_bypass", "auto_bypass", "timer_bypass"
"Manual", "Auto", "Timer",
"Manual Bypass", "Auto Bypass", "Timer Bypass"
]
self._attr_current_option = self._get_mode_from_system()

def _get_mode_from_system(self):
"""Get the current mode from the system."""
mode_map = {
(0, False): "manual",
(1, False): "auto",
(2, False): "timer",
(0, True): "manual_bypass",
(1, True): "auto_bypass",
(2, True): "timer_bypass"
(OperationMode.MANUAL, False): "Manual",
(OperationMode.AUTO, False): "Auto",
(OperationMode.TIMER, False): "Timer",
(OperationMode.MANUAL, True): "Manual Bypass",
(OperationMode.AUTO, True): "Auto Bypass",
(OperationMode.TIMER, True): "Timer Bypass"
}
return mode_map.get((self._system.mode, self._system.bypass), "manual")
return mode_map.get((self._system.mode, self._system.bypass), "Manual")

async def async_select_option(self, option: str):
"""Change the selected option."""
from .fresh_air_controller import OperationMode

mode_map = {
"manual": (0, False),
"auto": (1, False),
"timer": (2, False),
"manual_bypass": (0, True),
"auto_bypass": (1, True),
"timer_bypass": (2, True)
"Manual": (OperationMode.MANUAL, False),
"Auto": (OperationMode.AUTO, False),
"Timer": (OperationMode.TIMER, False),
"Manual Bypass": (OperationMode.MANUAL, True),
"Auto Bypass": (OperationMode.AUTO, True),
"Timer Bypass": (OperationMode.TIMER, True)
}
if option in mode_map:
mode, bypass = mode_map[option]
# Set mode first
self._system.mode = mode
# Wait for 300ms
await asyncio.sleep(0.3)
# Then set bypass
self._system.bypass = bypass

self._attr_current_option = option
self.async_write_ha_state()

Expand All @@ -61,6 +69,6 @@ def device_info(self) -> DeviceInfo:
identifiers={(DOMAIN, self._system.unique_identifier)},
name="Fresh Air System",
manufacturer="Madelon",
model="XIXI",
sw_version="1.0",
model="Jinmaofu",
sw_version="0.1.0",
)
10 changes: 8 additions & 2 deletions custom_components/madelon_ventilation/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ def device_info(self) -> DeviceInfo:
)

async def async_update(self):
self._attr_native_value = self._system.temperature
"""Update temperature value."""
if self._system.available:
self._attr_native_value = self._system.temperature
self._attr_available = self._system.available

class FreshAirHumiditySensor(SensorEntity):
_attr_has_entity_name = True
Expand All @@ -80,4 +83,7 @@ def device_info(self) -> DeviceInfo:
)

async def async_update(self):
self._attr_native_value = self._system.humidity
"""Update humidity value."""
if self._system.available:
self._attr_native_value = self._system.humidity
self._attr_available = self._system.available
53 changes: 43 additions & 10 deletions dummy_server.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,58 @@
import asyncio
from pymodbus.server.async_io import StartAsyncTcpServer
from pymodbus.server.async_io import StartAsyncTcpServer, ModbusTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
import logging
import time

# Configure logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.WARNING)
log.setLevel(logging.DEBUG)

# # Set pymodbus logging level
# pymodbus_log = logging.getLogger("pymodbus")
# pymodbus_log.setLevel(logging.WARNING)
class CustomSlaveContext(ModbusSlaveContext):
"""Custom slave context that updates actual values when control values change."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._last_write_time = 0

def setValues(self, fx, address, values):
"""Override setValues to implement custom logic with delay requirement."""
current_time = time.time()
time_since_last_write = (current_time - self._last_write_time) * 1000 # Convert to milliseconds

if time_since_last_write < 200:
log.warning(f"Write too fast! Must wait at least 200ms between writes. Only {time_since_last_write:.1f}ms has passed.")
return # Simply return without writing instead of raising an exception

# Update last write time
self._last_write_time = current_time

# Proceed with the write
super().setValues(fx, address, values)
log.debug(f"Written value {values} to register {address}")

# Map of control registers to actual registers
CONTROL_TO_ACTUAL = {
7: 12, # supply_speed -> actual_supply
8: 13 # exhaust_speed -> actual_exhaust
}

# If we're writing to a control register, update its corresponding actual register
if address in CONTROL_TO_ACTUAL:
actual_address = CONTROL_TO_ACTUAL[address]
super().setValues(fx, actual_address, values)
log.debug(f"Updated actual register {actual_address} with value {values}")

# Create a Modbus data store with some initial values
store = ModbusSlaveContext(
# Initialize data store
store = CustomSlaveContext(
hr=ModbusSequentialDataBlock(0, [0]*100) # 100 holding registers initialized to 0
)
context = ModbusServerContext(slaves=store, single=True)

# Set up server identity
# Server identity
identity = ModbusDeviceIdentification()
identity.VendorName = 'pymodbus'
identity.ProductCode = 'PM'
Expand All @@ -29,13 +61,14 @@
identity.ModelName = 'pymodbus Server'
identity.MajorMinorRevision = '1.0'

# Start the Modbus server
async def run_server():
await StartAsyncTcpServer(
server = ModbusTcpServer(
context=context,
identity=identity,
address=("localhost", 8899)
)

await server.serve_forever()

if __name__ == "__main__":
asyncio.run(run_server())

0 comments on commit 4a8d2a4

Please sign in to comment.