diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e912e067..cb33ae4c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ // See https://aka.ms/vscode-remote/devcontainer.json for format details. { - "image": "mcr.microsoft.com/devcontainers/python:0-3.11", + "image": "mcr.microsoft.com/devcontainers/python:3.13", "name": "BETTER THERMOSTAT development", "initializeCommand": "/bin/mkdir -p ${localWorkspaceFolder}/.haconfig && /bin/cp ${localWorkspaceFolder}/.devcontainer/configuration.yaml ${localWorkspaceFolder}/.haconfig", "containerUser": "1000", @@ -48,4 +48,4 @@ "mounts": [ "source=${localWorkspaceFolder}/.haconfig,target=/config,type=bind,consistency=cached" ] -} \ No newline at end of file +} diff --git a/custom_components/better_thermostat/calibration.py b/custom_components/better_thermostat/calibration.py index 7435a863..00c6358b 100644 --- a/custom_components/better_thermostat/calibration.py +++ b/custom_components/better_thermostat/calibration.py @@ -173,9 +173,11 @@ def calculate_calibration_setpoint(self, entity_id) -> float | None: return None # Add tolerance check - _within_tolerance = self.cur_temp >= ( - self.bt_target_temp - self.tolerance - ) and self.cur_temp <= (self.bt_target_temp + self.tolerance) + _within_tolerance = ( + self.tolerance > 0.0 + and self.cur_temp >= (self.bt_target_temp - self.tolerance) + and self.cur_temp <= (self.bt_target_temp + self.tolerance) + ) if _within_tolerance: # When within tolerance, don't adjust calibration diff --git a/custom_components/better_thermostat/climate.py b/custom_components/better_thermostat/climate.py index 928c1a43..e88c14fc 100644 --- a/custom_components/better_thermostat/climate.py +++ b/custom_components/better_thermostat/climate.py @@ -967,6 +967,11 @@ async def startup(self): break async def calculate_heating_power(self): + if not hasattr(self, 'min_target_temp'): + self.min_target_temp = self.bt_target_temp or 18.0 + if not hasattr(self, 'max_target_temp'): + self.max_target_temp = self.bt_target_temp or 21.0 + if ( self.heating_start_temp is not None and self.heating_end_temp is not None @@ -979,29 +984,52 @@ async def calculate_heating_power(self): 1, ) if _time_diff_minutes > 1: + temp_range = max(self.max_target_temp - self.min_target_temp, 0.1) + weight_factor = max( + 0.5, + min( + 1.5, + 0.5 + (self.bt_target_temp - self.min_target_temp) / temp_range, + ), + ) + _degrees_time = round(_temp_diff / _time_diff_minutes, 4) self.heating_power = round( - (self.heating_power * 0.9 + _degrees_time * 0.1), 4 + (self.heating_power * (1 - 0.1 * weight_factor) + + _degrees_time * 0.1 * weight_factor), 4 ) + if len(self.last_heating_power_stats) >= 10: self.last_heating_power_stats = self.last_heating_power_stats[ - len(self.last_heating_power_stats) - 9 : + len(self.last_heating_power_stats) - 9: ] self.last_heating_power_stats.append( { "temp_diff": round(_temp_diff, 1), "time": _time_diff_minutes, "degrees_time": round(_degrees_time, 4), + "weight_factor": weight_factor, "heating_power": round(self.heating_power, 4), } ) + _LOGGER.debug( - f"better_thermostat {self.device_name}: calculate_heating_power / temp_diff: {round(_temp_diff, 1)} - time: {_time_diff_minutes} - degrees_time: {round(_degrees_time, 4)} - heating_power: {round(self.heating_power, 4)} - heating_power_stats: {self.last_heating_power_stats}" + f"better_thermostat {self.device_name}: calculate_heating_power / " + f"temp_diff: {round(_temp_diff, 1)} - time: {_time_diff_minutes} - " + f"degrees_time: { + round(_degrees_time, 4)} - weight_factor: {weight_factor} - " + f"heating_power: {round(self.heating_power, 4)} - " + f"heating_power_stats: {self.last_heating_power_stats}" ) + # reset for the next heating start self.heating_start_temp = None self.heating_end_temp = None + if self.heating_end_temp: + self.min_target_temp = min(self.min_target_temp, self.bt_target_temp) + self.max_target_temp = max(self.max_target_temp, self.bt_target_temp) + # heating starts if ( self.attr_hvac_action == HVACAction.HEATING @@ -1251,7 +1279,8 @@ async def async_set_temperature(self, **kwargs) -> None: if _new_setpoint is None and _new_setpointlow is None: _LOGGER.debug( - f"better_thermostat {self.device_name}: received a new setpoint from HA, but temperature attribute was not set, ignoring" + f"better_thermostat { + self.device_name}: received a new setpoint from HA, but temperature attribute was not set, ignoring" ) return diff --git a/custom_components/better_thermostat/utils/helpers.py b/custom_components/better_thermostat/utils/helpers.py index 3b863fce..68ade822 100644 --- a/custom_components/better_thermostat/utils/helpers.py +++ b/custom_components/better_thermostat/utils/helpers.py @@ -65,24 +65,58 @@ def mode_remap(self, entity_id, hvac_mode: str, inbound: bool = False) -> str: return hvac_mode _LOGGER.error( - f"better_thermostat {self.device_name}: {entity_id} HVAC mode {hvac_mode} is not supported by this device, is it possible that you forgot to set the heat auto swapped option?" + f"better_thermostat {self.device_name}: {entity_id} HVAC mode { + hvac_mode} is not supported by this device, is it possible that you forgot to set the heat auto swapped option?" ) return HVACMode.OFF def heating_power_valve_position(self, entity_id): _temp_diff = float(float(self.bt_target_temp) - float(self.cur_temp)) - valve_pos = (_temp_diff / self.heating_power) / 100 + + a = 0.019 + b = 0.946 + valve_pos = a * (_temp_diff / self.heating_power) ** b + if valve_pos < 0.0: valve_pos = 0.0 if valve_pos > 1.0: valve_pos = 1.0 _LOGGER.debug( - f"better_thermostat {self.device_name}: {entity_id} / heating_power_valve_position - temp diff: {round(_temp_diff, 1)} - heating power: {round(self.heating_power, 4)} - expected valve position: {round(valve_pos * 100)}%" + f"better_thermostat {self.device_name}: {entity_id} / heating_power_valve_position - temp diff: {round( + _temp_diff, 1)} - heating power: {round(self.heating_power, 4)} - expected valve position: {round(valve_pos * 100)}%" ) return valve_pos + # Example values for different heating_power and temp_diff: + # With heating_power of 0.02: + # | temp_diff | valve_pos | + # |-----------|------------| + # | 0.1 | 0.0871 | + # | 0.2 | 0.1678 | + # | 0.3 | 0.2462 | + # | 0.4 | 0.3232 | + # | 0.5 | 0.3992 | + + # With heating_power of 0.01: + # | temp_diff | valve_pos | + # |-----------|------------| + # | 0.1 | 0.1678 | + # | 0.2 | 0.3232 | + # | 0.3 | 0.4744 | + # | 0.4 | 0.6227 | + # | 0.5 | 0.7691 | + + # With heating_power of 0.005: + # | temp_diff | valve_pos | + # |-----------|------------| + # | 0.1 | 0.3232 | + # | 0.2 | 0.6227 | + # | 0.3 | 0.9139 | + # | 0.4 | 1.0000 | + # | 0.5 | 1.0000 | + def convert_to_float( value: str | float, instance_name: str, context: str @@ -111,7 +145,8 @@ def convert_to_float( return round_by_step(float(value), 0.1) except (ValueError, TypeError, AttributeError, KeyError): _LOGGER.debug( - f"better thermostat {instance_name}: Could not convert '{value}' to float in {context}" + f"better thermostat {instance_name}: Could not convert '{ + value}' to float in {context}" ) return None @@ -234,7 +269,8 @@ async def find_valve_entity(self, entity_id): if entity.device_id == reg_entity.device_id: if "_valve_position" in uid or "_position" in uid: _LOGGER.debug( - f"better thermostat: Found valve position entity {entity.entity_id} for {entity_id}" + f"better thermostat: Found valve position entity { + entity.entity_id} for {entity_id}" ) return entity.entity_id @@ -296,7 +332,8 @@ async def find_local_calibration_entity(self, entity_id): if entity.device_id == reg_entity.device_id: if "temperature_calibration" in uid or "temperature_offset" in uid: _LOGGER.debug( - f"better thermostat: Found local calibration entity {entity.entity_id} for {entity_id}" + f"better thermostat: Found local calibration entity { + entity.entity_id} for {entity_id}" ) return entity.entity_id diff --git a/scripts/upgrade-version b/scripts/upgrade-version index 5a24b5e3..de8be053 100755 --- a/scripts/upgrade-version +++ b/scripts/upgrade-version @@ -2,6 +2,19 @@ set -e +# Check versions +echo "Fetching available Home Assistant versions..." +AVAILABLE_VERSIONS=$(curl -s https://pypi.org/pypi/homeassistant/json | jq -r '.releases | keys | .[]' | sort -V) + +if [[ -z "$AVAILABLE_VERSIONS" ]]; then + echo "Error: Could not fetch available versions." + exit 1 +fi + +# Show available versions +echo "Available versions:" +echo "$AVAILABLE_VERSIONS" + read -p "What HA Version? " VERSION echo "Installing ${my_var}!" @@ -10,4 +23,4 @@ cd "$(dirname "$0")/.." python3 -m pip install homeassistant==$VERSION -echo "Done." \ No newline at end of file +echo "Done."