Project tutorial

SmartLake © GPL3+

An all-round and powerful environmental monitoring system for the Green IoT.

  • 2,045 views
  • 0 comments
  • 10 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)
4966285
Solder Wire, Lead Free
electric drill
several drills
electronic tools
coping saw

Apps and online services

About this project

Introduction

Our planet is our home, our only home. Where should we go if we destroy it?

The Dalai Lama, 2004

This quote of great man marks the beginning of our project story. No-one has been able to provide an answer to this essential question so far. Thus, it is our task to preserve and protect Earth as it is the only planet we can live on if humankind is to survive and thrive. Thanks to today’s state-of-the-art technology, whether it is seen as a curse or as a bliss, we are able to design and put to use effective protection systems that can help to save planet Earth.

It is in particular the Internet of Things, and its sector the Green Internet of Things (Green IoT) which is gaining momentum, which is expected to contribute significantly to the preservation of habitats for wildlife, plants and people.

In our project, we will present a universal monitoring system which is based on Sigfox and offers a wide range of applications to monitor environmental aspects of soil, water and air.

The challenge

The idea to develop this system was born in the summer of 2018. That summer was an extremely long hot and dry season, very unusual for Germany, and saw an overheating of many rivers and lakes leading to large amounts of fish dying. This is shown in Figure 1:

Figure 1: Dead fish in the pond of a municipal park ((c) waz.de, Andreas Rorowski)

The causes of these deaths can be explained easily, and they are lethal for any creatures living in water: the temperature of the water rises, reaches critical values and the oxygen dissolved in the water decreases. Additionally, the water volume in a lake or pond reduces due to increased evaporation, and the habitat of the fish shrinks dramatically.

However, it is just as simple to remedy such critical situations: large volumes of fresh water need to be supplied into the lakes and ponds; additionally, air (i.e. oxygen) can be injected, too.

These two measures alone would suffice to save the fish, but: such critical water conditions have to be recognized in due time by the responsible bodies (environmental agencies, local authorities, fire brigades, technical support organizations etc.) to allow for a prompt remediation.

And this is where modern environmental monitoring systems take center stage: measuring stations, set up along the water bodies, cyclically record the crucial water parameters and transmit them to central control stations for further evaluation.

Individual measuring intervals can be selected (e.g. every 10 minutes, every 12 hours, once a day) and once the freely defined limit values are exceeded, corresponding alarm emails are sent to the people responsible.

Thus, a swift response is possible before life-threatening situations occur.

The solution

The environmental monitoring system which we have developed at the university of applied sciences, Technische Hochschule Georg Agricola (THGA), in Bochum, Germany, uses measuring stations to initially determine two relevant water parameters (oxygen level in the water and water temperature) and transmitted to the Sigfox backend via the Sigfox network.

From there, the data will be transmitted further using the internet to a web-based dashboard program (freeware) which allows the visualization and further evaluation by the user.

If the values measured reach critical levels, emails can be sent immediately. Sensors to determine additional water parameters (conductivity, pH-value, turbidity, etc.) can simply be added and integrated into the system.

Nevertheless, this monitoring system is not only suitable for monitoring waterbodies: it can also be used in many areas of active environmental protection, as the core electronic system remains the same and only the sensors have to be selected and adjusted for the specific purpose.

The software used can be freely designed to record, process and transmit all kinds of environmental protection cases.

The only thing that needs to be in place on the spot is the Sigfox network.

The core electronic system of the measuring stations

The central microcontroller module of the measuring stations is the well-known Sigfox Arduino Maker Board MKR FOX1200, Figure 2:

Figure 2: The Sigfox Arduino Maker Board MKR FOX1200

In order to enhance the performance of this module, we have developed a motherboard that features additional components and to which the MKR FOX1200 is mounted, Figure 3:

Figure 3: The motherboard forming the core of the Sigfox environmental measuring stations

Figure 4 shows the block diagram of the motherboard:

*

Figure 4: Block diagram of the motherboard

(see ´Enclosure - Missing figures´)

*

The essential components of the boards are listed below:

  • kernel system: Sigfox Arduino MKR FOX1200 Board
  • 2.9inePaper display (296 * 128 pixels) for local display of information
  • three photo-MOS semiconductor relays (60V DC/AV, 1100 mA) to connect relays, gates, solenoid valves, motors, sirens, warning lights etc.
  • a piezo-buzzer to emit acoustic signals
  • an LDR to measure the ambient brightness
  • an impedance converter for the analog output: buffered output of analog (control) voltages
  • an integrator to convert, if necessary, the PWM signals to direct voltage
  • a slot for BMP280 breakout boards (measuring air pressure and temperature)
  • two slots for MAX31865 breakout boards (measuring temperatures by means of Pt100-RTD)
  • interfaces connected to sockets for I2C(5V), I2C(3.3V), UART(3.3V); here, for example, additional sensors or actuators can be simply connected
  • the remaining port pins of the MKR FOX1200 are connected to screw-type terminals
  • voltage supply: 9-15 VDC

This motherboard is the central unit of all our high-performing Sigfox environmental measuring stations and, due to its interfaces, provides the opportunity to connect a large number of sensors which can be used to measure different environmental data such as:

  • temperature
  • humidity
  • oxygen (O2)
  • carbon dioxide (CO2)
  • methane (CH4)
  • carbon monoxide (CO)
  • pH-value
  • conductivity
  • water level
  • particle dust
  • UV radiation
  • precipitation
  • ….

Thus, our universal measuring stations are ideally suitable for use in many areas of environmental monitoring (Green IoT.

SmartLake – practical uses

The first application of the Project SmartLake was to implement the monitoring of stagnant waters (lakes). Figure 5 shows one measuring station of this project:

Figure 5: A measuring station of the SmartLake Project

Figure 6 shows the block diagram of the station, and Figure 7 shows the path the sensor signal takes to the user dashboard:

*

Figure 6: The block diagram of the measuring station

(see ´Enclosure - Missing figures´)

*

*

Figure 7: The path of the sensor signal to the user dashboard

(see ´Enclosure - Missing figures´)

*

The kernel of the measuring station is the MKR FOX1200 motherboard, supplemented by a solar power supply unit (solar panel, solar charge controller and rechargeable battery).

These components are installed into a waterproof enclosure of ingress protection IP67 (water and dust).

The solar generator enables a smooth 24/7 year-round permanent operation of the station.

The different sensors are connected using external plugs. The solar panel cables are led directly into the station, and the connection to the development computer is done using a water-tight USB slot.

How it works

The water parameters to be measured are recorded by sensors suitable for the respective purpose:

  • A sensor made by AtlasSientific ( Figure 8) is used to measure the oxygen level in the water. Such sensors are used in professional applications (e.g. fish farming, hydroponics, environmental monitoring) and thus are rather expensive. However, they provide the advantage of being particularly robust which is very useful for extreme outdoor uses such as lake monitoring: these reliable sensors run maintenance-free and over long periods of time. Of course, it is also possible to use other, less expensive senor units provided they can be actuated via the I2C-bus. The sensor output signal is processed by an appropriate measuring amplifier PCB, digitized and transmitted to the MKR FOX1200 board via the I2C-bus.
  • The water temperature is measured using a Pt100-RTD which is water-tight encapsulated, Figure 9. A MAX31865 breakout board is used to record the values measured by Pt100; these are then digitized and transmitted to the MKR FOX1200 via the SPI-bus. The motherboard accommodates two of these Pt100 measuring points; at the moment, however, only one is in use.
  • The breakout board of the BMP280, as shown in Figure 10, is used to determine the ambient temperature (the BMP280 also measures the current air pressure, but this values is not used further for this purpose).

Figure 8: O2 sensor and its measuring amplifier PCB (AtlasScientific)

Figure 9: Pt100 with breakout board MAX31865

Figure 10: Breakout board with BMP280

The two sensors are connected to the measuring station by means of a BNC connector and and a circular connector as shown in Figure 11:

Figure 11: Sensors connected to the measuring station

The cycle time of recording the values measured can be set and defined in the program; according to the Sigfox specifications the minimum interval is 10 minutes, Figure 12:

/***********************************************************/
/*** Waiting time for Sigfox transmission (in minutes) ***/
/***********************************************************/
unsigned int steps = 180; // Steps
signed int k = steps;
/* With a time delay of delay(10) in the loop-function
the following applies to the cycle time:
steps Time between two transmission
52 3:02 min
180 10:01 min
270 14:56 min
540 28:56 min
*/

Figure 12: Defining the measuring cycle time in the program

The values measured are then processed and displayed permanently on a 2.9in ePaper display permanent as local signals.

Once the defined cycle time is over, the data will be transmitted to the Sigfox back-end, Figure 13:

Figure 13: Data received by the Sigfox back-end

Additionally, alarm emails can be sent immediately once pre-set limit values are exceeded.

Power to the entire measuring station is supplied by a solar generator which consists of a solar panel, a charge controller and a rechargeable battery as shown in Figure 14:

Figure 14: Solar generator

System advantages

The measuring station which is the result of this development process can be used in many areas where monitoring is required. Those areas can include industrial systems or processes; monitoring in agriculture and forestry; monitoring of grids supply gas, water or electricity, oil and gas pipelines and such.

Due to the flexible options to connect numerous different sensors and actuators, from very simple designs to sophisticated professional units, monitoring assignments of very different natures can be optimally designed and operated.

The use of the solar generator ensures that operation does not need to rely on local power supply; the stations can be erected in a field, on a building or in a vehicle 24/7, year-round.

The hardware-related design of the station can be implemented easily and at low cost, and the cost of the Sigfox network are also very low.

The only thing that needs to be individually adjusted is the software used for recording the values measured and for entering these values into the Sigfox payload.

To sum up, there is an almost unlimited number of possible applications of these Sigfox-monitoring stations and the Sigfox network enables a simple and nearly worldwide access to data.

Design of the measuring station

The assembly of the motherboard

Figure 15 shows the circuit diagram of the motherboard:

Figure 15: Motherboard circuit diagram

For this motherboard, we designed a special PCB using the EAGLE CAD system.

If people want to rebuild this PCB, they can buy one of our PCBs or create, modify and enlarge their own PCBs based on the EAGLE files which have been published.

Below, we are going to show step by step how the motherboard is assembled, Figure 16; simple soldering knowledge is sufficient to carry out this work.

*

Figure 16: Assembly of the motherboard

(see ´Enclosure - Missing figures´)

*

Mounting the measuring station

Once a suitable enclosure has been found for the measuring station, Figure 17, modification of the enclosure can begin, Figure 18:

Figure 17: A suitable enclosure for the measuring station

Figure 18: Modifying the enclosure

Once the enclosure has been modified, the motherboard will be installed into the enclosure together with the rechargeable battery, the solar charge controller and the measuring amplifier card for the O2 sensor.

The O2 sensor and the Pt100 will be connected using a appropriate BNC connector and a circular connector; the cables of the solar panel will be led into the enclosure using cable glands, Figure 19:

Figure 19: installing the components

By doing so, the hardware installation of the measuring station is complete which now takes us to the operating software for the MKR FOX1200 board.

Calibrating the sensors

The sensors used – O2-sensor, BMP280 and Pt100 – can be easily calibrated.

The Pt100 is simply connected to the measuring amplifier in two-wire system; the amplifier processes the value measured and sends it to the MKR FOX1200 board via the I2C bus. There, the data are processed further.

Likewise, the BMP280 (measuring air pressure and air temperature) does not need particular calibration: the breakout board is simply inserted and data is retrieved via the I2C bus.

It is, however, necessary to calibrate the O2-sensor made by AtlasScientific, but this can be implemented very easily.

Please note:

Before installing the O2 meauring amplifier, the data transfer mode needs to be switched from UART to I2C.

This can be done without any problems and is described in the data sheet of the measuring amplifier (p.41)

For the calibration of the sensor we have written a little Arduino sketch (DO_I2C-V5_en.ino, based of a sketch from AtlasScientific), which allows to operate, address and configure the sensor, Figure 20:

/*************************************************************************/
/* */
/* Sigfox-Challenge 2019 */
/* */
/* SmartLake */
/* */
/* Demoprogramm for the AtlasScientific-Sensor */
/* for Dissolved Oxygen (DO) */
/* Developed by: Frank Schleking, Philipp Krienke */
/* Filip Schmachtenberger, Bernd vom Berg */
/* */
/* University Georg Agricola, Bochum, Germany */
/* */
/* DO_I2C-V5_en.ino Version 5.0, 12.10.2019 */
/* */
/*************************************************************************/
//Include the required standard libraries
#include <Wire.h> //I2C-Library
#define address 97 //default I2C ID adress for EZO DO Circuit.
// Global Variables
char do_data[20]; // Data from the DO sensor as a string
unsigned char taste;
/*******************************************************************/
/*********** Setup - Function ************************************/
/*******************************************************************/
void setup() // called once
{
Serial.begin(9600); //enable serial port.
Wire.begin(); //enable I2C port.
delay(2000); //Switching time for ser. monitor
}
/*********************************************************************************/
/*********************************************************************************/
void loop() //the main infinite loop
{
char wahl;
// clear screen
clear_screen();
// Output title Screen on serial monitor
titel_bild();
// Wait for input
while (Serial.available() == 0);
// Evaluation of the input
wahl = Serial.read();
Serial.print(wahl);
delay(1000); // short waiting time that you can see the input
// Evaluation of the input
switch (wahl)
{
case '1': DO_information();
break;
case '2': DO_mess();
break;
case '3': DO_rueck();
break;
case'4': DO_cal_luft();
break;
default: Serial.print("\n\nInvaild selection. Please try again!");
delay(4000);
break;
}
}
/************************************************************************************/
/************************************************************************************/
/************************************************************************************/
/*** Clear serial monitor ´tricky´ ***/
void clear_screen(void)
{
unsigned char i;
for (i = 0; i < 35; i++) Serial.println(" ");
}
/*******************************************************************/
/*** Output title Screen on serial monitor***/
void titel_bild(void)
{
clear_screen();
Serial.println("**************************************************************");
Serial.println("***** Demoprogramm for the AtlasScientific-Sensor *****");
Serial.println("***** for Dissolved Oxygen (DO) *****");
Serial.println("***** *****");
Serial.println("***** I2C-Bus-Adress : 97 (dez) *****");
Serial.println("**************************************************************");
Serial.println("\nPlease choose:\n");
Serial.println(" 1) Read DO-Sensor-Information");
Serial.println(" 2) Read DO-Values");
Serial.println(" 3) Choose unit for DO-Sensor-Values");
Serial.println(" 4) Calibration to O2 air value: 9,09 - 9,1 mg/l");
Serial.print("\n\nYour Choice: ");
}
/********************************************************************/
void DO_send (char data[20])
{
unsigned char i = 0;
unsigned char code = 0;
unsigned char in_char = 0;
int time_;
// Waiting time for sensor-response
if (data[0] == 'c' || data[0] == 'r')time_ = 575; //if a command has been sent to calibrate or take a reading we wait 575ms so that the circuit has time to take the reading.
else time_ = 300; //if any other command has been sent we wait only 300ms.
// !!!! Anders als im Original-Programm !!!!
// Start I2C communication
Wire.beginTransmission(address); //call the circuit by its ID number.
// Send: Command
Wire.write(data); //transmit the command that was sent through the serial port.
Wire.endTransmission(); //end the I2C data transmission.
// Receiving the answer if command is not equal to 'sleep'
if (strcmp(data, "sleep") != 0) //if the command that has been sent is NOT the sleep command, wait the correct amount of time and request data.
{ //if it is the sleep command, we do nothing. Issuing a sleep command and then requesting data will wake the D.O. circuit.
delay(time_); //wait the correct amount of time for the circuit to complete its instruction.
// Request of max. 20 bytes of data from the slave
Wire.requestFrom(address, 20, 1); //call the circuit and request 20 bytes (this is more than we need)
// Reading the first byte = status byte
code = Wire.read(); //the first byte is the response code, we read this separately.
/*
// If required, for test purposes: Evaluation of the status byte
switch (code) //switch case based on what the response code is.
{
case 1: //decimal 1.
Serial.println("Success"); //means the command was successful.
break; //exits the switch case.
case 2: //decimal 2.
Serial.println("Failed"); //means the command has failed.
break; //exits the switch case.
case 254: //decimal 254.
Serial.println("Pending"); //means the command has not yet been finished calculating.
break; //exits the switch case.
case 255: //decimal 255.
Serial.println("No Data"); //means there is no further data to send.
break; //exits the switch case.
}
*/
// Reading in and storing the remaining bytes in array do_data
while (Wire.available()) //are there bytes to receive.
{
in_char = Wire.read(); //receive a byte.
do_data[i] = in_char; //load this byte into our array.
i += 1; //incur the counter for the array element.
if (in_char == 0) //if we see that we have been sent a null command.
{
i = 0; //reset the counter i to 0.
Wire.endTransmission(); //end the I2C data transmission.
break; //exit the while loop.
}
}
/*
// If required, for test purposes: Output of the received data
Serial.print("Return Value: ");
Serial.println(do_data); //print the data.
Serial.println(); //this just makes the output easier to read by adding an extra blank line
*/
}
}
/************************************************************************************/
/*** DO - Read DO-Sensor-Information ***/
void DO_information(void)
{
clear_screen();
Serial.println("DO - Reading Information");
Serial.println("(Cancel with key... )\n\n");
delay(2000);
while(1)
{
// Reading Sensor-Value
DO_send("i");
Serial.print("DO-Info: ");
Serial.println(do_data);
Serial.println();
delay(500);
if (Serial.available() > 0) // Waiting for keystroke
{
taste = Serial.read();
break;
}
}
}
/************************************************************************************/
/*** Get DO-Values ***/
void DO_mess(void)
{
clear_screen();
Serial.println("Read DO-Values");
Serial.println("(Cancel with key... )\n\n");
delay(2000);
while(1)
{
// Query of the sensor
DO_send("r");
Serial.print("DO-Value: ");
Serial.println(do_data);
Serial.println();
delay(1000);
if (Serial.available() > 0) // Waiting for keystroke
{
taste = Serial.read();
break;
}
}
}
/************************************************************************************/
/*** Choose unit for DO-Sensor-Values ***/
void DO_rueck(void)
{
char wahl;
while(1)
{
clear_screen();
Serial.println("Choose unit for DO-Sensor-Values\n");
Serial.println("Please choose:\n");
Serial.println(" 1 = Value in mg/l");
Serial.println(" 2 = Value in % saturation");
Serial.println(" 3 = Both units\n\n");
Serial.print("Your Choice: ");
// Wait for input
while (Serial.available() == 0);
// Evaluation of the input
wahl = Serial.read();
Serial.print(wahl);
// Evaluation of the input
switch (wahl)
{
case '1': DO_send("o,mg,1"); // mg/l enable
DO_send("o,%,0"); // % disable
break;
case '2': DO_send("o,mg,0"); // mg/l disable
DO_send("o,%,1"); // % enable
break;
case '3': DO_send("o,mg,1"); // mg/l enable
DO_send("o,%,1"); // % enable
break;
default: Serial.print("\n\nInvalid Choice. Please try again!");
delay(4000);
break;
}
if ((wahl == 0x31) || (wahl == 0x32) || (wahl == 0x33))
{
Serial.println("\n\nSet! Continue with any key ...");
while (Serial.available() == 0); // Waiting for keystroke
taste = Serial.read();
break;
}
}
}
/*****************************************************************************/
/*** DO - Calibration to O2 air value ***/
void DO_cal_luft(void)
{
while(1)
{
clear_screen();
Serial.println("DO - Calibration to O2 air value: 9,09 - 9,1 mg/l\n");
Serial.println("Dry sensor and hang vertically in air.\n");
Serial.println("Observe readings and if stable (no matter what value),");
Serial.println("Press the 'c' key ...\n");
Serial.println("Start the measurements now with a keystroke ...\n\n");
// Waiting for input
while (Serial.available() == 0);
// Evaluation of the input
taste = Serial.read();
// Continuous measurement
while(1)
{
// Query of the sensor
DO_send("r");
Serial.print("DO-Value: ");
Serial.println(do_data);
delay(1000);
if (Serial.available() > 0) // Waiting for keystroke
{
taste = Serial.read();
break;
}
}
// Call calibration
DO_send("cal");
Serial.println("\n\nWaiting ...");
delay(1300);
Serial.println("\nCalibration done! \n\n");
Serial.println("Exit with any key ... ");
// Continuous measurement
while(1)
{
// Reading the sensor
DO_send("r");
Serial.print("DO-Value: ");
Serial.println(do_data);
delay(1000);
if (Serial.available() > 0) // Waiting for keystroke
{
taste = Serial.read();
break;
}
}
// Return
return;
}
}
/************************************************************************************/

Figure 20: The Arduino sketch for operating the O2-sensor

Figure 21 shows the main menu of this sketch:

Figure 21: Main menu for sketch operating the O2-sensor

To calibrate the sensor, it should be suspended vertically and dry in the air. Then, you need to wait a few minutes before selecting menu item "4) Calibration to O2 air value: 9, 09 - 9, 1 mg/l" to start the recording of the measuring values. Once the value measured is stable (no matter which value is shown), you press the ‘c’ key. This value measured is now equivalent to the median oxygen level in air (9.09 mg/l); accordingly, the current value is recorded as 9.09 mg/l and thus stored in the sensor.

The sensor will now convert all values measured subsequently to that value.

The other menu items of Figure 21 are needed for the test operation of the sensor and can be used anytime.

After all these steps have been completed, the measuring station is ready for use within the Sigfox network.

The operating software for the monitoring station

The entire operating software for the MKR FOX1200 has been created in C using the Arduino IDE.

Figure 22 shows the flowchart of the sketch:

*

Figure 22: Flowchart of the Arduino sketch

(see ´Enclosure - Missing figures´)

*

The Arduino sketch has been extensively documented and is provided in the Attachments; here, we would only like to explain a few core features.

Firstly, the following libraries have to be embedded in the IDE:

Arduino libraries:

  • Arduino Low Power
  • Arduino Sigfox for MKRFOX1200
  • RTCZero

External libraries:

  • BMx280MI
  • GxEPD
  • Adafruit Circuit Playground
  • Adafruit GFX library
  • Adafruit MAX31865 library

Then, the Arduino sketch starts with embedding the libraries, defining the specific variables for the individual system components and then also defining the global variables, Figure 23:

/*************************************************************************/
/* */
/* Sigfox-Challenge 2019 */
/* */
/* SmartLake */
/* */
/* An all-round and powerfull monitoring-system for the Green IoT */
/* */
/* Developed by: Frank Schleking, Philipp Krienke */
/* Filip Schmachtenberger, Bernd vom Berg */
/* */
/* University Georg Agricola, Bochum, Germany */
/* */
/* SF-Cha-5_en.ino Version 5.0, 12.10.2019 */
/* */
/*************************************************************************/
//Include the required standard libraries
#include <ArduinoLowPower.h> // Contains features for sleep and deep-sleep
#include <SigFox.h> // Contains the Sigfox functions
#include <SPI.h> // Library for SPI-BUS
#include <Wire.h> // I2C-Library
#include <Adafruit_MAX31865.h> // for the pt100 - MAX31865-Board
// MAX31865-Board (Pt100)____________________________________________________
// use hardware SPI, just pass in the CS pin (active low)
Adafruit_MAX31865 pt100_1 = Adafruit_MAX31865(A5); // for Pt100_1
Adafruit_MAX31865 pt100_2 = Adafruit_MAX31865(A6); // for Pt100_2
// The value of the Rref resistor. Use 430.0 for PT100
#define RREF 430.0
// The 'nominal' 0-degrees-C resistance of the sensor
// 100.0 for PT100
#define RNOMINAL 100.0
// E-PAPER-Display___________________________________________________________
//Include the e-paper display library and additional required libraries
#include <GxEPD.h>
//Include the Library for the selected Display: 2,9´´ black/white
#include <GxGDEH029A1/GxGDEH029A1.h> // for 2.9 inch s/w Display
//Include the Communication-Libraries for the Displays
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>
// Include fonts of the Adafruit-GFX Library
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
// Create an object of the GxIO-Class
// Defining the pins used for CS, DC, and Reset.
// The name of the object is "io"
GxIO_Class io(SPI, /*CS=*/ 4, /*DC=*/ 7, /*RST=*/ A1);
// Create an object of the GxEPD-Class an define the pins
// for Reset an Busy and the previously created object of the GxIO-class "io"
// The name of the object is "display"
GxEPD_Class display(io, /*RST=*/ A1, /*BUSY=*/ 5);
// BMP280-Ambient-Pressure-Sensor_____________________________________________________
// Include the BMP280-Library
#include <BMx280MI.h>
//The default I2C-Adress of the BMP280 is 0x76
#define I2C_ADDRESS 0x76
// Create a BMx280I2C object using the I2C interface with I2C Address 0x76
// The name of the object is "bmx280"
BMx280I2C bmx280(I2C_ADDRESS);
// Pt100 - MAX31865__________________________________________________________
#define Pt_address 97 //default I2C ID number for EZO DO Circuit.
//Global Variables___________________________________________________________
int versorgungs_pin = 0; // Port pin for switching the power supply for
// the ePaper display
// Variables for the measured values
float luftdruck = 0; // Air pressure from the BMP280
float temperatur = 0; // Temperature from the BMP280
float temp_pt100_1 = 0; // Pt100-value 1
float temp_pt100_2 = 0; // Pt100-value 2
char do_data[20]; // Data from the DO sensor as a string
float O2; // DO-value as a float
// General variables
unsigned char taste;
unsigned char first = 1;
unsigned long time_new, time_old; // For the time measurement between
// the transmissions
/***********************************************************/
/*** Waiting time for Sigfox transmission (in minutes) ***/
/***********************************************************/
unsigned int steps = 180; // Steps
signed int k = steps;
/* With a time delay of delay(10) in the loop-function
the following applies to the cycle time:
steps Time between two transmission
52 3:02 min
180 10:01 min
270 14:56 min
540 28:56 min
*/

Figure 23: Embedding the libraries and defining the specific variables

In the setup part of the sketch which now follows the individual assemblies are initialized, Figure 24:

void setup()
{
//Initialisiere the serial Interface
Serial.begin(9600);
//Initialize the I2C-Interface
Wire.begin();
//Declare power supply pin as output (MOSFETs Pin 1)
pinMode(versorgungs_pin, OUTPUT);
// Switch on the power supply for the Display
digitalWrite(versorgungs_pin, HIGH);
//Initialize the E-Paper-Display
display.init();
// Waiting for communication via serial monitor
delay(2000);
/********* Initialization BMP280 ******/
if (!bmx280.begin())
{
Serial.println("Initialization failed. Please check BMP280 and I2C-Adress");
while (1);
}
/******** Initialization for MAX31865 ******/
//set to 2WIRE or 4WIRE as necessary; here: 2WIRE
pt100_1.begin(MAX31865_2WIRE);
pt100_2.begin(MAX31865_2WIRE);
// Show start-up sequence on ePaper display
start_up();
//Creates the mask for the ePaper display with the measurement names and units
display_maske();
}

Figure 24: Setup part: initializing the individual assemblies

The main program (loop part of the sketch) carries out the measuring of the three values and displays them for checking on the ePaper display and via the serial monitor, Figure 25:

void loop()
{
// Required variables
unsigned int z;
// Start measurement of BMP280
bmx280.writeOversamplingPressure(BMx280MI::OSRS_P_x16);
bmx280.writeOversamplingTemperature(BMx280MI::OSRS_T_x16);
/***** Get measured values from BMP280 *****/
if (first)
{
luftdruck = bmx280.readPressure()/100; // Ignore the first value
first = 0;
}
delay(10);
temperatur = bmx280.readTemperature(); // Temperature reading
delay(10);
luftdruck = bmx280.readPressure()/100; // Air pressure reading
// From the Pt100 measuring point 1
temp_pt100_1=pt100_1.temperature(RNOMINAL, RREF);
delay(10);
// From the O2 sensor
DO_send("r");
// Convert do_data to float
O2 = (do_data[0]-0x30) + ((do_data[2]-0x30)/10.0) + ((do_data[3]-0x30)/100.0);
// Output measured value on serial monitor
Serial.print("O2-Water: ");
Serial.print(do_data);
Serial.println(" mg/l");
Serial.print("Temp.-Water: ");
Serial.print(temp_pt100_1,1);
Serial.println(" °C");
Serial.print("Temp.-Amb.: ");
Serial.print(temperatur,1);
Serial.println(" °C");
Serial.print("Press.-Amb.: ");
Serial.print(luftdruck,1);
Serial.println(" hPa\n");
// Output measured value on the ePaper-Display
display_messwerte(luftdruck, temperatur, temp_pt100_1, temp_pt100_2);

Figure 25: Recording the values measured and displaying them via the serial monitor and on the ePaper display

Then it will be checked whether the transmitting cycle time has already run out. If that is the case, the data measured will be sent to the Sigfox cloud via the Sigfox network (using the function send data(…)).

If the cycle time has not finished yet, the time counter (the steps variable) is raised by 1 and displayed on the ePaper display and the serial monitor. Then, the recording cycle of the values measured starts again, Figure 26:

// Send readings to the Sigfox cloud when the wait time has expired
if (k == steps)
{
daten_senden(O2, temp_pt100_1, temperatur);
display.update(); // refresh Display
k = -1;
}
// Waiting time between the transmissions
delay(10); // Wait 10 ms
k = k + 1;
z = steps-k;
Serial.println("Next transmission in: " + String(z) + " Steps\n");
/***** Step counter on the ePaper display *****/
// White rectangle for deleting the measured value field
display.fillRect(185, 40, 68, 15, GxEPD_WHITE);
// Output residual value
display.setCursor(185,51);
display.println(z);
// Partial refresh from the display: Range for the measured values
display.updateWindow(185, 40, 68, 15, true);

Figure 26: Checking the transmitting cycle time and the transmission of the Sigfox telegraph

A few functions written by our team complete the program for the MKR FOX1200.

The essential functions written by us are these:

  • recording the measuring value of the oxygen sensor and
  • transmitting data to the Sigfox cloud

Figure 27 shows the function of how the data of the oxygen sensor is retrieved (DO = Dissolved Oxygen) via the I2C bus:

// Read from the O2 sensor
DO_send("r");
// Convert do_data to float
O2 = (do_data[0]-0x30) + ((do_data[2]-0x30)/10.0) + ((do_data[3]-0x30)/100.0);

//
//
//And here the function:
//
//


/*** Read the oxygen sensor via I2C: Process and send the telegram ***/
void DO_send (char data[20])
{
unsigned char i = 0;
unsigned char code = 0;
unsigned char in_char = 0;
int time_;
// Set the waiting time for sensor response
if (data[0] == 'c' || data[0] == 'r')time_ = 575; //if a command has been sent to calibrate or take a reading we wait 575ms so that the circuit has time to take the reading.
else time_ = 300; //if any other command has been sent we wait only 300ms.
// !!!! Anders als im Original-Programm !!!!
// Start I2C communication
Wire.beginTransmission(Pt_address); //call the circuit by its ID number.
// Send: Command
Wire.write(data); //transmit the command that was sent through the serial port.
Wire.endTransmission(); //end the I2C data transmission.
// Receiving the answer if command is not equal to 'sleep'
if (strcmp(data, "sleep") != 0) //if the command that has been sent is NOT the sleep command, wait the correct amount of time and request data.
{ //if it is the sleep command, we do nothing. Issuing a sleep command and then requesting data will wake the D.O. circuit.
delay(time_); //wait the correct amount of time for the circuit to complete its instruction.
// Request of max. 20 bytes of data from the slave
Wire.requestFrom(Pt_address, 20, 1); //call the circuit and request 20 bytes (this is more than we need)
// Reading the first byte = status byte
code = Wire.read(); //the first byte is the response code, we read this separately.
/*
// If required, for test purposes: Evaluation of the status byte
switch (code) //switch case based on what the response code is.
{
case 1: //decimal 1.
Serial.println("Success"); //means the command was successful.
break; //exits the switch case.
case 2: //decimal 2.
Serial.println("Failed"); //means the command has failed.
break; //exits the switch case.
case 254: //decimal 254.
Serial.println("Pending"); //means the command has not yet been finished calculating.
break; //exits the switch case.
case 255: //decimal 255.
Serial.println("No Data"); //means there is no further data to send.
break; //exits the switch case.
}
*/
// Reading in and storing the remaining bytes in array do_data
while (Wire.available()) //are there bytes to receive.
{
in_char = Wire.read(); //receive a byte.
do_data[i] = in_char; //load this byte into our array.
i += 1; //incur the counter for the array element.
if (in_char == 0) //if we see that we have been sent a null command.
{
i = 0; //reset the counter i to 0.
Wire.endTransmission(); //end the I2C data transmission.
break; //exit the while loop.
}
}
/*
// If required, for test purposes: Output of the received data
Serial.print("Return Value: ");
Serial.println(do_data); //print the data.
Serial.println();
*/
}

Figure 27: Retrieving data from the oxygen sensor

Figure 28 illustrates the routine of transmitting data to the Sigfox back-end:

/*** Send data to the Sigfox cloud ***/
void daten_senden(float O2, float temp_water, float temp_amb)
{
/*** Determine and output time between two transmissions ***/
time_old = time_new;
time_new = millis();
Serial.print("Time between transmissions: ");
Serial.print((time_new-time_old)/60000.0);
Serial.println(" min.");
/*** transmissions begins ***/
Serial.println("\n TRANSMISSION !!\n");
// Initialize the Sigfox modem
SigFox.begin();
delay(100);
// Enable debug led and disable automatic deep sleep
SigFox.debug();
// Clears all pending interrupts
SigFox.status();
delay(1);
// The structure to be sent must be declared as "packed".
// This removes the padding bytes.
typedef struct __attribute__ ((packed)) sigfox_message {
float O2; // 4 Bytes
float Temp_Water; // 4 Bytes
float Temp_Amb; // 4 Bytes
} SigfoxMessage;
// Create the variable "reading" from the previously created structure type
SigfoxMessage reading;
// Write the measured values into the structure
reading.O2 = O2;
reading.Temp_Water = temp_water;
reading.Temp_Amb = temp_amb;
//Preparing to send a package
SigFox.beginPacket();
// Send the message to the Sigfox backend
SigFox.write((char*)&reading, sizeof(reading));
// If endPacket() returns 'true' then error message
int ret = SigFox.endPacket();
if (ret > 0)
{
Serial.println("Error: no transmission!");
}
else
{
Serial.println("TRANSMISSION OK !!\n\n");
}
//De-Initializes the Sigfox library and the module
SigFox.end();
}

Figure 28: Sending data to the Sigfox back-end

Here, the structure of the Sigfox payload requires special attention as only a maximum of 12 user data bytes are transmitted in the payload.

In our application, the payload is completely filled with the three float measuring values of 4 bytes each:

  • 4 bytes for the oxygen value (O2)
  • 4 bytes for the water temperature (temp_water)
  • 4 bytes for the ambient temperature (temp_amb)

To enable a simple writing of these values into the payload, we have defined an own structure in C (called sigfox_message), added an own data type (called SigfoxMessage) and a suitable variable of this data type (called reading), as shown in Figure 29:

// The structure to be sent must be declared as "packed".
// This removes the padding bytes.
typedef struct __attribute__ ((packed)) sigfox_message {
float O2; // 4 Bytes
float Temp_Water; // 4 Bytes
float Temp_Amb; // 4 Bytes
} SigfoxMessage;
// Create the variable "reading" from the previously created structure type
SigfoxMessage reading;
// Write the measured values into the structure
reading.O2 = O2;
reading.Temp_Water = temp_water;
reading.Temp_Amb = temp_amb;

Figure 29: Self-defined structure, data type and variable plus how the values measured are written into this variable

Now, the three values measured can very easily be written into this variable.

Then, the variable itself is just transferred to the Sigfox “send” function which transmits the data:

// Send the message to the Sigfox backend
SigFox.write((char*)&reading, sizeof(reading));

Working with such C-structures means that the transmission of the Sigfox payload can easily be adapted to other applications: only the design of the structure needs to be modified (i.e. to adjust the configuration of the 12 payload bytes with regard to the application), the new measuring values have to be written into the variable and then passed on for transmission.

Registering the station with the Sigfox back-end

First of all, the measuring station needs to be registered with the Sigfox back-end.

To do so, you need the internal ID and the PAC number of the Sigfox modem on the MKR FOX1200 board used.

To be able to read these data, we wrote again a little Arduino demo sketch (Sigfox-3_0_en.ino) as shown in Figure 30:

/*** Read out and display the configuration data of the Sigfox modem ***/
void sf_konfig_daten(void)
{
// Clear screen
clear_screen();
// Activate and initialize the Sigfox modem
if (!SigFox.begin())
{
Serial.println("Sigfox modem not found! - Continue with RESET!");
while (1); // In this case: infinite loop
}
else
{
Serial.println("Sigfox-Modem OK !\n");
}
//Read and save the firmware version, ID, PAC and Temp. of the modem
String version = SigFox.SigVersion();
String ID = SigFox.ID();
String PAC = SigFox.PAC();
float temp = SigFox.internalTemperature();
// Send modem information to the serial monitor
Serial.println("MKR FOX 1200 - read configuration data:\n");
Serial.println("Firmware-Version: " + version);
Serial.println("ID = " + ID);
Serial.println("PAC = " + PAC);
Serial.println("Int. Temperature = " + String(temp, 1) + " °C");
delay(100); //Wait 100ms until the serial transfer is finished
SigFox.end(); // send the modem into sleep
Serial.println("\n\nPlease press the button!");
while (Serial.available() == 0); // Waiting for keystroke
taste = Serial.read();
}

Figure 30: The Arduino sketch for the Sigfox demo operation - part: Read out and display the configuration data of the Sigfox modem

Load the sketch and select menu item "1) Read Sigfox configuration data” from the main menu, Figure 31:

Figure 31: Main menu of Sigfox demo sketch

Now the data needed are read from the Sigfox modem of the MKR FOX1200 board which need to be noted for later use.

Next, the MKR FOX1200 board can be registered with the Sigfox backend using the address below:

https://buy.sigfox.com/activate

There, you need to carry out all steps necessary.

Now you can log in yourself as a user to the Sigfox backend providing your e-mail address and a password:

https://backend.sigfox.com/auth/login

Click the tab “Device” on the Sigfox portal to go to the device overview; there you will see that your MKR FOX1200 board has already been registered as shown in Figure 32:

Figure 32: Sigfox device overview

Everything looks fine and you can leave the Sigfox back-end.

Registering with thinger.io

We operate our measuring stations via Sigfox network in conjunction with the freeware dashboard program thinger.io (www.thinger.io).

thinger.io is an open-source IoT visualization platform which allows to create quickly and simply clear and illustrative data visualizations. For smaller projects, this software is free of charge.

To develop an individual dashboard for your own PC, laptop or smart phone just a few more simple steps are needed:

  • create a free user account with thinger.io
  • set up a data bucket with thinger.io which receives the measured values sent by the Sigfox backend
  • define the token, i.e. the access point for the backend of thinger.io and set up an authentication for the access token at thinger.io which then authorizes the Sigfox backend to transfer data to thinger.io.
  • configure a callback on the Sigfox backend to transfer the data from the Sigfox backend to your data bucket in thinger.io via the internet.
  • design a pretty dashboard on the thinger.io site to visualize your data.

Now, go to thinger.io using this URL:

www.thinger.io

The pictures shown in Figure 33 below explain how to:

  • register with thinger.io
  • set up a data bucket and
  • create a proper access point (token) with thinger.io

Figure 33: Registration and set up with thinger.io

If you finished those steps, the first part of your thinger.io work is done.

Configuration of the Sigfox call-back on the Sigfox backend

Configuration of the Sigfox call-back on the Sigfox backend

As soon as the MKR FOX1200 sends data to the Sigfox backend, these data are to be transmitted automatically to thinger.io for further processing (display). For that purpose, the Sigfox backend provides callbacks.

To create a pertinent callback, start the Sigfox backend.

The pictures shown in Figure 34 below illustrate in detail the configuration of the callback needed:

Figure 34: Creating a callback on the Sigfox backend

And now you have to fill in the fields of callback page very carefully, Figure 35:

Figure 35: Fill in the callback fields

You have to fill in:

  • Type: ´DATA´ and ´UPLINK´
  • Channel: ´URL´
  • Custom payload config: here you put the names of the variables to transfer and their data types. Fill in exactly: O2::float:32:little-endian Temp_Water::float:32:little-endian Temp_Amb::float:32:little-endian
  • Url pattern: here you fill in the URL of your data bucket on thinger.io. Fill in: https://api.thinger.io/v1/users/XXXXX/buckets/ZZZZZ/data with: XXXXX: your user id of your tinger.io account and ZZZZZ: your data bucket id in thinger.io
  • Use HTTP Method: ´POST´
  • Headers: ´Authorization´ and then: Bearer yJhbGciOiJIUzI1........... with yJhbGciOiJIUzI1........... your specific ´Access Token´ from thinger.io
  • Content type: ´application/json´
  • Body: here you have to put the data to be transfered in the json-format:
{
"Device-ID" : "{device}",
"O2" : {customData#O2},
"Temp_Water" : {customData#Temp_Water},
"Temp_Amb" : {customData#Temp_Amb}
}

After fill in the fields click the ´Ok´ button.

Now this callback is used to send the measuring data to thinger.io as soon as they have been received by the Sigfox backend.

At thinger.io, they will then be displayed on a suitable dashboard.

Developing up a dashboard on thinger.io

A Dashboard on thinger.io can easily be designed with just a few mouse clicks. Especially for the initial application, the user does not need any programming skills: everything is done purely graphically and by filling the appropriate configuration boxes!

The information buttons (i) provided can be clicked anytime to receive useful tips on how to fill in the boxes.

Figure 36 shows the individual steps of how to set up dashboard which is still empty:

Figure 36: Setting up an empty dashboard

Once the empty dashboard has been set up, suitable display and presentation elements (called widgets) as needed by the user are selected, configured and placed on the dashboard.This process is also completed with a few mouse clicks.

For our three measuring values, we only need two types of display elements:

  • a time series chart widget which show the timeline of the values measured
  • a number display of the last value measured (Text/Value widget)

The pictures in Figure 37 and Figure 38 show how these widgets are used on the dashboard:

Figure 37: Using the ´Time Series Chart´ - widget on the dashboard

Figure 38: Using the ´Text/Value´ - widget on the dashboard

To save your dashboard switch off and on the edit button.

Repeat all this steps for the other measured values ´Temperature of the water´ and ´Temperature of the ambient´.

Now your project is finished: the measured values, send via the Sigfox backend to thinger.io, will display on your owndashboard!

Publishing the dashboard

Now you can publish your dashboard worldwide via the internet:

Figure 39: Publishing the dashboard

Results of our SmartLake Project

Figure 40 shows how the measuring station is used at a municipal lake:

Figure 40: Measuring station used at a municipal lake

Figure 41 shows the dashboard on the user’s PC and the dashboard on his smartphone:

Figure 41: The dashboards

The people responsible at the respective authorities of the city receive information on the condition of the lake around the clock and can respond accordingly and in due time (providing freshwater inflow or oxygen) before critical situations may occur endangering the animals and plants living in the lake.

Now, a huge step has been taken to protect and preserve the lake.

You can find the dashboard of this project under: https://bit.ly/2P41j1V

Conclusion

Our planet is our home, our only home. Where should we go if we destroy it?

The Dalai Lama, 2004

By using the global Sigfox network and applying our universal and flexible monitoring measuring stations we are able to make a significant contribution to protecting the habitats of animals, plants and people.

The entire mobile and independent system can easily be enlarged and extended for new and different application purposes:

  • use of sensors to measure different values
  • configuration of alarm emails if preset limit values are exceeded
  • simple use of several measuring stations at one site to record additional measuring data: such additional stations can be integrated into the Sigfox network at just a few mouse clicks, and the pertinent dashboards can also be set up quickly
  • cross-border monitoring as the Sigfox network is being developed further in many countries

In other words, the technical conditions and solutions to protect our environment do exist right here, right now.

Now, it is up to the decision-makers in politics and society to take serious and sustainable action!

Code

DO_I2C-V5_en.inoArduino
Software for the operation of the O2 sensor
/*************************************************************************/
/*                                                                       */
/*                     Sigfox-Challenge 2019                             */
/*                                                                       */
/*                          SmartLake                                    */
/*                                                                       */
/*         Demoprogramm for the AtlasScientific-Sensor                   */
/*                     for Dissolved Oxygen  (DO)                        */
/*  Developed by:       Frank Schleking, Philipp Krienke                 */
/*                      Filip Schmachtenberger, Bernd vom Berg           */
/*                                                                       */
/*              University Georg Agricola, Bochum, Germany               */
/*                                                                       */
/*            DO_I2C-V5_en.ino       Version 5.0,   12.10.2019           */
/*                                                                       */
/*************************************************************************/

//Include the required standard libraries
#include <Wire.h>               //I2C-Library

#define address 97              //default I2C ID adress for EZO DO Circuit.


// Global Variables
char do_data[20];               // Data from the DO sensor as a string
unsigned char taste;


/*******************************************************************/
/***********  Setup - Function  ************************************/
/*******************************************************************/

void setup()                    // called once
{
  Serial.begin(9600);           //enable serial port.
  Wire.begin();                 //enable I2C port.

  delay(2000);                  //Switching time for ser. monitor
}

/*********************************************************************************/
/*********************************************************************************/

void loop()                     //the main infinite loop
{
  char wahl;

  // clear screen
  clear_screen();

  // Output title Screen on serial monitor
  titel_bild();

  // Wait for input
  while (Serial.available() == 0);

  // Evaluation of the input
  wahl = Serial.read();
  Serial.print(wahl);
  delay(1000);          //  short waiting time that you can see the input

  // Evaluation of the input
  switch (wahl)
  {
    case '1': DO_information();
              break;

    case '2': DO_mess();
              break;

    case '3': DO_rueck();
              break;

    case'4':  DO_cal_luft();
              break;

    default:  Serial.print("\n\nInvaild selection. Please try again!");
              delay(4000);
              break;
  }

}


/************************************************************************************/
/************************************************************************************/
/************************************************************************************/

/*** Clear serial monitor tricky ***/
void clear_screen(void)
{
  unsigned char i;
  for (i = 0; i < 35; i++) Serial.println(" ");
}

/*******************************************************************/

/*** Output title Screen on serial monitor***/
void titel_bild(void)
{
  clear_screen();
  Serial.println("**************************************************************");
  Serial.println("*****    Demoprogramm for the AtlasScientific-Sensor     *****");
  Serial.println("*****             for Dissolved Oxygen  (DO)             *****");
  Serial.println("*****                                                    *****");
  Serial.println("*****           I2C-Bus-Adress :  97 (dez)               *****");
  Serial.println("**************************************************************");
  Serial.println("\nPlease choose:\n");
  Serial.println("   1)  Read DO-Sensor-Information");
  Serial.println("   2)  Read DO-Values");
  Serial.println("   3)  Choose unit for DO-Sensor-Values");
  Serial.println("   4)  Calibration to O2 air value: 9,09 - 9,1 mg/l");

  Serial.print("\n\nYour Choice:  ");
}

/********************************************************************/

void DO_send (char data[20])
{
  unsigned char i = 0;
  unsigned char code = 0;
  unsigned char in_char = 0;
  int time_;
  
  // Waiting time for sensor-response
  if (data[0] == 'c' || data[0] == 'r')time_ = 575;     //if a command has been sent to calibrate or take a reading we wait 575ms so that the circuit has time to take the reading.
  else time_ = 300;                                     //if any other command has been sent we wait only 300ms.
                                                        // !!!! Anders als im Original-Programm !!!!
  // Start I2C communication
  Wire.beginTransmission(address);            //call the circuit by its ID number.

  // Send: Command
  Wire.write(data);                           //transmit the command that was sent through the serial port.
  Wire.endTransmission();                     //end the I2C data transmission.

  // Receiving the answer if command is not equal to 'sleep'
  if (strcmp(data, "sleep") != 0)             //if the command that has been sent is NOT the sleep command, wait the correct amount of time and request data.
  {                                           //if it is the sleep command, we do nothing. Issuing a sleep command and then requesting data will wake the D.O. circuit.
    delay(time_);                             //wait the correct amount of time for the circuit to complete its instruction.

    // Request of max. 20 bytes of data from the slave
    Wire.requestFrom(address, 20, 1);         //call the circuit and request 20 bytes (this is more than we need)

    // Reading the first byte = status byte
    code = Wire.read();                       //the first byte is the response code, we read this separately.

/*

    // If required, for test purposes: Evaluation of the status byte
    switch (code)                             //switch case based on what the response code is.
    {  
      case 1:                                 //decimal 1.
              Serial.println("Success");      //means the command was successful.
              break;                          //exits the switch case.

        case 2:                               //decimal 2.
              Serial.println("Failed");       //means the command has failed.
              break;                          //exits the switch case.

        case 254:                             //decimal 254.
                Serial.println("Pending");    //means the command has not yet been finished calculating.
                break;                        //exits the switch case.

        case 255:                             //decimal 255.
                Serial.println("No Data");    //means there is no further data to send.
                break;                        //exits the switch case.
    }

*/

    // Reading in and storing the remaining bytes in array do_data
    while (Wire.available())                  //are there bytes to receive.
    {
      in_char = Wire.read();                  //receive a byte.
      do_data[i] = in_char;                   //load this byte into our array.
      i += 1;                                 //incur the counter for the array element.
      if (in_char == 0)                       //if we see that we have been sent a null command.
      {    
        i = 0;                                //reset the counter i to 0.
        Wire.endTransmission();               //end the I2C data transmission.
        break;                                //exit the while loop.
      }
    }


/*
    // If required, for test purposes: Output of the received data
    Serial.print("Return Value: ");
    Serial.println(do_data);                  //print the data.
    Serial.println();                         //this just makes the output easier to read by adding an extra blank line 

*/
  }
}

/************************************************************************************/

/*** DO - Read DO-Sensor-Information ***/

void DO_information(void)
{
  clear_screen();

  Serial.println("DO - Reading Information");
  Serial.println("(Cancel with key... )\n\n");

  delay(2000);

  while(1)
  {
    // Reading Sensor-Value
    DO_send("i");
  
    Serial.print("DO-Info: ");
    Serial.println(do_data);
    Serial.println();
  
    delay(500);

    if (Serial.available() > 0)   // Waiting for keystroke
    {
      taste = Serial.read();
      break;
    }
  }
}

/************************************************************************************/

/*** Get DO-Values ***/

void DO_mess(void)
{
  clear_screen();

  Serial.println("Read DO-Values");
  Serial.println("(Cancel with key... )\n\n");

  delay(2000);

  while(1)
  {
    // Query of the sensor
    DO_send("r");
  
    Serial.print("DO-Value: ");
    Serial.println(do_data);
    Serial.println();
  
    delay(1000);

    if (Serial.available() > 0)   // Waiting for keystroke
    {
      taste = Serial.read();
      break;
    }
  }
}

/************************************************************************************/

/*** Choose unit for DO-Sensor-Values ***/

void DO_rueck(void)
{
  char wahl;

  while(1)
  {
    clear_screen();
  
    Serial.println("Choose unit for DO-Sensor-Values\n");
    Serial.println("Please choose:\n");
    Serial.println("  1 = Value in mg/l");
    Serial.println("  2 = Value in % saturation");
    Serial.println("  3 = Both units\n\n");
    Serial.print("Your Choice:   ");
  
    // Wait for input
    while (Serial.available() == 0);
  
    // Evaluation of the input
    wahl = Serial.read();
    Serial.print(wahl);
  
    // Evaluation of the input
    switch (wahl)
    {
      case '1': DO_send("o,mg,1");    // mg/l enable
                DO_send("o,%,0");     // % disable
                break;
  
      case '2': DO_send("o,mg,0");    // mg/l disable
                DO_send("o,%,1");     // % enable
                break;
  
      case '3': DO_send("o,mg,1");    // mg/l enable
                DO_send("o,%,1");     // % enable
                break;
  
      default:  Serial.print("\n\nInvalid Choice. Please try again!");
                delay(4000);
                break;
    }

    if ((wahl == 0x31) || (wahl == 0x32) || (wahl == 0x33)) 
    {
      Serial.println("\n\nSet!  Continue with any key ...");
      while (Serial.available() == 0);   // Waiting for keystroke   
      taste = Serial.read();
      break;      
    }
  }

}

/*****************************************************************************/

/*** DO - Calibration to O2 air value ***/

void DO_cal_luft(void)
{
  while(1)
  {
    clear_screen();
  
    Serial.println("DO - Calibration to O2 air value: 9,09 - 9,1 mg/l\n");
    Serial.println("Dry sensor and hang vertically in air.\n");
    Serial.println("Observe readings and if stable (no matter what value),");
    Serial.println("Press the 'c' key ...\n");
    Serial.println("Start the measurements now with a keystroke ...\n\n");
    
    // Waiting for input
    while (Serial.available() == 0);
  
    // Evaluation of the input
    taste = Serial.read();

    // Continuous measurement
    while(1)
    {
      // Query of the sensor
      DO_send("r");
  
      Serial.print("DO-Value: ");
      Serial.println(do_data);
      delay(1000);

      if (Serial.available() > 0)   // Waiting for keystroke
      {
        taste = Serial.read();
        break;
      }
    }

    // Call calibration
    DO_send("cal");
    
    Serial.println("\n\nWaiting ...");
    delay(1300);

    Serial.println("\nCalibration done! \n\n");
    Serial.println("Exit with any key ... ");

    // Continuous measurement
    while(1)
    {
      // Reading the sensor
      DO_send("r");
  
      Serial.print("DO-Value: ");
      Serial.println(do_data);
      delay(1000);

      if (Serial.available() > 0)   // Waiting for keystroke
      {
        taste = Serial.read();
        break;
      }
    }
    
    // Return
    return;

  }

}

/************************************************************************************/
SF-Cha-5_en.inoArduino
Software for the operation of the whole station
/*************************************************************************/
/*                                                                       */
/*                     Sigfox-Challenge 2019                             */
/*                                                                       */
/*                          SmartLake                                    */
/*                                                                       */
/*   An all-round and powerfull monitoring-system for the Green IoT      */
/*                                                                       */
/*  Developed by:       Frank Schleking, Philipp Krienke                 */
/*                      Filip Schmachtenberger, Bernd vom Berg           */
/*                                                                       */
/*              University Georg Agricola, Bochum, Germany               */
/*                                                                       */
/*               SF-Cha-5_en    Version 5.0,   12.10.2019                */
/*                                                                       */
/*************************************************************************/

//Include the required standard libraries
#include <ArduinoLowPower.h>	  // Contains features for sleep and deep-sleep
#include <SigFox.h>				  // Contains the Sigfox functions
#include <SPI.h>				  // Library for SPI-BUS
#include <Wire.h>				  // I2C-Library
#include <Adafruit_MAX31865.h>    // for the pt100 - MAX31865-Board


// MAX31865-Board (Pt100)____________________________________________________
// use hardware SPI, just pass in the CS pin (active low)
Adafruit_MAX31865 pt100_1 = Adafruit_MAX31865(A5);  // for Pt100_1
Adafruit_MAX31865 pt100_2 = Adafruit_MAX31865(A6);  // for Pt100_2

// The value of the Rref resistor. Use 430.0 for PT100
#define RREF      430.0

// The 'nominal' 0-degrees-C resistance of the sensor
// 100.0 for PT100
#define RNOMINAL  100.0


// E-PAPER-Display___________________________________________________________
//Include the e-paper display library and additional required libraries
#include <GxEPD.h>

//Include the Library for the selected Display: 2,9 black/white
#include <GxGDEH029A1/GxGDEH029A1.h>      // for 2.9 inch s/w Display

//Include the Communication-Libraries for the Displays
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>

// Include fonts of the Adafruit-GFX Library
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>

// Create an object of the GxIO-Class
// Defining the pins used for CS, DC, and Reset.
// The name of the object is "io" 
GxIO_Class io(SPI, /*CS=*/ 4, /*DC=*/ 7, /*RST=*/ A1);

// Create an object of the GxEPD-Class an define the pins 
// for Reset an Busy and the previously created object of the GxIO-class "io"
// The name of the object is "display"
GxEPD_Class display(io, /*RST=*/ A1, /*BUSY=*/ 5);


// BMP280-Ambient-Pressure-Sensor_____________________________________________________
// Include the BMP280-Library
#include <BMx280MI.h>

//The default I2C-Adress of the BMP280 is 0x76
#define I2C_ADDRESS 0x76

// Create a BMx280I2C object using the I2C interface with I2C Address 0x76 
// The name of the object is "bmx280"
BMx280I2C bmx280(I2C_ADDRESS);


// Pt100 - MAX31865__________________________________________________________
#define Pt_address 97          //default I2C ID number for EZO DO Circuit.


//Global Variables___________________________________________________________
int versorgungs_pin = 0;    // Port pin for switching the power supply for 
							// the ePaper display

// Variables for the measured values
float luftdruck = 0;        // Air pressure from the BMP280
float temperatur = 0;       // Temperature from the BMP280

float temp_pt100_1 = 0;     // Pt100-value 1
float temp_pt100_2 = 0;     // Pt100-value 2

char do_data[20];           // Data from the DO sensor as a string
float O2;                   // DO-value as a float

// General variables
unsigned char taste;
unsigned char first = 1;
unsigned long time_new, time_old;   // For the time measurement between 
									// the transmissions


/***********************************************************/
/***  Waiting time for Sigfox transmission (in minutes)  ***/
/***********************************************************/
unsigned int steps = 180;  // Steps
signed int k = steps;

/* With a time delay of delay(10) in the loop-function 
   the following applies to the cycle time:

    steps      Time between two transmission
    52                3:02 min
    180              10:01 min
    270              14:56 min
    540              28:56 min

*/


/*******************************************************************/
/***********  Setup - Function  ************************************/
/*******************************************************************/

void setup()
{

  //Initialisiere the serial Interface
  Serial.begin(9600);
  
  //Initialize the I2C-Interface
  Wire.begin();
  
  //Declare power supply pin as output (MOSFETs Pin 1)
  pinMode(versorgungs_pin, OUTPUT);
  
  // Switch on the power supply for the Display
  digitalWrite(versorgungs_pin, HIGH);
  
  //Initialize the E-Paper-Display
  display.init();
  
  // Waiting for communication via serial monitor
  delay(2000);

  
  /********* Initialization BMP280  ******/
  if (!bmx280.begin())
  {
    Serial.println("Initialization failed. Please check BMP280 and I2C-Adress");
    while (1);
  }


  /********  Initialization for MAX31865  ******/
  //set to 2WIRE or 4WIRE as necessary; here: 2WIRE
  pt100_1.begin(MAX31865_2WIRE);
  pt100_2.begin(MAX31865_2WIRE);


  // Show start-up sequence on ePaper display
  start_up();


  //Creates the mask for the ePaper display with the measurement names and units
  display_maske();

}


/*******************************************************************/
/***********  Loop - Function  *************************************/
/*******************************************************************/

void loop() 
{
  // Required variables
  unsigned int z;
  
  // Start measurement of BMP280 
  bmx280.writeOversamplingPressure(BMx280MI::OSRS_P_x16);
  bmx280.writeOversamplingTemperature(BMx280MI::OSRS_T_x16);
  
  /*****  Get measured values from BMP280  *****/
  if (first)
    {
      luftdruck = bmx280.readPressure()/100;    // Ignore the first value
      first = 0;
    }
  delay(10);
  temperatur = bmx280.readTemperature();        // Temperature reading
  delay(10);
  luftdruck = bmx280.readPressure()/100;        // Air pressure reading
  
  
  // From the Pt100 measuring point 1
  temp_pt100_1=pt100_1.temperature(RNOMINAL, RREF);
  delay(10);

  // From the O2 sensor
  DO_send("r");
  // Convert do_data to float
  O2 = (do_data[0]-0x30) + ((do_data[2]-0x30)/10.0) + ((do_data[3]-0x30)/100.0);

  // Output measured value on serial monitor
  Serial.print("O2-Water:     ");
  Serial.print(do_data);
  Serial.println(" mg/l");
  
  Serial.print("Temp.-Water:  ");
  Serial.print(temp_pt100_1,1);
  Serial.println(" C");
  
  Serial.print("Temp.-Amb.:   ");
  Serial.print(temperatur,1);
  Serial.println(" C");
  
  Serial.print("Press.-Amb.:  ");
  Serial.print(luftdruck,1);
  Serial.println(" hPa\n");
  
  
  // Output measured value on the ePaper-Display
  display_messwerte(luftdruck, temperatur, temp_pt100_1, temp_pt100_2);
    
  
  // Send readings to the Sigfox cloud when the wait time has expired
  if (k == steps)
  {
    daten_senden(O2, temp_pt100_1, temperatur);
    display.update();       // refresh Display
    k = -1;
  }

  // Waiting time between the transmissions
  delay(10);      // Wait 10 ms
  k = k + 1;
  z = steps-k;
  Serial.println("Next transmission in: " + String(z) + " Steps\n");

  /*****  Step counter on the ePaper display *****/
  // White rectangle for deleting the measured value field
  display.fillRect(185, 40, 68, 15, GxEPD_WHITE);
  
  // Output residual value
  display.setCursor(185,51);
  display.println(z);

  // Partial refresh from the display: Range for the measured values
  display.updateWindow(185, 40, 68, 15, true);      

}

/*******************************************************************/
/***********  Own functions  ***************************************/
/*******************************************************************/

/*** Start-Up Function ***/

void start_up(void)
{
  unsigned char s_time = 6;     // Start-Up-Counter
  unsigned char i;

  // Clear screen
  display.update();
    
  // Specifications for the ePaper display
  display.fillScreen(GxEPD_WHITE);        // Background color
  display.setTextColor(GxEPD_BLACK);      // text Color
  display.setRotation(1);                 // Turn the display by 90
  display.setFont(&FreeMonoBold12pt7b);   // Font

  // text output  
  display.println("\n\n  System starts in:\n");
  display.println("            s");

  // Refresh display
  display.updateWindow(0, 0, 296, 127, true);

  
  // Delay loop with output on ePaper display
  for (i = 1; i <= s_time; i++)
  {
    delay(100);
    display.fillRect(120, 88, 46, 16, GxEPD_WHITE);
    display.setCursor(120, 102);
    display.print(s_time-i);
    display.updateWindow(120, 90, 50, 14, true);
  }
}

/*******************************************************************/

/*** Clear serial monitor tricky ***/

void clear_screen(void)
{
  unsigned char i;
  for (i = 0; i < 35; i++) Serial.println(" ");
}

/*******************************************************************/

/*** Send data to the Sigfox cloud ***/

void daten_senden(float O2, float temp_water, float temp_amb)
{

  /*** Determine and output time between two transmissions  ***/
  time_old = time_new;
  time_new = millis();
  Serial.print("Time between transmissions: ");
  Serial.print((time_new-time_old)/60000.0);
  Serial.println(" min.");
  
  /*** transmissions begins  ***/
  Serial.println("\n TRANSMISSION !!\n");
  
  // Initialize the Sigfox modem
  SigFox.begin();
  delay(100);
 
  // Enable debug led and disable automatic deep sleep
  SigFox.debug();
 
  // Clears all pending interrupts
  SigFox.status();
  delay(1);

  // The structure to be sent must be declared as "packed". 
  // This removes the padding bytes.
  typedef struct __attribute__ ((packed)) sigfox_message {
    float O2;             // 4 Bytes
    float Temp_Water;	  // 4 Bytes
    float Temp_Amb;       // 4 Bytes
    
    } SigfoxMessage;
  
  // Create the variable "reading" from the previously created structure type 
  SigfoxMessage reading;
  
  // Write the measured values into the structure
  reading.O2         = O2;
  reading.Temp_Water = temp_water;
  reading.Temp_Amb   = temp_amb;
  
  //Preparing to send a package
  SigFox.beginPacket();
  
  // Send the message to the Sigfox backend
  SigFox.write((char*)&reading, sizeof(reading));
  
  // If endPacket() returns 'true' then error message
  int ret = SigFox.endPacket(); 
  if (ret > 0)
  {
    Serial.println("Error: no transmission!");
  } 
  else 
  {
    Serial.println("TRANSMISSION OK !!\n\n");
  }
  
  //De-Initializes the Sigfox library and the module
  SigFox.end();
 }

/*******************************************************************/

/*** Create a fixed screen mask for the e-paper display ***/

void display_maske(void)
{
  // Display settings
  display.fillScreen(GxEPD_WHITE);
  display.setTextColor(GxEPD_BLACK);
  display.setRotation(1);  // Turn the display 90
  
  display.setFont(&FreeMonoBold9pt7b);
  display.setCursor(0, 15);
  
  display.println("   Sigfox Challenge 2019");
  display.println("  SmartLake - THGA Bochum");
  display.println("Next Transm. in:       Stp");

  display.setCursor(0, 80);
  display.println("O2-Water:            mg/l");

  display.setCursor(0, 100);
  display.println("Temp.- Water:        C");

  display.setCursor(0, 120);
  display.println("Temp.- Amb.:         C");

  // Refresh display
  display.updateWindow(0, 0, 296, 127, true);
    
}

/*******************************************************************/

/*** Display measured values on ePaper display ***/

void display_messwerte(float pressure, float temp1, float temp2, float temp3)
{

  // Coordinates of the box
  uint16_t box_x = 150;   //x-Position
  uint16_t box_y = 68;    //y-Position
  uint16_t box_w = 65;    //width
  uint16_t box_h = 56;    //Height
  
  display.setFont(&FreeMonoBold9pt7b);
  display.setTextColor(GxEPD_BLACK);
  display.fillRect(box_x, box_y, box_w, box_h, GxEPD_WHITE);

  display.setCursor(160, 80);
  display.print(do_data);           // O2 concentration

  display.setCursor(160, 100);
  display.print(temp2,1);           // Water-Temp. (Pt100)

  display.setCursor(160, 120);
  display.print(temp1,1);           // Ambient-Temp.

  display.updateWindow(box_x, box_y, box_w, box_h, true);
  delay(50);
  display.powerDown();
}

/********************************************************************/

/*** Read the oxygen sensor via I2C: Process and send the telegram  ***/

void DO_send (char data[20])
{
  unsigned char i = 0;
  unsigned char code = 0;
  unsigned char in_char = 0;
  int time_;
  
  // Set the waiting time for sensor response
  if (data[0] == 'c' || data[0] == 'r')time_ = 575;     //if a command has been sent to calibrate or take a reading we wait 575ms so that the circuit has time to take the reading.
  else time_ = 300;                                     //if any other command has been sent we wait only 300ms.
                                                        // !!!! Anders als im Original-Programm !!!!
  // Start I2C communication
  Wire.beginTransmission(Pt_address);         //call the circuit by its ID number.

  // Send: Command
  Wire.write(data);                           //transmit the command that was sent through the serial port.
  Wire.endTransmission();                     //end the I2C data transmission.

  // Receiving the answer if command is not equal to 'sleep'
  if (strcmp(data, "sleep") != 0)             //if the command that has been sent is NOT the sleep command, wait the correct amount of time and request data.
  {                                           //if it is the sleep command, we do nothing. Issuing a sleep command and then requesting data will wake the D.O. circuit.
    delay(time_);                             //wait the correct amount of time for the circuit to complete its instruction.

    // Request of max. 20 bytes of data from the slave
    Wire.requestFrom(Pt_address, 20, 1);         //call the circuit and request 20 bytes (this is more than we need)

    // Reading the first byte = status byte
    code = Wire.read();                       //the first byte is the response code, we read this separately.


/*
    // If required, for test purposes: Evaluation of the status byte
    switch (code)                             //switch case based on what the response code is.
    {  
      case 1:                                 //decimal 1.
              Serial.println("Success");      //means the command was successful.
              break;                          //exits the switch case.

        case 2:                               //decimal 2.
              Serial.println("Failed");       //means the command has failed.
              break;                          //exits the switch case.

        case 254:                             //decimal 254.
                Serial.println("Pending");    //means the command has not yet been finished calculating.
                break;                        //exits the switch case.

        case 255:                             //decimal 255.
                Serial.println("No Data");    //means there is no further data to send.
                break;                        //exits the switch case.
    }

*/

    // Reading in and storing the remaining bytes in array do_data
    while (Wire.available())                  //are there bytes to receive.
    {
      in_char = Wire.read();                  //receive a byte.
      do_data[i] = in_char;                   //load this byte into our array.
      i += 1;                                 //incur the counter for the array element.
      if (in_char == 0)                       //if we see that we have been sent a null command.
      {    
        i = 0;                                //reset the counter i to 0.
        Wire.endTransmission();               //end the I2C data transmission.
        break;                                //exit the while loop.
      }
    }

/*
    // If required, for test purposes: Output of the received data
    Serial.print("Return Value: ");
    Serial.println(do_data);                  //print the data.
    Serial.println();

*/
  
  }

}

/************************************************************************************/
/************************************************************************************/
Sigfox-3_0_en.inoArduino
Software for the operation of the Sigfox-Modem of the MKR FOX1200
/*************************************************************************/
/*                                                                       */
/*                     Sigfox-Challenge 2019                             */
/*                                                                       */
/*                          SmartLake                                    */
/*                                                                       */
/*                 Demoprogramm for the Sigfox-Modem                     */
/*                     for Dissolved Oxygen  (DO)                        */
/*  Developed by:       Frank Schleking, Philipp Krienke                 */
/*                      Filip Schmachtenberger, Bernd vom Berg           */
/*                                                                       */
/*              University Georg Agricola, Bochum, Germany               */
/*                                                                       */
/*            Sigfox-3_0_en.ino       Version 3.0,   04.09.2019          */
/*                                                                       */
/*************************************************************************/


/**************************************************************/
/*** Specifications for the ePaper display*********************/
/**************************************************************/

// Include the ePaper-Base-Library
#include <GxEPD.h>

// Include the Library for the selected Display: 2,9 black/white
#include <GxGDEH029A1/GxGDEH029A1.h>      // 2.9" b/w

// Include the Communication-Libraries for the Displays
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>

//Create objects for communication and display with used selected pins
GxIO_Class io(SPI, /*CS=*/ 4, /*DC=*/ 7, /*RST=*/ 10);
GxEPD_Class display(io, /*RST=*/ 10, /*BUSY=*/ 5);

// Include fonts of the Adafruit-GFX Library
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeMonoBold18pt7b.h>
#include <Fonts/FreeMonoBold24pt7b.h>

// Port-Pin 0: Power On/Off for the ePaper-Display
unsigned char ePaper = 0;


/**************************************************************/
/*** Specifications for the BMP280-Sensor ***********************/
/**************************************************************/

// Include the BMP280-Library
/* The Libraries for I2C und SPI are included automatically */
#include <BMx280MI.h>

//The default I2C-Adress of the BMP280
#define I2C_ADDRESS 0x76    // Original Adafruit-Board:  0x77 or 
                            // 0x76 for some china-clones

/* Create a BMx280I2C object using the I2C interface with I2C Address*/
BMx280I2C bmx280(I2C_ADDRESS);

// Global Variables for the measurements
float pressure = 0;
float temp = 0;


/**************************************************************/
/*** Specifications for the Sigfox-Modem ************************/
/**************************************************************/

#include <SigFox.h>           //Include Sigfox-Library

/****************************************************/
/********** Needed for Sigfox operation *********/
/****************************************************/

/* For the Sigfox transmission mode:
Creation of special structures in which the values for the payload 
are suitably stored. In the payload, a data field of a maximum of 
12 bytes is available for the user. The actual frames of the 
following structures should be adopted in this way, the contents, 
ie the values to be transmitted, can of course be adapted 
individually (maximum 12 bytes)
*/

// Structure / data type 1 for our example: transmission of fixed numerical values
  typedef struct __attribute__ ((packed)) sigfox_message_1 {
    unsigned char   wert_1;     // 1st value in the payload: 1 byte in size
    unsigned int    wert_2;     // 2nd value in the payload: 4 bytes in size
    float           wert_3;     // 3rd value in the payload: 4 bytes in size
    unsigned char   wert_4;     // 4th value in the payload: 1 byte in size

    // All in all, exactly 10 bytes are needed here

} Sigi_Dat_1;


// Structure / data type 2 for our example: Transmission of measured values
  typedef struct __attribute__ ((packed)) sigfox_message_2 {
    float           wert_1;     // 1st value in the payload: 4 bytes in size
    float           wert_2;     // 2nd value in the payload: 4 bytes in size
    uint16_t        wert_3;     // 3rd value in the payload: 2 bytes in size

    // All in all, exactly 10 bytes are needed here

} Sigi_Dat_2;


/* Here, a variable named 'SF_send' is created, of the data type of the previously 
created structure 'Sigi_Dat_1'. Later, in the application, the sending data is 
written into this variable and the payload is then filled up.*/
Sigi_Dat_1 SF_send;


/* Here a variable named 'SF_send_mw' is created, of the data type of the previously
created structure 'Sigi_Dat_2'. Later, in the application, the sending data is 
written into this variable and the payload is then filled up.*/
Sigi_Dat_2 SF_send_mw;


/*************************************************/
/********** Miscellaneous ************************/
/*************************************************/

// Other global variables
unsigned char taste;

// readings
uint16_t LDR;     // LDR-value at analog input 2


/**************************************************************/
/*** Setup-Specifications, executed once *******/
/**************************************************************/

void setup()
{
    
  // Delay for switching to serial Monitor
  // Minimum 1.000 ms !!
  delay(2000);
  
  /***********************************************************/
  /* For the ePaper-Display **********************************/
  /***********************************************************/

  // Power On/Off-Mosfet for the ePaper 
  pinMode(ePaper,OUTPUT);       // Set Pin as output
  digitalWrite(ePaper, HIGH);   //Display Power On
  delay(500);

  // Initialize the Displays
  display.init();

  // Fill Screen:  BLACK or WHITE
  display.fillScreen(GxEPD_WHITE);

  // Text-Color:  BLACK or WHITE
  display.setTextColor(GxEPD_BLACK);

  // Set the display in landscape-mode
  display.setRotation(1);           // Rotate Display 90
                                    // 1 = landscape, 0 = portrait

  // Set the required Adafruit-Font
  display.setFont(&FreeMonoBold9pt7b);

  // Refresh Display
  display.update();

  /*Display Power Off*/
  digitalWrite(ePaper, LOW); 
  
  Serial.println("ePaper-Display Init OK !\n");

  /**********************************************************/
  /* For the BMP280-Sensor **********************************/
  /**********************************************************/

  //Initialize the I2C-Interface
  Wire.begin();

  /* Initialize the BMP280 with bmx280.begin(). When initialization fails, 
  begin() returns a false and the Code ends in a loop*/
  if (!bmx280.begin())
  {
    Serial.println("Initialization failed. Please check BMP280 and I2C-Adress");
    while (1);
  }
  else
  {
    Serial.println("BMP280 Init OK !\n");
  }


  /**********************************************************/
  /* For the Sigfox-Modem ***********************************/
  /**********************************************************/

  // Check if Sigfox modem is present and can be initialized.
  // Output error if modem does not exist or not ok
  if (!SigFox.begin()) //Initialize Sigfox modem
  {
    Serial.println("Sigfox modem not found! - Continue with RESET!");
    while (1);  // In this case: infinite loop
  }
  else
  {
    Serial.println("Sigfox-Modem Init OK !\n");
  }

  /**********************************************************/
  /* For the A / D converter ********************************/
  /**********************************************************/

  // Settings for the A / D converter
  analogReference(AR_DEFAULT);    // Setting the Reference-Voltage
                                  // Default-Value: 3,3 V

  analogReadResolution(10);       // Setting up the Resolution: 10 Bit

 /**********************************************************/
  /* Miscellaneous *****************************************/
  /*********************************************************/
 
  // Waiting
  delay(2000);

}


/************************************************************/
/*** Start the Main-Programm in a loop***********************/
/************************************************************/

void loop()
{

  char wahl;

  /* Start BMP280. There are 5 different operating modes. 
     See table in datasheet. One of the 5 Oversampling-Setting (non 0) 
	 starts the measuremt.For example "OSRS_P_x16" for "Ultra high Resolution" */
  bmx280.writeOversamplingPressure(BMx280MI::OSRS_P_x16);     // For pressure-measurement
  bmx280.writeOversamplingTemperature(BMx280MI::OSRS_T_x16);  // For temperature-measurement

  // Show the Main Page on the ser. Monitor
  titel_bild();

  // Waiting for input
  while (Serial.available()==0);
  
  // Processing the input
  wahl = Serial.read();
  Serial.print(wahl);
  delay(1000);          // So input is visible for a second

  // Calling the function for the specified case
  switch (wahl)
  {
    case '1': sf_konfig_daten();
              break;

    case '2': sf_send_hello();
              break;

    case '3': sf_send_zahlen();
              break;

    case'4':  sf_send_mw();
              break;

    default:  Serial.print("\n\nInvalid selection! Please try again !!");
              delay(4000);
              break;
  }

}

/*******************************************************************/
/*** Own functions always at the end of the code !! ***/
/*******************************************************************/

/*** Clear serial monitor tricky  ***/
void clear_screen(void)
{
  unsigned char i;
  for (i=0; i<35; i++) Serial.println(" ");
}

/*******************************************************************/

/*** Main Page for the serial monitor ***/
void titel_bild(void)
{
  clear_screen();
  Serial.println("**************************************************************");
  Serial.println("***** Demo-program for Arduino-Sigfox-Board MKR FOX 1200 *****");
  Serial.println("***** Here:  Operation of the Sigfox modem               *****");
  Serial.println("**************************************************************");
  Serial.println("\nPlease choose:\n");
  Serial.println("   1)  Read Sigfox configuration data");
  Serial.println("   2)  Broadcasting 'Hello World'");
  Serial.println("   3)  Sending four fixed numbers");
  Serial.println("   4)  Send out three readings");

  Serial.print("\n\nYour choice:  ");
}

/********************************************************************/

/*** Read out and display the configuration data of the Sigfox modem ***/
void sf_konfig_daten(void)
{

  // Clear screen
  clear_screen();

  // Activate and initialize the Sigfox modem
  if (!SigFox.begin())
  {
    Serial.println("Sigfox modem not found! - Continue with RESET!");
    while (1);  // In this case: infinite loop
  }
  else
  {
    Serial.println("Sigfox-Modem OK !\n");
  }

  //Read and save the firmware version, ID, PAC and Temp. of the modem
  String version = SigFox.SigVersion();
  String ID = SigFox.ID();
  String PAC = SigFox.PAC();
  float temp = SigFox.internalTemperature();

  // Send modem information to the serial monitor
  Serial.println("MKR FOX 1200 - read configuration data:\n");
  Serial.println("Firmware-Version:     " + version);
  Serial.println("ID =                  " + ID);
  Serial.println("PAC =                 " + PAC);
  Serial.println("Int. Temperature =    " + String(temp, 1) + " C");

  delay(100); //Wait 100ms until the serial transfer is finished

  SigFox.end(); // send the modem into sleep

  Serial.println("\n\nPlease press the button!");

  while (Serial.available() == 0);  // Waiting for keystroke
  taste = Serial.read();
}

/********************************************************************/

/*** Broadcasting 'Hello World' ****/

void sf_send_hello (void)
{
  // Clear screen
  clear_screen();
  
  Serial.println("Sending 'Hello World' over Sigfox");
  Serial.println("=======================================\n");

  // Activate Sigfox modem and query errors
  if (!SigFox.begin())        // Error occurred
  {
    Serial.println("Sigfox module error! - Continue with RESET!");
    while (1);  // In case of error: infinite loop
  }

  Serial.println("Initialization OK!\n");

  // Enable Sigfox modem debug mode and issue the device ID
  // to make it easier to find in the backend
  SigFox.debug();
  Serial.println("Station-ID = " + SigFox.ID());

  // Preparation to send a package
  SigFox.beginPacket();

  Serial.println("Broadcast starts!");

  // Sending out the string = sequence of characters = sigfox message
  SigFox.print("Hello World!");

  Serial.println("Transmission is running, waiting for feedback ...!");

  // Stop sigfox transfer and query for errors:
  // Status in the variable ret
  int ret = SigFox.endPacket();

  Serial.print("\nError status (0 = no error):");
  Serial.println(ret);

  // Deinitialization of the Sigfox library and the module
  SigFox.end();

  Serial.println("\nContinue with the push of a button ...");

  while (Serial.available() == 0);  // Waiting for keystroke
  taste = Serial.read();

}

/********************************************************************/

/*** Sending four fixed numbers ***/

void sf_send_zahlen(void)
{
  // clear screen
  clear_screen();
  
  // Definition of four fixed numerical values
  unsigned char   AIN_1 = 0x1f ;            // 1st value, 1 byte in size
  unsigned int    pressure = 0x12345678;    // 2nd value, 4 (!!) bytes big
  float           temp = 1233.56;           // 3rd value, 4 bytes in size
  unsigned char   AIN_2 = 0x55;             // 4th value, 1 byte in size

  // Activate Sigfox modem and query errors
  if (!SigFox.begin())        // Error occurred
  {
    Serial.println("Sigfox module error! - Continue with RESET!");
    while (1);  // In case of error: infinite loop
  }
  else
  {
      Serial.println("Sigfox-Modem OK !\n");
  }

  // Enable debug LED and disable power saving modes
  SigFox.debug();
   
  // Clear all pending interrupts
  SigFox.status();
  delay(1);
  
  // Now the actual values to be transferred are written to the 
  //(structure)variable 'SF_send', thus: assembling the content for the payload
  SF_send.wert_1 =        AIN_1;        // unsigned char - Value:  1 Byte
  SF_send.wert_2 =        pressure;     // unsigned int - Value:   4 Byte
  SF_send.wert_3 =        temp;         // float - Value:          4 Byte
  SF_send.wert_4 =        AIN_2;        // unsigned char - Value:  1 Byte

  // control expenses
  Serial.println("The following values are transferred to the payload:");
  Serial.println("  1. Value (unsigned char):   " + String (AIN_1, HEX) + "  (hex)");
  Serial.println("  2. Value (unsigned int):    " + String(pressure, HEX) + "  (hex)");
  Serial.println("  3. Value (float):           " + String(temp,2));
  Serial.println("  4. Value (unsigned char):   " + String(AIN_2, HEX) + "  (hex)\n\n");

  //
  // And now the sigfox-transmission of the previously assembled 
  // structure-variable 'SF_send' (= content of the payload) takes place
  //

  Serial.println("Sigfox transmission starts ...\n");

  // Preparing to send a package
  SigFox.beginPacket();

  // Send structure variable to the Sigfox backend
  SigFox.write((char*)&SF_send, sizeof(SF_send));

  // Error checking: If endPacket () returns a 1, then error message
  int ret = SigFox.endPacket(); 
  if (ret > 0)
  {
    Serial.println("Error: no transfer! - Continue with RESET!");
    while(1);     // loop
  } 
  else 
  {
    Serial.println("Sigfox transmission OK!");
  }

  // De-initializing the Sigfox library and the Sigfox module
  SigFox.end();
 
  Serial.println("\nContinue with the push of a button ...");
  while (Serial.available() == 0);  // Waiting for keystroke
  taste = Serial.read();
  
}

/********************************************************************/

/*** Transmission of three real measured values ***/

void sf_send_mw(void)
{
  unsigned char ta;
  unsigned int i=0;
  
  // clear screen
  clear_screen();

  // Turn on voltage for ePaper display
  digitalWrite(ePaper, HIGH);
  delay(200);

  // Output mask for ePaper display
  ePaper_mask();

  // endless loop
  while(1)
  {
    // Infinite loop: permanent data logging
    while(1)
    {
      i= i+1;
      
      // Acquisition of the three measured values
      mw_erf();
      
      // Display of the three measured values on serial monitor and ePaper display
      // On the serial monitor:
      Serial.println("Measurement: " + String(i));   
      Serial.println("Temperature: " + String(temp,1) + " C");
      Serial.println("Pressure:    " + String(pressure,1) + " hPa");
      Serial.println("LDR:         " + String(LDR, HEX) + "   hex\n");    
      Serial.println("Send the values over Sigfox with 's', exit with 'x' !\n\n");

      // On the ePaper display:
      show_ePaper(i);
  
      // Query input
      ta = Serial.read();
      if ((ta == 's') || (ta == 'x')) break;
  
      delay(500);
    }
  
    // Comparison of the input
    if (ta == 'x') // Function is completely finished
    {
      // Switch off voltage for ePaper display
      digitalWrite(ePaper, LOW);
      
      return;     
	  }
    
    // Otherwise ... 
    // A sigfox telegram is sent ...
    
    // Message on serial monitor
    clear_screen();
    Serial.println("Sending a Sigfox telegram ...\n");

    // Message on ePaper display
    ePaper_sf();
    
    delay(500);
    
    // Activate Sigfox modem and query errors
    if (!SigFox.begin())        // Error occurred
    {
      Serial.println("Sigfox module error! - Continue with RESET!");
      while (1);  // In case of error: infinite loop
    }
    else
    {
        Serial.println("Sigfox-Modem OK !\n");
    }
  
    // Enable debug LED and disable power saving modes
    SigFox.debug();
     
    // Clear all pending interrupts
    SigFox.status();
    delay(1);
    

    // Now the actual values to be transferred are written to the 
    //(structure)variable 'SF_send_mw', thus: assembling the content 
	//for the payload
    SF_send_mw.wert_1 =        temp;        // float - value:        4 Byte
    SF_send_mw.wert_2 =        pressure;    // float - value:        4 Byte
    SF_send_mw.wert_3 =        LDR;         // uint16_t - value:     2 Byte
   
    // control expenses
    Serial.println("The following values are transferred to the payload:");
    Serial.println("  1. value (float):          " + String (temp,1));
    Serial.println("  2. value (float):          " + String(pressure,1));
    Serial.println("  3. value (uint16_t):       " + String(LDR,HEX) + "  (hex)\n\n");
  
  //
  // And now the sigfox-transmission of the previously assembled 
  // structure-variable 'SF_send_mw' (= content of the payload) takes place
  //
  
    Serial.println("Sigfox transmission starts ...\n");
  
    // Preparing to send a package
    SigFox.beginPacket();
  
    // Send structure-variable to the Sigfox backend
    SigFox.write((char*)&SF_send_mw, sizeof(SF_send_mw));
  
    // Error checking: If endPacket () returns a 1, then error message
    int ret = SigFox.endPacket(); 
    if (ret > 0)
    {
      Serial.println("Error: no transfer! - Continue with RESET!");
      while(1);     // endless loop
    } 
    else 
    {
      Serial.println("Sigfox transmission OK!");
    }
  
    // De-initializing the Sigfox library and the Sigfox module
    SigFox.end();
  
    delay(2000);

    // clear screen
    clear_screen();

    // Mask on ePaper display
    ePaper_mask();
  }
}

/********************************************************************/

/*** Acquisition of three measured values ***/

void mw_erf(void)
{
  // Read analog input 2
  LDR = analogRead(2);

  // Read BMP280
  temp = bmx280.readTemperature();
  pressure = bmx280.readPressure()/100;  
}

/********************************************************************/

/*** Screen mask for ePaper display ***/

void ePaper_mask(void)
{  
  // Initialization of the display
  display.init();

  // Set the required Adafruit-Font
  display.setFont(&FreeMonoBold12pt7b);

  // Refresh Display
  display.update();

  // Move the cursor to the beginning of the 16th line so
  // that the first line is displayed correctly
  display.setCursor(2,15);

  // Show the screen mask on the ePaper display
  display.println("Measuring:        ");
  display.println(" Number :");
  display.println(" Press. :         hPa");
  display.println(" Temp.:           C");
  display.println(" LDR:             hex");

  // Total refresh of the display to display new mask
  display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, false);
}

/********************************************************************/

/*** Representation of the values on the ePaper display ***/

void show_ePaper(unsigned int i)
{
  
  // White rectangle for deleting the measured value field
  display.fillRect(155, 24, 84, 88, GxEPD_WHITE);

  // Display measured values on ePaper
  // Output of the measurement no.
  display.setCursor(155,39);
  display.println(i);

  // Output of the air pressure value
  display.setCursor(155,63);
  display.println(pressure,1);

  // Output of the temperature
  display.setCursor(155,87);
  display.println(temp,1);

  // Output of the measured value AIN_2
  display.setCursor(155,111);
  display.println(LDR,HEX);

  // Partial refresh from the display: Range for the measured values
  display.updateWindow(155, 24, 84, 88, true);
}

/********************************************************************/

/*** Sigfox message on ePaper ***/

void ePaper_sf(void)
{
  // Initialization of the display
  display.init();

  // Move the cursor to the beginning of the 16th line so
  // that the first line is displayed correctly
  display.setCursor(2,15);
  
  display.println("\n\n Transmission via");
  display.println("  Sigfox running ...");
  
  // Complete refresh of the display to display new mask
  display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, false);
}

Custom parts and enclosures

Enclosure - Missing figures
Download here the mssing figures which we can not put in the Hackster.io project / story. (???)

Schematics

eagle files for the motherboard
Here we placed all the eagle files for the MKR FOX1200 motherboard
sigfox-challenge_v2_3gxXwcBnCX.zip

Comments

Similar projects you might like

Soldering Iron Controller for Hakko 907

Project tutorial by Alexander

  • 35,348 views
  • 22 comments
  • 71 respects

Thirst Alert Plant Alarm

Project tutorial by Patchr

  • 4,415 views
  • 3 comments
  • 39 respects

AirOwl - Know What You Breathe!

Project tutorial by Team Oizom

  • 11,841 views
  • 16 comments
  • 60 respects

Temperature Streaming with Arduino + Big Data Tools

Project showcase by Gabriel Rodriguez

  • 9,721 views
  • 1 comment
  • 34 respects

WaterAid

Project tutorial by Andrei Florian

  • 6,549 views
  • 3 comments
  • 38 respects

Continuous Erosion

Project showcase by Julian Hespenheide

  • 1,405 views
  • 1 comment
  • 3 respects
Add projectSign up / Login