Hello !
I’ve made an esphome stopwatch and i want to share my code.
I’ts not perfect but it works
Limitations :
- The HC-SR04 sensor can only be fire up every 50ms, so the stopwatch have a sensitivity of 0.05s
- The stopwatch is synchronysing on a ntp server. That’s how you can use two stopwatch. Someone told me that it could have issues, but after few hours, i didn’t found any. I will make more tests.
- I use it with nodered so i prefer to use mqtt protocol (but it’s working with esphome api)
Configuration :
- You can adjust the detection distance in the “Detection distance (cm)” number. Default is 100 cm, so, if HC-SR04 “see” something minus 100 cm, it starts
Running :
You can see the state of the stopwatch with the onboard led (pin 2 on my nodemcuV2, it’s hardcoded in my stopwatch_custom_sensor.h, you can change it).
- When the led pin blink every 1000ms (1 second), the stopwatch is waiting for button_start_detection (Press it on homeassistant or send PRESS in paypload in nodered)
- When the led pin blink every 100ms (0.1 second), the stopwatch is waiting for someone (or something) in front of him to start
- When the led pin blink 10 times 25ms (0.025 second), the stopwatch have detect someone (or something), it send the timestamp with a precision of 0.01s (remember that the sensor have a precision of 0.05s)
Here is the .yaml code :
substitutions:
stopwatch_number: "1"
esphome:
name: stopwatch-${stopwatch_number}
friendly_name: stopwatch-${stopwatch_number}
includes:
- stopwatch_custom_sensor.h
esp8266:
board: nodemcuv2
# Enable logging
# logger:
# Example configuration entry
mqtt:
broker: mymosquitto.com
username: admin
password: !secret mqtt_password
# Enable Home Assistant API
#api:
# encryption:
# key: "B5Fa94LAON5+ePby6NzUhKOZ+FkzefoVgZcyysWHJMc="
ota:
password: "0d18357050485c325b5435acc4a76705"
wifi:
networks:
- ssid: !secret wifi_ssid_cyp
password: !secret wifi_password_cyp
- ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: stopwatch-${stopwatch_number}-Hotspot
password: "8uu4d7gYav0s"
captive_portal:
# Web server
web_server:
port: 80
# Example configuration entry
time:
- platform: sntp
id: sntp_time
# Add number template
number:
- platform: template
name: "Detection distance (cm)"
id: number_detection_distance
optimistic: true
min_value: 10
max_value: 200
initial_value: 100
step: 10
on_value:
lambda: |-
((ChronoCustomSensor*)chrono_custom_id)->distanceDetection = x;
text_sensor:
- platform: custom
lambda: |-
auto chrono_custom = new ChronoCustomSensor();
App.register_component(chrono_custom);
return {chrono_custom};
text_sensors:
name: "Chrono Custom Sensor"
id: chrono_custom_id
# Add button template
button:
- platform: template
name: "Start detection"
id: button_start_detection
on_press:
- logger.log: Button Start detection Pressed
- lambda: !lambda |-
ESP_LOGD("main", "Button Start detection", "pressed !");
if (((ChronoCustomSensor*)chrono_custom_id)->currentState == ((ChronoCustomSensor*)chrono_custom_id)->State::Wait) {
((ChronoCustomSensor*)chrono_custom_id)->currentState = ((ChronoCustomSensor*)chrono_custom_id)->State::Chrono;
// ((ChronoCustomSensor*)chrono_custom_id)->publish_state("0");
}
And here is stopwatch_custom_sensor.h (put it in /config/esphome/stopwatch_custom_sensor.h in home assistant)
#include "esphome.h"
class ChronoCustomSensor: public Component, public TextSensor {
// code from https://randomnerdtutorials.com/esp8266-nodemcu-hc-sr04-ultrasonic-arduino/
// Should be better to use esphome ultrasonic ??
public:
float get_setup_priority() const override { return esphome::setup_priority::AFTER_WIFI; }
unsigned long previousMillis = 0; // will store last time LED was updated
int ledState = LOW; // ledState used to set the LED
int blinkInterval = 1000;
int shortInterval = 100;
const int trigPin = 12;
const int echoPin = 14;
//define sound velocity in cm/uS
#define SOUND_VELOCITY 0.034
#define CM_TO_INCH 0.393701
long duration;
float distanceCm;
int distanceDetection = 100;
int detectionRetry = 0; // Number of time under the distanceDetection
enum class State {
Wait,
Chrono,
Detect
};
State currentState = State::Wait;
struct timeval tv;
void setup() override {
pinMode(2, OUTPUT); // LED pin as output.
pinMode(trigPin, OUTPUT); // Sets the trigPin as an Output
pinMode(echoPin, INPUT); // Sets the echoPin as an Input
}
void loop() override {
unsigned long currentMillis = millis();
switch (currentState) {
case State::Wait :
// Blink Led Slow
if (currentMillis - previousMillis >= blinkInterval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if (ledState == LOW) {
blinkInterval = 1500;
ledState = HIGH;
} else {
blinkInterval = 100;
ledState = LOW;
}
digitalWrite(2, ledState);
}
break;
case State::Chrono :
// Blink Led quick
if (currentMillis - previousMillis >= shortInterval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(2, ledState);
// ESP_LOGD("main", "distance cm %d", (int)distanceCm);
// ESP_LOGD("main", "distance detection %d", (int)distanceDetection);
}
// Distance measure
// Clears the trigPin
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
// Sets the trigPin on HIGH state for 10 micro seconds
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// Reads the echoPin, returns the sound wave travel time in microseconds
duration = pulseIn(echoPin, HIGH);
// Calculate the distance
distanceCm = duration * 0.034/2;
if ((int)distanceCm < (int)distanceDetection) {
detectionRetry++;
if (detectionRetry == 2) {
detectionRetry = 0;
currentState = State::Detect;
}
if (detectionRetry == 0) {
gettimeofday(&tv, NULL); // get the first detect
}
} else {
detectionRetry = 0;
}
break;
case State::Detect :
// Sending time
ESP_LOGD("main", "tv_sec %d", tv.tv_sec);
ESP_LOGD("main", "tv_usec %d", tv.tv_usec);
int msec = tv.tv_usec / 10000;
ESP_LOGD("main", "tv_usec %d", msec);
for (int i=0; i < 10; i++) {
digitalWrite(2, LOW);
delay(25);
digitalWrite(2, HIGH);
delay(25);
}
char buffer [128];
unsigned long long int mylong = tv.tv_sec * 100 + msec;
int ret = snprintf(buffer, sizeof(buffer), "%llu", mylong);
publish_state(buffer);
currentState = State::Wait;
break;
}
}
};
Feel free to improve my work !