8static const char *
const TAG =
"hoermann_hcp";
11static constexpr uint16_t COMMAND_REG = 0x9C41;
12static constexpr uint16_t STATE_REG = 0x9CB9;
13static constexpr uint16_t BROADCAST_REG = 0x9D31;
14static constexpr float CLOSE_POSITION_THRESHOLD = 0.05f;
15static constexpr float OPEN_POSITION_THRESHOLD = 0.95f;
17static constexpr uint8_t MAX_LIGHT_TOGGLES_IN_FLIGHT = 4;
22static constexpr HoermannHcpCommand COMMAND_OPEN{
"open", 0x0210, 0x0110};
23static constexpr HoermannHcpCommand COMMAND_CLOSE{
"close", 0x0220, 0x0120};
24static constexpr HoermannHcpCommand COMMAND_IMPULSE{
"impulse", 0x0240, 0x0140};
26static constexpr HoermannHcpCommand COMMAND_VENT{
"vent", 0x0200, 0x0100, 0x4000, 0x4000};
27static constexpr HoermannHcpCommand COMMAND_HALF_OPEN{
"half open", 0x0200, 0x0100, 0x0400, 0x0400};
29static constexpr HoermannHcpCommand COMMAND_TOGGLE_LAMP{
"toggle light", 0x0100, 0x0800, 0x0200, 0x0200,
false};
33struct DoorStateMapping {
37static constexpr DoorStateMapping DOOR_STATE_MAPPINGS[] = {
46 for (uint16_t i = 0; i < count; i++)
47 registers.push_back(0x0000);
74 ESP_LOGW(TAG,
"Bus controller stopped polling during '%s' command, dropping it mid key press",
77 ESP_LOGW(TAG,
"Bus controller did not fetch '%s' command, dropping it", this->
next_command_->
name);
86 ESP_LOGW(TAG,
"Door did not start moving towards the requested position, dropping it");
91 ESP_LOGW(TAG,
"Door did not report the lamp changing, giving up on the toggle");
102 "Hoermann HCP bridge:\n"
103 " Modbus server address: 0x%02X",
109 if (start_address != STATE_REG) {
110 ESP_LOGW(TAG,
"Unknown read address 0x%04X", start_address);
119 const uint16_t command =
static_cast<uint16_t
>((this->
command_reg_value_ & 0x00FF) << 8);
121 switch (number_of_registers) {
125 registers.
push_back(
static_cast<uint16_t
>(0x0001 | command));
127 push_zeros(registers, 4);
131 registers.
push_back(
static_cast<uint16_t
>(0x0004 | counter));
136 ESP_LOGD(TAG,
"Bus scan received from bus controller");
138 registers.
push_back(
static_cast<uint16_t
>(0x0005 | command));
144 ESP_LOGW(TAG,
"Unknown read request (read %u registers)", number_of_registers);
145 push_zeros(registers, number_of_registers);
154 if (start_address == COMMAND_REG) {
162 if (start_address != BROADCAST_REG) {
164 ESP_LOGV(TAG,
"Ignoring write to address 0x%04X", start_address);
172 if (registers.
size() > 2)
174 if (registers.
size() > 1)
176 if (registers.
size() > 6) {
184 ESP_LOGD(TAG,
"Broadcast of %u registers carries no lamp state",
static_cast<unsigned>(registers.
size()));
191 if (command ==
nullptr) {
192 push_zeros(registers, 2);
198 ESP_LOGI(TAG,
"Sending '%s' command to door", command->
name);
205 push_zeros(registers, 2);
209 ESP_LOGD(TAG,
"Released '%s' command", command->
name);
222 const uint8_t
position =
static_cast<uint8_t
>(value);
244 if (previous == value)
247 const uint8_t
state = value >> 8;
253 for (
const auto &mapping : DOOR_STATE_MAPPINGS) {
254 if (mapping.code ==
state) {
260 if (
state != (previous >> 8)) {
261 ESP_LOGW(TAG,
"Unknown door state 0x%02X",
state);
275 ESP_LOGW(TAG,
"Not connected to the bus controller, dropping '%s' command", command.
name);
279 ESP_LOGW(TAG,
"Previous command not yet fetched by the bus controller");
297 ESP_LOGW(TAG,
"Too many lamp toggles are still waiting to be confirmed, dropping this one");
316 ESP_LOGD(TAG,
"Cancelling '%s' command the controller had not fetched", this->
next_command_->
name);
332 if (
position <= CLOSE_POSITION_THRESHOLD)
334 if (position >= OPEN_POSITION_THRESHOLD)
342 if (!this->
queue_command_(opening ? COMMAND_OPEN : COMMAND_CLOSE))
358 if (this->
valid_ == valid)
363 ESP_LOGI(TAG,
"Bus controller connected");
366 ESP_LOGW(TAG,
"Bus controller connection lost (no request for %" PRIu32
"ms)",
millis() - this->
last_response_);
382 if (was_light_toggle) {
455 ESP_LOGD(TAG,
"Lamp %s at the door", ONOFF(on));
Minimal static vector - saves memory by avoiding std::vector overhead.
void push_back(const T &value)
void on_light_reg_(uint16_t value)
void set_door_state_(DoorState state)
void forget_light_toggles_()
bool set_position(float position)
void dump_config() override
void on_state_reg_(uint16_t value)
void set_valid_(bool valid)
modbus::ResponseStatus on_read_holding_registers(uint16_t start_address, uint16_t number_of_registers, modbus::RegisterValues ®isters) override
CallbackManager< void()> state_callback_
void set_light_seen_(bool seen)
void update_current_position_()
void set_light_on_(bool on)
uint16_t command_reg_value_
modbus::ResponseStatus on_write_registers(uint16_t start_address, const modbus::RegisterValues ®isters) override
uint16_t connection_timeout_ms_
const HoermannHcpCommand * next_command_
uint16_t key_press_delay_ms_
bool is_light_toggle_pending_() const
void push_command_registers_(modbus::RegisterValues ®isters)
uint32_t command_written_at_
uint8_t unsent_light_toggles_() const
bool cancel_light_toggle()
uint8_t light_toggles_in_flight_
bool short_broadcast_logged_
uint32_t target_queued_at_
void light_toggle_settled_()
DoorState target_direction_
uint32_t light_toggle_released_at_
bool queue_command_(const HoermannHcpCommand &command)
void on_position_reg_(uint16_t value)
uint32_t command_queued_at_
uint8_t get_address() const
StaticVector< uint16_t, MAX_NUM_OF_REGISTERS_TO_READ > RegisterValues
std::optional< ExceptionCode > ResponseStatus
uint32_t IRAM_ATTR HOT millis()
uint16_t released_value_2