Arduino Nano W5500 Ethernet MQTT Based 8 Channel Opto-Isolated I/O Board

Here is my code
Its reading my 4 dht22 every 10 secondes (and probably stalling my code …)
It.wait for button input (also 4, 2.room main switch and 2 closet)
And publish the information on the mqtt broker
It also subscribe to light topic and relay topic
To turn on the light and the electrical heating in each room

It seams that I am at the limit of the mcu … I’m using 78% ram and 88% flash (reported by platforming)
The button are not always working. And I would like to have the topic real name … And not what you see something like
Chambre/Alexis/main/light/status

#include <OneButton.h>

#include <DHT.h>
#include <PubSubClient.h>
#include <Ethernet.h>

//Configuration //
//#define Enable_Dhcp  true                 // true/false
//IPAddress ip(192, 168, 1, 105);           //Static Adress if Enable_Dhcp = false

//Static Mac Address
static uint8_t mac[] = { 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0x01 };  // s/a
#define DHTTYPE DHT22
#define DHTPIN0  A0 //alexis main dht22
#define DHTPIN1  A1 //alexis closet dht22
#define DHTPIN2  A2 //samuel main dht22
#define DHTPIN3  A3 //samuel Closet dht22
OneButton button1(A4, false, false); //Alexis main light switch
OneButton button2(2, false); //Alexis closet light switch
OneButton button3(A5, false); //Samuel main light switch
OneButton button4(3, false); //Samuel closet light switch

//pinout A0-A5  2-9
const int sendDhtInfo = 10000;    // Dht22 will report every X milliseconds.
unsigned long lastSend = 0;
const char* dhtPublish[] = { "c/a/clim/main/", "c/a/clim/closet/", "c/s/clim/main/", "c/s/clim/closet/" };

const int output_pin[6] = { 4, 5, 6, 7, 8, 9 }; //Relay Pinout turn on/off light et chauffage
//4=l main a
//5=l garde-robe a
//6=chauffage a
//7=l main s
//8=l garde-robe s
//9=chauffage s
const char* subscribeRelay[] = { "c/a/l/main/status/", "c/a/l/closet/status/",
                                 "c/a/heat/main/status/", "c/s/l/closet/status/",
                                 "c/s/l/main/status/", "c/s/l/closet/status/"
                               };

const char* inputPublish[] = { "c/a/l/main/simple/", "c/a/l/main/double/", "c/a/l/main/long/",
                               "c/a/l/closet/simple/","c/a/l/closet/double/","c/a/l/closet/long/",
                               "c/s/l/main/simple/", "c/s/l/main/double/", "c/s/l/main/long/",
                               "c/s/l/closet/simple/","c/s/l/closet/double/","c/s/l/closet/long/" 
                               };

// MQTT Settings //
const char* broker = "ubuntu.jaune.lan"; // MQTT broker
//#define mqttUser "USERNAME"         //Username for MQTT Broker
//#define mqttPassword "PASS"       //Password for MQTT Broker

DHT dht[] = { { DHTPIN0, DHTTYPE }, { DHTPIN1, DHTTYPE }, { DHTPIN2, DHTTYPE }, { DHTPIN3, DHTTYPE } }; // { DHTPIN4, DHTTYPE }, { DHTPIN5, DHTTYPE } };

EthernetClient ethclient;
PubSubClient client(ethclient);

void callback(char* topic, byte* payload, unsigned int length)
{
  Serial.println("Callback");
  Serial.println(topic);
  byte output_number = payload[0] - '0';

  for (int i = 0; i < sizeof(subscribeRelay) / sizeof(subscribeRelay[0]); i++)
  {
    int strcomparison = strcmp(topic, subscribeRelay[i]);
    if (strcomparison == 0)
    {
      if (output_number == 1)// || ((char)payload[0] == '1'))
      {
        digitalWrite(output_pin[i], HIGH);
      }
      if (output_number == 0)
      {
        digitalWrite(output_pin[i], LOW);
      }
    }

  }
}

void reconnect() {
  while (!client.connected()) {
    char clientBuffer[40];

    String clientString = "ip : "+ String(Ethernet.localIP()[0]) + "." + String(Ethernet.localIP()[1]) + "." + String(Ethernet.localIP()[2]) + "." + String(Ethernet.localIP()[3]);
    clientString.toCharArray(clientBuffer, clientString.length() + 1);
    if (client.connect(clientBuffer)) 
    {
      client.publish("arduino/chambre/ip/", clientBuffer);

      for (int i = 0; i < sizeof(subscribeRelay) / sizeof(subscribeRelay[0]); i++)
      {
        client.subscribe(subscribeRelay[i]);
      }
    }
  }
}
void setup()
{
  Serial.begin(115200);
  // if (Enable_Dhcp == true)
  // {
    while (!Ethernet.begin(mac))  { }

  enable_and_reset_all_outputs(); //Reset and Set all pin on OUTPUT mode

  client.setServer(broker, 1883);
  client.setCallback(callback);
  for (int i = 0; i < sizeof(dhtPublish) / sizeof(dhtPublish[0]); i++)
    {
      dht[i].begin();
    }

  button1.attachClick(click1);
  button1.attachDoubleClick(doubleclick1);
  //button1.attachLongPressStart(longPressStart1);
  //button1.attachLongPressStop(longPressStop1);
  //button1.attachDuringLongPress(longPress1);

  button2.attachClick(click2);
  button2.attachDoubleClick(doubleclick2);
  //button2.attachLongPressStart(longPressStart2);

   button3.attachClick(click3);
   button3.attachDoubleClick(doubleclick3);
   //button3.attachLongPressStart(longPressStart3);

   button4.attachClick(click4);
   button4.attachDoubleClick(doubleclick4);
   //button4.attachLongPressStart(longPressStart4);

}

void loop()
{
  if (!client.connected())
  {
    reconnect();
  }

  if (millis() - lastSend > sendDhtInfo)
  {
    readDHT();
    lastSend = millis();
  }
  
  button1.tick();
  button2.tick();
  button3.tick();
  button4.tick();

  client.loop();
}

void readDHT()
{
  for (int i = 0; i < sizeof(dhtPublish) / sizeof(dhtPublish[0]); i++)
  {
    float temperature = dht[i].readTemperature();
    float humidity = dht[i].readHumidity();
    //float heatindex;
    if(isnan(humidity) || isnan(temperature))
    {
      temperature = 100.0f;
      humidity = 100.0f;
     // heatindex = 100.0f;
    }
    else
    {
    // heatindex = dht[i].computeHeatIndex(temperature,humidity,false);
    }

    String payload = "{";
    payload += "\"temperature\":"; payload += String(temperature).c_str(); payload += ",";
    payload += "\"humidity\":"; payload += String(humidity).c_str(); payload += ",";
   // payload += "\"heatindex\":"; payload += String(heatindex).c_str();
    payload += "}";

    // Send payload
    char attributes[100];
    payload.toCharArray(attributes, (payload.length() + 1));
    client.publish(dhtPublish[i], attributes);

  }
}

void enable_and_reset_all_outputs()
{
  for (int i = 0; i < sizeof(subscribeRelay) / sizeof(subscribeRelay[0]); i++)
  {
    pinMode(output_pin[i], OUTPUT);
    digitalWrite(output_pin[i], LOW);
  }
}


//Boutton1
void click1() 
{
  client.publish("c/a/l/main/simple/", "1");
} 
void doubleclick1() 
{
  client.publish("c/a/l/main/double/", "1");
} 
void longPressStart1() 
{
  client.publish("c/a/l/main/long/", "1");
} 


//Boutton2
void click2() 
{
  client.publish("c/a/l/closet/simple/", "1");
} 
void doubleclick2() 
{
  client.publish("c/a/l/closet/double/", "1");
} 
void longPressStart2() 
{
  client.publish("c/a/l/closet/long/", "1");
} 

//Buton3
void click3() 
{
  client.publish("c/s/l/closet/simple/", "1");
} 
void doubleclick3() 
{
  client.publish("c/s/l/closet/double/", "1");
} 
void longPressStart3() 
{
  client.publish("c/s/l/closet/long/", "1");
} 

//buton4
void click4() 
{
  client.publish("c/s/l/closet/simple/", "1");
} 
void doubleclick4() 
{
  client.publish("c/s/l/closet/double/", "1");
} 
void longPressStart4() 
{
  client.publish("c/s/l/closet/long/", "1");
} 

After a quick glance, I’ve found several issues which will account for your memory use button responsiveness issues:

  • Anytime you explicitly define char arrays in quotes like “this is my array!”, that data needs to be copied to RAM anytime the processor needs it. So each one of those cstrings in subscribeRelay, inputPublish, etc. are taking up lots of memory when you access the variable. You can save this by placing them into PROGMEM and using the _P equivelants of functions to manipulate them (see my code)
  • Same thing everytime you do a Serial.println(“Insert my cstring char array here”) . That cstring needs to be moved to RAM first. Use the F("") macro to print these cstrings out of PROGMEM like Serial.println(F(“This string is being printed right from PROGMEM to the serial port so it can be as long as I want and it won’t eat RAM!!!”))
  • Never, ever, use String. The String class makes it easier to work with characters as a string rather than a cstring, but it completely fragments memory, leaving memory unusable for other operations, eventually running the processor out. Move these to cstrings and then use functions like strcmp, strcat to manipulate (see my code)
  • You definitely don’t need to read 4 DHT sensors every 10 seconds and publish the data. It can take up to about 750mS to read one DHT sensor, and they are so slow that in 10 seconds you won’t see much of a response. Depending on what you are measuring (a room, for example) it might not be possible for it to radically change temperature in 10 seconds unless something crazy happens like an explosion or a door is opened wide to a windy cold exterior. You could read one sensor per minute over a 4 minute period and publish as each is read.
  • It is definitely the DHT sensors read that is causing your button issues. You call readDHT() which can take up to a few seconds for all those sensors, so button.tick() which happens after the blocking readDHT() is missing button presses. It needs to be called in loop() as often as possible
  • Anytime you client.publish(“string/here/character/array”) everything between the quotes needs to be copied to RAM. As above, store these in PROGMEM and use publish_P
  • Instead of defining all the topics as seperate cstrings/strings, dynamically generate them like I do in my code.

is there a way for a arduino uno+ethernet shield +4 relays!???

Look at my code posted Sept 20: Arduino Nano W5500 Ethernet MQTT Based 8 Channel Opto-Isolated I/O Board .

This will run on an UNO with an Ethernet shield.

You just need to adjust the InputPins, RelayPins and NumOfInputs / NumOfRelays for your application.

this happens!!!no good for arduino uno!?

Please post the error message, not a picture of it.

error
to compile arduino uno

In your picture, I see two Adafruit Circuit Playground libraries included in the sketch.

These are not part of my code.

Start with a totally blank sketch and paste my code in.

Then make the changes for your pin numbers and appropriate Ethernet shield (uncomment the correct library).

ok,i manage to put the sketch to arduino!!!and now where can i see it?is there a interface!!!or the home assistant can find it!?

The program will obtain an IP address via DHCP. If successful, it will connect to the MQTT server defined here:

//MQTT related variables
const char MQTTServer[] = "192.168.107.11";

It will subscribe to the topics /(BoardTopic)/r(relay number)/

Publish a 1 to /(BoardTopic)/r(relay number)/ to turn the relay ON, publish 0 to turn it OFF

When a relay is changed the state will be reported as /(BoardTopic)/r(relay number)_s/

(relay number) represents the position in the RelayPins[] array.

When you change one of the InputPins[], it will publish /BoardTopic/s(input number)/ with a payload of 1 for high, 0 for low.

Additionally you will get /BoardTopic/s(input number)/held/ when the input changes state again to show hold time in mS the input was held. There is a hold time timeout of 4 seconds. After 4 seconds, no held time will be published.

There is no user interface. All interaction takes place via MQTT and configuration at time of compile.

In Home Assistant, you configure your entities manually.

For example, here is a relay output configured as a light:

light:
  - platform: mqtt
    name: "Shop Light South"
    state_topic: "/shop/r7_s/"
    command_topic: "/shop/r7/"
    payload_on: "1"
    payload_off: "0"
    qos: 0
    retain: true

Here is an input as a sensor which exposes hold time:

sensor shopbuttons:
 - platform: mqtt
   state_topic: "/shop/s0/held/"
   name: "Shop Button 0"

Here is an automation which toggles on a bunch of lights for a held state of 1 - 2.2 seconds:

- id: '888a82222900846484947332344'
  alias: Toggle All Shop Lights 1 Sec Press Fab Light Button (Entry Timer Idle)
  trigger:
  - platform: mqtt
    topic: /shop/s4/held/
  condition:
  - condition: numeric_state
    entity_id: sensor.shop_button_4
    above: 1000
    below: 2200  
  action:
  - data:
      entity_id:
        - light.shop_fab_light
        - light.shop_light_north
        - light.shop_light_south
    service: light.toggle

Every minute telemetry with uptime, IP, free memory, version is sent out on /BoardTopic/tele/ encoded in JSON.

Lots of debug information is available via the serial port at 9600 baud.

1 Like

Will you post your code on GitHub ?

I will not be posting my code on GitHub as I only host my content on my website (with the exception of YouTube and forum posts). I don’t believe in large monopolies (ie. Instructables, GitHub, etc.) gobbling up content. Currently I am working on a large writeup for my website which covers my entire automation setup including this code. It’s a big project, and has taken a while but is about 85% done.

However, for the time being if you look at the post from Sept 2020 ( Arduino Nano W5500 Ethernet MQTT Based 8 Channel Opto-Isolated I/O Board - #3 by AaronCake ) you will find an older stable version of this code that allows MQTT control of inputs and outputs.

My code uploads successfully However in the serial monitor the arduino hangs on:

ETHERNET: Begin…

I have waited over 5 minutes and it just hangs.

Any suggestions?

Cheers
Jake

Are you using the correct library for your Ethernet module? Ethernet2 for W5500, Ethernet for W5100.

Does your Ethernet module work? Try using the DHCP Printer example that comes with the Arduino IDE.

Do you see a link light on the Ethernet module?

Assuming your DHCP server is healthy and you have a link, if everything is working obtaining a DHCP address should be a second or two at most.

I have managed to resolve the issue. I was using the wrong IP address :man_facepalming:

I recently updated my network and forgot to get the new IP haha
Thank you for the code and for getting back to me!!

Cheers

Hi @AaronCake did you finished your website about your automation setup? I’m also designing devices for Home Automation with Home Assistant. I would like to share with you and have some feedback from someaone in a more advanced phase.

Not yet, though getting closer. It is a large writeup completely detailing my automation setup and I only have at best about an hour a week to work on it. Edit…but if you would like to discuss things, that’s no problem either here or via PM.

Hi. Any news?

Still working on it.

Went live a few minutes ago. My complete HA writeup:

https://www.aaroncake.net/house/automation/index.asp