Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!

SmartLake

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

AdvancedFull instructions provided15 hours4,757

Things used in this project

Hardware components

Through Hole Resistor, 4.7 kohm
Through Hole Resistor, 4.7 kohm
1%
×1
Resistor 10k ohm
Resistor 10k ohm
×2
Through Hole Resistor, 560 ohm
Through Hole Resistor, 560 ohm
×3
Resistor 432ohm
×3
Capacitor 10 µF
Capacitor 10 µF
×2
Capacitor 100 µF
Capacitor 100 µF
×1
Capacitor 100 nF
Capacitor 100 nF
×2
diode 1N4001
×1
3 mm LED: Green
3 mm LED: Green
×3
IRF 3708
×1
mcp602 - OpAmp
×1
Linear Regulator (7805)
Linear Regulator (7805)
×1
AQY 212 GH
×3
GL5539 - LDR
10 pieces in a packet
×1
printswitch
×1
screwterminal
×4
screwterminal
×3
screwterminal
×1
femal connector MiniDIN
×1
femal connector MiniDIN
×2
microfuse 500mA
×1
fuse holder
×2
IC & Component Socket, 8 Contacts
IC & Component Socket, 8 Contacts
×1
ic socket 4 contacts
×3
multi-pin connector, 50 pins
×1
socket board, 22 pins
×1
socket board, 50 pins
×1
Generic Jumper (0.1")
Generic Jumper (0.1")
×16
Arduino MKR Fox 1200
Arduino MKR Fox 1200
×1
arduino mkr fox 1200 antenna
×1
adafruit pt100 breakout board MAX31865
×2
waveshare 2.9 inch ePaper display with SPI
×1
GY-BMP280 breakout board
5 pieces in a packet
×1
I2C-level converter shifter
5 pieces in a packet
×1
Case (Fibox ARCA302015)
×1
Mounting frame for Case
×1
USB socket
×1
PT100 RTD
×1
Plug for PT100
×1
Socket for PT100
×1
BNC socket for A.S. Sensors
×2
Solar panel
×1
Accu charging regulator
×1
Accu
×1
Mini DIN connectors
×4
Micro USB cable
×1
PCB for the MKR FOX1200 Motherboard
on email request: universal_exports@arcor.de
×1
insulating plate for montage of the MKR FOX1200 motherboard
size: about 180 *180 * 1 mm
×1
grid-style board
×1
plexiglas disc
size: about 95 * 45 * 1 mm
×1

Software apps and online services

Arduino IDE
Arduino IDE
Free dashboard program thinger.io

Hand tools and fabrication machines

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

Story

Read more

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

Code

DO_I2C-V5_en.ino

Arduino
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.ino

Arduino
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.ino

Arduino
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);
}

Credits

Bernd vom Berg

Bernd vom Berg

2 projects • 2 followers
Frank Schleking

Frank Schleking

1 project • 1 follower
Filip Schmachtenberger

Filip Schmachtenberger

1 project • 0 followers
Philipp Krienke

Philipp Krienke

1 project • 0 followers

Comments