Skip to content

Instantly share code, notes, and snippets.

@mbillow
Last active March 9, 2021 13:55
Show Gist options
  • Save mbillow/39a159f1161fef9f97d1f9dad5b2e9a9 to your computer and use it in GitHub Desktop.
Save mbillow/39a159f1161fef9f97d1f9dad5b2e9a9 to your computer and use it in GitHub Desktop.

Using a NodeMCU and RXB6 to Publish Doorbell Presses to MQTT

The Problem

I have a Eufy Video doorbell that does a great* job with push notifications, 2K video streaming, and providing basic doorbell functionality; all without a monthy subscription. It comes with a basic 433MHz chime that you can pair and put anywhere in your house. This doesn't really work well with a two story house... it is no where near loud enough to hear it upstairs.

* Despite notifying me that the tree in my front yard is a person when it is windy enough.

Everything in our house is "wired" together through an instance of Home Assistant. We also have Sonos speakers in pretty much every room. The solution seems obvious: make HA play a noise through Sonos. This would be easy if Eufy exposed any kind of consumer facing API... but alas they do not.

Note: There is a bunch of random, unsupported information about the API and MQTT out there. If there is one thing that I have learned from my day job, it is that internal API design is fast, loose, and can change at any moment. Relying on this is not something that is likely to remain working long term.

The Solution

The idea is pretty simple: Listen in on the Doorbell -> Chime radio comms and push messages to an MQTT topic. Once there we can leverage the existing NodeRed and HA setup to do the Sonos magic.

The BOM

I am going to include Amazon links because that is likely the most consumer friendly option. Feel free to buy your heart out on Aliexpress if you have the patience to wait.

  • 1x NodeMCU

    • These are super handy little ESP8266 boards with decent I/O and (more importantly) a WiFi chip.
  • 1x RXB6 433Mhz Superhet Reciever

    • This is likely overkill, but the concepts of Superheterodyne recievers have always been cool to me... sooo it's worth the $5 to me.
    • If you want to learn more about superhet radios, Technology Conncetions has an awesome video. His channel is also really cool in general, you should check it out.
  • 1x 433Mhz Antenna

    • You could also just use a wire twisted around a pencil. I wanted something compact because my eventual plan is to encase this and hide it somewhere.

Wiring it Up

The NodeMCU runs at 3.3V and the RXB6 runs at 5V. Luckily, the NodeMCU has the circuitry to convert 5V to what it needs, so you can power the NodeMCU over USB (which typically provides 5V power) and run the RXB6 off the Vin pin.

Wiring Diagram | Breadboarded Design

Programming the NodeMCU

Unless I am doing WLED stuff, I tend to always flash my NodeMCUs via the Arduino IDE. You can get a copy here if you don't already have it.

If this is your first NodeMCU/ESP8266 project, you will need to add the ESP8266 Board Manager to the IDE using these instructions.

You will also need to add a few libraries to the Arduino IDE:

Note: If you haven't installed Arduino libraries before, download the ZIP files from GitHub and load them in via Sketch > Include Library > Add .ZIP Library.

Once you have all of this installed, you can connect your NodeMCU via USB. Go to Tools > Port and select the USB device for your NodeMCU. Then, set the board type to Node MCU 1.0 via the Tools > Board > ESP8266 Boards menu.

Press and hold both the RST and Flash buttons, let go of the RST button and after a single flash of the LED, then let go of the Flash button.

Load the program below into the Arduino IDE, fill in the variables, and hit the Upload button (the right facing arrow at the top). This will compile and load the program onto the NodeMCU.

Load the serial monitor (from the Tools menu) and set it to 115200 Baud. As the NodeMCU starts up, you will see it connecting to your WiFi network, getting and IP address, and attempting to connect to your MQTT broker.

We aren't done yet though. We need you figure out the code your doorbell is sending out to the chime. Once everything has booted (the broker has printed it's connected message), you should be able to press your doorbell and have a message appear in the console. If nothing shows up, make sure you have wired everything correctly.

Unknown signal received!
Received XXXXXX / 24 bit Protocol: 1

Take the XXXXXX portion and set the doorbell_id variable at the top of the program. Re-upload the program and let it boot. This time presing your doorbell should result in a "Ding Dong!" message and should publish a message to the MQTT topic.

NodeRED and Home Assistant

I have included my NodeRED flow JSON below. You will likely need to modify the names and entity regex(es) but it should give you a pretty good jumping off point.

/*
Publish RF Doorbell Signal to MQTT
*/
#include <RCSwitch.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#define wifi_ssid "YOUR_WIFI_SSID"
#define wifi_password "YOUR_WIFI_PASSWORD"
#define mqtt_server "192.168.1.1"
#define mqtt_user "MQTT_USERNAME"
#define mqtt_password "MQTT_PASSWD"
// This is the RF message the doorbell sends.
// You will need to run this as is first, and get the ID after
// the "Unknown signal received!" message
#define doorbell_id 5179940
#define doorbell_topic "sensor/doorbell"
RCSwitch mySwitch = RCSwitch();
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
Serial.begin (115200);
mySwitch.enableReceive(D2);
setup_wifi();
client.setServer(mqtt_server, 1883);
Serial.println ("start");
}
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(wifi_ssid);
WiFi.begin(wifi_ssid, wifi_password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
// If you do not want to use a username and password, change next line to
// if (client.connect("ESP8266Client")) {
if (client.connect("ESP8266Client", mqtt_user, mqtt_password)) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
long lastMsg = 0;
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
// You get roughly 4-5 messages within a second each time the
// button is pressed, so lets add some time contraints.
long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
if (mySwitch.available()) {
int value = mySwitch.getReceivedValue();
if (value == doorbell_id) {
Serial.println("Ding Dong!");
client.publish(doorbell_topic, "front_door");
} else {
Serial.println("Unknown signal received!");
Serial.print("Received ");
Serial.print( mySwitch.getReceivedValue() );
Serial.print(" / ");
Serial.print( mySwitch.getReceivedBitlength() );
Serial.print("bit ");
Serial.print("Protocol: ");
Serial.println( mySwitch.getReceivedProtocol() );
}
mySwitch.resetAvailable();
}
}
}
[
{
"id": "d8ebccc8.ddb8c",
"type": "tab",
"label": "Sonos Doorbell",
"disabled": false,
"info": ""
},
{
"id": "a0cc0f9.6f370f",
"type": "function",
"z": "d8ebccc8.ddb8c",
"name": "Filter and Store Current Sonos States",
"func": "var sonosSpeakers = []\n\nfor (i = 0; i < msg.payload.length; i++) {\n var entity = msg.payload[i];\n if (entity.entity_id.includes(\"sonos\")) {\n sonosSpeakers.push({\n \"entity_id\": entity.entity_id,\n \"media_content_id\": entity.attributes.media_content_id,\n \"media_content_type\": entity.attributes.media_content_type,\n \"media_position\": entity.attributes.media_position,\n \"state\": entity.state,\n \"volume_level\": entity.attributes.volume_level\n })\n }\n}\n\nflow.set(\"speaker_ids\", sonosSpeakers.map(x => x.entity_id).join(\", \"))\nflow.set(\"speaker_states\", sonosSpeakers)\nreturn {};",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 790,
"y": 240,
"wires": [
[
"6281911b.68e04"
]
]
},
{
"id": "4d544b1b.7d10f4",
"type": "ha-api",
"z": "d8ebccc8.ddb8c",
"name": "Get All States",
"server": "edcc1a69.99d288",
"debugenabled": false,
"protocol": "websocket",
"method": "get",
"path": "",
"data": "{\"type\":\"get_states\"}",
"dataType": "json",
"location": "payload",
"locationType": "msg",
"responseType": "json",
"x": 470,
"y": 240,
"wires": [
[
"a0cc0f9.6f370f"
]
]
},
{
"id": "ca95a51b.cf84d8",
"type": "api-call-service",
"z": "d8ebccc8.ddb8c",
"name": "Ding Dong!",
"server": "edcc1a69.99d288",
"version": 1,
"debugenabled": false,
"service_domain": "media_player",
"service": "play_media",
"entityId": "",
"data": "",
"dataType": "json",
"mergecontext": "",
"output_location": "payload",
"output_location_type": "msg",
"mustacheAltTags": false,
"x": 890,
"y": 480,
"wires": [
[
"761c0d9b.3c4714"
]
]
},
{
"id": "aa1fd25c.12df4",
"type": "api-call-service",
"z": "d8ebccc8.ddb8c",
"name": "Set Volume",
"server": "edcc1a69.99d288",
"version": 1,
"debugenabled": false,
"service_domain": "media_player",
"service": "volume_set",
"entityId": "",
"data": "",
"dataType": "json",
"mergecontext": "",
"output_location": "",
"output_location_type": "none",
"mustacheAltTags": false,
"x": 890,
"y": 400,
"wires": [
[
"80e49bb4.08d378"
]
]
},
{
"id": "45fb844b.9d8b3c",
"type": "api-call-service",
"z": "d8ebccc8.ddb8c",
"name": "Media Pause",
"server": "edcc1a69.99d288",
"version": 1,
"debugenabled": false,
"service_domain": "media_player",
"service": "media_pause",
"entityId": "",
"data": "",
"dataType": "json",
"mergecontext": "",
"output_location": "",
"output_location_type": "none",
"mustacheAltTags": false,
"x": 1010,
"y": 320,
"wires": [
[
"3ba1fa3a.7c5796"
]
]
},
{
"id": "6281911b.68e04",
"type": "function",
"z": "d8ebccc8.ddb8c",
"name": "Get Entity IDs",
"func": "return {\n \"payload\": {\n \"data\": {\n \"entity_id\": flow.get(\"speaker_ids\")\n }\n }\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 560,
"y": 320,
"wires": [
[
"3a94e9f.37a6c16"
]
]
},
{
"id": "3ba1fa3a.7c5796",
"type": "function",
"z": "d8ebccc8.ddb8c",
"name": "Format Volume",
"func": "return {\n \"payload\": {\n \"data\": {\n \"entity_id\": flow.get(\"speaker_ids\"),\n \"volume_level\": 0.5\n }\n }\n};",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 700,
"y": 400,
"wires": [
[
"aa1fd25c.12df4"
]
]
},
{
"id": "80e49bb4.08d378",
"type": "function",
"z": "d8ebccc8.ddb8c",
"name": "Format Play",
"func": "return {\n \"payload\": {\n \"data\": {\n \"entity_id\": flow.get(\"speaker_ids\"),\n \"media_content_id\": \"\",\n \"media_content_type\": \"music\"\n }\n }\n};",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 690,
"y": 480,
"wires": [
[
"ca95a51b.cf84d8"
]
]
},
{
"id": "761c0d9b.3c4714",
"type": "delay",
"z": "d8ebccc8.ddb8c",
"name": "",
"pauseType": "delay",
"timeout": "10",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"x": 780,
"y": 560,
"wires": [
[
"7a4d8300.a5524c"
]
]
},
{
"id": "3a94e9f.37a6c16",
"type": "api-call-service",
"z": "d8ebccc8.ddb8c",
"name": "Snapshot Sonos",
"server": "edcc1a69.99d288",
"version": 1,
"debugenabled": false,
"service_domain": "sonos",
"service": "snapshot",
"entityId": "",
"data": "",
"dataType": "json",
"mergecontext": "",
"output_location": "",
"output_location_type": "none",
"mustacheAltTags": false,
"x": 790,
"y": 320,
"wires": [
[
"45fb844b.9d8b3c"
]
]
},
{
"id": "92dabf49.9a73e",
"type": "api-call-service",
"z": "d8ebccc8.ddb8c",
"name": "Restore Sonos",
"server": "edcc1a69.99d288",
"version": 1,
"debugenabled": false,
"service_domain": "sonos",
"service": "restore",
"entityId": "",
"data": "",
"dataType": "json",
"mergecontext": "",
"output_location": "",
"output_location_type": "none",
"mustacheAltTags": false,
"x": 940,
"y": 620,
"wires": [
[]
]
},
{
"id": "7a4d8300.a5524c",
"type": "function",
"z": "d8ebccc8.ddb8c",
"name": "Get Speakers from Flow State",
"func": "return {\n \"payload\": {\n \"data\": {\n \"entity_id\": flow.get(\"speaker_ids\")\n }\n }\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 630,
"y": 620,
"wires": [
[
"92dabf49.9a73e"
]
]
},
{
"id": "7839c619.ffb938",
"type": "mqtt in",
"z": "d8ebccc8.ddb8c",
"name": "Doorbell Rings",
"topic": "sensor/doorbell",
"qos": "2",
"datatype": "auto",
"broker": "73256168.b59a3",
"x": 140,
"y": 160,
"wires": [
[
"4d544b1b.7d10f4"
]
]
},
{
"id": "edcc1a69.99d288",
"type": "server",
"z": "",
"name": "Home Assistant",
"legacy": false,
"addon": false,
"rejectUnauthorizedCerts": true,
"ha_boolean": "y|yes|true|on|home|open",
"connectionDelay": true,
"cacheJson": false
},
{
"id": "73256168.b59a3",
"type": "mqtt-broker",
"z": "",
"name": "Mosquitto",
"broker": "mosquitto",
"port": "1883",
"clientid": "",
"usetls": false,
"compatmode": false,
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"willTopic": "",
"willQos": "0",
"willPayload": ""
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment