mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'dev' into jesserockz-2022-199
This commit is contained in:
		| @@ -27,7 +27,7 @@ repos: | ||||
|           - --branch=release | ||||
|           - --branch=beta | ||||
|   - repo: https://github.com/asottile/pyupgrade | ||||
|     rev: v3.3.1 | ||||
|     rev: v3.3.2 | ||||
|     hooks: | ||||
|       - id: pyupgrade | ||||
|         args: [--py39-plus] | ||||
|   | ||||
| @@ -110,6 +110,7 @@ esphome/components/honeywellabp/* @RubyBailey | ||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||
| esphome/components/hte501/* @Stock-M | ||||
| esphome/components/hydreon_rgxx/* @functionpointer | ||||
| esphome/components/hyt271/* @Philippe12 | ||||
| esphome/components/i2c/* @esphome/core | ||||
| esphome/components/i2s_audio/* @jesserockz | ||||
| esphome/components/i2s_audio/media_player/* @jesserockz | ||||
| @@ -139,6 +140,7 @@ esphome/components/ltr390/* @sjtrny | ||||
| esphome/components/matrix_keypad/* @ssieb | ||||
| esphome/components/max31865/* @DAVe3283 | ||||
| esphome/components/max44009/* @berfenger | ||||
| esphome/components/max6956/* @looping40 | ||||
| esphome/components/max7219digit/* @rspaargaren | ||||
| esphome/components/max9611/* @mckaymatthew | ||||
| esphome/components/mcp23008/* @jesserockz | ||||
| @@ -188,6 +190,7 @@ esphome/components/nfc/* @jesserockz | ||||
| esphome/components/number/* @esphome/core | ||||
| esphome/components/ota/* @esphome/core | ||||
| esphome/components/output/* @esphome/core | ||||
| esphome/components/pca6416a/* @Mat931 | ||||
| esphome/components/pca9554/* @hwstar | ||||
| esphome/components/pcf85063/* @brogon | ||||
| esphome/components/pid/* @OttoWinter | ||||
|   | ||||
| @@ -26,7 +26,7 @@ RUN \ | ||||
|         python3-cryptography=3.3.2-1 \ | ||||
|         python3-venv=3.9.2-3 \ | ||||
|         iputils-ping=3:20210202-1 \ | ||||
|         git=1:2.30.2-1 \ | ||||
|         git=1:2.30.2-1+deb11u2 \ | ||||
|         curl=7.74.0-1.3+deb11u7 \ | ||||
|         openssh-client=1:8.4p1-5+deb11u1 \ | ||||
|     && rm -rf \ | ||||
| @@ -63,7 +63,7 @@ RUN \ | ||||
| COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / | ||||
| RUN \ | ||||
|     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini | ||||
|     && /platformio_install_deps.py /platformio.ini --libraries | ||||
|  | ||||
|  | ||||
| # ======================= docker-type image ======================= | ||||
|   | ||||
| @@ -429,15 +429,16 @@ void APIServer::on_shutdown() { | ||||
|  | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
| bool APIServer::start_voice_assistant() { | ||||
|   bool result = false; | ||||
|   for (auto &c : this->clients_) { | ||||
|     result |= c->request_voice_assistant(true); | ||||
|     if (c->request_voice_assistant(true)) | ||||
|       return true; | ||||
|   } | ||||
|   return result; | ||||
|   return false; | ||||
| } | ||||
| void APIServer::stop_voice_assistant() { | ||||
|   for (auto &c : this->clients_) { | ||||
|     c->request_voice_assistant(false); | ||||
|     if (c->request_voice_assistant(false)) | ||||
|       return; | ||||
|   } | ||||
| } | ||||
| #endif | ||||
|   | ||||
| @@ -18,5 +18,5 @@ async def to_code(config): | ||||
|         # https://github.com/esphome/AsyncTCP/blob/master/library.json | ||||
|         cg.add_library("esphome/AsyncTCP-esphome", "1.2.2") | ||||
|     elif CORE.is_esp8266: | ||||
|         # https://github.com/OttoWinter/ESPAsyncTCP | ||||
|         cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3") | ||||
|         # https://github.com/esphome/ESPAsyncTCP | ||||
|         cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3") | ||||
|   | ||||
| @@ -29,8 +29,35 @@ BLEClientConnectTrigger = ble_client_ns.class_( | ||||
| BLEClientDisconnectTrigger = ble_client_ns.class_( | ||||
|     "BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef) | ||||
| ) | ||||
| BLEClientPasskeyRequestTrigger = ble_client_ns.class_( | ||||
|     "BLEClientPasskeyRequestTrigger", automation.Trigger.template(BLEClientNodeConstRef) | ||||
| ) | ||||
| BLEClientPasskeyNotificationTrigger = ble_client_ns.class_( | ||||
|     "BLEClientPasskeyNotificationTrigger", | ||||
|     automation.Trigger.template(BLEClientNodeConstRef, cg.uint32), | ||||
| ) | ||||
| BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_( | ||||
|     "BLEClientNumericComparisonRequestTrigger", | ||||
|     automation.Trigger.template(BLEClientNodeConstRef, cg.uint32), | ||||
| ) | ||||
|  | ||||
| # Actions | ||||
| BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action) | ||||
| BLEPasskeyReplyAction = ble_client_ns.class_( | ||||
|     "BLEClientPasskeyReplyAction", automation.Action | ||||
| ) | ||||
| BLENumericComparisonReplyAction = ble_client_ns.class_( | ||||
|     "BLEClientNumericComparisonReplyAction", automation.Action | ||||
| ) | ||||
| BLERemoveBondAction = ble_client_ns.class_( | ||||
|     "BLEClientRemoveBondAction", automation.Action | ||||
| ) | ||||
|  | ||||
| CONF_PASSKEY = "passkey" | ||||
| CONF_ACCEPT = "accept" | ||||
| CONF_ON_PASSKEY_REQUEST = "on_passkey_request" | ||||
| CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" | ||||
| CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" | ||||
|  | ||||
| # Espressif platformio framework is built with MAX_BLE_CONN to 3, so | ||||
| # enforce this in yaml checks. | ||||
| @@ -56,6 +83,29 @@ CONFIG_SCHEMA = ( | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_PASSKEY_REQUEST): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
|                         BLEClientPasskeyRequestTrigger | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_PASSKEY_NOTIFICATION): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
|                         BLEClientPasskeyNotificationTrigger | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional( | ||||
|                 CONF_ON_NUMERIC_COMPARISON_REQUEST | ||||
|             ): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
|                         BLEClientNumericComparisonRequestTrigger | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| @@ -85,13 +135,34 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema( | ||||
|     } | ||||
| ) | ||||
|  | ||||
| BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||
|         cv.Required(CONF_ACCEPT): cv.templatable(cv.boolean), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| BLE_PASSKEY_REPLY_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||
|         cv.Required(CONF_PASSKEY): cv.templatable(cv.int_range(min=0, max=999999)), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA | ||||
| ) | ||||
| async def ble_write_to_code(config, action_id, template_arg, args): | ||||
|     paren = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|  | ||||
|     value = config[CONF_VALUE] | ||||
|     if cg.is_template(value): | ||||
| @@ -137,6 +208,54 @@ async def ble_write_to_code(config, action_id, template_arg, args): | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.numeric_comparison_reply", | ||||
|     BLENumericComparisonReplyAction, | ||||
|     BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA, | ||||
| ) | ||||
| async def numeric_comparison_reply_to_code(config, action_id, template_arg, args): | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|  | ||||
|     accept = config[CONF_ACCEPT] | ||||
|     if cg.is_template(accept): | ||||
|         templ = await cg.templatable(accept, args, cg.bool_) | ||||
|         cg.add(var.set_value_template(templ)) | ||||
|     else: | ||||
|         cg.add(var.set_value_simple(accept)) | ||||
|  | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.passkey_reply", BLEPasskeyReplyAction, BLE_PASSKEY_REPLY_ACTION_SCHEMA | ||||
| ) | ||||
| async def passkey_reply_to_code(config, action_id, template_arg, args): | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|  | ||||
|     passkey = config[CONF_PASSKEY] | ||||
|     if cg.is_template(passkey): | ||||
|         templ = await cg.templatable(passkey, args, cg.uint32) | ||||
|         cg.add(var.set_value_template(templ)) | ||||
|     else: | ||||
|         cg.add(var.set_value_simple(passkey)) | ||||
|  | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.remove_bond", | ||||
|     BLERemoveBondAction, | ||||
|     BLE_REMOVE_BOND_ACTION_SCHEMA, | ||||
| ) | ||||
| async def remove_bond_to_code(config, action_id, template_arg, args): | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|  | ||||
|     return var | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
| @@ -148,3 +267,12 @@ async def to_code(config): | ||||
|     for conf in config.get(CONF_ON_DISCONNECT, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|     for conf in config.get(CONF_ON_PASSKEY_REQUEST, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|     for conf in config.get(CONF_ON_PASSKEY_NOTIFICATION, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf) | ||||
|     for conf in config.get(CONF_ON_NUMERIC_COMPARISON_REQUEST, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf) | ||||
|   | ||||
| @@ -37,6 +37,44 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode { | ||||
|  public: | ||||
|   explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() override {} | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||
|     if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && | ||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||
|       this->trigger(); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLEClientNode { | ||||
|  public: | ||||
|   explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() override {} | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||
|     if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && | ||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||
|       uint32_t passkey = param->ble_security.key_notif.passkey; | ||||
|       this->trigger(passkey); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, public BLEClientNode { | ||||
|  public: | ||||
|   explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() override {} | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||
|     if (event == ESP_GAP_BLE_NC_REQ_EVT && | ||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||
|       uint32_t passkey = param->ble_security.key_notif.passkey; | ||||
|       this->trigger(passkey); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class BLEWriterClientNode : public BLEClientNode { | ||||
|  public: | ||||
|   BLEWriterClientNode(BLEClient *ble_client) { | ||||
| @@ -94,6 +132,86 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ | ||||
|   std::function<std::vector<uint8_t>(Ts...)> value_template_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> { | ||||
|  public: | ||||
|   BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; } | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     uint32_t passkey; | ||||
|     if (has_simple_value_) { | ||||
|       passkey = this->value_simple_; | ||||
|     } else { | ||||
|       passkey = this->value_template_(x...); | ||||
|     } | ||||
|     if (passkey > 999999) | ||||
|       return; | ||||
|     esp_bd_addr_t remote_bda; | ||||
|     memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); | ||||
|     esp_ble_passkey_reply(remote_bda, true, passkey); | ||||
|   } | ||||
|  | ||||
|   void set_value_template(std::function<uint32_t(Ts...)> func) { | ||||
|     this->value_template_ = std::move(func); | ||||
|     has_simple_value_ = false; | ||||
|   } | ||||
|  | ||||
|   void set_value_simple(const uint32_t &value) { | ||||
|     this->value_simple_ = value; | ||||
|     has_simple_value_ = true; | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   BLEClient *parent_{nullptr}; | ||||
|   bool has_simple_value_ = true; | ||||
|   uint32_t value_simple_{0}; | ||||
|   std::function<uint32_t(Ts...)> value_template_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> { | ||||
|  public: | ||||
|   BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; } | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     esp_bd_addr_t remote_bda; | ||||
|     memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); | ||||
|     if (has_simple_value_) { | ||||
|       esp_ble_confirm_reply(remote_bda, this->value_simple_); | ||||
|     } else { | ||||
|       esp_ble_confirm_reply(remote_bda, this->value_template_(x...)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void set_value_template(std::function<bool(Ts...)> func) { | ||||
|     this->value_template_ = std::move(func); | ||||
|     has_simple_value_ = false; | ||||
|   } | ||||
|  | ||||
|   void set_value_simple(const bool &value) { | ||||
|     this->value_simple_ = value; | ||||
|     has_simple_value_ = true; | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   BLEClient *parent_{nullptr}; | ||||
|   bool has_simple_value_ = true; | ||||
|   bool value_simple_{false}; | ||||
|   std::function<bool(Ts...)> value_template_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> { | ||||
|  public: | ||||
|   BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; } | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     esp_bd_addr_t remote_bda; | ||||
|     memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); | ||||
|     esp_ble_remove_bond_device(remote_bda); | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   BLEClient *parent_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace ble_client | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -27,7 +27,7 @@ class BLEClient; | ||||
| class BLEClientNode { | ||||
|  public: | ||||
|   virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                                    esp_ble_gattc_cb_param_t *param) = 0; | ||||
|                                    esp_ble_gattc_cb_param_t *param){}; | ||||
|   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {} | ||||
|   virtual void loop() {} | ||||
|   void set_address(uint64_t address) { address_ = address; } | ||||
|   | ||||
| @@ -40,6 +40,7 @@ DEVICE = { | ||||
|  | ||||
| NextAction = dfplayer_ns.class_("NextAction", automation.Action) | ||||
| PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action) | ||||
| PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action) | ||||
| PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action) | ||||
| PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action) | ||||
| SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action) | ||||
| @@ -113,6 +114,25 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args): | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "dfplayer.play_mp3", | ||||
|     PlayMp3Action, | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
|             cv.Required(CONF_FILE): cv.templatable(cv.int_), | ||||
|         }, | ||||
|         key=CONF_FILE, | ||||
|     ), | ||||
| ) | ||||
| async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     await cg.register_parented(var, config[CONF_ID]) | ||||
|     template_ = await cg.templatable(config[CONF_FILE], args, float) | ||||
|     cg.add(var.set_file(template_)) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "dfplayer.play", | ||||
|     PlayFileAction, | ||||
|   | ||||
| @@ -7,10 +7,10 @@ namespace dfplayer { | ||||
| static const char *const TAG = "dfplayer"; | ||||
|  | ||||
| void DFPlayer::play_folder(uint16_t folder, uint16_t file) { | ||||
|   if (folder < 100 && file < 256) { | ||||
|   if (folder <= 10 && file <= 1000) { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file); | ||||
|   } else if (folder <= 10 && file <= 1000) { | ||||
|   } else if (folder < 100 && file < 256) { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file); | ||||
|   } else { | ||||
|   | ||||
| @@ -35,6 +35,10 @@ class DFPlayer : public uart::UARTDevice, public Component { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x02); | ||||
|   } | ||||
|   void play_mp3(uint16_t file) { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x12, file); | ||||
|   } | ||||
|   void play_file(uint16_t file) { | ||||
|     this->ack_set_is_playing_ = true; | ||||
|     this->send_cmd_(0x03, file); | ||||
| @@ -113,6 +117,16 @@ class DFPlayer : public uart::UARTDevice, public Component { | ||||
| DFPLAYER_SIMPLE_ACTION(NextAction, next) | ||||
| DFPLAYER_SIMPLE_ACTION(PreviousAction, previous) | ||||
|  | ||||
| template<typename... Ts> class PlayMp3Action : public Action<Ts...>, public Parented<DFPlayer> { | ||||
|  public: | ||||
|   TEMPLATABLE_VALUE(uint16_t, file) | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     auto file = this->file_.value(x...); | ||||
|     this->parent_->play_mp3(file); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> { | ||||
|  public: | ||||
|   TEMPLATABLE_VALUE(uint16_t, file) | ||||
|   | ||||
| @@ -9,6 +9,7 @@ CODEOWNERS = ["@jesserockz"] | ||||
| CONFLICTS_WITH = ["esp32_ble_beacon"] | ||||
|  | ||||
| CONF_BLE_ID = "ble_id" | ||||
| CONF_IO_CAPABILITY = "io_capability" | ||||
|  | ||||
| NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] | ||||
|  | ||||
| @@ -19,10 +20,21 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler") | ||||
| GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler") | ||||
| GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler") | ||||
|  | ||||
| IoCapability = esp32_ble_ns.enum("IoCapability") | ||||
| IO_CAPABILITY = { | ||||
|     "none": IoCapability.IO_CAP_NONE, | ||||
|     "keyboard_only": IoCapability.IO_CAP_IN, | ||||
|     "keyboard_display": IoCapability.IO_CAP_KBDISP, | ||||
|     "display_only": IoCapability.IO_CAP_OUT, | ||||
|     "display_yes_no": IoCapability.IO_CAP_IO, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(ESP32BLE), | ||||
|         cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( | ||||
|             IO_CAPABILITY, lower=True | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
| @@ -39,6 +51,7 @@ FINAL_VALIDATE_SCHEMA = validate_variant | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) | ||||
|  | ||||
|     if CORE.using_esp_idf: | ||||
|         add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) | ||||
|   | ||||
| @@ -134,8 +134,7 @@ bool ESP32BLE::ble_setup_() { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; | ||||
|   err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); | ||||
|   err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t)); | ||||
|   if (err != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err); | ||||
|     return false; | ||||
| @@ -215,9 +214,31 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } | ||||
| void ESP32BLE::dump_config() { | ||||
|   const uint8_t *mac_address = esp_bt_dev_get_address(); | ||||
|   if (mac_address) { | ||||
|     const char *io_capability_s; | ||||
|     switch (this->io_cap_) { | ||||
|       case ESP_IO_CAP_OUT: | ||||
|         io_capability_s = "display_only"; | ||||
|         break; | ||||
|       case ESP_IO_CAP_IO: | ||||
|         io_capability_s = "display_yes_no"; | ||||
|         break; | ||||
|       case ESP_IO_CAP_IN: | ||||
|         io_capability_s = "keyboard_only"; | ||||
|         break; | ||||
|       case ESP_IO_CAP_NONE: | ||||
|         io_capability_s = "none"; | ||||
|         break; | ||||
|       case ESP_IO_CAP_KBDISP: | ||||
|         io_capability_s = "keyboard_display"; | ||||
|         break; | ||||
|       default: | ||||
|         io_capability_s = "invalid"; | ||||
|         break; | ||||
|     } | ||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE:"); | ||||
|     ESP_LOGCONFIG(TAG, "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2], | ||||
|                   mac_address[3], mac_address[4], mac_address[5]); | ||||
|     ESP_LOGCONFIG(TAG, "  IO Capability: %s", io_capability_s); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); | ||||
|   } | ||||
|   | ||||
| @@ -25,6 +25,14 @@ typedef struct { | ||||
|   uint16_t mtu; | ||||
| } conn_status_t; | ||||
|  | ||||
| enum IoCapability { | ||||
|   IO_CAP_OUT = ESP_IO_CAP_OUT, | ||||
|   IO_CAP_IO = ESP_IO_CAP_IO, | ||||
|   IO_CAP_IN = ESP_IO_CAP_IN, | ||||
|   IO_CAP_NONE = ESP_IO_CAP_NONE, | ||||
|   IO_CAP_KBDISP = ESP_IO_CAP_KBDISP, | ||||
| }; | ||||
|  | ||||
| class GAPEventHandler { | ||||
|  public: | ||||
|   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; | ||||
| @@ -44,6 +52,8 @@ class GATTsEventHandler { | ||||
|  | ||||
| class ESP32BLE : public Component { | ||||
|  public: | ||||
|   void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } | ||||
|  | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
| @@ -72,6 +82,7 @@ class ESP32BLE : public Component { | ||||
|  | ||||
|   Queue<BLEEvent> ble_events_; | ||||
|   BLEAdvertising *advertising_; | ||||
|   esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; | ||||
| }; | ||||
|  | ||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|   | ||||
| @@ -77,10 +77,12 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) { | ||||
|     this->enrollment_done_callback_.call(this->enrollment_slot_); | ||||
|     this->get_fingerprint_count_(); | ||||
|   } else { | ||||
|     this->enrollment_failed_callback_.call(this->enrollment_slot_); | ||||
|     if (this->enrollment_slot_ != ENROLLMENT_SLOT_UNUSED) { | ||||
|       this->enrollment_failed_callback_.call(this->enrollment_slot_); | ||||
|     } | ||||
|   } | ||||
|   this->enrollment_image_ = 0; | ||||
|   this->enrollment_slot_ = 0; | ||||
|   this->enrollment_slot_ = ENROLLMENT_SLOT_UNUSED; | ||||
|   if (this->enrolling_binary_sensor_ != nullptr) { | ||||
|     this->enrolling_binary_sensor_->publish_state(false); | ||||
|   } | ||||
|   | ||||
| @@ -13,6 +13,8 @@ namespace fingerprint_grow { | ||||
|  | ||||
| static const uint16_t START_CODE = 0xEF01; | ||||
|  | ||||
| static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF; | ||||
|  | ||||
| enum GrowPacketType { | ||||
|   COMMAND = 0x01, | ||||
|   DATA = 0x02, | ||||
| @@ -158,7 +160,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic | ||||
|   uint32_t new_password_ = -1; | ||||
|   GPIOPin *sensing_pin_{nullptr}; | ||||
|   uint8_t enrollment_image_ = 0; | ||||
|   uint16_t enrollment_slot_ = 0; | ||||
|   uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED; | ||||
|   uint8_t enrollment_buffers_ = 5; | ||||
|   bool waiting_removal_ = false; | ||||
|   uint32_t last_aura_led_control_ = 0; | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/hyt271/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/hyt271/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@Philippe12"] | ||||
							
								
								
									
										52
									
								
								esphome/components/hyt271/hyt271.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								esphome/components/hyt271/hyt271.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| #include "hyt271.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace hyt271 { | ||||
|  | ||||
| static const char *const TAG = "hyt271"; | ||||
|  | ||||
| static const uint8_t HYT271_ADDRESS = 0x28; | ||||
|  | ||||
| void HYT271Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "HYT271:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_); | ||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_); | ||||
| } | ||||
| void HYT271Component::update() { | ||||
|   uint8_t raw_data[4]; | ||||
|  | ||||
|   if (this->write(&raw_data[0], 0) != i2c::ERROR_OK) { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGE(TAG, "Communication with HYT271 failed! => Ask new values"); | ||||
|     return; | ||||
|   } | ||||
|   this->set_timeout("wait_convert", 50, [this]() { | ||||
|     uint8_t raw_data[4]; | ||||
|     if (this->read(raw_data, 4) != i2c::ERROR_OK) { | ||||
|       this->status_set_warning(); | ||||
|       ESP_LOGE(TAG, "Communication with HYT271 failed! => Read values"); | ||||
|       return; | ||||
|     } | ||||
|     uint16_t raw_temperature = ((raw_data[2] << 8) | raw_data[3]) >> 2; | ||||
|     uint16_t raw_humidity = ((raw_data[0] & 0x3F) << 8) | raw_data[1]; | ||||
|  | ||||
|     float temperature = ((float(raw_temperature)) * (165.0f / 16383.0f)) - 40.0f; | ||||
|     float humidity = (float(raw_humidity)) * (100.0f / 16383.0f); | ||||
|  | ||||
|     ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); | ||||
|  | ||||
|     if (this->temperature_ != nullptr) | ||||
|       this->temperature_->publish_state(temperature); | ||||
|     if (this->humidity_ != nullptr) | ||||
|       this->humidity_->publish_state(humidity); | ||||
|     this->status_clear_warning(); | ||||
|   }); | ||||
| } | ||||
| float HYT271Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| }  // namespace hyt271 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										27
									
								
								esphome/components/hyt271/hyt271.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/hyt271/hyt271.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace hyt271 { | ||||
|  | ||||
| class HYT271Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||
|  | ||||
|   void dump_config() override; | ||||
|   /// Update the sensor values (temperature+humidity). | ||||
|   void update() override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|  protected: | ||||
|   sensor::Sensor *temperature_{nullptr}; | ||||
|   sensor::Sensor *humidity_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace hyt271 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										56
									
								
								esphome/components/hyt271/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/hyt271/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import ( | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_ID, | ||||
|     CONF_TEMPERATURE, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_PERCENT, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| hyt271_ns = cg.esphome_ns.namespace("hyt271") | ||||
| HYT271Component = hyt271_ns.class_( | ||||
|     "HYT271Component", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(HYT271Component), | ||||
|             cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_CELSIUS, | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Required(CONF_HUMIDITY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_PERCENT, | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_HUMIDITY, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x28)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature(sens)) | ||||
|  | ||||
|     if CONF_HUMIDITY in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_HUMIDITY]) | ||||
|         cg.add(var.set_humidity(sens)) | ||||
							
								
								
									
										148
									
								
								esphome/components/max6956/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								esphome/components/max6956/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins, automation | ||||
| from esphome.components import i2c | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_NUMBER, | ||||
|     CONF_MODE, | ||||
|     CONF_INVERTED, | ||||
|     CONF_INPUT, | ||||
|     CONF_OUTPUT, | ||||
|     CONF_PULLUP, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@looping40"] | ||||
|  | ||||
| DEPENDENCIES = ["i2c"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_BRIGHTNESS_MODE = "brightness_mode" | ||||
| CONF_BRIGHTNESS_GLOBAL = "brightness_global" | ||||
|  | ||||
|  | ||||
| max6956_ns = cg.esphome_ns.namespace("max6956") | ||||
|  | ||||
| MAX6956 = max6956_ns.class_("MAX6956", cg.Component, i2c.I2CDevice) | ||||
| MAX6956GPIOPin = max6956_ns.class_("MAX6956GPIOPin", cg.GPIOPin) | ||||
|  | ||||
| # Actions | ||||
| SetCurrentGlobalAction = max6956_ns.class_("SetCurrentGlobalAction", automation.Action) | ||||
| SetCurrentModeAction = max6956_ns.class_("SetCurrentModeAction", automation.Action) | ||||
|  | ||||
| MAX6956_CURRENTMODE = max6956_ns.enum("MAX6956CURRENTMODE") | ||||
| CURRENT_MODES = { | ||||
|     "global": MAX6956_CURRENTMODE.GLOBAL, | ||||
|     "segment": MAX6956_CURRENTMODE.SEGMENT, | ||||
| } | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.declare_id(MAX6956), | ||||
|             cv.Optional(CONF_BRIGHTNESS_GLOBAL, default="0"): cv.int_range( | ||||
|                 min=0, max=15 | ||||
|             ), | ||||
|             cv.Optional(CONF_BRIGHTNESS_MODE, default="global"): cv.enum( | ||||
|                 CURRENT_MODES, lower=True | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(i2c.i2c_device_schema(0x40)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
|     cg.add(var.set_brightness_mode(config[CONF_BRIGHTNESS_MODE])) | ||||
|     cg.add(var.set_brightness_global(config[CONF_BRIGHTNESS_GLOBAL])) | ||||
|  | ||||
|  | ||||
| def validate_mode(value): | ||||
|     if not (value[CONF_INPUT] or value[CONF_OUTPUT]): | ||||
|         raise cv.Invalid("Mode must be either input or output") | ||||
|     if value[CONF_INPUT] and value[CONF_OUTPUT]: | ||||
|         raise cv.Invalid("Mode must be either input or output") | ||||
|     if value[CONF_PULLUP] and not value[CONF_INPUT]: | ||||
|         raise cv.Invalid("Pullup only available with input") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| CONF_MAX6956 = "max6956" | ||||
|  | ||||
| MAX6956_PIN_SCHEMA = cv.All( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(MAX6956GPIOPin), | ||||
|         cv.Required(CONF_MAX6956): cv.use_id(MAX6956), | ||||
|         cv.Required(CONF_NUMBER): cv.int_range(min=4, max=31), | ||||
|         cv.Optional(CONF_MODE, default={}): cv.All( | ||||
|             { | ||||
|                 cv.Optional(CONF_INPUT, default=False): cv.boolean, | ||||
|                 cv.Optional(CONF_PULLUP, default=False): cv.boolean, | ||||
|                 cv.Optional(CONF_OUTPUT, default=False): cv.boolean, | ||||
|             }, | ||||
|             validate_mode, | ||||
|         ), | ||||
|         cv.Optional(CONF_INVERTED, default=False): cv.boolean, | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @pins.PIN_SCHEMA_REGISTRY.register(CONF_MAX6956, MAX6956_PIN_SCHEMA) | ||||
| async def max6956_pin_to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     parent = await cg.get_variable(config[CONF_MAX6956]) | ||||
|  | ||||
|     cg.add(var.set_parent(parent)) | ||||
|  | ||||
|     num = config[CONF_NUMBER] | ||||
|     cg.add(var.set_pin(num)) | ||||
|     cg.add(var.set_inverted(config[CONF_INVERTED])) | ||||
|     cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "max6956.set_brightness_global", | ||||
|     SetCurrentGlobalAction, | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             cv.GenerateID(CONF_ID): cv.use_id(MAX6956), | ||||
|             cv.Required(CONF_BRIGHTNESS_GLOBAL): cv.templatable( | ||||
|                 cv.int_range(min=0, max=15) | ||||
|             ), | ||||
|         }, | ||||
|         key=CONF_BRIGHTNESS_GLOBAL, | ||||
|     ), | ||||
| ) | ||||
| async def max6956_set_brightness_global_to_code(config, action_id, template_arg, args): | ||||
|     paren = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||
|     template_ = await cg.templatable(config[CONF_BRIGHTNESS_GLOBAL], args, float) | ||||
|     cg.add(var.set_brightness_global(template_)) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "max6956.set_brightness_mode", | ||||
|     SetCurrentModeAction, | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(MAX6956), | ||||
|             cv.Required(CONF_BRIGHTNESS_MODE): cv.templatable( | ||||
|                 cv.enum(CURRENT_MODES, lower=True) | ||||
|             ), | ||||
|         }, | ||||
|         key=CONF_BRIGHTNESS_MODE, | ||||
|     ), | ||||
| ) | ||||
| async def max6956_set_brightness_mode_to_code(config, action_id, template_arg, args): | ||||
|     paren = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||
|     template_ = await cg.templatable(config[CONF_BRIGHTNESS_MODE], args, float) | ||||
|     cg.add(var.set_brightness_mode(template_)) | ||||
|     return var | ||||
							
								
								
									
										40
									
								
								esphome/components/max6956/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/max6956/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/components/max6956/max6956.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace max6956 { | ||||
|  | ||||
| template<typename... Ts> class SetCurrentGlobalAction : public Action<Ts...> { | ||||
|  public: | ||||
|   SetCurrentGlobalAction(MAX6956 *max6956) : max6956_(max6956) {} | ||||
|  | ||||
|   TEMPLATABLE_VALUE(uint8_t, brightness_global) | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     this->max6956_->set_brightness_global(this->brightness_global_.value(x...)); | ||||
|     this->max6956_->write_brightness_global(); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   MAX6956 *max6956_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class SetCurrentModeAction : public Action<Ts...> { | ||||
|  public: | ||||
|   SetCurrentModeAction(MAX6956 *max6956) : max6956_(max6956) {} | ||||
|  | ||||
|   TEMPLATABLE_VALUE(max6956::MAX6956CURRENTMODE, brightness_mode) | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     this->max6956_->set_brightness_mode(this->brightness_mode_.value(x...)); | ||||
|     this->max6956_->write_brightness_mode(); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   MAX6956 *max6956_; | ||||
| }; | ||||
| }  // namespace max6956 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										170
									
								
								esphome/components/max6956/max6956.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								esphome/components/max6956/max6956.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| #include "max6956.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace max6956 { | ||||
|  | ||||
| static const char *const TAG = "max6956"; | ||||
|  | ||||
| /// Masks for MAX6956 Configuration register | ||||
| const uint32_t MASK_TRANSITION_DETECTION = 0x80; | ||||
| const uint32_t MASK_INDIVIDUAL_CURRENT = 0x40; | ||||
| const uint32_t MASK_NORMAL_OPERATION = 0x01; | ||||
|  | ||||
| const uint32_t MASK_1PORT_VALUE = 0x03; | ||||
| const uint32_t MASK_PORT_CONFIG = 0x03; | ||||
| const uint8_t MASK_CONFIG_CURRENT = 0x40; | ||||
| const uint8_t MASK_CURRENT_PIN = 0x0F; | ||||
|  | ||||
| /************************************** | ||||
|  *    MAX6956                         * | ||||
|  **************************************/ | ||||
| void MAX6956::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up MAX6956..."); | ||||
|   uint8_t configuration; | ||||
|   if (!this->read_reg_(MAX6956_CONFIGURATION, &configuration)) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   write_brightness_global(); | ||||
|   write_brightness_mode(); | ||||
|  | ||||
|   /** TO DO : read transition detection in yaml | ||||
|       TO DO : read indivdual current in yaml **/ | ||||
|   this->read_reg_(MAX6956_CONFIGURATION, &configuration); | ||||
|   ESP_LOGD(TAG, "Initial reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration); | ||||
|   configuration = configuration | MASK_NORMAL_OPERATION; | ||||
|   this->write_reg_(MAX6956_CONFIGURATION, configuration); | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "Enabling normal operation"); | ||||
|   ESP_LOGD(TAG, "setup reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration); | ||||
| } | ||||
|  | ||||
| bool MAX6956::digital_read(uint8_t pin) { | ||||
|   uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin; | ||||
|   uint8_t value = 0; | ||||
|   this->read_reg_(reg_addr, &value); | ||||
|   return (value & MASK_1PORT_VALUE); | ||||
| } | ||||
|  | ||||
| void MAX6956::digital_write(uint8_t pin, bool value) { | ||||
|   uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin; | ||||
|   this->write_reg_(reg_addr, value); | ||||
| } | ||||
|  | ||||
| void MAX6956::pin_mode(uint8_t pin, gpio::Flags flags) { | ||||
|   uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4; | ||||
|   uint8_t config = 0; | ||||
|   uint8_t shift = 2 * (pin % 4); | ||||
|   MAX6956GPIOMode mode = MAX6956_INPUT; | ||||
|  | ||||
|   if (flags == gpio::FLAG_INPUT) { | ||||
|     mode = MAX6956GPIOMode::MAX6956_INPUT; | ||||
|   } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { | ||||
|     mode = MAX6956GPIOMode::MAX6956_INPUT_PULLUP; | ||||
|   } else if (flags == gpio::FLAG_OUTPUT) { | ||||
|     mode = MAX6956GPIOMode::MAX6956_OUTPUT; | ||||
|   } | ||||
|  | ||||
|   this->read_reg_(reg_addr, &config); | ||||
|   config &= ~(MASK_PORT_CONFIG << shift); | ||||
|   config |= (mode << shift); | ||||
|   this->write_reg_(reg_addr, config); | ||||
| } | ||||
|  | ||||
| void MAX6956::pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags) { | ||||
|   uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4; | ||||
|   uint8_t config = 0; | ||||
|   uint8_t shift = 2 * (pin % 4); | ||||
|   MAX6956GPIOMode mode = MAX6956GPIOMode::MAX6956_LED; | ||||
|  | ||||
|   if (flags == max6956::FLAG_LED) { | ||||
|     mode = MAX6956GPIOMode::MAX6956_LED; | ||||
|   } | ||||
|  | ||||
|   this->read_reg_(reg_addr, &config); | ||||
|   config &= ~(MASK_PORT_CONFIG << shift); | ||||
|   config |= (mode << shift); | ||||
|   this->write_reg_(reg_addr, config); | ||||
| } | ||||
|  | ||||
| void MAX6956::set_brightness_global(uint8_t current) { | ||||
|   if (current > 15) { | ||||
|     ESP_LOGE(TAG, "Global brightness out off range (%u)", current); | ||||
|     return; | ||||
|   } | ||||
|   global_brightness_ = current; | ||||
| } | ||||
|  | ||||
| void MAX6956::write_brightness_global() { this->write_reg_(MAX6956_GLOBAL_CURRENT, global_brightness_); } | ||||
|  | ||||
| void MAX6956::set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode) { brightness_mode_ = brightness_mode; }; | ||||
|  | ||||
| void MAX6956::write_brightness_mode() { | ||||
|   uint8_t reg_addr = MAX6956_CONFIGURATION; | ||||
|   uint8_t config = 0; | ||||
|  | ||||
|   this->read_reg_(reg_addr, &config); | ||||
|   config &= ~MASK_CONFIG_CURRENT; | ||||
|   config |= brightness_mode_ << 6; | ||||
|   this->write_reg_(reg_addr, config); | ||||
| } | ||||
|  | ||||
| void MAX6956::set_pin_brightness(uint8_t pin, float brightness) { | ||||
|   uint8_t reg_addr = MAX6956_CURRENT_START + (pin - MAX6956_MIN) / 2; | ||||
|   uint8_t config = 0; | ||||
|   uint8_t shift = 4 * (pin % 2); | ||||
|   uint8_t bright = roundf(brightness * 15); | ||||
|  | ||||
|   if (prev_bright_[pin - MAX6956_MIN] == bright) | ||||
|     return; | ||||
|  | ||||
|   prev_bright_[pin - MAX6956_MIN] = bright; | ||||
|  | ||||
|   this->read_reg_(reg_addr, &config); | ||||
|   config &= ~(MASK_CURRENT_PIN << shift); | ||||
|   config |= (bright << shift); | ||||
|   this->write_reg_(reg_addr, config); | ||||
| } | ||||
|  | ||||
| bool MAX6956::read_reg_(uint8_t reg, uint8_t *value) { | ||||
|   if (this->is_failed()) | ||||
|     return false; | ||||
|  | ||||
|   return this->read_byte(reg, value); | ||||
| } | ||||
|  | ||||
| bool MAX6956::write_reg_(uint8_t reg, uint8_t value) { | ||||
|   if (this->is_failed()) | ||||
|     return false; | ||||
|  | ||||
|   return this->write_byte(reg, value); | ||||
| } | ||||
|  | ||||
| void MAX6956::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "MAX6956"); | ||||
|  | ||||
|   if (brightness_mode_ == MAX6956CURRENTMODE::GLOBAL) { | ||||
|     ESP_LOGCONFIG(TAG, "current mode: global"); | ||||
|     ESP_LOGCONFIG(TAG, "global brightness: %u", global_brightness_); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "current mode: segment"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /************************************** | ||||
|  *    MAX6956GPIOPin                  * | ||||
|  **************************************/ | ||||
| void MAX6956GPIOPin::setup() { pin_mode(flags_); } | ||||
| void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } | ||||
| bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } | ||||
| void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } | ||||
| std::string MAX6956GPIOPin::dump_summary() const { | ||||
|   char buffer[32]; | ||||
|   snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_); | ||||
|   return buffer; | ||||
| } | ||||
|  | ||||
| }  // namespace max6956 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										94
									
								
								esphome/components/max6956/max6956.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								esphome/components/max6956/max6956.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace max6956 { | ||||
|  | ||||
| /// Modes for MAX6956 pins | ||||
| enum MAX6956GPIOMode : uint8_t { | ||||
|   MAX6956_LED = 0x00, | ||||
|   MAX6956_OUTPUT = 0x01, | ||||
|   MAX6956_INPUT = 0x02, | ||||
|   MAX6956_INPUT_PULLUP = 0x03 | ||||
| }; | ||||
|  | ||||
| /// Range for MAX6956 pins | ||||
| enum MAX6956GPIORange : uint8_t { | ||||
|   MAX6956_MIN = 4, | ||||
|   MAX6956_MAX = 31, | ||||
| }; | ||||
|  | ||||
| enum MAX6956GPIORegisters { | ||||
|   MAX6956_GLOBAL_CURRENT = 0x02, | ||||
|   MAX6956_CONFIGURATION = 0x04, | ||||
|   MAX6956_TRANSITION_DETECT_MASK = 0x06, | ||||
|   MAX6956_DISPLAY_TEST = 0x07, | ||||
|   MAX6956_PORT_CONFIG_START = 0x09,   // Port Configuration P7, P6, P5, P4 | ||||
|   MAX6956_CURRENT_START = 0x12,       // Current054 | ||||
|   MAX6956_1PORT_VALUE_START = 0x20,   // Port 0 only (virtual port, no action) | ||||
|   MAX6956_8PORTS_VALUE_START = 0x44,  // 8 ports 4–11 (data bits D0–D7) | ||||
| }; | ||||
|  | ||||
| enum MAX6956GPIOFlag { FLAG_LED = 0x20 }; | ||||
|  | ||||
| enum MAX6956CURRENTMODE { GLOBAL = 0x00, SEGMENT = 0x01 }; | ||||
|  | ||||
| class MAX6956 : public Component, public i2c::I2CDevice { | ||||
|  public: | ||||
|   MAX6956() = default; | ||||
|  | ||||
|   void setup() override; | ||||
|  | ||||
|   bool digital_read(uint8_t pin); | ||||
|   void digital_write(uint8_t pin, bool value); | ||||
|   void pin_mode(uint8_t pin, gpio::Flags flags); | ||||
|   void pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags); | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   void set_brightness_global(uint8_t current); | ||||
|   void set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode); | ||||
|   void set_pin_brightness(uint8_t pin, float brightness); | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void write_brightness_global(); | ||||
|   void write_brightness_mode(); | ||||
|  | ||||
|  protected: | ||||
|   // read a given register | ||||
|   bool read_reg_(uint8_t reg, uint8_t *value); | ||||
|   // write a value to a given register | ||||
|   bool write_reg_(uint8_t reg, uint8_t value); | ||||
|   max6956::MAX6956CURRENTMODE brightness_mode_; | ||||
|   uint8_t global_brightness_; | ||||
|  | ||||
|  private: | ||||
|   int8_t prev_bright_[28] = {0}; | ||||
| }; | ||||
|  | ||||
| class MAX6956GPIOPin : public GPIOPin { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void pin_mode(gpio::Flags flags) override; | ||||
|   bool digital_read() override; | ||||
|   void digital_write(bool value) override; | ||||
|   std::string dump_summary() const override; | ||||
|  | ||||
|   void set_parent(MAX6956 *parent) { parent_ = parent; } | ||||
|   void set_pin(uint8_t pin) { pin_ = pin; } | ||||
|   void set_inverted(bool inverted) { inverted_ = inverted; } | ||||
|   void set_flags(gpio::Flags flags) { flags_ = flags; } | ||||
|  | ||||
|  protected: | ||||
|   MAX6956 *parent_; | ||||
|   uint8_t pin_; | ||||
|   bool inverted_; | ||||
|   gpio::Flags flags_; | ||||
| }; | ||||
|  | ||||
| }  // namespace max6956 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										28
									
								
								esphome/components/max6956/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/max6956/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import output | ||||
| from esphome.const import CONF_PIN, CONF_ID | ||||
| from .. import MAX6956, max6956_ns, CONF_MAX6956 | ||||
|  | ||||
| DEPENDENCIES = ["max6956"] | ||||
|  | ||||
| MAX6956LedChannel = max6956_ns.class_( | ||||
|     "MAX6956LedChannel", output.FloatOutput, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( | ||||
|     { | ||||
|         cv.Required(CONF_ID): cv.declare_id(MAX6956LedChannel), | ||||
|         cv.GenerateID(CONF_MAX6956): cv.use_id(MAX6956), | ||||
|         cv.Required(CONF_PIN): cv.int_range(min=4, max=31), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     parent = await cg.get_variable(config[CONF_MAX6956]) | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await output.register_output(var, config) | ||||
|     cg.add(var.set_pin(config[CONF_PIN])) | ||||
|     cg.add(var.set_parent(parent)) | ||||
							
								
								
									
										26
									
								
								esphome/components/max6956/output/max6956_led_output.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/max6956/output/max6956_led_output.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| #include "max6956_led_output.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace max6956 { | ||||
|  | ||||
| static const char *const TAG = "max6956_led_channel"; | ||||
|  | ||||
| void MAX6956LedChannel::write_state(float state) { this->parent_->set_pin_brightness(this->pin_, state); } | ||||
|  | ||||
| void MAX6956LedChannel::write_state(bool state) { this->parent_->digital_write(this->pin_, state); } | ||||
|  | ||||
| void MAX6956LedChannel::setup() { | ||||
|   this->parent_->pin_mode(this->pin_, max6956::FLAG_LED); | ||||
|   this->turn_off(); | ||||
| } | ||||
|  | ||||
| void MAX6956LedChannel::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "MAX6956 current:"); | ||||
|   ESP_LOGCONFIG(TAG, "  MAX6956 pin: %d", this->pin_); | ||||
|   LOG_FLOAT_OUTPUT(this); | ||||
| } | ||||
|  | ||||
| }  // namespace max6956 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										28
									
								
								esphome/components/max6956/output/max6956_led_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/max6956/output/max6956_led_output.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/max6956/max6956.h" | ||||
| #include "esphome/components/output/float_output.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace max6956 { | ||||
|  | ||||
| class MAX6956; | ||||
|  | ||||
| class MAX6956LedChannel : public output::FloatOutput, public Component { | ||||
|  public: | ||||
|   void set_parent(MAX6956 *parent) { this->parent_ = parent; } | ||||
|   void set_pin(uint8_t pin) { pin_ = pin; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|  protected: | ||||
|   void write_state(float state) override; | ||||
|   void write_state(bool state) override; | ||||
|  | ||||
|   MAX6956 *parent_; | ||||
|   uint8_t pin_; | ||||
| }; | ||||
|  | ||||
| }  // namespace max6956 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										78
									
								
								esphome/components/pca6416a/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								esphome/components/pca6416a/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.components import i2c | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_INPUT, | ||||
|     CONF_NUMBER, | ||||
|     CONF_MODE, | ||||
|     CONF_INVERTED, | ||||
|     CONF_OUTPUT, | ||||
|     CONF_PULLUP, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@Mat931"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
| MULTI_CONF = True | ||||
| pca6416a_ns = cg.esphome_ns.namespace("pca6416a") | ||||
|  | ||||
| PCA6416AComponent = pca6416a_ns.class_("PCA6416AComponent", cg.Component, i2c.I2CDevice) | ||||
| PCA6416AGPIOPin = pca6416a_ns.class_( | ||||
|     "PCA6416AGPIOPin", cg.GPIOPin, cg.Parented.template(PCA6416AComponent) | ||||
| ) | ||||
|  | ||||
| CONF_PCA6416A = "pca6416a" | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA6416AComponent)}) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(i2c.i2c_device_schema(0x21)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
|  | ||||
|  | ||||
| def validate_mode(value): | ||||
|     if not (value[CONF_INPUT] or value[CONF_OUTPUT]): | ||||
|         raise cv.Invalid("Mode must be either input or output") | ||||
|     if value[CONF_INPUT] and value[CONF_OUTPUT]: | ||||
|         raise cv.Invalid("Mode must be either input or output") | ||||
|     if value[CONF_PULLUP] and not value[CONF_INPUT]: | ||||
|         raise cv.Invalid("Pullup only available with input") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| PCA6416A_PIN_SCHEMA = cv.All( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(PCA6416AGPIOPin), | ||||
|         cv.Required(CONF_PCA6416A): cv.use_id(PCA6416AComponent), | ||||
|         cv.Required(CONF_NUMBER): cv.int_range(min=0, max=16), | ||||
|         cv.Optional(CONF_MODE, default={}): cv.All( | ||||
|             { | ||||
|                 cv.Optional(CONF_INPUT, default=False): cv.boolean, | ||||
|                 cv.Optional(CONF_PULLUP, default=False): cv.boolean, | ||||
|                 cv.Optional(CONF_OUTPUT, default=False): cv.boolean, | ||||
|             }, | ||||
|             validate_mode, | ||||
|         ), | ||||
|         cv.Optional(CONF_INVERTED, default=False): cv.boolean, | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @pins.PIN_SCHEMA_REGISTRY.register("pca6416a", PCA6416A_PIN_SCHEMA) | ||||
| async def pca6416a_pin_to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     parent = await cg.get_variable(config[CONF_PCA6416A]) | ||||
|  | ||||
|     cg.add(var.set_parent(parent)) | ||||
|  | ||||
|     num = config[CONF_NUMBER] | ||||
|     cg.add(var.set_pin(num)) | ||||
|     cg.add(var.set_inverted(config[CONF_INVERTED])) | ||||
|     cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) | ||||
|     return var | ||||
							
								
								
									
										174
									
								
								esphome/components/pca6416a/pca6416a.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								esphome/components/pca6416a/pca6416a.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | ||||
| #include "pca6416a.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace pca6416a { | ||||
|  | ||||
| enum PCA6416AGPIORegisters { | ||||
|   // 0 side | ||||
|   PCA6416A_INPUT0 = 0x00, | ||||
|   PCA6416A_OUTPUT0 = 0x02, | ||||
|   PCA6416A_INVERT0 = 0x04, | ||||
|   PCA6416A_CONFIG0 = 0x06, | ||||
|   PCAL6416A_PULL_EN0 = 0x46, | ||||
|   PCAL6416A_PULL_DIR0 = 0x48, | ||||
|   // 1 side | ||||
|   PCA6416A_INPUT1 = 0x01, | ||||
|   PCA6416A_OUTPUT1 = 0x03, | ||||
|   PCA6416A_INVERT1 = 0x05, | ||||
|   PCA6416A_CONFIG1 = 0x07, | ||||
|   PCAL6416A_PULL_EN1 = 0x47, | ||||
|   PCAL6416A_PULL_DIR1 = 0x49, | ||||
| }; | ||||
|  | ||||
| static const char *const TAG = "pca6416a"; | ||||
|  | ||||
| void PCA6416AComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up PCA6416A..."); | ||||
|   // Test to see if device exists | ||||
|   uint8_t value; | ||||
|   if (!this->read_register_(PCA6416A_INPUT0, &value)) { | ||||
|     ESP_LOGE(TAG, "PCA6416A not available under 0x%02X", this->address_); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Test to see if the device supports pull-up resistors | ||||
|   if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == esphome::i2c::ERROR_OK) { | ||||
|     this->has_pullup_ = true; | ||||
|   } | ||||
|  | ||||
|   // No polarity inversion | ||||
|   this->write_register_(PCA6416A_INVERT0, 0); | ||||
|   this->write_register_(PCA6416A_INVERT1, 0); | ||||
|   // Set all pins to input | ||||
|   this->write_register_(PCA6416A_CONFIG0, 0xff); | ||||
|   this->write_register_(PCA6416A_CONFIG1, 0xff); | ||||
|   // Read current output register state | ||||
|   this->read_register_(PCA6416A_OUTPUT0, &this->output_0_); | ||||
|   this->read_register_(PCA6416A_OUTPUT1, &this->output_1_); | ||||
|  | ||||
|   ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(), | ||||
|            this->status_has_error()); | ||||
| } | ||||
|  | ||||
| void PCA6416AComponent::dump_config() { | ||||
|   if (this->has_pullup_) { | ||||
|     ESP_LOGCONFIG(TAG, "PCAL6416A:"); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "PCA6416A:"); | ||||
|   } | ||||
|   LOG_I2C_DEVICE(this) | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, "Communication with PCA6416A failed!"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool PCA6416AComponent::digital_read(uint8_t pin) { | ||||
|   uint8_t bit = pin % 8; | ||||
|   uint8_t reg_addr = pin < 8 ? PCA6416A_INPUT0 : PCA6416A_INPUT1; | ||||
|   uint8_t value = 0; | ||||
|   this->read_register_(reg_addr, &value); | ||||
|   return value & (1 << bit); | ||||
| } | ||||
|  | ||||
| void PCA6416AComponent::digital_write(uint8_t pin, bool value) { | ||||
|   uint8_t reg_addr = pin < 8 ? PCA6416A_OUTPUT0 : PCA6416A_OUTPUT1; | ||||
|   this->update_register_(pin, value, reg_addr); | ||||
| } | ||||
|  | ||||
| void PCA6416AComponent::pin_mode(uint8_t pin, gpio::Flags flags) { | ||||
|   uint8_t io_dir = pin < 8 ? PCA6416A_CONFIG0 : PCA6416A_CONFIG1; | ||||
|   uint8_t pull_en = pin < 8 ? PCAL6416A_PULL_EN0 : PCAL6416A_PULL_EN1; | ||||
|   uint8_t pull_dir = pin < 8 ? PCAL6416A_PULL_DIR0 : PCAL6416A_PULL_DIR1; | ||||
|   if (flags == gpio::FLAG_INPUT) { | ||||
|     this->update_register_(pin, true, io_dir); | ||||
|     if (has_pullup_) { | ||||
|       this->update_register_(pin, true, pull_dir); | ||||
|       this->update_register_(pin, false, pull_en); | ||||
|     } | ||||
|   } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { | ||||
|     this->update_register_(pin, true, io_dir); | ||||
|     if (has_pullup_) { | ||||
|       this->update_register_(pin, true, pull_dir); | ||||
|       this->update_register_(pin, true, pull_en); | ||||
|     } else { | ||||
|       ESP_LOGW(TAG, "Your PCA6416A does not support pull-up resistors"); | ||||
|     } | ||||
|   } else if (flags == gpio::FLAG_OUTPUT) { | ||||
|     this->update_register_(pin, false, io_dir); | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) { | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGD(TAG, "Device marked failed"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if ((this->last_error_ = this->read_register(reg, value, 1, true)) != esphome::i2c::ERROR_OK) { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) { | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGD(TAG, "Device marked failed"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void PCA6416AComponent::update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr) { | ||||
|   uint8_t bit = pin % 8; | ||||
|   uint8_t reg_value = 0; | ||||
|   if (reg_addr == PCA6416A_OUTPUT0) { | ||||
|     reg_value = this->output_0_; | ||||
|   } else if (reg_addr == PCA6416A_OUTPUT1) { | ||||
|     reg_value = this->output_1_; | ||||
|   } else { | ||||
|     this->read_register_(reg_addr, ®_value); | ||||
|   } | ||||
|  | ||||
|   if (pin_value) { | ||||
|     reg_value |= 1 << bit; | ||||
|   } else { | ||||
|     reg_value &= ~(1 << bit); | ||||
|   } | ||||
|  | ||||
|   this->write_register_(reg_addr, reg_value); | ||||
|  | ||||
|   if (reg_addr == PCA6416A_OUTPUT0) { | ||||
|     this->output_0_ = reg_value; | ||||
|   } else if (reg_addr == PCA6416A_OUTPUT1) { | ||||
|     this->output_1_ = reg_value; | ||||
|   } | ||||
| } | ||||
|  | ||||
| float PCA6416AComponent::get_setup_priority() const { return setup_priority::IO; } | ||||
|  | ||||
| void PCA6416AGPIOPin::setup() { pin_mode(flags_); } | ||||
| void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } | ||||
| bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } | ||||
| void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } | ||||
| std::string PCA6416AGPIOPin::dump_summary() const { | ||||
|   char buffer[32]; | ||||
|   snprintf(buffer, sizeof(buffer), "%u via PCA6416A", pin_); | ||||
|   return buffer; | ||||
| } | ||||
|  | ||||
| }  // namespace pca6416a | ||||
| }  // namespace esphome | ||||
							
								
								
									
										63
									
								
								esphome/components/pca6416a/pca6416a.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								esphome/components/pca6416a/pca6416a.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace pca6416a { | ||||
|  | ||||
| class PCA6416AComponent : public Component, public i2c::I2CDevice { | ||||
|  public: | ||||
|   PCA6416AComponent() = default; | ||||
|  | ||||
|   /// Check i2c availability and setup masks | ||||
|   void setup() override; | ||||
|   /// Helper function to read the value of a pin. | ||||
|   bool digital_read(uint8_t pin); | ||||
|   /// Helper function to write the value of a pin. | ||||
|   void digital_write(uint8_t pin, bool value); | ||||
|   /// Helper function to set the pin mode of a pin. | ||||
|   void pin_mode(uint8_t pin, gpio::Flags flags); | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   bool read_register_(uint8_t reg, uint8_t *value); | ||||
|   bool write_register_(uint8_t reg, uint8_t value); | ||||
|   void update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr); | ||||
|  | ||||
|   /// The mask to write as output state - 1 means HIGH, 0 means LOW | ||||
|   uint8_t output_0_{0x00}; | ||||
|   uint8_t output_1_{0x00}; | ||||
|   /// Storage for last I2C error seen | ||||
|   esphome::i2c::ErrorCode last_error_; | ||||
|   /// Only the PCAL6416A has pull-up resistors | ||||
|   bool has_pullup_{false}; | ||||
| }; | ||||
|  | ||||
| /// Helper class to expose a PCA6416A pin as an internal input GPIO pin. | ||||
| class PCA6416AGPIOPin : public GPIOPin { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void pin_mode(gpio::Flags flags) override; | ||||
|   bool digital_read() override; | ||||
|   void digital_write(bool value) override; | ||||
|   std::string dump_summary() const override; | ||||
|  | ||||
|   void set_parent(PCA6416AComponent *parent) { parent_ = parent; } | ||||
|   void set_pin(uint8_t pin) { pin_ = pin; } | ||||
|   void set_inverted(bool inverted) { inverted_ = inverted; } | ||||
|   void set_flags(gpio::Flags flags) { flags_ = flags; } | ||||
|  | ||||
|  protected: | ||||
|   PCA6416AComponent *parent_; | ||||
|   uint8_t pin_; | ||||
|   bool inverted_; | ||||
|   gpio::Flags flags_; | ||||
| }; | ||||
|  | ||||
| }  // namespace pca6416a | ||||
| }  // namespace esphome | ||||
| @@ -81,7 +81,32 @@ void PN532::setup() { | ||||
|   this->turn_off_rf_(); | ||||
| } | ||||
|  | ||||
| bool PN532::powerdown() { | ||||
|   updates_enabled_ = false; | ||||
|   requested_read_ = false; | ||||
|   ESP_LOGI(TAG, "Powering down PN532"); | ||||
|   if (!this->write_command_({PN532_COMMAND_POWERDOWN, 0b10100000})) {  // enable i2c,spi wakeup | ||||
|     ESP_LOGE(TAG, "Error writing powerdown command to PN532"); | ||||
|     return false; | ||||
|   } | ||||
|   std::vector<uint8_t> response; | ||||
|   if (!this->read_response(PN532_COMMAND_POWERDOWN, response)) { | ||||
|     ESP_LOGE(TAG, "Error reading PN532 powerdown response"); | ||||
|     return false; | ||||
|   } | ||||
|   if (response[0] != 0x00) { | ||||
|     ESP_LOGE(TAG, "Error on PN532 powerdown: %02x", response[0]); | ||||
|     return false; | ||||
|   } | ||||
|   ESP_LOGV(TAG, "Powerdown successful"); | ||||
|   delay(1); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void PN532::update() { | ||||
|   if (!updates_enabled_) | ||||
|     return; | ||||
|  | ||||
|   for (auto *obj : this->binary_sensors_) | ||||
|     obj->on_scan_end(); | ||||
|  | ||||
|   | ||||
| @@ -17,6 +17,7 @@ static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14; | ||||
| static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32; | ||||
| static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; | ||||
| static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; | ||||
| static const uint8_t PN532_COMMAND_POWERDOWN = 0x16; | ||||
|  | ||||
| class PN532BinarySensor; | ||||
|  | ||||
| @@ -30,6 +31,7 @@ class PN532 : public PollingComponent { | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|   void loop() override; | ||||
|   void on_shutdown() override { powerdown(); } | ||||
|  | ||||
|   void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); } | ||||
|   void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } | ||||
| @@ -45,6 +47,7 @@ class PN532 : public PollingComponent { | ||||
|   void clean_mode(); | ||||
|   void format_mode(); | ||||
|   void write_mode(nfc::NdefMessage *message); | ||||
|   bool powerdown(); | ||||
|  | ||||
|  protected: | ||||
|   void turn_off_rf_(); | ||||
| @@ -79,6 +82,7 @@ class PN532 : public PollingComponent { | ||||
|   bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message); | ||||
|   bool clean_mifare_ultralight_(); | ||||
|  | ||||
|   bool updates_enabled_{true}; | ||||
|   bool requested_read_{false}; | ||||
|   std::vector<PN532BinarySensor *> binary_sensors_; | ||||
|   std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_; | ||||
|   | ||||
| @@ -791,6 +791,57 @@ async def raw_action(var, config, args): | ||||
|     cg.add(var.set_carrier_frequency(templ)) | ||||
|  | ||||
|  | ||||
| # Drayton | ||||
| ( | ||||
|     DraytonData, | ||||
|     DraytonBinarySensor, | ||||
|     DraytonTrigger, | ||||
|     DraytonAction, | ||||
|     DraytonDumper, | ||||
| ) = declare_protocol("Drayton") | ||||
| DRAYTON_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)), | ||||
|         cv.Required(CONF_CHANNEL): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)), | ||||
|         cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA) | ||||
| def drayton_binary_sensor(var, config): | ||||
|     cg.add( | ||||
|         var.set_data( | ||||
|             cg.StructInitializer( | ||||
|                 DraytonData, | ||||
|                 ("address", config[CONF_ADDRESS]), | ||||
|                 ("channel", config[CONF_CHANNEL]), | ||||
|                 ("command", config[CONF_COMMAND]), | ||||
|             ) | ||||
|         ) | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @register_trigger("drayton", DraytonTrigger, DraytonData) | ||||
| def drayton_trigger(var, config): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @register_dumper("drayton", DraytonDumper) | ||||
| def drayton_dumper(var, config): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @register_action("drayton", DraytonAction, DRAYTON_SCHEMA) | ||||
| async def drayton_action(var, config, args): | ||||
|     template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16) | ||||
|     cg.add(var.set_address(template_)) | ||||
|     template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8) | ||||
|     cg.add(var.set_channel(template_)) | ||||
|     template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8) | ||||
|     cg.add(var.set_command(template_)) | ||||
|  | ||||
|  | ||||
| # RC5 | ||||
| RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5") | ||||
| RC5_SCHEMA = cv.Schema( | ||||
|   | ||||
							
								
								
									
										213
									
								
								esphome/components/remote_base/drayton_protocol.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								esphome/components/remote_base/drayton_protocol.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | ||||
| #include "drayton_protocol.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace remote_base { | ||||
|  | ||||
| static const char *const TAG = "remote.drayton"; | ||||
|  | ||||
| static const uint32_t BIT_TIME_US = 500; | ||||
| static const uint8_t CARRIER_KHZ = 2; | ||||
| static const uint8_t NBITS_PREAMBLE = 12; | ||||
| static const uint8_t NBITS_SYNC = 4; | ||||
| static const uint8_t NBITS_ADDRESS = 16; | ||||
| static const uint8_t NBITS_CHANNEL = 5; | ||||
| static const uint8_t NBITS_COMMAND = 7; | ||||
| static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND; | ||||
|  | ||||
| static const uint8_t CMD_ON = 0x41; | ||||
| static const uint8_t CMD_OFF = 0x02; | ||||
|  | ||||
| /* | ||||
| Drayton Protocol | ||||
| Using an oscilloscope to capture the data transmitted by the Digistat two | ||||
| distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit | ||||
| has a period of 500us, a bit rate of 2000 baud. | ||||
|  | ||||
| Each packet consists of an initial 1010 pattern to set up the receiver bias. | ||||
| The number of these bits seen at the receiver varies depending on the state | ||||
| of the bias when the packet transmission starts. The receiver algoritmn takes | ||||
| account of this. | ||||
|  | ||||
| The packet appears to be Manchester encoded, with a '10' tranmitted pair | ||||
| representing a '1' bit and a '01' pair representing a '0' bit. Each packet is | ||||
| begun with a '1100' syncronisation symbol which breaks this rule. Following | ||||
| the sync are 28 '01' or '10' pairs. | ||||
|  | ||||
| -------------------- | ||||
|  | ||||
| Boiler On Command as received: | ||||
| 101010101010110001101001010101101001010101010101100101010101101001011001 | ||||
| ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-1-0-0-0-0-0-1-1-0-0-1-0 | ||||
|  | ||||
| (Where pppp represents the preamble bits and SSSS represents the sync symbol) | ||||
|  | ||||
| 28 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex) | ||||
|  | ||||
| Boiler Off Command as received: | ||||
| 101010101010110001101001010101101001010101010101010101010110011001011001 | ||||
| ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-0-0-0-0-0-1-0-1-0-0-1-0 | ||||
|  | ||||
| 28 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex) | ||||
|  | ||||
| -------------------- | ||||
|  | ||||
| I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to | ||||
| capture and retransmit the Digistat packets. RFLink splits each packet into an | ||||
| ID, SWITCH, and CMD field. | ||||
|  | ||||
| 0;17;Drayton;ID=c300;SWITCH=12;CMD=ON; | ||||
| 20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF; | ||||
|  | ||||
| -------------------- | ||||
|  | ||||
| Spliting my received data into three parts of 16, 7 and 5 bits gives address, | ||||
| channel and Command values of: | ||||
|  | ||||
| On  6180832  0110000110000000 1000001 10010 | ||||
| address: '0x6180' channel: '0x12' command: '0x41' | ||||
|  | ||||
| Off 6180052  0110000110000000 0000010 10010 | ||||
| address: '0x6180' channel: '0x12' command: '0x02' | ||||
|  | ||||
| These values are slightly different to those used by RFLink (the RFLink | ||||
| ID/Adress value is rotated/manipulated), and I don't know who's interpretation | ||||
| is correct. A larger data sample would help (I have only found five different | ||||
| packet captures online) or definitive information from Drayton. | ||||
|  | ||||
| Splitting each packet in this way works well for me with esphome. Any | ||||
| corrections or additional data samples would be gratefully received. | ||||
|  | ||||
| marshn | ||||
|  | ||||
| */ | ||||
|  | ||||
| void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) { | ||||
|   uint16_t khz = CARRIER_KHZ; | ||||
|   dst->set_carrier_frequency(khz * 1000); | ||||
|  | ||||
|   // Preamble = 101010101010 | ||||
|   uint32_t out_data = 0x0AAA; | ||||
|   for (uint32_t mask = 1UL << (NBITS_PREAMBLE - 1); mask != 0; mask >>= 1) { | ||||
|     if (out_data & mask) { | ||||
|       dst->mark(BIT_TIME_US); | ||||
|     } else { | ||||
|       dst->space(BIT_TIME_US); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Sync = 1100 | ||||
|   out_data = 0x000C; | ||||
|   for (uint32_t mask = 1UL << (NBITS_SYNC - 1); mask != 0; mask >>= 1) { | ||||
|     if (out_data & mask) { | ||||
|       dst->mark(BIT_TIME_US); | ||||
|     } else { | ||||
|       dst->space(BIT_TIME_US); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command); | ||||
|  | ||||
|   out_data = data.address; | ||||
|   out_data <<= NBITS_COMMAND; | ||||
|   out_data |= data.command; | ||||
|   out_data <<= NBITS_CHANNEL; | ||||
|   out_data |= data.channel; | ||||
|  | ||||
|   ESP_LOGV(TAG, "Send Drayton: out_data %08x", out_data); | ||||
|  | ||||
|   for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) { | ||||
|     if (out_data & mask) { | ||||
|       dst->mark(BIT_TIME_US); | ||||
|       dst->space(BIT_TIME_US); | ||||
|     } else { | ||||
|       dst->space(BIT_TIME_US); | ||||
|       dst->mark(BIT_TIME_US); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) { | ||||
|   DraytonData out{ | ||||
|       .address = 0, | ||||
|       .channel = 0, | ||||
|       .command = 0, | ||||
|   }; | ||||
|  | ||||
|   if (src.size() < 45) { | ||||
|     return {}; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGVV(TAG, "Decode Drayton: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), | ||||
|             src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), | ||||
|             src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), | ||||
|             src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19)); | ||||
|  | ||||
|   // If first preamble item is a space, skip it | ||||
|   if (src.peek_space_at_least(1)) { | ||||
|     src.advance(1); | ||||
|   } | ||||
|  | ||||
|   // Look for sync pulse, after. If sucessful index points to space of sync symbol | ||||
|   for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) { | ||||
|     ESP_LOGVV(TAG, "Decode Drayton: preamble %d  %d %d", preamble, src.peek(preamble), src.peek(preamble + 1)); | ||||
|     if (src.peek_mark(2 * BIT_TIME_US, preamble) && | ||||
|         (src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) { | ||||
|       src.advance(preamble + 1); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Read data. Index points to space of sync symbol | ||||
|   // Extract first bit | ||||
|   // Checks next bit to leave index pointing correctly | ||||
|   uint32_t out_data = 0; | ||||
|   uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1; | ||||
|   if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { | ||||
|     out_data |= 0 << bit; | ||||
|   } else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) && | ||||
|              (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { | ||||
|     out_data |= 1 << bit; | ||||
|   } else { | ||||
|     ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %d", src.get_index()); | ||||
|     return {}; | ||||
|   } | ||||
|  | ||||
|   // Before/after each bit is read the index points to the transition at the start of the bit period or, | ||||
|   // if there is no transition at the start of the bit period, then the transition in the middle of | ||||
|   // the previous bit period. | ||||
|   while (--bit >= 1) { | ||||
|     ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data); | ||||
|     if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) && | ||||
|         (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { | ||||
|       out_data |= 0 << bit; | ||||
|     } else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) && | ||||
|                (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { | ||||
|       out_data |= 1 << bit; | ||||
|     } else { | ||||
|       ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08x", bit, out_data); | ||||
|       return {}; | ||||
|     } | ||||
|   } | ||||
|   if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) { | ||||
|     out_data |= 0; | ||||
|   } else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) { | ||||
|     out_data |= 1; | ||||
|   } | ||||
|   ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data); | ||||
|  | ||||
|   out.channel = (uint8_t) (out_data & 0x1F); | ||||
|   out_data >>= NBITS_CHANNEL; | ||||
|   out.command = (uint8_t) (out_data & 0x7F); | ||||
|   out_data >>= NBITS_COMMAND; | ||||
|   out.address = (uint16_t) (out_data & 0xFFFF); | ||||
|  | ||||
|   return out; | ||||
| } | ||||
| void DraytonProtocol::dump(const DraytonData &data) { | ||||
|   ESP_LOGD(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address, | ||||
|            ((data.address << 1) & 0xffff), data.channel, data.command); | ||||
| } | ||||
|  | ||||
| }  // namespace remote_base | ||||
| }  // namespace esphome | ||||
							
								
								
									
										44
									
								
								esphome/components/remote_base/drayton_protocol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/remote_base/drayton_protocol.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "remote_base.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace remote_base { | ||||
|  | ||||
| struct DraytonData { | ||||
|   uint16_t address; | ||||
|   uint8_t channel; | ||||
|   uint8_t command; | ||||
|  | ||||
|   bool operator==(const DraytonData &rhs) const { | ||||
|     return address == rhs.address && channel == rhs.channel && command == rhs.command; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class DraytonProtocol : public RemoteProtocol<DraytonData> { | ||||
|  public: | ||||
|   void encode(RemoteTransmitData *dst, const DraytonData &data) override; | ||||
|   optional<DraytonData> decode(RemoteReceiveData src) override; | ||||
|   void dump(const DraytonData &data) override; | ||||
| }; | ||||
|  | ||||
| DECLARE_REMOTE_PROTOCOL(Drayton) | ||||
|  | ||||
| template<typename... Ts> class DraytonAction : public RemoteTransmitterActionBase<Ts...> { | ||||
|  public: | ||||
|   TEMPLATABLE_VALUE(uint16_t, address) | ||||
|   TEMPLATABLE_VALUE(uint8_t, channel) | ||||
|   TEMPLATABLE_VALUE(uint8_t, command) | ||||
|  | ||||
|   void encode(RemoteTransmitData *dst, Ts... x) override { | ||||
|     DraytonData data{}; | ||||
|     data.address = this->address_.value(x...); | ||||
|     data.channel = this->channel_.value(x...); | ||||
|     data.command = this->command_.value(x...); | ||||
|     DraytonProtocol().encode(dst, data); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace remote_base | ||||
| }  // namespace esphome | ||||
| @@ -286,7 +286,9 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value( | ||||
|             switch.switch_schema( | ||||
|                 SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG | ||||
|                 SprinklerControllerSwitch, | ||||
|                 entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                 default_restore_mode="RESTORE_DEFAULT_OFF", | ||||
|             ), | ||||
|             key=CONF_NAME, | ||||
|         ), | ||||
| @@ -333,7 +335,9 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema( | ||||
|         cv.Optional(CONF_NAME): cv.string, | ||||
|         cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value( | ||||
|             switch.switch_schema( | ||||
|                 SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG | ||||
|                 SprinklerControllerSwitch, | ||||
|                 entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                 default_restore_mode="RESTORE_DEFAULT_OFF", | ||||
|             ), | ||||
|             key=CONF_NAME, | ||||
|         ), | ||||
| @@ -343,19 +347,25 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema( | ||||
|         ), | ||||
|         cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value( | ||||
|             switch.switch_schema( | ||||
|                 SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG | ||||
|                 SprinklerControllerSwitch, | ||||
|                 entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                 default_restore_mode="RESTORE_DEFAULT_OFF", | ||||
|             ), | ||||
|             key=CONF_NAME, | ||||
|         ), | ||||
|         cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value( | ||||
|             switch.switch_schema( | ||||
|                 SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG | ||||
|                 SprinklerControllerSwitch, | ||||
|                 entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                 default_restore_mode="RESTORE_DEFAULT_OFF", | ||||
|             ), | ||||
|             key=CONF_NAME, | ||||
|         ), | ||||
|         cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value( | ||||
|             switch.switch_schema( | ||||
|                 SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG | ||||
|                 SprinklerControllerSwitch, | ||||
|                 entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                 default_restore_mode="RESTORE_DEFAULT_OFF", | ||||
|             ), | ||||
|             key=CONF_NAME, | ||||
|         ), | ||||
|   | ||||
| @@ -1176,6 +1176,21 @@ optional<uint32_t> Sprinkler::time_remaining_current_operation() { | ||||
|   return nullopt; | ||||
| } | ||||
|  | ||||
| bool Sprinkler::any_controller_is_active() { | ||||
|   if (this->state_ != IDLE) { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   for (auto &controller : this->other_controllers_) { | ||||
|     if (controller != this) {  // dummy check | ||||
|       if (controller->controller_state() != IDLE) { | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| SprinklerControllerSwitch *Sprinkler::control_switch(size_t valve_number) { | ||||
|   if (this->is_a_valid_valve(valve_number)) { | ||||
|     return this->valve_[valve_number].controller_switch; | ||||
|   | ||||
| @@ -406,6 +406,12 @@ class Sprinkler : public Component { | ||||
|   /// returns the amount of time remaining in seconds for all valves remaining, including the active valve, if any | ||||
|   optional<uint32_t> time_remaining_current_operation(); | ||||
|  | ||||
|   /// returns true if this or any sprinkler controller this controller knows about is active | ||||
|   bool any_controller_is_active(); | ||||
|  | ||||
|   /// returns the current state of the sprinkler controller | ||||
|   SprinklerState controller_state() { return this->state_; }; | ||||
|  | ||||
|   /// returns a pointer to a valve's control switch object | ||||
|   SprinklerControllerSwitch *control_switch(size_t valve_number); | ||||
|  | ||||
| @@ -503,7 +509,6 @@ class Sprinkler : public Component { | ||||
|   /// callback functions for timers | ||||
|   void valve_selection_callback_(); | ||||
|   void sm_timer_callback_(); | ||||
|   void pump_stop_delay_callback_(); | ||||
|  | ||||
|   /// Maximum allowed queue size | ||||
|   const uint8_t max_queue_size_{100}; | ||||
|   | ||||
| @@ -39,6 +39,9 @@ WaveshareEPaper4P2InBV2 = waveshare_epaper_ns.class_( | ||||
| WaveshareEPaper5P8In = waveshare_epaper_ns.class_( | ||||
|     "WaveshareEPaper5P8In", WaveshareEPaper | ||||
| ) | ||||
| WaveshareEPaper5P8InV2 = waveshare_epaper_ns.class_( | ||||
|     "WaveshareEPaper5P8InV2", WaveshareEPaper | ||||
| ) | ||||
| WaveshareEPaper7P5In = waveshare_epaper_ns.class_( | ||||
|     "WaveshareEPaper7P5In", WaveshareEPaper | ||||
| ) | ||||
| @@ -80,6 +83,7 @@ MODELS = { | ||||
|     "4.20in": ("b", WaveshareEPaper4P2In), | ||||
|     "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), | ||||
|     "5.83in": ("b", WaveshareEPaper5P8In), | ||||
|     "5.83inv2": ("b", WaveshareEPaper5P8InV2), | ||||
|     "7.50in": ("b", WaveshareEPaper7P5In), | ||||
|     "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), | ||||
|     "7.50in-bc": ("b", WaveshareEPaper7P5InBC), | ||||
|   | ||||
| @@ -1037,6 +1037,88 @@ void WaveshareEPaper5P8In::dump_config() { | ||||
|   LOG_PIN("  Busy Pin: ", this->busy_pin_); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| // ======================================================== | ||||
| //               5.83in V2 | ||||
| // Datasheet/Specification/Reference: | ||||
| //  - https://www.waveshare.com/w/upload/3/37/5.83inch_e-Paper_V2_Specification.pdf | ||||
| //  - https://github.com/waveshare/e-Paper/blob/master/Arduino/epd5in83_V2/epd5in83_V2.cpp | ||||
| // ======================================================== | ||||
| void WaveshareEPaper5P8InV2::initialize() { | ||||
|   // COMMAND POWER SETTING | ||||
|   this->command(0x01); | ||||
|   this->data(0x07); | ||||
|   this->data(0x07); | ||||
|   this->data(0x3f); | ||||
|   this->data(0x3f); | ||||
|  | ||||
|   // COMMAND POWER ON | ||||
|   this->command(0x04); | ||||
|   delay(10); | ||||
|   this->wait_until_idle_(); | ||||
|  | ||||
|   // PANNEL SETTING | ||||
|   this->command(0x00); | ||||
|   this->data(0x1F); | ||||
|  | ||||
|   // COMMAND RESOLUTION SETTING | ||||
|   this->command(0x61); | ||||
|   this->data(0x02); | ||||
|   this->data(0x88); | ||||
|   this->data(0x01); | ||||
|   this->data(0xE0); | ||||
|  | ||||
|   this->command(0x15); | ||||
|   this->data(0x00); | ||||
|  | ||||
|   // COMMAND TCON SETTING | ||||
|   this->command(0x60); | ||||
|   this->data(0x22); | ||||
|  | ||||
|   // Do we need this? | ||||
|   // COMMAND PLL CONTROL | ||||
|   this->command(0x30); | ||||
|   this->data(0x3C);  // 3A 100HZ   29 150Hz 39 200HZ  31 171HZ | ||||
| } | ||||
| void HOT WaveshareEPaper5P8InV2::display() { | ||||
|   // Reuse the code from WaveshareEPaper4P2In::display() | ||||
|   // COMMAND VCM DC SETTING REGISTER | ||||
|   this->command(0x82); | ||||
|   this->data(0x12); | ||||
|  | ||||
|   // COMMAND VCOM AND DATA INTERVAL SETTING | ||||
|   this->command(0x50); | ||||
|   this->data(0x97); | ||||
|  | ||||
|   // COMMAND DATA START TRANSMISSION 1 | ||||
|   this->command(0x10); | ||||
|   delay(2); | ||||
|   this->start_data_(); | ||||
|   this->write_array(this->buffer_, this->get_buffer_length_()); | ||||
|   this->end_data_(); | ||||
|   delay(2); | ||||
|  | ||||
|   // COMMAND DATA START TRANSMISSION 2 | ||||
|   this->command(0x13); | ||||
|   delay(2); | ||||
|   this->start_data_(); | ||||
|   this->write_array(this->buffer_, this->get_buffer_length_()); | ||||
|   this->end_data_(); | ||||
|  | ||||
|   // COMMAND DISPLAY REFRESH | ||||
|   this->command(0x12); | ||||
| } | ||||
| int WaveshareEPaper5P8InV2::get_width_internal() { return 648; } | ||||
| int WaveshareEPaper5P8InV2::get_height_internal() { return 480; } | ||||
| void WaveshareEPaper5P8InV2::dump_config() { | ||||
|   LOG_DISPLAY("", "Waveshare E-Paper", this); | ||||
|   ESP_LOGCONFIG(TAG, "  Model: 5.83inv2"); | ||||
|   LOG_PIN("  Reset Pin: ", this->reset_pin_); | ||||
|   LOG_PIN("  DC Pin: ", this->dc_pin_); | ||||
|   LOG_PIN("  Busy Pin: ", this->busy_pin_); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| void WaveshareEPaper7P5InBV2::initialize() { | ||||
|   // COMMAND POWER SETTING | ||||
|   this->command(0x01); | ||||
|   | ||||
| @@ -284,6 +284,49 @@ class WaveshareEPaper5P8In : public WaveshareEPaper { | ||||
|   int get_height_internal() override; | ||||
| }; | ||||
|  | ||||
| class WaveshareEPaper5P8InV2 : public WaveshareEPaper { | ||||
|  public: | ||||
|   void initialize() override; | ||||
|  | ||||
|   void display() override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void deep_sleep() override { | ||||
|     // COMMAND VCOM AND DATA INTERVAL SETTING | ||||
|     this->command(0x50); | ||||
|     this->data(0x17);  // border floating | ||||
|  | ||||
|     // COMMAND VCM DC SETTING | ||||
|     this->command(0x82); | ||||
|     // COMMAND PANEL SETTING | ||||
|     this->command(0x00); | ||||
|  | ||||
|     delay(100);  // NOLINT | ||||
|  | ||||
|     // COMMAND POWER SETTING | ||||
|     this->command(0x01); | ||||
|     this->data(0x00); | ||||
|     this->data(0x00); | ||||
|     this->data(0x00); | ||||
|     this->data(0x00); | ||||
|     this->data(0x00); | ||||
|     delay(100);  // NOLINT | ||||
|  | ||||
|     // COMMAND POWER OFF | ||||
|     this->command(0x02); | ||||
|     this->wait_until_idle_(); | ||||
|     // COMMAND DEEP SLEEP | ||||
|     this->command(0x07); | ||||
|     this->data(0xA5);  // check byte | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   int get_width_internal() override; | ||||
|  | ||||
|   int get_height_internal() override; | ||||
| }; | ||||
|  | ||||
| class WaveshareEPaper7P5In : public WaveshareEPaper { | ||||
|  public: | ||||
|   void initialize() override; | ||||
|   | ||||
| @@ -428,6 +428,9 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) { | ||||
| std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { | ||||
|   return json::build_json([obj, value, start_config](JsonObject root) { | ||||
|     set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       root["assumed_state"] = obj->assumed_state(); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { | ||||
|   | ||||
| @@ -88,7 +88,7 @@ lib_deps = | ||||
|     ${common:arduino.lib_deps} | ||||
|     ESP8266WiFi                           ; wifi (Arduino built-in) | ||||
|     Update                                ; ota (Arduino built-in) | ||||
|     ottowinter/ESPAsyncTCP-esphome@1.2.3  ; async_tcp | ||||
|     esphome/ESPAsyncTCP-esphome@1.2.3  ; async_tcp | ||||
|     ESP8266HTTPClient                     ; http_request (Arduino built-in) | ||||
|     ESP8266mDNS                           ; mdns (Arduino built-in) | ||||
|     DNSServer                             ; captive_portal (Arduino built-in) | ||||
|   | ||||
| @@ -10,8 +10,8 @@ platformio==6.1.6  # When updating platformio, also update Dockerfile | ||||
| esptool==4.5.1 | ||||
| click==8.1.3 | ||||
| esphome-dashboard==20230214.0 | ||||
| aioesphomeapi==13.7.1 | ||||
| zeroconf==0.56.0 | ||||
| aioesphomeapi==13.7.2 | ||||
| zeroconf==0.60.0 | ||||
|  | ||||
| # esp-idf requires this, but doesn't bundle it by default | ||||
| # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| pylint==2.17.3 | ||||
| flake8==6.0.0  # also change in .pre-commit-config.yaml when updating | ||||
| black==23.3.0  # also change in .pre-commit-config.yaml when updating | ||||
| pyupgrade==3.3.1  # also change in .pre-commit-config.yaml when updating | ||||
| pyupgrade==3.3.2  # also change in .pre-commit-config.yaml when updating | ||||
| pre-commit | ||||
|  | ||||
| # Unit tests | ||||
|   | ||||
| @@ -2,12 +2,22 @@ | ||||
| # This script is used to preinstall | ||||
| # all platformio libraries in the global storage | ||||
|  | ||||
| import argparse | ||||
| import configparser | ||||
| import subprocess | ||||
| import sys | ||||
|  | ||||
| config = configparser.ConfigParser(inline_comment_prefixes=(";",)) | ||||
| config.read(sys.argv[1]) | ||||
|  | ||||
| parser = argparse.ArgumentParser(description="") | ||||
| parser.add_argument("file", help="Path to platformio.ini", nargs=1) | ||||
| parser.add_argument("-l", "--libraries", help="Install libraries", action="store_true") | ||||
| parser.add_argument("-p", "--platforms", help="Install platforms", action="store_true") | ||||
| parser.add_argument("-t", "--tools", help="Install tools", action="store_true") | ||||
|  | ||||
| args = parser.parse_args() | ||||
|  | ||||
| config.read(args.file) | ||||
|  | ||||
|  | ||||
| libs = [] | ||||
| tools = [] | ||||
| @@ -15,7 +25,7 @@ platforms = [] | ||||
| # Extract from every lib_deps key in all sections | ||||
| for section in config.sections(): | ||||
|     conf = config[section] | ||||
|     if "lib_deps" in conf: | ||||
|     if "lib_deps" in conf and args.libraries: | ||||
|         for lib_dep in conf["lib_deps"].splitlines(): | ||||
|             if not lib_dep: | ||||
|                 # Empty line or comment | ||||
| @@ -28,10 +38,10 @@ for section in config.sections(): | ||||
|                 continue | ||||
|             libs.append("-l") | ||||
|             libs.append(lib_dep) | ||||
|     if "platform" in conf: | ||||
|     if "platform" in conf and args.platforms: | ||||
|         platforms.append("-p") | ||||
|         platforms.append(conf["platform"]) | ||||
|     if "platform_packages" in conf: | ||||
|     if "platform_packages" in conf and args.tools: | ||||
|         for tool in conf["platform_packages"].splitlines(): | ||||
|             if not tool: | ||||
|                 # Empty line or comment | ||||
|   | ||||
| @@ -15,4 +15,4 @@ pip3 install --no-use-pep517 -e . | ||||
|  | ||||
| pre-commit install | ||||
|  | ||||
| script/platformio_install_deps.py platformio.ini | ||||
| script/platformio_install_deps.py platformio.ini --libraries --tools --platforms | ||||
|   | ||||
| @@ -294,6 +294,9 @@ wled: | ||||
|  | ||||
| adalight: | ||||
|  | ||||
| esp32_ble: | ||||
|   io_capability: keyboard_only | ||||
|  | ||||
| esp32_ble_tracker: | ||||
|  | ||||
| ble_client: | ||||
| @@ -307,6 +310,19 @@ ble_client: | ||||
|     on_disconnect: | ||||
|       then: | ||||
|         - switch.turn_on: ble1_status | ||||
|     on_passkey_request: | ||||
|       then: | ||||
|         - ble_client.passkey_reply: | ||||
|             id: ble_blah | ||||
|             passkey: 123456 | ||||
|     on_passkey_notification: | ||||
|       then: | ||||
|         - logger.log: "Passkey notification received" | ||||
|     on_numeric_comparison_request: | ||||
|       then: | ||||
|         - ble_client.numeric_comparison_reply: | ||||
|             id: ble_blah | ||||
|             accept: True | ||||
|   - mac_address: C4:4F:33:11:22:33 | ||||
|     id: my_bedjet_ble_client | ||||
| bedjet: | ||||
| @@ -1276,6 +1292,13 @@ sensor: | ||||
|     name: DHT Absolute Humidity | ||||
|     temperature: dht_temperature | ||||
|     humidity: dht_humidity | ||||
|   - platform: hyt271 | ||||
|     i2c_id: i2c_bus | ||||
|     temperature: | ||||
|       name: "Temperature hyt271" | ||||
|       id: temp_etuve | ||||
|     humidity: | ||||
|       name: "Humidity hyt271" | ||||
|  | ||||
| esp32_touch: | ||||
|   setup_mode: false | ||||
| @@ -1449,6 +1472,13 @@ binary_sensor: | ||||
|       number: 1 | ||||
|       mode: INPUT | ||||
|       inverted: true | ||||
|   - platform: gpio | ||||
|     name: PCA6416A binary sensor | ||||
|     pin: | ||||
|       pca6416a: pca6416a_hub | ||||
|       number: 15 | ||||
|       mode: INPUT | ||||
|       inverted: true | ||||
|   - platform: gpio | ||||
|     name: MCP21 binary sensor | ||||
|     pin: | ||||
| @@ -2934,6 +2964,11 @@ pca9554: | ||||
|     address: 0x3F | ||||
|     i2c_id: i2c_bus | ||||
|  | ||||
| pca6416a: | ||||
|   - id: pca6416a_hub | ||||
|     address: 0x21 | ||||
|     i2c_id: i2c_bus | ||||
|  | ||||
| mcp23017: | ||||
|   - id: mcp23017_hub | ||||
|     open_drain_interrupt: true | ||||
|   | ||||
| @@ -374,6 +374,16 @@ binary_sensor: | ||||
|     on_press: | ||||
|       - logger.log: Touched | ||||
|  | ||||
|   - platform: gpio | ||||
|     name: MaxIn Pin 4 | ||||
|     pin: | ||||
|       max6956: max6956_1 | ||||
|       number: 4 | ||||
|       mode: | ||||
|         input: true | ||||
|         pullup: true | ||||
|       inverted: false | ||||
|  | ||||
| climate: | ||||
|   - platform: tuya | ||||
|     id: tuya_climate | ||||
| @@ -717,4 +727,8 @@ voice_assistant: | ||||
|         format: "Voice assistant error - code %s, message: %s" | ||||
|         args: [code.c_str(), message.c_str()] | ||||
|  | ||||
| max6956: | ||||
|   - id: max6956_1 | ||||
|     address: 0x40 | ||||
|  | ||||
| es8388: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user