MyExtension
-
Hi,
Even if I'm not very good with programming and terrible with C/C++, I tried to put together a sort of extension to the core engine providing additional functionalities but without impacting the core code. The idea was to embed within the sketch (better if in the future in a library), a sort of additional service taking care of common tasks automatically so to simplify the development. The "extension" hooks up (well, manually) into setup(), presentation(), loop() and receive(), present itself as a child-id node type S_CUSTOM to the controller and communicates with it with V_CUSTOM messages. A summary of what it does is:
- Report battery level periodically and automatically, the period is configurable
- Able to reboot the board on demand with the default bootloader, by connecting one of the pins to RST
- Support multiple sleep mode: no sleep, sleep once and sleep cycle
- Sleep mode and sleep time can be configured remotely with V_CUSTOM messages
- Sleep mode and sleep time are made persistent within the EEPROM once changed
- A pin can be configure to wake up a sleeping board when connected to ground
- Can report battery level on demand
- Allow changing the node_id remotely
- Stop sleeping when a WAKEUP message is sent just after a smart sleeping cycle
- Calculate battery from internal vcc without requiring a pin and the resistors
- Can calculate the battery percentage using custom boundaries
- Return the battery voltage through the custom service
A message to the service would look like 254;200;2;0;48;HELLO and the language (kind of inspired by the old ciseco LLAP) used in the payload of the S_CUSTOM message is the following:
- BATT: return the battery level
- HELLO: hello request
- NOSLEEP: set and save sleeping mode to no sleep
- SLEEPONCE: set and save sleeping mode to sleep once and go to sleep just after
- SLEEPCYCLE: set and save sleeping mode to sleep cycle and got to sleep just after
- SLEEPnnnX: set and save the sleep interval to nnn where X is S=Seconds, M=mins, H=Hours, D=Days. E.g. SLEEP010M would be 10 minutes
- MYxxx: change the node id to the provided one. E.g. MY025: change the node id to 25. Requires a reboot/restart
- WAKEUP: when received after a sleeping cycle, abort cycle sleeping since the controller needs us awake
- REBOOT: reboot the board
- RESET: clear the user's eeprom
- VERSION: send back the extension's version
Now, this is just a proof of concept not suitable for production but I would like to hear your feedback. Does the idea make some sense? Do you see anything that could be interesting to have in the core engine instead? Should I go on developing the idea or trash everything?
Thanks in advance!
// Enable debug prints to serial monitor #define MY_DEBUG // Enable and select radio type attached #define MY_RADIO_NRF24 //#define MY_RADIO_RFM69 #include <MySensors.h> /* * MyExtension */ // sample request messages /* 254;200;2;0;48;BATT 254;200;2;0;48;HELLO 254;200;2;0;48;SLEEP005S 254;200;2;0;48;WAKEUP 254;200;2;0;48;NOSLEEP 254;200;2;0;48;SLEEPONCE 254;200;2;0;48;SLEEPCYCLE 254;200;2;0;48;SLEEP010M 254;200;2;0;48;REBOOT 254;200;2;0;48;VERSION */ // define supported sleeping mode #define MY_EXT_NO_SLEEP 0 #define MY_EXT_SLEEP_CYCLE 1 #define MY_EXT_SLEEP_ONCE 2 #define MY_EXT_SLEEP_UNIT_S 0 #define MY_EXT_SLEEP_UNIT_M 1 #define MY_EXT_SLEEP_UNIT_H 2 #define MY_EXT_SLEEP_UNIT_D 3 // define eeprom addresses #define MY_EXT_EEPROM_SLEEP_SAVED 0 #define MY_EXT_EEPROM_SLEEP_MODE 1 #define MY_EXT_EEPROM_SLEEP_TIME_MAJOR 2 #define MY_EXT_EEPROM_SLEEP_TIME_MINOR 3 #define MY_EXT_EEPROM_SLEEP_UNIT 4 // version #define MY_EXT_VERSION 1.0 /* * configuration settings */ // define if the board has to sleep every time entering loop(). It can be MY_EXT_NO_SLEEP (no sleep), MY_EXT_SLEEP_ONCE (sleep only once) or MY_EXT_SLEEP_CYCLE (sleep at every cycle) #define MY_EXT_SLEEP_MODE MY_EXT_NO_SLEEP // define for how long the board will sleep #define MY_EXT_SLEEP_TIME 0 // define the unit of MY_EXT_SLEEP_TIME. It can be MY_EXT_SLEEP_UNIT_S (seconds), MY_EXT_SLEEP_UNIT_M (minutes), MY_EXT_SLEEP_UNIT_H (hours), MY_EXT_SLEEP_UNIT_D (days) #define MY_EXT_SLEEP_UNIT MY_EXT_SLEEP_UNIT_S // if defined, allow modifying sleep mode and interval remotely with NOSLEEP/SLEEPONCE/SLEEPCYCLE and SLEEPnnnX #define MY_EXT_SLEEP_REMOTE_CONFIGURATION // if defined, reset the board with the configured pin when receiving a REBOOT message #define MY_EXT_SOFT_REBOOT // the pin to connect to the RST pin to reboot the board #define MY_EXT_SOFT_REBOOT_PIN 4 // if defined, wakeup from sleep when the configured pin changes #define MY_EXT_SLEEP_INTERRUPT // if defined, when waking up from the interrupt, the board stops sleeping. Disable it when attaching e.g. a motion sensor #define MY_EXT_SLEEP_INTERRUPT_ABORT_SLEEP // the pin to connect sued for waking up a sleeping board. Can be either 2, 3 or a different pin provided is reconfigured to act as an interrupt pin #define MY_EXT_SLEEP_INTERRUPT_PIN 3 // the interrupt mode. Can be either FALLING or RISING #define MY_EXT_SLEEP_INTERRUPT_MODE FALLING // the expected vcc when the batter is fully charged, used to calculate the percentage #define MY_EXT_BATTERY_MAX 3.3 // the expected vcc when the batter is fully discharged, used to calculate the percentage #define MY_EXT_BATTERY_MIN 2.8 // if defined, report automatically the battery level back to the controller after the given number of hours (if the board sleeping it will be reported when awake next) #define MY_EXT_REPORT_BATTERY // how frequently (in hours) to report the battery level to the controller #define MY_EXT_REPORT_BATTERY_H 1 // if defined, enable debug of the extension on serial port #define MY_EXT_DEBUG // the sensor child id used by the extension to send messages to the controller #define MY_EXT_CHILD_ID 200 /* * class definition */ class MyExtension { public: MyExtension(); // functions to be explicitely called within the sketch allow the extension hooking into the flow void setup(); void presentation(); void loop(); void receive(const MyMessage &); private: // define if and how to sleep int _sleep_mode = MY_EXT_SLEEP_MODE; // define for how long to sleep (time) int _sleep_time = MY_EXT_SLEEP_TIME; // define for how long to sleep (unit) int _sleep_unit = MY_EXT_SLEEP_UNIT; // store last time the battery level has been reported long _battery_last_reported = millis(); // implement custom sleep void _sleep(); // process an incoming message/command void _process(const char *); // the container of the message used to respond to any request MyMessage msg; }; /* * implementation */ // constructor inline MyExtension::MyExtension() { // initialize the response message container msg = MyMessage(MY_EXT_CHILD_ID,V_CUSTOM); } // what to do when called within setup() inline void MyExtension::setup() { #ifdef MY_EXT_SOFT_REBOOT // setup the reboot pin digitalWrite(MY_EXT_SOFT_REBOOT_PIN,HIGH); pinMode(MY_EXT_SOFT_REBOOT_PIN,OUTPUT); #endif #ifdef MY_EXT_SLEEP_INTERRUPT // setup the sleep interrupt pin pinMode(MY_EXT_SLEEP_INTERRUPT_PIN,INPUT); digitalWrite(MY_EXT_SLEEP_INTERRUPT_PIN,HIGH ? MY_EXT_SLEEP_INTERRUPT_MODE == FALLING : LOW); #endif #ifdef MY_EXT_SLEEP_REMOTE_CONFIGURATION // restore sleep configuration from eeprom if (loadState(MY_EXT_EEPROM_SLEEP_SAVED) == 1) { // sleep settings found in the eeprom, restore them _sleep_mode = loadState(MY_EXT_EEPROM_SLEEP_MODE); _sleep_time = loadState(MY_EXT_EEPROM_SLEEP_TIME_MINOR); int major = loadState(MY_EXT_EEPROM_SLEEP_TIME_MAJOR); if (major == 1) _sleep_time = _sleep_time + 250; if (major == 2) _sleep_time = _sleep_time + 250*2; if (major == 3) _sleep_time = _sleep_time + 250*3; _sleep_unit = loadState(MY_EXT_EEPROM_SLEEP_UNIT); #ifdef MY_EXT_DEBUG Serial.print("Restored from eeprom sleep settings: sleep_mode="); Serial.print(_sleep_mode); Serial.print(" sleep_time="); Serial.print(_sleep_time); Serial.print(" sleep_unit="); Serial.println(_sleep_unit); #endif } #endif } // what to do when called within presentation() inline void MyExtension::presentation() { // present the extension as a custom sensor to the controller present(MY_EXT_CHILD_ID,S_CUSTOM); #ifdef MY_EXT_REPORT_BATTERY // report battery level MyExtension::_process("BATT"); #endif } // what to do when called within loop() inline void MyExtension::loop() { // continue/start sleeping if requested if (_sleep_mode != MY_EXT_NO_SLEEP) MyExtension::_sleep(); } // wrapper of smart sleep inline void MyExtension::_sleep() { // calculate the seconds to sleep long sleep_sec = _sleep_time; if (_sleep_unit == MY_EXT_SLEEP_UNIT_M) sleep_sec = sleep_sec*60; else if (_sleep_unit == MY_EXT_SLEEP_UNIT_H) sleep_sec = sleep_sec*3600; else if (_sleep_unit == MY_EXT_SLEEP_UNIT_D) sleep_sec = sleep_sec*43200; long sleep_ms = sleep_sec*1000; #ifdef MY_EXT_DEBUG Serial.print("Going to sleep for "); Serial.print(sleep_sec); Serial.println("s"); #endif // notify the controller I'm going to sleep send(msg.set("SLEEPING")); // go in smart sleep for the requested sleep interval #ifdef MY_EXT_SLEEP_INTERRUPT // sleep and wakeup when the interrupt triggers int ret = sleep(digitalPinToInterrupt(MY_EXT_SLEEP_INTERRUPT_PIN),MY_EXT_SLEEP_INTERRUPT_MODE,sleep_ms,true); #else // sleep without any interrupt int ret = sleep(sleep_ms,true); #endif #ifdef MY_EXT_SLEEP_INTERRUPT if (ret > -1) { #ifdef MY_EXT_DEBUG Serial.println("Woke up by interrupt"); #endif #ifdef MY_EXT_SLEEP_INTERRUPT_ABORT_SLEEP // when waking up from an interrupt on the wakup pin, stop sleeping _sleep_mode = MY_EXT_NO_SLEEP; #endif // restore the sleep interrupt pin digitalWrite(MY_EXT_SLEEP_INTERRUPT_PIN,HIGH ? MY_EXT_SLEEP_INTERRUPT_MODE == FALLING : LOW); } #endif // coming out of sleep #ifdef MY_EXT_DEBUG Serial.println("Awake"); #endif // if supposed to sleep once, reset sleep mode if (_sleep_mode == MY_EXT_SLEEP_ONCE) _sleep_mode = MY_EXT_NO_SLEEP; // notify the controller I am awake send(msg.set("AWAKE")); #ifdef MY_EXT_REPORT_BATTERY if ( (millis()-_battery_last_reported)/1000/3600 > MY_EXT_REPORT_BATTERY_H) { // time to report the battery level again MyExtension::_process("BATT"); _battery_last_reported = millis(); } #endif } // process a message/command inline void MyExtension::_process(const char *message) { // BATT: return the battery level if (strcmp(message,"BATT") == 0) { // measure the provided vcc float volt = (float)hwCPUVoltage()/1000; // send the measure back with a custom message char reply[25] = "BATT"; dtostrf(volt,1,2,&reply[strlen(reply)]); send(msg.set(reply)); // calculate the percentage int percentage = 100-(MY_EXT_BATTERY_MAX-volt/(MY_EXT_BATTERY_MAX-MY_EXT_BATTERY_MIN)); if (percentage > 100) percentage = 100; if (percentage < 0) percentage = 0; // send battery level percentage sendBatteryLevel(percentage); } // HELLO: hello request else if (strcmp(message,"HELLO") == 0) { send(msg.set("HELLO")); } #ifdef MY_EXT_SLEEP_REMOTE_CONFIGURATION // NOSLEEP: set and save sleeping mode to no sleep else if (strcmp(message,"NOSLEEP") == 0) { // go to sleep _sleep_mode = MY_EXT_NO_SLEEP; // save it to the eeprom saveState(MY_EXT_EEPROM_SLEEP_SAVED,1); saveState(MY_EXT_EEPROM_SLEEP_MODE,MY_EXT_NO_SLEEP); send(msg.set(message)); } // SLEEPONCE: set and save sleeping mode to sleep once and go to sleep just after else if (strcmp(message,"SLEEPONCE") == 0) { // go to sleep _sleep_mode = MY_EXT_SLEEP_ONCE; // save it to the eeprom saveState(MY_EXT_EEPROM_SLEEP_SAVED,1); saveState(MY_EXT_EEPROM_SLEEP_MODE,MY_EXT_SLEEP_ONCE); } // SLEEPCYCLE: set and save sleeping mode to sleep cycle and got to sleep just after else if (strcmp(message,"SLEEPCYCLE") == 0) { // go to sleep _sleep_mode = MY_EXT_SLEEP_CYCLE; // save it to the eeprom saveState(MY_EXT_EEPROM_SLEEP_SAVED,1); saveState(MY_EXT_EEPROM_SLEEP_MODE,MY_EXT_SLEEP_CYCLE); } // SLEEPnnnX: set and save the sleep interval to nnn where X is S=Seconds, M=mins, H=Hours, D=Days. E.g. SLEEP010M would be 10 minutes else if (strlen(message) == 9 && strncmp("SLEEP",message,strlen("SLEEP")) == 0) { // parse and set the sleep interval int offset = 5; // extract the unit (S=secs, M=mins, H=hours, D=Days) char unit[2]; sprintf(unit,"%c",message[3+offset]); unit[1] = '\0'; if (strcmp(unit,"S") == 0) _sleep_unit = MY_EXT_SLEEP_UNIT_S; else if (strcmp(unit,"M") == 0) _sleep_unit = MY_EXT_SLEEP_UNIT_M; else if (strcmp(unit,"H") == 0) _sleep_unit = MY_EXT_SLEEP_UNIT_H; else if (strcmp(unit,"D") == 0) _sleep_unit = MY_EXT_SLEEP_UNIT_D; else return; // extract the requested time char s[4]; s[0] = message[0+offset]; s[1] = message[1+offset]; s[2] = message[2+offset]; s[3] = '\0'; _sleep_time = atoi(s); #ifdef MY_EXT_DEBUG Serial.print("Set sleep_interval to "); Serial.print(_sleep_time); Serial.print(" "); Serial.println(_sleep_unit); #endif // save it to eeprom saveState(MY_EXT_EEPROM_SLEEP_UNIT,_sleep_unit); // encode sleep time int major = 0; if (_sleep_time > 750) major = 3; else if (_sleep_time > 500) major = 2; else if (_sleep_time > 250) major = 1; int minor = _sleep_time - 250*major; saveState(MY_EXT_EEPROM_SLEEP_SAVED,1); saveState(MY_EXT_EEPROM_SLEEP_TIME_MINOR,minor); saveState(MY_EXT_EEPROM_SLEEP_TIME_MAJOR,major); // interval set, reply back with the same message to acknowledge. Wait for SLEEP to start sleeping send(msg.set(message)); } #endif // MYxxx: change the node id to the provided one. E.g. MY025: change the node id to 25. Requires a reboot/restart else if (strlen(message) == 5 && strncmp("MY",message,strlen("MY")) == 0) { // extract the node id char s[4]; s[0] = message[2]; s[1] = message[3]; s[2] = message[4]; s[3] = '\0'; int node_id = atoi(s); // Save static ID to eeprom hwWriteConfig(EEPROM_NODE_ID_ADDRESS, (uint8_t)node_id); _transportConfig.nodeId = (uint8_t)node_id; } // WAKEUP: when received after a sleeping cycle, abort cycle sleeping since the controller needs us awake else if (strcmp(message,"WAKEUP") == 0) { send(msg.set(message)); _sleep_mode = MY_EXT_NO_SLEEP; } // REBOOT: reboot the board else if (strcmp(message,"REBOOT") == 0) { #ifdef MY_EXT_SOFT_REBOOT // set the reboot pin connected to RST to low so to reboot the board send(msg.set(message)); digitalWrite(MY_EXT_SOFT_REBOOT_PIN,LOW); #endif } // RESET: clear the user's eeprom else if (strcmp(message,"RESET") == 0) { for (int i = 0; i<=255; i++) saveState(i,0xFF); send(msg.set(message)); } // VERSION: send back the extension's version else if (strcmp(message,"VERSION") == 0) { send(msg.set(MY_EXT_VERSION,1)); } } // what to do when called within receive() inline void MyExtension::receive(const MyMessage &message) { // ignore messages not for this extension if (message.sensor != MY_EXT_CHILD_ID) return; #ifdef MY_EXT_DEBUG Serial.print("Received message from="); Serial.print(message.sender); Serial.print(" command="); Serial.print(message.getCommand()); Serial.print(" type="); Serial.print(message.type); Serial.print(" payload="); Serial.println(message.getString()); #endif // parse incoming req messages if (message.getCommand() == C_REQ && message.type == V_CUSTOM) { // process only V_CUSTOM messages MyExtension::_process(message.getString()); } } // instantiate MyExtension MyExtension extension; //setup void setup() { // setup MyExtension extension.setup(); } // presentation void presentation() { // Send the sketch version information to the gateway and Controller sendSketchInfo("Test Sketch", "1.0"); // present MyExtension extension.presentation(); } // loop void loop() { // loop for MyExtension extension.loop(); } // receive void receive(const MyMessage &message) { // receive for MyExtension extension.receive(message); }
-
Most of my nodes run on battery so I can't really use this but it definitely sounds nice!
-
Hi @LastSamurai! When I put this together I had a battery powered project in mind, to actually facilitate the development of it and make interactions with sleeping sensors easier. What do you see here preventing this to work on nodes running on batteries? As said I'm both a bad programmer and new to mysensors so I wouldn't be surprised if there is something that I've missed
Thanks!
-
Well the node has to be able to answer messages from the gateway if I understand it correctly. Battery powered sensors sleep most of the time so they aren't able to receive messages. Only way around this would be something like smartsleep (which I haven't used yet and needs controllers to support it).
-
Oh I see what you are saying. This was actually the idea, to wrap around smart sleep (which plays a central role in the extension) to make easier for a controller or a user to use it and interact with the sensor. A key requirement for my project is to be able to interact with a sensor without a physical access to it so in addition to smart sleep (which just gives the possibility for a controller to send something in a short time), I needed a way to force the sensor to stop sleeping, do something (e.g. configuration change) and then putting it to sleep once again remotely.
But agree with you, in case you are not using smart sleep you will just get automatic battery level reports out of this.
Thanks!
-
Ok then it should work as you intended (if smartsleep already works without errors). Currently I don't need to do stuff like this but when I do I will come back to your library
-
Nice work but please considering giving it a better name than "MyExtension"