diff --git a/README.md b/README.md new file mode 100644 index 0000000..810d518 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Gateway & sensors + +## Branches +Each sensor has a dedicated branch. E.g.: +* temp_sensor +* oil_sensor + +The gateway uses `master` as the main branch. Other sensors' branches get merged once they are ready for production. + +## Release flow diff --git a/gateway/.gitignore b/gateway/.gitignore index 89cc49c..3074d78 100644 --- a/gateway/.gitignore +++ b/gateway/.gitignore @@ -3,3 +3,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +include/credentials.h diff --git a/gateway/README.md b/gateway/README.md index 40206bc..be137bd 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -44,7 +44,7 @@ enum SensorId : unsigned short { }; ``` #### Stairs temperature -##### Value and volatage +##### Value and voltage ```json { "sensor": { @@ -66,7 +66,7 @@ enum SensorId : unsigned short { } ``` #### Oil sensor -##### Value and volatage +##### Value and voltage ```json { "sensor": { diff --git a/gateway/include/Protocol.h b/gateway/include/Protocol.h index 9d966c9..e8c7376 100644 --- a/gateway/include/Protocol.h +++ b/gateway/include/Protocol.h @@ -2,26 +2,34 @@ #include #include +enum ProtocolNo : unsigned int { + NO_PROTOCOL = 0, + PROTOCOL_1 = 1, + PROTOCOL_2 = 2, + PROTOCOL_13 = 13 +}; + class Protocol { protected: - unsigned int protocol; + ProtocolNo no; public: - Protocol(unsigned int protocol) { - this->protocol = protocol; + Protocol(ProtocolNo protocol) : no(protocol) {} + + Protocol& setProtocol(unsigned int p) { + no = static_cast(p); + return *this; } - virtual ~Protocol() {} virtual void fromJson(JsonObjectConst& rcSwitch, RCSwitch& rcDevice) { - unsigned int protocol = rcSwitch["protocol"]; + ProtocolNo protocol = rcSwitch["protocol"]; rcDevice.setProtocol(protocol); rcDevice.send(rcSwitch["value"]); } virtual void toJson(unsigned long value, JsonDocument& jsonDoc) { JsonObject rcSwitch = jsonDoc.createNestedObject("rcSwitch"); - rcSwitch["protocol"] = protocol; + rcSwitch["protocol"] = no; rcSwitch["value"] = value; } - -}; \ No newline at end of file +} fallbackProtocol{ NO_PROTOCOL }; diff --git a/gateway/include/Protocol_1.h b/gateway/include/Protocol_1.h index f05e7f8..6f7d317 100644 --- a/gateway/include/Protocol_1.h +++ b/gateway/include/Protocol_1.h @@ -5,25 +5,32 @@ class Protocol_1 : public Protocol { public: - Protocol_1() : Protocol(1) { - } + Protocol_1() : Protocol(PROTOCOL_1) {} void fromJson(JsonObjectConst& rcSwitch, RCSwitch& rcDevice) override { - unsigned int protocol = rcSwitch["protocol"]; + ProtocolNo protocol = rcSwitch["protocol"]; rcDevice.setProtocol(protocol); - char* group = rcSwitch["group"]; + const char* group = rcSwitch["group"]; int channel = rcSwitch["channel"]; rcSwitch["state"] ? rcDevice.switchOn(group, channel) : rcDevice.switchOff(group, channel); } void toJson(unsigned long value, JsonDocument& jsonDoc) override { JsonObject rcSwitch = jsonDoc.createNestedObject("rcSwitch"); - rcSwitch["protocol"] = protocol; + rcSwitch["protocol"] = no; RcDecoder decoder; decoder.decode(value); rcSwitch["state"] = decoder.state; - rcSwitch["group"] = String(decoder.group, BIN); + rcSwitch["group"] = decoder.group; rcSwitch["channel"] = decoder.device; rcSwitch["raw_value"] = value; } -}; + +#if defined(ESP8266) + static std::string buildId(const char* group, const unsigned char channel) { + char uId[30]; + sprintf(uId, "%s_%d", group, channel); + return std::string{ uId }; + } +#endif +} protocol1; \ No newline at end of file diff --git a/gateway/include/Protocol_2.h b/gateway/include/Protocol_2.h index 589154e..a051345 100644 --- a/gateway/include/Protocol_2.h +++ b/gateway/include/Protocol_2.h @@ -5,8 +5,7 @@ class Protocol_2 : public Protocol { public: - Protocol_2() : Protocol(2) { - } + Protocol_2() : Protocol(PROTOCOL_2) {} void toJson(unsigned long value, JsonDocument& jsonDoc) override { switch (value) { @@ -33,4 +32,4 @@ public: } } -}; \ No newline at end of file +} protocol2; diff --git a/gateway/include/Protocol_Doorbell.h b/gateway/include/Protocol_Doorbell.h new file mode 100644 index 0000000..90444bf --- /dev/null +++ b/gateway/include/Protocol_Doorbell.h @@ -0,0 +1,58 @@ +#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(PROTOCOL_13) {} + + void ring(const char* value) { + preamble(); + for (int i = 0; i < 7; i++) { + delayMicroseconds(TX_DELAY); + code(value); + } + } + +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; diff --git a/gateway/include/RcDecoder.h b/gateway/include/RcDecoder.h index e43e564..fd2aa62 100644 --- a/gateway/include/RcDecoder.h +++ b/gateway/include/RcDecoder.h @@ -4,7 +4,7 @@ struct RcDecoder { bool state; - char group; + char group[6]; unsigned char device; void decode(unsigned long value) { @@ -16,7 +16,7 @@ struct RcDecoder { } state = RC_STATE(res); - group = RC_GROUP(res); + sprintf(group, "%05ld", RC_GROUP(res)); switch (RC_DEVICE(res)) { case 0b10000: device = 1; diff --git a/gateway/include/TinyComponent.h b/gateway/include/TinyComponent.h index beb5c12..725fce1 100644 --- a/gateway/include/TinyComponent.h +++ b/gateway/include/TinyComponent.h @@ -6,9 +6,11 @@ bool buildSensorJson(unsigned long value, JsonDocument& jsonDoc) { sensor["id"] = ID(value); float voltage = (float)GET_VCC(value) / 1000; + JsonObject diagnostic = sensor.createNestedObject("diagnostic"); if (voltage != 0) { - JsonObject diagnostic = sensor.createNestedObject("diagnostic"); diagnostic["voltage"] = voltage; + } else { + diagnostic["voltage"] = ""; } switch (GET_TYPE(value)) { diff --git a/gateway/include/credentials.h.tpl b/gateway/include/credentials.h.tpl new file mode 100644 index 0000000..c5637e4 --- /dev/null +++ b/gateway/include/credentials.h.tpl @@ -0,0 +1,4 @@ +struct WifiCredentials { + const char* ssid; + const char* password; +} credentials[] = {"foo", "bar"}; diff --git a/gateway/include/devices.h b/gateway/include/devices.h new file mode 100644 index 0000000..589f7fb --- /dev/null +++ b/gateway/include/devices.h @@ -0,0 +1,136 @@ +#pragma once + +#define MAIN_DEVICE_ID "rc-gateway" + +#include "ha.h" + +using namespace Ha; + +typedef unordered_multimap mapswitches; + +mapswitches onSwitches; +mapswitches offSwitches; +unordered_map p1Switches; + +auto gatewayDevice = &DeviceConfig::create(MAIN_DEVICE_ID).withName("RC Gateway").withManufacturer("Adafruit").withModel("Huzzah Esp8266"); + +namespace OilTank { + Sensor* buildRoomSensor(const char* id) { + DeviceConfig* device = &DeviceConfig::create(id) + .withName("Servers room") + .withManufacturer("Atmel") + .withModel("AtTiny85") + .withArea("Basement") + .withParent(gatewayDevice); + return Builder::instance(id) + .asDevice(device) + .withValueTemplate("{{ value_json.sensor.temperature }}") + .addDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) + .addDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.servers_room_battery_voltage')|float-2.5)|round(2)*100/2)|int }}"}) + .build(); + } + + Sensor* buildTankSensor(const char* id) { + DeviceConfig* device = &DeviceConfig::create(id) + .withName("Oil tank") + .withManufacturer("Arduino") + .withModel("Pro Mini") + .withArea("Basement") + .withParent(gatewayDevice); + return Builder::instance(new Sensor{ "Depth", id }) + .asDevice(device) + .withDeviceClass("distance") + .withUnitMseasure("cm") + .withValueTemplate("{{ value_json.sensor.value }}") + .addSecondary( + Builder::instance(new Sensor{ "Level", id }) + .withUnitMseasure("%") + .withValueTemplate("{{ 100 - ((value_json.sensor.value-7)|float*100/110)|round(2) }}") + .build() + ) + .addDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) + .addDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_battery_voltage')|float-3.6)|round(2)*100/1.6)|int }}"}) + .build(); + } +} + +struct PollinSwitch : Switch { + const char* group; + unsigned char channel; + + PollinSwitch(const char* name, const char* group, const unsigned char channel, const char* area = nullptr) + : Switch(nullptr, [group, channel]{ + // copy id from string into a new pointer, to avoid memory leaks + string s = Protocol_1::buildId(group, channel); + char* uId = new char[s.length() + 1]; + strcpy(uId, s.c_str()); + return uId; + }()), group(group), channel(channel) { + mainDevice = &DeviceConfig::create(id).withName(name).withManufacturer("Pollin").withArea(area).withParent(gatewayDevice); + withStateTopic(); + deviceClass = "outlet"; + p1Switches.insert({ string(id), this }); + } + + void onCommand(const char* msg) override { + strcmp("ON", msg) == 0 ? mySwitch.switchOn(group, channel) : mySwitch.switchOff(group, channel); + publisher(stateTopic, msg); + } + +}; + +struct EasyHomeSwitch : Switch { + unsigned long on[8] = { 4326554, 4537114, 4767530, 4972714 }; + unsigned long off[8] = { 4483146, 4626810, 4661562, 4819642 }; + + EasyHomeSwitch(const char* name, const char* id, unsigned long on[4], unsigned long off[4], const char* area = nullptr) + : Switch(nullptr, id) { + memcpy(&this->on[4], on, 4 * sizeof(unsigned long)); + memcpy(&this->off[4], off, 4 * sizeof(unsigned long)); + mainDevice = &DeviceConfig::create(id).withName(name).withManufacturer("Intertek").withModel("Easy Home").withArea(area).withParent(gatewayDevice); + withStateTopic(); + deviceClass = "outlet"; + for (int i = 0; i < 8; i++) { + onSwitches.insert({ this->on[i], this }); + offSwitches.insert({ this->off[i], this }); + } + } + + void onCommand(const char* msg) override { + mySwitch.setProtocol(4); + strcmp("ON", msg) == 0 ? mySwitch.send(on[4], 24) : mySwitch.send(off[4], 24); + publisher(stateTopic, msg); + } +}; + +Command* commands[] = { + Builder