Home Assistant: ESP8266 as Basic Alarm Panel

I was not confident that the little Zigbee wireless contact sensors were consistent enough for alerting me of perimeter doors opening and closing, so I bought a NodeMCU and some Normally Closed contact sensors, and added some simple pin monitoring to alert Home Assistant via MQTT that either my gate was opened or my garage door was opened.

It really can’t be simpler. The code also could be cleaned up, but it works. I know it’s not the best form, but I’m not concerned so long as it works.

Update 2022-05-11: My original sketch didn’t use authentication, because when I wrote it, Home Assistant’s MQTT broker didn’t require it. Since then, the MQTT broker update “broke” my connections, because now username/password is required. I fixed it with the three lines in RED below.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.
const char* ssid = "MyWifiSSID";
const char* password = "myWifiPassword";
const char* mqtt_server = "homeassistant.local";

WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE  (50)
#define MQTT_USER "yourmqttuser"
#define MQTT_PASSWORD "yourmqttpass"
char msg[MSG_BUFFER_SIZE];
int value = 0;

String hostname = "Nodemcu1";

void setup_wifi() {
  delay(5);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  //WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
  WiFi.setHostname(hostname.c_str());
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

/*
 * This is what receives MQTT.
 * Fetch the 433 code payload and send it using the transmitter
 */
void callback(char* topic, byte* payload, unsigned int length) {
  String strFromHa;
  int intFromHa;
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  //comes through as an array
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
    strFromHa += (char)payload[i];
  }
  //convert to INT
  //intFromHa = strFromHa.toInt();

}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    //if (client.connect(clientId.c_str())) {
    if (client.connect(clientId.c_str(), MQTT_USER, MQTT_PASSWORD)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopicContact", "hello world");
      // ... and resubscribe
      client.subscribe("inTopicContact");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  pinMode(D5, INPUT_PULLUP);
  pinMode(D6, INPUT_PULLUP);
//mqtt
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

//mqtt
void sendMqtt(int intSensorNum) {
  //snprintf (msg, MSG_BUFFER_SIZE, "D5 #%ld", intSensorNum);
  //I think the %ld = end of line
  snprintf (msg, MSG_BUFFER_SIZE, "#%ld", intSensorNum);
  Serial.print("Publish message: ");
  Serial.println(msg);
  client.publish("outTopicContact", msg);
}

int intSecsOpen = 0;
int intSecsOpen5 = 0;
int intSecsOpen6 = 0;
void loop() {
  Serial.println("-----Waiting for Input----- " + String(intSecsOpen));

  //D5 - Blue Wire - garage door
  boolean btnPressed5 = !digitalRead(D5);
  if(btnPressed5 == false) {
    Serial.println("Door is open on D5.");
    intSecsOpen5++;
    //Only send alert when first detected.
    if (intSecsOpen5 == 1) {
      sendMqtt(50);
    }
  }
  //If it's been open for more than a second, monitor for it closing
  if (intSecsOpen5 > 0) {
    if(btnPressed5 == true) {
      //Once it's closed, reset the seconds open timer.
      //Or if it's been open for an hour - not sure if I'll add this.
      intSecsOpen5 = 0;
        sendMqtt(51);
        Serial.println("-----Door is closed on D5.-----");
    }
  }


  //D6 - Orange Wire - back gate
  boolean btnPressed6 = !digitalRead(D6);
  if(btnPressed6 == false) {
    Serial.println("Door is open on D6.");
    intSecsOpen6++;
    //Only send alert when first detected.
    if (intSecsOpen6 == 1) {
      sendMqtt(60);
    }
  }
  //If it's been open for more than a second, monitor for it closing
  if (intSecsOpen6 > 0) {
    if(btnPressed6 == true) {
      //Once it's closed, reset the seconds open timer.
      //Or if it's been open for an hour - not sure if I'll add this.
      intSecsOpen6 = 0;
        sendMqtt(61);
        Serial.println("-----Door is closed on D6.-----");
    }
  }

  //mqtt
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  client.publish("X", "online"); //adding this to keep it occupied.
  delay(1000);

  //send a keep alive message every 5 minutes also helps with troubleshooting
  if (intSecsOpen == 300) {
    sendMqtt(99);
    intSecsOpen = 0;
  }
  intSecsOpen++;
}

Note: I had to add the client.publish line and the “keep alive” lines, because the unit kept losing the MQTT connection.

Upload the INO, wire it up, then go to Home Assistant > Configuration > Devices > MQTT Configuration > then listen to topic: outTopicContact

Then set up an Automation for Open and Closed, using the trigger MQTT and the payload to distinguish between the sensors. Payload of #50 = garage open; #51 = garage closed.

Home Assistant: Various Setup Notes

I feel that it’s necessary to document the simplest of setups, because it’s not clear anywhere else. Here’s a list of little nuggets of info to make setup easier.

Skip to:

Add Google Home Mini to Home Assistant

It’s pretty fun to be able to control the Google Home Mini with voice, but I’d rather not sacrifice privacy for convenience, so I switched off the microphone. Now the Minis are just speakers I can use with Home Assistant – still pretty cool.

I don’t think there’s a way around installing the Google Home app in order to set these up. So first, set up your Google Home Mini in the Google Home app.

Then in Home Assistant, go to Configuration > Devices & Services > ADD INTEGRATION, and add the Google Cast integration. It will find your device(s) automatically.

I had two Minis, and I set up a Group that still doesn’t work called ggg.

I wanted to add a button to my dashboard to play Radio Paradise, so I created a Helper button. Then I created an Automation for when the button is pressed and added a Call Service that allows me to add the Entity and the URL for Radio Paradise.

How to add streaming audio to Google Home Mini via Home Assistant

And the automations.YAML:

- id: '1647188332619'
  alias: PlayRadioParadise
  description: ''
  trigger:
  - platform: state
    entity_id: input_boolean.radio_paradise
    from: 'off'
    to: 'on'
  condition: []
  action:
  - service: media_player.play_media
    data:
      media_content_id: https://stream.radioparadise.com/aac-128
      media_content_type: audio/mp3
    target:
      entity_id: media_player.google_den

Add Text to Speech (TTS) Using Google Home Mini

Additionally, I wanted a Text to Speech notification when my front door motion sensor went off, so I added the TTS action to my Front Door Motion trigger:

How to add TTS to a Google Home Mini in Home Assistant

Typical of the Home Assistant documentation, there’s no information on what the Cache field does. One presumes that it aligns with cache: “Allow TTS to cache voice file to local storage”, where the default is true. So what happens is that the first time it’s run, Home Assistant generates an MP3 file of a voice reading the text that’s stored in the config/tts directory – in this case, it’s Google’s voice.

And the automations.YAML:

  action:
  - service: tts.google_translate_say
    data:
      entity_id: media_player.google_den
      message: Il y a quelqu'un à la porte d'entrée
      language: fr

I later found a way to play the TTS and resume music, if music was streaming.

Add a Volume Slider to Media Player Card

The out of the box media player “card” doesn’t have a volume slider – fun, right? Apparently, there’s a Universal Media Player that you can configure to include a volume control, but I was unable to figure out how to do it [time spent: 1hr].

Others suggested using a custom Mini Media Player, which you can install using HACS (I used this install tutorial – NOTHING IS EASY).

Another option is adding this slider (slider-entity-row), which also requires HACS. I didn’t try this one.

I thought it was a HACS integration, but as it turns out, it is not; it’s a Frontend. So the steps are:

  1. Install HACS (Oh, and if you don’t have Terminal installed, you need to do that first.).

2. Reboot Home Assistant.

3. Open HACS, and click Frontend – it is not an Integration.

4. Search for Mini Media Player, and install it.

5. Try to add it.

6. Ponder why this error displays. This isn’t my first time installing HACS, so I wasn’t surprised.

HACS is disabled – Ratelimited. GitHub API calls are ratelimited, this will clear in less than 1 hour.

Note: I later found that you can fix this error by adding the following lines to your configuration.yaml file:

7. Download it.

I expected to find this new card in the Edit Dashboard > ADD CARD, but it wasn’t there. I rebooted Home Assistant. It’s still not there. I can see it’s installed in HACS > Frontend. I’m now trying to find out what to do next. The instructions are no help at all – it shows how to manually install it, but nothing about HACS. Do I manually install it now?

8. Looking at the Simple Install instructions, I guess I have to copy the mini-media-player-bundle.js to /config/www then go to Configuration > Dashboards > ADD DASHBOARD. The Simple Install instructions do not work.

9. OK, there’s the YAML Mode installation instructions. Let’s try that! I added the following to configuration.yaml:

lovelace:
  resources:
    - url: /local/mini-media-player-bundle.js?v=1.16.2
      type: module

10. And rebooted Home Assistant. And it’s still not there.

11. Go to browser settings > Delete Cache.

JFC

Simple but poorly documented.

FINALLY!

So after about 2 hours of fumbling in the dark, I finally was able to add a volume control to the media player. I also inadvertently deleted not just my cache but all my saved passwords as well.

So the moral of the story: don’t install it via HACS. Instead, download the JS (follow the Simple Install instructions) and put it in your config/www directory.

Serenity now … serenity now … serenity now …

How to Take an Automation Snapshot with Your Camera

In your automation, add an Action:

That /config/www/doorbell.jpg translates to http://homeassistant.local:8123/local/doorbell.jpg

Then add an action to send that file, adding the URL (http://homeassistant.local:8123/local/doorbell.jpg) to the Data field:

Apparently, this doesn’t actually send the image; instead, it sends the URL to the image, so it’s only visible if you’re on your network.

Create a Notifier Group

Instead of selecting each individual device to notify, using the Home Assistant app, you can create a notification group. I found it difficult to find the device name to use, and eventually found it by editing an Automation.

Here’s the official Notify Group documentation, and more about the mobile_app integration here. Neither of these pages provided the detailed steps necessary for me to set this up.

Open an Automation, and add a Call Service action, then enter: notify. to view your list of devices that you can notify.

There are three individual devices in this example, and you can see the new group I made: all_devices.

Now, use the File Editor to edit your configuration.yaml file, and add:

notify:
  - name: ALL_DEVICES
    platform: group
    services:
      - service: mobile_app_iphone
      - service: mobile_app_gens_ipad
      - service: mobile_app_kevins_ipad

Note that you need to prefix “mobile_app_” to each of the names in your Notify Group.

Also note that some Apple updates can rename your phone to something arbitrary like iPhone (44), and you’ll need to rename it back to the original name on the phone in Settings > General > About > Name.

Save, check configuration, and restart. Now you have the all_devices notification group to choose when you set up future Automations.

There’s a lot more you can do with notifications. I have yet been able to send an image in the notification, such as a snapshot from a camera where motion was detected.

Add System Monitoring

Watched this Youtube that recommended adding System Monitoring, so I did it. It requires you to add lines to your configuration.yaml.

sensor:
  - platform: systemmonitor
    resources:
      - type: disk_use_percent
        arg: /config
      - type: memory_free
      - type: processor_use
      - type: processor_temperature

I can’t get the processor_temperature to work – maybe my Dell Wyse doesn’t provide it.

Install Sage by Hughes Zigbee Doorbell Sensor

My doorbell terminals were reversed from most of what I see on the Internet. I did not turn off power to the house, because there’s no power going to the doorbell until the door bell is pressed – and it’s only 20VAC anyway, so it’s not terribly dangerous to work with. (Answers the question: Do I need to turn off power to install the Sage by Hughes Zigbee door bell sensor? – Not necessary, no. But if you are a nervous Nelly, sure, why not?)

This isn’t my doorbell, but it is the way mine is labeled and wired.

After wiring it up, I removed the battery blocker to enable the Sage, and there was no button to press for pairing. I went to Configuration > ZHA > ADD DEVICE, and it paired automatically.

The battery shows as zero percent in Home Assistant, although I checked the battery and it’s fully charged. Also, the device is activated when the “Secondary” bell is pressed – that took some trial and error.

Overall, this was really easy to install and set up. The lithium battery it comes with should last for years.

Resume Playing Media After TTS Announcement Using Variables

I’m not sure why there are integrations for variables, such as hass-variables and home-assistant-variables. I mention them here, because after reading through how to set variables for media stream and volume, I saw two different posts saying that Home Assistant doesn’t natively have variables. However, when I added variables to my automation, they worked just fine. So the following steps allowed me to add the stream URL and volume variables to and automation, which I used to resume a media stream and reset the volume.

Note: This only works when playing Internet radio, like https://ice1.somafm.com/seventies-128-mp3. It doesn’t work when I’m streaming Pandora.

I’m using a Google Home device to stream Internet radio stations. Below is an example of my media player attributes (found in Developer Tools > media_player.mydevicename).

volume_level: 0.3499999940395355
is_volume_muted: false
media_content_id: https://ice1.somafm.com/seventies-128-mp3
media_position: 0.884747
media_position_updated_at: 2022-03-23T16:52:26.189829+00:00
app_id: CC1AD376
app_name: Default Media Receiver
entity_picture_local: null
friendly_name: Gmega
supported_features: 152463

The attributes I’m interested in are media_content_id and maybe the volume_level. I want an automation to play the Text to Speech notification, then resume playing what it was playing before.

Up to this point, I’ve mostly avoided having to directly edit the automations.yaml, but this automation requires it. In pseudo-code:

Trigger: When doorbell is pressed.
Actions:
  Get the currently playing stream and volume of the Google Home device and make them variables
  Set the volume of the Google Home to 50%
  Wait a second (because it takes a second to set volume)
  Play the TTS message: Someone rang the front door bell
  Wait 3 seconds
  Set the volume of the Google home back to what it was before
  Continue streaming

This took a few trial and errors, but I was able to get it running. Below is the YAML:

- id: '1647788235635'
  alias: Doorbell Front [TTS]
  variables:
    gmega_media_content_id: '{{ state_attr(''media_player.gmega'',''media_content_id'')
      }}'
    gmega_State: '{{ states(''media_player.gmega'') }}'
    gmega_Volume: '{{ state_attr(''media_player.gmega'',''volume_level'')
      }}'
  description: ''
  trigger:
  - device_id: 52d13f765c829a88454a4080019265af
    domain: zha
    platform: device
    type: remote_button_short_press
    subtype: button_2
  condition: []
  action:
  - service: media_player.volume_set
    data:
      entity_id: media_player.gmega
      volume_level: 0.5
  - delay:
      hours: 0
      minutes: 0
      seconds: 1
      milliseconds: 0
  - service: tts.google_translate_say
    data:
      entity_id: media_player.gmega
      message: Someone rang the front door bell
  - delay:
      hours: 0
      minutes: 0
      seconds: 3
      milliseconds: 0
  - service: media_player.volume_set
    data:
      entity_id: media_player.gmega
      volume_level: '{{ gmega_Volume }}'
  - service: media_player.play_media
    target:
      entity_id: media_player.gmega
    data:
      media_content_id: '{{ gmega_media_content_id }}'
      media_content_type: music
  mode: single

Now that I have some weeks of configuring automations in the UI, I can see how the YAML reflects those fields, and I now have a better understanding of how to write the YAML. I use the File Editor to check my syntax, and I watch for errors when reloading the automations. So it took about 6 weeks of significant time investment to get to a point where YAML is not quite as intimidating as it was before.

I still have yet to figure out how this YAML works. Maybe it’s a script? I tried it and HA didn’t like sequence.

More to come…

Home Assistant: How to Add Wyze Cameras without Flashing with RTSP

Like most things with Home Assistant, much of the documentation is there, while key details are missing or presumed knowledge. While I was able to get this to work, I’m not confident that it works as well as a natively compatible camera. The stream is acceptable, but my weaker cameras tend to pause – probably due to a poor connection. I was able to get a live stream on the dashboard after I began editing my own dashboards – and not relying on Home Assistant to do it for me.

Install the Add On

I generally followed the steps in the Github instructions.

Install the docker-wyze-bridge add-on: go to Github, scoll down to and click ADD REPOSITORY.

Open the repository in your Home Assistant, and add it.

Don’t Start it yet!

Click the Configuration tab, and enter your Wyze email and password. Leave everything else as default; I had to set the RTSP_READTIMEOUT when setting up the config. From what I can understand, the RTSP_READTIMEOUT is the time allowed for the cameras to drop offline before the integration considers them unavailable. I had a camera with a weak connection, so I set this to 10 seconds to avoid losing the feed (actually I lose the feed, but at least the image remains, giving me the illusion that it’s still running).

Click the Info tab and Start.

Now go to your Overview page, and you will see the video stills. Your stream isn’t yet set up.

Go back to the Docker Wyze Bridge add on, and click the Log tab to find your camera stream names. You should see something like this:

2022/03/07 11:35:07 [RTSP][FRONTDOOR] ? '/frontdoor' stream is UP! (3/3)
2022/03/07 11:35:08 [RTSP][BACKDOOR] ? '/backdoor' stream is UP! (3/3)
2022/03/07 11:35:08 [RTSP][GARAGE] ? '/garage' stream is UP! (3/3)

If you don’t see your cameras, you may need to Restart the add on.

Note your camera names here – they are not quite the same as what you named them in your Wyze app, nor are the the same as displayed in your router. This detail alone took many trial and errors before I found that I could just look it up in the add on the docker-wyze-bridge add in Log tab

Check the Video Stream with VLC

To check to see if they’re working, install VLC Media Player, then open it > Media > Open Network Stream, enter the rtsp stream address, and click Play. Note the IP address is the address of your Home Assistant, and not the IP of the camera.

You should see your video stream there. If not, troubleshoot the stream before trying to add it to your Home Assistant.

Add Stream to Configuration.YAML

Once you have a functioning stream, use the HA File Editor to edit your configuration.yaml, and add the following:

camera:
  - platform: generic
    name: Backdoor
    stream_source: rtsp://192.168.0.84:8554/backdoor
  - platform: generic
    name: Frontdoor
    stream_source: rtsp://192.168.0.84:8554/frontdoor
  - platform: generic
    name: Garage
    stream_source: rtsp://192.168.0.84:8554/garage

where the IP address is the IP address of your Home Assistant.

Click Configuration > Settings > CHECK CONFIGURATION.

If it’s OK then click RESTART.

After restarting Home Assistant, you may need to restart the docker-wyze-bridge add-on too. You should now see your streaming cameras on your dashboard.

Make it Live on Dashboard

Edit your dashboard, and click EDIT on the camera card.

Select live for the Camera View.

I can see that the cameras with the worst connection tend to time out, so I may need to buy a WiFi repeater.

Next: Which Camera Platform Works Best

This post discusses the different platforms and what worked best for him/her.

I tried it with FFMPEG, but the stream kept freezing and going Unavailable. To try FFMPEG, add this to the configuration.yaml:

stream:
ffmpeg:
  ffmpeg_bin: /usr/bin/ffmpeg
camera:
  - platform: ffmpeg
    name: BackdoorFF
    input: -rtsp_transport tcp -i rtsp://192.168.0.84:8554/backdoor
    extra_arguments: '-q:v 1 -r 15 -vf "scale=640:360"'
  - platform: generic
    name: Frontdoor
    stream_source: rtsp://192.168.0.84:8554/frontdoor
  - platform: generic
    name: Garage
    stream_source: rtsp://192.168.0.84:8554/garage

The extra_arguments may or may not work. It streams without that line. It didn’t appear to me that it was re-scaled, so it probably didn’t work.

Overall, it was 1) worse video feed quality 2) tended to drop out. So I reverted back to the generic camera.