Add Micronova component (#4760)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Graham Brown <grahambrown11@gmail.com>
This commit is contained in:
Joris S 2023-11-03 07:54:47 +01:00 committed by GitHub
parent 22cdb8dfc3
commit 13994d9bd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1134 additions and 0 deletions

View file

@ -184,6 +184,7 @@ esphome/components/mcp9808/* @k7hpn
esphome/components/md5/* @esphome/core
esphome/components/mdns/* @esphome/core
esphome/components/media_player/* @jesserockz
esphome/components/micronova/* @jorre05
esphome/components/microphone/* @jesserockz
esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov

View file

@ -0,0 +1,69 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import uart
from esphome.const import (
CONF_ID,
)
CODEOWNERS = ["@jorre05"]
DEPENDENCIES = ["uart"]
CONF_MICRONOVA_ID = "micronova_id"
CONF_ENABLE_RX_PIN = "enable_rx_pin"
CONF_MEMORY_LOCATION = "memory_location"
CONF_MEMORY_ADDRESS = "memory_address"
micronova_ns = cg.esphome_ns.namespace("micronova")
MicroNovaFunctions = micronova_ns.enum("MicroNovaFunctions", is_class=True)
MICRONOVA_FUNCTIONS_ENUM = {
"STOVE_FUNCTION_SWITCH": MicroNovaFunctions.STOVE_FUNCTION_SWITCH,
"STOVE_FUNCTION_ROOM_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE,
"STOVE_FUNCTION_THERMOSTAT_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE,
"STOVE_FUNCTION_FUMES_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE,
"STOVE_FUNCTION_STOVE_POWER": MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER,
"STOVE_FUNCTION_FAN_SPEED": MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED,
"STOVE_FUNCTION_STOVE_STATE": MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE,
"STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR": MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR,
"STOVE_FUNCTION_WATER_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE,
"STOVE_FUNCTION_WATER_PRESSURE": MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE,
"STOVE_FUNCTION_POWER_LEVEL": MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL,
"STOVE_FUNCTION_CUSTOM": MicroNovaFunctions.STOVE_FUNCTION_CUSTOM,
}
MicroNova = micronova_ns.class_("MicroNova", cg.PollingComponent, uart.UARTDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MicroNova),
cv.Required(CONF_ENABLE_RX_PIN): pins.gpio_output_pin_schema,
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.polling_component_schema("60s"))
)
def MICRONOVA_LISTENER_SCHEMA(default_memory_location, default_memory_address):
return cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(
CONF_MEMORY_LOCATION, default=default_memory_location
): cv.hex_int_range(),
cv.Optional(
CONF_MEMORY_ADDRESS, default=default_memory_address
): cv.hex_int_range(),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
enable_rx_pin = await cg.gpio_pin_expression(config[CONF_ENABLE_RX_PIN])
cg.add(var.set_enable_rx_pin(enable_rx_pin))

View file

@ -0,0 +1,44 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import button
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
MicroNovaButton = micronova_ns.class_("MicroNovaButton", button.Button, cg.Component)
CONF_CUSTOM_BUTTON = "custom_button"
CONF_MEMORY_DATA = "memory_data"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_CUSTOM_BUTTON): button.button_schema(
MicroNovaButton,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0xA0, default_memory_address=0x7D
)
)
.extend({cv.Required(CONF_MEMORY_DATA): cv.hex_int_range()}),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if custom_button_config := config.get(CONF_CUSTOM_BUTTON):
bt = await button.new_button(custom_button_config, mv)
cg.add(bt.set_memory_location(custom_button_config.get(CONF_MEMORY_LOCATION)))
cg.add(bt.set_memory_address(custom_button_config.get(CONF_MEMORY_ADDRESS)))
cg.add(bt.set_memory_data(custom_button_config[CONF_MEMORY_DATA]))
cg.add(bt.set_function(MicroNovaFunctions.STOVE_FUNCTION_CUSTOM))

View file

@ -0,0 +1,18 @@
#include "micronova_button.h"
namespace esphome {
namespace micronova {
void MicroNovaButton::press_action() {
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_CUSTOM:
this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_);
break;
default:
break;
}
this->micronova_->update();
}
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,23 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace micronova {
class MicroNovaButton : public Component, public button::Button, public MicroNovaButtonListener {
public:
MicroNovaButton(MicroNova *m) : MicroNovaButtonListener(m) {}
void dump_config() override { LOG_BUTTON("", "Micronova button", this); }
void set_memory_data(uint8_t f) { this->memory_data_ = f; }
uint8_t get_memory_data() { return this->memory_data_; }
protected:
void press_action() override;
};
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,148 @@
#include "micronova.h"
#include "esphome/core/log.h"
namespace esphome {
namespace micronova {
void MicroNova::setup() {
if (this->enable_rx_pin_ != nullptr) {
this->enable_rx_pin_->setup();
this->enable_rx_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->enable_rx_pin_->digital_write(false);
}
this->current_transmission_.request_transmission_time = millis();
this->current_transmission_.memory_location = 0;
this->current_transmission_.memory_address = 0;
this->current_transmission_.reply_pending = false;
this->current_transmission_.initiating_listener = nullptr;
}
void MicroNova::dump_config() {
ESP_LOGCONFIG(TAG, "MicroNova:");
if (this->enable_rx_pin_ != nullptr) {
LOG_PIN(" Enable RX Pin: ", this->enable_rx_pin_);
}
for (auto &mv_sensor : this->micronova_listeners_) {
mv_sensor->dump_config();
ESP_LOGCONFIG(TAG, " sensor location:%02X, address:%02X", mv_sensor->get_memory_location(),
mv_sensor->get_memory_address());
}
}
void MicroNova::update() {
ESP_LOGD(TAG, "Schedule sensor update");
for (auto &mv_listener : this->micronova_listeners_) {
mv_listener->set_needs_update(true);
}
}
void MicroNova::loop() {
// Only read one sensor that needs update per loop
// If STOVE_REPLY_DELAY time has passed since last loop()
// check for a reply from the stove
if ((this->current_transmission_.reply_pending) &&
(millis() - this->current_transmission_.request_transmission_time > STOVE_REPLY_DELAY)) {
int stove_reply_value = this->read_stove_reply();
if (this->current_transmission_.initiating_listener != nullptr) {
this->current_transmission_.initiating_listener->process_value_from_stove(stove_reply_value);
this->current_transmission_.initiating_listener = nullptr;
}
this->current_transmission_.reply_pending = false;
return;
} else if (!this->current_transmission_.reply_pending) {
for (auto &mv_listener : this->micronova_listeners_) {
if (mv_listener->get_needs_update()) {
mv_listener->set_needs_update(false);
this->current_transmission_.initiating_listener = mv_listener;
mv_listener->request_value_from_stove();
return;
}
}
}
}
void MicroNova::request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener) {
uint8_t write_data[2] = {0, 0};
uint8_t trash_rx;
if (this->reply_pending_mutex_.try_lock()) {
// clear rx buffer.
// Stove hickups may cause late replies in the rx
while (this->available()) {
this->read_byte(&trash_rx);
ESP_LOGW(TAG, "Reading excess byte 0x%02X", trash_rx);
}
write_data[0] = location;
write_data[1] = address;
ESP_LOGV(TAG, "Request from stove [%02X,%02X]", write_data[0], write_data[1]);
this->enable_rx_pin_->digital_write(true);
this->write_array(write_data, 2);
this->flush();
this->enable_rx_pin_->digital_write(false);
this->current_transmission_.request_transmission_time = millis();
this->current_transmission_.memory_location = location;
this->current_transmission_.memory_address = address;
this->current_transmission_.reply_pending = true;
this->current_transmission_.initiating_listener = listener;
} else {
ESP_LOGE(TAG, "Reply is pending, skipping read request");
}
}
int MicroNova::read_stove_reply() {
uint8_t reply_data[2] = {0, 0};
uint8_t checksum = 0;
// assert enable_rx_pin is false
this->read_array(reply_data, 2);
this->reply_pending_mutex_.unlock();
ESP_LOGV(TAG, "Reply from stove [%02X,%02X]", reply_data[0], reply_data[1]);
checksum = ((uint16_t) this->current_transmission_.memory_location +
(uint16_t) this->current_transmission_.memory_address + (uint16_t) reply_data[1]) &
0xFF;
if (reply_data[0] != checksum) {
ESP_LOGE(TAG, "Checksum missmatch! From [0x%02X:0x%02X] received [0x%02X,0x%02X]. Expected 0x%02X, got 0x%02X",
this->current_transmission_.memory_location, this->current_transmission_.memory_address, reply_data[0],
reply_data[1], checksum, reply_data[0]);
return -1;
}
return ((int) reply_data[1]);
}
void MicroNova::write_address(uint8_t location, uint8_t address, uint8_t data) {
uint8_t write_data[4] = {0, 0, 0, 0};
uint16_t checksum = 0;
if (this->reply_pending_mutex_.try_lock()) {
write_data[0] = location;
write_data[1] = address;
write_data[2] = data;
checksum = ((uint16_t) write_data[0] + (uint16_t) write_data[1] + (uint16_t) write_data[2]) & 0xFF;
write_data[3] = checksum;
ESP_LOGV(TAG, "Write 4 bytes [%02X,%02X,%02X,%02X]", write_data[0], write_data[1], write_data[2], write_data[3]);
this->enable_rx_pin_->digital_write(true);
this->write_array(write_data, 4);
this->flush();
this->enable_rx_pin_->digital_write(false);
this->current_transmission_.request_transmission_time = millis();
this->current_transmission_.memory_location = location;
this->current_transmission_.memory_address = address;
this->current_transmission_.reply_pending = true;
this->current_transmission_.initiating_listener = nullptr;
} else {
ESP_LOGE(TAG, "Reply is pending, skipping write");
}
}
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,164 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/log.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include <vector>
namespace esphome {
namespace micronova {
static const char *const TAG = "micronova";
static const int STOVE_REPLY_DELAY = 60;
static const std::string STOVE_STATES[11] = {"Off",
"Start",
"Pellets loading",
"Ignition",
"Working",
"Brazier Cleaning",
"Final Cleaning",
"Standby",
"No pellets alarm",
"No ignition alarm",
"Undefined alarm"};
enum class MicroNovaFunctions {
STOVE_FUNCTION_VOID = 0,
STOVE_FUNCTION_SWITCH = 1,
STOVE_FUNCTION_ROOM_TEMPERATURE = 2,
STOVE_FUNCTION_THERMOSTAT_TEMPERATURE = 3,
STOVE_FUNCTION_FUMES_TEMPERATURE = 4,
STOVE_FUNCTION_STOVE_POWER = 5,
STOVE_FUNCTION_FAN_SPEED = 6,
STOVE_FUNCTION_STOVE_STATE = 7,
STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR = 8,
STOVE_FUNCTION_WATER_TEMPERATURE = 9,
STOVE_FUNCTION_WATER_PRESSURE = 10,
STOVE_FUNCTION_POWER_LEVEL = 11,
STOVE_FUNCTION_CUSTOM = 12
};
class MicroNova;
//////////////////////////////////////////////////////////////////////
// Interface classes.
class MicroNovaBaseListener {
public:
MicroNovaBaseListener() {}
MicroNovaBaseListener(MicroNova *m) { this->micronova_ = m; }
virtual void dump_config();
void set_micronova_object(MicroNova *m) { this->micronova_ = m; }
void set_function(MicroNovaFunctions f) { this->function_ = f; }
MicroNovaFunctions get_function() { return this->function_; }
void set_memory_location(uint8_t l) { this->memory_location_ = l; }
uint8_t get_memory_location() { return this->memory_location_; }
void set_memory_address(uint8_t a) { this->memory_address_ = a; }
uint8_t get_memory_address() { return this->memory_address_; }
protected:
MicroNova *micronova_{nullptr};
MicroNovaFunctions function_ = MicroNovaFunctions::STOVE_FUNCTION_VOID;
uint8_t memory_location_ = 0;
uint8_t memory_address_ = 0;
};
class MicroNovaSensorListener : public MicroNovaBaseListener {
public:
MicroNovaSensorListener() {}
MicroNovaSensorListener(MicroNova *m) : MicroNovaBaseListener(m) {}
virtual void request_value_from_stove() = 0;
virtual void process_value_from_stove(int value_from_stove) = 0;
void set_needs_update(bool u) { this->needs_update_ = u; }
bool get_needs_update() { return this->needs_update_; }
protected:
bool needs_update_ = false;
};
class MicroNovaNumberListener : public MicroNovaBaseListener {
public:
MicroNovaNumberListener(MicroNova *m) : MicroNovaBaseListener(m) {}
virtual void request_value_from_stove() = 0;
virtual void process_value_from_stove(int value_from_stove) = 0;
void set_needs_update(bool u) { this->needs_update_ = u; }
bool get_needs_update() { return this->needs_update_; }
protected:
bool needs_update_ = false;
};
class MicroNovaSwitchListener : public MicroNovaBaseListener {
public:
MicroNovaSwitchListener(MicroNova *m) : MicroNovaBaseListener(m) {}
virtual void set_stove_state(bool v) = 0;
virtual bool get_stove_state() = 0;
protected:
uint8_t memory_data_on_ = 0;
uint8_t memory_data_off_ = 0;
};
class MicroNovaButtonListener : public MicroNovaBaseListener {
public:
MicroNovaButtonListener(MicroNova *m) : MicroNovaBaseListener(m) {}
protected:
uint8_t memory_data_ = 0;
};
/////////////////////////////////////////////////////////////////////
// Main component class
class MicroNova : public PollingComponent, public uart::UARTDevice {
public:
MicroNova() {}
void setup() override;
void loop() override;
void update() override;
void dump_config() override;
void register_micronova_listener(MicroNovaSensorListener *l) { this->micronova_listeners_.push_back(l); }
void request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener);
void write_address(uint8_t location, uint8_t address, uint8_t data);
int read_stove_reply();
void set_enable_rx_pin(GPIOPin *enable_rx_pin) { this->enable_rx_pin_ = enable_rx_pin; }
void set_current_stove_state(uint8_t s) { this->current_stove_state_ = s; }
uint8_t get_current_stove_state() { return this->current_stove_state_; }
void set_stove(MicroNovaSwitchListener *s) { this->stove_switch_ = s; }
MicroNovaSwitchListener *get_stove_switch() { return this->stove_switch_; }
protected:
uint8_t current_stove_state_ = 0;
GPIOPin *enable_rx_pin_{nullptr};
struct MicroNovaSerialTransmission {
uint32_t request_transmission_time;
uint8_t memory_location;
uint8_t memory_address;
bool reply_pending;
MicroNovaSensorListener *initiating_listener;
};
Mutex reply_pending_mutex_;
MicroNovaSerialTransmission current_transmission_;
std::vector<MicroNovaSensorListener *> micronova_listeners_{};
MicroNovaSwitchListener *stove_switch_{nullptr};
};
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,110 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import number
from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
UNIT_CELSIUS,
CONF_STEP,
)
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
ICON_FLASH = "mdi:flash"
CONF_THERMOSTAT_TEMPERATURE = "thermostat_temperature"
CONF_POWER_LEVEL = "power_level"
CONF_MEMORY_WRITE_LOCATION = "memory_write_location"
MicroNovaNumber = micronova_ns.class_("MicroNovaNumber", number.Number, cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_THERMOSTAT_TEMPERATURE): number.number_schema(
MicroNovaNumber,
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x20, default_memory_address=0x7D
)
)
.extend(
{
cv.Optional(
CONF_MEMORY_WRITE_LOCATION, default=0xA0
): cv.hex_int_range(),
cv.Optional(CONF_STEP, default=1.0): cv.float_range(min=0.1, max=10.0),
}
),
cv.Optional(CONF_POWER_LEVEL): number.number_schema(
MicroNovaNumber,
icon=ICON_FLASH,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x20, default_memory_address=0x7F
)
)
.extend(
{cv.Optional(CONF_MEMORY_WRITE_LOCATION, default=0xA0): cv.hex_int_range()}
),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if thermostat_temperature_config := config.get(CONF_THERMOSTAT_TEMPERATURE):
numb = await number.new_number(
thermostat_temperature_config,
min_value=0,
max_value=40,
step=thermostat_temperature_config.get(CONF_STEP),
)
cg.add(numb.set_micronova_object(mv))
cg.add(mv.register_micronova_listener(numb))
cg.add(
numb.set_memory_location(
thermostat_temperature_config[CONF_MEMORY_LOCATION]
)
)
cg.add(
numb.set_memory_address(thermostat_temperature_config[CONF_MEMORY_ADDRESS])
)
cg.add(
numb.set_memory_write_location(
thermostat_temperature_config.get(CONF_MEMORY_WRITE_LOCATION)
)
)
cg.add(
numb.set_function(MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE)
)
if power_level_config := config.get(CONF_POWER_LEVEL):
numb = await number.new_number(
power_level_config,
min_value=1,
max_value=5,
step=1,
)
cg.add(numb.set_micronova_object(mv))
cg.add(mv.register_micronova_listener(numb))
cg.add(numb.set_memory_location(power_level_config[CONF_MEMORY_LOCATION]))
cg.add(numb.set_memory_address(power_level_config[CONF_MEMORY_ADDRESS]))
cg.add(
numb.set_memory_write_location(
power_level_config.get(CONF_MEMORY_WRITE_LOCATION)
)
)
cg.add(numb.set_function(MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL))

View file

@ -0,0 +1,45 @@
#include "micronova_number.h"
namespace esphome {
namespace micronova {
void MicroNovaNumber::process_value_from_stove(int value_from_stove) {
float new_sensor_value = 0;
if (value_from_stove == -1) {
this->publish_state(NAN);
return;
}
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE:
new_sensor_value = ((float) value_from_stove) * this->traits.get_step();
break;
case MicroNovaFunctions::STOVE_FUNCTION_POWER_LEVEL:
new_sensor_value = (float) value_from_stove;
break;
default:
break;
}
this->publish_state(new_sensor_value);
}
void MicroNovaNumber::control(float value) {
uint8_t new_number = 0;
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE:
new_number = (uint8_t) (value / this->traits.get_step());
break;
case MicroNovaFunctions::STOVE_FUNCTION_POWER_LEVEL:
new_number = (uint8_t) value;
break;
default:
break;
}
this->micronova_->write_address(this->memory_write_location_, this->memory_address_, new_number);
this->micronova_->update();
}
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,28 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/components/number/number.h"
namespace esphome {
namespace micronova {
class MicroNovaNumber : public number::Number, public MicroNovaSensorListener {
public:
MicroNovaNumber() {}
MicroNovaNumber(MicroNova *m) : MicroNovaSensorListener(m) {}
void dump_config() override { LOG_NUMBER("", "Micronova number", this); }
void control(float value) override;
void request_value_from_stove() override {
this->micronova_->request_address(this->memory_location_, this->memory_address_, this);
}
void process_value_from_stove(int value_from_stove) override;
void set_memory_write_location(uint8_t l) { this->memory_write_location_ = l; }
uint8_t get_memory_write_location() { return this->memory_write_location_; }
protected:
uint8_t memory_write_location_ = 0;
};
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,172 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_REVOLUTIONS_PER_MINUTE,
)
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
UNIT_BAR = "bar"
MicroNovaSensor = micronova_ns.class_("MicroNovaSensor", sensor.Sensor, cg.Component)
CONF_ROOM_TEMPERATURE = "room_temperature"
CONF_FUMES_TEMPERATURE = "fumes_temperature"
CONF_STOVE_POWER = "stove_power"
CONF_FAN_SPEED = "fan_speed"
CONF_WATER_TEMPERATURE = "water_temperature"
CONF_WATER_PRESSURE = "water_pressure"
CONF_MEMORY_ADDRESS_SENSOR = "memory_address_sensor"
CONF_FAN_RPM_OFFSET = "fan_rpm_offset"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_ROOM_TEMPERATURE): sensor.sensor_schema(
MicroNovaSensor,
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x01
)
),
cv.Optional(CONF_FUMES_TEMPERATURE): sensor.sensor_schema(
MicroNovaSensor,
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x5A
)
),
cv.Optional(CONF_STOVE_POWER): sensor.sensor_schema(
MicroNovaSensor,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x34
)
),
cv.Optional(CONF_FAN_SPEED): sensor.sensor_schema(
MicroNovaSensor,
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x37
)
)
.extend(
{cv.Optional(CONF_FAN_RPM_OFFSET, default=0): cv.int_range(min=0, max=255)}
),
cv.Optional(CONF_WATER_TEMPERATURE): sensor.sensor_schema(
MicroNovaSensor,
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x3B
)
),
cv.Optional(CONF_WATER_PRESSURE): sensor.sensor_schema(
MicroNovaSensor,
unit_of_measurement=UNIT_BAR,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x3C
)
),
cv.Optional(CONF_MEMORY_ADDRESS_SENSOR): sensor.sensor_schema(
MicroNovaSensor,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x00
)
),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if room_temperature_config := config.get(CONF_ROOM_TEMPERATURE):
sens = await sensor.new_sensor(room_temperature_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(room_temperature_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(room_temperature_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE))
if fumes_temperature_config := config.get(CONF_FUMES_TEMPERATURE):
sens = await sensor.new_sensor(fumes_temperature_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(fumes_temperature_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(fumes_temperature_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE))
if stove_power_config := config.get(CONF_STOVE_POWER):
sens = await sensor.new_sensor(stove_power_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(stove_power_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(stove_power_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER))
if fan_speed_config := config.get(CONF_FAN_SPEED):
sens = await sensor.new_sensor(fan_speed_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(fan_speed_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(fan_speed_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED))
cg.add(sens.set_fan_speed_offset(fan_speed_config[CONF_FAN_RPM_OFFSET]))
if memory_address_sensor_config := config.get(CONF_MEMORY_ADDRESS_SENSOR):
sens = await sensor.new_sensor(memory_address_sensor_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(
sens.set_memory_location(memory_address_sensor_config[CONF_MEMORY_LOCATION])
)
cg.add(
sens.set_memory_address(memory_address_sensor_config[CONF_MEMORY_ADDRESS])
)
cg.add(
sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR)
)
if water_temperature_config := config.get(CONF_WATER_TEMPERATURE):
sens = await sensor.new_sensor(water_temperature_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(water_temperature_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(water_temperature_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE))
if water_pressure_config := config.get(CONF_WATER_PRESSURE):
sens = await sensor.new_sensor(water_pressure_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(water_pressure_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(water_pressure_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE))

View file

@ -0,0 +1,35 @@
#include "micronova_sensor.h"
namespace esphome {
namespace micronova {
void MicroNovaSensor::process_value_from_stove(int value_from_stove) {
if (value_from_stove == -1) {
this->publish_state(NAN);
return;
}
float new_sensor_value = (float) value_from_stove;
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_ROOM_TEMPERATURE:
new_sensor_value = new_sensor_value / 2;
break;
case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE:
break;
case MicroNovaFunctions::STOVE_FUNCTION_FAN_SPEED:
new_sensor_value = new_sensor_value == 0 ? 0 : (new_sensor_value * 10) + this->fan_speed_offset_;
break;
case MicroNovaFunctions::STOVE_FUNCTION_WATER_TEMPERATURE:
new_sensor_value = new_sensor_value / 2;
break;
case MicroNovaFunctions::STOVE_FUNCTION_WATER_PRESSURE:
new_sensor_value = new_sensor_value / 10;
break;
default:
break;
}
this->publish_state(new_sensor_value);
}
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,27 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace micronova {
class MicroNovaSensor : public sensor::Sensor, public MicroNovaSensorListener {
public:
MicroNovaSensor(MicroNova *m) : MicroNovaSensorListener(m) {}
void dump_config() override { LOG_SENSOR("", "Micronova sensor", this); }
void request_value_from_stove() override {
this->micronova_->request_address(this->memory_location_, this->memory_address_, this);
}
void process_value_from_stove(int value_from_stove) override;
void set_fan_speed_offset(uint8_t f) { this->fan_speed_offset_ = f; }
uint8_t get_set_fan_speed_offset() { return this->fan_speed_offset_; }
protected:
int fan_speed_offset_ = 0;
};
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,56 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
from esphome.const import (
ICON_POWER,
)
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
CONF_STOVE = "stove"
CONF_MEMORY_DATA_ON = "memory_data_on"
CONF_MEMORY_DATA_OFF = "memory_data_off"
MicroNovaSwitch = micronova_ns.class_("MicroNovaSwitch", switch.Switch, cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_STOVE): switch.switch_schema(
MicroNovaSwitch,
icon=ICON_POWER,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x80, default_memory_address=0x21
)
)
.extend(
{
cv.Optional(CONF_MEMORY_DATA_OFF, default=0x06): cv.hex_int_range(),
cv.Optional(CONF_MEMORY_DATA_ON, default=0x01): cv.hex_int_range(),
}
),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if stove_config := config.get(CONF_STOVE):
sw = await switch.new_switch(stove_config, mv)
cg.add(mv.set_stove(sw))
cg.add(sw.set_memory_location(stove_config[CONF_MEMORY_LOCATION]))
cg.add(sw.set_memory_address(stove_config[CONF_MEMORY_ADDRESS]))
cg.add(sw.set_memory_data_on(stove_config[CONF_MEMORY_DATA_ON]))
cg.add(sw.set_memory_data_off(stove_config[CONF_MEMORY_DATA_OFF]))
cg.add(sw.set_function(MicroNovaFunctions.STOVE_FUNCTION_SWITCH))

View file

@ -0,0 +1,33 @@
#include "micronova_switch.h"
namespace esphome {
namespace micronova {
void MicroNovaSwitch::write_state(bool state) {
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_SWITCH:
if (state) {
// Only send power-on when current state is Off
if (this->micronova_->get_current_stove_state() == 0) {
this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_on_);
this->publish_state(true);
} else
ESP_LOGW(TAG, "Unable to turn stove on, invalid state: %d", micronova_->get_current_stove_state());
} else {
// don't send power-off when status is Off or Final cleaning
if (this->micronova_->get_current_stove_state() != 0 && micronova_->get_current_stove_state() != 6) {
this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_off_);
this->publish_state(false);
} else
ESP_LOGW(TAG, "Unable to turn stove off, invalid state: %d", micronova_->get_current_stove_state());
}
this->micronova_->update();
break;
default:
break;
}
}
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,29 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/core/component.h"
#include "esphome/components/switch/switch.h"
namespace esphome {
namespace micronova {
class MicroNovaSwitch : public Component, public switch_::Switch, public MicroNovaSwitchListener {
public:
MicroNovaSwitch(MicroNova *m) : MicroNovaSwitchListener(m) {}
void dump_config() override { LOG_SWITCH("", "Micronova switch", this); }
void set_stove_state(bool v) override { this->publish_state(v); }
bool get_stove_state() override { return this->state; }
void set_memory_data_on(uint8_t f) { this->memory_data_on_ = f; }
uint8_t get_memory_data_on() { return this->memory_data_on_; }
void set_memory_data_off(uint8_t f) { this->memory_data_off_ = f; }
uint8_t get_memory_data_off() { return this->memory_data_off_; }
protected:
void write_state(bool state) override;
};
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,43 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
CONF_STOVE_STATE = "stove_state"
MicroNovaTextSensor = micronova_ns.class_(
"MicroNovaTextSensor", text_sensor.TextSensor, cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_STOVE_STATE): text_sensor.text_sensor_schema(
MicroNovaTextSensor
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x21
)
),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if stove_state_config := config.get(CONF_STOVE_STATE):
sens = await text_sensor.new_text_sensor(stove_state_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(stove_state_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(stove_state_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE))

View file

@ -0,0 +1,31 @@
#include "micronova_text_sensor.h"
namespace esphome {
namespace micronova {
void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) {
if (value_from_stove == -1) {
this->publish_state("unknown");
return;
}
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_STOVE_STATE:
this->micronova_->set_current_stove_state(value_from_stove);
this->publish_state(STOVE_STATES[value_from_stove]);
// set the stove switch to on for any value but 0
if (value_from_stove != 0 && this->micronova_->get_stove_switch() != nullptr &&
!this->micronova_->get_stove_switch()->get_stove_state()) {
this->micronova_->get_stove_switch()->set_stove_state(true);
} else if (value_from_stove == 0 && this->micronova_->get_stove_switch() != nullptr &&
this->micronova_->get_stove_switch()->get_stove_state()) {
this->micronova_->get_stove_switch()->set_stove_state(false);
}
break;
default:
break;
}
}
} // namespace micronova
} // namespace esphome

View file

@ -0,0 +1,20 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/components/text_sensor/text_sensor.h"
namespace esphome {
namespace micronova {
class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaSensorListener {
public:
MicroNovaTextSensor(MicroNova *m) : MicroNovaSensorListener(m) {}
void dump_config() override { LOG_TEXT_SENSOR("", "Micronova text sensor", this); }
void request_value_from_stove() override {
this->micronova_->request_address(this->memory_location_, this->memory_address_, this);
}
void process_value_from_stove(int value_from_stove) override;
};
} // namespace micronova
} // namespace esphome

View file

@ -342,6 +342,10 @@ mcp23s17:
cs_pin: GPIO12
deviceaddress: 1
micronova:
enable_rx_pin: 4
uart_id: uart_0
dfrobot_sen0395:
- id: mmwave
uart_id: dfrobot_mmwave_uart
@ -1518,6 +1522,24 @@ sensor:
field_strength_z:
name: "Magnet Z"
id: magnet_z
- platform: micronova
room_temperature:
name: Room Temperature
fumes_temperature:
name: Fumes Temperature
water_temperature:
name: Water temperature
water_pressure:
name: Water pressure
stove_power:
name: Stove Power
fan_speed:
fan_rpm_offset: 240
name: Fan RPM
memory_address_sensor:
memory_location: 0x20
memory_address: 0x7d
name: Adres sensor
esp32_touch:
setup_mode: false
@ -2705,6 +2727,9 @@ switch:
name: "control ld2410 engineering mode"
bluetooth:
name: "control ld2410 bluetooth"
- platform: micronova
stove:
name: Stove on/off
fan:
- platform: binary
@ -3489,6 +3514,12 @@ number:
name: g8 move threshold
still_threshold:
name: g8 still threshold
- platform: micronova
thermostat_temperature:
name: Micronova Thermostaat
step: 1
power_level:
name: Micronova Power level
select:
- platform: template
@ -3619,6 +3650,12 @@ button:
uart_id: uart_0
name: UART button
data: "Pressed\r\n"
- platform: micronova
custom_button:
name: Custom Micronova Button
memory_location: 0xA0
memory_address: 0x7D
memory_data: 0x0F
ld2410:
id: my_ld2410
@ -3727,3 +3764,4 @@ alarm_control_panel:
then:
- lambda: !lambda |-
ESP_LOGD("TEST", "State change %s", alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()));