forked from esphome/esphome
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Uponor Smatrix component (esphome#5769)
Co-authored-by: Jesse Hills <[email protected]>
- Loading branch information
1 parent
76a3ffc
commit 58c0d8c
Showing
21 changed files
with
796 additions
and
4 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import esphome.codegen as cg | ||
import esphome.config_validation as cv | ||
from esphome.components import uart, time | ||
from esphome.const import ( | ||
CONF_ADDRESS, | ||
CONF_ID, | ||
CONF_TIME_ID, | ||
) | ||
|
||
CODEOWNERS = ["@kroimon"] | ||
|
||
DEPENDENCIES = ["uart"] | ||
|
||
MULTI_CONF = True | ||
|
||
uponor_smatrix_ns = cg.esphome_ns.namespace("uponor_smatrix") | ||
UponorSmatrixComponent = uponor_smatrix_ns.class_( | ||
"UponorSmatrixComponent", cg.Component, uart.UARTDevice | ||
) | ||
UponorSmatrixDevice = uponor_smatrix_ns.class_( | ||
"UponorSmatrixDevice", cg.Parented.template(UponorSmatrixComponent) | ||
) | ||
|
||
CONF_UPONOR_SMATRIX_ID = "uponor_smatrix_id" | ||
CONF_TIME_DEVICE_ADDRESS = "time_device_address" | ||
|
||
CONFIG_SCHEMA = ( | ||
cv.Schema( | ||
{ | ||
cv.GenerateID(): cv.declare_id(UponorSmatrixComponent), | ||
cv.Optional(CONF_ADDRESS): cv.hex_uint16_t, | ||
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), | ||
cv.Optional(CONF_TIME_DEVICE_ADDRESS): cv.hex_uint16_t, | ||
} | ||
) | ||
.extend(cv.COMPONENT_SCHEMA) | ||
.extend(uart.UART_DEVICE_SCHEMA) | ||
) | ||
|
||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||
"uponor_smatrix", | ||
baud_rate=19200, | ||
require_tx=True, | ||
require_rx=True, | ||
data_bits=8, | ||
parity=None, | ||
stop_bits=1, | ||
) | ||
|
||
# A schema to use for all Uponor Smatrix devices | ||
UPONOR_SMATRIX_DEVICE_SCHEMA = cv.Schema( | ||
{ | ||
cv.GenerateID(CONF_UPONOR_SMATRIX_ID): cv.use_id(UponorSmatrixComponent), | ||
cv.Required(CONF_ADDRESS): cv.hex_uint16_t, | ||
} | ||
) | ||
|
||
|
||
async def to_code(config): | ||
cg.add_global(uponor_smatrix_ns.using) | ||
var = cg.new_Pvariable(config[CONF_ID]) | ||
await cg.register_component(var, config) | ||
await uart.register_uart_device(var, config) | ||
|
||
if address := config.get(CONF_ADDRESS): | ||
cg.add(var.set_system_address(address)) | ||
if time_id := config.get(CONF_TIME_ID): | ||
time_ = await cg.get_variable(time_id) | ||
cg.add(var.set_time_id(time_)) | ||
if time_device_address := config.get(CONF_TIME_DEVICE_ADDRESS): | ||
cg.add(var.set_time_device_address(time_device_address)) | ||
|
||
|
||
async def register_uponor_smatrix_device(var, config): | ||
parent = await cg.get_variable(config[CONF_UPONOR_SMATRIX_ID]) | ||
cg.add(var.set_parent(parent)) | ||
cg.add(var.set_device_address(config[CONF_ADDRESS])) | ||
cg.add(parent.register_device(var)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import esphome.codegen as cg | ||
import esphome.config_validation as cv | ||
from esphome.components import climate | ||
from esphome.const import CONF_ID | ||
|
||
from .. import ( | ||
uponor_smatrix_ns, | ||
UponorSmatrixDevice, | ||
UPONOR_SMATRIX_DEVICE_SCHEMA, | ||
register_uponor_smatrix_device, | ||
) | ||
|
||
DEPENDENCIES = ["uponor_smatrix"] | ||
|
||
UponorSmatrixClimate = uponor_smatrix_ns.class_( | ||
"UponorSmatrixClimate", | ||
climate.Climate, | ||
cg.Component, | ||
UponorSmatrixDevice, | ||
) | ||
|
||
CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( | ||
{ | ||
cv.GenerateID(): cv.declare_id(UponorSmatrixClimate), | ||
} | ||
).extend(UPONOR_SMATRIX_DEVICE_SCHEMA) | ||
|
||
|
||
async def to_code(config): | ||
var = cg.new_Pvariable(config[CONF_ID]) | ||
await cg.register_component(var, config) | ||
await climate.register_climate(var, config) | ||
await register_uponor_smatrix_device(var, config) |
101 changes: 101 additions & 0 deletions
101
esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
#include "uponor_smatrix_climate.h" | ||
#include "esphome/core/helpers.h" | ||
#include "esphome/core/log.h" | ||
|
||
namespace esphome { | ||
namespace uponor_smatrix { | ||
|
||
static const char *const TAG = "uponor_smatrix.climate"; | ||
|
||
void UponorSmatrixClimate::dump_config() { | ||
LOG_CLIMATE("", "Uponor Smatrix Climate", this); | ||
ESP_LOGCONFIG(TAG, " Device address: 0x%04X", this->address_); | ||
} | ||
|
||
void UponorSmatrixClimate::loop() { | ||
const uint32_t now = millis(); | ||
|
||
// Publish state after all update packets are processed | ||
if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) { | ||
float temp = raw_to_celsius((this->preset == climate::CLIMATE_PRESET_ECO) | ||
? (this->target_temperature_raw_ - this->eco_setback_value_raw_) | ||
: this->target_temperature_raw_); | ||
float step = this->get_traits().get_visual_target_temperature_step(); | ||
this->target_temperature = roundf(temp / step) * step; | ||
this->publish_state(); | ||
this->last_data_ = 0; | ||
} | ||
} | ||
|
||
climate::ClimateTraits UponorSmatrixClimate::traits() { | ||
auto traits = climate::ClimateTraits(); | ||
traits.set_supports_current_temperature(true); | ||
traits.set_supports_current_humidity(true); | ||
traits.set_supported_modes({climate::CLIMATE_MODE_HEAT}); | ||
traits.set_supports_action(true); | ||
traits.set_supported_presets({climate::CLIMATE_PRESET_ECO}); | ||
traits.set_visual_min_temperature(this->min_temperature_); | ||
traits.set_visual_max_temperature(this->max_temperature_); | ||
traits.set_visual_current_temperature_step(0.1f); | ||
traits.set_visual_target_temperature_step(0.5f); | ||
return traits; | ||
} | ||
|
||
void UponorSmatrixClimate::control(const climate::ClimateCall &call) { | ||
if (call.get_target_temperature().has_value()) { | ||
uint16_t temp = celsius_to_raw(*call.get_target_temperature()); | ||
if (this->preset == climate::CLIMATE_PRESET_ECO) { | ||
// During ECO mode, the thermostat automatically substracts the setback value from the setpoint, | ||
// so we need to add it here first | ||
temp += this->eco_setback_value_raw_; | ||
} | ||
|
||
// For unknown reasons, we need to send a null setpoint first for the thermostat to react | ||
UponorSmatrixData data[] = {{UPONOR_ID_TARGET_TEMP, 0}, {UPONOR_ID_TARGET_TEMP, temp}}; | ||
this->send(data, sizeof(data) / sizeof(data[0])); | ||
} | ||
} | ||
|
||
void UponorSmatrixClimate::on_device_data(const UponorSmatrixData *data, size_t data_len) { | ||
for (int i = 0; i < data_len; i++) { | ||
switch (data[i].id) { | ||
case UPONOR_ID_TARGET_TEMP_MIN: | ||
this->min_temperature_ = raw_to_celsius(data[i].value); | ||
break; | ||
case UPONOR_ID_TARGET_TEMP_MAX: | ||
this->max_temperature_ = raw_to_celsius(data[i].value); | ||
break; | ||
case UPONOR_ID_TARGET_TEMP: | ||
// Ignore invalid values here as they are used by the controller to explicitely request the setpoint from a | ||
// thermostat | ||
if (data[i].value != UPONOR_INVALID_VALUE) | ||
this->target_temperature_raw_ = data[i].value; | ||
break; | ||
case UPONOR_ID_ECO_SETBACK: | ||
this->eco_setback_value_raw_ = data[i].value; | ||
break; | ||
case UPONOR_ID_DEMAND: | ||
if (data[i].value & 0x1000) { | ||
this->mode = climate::CLIMATE_MODE_COOL; | ||
this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_COOLING : climate::CLIMATE_ACTION_IDLE; | ||
} else { | ||
this->mode = climate::CLIMATE_MODE_HEAT; | ||
this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_HEATING : climate::CLIMATE_ACTION_IDLE; | ||
} | ||
break; | ||
case UPONOR_ID_MODE1: | ||
this->set_preset_((data[i].value & 0x0008) ? climate::CLIMATE_PRESET_ECO : climate::CLIMATE_PRESET_NONE); | ||
break; | ||
case UPONOR_ID_ROOM_TEMP: | ||
this->current_temperature = raw_to_celsius(data[i].value); | ||
break; | ||
case UPONOR_ID_HUMIDITY: | ||
this->current_humidity = data[i].value & 0x00FF; | ||
} | ||
} | ||
|
||
this->last_data_ = millis(); | ||
} | ||
|
||
} // namespace uponor_smatrix | ||
} // namespace esphome |
28 changes: 28 additions & 0 deletions
28
esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
#pragma once | ||
|
||
#include "esphome/components/climate/climate.h" | ||
#include "esphome/components/uponor_smatrix/uponor_smatrix.h" | ||
#include "esphome/core/component.h" | ||
|
||
namespace esphome { | ||
namespace uponor_smatrix { | ||
|
||
class UponorSmatrixClimate : public climate::Climate, public Component, public UponorSmatrixDevice { | ||
public: | ||
void dump_config() override; | ||
void loop() override; | ||
|
||
protected: | ||
climate::ClimateTraits traits() override; | ||
void control(const climate::ClimateCall &call) override; | ||
void on_device_data(const UponorSmatrixData *data, size_t data_len) override; | ||
|
||
uint32_t last_data_; | ||
float min_temperature_{5.0f}; | ||
float max_temperature_{35.0f}; | ||
uint16_t eco_setback_value_raw_{0x0048}; | ||
uint16_t target_temperature_raw_; | ||
}; | ||
|
||
} // namespace uponor_smatrix | ||
} // namespace esphome |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import esphome.codegen as cg | ||
import esphome.config_validation as cv | ||
from esphome.components import sensor | ||
from esphome.const import ( | ||
CONF_EXTERNAL_TEMPERATURE, | ||
CONF_HUMIDITY, | ||
CONF_TEMPERATURE, | ||
CONF_ID, | ||
DEVICE_CLASS_HUMIDITY, | ||
DEVICE_CLASS_TEMPERATURE, | ||
STATE_CLASS_MEASUREMENT, | ||
UNIT_CELSIUS, | ||
UNIT_PERCENT, | ||
) | ||
|
||
from .. import ( | ||
uponor_smatrix_ns, | ||
UponorSmatrixDevice, | ||
UPONOR_SMATRIX_DEVICE_SCHEMA, | ||
register_uponor_smatrix_device, | ||
) | ||
|
||
DEPENDENCIES = ["uponor_smatrix"] | ||
|
||
UponorSmatrixSensor = uponor_smatrix_ns.class_( | ||
"UponorSmatrixSensor", | ||
sensor.Sensor, | ||
cg.Component, | ||
UponorSmatrixDevice, | ||
) | ||
|
||
CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend( | ||
{ | ||
cv.GenerateID(): cv.declare_id(UponorSmatrixSensor), | ||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||
unit_of_measurement=UNIT_CELSIUS, | ||
accuracy_decimals=1, | ||
device_class=DEVICE_CLASS_TEMPERATURE, | ||
state_class=STATE_CLASS_MEASUREMENT, | ||
), | ||
cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema( | ||
unit_of_measurement=UNIT_CELSIUS, | ||
accuracy_decimals=1, | ||
device_class=DEVICE_CLASS_TEMPERATURE, | ||
state_class=STATE_CLASS_MEASUREMENT, | ||
), | ||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||
unit_of_measurement=UNIT_PERCENT, | ||
accuracy_decimals=0, | ||
device_class=DEVICE_CLASS_HUMIDITY, | ||
state_class=STATE_CLASS_MEASUREMENT, | ||
), | ||
} | ||
).extend(UPONOR_SMATRIX_DEVICE_SCHEMA) | ||
|
||
|
||
async def to_code(config): | ||
var = cg.new_Pvariable(config[CONF_ID]) | ||
await cg.register_component(var, config) | ||
await register_uponor_smatrix_device(var, config) | ||
|
||
if temperature_config := config.get(CONF_TEMPERATURE): | ||
sens = await sensor.new_sensor(temperature_config) | ||
cg.add(var.set_temperature_sensor(sens)) | ||
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE): | ||
sens = await sensor.new_sensor(external_temperature_config) | ||
cg.add(var.set_external_temperature_sensor(sens)) | ||
if humidity_config := config.get(CONF_HUMIDITY): | ||
sens = await sensor.new_sensor(humidity_config) | ||
cg.add(var.set_humidity_sensor(sens)) |
Oops, something went wrong.