Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/heating power valve pos #1581

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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: 2 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -48,4 +48,4 @@
"mounts": [
"source=${localWorkspaceFolder}/.haconfig,target=/config,type=bind,consistency=cached"
]
}
}
8 changes: 5 additions & 3 deletions custom_components/better_thermostat/calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 33 additions & 4 deletions custom_components/better_thermostat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
49 changes: 43 additions & 6 deletions custom_components/better_thermostat/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
15 changes: 14 additions & 1 deletion scripts/upgrade-version
Original file line number Diff line number Diff line change
Expand Up @@ -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}!"

Expand All @@ -10,4 +23,4 @@ cd "$(dirname "$0")/.."

python3 -m pip install homeassistant==$VERSION

echo "Done."
echo "Done."
Loading