# Board: ESP32-C3 Super Mini (Generic) # Definition: definitions/boards/esp32-c3-supermini/manifest.yaml # Based largely on https://github.com/misterblack1/HU-058_ESPHome by Adrian Black aka "Adrian's Digital Basement" # AI slop in here substitutions: device_description: "ESP32 Geiger counter with radiation dose rate, accumulated dose and MAX7219 seven-segment display" name: "sleepingroom-clock" friendly_name: "Schlafzimmeruhr" esphome: comment: ${device_description} name: ${name} friendly_name: ${friendly_name} name_add_mac_suffix: false project: name: sensor.radiation version: "1.0" min_version: 2022.11.0 # Template numbers and selects restore their value by publishing it, which # does not fire their set_action, so a restored effect would show in the UI # and do nothing. Push the lot into the component once everything is up. on_boot: priority: -100 then: - lambda: |- id(panel)->set_effect_speed(id(effect_speed).state); id(panel)->set_effect_angle(id(effect_angle).state); id(panel)->set_effect_hue_span(id(effect_hue_span).state); id(panel)->set_flash_fade(id(fx_flash_fade).state); std::string sp = id(effect_spread).current_option(); id(panel)->set_effect_spread(sp == "Per Digit" ? 1 : sp == "Per LED" ? 2 : 0); std::string ef = id(effect_mode).current_option(); id(panel)->set_effect(ef == "Color Cycle" ? 1 : ef == "Flash" ? 2 : 0); esp32: variant: esp32c3 flash_size: 4MB framework: type: esp-idf # Enable logging logger: # Enable Home Assistant API api: encryption: key: !secret apikey actions: - action: show_seconds variables: duration: int then: - lambda: id(panel)->show_seconds(duration); # Right aligned digits with an optional unit in the rightmost position, so # 78F and -5C both read correctly. A unit of C or F also lights the degree # mark. Pass an empty unit to give the number all four positions. There is # no decimal point on the panel, so round before sending. - action: show_number variables: value: int unit: string duration: int then: - lambda: id(panel)->show_number(value, unit, duration); # Every populated LED, white, for a few seconds, then back to the time. # It ignores color, effects, brightness and the light being off. Duration # is milliseconds, and 3000 is the button below. - action: lamp_test variables: duration: int then: - lambda: id(panel)->lamp_test(duration); # Per digit color. Digit 0 is the leftmost and 3 the rightmost, and -1 # sets all four at once. Components are 0 to 255 and are not rescaled # against each other, so a digit can be dimmer than its neighbors as well # as a different hue. Each digit carries its own annunciator, so the colon # takes the color of the hour ones digit and the AM mark the hour tens. # This is runtime state and is not remembered across a reboot. - action: set_digit_color variables: digit: int red: int green: int blue: int then: - lambda: |- id(panel)->set_digit_color(digit, red / 255.0f, green / 255.0f, blue / 255.0f); # Hand the whole panel back to the Display light, gradients included. - action: clear_digit_colors then: - lambda: id(panel)->clear_digit_colors(); # One LED, by the id on the board layout map. See docs/led-layout.md. - action: set_position_color variables: position: int red: int green: int blue: int then: - lambda: |- id(panel)->set_position_color(position, red / 255.0f, green / 255.0f, blue / 255.0f); # A linear ramp between two colors across the whole panel. Angle is in # degrees, 0 left to right and 90 top to bottom, so -45 runs from the # bottom left corner to the top right. # Everything about the running effect in one call, for automations that # want to set it up atomically rather than nudging five entities. # mode 0 off, 1 color cycle, 2 flash. spread 0 whole panel, 1 per digit, # 2 per LED. speed is seconds for one full cycle, which for flash is one # complete on and off. angle is the direction the spread runs. hue_span is # how many degrees of the color wheel the panel covers end to end. # flash_fade is seconds per flash transition, 0 to snap. - action: set_effect variables: mode: int spread: int speed: float angle: float hue_span: float flash_fade: float then: # Drives the entities rather than the component, so the Home Assistant # UI ends up showing what the panel is actually doing. Calling the # component directly would work and leave every slider stale. # # Values are clamped here because a number call outside its range is # dropped rather than clamped, which would silently ignore the whole # setting. - lambda: |- id(effect_speed).make_call().set_value(clamp(speed, 0.2f, 600.0f)).perform(); id(effect_angle).make_call().set_value(clamp(angle, -180.0f, 180.0f)).perform(); id(effect_hue_span).make_call().set_value(clamp(hue_span, 0.0f, 360.0f)).perform(); id(fx_flash_fade).make_call().set_value(clamp(flash_fade, 0.0f, 30.0f)).perform(); id(effect_spread).make_call().set_option( spread == 1 ? "Per Digit" : spread == 2 ? "Per LED" : "Whole Panel").perform(); id(effect_mode).make_call().set_option( mode == 1 ? "Color Cycle" : mode == 2 ? "Flash" : "None").perform(); - action: set_gradient variables: red1: int green1: int blue1: int red2: int green2: int blue2: int angle: float then: - lambda: |- id(panel)->set_gradient(red1 / 255.0f, green1 / 255.0f, blue1 / 255.0f, red2 / 255.0f, green2 / 255.0f, blue2 / 255.0f, angle); ota: platform: esphome encryption: network: enable_ipv6: true wifi: ssid: "Voltage-legacy" password: !secret voltage_legacy_psk #use_address: ${name}.home power_save_mode: high fast_connect: on min_auth_mode: WPA2 # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "Schlafzimmeruhr Fallback Hotspot" password: !secret fallback_psk captive_portal: external_components: - source: type: local path: components time: # No timezone on this one, deliberately. Home Assistant pushes its own # timezone to the device on every time sync, DST rules included, but only # if this platform has no timezone of its own. Setting one here compiles # that path out and pins the clock to whatever is written instead. - platform: homeassistant id: ha_time # SNTP is the fallback for a boot with no Home Assistant. It has nowhere to # learn a timezone from, so this one is spelled out. It is also what the # display shows for the first few seconds of every boot, until Home # Assistant answers. Change it if the clock moves. #- platform: sntp # id: sntp_time # timezone: !secret timezone aip33628: id: panel clk_pin: GPIO21 # MCU socket pin 14 (HU-058D) or 16 (HU-058/SE), driver 1 data_pin: GPIO20 # MCU socket pin 5, driver 1 clk2_pin: GPIO10 # MCU socket pin 1, driver 2 data2_pin: GPIO9 # MCU socket pin 2, driver 2 time_id: ha_time # Power on defaults. The switches below own both after that, and they # restore their last state, so these only matter for a first ever boot. twelve_hour: false blink_colon: true # Ceiling on IS[3:0], 0 to 15. Fixed, never varied by content. The stock # firmware ran 0xF and drew 404mA doing it. Drop this if the drivers # run hotter than you want. See docs/display-map.md. max_current: 15 light: - platform: aip33628 id: display aip33628_id: panel name: Display restore_mode: RESTORE_DEFAULT_ON # Gamma is applied inside the component, because it also has to allow for # the 2.5mA floor the drivers cannot go below. Leaving it on here as well # would square it. gamma_correct: 1.0 # The two front panel buttons. Nothing on the board pulls these nets up. The # stock 8051 held them high with its own quasi-bidirectional port pullup, and # that chip is out of the socket, so the ESP32 internal pullup is the only one # and the lines never see 5V. Switch closed shorts to ground, hence inverted. # # No behavior is attached here on purpose. Home Assistant is the control # surface, so these are just inputs it can bind to. Press and hold patterns # belong in an automation, not in this file. binary_sensor: - platform: gpio id: button_1 name: Button 1 pin: number: GPIO8 # MCU socket pin 9, net S1, top button on the case mode: input: true pullup: true inverted: true filters: - delayed_on: 20ms - delayed_off: 20ms - platform: gpio id: button_2 name: Button 2 pin: number: GPIO7 # MCU socket pin 10, net S2, bottom button on the case mode: input: true pullup: true inverted: true filters: - delayed_on: 20ms - delayed_off: 20ms switch: - platform: template id: twelve_hour_mode name: 12 Hour Time optimistic: true restore_mode: RESTORE_DEFAULT_ON turn_on_action: - lambda: id(panel)->set_twelve_hour(true); turn_off_action: - lambda: id(panel)->set_twelve_hour(false); # Off means a steady colon, not a dark one. - platform: template id: colon_blink name: Blink Colon optimistic: true restore_mode: RESTORE_DEFAULT_ON turn_on_action: - lambda: id(panel)->set_blink_colon(true); turn_off_action: - lambda: id(panel)->set_blink_colon(false); button: # Three seconds of every populated LED lit white at half brightness, then # back to the time with the settings it had before. - platform: template name: Lamp Test on_press: - lambda: id(panel)->lamp_test(3000); # A shortcut for the common case, and something to press by hand. # Anything more elaborate belongs in a Home Assistant automation calling # the api actions above. - platform: template name: Show Seconds on_press: - lambda: id(panel)->show_seconds(5000); # A UI handle for set_digit_color, and the quickest way to see the four # digits driven independently. - platform: template name: Digit Color Demo on_press: - lambda: |- id(panel)->set_digit_color(0, 1.00f, 0.00f, 0.00f); id(panel)->set_digit_color(1, 1.00f, 0.45f, 0.00f); id(panel)->set_digit_color(2, 0.00f, 1.00f, 0.25f); id(panel)->set_digit_color(3, 0.10f, 0.30f, 1.00f); - platform: template name: Clear Digit Colors on_press: - lambda: id(panel)->clear_digit_colors(); # Green in the bottom left corner running to yellow in the top right, which # is the sort of thing the stock firmware did. -45 degrees is that diagonal. - platform: template name: Diagonal Gradient on_press: - lambda: |- id(panel)->set_gradient(0.00f, 1.00f, 0.10f, 1.00f, 0.85f, 0.00f, -45.0f); # A UI handle for the show_number action, which otherwise lives only under # Developer Tools > Actions. Setting either entity redraws immediately and the # panel falls back to the time ten seconds later on its own. select: # Effects run on the device, not in Home Assistant, so a fade stays smooth # and keeps going if the network drops. Every entity here is a normal one, # so an automation can drive them, or call the set_effect action to set the # lot at once. - platform: template name: Effect id: effect_mode optimistic: true restore_value: true initial_option: "None" options: ["None", "Color Cycle", "Flash"] set_action: - lambda: |- std::string v = x; id(panel)->set_effect(v == "Color Cycle" ? 1 : v == "Flash" ? 2 : 0); # Whole Panel moves everything together. Per Digit gives each digit its own # phase, so the color walks across the four of them. Per LED gives every # position its own, which is the smooth sweep. - platform: template name: Effect Spread id: effect_spread optimistic: true restore_value: true initial_option: "Whole Panel" options: ["Whole Panel", "Per Digit", "Per LED"] set_action: - lambda: |- std::string v = x; id(panel)->set_effect_spread(v == "Per Digit" ? 1 : v == "Per LED" ? 2 : 0); - platform: template name: Test Unit id: test_unit optimistic: true restore_value: true initial_option: "F" options: ["None", "F", "C"] set_action: - lambda: |- std::string u = x; if (u == "None") u.clear(); id(panel)->show_number((int) id(test_number).state, u, 10000); number: # Seconds for one full cycle. For Flash this is one complete on and off. - platform: template name: Effect Speed id: effect_speed optimistic: true restore_value: true initial_value: 10 min_value: 0.2 max_value: 600 step: 0.2 unit_of_measurement: s mode: box set_action: - lambda: id(panel)->set_effect_speed(x); # Which way the spread runs. 0 is left to right, 90 top to bottom, 180 # right to left. -45 is the bottom left to top right diagonal and 135 is # the other one, top right to bottom left. - platform: template name: Effect Angle id: effect_angle optimistic: true restore_value: true initial_value: 0 min_value: -180 max_value: 180 step: 15 unit_of_measurement: deg mode: box set_action: - lambda: id(panel)->set_effect_angle(x); # How many degrees of the color wheel the panel covers end to end. A full # 360 packs the whole spectrum across four digits and reads as noise, so # the useful settings are narrow. 60 is roughly red through orange, 90 # reaches yellow. 0 puts the whole panel on one color that still cycles. # Only Color Cycle uses this, and only when Spread is not Whole Panel. - platform: template name: Effect Hue Span id: effect_hue_span optimistic: true restore_value: true initial_value: 90 min_value: 0 max_value: 360 step: 5 unit_of_measurement: deg mode: box set_action: - lambda: id(panel)->set_effect_hue_span(x); # Seconds each flash transition takes, independent of the flash rate. 0 # snaps on and off. 0.1 gives a quick ramp. Anything past half the Effect # Speed is capped there, which is the point where it becomes a triangle. - platform: template name: Flash Fade id: fx_flash_fade optimistic: true restore_value: true initial_value: 0 min_value: 0 max_value: 30 step: 0.05 unit_of_measurement: s mode: box set_action: - lambda: id(panel)->set_flash_fade(x); - platform: template name: Test Number id: test_number optimistic: true restore_value: true initial_value: 72 min_value: -999 max_value: 9999 step: 1 mode: box set_action: - lambda: |- std::string u = id(test_unit).current_option(); if (u == "None") u.clear(); id(panel)->show_number((int) x, u, 10000);