diff --git a/CODEOWNERS b/CODEOWNERS index bfa4a7e59f..56899be01a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -198,6 +198,7 @@ esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core +esphome/components/wake_on_lan/* @willwill2will54 esphome/components/web_server_base/* @OttoWinter esphome/components/whirlpool/* @glmnet esphome/components/xiaomi_lywsd03mmc/* @ahpohl diff --git a/esphome/components/wake_on_lan/__init__.py b/esphome/components/wake_on_lan/__init__.py new file mode 100644 index 0000000000..3548fb02f4 --- /dev/null +++ b/esphome/components/wake_on_lan/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@willwill2will54"] diff --git a/esphome/components/wake_on_lan/button.py b/esphome/components/wake_on_lan/button.py new file mode 100644 index 0000000000..2710eb3df9 --- /dev/null +++ b/esphome/components/wake_on_lan/button.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CONF_TARGET_MAC_ADDRESS = "target_mac_address" + +wake_on_lan_ns = cg.esphome_ns.namespace("wake_on_lan") + +WakeOnLanButton = wake_on_lan_ns.class_("WakeOnLanButton", button.Button, cg.Component) + +DEPENDENCIES = ["network"] + +CONFIG_SCHEMA = cv.All( + button.BUTTON_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + cv.Schema( + { + cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address, + cv.GenerateID(): cv.declare_id(WakeOnLanButton), + } + ), + ), + cv.only_with_arduino, +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + yield cg.add(var.set_macaddr(*config[CONF_TARGET_MAC_ADDRESS].parts)) + yield cg.register_component(var, config) + yield button.register_button(var, config) diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp new file mode 100644 index 0000000000..893aa75895 --- /dev/null +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -0,0 +1,58 @@ +#ifdef USE_ARDUINO + +#include "wake_on_lan.h" +#include "esphome/core/log.h" +#include "esphome/components/network/ip_address.h" +#include "esphome/components/network/util.h" + +namespace esphome { +namespace wake_on_lan { + +static const char *const TAG = "wake_on_lan.button"; +static const uint8_t PREFIX[6] = {255, 255, 255, 255, 255, 255}; + +void WakeOnLanButton::set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f) { + macaddr_[0] = a; + macaddr_[1] = b; + macaddr_[2] = c; + macaddr_[3] = d; + macaddr_[4] = e; + macaddr_[5] = f; +} + +void WakeOnLanButton::dump_config() { + LOG_BUTTON("", "Wake-on-LAN Button", this); + ESP_LOGCONFIG(TAG, " Target MAC address: %02X:%02X:%02X:%02X:%02X:%02X", macaddr_[0], macaddr_[1], macaddr_[2], + macaddr_[3], macaddr_[4], macaddr_[5]); +} + +void WakeOnLanButton::press_action() { + ESP_LOGI(TAG, "Sending Wake-on-LAN Packet..."); + bool begin_status = false; + bool end_status = false; + uint32_t interface = esphome::network::get_ip_address(); + IPAddress interface_ip = IPAddress(interface); + IPAddress broadcast = IPAddress(255, 255, 255, 255); +#ifdef USE_ESP8266 + begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, interface_ip, 128); +#endif +#ifdef USE_ESP32 + begin_status = this->udp_client_.beginPacket(broadcast, 9); +#endif + + if (begin_status) { + this->udp_client_.write(PREFIX, 6); + for (size_t i = 0; i < 16; i++) { + this->udp_client_.write(macaddr_, 6); + } + end_status = this->udp_client_.endPacket(); + } + if (!begin_status || end_status) { + ESP_LOGE(TAG, "Sending Wake-on-LAN Packet Failed!"); + } +} + +} // namespace wake_on_lan +} // namespace esphome + +#endif diff --git a/esphome/components/wake_on_lan/wake_on_lan.h b/esphome/components/wake_on_lan/wake_on_lan.h new file mode 100644 index 0000000000..72f900e3fa --- /dev/null +++ b/esphome/components/wake_on_lan/wake_on_lan.h @@ -0,0 +1,27 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/button/button.h" +#include "esphome/core/component.h" +#include "WiFiUdp.h" + +namespace esphome { +namespace wake_on_lan { + +class WakeOnLanButton : public button::Button, public Component { + public: + void set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f); + + void dump_config() override; + + protected: + WiFiUDP udp_client_{}; + void press_action() override; + uint8_t macaddr_[6]; +}; + +} // namespace wake_on_lan +} // namespace esphome + +#endif diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py new file mode 100644 index 0000000000..c84715dcd8 --- /dev/null +++ b/tests/component_tests/button/test_button.py @@ -0,0 +1,46 @@ +"""Tests for the button component""" + + +def test_button_is_setup(generate_main): + """ + When the button is set in the yaml file if should be registered in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/button/test_button.yaml") + + # Then + assert "new wake_on_lan::WakeOnLanButton();" in main_cpp + assert "App.register_button" in main_cpp + assert "App.register_component" in main_cpp + + +def test_button_sets_mandatory_fields(generate_main): + """ + When the mandatory fields are set in the yaml, they should be set in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/button/test_button.yaml") + + # Then + assert 'wol_1->set_name("wol_test_1");' in main_cpp + assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp + + +def test_button_config_value_internal_set(generate_main): + """ + Test that the "internal" config value is correctly set + """ + # Given + + # When + main_cpp = generate_main( + "tests/component_tests/button/test_button.yaml" + ) + + # Then + assert "wol_1->set_internal(true);" in main_cpp + assert "wol_2->set_internal(false);" in main_cpp diff --git a/tests/component_tests/button/test_button.yaml b/tests/component_tests/button/test_button.yaml new file mode 100644 index 0000000000..3eac129e8c --- /dev/null +++ b/tests/component_tests/button/test_button.yaml @@ -0,0 +1,21 @@ +esphome: + name: test + platform: ESP8266 + board: d1_mini_lite + +wifi: + ssid: SomeNetwork + password: SomePassword + +button: + - platform: wake_on_lan + target_mac_address: 12:34:56:78:90:ab + name: wol_test_1 + id: wol_1 + internal: true + - platform: wake_on_lan + target_mac_address: 12:34:56:78:90:ab + name: wol_test_2 + id: wol_2 + internal: false + diff --git a/tests/test3.yaml b/tests/test3.yaml index 607d985704..d5df2ac3f2 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1350,6 +1350,12 @@ daly_bms: update_interval: 20s uart_id: uart1 +button: + - platform: wake_on_lan + target_mac_address: 12:34:56:78:90:ab + name: wol_test_1 + id: wol_1 + cd74hc4067: pin_s0: GPIO12 pin_s1: GPIO13