From 857a895d5169f584830eaed0fa543fb7126089c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Koco=C5=84?= Date: Sat, 11 Apr 2026 02:20:59 +0200 Subject: [PATCH] =?UTF-8?q?wiegand=5F2=20dzia=C5=82a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- boneio-32x10_switches_v0_7_standalone.yaml | 1298 ++++++++++++++++++++ configurator/generate_yaml.py | 305 +++++ configurator/requirements.txt | 1 + packages/devices_v0_7/display.yaml | 4 +- 4 files changed, 1606 insertions(+), 2 deletions(-) create mode 100644 boneio-32x10_switches_v0_7_standalone.yaml create mode 100644 configurator/generate_yaml.py create mode 100644 configurator/requirements.txt diff --git a/boneio-32x10_switches_v0_7_standalone.yaml b/boneio-32x10_switches_v0_7_standalone.yaml new file mode 100644 index 0000000..a1ad322 --- /dev/null +++ b/boneio-32x10_switches_v0_7_standalone.yaml @@ -0,0 +1,1298 @@ +substitutions: + name: boneio-32-sw-07-f43278 + friendly_name: 'BoneIO ESP 32x10 Switches' + serial_prefix: 'esp' #Don't change it. + +esphome: + name: '${name}' + friendly_name: '${friendly_name}' + name_add_mac_suffix: false + project: + name: boneio.32x10-lights + version: '0.7' + on_boot: + - priority: 1001 + then: + - lambda: |- + gpio_reset_pin((gpio_num_t)14); + gpio_reset_pin((gpio_num_t)15); + - priority: 0 + then: + - display.page.show: first_page + - component.update: ip_address + - component.update: oled_display + - script.execute: screensaver_script + +esp32: + board: nodemcu-32s + framework: + type: esp-idf + +ethernet: + id: eth + type: LAN8720 + mdc_pin: GPIO23 + mdio_pin: GPIO18 + clk: + pin: GPIO0 + mode: CLK_EXT_IN + phy_addr: 1 + power_pin: GPIO16 + +logger: +api: + reboot_timeout: 0s +ota: + - platform: esphome + - platform: web_server + +web_server: + port: 80 + local: true + +time: + - platform: homeassistant + timezone: Europe/Warsaw + id: homeassistant_time + +# ─── External components ────────────────────────────────────────────────────── + +external_components: + - source: github://boneIO-eu/esphome-LM75@main + components: [lm75] + +# ─── I2C ───────────────────────────────────────────────────────────────────── + +i2c: + - id: i2c_bus + sda: GPIO17 + scl: GPIO33 + scan: True + frequency: 400kHz + +# ─── GPIO Expanders ─────────────────────────────────────────────────────────── + +pcf8574: + - id: 'pcf_inputs_1to14' + i2c_id: i2c_bus + address: 0x20 + pcf8575: true + - id: 'pcf_inputs_15to28' + i2c_id: i2c_bus + address: 0x21 + pcf8575: true + - id: 'pcf_inputs_28to35_menu' + i2c_id: i2c_bus + address: 0x22 + pcf8575: false + - id: 'pcf_left' + address: 0x23 + pcf8575: true + i2c_id: i2c_bus + - id: 'pcf_right' + address: 0x24 + pcf8575: true + i2c_id: i2c_bus + +# ─── 1-Wire ─────────────────────────────────────────────────────────────────── + +one_wire: + - platform: gpio + pin: GPIO32 + +# ─── Wiegand ────────────────────────────────────────────────────────────────── + +wiegand: + - id: wiegand_reader_1 + d0: GPIO4 + d1: GPIO35 + on_tag: + - text_sensor.template.publish: + id: wiegand_1_tag + state: !lambda 'return x;' + - homeassistant.tag_scanned: !lambda 'return x;' + - lambda: |- + std::string tag = id(wiegand_1_tag).state; + std::string expected = id(ha_card_apartament_1).state; + if (tag == expected) { + ESP_LOGI("wiegand1", "Karta poprawna: %s - zwalniam zamek", tag.c_str()); + id(unlock_apartament_1).execute(); + } else { + ESP_LOGI("wiegand1", "Karta nieznana: %s (oczekiwano: %s)", tag.c_str(), expected.c_str()); + } + on_key: + - lambda: |- + ESP_LOGI("wiegand1", "Klawisz: %d", x); + on_raw: + - lambda: |- + ESP_LOGI("wiegand1", "Raw: %d bitow, wartosc %llx", bits, value); + + - id: wiegand_reader_2 + d0: GPIO36 + d1: GPIO39 + on_tag: + - text_sensor.template.publish: + id: wiegand_2_tag + state: !lambda 'return x;' + - homeassistant.tag_scanned: !lambda 'return x;' + - lambda: |- + std::string tag = id(wiegand_2_tag).state; + std::string expected = id(ha_card_apartament_2).state; + if (tag == expected) { + ESP_LOGI("wiegand2", "Karta poprawna: %s - zwalniam zamek", tag.c_str()); + id(unlock_apartament_2).execute(); + } else { + ESP_LOGI("wiegand2", "Karta nieznana: %s (oczekiwano: %s)", tag.c_str(), expected.c_str()); + } + on_key: + - lambda: |- + ESP_LOGI("wiegand2", "Klawisz: %d", x); + on_raw: + - lambda: |- + ESP_LOGI("wiegand2", "Raw: %d bitow, wartosc %llx", bits, value); + +key_collector: + - id: pincode_reader_1 + source_id: wiegand_reader_1 + min_length: 2 + max_length: 8 + end_keys: "#" + end_key_required: true + back_keys: "*" + allowed_keys: "0123456789" + timeout: 5s + on_progress: + - lambda: |- + ESP_LOGI("pin1", "PIN w trakcie: '%s'", x.c_str()); + on_result: + - text_sensor.template.publish: + id: wiegand_1_pin + state: !lambda 'return x;' + - lambda: |- + char expected[16]; + snprintf(expected, sizeof(expected), "%.0f", id(ha_pin_apartament_1).state); + if (x == expected) { + ESP_LOGI("pin1", "PIN poprawny: '%s' - zwalniam zamek", x.c_str()); + id(unlock_apartament_1).execute(); + } else { + ESP_LOGI("pin1", "PIN niepoprawny: '%s' (oczekiwano: '%s')", x.c_str(), expected); + } + on_timeout: + - lambda: |- + ESP_LOGI("pin1", "PIN timeout: '%s'", x.c_str()); + + - id: pincode_reader_2 + source_id: wiegand_reader_2 + min_length: 2 + max_length: 8 + end_keys: "#" + end_key_required: true + back_keys: "*" + allowed_keys: "0123456789" + timeout: 5s + on_progress: + - lambda: |- + ESP_LOGI("pin2", "PIN w trakcie: '%s'", x.c_str()); + on_result: + - text_sensor.template.publish: + id: wiegand_2_pin + state: !lambda 'return x;' + - lambda: |- + char expected[16]; + snprintf(expected, sizeof(expected), "%.0f", id(ha_pin_apartament_2).state); + if (x == expected) { + ESP_LOGI("pin2", "PIN poprawny: '%s' - zwalniam zamek", x.c_str()); + id(unlock_apartament_2).execute(); + } else { + ESP_LOGI("pin2", "PIN niepoprawny: '%s' (oczekiwano: '%s')", x.c_str(), expected); + } + on_timeout: + - lambda: |- + ESP_LOGI("pin2", "PIN timeout: '%s'", x.c_str()); + +# ─── Sensors ────────────────────────────────────────────────────────────────── + +sensor: + - platform: homeassistant + id: ha_pin_apartament_1 + entity_id: input_number.apartament_1 + internal: true + + - platform: homeassistant + id: ha_pin_apartament_2 + entity_id: input_number.apartament_2 + internal: true + + + - platform: uptime + id: wt32_uptime_seconds + update_interval: 60s + entity_category: diagnostic + on_raw_value: + then: + - text_sensor.template.publish: + id: wt32_uptime + state: !lambda |- + int seconds = round(id(wt32_uptime_seconds).raw_state); + int days = seconds / (24 * 3600); + seconds = seconds % (24 * 3600); + int hours = seconds / 3600; + seconds = seconds % 3600; + int minutes = seconds / 60; + return ( + (days ? to_string(days) + "d " : "") + + (hours ? to_string(hours) + "h " : "") + + (to_string(minutes) + "m") + ).c_str(); + + - platform: ina219 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + id: ina_current + name: 'INA219 Current' + power: + id: ina_power + name: 'INA219 Power' + bus_voltage: + id: ina_bus_voltage + name: 'INA219 Bus Voltage' + shunt_voltage: + id: ina_shunt_voltage + name: 'INA219 Shunt Voltage' + max_voltage: 32.0V + max_current: 3.2A + update_interval: 30s + + - platform: lm75 + id: boneIO_temp + name: 'LM75B Temperature' + update_interval: 30s + +# ─── Text Sensors ───────────────────────────────────────────────────────────── + +text_sensor: + - platform: homeassistant + id: ha_card_apartament_1 + entity_id: input_text.apartament_karta_1 + internal: true + + - platform: homeassistant + id: ha_card_apartament_2 + entity_id: input_text.apartament_karta_2 + internal: true + + + - platform: template + name: Uptime + id: wt32_uptime + entity_category: diagnostic + icon: mdi:clock-start + + - platform: template + name: 'IP Address' + id: ip_address + entity_category: diagnostic + icon: 'mdi:ip-network' + lambda: |- + return id(eth).get_ip_addresses().empty() ? "Unset" : id(eth).get_ip_addresses()[0].str(); + update_interval: 60s + + - platform: template + name: 'Serial No.' + id: serial_no + lambda: |- + std::string mac = get_mac_address(); + return std::string("${serial_prefix}") + mac.substr(mac.length()/2); + icon: mdi:expansion-card-variant + entity_category: diagnostic + update_interval: 60min + + - platform: template + name: 'Wiegand 1 Tag' + id: wiegand_1_tag + icon: mdi:card-account-details + + - platform: template + name: 'Wiegand 2 Tag' + id: wiegand_2_tag + icon: mdi:card-account-details + + - platform: template + name: 'Wiegand 1 PIN' + id: wiegand_1_pin + icon: mdi:numeric + + - platform: template + name: 'Wiegand 2 PIN' + id: wiegand_2_pin + icon: mdi:numeric + +# ─── Buzzer ─────────────────────────────────────────────────────────────────── + +switch: + - platform: gpio + id: buzzer + name: 'Buzzer' + pin: + number: GPIO2 + mode: + output: true + inverted: false + +# ─── Output ─────────────────────────────────────────────────────────────────── + + - platform: output + name: 'Elektrozamek apartament 1' + output: out_01 + id: switch_01 + restore_mode: ALWAYS_ON + - platform: output + name: 'Elektrozamek apartament 2' + output: out_02 + id: switch_02 + restore_mode: ALWAYS_ON + - platform: output + name: 'Switch 03' + output: out_03 + id: switch_03 + - platform: output + name: 'Switch 04' + output: out_04 + id: switch_04 + - platform: output + name: 'Switch 05' + output: out_05 + id: switch_05 + - platform: output + name: 'Switch 06' + output: out_06 + id: switch_06 + - platform: output + name: 'Switch 07' + output: out_07 + id: switch_07 + - platform: output + name: 'Switch 08' + output: out_08 + id: switch_08 + - platform: output + name: 'Switch 09' + output: out_09 + id: switch_09 + - platform: output + name: 'Switch 10' + output: out_10 + id: switch_10 + - platform: output + name: 'Switch 11' + output: out_11 + id: switch_11 + - platform: output + name: 'Switch 12' + output: out_12 + id: switch_12 + - platform: output + name: 'Switch 13' + output: out_13 + id: switch_13 + - platform: output + name: 'Switch 14' + output: out_14 + id: switch_14 + - platform: output + name: 'Switch 15' + output: out_15 + id: switch_15 + - platform: output + name: 'Switch 16' + output: out_16 + id: switch_16 + - platform: output + name: 'Switch 17' + output: out_17 + id: switch_17 + - platform: output + name: 'Switch 18' + output: out_18 + id: switch_18 + - platform: output + name: 'Switch 19' + output: out_19 + id: switch_19 + - platform: output + name: 'Switch 20' + output: out_20 + id: switch_20 + - platform: output + name: 'Switch 21' + output: out_21 + id: switch_21 + - platform: output + name: 'Switch 22' + output: out_22 + id: switch_22 + - platform: output + name: 'Switch 23' + output: out_23 + id: switch_23 + - platform: output + name: 'Switch 24' + output: out_24 + id: switch_24 + - platform: output + name: 'Switch 25' + output: out_25 + id: switch_25 + - platform: output + name: 'Switch 26' + output: out_26 + id: switch_26 + - platform: output + name: 'Switch 27' + output: out_27 + id: switch_27 + - platform: output + name: 'Switch 28' + output: out_28 + id: switch_28 + - platform: output + name: 'Switch 29' + output: out_29 + id: switch_29 + - platform: output + name: 'Switch 30' + output: out_30 + id: switch_30 + - platform: output + name: 'Switch 31' + output: out_31 + id: switch_31 + - platform: output + name: 'Switch 32' + output: out_32 + id: switch_32 + +# ─── GPIO Outputs (PCF expanders) ───────────────────────────────────────────── + +output: + - platform: gpio + id: out_01 + pin: + pcf8574: pcf_left + number: 15 + mode: + output: true + inverted: true + - platform: gpio + id: out_02 + pin: + pcf8574: pcf_left + number: 14 + mode: + output: true + inverted: true + - platform: gpio + id: out_03 + pin: + pcf8574: pcf_left + number: 13 + mode: + output: true + inverted: true + - platform: gpio + id: out_04 + pin: + pcf8574: pcf_left + number: 12 + mode: + output: true + inverted: true + - platform: gpio + id: out_05 + pin: + pcf8574: pcf_left + number: 11 + mode: + output: true + inverted: true + - platform: gpio + id: out_06 + pin: + pcf8574: pcf_left + number: 10 + mode: + output: true + inverted: true + - platform: gpio + id: out_07 + pin: + pcf8574: pcf_left + number: 9 + mode: + output: true + inverted: true + - platform: gpio + id: out_08 + pin: + pcf8574: pcf_left + number: 8 + mode: + output: true + inverted: true + - platform: gpio + id: out_09 + pin: + pcf8574: pcf_right + number: 15 + mode: + output: true + inverted: true + - platform: gpio + id: out_10 + pin: + pcf8574: pcf_right + number: 14 + mode: + output: true + inverted: true + - platform: gpio + id: out_11 + pin: + pcf8574: pcf_right + number: 13 + mode: + output: true + inverted: true + - platform: gpio + id: out_12 + pin: + pcf8574: pcf_right + number: 12 + mode: + output: true + inverted: true + - platform: gpio + id: out_13 + pin: + pcf8574: pcf_right + number: 11 + mode: + output: true + inverted: true + - platform: gpio + id: out_14 + pin: + pcf8574: pcf_right + number: 10 + mode: + output: true + inverted: true + - platform: gpio + id: out_15 + pin: + pcf8574: pcf_right + number: 9 + mode: + output: true + inverted: true + - platform: gpio + id: out_16 + pin: + pcf8574: pcf_right + number: 8 + mode: + output: true + inverted: true + - platform: gpio + id: out_17 + pin: + pcf8574: pcf_left + number: 0 + mode: + output: true + inverted: true + - platform: gpio + id: out_18 + pin: + pcf8574: pcf_left + number: 1 + mode: + output: true + inverted: true + - platform: gpio + id: out_19 + pin: + pcf8574: pcf_left + number: 2 + mode: + output: true + inverted: true + - platform: gpio + id: out_20 + pin: + pcf8574: pcf_left + number: 3 + mode: + output: true + inverted: true + - platform: gpio + id: out_21 + pin: + pcf8574: pcf_left + number: 4 + mode: + output: true + inverted: true + - platform: gpio + id: out_22 + pin: + pcf8574: pcf_left + number: 5 + mode: + output: true + inverted: true + - platform: gpio + id: out_23 + pin: + pcf8574: pcf_left + number: 6 + mode: + output: true + inverted: true + - platform: gpio + id: out_24 + pin: + pcf8574: pcf_left + number: 7 + mode: + output: true + inverted: true + - platform: gpio + id: out_25 + pin: + pcf8574: pcf_right + number: 0 + mode: + output: true + inverted: true + - platform: gpio + id: out_26 + pin: + pcf8574: pcf_right + number: 1 + mode: + output: true + inverted: true + - platform: gpio + id: out_27 + pin: + pcf8574: pcf_right + number: 2 + mode: + output: true + inverted: true + - platform: gpio + id: out_28 + pin: + pcf8574: pcf_right + number: 3 + mode: + output: true + inverted: true + - platform: gpio + id: out_29 + pin: + pcf8574: pcf_right + number: 4 + mode: + output: true + inverted: true + - platform: gpio + id: out_30 + pin: + pcf8574: pcf_right + number: 5 + mode: + output: true + inverted: true + - platform: gpio + id: out_31 + pin: + pcf8574: pcf_right + number: 6 + mode: + output: true + inverted: true + - platform: gpio + id: out_32 + pin: + pcf8574: pcf_right + number: 7 + mode: + output: true + inverted: true + +# ─── Binary Sensors (Inputs) ────────────────────────────────────────────────── + +binary_sensor: + - platform: gpio + entity_category: config + id: boneIO_button + pin: + pcf8574: pcf_inputs_28to35_menu + number: 7 + mode: + input: true + inverted: true + on_press: + then: + - if: + condition: + display.is_displaying_page: last_page + then: + - display.page.show: first_page + - component.update: oled_display + else: + - if: + condition: + display.is_displaying_page: screensaver + then: + - display.page.show: first_page + - component.update: oled_display + else: + - display.page.show_next: oled_display + - component.update: oled_display + - script.execute: screensaver_script + + - platform: gpio + name: 'IN_01' + id: in_01 + pin: + pcf8574: pcf_inputs_1to14 + number: 0 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_01 + + - platform: gpio + name: 'IN_02' + id: in_02 + pin: + pcf8574: pcf_inputs_1to14 + number: 1 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_02 + + - platform: gpio + name: 'IN_03' + id: in_03 + pin: + pcf8574: pcf_inputs_1to14 + number: 2 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_03 + + - platform: gpio + name: 'IN_04' + id: in_04 + pin: + pcf8574: pcf_inputs_1to14 + number: 3 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_04 + + - platform: gpio + name: 'IN_05' + id: in_05 + pin: + pcf8574: pcf_inputs_1to14 + number: 4 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_05 + + - platform: gpio + name: 'IN_06' + id: in_06 + pin: + pcf8574: pcf_inputs_1to14 + number: 5 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_06 + + - platform: gpio + name: 'IN_07' + id: in_07 + pin: + pcf8574: pcf_inputs_1to14 + number: 6 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_07 + + - platform: gpio + name: 'IN_08' + id: in_08 + pin: + pcf8574: pcf_inputs_1to14 + number: 8 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_08 + + - platform: gpio + name: 'IN_09' + id: in_09 + pin: + pcf8574: pcf_inputs_1to14 + number: 9 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_09 + + - platform: gpio + name: 'IN_10' + id: in_10 + pin: + pcf8574: pcf_inputs_1to14 + number: 10 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_10 + + - platform: gpio + name: 'IN_11' + id: in_11 + pin: + pcf8574: pcf_inputs_1to14 + number: 11 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_11 + + - platform: gpio + name: 'IN_12' + id: in_12 + pin: + pcf8574: pcf_inputs_1to14 + number: 12 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_12 + + - platform: gpio + name: 'IN_13' + id: in_13 + pin: + pcf8574: pcf_inputs_1to14 + number: 13 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_13 + + - platform: gpio + name: 'IN_14' + id: in_14 + pin: + pcf8574: pcf_inputs_1to14 + number: 14 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_14 + + - platform: gpio + name: 'IN_15' + id: in_15 + pin: + pcf8574: pcf_inputs_15to28 + number: 6 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_15 + + - platform: gpio + name: 'IN_16' + id: in_16 + pin: + pcf8574: pcf_inputs_15to28 + number: 5 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_16 + + - platform: gpio + name: 'IN_17' + id: in_17 + pin: + pcf8574: pcf_inputs_15to28 + number: 4 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_17 + + - platform: gpio + name: 'IN_18' + id: in_18 + pin: + pcf8574: pcf_inputs_15to28 + number: 3 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_18 + + - platform: gpio + name: 'IN_19' + id: in_19 + pin: + pcf8574: pcf_inputs_15to28 + number: 2 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_19 + + - platform: gpio + name: 'IN_20' + id: in_20 + pin: + pcf8574: pcf_inputs_15to28 + number: 1 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_20 + + - platform: gpio + name: 'IN_21' + id: in_21 + pin: + pcf8574: pcf_inputs_15to28 + number: 0 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_21 + + - platform: gpio + name: 'IN_22' + id: in_22 + pin: + pcf8574: pcf_inputs_15to28 + number: 8 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_22 + + - platform: gpio + name: 'IN_23' + id: in_23 + pin: + pcf8574: pcf_inputs_15to28 + number: 9 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_23 + + - platform: gpio + name: 'IN_24' + id: in_24 + pin: + pcf8574: pcf_inputs_15to28 + number: 10 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_24 + + - platform: gpio + name: 'IN_25' + id: in_25 + pin: + pcf8574: pcf_inputs_15to28 + number: 11 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_25 + + - platform: gpio + name: 'IN_26' + id: in_26 + pin: + pcf8574: pcf_inputs_15to28 + number: 12 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_26 + + - platform: gpio + name: 'IN_27' + id: in_27 + pin: + pcf8574: pcf_inputs_15to28 + number: 13 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_27 + + - platform: gpio + name: 'IN_28' + id: in_28 + pin: + pcf8574: pcf_inputs_15to28 + number: 14 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_28 + + - platform: gpio + name: 'IN_29' + id: in_29 + pin: + pcf8574: pcf_inputs_28to35_menu + number: 0 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_29 + + - platform: gpio + name: 'IN_30' + id: in_30 + pin: + pcf8574: pcf_inputs_28to35_menu + number: 1 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_30 + + - platform: gpio + name: 'IN_31' + id: in_31 + pin: + pcf8574: pcf_inputs_28to35_menu + number: 2 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_31 + + - platform: gpio + name: 'IN_32' + id: in_32 + pin: + pcf8574: pcf_inputs_28to35_menu + number: 3 + mode: + input: true + inverted: true + on_press: + then: + - switch.toggle: switch_32 + + - platform: gpio + name: 'IN_33' + pin: + pcf8574: pcf_inputs_28to35_menu + number: 4 + mode: + input: true + inverted: true + + - platform: gpio + name: 'IN_34' + pin: + pcf8574: pcf_inputs_28to35_menu + number: 5 + mode: + input: true + inverted: true + + - platform: gpio + name: 'IN_35' + pin: + pcf8574: pcf_inputs_28to35_menu + number: 6 + mode: + input: true + inverted: true + +# ─── Display ────────────────────────────────────────────────────────────────── + +font: + - file: 'gfonts://Ubuntu' + id: size_10 + size: 10 + +script: + - id: unlock_apartament_1 + mode: single + then: + - switch.turn_off: switch_01 + - delay: 5s + - switch.turn_on: switch_01 + + - id: unlock_apartament_2 + mode: single + then: + - switch.turn_off: switch_02 + - delay: 5s + - switch.turn_on: switch_02 + + - id: screensaver_script + mode: restart + then: + - delay: 30s + - display.page.show: screensaver + - component.update: oled_display + +display: + - platform: ssd1306_i2c + id: oled_display + model: 'SH1106 128x64' + address: 0x3C + contrast: 0.5 + pages: + - id: first_page + lambda: |- + it.rectangle(0, 0, 126, 15); + it.printf(64,11, id(size_10), TextAlign::BASELINE_CENTER, "Peak Control"); + it.printf(4, 37, id(size_10), TextAlign::BASELINE_LEFT ,"IP addr:"); + it.printf(124, 37, id(size_10), TextAlign::BASELINE_RIGHT ,"%s", id(ip_address).state.c_str()); + it.printf(4, 49, id(size_10), TextAlign::BASELINE_LEFT ,"Uptime:"); + it.printf(124, 49, id(size_10), TextAlign::BASELINE_RIGHT ,"%s", id(wt32_uptime).state.c_str()); + it.printf(4, 61, id(size_10), TextAlign::BASELINE_LEFT ,"Temperature:"); + it.printf(124, 61, id(size_10), TextAlign::BASELINE_RIGHT ,"%.2f°C", id(boneIO_temp).state); + - id: last_page + lambda: |- + it.rectangle(0, 0, 126, 15); + it.printf(64,11, id(size_10), TextAlign::BASELINE_CENTER, "Peak Control"); + it.printf(4, 25, id(size_10), TextAlign::BASELINE_LEFT ,"Current:"); + it.printf(124, 25, id(size_10), TextAlign::BASELINE_RIGHT ,"%.3fA", id(ina_current).state); + it.printf(4, 37, id(size_10), TextAlign::BASELINE_LEFT ,"Power:"); + it.printf(124, 37, id(size_10), TextAlign::BASELINE_RIGHT ,"%.2fW", id(ina_power).state); + it.printf(4, 49, id(size_10), TextAlign::BASELINE_LEFT ,"Bus Volt:"); + it.printf(124, 49, id(size_10), TextAlign::BASELINE_RIGHT ,"%.2fV", id(ina_bus_voltage).state); + it.printf(4, 61, id(size_10), TextAlign::BASELINE_LEFT ,"Shunt Volt:"); + it.printf(124, 61, id(size_10), TextAlign::BASELINE_RIGHT ,"%.2fV", id(ina_shunt_voltage).state); + - id: screensaver + lambda: |- + it.fill(COLOR_OFF); + +###################### +### MODBUS SECTION ### +###################### +# UNCOMMENT BELOW TO USE MODBUS +# uart: +# id: uart_pin14_15 +# rx_pin: +# number: GPIO14 +# mode: +# input: true +# pullup: true +# tx_pin: GPIO15 +# baud_rate: 9600 +# stop_bits: 1 + +# modbus: +# send_wait_time: 200ms +# uart_id: uart_pin14_15 +# id: boneio_modbus + +# modbus_controller: +# - id: YOURDEVICE ID +# address: 0x09 +# modbus_id: mod_bus +# setup_priority: -10 +# update_interval: 60s diff --git a/configurator/generate_yaml.py b/configurator/generate_yaml.py new file mode 100644 index 0000000..b974176 --- /dev/null +++ b/configurator/generate_yaml.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 +""" +BoneIO YAML Configurator +Generuje konfigurację ESPHome (switch/light/fan) na podstawie arkusza Excel. + +Użycie: + python generate_yaml.py # tworzy pusty szablon boneio_config.xlsx + python generate_yaml.py --template # to samo co wyżej + python generate_yaml.py boneio_config.xlsx # generuje YAML z wypełnionego arkusza +""" + +import sys +import argparse +from pathlib import Path + +try: + import openpyxl + from openpyxl.styles import PatternFill, Font, Alignment + from openpyxl.worksheet.datavalidation import DataValidation +except ImportError: + print("Wymagana biblioteka: pip install openpyxl") + sys.exit(1) + +TEMPLATE_FILE = "boneio_config.xlsx" +MAX_OUTPUTS = 32 +ENTITY_TYPES = ["switch", "light", "fan"] + +# Kolumny (1-based) +COL_NR = 1 # A +COL_ID = 2 # B +COL_NAME = 3 # C +COL_TYPE = 4 # D +COL_ROOM = 5 # E +COL_ICON = 6 # F +COL_INPUT1 = 7 # G +COL_INPUT2 = 8 # H + +HEADERS = [ + "Nr", + "Output ID", + "Nazwa", + "Typ", + "Pokój", + "Ikona (mdi:...)", + "Input 1", + "Input 2", +] + +COLOR_HEADER = "1F4E79" +COLOR_ODD_ROW = "DCE6F1" +COLOR_EVEN_ROW = "FFFFFF" +COLOR_EXAMPLE = "F2F2F2" + + +def create_template(filename=TEMPLATE_FILE): + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "Konfiguracja" + + # --- Nagłówek --- + header_fill = PatternFill(start_color=COLOR_HEADER, end_color=COLOR_HEADER, fill_type="solid") + header_font = Font(color="FFFFFF", bold=True, size=11) + + for col, label in enumerate(HEADERS, 1): + cell = ws.cell(row=1, column=col, value=label) + cell.fill = header_fill + cell.font = header_font + cell.alignment = Alignment(horizontal="center", vertical="center") + + ws.row_dimensions[1].height = 25 + + # --- Szerokości kolumn --- + ws.column_dimensions["A"].width = 5 + ws.column_dimensions["B"].width = 12 + ws.column_dimensions["C"].width = 32 + ws.column_dimensions["D"].width = 10 + ws.column_dimensions["E"].width = 22 + ws.column_dimensions["F"].width = 22 + ws.column_dimensions["G"].width = 16 + ws.column_dimensions["H"].width = 16 + + # --- Walidacja listy rozwijalnej dla kolumny Typ --- + dv = DataValidation( + type="list", + formula1='"switch,light,fan"', + allow_blank=True, + showDropDown=False, + showErrorMessage=True, + errorTitle="Błędny typ", + error="Wybierz: switch, light lub fan", + ) + dv.sqref = f"D2:D{MAX_OUTPUTS + 1}" + ws.add_data_validation(dv) + + # --- Wiersze wyjść --- + for i in range(1, MAX_OUTPUTS + 1): + row = i + 1 + fill_color = COLOR_ODD_ROW if i % 2 == 1 else COLOR_EVEN_ROW + row_fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type="solid") + + ws.cell(row=row, column=COL_NR, value=i).font = Font(color="888888") + ws.cell(row=row, column=COL_ID, value=f"out_{i:02d}").font = Font(color="888888") + + for col in range(1, len(HEADERS) + 1): + cell = ws.cell(row=row, column=col) + cell.fill = row_fill + cell.alignment = Alignment(vertical="center") + + # Zamrożenie nagłówka i pierwszych dwóch kolumn + ws.freeze_panes = "C2" + + # --- Arkusz z podpowiedziami --- + ws2 = wb.create_sheet("Pomoc") + ws2.column_dimensions["A"].width = 28 + ws2.column_dimensions["B"].width = 35 + + def h(row, col, val, bold=False): + c = ws2.cell(row=row, column=col, value=val) + if bold: + c.font = Font(bold=True) + return c + + h(1, 1, "Typ encji", bold=True) + h(1, 2, "Opis", bold=True) + h(2, 1, "switch"); h(2, 2, "Zwykły przełącznik (np. gniazdko, grzejnik)") + h(3, 1, "light"); h(3, 2, "Światło — widoczne jako żarówka w HA") + h(4, 1, "fan"); h(4, 2, "Wentylator — widoczny z ikoną wentylatora w HA") + + h(6, 1, "Input 1 / Input 2", bold=True) + h(7, 1, ""); h(7, 2, "ID binarnego sensora z YAML (np. boneIO_input_01)") + h(8, 2, "Wciśnięcie przycisku będzie toggleować dany output") + h(9, 2, "Możesz podać dwa różne przyciski sterujące tym samym outputem") + + h(11, 1, "Popularne ikony", bold=True) + icons = [ + ("mdi:lightbulb", "żarówka"), + ("mdi:ceiling-light", "lampa sufitowa"), + ("mdi:floor-lamp", "lampa stojąca"), + ("mdi:wall-sconce-flat", "kinkiet"), + ("mdi:led-strip-variant", "taśma LED"), + ("mdi:outdoor-lamp", "lampa ogrodowa"), + ("mdi:fan", "wentylator"), + ("mdi:toggle-switch", "przełącznik"), + ("mdi:power-socket-eu", "gniazdko"), + ("mdi:heating-coil", "ogrzewanie"), + ("mdi:air-conditioner", "klimatyzacja"), + ("mdi:garage", "garaż"), + ("mdi:gate", "brama"), + ("mdi:television", "TV / AV"), + ("mdi:water-pump", "pompa"), + ] + ex_fill = PatternFill(start_color=COLOR_EXAMPLE, end_color=COLOR_EXAMPLE, fill_type="solid") + for idx, (icon, desc) in enumerate(icons, 12): + c1 = ws2.cell(row=idx, column=1, value=icon) + c2 = ws2.cell(row=idx, column=2, value=desc) + c1.fill = ex_fill + c2.fill = ex_fill + + wb.save(filename) + print(f"[OK] Szablon zapisany: {filename}") + print(f" Wypełnij arkusz 'Konfiguracja', potem uruchom:") + print(f" python generate_yaml.py {filename}") + + +# --------------------------------------------------------------------------- + +def load_entries(ws): + entries = [] + for i in range(1, MAX_OUTPUTS + 1): + row = i + 1 + name = ws.cell(row=row, column=COL_NAME).value + etype = (ws.cell(row=row, column=COL_TYPE).value or "").lower().strip() + if not name or etype not in ENTITY_TYPES: + continue + entries.append({ + "nr": i, + "output_id": ws.cell(row=row, column=COL_ID).value or f"out_{i:02d}", + "name": str(name).strip(), + "type": etype, + "room": str(ws.cell(row=row, column=COL_ROOM).value or "").strip(), + "icon": str(ws.cell(row=row, column=COL_ICON).value or "").strip(), + "input1": str(ws.cell(row=row, column=COL_INPUT1).value or "").strip(), + "input2": str(ws.cell(row=row, column=COL_INPUT2).value or "").strip(), + }) + return entries + + +def entity_id(e): + return f"{e['type']}_{e['nr']:02d}" + + +def yaml_block(entries, etype, platform): + items = [e for e in entries if e["type"] == etype] + if not items: + return [] + + lines = [f"{etype}:"] + for e in items: + eid = entity_id(e) + room_comment = f" # {e['room']}" if e["room"] else "" + lines.append(f" - platform: {platform}{room_comment}") + lines.append(f' name: "{e["name"]}"') + lines.append(f" output: {e['output_id']}") + lines.append(f" id: {eid}") + if e["icon"]: + lines.append(f" icon: {e['icon']}") + lines.append("") + return lines + + +def generate_yaml(filename): + wb = openpyxl.load_workbook(filename) + + if "Konfiguracja" not in wb.sheetnames: + print(f"[ERROR] Arkusz 'Konfiguracja' nie znaleziony w {filename}") + sys.exit(1) + + ws = wb["Konfiguracja"] + entries = load_entries(ws) + + if not entries: + print("[WARN] Brak skonfigurowanych wyjść. Wypełnij kolumny Nazwa i Typ.") + return + + # Mapowanie wejść + input_map: dict[str, list[tuple[str, str]]] = {} + for e in entries: + action = f"{e['type']}.toggle" + eid = entity_id(e) + for inp in [e["input1"], e["input2"]]: + if inp: + input_map.setdefault(inp, []).append((action, eid)) + + lines = [ + "# ============================================================", + f"# Wygenerowano przez BoneIO Configurator", + f"# Źródło: {Path(filename).name}", + "# ============================================================", + "", + ] + + lines += yaml_block(entries, "switch", "output") + lines += yaml_block(entries, "light", "binary") + lines += yaml_block(entries, "fan", "binary") + + if input_map: + lines += [ + "# --- Mapowanie wejść ---", + "# Dopisz poniższe on_click do istniejących binary_sensor w swoim YAML", + "# (wyszukaj ID sensora i dodaj on_click)", + "", + ] + for inp_id, actions in sorted(input_map.items()): + lines.append(f" # binary_sensor id: {inp_id}") + lines.append(f" # on_click:") + for action, eid in actions: + lines.append(f" # - {action}: {eid}") + lines.append("") + + output_text = "\n".join(lines) + + out_file = Path(filename).with_suffix(".yaml") + out_file.write_text(output_text, encoding="utf-8") + + n_sw = sum(1 for e in entries if e["type"] == "switch") + n_li = sum(1 for e in entries if e["type"] == "light") + n_fan = sum(1 for e in entries if e["type"] == "fan") + + print(f"[OK] YAML zapisany: {out_file}") + print(f" switch: {n_sw} light: {n_li} fan: {n_fan} (razem: {len(entries)})") + if input_map: + print(f" mapowania wejść: {len(input_map)}") + print() + print(output_text) + + +# --------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser( + description="BoneIO YAML Configurator — switch / light / fan" + ) + parser.add_argument( + "file", + nargs="?", + help="Plik Excel do przetworzenia (pomięcie = tworzy szablon)", + ) + parser.add_argument( + "--template", + action="store_true", + help="Utwórz pusty szablon Excel", + ) + args = parser.parse_args() + + if args.template or not args.file: + create_template() + else: + if not Path(args.file).exists(): + print(f"[ERROR] Plik nie istnieje: {args.file}") + sys.exit(1) + generate_yaml(args.file) + + +if __name__ == "__main__": + main() diff --git a/configurator/requirements.txt b/configurator/requirements.txt new file mode 100644 index 0000000..9cc1e67 --- /dev/null +++ b/configurator/requirements.txt @@ -0,0 +1 @@ +openpyxl>=3.1.0 diff --git a/packages/devices_v0_7/display.yaml b/packages/devices_v0_7/display.yaml index 3280925..96cb95d 100644 --- a/packages/devices_v0_7/display.yaml +++ b/packages/devices_v0_7/display.yaml @@ -126,9 +126,9 @@ display: it.printf(4, 25, id(size_10), TextAlign::BASELINE_LEFT, "Version:"); it.printf(124, 25, id(size_10), TextAlign::BASELINE_RIGHT, "%s", ESPHOME_VERSION); it.printf(4, 37, id(size_10), TextAlign::BASELINE_LEFT, "Free heap:"); - it.printf(124, 37, id(size_10), TextAlign::BASELINE_RIGHT, "%u B", ESP.getFreeHeap()); + it.printf(124, 37, id(size_10), TextAlign::BASELINE_RIGHT, "%u B", esp_get_free_heap_size()); it.printf(4, 49, id(size_10), TextAlign::BASELINE_LEFT, "CPU freq:"); - it.printf(124, 49, id(size_10), TextAlign::BASELINE_RIGHT, "%u MHz", ESP.getCpuFreqMHz()); + it.printf(124, 49, id(size_10), TextAlign::BASELINE_RIGHT, "%u MHz", esp_clk_cpu_freq() / 1000000); - id: last_page lambda: |- it.rectangle(0, 0, 126, 15);