homieiot / homie-esp8266

💡 ESP8266 framework for Homie, a lightweight MQTT convention for the IoT
http://homieiot.github.io/homie-esp8266
MIT License
1.36k stars 308 forks source link

Question: Using node handlers... not supposed to call them within the firmware? #350

Closed joelrader closed 7 years ago

joelrader commented 7 years ago

I've been playing around with Homie v2 trying to get myself acquainted with the functionality, and I'm a bit stuck at node handlers.

If I have a node statusLED that I've created and then advertised with something like statusLEDNode.advertise("on").settable(statusLEDHandler);, shouldn't I be able to call that handler by performing a statusLEDNode.setProperty("on").send("true"); call within my loopHandler?

What I'm finding is that my loopHandler is running as it should, but when I call setProperty the statusLEDHandler never runs. Is this because you're not supposed to call the code directly within the firmware, but just listen to the MQTT broker for changes on the channel? My thought was that I could set node values programmatically as well as via MQTT.

Either that or I have a bug in my code... but I've been staring at it for awhile now and don't see where my logic is off.

amayii0 commented 7 years ago

That should work: https://github.com/marvinroger/homie-esp8266/blob/develop/examples/IteadSonoffButton/IteadSonoffButton.ino#L34

Hmmm except if indeed it's expected to be raised from MQTT and no within sketch

joelrader commented 7 years ago

Maybe there's a bug in my code I'm not seeing? I'm basically just trying to use an LED as a "heartbeat" to switch every few seconds.

#include <Homie.h>

#define FW_NAME "simpleTest"
#define FW_VERSION "0.0.1"
#define FW_BRAND "Simple_IoT"

/* Magic sequence for Autodetectable Binary Upload */
const char *__FLAGGED_FW_NAME    = "\xbf\x84\xe4\x13\x54" FW_NAME    "\x93\x44\x6b\xa7\x75";
const char *__FLAGGED_FW_VERSION = "\x6a\x3f\x3e\x0e\xe1" FW_VERSION "\xb0\x30\x48\xd4\x1a";
const char *__FLAGGED_FW_BRAND   = "\xfb\x2a\xf5\x68\xc0" FW_BRAND   "\x6e\x2f\x0f\xeb\x2d";
/* End of magic sequence for Autodetectable Binary Upload */

// Using PIN 14 on ESP-8266 for general purpose LED
const int PIN_LED = 14;
bool statusLEDOn = false;

const int UPDATE_INTERVAL = 10000;
unsigned long lastUpdateTime = 0;

// Set up nodes for the keypad
HomieNode statusLEDNode("Status LED", "On/Off");

bool statusLEDHandler(const HomieRange& range, const String& value) {
  Homie.getLogger() << "Entering statusLEDHandler" << endl;  // This never runs?
  if (value != "true" && value != "false") return false;
  bool on = (value == "true");
  digitalWrite(PIN_LED, on ? HIGH : LOW);
  statusLEDNode.setProperty("on").send(value);
  Homie.getLogger() << "Status LED is " << (on ? "on" : "off") << endl;
  return true;
}

void setupHandler() {
  Homie.getLogger() << "In setupHandler" << endl;
}

void loopHandler() {
    if (millis() - lastUpdateTime >= UPDATE_INTERVAL) {
      lastUpdateTime = millis();
      statusLEDOn ^= true;

      // Code below doesn't ever kick off the statusLEDHandler?
      statusLEDNode.setProperty("on").send( (statusLEDOn ? "true" : "false") );
  }
}

bool broadcastHandler(const String& level, const String& value) {
  Homie.getLogger() << "Received broadcast level " << level << ": " << value << endl;
  return true;
}

HomieSetting<const char*> manufacturerSetting("manufacturer", "Device created by ");  // id, description

void setup() {
  Serial.begin(115200);
  Serial << endl << endl;

  // Set up LED
  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, HIGH);

  // Before Homie.setup()
  Homie_setFirmware(FW_NAME, FW_VERSION);
  Homie_setBrand(FW_BRAND);
  Homie.setBroadcastHandler(broadcastHandler);
  Homie.setSetupFunction(setupHandler).setLoopFunction(loopHandler);

  statusLEDNode.advertise("on").settable(statusLEDHandler);

  manufacturerSetting.setDefaultValue("Acme, Inc.");

  Homie.setup();
}

void loop() {
  Homie.loop();
}
joelrader commented 7 years ago

After looking at the Itead code... it appears the toggleRelay() function is what actually triggers the digital write for the physical relay. It does a setProperty call, but I don't believe that actually runs the code in the handler. Otherwise, the relay would be triggered twice. It appears the switchOnHandler only listens to the MQTT broker and then acts based upon changes.

This approach works, but would require you to duplicate code segments, assuming you want the same action to happen based upon a node change. If you want a different action to take place whether the call was made via MQTT or internally, then this would allow for that. Or, both segments could then call the same function.

My question: is this intended functionality? Or, I do see that the toggleRelay() and switchOnHandler actually toggle the relay pin opposite of each other. I'm not sure I follow the logic on that unless maybe the switch is normally tied LOW (to off?).

bertmelis commented 7 years ago

It's intended so you can separate the Homie interface from a physiczl interface (eg a button) and from the attached action (eg toggle a relay).

euphi commented 7 years ago

According to Homie convention you shall always send the Node property after receiving a set command.

For example, a kitchen-light device exposing a light node would subscribe to homie/kitchen-light/light/on/set and it would receive:

homie/kitchen-light/light/on/set ← true The device would then turn on the light, and update its on state. This provides pessimistic feedback, which is important for home automation.

homie/kitchen-light/light/on → true

You are actually doing this in your implementation of the loopHandler in the line

statusLEDNode.setProperty("on").send(value);

If this would trigger the statusLEDHandler again you would be stuck in an infinite loop....

Therefore the homie convention expects that you use the "set" topic to explicitly set a state. You can't send a set-message to yourself.

Just create a void updateLED(bool on) function that updates the state of the LED and calls statusLEDNode.setProperty("on").send(value?"true":"false");. Call this function from within your statusLEDHandler and your loopHandler.

joelrader commented 7 years ago

Thanks for the feedback, confirms my suspicion on how/when the handlers were called. I read the piece on nodes, but it wasn't quite clear how it worked in practice when I read the code examples. Depending on how I want a node to operate, I can modify how I use it.

Thanks!

joelrader commented 7 years ago

Re-opening this issue. I'm not able to get the statusLEDHandler to fire when either true or false is published to the topic. Shouldn't a message published to the Status LED/on topic trigger the handler code? I've slightly modified the code to trigger the LED, and then using HomieLogger to output to the serial monitor. Not seeing anything though. I'm probably missing something simple....

EDIT: Yup! Something simple. I was publishing messages (retained, QoS 1) to the wrong topic. RTFM says to publish changes to: homie/<dev_id>/<node>/<property>/<value>/**set**. I was publishing up one level to <value>, which obviously didn't work.