Warning: Seriously nerdy stuff lies ahead. Proceed with caution.
This week we have an update on our "bus automation" project. Last time we introduced the technologies that we were exploring as well as some of the hardware we intended to use for experimentation.
I spent some time configuring and learning "Home Assistant", the platform we intend to use for our home automation stuff. I also configured, installed and configured MQTT as well as Node-Red. All of these are running on a Raspberry Pi 3 B+.
I decided that a nice first foray into the home automation stuff was to try to get 12 Volt lights working wirelessly using MQTT through Home Assistant. This took many nights of reading and troubleshooting as I am not familiar with this stuff at all.
For this first iteration, I decided to use a simple relay board with 8 relays to control 8 lights wirelessly using the ESP-32 Board leveraging MQTT on the Arduino stack. We configured our example light board with two lights over the "front area" near the driver and six lights in the "living room" area. We arranged them in two rows with lights on the driver's side and the passenger side.
The first step was to make Home Assistant aware of the new "switches" I was about to install using the ESP32 and the 8 relay board. This was a simple change to the "configuration.yaml" file to include the definitions for the lights: The important thing to notice here is the command_topic
and the state_topic
switch front driver:
icon: mdi:ceiling-light
platform: mqtt
name: "Front Driver Light"
command_topic: "cmnd/frontDriver1/power"
state_topic: "stat/frontDriver1/power"
qos: 1
payload_on: "ON"
payload_off: "OFF"
retain: true
switch front passenger:
icon: mdi:ceiling-light
platform: mqtt
name: "Front Passenger Light"
command_topic: "cmnd/frontPassenger1/power"
state_topic: "stat/frontPassenger1/power"
qos: 1
payload_on: "ON"
payload_off: "OFF"
retain: true
switch living driver1:
icon: mdi:ceiling-light
platform: mqtt
name: "Living Driver Light 1"
command_topic: "cmnd/livingDriver1/power"
state_topic: "stat/livingDriver1/power"
qos: 1
payload_on: "ON"
payload_off: "OFF"
retain: true
switch living passenger1:
icon: mdi:ceiling-light
platform: mqtt
name: "Living Passenger Light 1"
command_topic: "cmnd/livingPassenger1/power"
state_topic: "stat/livingPassenger1/power"
qos: 1
payload_on: "ON"
payload_off: "OFF"
retain: true
switch living driver2:
icon: mdi:ceiling-light
platform: mqtt
name: "Living Driver Light 2"
command_topic: "cmnd/livingDriver2/power"
state_topic: "stat/livingDriver2/power"
qos: 1
payload_on: "ON"
payload_off: "OFF"
retain: true
switch living passenger2:
icon: mdi:ceiling-light
platform: mqtt
name: "Living Passenger Light 2"
command_topic: "cmnd/livingPassenger2/power"
state_topic: "stat/livingPassenger2/power"
qos: 1
payload_on: "ON"
payload_off: "OFF"
retain: true
switch living driver3:
icon: mdi:ceiling-light
platform: mqtt
name: "Living Driver Light 3"
command_topic: "cmnd/livingDriver3/power"
state_topic: "stat/livingDriver3/power"
qos: 1
payload_on: "ON"
payload_off: "OFF"
retain: true
switch living passenger3:
icon: mdi:ceiling-light
platform: mqtt
name: "Living Passenger Light 3"
command_topic: "cmnd/livingPassenger3/power"
state_topic: "stat/livingPassenger3/power"
qos: 1
payload_on: "ON"
payload_off: "OFF"
retain: true
After that was done, I grouped the lights into two groups Front Lights
and Living Room Lights
. I then assigned the entities defined above into these groups by making the following entries into the "groups.yaml" file:
Front_Lights:
name: Front Lights
entities:
- switch.front_driver_light
- switch.front_passenger_light
Living_room_lights:
name: Living Room Lights
entities:
- switch.living_driver_light_1
- switch.living_passenger_light_1
- switch.living_driver_light_2
- switch.living_passenger_light_2
- switch.living_driver_light_3
- switch.living_passenger_light_3
That took care of home assistant and gave a screen to be able to control and keep the state of the lights through MQTT. The results was a screen that looked like the following:
Next was figuring out how to program the ESP32 to work with the 8-relay board and listen for these MQTT messages as well as send back "status" messages.
Fortunately, there are many great tutorials and people sharing information so getting this setup going was not too hard. The basic steps are:
- Initialize the output pins and set them all to OFF
- Connect to WiFi
- Connect to the MQTT server
- Subscribe to the 8 Events (one for each light)
- Handle the MQTT message (payload of "on" or "off")
- Report the status back to the MQTT Service once we turn the relay on or off
The actual code will be posted at the bottom (WAY at the bottom) of this post so you can have an idea of what that looks like.
Once this was all working we had to get some lights to experiment with.
We had a few criteria for choosing our overhead lighting. Number one was that the lights had to be very energy efficient. This, of course, meant that we had to go with LED lights. We also prefer the style of lights that look like home lights as opposed to something you would find in a car. From our research, we knew that we wanted to go with LED lights in the 2700K-3200K temperature range. This puts them in the "Warm White" range which we think will work best for us. We had to also keep in mind that we are using 12 Volts for lighting like most standard RVs. We prefer to have a lot of small lights providing light as opposed to a few big lights. With these criteria in mind, we began our search.
We decided to try some simple 2.8-inch LED lights we found on Amazon.com. We did not want to buy the cheapest lights we could find for fear that the LED lights used would be of lower quality. These lights are rated for 165mA @ 12V or 1.8 Watts. They produce 200 lumens at a color temperature of 2800-3200K (warm white). We ordered the lights, and upon inspecting them we were pleased that they appear to be well made and worked well. The light produced is a warm color temperature and the diffuser helps to soften the light even more. The body is plastic but there is some aluminum material in the base that I am assuming works as a heat sink for the LED circuit. A few tests on the lights revealed that they were only actually pulling about 130 milliAmps of current. They did give off good light and all appeared to be functioning very well.
Now that we had decided on lights it was time to hook them up.
The wiring was done using a 12 Volt PC power supply to generate the 12 Volts. We used a USB cell phone charger to provide the 5 Volts for the ESP-32. For the wires, we used any wires we had laying around including a few left over from the BMS wiring and some left from the 48 Volt to 12 Volt buck converters.
I used a piece of cardboard and cut out 8 holes as described above to serve as a mount point for the lights.
Wiring the 8 relay board to the ESP-32 was simple as it is designed to work with Arduino boards. I simply hooked up Vcc, ground, and 1 signal pin to each of the 8 relay inputs (8 signal wires total).
To wire up the lights I hooked all of the +12 Volts leads together and wired in all the lights in common. The negative got wired through each of the relays on the board (giving us individual control over each light).
After all this setup, it was time to test how it all worked. Watch the video to see how it worked out!
Watch the video:
Click here If you cannot see the video.
Parts We Used
The Code:
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiServer.h>
#include <WiFiUdp.h>
#include <PubSubClient.h>
boolean debug = true;
//Config stuff here
const char* ssid = "SSID_GOES_HERE";
const char* pass = "WIFI_PASSWORD_HERE";
const char* broker = "IP_ADDRESS_OF_MQTT_SERVER_HERE";
const char* brokerUser = "MQTT_USER";
const char* brokerPass = "MQTT_PASSWORD";
const char* inTopicFrontDriver1 = "cmnd/frontDriver1/power";
const char* stateTopicFrontDriver1 = "stat/frontDriver1/power";
const char* inTopicFrontPassenger1 = "cmnd/frontPassenger1/power";
const char* stateTopicFrontPassenger1 = "stat/frontPassenger1/power";
const char* inTopicLivingDriver1 = "cmnd/livingDriver1/power";
const char* stateTopicLivingDriver1 = "stat/livingDriver1/power";
const char* inTopicLivingPassenger1 = "cmnd/livingPassenger1/power";
const char* stateTopicLivingPassenger1 = "stat/livingPassenger1/power";
const char* inTopicLivingDriver2 = "cmnd/livingDriver2/power";
const char* stateTopicLivingDriver2 = "stat/livingDriver2/power";
const char* inTopicLivingPassenger2 = "cmnd/livingPassenger2/power";
const char* stateTopicLivingPassenger2 = "stat/livingPassenger2/power";
const char* inTopicLivingDriver3 = "cmnd/livingDriver3/power";
const char* stateTopicLivingDriver3 = "stat/livingDriver3/power";
const char* inTopicLivingPassenger3 = "cmnd/livingPassenger3/power";
const char* stateTopicLivingPassenger3 = "stat/livingPassenger3/power";
char* stateOn = "ON";
char* stateOff = "OFF";
boolean m_front_driver1_state = false;
boolean m_front_passenger1_state = false;
boolean m_living_driver1_state = false;
boolean m_living_passenger1_state = false;
boolean m_living_driver2_state = false;
boolean m_living_passenger2_state = false;
boolean m_living_driver3_state = false;
boolean m_living_passenger3_state = false;
WiFiClient espClient;
PubSubClient client(espClient);
long currentTime, lastTime;
int count = 0;
//Pins for the Relays
int relay1 = 32;
int relay2 = 33;
int relay3 = 25;
int relay4 = 26;
int relay5 = 27;
int relay6 = 14;
int relay7 = 12;
int relay8 = 13;
void reconnect() {
while (!client.connected()) {
if (debug) {
Serial.print("\nConnecting to ");
Serial.println(broker);
}
if (client.connect("brokerConnection", brokerUser, brokerPass)) {
client.subscribe(inTopicFrontDriver1);
client.subscribe(inTopicFrontPassenger1);
client.subscribe(inTopicLivingDriver1);
client.subscribe(inTopicLivingPassenger1);
client.subscribe(inTopicLivingDriver2);
client.subscribe(inTopicLivingPassenger2);
client.subscribe(inTopicLivingDriver3);
client.subscribe(inTopicLivingPassenger3);
if (debug) {
Serial.print("\nConnected to ");
Serial.println(broker);
Serial.print("topic ==>");
}
} else {
if (debug) {
Serial.print("\nTrying again ...");
}
delay(5000);
}
}
}
void setupWifi() {
delay(100);
if (debug) {
Serial.print("\nConnecting to ");
Serial.println(ssid);
}
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print("-");
}
if (debug) {
Serial.print("\nConnected!! **** ");
Serial.println(ssid);
}
}
void callback(char* topic, byte* payload, unsigned int length) {
String payloadString;
for (uint8_t i = 0; i < length; i++) {
payloadString.concat((char)payload[i]);
}
if (debug) {
Serial.print("Received message: ");
Serial.println(topic);
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
if (strcmp(topic, inTopicFrontDriver1) == 0) {
if (payloadString.equals(String(stateOn))) {
m_front_driver1_state = true;
} else if (payloadString.equals(String(stateOff))) {
m_front_driver1_state = false;
}
}
if (strcmp(topic, inTopicFrontPassenger1) == 0) {
if (payloadString.equals(String(stateOn))) {
m_front_passenger1_state = true;
} else if (payloadString.equals(String(stateOff))) {
m_front_passenger1_state = false;
}
}
if (strcmp(topic, inTopicLivingDriver1) == 0) {
if (payloadString.equals(String(stateOn))) {
m_living_driver1_state = true;
} else if (payloadString.equals(String(stateOff))) {
m_living_driver1_state = false;
}
}
if (strcmp(topic, inTopicLivingPassenger1) == 0) {
if (payloadString.equals(String(stateOn))) {
m_living_passenger1_state = true;
} else if (payloadString.equals(String(stateOff))) {
m_living_passenger1_state = false;
}
}
if (strcmp(topic, inTopicLivingDriver2) == 0) {
if (payloadString.equals(String(stateOn))) {
m_living_driver2_state = true;
} else if (payloadString.equals(String(stateOff))) {
m_living_driver2_state = false;
}
}
if (strcmp(topic, inTopicLivingPassenger2) == 0) {
if (payloadString.equals(String(stateOn))) {
m_living_passenger2_state = true;
} else if (payloadString.equals(String(stateOff))) {
m_living_passenger2_state = false;
}
}
if (strcmp(topic, inTopicLivingDriver3) == 0) {
if (payloadString.equals(String(stateOn))) {
m_living_driver3_state = true;
} else if (payloadString.equals(String(stateOff))) {
m_living_driver3_state = false;
}
}
if (strcmp(topic, inTopicLivingPassenger3) == 0) {
if (payloadString.equals(String(stateOn))) {
m_living_passenger3_state = true;
} else if (payloadString.equals(String(stateOff))) {
m_living_passenger3_state = false;
}
}
if (debug) {
Serial.print("topic ==>");
Serial.println(m_front_driver1_state);
}
setLightState();
publishLightState();
}
// function called to turn on/off the light
void setLightState() {
if (m_front_driver1_state) {
digitalWrite(relay1, LOW);
} else {
digitalWrite(relay1, HIGH);
}
if (m_front_passenger1_state) {
digitalWrite(relay2, LOW);
} else {
digitalWrite(relay2, HIGH);
}
if (m_living_driver1_state) {
digitalWrite(relay3, LOW);
} else {
digitalWrite(relay3, HIGH);
}
if (m_living_passenger1_state) {
digitalWrite(relay4, LOW);
} else {
digitalWrite(relay4, HIGH);
}
if (m_living_driver2_state) {
digitalWrite(relay5, LOW);
} else {
digitalWrite(relay5, HIGH);
}
if (m_living_passenger2_state) {
digitalWrite(relay6, LOW);
} else {
digitalWrite(relay6, HIGH);
}
if (m_living_driver3_state) {
digitalWrite(relay7, LOW);
} else {
digitalWrite(relay7, HIGH);
}
if (m_living_passenger3_state) {
digitalWrite(relay8, LOW);
} else {
digitalWrite(relay8, HIGH);
}
publishLightState();
client.loop();
}
// function called to publish the state of the light (on/off)
void publishLightState() {
if (m_front_driver1_state) {
client.publish(stateTopicFrontDriver1, stateOn, true);
} else {
client.publish(stateTopicFrontDriver1, stateOff, true);
}
if (m_front_passenger1_state) {
client.publish(stateTopicFrontPassenger1, stateOn, true);
} else {
client.publish(stateTopicFrontPassenger1, stateOff, true);
}
if (m_living_driver1_state) {
client.publish(stateTopicLivingDriver1, stateOn, true);
} else {
client.publish(stateTopicLivingDriver1, stateOff, true);
}
if (m_living_passenger1_state) {
client.publish(stateTopicLivingPassenger1, stateOn, true);
} else {
client.publish(stateTopicLivingPassenger1, stateOff, true);
}
if (m_living_driver2_state) {
client.publish(stateTopicLivingDriver2, stateOn, true);
} else {
client.publish(stateTopicLivingDriver2, stateOff, true);
}
if (m_living_passenger2_state) {
client.publish(stateTopicLivingPassenger2, stateOn, true);
} else {
client.publish(stateTopicLivingPassenger2, stateOff, true);
}
if (m_living_driver3_state) {
client.publish(stateTopicLivingDriver3, stateOn, true);
} else {
client.publish(stateTopicLivingDriver3, stateOff, true);
}
if (m_living_passenger3_state) {
client.publish(stateTopicLivingPassenger3, stateOn, true);
} else {
client.publish(stateTopicLivingPassenger3, stateOff, true);
}
client.loop();
}
void setup() {
Serial.begin(115200);
pinMode(relay1, OUTPUT);
pinMode(relay2, OUTPUT);
pinMode(relay3, OUTPUT);
pinMode(relay4, OUTPUT);
pinMode(relay5, OUTPUT);
pinMode(relay6, OUTPUT);
pinMode(relay7, OUTPUT);
pinMode(relay8, OUTPUT);
digitalWrite(relay1, HIGH);
digitalWrite(relay2, HIGH);
digitalWrite(relay3, HIGH);
digitalWrite(relay4, HIGH);
digitalWrite(relay5, HIGH);
digitalWrite(relay6, HIGH);
digitalWrite(relay7, HIGH);
digitalWrite(relay8, HIGH);
setupWifi();
client.setServer(broker, 1883);
client.setCallback(callback);
}
void loop() {
// put your main code here, to run repeatedly:
if (!client.connected()) {
reconnect();
}
client.loop();
}
0 Comments
Comments powered by Disqus