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

Using mDNS to connect to the MQTT broker #310

Closed rakeshpai closed 5 years ago

rakeshpai commented 7 years ago

Homie connects correctly to my MQTT broker if I use the following in my config.json

{
  mqtt: "192.168.1.33"
}

However, Homie fails to connect if I use the following:

{
  mqtt: "alfred.local"
}

I just get a series of the following in my serial monitor:

↕ Attempting to connect to MQTT...
✖ Cannot connect, reason: MQTT_CONNECT_FAILED
↕ Attempting to connect to MQTT...
✖ Cannot connect, reason: MQTT_CONNECT_FAILED

I know that my mDNS is working fine on my mqtt server. (It's avahi on Linux)

$ ping alfred.local
PING alfred.local (192.168.1.33) 56(84) bytes of data.
64 bytes from 192.168.1.33: icmp_seq=1 ttl=64 time=0.012 ms
64 bytes from 192.168.1.33: icmp_seq=2 ttl=64 time=0.022 ms
64 bytes from 192.168.1.33: icmp_seq=3 ttl=64 time=0.022 ms
...

I'd like to use mDNS to address my MQTT server from Homie, so that I don't have to set up static IPs in my router. It appears from the above that this isn't currently supported? Or am I doing something wrong?

jamesmyatt commented 7 years ago

I am interested in this too. I resorted to a static IP address for my MQTT broker.

I have a feeling that it's not so simple to get an IP address from mDNS.

bertmelis commented 7 years ago

There is a standard library for mDNS for ESP8266. So it's fairly easy to get an IP for a responding broker.

For the integration: I guess you just want to enter the hostname into the config.json and let Homie do the thinking?

jamesmyatt commented 7 years ago

@bertmelis I'm pretty sure it's not as simple as just putting the mDNS host name in config.json. It's what I tried and looks like it's what @rakeshpai tried. But certainly that's how it should work.

I think that the ESP8266mDNS library (https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266mDNS) only allows you to advertise your own IP address and respond to requests for it (as is already used by Homie) but I don't think it allows you to request the IP address for a given .local name.

rakeshpai commented 7 years ago

@bertmelis Sorry for the late reply. Missed this thread somehow.

For the integration: I guess you just want to enter the hostname into the config.json and let Homie do the thinking?

I'm not sure there can be any other way, considering that the config.json is the only way to set the mqtt server, unless I'm missing something. I don't mind resolving the IP externally (though I don't know how yet) and passing it to Homie, but I don't think it's possible to programatically pass the IP to Homie. Also, it will be very convenient if Homie does the IP resolution internally, of course. :)

bertmelis commented 7 years ago

@Nzbuu I even believe there are 2 options:

However, the real question lies in the integration. You should query for the service on every connect and for easy of use also the setup firmware packages should be updated (uibundle etc).

marvinroger commented 7 years ago

Hi,

This feature existed in Homie v1.x. With Homie v2, we're using an asynchronous Wi-Fi stack, which means we cannot use the standard MDNS.queryService function, which is a synchronous implementation. So this is unfortunately impossible until an asynchronous MDNS client is available.

cecilios commented 7 years ago

The following code works OK for mi using current Homie v2. Feel free to use it.

//---------------------------------------------------------------------------------------
void BootNormal::set_mqtt_broker_ip()
{
    //Use mDNS server for finding an MQTT broker in the LAN. If any found,
    //configure MQTT client for connecting to that broker. Otherwise,
    //use static configuration parameters, as defined in config.h

    AsyncMqttClient& client = Interface::get().getMqttClient();
    Config& cfg = Interface::get().getConfig();

    int n = find_service("mqtt","tcp");
    if (n > 0)
    {
        //use the first broker found
        IPAddress ip = MDNS.IP(0);
        uint16_t port = MDNS.port(0);
        Interface::get().getLogger() << F("Using discovered MQTT broker at ") << ip
            << F(":") << port << endl;
        client.setServer(ip, port);
    }
    else
    {
        //use fallback data in config
        client.setServer(cfg.get().mqtt.server.host,
                         cfg.get().mqtt.server.port);
        Interface::get().getLogger() << F("Using fallback MQTT data. host: ")
            << cfg.get().mqtt.server.host << F(", port: ")
            << cfg.get().mqtt.server.port << endl;
    }
}

//---------------------------------------------------------------------------------------
int BootNormal::find_service(const char* service, const char* protocol)
{
    Interface::get().getLogger() << F("Sending mDNS query. Service=") << service
        << F(", protocol=") << protocol << endl;
    int n = MDNS.queryService(service, protocol);
    if (n == 0)
    {
        Interface::get().getLogger() << F("no services found") << endl;
    }
    else
    {
        Interface::get().getLogger() << n << F(" service(s) found") << endl;
        for (int i = 0; i < n; ++i)
        {
          // Print details for each service found
          Interface::get().getLogger() << (i + 1)
            << F(": ") << MDNS.hostname(i)
            << F(" (") << MDNS.IP(i)
            << F(":") << MDNS.port(i)
            << F(")") << endl;
        }
    }
    return n;
}
lorenwest commented 6 years ago

@marvinroger you mentioned MDNS.queryService() is problematic because it's synchronous.

Do you think it's ok in this context - before MQTT is connected, or is there another issue that would cause the above solution to be unstable?

marvinroger commented 6 years ago

@lorenwest for example, I use a shutters library (https://github.com/marvinroger/arduino-shutters) that is time sensitive and that must be called regularly every few ms. If MDNS.queryService was used in the homie-esp8266 code, it might delay the loop() function for a few seconds, which would basically unsync my shutters.

There's a lot of other use cases where some code has to be called every few ms, even if the device is not yet connected to MQTT.

I don't want the framework to behave that way. As long as there's no async implementation of MDNS, it is, I believe, better to do it directly in the sketch. That way, the developer is responsible for the blocking code.

lorenwest commented 6 years ago

Gotcha - that makes sense. Keep the library clear of any sync code, and have that be the decision of the sketch author based on their needs. Thanks for the explanation.

stritti commented 5 years ago

I will close this issue as there seems to be conses.