Merge branch 'doorbell' into huzzah

This commit is contained in:
Nicu Hodos 2024-05-19 18:11:28 +02:00
commit 4096bfc83a
10 changed files with 183 additions and 101 deletions

View File

@ -2,15 +2,24 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <RCSwitch.h> #include <RCSwitch.h>
using namespace std;
class Protocol { class Protocol {
protected: protected:
unsigned int protocol; unsigned int no;
public: public:
static unordered_map<unsigned int, Protocol*> mapProtocols;
Protocol(unsigned int protocol) { Protocol(unsigned int protocol) {
this->protocol = protocol; no = protocol;
mapProtocols.insert({ no, this });
}
Protocol& setProtocol(unsigned int p) {
no = p;
return *this;
} }
virtual ~Protocol() {}
virtual void fromJson(JsonObjectConst& rcSwitch, RCSwitch& rcDevice) { virtual void fromJson(JsonObjectConst& rcSwitch, RCSwitch& rcDevice) {
unsigned int protocol = rcSwitch["protocol"]; unsigned int protocol = rcSwitch["protocol"];
@ -20,8 +29,9 @@ public:
virtual void toJson(unsigned long value, JsonDocument& jsonDoc) { virtual void toJson(unsigned long value, JsonDocument& jsonDoc) {
JsonObject rcSwitch = jsonDoc.createNestedObject("rcSwitch"); JsonObject rcSwitch = jsonDoc.createNestedObject("rcSwitch");
rcSwitch["protocol"] = protocol; rcSwitch["protocol"] = no;
rcSwitch["value"] = value; rcSwitch["value"] = value;
} }
};
}; unordered_map<unsigned int, Protocol*> Protocol::mapProtocols;
Protocol fallbackProtocol{ 0 };

View File

@ -5,8 +5,7 @@
class Protocol_1 : public Protocol { class Protocol_1 : public Protocol {
public: public:
Protocol_1() : Protocol(1) { Protocol_1() : Protocol(1) {}
}
void fromJson(JsonObjectConst& rcSwitch, RCSwitch& rcDevice) override { void fromJson(JsonObjectConst& rcSwitch, RCSwitch& rcDevice) override {
unsigned int protocol = rcSwitch["protocol"]; unsigned int protocol = rcSwitch["protocol"];
@ -18,7 +17,7 @@ public:
void toJson(unsigned long value, JsonDocument& jsonDoc) override { void toJson(unsigned long value, JsonDocument& jsonDoc) override {
JsonObject rcSwitch = jsonDoc.createNestedObject("rcSwitch"); JsonObject rcSwitch = jsonDoc.createNestedObject("rcSwitch");
rcSwitch["protocol"] = protocol; rcSwitch["protocol"] = no;
RcDecoder decoder; RcDecoder decoder;
decoder.decode(value); decoder.decode(value);
rcSwitch["state"] = decoder.state; rcSwitch["state"] = decoder.state;
@ -32,4 +31,4 @@ public:
sprintf(uId, "%s_%d", group, channel); sprintf(uId, "%s_%d", group, channel);
return std::string{ uId }; return std::string{ uId };
} }
}; } protocol1;

View File

@ -5,8 +5,7 @@
class Protocol_2 : public Protocol { class Protocol_2 : public Protocol {
public: public:
Protocol_2() : Protocol(2) { Protocol_2() : Protocol(2) {}
}
void toJson(unsigned long value, JsonDocument& jsonDoc) override { void toJson(unsigned long value, JsonDocument& jsonDoc) override {
switch (value) { switch (value) {
@ -33,4 +32,4 @@ public:
} }
} }
}; } protocol2;

View File

@ -0,0 +1,64 @@
#pragma once
#include "Protocol.h"
#define BIT_LENGTH 40
#define BIT_LENGTH_3 BIT_LENGTH*3
#define TX_DELAY 620
class Protocol_Doorbell : public Protocol {
public:
Protocol_Doorbell() : Protocol(16) {}
void ring() {
preamble();
for (int i = 0; i < 7; i++) {
delayMicroseconds(TX_DELAY);
code("00000000110100101000100");
}
}
void fromJson(JsonObjectConst& rcSwitch, RCSwitch& rcDevice) override {
}
void toJson(unsigned long value, JsonDocument& jsonDoc) override {
}
private:
void transmitBit(uint8_t value) {
digitalWrite(SEND_PIN, value);
delayMicroseconds(BIT_LENGTH);
digitalWrite(SEND_PIN, LOW);
}
void transmitHigh() {
digitalWrite(SEND_PIN, HIGH);
delayMicroseconds(BIT_LENGTH_3);
digitalWrite(SEND_PIN, LOW);
delayMicroseconds(BIT_LENGTH);
}
void transmitLow() {
digitalWrite(SEND_PIN, HIGH);
delayMicroseconds(BIT_LENGTH);
digitalWrite(SEND_PIN, LOW);
delayMicroseconds(BIT_LENGTH_3);
}
void preamble() {
noInterrupts();
for (int i = 0; i < 370; i++) {
transmitBit(HIGH);
transmitBit(LOW);
}
interrupts();
}
void code(const char* value) {
noInterrupts();
for (const char* p = value; *p; p++) {
*p == '1' ? transmitHigh() : transmitLow();
}
interrupts();
}
} doorbell;

View File

@ -6,44 +6,50 @@
using namespace Ha; using namespace Ha;
DeviceConfig* gatewayDevice = (new DeviceConfig{MAIN_DEVICE_ID})->withName("RC Gateway")->withManufacturer("Adafruit")->withModel("Huzzah Esp8266"); typedef unordered_multimap<unsigned long, Ha::Switch*> mapswitches;
mapswitches onSwitches;
mapswitches offSwitches;
unordered_map<string, Ha::Switch*> p1Switches;
auto gatewayDevice = &DeviceConfig::create(MAIN_DEVICE_ID).withName("RC Gateway").withManufacturer("Adafruit").withModel("Huzzah Esp8266");
namespace OilTank { namespace OilTank {
Sensor* buildRoomSensor(const char* id) { Sensor* buildRoomSensor(const char* id) {
auto device = DeviceConfig::create(id) DeviceConfig* device = &DeviceConfig::create(id)
->withName("Oil tank room") .withName("Oil tank room")
->withManufacturer("Atmel") .withManufacturer("Atmel")
->withModel("AtTiny85") .withModel("AtTiny85")
->withArea("Basement") .withArea("Basement")
->withParent(gatewayDevice); .withParent(gatewayDevice);
return Builder<TemperatureSensor>::instance(id) return Builder<TemperatureSensor>::instance(id)
.asDevice(device) .asDevice(device)
.withValueTemplate("{{ value_json.sensor.temperature }}") .withValueTemplate("{{ value_json.sensor.temperature }}")
.withDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) .asDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"})
.withDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_room_battery_voltage')|float-2.5)|round(2)*100/2)|int }}"}) .asDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_room_battery_voltage')|float-2.5)|round(2)*100/2)|int }}"})
.build(); .build();
} }
Sensor* buildTankSensor(const char* id) { Sensor* buildTankSensor(const char* id) {
auto device = DeviceConfig::create(id) DeviceConfig* device = &DeviceConfig::create(id)
->withName("Oil tank") .withName("Oil tank")
->withManufacturer("Arduino") .withManufacturer("Arduino")
->withModel("Pro Mini") .withModel("Pro Mini")
->withArea("Basement") .withArea("Basement")
->withParent(gatewayDevice); .withParent(gatewayDevice);
return Builder<Sensor>::instance(new Sensor{ "Depth", id }) return Builder<Sensor>::instance(new Sensor{ "Depth", id })
.asDevice(device) .asDevice(device)
.withDeviceClass("distance") .withDeviceClass("distance")
.withUnitMseasure("cm") .withUnitMseasure("cm")
.withValueTemplate("{{ value_json.sensor.value }}") .withValueTemplate("{{ value_json.sensor.value }}")
.withSecondary( .asSecondary(
Builder<Sensor>::instance(new Sensor{ "Level", id }) Builder<Sensor>::instance(new Sensor{ "Level", id })
.withUnitMseasure("%") .withUnitMseasure("%")
.withValueTemplate("{{ 100 - ((value_json.sensor.value-7)|float*100/110)|round(2) }}") .withValueTemplate("{{ 100 - ((value_json.sensor.value-7)|float*100/110)|round(2) }}")
.build() .build()
) )
.withDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) .asDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"})
.withDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_battery_voltage')|float-3.6)|round(2)*100/1.6)|int }}"}) .asDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_battery_voltage')|float-3.6)|round(2)*100/1.6)|int }}"})
.build(); .build();
} }
} }
@ -60,7 +66,7 @@ struct PollinSwitch : Switch {
strcpy(uId, s.c_str()); strcpy(uId, s.c_str());
return uId; return uId;
}()), group(group), channel(channel) { }()), group(group), channel(channel) {
mainDevice = (new DeviceConfig{id})->withName(name)->withManufacturer("Pollin")->withArea(area)->withParent(gatewayDevice); mainDevice = &DeviceConfig::create(id).withName(name).withManufacturer("Pollin").withArea(area).withParent(gatewayDevice);
deviceClass = "outlet"; deviceClass = "outlet";
p1Switches.insert({ string(id), this }); p1Switches.insert({ string(id), this });
} }
@ -80,7 +86,7 @@ struct EasyHomeSwitch : Switch {
: Switch(name, id) { : Switch(name, id) {
memcpy(&this->on[4], on, 4 * sizeof(unsigned long)); memcpy(&this->on[4], on, 4 * sizeof(unsigned long));
memcpy(&this->off[4], off, 4 * sizeof(unsigned long)); memcpy(&this->off[4], off, 4 * sizeof(unsigned long));
mainDevice = (new DeviceConfig{id})->withName(name)->withManufacturer("Intertek")->withModel("Easy Home")->withArea(area)->withParent(gatewayDevice); mainDevice = &DeviceConfig::create(id).withName(name).withManufacturer("Intertek").withModel("Easy Home").withArea(area).withParent(gatewayDevice);
deviceClass = "outlet"; deviceClass = "outlet";
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
onSwitches.insert({ this->on[i], this }); onSwitches.insert({ this->on[i], this });
@ -101,6 +107,19 @@ Command* commands[] = {
if (strcmp("PRESS", msg) == 0) ESP.restart(); if (strcmp("PRESS", msg) == 0) ESP.restart();
} }
}).asDevice(gatewayDevice).build(), }).asDevice(gatewayDevice).build(),
Builder<Button>::instance(new Button{"Front door", "doorbell_front",
[](const char* msg) {
if (strcmp("PRESS", msg) == 0) doorbell.ring();
}
})
.asDevice(
&DeviceConfig::create("doorbell")
.withName("Doorbell")
.withManufacturer("Thomson")
.withModel("Kinetic Halo")
.withParent(gatewayDevice)
)
.build(),
(new EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }, "Basement"})->withStateTopic(), (new EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }, "Basement"})->withStateTopic(),
(new EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, "Basement"})->withStateTopic(), (new EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, "Basement"})->withStateTopic(),
(new PollinSwitch{"Meeting sensor", "00001", 1, "Dining room"})->withStateTopic(), (new PollinSwitch{"Meeting sensor", "00001", 1, "Dining room"})->withStateTopic(),

View File

@ -16,12 +16,10 @@ namespace Ha {
const char* model = nullptr; const char* model = nullptr;
const char* manufacturer = nullptr; const char* manufacturer = nullptr;
const char* area = nullptr; const char* area = nullptr;
DeviceConfig* parent = nullptr; const DeviceConfig* parent = nullptr;
DeviceConfig(const char* id) : id(id) {} static DeviceConfig& create(const char* id) {
return *(new DeviceConfig{ id });
static DeviceConfig* create(const char* id) {
return new DeviceConfig{ id };
} }
void buildConfig(JsonDocument& jsonDoc) { void buildConfig(JsonDocument& jsonDoc) {
@ -35,30 +33,33 @@ namespace Ha {
identifiers.add(id); identifiers.add(id);
} }
DeviceConfig* withName(const char* value) { DeviceConfig& withName(const char* value) {
name = value; name = value;
return this; return *this;
} }
DeviceConfig* withModel(const char* value) { DeviceConfig& withModel(const char* value) {
model = value; model = value;
return this; return *this;
} }
DeviceConfig* withManufacturer(const char* value) { DeviceConfig& withManufacturer(const char* value) {
manufacturer = value; manufacturer = value;
return this; return *this;
} }
DeviceConfig* withArea(const char* value) { DeviceConfig& withArea(const char* value) {
area = value; area = value;
return this; return *this;
} }
DeviceConfig* withParent(DeviceConfig* deviceConfig) { DeviceConfig& withParent(const DeviceConfig* deviceConfig) {
parent = deviceConfig; parent = deviceConfig;
return this; return *this;
} }
protected:
DeviceConfig(const char* id) : id(id) {}
}; };
struct Component { struct Component {
@ -108,7 +109,6 @@ namespace Ha {
publisher(configTopic, ""); publisher(configTopic, "");
} }
}; };
List<Component> Component::components;
struct AbstractBuilder { struct AbstractBuilder {
static List<AbstractBuilder> builders; static List<AbstractBuilder> builders;
@ -122,7 +122,6 @@ namespace Ha {
builders.empty(); builders.empty();
} }
}; };
List<AbstractBuilder> AbstractBuilder::builders;
template <class T> template <class T>
struct Builder : AbstractBuilder { struct Builder : AbstractBuilder {
@ -161,15 +160,14 @@ namespace Ha {
return *this; return *this;
} }
Builder& withSecondary(Component* c) { Builder& asSecondary(Component* c) {
c->mainDevice = new DeviceConfig{ cmp->id }; c->mainDevice = &DeviceConfig::create(cmp->id);
return *this; return *this;
} }
Builder& withDiagnostic(Component* c) { Builder& asDiagnostic(Component* c) {
c->entityCategory = "diagnostic"; c->entityCategory = "diagnostic";
c->mainDevice = new DeviceConfig{ cmp->id }; return asSecondary(c);
return *this;
} }
Builder& asDevice(DeviceConfig* deviceConfig) { Builder& asDevice(DeviceConfig* deviceConfig) {
@ -198,7 +196,6 @@ namespace Ha {
} }
}; };
unordered_map<string, Command*> Command::mapCommands;
struct Button : Command { struct Button : Command {
@ -227,7 +224,7 @@ namespace Ha {
if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic; if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic;
} }
void publishState(bool state) { void updateState(bool state) {
publisher(stateTopic, state ? "ON" : "OFF"); publisher(stateTopic, state ? "ON" : "OFF");
} }
}; };
@ -235,13 +232,11 @@ namespace Ha {
struct Sensor : Component, StateConfig<Sensor> { struct Sensor : Component, StateConfig<Sensor> {
const char* unitMeasure = nullptr; const char* unitMeasure = nullptr;
const char* valueTemplate = nullptr; const char* valueTemplate = nullptr;
static unordered_map<string, Sensor*> mapSensors;
Sensor() : Component(name, id, "sensor") {
withStateTopic();
}
Sensor(const char* name, const char* id) : Component(name, id, "sensor") { Sensor(const char* name, const char* id) : Component(name, id, "sensor") {
withStateTopic(); withStateTopic();
mapSensors.insert({ id, this });
} }
void buildUniqueId(char* uniqueId) override { void buildUniqueId(char* uniqueId) override {
@ -267,6 +262,10 @@ namespace Ha {
jsonDoc["state_topic"] = stateTopic; jsonDoc["state_topic"] = stateTopic;
jsonDoc["suggested_display_precision"] = 2; jsonDoc["suggested_display_precision"] = 2;
} }
void updateState(const char* message) {
publisher(stateTopic, message);
}
}; };
struct TemperatureSensor : Sensor { struct TemperatureSensor : Sensor {
@ -307,4 +306,9 @@ namespace Ha {
// valueTemplate = "{{ value_json.sensor.pressure }}"; // valueTemplate = "{{ value_json.sensor.pressure }}";
} }
}; };
List<Component> Component::components;
List<AbstractBuilder> AbstractBuilder::builders;
unordered_map<string, Command*> Command::mapCommands;
unordered_map<string, Sensor*> Sensor::mapSensors;
} }

View File

@ -1,22 +1,9 @@
#include <TaskScheduler.h> #include <TaskScheduler.h>
#define SEND_PIN 14
#define RECEIVE_PIN 12
#define RED_LED LED_BUILTIN
#define BLUE_LED 2
using namespace std; using namespace std;
Scheduler ts; Scheduler ts;
namespace Ha {
struct Switch;
}
typedef unordered_multimap<unsigned long, Ha::Switch*> mapswitches;
mapswitches onSwitches;
mapswitches offSwitches;
unordered_map<string, Ha::Switch*> p1Switches;
void turnLed(uint8_t led, bool on = true) { void turnLed(uint8_t led, bool on = true) {
on ? digitalWrite(led, LOW) : digitalWrite(led, HIGH); on ? digitalWrite(led, LOW) : digitalWrite(led, HIGH);
} }
@ -57,7 +44,7 @@ namespace Board {
case 1: { case 1: {
string id = Protocol_1::buildId((const char*)rcSwitch["group"], (int)rcSwitch["channel"]); string id = Protocol_1::buildId((const char*)rcSwitch["group"], (int)rcSwitch["channel"]);
Ha::Switch* el = p1Switches[id]; Ha::Switch* el = p1Switches[id];
if (el) el->publishState((bool)rcSwitch["state"]); if (el) el->updateState((bool)rcSwitch["state"]);
break; break;
} }
case 2: case 2:
@ -66,11 +53,11 @@ namespace Board {
unsigned long value = rcSwitch["value"]; unsigned long value = rcSwitch["value"];
auto range = onSwitches.equal_range(value); auto range = onSwitches.equal_range(value);
for_each(range.first, range.second, [](mapswitches::value_type& x){ for_each(range.first, range.second, [](mapswitches::value_type& x){
x.second->publishState(true); x.second->updateState(true);
}); });
range = offSwitches.equal_range(value); range = offSwitches.equal_range(value);
for_each(range.first, range.second, [](mapswitches::value_type& x){ for_each(range.first, range.second, [](mapswitches::value_type& x){
x.second->publishState(false); x.second->updateState(false);
}); });
} }
} }
@ -79,10 +66,8 @@ namespace Board {
void parseSensors(JsonDocument& jsonDoc, char* message) { void parseSensors(JsonDocument& jsonDoc, char* message) {
JsonObjectConst json = jsonDoc["sensor"]; JsonObjectConst json = jsonDoc["sensor"];
string id = to_string((unsigned int)json["id"]); string id = to_string((unsigned int)json["id"]);
char stateTopic[TOPIC_SIZE]; auto sensor = Sensor::mapSensors[id];
sprintf(stateTopic, "homeassistant/sensor/%s/%s/state", MAIN_DEVICE_ID, id.c_str()); if (sensor) sensor->updateState(message);
Mqtt::publish(stateTopic, message);
} }
void publishResponse(JsonDocument& jsonDoc) { void publishResponse(JsonDocument& jsonDoc) {

12
gateway/include/pins.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#if defined(ESP8266)
#define SEND_PIN 14
#define RECEIVE_PIN 12
#define RED_LED LED_BUILTIN
#define BLUE_LED 2
#else
#define RESET_PIN 10
#define SEND_PIN 11
#define RECEIVE_PIN 2
#endif

View File

@ -1,9 +1,5 @@
#include "output.h" #include "output.h"
#define RESET_PIN 10
#define SEND_PIN 11
#define RECEIVE_PIN 2
namespace Board { namespace Board {
void setup() { void setup() {

View File

@ -1,9 +1,11 @@
#include <Arduino.h> #include <Arduino.h>
#include <RCSwitch.h> #include <RCSwitch.h>
#include <SerialReader.h> #include <SerialReader.h>
#include "pins.h"
#include "Dht.h" #include "Dht.h"
#include "Protocol_1.h" #include "Protocol_1.h"
#include "Protocol_2.h" #include "Protocol_2.h"
#include "Protocol_Doorbell.h"
RCSwitch mySwitch; RCSwitch mySwitch;
SerialReader<200> serialReader; SerialReader<200> serialReader;
@ -29,15 +31,9 @@ void setup() {
delay(1000); delay(1000);
} }
Protocol* findProtocol(unsigned int protocol) { Protocol& findProtocol(unsigned int protocol) {
switch (protocol) { auto p = Protocol::mapProtocols[protocol];
case 1: return p ? *p : fallbackProtocol.setProtocol(protocol);
return new Protocol_1();
case 2:
return new Protocol_2();
default:
return new Protocol(protocol);
}
} }
void readRcSwitch() { void readRcSwitch() {
@ -52,9 +48,8 @@ void readRcSwitch() {
mySwitch.resetAvailable(); mySwitch.resetAvailable();
StaticJsonDocument<128> jsonDoc; StaticJsonDocument<128> jsonDoc;
Protocol* p = findProtocol(mySwitch.getReceivedProtocol()); Protocol& p = findProtocol(mySwitch.getReceivedProtocol());
p->toJson(value, jsonDoc); p.toJson(value, jsonDoc);
delete p;
if (!jsonDoc.isNull()) { if (!jsonDoc.isNull()) {
serializeJson(jsonDoc, Serial); serializeJson(jsonDoc, Serial);
Serial.println(); Serial.println();
@ -75,14 +70,13 @@ void handleJsonError(DeserializationError err, const char* cmd) {
} }
void runJsonCommand(char* cmd) { void runJsonCommand(char* cmd) {
StaticJsonDocument<50> jsonDoc; StaticJsonDocument<128> jsonDoc;
DeserializationError err = deserializeJson(jsonDoc, cmd); DeserializationError err = deserializeJson(jsonDoc, cmd);
if (err == DeserializationError::Ok) { if (err == DeserializationError::Ok) {
if (jsonDoc.containsKey("rcSwitch")) { if (jsonDoc.containsKey("rcSwitch")) {
JsonObjectConst rcSwitch = jsonDoc["rcSwitch"]; JsonObjectConst rcSwitch = jsonDoc["rcSwitch"];
Protocol* p = findProtocol(rcSwitch["protocol"]); Protocol& p = findProtocol(rcSwitch["protocol"]);
p->fromJson(rcSwitch, mySwitch); p.fromJson(rcSwitch, mySwitch);
delete p;
serializeJson(jsonDoc, Serial); serializeJson(jsonDoc, Serial);
Serial.println(); Serial.println();
Board::publishResponse(jsonDoc); Board::publishResponse(jsonDoc);