I have an AZ Delivery D1 mini running a DHT22 sensor and a SSD 1306 0.96" OLED display. The general idea is that pressing the button should display temperature, humidity and time for a short period, then a screensaver for a few minutes, and then blank the display. further button presses should then start the whle thing over, no matter where the ESP in the cycle I just described.
YAML is as follows (apologies for the length):
###############################################################################
substitutions:
device_name: masterbedthermohygrometer
yaml_file_name: ${device_name}.yaml
friendly_name: "Master Bedroom Thermohygrometer"
device_description: "Thermohygrometer using DHT22 temperature and humidity sensor and SSD 1306 display."
globals:
- id: dispNumMode
type: int
restore_value: no
initial_value: '1'
- id: screenSaverNum
type: int
restore_value: no
initial_value: '0'
esphome:
name: ${device_name}
comment: '${device_description}'
platform: ESP8266
board: d1_mini
wifi:
ssid: !secret iot_wifi_ssid
password: !secret iot_wifi_pass
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: ${friendly_name}
password: !secret esphome_admin_pass
captive_portal:
# Enable logging
logger:
# Enable Home Assistant API
api:
reboot_timeout: 15min
password: !secret esphome_admin_pass
encryption:
key: !secret esphome_noise_key
ota:
password: !secret esphome_admin_pass
######################
# the web_server & sensor components can be removed
# without affecting core functionaility.
web_server:
port: 80
auth:
username: !secret esphome_admin_user
password: !secret esphome_admin_pass
#######################################
# Device specific Config Begins Below #
#######################################
# Get time from Home Assistant...
time:
- platform: homeassistant
id: ha_time
i2c:
sda: D5
scl: D6
text_sensor:
- platform: version
hide_timestamp: true
name: "${device_name} - ESPHome Version"
- platform: wifi_info
ip_address:
name: "${device_name} - IP Address"
ssid:
name: "${device_name} - Connected SSID"
bssid:
name: "${device_name} - Connected BSSID"
mac_address:
name: "${device_name} - Mac Wifi Address"
sensor:
- platform: wifi_signal
name: "${device_name} - Wifi Signal Strength"
update_interval: 60s
- platform: uptime
name: "${device_name} - Uptime"
- platform: dht
model: DHT22
pin: D7
temperature:
name: "${friendly_name} - Temperature"
id: temp
filters:
- multiply: 1.0 # Calibration. In this case, to
- offset: -2.5 # a Wiser room stat. Unsure of
# he Wiser's accuracy, but all need
# to read the same.
humidity:
name: "${friendly_name} - Humidity"
id: hum
update_interval: 60s
binary_sensor:
- platform: gpio
pin:
number: D4
inverted: true
mode:
input: true
pullup: true
name: "${device_name} - Button"
id: button
on_click:
then:
- switch.turn_on: ${device_name}_screensaver
- switch.turn_on: ${device_name}_display
font:
- file: "fonts/Rabelo-Free/Rabelo-Regular.ttf"
id: rabelo
size: 15
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
address: 0x3C
lambda: |-
const int screenSizeX = 128; // Screen size; X, Y
const int screenSizeY = 64;
const int lineCount = 20; // The number of lines we are
// going to draw
const int lineStepSize = 10; // The size of the step by which
// we will move eack line end
const float pi = 3.14159265; // The constant Pi.
static int genNewLines = 1; // True. Flag to signal that new
// lines need to be generated.
static int line1xStart = 0; // this is where we are starting
static int line1yStart = 0; // to draw (the first of) the
static int line2xStart = 0; // lines.
static int line2yStart = 0;
int line1x = line1xStart; // Variables to hold line starts
int line1y = line1yStart; // and end as we draw each in
int line2x = line2xStart; // the sequence
int line2y = line2yStart;
static int line1deltaX = 0; // These are the deltas in X/Y
static int line1deltaY = 0; // as we move the line-ends for
static int line2deltaX = 0; // each subsequent line in
static int line2deltaY = 0; // sequence
int thisLine = 0; // simple counter for use to
// keep track of lines as we
// draw them
float startAngle = 0; // The angle we are moving the
// start of the line by
float endAngle = 0; // The angle we are moving the
// end of the line by
if (genNewLines) {
// We are effectively in a loop already as ESPHome will
// repeatedly call our code. This bit is for initialisations
// that happen on the first time through (so only once, after
// power-on.
ESP_LOGD("${yaml_file_name}", "Seeding PRNG...");
// seed PRNG, generate QIX line start points ...
srand(1); // will produce the same sequence
// each run. Will sort out later.
genNewLines = 0; // False
ESP_LOGD("${yaml_file_name}", "Generating 4 random screen points...");
line1x = rand() % screenSizeX; // line start initial
line1y = rand() % screenSizeY;
line2x = rand() % screenSizeX; // line end initial
line2y = rand() % screenSizeY;
startAngle = (rand() % 360) * (pi / 180); // start direction
// for start of line
endAngle = (rand() % 360) * (pi / 180); // start direction
// for end of line
line1deltaX = lineStepSize * cos(startAngle); // Start of line
line1deltaX = lineStepSize * sin(startAngle); // X, Y movement.
line2deltaX = lineStepSize * cos(endAngle); // End of line
line2deltaY = lineStepSize * sin(endAngle); // X, Y movement.
}
// decode global integers into enums ...
enum screenMode {
STATS,
SCREENSAVE
};
enum screenSaveMode {
BLANK,
QIX,
};
screenMode dispMode = STATS;
dispMode = (screenMode) id(dispNumMode);
screenSaveMode screenSaver = BLANK;
screenSaver = (screenSaveMode) id(screenSaverNum);
// Render the display...
if (dispMode == STATS) {
// Code for displaying stats ...
auto timeNow = id(ha_time).now();
// ESP_LOGD("${yaml_file_name}", "Display is on; displaying stats.");
it.printf ( 0, 0, id(rabelo), "Temperture: %.1f", id(temp).state);
it.printf ( 0, 20, id(rabelo), "Humidity: %.1f", id(hum).state);
it.printf ( 0, 40, id(rabelo), "Time:");
it.strftime(45, 40, id(rabelo), "%H:%M:%S", timeNow);
// genNewLines = 1; // True
} else if (dispMode == SCREENSAVE) {
// Code for screensavers ...
if (screenSaver == BLANK) {
// Blank the screen ...
// ESP_LOGD("${yaml_file_name}", "Display is off; blanking.");
it.clear();
// genNewLines = 1; // True
} else if (screenSaver == QIX) {
// "QIX" screensaver. We are drawing a series of lines that progress
// around the screen. We accomplish this by picking a direction for
// each line-end in the sequence to move, and keep moving in that
// direction for each successive line. When one line end or other
// hits the edge of the screen, we pick a new direction.
// There is an added wrinkle, here, in that it seems ESPHome clears
// the screen between invokations of this code. The way we have to
// work, therefore, is remember where the first line was drawn last
// time round, advance by one, and then start drawing from there
// (redrawing the entire sequence of lines again, having stepped on
// by one.)
// ESP_LOGD("${yaml_file_name}", "Screensaver is on; processing.");
// ESP_LOGD("${yaml_file_name}",
// "X1: %i, Y1: %i, X2: %i, Y2: %i",
// line1xStart,
// line1yStart,
// line2xStart,
// line2yStart);
// Now draw the lines ...
for ( thisLine = 0; thisLine <= lineCount ; thisLine++ ) {
line1x += line1deltaX; // Move to the next line
line1y += line1deltaY;
line2x += line2deltaX;
line2y += line2deltaY;
// Did any of the line ends fall off the edge of the screen?
// If so, clamp it to the edge of the screen and chose and new
// direction (based on what edge of the screen we fell off.
if (line1x <= 0) {
startAngle = (270 + (rand() % 180)) * (pi / 180); // start
// direction
// for start of
// line
line1deltaX = int(lineStepSize * cos(endAngle)); // Start of line
line1deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.
line1x = 0;
} else if (line1x >= screenSizeX) {
startAngle = (90 + (rand() % 180)) * (pi / 180); // start
// direction
// for start of
// line
line1deltaX = int(lineStepSize * cos(endAngle)); // Start of line
line1deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.
line1x = screenSizeX;
}
if (line1y <= 0) {
startAngle = (rand() % 180) * (pi / 180); // start direction
// for start of line
line1deltaX = int(lineStepSize * cos(endAngle)); // Start of line
line1deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.
line1y = 0;
} else if (line1y >= screenSizeY) {
startAngle = (180 + (rand() % 180)) * (pi / 180); // start
// direction
// for start of
// line
line1deltaX = int(lineStepSize * cos(endAngle)); // Start of line
line1deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.
line1y = screenSizeY;
}
if (line2x <= 0) {
endAngle = (270 + (rand() % 180)) * (pi / 180); // start
// direction
// for start of
// line
line2deltaX = int(lineStepSize * cos(endAngle)); // Start of line
line2deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.
line2x = 0;
} else if (line2x >= screenSizeX) {
endAngle = (90 + (rand() % 180)) * (pi / 180); // start
// direction
// for start of
// line
line2deltaX = int(lineStepSize * cos(endAngle)); // Start of line
line2deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.
line2x = screenSizeX;
}
if (line2y <= 0) {
endAngle = (rand() % 180) * (pi / 180); // start direction
// for start of line
line2deltaX = int(lineStepSize * cos(endAngle)); // Start of line
line2deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.
line2y = 0;
} else if (line2y >= screenSizeY) {
endAngle = (180 + (rand() % 180)) * (pi / 180); // start
// direction
// for start of
// line
line2deltaX = int(lineStepSize * cos(endAngle)); // Start of line
line2deltaY = int(lineStepSize * sin(endAngle)); // X, Y movement.
line2y = screenSizeY;
}
// Draw the current line
it.line(line1x, line1y, line2x, line2y, COLOR_ON);
// Save the position of the first line we draw so that we
// can go back to it when we render the next screen.
if (thisLine == 0) {
line1xStart = line1x;
line1yStart = line1y;
line2xStart = line2x;
line2yStart = line2y;
}
}
}
}
switch:
- platform: template
name: "${device_name} - Display"
id: ${device_name}_display
lambda: |-
return {};
turn_on_action:
- logger.log: "Turning on display"
- switch.template.publish:
id: ${device_name}_display
state: ON
- lambda: |-
id(dispNumMode) = 0;
- delay: 10s
- switch.turn_off: ${device_name}_display
turn_off_action:
- logger.log: "Turning off display"
- switch.template.publish:
id: ${device_name}_display
state: OFF
- lambda: |-
id(dispNumMode) = 1;
- platform: template
name: "${device_name} - Screensaver"
id: ${device_name}_screensaver
lambda: |-
return {};
turn_on_action:
- logger.log: "Turning on Screensaver"
- switch.template.publish:
id: ${device_name}_screensaver
state: ON
- lambda: |-
id(screenSaverNum) = 1;
- delay: 10 min
- switch.turn_off: ${device_name}_screensaver
turn_off_action:
- logger.log: "Turning off Screensaver"
- switch.template.publish:
id: ${device_name}_screensaver
state: OFF
- lambda: |-
id(screenSaverNum) = 0;
Sometimes, pressing the button works as expected. Something like the following is logged:
[11:46:44][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state ON [11:46:44][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state OFF [11:46:44][D][switch:013]: 'masterbedthermohygrometer - Screensaver' Turning ON. [11:46:44][D][main:458]: Turning on Screensaver [11:46:44][D][switch:013]: 'masterbedthermohygrometer - Display' Turning ON. [11:46:44][D][main:437]: Turning on display [11:46:44][D][switch:037]: 'masterbedthermohygrometer - Display': Sending state ON
Sometimes, pressing the button does not work (does nothing); something like the following is logged:
[11:57:37][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state ON [11:57:38][D][binary_sensor:036]: 'masterbedthermohygrometer - Button': Sending state OFF
The above suggests that the ESP is seeing the button press, however the on_click action in the config for the button isn’t being triggered.
Worth noting is that multiple presses on the button in quick succession will fire the on_click action.
Does anyone have any idea what might be going on? …and thoughts on what I should do to solve?
Thanks,
Paul.