A Simple MQTT Pub/Sub Node With Arduino UNO and W5100 Ethernetshield

Introduction: A Simple MQTT Pub/Sub Node With Arduino UNO and W5100 Ethernetshield

About: I am a physician by trade. After a career in the pharmeceutical world I decided to take it a bit slower and do things I like. Other than my hobbies that involves grassroots medicine in S.E.&P Asia. I have bu...

UPDATE: new code added

Nowadays many MQTT nodes are based around the ESP8266. However far before the ESP8266 the Arduino UNO already had Ethernet capabilities with an Ethernetshield.
The aim of this instructable is to show how one can use A UNO with W5100 shield or module as an MQTT node and in the software I will show some simple techniques of how to deal with outbound MQTT messages and inbound MQTT messages. If you still have an Old ENC28J60 Ethershield, don't despair, one can use those as well. I will cover that in my next instructable.

If you know what MQTT is, go to the next Step. If not, read on
For the uninitiated a brief explanation of MQTT: MQTT is a protocol to transfer messages between devices that are connected to a LAN. Those messages are formed by a topic and a payload
A device can publish messages to the LAN and it can subscribe to messages on the LAN. Now when I say "publish to the LAN" that is a bit of a eufemism, because in fact a node will be publishing its messages and subscribing to messages from a so called MQTT broker. The broker is merely a device that will receive the published messages, keep track of what device subscribed to what topic and then send the proper message to the right device.

An MQTT broker can be housed on your own LAN and often a RaspberryPi is used for that, or a public broker can be used.

Now obviously, just building one Node is not going to do much good. Who is it going to talk to? So obviously this would fit in a bigger systems with various nodes talking to eachother and the user having a frienly interface such as OpenHab. But in the google playstore various MQTTdashboards are available as app that allow for a simple interface. When developing MQTT software, it is always smart to install mqtt-spy. It is available for several platforms and what it basically does is to show MQTT messages going around on your LAN and to allow you to manually send MQTT messages

Step 1: The Software

The node I describe has several functions:
It reads from several switches (e.g. door contacts), it reads the value of an LDR, it reads temperature and humidity from a DHT11, it reads a bell signal, and it reads the state of a relay and sends those onto the broker. It does that every 3 seconds

Publishing is done with the command: client.publish(topic,message);
The command is a bit picky with its format. It will happily publish:
client.publish("MyTopic","Hello World");
but it will not do a string variable such as:
String messagestr="Hello World";
client.publish("MyTopic",messagestr);

Often that is not a problem, but in some cases. I have used the sending of the ip number as an example of how to deal with that.

The node subscribes to only one topic and depending on the message in that topic, it performs several functions:
0- it switches a relay off
1-it switches a relay on
2-it publishes the IP address of the node
3-it publishes the state of the relay

On startup of the node, it automatically publishes the IP address as well.

Subscriptions are usually handled in a function called 'Callback' (but up to you to chose the name)
For ease I have only used one topic and commands of 1 character length.
however it is easy to check for longer command messages and I have shown in the software how to do that.
So instead of just sending a "0" or a "1" to the general subscription topic, it is possible to make a topic called "/home/br/sb/relay" and use messages such as "ON" and "OFF" to switch the relay. However, that all costs memory and on a UNO with an Ethernetcard one needs to be a bit careful with memory use (though it still ahs much more compared to using an ENC28j60 Ethernetcard)

Step 2: A Simple MQTT Pub/Sub Node With Arduino UNO and W5100 Ethernetshield: the Program

ll
/*
          Arduino UNO with W5100 Ethernetshield or  W5100 Ethernet module, used as MQTT client
          It will connect over Wifi to the MQTT broker and controls a digital output (LED, relay)
          and gives the Temperature and Humidity, as well as the state of some switches
          The topics have the format "home/br/sb" for southbound messages and  "home/nb" for northbound messages
          Southbound are messages going to the client, northbound are messages coming from the client
          As the available memory of a UNO  with Ethernetcard is limited, I have kept the topics short
          Also, the payloads  are kept short
          The Northbound topics are
          home/br/nb/temp  for temperature
          home/br/nb/humid  for humidity
          home/br/nb/deur  for a door switch
          home/br/nb/l for  the lightintensity
          home/br/nb/pr  for the status of a PIR sensor
          home/br/nb/ip showing the IP number of the client
          home/br/nb/relay showing the relaystate

          There is only one southbound topic:
          home/br/sb
          The payload here determines the action:
          0 -Switch the relay off
          1-Switch the  relay on
          2-Publish the IP number of the client
          3 Ask for the relaystate// REMOVED

          On Startup, the Client publishes the IP number
          
*/
#include <Ethernet.h>// Ethernet.h library
#include "PubSubClient.h" //PubSubClient.h Library from Knolleary
#include "DHT.h" //DHT.h library
#define CLIENT_ID       "Hal"
#define PUBLISH_DELAY   3000 // that is 3 seconds interval
#define DHTPIN          3
#define DHTTYPE         DHT11
#define ledPin 13
#define relayPin 8
String ip = "";
bool statusKD = HIGH;
bool statusBD = HIGH;
bool statusGD = HIGH;
bool relaystate = LOW;
bool pir = LOW;
bool startsend = HIGH;// flag for sending at startup
int lichtstatus; //contains LDR reading
uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x06};

EthernetClient ethClient;
PubSubClient mqttClient;
DHT dht(DHTPIN, DHTTYPE);

long previousMillis;

void setup() {
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT);
  pinMode(relayPin, OUTPUT);

  // setup serial communication

  Serial.begin(9600);
  while (!Serial) {};
  Serial.println(F("MQTT Arduino Demo"));
  Serial.println();

  // setup ethernet communication using DHCP
  if (Ethernet.begin(mac) == 0) {
    //Serial.println(F("Unable to configure Ethernet using DHCP"));
    for (;;);
  }

  Serial.println(F("Ethernet configured via DHCP"));
  Serial.print("IP address: ");
  Serial.println(Ethernet.localIP());
  Serial.println();
 //convert ip Array into String
  ip = String (Ethernet.localIP()[0]);
  ip = ip + ".";
  ip = ip + String (Ethernet.localIP()[1]);
  ip = ip + ".";
  ip = ip + String (Ethernet.localIP()[2]);
  ip = ip + ".";
  ip = ip + String (Ethernet.localIP()[3]);
  //Serial.println(ip);

  // setup mqtt client
  mqttClient.setClient(ethClient);
  // mqttClient.setServer("test.mosquitto.org", 1883);//use public broker
  mqttClient.setServer( "192.168.1.102", 1883); // or local broker
  //Serial.println(F("MQTT client configured"));
  mqttClient.setCallback(callback);
  // setup DHT sensor
  dht.begin();
  Serial.println(F("DHT sensor initialized"));
  Serial.println();
  Serial.println(F("Ready to send data"));
  previousMillis = millis();
  mqttClient.publish("home/br/nb/ip", ip.c_str());
}

void loop() {
  statusBD = digitalRead(4);// FrontdoorSwitch
  statusGD = digitalRead(5);// Garagedoor Switch
  statusKD = (digitalRead(6));//LivingRoom Switch
  lichtstatus = analogRead(A0);//Reads an LDR
  pir = digitalRead(7);//Reads a PIR sensor
  relaystate = digitalRead(relayPin);// Reads the state of a relay

  // it's time to send new data?
  if (millis() - previousMillis > PUBLISH_DELAY) {
    sendData();
    previousMillis = millis();
  }
  mqttClient.loop();
}

void sendData() {
  char msgBuffer[20];
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  Serial.print("Temperature: ");
  Serial.print(t);
  Serial.println("oC");
  Serial.print("Humidity: ");
  Serial.print(h);
  Serial.println("%");
  Serial.print("Relay is: ");
  Serial.println((relaystate == LOW) ? "OPEN" : "CLOSED");
  if (mqttClient.connect(CLIENT_ID)) {
    mqttClient.publish("home/br/nb/temp", dtostrf(t, 6, 2, msgBuffer));
    mqttClient.publish("home/br/nb/humid", dtostrf(h, 6, 2, msgBuffer));
    mqttClient.publish("home/br/nb/deur", (statusBD == HIGH) ? "OPEN" : "CLOSED");
    mqttClient.publish("home/br/nb/garage", (statusGD == HIGH) ? "OPEN" : "CLOSED");
    mqttClient.publish("home/br/nb/bel", (statusKD == HIGH) ? "OPEN" : "CLOSED");
    mqttClient.publish("home/br/nb/l", dtostrf(lichtstatus, 4, 0, msgBuffer));
    mqttClient.publish("home/br/nb/p", (pir == HIGH) ? "OPEN" : "CLOSED");
    mqttClient.publish("home/br/nb/relay", (relaystate == LOW) ? "OPEN" : "CLOSED");
    mqttClient.subscribe("home/br/sb");
    if (startsend) {
      mqttClient.publish("home/br/nb/ip", ip.c_str());
      startsend = LOW;
    }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  char msgBuffer[20];
  // I am only using one ascii character as command, so do not need to take an entire word as payload
  // However, if you want to send full word commands, uncomment the next line and use for string comparison
  // payload[length]='\0';// terminate string with 0
//String strPayload = String((char*)payload);  // convert to string
  // Serial.println(strPayload); 
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");//MQTT_BROKER
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
  Serial.println(payload[0]);

  // Examine only the first character of the message
  if (payload[0] == 49)             // Message "1" in ASCII (turn output ON)
  {
    digitalWrite(LED_BUILTIN, HIGH);    //
    digitalWrite(relayPin, HIGH);
  } else if (payload[0] == 48)      // Message "0" in ASCII (turn output OFF)
  {
    digitalWrite(relayPin, LOW);     //
    digitalWrite(LED_BUILTIN, LOW);
  } else if (payload[0] == 50)
  {
    mqttClient.publish("home/br/nb/ip", ip.c_str());// publish IP nr
  } else {
    Serial.println("Unknown value");
    mqttClient.publish("home/br/nb", "Syntax Error");
  }

}

As copying this program could introduce all sorts of errors and because Instructabels sometimes drops the library declarations, the code is available for download here. I also have attached it to this instructable. Mind you that the to be downloaded code may still have some comments that mean more to me than to you.

UPDATE
Added new code: W5100MQTTV2.1.ino

Share

    Recommendations

    • Creative Misuse Contest

      Creative Misuse Contest
    • Water Contest

      Water Contest
    • Metalworking Contest

      Metalworking Contest

    26 Discussions

    great post and thanks for share, :D. but I having a problem when send a "0" to turn off the led, the digitalwrite works fine, but i dont know why but inmediatly it is turning on again.

    4 replies

    I attached a new code at the bottom of the instructable

    a quick note that i notice in your question is, based on this sketch 0 should turn on your LED and 1 should turn it off at least that is the way i understand it and im honestly really new to this great tool the arduino. and as for why it immediately turns back on again is do to a -r (retain) flag is needed. from most of the apps i have tried they have a retain button, from command line it would look like this

    to turn LED on

    mosquitto_pub -h 10.0.0.189 -t home/br/sb -m 0 -r

    to turn LED off

    mosquitto_pub -h 10.0.0.189 -t home/br/sb -m 1 -r

    and in most home automation programs i would assume its gonna be in somewhere like the Openhab2 location /etc/openhab2/services/mqtt.cfg your mostly looking for the place you set up your MQTT Config File there will be somewhere with a retain section blocked out just set to true or unblock and you should be good to go.

    Download this file and try again:

    https://drive.google.com/open?id=1x6k5WDlocTFux0lB...

    Note I have not been able to test it myself yet as my W5100 shield is engaged in another project, but I think it is solved now.
    I made small changes, namely that the commands are now ON and OFF rather than 1 and 0.
    Please let me know the result. If it doesn't work, I guess I have to pull out my shield and try here

    It has been a while since I used this code. As a result of your question I looked at it again and cant immediately find anything wrong. I will pull out my Arduino and ethernetshield and see what could be causing it.
    In the mean time, if you have MQTT-spy installed, you could have a look if perhaps an unexpected mqtt message is sent.

    ok now that i got those few comments out the way for them to get help. i need a little help, but first off i would like to say thank you, thank you, thank you... I'm literally a beginner to RPI, Arduino, Programming, etc. and this is the first instructions I have been able to follow and understand to set up an arduino/w5100/mqtt with a working sketch for my sensors and relays. so again I am beyond greatful that i am somewhat starting to understand.

    a few comments about things i noticed and had issues with when connecting everything up that may help save people time later. Digital pins 0, 1, 4, 10, 11, 12, 13 are not available due to the W5100 ethernet shield so avail. digital pins are 2, 3, 5, 6, 7, 8, 9 that is a total of 7 digital pins. hmmm...all of a sudden i can't remember the other things at the moment (if they come to me ill comment or update this post.)

    im trying to add some relays to my arduino 6 to be exact with a temp sensor attached. i have everything up and running based on your sketch running cleanly and am very happy, but im a little confused on how to add them to the subscribe topic with the callback function. i believe this is a fairly similar question to Peter's, but he was referring to the publish portion. any help would be greatly appreciated. and 1 last time a very big THANK YOU! for this

    3 replies

    The callback function is called whenever a topic arrives that the client has subscribed to. Both the topic as well as the message are passed in the callback function.
    What you need to do is check in your callback function what exactly the incoming topic is (which relay?) and what the message is (ON or OFF?)
    So suppose you want your relays to be switched with the following topics:
    home/relay/one
    home/relay/two
    ……..
    home/relay/six

    then you declare your subscription like this
    home/relay/#

    In your callback you then do the following checks:
    if (topic=="home/relay/one)
    {
    if (message=="ON") ---> then switch relay on
    if (message=="OFF")--->then switch relay off

    }

    if (topic=="home relay/two")

    {
    etc
    }

    ofcourse this is only one way of doing it, you could do it more efficient, e.g. by stripping the "home/relay/" part of the incoming topic and then use a case statement. That is even easier if you use numbers to address the relays:
    home/relay/1 ……….home/relay/6

    newtopic= Rightstring(topic,1);

    switch newtopic

    case "1"
    do ON/OFF check

    case "2"
    do ON/OFF check

    etc etc

    in this example

    https://arduinodiy.wordpress.com/2018/06/14/simple...

    I switch 4 relays. It is quite modular and can easily be expanded to six or 8 or 100 relays

    Seriously thank you!!!! It took me a little bit to understand and get the first example to work, but I got it goin and am off and running on my openhab home automation project. I will mess with and try to understand the case statement and switch portion next, but for now I'm am happy that it is functioning the exact way I wanted/needed it too... Do me a favor and take your right hand throw it over your left shoulder, then pat 2x!!!(that's from me and the many others that have used this as a model to learn).

    Dear diy_bloke

    Thank you again for the work you've done on this instructable, it's been a massive help in getting my devices on my Home Assistant and I've finally managed to resolve the mqtt issues I was having before. In an attempt to reduce the wifi in my house I am looking to move more devices over to ethernet. I have tried to add more relays to your code but have had some strange results where both relays turn on/off and flash etc. I tried to paste the code earlier but it didn't work.

    2 replies

    thank you for your kind words. Relays shld not just go on and off. Can you try and paste yr code?

    not sure if you ever got this worked out. for your sake (hoping you still have hair), but i believe it is due to a retain flag not being placed in your publish msg see above comment for more details

    Thanks a lot for sharing this, I have used it for controlling a row of 10 relays using mqtt; I am still trying to figure how to publish the state of my relays in a generic way (so without naming it as 'Lamp livingroom' for example), without 10 lines of code for each separate topic.

    4 replies

    I dont think you need 10 lines of code for each topic. I presume you have added these 10 relays all the the arduino?
    The only thing you would need to do for each relay is
    -read state of relay
    -publish the state
    That is two lines for each, or maybe I totally misunderstand you

    Sorry, I wasn't clear: in your code you publish the state of your relay in a topic named home/br/nb/relay; if I wanted to control multiple relays, I would have to write lines for each relay separately, because each topic is unique. Given that you can't seem to use variables for the topic in

    'mqttClient.publish', this seems inadequate. My solution at the moment is to publish each button's state as 'RXY', where X is the number of the relay and Y is 0 or 1, all on the same topic. I got it working with the Home Assistant software.

    Thanks for expanding, although I still do not get why you would need 10 lines of code for each relay just to read the state, but you got it working and that is what counts.

    You CAN use variables for the topic though.
    You would do that as follows:

    #define Id
    char buff_topic[15];
    sprintf(buff_topic, "home/relay%02d", Id);
    client.publish(buff_topic, "message");

    If you want to send variables often, it might be better to use the pubsubclient lib from Ian tester (Imroy) as that allows simple String(variable) statements to be sent. Surely for the message and I presume also for the topic

    I am trying to integrate this into my Home Assistant but struggling to get the correct YAML entries. What would the "State topic" be for the other end of the mqtt communication?

    2 replies

    i do not use home assistant so i might nt be the right person to ask. What this device does is to send mqtt messages, as any other.

    Home assistent connects with mqtt through a broker and your yaml fine should just connect to the message as is:

    So suppose this module would send the livingroom temperature, add the following to your configuration.yaml file:

    # configuration.yml
    sensor:
    - platform: mqtt
    state_topic: "home/livingroom/temperature"

    Thanks for looking into this for me. I think I know what the problem is...

    I am using a local mqtt server but the IDE code does not include the "username" and "password".

    I have added them to your client connect line:

    if (mqttClient.connect(CLIENT_ID),"username", "password")

    not sure if this is correct as I haven't got it to work yet?