From 4ab725a59e3acebcaa97ff5b6c002c46fb99a0ed Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Tue, 30 Sep 2025 09:56:15 +0200 Subject: [PATCH 01/10] create presence sensor device --- devices/presence_ticker/.gitignore | 5 ++ devices/presence_ticker/README.md | 5 ++ .../presence_ticker/docs/presence_sensor.fzz | 3 ++ .../docs/presence_sensor_bb.png | 3 ++ devices/presence_ticker/include/README | 39 ++++++++++++++++ devices/presence_ticker/lib/README | 46 +++++++++++++++++++ devices/presence_ticker/platformio.ini | 24 ++++++++++ .../presence_ticker/src/presence_ticker.cpp | 36 +++++++++++++++ devices/presence_ticker/test/README | 11 +++++ lib/Tiny/Tiny.h | 3 +- rc-gateway.code-workspace | 41 ++++++++++++++++- 11 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 devices/presence_ticker/.gitignore create mode 100644 devices/presence_ticker/README.md create mode 100644 devices/presence_ticker/docs/presence_sensor.fzz create mode 100644 devices/presence_ticker/docs/presence_sensor_bb.png create mode 100644 devices/presence_ticker/include/README create mode 100644 devices/presence_ticker/lib/README create mode 100644 devices/presence_ticker/platformio.ini create mode 100644 devices/presence_ticker/src/presence_ticker.cpp create mode 100644 devices/presence_ticker/test/README diff --git a/devices/presence_ticker/.gitignore b/devices/presence_ticker/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/devices/presence_ticker/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/devices/presence_ticker/README.md b/devices/presence_ticker/README.md new file mode 100644 index 0000000..11a8e19 --- /dev/null +++ b/devices/presence_ticker/README.md @@ -0,0 +1,5 @@ +# Presence ticker + +It sends state on regular intervals, so that presence of the bearer can be tracked. + +![Schematics](docs/presence_sensor_bb.png) diff --git a/devices/presence_ticker/docs/presence_sensor.fzz b/devices/presence_ticker/docs/presence_sensor.fzz new file mode 100644 index 0000000..2cdd17a --- /dev/null +++ b/devices/presence_ticker/docs/presence_sensor.fzz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09a14fc2865ec4834054f390ba1b4683c393835ac0aed6e68e30e19e7dae4982 +size 6814 diff --git a/devices/presence_ticker/docs/presence_sensor_bb.png b/devices/presence_ticker/docs/presence_sensor_bb.png new file mode 100644 index 0000000..13c3656 --- /dev/null +++ b/devices/presence_ticker/docs/presence_sensor_bb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1603cb8e433a7ee1fd0a2300dfaf574386b5e681dd17ccebe104cfc635ed038e +size 120232 diff --git a/devices/presence_ticker/include/README b/devices/presence_ticker/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/devices/presence_ticker/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/devices/presence_ticker/lib/README b/devices/presence_ticker/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/devices/presence_ticker/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/devices/presence_ticker/platformio.ini b/devices/presence_ticker/platformio.ini new file mode 100644 index 0000000..0025c14 --- /dev/null +++ b/devices/presence_ticker/platformio.ini @@ -0,0 +1,24 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:attiny85] +platform = atmelavr +board = attiny85 +framework = arduino +lib_deps = + sui77/rc-switch @ ^2.6.4 +lib_extra_dirs = + ../../lib +upload_protocol = stk500v1 +upload_flags = + -P$UPLOAD_PORT + -b$UPLOAD_SPEED +upload_port = /dev/ttyACM0 +upload_speed = 19200 diff --git a/devices/presence_ticker/src/presence_ticker.cpp b/devices/presence_ticker/src/presence_ticker.cpp new file mode 100644 index 0000000..172d121 --- /dev/null +++ b/devices/presence_ticker/src/presence_ticker.cpp @@ -0,0 +1,36 @@ +#include +#include +#include "ContactSensor.h" + +#define MINUTES(value) (uint16_t)(value*60/8) // minutes*60(seconds)/8s(WDT) +#define HOURS(value) MINUTES(value)*60 + +#define SEND_INTERVAL MINUTES(2) +#define SEND_VCC_INTERVAL HOURS(6) + +// Pins +#define SENDER_PIN PIN_B2 + +volatile uint16_t counter = 0; +ContactSensor sensor(PRESENCE_SENSOR); + +void setup() { + + TinySwitch::setup(SENDER_PIN); + TinyPower::setup(); + TinyPower::enableWdt(WDTO_8S); +} + +void loop() { + if (counter % SEND_VCC_INTERVAL == 0) { + sensor.sendStateAndVoltage(true); + counter = 0; + } else if (counter % SEND_INTERVAL == 0) { + sensor.sendState(true); + } + TinyPower::sleep(); +} + +ISR(WDT_vect) { + counter++; +} diff --git a/devices/presence_ticker/test/README b/devices/presence_ticker/test/README new file mode 100644 index 0000000..b94d089 --- /dev/null +++ b/devices/presence_ticker/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/lib/Tiny/Tiny.h b/lib/Tiny/Tiny.h index 653be12..748d4d9 100644 --- a/lib/Tiny/Tiny.h +++ b/lib/Tiny/Tiny.h @@ -35,5 +35,6 @@ enum SensorId : unsigned short { TEMP_SENSOR = 4, LIGHT_SENSOR = 5, MOVEMENT_SENSOR = 6, - OIL_SENSOR = 7 + OIL_SENSOR = 7, + PRESENCE_SENSOR = 8 }; diff --git a/rc-gateway.code-workspace b/rc-gateway.code-workspace index 5e605b8..db5bd71 100644 --- a/rc-gateway.code-workspace +++ b/rc-gateway.code-workspace @@ -1,13 +1,20 @@ { "folders": [ { + "name": "rc-gateway", "path": "." }, { + "name": "temp_sensor", "path": "devices/temp_sensor" }, { + "name": "oil_sensor", "path": "devices/oil_sensor" + }, + { + "name": "presence_ticker", + "path": "devices/presence_ticker" } ], "settings": { @@ -46,7 +53,39 @@ "iterator": "cpp", "memory": "cpp", "variant": "cpp", - "string_view": "cpp" + "string_view": "cpp", + "regex": "cpp", + "cstdint": "cpp", + "bit": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstdio": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "map": "cpp", + "set": "cpp", + "unordered_map": "cpp", + "exception": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "mutex": "cpp", + "new": "cpp", + "ranges": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp" } } } \ No newline at end of file From 4ea9c83ff9a296f7a58220274c744e0478847f6a Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Tue, 30 Sep 2025 16:26:26 +0200 Subject: [PATCH 02/10] add test for max value --- lib/Tiny/Tiny.h | 6 +++--- test/native/test_sensor_builder/sensor_builder.cpp | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/Tiny/Tiny.h b/lib/Tiny/Tiny.h index 748d4d9..b2825c7 100644 --- a/lib/Tiny/Tiny.h +++ b/lib/Tiny/Tiny.h @@ -6,16 +6,16 @@ #define MASK_STATE 0x1 #define MASK_TYPE 0xF -#define ID(value) (value & 0x1F) +#define ID(value) ((unsigned long)value & MASK_ID) #define VCC(value) (((unsigned long)value & MASK_VCC) << 5) #define TEMP(value) (((unsigned long)value & MASK_VALUE) << 18) #define HUMIDITY(value) (((unsigned long)value & MASK_VALUE) << 18) #define VALUE(value) (((unsigned long)value & MASK_VALUE) << 18) -#define STATE(value) ((value & MASK_STATE) << 27) +#define STATE(value) (((unsigned long)value & MASK_STATE) << 27) #define TYPE(value) (((unsigned long)value & MASK_TYPE) << 28) #define GET_TYPE(value) (((unsigned long)value >> 28) & MASK_TYPE) -#define GET_STATE(value) ((value >> 27) & MASK_STATE) +#define GET_STATE(value) (((unsigned long)value >> 27) & MASK_STATE) #define GET_TEMP(value) (((unsigned long)value >> 18) & MASK_VALUE) #define GET_HUMIDITY(value) (((unsigned long)value >> 18) & MASK_VALUE) #define GET_VALUE(value) (((unsigned long)value >> 18) & MASK_VALUE) diff --git a/test/native/test_sensor_builder/sensor_builder.cpp b/test/native/test_sensor_builder/sensor_builder.cpp index db130f0..f011daa 100644 --- a/test/native/test_sensor_builder/sensor_builder.cpp +++ b/test/native/test_sensor_builder/sensor_builder.cpp @@ -24,6 +24,15 @@ void test_max_temp(void) { TEST_ASSERT_EQUAL(102.3, sensor["temperature"]); } +void test_max_value(void) { + StaticJsonDocument<200> jsonDoc; + unsigned long value = VALUE(1023) | TYPE(SensorType::GENERIC); + TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); + + JsonObject sensor = jsonDoc["sensor"]; + TEST_ASSERT_EQUAL(1023, sensor["value"]); +} + void test_overflow_value(void) { StaticJsonDocument<200> jsonDoc; unsigned long value = VALUE(1024) | TYPE(SensorType::GENERIC); @@ -101,6 +110,7 @@ int main(int argc, char **argv) { UNITY_BEGIN(); RUN_TEST(test_unknown_sensor_type); RUN_TEST(test_max_temp); + RUN_TEST(test_max_value); RUN_TEST(test_overflow_value); RUN_TEST(test_max_voltage); RUN_TEST(test_overflow_voltage); From 77422d5f59a5864fdb7bf1b443d66e1d95b2638d Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Tue, 30 Sep 2025 18:47:29 +0200 Subject: [PATCH 03/10] use wrapper class for SensorId and overload operators so that it can be used both as number and string --- include/devices.h | 59 ++++++++----------- lib/Tiny/ContactSensor.h | 4 +- lib/Tiny/GenericSensor.h | 4 +- lib/Tiny/HumiditySensor.h | 4 +- lib/Tiny/TemperatureSensor.h | 4 +- lib/Tiny/Tiny.h | 35 +++++++---- lib/Tiny/TinySensor.h | 4 +- .../test_sensor_builder/sensor_builder.cpp | 16 ++--- 8 files changed, 63 insertions(+), 67 deletions(-) diff --git a/include/devices.h b/include/devices.h index fad2dd5..27e7696 100644 --- a/include/devices.h +++ b/include/devices.h @@ -15,46 +15,40 @@ unordered_map p1Switches; auto gatewayDevice = &DeviceConfig::create(MAIN_DEVICE_ID).withName("RC Gateway").withManufacturer("Adafruit").withModel("Huzzah Esp8266"); -Sensor* buildRoomSensor(const char* id) { - DeviceConfig* device = &DeviceConfig::create(id) +auto roomSensor = Builder::instance(TEMP_SENSOR) + .asDevice(&DeviceConfig::create(TEMP_SENSOR) .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", "{{ ((value_json.sensor.diagnostic.voltage|float-2.5)|round(2)*100/2)|int }}"}) - .build(); -} + .withParent(gatewayDevice)) + .withValueTemplate("{{ value_json.sensor.temperature }}") + .addDiagnostic(new VoltageSensor{TEMP_SENSOR, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) + .addDiagnostic(new BatterySensor{TEMP_SENSOR, "Battery level", "{{ ((value_json.sensor.diagnostic.voltage|float-2.5)|round(2)*100/2)|int }}"}) + .build(); -Sensor* buildTankSensor(const char* id) { - DeviceConfig* device = &DeviceConfig::create(id) +auto tankSensor = Builder::instance(new Sensor{ "Level", OIL_SENSOR }) + .asDevice(&DeviceConfig::create(OIL_SENSOR) .withName("Oil tank") .withManufacturer("Arduino") .withModel("Pro Mini") .withArea("Basement") - .withParent(gatewayDevice); - return Builder::instance(new Sensor{ "Level", id }) - .asDevice(device) - .withUnitMeasure("%") + .withParent(gatewayDevice)) + .withUnitMeasure("%") + .withSensorStateClass(MEASUREMENT) + .withIcon("mdi:hydraulic-oil-level") + .withValueTemplate("{{ 100 - ((value_json.sensor.value-12)|float*100/120)|round(2) }}") + .addSecondary( + Builder::instance(new Sensor{ "Depth", OIL_SENSOR }) + .withDeviceClass("distance") + .withUnitMeasure("cm") .withSensorStateClass(MEASUREMENT) - .withIcon("mdi:hydraulic-oil-level") - .withValueTemplate("{{ 100 - ((value_json.sensor.value-12)|float*100/120)|round(2) }}") - .addSecondary( - Builder::instance(new Sensor{ "Depth", id }) - .withDeviceClass("distance") - .withUnitMeasure("cm") - .withSensorStateClass(MEASUREMENT) - .withValueTemplate("{{ value_json.sensor.value }}") - .build() - ) - .addDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) - .addDiagnostic(new BatterySensor{id, "Battery level", "{{ ((value_json.sensor.diagnostic.voltage|float-4.0)|round(2)*100/2.4)|int }}"}) - .build(); -} + .withValueTemplate("{{ value_json.sensor.value }}") + .build() + ) + .addDiagnostic(new VoltageSensor{OIL_SENSOR, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) + .addDiagnostic(new BatterySensor{OIL_SENSOR, "Battery level", "{{ ((value_json.sensor.diagnostic.voltage|float-4.0)|round(2)*100/2.4)|int }}"}) + .build(); struct PollinSwitch : Switch { constexpr static const char* man = "Pollin"; @@ -143,8 +137,3 @@ Command* commands[] = { new PollinSwitch{"00011", 4, "homebox"}, new PollinSwitch{"11111", 4, "Train", "Playroom"} }; - -Sensor* sensors[] = { - buildRoomSensor("4"), - buildTankSensor("7") -}; diff --git a/lib/Tiny/ContactSensor.h b/lib/Tiny/ContactSensor.h index dd3e702..ab15b29 100644 --- a/lib/Tiny/ContactSensor.h +++ b/lib/Tiny/ContactSensor.h @@ -6,9 +6,7 @@ class ContactSensor: public TinySensor { SensorType sensorType = CONTACT; public: - ContactSensor(short id) : - TinySensor(id) { - } + ContactSensor(SensorId id) : TinySensor(id) {} void sendStateAndVoltage(bool state) { sendInfo(ID(id) | VCC(readVcc()) | STATE(!state) | TYPE(sensorType)); diff --git a/lib/Tiny/GenericSensor.h b/lib/Tiny/GenericSensor.h index 2522267..110a9e0 100644 --- a/lib/Tiny/GenericSensor.h +++ b/lib/Tiny/GenericSensor.h @@ -6,9 +6,7 @@ class GenericSensor : public TinySensor { SensorType sensorType = GENERIC; public: - GenericSensor(short id) : - TinySensor(id) { - } + GenericSensor(SensorId id) : TinySensor(id) {} void sendValueAndVoltage(int value) { sendInfo(ID(id) | VCC(readVcc()) | VALUE(value) | TYPE(sensorType)); diff --git a/lib/Tiny/HumiditySensor.h b/lib/Tiny/HumiditySensor.h index 44a7db1..de59f4f 100644 --- a/lib/Tiny/HumiditySensor.h +++ b/lib/Tiny/HumiditySensor.h @@ -6,9 +6,7 @@ class HumiditySensor : public TinySensor { SensorType sensorType = HUMIDITY; public: - HumiditySensor(short id) : - TinySensor(id) { - } + HumiditySensor(SensorId id) : TinySensor(id) {} void sendHumidityAndVoltage(int humidity) { sendInfo(ID(id) | VCC(readVcc()) | HUMIDITY(humidity) | TYPE(sensorType)); diff --git a/lib/Tiny/TemperatureSensor.h b/lib/Tiny/TemperatureSensor.h index f3669e7..3d49bce 100644 --- a/lib/Tiny/TemperatureSensor.h +++ b/lib/Tiny/TemperatureSensor.h @@ -6,9 +6,7 @@ class TemperatureSensor : public TinySensor { SensorType sensorType = TEMPERATURE; public: - TemperatureSensor(short id) : - TinySensor(id) { - } + TemperatureSensor(SensorId id) : TinySensor(id) {} void sendTempAndVoltage(int temp) { sendInfo(ID(id) | VCC(readVcc()) | TEMP(temp) | TYPE(sensorType)); diff --git a/lib/Tiny/Tiny.h b/lib/Tiny/Tiny.h index b2825c7..a7cae64 100644 --- a/lib/Tiny/Tiny.h +++ b/lib/Tiny/Tiny.h @@ -28,13 +28,28 @@ enum SensorType : unsigned short { CONTACT = 7 }; -enum SensorId : unsigned short { - WINDOW1 = 1, - WINDOW2 = 2, - WATER_SENSOR = 3, - TEMP_SENSOR = 4, - LIGHT_SENSOR = 5, - MOVEMENT_SENSOR = 6, - OIL_SENSOR = 7, - PRESENCE_SENSOR = 8 -}; +class SensorId { + uint8_t value = 0; + char strValue[4]; + +public: + SensorId(uint8_t id) { + value = id; + snprintf(strValue, 4, "%d", value); + } + + operator uint8_t() { + return value; + } + + operator const char*() { + return strValue; + } +} +WINDOW1(1), +WINDOW2(2), +WATER_SENSOR(3), +TEMP_SENSOR(4), +LIGHT_SENSOR(5), +MOVEMENT_SENSOR(6), +OIL_SENSOR(7); diff --git a/lib/Tiny/TinySensor.h b/lib/Tiny/TinySensor.h index 8864fa5..673c160 100644 --- a/lib/Tiny/TinySensor.h +++ b/lib/Tiny/TinySensor.h @@ -7,7 +7,7 @@ using TinySwitch::sendInfo; class TinySensor { protected: - short id; + uint8_t id; long readVcc() { // Read 1.1V reference against AVcc @@ -33,7 +33,7 @@ protected: } public: - TinySensor(short id) { + TinySensor(SensorId id) { this->id = id; } }; diff --git a/test/native/test_sensor_builder/sensor_builder.cpp b/test/native/test_sensor_builder/sensor_builder.cpp index f011daa..7b094f7 100644 --- a/test/native/test_sensor_builder/sensor_builder.cpp +++ b/test/native/test_sensor_builder/sensor_builder.cpp @@ -62,21 +62,21 @@ void test_overflow_voltage(void) { void test_temp_sensor(void) { StaticJsonDocument<200> jsonDoc; - unsigned long value = ID(SensorId::TEMP_SENSOR) | TEMP(210) | TYPE(SensorType::TEMPERATURE); + unsigned long value = ID(TEMP_SENSOR) | TEMP(210) | TYPE(SensorType::TEMPERATURE); TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); JsonObject sensor = jsonDoc["sensor"]; - TEST_ASSERT_EQUAL(SensorId::TEMP_SENSOR, sensor["id"]); + TEST_ASSERT_EQUAL(TEMP_SENSOR, sensor["id"]); TEST_ASSERT_EQUAL(21, sensor["temperature"]); } void test_temp_sensor_with_voltage(void) { StaticJsonDocument<200> jsonDoc; - unsigned long value = ID(SensorId::TEMP_SENSOR) | TEMP(320) | TYPE(SensorType::TEMPERATURE) | VCC(2847L); + unsigned long value = ID(TEMP_SENSOR) | TEMP(320) | TYPE(SensorType::TEMPERATURE) | VCC(2847L); TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); JsonObject sensor = jsonDoc["sensor"]; - TEST_ASSERT_EQUAL(SensorId::TEMP_SENSOR, sensor["id"]); + TEST_ASSERT_EQUAL(TEMP_SENSOR, sensor["id"]); TEST_ASSERT_EQUAL(32, sensor["temperature"]); JsonObject diagnostic = sensor["diagnostic"]; @@ -85,21 +85,21 @@ void test_temp_sensor_with_voltage(void) { void test_oil_sensor(void) { StaticJsonDocument<200> jsonDoc; - unsigned long value = ID(SensorId::OIL_SENSOR) | VALUE(150) | TYPE(SensorType::GENERIC); + unsigned long value = ID(OIL_SENSOR) | VALUE(150) | TYPE(SensorType::GENERIC); TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); JsonObject sensor = jsonDoc["sensor"]; - TEST_ASSERT_EQUAL(SensorId::OIL_SENSOR, sensor["id"]); + TEST_ASSERT_EQUAL(OIL_SENSOR, sensor["id"]); TEST_ASSERT_EQUAL(150, sensor["value"]); } void test_oil_sensor_with_voltage(void) { StaticJsonDocument<200> jsonDoc; - unsigned long value = ID(SensorId::OIL_SENSOR) | TEMP(200) | TYPE(SensorType::GENERIC) | VCC(2847L); + unsigned long value = ID(OIL_SENSOR) | TEMP(200) | TYPE(SensorType::GENERIC) | VCC(2847L); TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); JsonObject sensor = jsonDoc["sensor"]; - TEST_ASSERT_EQUAL(SensorId::OIL_SENSOR, sensor["id"]); + TEST_ASSERT_EQUAL(OIL_SENSOR, sensor["id"]); TEST_ASSERT_EQUAL(200, sensor["value"]); JsonObject diagnostic = sensor["diagnostic"]; From f245443a4d5fc250e39fb9ad7ccc7f96d1f60fb7 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Wed, 1 Oct 2025 18:11:01 +0200 Subject: [PATCH 04/10] add presence sensor into rc-gateway --- include/devices.h | 13 +++++++ include/huzzah.h | 2 +- lib/Tiny/Tiny.h | 3 +- platformio.ini | 2 +- .../test_sensor_builder/sensor_builder.cpp | 39 +++++++++++++++---- 5 files changed, 49 insertions(+), 10 deletions(-) diff --git a/include/devices.h b/include/devices.h index 27e7696..bb2068c 100644 --- a/include/devices.h +++ b/include/devices.h @@ -50,6 +50,19 @@ auto tankSensor = Builder::instance(new Sensor{ "Level", OIL_SENSOR }) .addDiagnostic(new BatterySensor{OIL_SENSOR, "Battery level", "{{ ((value_json.sensor.diagnostic.voltage|float-4.0)|round(2)*100/2.4)|int }}"}) .build(); +auto presenceTracker = Builder::instance(PRESENCE_SENSOR) + .asDevice(&DeviceConfig::create(PRESENCE_SENSOR) + .withName("Kid presence") + .withManufacturer("Atmel") + .withModel("AtTiny85") + .withParent(gatewayDevice)) + .withValueTemplate("{{ value_json.sensor.state }}") + .addDiagnostic(new VoltageSensor{PRESENCE_SENSOR, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) + .addDiagnostic(new BatterySensor{PRESENCE_SENSOR, "Battery level", "{{ ((value_json.sensor.diagnostic.voltage|float-2.5)|round(2)*100/2)|int }}"}) + .withOffDelaySeconds(5*60) + .withDeviceClass("presence") + .build(); + struct PollinSwitch : Switch { constexpr static const char* man = "Pollin"; const char* group; diff --git a/include/huzzah.h b/include/huzzah.h index 60b31ad..7e76092 100644 --- a/include/huzzah.h +++ b/include/huzzah.h @@ -98,7 +98,7 @@ namespace Board { void parseSensors(JsonDocument& jsonDoc, char* message) { JsonObjectConst json = jsonDoc["sensor"]; string id = to_string((unsigned int)json["id"]); - auto sensor = Sensor::mapSensors[id]; + auto sensor = GenericSensor::mapSensors[id]; if (sensor) sensor->updateState(message); } diff --git a/lib/Tiny/Tiny.h b/lib/Tiny/Tiny.h index a7cae64..1c5e706 100644 --- a/lib/Tiny/Tiny.h +++ b/lib/Tiny/Tiny.h @@ -52,4 +52,5 @@ WATER_SENSOR(3), TEMP_SENSOR(4), LIGHT_SENSOR(5), MOVEMENT_SENSOR(6), -OIL_SENSOR(7); +OIL_SENSOR(7), +PRESENCE_SENSOR(8); diff --git a/platformio.ini b/platformio.ini index 18f0ae1..f7be12c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,7 +33,7 @@ framework = arduino lib_deps = ${env.lib_deps} arkhipenko/TaskScheduler@^3.8.5 - https://git.hodos.ro/libraries/ha-mqtt.git@^1.10.0 + https://git.hodos.ro/libraries/ha-mqtt.git@^1.11.0 https://git.hodos.ro/libraries/wifi.git@^2.0.0 esphome/ESPAsyncWebServer-esphome@^3.4.0 upload_port = 192.168.6.161 diff --git a/test/native/test_sensor_builder/sensor_builder.cpp b/test/native/test_sensor_builder/sensor_builder.cpp index 7b094f7..97d22b6 100644 --- a/test/native/test_sensor_builder/sensor_builder.cpp +++ b/test/native/test_sensor_builder/sensor_builder.cpp @@ -12,7 +12,7 @@ void tearDown(void) { void test_unknown_sensor_type(void) { StaticJsonDocument<200> jsonDoc; unsigned long value = TYPE(0); - TEST_ASSERT_EQUAL(false, buildSensorJson(value, jsonDoc)); + TEST_ASSERT_FALSE(buildSensorJson(value, jsonDoc)); } void test_max_temp(void) { @@ -45,7 +45,7 @@ void test_overflow_value(void) { void test_max_voltage(void) { StaticJsonDocument<200> jsonDoc; unsigned long value = VCC(8191) | TYPE(SensorType::GENERIC); - TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); + TEST_ASSERT_TRUE(buildSensorJson(value, jsonDoc)); JsonObject diagnostic = jsonDoc["sensor"]["diagnostic"]; TEST_ASSERT_EQUAL(8.191, diagnostic["voltage"]); @@ -54,7 +54,7 @@ void test_max_voltage(void) { void test_overflow_voltage(void) { StaticJsonDocument<200> jsonDoc; unsigned long value = VCC(8192) | TYPE(SensorType::GENERIC); - TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); + TEST_ASSERT_TRUE(buildSensorJson(value, jsonDoc)); JsonObject diagnostic = jsonDoc["sensor"]["diagnostic"]; TEST_ASSERT_EQUAL(0, diagnostic["voltage"]); @@ -63,7 +63,7 @@ void test_overflow_voltage(void) { void test_temp_sensor(void) { StaticJsonDocument<200> jsonDoc; unsigned long value = ID(TEMP_SENSOR) | TEMP(210) | TYPE(SensorType::TEMPERATURE); - TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); + TEST_ASSERT_TRUE(buildSensorJson(value, jsonDoc)); JsonObject sensor = jsonDoc["sensor"]; TEST_ASSERT_EQUAL(TEMP_SENSOR, sensor["id"]); @@ -73,7 +73,7 @@ void test_temp_sensor(void) { void test_temp_sensor_with_voltage(void) { StaticJsonDocument<200> jsonDoc; unsigned long value = ID(TEMP_SENSOR) | TEMP(320) | TYPE(SensorType::TEMPERATURE) | VCC(2847L); - TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); + TEST_ASSERT_TRUE(buildSensorJson(value, jsonDoc)); JsonObject sensor = jsonDoc["sensor"]; TEST_ASSERT_EQUAL(TEMP_SENSOR, sensor["id"]); @@ -86,7 +86,7 @@ void test_temp_sensor_with_voltage(void) { void test_oil_sensor(void) { StaticJsonDocument<200> jsonDoc; unsigned long value = ID(OIL_SENSOR) | VALUE(150) | TYPE(SensorType::GENERIC); - TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); + TEST_ASSERT_TRUE(buildSensorJson(value, jsonDoc)); JsonObject sensor = jsonDoc["sensor"]; TEST_ASSERT_EQUAL(OIL_SENSOR, sensor["id"]); @@ -96,7 +96,7 @@ void test_oil_sensor(void) { void test_oil_sensor_with_voltage(void) { StaticJsonDocument<200> jsonDoc; unsigned long value = ID(OIL_SENSOR) | TEMP(200) | TYPE(SensorType::GENERIC) | VCC(2847L); - TEST_ASSERT_EQUAL(true, buildSensorJson(value, jsonDoc)); + TEST_ASSERT_TRUE(buildSensorJson(value, jsonDoc)); JsonObject sensor = jsonDoc["sensor"]; TEST_ASSERT_EQUAL(OIL_SENSOR, sensor["id"]); @@ -106,6 +106,29 @@ void test_oil_sensor_with_voltage(void) { TEST_ASSERT_EQUAL(2.847, diagnostic["voltage"]); } +void test_presence_sensor(void) { + StaticJsonDocument<200> jsonDoc; + unsigned long value = ID(PRESENCE_SENSOR) | STATE(1) | TYPE(SensorType::CONTACT); + TEST_ASSERT_TRUE(buildSensorJson(value, jsonDoc)); + + JsonObject sensor = jsonDoc["sensor"]; + TEST_ASSERT_EQUAL(PRESENCE_SENSOR, sensor["id"]); + TEST_ASSERT_EQUAL_STRING("ON", sensor["state"]); +} + +void test_presence_sensor_with_voltage(void) { + StaticJsonDocument<200> jsonDoc; + unsigned long value = ID(PRESENCE_SENSOR) | STATE(0) | TYPE(SensorType::CONTACT) | VCC(3847L); + TEST_ASSERT_TRUE(buildSensorJson(value, jsonDoc)); + + JsonObject sensor = jsonDoc["sensor"]; + TEST_ASSERT_EQUAL(PRESENCE_SENSOR, sensor["id"]); + TEST_ASSERT_EQUAL_STRING("OFF", sensor["state"]); + + JsonObject diagnostic = sensor["diagnostic"]; + TEST_ASSERT_EQUAL(3.847, diagnostic["voltage"]); +} + int main(int argc, char **argv) { UNITY_BEGIN(); RUN_TEST(test_unknown_sensor_type); @@ -118,6 +141,8 @@ int main(int argc, char **argv) { RUN_TEST(test_temp_sensor_with_voltage); RUN_TEST(test_oil_sensor); RUN_TEST(test_oil_sensor_with_voltage); + RUN_TEST(test_presence_sensor); + RUN_TEST(test_presence_sensor_with_voltage); UNITY_END(); return 0; From 82df106df1aa9d36541c1c4ad9e336b72bdbbba3 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Thu, 2 Oct 2025 16:34:51 +0200 Subject: [PATCH 05/10] use uppercase ON/OFF when reading contact sensor type - it aligns with home assistant --- include/TinyComponent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/TinyComponent.h b/include/TinyComponent.h index 725fce1..999d5f7 100644 --- a/include/TinyComponent.h +++ b/include/TinyComponent.h @@ -24,7 +24,7 @@ bool buildSensorJson(unsigned long value, JsonDocument& jsonDoc) { sensor["humidity"] = (float)GET_HUMIDITY(value) / 10; break; case SensorType::CONTACT: - sensor["state"] = GET_STATE(value) ? "on" : "off"; + sensor["state"] = GET_STATE(value) ? "ON" : "OFF"; break; default: return false; From 7c9678a57cf3016a8f48c0de77005fc540a88fcf Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Thu, 2 Oct 2025 16:29:54 +0200 Subject: [PATCH 06/10] fix bug where voltage is always sent --- devices/presence_ticker/src/presence_ticker.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/devices/presence_ticker/src/presence_ticker.cpp b/devices/presence_ticker/src/presence_ticker.cpp index 172d121..3ccc500 100644 --- a/devices/presence_ticker/src/presence_ticker.cpp +++ b/devices/presence_ticker/src/presence_ticker.cpp @@ -22,15 +22,16 @@ void setup() { } void loop() { - if (counter % SEND_VCC_INTERVAL == 0) { - sensor.sendStateAndVoltage(true); - counter = 0; + if (counter == 0) { + sensor.sendStateAndVoltage(false); } else if (counter % SEND_INTERVAL == 0) { - sensor.sendState(true); + sensor.sendState(false); } TinyPower::sleep(); } ISR(WDT_vect) { - counter++; + if (++counter % SEND_VCC_INTERVAL == 0) { + counter = 0; + } } From 6edcafe27c2bfa45a67e2bdd6a71e2d4546e7fe5 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Fri, 3 Oct 2025 10:24:33 +0200 Subject: [PATCH 07/10] adjust battery percent formula: max 3.3V and min 2.7V (the minimum voltage required by ATTiny85) --- include/devices.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/devices.h b/include/devices.h index bb2068c..35acd0c 100644 --- a/include/devices.h +++ b/include/devices.h @@ -58,7 +58,7 @@ auto presenceTracker = Builder::instance(PRESENCE_SENSOR) .withParent(gatewayDevice)) .withValueTemplate("{{ value_json.sensor.state }}") .addDiagnostic(new VoltageSensor{PRESENCE_SENSOR, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) - .addDiagnostic(new BatterySensor{PRESENCE_SENSOR, "Battery level", "{{ ((value_json.sensor.diagnostic.voltage|float-2.5)|round(2)*100/2)|int }}"}) + .addDiagnostic(new BatterySensor{PRESENCE_SENSOR, "Battery level", "{{ ((value_json.sensor.diagnostic.voltage|float-2.7)|round(2)*100/0.6)|int }}"}) .withOffDelaySeconds(5*60) .withDeviceClass("presence") .build(); From 475a58d8a46ebdd4a38e7c3e21a42e6ebf7f49e5 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Fri, 3 Oct 2025 10:32:14 +0200 Subject: [PATCH 08/10] upgrade ha-mqtt to v1.12 that changed path for state and command topics --- include/huzzah.h | 6 ++++-- platformio.ini | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/huzzah.h b/include/huzzah.h index 7e76092..6f2ac7d 100644 --- a/include/huzzah.h +++ b/include/huzzah.h @@ -13,6 +13,8 @@ Scheduler ts; #include "wifi.h" #include "webserver.h" +#define RAW_TOPIC MAIN_DEVICE_ID "/raw" + namespace Board { Task tReadCommand(TASK_IMMEDIATE, TASK_FOREVER, [](){ @@ -105,7 +107,7 @@ namespace Board { void publishResponse(JsonDocument& jsonDoc) { char message[255]; serializeJson(jsonDoc, message); - Mqtt::publish("homeassistant/sensor/rc-gateway/raw", message); + Mqtt::publish(RAW_TOPIC, message); if (jsonDoc.containsKey("rcSwitch")) parseSwitches(jsonDoc); if (jsonDoc.containsKey("sensor")) parseSensors(jsonDoc, message); } @@ -113,7 +115,7 @@ namespace Board { void handleJsonError(JsonDocument& jsonError) { char message[255]; serializeJson(jsonError, message); - Mqtt::publish("homeassistant/sensor/rc-gateway/raw", message); + Mqtt::publish(RAW_TOPIC, message); } } // {"rcSwitch":{"protocol":1,"state":false,"group":"11111","channel":4}} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index f7be12c..f7fb245 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,7 +33,7 @@ framework = arduino lib_deps = ${env.lib_deps} arkhipenko/TaskScheduler@^3.8.5 - https://git.hodos.ro/libraries/ha-mqtt.git@^1.11.0 + https://git.hodos.ro/libraries/ha-mqtt.git@^1.12.0 https://git.hodos.ro/libraries/wifi.git@^2.0.0 esphome/ESPAsyncWebServer-esphome@^3.4.0 upload_port = 192.168.6.161 From 346483c43cf34d80efa603e7685e87af72056264 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Fri, 3 Oct 2025 11:02:59 +0200 Subject: [PATCH 09/10] renamed project --- devices/{presence_ticker => presence_sensor}/.gitignore | 0 devices/{presence_ticker => presence_sensor}/README.md | 0 .../docs/presence_sensor.fzz | 0 .../docs/presence_sensor_bb.png | 0 devices/{presence_ticker => presence_sensor}/include/README | 0 devices/{presence_ticker => presence_sensor}/lib/README | 0 devices/{presence_ticker => presence_sensor}/platformio.ini | 0 .../src/presence_sensor.cpp} | 0 devices/{presence_ticker => presence_sensor}/test/README | 0 rc-gateway.code-workspace | 4 ++-- 10 files changed, 2 insertions(+), 2 deletions(-) rename devices/{presence_ticker => presence_sensor}/.gitignore (100%) rename devices/{presence_ticker => presence_sensor}/README.md (100%) rename devices/{presence_ticker => presence_sensor}/docs/presence_sensor.fzz (100%) rename devices/{presence_ticker => presence_sensor}/docs/presence_sensor_bb.png (100%) rename devices/{presence_ticker => presence_sensor}/include/README (100%) rename devices/{presence_ticker => presence_sensor}/lib/README (100%) rename devices/{presence_ticker => presence_sensor}/platformio.ini (100%) rename devices/{presence_ticker/src/presence_ticker.cpp => presence_sensor/src/presence_sensor.cpp} (100%) rename devices/{presence_ticker => presence_sensor}/test/README (100%) diff --git a/devices/presence_ticker/.gitignore b/devices/presence_sensor/.gitignore similarity index 100% rename from devices/presence_ticker/.gitignore rename to devices/presence_sensor/.gitignore diff --git a/devices/presence_ticker/README.md b/devices/presence_sensor/README.md similarity index 100% rename from devices/presence_ticker/README.md rename to devices/presence_sensor/README.md diff --git a/devices/presence_ticker/docs/presence_sensor.fzz b/devices/presence_sensor/docs/presence_sensor.fzz similarity index 100% rename from devices/presence_ticker/docs/presence_sensor.fzz rename to devices/presence_sensor/docs/presence_sensor.fzz diff --git a/devices/presence_ticker/docs/presence_sensor_bb.png b/devices/presence_sensor/docs/presence_sensor_bb.png similarity index 100% rename from devices/presence_ticker/docs/presence_sensor_bb.png rename to devices/presence_sensor/docs/presence_sensor_bb.png diff --git a/devices/presence_ticker/include/README b/devices/presence_sensor/include/README similarity index 100% rename from devices/presence_ticker/include/README rename to devices/presence_sensor/include/README diff --git a/devices/presence_ticker/lib/README b/devices/presence_sensor/lib/README similarity index 100% rename from devices/presence_ticker/lib/README rename to devices/presence_sensor/lib/README diff --git a/devices/presence_ticker/platformio.ini b/devices/presence_sensor/platformio.ini similarity index 100% rename from devices/presence_ticker/platformio.ini rename to devices/presence_sensor/platformio.ini diff --git a/devices/presence_ticker/src/presence_ticker.cpp b/devices/presence_sensor/src/presence_sensor.cpp similarity index 100% rename from devices/presence_ticker/src/presence_ticker.cpp rename to devices/presence_sensor/src/presence_sensor.cpp diff --git a/devices/presence_ticker/test/README b/devices/presence_sensor/test/README similarity index 100% rename from devices/presence_ticker/test/README rename to devices/presence_sensor/test/README diff --git a/rc-gateway.code-workspace b/rc-gateway.code-workspace index db5bd71..91e8b64 100644 --- a/rc-gateway.code-workspace +++ b/rc-gateway.code-workspace @@ -13,8 +13,8 @@ "path": "devices/oil_sensor" }, { - "name": "presence_ticker", - "path": "devices/presence_ticker" + "name": "presence_sensor", + "path": "devices/presence_sensor" } ], "settings": { From 7a220bcc38f77672d3811e105932e1e8d8ba4c84 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Sun, 5 Oct 2025 09:49:54 +0200 Subject: [PATCH 10/10] fix bug where voltage was sent every 6 minutes instead of 6 hours --- devices/presence_sensor/src/presence_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devices/presence_sensor/src/presence_sensor.cpp b/devices/presence_sensor/src/presence_sensor.cpp index 3ccc500..a11a866 100644 --- a/devices/presence_sensor/src/presence_sensor.cpp +++ b/devices/presence_sensor/src/presence_sensor.cpp @@ -3,7 +3,7 @@ #include "ContactSensor.h" #define MINUTES(value) (uint16_t)(value*60/8) // minutes*60(seconds)/8s(WDT) -#define HOURS(value) MINUTES(value)*60 +#define HOURS(value) (uint16_t)(MINUTES(value)*60) #define SEND_INTERVAL MINUTES(2) #define SEND_VCC_INTERVAL HOURS(6)