sqfmi / badgy

Home of Badgy - IoT Badge
http://badgy.sqfmi.com
MIT License
309 stars 63 forks source link

Esphome support? #58

Open jlongman opened 1 year ago

jlongman commented 1 year ago

Is badgy supported by esphome? Does anyone have config files if so?

@chunkysteveo you tagged a post with esphome here

The waveshare eink displays seem similar and closest, but different enough it seemed worth asking.

(Rev 2A if it matters)

chunkysteveo commented 1 year ago

It is "supported" by ESPhome, yes.... depending on what you want to do with it...?!

You can use the ePaper screen (with partial refresh), and the 5-way switch. Accessing the onboard (ESP8266) as a status light seemed to cause issues, so I don't include that.

The ADC can also be included to measure the voltage level of the battery. But be aware the voltage divider used isn't right for a lipo battery.

There's a lot of pins used on the ESP8266 which tend to interfere with the OTA process by some line going low (I think?!), so I also include the "Safe mode reboot" switch, which seems allows for 100% guarantee upload (weeks of troubleshooting and testing!).

Do you have a specific example you'd like to set the Badgy up with, I can post an example YAML config?

chunkysteveo commented 1 year ago

20220922_113250 20220922_213142

Here's two examples I have using Badgy and Home Assistant using ESPHome. The buttons aren't really used, but they could be to move between pages of content as an example.

chunkysteveo commented 1 year ago

@chunkysteveo you tagged a post with esphome here

Yeah, that example was also another version I had - before I updated it to the hourly/daily forecast one seen above. Those "weather" examples use a custom sensor within Home Assistant to format a bunch of other sensors into a simple text sensor that the Badgy can process and display.

jlongman commented 1 year ago

Amazing! I’m interested in both of those, yes, and I also have a co2 monitor that I might do on its own in as- large-as-possible text. Getting the switches going might be interesting to flip between them, later, but as I have two of them I can start without it.

chunkysteveo commented 1 year ago

Let me put a 'hello world' config yaml file up here for you to start on, which should give you some good pointers and a good base... how OK are you with ESPHome and HA?

jlongman commented 1 year ago

A ‘hello world’ would help a lot, thanks!

Okay with esphome, I’ve put some basic sonoff switches, a co2 meter together; and competent with HA, I’ve wrestled and won with templates, etc.

chunkysteveo commented 1 year ago

Not had any time to gather all my scripts and info together, but below is a quick and dirty "hello world" which should get you working as a proof of concept:

In HA, you will need weather.home (Met.no weather service), binary_sensor.door_sensor_back_door_contact (a binary sensor such as a door sensor), sensor.multisensor_garage_temperature (a temperature sensor of some sort), binary_sensor.multisensor_garage_motion (a motion sensor binary sensor such as a PIR sensor. This is to blank the screen if no one is present to save on the epaper life, comment out the if else statement if you want it to always be on), and you will need to download and add some fonts to the ESPHome directory (fonts/materialdesignicons-webfont.ttf, fonts/materialdesignicons-webfont-6.9.96.ttf (I need to combine these two MDI calls into one (lazy!), feel free to fix!), fonts/Roboto-Bold.ttf, fonts/Roboto-Regular.ttf)

Hopefully the below will give you some ideas and paths to explore. The end result will be a vertical Badgy with two temperature readings, a weather cloud/sun icon from Met.no service, and a CLOSED/OPEN and door icon at the bottom, along with the update time, battery icon, and wifi range icon. This is a mis-match of a bunch of versions I have running, so it's cut back from what I run, but has a lot of things in there that I have learned..... Use the Safe Restart button if you are having issues OTA updates to Badgy.

substitutions:
  display_name: 'EPaper Display Badgy 01'
  device_description: 'EPaper Display Badgy 01. The White one!'
  friendly_name: 'EPaper Display Badgy 01'
  device_name: epaper-display-01
  node_name: epaper_display_01

globals:
  - id: data_updated
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: initial_data_received
    type: bool
    restore_value: no
    initial_value: 'false'

esphome:
  name: ${device_name}
  comment: ${device_description}
  on_boot:
    - priority: 200.0
      then:
        - component.update: epaper
    - priority: 800.0
      then:
        - component.update: ${node_name}_uptime_raw
        - component.update: wifisignal

esp8266:
  board: nodemcuv2

# Enable logging
logger:
  # level: debug

# Enable Home Assistant API
api:

ota:
  password: !secret ota_password
  num_attempts: 5

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Epaper-Display-01"
    password: "PdfmXwsyIhdgEjsxnf"

captive_portal:

time:
  - platform: homeassistant
    id: homeassistant_time

#light:
#  - platform: status_led
#    name: ${display_name} Onboard LED
#    pin:
#      number: GPIO2
#      inverted: true     

text_sensor:
  - platform: wifi_info
    ip_address:
      name: ${display_name} IP Address
    ssid:
      name: ${display_name} Connected SSID
    bssid:
      name: ${display_name} Connected BSSID   
  - platform: template
    name: ${friendly_name} Uptime
    id: ${node_name}_uptime
    icon: mdi:clock-start

  - platform: homeassistant
    name: forecast
    id: weather_forecast_today
    entity_id: weather.home
    internal: true  

binary_sensor:
  - platform: status
    name: ${display_name} Status

  - platform: gpio
    pin: 
      number: GPIO3
      mode: INPUT_PULLUP
      inverted: true
    name: ${display_name} SW LEFT
    filters:
      - delayed_on: 10ms  
  - platform: gpio
    pin: 
      number: GPIO1
      mode: INPUT_PULLUP
      inverted: true
    name: ${display_name} SW DOWN
    filters:
      - delayed_on: 10ms   
  - platform: gpio
    pin: 
      number: GPIO10
      mode: INPUT_PULLUP
      inverted: true
    name: ${display_name} SW UP
    filters:
      - delayed_on: 10ms  
  - platform: gpio
    pin: 
      number: GPIO12
      mode: INPUT_PULLUP
      inverted: true
    name: ${display_name} SW RIGHT
    filters:
      - delayed_on: 10ms
  - platform: gpio
    pin: 
      number: GPIO5
      mode: INPUT_PULLUP
      inverted: true
    name: ${display_name} SW CENTRE
    filters:
      - delayed_on: 10ms
  - platform: homeassistant
    id: door_backdoor
    entity_id: binary_sensor.door_sensor_back_door_contact
    internal: true  
    on_state:
      then:
        - component.update: epaper
  - platform: homeassistant
    id: multisensor_garage_motion_sensor
    entity_id: binary_sensor.multisensor_garage_motion
    internal: true  
    on_state:
      then:
      - component.update: epaper        
sensor:
  - platform: adc
    pin: A0
    #attenuation: auto
    name: ${display_name} ADC Voltage RAW
    id: li_ion_voltage_raw
    update_interval: 10s
    entity_category: diagnostic
    unit_of_measurement: "V"  
    state_class: measurement
    device_class: voltage
    on_raw_value:
      then:
        - sensor.template.publish:
            id: li_ion_voltage
            state: !lambda |-
             return ((id(li_ion_voltage_raw).state*(314000+100000)) / 100000);
  - platform: template
    name: ${display_name} Battery Voltage
    id: li_ion_voltage
    #update_interval: 60s
    accuracy_decimals: 2
    entity_category: diagnostic
    unit_of_measurement: "V"  
    device_class: voltage
    state_class: measurement     
  - platform: template
    name: ${display_name} Battery Percentage
    id: li_ion_voltage_percentage
    unit_of_measurement: '%'
    update_interval: 12s
    entity_category: diagnostic
    accuracy_decimals: 0
    device_class: battery
    state_class: measurement
    lambda: |-
      return (((id(li_ion_voltage).state - 3) /1.14) * 100.00);  
  - platform: wifi_signal
    name: ${display_name} WiFi Singal Strength
    id: wifisignal
    update_interval: 120s
  - platform: uptime
    name: "${friendly_name} Uptime Sensor"
    id: ${node_name}_uptime_raw
    update_interval: 60s
    on_raw_value:
      then:
        - logger.log:
            format: "Raw Value of Uptime sensor: %f, %f"
            args: ['id(${node_name}_uptime_raw).raw_state', 'id(${node_name}_uptime_raw).state']
            level: INFO
        - text_sensor.template.publish:
            id: ${node_name}_uptime
            state: !lambda |-
              int seconds = round(id(${node_name}_uptime_raw).raw_state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (days ? String(days) + "d " : "") +
                (hours ? String(hours) + "h " : "") +
                (minutes ? String(minutes) + "m " : "") +
                (String(seconds) + "s") 
              ).c_str();     

  - platform: homeassistant
    id: temp_garage
    entity_id: sensor.multisensor_garage_temperature
    internal: true
    on_value:
      then:
      - component.update: epaper
  - platform: homeassistant
    id: temp_solar_weather
    #entity_id: sensor.solar_weather_station_temperature
    entity_id: sensor.weather_home_temperature
    internal: true
    on_value:
      then:
      - component.update: epaper

button:
  - platform: restart
    name: ${display_name} restart
  - platform: safe_mode
    name: "${display_name} Restart (Safe Mode)"    

font:
  - file: 'fonts/materialdesignicons-webfont.ttf'
    # https://github.com/Templarian/MaterialDesign-Webfont/tree/v5.8.55
    id: icon_font_new
    size: 32
    glyphs: [
      "\U000F0F54", # mdi-home-thermometer
      "\U000F0F55", # mdi-home-thermometer-outline
      "\U000F18D6", # mdi-sun-thermometer
      "\U000F054A", # mdi-umbrella
      "\U000F081B", # mdi-door-closed
      "\U000F081C", # mdi-door-open
      "\U000F0438", # mdi-radiator
      "\U000F0AD7", # mdi-radiator-disabled
      "\U000F0AD8", # mdi-radiator-off
      "\U000F0828", # mdi-hot-tub
      "\U000F1903", # home-lightning-bolt
      "\U000F0238", # mdi-fire"
      "\U000F07D0", # mdi-home-assistant
      "\U000F0F61", # mdi-moon-first-quarter
      "\U000F0F62", # mdi-moon-full
      "\U000F0F63", # mdi-moon-last-quarter
      "\U000F0F64", # mdi-moon-new
      "\U000F0F65", # mdi-moon-waning-crescent
      "\U000F0F66", # mdi-moon-waning-gibbous
      "\U000F0F67", # mdi-moon-waxing-crescent
      "\U000F0F68"  # mdi-moon-waxing-gibbous
      ]

  - file: 'fonts/materialdesignicons-webfont-6.9.96.ttf'
    # https://github.com/Templarian/MaterialDesign-Webfont/tree/v6.9.96
    id: icon_font_small
    size: 14
    glyphs: [
      # Wifi
      "\U000F092B", # mdi-wifi-strength-alert-outline
      "\U000F091F", # mdi-wifi-strength-1
      "\U000F0922", # mdi-wifi-strength-2
      "\U000F0925", # mdi-wifi-strength-3
      "\U000F0928", # mdi-wifi-strength-4
      "\U000F0438", #mdi-radiator
      "\U000F0AD7", #mdi-radiator-disabled
      "\U000F0AD8", #mdi-radiator-off
      "\U000F1903", #home-lightning-bolt
      "\U000F12A3", #mdi-battery-high
      "\U000F12A2", #mdi-battery-medium
      "\U000F12A1", #mdi-battery-low
      "\U000F008E", #mdi-battery-outline
      ]
  - file: 'fonts/Roboto-Bold.ttf'
    id: temp_font
    size: 32
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/']
  - file: 'fonts/Roboto-Regular.ttf'
    id: footer_font
    size: 12
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z','å', 'ä', 'ö', '/','º','µ','³']
  - file: 'fonts/materialdesignicons-webfont.ttf' 
    id: weather_font
    size: 32
    glyphs: 
      # Weather
      - "\U000F0590" # weather-cloudy
      - "\U000F0F2F" # weather-cloudy-alert
      - "\U000F0E6E" # weather-cloudy-arrow-right
      - "\U000F0591" # weather-fog
      - "\U000F0592" # weather-hail
      - "\U000F0F30" # weather-hazy
      - "\U000F0898" # weather-hurricane
      - "\U000F0593" # weather-lightning
      - "\U000F067E" # weather-lightning-rainy
      - "\U000F0594" # weather-night
      - "\U000F0F31" # weather-night-partly-cloudy
      - "\U000F0595" # weather-partly-cloudy
      - "\U000F0F32" # weather-partly-lightning
      - "\U000F0F33" # weather-partly-rainy
      - "\U000F0F34" # weather-partly-snowy
      - "\U000F0F35" # weather-partly-snowy-rainy
      - "\U000F0596" # weather-pouring
      - "\U000F0597" # weather-rainy
      - "\U000F0598" # weather-snowy
      - "\U000F0F36" # weather-snowy-heavy
      - "\U000F067F" # weather-snowy-rainy
      - "\U000F0599" # weather-sunny
      - "\U000F0F37" # weather-sunny-alert
      - "\U000F14E4" # weather-sunny-off
      - "\U000F059A" # weather-sunset
      - "\U000F059B" # weather-sunset-down
      - "\U000F059C" # weather-sunset-up
      - "\U000F0F38" # weather-tornado
      - "\U000F059D" # weather-windy
      - "\U000F059E" # weather-windy-variant

  - file: 'fonts/Roboto-Bold.ttf'
    id: status_font
    size: 18
    glyphs:
      ['&', '@', '!', ',', '.', '"', '%', '(', ')', '+', '-', '_', ':', '°', '0',
       '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
       'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', 'a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
       'u', 'v', 'w', 'x', 'y', 'z', 'å', 'ä', 'ö', '/']  

spi:
  clk_pin: GPIO14
  mosi_pin: GPIO13  #DIN

display:
  - platform: waveshare_epaper
    id: epaper
    cs_pin: GPIO15
    dc_pin: GPIO0
    busy_pin: GPIO4
    reset_pin: GPIO2
    model: 2.90in
    #model: 2.90in-b
    rotation: 180°
    # full_update_every: 30
    update_interval: 3600s
    lambda: |-
        if(id(multisensor_garage_motion_sensor).state){

          // Map weather states to MDI characters.
          std::map<std::string, std::string> weather_icon_map
            {
              {"cloudy", "\U000F0590"},
              {"cloudy-alert", "\U000F0F2F"},
              {"cloudy-arrow-right", "\U000F0E6E"},
              {"fog", "\U000F0591"},
              {"hail", "\U000F0592"},
              {"hazy", "\U000F0F30"},
              {"hurricane", "\U000F0898"},
              {"lightning", "\U000F0593"},
              {"lightning-rainy", "\U000F067E"},
              {"night", "\U000F0594"},
              {"night-partly-cloudy", "\U000F0F31"},
              {"partlycloudy", "\U000F0595"},
              {"partly-lightning", "\U000F0F32"},
              {"partly-rainy", "\U000F0F33"},
              {"partly-snowy", "\U000F0F34"},
              {"partly-snowy-rainy", "\U000F0F35"},
              {"pouring", "\U000F0596"},
              {"rainy", "\U000F0597"},
              {"snowy", "\U000F0598"},
              {"snowy-heavy", "\U000F0F36"},
              {"snowy-rainy", "\U000F067F"},
              {"sunny", "\U000F0599"},
              {"sunny-alert", "\U000F0F37"},
              {"sunny-off", "\U000F14E4"},
              {"sunset", "\U000F059A"},
              {"sunset-down", "\U000F059B"},
              {"sunset-up", "\U000F059C"},
              {"tornado", "\U000F0F38"},
              {"windy", "\U000F059D"},
              {"windy-variant", "\U000F059E"},
              {"first_quarter", "\U000F0F61"},
              {"full_moon", "\U000F0F62"},
              {"last_quarter", "\U000F0F63"},
              {"new_moon", "\U000F0F64"},
              {"waning_crescent", "\U000F0F65"},
              {"waning_gibbous", "\U000F0F66"},
              {"waxing_crescent", "\U000F0F67"},
              {"waxing_gibbous", "\U000F0F68"}
            };

          int x, y, c, x_top, y_top;
          ESP_LOGI("display", "Updating..");
          /* Print time in HH:MM format */
          /*if(id(homeassistant_time).now().is_valid()) {
                it.strftime(64, 0, id(temp_font), TextAlign::TOP_CENTER, "%H:%M", id(homeassistant_time).now());
          }*/

          /* FOOTER */
          if(id(homeassistant_time).now().is_valid()) {
            it.strftime(128 - 16, 296 - 3 , id(footer_font), TextAlign::BASELINE_RIGHT , "%d/%m/%Y %H:%M", id(homeassistant_time).now());

            /* Battery Strength */
            if(id(li_ion_voltage_percentage).has_state()) {
              x = 12, y = 296 - 0;
              if (id(li_ion_voltage_percentage).state >= 90) {
                  //Excellent
                  it.print(x, y, id(icon_font_small), TextAlign::BOTTOM_RIGHT, "\U000F12A3");
                  ESP_LOGI("Battery", "Exellent");
              } else if (id(li_ion_voltage_percentage).state  >= 60) {
                  //Good
                  it.print(x, y, id(icon_font_small), TextAlign::BOTTOM_RIGHT, "\U000F12A2");
                  ESP_LOGI("Battery", "Good");
              } else if (id(li_ion_voltage_percentage).state  >= 30) {
                  //Fair
                  it.print(x, y, id(icon_font_small), TextAlign::BOTTOM_RIGHT, "\U000F12A1");
                  ESP_LOGI("Battery", "Fair");
              } else {
                  //Unlikely working signal
                  it.print(x, y, id(icon_font_small), TextAlign::BOTTOM_RIGHT, "\U000F008E");
                  ESP_LOGI("Battery", "Unlikely");
              }
            }

            /* WiFi Signal Strength */
            if(id(wifisignal).has_state()) {
              x = 128 - 0, y = 296 - 0;
              if (id(wifisignal).state >= -50) {
                  //Excellent
                  it.print(x, y, id(icon_font_small), TextAlign::BOTTOM_RIGHT, "\U000F0928");
                  ESP_LOGI("WiFi", "Exellent");
              } else if (id(wifisignal).state  >= -60) {
                  //Good
                  it.print(x, y, id(icon_font_small), TextAlign::BOTTOM_RIGHT, "\U000F0925");
                  ESP_LOGI("WiFi", "Good");
              } else if (id(wifisignal).state  >= -67) {
                  //Fair
                  it.print(x, y, id(icon_font_small), TextAlign::BOTTOM_RIGHT, "\U000F0922");
                  ESP_LOGI("WiFi", "Fair");
              } else if (id(wifisignal).state  >= -70) {
                  //Weak
                  it.print(x, y, id(icon_font_small), TextAlign::BOTTOM_RIGHT, "\U000F091F");
                  ESP_LOGI("WiFi", "Weak");
              } else {
                  //Unlikely working signal
                  it.print(x, y, id(icon_font_small), TextAlign::BOTTOM_RIGHT, "\U000F092B");
                  ESP_LOGI("WiFi", "Unlikely");
              }
            }
          }

          y = 100, x = 100;
          x_top = 0, y_top = 32+4;

          // Print garage temperature (from homeassistant sensor)
          y = 100, x = 100;
          if (id(temp_garage).has_state()) {
            it.print(32, y_top+32-3, id(icon_font_new), TextAlign::BOTTOM_RIGHT, "\U000F0F55");
            it.printf(28+100, y_top+32, id(temp_font), TextAlign::BOTTOM_RIGHT , "%5.1f°C", id(temp_garage).state);
          }
          // Print Solar Weather Station temperature (from homeassistant sensor)
          if (id(temp_solar_weather).has_state()) {
            it.printf(34, y_top+32+32-3, id(weather_font), TextAlign::BOTTOM_RIGHT, "%s", weather_icon_map[id(weather_forecast_today).state.c_str()].c_str());
            //it.print(34, y_top+32+32-3, id(icon_font_new), TextAlign::BOTTOM_RIGHT, "\U000F18D6");
            it.printf(28+100, y_top+32+32, id(temp_font), TextAlign::BOTTOM_RIGHT , "%5.1f°C", id(temp_solar_weather).state);
          }

          //DOOR
          if (id(door_backdoor).has_state()) {
            it.printf(64, 220, id(temp_font), TextAlign::TOP_CENTER, "%s", id(door_backdoor).state ? "OPEN" : "CLOSED");
            if (id(door_backdoor).state) {
              it.print(2, 250, id(icon_font_new), "\U000F081C");
            } else {
              it.print(2, 250, id(icon_font_new), "\U000F081B");
            }
          }          
        }
        else{
          it.fill(COLOR_OFF);

        }
jlongman commented 1 year ago

Finally some time to get back to this, and preliminary results are good!

First attempts I was seeing nothing but either I watching the logs wirelessly prevents it from displaying or maybe I had update_interval too low or wasn't patient enough.

Now that I've got the basics I'll look at getting my CO2 levels in, thanks muchly!

chunkysteveo commented 1 year ago

Ahhh - i wondered when you'd look at this. Good to hear it's working. I have similar results with blank screens, reboots etc. Not sure if it's the device wiring or the code, but it's not the most stable ePaper screen for HA.

Enjoy tinkering with the YAML! Check out https://github.com/Madelena/esphome-weatherman-dashboard for some good inspiration too. And there are a bunch of epaper esphome projects to borrow ideas from!

jlongman commented 8 months ago

BTW, I'd given up on it for a while and checked in again and its working brilliantly now! I think something in esphome improved more than any change I did. Now I have to find the other one I've misplaced!

Showing:

badgy

chunkysteveo commented 8 months ago

BTW, I'd given up on it for a while and checked in again and its working brilliantly now! I think something in esphome improved more than any change I did. Now I have to find the other one I've misplaced!

Showing:

  • time
  • indoor temp
  • outdoor temp
  • co2
  • status of front and back doors
  • back door on right, front door on left
  • last update time and wifi strength.

badgy

Haha! Looking awesome!!! Glad it is working for you. Mine has lived on my wall ever since with all sorts of temps and door icons!