Add support for multiple light bars/remotes (#3), bump to version 0.2
This commit is contained in:
61
Lightbar.ino
61
Lightbar.ino
@ -1,24 +1,75 @@
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "constants.h"
|
||||
#include "config.h"
|
||||
#include "radio.h"
|
||||
#include "lightbar.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
Lightbar lightbar(INCOMING_SERIAL, OUTGOING_SERIAL, RADIO_PIN_CE, RADIO_PIN_CSN);
|
||||
MQTT mqtt(&lightbar, WIFI_SSID, WIFI_PASSWORD, MQTT_SERVER, MQTT_PORT, MQTT_USER, MQTT_PASSWORD, MQTT_ROOT_TOPIC, HOME_ASSISTANT_DISCOVERY, HOME_ASSISTANT_DISCOVERY_PREFIX, HOME_ASSISTANT_DEVICE_NAME);
|
||||
WiFiClient wifiClient;
|
||||
Radio radio(RADIO_PIN_CE, RADIO_PIN_CSN);
|
||||
MQTT mqtt(&wifiClient, MQTT_SERVER, MQTT_PORT, MQTT_USER, MQTT_PASSWORD, MQTT_ROOT_TOPIC, HOME_ASSISTANT_DISCOVERY, HOME_ASSISTANT_DISCOVERY_PREFIX);
|
||||
|
||||
void setupWifi()
|
||||
{
|
||||
Serial.print("[WiFi] Connecting to network \"");
|
||||
Serial.print(WIFI_SSID);
|
||||
Serial.print("\"...");
|
||||
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||
WiFi.setHostname(mqtt.getClientId().c_str());
|
||||
|
||||
uint retries = 0;
|
||||
while (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
delay(1000);
|
||||
Serial.print(".");
|
||||
retries++;
|
||||
if (retries > 60)
|
||||
ESP.restart();
|
||||
}
|
||||
Serial.println();
|
||||
Serial.println("[WiFi] connected!");
|
||||
|
||||
Serial.print("[WiFi] IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
Serial.println("##########################################");
|
||||
Serial.println("# LIGHTBAR2MQTT (Version 0.1) #");
|
||||
Serial.println("# LIGHTBAR2MQTT (Version " + constants::VERSION + ") #");
|
||||
Serial.println("# https://github.com/ebinf/lightbar2mqtt #");
|
||||
Serial.println("##########################################");
|
||||
|
||||
lightbar.setup();
|
||||
radio.setup();
|
||||
|
||||
setupWifi();
|
||||
|
||||
for (int i = 0; i < sizeof(REMOTES) / sizeof(SerialWithName); i++)
|
||||
{
|
||||
Remote *remote = new Remote(&radio, REMOTES[i].serial, REMOTES[i].name);
|
||||
mqtt.addRemote(remote);
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizeof(LIGHTBARS) / sizeof(SerialWithName); i++)
|
||||
{
|
||||
Lightbar *lightbar = new Lightbar(&radio, LIGHTBARS[i].serial, LIGHTBARS[i].name);
|
||||
mqtt.addLightbar(lightbar);
|
||||
}
|
||||
|
||||
mqtt.setup();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (!WiFi.isConnected())
|
||||
{
|
||||
Serial.println("[WiFi] connection lost!");
|
||||
setupWifi();
|
||||
}
|
||||
|
||||
mqtt.loop();
|
||||
lightbar.loop();
|
||||
radio.loop();
|
||||
}
|
21
README.md
21
README.md
@ -19,6 +19,7 @@ This project would not have been possible without the work of these amazing peop
|
||||
- The light bar is represented as a `light` entity
|
||||
- The remote is represented as a `sensor` entity
|
||||
- Actions taken on the remote also trigger `device_automation`s. This allows you to trigger automations in Home Assistant based on actions taken on the remote
|
||||
- Use multiple light bars or remotes with ease! Each one can be controlled/monitored individually and will also have separate entities in Home Assistant.
|
||||
|
||||
## Requirements
|
||||
|
||||
@ -54,14 +55,14 @@ Connect the nRF24 module to the ESP32 as follows. At least these pins work for m
|
||||
- [RF24](https://nrf24.github.io/RF24/) by TMRh20, _Version 1.4.10_
|
||||
5. Select your serial port and board. Upload the sketch to your ESP32.
|
||||
6. Inspect the serial monitor (115200 baud). If everything is set up correctly, the ESP32 should connect to your WiFi and MQTT broker.
|
||||
7. At this point, you probably don't know the serial of your remote. Just press/turn the remote and you should see the serial in the serial monitor. (E.g. `[Radio] Ignoring package with not matching serial: 0x7B7E12`) Copy it and paste it into the `config.h` file.
|
||||
7. At this point, you probably don't know the serial of your remote. Just press/turn the remote and you should see the serial in the serial monitor. (E.g. `[Radio] Ignoring package with not matching serial: 0x7B7E12`) Copy it and paste it into the `config.h` file in the "Remotes" section.
|
||||
8. Upload the sketch again.
|
||||
9. If your Home Assistant has the MQTT integration set up, the light bar should be discovered automatically.
|
||||
10. Enjoy controlling your light bar via MQTT!
|
||||
|
||||
### 3. Pairing Light bar and ESP32 (optional)
|
||||
|
||||
If you opt to use different values for `INCOMING_SERIAL` and `OUTGOING_SERIAL` in the `config.h` file, you need to pair the light bar with the ESP32. To do this, power-cycle the light bar and within 10 seconds either press the "Pair" button in Home Assistant or send a message to the pair topic (see below). The light bar should blink a few times if the pairing was successful.
|
||||
If you opt to use different values for the serial and remote in the `config.h` file, you need to pair the light bar with the ESP32. To do this, power-cycle the light bar and within 10 seconds either press the "Pair" button in Home Assistant or send a message to the pair topic (see below). The light bar should blink a few times if the pairing was successful.
|
||||
|
||||
## Usage
|
||||
|
||||
@ -76,9 +77,11 @@ All MQTT topics are prefixed with a root topic. You can set this root topic in t
|
||||
...
|
||||
```
|
||||
|
||||
In order to be able to control multiple light bars or remotes, each one has their own sub-topics, starting with the chosen serial (all lower case).
|
||||
|
||||
#### Light Bar
|
||||
|
||||
To control the light bar, you can send messages to the following topic: `<MQTT_ROOT_TOPIC>/l2m_<MAC of your ESP32>/lightbar/command` e.g. `lightbar2mqtt/l2m_1234567890AB/lightbar/command`. The payload should be a JSON object with the following keys:
|
||||
To control the light bar, you can send messages to the following topic: `<MQTT_ROOT_TOPIC>/l2m_<MAC of your ESP32>/0x<Serial of the light bar>/command` e.g. `lightbar2mqtt/l2m_1234567890AB/0xabcdef/command`. The payload should be a JSON object with the following keys:
|
||||
|
||||
- `state`: `"ON"` or `"OFF"`
|
||||
- `brightness`: `0` (off) to `15` (full brightness)
|
||||
@ -96,7 +99,7 @@ Example:
|
||||
|
||||
#### Remote
|
||||
|
||||
The remote sends its state to the following topic: `<MQTT_ROOT_TOPIC>/l2m_<MAC of your ESP32>/remote/state` e.g. `lightbar2mqtt/l2m_1234567890AB/remote/state`. The payload is a plain string with one the following values:
|
||||
The remote sends its state to the following topic: `<MQTT_ROOT_TOPIC>/l2m_<MAC of your ESP32>/0x<Serial of the remote>/state` e.g. `lightbar2mqtt/l2m_1234567890AB/0x123456/state`. The payload is a plain string with one the following values:
|
||||
|
||||
- `press`
|
||||
- `turn_clockwise`
|
||||
@ -107,7 +110,7 @@ The remote sends its state to the following topic: `<MQTT_ROOT_TOPIC>/l2m_<MAC o
|
||||
|
||||
#### Pairing
|
||||
|
||||
To pair the light bar with the ESP32, send a message to the following topic: `<MQTT_ROOT_TOPIC>/l2m_<MAC of your ESP32>/pair` e.g. `lightbar2mqtt/l2m_1234567890AB/pair`. The payload can be anything, it will be ignored.
|
||||
To pair the light bar with the ESP32, send a message to the following topic: `<MQTT_ROOT_TOPIC>/l2m_<MAC of your ESP32>/0x<Serial of the light bar>/pair` e.g. `lightbar2mqtt/l2m_1234567890AB/0xabcdef/pair`. The payload can be anything, it will be ignored.
|
||||
|
||||
Please note that the light bar needs to be power-cycled within 10 seconds _before_ sending the pairing message.
|
||||
|
||||
@ -117,21 +120,21 @@ The ESP32 sends its availability to the following topic: `<MQTT_ROOT_TOPIC>/l2m_
|
||||
|
||||
### Home Assistant
|
||||
|
||||
If your Home Assistant has the MQTT integration set up, the light bar should be discovered automatically.
|
||||
If your Home Assistant has the MQTT integration set up, the light bar(s) and remote(s) should be discovered automatically.
|
||||
|
||||
Additionally, the above mentioned events from the remote are also available as `device_automation` triggers. You can use these triggers to create automations in Home Assistant based on the actions taken on the remote. To do so, create a new automation in Home Assistant and select "Device" as the trigger type. Select the light bar entity and the desired trigger, e.g. `"press" action`.
|
||||
Additionally, the above mentioned events from the remote are also available as `device_automation` triggers. You can use these triggers to create automations in Home Assistant based on the actions taken on the remote. To do so, create a new automation in Home Assistant and select "Device" as the trigger type. Select the corresponding remote entity and the desired trigger, e.g. `"press" action`.
|
||||
|
||||
### Known Issues / Limitations
|
||||
|
||||
- The light bar does not send its state to the ESP32. This means that if you change the state of the light bar via the controller, the ESP32 will not know about it. This is a limitation of the protocol used by the light bar.
|
||||
- There is no way of knowing whether the light bar is currently on or off. Therefore this project assumes that the light bar is on when the ESP32 starts. If you turn off the light bar (e.g. via the remote), this might lead to an inverted state in Home Assistant. Just turn the device on in Home Assistant and power-cycle the light bar to fix this.
|
||||
- Sometimes actions taken on the remote are not recognized by the ESP32. When building automations in Home Assistant, don't rely on the remote events to be 100% accurate. Normally, the second or third try should work.
|
||||
- Sometimes actions taken on the remote are not recognized by the ESP32. When building automations in Home Assistant, don't rely on the remote events to be 100% accurate. Normally, the second or third try should work. It is therefore also recommended to decouple the light bar and original remote, as otherwise some actions on the remote might change the state of the light bar but not trigger anything in Home Assistant.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you find a bug or have an idea for a new feature, feel free to open an issue or create a pull request. I'm happy to see this project grow and improve!
|
||||
|
||||
I've designed the code to be easily™ extendable. For example, it should be relatively easy to add support for multiple light bars or multiple remotes.
|
||||
I've designed the code to be easily™ extendable. ~~For example, it should be relatively easy to add support for multiple light bars or multiple remotes.~~
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
#include "constants.h"
|
||||
|
||||
/* -- nRF24 --------------------------------------------------------------------------------------------------- */
|
||||
// The pin number to which the nRF24's Chip Enable (CE) pin is connected.
|
||||
#define RADIO_PIN_CE 4
|
||||
@ -5,16 +7,29 @@
|
||||
// The pin number to which the nRF24's Chip Select Null (CSN) pin is connected.
|
||||
#define RADIO_PIN_CSN 5
|
||||
|
||||
/* -- Light Bar ----------------------------------------------------------------------------------------------- */
|
||||
// The serial of the remote to listen to.
|
||||
/* -- Light Bars ---------------------------------------------------------------------------------------------- */
|
||||
// All light bars that should be controlled by this controller. Each light bar must have a unique serial.
|
||||
// Each entry consists of the serial and the name of the light bar. By default, up to 10 light bars can be added.
|
||||
//
|
||||
// If the serial is set to the same value as one remote's, the original remote will still control the light bar
|
||||
// directly. To separate the light bar from the original remote, set this to a different value, e.g. 0xABCDEF.
|
||||
//
|
||||
// The name will be used in Home Assistant.
|
||||
constexpr SerialWithName LIGHTBARS[] = {
|
||||
{0xABCDEF, "Light Bar 1"},
|
||||
};
|
||||
|
||||
/* -- Remotes ------------------------------------------------------------------------------------------------- */
|
||||
// All remotes that this controller should listen to. Each remote must have a unique serial.
|
||||
// Each entry consists of the serial and the name of the remote. By default, up to 10 remotes can be added.
|
||||
//
|
||||
// If you don't know the serial of your remote, just set this to any value and flash your controller. Once
|
||||
// the controller is running, the serial of your remote will be printed to the console.
|
||||
#define INCOMING_SERIAL 0x123456
|
||||
|
||||
// The serial, the controller should emulate.
|
||||
// If set to the same value as INCOMING_SERIAL, the original remote will still control the ligtbar directly.
|
||||
// To separate the light bar from the original remote, set this to a different value, e.g. 0xABCDEF.
|
||||
#define OUTGOING_SERIAL 0xABCDEF
|
||||
//
|
||||
// The name will be used in Home Assistant.
|
||||
constexpr SerialWithName REMOTES[] = {
|
||||
{0x123456, "Remote 1"},
|
||||
};
|
||||
|
||||
/* -- WiFi ---------------------------------------------------------------------------------------------------- */
|
||||
// The SSID of the WiFi network to connect to.
|
||||
|
32
constants.h
Normal file
32
constants.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef CONSTANTS_H
|
||||
#define CONSTANTS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace constants
|
||||
{
|
||||
// The version number of lightbar2mqtt.
|
||||
const String VERSION = "0.2";
|
||||
|
||||
// The maximum number of light bars that can be connected to the controller.
|
||||
const uint8_t MAX_LIGHTBARS = 10;
|
||||
|
||||
// The maximum number of remotes that can be connected to the controller.
|
||||
const uint8_t MAX_REMOTES = 10;
|
||||
|
||||
// The maximum number of serials, the controller will be able to save latest package ids for.
|
||||
// This should always >= MAX_REMOTES + MAX_LIGHTBARS.
|
||||
const uint8_t MAX_SERIALS = 32;
|
||||
|
||||
// The maximum number of command listeners that can be registered for a remote.
|
||||
const uint8_t MAX_COMMAND_LISTENERS = 10;
|
||||
};
|
||||
|
||||
struct SerialWithName
|
||||
{
|
||||
uint32_t serial;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
#endif
|
39
lightbar.cpp
39
lightbar.cpp
@ -1,30 +1,41 @@
|
||||
#include "lightbar.h"
|
||||
|
||||
Lightbar::Lightbar(uint32_t incoming_serial, uint32_t outgoing_serial, uint8_t ce, uint8_t csn)
|
||||
Lightbar::Lightbar(Radio *radio, uint32_t serial, const char *name)
|
||||
{
|
||||
this->radio = new Radio(ce, csn);
|
||||
this->radio->setOutgoingSerial(outgoing_serial);
|
||||
this->incoming_serial = incoming_serial;
|
||||
this->radio = radio;
|
||||
this->serial = serial;
|
||||
this->name = name;
|
||||
|
||||
this->serialString = "0x" + String(this->serial, HEX);
|
||||
}
|
||||
|
||||
Lightbar::~Lightbar()
|
||||
{
|
||||
delete this->radio;
|
||||
}
|
||||
|
||||
void Lightbar::setup()
|
||||
uint32_t Lightbar::getSerial()
|
||||
{
|
||||
this->radio->setup();
|
||||
return this->serial;
|
||||
}
|
||||
|
||||
const String Lightbar::getSerialString()
|
||||
{
|
||||
return this->serialString;
|
||||
}
|
||||
|
||||
const char *Lightbar::getName()
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
void Lightbar::sendRawCommand(Command command, byte options)
|
||||
{
|
||||
this->radio->sendCommand(command, options);
|
||||
this->radio->sendCommand(serial, command, options);
|
||||
}
|
||||
|
||||
void Lightbar::sendRawCommand(Command command)
|
||||
{
|
||||
this->radio->sendCommand(command);
|
||||
this->radio->sendCommand(serial, command);
|
||||
}
|
||||
|
||||
void Lightbar::onOff()
|
||||
@ -93,14 +104,4 @@ void Lightbar::setBrightness(uint8_t value)
|
||||
// for details.
|
||||
this->sendRawCommand(Lightbar::Command::DIMMER, 0x0 - 16);
|
||||
this->sendRawCommand(Lightbar::Command::BRIGHTER, (byte)value);
|
||||
}
|
||||
|
||||
void Lightbar::loop()
|
||||
{
|
||||
this->radio->loop();
|
||||
}
|
||||
|
||||
bool Lightbar::registerCommandListener(std::function<void(uint32_t, byte, byte)> callback)
|
||||
{
|
||||
return this->radio->addIncomingSerial(this->incoming_serial, callback);
|
||||
}
|
18
lightbar.h
18
lightbar.h
@ -1,14 +1,16 @@
|
||||
#ifndef LIGHTBAR_H
|
||||
#define LIGHTBAR_H
|
||||
|
||||
#include "radio.h";
|
||||
#include "radio.h"
|
||||
|
||||
class Lightbar
|
||||
{
|
||||
public:
|
||||
Lightbar(uint32_t incoming_serial, uint32_t outgoing_serial, uint8_t ce, uint8_t csn);
|
||||
Lightbar(Radio *radio, uint32_t serial, const char *name);
|
||||
~Lightbar();
|
||||
void setup();
|
||||
uint32_t getSerial();
|
||||
const String getSerialString();
|
||||
const char *getName();
|
||||
|
||||
enum Command
|
||||
{
|
||||
@ -34,16 +36,12 @@ public:
|
||||
void setMiredTemperature(uint mireds);
|
||||
void setBrightness(uint8_t value);
|
||||
|
||||
void loop();
|
||||
|
||||
bool registerCommandListener(std::function<void(uint32_t, byte, byte)> callback);
|
||||
|
||||
static void callback(uint32_t serial, byte command, byte options);
|
||||
|
||||
private:
|
||||
Radio *radio;
|
||||
bool onState = false;
|
||||
uint32_t incoming_serial;
|
||||
uint32_t serial;
|
||||
String serialString;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
#endif
|
386
mqtt.cpp
386
mqtt.cpp
@ -1,12 +1,10 @@
|
||||
#include "mqtt.h"
|
||||
|
||||
#include <Arduino_JSON.h>
|
||||
#include <esp_mac.h>
|
||||
|
||||
MQTT::MQTT(Lightbar *lightbar, const char *wifiSsid, const char *wifiPassword, const char *mqttServer, int mqttPort, const char *mqttUser, const char *mqttPassword, const char *mqttRootTopic, bool homeAssistantAutoDiscovery, const char *homeAssistantAutoDiscoveryPrefix, const char *homeAssistantDeviceName)
|
||||
#include "mqtt.h"
|
||||
|
||||
MQTT::MQTT(WiFiClient *wifiClient, const char *mqttServer, int mqttPort, const char *mqttUser, const char *mqttPassword, const char *mqttRootTopic, bool homeAssistantAutoDiscovery, const char *homeAssistantAutoDiscoveryPrefix)
|
||||
{
|
||||
this->wifiSsid = wifiSsid;
|
||||
this->wifiPassword = wifiPassword;
|
||||
this->mqttServer = mqttServer;
|
||||
this->mqttPort = mqttPort;
|
||||
this->mqttUser = mqttUser;
|
||||
@ -14,12 +12,10 @@ MQTT::MQTT(Lightbar *lightbar, const char *wifiSsid, const char *wifiPassword, c
|
||||
this->mqttRootTopic = String(mqttRootTopic);
|
||||
this->homeAssistantDiscovery = homeAssistantAutoDiscovery;
|
||||
this->homeAssistantDiscoveryPrefix = String(homeAssistantAutoDiscoveryPrefix);
|
||||
this->homeAssistantDeviceName = String(homeAssistantDeviceName);
|
||||
|
||||
this->wifiClient = new WiFiClient();
|
||||
this->client = new PubSubClient(*this->wifiClient);
|
||||
this->lightbar = lightbar;
|
||||
this->lightbar->registerCommandListener(std::bind(&MQTT::sendAction, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
this->remoteCommandHandler = std::bind(&MQTT::sendAction, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
|
||||
this->client = new PubSubClient(*wifiClient);
|
||||
|
||||
String mac = "";
|
||||
unsigned char mac_base[6] = {0};
|
||||
@ -30,12 +26,22 @@ MQTT::MQTT(Lightbar *lightbar, const char *wifiSsid, const char *wifiPassword, c
|
||||
mac = buffer;
|
||||
}
|
||||
this->clientId = "l2m_" + mac;
|
||||
this->combinedRootTopic = this->mqttRootTopic + "/" + this->clientId;
|
||||
}
|
||||
|
||||
MQTT::~MQTT()
|
||||
{
|
||||
delete this->client;
|
||||
delete this->wifiClient;
|
||||
}
|
||||
|
||||
const String MQTT::getCombinedRootTopic()
|
||||
{
|
||||
return this->combinedRootTopic;
|
||||
}
|
||||
|
||||
const String MQTT::getClientId()
|
||||
{
|
||||
return this->clientId;
|
||||
}
|
||||
|
||||
void MQTT::onMessage(char *topic, byte *payload, unsigned int length)
|
||||
@ -49,93 +55,40 @@ void MQTT::onMessage(char *topic, byte *payload, unsigned int length)
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
if (!strcmp(topic, String(this->mqttRootTopic + "/" + this->clientId + "/pair").c_str()))
|
||||
{
|
||||
this->lightbar->pair();
|
||||
return;
|
||||
}
|
||||
|
||||
JSONVar command = JSON.parse(String(payload, length));
|
||||
|
||||
if (JSON.typeof(command) != "object")
|
||||
return;
|
||||
|
||||
if (command.hasOwnProperty("state"))
|
||||
Lightbar *lightbar = nullptr;
|
||||
for (int i = 0; i < this->lightbarCount; i++)
|
||||
{
|
||||
const char *state = command["state"];
|
||||
this->lightbar->setOnOff(strcmp(state, "ON"));
|
||||
}
|
||||
|
||||
if (command.hasOwnProperty("brightness"))
|
||||
{
|
||||
this->lightbar->setBrightness((uint8_t)command["brightness"]);
|
||||
}
|
||||
|
||||
if (command.hasOwnProperty("color_temp"))
|
||||
{
|
||||
this->lightbar->setMiredTemperature((uint)command["color_temp"]);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTT::setupWifi()
|
||||
{
|
||||
Serial.print("[WiFi] Connecting to network \"");
|
||||
Serial.print(this->wifiSsid);
|
||||
Serial.print("\"...");
|
||||
|
||||
WiFi.begin(this->wifiSsid, this->wifiPassword);
|
||||
WiFi.setHostname(this->clientId.c_str());
|
||||
|
||||
uint retries = 0;
|
||||
while (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
delay(1000);
|
||||
Serial.print(".");
|
||||
retries++;
|
||||
if (retries > 60)
|
||||
ESP.restart();
|
||||
}
|
||||
Serial.println();
|
||||
Serial.println("[WiFi] connected!");
|
||||
|
||||
Serial.print("[WiFi] IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
void MQTT::setupMqtt()
|
||||
{
|
||||
this->client->setServer(this->mqttServer, this->mqttPort);
|
||||
this->client->setCallback(std::bind(&MQTT::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
|
||||
while (!this->client->connected())
|
||||
{
|
||||
Serial.println("[MQTT] Connecting to MQTT broker...");
|
||||
uint retries = 0;
|
||||
if (this->client->connect(this->clientId.c_str(), this->mqttUser, this->mqttPassword, String(this->mqttRootTopic + "/" + this->clientId + "/availability").c_str(), 1, true, "offline"))
|
||||
lightbar = this->lightbars[i];
|
||||
if (!strcmp(topic, String(this->getCombinedRootTopic() + "/" + lightbar->getSerialString() + "/pair").c_str()))
|
||||
{
|
||||
Serial.println("[MQTT] connected!");
|
||||
this->client->publish(String(this->mqttRootTopic + "/" + this->clientId + "/availability").c_str(), "online", true);
|
||||
this->client->subscribe(String(this->mqttRootTopic + "/" + this->clientId + "/lightbar/command").c_str());
|
||||
this->client->subscribe(String(this->mqttRootTopic + "/" + this->clientId + "/pair").c_str());
|
||||
lightbar->pair();
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
if (strcmp(topic, String(this->getCombinedRootTopic() + "/" + lightbar->getSerialString() + "/command").c_str()))
|
||||
continue;
|
||||
|
||||
if (JSON.typeof(command) != "object")
|
||||
continue;
|
||||
|
||||
if (command.hasOwnProperty("state"))
|
||||
{
|
||||
Serial.print("[MQTT] Connection failed! rc=");
|
||||
Serial.print(this->client->state());
|
||||
Serial.println(" try again in 1 second");
|
||||
while (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
this->setupWifi();
|
||||
}
|
||||
delay(1000);
|
||||
retries++;
|
||||
if (retries > 60)
|
||||
ESP.restart();
|
||||
const char *state = command["state"];
|
||||
lightbar->setOnOff(strcmp(state, "ON"));
|
||||
}
|
||||
|
||||
if (command.hasOwnProperty("brightness"))
|
||||
{
|
||||
lightbar->setBrightness((uint8_t)command["brightness"]);
|
||||
}
|
||||
|
||||
if (command.hasOwnProperty("color_temp"))
|
||||
{
|
||||
lightbar->setMiredTemperature((uint)command["color_temp"]);
|
||||
}
|
||||
}
|
||||
|
||||
if (homeAssistantDiscovery)
|
||||
this->sendHomeAssistantDiscoveryMessages();
|
||||
}
|
||||
|
||||
void MQTT::setup()
|
||||
@ -143,33 +96,156 @@ void MQTT::setup()
|
||||
Serial.print("[MQTT] Device ID: ");
|
||||
Serial.println(this->clientId);
|
||||
Serial.print("[MQTT] Root Topic: ");
|
||||
Serial.println(this->mqttRootTopic + "/" + this->clientId);
|
||||
Serial.println(this->getCombinedRootTopic());
|
||||
|
||||
this->setupWifi();
|
||||
this->setupMqtt();
|
||||
this->client->setServer(this->mqttServer, this->mqttPort);
|
||||
this->client->setCallback(std::bind(&MQTT::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
|
||||
while (!this->client->connected())
|
||||
{
|
||||
Serial.println("[MQTT] Connecting to MQTT broker...");
|
||||
uint retries = 0;
|
||||
if (this->client->connect(this->clientId.c_str(), this->mqttUser, this->mqttPassword, String(this->getCombinedRootTopic() + "/availability").c_str(), 1, true, "offline"))
|
||||
{
|
||||
Serial.println("[MQTT] connected!");
|
||||
this->client->publish(String(this->getCombinedRootTopic() + "/availability").c_str(), "online", true);
|
||||
this->client->subscribe(String(this->getCombinedRootTopic() + "/+/command").c_str());
|
||||
this->client->subscribe(String(this->getCombinedRootTopic() + "/+/pair").c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print("[MQTT] Connection failed! rc=");
|
||||
Serial.print(this->client->state());
|
||||
Serial.println(" try again in 1 second");
|
||||
delay(1000);
|
||||
retries++;
|
||||
if (retries > 60)
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
this->sendAllHomeAssistantDiscoveryMessages();
|
||||
}
|
||||
|
||||
void MQTT::sendHomeAssistantDiscoveryMessages()
|
||||
bool MQTT::addLightbar(Lightbar *lightbar)
|
||||
{
|
||||
if (this->lightbarCount >= constants::MAX_LIGHTBARS)
|
||||
{
|
||||
Serial.println("[MQTT] Could not add light bar, because too many light bars are saved!");
|
||||
Serial.println("[MQTT] Please check if you actually want to save more than " + String(constants::MAX_LIGHTBARS, DEC) + " light bars.");
|
||||
Serial.println("[MQTT] If you do, increase MAX_LIGHTBARS in constants.h and recompile.");
|
||||
return false;
|
||||
}
|
||||
this->lightbars[this->lightbarCount] = lightbar;
|
||||
this->lightbarCount++;
|
||||
this->sendHomeAssistantLightbarDiscoveryMessages(lightbar);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MQTT::removeLightbar(Lightbar *lightbar)
|
||||
{
|
||||
for (int i = 0; i < this->lightbarCount; i++)
|
||||
{
|
||||
if (this->lightbars[i] == lightbar)
|
||||
{
|
||||
for (int j = i; j < this->lightbarCount - 1; j++)
|
||||
{
|
||||
this->lightbars[j] = this->lightbars[j + 1];
|
||||
}
|
||||
this->lightbarCount--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MQTT::addRemote(Remote *remote)
|
||||
{
|
||||
if (this->remoteCount >= constants::MAX_REMOTES)
|
||||
{
|
||||
Serial.println("[MQTT] Could not add remote, because too many remotes are saved!");
|
||||
Serial.println("[MQTT] Please check if you actually want to save more than " + String(constants::MAX_REMOTES, DEC) + " remotes.");
|
||||
Serial.println("[MQTT] If you do, increase MAX_REMOTES in constants.h and recompile.");
|
||||
return false;
|
||||
}
|
||||
this->remotes[this->remoteCount] = remote;
|
||||
this->remoteCount++;
|
||||
remote->registerCommandListener(this->remoteCommandHandler);
|
||||
this->sendHomeAssistantRemoteDiscoveryMessages(remote);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MQTT::removeRemote(Remote *remote)
|
||||
{
|
||||
for (int i = 0; i < this->remoteCount; i++)
|
||||
{
|
||||
if (this->remotes[i] == remote)
|
||||
{
|
||||
this->remotes[i]->registerCommandListener(this->remoteCommandHandler);
|
||||
for (int j = i; j < this->remoteCount - 1; j++)
|
||||
{
|
||||
this->remotes[j] = this->remotes[j + 1];
|
||||
}
|
||||
this->remoteCount--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MQTT::sendAllHomeAssistantDiscoveryMessages()
|
||||
{
|
||||
if (!this->homeAssistantDiscovery)
|
||||
return;
|
||||
for (int i = 0; i < this->lightbarCount; i++)
|
||||
{
|
||||
this->sendHomeAssistantLightbarDiscoveryMessages(this->lightbars[i]);
|
||||
}
|
||||
for (int i = 0; i < this->remoteCount; i++)
|
||||
{
|
||||
this->sendHomeAssistantRemoteDiscoveryMessages(this->remotes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTT::sendHomeAssistantLightbarDiscoveryMessages(Lightbar *lightbar)
|
||||
{
|
||||
if (!this->homeAssistantDiscovery)
|
||||
return;
|
||||
|
||||
Serial.print("[MQTT] Sending lightbar discovery messages for ");
|
||||
Serial.println(lightbar->getSerialString());
|
||||
|
||||
const String topicClient = this->clientId + "_" + lightbar->getSerialString();
|
||||
const String baseConfig = R"json(
|
||||
"schema": "json",
|
||||
"o": {
|
||||
"name": "lightbar2mqtt",
|
||||
"sw_version": "0.1",
|
||||
"sw_version": ")json" +
|
||||
constants::VERSION +
|
||||
R"json(",
|
||||
"support_url": "https://github.com/ebinf/lightbar2mqtt"
|
||||
},
|
||||
"~": ")json" + this->mqttRootTopic +
|
||||
"~": ")json" + this->getCombinedRootTopic() +
|
||||
"/" +
|
||||
this->clientId +
|
||||
lightbar->getSerialString() +
|
||||
R"json(",
|
||||
"availability_topic": "~/availability",
|
||||
"dev": {
|
||||
"ids": ")json" + this->clientId +
|
||||
"availability_topic": ")json" +
|
||||
this->getCombinedRootTopic() + R"json(/availability",
|
||||
"dev":
|
||||
{
|
||||
"ids" : ")json" + topicClient +
|
||||
R"json(",
|
||||
"name": ")json" + homeAssistantDeviceName +
|
||||
"name": ")json" +
|
||||
lightbar->getName() +
|
||||
R"json(",
|
||||
"mdl": "MJGJD01YL",
|
||||
"mf": "Xiaomi"
|
||||
"mdl": "Mi Computer Monitor Light Bar (MJGJD01YL)",
|
||||
"mf": "Xiaomi",
|
||||
"sw": "lightbar2mqtt )json" +
|
||||
constants::VERSION +
|
||||
R"json(",
|
||||
"sn": ")json" +
|
||||
lightbar->getSerialString() +
|
||||
R"json("
|
||||
},)json";
|
||||
|
||||
String rendevous_str = "{" +
|
||||
@ -181,30 +257,16 @@ void MQTT::sendHomeAssistantDiscoveryMessages()
|
||||
"brightness": true,
|
||||
"brightness_scale": 15,
|
||||
"name": "Light bar",
|
||||
"cmd_t": "~/lightbar/command",
|
||||
"uniq_id": ")json" + this->clientId +
|
||||
"cmd_t": "~/command",
|
||||
"uniq_id": ")json" + topicClient +
|
||||
R"json(_lightbar",
|
||||
"max_mireds": 370,
|
||||
"min_mireds":153,
|
||||
"p": "light"
|
||||
"p": "light",
|
||||
"icon": "mdi:wall-sconce-flat"
|
||||
)json" + "}";
|
||||
|
||||
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/light/" + this->clientId + "/lightbar/config").c_str(), rendevous_str.length(), true);
|
||||
this->client->print(rendevous_str);
|
||||
this->client->endPublish();
|
||||
|
||||
rendevous_str = "{" +
|
||||
baseConfig +
|
||||
R"json(
|
||||
"name": "Remote",
|
||||
"state_topic": "~/remote/state",
|
||||
"uniq_id": ")json" +
|
||||
this->clientId + R"json(_remote",
|
||||
"value_template": "{{ value }}",
|
||||
"p": "sensor"
|
||||
)json" + "}";
|
||||
|
||||
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/sensor/" + this->clientId + "/remote/config").c_str(), rendevous_str.length(), true);
|
||||
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/light/" + topicClient + "/lightbar/config").c_str(), rendevous_str.length(), true);
|
||||
this->client->print(rendevous_str);
|
||||
this->client->endPublish();
|
||||
|
||||
@ -214,10 +276,66 @@ void MQTT::sendHomeAssistantDiscoveryMessages()
|
||||
"name": "Pair",
|
||||
"cmd_t": "~/pair",
|
||||
"uniq_id": ")json" +
|
||||
this->clientId + R"json(_pair",
|
||||
topicClient + R"json(_pair",
|
||||
"p": "button"
|
||||
)json" + "}";
|
||||
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/button/" + this->clientId + "/pair/config").c_str(), rendevous_str.length(), true);
|
||||
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/button/" + topicClient + "/pair/config").c_str(), rendevous_str.length(), true);
|
||||
this->client->print(rendevous_str);
|
||||
this->client->endPublish();
|
||||
}
|
||||
|
||||
void MQTT::sendHomeAssistantRemoteDiscoveryMessages(Remote *remote)
|
||||
{
|
||||
if (!this->homeAssistantDiscovery)
|
||||
return;
|
||||
|
||||
Serial.print("[MQTT] Sending remote discovery messages for ");
|
||||
Serial.println(remote->getSerialString());
|
||||
|
||||
const String topicClient = this->clientId + "_" + remote->getSerialString();
|
||||
const String baseConfig = R"json(
|
||||
"schema": "json",
|
||||
"o": {
|
||||
"name": "lightbar2mqtt",
|
||||
"sw_version": ")json" +
|
||||
constants::VERSION +
|
||||
R"json(",
|
||||
"support_url": "https://github.com/ebinf/lightbar2mqtt"
|
||||
},
|
||||
"~": ")json" + this->getCombinedRootTopic() +
|
||||
"/" +
|
||||
remote->getSerialString() +
|
||||
R"json(",
|
||||
"availability_topic": ")json" +
|
||||
this->getCombinedRootTopic() + R"json(/availability",
|
||||
"dev": {
|
||||
"ids": ")json" + topicClient +
|
||||
R"json(",
|
||||
"name": ")json" + remote->getName() +
|
||||
R"json(",
|
||||
"mdl": "Mi Computer Monitor Light Bar Remote Control (MJGJD01YL)",
|
||||
"mf": "Xiaomi",
|
||||
"sw": "lightbar2mqtt )json" +
|
||||
constants::VERSION +
|
||||
R"json(",
|
||||
"sn": ")json" + remote->getSerialString() +
|
||||
R"json("
|
||||
},)json";
|
||||
|
||||
String rendevous_str = "{" +
|
||||
baseConfig +
|
||||
R"json(
|
||||
"name": "Remote",
|
||||
"state_topic": "~/state",
|
||||
"uniq_id": ")json" +
|
||||
topicClient + R"json(_remote",
|
||||
"value_template": "{{ value }}",
|
||||
"enabled_by_default": true,
|
||||
"entity_category": "diagnostic",
|
||||
"icon": "mdi:gesture-double-tap"
|
||||
)json" + "}";
|
||||
|
||||
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/sensor/" + topicClient + "/remote/config").c_str(), rendevous_str.length(), true);
|
||||
this->client->print(rendevous_str);
|
||||
this->client->endPublish();
|
||||
|
||||
@ -241,11 +359,11 @@ void MQTT::sendHomeAssistantDiscoveryMessages()
|
||||
"subtype": ")json" + cmd +
|
||||
R"json(",
|
||||
"type": "action",
|
||||
"topic": "~/remote/state",
|
||||
"topic": "~/state",
|
||||
"p": "device_automation"
|
||||
)json" + "}";
|
||||
|
||||
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/device_automation/" + this->clientId + "/action_" + cmd + "/config").c_str(), rendevous_str.length(), true);
|
||||
this->client->beginPublish(String(homeAssistantDiscoveryPrefix + "/device_automation/" + topicClient + "/action_" + cmd + "/config").c_str(), rendevous_str.length(), true);
|
||||
this->client->print(rendevous_str);
|
||||
this->client->endPublish();
|
||||
}
|
||||
@ -256,14 +374,14 @@ void MQTT::loop()
|
||||
if (!this->client->connected())
|
||||
{
|
||||
Serial.println("[MQTT] connection lost!");
|
||||
this->setupMqtt();
|
||||
this->setup();
|
||||
}
|
||||
this->client->loop();
|
||||
}
|
||||
|
||||
void MQTT::sendAction(uint32_t serial, byte command, byte options)
|
||||
void MQTT::sendAction(Remote *remote, byte command, byte options)
|
||||
{
|
||||
const char *action;
|
||||
String action;
|
||||
switch ((uint8_t)command)
|
||||
{
|
||||
case Lightbar::Command::ON_OFF:
|
||||
@ -293,5 +411,13 @@ void MQTT::sendAction(uint32_t serial, byte command, byte options)
|
||||
default:
|
||||
return;
|
||||
}
|
||||
this->client->publish(String(this->mqttRootTopic + "/" + this->clientId + "/remote/state").c_str(), action);
|
||||
|
||||
String topic = String(this->getCombinedRootTopic() + "/" + remote->getSerialString() + "/state");
|
||||
Serial.print("[MQTT] Sending message (");
|
||||
Serial.print(topic);
|
||||
Serial.print("): ");
|
||||
Serial.println(action);
|
||||
this->client->publish(topic.c_str(), action.c_str());
|
||||
delay(200);
|
||||
this->client->publish(topic.c_str(), NULL);
|
||||
}
|
33
mqtt.h
33
mqtt.h
@ -1,27 +1,40 @@
|
||||
#include <PubSubClient.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "constants.h"
|
||||
#include "lightbar.h"
|
||||
#include "remote.h"
|
||||
|
||||
#ifndef MQTT_H
|
||||
#define MQTT_H
|
||||
|
||||
class Remote;
|
||||
class Lightbar;
|
||||
|
||||
class MQTT
|
||||
{
|
||||
public:
|
||||
MQTT(Lightbar *lightbar, const char *wifiSsid, const char *wifiPassword, const char *mqttServer, int mqttPort, const char *mqttUser, const char *mqttPassword, const char *mqttRootTopic, bool homeAssistantAutoDiscovery, const char *homeAssistantAutoDiscoveryPrefix, const char *homeAssistantDeviceName);
|
||||
MQTT(WiFiClient *wifiClient, const char *mqttServer, int mqttPort, const char *mqttUser, const char *mqttPassword, const char *mqttRootTopic, bool homeAssistantAutoDiscovery, const char *homeAssistantAutoDiscoveryPrefix);
|
||||
~MQTT();
|
||||
void setup();
|
||||
void loop();
|
||||
bool addLightbar(Lightbar *lightbar);
|
||||
bool removeLightbar(Lightbar *lightbar);
|
||||
bool addRemote(Remote *remote);
|
||||
bool removeRemote(Remote *remote);
|
||||
void onMessage(char *topic, byte *payload, unsigned int length);
|
||||
void sendAction(uint32_t serial, byte command, byte options);
|
||||
void sendAction(Remote *remote, byte command, byte options);
|
||||
const String getCombinedRootTopic();
|
||||
const String getClientId();
|
||||
|
||||
private:
|
||||
WiFiClient *wifiClient;
|
||||
PubSubClient *client;
|
||||
String clientId;
|
||||
Lightbar *lightbar;
|
||||
const char *wifiSsid;
|
||||
const char *wifiPassword;
|
||||
Lightbar *lightbars[constants::MAX_LIGHTBARS];
|
||||
int lightbarCount = 0;
|
||||
Remote *remotes[constants::MAX_REMOTES];
|
||||
int remoteCount = 0;
|
||||
const char *mqttServer;
|
||||
int mqttPort = 1883;
|
||||
const char *mqttUser = "";
|
||||
@ -29,11 +42,13 @@ private:
|
||||
String mqttRootTopic = "lightbar2mqtt";
|
||||
bool homeAssistantDiscovery = true;
|
||||
String homeAssistantDiscoveryPrefix = "homeassistant";
|
||||
String homeAssistantDeviceName = "Mi Computer Monitor Light Bar";
|
||||
|
||||
void setupWifi();
|
||||
void setupMqtt();
|
||||
void sendHomeAssistantDiscoveryMessages();
|
||||
String combinedRootTopic;
|
||||
std::function<void(Remote *, byte, byte)> remoteCommandHandler;
|
||||
|
||||
void sendAllHomeAssistantDiscoveryMessages();
|
||||
void sendHomeAssistantLightbarDiscoveryMessages(Lightbar *lightbar);
|
||||
void sendHomeAssistantRemoteDiscoveryMessages(Remote *remote);
|
||||
};
|
||||
|
||||
#endif
|
149
radio.cpp
149
radio.cpp
@ -21,27 +21,52 @@ Radio::~Radio()
|
||||
this->radio.powerDown();
|
||||
}
|
||||
|
||||
void Radio::setOutgoingSerial(uint32_t serial)
|
||||
bool Radio::addRemote(Remote *remote)
|
||||
{
|
||||
this->outgoing_serial = serial;
|
||||
}
|
||||
|
||||
bool Radio::addIncomingSerial(uint32_t serial, std::function<void(uint32_t, byte, byte)> callback)
|
||||
{
|
||||
if (this->num_remotes >= MAX_REMOTES)
|
||||
if (this->num_remotes >= constants::MAX_REMOTES)
|
||||
{
|
||||
Serial.println("[Radio] Could not add remote, because too many remotes are saved!");
|
||||
Serial.println("[Radio] Please check if you actually want to save more than " + String(constants::MAX_REMOTES, DEC) + " remotes.");
|
||||
Serial.println("[Radio] If you do, increase MAX_REMOTES in constants.h and recompile.");
|
||||
return false;
|
||||
this->remotes[this->num_remotes].serial = serial;
|
||||
this->remotes[this->num_remotes].last_received_package_id = 0;
|
||||
this->remotes[this->num_remotes].callback = callback;
|
||||
}
|
||||
if (this->num_package_ids >= constants::MAX_SERIALS)
|
||||
{
|
||||
Serial.println("[Radio] Could not add remote, because too many serials are saved!");
|
||||
Serial.println("[Radio] Please check if you actually want to save more than " + String(constants::MAX_SERIALS, DEC) + " serials.");
|
||||
Serial.println("[Radio] If you do, increase MAX_SERIALS in constants.h and recompile.");
|
||||
return false;
|
||||
}
|
||||
this->remotes[this->num_remotes] = remote;
|
||||
this->num_remotes++;
|
||||
this->package_ids[this->num_package_ids].serial = remote->getSerial();
|
||||
this->package_ids[this->num_package_ids].package_id = 0;
|
||||
this->num_package_ids++;
|
||||
Serial.print("[Radio] Remote ");
|
||||
Serial.print(remote->getSerialString());
|
||||
Serial.println(" added!");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Radio::removeIncomingSerial(uint32_t serial)
|
||||
bool Radio::removeRemote(Remote *remote)
|
||||
{
|
||||
for (int i = 0; i < this->num_remotes; i++)
|
||||
{
|
||||
if (this->remotes[i].serial == serial)
|
||||
if (this->remotes[i] == remote)
|
||||
{
|
||||
for (int j = 0; j < this->num_package_ids; j++)
|
||||
{
|
||||
if (this->package_ids[j].serial == remote->getSerial())
|
||||
{
|
||||
for (int k = j; k < this->num_package_ids - 1; k++)
|
||||
{
|
||||
this->package_ids[k] = this->package_ids[k + 1];
|
||||
}
|
||||
this->num_package_ids--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = i; j < this->num_remotes - 1; j++)
|
||||
{
|
||||
this->remotes[j] = this->remotes[j + 1];
|
||||
@ -53,15 +78,39 @@ bool Radio::removeIncomingSerial(uint32_t serial)
|
||||
return false;
|
||||
}
|
||||
|
||||
void Radio::sendCommand(byte command, byte options)
|
||||
void Radio::sendCommand(uint32_t serial, byte command, byte options)
|
||||
{
|
||||
PackageIdForSerial *package_id = nullptr;
|
||||
for (int i = 0; i < this->num_package_ids; i++)
|
||||
{
|
||||
if (this->package_ids[i].serial == serial)
|
||||
{
|
||||
package_id = &this->package_ids[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (package_id == nullptr)
|
||||
{
|
||||
if (this->num_package_ids >= constants::MAX_SERIALS)
|
||||
{
|
||||
Serial.println("[Radio] Could not send command, because too many serials are saved!");
|
||||
Serial.println("[Radio] Please check if you actually want to save more than " + String(constants::MAX_SERIALS, DEC) + " serials.");
|
||||
Serial.println("[Radio] If you do, increase MAX_SERIALS in constants.h and recompile.");
|
||||
return;
|
||||
}
|
||||
package_id = &this->package_ids[this->num_package_ids];
|
||||
package_id->serial = serial;
|
||||
package_id->package_id = 0;
|
||||
this->num_package_ids++;
|
||||
}
|
||||
|
||||
byte data[17] = {0};
|
||||
memcpy(data, Radio::preamble, sizeof(Radio::preamble));
|
||||
data[8] = (this->outgoing_serial & 0xFF0000) >> 16;
|
||||
data[9] = (this->outgoing_serial & 0x00FF00) >> 8;
|
||||
data[10] = this->outgoing_serial & 0x0000FF;
|
||||
data[8] = (serial & 0xFF0000) >> 16;
|
||||
data[9] = (serial & 0x00FF00) >> 8;
|
||||
data[10] = serial & 0x0000FF;
|
||||
data[11] = 0xFF;
|
||||
data[12] = this->last_sent_package_id;
|
||||
data[12] = ++package_id->package_id;
|
||||
data[13] = command;
|
||||
data[14] = options;
|
||||
|
||||
@ -71,8 +120,6 @@ void Radio::sendCommand(byte command, byte options)
|
||||
data[15] = (checksum & 0xFF00) >> 8;
|
||||
data[16] = checksum & 0x00FF;
|
||||
|
||||
this->last_sent_package_id += 1;
|
||||
|
||||
Serial.print("[Radio] Sending command: 0x");
|
||||
for (int i = 0; i < 17; i++)
|
||||
{
|
||||
@ -89,9 +136,9 @@ void Radio::sendCommand(byte command, byte options)
|
||||
this->radio.startListening();
|
||||
}
|
||||
|
||||
void Radio::sendCommand(byte command)
|
||||
void Radio::sendCommand(uint32_t serial, byte command)
|
||||
{
|
||||
return this->sendCommand(command, 0x0);
|
||||
return this->sendCommand(serial, command, 0x0);
|
||||
}
|
||||
|
||||
void Radio::setup()
|
||||
@ -135,10 +182,12 @@ void Radio::loop()
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
// Only continue if there is a package available.
|
||||
if (!this->radio.available())
|
||||
return;
|
||||
if (this->radio.available())
|
||||
this->handlePackage();
|
||||
}
|
||||
|
||||
void Radio::handlePackage()
|
||||
{
|
||||
// Read raw data, append a 5 and shift it. See
|
||||
// https://github.com/lamperez/xiaomi-lightbar-nrf24?tab=readme-ov-file#baseband-packet-format
|
||||
// on why that is necessary.
|
||||
@ -169,33 +218,51 @@ void Radio::loop()
|
||||
}
|
||||
|
||||
// Check if package is coming from a observed remote.
|
||||
bool found = false;
|
||||
Remote *remote = nullptr;
|
||||
uint32_t serial = data[8] << 16 | data[9] << 8 | data[10];
|
||||
for (int i = 0; i < this->num_remotes; i++)
|
||||
{
|
||||
Remote remote = this->remotes[i];
|
||||
if (serial != remote.serial)
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
|
||||
// Make sure the same package was not handled before.
|
||||
uint8_t package_id = data[12];
|
||||
if (package_id <= remote.last_received_package_id && package_id > remote.last_received_package_id - 64)
|
||||
if (serial == this->remotes[i]->getSerial())
|
||||
{
|
||||
Serial.println("[Radio] Ignoring package with too low package number!");
|
||||
continue;
|
||||
remote = this->remotes[i];
|
||||
break;
|
||||
}
|
||||
remote.last_received_package_id = package_id;
|
||||
|
||||
Serial.println("[Radio] Package received!");
|
||||
remote.callback(remote.serial, data[13], data[14]);
|
||||
}
|
||||
|
||||
if (!found)
|
||||
if (remote == nullptr)
|
||||
{
|
||||
Serial.print("[Radio] Ignoring package with not matching serial: 0x");
|
||||
Serial.print("[Radio] Ignoring package with unknown serial: 0x");
|
||||
Serial.print(serial, HEX);
|
||||
Serial.println("");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the same package was not handled before.
|
||||
uint8_t package_id = data[12];
|
||||
PackageIdForSerial *package_id_for_serial = nullptr;
|
||||
for (int i = 0; i < this->num_package_ids; i++)
|
||||
{
|
||||
if (this->package_ids[i].serial == serial)
|
||||
{
|
||||
package_id_for_serial = &this->package_ids[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (package_id_for_serial == nullptr)
|
||||
{
|
||||
Serial.print("[Radio] Could not find latest package id for serial 0x");
|
||||
Serial.print(serial, HEX);
|
||||
Serial.println("!");
|
||||
return;
|
||||
}
|
||||
if (package_id <= package_id_for_serial->serial && package_id > package_id_for_serial->serial - 64)
|
||||
{
|
||||
Serial.println("[Radio] Ignoring package with too low package number!");
|
||||
return;
|
||||
}
|
||||
package_id_for_serial->package_id = package_id;
|
||||
|
||||
Serial.println("[Radio] Package received!");
|
||||
remote->callback(data[13], data[14]);
|
||||
}
|
27
radio.h
27
radio.h
@ -4,13 +4,15 @@
|
||||
#include <RF24.h>
|
||||
#include <CRC16.h>
|
||||
|
||||
#define MAX_REMOTES 10
|
||||
#include "constants.h"
|
||||
#include "remote.h"
|
||||
|
||||
struct Remote
|
||||
class Remote;
|
||||
|
||||
struct PackageIdForSerial
|
||||
{
|
||||
uint32_t serial;
|
||||
uint8_t last_received_package_id;
|
||||
std::function<void(uint32_t, byte command, byte options)> callback;
|
||||
uint8_t package_id;
|
||||
};
|
||||
|
||||
class Radio
|
||||
@ -19,19 +21,18 @@ public:
|
||||
Radio(uint8_t ce, uint8_t csn);
|
||||
~Radio();
|
||||
void setup();
|
||||
void sendCommand(byte command, byte options);
|
||||
void sendCommand(byte command);
|
||||
void sendCommand(uint32_t serial, byte command, byte options);
|
||||
void sendCommand(uint32_t serial, byte command);
|
||||
void loop();
|
||||
void setOutgoingSerial(uint32_t serial);
|
||||
bool addIncomingSerial(uint32_t serial, std::function<void(uint32_t, byte, byte)> callback);
|
||||
bool removeIncomingSerial(uint32_t serial);
|
||||
bool addRemote(Remote *remote);
|
||||
bool removeRemote(Remote *remote);
|
||||
|
||||
private:
|
||||
RF24 radio;
|
||||
uint8_t last_sent_package_id = 0;
|
||||
uint32_t outgoing_serial;
|
||||
PackageIdForSerial package_ids[constants::MAX_SERIALS];
|
||||
uint8_t num_package_ids = 0;
|
||||
|
||||
Remote remotes[MAX_REMOTES];
|
||||
Remote *remotes[constants::MAX_REMOTES];
|
||||
uint8_t num_remotes = 0;
|
||||
|
||||
static const uint64_t address = 0xAAAAAAAAAAAA;
|
||||
@ -40,6 +41,8 @@ private:
|
||||
// For details on how these parameters were chosen, see
|
||||
// https://github.com/lamperez/xiaomi-lightbar-nrf24?tab=readme-ov-file#crc-checksum
|
||||
CRC16 crc = CRC16(0x1021, 0xfffe, 0x0000, false, false);
|
||||
|
||||
void handlePackage();
|
||||
};
|
||||
|
||||
#endif
|
70
remote.cpp
Normal file
70
remote.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include "remote.h"
|
||||
|
||||
Remote::Remote(Radio *radio, uint32_t serial, const char *name)
|
||||
{
|
||||
this->radio = radio;
|
||||
this->serial = serial;
|
||||
this->name = name;
|
||||
|
||||
this->radio->addRemote(this);
|
||||
|
||||
this->serialString = "0x" + String(this->serial, HEX);
|
||||
}
|
||||
|
||||
Remote::~Remote()
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t Remote::getSerial()
|
||||
{
|
||||
return this->serial;
|
||||
}
|
||||
|
||||
String Remote::getSerialString()
|
||||
{
|
||||
return this->serialString;
|
||||
}
|
||||
|
||||
const char *Remote::getName()
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
void Remote::callback(byte command, byte options)
|
||||
{
|
||||
for (int i = 0; i < this->numCommandListeners; i++)
|
||||
{
|
||||
this->commandListeners[i](this, command, options);
|
||||
}
|
||||
}
|
||||
|
||||
bool Remote::registerCommandListener(std::function<void(Remote *, byte, byte)> callback)
|
||||
{
|
||||
if (this->numCommandListeners >= constants::MAX_COMMAND_LISTENERS)
|
||||
{
|
||||
Serial.println("[Remote] Could not add command listener to remote, because too many are saved!");
|
||||
Serial.println("[Remote] Please check if you actually want to save more than " + String(constants::MAX_COMMAND_LISTENERS, DEC) + " command listeners.");
|
||||
Serial.println("[Remote] If you do, increase MAX_COMMAND_LISTENERS in constants.h and recompile.");
|
||||
return false;
|
||||
}
|
||||
this->commandListeners[this->numCommandListeners] = callback;
|
||||
this->numCommandListeners++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Remote::unregisterCommandListener(std::function<void(Remote *, byte, byte)> callback)
|
||||
{
|
||||
for (int i = 0; i < this->numCommandListeners; i++)
|
||||
{
|
||||
if (this->commandListeners[i].target<void(Remote *, byte, byte)>() == callback.target<void(Remote *, byte, byte)>())
|
||||
{
|
||||
for (int j = i; j < this->numCommandListeners - 1; j++)
|
||||
{
|
||||
this->commandListeners[j] = this->commandListeners[j + 1];
|
||||
}
|
||||
this->numCommandListeners--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
34
remote.h
Normal file
34
remote.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef REMOTE_H
|
||||
#define REMOTE_H
|
||||
|
||||
#include "constants.h"
|
||||
#include "radio.h"
|
||||
|
||||
class Radio;
|
||||
|
||||
class Remote
|
||||
{
|
||||
public:
|
||||
Remote(Radio *radio, uint32_t serial, const char *name);
|
||||
~Remote();
|
||||
|
||||
uint32_t getSerial();
|
||||
String getSerialString();
|
||||
const char *getName();
|
||||
|
||||
bool registerCommandListener(std::function<void(Remote *, byte, byte)> callback);
|
||||
bool unregisterCommandListener(std::function<void(Remote *, byte, byte)> callback);
|
||||
|
||||
void callback(byte command, byte options);
|
||||
|
||||
private:
|
||||
Radio *radio;
|
||||
uint32_t serial;
|
||||
const char *name;
|
||||
String serialString;
|
||||
|
||||
std::function<void(Remote *, byte, byte)> commandListeners[constants::MAX_COMMAND_LISTENERS];
|
||||
uint8_t numCommandListeners = 0;
|
||||
};
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user