Remote Allotment Monitoring System With Tool Use Tracker Utilising WEMOS and MQTT

by aawmartin in Circuits > Arduino

27 Views, 1 Favorites, 0 Comments

Remote Allotment Monitoring System With Tool Use Tracker Utilising WEMOS and MQTT

5634.jpg

Hello this is our combination soil moisture monitoring system, thermostat, and tool logging Station. It is based on the WEMOS D1 mini (clone) and allow for the tracking of select plants soil moisture level, the Atmospheric temperature at the stations location and keeping track of what tools are in use in shared gardening environments. Each of these elements are modular and can be added or removed as needed. Both the Moisture sensors and tool tracking are scalable up to the capacity of the devices used and can be tracked through a MQTT server.

Supplies

This is the full list of all compoents used. Each individual section will also have a component breakdown for what was used in that section:

  1. Soil Moisture Sensor (1 per Periferal Sensor
  2. LEDs (2 per Periferal Sensor)
  3. WEMOS D1 Mini (2 +1 per Periferal Sensor)
  4. Wires (All of them)
  5. Either a bread board or suitable PCB board & Solder
  6. DHT sensor
  7. MFRC522 RFID reader Module
  8. RFID Dongle (1 for each tool you wish to track)

Set Up

Each WEMOS will require being connected to the wifi and then to the MQTT server and depending on your network it requires being registered to access the network. This is mostly for industrial and corporate networks not your home network. Speak to your IT department to be sure. Since we are also using the Arduino IDE framework to program the WEMOS we will need download this Library:

esp8266 by ESP8266 Community

And add this URL to our additional board manager URls manager found under File/ Preferences for window or Arduino IDE / Settings for Mac:

https://arduino.esp8266.com/stable/package_esp8266com_index.json

Once you Have done that your device will be able to be used to program a wide variety of WEMOS’s but more importantly the one we will be using repeated through this project.

Each of your WEMOS’s will need the following code in order to function:

#include <ESP8266WiFi.h> //Used to connect to the wifi

#include <MQTT.h> // Used to connect to an MQTT server

const char ssid[] = "NETWORK_HERE"; // Wifi network. Input your network name in-between the parenthesis.

const char pass[] = "PASSWORD_HERE"; // Wifi Password. Input your networks password in-between the parenthesis if your network requires one.

WiFiClientSecure net;// Sets up a network object to connect via

MQTTClient client; // Sets up an Object to connect to the MQTT server with


void connect() {

while (WiFi.status() != WL_CONNECTED) { //Try and connect

delay(500);

}

net.setInsecure(); // Don't care about verifying a secure connection

while (!client.connect("Portal Name", "Username", "Password")) {

//These 3 fields are used to connect to the specific MQTT server you want to run the project on and the first field should be uniquely named for each different WEMOS you are using as this is how the server recognises the device. Failure to do so will result in a failure to connect to the sever and other issues.

delay(500);

}

client.subscribe("Project/Codes"); //Subscribe to recieve messages from the specific server channel

}


Next portion nested within void setup:

WiFi.begin(ssid, pass); //log into the wifi

client.begin("host", Port , net); //Connect to the correct serve via the correct port. Change these to the required values

client.onMessage(messageReceived); // Establish communication and confirm recieving

connect(); // Run connection subroutine


Final Portion nested within Void Loop:

client.loop();

delay(10); //LAG counter

if (!client.connected()) { //If not connected try to reconnect

connect();

}

With this code the WEMOS should connect to your network and then to the server you choose and will attempt to reconnect if disconnected and listen for any messages coming in from the selected channel.

Modularity

There are 4 elements of this project wee need to apply specific code for:

  1. The Moisture Sensor Peripheral
  2. The Moisture Sensor Base
  3. The DHT Temperature Sensor
  4. RFID Scanner

The Moisture Sensor Peripheral (Now referred to as MSP) and the RFID Scanner require separated individual boards whereas the Base and Temperature sensor can be mounted together on one board. You will need an MSP for each plant you wish to monitor, and each requires its own board.

MSP

Picture1.png

Components (Per sensor)

  1. Soil Moisture Sensor
  2. LED
  3. WEMOS D1 Mini
  4. Wires
  5. Either a bread board or suitable PCB board & Solder


Wiring

Most of the wiring is direct except from a ground rail that you need to set up on one line of the PCB (It can and is recommended to be the same rail as the WEMOS ground pin.). Make sure to split the rails under neath the WEMOS as to not connect the pins across and the rails should be orientated perpendicular rather than parallel to the pins.

Sensor Power -> 5V

Sensor Ground -> Ground Rail

Sensor signal -> A0

LED + -> D1

LED- -> Ground Rail



Code

Note: This code is the same for all of the periferal sensors with the exception of a few key points. These are:

  1. Portal Name (Must be unique for each WEMOS)
  2. myVal and response codes (Will be a selected number= X with X0 being the call code, X1 being the happy response and X2 being the negative response)
  3. Publish Channel (Usual defined as SensorX to avoid confusion)
  4. Sensor name .
  5. Resistance value to determine the desired hydration


Insert the following before void Setup but after Void connect:

void messageReceived(String &topic, String &payload) { //If you recieve a message on the call responce channel

Serial.println(payload); //Print it out

int myVal = payload.toInt(); // Convert string to interger

if (myVal == X0) { //Check if its your call message

if (analogRead(A0) <= 400) { //If yes see if plant is wet (this value can be calibrated to meet a plants desired moisture level)

digitalWrite (D1,LOW); // if yes light goes off

client.publish("Product/Codes", "X1"); // we send happy code

client.publish("Product/SensorX", "Sensor X is: HAPPY"); // and say on server this sensor is happy

}

else { //if plant is not wet

digitalWrite (D1,HIGH); // turn LED on to alert user

client.publish("Product/Codes", "X2"); // Send thirst code

client.publish("("Product/SensorX", "Sensor X is THIRSTY"); // And say on server this sensor is thirsty

}

}

}


Insert into void setup:

Serial.begin(115200); //Start Serial

pinMode(D1,OUTPUT); // Set LED pin to output

pinMode (A0, INPUT); // Set moisture sensor pin to input


Insert this in void loop to monitor the hydro sensor via serial:

Serial.println(analogRead(A0)); //Send the Moisture sensor data to serial to check its working.

delay(500); // Every 0.5 seconds

Moisture Sensor Base

5635.jpg

Components

  1. WEMOS D1 mini
  2. Wires
  3. Breadboard or PCB and Solder
  4. LED per Sensor (Up to a maximum of 8, you can attach more sensors but won’t be able to use the LEDs as a visual means of tracking their state. We are yet to test the server-side capacity so it is currently only limited by code outputs )

Wiring

The only real wiring for this component is the LEDS from their respective pins to the ground rail. These LED might wish to be on longer wires depending on how you wish to mount them.


Code

Our demo here is currently set up for 3 sensor with 3 LEDs. For each additional sensor you will need to add another copy of the skeleton code section to the appropriate section and fill it in with the desired values. The is a limit for the number of LEDS of 8 but we are yet to find the limit server side for the number of sensors.


Inserted just below MQTTClient:

unsigned long lastMillis = 0; // Int used in a rolling timer. Allows it to count continuosly without read delay

int Count = 1; // Used to track which sensor in being called on

int myVal = 0; // Used as a call and responce for the sensors


The Response code inserted before void Setup but after void Connect:

void messageReceived(String &topic, String &payload) { //Recieve message from subscribed channel. Recieved in the form of a string.

int myVal = payload.toInt();// Convert string to interger

(The 2 lines above are fully required even if you aren’t using LEDS. If you aren’t wishing to use LEDs you can remove the rest of this section as it is only for their control as it’s the sensors themselves that control the server-side monitoring.)

if (myVal == 11) { //Sensor 1 Happy

digitalWrite(D7, LOW);

}

if (myVal == 12) { // Sensor 1 Thirsty

digitalWrite(D7, HIGH);

}

if (myVal == 21) { //Sensor 2 Happy

digitalWrite(D6, LOW);

}

if (myVal == 22) { // Sensor 2 Thirsty

digitalWrite(D6, HIGH);

}

if (myVal == 31) { //Sensor 3 Happy

digitalWrite(D5, LOW);

}

if (myVal == 32) { // Sensor 3 Thirsty

digitalWrite(D5, HIGH);

}

}

Skeleton Code for LED Control

if (myVal == X1) { //Sensor X is Happy

digitalWrite(Xpin, LOW);

}

if (myVal == X2) { // Sensor X is Thirsty

digitalWrite(Xpin, HIGH);

}

NOTE: In testing this component, we did find noticeable lag between the code being sent and the LED changing. We tried a few different fixes, but nothing worked in the way we wanted. Server-side monitoring was unaffected and worked flawlessly .


In void Setup:

pinMode(D5,OUTPUT); //LED for each sensor set up on the relevant pin. Can be expanded up to the number of availible pins

pinMode(D6,OUTPUT);

pinMode(D7,OUTPUT);


In Void Loop. The main call code. We put this after all other items in this section as we want to avoid lag, make sure that we are connected and have any other processes happen at their coded rate:

if (millis() - lastMillis > 10000) { //10 second continous timer

lastMillis = millis(); // This section checks if it hase been atleast 10 IRL seconds if yes continue to do the thing and rest the timer. If no then wait.

The Nested Call Code Example. Nested within the timers if statement as we only want this to happen when the timer goes off. The count is based on the number of sensors you have with each count checking the lasts call to see if it has been responded to if it has they call their own and increases the count. If not they mark the sensor as lost (since it hasn’t responded) and they call their own and increases the count. It is sequenced in reverse to prevent run on through the sequence. Lastly after it has checked all the possible counts for sensors it checks if it has reached the top one (Marked as 0) and if it has it resets to 1.

if (Count = 3) {

if (myVal = 20) {

client.publish"Product/Sensor3", "Yellow is: LOST");

client.publish("Product/Code ", "30");

delay(500);

}

else {

client.publish("Product/Code ", "30");

delay(500);

}

Count = 0;

delay(500);

}

if (Count = 2) {

if (myVal = 10) {

client.publish("Product/Sensor2", "Red is: LOST");

client.publish("Product/Code ", "20");

delay(500);

}

else {

client.publish("Product/Code ", "20");

delay(500);

}

Count = 3;

delay(500);

}

if (Count = 1) {

if (myVal = 30) {

client.publish("Product/Sensor1", "Blue is: LOST");

client.publish("Product/Code ", "10");

delay(500);

}

else {

client.publish("Product/Code”, "10");

delay(500);

}

Count = 2;

delay(500);

}

if (Count = 0) {

Count = 1;

delay(500);

}

}


Skeleton for call code:

A equal the count for the sensor, an integer between 1 and the number of sensors

Y0 is the previous counts call code

Y is the previous sensor Will change which channel the message is published in and the which sensor the message refers to,

X is the current sensor Will change which channel the message is published in and the which sensor the message refers to, will be the same as A

X0 is the current sensors code which you are testing.

B next count, A+1


if (Count = A) {

if (myVal = Y0) {

client.publish("Product/SensorY", "Sensor Y is: LOST");

client.publish("Product/Code", "X0");

delay(500);

}

else {

client.publish("Product/Code", "X0");

delay(500);

}

Count = B;

delay(500);

}

Note: for the last sensor (which will be the first in the sequence) B will always be 0 and you will have:

if (Count = 0) {

Count = 1;

delay(500);

}

}

To act as a reset for the count before the next timer check.

DHT Temperature Sensor

Components

  1. DHT sensor
  2. Wires
  3. WEMOS D1 mini (can be the same as used for the moisture sensor base)


Wiring and Setup

For this component you need to have downloaded the DHT sensor library by Adafruit. Wiring is all direct but again uses a ground rail if you are using the same WEMOS as the base and are using LEDS. If either of statements are false you can wire it in directly.

Power ->

Ground -> Ground

Signal -> 4


Code

In top of Code with other library includes:

#include "DHT.h" // Used to allow the DHT sensor to function


#define DHTPIN 4 //Assigns the DHT pin Note this is the ISO pin label not the D label

#define DHTTYPE DHT11 // Defines the sensor type

DHT dht(DHTPIN, DHTTYPE); // Setsup DHT with both the previous factors


In Void Setup:

dht.begin(); // Activate the DHT sensor


In Void Loop:

delay(5000); //Delay for time between temperature updates

float t = dht.readTemperature(); //Take temperature reading from data

String Temp; // Setup a string for the temperature

Temp = "The temperature is currenly " + String(t) + " Degrees Celcius"; //Turn data into a nice to read message in string format

client.publish("Product/Temp", Temp); // Publish it to the temperature subchannel

// Note: you can also retrieve both humidity and temperature in Fahrenheit, but we chose to exclude these values to avoid overloading the server. You can include them with:

float h = dht.readHumidity(); // For Humidity

float f = dht.readTemperature(true); // For Fahrenheit

RFID Scanner

5636.jpg

Components

  1. WEMOS D1 mini
  2. MFRC522 RFID reader Module
  3. Jumper Cables (Both of our components came with pre-soldered male pins so we only used female to female wires.)
  4. An RFID Dongle for each tool

Set Up & Wiring

Install the library ‘ MFRC522 by GitHubCommunity’ . Wiring is all Direct and follows these connections:

RST -> D1

SS -> D2

MOSI -> D7

MISO -> D6

SCK -> D5

GND -> Ground

3.3V _> 3V3


Code

There are 2 flexible variables in this code: 1. The storage array of your RFID codes found in the matrix (Each dongle will have a unique hex code that the code checks against its database to see if a tool has been scanned and if so which. 2. The tool name variables each used to track the state of a specific tool.

Each of these increase with each tool you wish to track and can cause some complexity beyond what we have detailed here.

Primarily before void Connect:

#include <SPI.h>

#include <MFRC522.h> //SPi and MFRC522 Librarys used to make the RFID module function

#define RSTPIN D1

#define SSPIN D2

MFRC522 rc(SSPIN, RSTPIN); // Defining RFID communication pins to allow it to function

int readSuccess; // Int to check if a card has been detected

int Code; // Int to define which code has been read relevant to the respective tool

int ToolA ; // Keeps track of the first tool’s state

int ToolB; // Keeps track of the second tool’s state

int ToolC; // Keeps track of the third tool’s state

//2-dimensional array to store card codes

byte defcard[][4] = { { 0x93,0xBA,0x40,0x16 }, { 0x1D,0x89,0xC5,0x1 },{0x77,0x33,0xA,0x1} }; // Each code refers to a specific tool, These will be different with your own dongles. Dongle codes can be found by scanning them after up loading this full code. Their specific hex code will pop into serial monitor when scanned.

int N = 3; //change this to the number of tools you will track

byte readcard[4];


Added to Void Set Up to initialise the reader and display the desired codes:

SPI.begin(); //Start the reciever database

rc.PCD_Init(); //initialize the receiver

rc.PCD_DumpVersionToSerial(); // Tell us what version we're using

Serial.println(F("the authorised cards are")); //display authorised cards just to demonstrate you may comment this section out

for (int i = 0; i < N; i++) { // Check each card in order

Serial.print(i + 1);

Serial.print(" ");

for (int j = 0; j < 4; j++) {

Serial.print(defcard[i][j], HEX); //Print each cards code in order

}

Serial.println("");

}

Serial.println("");

Serial.println(F("Scan Access Card to see Details"));

}


Code to add to the bottom of Void loop. This checks the scanned dongle and if it’s a correct one marks which one before changing that tools state and sending the message to the server:

readSuccess = getid(); //If we register a card

if (readSuccess) {

int match = 0;

//See if it is one of our registered cards/tools

for (int i = 0; i < N; i++) {

if (!memcmp(readcard, defcard[i], 4)) { //if it is

Code = i; //Set the tool value to the corresponding tool

Serial.println(i); // print it

match++;

}

}

if (Code == 1){ // If its tool 1…

if (ToolA ==1){ // ...and its being taken out...

client.publish("Product/Tool1", "Tool 1 is: IN USE"); //... Say its being taken out on the server...

ToolA = 2;//... and change its status to out

delay(2000);

}

else{ // ...and its being put back...

client.publish("Product/Tool1", "Tool 1 is: AVAILABLE"); //... Say its being put back on the server...

ToolA = 1; //... and change its status to available

delay(2000);

}

}

if (Code == 2){// If its the 2nd tool...

if (ToolB==1){ // ...and its being taken out...

client.publish("Product/Tool2", "Tool 2 is: IN USE"); //... Say its being taken out on the server...

ToolB = 2; //... and change its status to out

delay(2000);

}

else{ // ...and its being put back...

client.publish("Product/Tool2", "Tool 2 is: AVAILABLE"); //... Say its being put back on the server...

ToolB = 1; //... and change its status to available

delay(2000);

}

}

if (Code == 0){// If its the 2nd fork... Note: Small quirk of the matrix is that it starts at 0 for this variable.

if (ToolC ==1){ // ...and its being taken out...

client.publish("Product/Tool3",” Tool 3 is: IN USE"); //... Say its being taken out on the server...

ToolC = 2; // ... and change its status to out

delay(2000);

}

else{ // ...and its being put back...

client.publish("Product/Tool3", "Tool 3 is: AVAILABLE"); //... Say its being put back on the server...

ToolC = 1; //... and change its status to available

delay(2000);

}

} //Delays included to prevent double scans

}

}

Skeleton Code

Matrices Starts at 0So 1 =0, 2 =1, ect for Code variable. Channels and tool variable can customised to reflect to tool in question but each must be unique.

A = Matrices value for Code variable

ToolX is the variable for the tools status


if (Code == A){

if (ToolX==1){

client.publish("Product/ToolX", "Tool X is: IN USE”);

ToolX = 2;

delay(2000);

}

else{

client.publish("Product/ToolX", "Tool X is: AVAILABLE");

ToolX = 1;

delay(2000);

}

}


Insert After Void Loop outside of any other voids. This subfunction collects the id of the scanned dongle and displays it via the Serial monitor. This is especially useful during set up and expansion of the product.


int getid() {

if (!rc.PICC_IsNewCardPresent()) {

return 0;

}

if (!rc.PICC_ReadCardSerial()) {

return 0;

}


Serial.println("THE UID OF THE SCANNED CARD IS:");

for (int i = 0; i < 4; i++) {

readcard[i] = rc.uid.uidByte[i]; //storing the UID of the tag in readcard

Serial.print("0x");

Serial.print(readcard[i], HEX);

if (i < 3) {

Serial.print(",");

}

}

Serial.println("");

Serial.println("Now Comparing with Authorised cards");

rc.PICC_HaltA();

return 1;

}


Conclusion

We hope that this brief instructible is helpful for when you try to create and customise your own allotment monitoring system. Its Versatility and modularity will hopefully appeal to your needs.



Authors:

The Nature Uniting Technology Towards Interconnected Networks Group

Calum Moore

Luke Ryder

Fergus Ried

Alan Arthur William Martin