Skip to content

Instantly share code, notes, and snippets.

@zry98
Created June 29, 2023 19:22
Show Gist options
  • Save zry98/5ce73cafb42d77dd6eb37c3df786d28a to your computer and use it in GitHub Desktop.
Save zry98/5ce73cafb42d77dd6eb37c3df786d28a to your computer and use it in GitHub Desktop.
Digoo weather station sensor component for ESPHome
substitutions:
device_name: 'Digoo Station'
external_location: 'Balcony'
rf_receiver_pin: '4'
digoo_sensor_channel: '2'
digoo_sensor_id: '9'
esphome:
name: digoo-station
comment: 'ESP station for environment monitoring'
includes:
- includes/rf433_receiver.h
esp8266:
board: esp12e
restore_from_flash: true
preferences:
flash_write_interval: 24h
logger:
level: WARN
baud_rate: 115200
wifi:
fast_connect: true
networks:
- ssid: !secret wifi_ssid
bssid: !secret wifi_bssid
password: !secret wifi_password
domain: !secret domain_name
api:
id: hass_api
ota:
password: !secret ota_password
on_begin:
then:
# disable interrupt for RF receiver during OTA
- lambda: 'detachInterrupt(digitalPinToInterrupt(${rf_receiver_pin}));'
button:
# for hard restart
- platform: restart
id: restart_button
name: 'Restart ${device_name}'
entity_category: diagnostic
status_led:
pin:
number: 2
inverted: true
sensor:
# 433 MHz RF receiver for digoo sensor
- platform: custom
lambda: |-
auto digoo = new DigooSensor(${rf_receiver_pin}, ${digoo_sensor_channel}, ${digoo_sensor_id});
App.register_component(digoo);
return {digoo->temperature_sensor, digoo->humidity_sensor, digoo->battery_sensor};
sensors:
- name: '${external_location} Temperature'
device_class: temperature
unit_of_measurement: '°C'
accuracy_decimals: 1
icon: mdi:thermometer
- name: '${external_location} Humidity'
device_class: humidity
unit_of_measurement: '%'
accuracy_decimals: 0
icon: mdi:water-percent
- id: balcony_digoo_sensor_battery
accuracy_decimals: 0
internal: true
binary_sensor:
# digoo sensor battery monitor
- platform: template
name: '${external_location} Digoo Sensor Battery Low'
lambda: 'return int(id(balcony_digoo_sensor_battery).state) == 0;'
icon: mdi:battery-alert
/**
* Example Digoo sensor packet: 1000 | 0101 | 1 | 0 | 01 | 000010001010 | 1111 | 01000101
* - | sensorID | battery | - | channel | temperature | - | humidity
*
* channel 1 = 0, temperature is 12 bit signed scaled by 10, `-` are constant bits
*/
#pragma once
#ifndef VERSION_CODE
#include "esphome.h"
#include "Arduino.h"
using namespace esphome;
using namespace esphome::sensor;
using namespace esphome::binary_sensor;
using namespace esphome::api;
#define micros() esphome::micros()
#define millis() esphome::millis()
APIServer *hass_api;
#endif
#define DIGOO_PACKET_SIZE 36
#define DIGOO_END_PACKET 3500
#define DIGOO_ZERO 800
#define DIGOO_ONE 1800
#define MAX_WIDTH 150000
#define PULSE_BUFFER_SIZE 128
static volatile uint32_t digoo_pulse_buffer[PULSE_BUFFER_SIZE];
static volatile uint8_t digoo_pulse_counter;
static volatile uint32_t digoo_pulse_width;
static volatile uint64_t digoo_packet;
static volatile uint64_t digoo_packets[3] = {0, 0, 0};
static volatile uint8_t digoo_packet_counter = 0;
IRAM_ATTR HOT static void processDigooPacket() {
digoo_packet = 0;
for (uint8_t i = digoo_pulse_counter - DIGOO_PACKET_SIZE; i < digoo_pulse_counter; i++) {
digoo_pulse_width = digoo_pulse_buffer[i];
if (digoo_pulse_width >= DIGOO_ONE) {
digoo_packet = (digoo_packet << 1) | 1;
} else if (digoo_pulse_width >= DIGOO_ZERO) {
digoo_packet <<= 1;
}
}
if ((digoo_packet >> 8 & 0b1111) == 0b1111 && (digoo_packet >> 26 & 0b1) == 0b0) { // check for the constant bits
ESP_LOGD("DigooSensor", "RawPacket=%#011x", digoo_packet);
digoo_packets[digoo_packet_counter] = digoo_packet;
if (digoo_packet_counter < 3) {
digoo_packet_counter++;
} else {
digoo_packet_counter = 0;
}
}
}
static volatile unsigned long now, last_time, duration;
static void IRAM_ATTR HOT handleRFReceiverInterrupt() {
now = micros();
duration = now - last_time;
if (duration >= DIGOO_END_PACKET) {
if (digoo_pulse_counter >= DIGOO_PACKET_SIZE) {
processDigooPacket();
}
digoo_pulse_counter = 0;
} else {
if (duration < MAX_WIDTH) {
if (duration >= DIGOO_ZERO) { // a pulse
digoo_pulse_buffer[digoo_pulse_counter] = duration;
digoo_pulse_counter++;
}
}
if (digoo_pulse_counter >= PULSE_BUFFER_SIZE) { // reset
digoo_pulse_counter = 0;
}
}
last_time = now;
}
class DigooSensor : public PollingComponent {
private:
uint8_t rf_receiver_pin;
uint8_t channel, sensor_id;
uint64_t p = 0; // packet copy
uint8_t packet_channel, packet_id;
float temperature;
uint8_t humidity;
uint8_t battery;
bool available() {
return digoo_packets[0] != 0 && digoo_packets[1] == digoo_packets[0] && digoo_packets[2] == digoo_packets[0];
}
void getPacket() {
p = digoo_packets[2];
digoo_packets[0] = 0, digoo_packets[1] = 0, digoo_packets[2] = 0;
}
uint8_t getId() {
return (p >> 28) & 0b1111;
}
uint8_t getChannel() {
return ((p >> 24) & 0b11) + 1;
}
float getTemperature() {
int16_t t = p >> 12 & 0x0FFF;
t = 0x0800 & t ? 0xF000 | t : t;
return float(t) / 10.0f;
}
uint8_t getHumidity() {
return p & 0xFF;
}
uint8_t getBattery() {
return (p >> 27) & 0b1;
}
uint8_t isValidWeather() {
// sanity checks according to specs https://www.amazon.es/gp/product/B073HXWP2N/
// if (humidity < 20 || humidity > 95) {
if (humidity < 0 || humidity > 100) {
ESP_LOGW("DigooSensor", "Invalid humidity");
return false;
}
if (temperature < -20.0f || temperature > 60.0f) {
ESP_LOGW("DigooSensor", "Invalid temperature");
return false;
}
return true;
}
public:
Sensor *temperature_sensor = new Sensor();
Sensor *humidity_sensor = new Sensor();
Sensor *battery_sensor = new Sensor();
DigooSensor(uint8_t _rf_receiver_pin, uint8_t _channel, uint8_t _sensor_id) : PollingComponent(30000) {
rf_receiver_pin = _rf_receiver_pin;
channel = _channel;
sensor_id = _sensor_id;
}
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
void setup() override {
pinMode(rf_receiver_pin, INPUT);
digitalWrite(rf_receiver_pin, LOW);
attachInterrupt(digitalPinToInterrupt(rf_receiver_pin), handleRFReceiverInterrupt, CHANGE);
ESP_LOGD("DigooSensor", "OK");
}
void update() override {
if (!available()) {
return;
}
getPacket();
packet_channel = getChannel(), packet_id = getId();
ESP_LOGI("DigooSensor", "Channel=%d, SensorID=%d", packet_channel, packet_id);
if (packet_channel != channel || packet_id != sensor_id) { // not from the known sensor, skip it
return;
}
temperature = getTemperature();
humidity = getHumidity();
battery = getBattery();
ESP_LOGI("DigooSensor", "Temperature=%.1f, Humidity=%d, Battery=%d", temperature, humidity, battery);
if (isValidWeather()) {
temperature_sensor->publish_state(temperature);
humidity_sensor->publish_state(humidity);
battery_sensor->publish_state(battery);
return;
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment