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_TOGGLE_LAMP{
"toggle light", 0x0100, 0x0800, 0x0200, 0x0200,
false};
30struct DoorStateMapping {
34static constexpr DoorStateMapping DOOR_STATE_MAPPINGS[] = {
43 for (uint16_t i = 0; i < count; i++)
44 registers.push_back(0x0000);
71 ESP_LOGW(TAG,
"Bus controller stopped polling during '%s' command, dropping it mid key press",
74 ESP_LOGW(TAG,
"Bus controller did not fetch '%s' command, dropping it", this->
next_command_->
name);
83 ESP_LOGW(TAG,
"Door did not start moving towards the requested position, dropping it");
88 ESP_LOGW(TAG,
"Door did not report the lamp changing, giving up on the toggle");
99 "Hoermann HCP bridge:\n"
100 " Modbus server address: 0x%02X",
106 if (start_address != STATE_REG) {
107 ESP_LOGW(TAG,
"Unknown read address 0x%04X", start_address);
116 const uint16_t command =
static_cast<uint16_t
>((this->
command_reg_value_ & 0x00FF) << 8);
118 switch (number_of_registers) {
122 registers.
push_back(
static_cast<uint16_t
>(0x0001 | command));
124 push_zeros(registers, 4);
128 registers.
push_back(
static_cast<uint16_t
>(0x0004 | counter));
133 ESP_LOGD(TAG,
"Bus scan received from bus controller");
135 registers.
push_back(
static_cast<uint16_t
>(0x0005 | command));
141 ESP_LOGW(TAG,
"Unknown read request (read %u registers)", number_of_registers);
142 push_zeros(registers, number_of_registers);
151 if (start_address == COMMAND_REG) {
159 if (start_address != BROADCAST_REG) {
161 ESP_LOGV(TAG,
"Ignoring write to address 0x%04X", start_address);
169 if (registers.
size() > 2)
171 if (registers.
size() > 1)
173 if (registers.
size() > 6) {
181 ESP_LOGD(TAG,
"Broadcast of %u registers carries no lamp state",
static_cast<unsigned>(registers.
size()));
188 if (command ==
nullptr) {
189 push_zeros(registers, 2);
195 ESP_LOGI(TAG,
"Sending '%s' command to door", command->
name);
202 push_zeros(registers, 2);
206 ESP_LOGD(TAG,
"Released '%s' command", command->
name);
219 const uint8_t
position =
static_cast<uint8_t
>(value);
241 if (previous == value)
244 const uint8_t
state = value >> 8;
250 for (
const auto &mapping : DOOR_STATE_MAPPINGS) {
251 if (mapping.code ==
state) {
257 if (
state != (previous >> 8))
258 ESP_LOGW(TAG,
"Unknown door state 0x%02X",
state);
271 ESP_LOGW(TAG,
"Not connected to the bus controller, dropping '%s' command", command.
name);
275 ESP_LOGW(TAG,
"Previous command not yet fetched by the bus controller");
291 ESP_LOGW(TAG,
"Too many lamp toggles are still waiting to be confirmed, dropping this one");
310 ESP_LOGD(TAG,
"Cancelling '%s' command the controller had not fetched", this->
next_command_->
name);
326 if (
position <= CLOSE_POSITION_THRESHOLD)
328 if (position >= OPEN_POSITION_THRESHOLD)
336 if (!this->
queue_command_(opening ? COMMAND_OPEN : COMMAND_CLOSE))
352 if (this->
valid_ == valid)
357 ESP_LOGI(TAG,
"Bus controller connected");
360 ESP_LOGW(TAG,
"Bus controller connection lost (no request for %" PRIu32
"ms)",
millis() - this->
last_response_);
376 if (was_light_toggle) {
449 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