diff --git a/components/tc_bus/__init__.py b/components/tc_bus/__init__.py index 570ea30..fed06f5 100644 --- a/components/tc_bus/__init__.py +++ b/components/tc_bus/__init__.py @@ -14,6 +14,10 @@ "TCBusSendAction", automation.Action ) +TCBusUpdateSettingAction = tc_bus_ns.class_( + "TCBusUpdateSettingAction", automation.Action +) + TCBusProgrammingModeAction = tc_bus_ns.class_( "TCBusProgrammingModeAction", automation.Action ) @@ -22,11 +26,21 @@ "TCBusReadMemoryAction", automation.Action ) +CommandData = tc_bus_ns.struct(f"CommandData") +SettingData = tc_bus_ns.struct(f"SettingData") + ReadMemoryCompleteTrigger = tc_bus_ns.class_("ReadMemoryCompleteTrigger", automation.Trigger.template()) ReadMemoryTimeoutTrigger = tc_bus_ns.class_("ReadMemoryTimeoutTrigger", automation.Trigger.template()) - ReceivedCommandTrigger = tc_bus_ns.class_("ReceivedCommandTrigger", automation.Trigger.template()) -CommandData = tc_bus_ns.struct(f"CommandData") + +SettingType = tc_bus_ns.enum("Setting_Type") +SETTING_TYPES = { + "ringtone_floor_call": SettingType.SETTING_RINGTONE_FLOOR_CALL, + "ringtone_door_call": SettingType.SETTING_RINGTONE_DOOR_CALL, + "ringtone_internal_call": SettingType.SETTING_RINGTONE_INTERNAL_CALL, + "volume_ringtone": SettingType.SETTING_RINGTONE_VOLUME, + "volume_handset": SettingType.SETTING_HANDSET_VOLUME +} CommandType = tc_bus_ns.enum("Command_Type") COMMAND_TYPES = { @@ -55,7 +69,7 @@ "programming_mode": CommandType.COMMAND_TYPE_PROGRAMMING_MODE, "read_memory_block": CommandType.COMMAND_TYPE_READ_MEMORY_BLOCK, "select_memory_page": CommandType.COMMAND_TYPE_SELECT_MEMORY_PAGE, - "write_memory": CommandType.COMMAND_TYPE_WRITE_MEMORY, + "write_memory": CommandType.COMMAND_TYPE_WRITE_MEMORY } CONF_TC_ID = "tc_bus" @@ -73,6 +87,8 @@ CONF_PAYLOAD = "payload" CONF_PAYLOAD_LAMBDA = "payload_lambda" +CONF_VALUE = "value" + CONF_BUS_COMMAND = "bus_command" CONF_HARDWARE_VERSION = "hardware_version" CONF_DOOR_READINESS = "door_readiness" @@ -230,6 +246,41 @@ async def tc_bus_send_to_code(config, action_id, template_args, args): return var + +TC_BUS_UPDATE_SETTING_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.use_id(TCBus), + cv.Required(CONF_TYPE): cv.templatable(cv.enum(SETTING_TYPES, upper=False)), + cv.Required(CONF_ADDRESS): cv.templatable(cv.hex_uint8_t), + cv.Required(CONF_SERIAL_NUMBER): cv.templatable(cv.hex_uint32_t), + }) +) + +@automation.register_action( + "tc_bus.update_setting", + TCBusUpdateSettingAction, + TC_BUS_UPDATE_SETTING_SCHEMA +) +async def tc_bus_update_setting_to_code(config, action_id, template_args, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_args, parent) + + if CONF_TYPE in config: + type_template_ = await cg.templatable(config[CONF_TYPE], args, SettingType) + cg.add(var.set_type(type_template_)) + + if CONF_VALUE in config: + value_template_ = await cg.templatable(config[CONF_VALUE], args, cg.uint8) + cg.add(var.set_value(value_template_)) + + if CONF_SERIAL_NUMBER in config: + serial_number_template_ = await cg.templatable(config[CONF_SERIAL_NUMBER], args, cg.uint32) + cg.add(var.set_serial_number(serial_number_template_)) + + return var + + @automation.register_action( "tc_bus.set_programming_mode", TCBusProgrammingModeAction, diff --git a/components/tc_bus/automation.h b/components/tc_bus/automation.h index a14d77b..d4e2192 100644 --- a/components/tc_bus/automation.h +++ b/components/tc_bus/automation.h @@ -35,6 +35,23 @@ namespace esphome TCBusComponent *parent_; }; + template class TCBusUpdateSettingAction : public Action + { + public: + TCBusUpdateSettingAction(TCBusComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(SettingType, type) + TEMPLATABLE_VALUE(uint32_t, serial_number) + TEMPLATABLE_VALUE(uint8_t, value) + + void play(Ts... x) + { + this->parent_->update_setting(this->type_.value(x...), this->value_.value(x...), this->serial_number_.value(x...)); + } + + protected: + TCBusComponent *parent_; + }; + template class TCBusProgrammingModeAction : public Action { public: diff --git a/components/tc_bus/memory.h b/components/tc_bus/memory.h new file mode 100644 index 0000000..e4a03be --- /dev/null +++ b/components/tc_bus/memory.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/hal.h" + +namespace esphome +{ + namespace tc_bus + { + enum SettingType { + SETTING_RINGTONE_FLOOR_CALL, + SETTING_RINGTONE_DOOR_CALL, + SETTING_RINGTONE_INTERNAL_CALL, + SETTING_RINGTONE_VOLUME, + SETTING_HANDSET_VOLUME + }; + + struct Settings { + uint8_t handset_volume = 4; + uint8_t ringtone_volume = 4; + uint8_t door_call_ringtone = 0; + uint8_t internal_call_ringtone = 0; + uint8_t floor_call_ringtone = 0; + }; + } +} \ No newline at end of file diff --git a/components/tc_bus/protocol.h b/components/tc_bus/protocol.h index 4cebcb2..a75ef02 100644 --- a/components/tc_bus/protocol.h +++ b/components/tc_bus/protocol.h @@ -35,6 +35,14 @@ namespace esphome COMMAND_TYPE_WRITE_MEMORY }; + enum SettingType { + SETTING_RINGTONE_FLOOR_CALL, + SETTING_RINGTONE_DOOR_CALL, + SETTING_RINGTONE_INTERNAL_CALL, + SETTING_RINGTONE_VOLUME, + SETTING_HANDSET_VOLUME + }; + struct CommandData { uint32_t command; std::string command_hex; diff --git a/components/tc_bus/tc_bus.cpp b/components/tc_bus/tc_bus.cpp index 9fe7bc5..e663c7e 100644 --- a/components/tc_bus/tc_bus.cpp +++ b/components/tc_bus/tc_bus.cpp @@ -187,6 +187,13 @@ namespace esphome reading_memory_ = false; reading_memory_timer_ = 0; + // Save Settings values + settings->handset_volume = memory_buffer_[21] & 0xF; + settings->ringtone_volume = memory_buffer_[20] & 0xF; + settings->door_call_ringtone = (memory_buffer_[3] >> 4) & 0xF; + settings->internal_call_ringtone = (memory_buffer_[6] >> 4) & 0xF; + settings->floor_call_ringtone = (memory_buffer_[9] >> 4) & 0xF; + this->read_memory_complete_callback_.call(memory_buffer_); } else @@ -616,5 +623,128 @@ namespace esphome send_command(COMMAND_TYPE_READ_MEMORY_BLOCK, reading_memory_count_); } + void TCBusComponent::update_setting(SettingType type, uint8_t new_value, uint32_t serial_number) + { + if(memory_buffer_.size() == 0) + { + ESP_LOGW(TAG, "Memory Buffer is empty! Please read memory first!"); + return; + } + + if(serial_number == 0) + { + serial_number = this->serial_number_; + if (serial_number_lambda_.has_value()) { + auto optional_value = (*serial_number_lambda_)(); + if (optional_value.has_value()) { + serial_number = optional_value.value(); + } + } + } + + if(serial_number == 0) + { + ESP_LOGW(TAG, "Serial Number is not set!"); + return; + } + + // Prepare Transmission + ESP_LOGD(TAG, "Select Indoor Stations"); + send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, 0); // payload 0 = indoor stations + delay(50); + + ESP_LOGD(TAG, "Select Memory Page %i of Serial Number %i", 0, serial_number); + send_command(COMMAND_TYPE_SELECT_MEMORY_PAGE, 0, 0, serial_number); + delay(50); + + uint8_t index = 0; + + switch(type) + { + case SETTING_RINGTONE_DOOR_CALL: + index = 3; + uint8_t cell2 = memory_buffer_[index] & 0xF; + memory_buffer_[index] = (new_value << 4) | (cell2 & 0xF); + settings->door_call_ringtone = new_value; + break; + + case SETTING_RINGTONE_INTERNAL_CALL: + index = 6; + uint8_t cell2 = memory_buffer_[index] & 0xF; + memory_buffer_[index] = (new_value << 4) | (cell2 & 0xF); + settings->internal_call_ringtone = new_value; + break; + + case SETTING_RINGTONE_FLOOR_CALL: + index = 9; + uint8_t cell2 = memory_buffer_[index] & 0xF; + memory_buffer_[index] = (new_value << 4) | (cell2 & 0xF); + settings->floor_call_ringtone = new_value; + break; + + case SETTING_RINGTONE_VOLUME: + index = 20; + uint8_t cell1 = (memory_buffer_[index] >> 4) & 0xF; + memory_buffer_[index] = (cell1 << 4) | (new_value & 0xF); + settings->ringtone_volume = new_value; + break; + + case SETTING_HANDSET_VOLUME: + index = 21; + uint8_t cell1 = (memory_buffer_[index] >> 4) & 0xF; + memory_buffer_[index] = (cell1 << 4) | (new_value & 0xF); + settings->handset_volume = new_value; + break; + } + + uint16_t new_values = (memory_buffer_[index] << 8) | memory_buffer_[index + 1]; + send_command(COMMAND_TYPE_WRITE_MEMORY, index, new_values, serial_number); + } + + void TCBusComponent::write_memory(uint32_t serial_number) + { + if(memory_buffer_.size() == 0) + { + ESP_LOGW(TAG, "Memory Buffer is empty! Please read memory first!"); + return; + } + + if(serial_number == 0) + { + serial_number = this->serial_number_; + if (serial_number_lambda_.has_value()) { + auto optional_value = (*serial_number_lambda_)(); + if (optional_value.has_value()) { + serial_number = optional_value.value(); + } + } + } + + if(serial_number == 0) + { + ESP_LOGW(TAG, "Serial Number is not set!"); + return; + } + + // Prepare Transmission + ESP_LOGD(TAG, "Select Indoor Stations"); + send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, 0); // payload 0 = indoor stations + delay(50); + + ESP_LOGD(TAG, "Select Memory Page %i of Serial Number %i", 0, serial_number); + send_command(COMMAND_TYPE_SELECT_MEMORY_PAGE, 0, 0, serial_number); + delay(50); + + // Transmit Memory + uint8_t address = 0; + for (size_t i = 0; i < memory_buffer_.size(); i += 2) + { + uint16_t new_value = (memory_buffer_[i] << 8) | memory_buffer_[i + 1]; + send_command(COMMAND_TYPE_WRITE_MEMORY, address, new_value, serial_number); + address = address + 2; + delay(50); + } + } + } // namespace tc_bus } // namespace esphome \ No newline at end of file diff --git a/components/tc_bus/tc_bus.h b/components/tc_bus/tc_bus.h index 56073bc..51f46f5 100644 --- a/components/tc_bus/tc_bus.h +++ b/components/tc_bus/tc_bus.h @@ -1,6 +1,7 @@ #pragma once #include "protocol.h" +#include "memory.h" #include #include @@ -90,6 +91,10 @@ namespace esphome void read_memory(uint32_t serial_number); void request_memory_blocks(uint8_t start_address); + void write_memory(uint32_t serial_number = 0); + + void update_setting(SettingType type, uint8_t new_value, uint32_t serial_number = 0); + void publish_command(uint32_t command, bool fire_events); void add_received_command_callback(std::function &&callback); @@ -125,6 +130,8 @@ namespace esphome uint8_t reading_memory_count_ = 0; uint32_t reading_memory_timer_ = 0; std::vector memory_buffer_; + + Settings settings; }; } // namespace tc_bus diff --git a/firmware/addons/memory-utils.yaml b/firmware/addons/memory-utils.yaml new file mode 100644 index 0000000..56a69a1 --- /dev/null +++ b/firmware/addons/memory-utils.yaml @@ -0,0 +1,19 @@ +# Memory Utils Addon + +# Extend TC:BUS Component +tc_bus: + on_read_memory_complete: + - logger.log: "Completed memory reading!" + - lambda: |- + std::string hexString = str_upper_case(format_hex(x)); + ESP_LOGD("tcs_bus", "Memory: %s", hexString.c_str()); + + on_read_memory_timeout: + - logger.log: "Failed to read Memory" + + +button: + - platform: template + name: "Read Memory" + on_press: + - tc_bus.read_memory: \ No newline at end of file diff --git a/firmware/doorman-nuki-bridge.yaml b/firmware/doorman-nuki-bridge.yaml index c62640e..a9234aa 100644 --- a/firmware/doorman-nuki-bridge.yaml +++ b/firmware/doorman-nuki-bridge.yaml @@ -6,6 +6,7 @@ packages: device_base: !include base.yaml pattern_events: !include addons/pattern-events.yaml ring_to_open: !include addons/ring-to-open.yaml + memory_utils: !include addons/memory-utils.yaml addon_nuki_bridge: !include addons/nuki-bridge.yaml interactive_setup: !include addons/interactive-setup.yaml diff --git a/firmware/doorman-stock.yaml b/firmware/doorman-stock.yaml index 199f46a..b4667a0 100644 --- a/firmware/doorman-stock.yaml +++ b/firmware/doorman-stock.yaml @@ -6,6 +6,7 @@ packages: device_base: !include base.yaml pattern_events: !include addons/pattern-events.yaml ring_to_open: !include addons/ring-to-open.yaml + memory_utils: !include addons/memory-utils.yaml interactive_setup: !include addons/interactive-setup.yaml esp32_improv: