Void function, global variables & sensor data - am I on the right path?

Hello

New to Arduino and C++/Sketches. Code posted below.

By testing things, breaking things, copying examples and learning I'm setting up an Arduino board to read some sensors in my greenhouse and send the values using MQTT to WeeWx, an open source weather system running on a Raspberry Pi. In this case the sensor values from the greenhouse Arduino are an addition to the main weather station feed into WeeWx which is from an all-in-one weather station.

If that station ever broke I'd be tempted to build my own around an Arduino.

At this moment an RP2040 in the greenhouse is happily sending temperature sensor data from a single DHT22 to WeeWx using MQTT. Initially my sketch locked up after a few hours but when I moved the sensor read from the loop into a function it worked fine. I've since also moved the MQTT sending into its own function.

Now, using a Uno R4 to experiment, I'm looking to expand to more sensors. I've got a couple of Dallas 1-Wire temperature sensors which will either be in addition or a replacement for the DHT22, the 1-wire advantage being I can use longer cables and have more flexibility.

My questions are:
Am I correct in saying the void function I'm using for the DHT22 sensor read is modifying a global variable - in this case dhtsensorPin2 - and isn't returning anything. I can see it works, and understand how, but wondering if what I'm thinking & saying syntax-wise is correct.

Am I also correct in thinking I should:

  • Put stuff in functions.
  • Each sensor, or at least by sensor type (DHT22, 1-Wire, etc) should have its own function.
  • Call those functions in the loop - reading sensors & sending data stay out of the loop.
  • Use naming conventions (functions, global variables etc) that make sense as to their use.
  • Avoid using delay if possible.
  • Use comments in the code to help myself (especially later) and anyone else reading it.
  • Tidy up - once its working as we want it we should save space & make it easier to understand by removing commented out stuff we don't need.

My sketch below started as copy/paste from examples which I've then modified, so far moving the serial printing, MQTT printing and DHT22 sensor read into functions. Also lots of tidying up to do. The R4 is a "maybe other cool thing later" purchase so I'm using that to develop and test while the RP2040 does its current job in the greenhouse. I appreciate the R4 is different to the RP2040 and that I might have to use different libraries when it comes to eventually making more sensors work on the RP2040.

So from a code point of view am I on the right path?

Thanks,
Ashley

/*
  This example connects to an unencrypted WiFi network.
  Then it prints the MAC address of the WiFi module,
  the IP address obtained, and other network details.

  created 13 July 2010
  by dlf (Metodo2 srl)
  modified 31 May 2012
  by Tom Igoe

  Find the full UNO R4 WiFi Network documentation here:
  https://docs.arduino.cc/tutorials/uno-r4-wifi/wifi-examples#connect-with-wpa
 */

#include "DHT.h"
#include "ArduinoMqttClient.h"
// #include "WiFiNINA.h"                 // this is the wifi library for the RP2040 Connect - we use the RP2040 in the greenhouse
#include "WiFiS3.h"                      // this is the wifi library for the Uno R4 - we use the R4 to develop & learn in comfort.
#include "arduino_secrets.h"

#define DHTPIN2 2
#define DHTTYPE DHT22

DHT dht2(DHTPIN2, DHTTYPE);
float dhtsensorPin2;                     // the value returned from the sensor needs to be a float value.


///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = SECRET_SSID;              // your network SSID (name)
char pass[] = SECRET_PASS;              // your network password (use for WPA, or use as key for WEP)
int status = WL_IDLE_STATUS;            // the WiFi radio's status

WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);

// MQTT broker & topics.
// Our broker is Mosquito running on the same Raspberry Pi that we are subscribing from.
// Broker and subscriber can be on the same computer. In this case Mosquito & MQTTSubscribe plugin for WeeWx weather software.
const char broker[] = "192.168.45.252";
int        port     = 1883;

// For info only to remind me of what MQTTSubsribe driver in WeeWx is expecting to see, and why.
// WeeWx syntax in weewx.conf on the Raspberry PI for the MQTTSubsribe driver expects to see topic/sensorname.
// The value is then mapped to the relevant WeeWx sensor. extraTemp1 etc are already pre-defined for our use.
//    [[topics]]
//    [[[greenhouse/generalTemperature]]]
//    name = extraTemp1                           // these database names are mapped to something we understand such as Greenhouse Temperature.

// const char topic[]  = "greenhouse/generalTemperature" // - this is the real MQTT topic currently going to WeeWx
const char topic[]  = "greenhouse/one";        // testing - we don't want this appearing in WeeWx yet so we use something WeeWx isn't watching for.
// const char topic2[]  = "greenhouse/two";    // testing - we don't want this appearing in WeeWx yet so we use something WeeWx isn't watching for.
// const char topic3[]  = "greenhouse/three";  // testing - we don't want this appearing in WeeWx yet so we use something WeeWx isn't watching for.


// const char dhtsensorPin2[] = "20";          // original testing value to send over MQTT before we added a sensor

//set interval for sending messages (milliseconds)
const long interval = 8000;
unsigned long previousMillis = 0;

int count = 0;


void setup() {
  //Initialize serial:
  Serial.begin(9600);
 //  while (!Serial) {
     // wait for serial port to connect. Needed for native USB port only - commented out to avoid hang when RP2040 is stand-alone
 // }

  // Initialise our sensor
  dht2.begin();

  // check for the WiFi module:
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true);
  }

  String fv = WiFi.firmwareVersion();
  if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
    Serial.println("Please upgrade the firmware");
  }

  // attempt to connect to WiFi network:
  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to WPA SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network:
    status = WiFi.begin(ssid, pass);

    // wait 10 seconds for connection:
    delay(10000);
  }

  // you're connected now, so print out the data:
  Serial.print("You're connected to the network");
  printCurrentNet();
  printWifiData();

  // Connect to the MQTT Broker 
  Serial.print("Attempting to connect to the MQTT broker: ");
  Serial.println(broker);

  if (!mqttClient.connect(broker, port)) {
    Serial.print("MQTT connection failed! Error code = ");
    Serial.println(mqttClient.connectError());

    while (1);
  }

  Serial.println("You're connected to the MQTT broker!");
  Serial.println();

}

void loop() {

 //  float t = dht2.readTemperature();                  //  moved to its own fuction - if it works (it does!) we can delete this.

  // call poll() regularly to allow the library to send MQTT keep alive which
  // avoids being disconnected by the broker
  mqttClient.poll();

  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // save the last time a message was sent
    previousMillis = currentMillis;

    //record random value from A0, A1 and A2 - todo: this was in the initial MQTT example - needs removing.
    // int Rvalue = analogRead(A0);
    // int Rvalue2 = analogRead(A1);
    // int Rvalue3 = analogRead(A2);

  // Call our functions to do stuff
  readdhtSensor();                                      // Function reads the sensor data from our DHT22
  sendData();                                           // Function sends the sensor data via MQTT

    // Serial.print("Sending message to topic: ");      // moved to function 11th Oct - test
    // Serial.println(topic);                           // moved to function 11th Oct - test
    // Serial.println(dhtSensorPin2);                   // moved to function 11th Oct - test

    // Serial.print("Sending message to topic: ");
    // Serial.println(topic2);
    // Serial.println(Rvalue2);

    // Serial.print("Sending message to topic: ");
    // Serial.println(topic2);
    // Serial.println(Rvalue3);

    // send message, the Print interface can be used to set the message contents
    // mqttClient.beginMessage(topic);                 // moved to function 11th Oct - test
    // mqttClient.print(dhtSensorPin2);                // moved to function 11th Oct - test
    // mqttClient.endMessage();                        // moved to function 11th Oct - test

    // mqttClient.beginMessage(topic);
    // mqttClient.print(testmessage2);
    // mqttClient.endMessage();

    // mqttClient.beginMessage(topic3);
    // mqttClient.print(Rvalue3);
    // mqttClient.endMessage();

    // Serial.println();                                // moved to function 11th Oct - test
  }

}

void readdhtSensor()  {
    // Wait a few seconds between measurements. The DHT sensors are a bit slow.
    // delay(2000);  // note: our loop has a 8000 mili (8 second) delay so do we need delay here? - testing & reading about good practices indicates no.

    // Get our data from the sensor
  dhtsensorPin2 = dht2.readTemperature();               // reads the temperature from the DHT22 sensor in degrees C

}

void sendData()       {
    // print to the serial interface - we don't need it for the MQTT to work but its easier to see if the sensor(s) are returning anyhting.
    Serial.print("Sending message to topic: ");
    Serial.println(topic);
    Serial.println(dhtsensorPin2);

    // send message, the Print interface can be used to set the message contents
    mqttClient.beginMessage(topic);
    mqttClient.print(dhtsensorPin2);
    mqttClient.endMessage();

Serial.println();
}

void printWifiData() {
  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  
  Serial.println(ip);

  // print your MAC address:
  byte mac[6];
  WiFi.macAddress(mac);
  Serial.print("MAC address: ");
  printMacAddress(mac);
}

void printCurrentNet() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print the MAC address of the router you're attached to:
  byte bssid[6];
  WiFi.BSSID(bssid);
  Serial.print("BSSID: ");
  printMacAddress(bssid);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.println(rssi);

  // print the encryption type:
  byte encryption = WiFi.encryptionType();
  Serial.print("Encryption Type:");
  Serial.println(encryption, HEX);
  Serial.println();
}

void printMacAddress(byte mac[]) {
  for (int i = 0; i < 6; i++) {
    if (i > 0) {
      Serial.print(":");
    }
    if (mac[i] < 16) {
      Serial.print("0");
    }
    Serial.print(mac[i], HEX);
  }
  Serial.println();
}

Welcome to the fora.

I'll read your entire post later, good on you so far.

Usually we just call functions functions. A function that does not return anything has a type, void, which just means… they don't return anything. All functions have a type, indicating, um, the type of the variable it returns. Via the return statement, usually the very last thing a function that does return a value executes. Functions returning void (nothing) don't need a return statement, it is implied, but you can

  return;

explicitly return… nothing.

So you are right. That function puts its work in a global variable where literally everyone can grab it. This is often called a side effect.

That's convenient and OK mostly in a small sketch, but as you progress you might want to make functions that do return a value.

It really spends on the structure of the rest of the code.

L8R

a7

1 Like

couldn't your loop be simplified

void loop ()
{
    mqttClient.print (dht2.readTemperature());
    delay (8000);
}

doesn't this make more sense?

    WiFi.begin(ssid, pass);
    while (status != WL_CONNECTED)
        delay(1000);

for what you're doing, it makes sense to KISS, keep it simple.

much larger programs are more easily maintained by abstracting into separate parts that are more independent of one another

when a function becomes longer than what you can see on the screen, consider breaking into smaller functions

A pin is the brass things sticking out of the boards. They get used in pinMode statements and sometimes other statements. When you 'read' a sensor, the return value is NOT a pin, it will be a data value (look at doc'n or sample to determine what it is) often float but could be integer. Fix that first.

1 Like

Hi, yes I'm going to - still deciding what to call them, but its certainly not going to be "pinX" as how do I easily see what actual sensor I'm referring to - especially once its been running for a year or more and partly forgotten about. I'm sure there are good examples which demonstrate this - or indeed just by giving it some thought.

ps the deleted posts were because I thought I hadn't replied properly - I hadn't spotted how the threads are presented.

Thanks for the welcome & very helpful pointers.

It is going to be a reasonably small sketch.. I think! I've got two dallas one-wire temperature sensors one of which I'll drop down into my greenhouse heatsink - think of it as an underground storage heater fed by a wide pipe with a fan that takes hot air from the apex. And two DHT22's - 4x sensors in total, 5 readings if I also take humidity from one of the DHT's.

It looks like I can get away with using void, but learning functions that return values would as you say help if things get more complex and gain knowledge for the future.

I tried this small sketch and it works but I seem to have to put lots of float to make it compile and couldn't figure out printing in a separate function - how to get sensorValue into another function to print it, because now its work isn't in a global variable. I expect the simple answer is don't bother, print in the loop.

// Library to read the DHT22 sensor
#include "DHT.h"

// Pin and sensor type.
#define DHTPIN2 2
#define DHTTYPE DHT22

DHT dht2(DHTPIN2, DHTTYPE);

void setup() {
  // put your setup code here, to run once:
  
  //Initialize serial:
  Serial.begin(9600);
 
  // Initialise our sensor
  dht2.begin();

}

void loop() {
  // put your main code here, to run repeatedly:

float sensorValue = gettheData();
Serial.println(sensorValue);

}

float gettheData() {
   float sensorValue = dht2.readTemperature();
return sensorValue;

}

For my project I think I should be looking at doing the sensor reads in functions split by sensor types: one function for the DHT22s and one function for onewires, returning the relevant sensor values to print the result.

I expect a compromise would be leaving MQTT printing in the loop if easier. Its three lines of code per value sent and it really doesn't have to run fast - the software receiving the MQTT data updates its website every 10 mins.

Thanks again,

Ashley

You found somewhere about returning a value from a function. It must have not been a thorough tutorial or learning type discovery or it would have gotten around to mentioning…

...that you can pass values to a function via its parameters, which are in its definition, by supplying corresponding arguments when you call it.

pro tip: add c++ and arduino to a google search to focus it a bit, and read around a bit.


arduino c++ functions


Here's a stupid example to print a float passed to a function

void printFloat(float valueToPrint)
{
  Serial.print(valueToPrint);
}

and elsewhere you can call that

  printFloat(3.14159);

or

  printFloat(sensorValue);

All functions have a return type and zero or more parameters. Very common are functions that take a handful of arguments of various types and return a value of some type.

a7

1 Like

===>

float sValue = dht2.readTemperature();
return sValue;

The value of the variable sValue will be assigned to variable sensorValue of the loop() function. This is to avoid eye-irritating feeling of identical names (though allowed) for two local variables.

Like this you don't need the variable.

void loop() {
  // put your main code here, to run repeatedly:

    Serial.println(gettheData());

}

float gettheData() {
   return dht2.readTemperature();
}

That's a good point and not one I recall particularly being hammered into someone learning about functions.

In this case getTheData(), a function that returns a float, is, for all intents and purposes, a float.

So it can appear anywhere that a scalar float variable would be correct. No need to assign it to something for subsequent use.

Matters of style enter soon enough; sometimes a few extra steps can make code clearer, sometimes too many of those become a burden to the reader if not yet a significant burden on the microprocessor's resources.

Lotsa times verbose expressions get optimised by the compiler and end up being as economical or even better than what one might have written.

Write like others will read your code. When you come back to code you haven't looked at for some time you will be that reader and you will thank yourself.

"Some time" varies fro person to person. I can surprise myself reading code I wrote last week. Or confuse myself. :- |

a7

2 Likes

Just wanted to say a big thanks to everyone for all the pointers and advice.

I have a working sketch which is publishing data from 4x sensors (6x values) via MQTT to the WeeWX weather software. I've been reading, re-reading, and researching via your suggestions thinking about how to make things better.

Since the Arduino is currently working in a greenhouse at the end of my garden I'll probably end up getting another two DHT22's so I can play around with the sketch on the other one I purchased.

As an aside I never got into programming except for some BASIC on 8 bit computers as a kid (that ages me :laughing: ) by the time 16bit Amiga's and the like came along it was all games. Thought about it a few times, tried a free online Python course during covid restrictions, but never gelled with programming. I guess having a need, a use case, has changed that. An Arduino doesn't do anything specific for your task unless you program it, but I'm also liking C++ - it's starting to make sense in my mind and feels like a programming language I could enjoy learning more about.

Thanks again.