ModularSensors > Pages > ModularSensors > Examples Using ModularSensors > Everything at Once - a la carte > Example showing all possible functionality

Example showing all possible functionality

This shows most of the functionality of the library at once. It has code in it for every possible sensor and modem and for both AVR and SAMD boards. This example should never be used directly; it is intended to document all possibilities and to verify compilating.

To create your own code, I recommend starting from a much simpler targeted example, like the Logging to MMW example, and then adding to it based on only the parts of this menu example that apply to you.


Walking Through the Code

PlatformIO Configuration

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; http://docs.platformio.org/page/projectconf.html

[platformio]
description = ModularSensors example menu_a_la_carte

[env:mayfly]
monitor_speed = 115200
board = mayfly
platform = atmelavr
framework = arduino
lib_ldf_mode = deep+
lib_ignore =
    RTCZero
    Adafruit NeoPixel
    Adafruit GFX Library
    Adafruit SSD1306
    Adafruit ADXL343
    Adafruit STMPE610
    Adafruit TouchScreen
    Adafruit ILI9341
build_flags =
    -DSDI12_EXTERNAL_PCINT
    -DNEOSWSERIAL_EXTERNAL_PCINT
    -DMQTT_MAX_PACKET_SIZE=240
    -DTINY_GSM_RX_BUFFER=64
    -DTINY_GSM_YIELD_MS=2
    -DENABLE_SERIAL2
    -DENABLE_SERIAL3
    ; -D BUILD_MODEM_DIGI_XBEE_CELLULAR_TRANSPARENT  ; Turn on first time w/ a Digi LTE-M module
    ; -D MS_LOGGERBASE_DEBUG
    ; -D MS_DATAPUBLISHERBASE_DEBUG
    ; -D MS_ENVIRODIYPUBLISHER_DEBUG
lib_deps =
    envirodiy/EnviroDIY_ModularSensors
;  ^^ Use this when working from a tagged release of the library
;     See tags at https://platformio.org/lib/show/1648/EnviroDIY_ModularSensors

;    https://github.com/EnviroDIY/ModularSensors.git#develop
;  ^^ Use this when if you want to pull from the develop branch

    https://github.com/PaulStoffregen/AltSoftSerial.git
    https://github.com/SRGDamia1/NeoSWSerial.git
    https://github.com/EnviroDIY/SoftwareSerial_ExternalInts.git
;  ^^ These are software serial port emulator libraries, you may not need them

The Complete Code

/** =========================================================================
 * @file menu_a_la_carte.ino
 * @brief Example with all possible functionality.
 *
 * @author Sara Geleskie Damiano <sdamiano@stroudcenter.org>
 * @copyright (c) 2017-2022 Stroud Water Research Center (SWRC)
 *                          and the EnviroDIY Development Team
 *            This example is published under the BSD-3 license.
 *
 * Build Environment: Visual Studios Code with PlatformIO
 * Hardware Platform: EnviroDIY Mayfly Arduino Datalogger
 *
 * DISCLAIMER:
 * THIS CODE IS PROVIDED "AS IS" - NO WARRANTY IS GIVEN.
 * ======================================================================= */

// ==========================================================================
//  Defines for TinyGSM
// ==========================================================================
/** Start [defines] */
#ifndef TINY_GSM_RX_BUFFER
#define TINY_GSM_RX_BUFFER 64
#endif
#ifndef TINY_GSM_YIELD_MS
#define TINY_GSM_YIELD_MS 2
#endif
#ifndef MQTT_MAX_PACKET_SIZE
#define MQTT_MAX_PACKET_SIZE 240
#endif
/** End [defines] */


// ==========================================================================
//  Include the libraries required for any data logger
// ==========================================================================
/** Start [includes] */
// The Arduino library is needed for every Arduino program.
#include <Arduino.h>

// EnableInterrupt is used by ModularSensors for external and pin change
// interrupts and must be explicitly included in the main program.
#include <EnableInterrupt.h>

// Include the main header for ModularSensors
#include <ModularSensors.h>
/** End [includes] */


// ==========================================================================
//  Creating Additional Serial Ports
// ==========================================================================
// The modem and a number of sensors communicate over UART/TTL - often called
// "serial". "Hardware" serial ports (automatically controlled by the MCU) are
// generally the most accurate and should be configured and used for as many
// peripherals as possible.  In some cases (ie, modbus communication) many
// sensors can share the same serial port.

#if defined(ARDUINO_ARCH_AVR) || defined(__AVR__)  // For AVR boards
// Unfortunately, most AVR boards have only one or two hardware serial ports,
// so we'll set up three types of extra software serial ports to use

#ifdef BUILD_TEST_ALTSOFTSERIAL
// AltSoftSerial by Paul Stoffregen
// (https://github.com/PaulStoffregen/AltSoftSerial) is the most accurate
// software serial port for AVR boards. AltSoftSerial can only be used on one
// set of pins on each board so only one AltSoftSerial port can be used. Not all
// AVR boards are supported by AltSoftSerial.
/** Start [altsoftserial] */
#include <AltSoftSerial.h>
AltSoftSerial altSoftSerial;
/** End [altsoftserial] */
#endif  // #ifdef BUILD_TEST_ALTSOFTSERIAL

#ifdef BUILD_TEST_NEOSWSERIAL
// NeoSWSerial (https://github.com/SRGDamia1/NeoSWSerial) is the best software
// serial that can be used on any pin supporting interrupts.
// You can use as many instances of NeoSWSerial as you need.
// Not all AVR boards are supported by NeoSWSerial.
/** Start [neoswserial] */
#include <NeoSWSerial.h>          // for the stream communication
const int8_t neoSSerial1Rx = 11;  // data in pin
const int8_t neoSSerial1Tx = -1;  // data out pin
NeoSWSerial  neoSSerial1(neoSSerial1Rx, neoSSerial1Tx);
// To use NeoSWSerial in this library, we define a function to receive data
// This is just a short-cut for later
void neoSSerial1ISR() {
    NeoSWSerial::rxISR(*portInputRegister(digitalPinToPort(neoSSerial1Rx)));
}
/** End [neoswserial] */
#endif  // #ifdef BUILD_TEST_NEOSWSERIAL

#ifdef BUILD_TEST_SOFTSERIAL
// The "standard" software serial library uses interrupts that conflict
// with several other libraries used within this program.  I've created a
// [version of software serial that has been stripped of
// interrupts](https://github.com/EnviroDIY/SoftwareSerial_ExtInts) but it is
// still far from ideal.
// NOTE:  Only use if necessary.  This is not a very accurate serial port!
// You can use as many instances of SoftwareSerial as you need.
/** Start [softwareserial] */
const int8_t softSerialRx = A3;  // data in pin
const int8_t softSerialTx = A4;  // data out pin

#include <SoftwareSerial_ExtInts.h>  // for the stream communication
SoftwareSerial_ExtInts softSerial1(softSerialRx, softSerialTx);
/** End [softwareserial] */
#endif  // #ifdef BUILD_TEST_SOFTSERIAL


#if defined(MS_PALEOTERRA_SOFTWAREWIRE) || defined(MS_RAIN_SOFTWAREWIRE)
/** Start [softwarewire] */
// A software I2C (Wire) instance using Testato's SoftwareWire
// To use SoftwareWire, you must also set a define for the sensor you want to
// use Software I2C for, ie:
//   `#define MS_RAIN_SOFTWAREWIRE`
//   `#define MS_PALEOTERRA_SOFTWAREWIRE`
// or set the build flag(s):
//   `-D MS_RAIN_SOFTWAREWIRE`
//   `-D MS_PALEOTERRA_SOFTWAREWIRE`
#include <SoftwareWire.h>  // Testato's Software I2C
const int8_t softwareSDA = 5;
const int8_t softwareSCL = 4;
SoftwareWire softI2C(softwareSDA, softwareSCL);
/** End [softwarewire] */
#endif  //  #if defined MS_PALEOTERRA_SOFTWAREWIRE ...

#endif  // End software serial for avr boards

#if defined ARDUINO_ARCH_SAMD
/** Start [serial_ports_SAMD] */
// The SAMD21 has 6 "SERCOM" ports, any of which can be used for UART
// communication.  The "core" code for most boards defines one or more UART
// (Serial) ports with the SERCOMs and uses others for I2C and SPI.  We can
// create new UART ports on any available SERCOM.  The table below shows
// definitions for select boards.

// Board =>   Arduino Zero       Adafruit Feather    Sodaq Boards
// -------    ---------------    ----------------    ----------------
// SERCOM0    Serial1 (D0/D1)    Serial1 (D0/D1)     Serial (D0/D1)
// SERCOM1    Available          Available           Serial3 (D12/D13)
// SERCOM2    Available          Available           I2C (A4/A5)
// SERCOM3    I2C (D20/D21)      I2C (D20/D21)       SPI (D11/12/13)
// SERCOM4    SPI (D21/22/23)    SPI (D21/22/23)     SPI1/Serial2
// SERCOM5    EDBG/Serial        Available           Serial1

// If using a Sodaq board, do not define the new sercoms, instead:
// #define ENABLE_SERIAL2
// #define ENABLE_SERIAL3

#include <wiring_private.h>  // Needed for SAMD pinPeripheral() function

#ifndef ENABLE_SERIAL2
// Set up a 'new' UART using SERCOM1
// The Rx will be on digital pin 11, which is SERCOM1's Pad #0
// The Tx will be on digital pin 10, which is SERCOM1's Pad #2
// NOTE:  SERCOM1 is undefinied on a "standard" Arduino Zero and many clones,
//        but not all!  Please check the variant.cpp file for you individual
//        board! Sodaq Autonomo's and Sodaq One's do NOT follow the 'standard'
//        SERCOM definitions!
Uart Serial2(&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2);
// Hand over the interrupts to the sercom port
void SERCOM1_Handler() {
    Serial2.IrqHandler();
}
#endif

#ifndef ENABLE_SERIAL3
// Set up a 'new' UART using SERCOM2
// The Rx will be on digital pin 5, which is SERCOM2's Pad #3
// The Tx will be on digital pin 2, which is SERCOM2's Pad #2
// NOTE:  SERCOM2 is undefinied on a "standard" Arduino Zero and many clones,
//        but not all!  Please check the variant.cpp file for you individual
//        board! Sodaq Autonomo's and Sodaq One's do NOT follow the 'standard'
//        SERCOM definitions!
Uart Serial3(&sercom2, 5, 2, SERCOM_RX_PAD_3, UART_TX_PAD_2);
// Hand over the interrupts to the sercom port
void SERCOM2_Handler() {
    Serial3.IrqHandler();
}
#endif

/** End [serial_ports_SAMD] */
#endif  // End hardware serial on SAMD21 boards


// ==========================================================================
//  Assigning Serial Port Functionality
// ==========================================================================
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_SAMD_ZERO) || \
    defined(ATMEGA2560) || defined(ARDUINO_AVR_MEGA2560)
/** Start [assign_ports_hw] */
// If there are additional hardware Serial ports possible - use them!

// We give the modem first priority and assign it to hardware serial
// All of the supported processors have a hardware port available named Serial1
#define modemSerial Serial1

// Define the serial port for modbus
// Modbus (at 9600 8N1) is used by the Keller level loggers and Yosemitech
// sensors
#define modbusSerial Serial2

// The Maxbotix sonar is the only sensor that communicates over a serial port
// but does not use modbus
#define sonarSerial Serial3

/** End [assign_ports_hw] */
#else
/** Start [assign_ports_sw] */

// We give the modem first priority and assign it to hardware serial
// All of the supported processors have a hardware port available named Serial1
#define modemSerial Serial1

// Define the serial port for modbus
// Modbus (at 9600 8N1) is used by the Keller level loggers and Yosemitech
// sensors
// Since AltSoftSerial is the best software option, we use it for modbus
// If AltSoftSerial (or its pins) aren't avaiable, use NeoSWSerial
// SoftwareSerial **WILL NOT** work for modbus!
#ifdef BUILD_TEST_ALTSOFTSERIAL
#define modbusSerial altSoftSerial  // For AltSoftSerial
#elif defined BUILD_TEST_NEOSWSERIAL
#define modbusSerial neoSSerial1  // For Neo software serial
#elif defined BUILD_TEST_SOFTSERIAL
#define modbusSerial softSerial1  // For software serial
#else
#define modbusSerial Serial1  // Hardware serial
#endif

// The Maxbotix sonar is the only sensor that communicates over a serial port
// but does not use modbus
// Since the Maxbotix only needs one-way communication and sends a simple text
// string repeatedly, almost any software serial port will do for it.
#ifdef BUILD_TEST_ALTSOFTSERIAL
#define sonarSerial altSoftSerial  // For AltSoftSerial
#elif defined BUILD_TEST_NEOSWSERIAL
#define sonarSerial neoSSerial1  // For Neo software serial
#elif defined BUILD_TEST_SOFTSERIAL
#define sonarSerial softSerial1  // For software serial
#else
#define sonarSerial Serial1  // Hardware serial
#endif

/** End [assign_ports_sw] */
#endif


// ==========================================================================
//  Data Logging Options
// ==========================================================================
/** Start [logging_options] */
// The name of this program file
const char* sketchName = "menu_a_la_carte.ino";
// Logger ID, also becomes the prefix for the name of the data file on SD card
const char* LoggerID = "XXXXX";
// How frequently (in minutes) to log data
const uint8_t loggingInterval = 15;
// Your logger's timezone.
const int8_t timeZone = -5;  // Eastern Standard Time
// NOTE:  Daylight savings time will not be applied!  Please use standard time!

// Set the input and output pins for the logger
// NOTE:  Use -1 for pins that do not apply
const int32_t serialBaud = 115200;  // Baud rate for debugging
const int8_t  greenLED   = 8;       // Pin for the green LED
const int8_t  redLED     = 9;       // Pin for the red LED
const int8_t  buttonPin  = 21;      // Pin for debugging mode (ie, button pin)
const int8_t  wakePin    = 31;  // MCU interrupt/alarm pin to wake from sleep
// Mayfly 0.x D31 = A7
// Set the wake pin to -1 if you do not want the main processor to sleep.
// In a SAMD system where you are using the built-in rtc, set wakePin to 1
const int8_t sdCardPwrPin   = -1;  // MCU SD card power pin
const int8_t sdCardSSPin    = 12;  // SD card chip select/slave select pin
const int8_t sensorPowerPin = 22;  // MCU pin controlling main sensor power
/** End [logging_options] */


// ==========================================================================
//  Wifi/Cellular Modem Options
//    NOTE:  DON'T USE MORE THAN ONE MODEM OBJECT!
//           Delete the sections you are not using!
// ==========================================================================

#if defined BUILD_MODEM_DIGI_XBEE_CELLULAR_TRANSPARENT
/** Start [digi_xbee_cellular_transparent] */
// For any Digi Cellular XBee's
// NOTE:  The u-blox based Digi XBee's (3G global and LTE-M global) can be used
// in either bypass or transparent mode, each with pros and cons
// The Telit based Digi XBees (LTE Cat1) can only use this mode.
#include <modems/DigiXBeeCellularTransparent.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 9600;  // All XBee's use 9600 by default

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// The pin numbers here are for a Digi XBee with a Mayfly 0.x and LTE adapter
// For options https://github.com/EnviroDIY/LTEbee-Adapter/edit/master/README.md
const int8_t modemVccPin = -1;     // MCU pin controlling modem power
                                   // Option: modemVccPin = A5, if Mayfly SJ7 is
                                   // connected to the ASSOC pin
const int8_t modemStatusPin = 19;  // MCU pin used to read modem status
// NOTE:  If possible, use the `STATUS/SLEEP_not` (XBee pin 13) for status, but
// the CTS pin can also be used if necessary
const bool   useCTSforStatus = false;  // Flag to use the CTS pin for status
const int8_t modemResetPin   = 20;     // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = 23;     // MCU pin for modem sleep/wake request
const int8_t modemLEDPin =
    redLED;  // MCU pin connected an LED to show modem status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
DigiXBeeCellularTransparent modemXBCT(&modemSerial, modemVccPin, modemStatusPin,
                                      useCTSforStatus, modemResetPin,
                                      modemSleepRqPin, apn);
// Create an extra reference to the modem by a generic name
DigiXBeeCellularTransparent modem = modemXBCT;
/** End [digi_xbee_cellular_transparent] */
// ==========================================================================


#elif defined BUILD_MODEM_DIGI_XBEE_LTE_BYPASS
/** Start [digi_xbee_lte_bypass] */
// For the u-blox SARA R410M based Digi LTE-M XBee3
// NOTE:  According to the manual, this should be less stable than transparent
// mode, but my experience is the complete reverse.
#include <modems/DigiXBeeLTEBypass.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 9600;  // All XBee's use 9600 by default

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// The pin numbers here are for a Digi XBee with a Mayfly 0.x and LTE adapter
const int8_t modemVccPin    = A5;  // MCU pin controlling modem power
const int8_t modemStatusPin = 19;  // MCU pin used to read modem status
// NOTE:  If possible, use the `STATUS/SLEEP_not` (XBee pin 13) for status, but
// the CTS pin can also be used if necessary
const bool   useCTSforStatus = false;  // Flag to use the CTS pin for status
const int8_t modemResetPin   = 20;     // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = 23;     // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;     // MCU pin connected an LED to show modem
                                       // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
DigiXBeeLTEBypass modemXBLTEB(&modemSerial, modemVccPin, modemStatusPin,
                              useCTSforStatus, modemResetPin, modemSleepRqPin,
                              apn);
// Create an extra reference to the modem by a generic name
DigiXBeeLTEBypass modem = modemXBLTEB;
/** End [digi_xbee_lte_bypass] */
// ==========================================================================


#elif defined BUILD_MODEM_DIGI_XBEE_3G_BYPASS
/** Start [digi_xbee_3g_bypass] */
// For the u-blox SARA U201 based Digi 3G XBee with 2G fallback
// NOTE:  According to the manual, this should be less stable than transparent
// mode, but my experience is the complete reverse.
#include <modems/DigiXBee3GBypass.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 9600;  // All XBee's use 9600 by default

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// The pin numbers here are for a Digi XBee with a Mayfly and LTE adapter
const int8_t modemVccPin    = A5;  // MCU pin controlling modem power
const int8_t modemStatusPin = 19;  // MCU pin used to read modem status
// NOTE:  If possible, use the `STATUS/SLEEP_not` (XBee pin 13) for status, but
// the CTS pin can also be used if necessary
const bool   useCTSforStatus = false;  // Flag to use the CTS pin for status
const int8_t modemResetPin   = 20;     // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = 23;     // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;     // MCU pin connected an LED to show modem
                                       // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
DigiXBee3GBypass modemXB3GB(&modemSerial, modemVccPin, modemStatusPin,
                            useCTSforStatus, modemResetPin, modemSleepRqPin,
                            apn);
// Create an extra reference to the modem by a generic name
DigiXBee3GBypass modem = modemXB3GB;
/** End [digi_xbee_3g_bypass] */
// ==========================================================================


#elif defined BUILD_MODEM_DIGI_XBEE_WIFI
/** Start [digi_xbee_wifi] */
// For the Digi Wifi XBee (S6B)
#include <modems/DigiXBeeWifi.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 9600;  // All XBee's use 9600 by default

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// The pin numbers here are for a Digi XBee direcly connected to a Mayfly 1.x
const int8_t modemVccPin    = 18;  // MCU pin controlling modem power
const int8_t modemStatusPin = 19;  // MCU pin used to read modem status
// NOTE:  If possible, use the `STATUS/SLEEP_not` (XBee pin 13) for status, but
// the CTS pin can also be used if necessary
const bool   useCTSforStatus = true;  // Flag to use the CTS pin for status
const int8_t modemResetPin   = A5;    // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = 23;    // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;    // MCU pin connected an LED to show modem
                                      // status

// Network connection information
const char* wifiId  = "xxxxx";  // WiFi access point name
const char* wifiPwd = "xxxxx";  // WiFi password (WPA2)

// Create the modem object
DigiXBeeWifi modemXBWF(&modemSerial, modemVccPin, modemStatusPin,
                       useCTSforStatus, modemResetPin, modemSleepRqPin, wifiId,
                       wifiPwd);
// Create an extra reference to the modem by a generic name
DigiXBeeWifi modem = modemXBWF;
/** End [digi_xbee_wifi] */
// ==========================================================================


#elif defined BUILD_MODEM_ESPRESSIF_ESP8266 || \
    defined   BUILD_MODEM_ESPRESSIF_ESP32
/** Start [espressif_esp8266] */
// For almost anything based on the Espressif ESP8266 using the
// AT command firmware
#include <modems/EspressifESP8266.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 115200;  // Communication speed of the modem
// NOTE:  This baud rate too fast for an 8MHz board, like the Mayfly!  The
// module should be programmed to a slower baud rate or set to auto-baud using
// the AT+UART_CUR or AT+UART_DEF command.

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// Example pins here are for a EnviroDIY ESP32 Bluetooth/Wifi Bee with
// Mayfly 1.1
const int8_t modemVccPin   = 18;      // MCU pin controlling modem power
const int8_t modemResetPin = A5;      // MCU pin connected to modem reset pin
const int8_t modemLEDPin   = redLED;  // MCU pin connected an LED to show modem
                                      // status

// Network connection information
const char* wifiId  = "xxxxx";  // WiFi access point name
const char* wifiPwd = "xxxxx";  // WiFi password (WPA2)

// Create the modem object
EspressifESP8266 modemESP(&modemSerial, modemVccPin, modemResetPin, wifiId,
                          wifiPwd);
// Create an extra reference to the modem by a generic name
EspressifESP8266 modem = modemESP;
/** End [espressif_esp8266] */
// ==========================================================================


#elif defined BUILD_MODEM_QUECTEL_BG96
/** Start [quectel_bg96] */
// For the Dragino, Nimbelink or other boards based on the Quectel BG96
#include <modems/QuectelBG96.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 115200;  // Communication speed of the modem
// NOTE:  This baud rate too fast for an 8MHz board, like the Mayfly!  The
// module should be programmed to a slower baud rate or set to auto-baud using
// the AT+IPR=9600 command.

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// Example pins here are for a Mayfly 1.x and a Dragino IoT Bee
const int8_t modemVccPin     = 18;  // MCU pin controlling modem power
const int8_t modemStatusPin  = -1;  // MCU pin used to read modem status
const int8_t modemResetPin   = A5;  // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = -1;  // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;  // MCU pin connected an LED to show modem
                                    // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
QuectelBG96 modemBG96(&modemSerial, modemVccPin, modemStatusPin, modemResetPin,
                      modemSleepRqPin, apn);
// Create an extra reference to the modem by a generic name
QuectelBG96 modem = modemBG96;
/** End [quectel_bg96] */
// ==========================================================================


#elif defined BUILD_MODEM_SEQUANS_MONARCH
/** Start [sequans_monarch] */
// For the Nimbelink LTE-M Verizon/Sequans or other boards based on the Sequans
// Monarch series
#include <modems/SequansMonarch.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 921600;  // Default baud rate of SVZM20 is 921600
// NOTE:  This baud rate is much too fast for many Arduinos!  The module should
// be programmed to a slower baud rate or set to auto-baud using the AT+IPR
// command.

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// Nimbelink Skywire (NOT directly connectable to a Mayfly!)
const int8_t modemVccPin     = 18;  // MCU pin controlling modem power
const int8_t modemStatusPin  = 19;  // MCU pin used to read modem status
const int8_t modemResetPin   = 20;  // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = 23;  // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;  // MCU pin connected an LED to show modem
                                    // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
SequansMonarch modemSVZM(&modemSerial, modemVccPin, modemStatusPin,
                         modemResetPin, modemSleepRqPin, apn);
// Create an extra reference to the modem by a generic name
SequansMonarch modem = modemSVZM;
/** End [sequans_monarch] */
// ==========================================================================


#elif defined BUILD_MODEM_SIM_COM_SIM800
/** Start [sim_com_sim800] */
// For almost anything based on the SIMCom SIM800 EXCEPT the Sodaq 2GBee R6 and
// higher
#include <modems/SIMComSIM800.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 9600;  //  SIM800 does auto-bauding by default

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// Example pins are for a Sodaq GPRSBee R4 with a Mayfly 0.x
const int8_t modemVccPin     = -1;  // MCU pin controlling modem power
const int8_t modemStatusPin  = 19;  // MCU pin used to read modem status
const int8_t modemResetPin   = -1;  // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = 23;  // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;  // MCU pin connected an LED to show modem
                                    // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
SIMComSIM800 modemS800(&modemSerial, modemVccPin, modemStatusPin, modemResetPin,
                       modemSleepRqPin, apn);
// Create an extra reference to the modem by a generic name
SIMComSIM800 modem = modemS800;
/** End [sim_com_sim800] */
// ==========================================================================


#elif defined BUILD_MODEM_SIM_COM_SIM7000
/** Start [sim_com_sim7000] */
// For almost anything based on the SIMCom SIM7000
#include <modems/SIMComSIM7000.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 9600;  //  SIM7000 does auto-bauding by default

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
const int8_t modemVccPin     = 18;  // MCU pin controlling modem power
const int8_t modemStatusPin  = 19;  // MCU pin used to read modem status
const int8_t modemResetPin   = A5;  // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = 23;  // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;  // MCU pin connected an LED to show modem
                                    // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
SIMComSIM7000 modem7000(&modemSerial, modemVccPin, modemStatusPin,
                        modemResetPin, modemSleepRqPin, apn);
// Create an extra reference to the modem by a generic name
SIMComSIM7000 modem = modem7000;
/** End [sim_com_sim7000] */
// ==========================================================================


#elif defined BUILD_MODEM_SIM_COM_SIM7080
/** Start [sim_com_sim7080] */
// For almost anything based on the SIMCom SIM7080G
#include <modems/SIMComSIM7080.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud =
    9600;  //  SIM7080 does auto-bauding by default, but I set mine to 9600

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// and-global breakout bk-7080a
const int8_t modemVccPin     = 18;  // MCU pin controlling modem power
const int8_t modemStatusPin  = 19;  // MCU pin used to read modem status
const int8_t modemSleepRqPin = 23;  // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;  // MCU pin connected an LED to show modem
                                    // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
SIMComSIM7080 modem7080(&modemSerial, modemVccPin, modemStatusPin,
                        modemSleepRqPin, apn);
// Create an extra reference to the modem by a generic name
SIMComSIM7080 modem = modem7080;
/** End [sim_com_sim7080] */
// ==========================================================================


#elif defined BUILD_MODEM_SODAQ_2G_BEE_R6
/** Start [sodaq_2g_bee_r6] */
// For the Sodaq 2GBee R6 and R7 based on the SIMCom SIM800
// NOTE:  The Sodaq GPRSBee doesn't expose the SIM800's reset pin
#include <modems/Sodaq2GBeeR6.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud = 9600;  //  SIM800 does auto-bauding by default

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// Example pins are for a Sodaq GPRSBee R6 or R7 with a Mayfly 0.x
const int8_t modemVccPin     = 23;  // MCU pin controlling modem power
const int8_t modemStatusPin  = 19;  // MCU pin used to read modem status
const int8_t modemResetPin   = -1;  // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = -1;  // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;  // MCU pin connected an LED to show modem
                                    // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
Sodaq2GBeeR6 modem2GB(&modemSerial, modemVccPin, modemStatusPin, apn);
// Create an extra reference to the modem by a generic name
Sodaq2GBeeR6 modem = modem2GB;
/** End [sodaq_2g_bee_r6] */
// ==========================================================================


#elif defined BUILD_MODEM_SODAQ_UBEE_R410M
/** Start [sodaq_ubee_r410m] */
// For the Sodaq UBee based on the 4G LTE-M u-blox SARA R410M
#include <modems/SodaqUBeeR410M.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud =
    115200;  // Default baud rate of the SARA R410M is 115200
// NOTE:  The SARA R410N DOES NOT save baud rate to non-volatile memory.  After
// every power loss, the module will return to the default baud rate of 115200.
// NOTE:  115200 is TOO FAST for an 8MHz Arduino.  This library attempts to
// compensate by sending a baud rate change command in the wake function when
// compiled for a 8MHz board. Because of this, 8MHz boards, LIKE THE MAYFLY,
// *MUST* use a HardwareSerial instance as modemSerial.

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// Example pins are for a Sodaq uBee R410M with a Mayfly 0.x
const int8_t modemVccPin     = 23;  // MCU pin controlling modem power
const int8_t modemStatusPin  = 19;  // MCU pin used to read modem status
const int8_t modemResetPin   = -1;  // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = 20;  // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;  // MCU pin connected an LED to show modem
                                    // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
SodaqUBeeR410M modemR410(&modemSerial, modemVccPin, modemStatusPin,
                         modemResetPin, modemSleepRqPin, apn);
// Create an extra reference to the modem by a generic name
SodaqUBeeR410M modem = modemR410;
/** End [sodaq_ubee_r410m] */
// ==========================================================================


#elif defined BUILD_MODEM_SODAQ_UBEE_U201
/** Start [sodaq_ubee_u201] */
// For the Sodaq UBee based on the 3G u-blox SARA U201
#include <modems/SodaqUBeeU201.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section
const int32_t modemBaud =
    9600;  //  SARA U2xx module does auto-bauding by default

// Modem Pins - Describe the physical pin connection of your modem to your board
// NOTE:  Use -1 for pins that do not apply
// Example pins are for a Sodaq uBee U201 with a Mayfly 0.x
const int8_t modemVccPin     = 23;  // MCU pin controlling modem power
const int8_t modemStatusPin  = 19;  // MCU pin used to read modem status
const int8_t modemResetPin   = -1;  // MCU pin connected to modem reset pin
const int8_t modemSleepRqPin = 20;  // MCU pin for modem sleep/wake request
const int8_t modemLEDPin = redLED;  // MCU pin connected an LED to show modem
                                    // status

// Network connection information
const char* apn = "xxxxx";  // APN for GPRS connection

// Create the modem object
SodaqUBeeU201 modemU201(&modemSerial, modemVccPin, modemStatusPin,
                        modemResetPin, modemSleepRqPin, apn);
// Create an extra reference to the modem by a generic name
SodaqUBeeU201 modem = modemU201;
/** End [sodaq_ubee_u201] */
// ==========================================================================
#endif


/** Start [modem_variables] */
// Create RSSI and signal strength variable pointers for the modem
Variable* modemRSSI =
    new Modem_RSSI(&modem, "12345678-abcd-1234-ef00-1234567890ab", "RSSI");
Variable* modemSignalPct = new Modem_SignalPercent(
    &modem, "12345678-abcd-1234-ef00-1234567890ab", "signalPercent");
Variable* modemBatteryState = new Modem_BatteryState(
    &modem, "12345678-abcd-1234-ef00-1234567890ab", "modemBatteryCS");
Variable* modemBatteryPct = new Modem_BatteryPercent(
    &modem, "12345678-abcd-1234-ef00-1234567890ab", "modemBatteryPct");
Variable* modemBatteryVoltage = new Modem_BatteryVoltage(
    &modem, "12345678-abcd-1234-ef00-1234567890ab", "modemBatterymV");
Variable* modemTemperature =
    new Modem_Temp(&modem, "12345678-abcd-1234-ef00-1234567890ab", "modemTemp");
/** End [modem_variables] */


// ==========================================================================
//  Using the Processor as a Sensor
// ==========================================================================
/** Start [processor_stats] */
#include <sensors/ProcessorStats.h>

// Create the main processor chip "sensor" - for general metadata
const char*    mcuBoardVersion = "v1.1";
ProcessorStats mcuBoard(mcuBoardVersion);

// Create sample number, battery voltage, and free RAM variable pointers for the
// processor
Variable* mcuBoardBatt = new ProcessorStats_Battery(
    &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab");
Variable* mcuBoardAvailableRAM = new ProcessorStats_FreeRam(
    &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab");
Variable* mcuBoardSampNo = new ProcessorStats_SampleNumber(
    &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab");
/** End [processor_stats] */


#if defined(ARDUINO_ARCH_AVR) || defined(MS_SAMD_DS3231)
// ==========================================================================
//  Maxim DS3231 RTC (Real Time Clock)
// ==========================================================================
/** Start [maxim_ds3231] */
#include <sensors/MaximDS3231.h>

// Create a DS3231 sensor object
MaximDS3231 ds3231(1);

// Create a temperature variable pointer for the DS3231
Variable* ds3231Temp =
    new MaximDS3231_Temp(&ds3231, "12345678-abcd-1234-ef00-1234567890ab");
/** End [maxim_ds3231] */
#endif


#if defined BUILD_SENSOR_AO_SONG_AM2315
// ==========================================================================
//  AOSong AM2315 Digital Humidity and Temperature Sensor
// ==========================================================================
/** Start [ao_song_am2315] */
#include <sensors/AOSongAM2315.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t AM2315Power = sensorPowerPin;  // Power pin

// Create an AOSong AM2315 sensor object
AOSongAM2315 am2315(AM2315Power);

// Create humidity and temperature variable pointers for the AM2315
Variable* am2315Humid =
    new AOSongAM2315_Humidity(&am2315, "12345678-abcd-1234-ef00-1234567890ab");
Variable* am2315Temp =
    new AOSongAM2315_Temp(&am2315, "12345678-abcd-1234-ef00-1234567890ab");
/** End [ao_song_am2315] */
#endif


#if defined BUILD_SENSOR_AO_SONG_DHT
// ==========================================================================
//  AOSong DHT 11/21 (AM2301)/22 (AM2302) Digital Humidity and Temperature
// ==========================================================================
/** Start [ao_song_dht] */
#include <sensors/AOSongDHT.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t DHTPower = sensorPowerPin;  // Power pin
const int8_t DHTPin   = 10;              // DHT data pin
const int8_t dhtType =
    DHT11;  // DHT type, one of DHT11, DHT12, DHT21, DHT22, or AM2301

// Create an AOSong DHT sensor object
AOSongDHT dht(DHTPower, DHTPin, dhtType);

// Create humidity, temperature, and heat index variable pointers for the DHT
Variable* dhtHumid =
    new AOSongDHT_Humidity(&dht, "12345678-abcd-1234-ef00-1234567890ab");
Variable* dhtTemp = new AOSongDHT_Temp(&dht,
                                       "12345678-abcd-1234-ef00-1234567890ab");
Variable* dhtHI   = new AOSongDHT_HI(&dht,
                                     "12345678-abcd-1234-ef00-1234567890ab");
/** End [ao_song_dht] */
#endif


#if defined BUILD_SENSOR_APOGEE_SQ212
// ==========================================================================
//  Apogee SQ-212 Photosynthetically Active Radiation (PAR) Sensor
// ==========================================================================
/** Start [apogee_sq212] */
#include <sensors/ApogeeSQ212.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t  SQ212Power       = sensorPowerPin;  // Power pin
const int8_t  SQ212ADSChannel  = 3;     // The ADS channel for the SQ212
const uint8_t SQ212ADSi2c_addr = 0x48;  // The I2C address of the ADS1115 ADC

// Create an Apogee SQ212 sensor object
ApogeeSQ212 SQ212(SQ212Power, SQ212ADSChannel, SQ212ADSi2c_addr);

// Create PAR and raw voltage variable pointers for the SQ212
Variable* sq212PAR =
    new ApogeeSQ212_PAR(&SQ212, "12345678-abcd-1234-ef00-1234567890ab");
Variable* sq212voltage =
    new ApogeeSQ212_Voltage(&SQ212, "12345678-abcd-1234-ef00-1234567890ab");
/** End [apogee_sq212] */
#endif


#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_CO2
// ==========================================================================
//  Atlas Scientific EZO-CO2 Embedded NDIR Carbon Dioxide Sensor
// ==========================================================================
/** Start [atlas_scientific_co2] */
#include <sensors/AtlasScientificCO2.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t AtlasCO2Power    = sensorPowerPin;  // Power pin
uint8_t      AtlasCO2i2c_addr = 0x69;  // Default for CO2-EZO is 0x69 (105)
// All Atlas sensors have different default I2C addresses, but any of them can
// be re-addressed to any 8 bit number.  If using the default address for any
// Atlas Scientific sensor, you may omit this argument.

// Create an Atlas Scientific CO2 sensor object
// AtlasScientificCO2 atlasCO2(AtlasCO2Power, AtlasCO2i2c_addr);
AtlasScientificCO2 atlasCO2(AtlasCO2Power);

// Create concentration and temperature variable pointers for the EZO-CO2
Variable* atlasCO2CO2 = new AtlasScientificCO2_CO2(
    &atlasCO2, "12345678-abcd-1234-ef00-1234567890ab");
Variable* atlasCO2Temp = new AtlasScientificCO2_Temp(
    &atlasCO2, "12345678-abcd-1234-ef00-1234567890ab");
/** End [atlas_scientific_co2] */
#endif


#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_DO
// ==========================================================================
//  Atlas Scientific EZO-DO Dissolved Oxygen Sensor
// ==========================================================================
/** Start [atlas_scientific_do] */
#include <sensors/AtlasScientificDO.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t AtlasDOPower    = sensorPowerPin;  // Power pin
uint8_t      AtlasDOi2c_addr = 0x61;            // Default for DO is 0x61 (97)
// All Atlas sensors have different default I2C addresses, but any of them can
// be re-addressed to any 8 bit number.  If using the default address for any
// Atlas Scientific sensor, you may omit this argument.

// Create an Atlas Scientific DO sensor object
// AtlasScientificDO atlasDO(AtlasDOPower, AtlasDOi2c_addr);
AtlasScientificDO atlasDO(AtlasDOPower);

// Create concentration and percent saturation variable pointers for the EZO-DO
Variable* atlasDOconc = new AtlasScientificDO_DOmgL(
    &atlasDO, "12345678-abcd-1234-ef00-1234567890ab");
Variable* atlasDOpct = new AtlasScientificDO_DOpct(
    &atlasDO, "12345678-abcd-1234-ef00-1234567890ab");
/** End [atlas_scientific_do] */
#endif


#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_ORP
// ==========================================================================
//  Atlas Scientific EZO-ORP Oxidation/Reduction Potential Sensor
// ==========================================================================
/** Start [atlas_scientific_orp] */
#include <sensors/AtlasScientificORP.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t AtlasORPPower    = sensorPowerPin;  // Power pin
uint8_t      AtlasORPi2c_addr = 0x62;            // Default for ORP is 0x62 (98)
// All Atlas sensors have different default I2C addresses, but any of them can
// be re-addressed to any 8 bit number.  If using the default address for any
// Atlas Scientific sensor, you may omit this argument.

// Create an Atlas Scientific ORP sensor object
// AtlasScientificORP atlasORP(AtlasORPPower, AtlasORPi2c_addr);
AtlasScientificORP atlasORP(AtlasORPPower);

// Create a potential variable pointer for the ORP
Variable* atlasORPot = new AtlasScientificORP_Potential(
    &atlasORP, "12345678-abcd-1234-ef00-1234567890ab");
/** End [atlas_scientific_orp] */
#endif


#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_PH
// ==========================================================================
//  Atlas Scientific EZO-pH Sensor
// ==========================================================================
/** Start [atlas_scientific_ph] */
#include <sensors/AtlasScientificpH.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t AtlaspHPower    = sensorPowerPin;  // Power pin
uint8_t      AtlaspHi2c_addr = 0x63;            // Default for pH is 0x63 (99)
// All Atlas sensors have different default I2C addresses, but any of them can
// be re-addressed to any 8 bit number.  If using the default address for any
// Atlas Scientific sensor, you may omit this argument.

// Create an Atlas Scientific pH sensor object
// AtlasScientificpH atlaspH(AtlaspHPower, AtlaspHi2c_addr);
AtlasScientificpH atlaspH(AtlaspHPower);

// Create a pH variable pointer for the pH sensor
Variable* atlaspHpH =
    new AtlasScientificpH_pH(&atlaspH, "12345678-abcd-1234-ef00-1234567890ab");
/** End [atlas_scientific_ph] */
#endif


#if defined(BUILD_SENSOR_ATLAS_SCIENTIFIC_RTD) || \
    defined(BUILD_SENSOR_ATLAS_SCIENTIFIC_EC)
// ==========================================================================
//  Atlas Scientific EZO-RTD Temperature Sensor
// ==========================================================================
/** Start [atlas_scientific_rtd] */
#include <sensors/AtlasScientificRTD.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t AtlasRTDPower    = sensorPowerPin;  // Power pin
uint8_t      AtlasRTDi2c_addr = 0x66;  // Default for RTD is 0x66 (102)
// All Atlas sensors have different default I2C addresses, but any of them can
// be re-addressed to any 8 bit number.  If using the default address for any
// Atlas Scientific sensor, you may omit this argument.

// Create an Atlas Scientific RTD sensor object
// AtlasScientificRTD atlasRTD(AtlasRTDPower, AtlasRTDi2c_addr);
AtlasScientificRTD atlasRTD(AtlasRTDPower);

// Create a temperature variable pointer for the RTD
Variable* atlasTemp = new AtlasScientificRTD_Temp(
    &atlasRTD, "12345678-abcd-1234-ef00-1234567890ab");
/** End [atlas_scientific_rtd] */
#endif


#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_EC
// ==========================================================================
//  Atlas Scientific EZO-EC Conductivity Sensor
// ==========================================================================
/** Start [atlas_scientific_ec] */
#include <sensors/AtlasScientificEC.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t AtlasECPower    = sensorPowerPin;  // Power pin
uint8_t      AtlasECi2c_addr = 0x64;            // Default for EC is 0x64 (100)
// All Atlas sensors have different default I2C addresses, but any of them can
// be re-addressed to any 8 bit number.  If using the default address for any
// Atlas Scientific sensor, you may omit this argument.

// Create an Atlas Scientific Conductivity sensor object
// AtlasScientificEC atlasEC(AtlasECPower, AtlasECi2c_addr);
AtlasScientificEC atlasEC(AtlasECPower);

// Create four variable pointers for the EZO-ES
Variable* atlasCond = new AtlasScientificEC_Cond(
    &atlasEC, "12345678-abcd-1234-ef00-1234567890ab");
Variable* atlasTDS =
    new AtlasScientificEC_TDS(&atlasEC, "12345678-abcd-1234-ef00-1234567890ab");
Variable* atlasSal = new AtlasScientificEC_Salinity(
    &atlasEC, "12345678-abcd-1234-ef00-1234567890ab");
Variable* atlasGrav = new AtlasScientificEC_SpecificGravity(
    &atlasEC, "12345678-abcd-1234-ef00-1234567890ab");

// Create a calculated variable for the temperature compensated conductivity
// (that is, the specific conductance).  For this example, we will use the
// temperature measured by the Atlas RTD above this.  You could use the
// temperature returned by any other water temperature sensor if desired.
// **DO NOT** use your logger board temperature (ie, from the DS3231) to
// calculate specific conductance!
float calculateAtlasSpCond(void) {
    float spCond    = -9999;  // Always safest to start with a bad value
    float waterTemp = atlasTemp->getValue();
    float rawCond   = atlasCond->getValue();
    // ^^ Linearized temperature correction coefficient per degrees Celsius.
    // The value of 0.019 comes from measurements reported here:
    // Hayashi M. Temperature-electrical conductivity relation of water for
    // environmental monitoring and geophysical data inversion. Environ Monit
    // Assess. 2004 Aug-Sep;96(1-3):119-28.
    // doi: 10.1023/b:emas.0000031719.83065.68. PMID: 15327152.
    if (waterTemp != -9999 && rawCond != -9999) {
        // make sure both inputs are good
        float temperatureCoef = 0.019;
        spCond = rawCond / (1 + temperatureCoef * (waterTemp - 25.0));
    }
    return spCond;
}

// Properties of the calculated variable
// The number of digits after the decimal place
const uint8_t atlasSpCondResolution = 0;
// This must be a value from http://vocabulary.odm2.org/variablename/
const char* atlasSpCondName = "specificConductance";
// This must be a value from http://vocabulary.odm2.org/units/
const char* atlasSpCondUnit = "microsiemenPerCentimeter";
// A short code for the variable
const char* atlasSpCondCode = "atlasSpCond";
// The (optional) universallly unique identifier
const char* atlasSpCondUUID = "12345678-abcd-1234-ef00-1234567890ab";

// Finally, create the specific conductance variable and return a pointer to it
Variable* atlasSpCond =
    new Variable(calculateAtlasSpCond, atlasSpCondResolution, atlasSpCondName,
                 atlasSpCondUnit, atlasSpCondCode, atlasSpCondUUID);
/** End [atlas_scientific_ec] */
#endif


#if defined BUILD_SENSOR_BOSCH_BME280
// ==========================================================================
//  Bosch BME280 Environmental Sensor
// ==========================================================================
/** Start [bosch_bme280] */
#include <sensors/BoschBME280.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t BME280Power = sensorPowerPin;  // Power pin
uint8_t      BMEi2c_addr = 0x76;
// The BME280 can be addressed either as 0x77 (Adafruit default) or 0x76 (Grove
// default) Either can be physically mofidied for the other address

// Create a Bosch BME280 sensor object
BoschBME280 bme280(BME280Power, BMEi2c_addr);

// Create four variable pointers for the BME280
Variable* bme280Humid =
    new BoschBME280_Humidity(&bme280, "12345678-abcd-1234-ef00-1234567890ab");
Variable* bme280Temp =
    new BoschBME280_Temp(&bme280, "12345678-abcd-1234-ef00-1234567890ab");
Variable* bme280Press =
    new BoschBME280_Pressure(&bme280, "12345678-abcd-1234-ef00-1234567890ab");
Variable* bme280Alt =
    new BoschBME280_Altitude(&bme280, "12345678-abcd-1234-ef00-1234567890ab");
/** End [bosch_bme280] */
#endif


#if defined BUILD_SENSOR_BOSCH_BMP3XX
// ==========================================================================
//  Bosch BMP 3xx Barometric Pressure Sensor
// ==========================================================================
/** Start [bosch_bmp3xx] */
#include <sensors/BoschBMP3xx.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t bmp3xxPower = -1;  // Power pin
Mode bmpMode = FORCED_MODE;  // The operating mode of the BMP; normal or forced
Oversampling bmpPressureOversample = OVERSAMPLING_X32;
Oversampling bmpTempOversample     = OVERSAMPLING_X2;
IIRFilter    bmpFilterCoeff        = IIR_FILTER_OFF;
TimeStandby  bmpTimeStandby        = TIME_STANDBY_5MS;
uint8_t      bmpI2C_addr           = 0x77;
// The BMP3xx can be addressed either as 0x77 or 0x76

// Create a Bosch BMP3xx sensor object
BoschBMP3xx bmp3xx(bmp3xxPower, bmpMode, bmpPressureOversample,
                   bmpTempOversample, bmpFilterCoeff, bmpTimeStandby,
                   bmpI2C_addr);

// Create the variable pointers for the BMP3xx
Variable* bmp3xxTemp =
    new BoschBMP3xx_Temp(&bmp3xx, "12345678-abcd-1234-ef00-1234567890ab");
Variable* bmp3xxPress =
    new BoschBMP3xx_Pressure(&bmp3xx, "12345678-abcd-1234-ef00-1234567890ab");
Variable* bmp3xxAlt =
    new BoschBMP3xx_Altitude(&bmp3xx, "12345678-abcd-1234-ef00-1234567890ab");
/** End [bosch_bmp3xx] */
#endif


#if defined BUILD_SENSOR_CAMPBELL_CLARI_VUE10
// ==========================================================================
//  Campbell ClariVUE Turbidity Sensor
// ==========================================================================
/** Start [campbell_clari_vue10] */
#include <sensors/CampbellClariVUE10.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char* ClariVUESDI12address = "0";  // The SDI-12 Address of the ClariVUE10
const int8_t ClariVUEPower       = sensorPowerPin;  // Power pin
const int8_t ClariVUEData        = 7;               // The SDI-12 data pin
// NOTE:  you should NOT take more than one readings.  THe sensor already takes
// and averages 8 by default.

// Create a Campbell ClariVUE10 sensor object
CampbellClariVUE10 clarivue(*ClariVUESDI12address, ClariVUEPower, ClariVUEData);

// Create turbidity, temperature, and error variable pointers for the ClariVUE10
Variable* clarivueTurbidity = new CampbellClariVUE10_Turbidity(
    &clarivue, "12345678-abcd-1234-ef00-1234567890ab");
Variable* clarivueTemp = new CampbellClariVUE10_Temp(
    &clarivue, "12345678-abcd-1234-ef00-1234567890ab");
Variable* clarivueError = new CampbellClariVUE10_ErrorCode(
    &clarivue, "12345678-abcd-1234-ef00-1234567890ab");
/** End [campbell_clari_vue10] */
#endif


#if defined BUILD_SENSOR_CAMPBELL_OBS3
// ==========================================================================
//  Campbell OBS 3 / OBS 3+ Analog Turbidity Sensor
// ==========================================================================
/** Start [campbell_obs3] */
#include <sensors/CampbellOBS3.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t  OBS3Power          = sensorPowerPin;  // Power pin
const uint8_t OBS3NumberReadings = 10;
const uint8_t OBS3ADSi2c_addr    = 0x48;  // The I2C address of the ADS1115 ADC

const int8_t OBSLowADSChannel = 0;  // ADS channel for *low* range output

// Campbell OBS 3+ *Low* Range Calibration in Volts
const float OBSLow_A = 0.000E+00;  // "A" value (X^2) [*low* range]
const float OBSLow_B = 1.000E+00;  // "B" value (X) [*low* range]
const float OBSLow_C = 0.000E+00;  // "C" value [*low* range]

// Create a Campbell OBS3+ *low* range sensor object
CampbellOBS3 osb3low(OBS3Power, OBSLowADSChannel, OBSLow_A, OBSLow_B, OBSLow_C,
                     OBS3ADSi2c_addr, OBS3NumberReadings);

// Create turbidity and voltage variable pointers for the low range  of the OBS3
Variable* obs3TurbLow = new CampbellOBS3_Turbidity(
    &osb3low, "12345678-abcd-1234-ef00-1234567890ab", "TurbLow");
Variable* obs3VoltLow = new CampbellOBS3_Voltage(
    &osb3low, "12345678-abcd-1234-ef00-1234567890ab", "TurbLowV");


const int8_t OBSHighADSChannel = 1;  // ADS channel for *high* range output

// Campbell OBS 3+ *High* Range Calibration in Volts
const float OBSHigh_A = 0.000E+00;  // "A" value (X^2) [*high* range]
const float OBSHigh_B = 1.000E+00;  // "B" value (X) [*high* range]
const float OBSHigh_C = 0.000E+00;  // "C" value [*high* range]

// Create a Campbell OBS3+ *high* range sensor object
CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B,
                      OBSHigh_C, OBS3ADSi2c_addr, OBS3NumberReadings);

// Create turbidity and voltage variable pointers for the high range of the OBS3
Variable* obs3TurbHigh = new CampbellOBS3_Turbidity(
    &osb3high, "12345678-abcd-1234-ef00-1234567890ab", "TurbHigh");
Variable* obs3VoltHigh = new CampbellOBS3_Voltage(
    &osb3high, "12345678-abcd-1234-ef00-1234567890ab", "TurbHighV");
/** End [campbell_obs3] */
#endif

#if defined BUILD_SENSOR_CAMPBELL_RAIN_VUE10
// ==========================================================================
//  Campbell RainVUE Precipitation Sensor
// ==========================================================================
/** Start [campbell_rain_vue10] */
#include <sensors/CampbellRainVUE10.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char*  RainVUESDI12address = "0";  // The SDI-12 Address of the RainVUE10
const int8_t RainVUEPower        = -1;   // Power pin, for continous power
const int8_t RainVUEData = 5;  // The SDI-12 data pin, for continuous power
// NOTE:  you should NOT take more than one readings.  THe sensor counts
// cummulative tips and rain accumulation since the last measurement.

// Create a Campbell RainVUE10 sensor object
CampbellRainVUE10 rainvue(*RainVUESDI12address, RainVUEPower, RainVUEData);

// Create turbidity, temperature, and error variable pointers for the RainVUE10
Variable* rainvuePrecipitation = new CampbellRainVUE10_Precipitation(
    &rainvue, "12345678-abcd-1234-ef00-1234567890ab");
Variable* rainvueTips = new CampbellRainVUE10_Tips(
    &rainvue, "12345678-abcd-1234-ef00-1234567890ab");
Variable* rainvueRainRateAve = new CampbellRainVUE10_RainRateAve(
    &rainvue, "12345678-abcd-1234-ef00-1234567890ab");
Variable* rainvueRainRateMax = new CampbellRainVUE10_RainRateMax(
    &rainvue, "12345678-abcd-1234-ef00-1234567890ab");
/** End [campbell_rain_vue10] */
#endif


#if defined BUILD_SENSOR_DECAGON_CTD
// ==========================================================================
//  Decagon CTD-10 Conductivity, Temperature, and Depth Sensor
// ==========================================================================
/** Start [decagon_ctd] */
#include <sensors/DecagonCTD.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char*   CTDSDI12address   = "1";  // The SDI-12 Address of the CTD
const uint8_t CTDNumberReadings = 6;    // The number of readings to average
const int8_t  CTDPower          = sensorPowerPin;  // Power pin
const int8_t  CTDData           = 7;               // The SDI-12 data pin

// Create a Decagon CTD sensor object
DecagonCTD ctd(*CTDSDI12address, CTDPower, CTDData, CTDNumberReadings);

// Create conductivity, temperature, and depth variable pointers for the CTD
Variable* ctdCond = new DecagonCTD_Cond(&ctd,
                                        "12345678-abcd-1234-ef00-1234567890ab");
Variable* ctdTemp = new DecagonCTD_Temp(&ctd,
                                        "12345678-abcd-1234-ef00-1234567890ab");
Variable* ctdDepth =
    new DecagonCTD_Depth(&ctd, "12345678-abcd-1234-ef00-1234567890ab");
/** End [decagon_ctd] */
#endif


#if defined BUILD_SENSOR_DECAGON_ES2
// ==========================================================================
//  Decagon ES2 Conductivity and Temperature Sensor
// ==========================================================================
/** Start [decagon_es2] */
#include <sensors/DecagonES2.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char*   ES2SDI12address   = "3";  // The SDI-12 Address of the ES2
const int8_t  ES2Power          = sensorPowerPin;  // Power pin
const int8_t  ES2Data           = 7;               // The SDI-12 data pin
const uint8_t ES2NumberReadings = 5;

// Create a Decagon ES2 sensor object
DecagonES2 es2(*ES2SDI12address, ES2Power, ES2Data, ES2NumberReadings);

// Create specific conductance and temperature variable pointers for the ES2
Variable* es2Cond = new DecagonES2_Cond(&es2,
                                        "12345678-abcd-1234-ef00-1234567890ab");
Variable* es2Temp = new DecagonES2_Temp(&es2,
                                        "12345678-abcd-1234-ef00-1234567890ab");
/** End [decagon_es2] */
#endif


#if defined BUILD_SENSOR_EVERLIGHT_ALSPT19
// ==========================================================================
//  Everlight ALS-PT19 Ambient Light Sensor
// ==========================================================================
/** Start [everlight_alspt19] */
#include <sensors/EverlightALSPT19.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t  alsPower      = sensorPowerPin;  // Power pin
const int8_t  alsData       = A4;              // The ALS PT-19 data pin
const int8_t  alsSupply     = 3.3;  // The ALS PT-19 supply power voltage
const int8_t  alsResistance = 10;   // The ALS PT-19 loading resistance (in kΩ)
const uint8_t alsNumberReadings = 10;

// Create a Everlight ALS-PT19 sensor object
EverlightALSPT19 alsPt19(alsPower, alsData, alsSupply, alsResistance,
                         alsNumberReadings);

// For an EnviroDIY Mayfly, you can use the abbreviated version
// EverlightALSPT19 alsPt19(alsNumberReadings);

// Create voltage, current, and illuminance variable pointers for the ALS-PT19
Variable* alsPt19Volt = new EverlightALSPT19_Voltage(
    &alsPt19, "12345678-abcd-1234-ef00-1234567890ab");
Variable* alsPt19Current = new EverlightALSPT19_Current(
    &alsPt19, "12345678-abcd-1234-ef00-1234567890ab");
Variable* alsPt19Lux = new EverlightALSPT19_Illuminance(
    &alsPt19, "12345678-abcd-1234-ef00-1234567890ab");
/** End [everlight_alspt19] */
#endif


#if defined BUILD_SENSOR_TIADS1X15
// ==========================================================================
//  External Voltage via TI ADS1115
// ==========================================================================
/** Start [tiads1x15] */
#include <sensors/TIADS1x15.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t  ADSPower       = sensorPowerPin;  // Power pin
const int8_t  ADSChannel     = 2;               // The ADS channel of interest
const float   dividerGain    = 10;  //  Gain setting if using a voltage divider
const uint8_t evADSi2c_addr  = 0x48;  // The I2C address of the ADS1115 ADC
const uint8_t VoltReadsToAvg = 1;     // Only read one sample

// Create an External Voltage sensor object
TIADS1x15 ads1x15(ADSPower, ADSChannel, dividerGain, evADSi2c_addr,
                  VoltReadsToAvg);

// Create a voltage variable pointer
Variable* ads1x15Volt =
    new TIADS1x15_Voltage(&ads1x15, "12345678-abcd-1234-ef00-1234567890ab");
/** End [tiads1x15] */
#endif


#if defined BUILD_SENSOR_FREESCALE_MPL115A2
// ==========================================================================
//  Freescale Semiconductor MPL115A2 Barometer
// ==========================================================================
/** Start [freescale_mpl115a2] */
#include <sensors/FreescaleMPL115A2.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t  MPLPower              = sensorPowerPin;  // Power pin
const uint8_t MPL115A2ReadingsToAvg = 1;

// Create a FreescaleMPL115A2 barometer sensor object
FreescaleMPL115A2 mpl115a2(MPLPower, MPL115A2ReadingsToAvg);

// Create pressure and temperature variable pointers for the MPL
Variable* mplPress = new FreescaleMPL115A2_Pressure(
    &mpl115a2, "12345678-abcd-1234-ef00-1234567890ab");
Variable* mplTemp = new FreescaleMPL115A2_Temp(
    &mpl115a2, "12345678-abcd-1234-ef00-1234567890ab");
/** End [freescale_mpl115a2] */
#endif


#if defined BUILD_SENSOR_IN_SITU_RDO
// ==========================================================================
//  In-Situ RDO PRO-X Rugged Dissolved Oxygen Probe
// ==========================================================================
/** Start [in_situ_rdo] */
#include <sensors/InSituRDO.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char*   RDOSDI12address   = "5";  // The SDI-12 Address of the RDO PRO-X
const int8_t  RDOPower          = sensorPowerPin;  // Power pin
const int8_t  RDOData           = 7;               // The SDI-12 data pin
const uint8_t RDONumberReadings = 3;

// Create an In-Situ RDO PRO-X dissolved oxygen sensor object
InSituRDO insituRDO(*RDOSDI12address, RDOPower, RDOData, RDONumberReadings);

// Create dissolved oxygen percent, dissolved oxygen concentration, temperature,
// and oxygen partial pressure variable pointers for the RDO PRO-X
Variable* rdoDOpct =
    new InSituRDO_DOpct(&insituRDO, "12345678-abcd-1234-ef00-1234567890ab");
Variable* rdoDOmgL =
    new InSituRDO_DOmgL(&insituRDO, "12345678-abcd-1234-ef00-1234567890ab");
Variable* rdoTemp = new InSituRDO_Temp(&insituRDO,
                                       "12345678-abcd-1234-ef00-1234567890ab");
Variable* rdoO2pp =
    new InSituRDO_Pressure(&insituRDO, "12345678-abcd-1234-ef00-1234567890ab");
/** End [in_situ_rdo] */
#endif


#if defined BUILD_SENSOR_IN_SITU_TROLL_SDI12A
// ==========================================================================
//    In-Situ Aqua/Level TROLL Pressure, Temperature, and Depth Sensor
// ==========================================================================
/** Start [in_situ_troll_sdi12a] */
#include <sensors/InSituTrollSdi12a.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char* TROLLSDI12address =
    "1";  // The SDI-12 Address of the Aqua/Level TROLL
const int8_t TROLLPower =
    sensorPowerPin;  // Pin to switch power on and off (-1 if unconnected)
const int8_t  TROLLData           = 7;  // The SDI-12 data pin
const uint8_t TROLLNumberReadings = 2;  // The number of readings to average

// Create an In-Situ TROLL sensor object
InSituTrollSdi12a insituTROLL(*TROLLSDI12address, TROLLPower, TROLLData,
                              TROLLNumberReadings);

// Create pressure, temperature, and depth variable pointers for the TROLL
Variable* trollPressure = new InSituTrollSdi12a_Pressure(
    &insituTROLL, "12345678-abcd-1234-ef00-1234567890ab");
Variable* trollTemp = new InSituTrollSdi12a_Temp(
    &insituTROLL, "12345678-abcd-1234-ef00-1234567890ab");
Variable* trollDepth = new InSituTrollSdi12a_Depth(
    &insituTROLL, "12345678-abcd-1234-ef00-1234567890ab");
/** End [in_situ_troll_sdi12a] */
#endif


#if defined BUILD_SENSOR_KELLER_ACCULEVEL
// ==========================================================================
//  Keller Acculevel High Accuracy Submersible Level Transmitter
// ==========================================================================
/** Start [keller_acculevel] */
#include <sensors/KellerAcculevel.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte acculevelModbusAddress  = 0x01;  // The modbus address of KellerAcculevel
const int8_t  acculevelPower = A3;    // Acculevel Sensor power pin
const int8_t  alAdapterPower = sensorPowerPin;  // RS485 adapter power pin
const int8_t  al485EnablePin = -1;              // Adapter RE/DE pin
const uint8_t acculevelNumberReadings = 5;
// The manufacturer recommends taking and averaging a few readings

// Create a Keller Acculevel sensor object
KellerAcculevel acculevel(acculevelModbusAddress, modbusSerial, alAdapterPower,
                          acculevelPower, al485EnablePin,
                          acculevelNumberReadings);

// Create pressure, temperature, and height variable pointers for the Acculevel
Variable* acculevPress = new KellerAcculevel_Pressure(
    &acculevel, "12345678-abcd-1234-ef00-1234567890ab");
Variable* acculevTemp = new KellerAcculevel_Temp(
    &acculevel, "12345678-abcd-1234-ef00-1234567890ab");
Variable* acculevHeight = new KellerAcculevel_Height(
    &acculevel, "12345678-abcd-1234-ef00-1234567890ab");
/** End [keller_acculevel] */
#endif


#if defined BUILD_SENSOR_KELLER_NANOLEVEL
// ==========================================================================
//  Keller Nanolevel High Accuracy Submersible Level Transmitter
// ==========================================================================
/** Start [keller_nanolevel] */
#include <sensors/KellerNanolevel.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte nanolevelModbusAddress  = 0x01;  // The modbus address of KellerNanolevel
const int8_t  nlAdapterPower = sensorPowerPin;  // RS485 adapter power pin
const int8_t  nanolevelPower = A3;              // Sensor power pin
const int8_t  nl485EnablePin = -1;              // Adapter RE/DE pin
const uint8_t nanolevelNumberReadings = 5;
// The manufacturer recommends taking and averaging a few readings

// Create a Keller Nanolevel sensor object
KellerNanolevel nanolevel(nanolevelModbusAddress, modbusSerial, nlAdapterPower,
                          nanolevelPower, nl485EnablePin,
                          nanolevelNumberReadings);

// Create pressure, temperature, and height variable pointers for the Nanolevel
Variable* nanolevPress = new KellerNanolevel_Pressure(
    &nanolevel, "12345678-abcd-1234-ef00-1234567890ab");
Variable* nanolevTemp = new KellerNanolevel_Temp(
    &nanolevel, "12345678-abcd-1234-ef00-1234567890ab");
Variable* nanolevHeight = new KellerNanolevel_Height(
    &nanolevel, "12345678-abcd-1234-ef00-1234567890ab");
/** End [keller_nanolevel] */
#endif


#if defined BUILD_SENSOR_MAX_BOTIX_SONAR
// ==========================================================================
//  Maxbotix HRXL Ultrasonic Range Finder
// ==========================================================================
/** Start [max_botix_sonar] */
#include <sensors/MaxBotixSonar.h>

// A Maxbotix sonar with the trigger pin disconnect CANNOT share the serial port
// A Maxbotix sonar using the trigger may be able to share but YMMV

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t SonarPower    = sensorPowerPin;  // Excite (power) pin
const int8_t Sonar1Trigger = -1;              // Trigger pin
// Trigger should be a *unique* negative number if unconnected
const uint8_t sonar1NumberReadings = 3;  // The number of readings to average

// Create a MaxBotix Sonar sensor object
MaxBotixSonar sonar1(sonarSerial, SonarPower, Sonar1Trigger,
                     sonar1NumberReadings);

// Create an ultrasonic range variable pointer
Variable* sonar1Range =
    new MaxBotixSonar_Range(&sonar1, "12345678-abcd-1234-ef00-1234567890ab");
/** End [max_botix_sonar] */
#endif


#if defined(BUILD_SENSOR_MAXIM_DS18) || \
    defined(BUILD_SENSOR_ANALOG_ELEC_CONDUCTIVITY)
// ==========================================================================
//  Maxim DS18 One Wire Temperature Sensor
// ==========================================================================
/** Start [maxim_ds18] */
#include <sensors/MaximDS18.h>

// OneWire Address [array of 8 hex characters]
// If only using a single sensor on the OneWire bus, you may omit the address
DeviceAddress OneWireAddress1 = {0x28, 0xFF, 0xBD, 0xBA,
                                 0x81, 0x16, 0x03, 0x0C};
// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t OneWirePower       = sensorPowerPin;  // Power pin
const int8_t OneWireBus         = A0;              // OneWire Bus Pin
const int8_t ds18NumberReadings = 3;

// Create a Maxim DS18 sensor objects (use this form for a known address)
MaximDS18 ds18(OneWireAddress1, OneWirePower, OneWireBus, ds18NumberReadings);

// Create a Maxim DS18 sensor object (use this form for a single sensor on bus
// with an unknown address)
// MaximDS18 ds18(OneWirePower, OneWireBus);

// Create a temperature variable pointer for the DS18
Variable* ds18Temp = new MaximDS18_Temp(&ds18,
                                        "12345678-abcd-1234-ef00-1234567890ab");
/** End [maxim_ds18] */
#endif


#if defined BUILD_SENSOR_MEA_SPEC_MS5803
// ==========================================================================
//  Measurement Specialties MS5803-14BA pressure sensor
// ==========================================================================
/** Start [mea_spec_ms5803] */
#include <sensors/MeaSpecMS5803.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t  MS5803Power = sensorPowerPin;  // Power pin
const uint8_t MS5803i2c_addr =
    0x76;  // The MS5803 can be addressed either as 0x76 (default) or 0x77
const int16_t MS5803maxPressure =
    14;  // The maximum pressure measurable by the specific MS5803 model
const uint8_t MS5803ReadingsToAvg = 1;

// Create a MeaSpec MS5803 pressure and temperature sensor object
MeaSpecMS5803 ms5803(MS5803Power, MS5803i2c_addr, MS5803maxPressure,
                     MS5803ReadingsToAvg);

// Create pressure and temperature variable pointers for the MS5803
Variable* ms5803Press =
    new MeaSpecMS5803_Pressure(&ms5803, "12345678-abcd-1234-ef00-1234567890ab");
Variable* ms5803Temp =
    new MeaSpecMS5803_Temp(&ms5803, "12345678-abcd-1234-ef00-1234567890ab");
/** End [mea_spec_ms5803] */
#endif


#if defined BUILD_SENSOR_DECAGON_5TM
// ==========================================================================
//  Meter ECH2O Soil Moisture Sensor
// ==========================================================================
/** Start [decagon_5tm] */
#include <sensors/Decagon5TM.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char*  TMSDI12address = "2";             // The SDI-12 Address of the 5-TM
const int8_t TMPower        = sensorPowerPin;  // Power pin
const int8_t TMData         = 7;               // The SDI-12 data pin

// Create a Decagon 5TM sensor object
Decagon5TM fivetm(*TMSDI12address, TMPower, TMData);

// Create the matric potential, volumetric water content, and temperature
// variable pointers for the 5TM
Variable* fivetmEa = new Decagon5TM_Ea(&fivetm,
                                       "12345678-abcd-1234-ef00-1234567890ab");
Variable* fivetmVWC =
    new Decagon5TM_VWC(&fivetm, "12345678-abcd-1234-ef00-1234567890ab");
Variable* fivetmTemp =
    new Decagon5TM_Temp(&fivetm, "12345678-abcd-1234-ef00-1234567890ab");
/** End [decagon_5tm] */
#endif


#if defined BUILD_SENSOR_METER_HYDROS21
// ==========================================================================
//  Meter Hydros 21 Conductivity, Temperature, and Depth Sensor
// ==========================================================================
/** Start [meter_hydros21] */
#include <sensors/MeterHydros21.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char*   hydros21SDI12address = "1";  // The SDI-12 Address of the Hydros21
const uint8_t hydros21NumberReadings = 6;  // The number of readings to average
const int8_t  hydros21Power          = sensorPowerPin;  // Power pin
const int8_t  hydros21Data           = 7;               // The SDI-12 data pin

// Create a Decagon Hydros21 sensor object
MeterHydros21 hydros21(*hydros21SDI12address, hydros21Power, hydros21Data,
                       hydros21NumberReadings);

// Create conductivity, temperature, and depth variable pointers for the
// Hydros21
Variable* hydros21Cond =
    new MeterHydros21_Cond(&hydros21, "12345678-abcd-1234-ef00-1234567890ab");
Variable* hydros21Temp =
    new MeterHydros21_Temp(&hydros21, "12345678-abcd-1234-ef00-1234567890ab");
Variable* hydros21Depth =
    new MeterHydros21_Depth(&hydros21, "12345678-abcd-1234-ef00-1234567890ab");
/** End [meter_hydros21] */
#endif


#if defined BUILD_SENSOR_METER_TEROS11
// ==========================================================================
//  Meter Teros 11 Soil Moisture Sensor
// ==========================================================================
/** Start [meter_teros11] */
#include <sensors/MeterTeros11.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char*   teros11SDI12address = "4";  // The SDI-12 Address of the Teros 11
const int8_t  terosPower          = sensorPowerPin;  // Power pin
const int8_t  terosData           = 7;               // The SDI-12 data pin
const uint8_t teros11NumberReadings = 3;  // The number of readings to average

// Create a METER TEROS 11 sensor object
MeterTeros11 teros11(*teros11SDI12address, terosPower, terosData,
                     teros11NumberReadings);

// Create the matric potential, volumetric water content, and temperature
// variable pointers for the Teros 11
Variable* teros11Ea =
    new MeterTeros11_Ea(&teros11, "12345678-abcd-1234-ef00-1234567890ab");
Variable* teros11Temp =
    new MeterTeros11_Temp(&teros11, "12345678-abcd-1234-ef00-1234567890ab");
Variable* teros11VWC =
    new MeterTeros11_VWC(&teros11, "12345678-abcd-1234-ef00-1234567890ab");
Variable* teros11Count =
    new MeterTeros11_Count(&teros11, "12345678-abcd-1234-ef00-1234567890ab");
/** End [meter_teros11] */
#endif


#if defined BUILD_SENSOR_PALEO_TERRA_REDOX
// ==========================================================================
//  PaleoTerra Redox Sensors
// ==========================================================================
/** Start [paleo_terra_redox] */
#include <sensors/PaleoTerraRedox.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
int8_t  paleoTerraPower = sensorPowerPin;  // Power pin
uint8_t paleoI2CAddress = 0x68;  // the I2C address of the redox sensor

// Create the PaleoTerra sensor object
#ifdef MS_PALEOTERRA_SOFTWAREWIRE
PaleoTerraRedox ptRedox(&softI2C, paleoTerraPower, paleoI2CAddress);
// PaleoTerraRedox ptRedox(paleoTerraPower, softwareSDA, softwareSCL,
// paleoI2CAddress);
#else
PaleoTerraRedox ptRedox(paleoTerraPower, paleoI2CAddress);
#endif

// Create the voltage variable for the redox sensor
Variable* ptVolt = new PaleoTerraRedox_Voltage(
    &ptRedox, "12345678-abcd-1234-ef00-1234567890ab");
/** End [paleo_terra_redox] */
#endif


#if defined BUILD_SENSOR_RAIN_COUNTER_I2C
// ==========================================================================
//  External I2C Rain Tipping Bucket Counter
// ==========================================================================
/** Start [rain_counter_i2c] */
#include <sensors/RainCounterI2C.h>

const uint8_t RainCounterI2CAddress = 0x08;
// I2C Address for EnviroDIY external tip counter; 0x08 by default
const float depthPerTipEvent = 0.2;  // rain depth in mm per tip event

// Create a Rain Counter sensor object
#ifdef MS_RAIN_SOFTWAREWIRE
RainCounterI2C tbi2c(&softI2C, RainCounterI2CAddress, depthPerTipEvent);
// RainCounterI2C tbi2c(softwareSDA, softwareSCL, RainCounterI2CAddress,
//                      depthPerTipEvent);
#else
RainCounterI2C  tbi2c(RainCounterI2CAddress, depthPerTipEvent);
#endif

// Create number of tips and rain depth variable pointers for the tipping bucket
Variable* tbi2cTips =
    new RainCounterI2C_Tips(&tbi2c, "12345678-abcd-1234-ef00-1234567890ab");
Variable* tbi2cDepth =
    new RainCounterI2C_Depth(&tbi2c, "12345678-abcd-1234-ef00-1234567890ab");
/** End [rain_counter_i2c] */
#endif


#if defined BUILD_SENSOR_SENSIRION_SHT4X
// ==========================================================================
//  Sensirion SHT4X Digital Humidity and Temperature Sensor
// ==========================================================================
/** Start [sensirion_sht4x] */
#include <sensors/SensirionSHT4x.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t SHT4xPower     = sensorPowerPin;  // Power pin
const bool   SHT4xUseHeater = true;

// Create an Sensirion SHT4X sensor object
SensirionSHT4x sht4x(SHT4xPower, SHT4xUseHeater);

// Create humidity and temperature variable pointers for the SHT4X
Variable* sht4xHumid =
    new SensirionSHT4x_Humidity(&sht4x, "12345678-abcd-1234-ef00-1234567890ab");
Variable* sht4xTemp =
    new SensirionSHT4x_Temp(&sht4x, "12345678-abcd-1234-ef00-1234567890ab");
/** End [sensirion_sht4x] */
#endif


#if defined BUILD_SENSOR_TALLY_COUNTER_I2C
// ==========================================================================
//    Tally I2C Event Counter for rain or wind reed-switch sensors
// ==========================================================================
/** Start [tally_counter_i2c] */
#include <sensors/TallyCounterI2C.h>

const int8_t TallyPower = -1;  // Power pin (-1 if continuously powered)
// NorthernWidget Tally I2CPower is -1 by default because it is often deployed
// with power always on, but Tally also has a super capacitor that enables it
// to be self powered between readings/recharge as described at
// https://github.com/EnviroDIY/Project-Tally

const uint8_t TallyCounterI2CAddress = 0x33;
// NorthernWidget Tally I2C address is 0x33 by default

// Create a Tally Counter sensor object
TallyCounterI2C tallyi2c(TallyPower, TallyCounterI2CAddress);

// Create variable pointers for the Tally event counter
Variable* tallyEvents = new TallyCounterI2C_Events(
    &tallyi2c, "12345678-abcd-1234-ef00-1234567890ab");

// For  Wind Speed, create a Calculated Variable that converts, similar to:
// period = loggingInterval * 60.0;    // in seconds
// frequency = tallyEventCount/period; // average event frequency in Hz
// tallyWindSpeed = frequency * 2.5 * 1.60934;  // in km/h
// 2.5 mph/Hz & 1.60934 kmph/mph and 2.5 mph/Hz conversion factor from
// web: Inspeed-Version-II-Reed-Switch-Anemometer-Sensor-Only-WS2R
/** End [tally_counter_i2c] */
#endif


#if defined BUILD_SENSOR_TI_INA219
// ==========================================================================
//  TI INA219 High Side Current/Voltage Sensor (Current mA, Voltage, Power)
// ==========================================================================
/** Start [ti_ina219] */
#include <sensors/TIINA219.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t INA219Power    = sensorPowerPin;  // Power pin
uint8_t      INA219i2c_addr = 0x40;            // 1000000 (Board A0+A1=GND)
// The INA219 can have one of 16 addresses, depending on the connections of A0
// and A1
const uint8_t INA219ReadingsToAvg = 1;

// Create an INA219 sensor object
TIINA219 ina219(INA219Power, INA219i2c_addr, INA219ReadingsToAvg);

// Create current, voltage, and power variable pointers for the INA219
Variable* inaCurrent =
    new TIINA219_Current(&ina219, "12345678-abcd-1234-ef00-1234567890ab");
Variable* inaVolt =
    new TIINA219_Voltage(&ina219, "12345678-abcd-1234-ef00-1234567890ab");
Variable* inaPower = new TIINA219_Power(&ina219,
                                        "12345678-abcd-1234-ef00-1234567890ab");
/** End [ti_ina219] */
#endif


#if defined BUILD_SENSOR_TURNER_CYCLOPS
// ==========================================================================
//  Turner Cyclops-7F Submersible Fluorometer
// ==========================================================================
/** Start [turner_cyclops] */
#include <sensors/TurnerCyclops.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const int8_t  cyclopsPower          = sensorPowerPin;  // Power pin
const uint8_t cyclopsNumberReadings = 10;
const uint8_t cyclopsADSi2c_addr = 0x48;  // The I2C address of the ADS1115 ADC
const int8_t  cyclopsADSChannel  = 0;     // ADS channel

// Cyclops calibration information
const float cyclopsStdConc = 1.000;  // Concentration of the standard used
                                     // for a 1-point sensor calibration.
const float cyclopsStdVolt =
    1.000;  // The voltage (in volts) measured for the conc_std.
const float cyclopsBlankVolt =
    0.000;  // The voltage (in volts) measured for a blank.

// Create a Turner Cyclops sensor object
TurnerCyclops cyclops(cyclopsPower, cyclopsADSChannel, cyclopsStdConc,
                      cyclopsStdVolt, cyclopsBlankVolt, cyclopsADSi2c_addr,
                      cyclopsNumberReadings);

// Create the voltage variable pointer - used for any type of Cyclops
Variable* cyclopsVoltage =
    new TurnerCyclops_Voltage(&cyclops, "12345678-abcd-1234-ef00-1234567890ab");

// Create the variable pointer for the primary output parameter.  Only use
// **ONE** of these!  Which is possible depends on your specific sensor!
Variable* cyclopsChloro = new TurnerCyclops_Chlorophyll(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsRWT = new TurnerCyclops_Rhodamine(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsFluoroscein = new TurnerCyclops_Fluorescein(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsPhycocyanin = new TurnerCyclops_Phycocyanin(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsPhycoerythrin = new TurnerCyclops_Phycoerythrin(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsCDOM =
    new TurnerCyclops_CDOM(&cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsCrudeOil = new TurnerCyclops_CrudeOil(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsBrighteners = new TurnerCyclops_Brighteners(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsTurbidity = new TurnerCyclops_Turbidity(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsPTSA =
    new TurnerCyclops_PTSA(&cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsBTEX =
    new TurnerCyclops_BTEX(&cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsTryptophan = new TurnerCyclops_Tryptophan(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
Variable* cyclopsRedChloro = new TurnerCyclops_RedChlorophyll(
    &cyclops, "12345678-abcd-1234-ef00-1234567890ab");
/** End [turner_cyclops] */
#endif


#if defined BUILD_SENSOR_ANALOG_ELEC_CONDUCTIVITY
// ==========================================================================
//   Analog Electrical Conductivity using the Processor's Analog Pins
// ==========================================================================
/** Start [analog_elec_conductivity] */
#include <sensors/AnalogElecConductivity.h>

const int8_t ECpwrPin   = A4;  // Power pin (-1 if continuously powered)
const int8_t ECdataPin1 = A0;  // Data pin (must be an analog pin, ie A#)

// Create an Analog Electrical Conductivity sensor object
AnalogElecConductivity analogEC_phy(ECpwrPin, ECdataPin1);

// Create a conductivity variable pointer for the analog sensor
Variable* analogEc_cond = new AnalogElecConductivity_EC(
    &analogEC_phy, "12345678-abcd-1234-ef00-1234567890ab");

// Create a calculated variable for the temperature compensated conductivity
// (that is, the specific conductance).  For this example, we will use the
// temperature measured by the Maxim DS18 saved as ds18Temp several sections
// above this.  You could use the temperature returned by any other water
// temperature sensor if desired.  **DO NOT** use your logger board temperature
// (ie, from the DS3231) to calculate specific conductance!
float calculateAnalogSpCond(void) {
    float spCond          = -9999;  // Always safest to start with a bad value
    float waterTemp       = ds18Temp->getValue();
    float rawCond         = analogEc_cond->getValue();
    float temperatureCoef = 0.019;
    // ^^ Linearized temperature correction coefficient per degrees Celsius.
    // The value of 0.019 comes from measurements reported here:
    // Hayashi M. Temperature-electrical conductivity relation of water for
    // environmental monitoring and geophysical data inversion. Environ Monit
    // Assess. 2004 Aug-Sep;96(1-3):119-28.
    // doi: 10.1023/b:emas.0000031719.83065.68. PMID: 15327152.
    if (waterTemp != -9999 && rawCond != -9999) {
        // make sure both inputs are good
        spCond = rawCond / (1 + temperatureCoef * (waterTemp - 25.0));
    }
    return spCond;
}

// Properties of the calculated variable
// The number of digits after the decimal place
const uint8_t analogSpCondResolution = 0;
// This must be a value from http://vocabulary.odm2.org/variablename/
const char* analogSpCondName = "specificConductance";
// This must be a value from http://vocabulary.odm2.org/units/
const char* analogSpCondUnit = "microsiemenPerCentimeter";
// A short code for the variable
const char* analogSpCondCode = "anlgSpCond";
// The (optional) universallly unique identifier
const char* analogSpCondUUID = "12345678-abcd-1234-ef00-1234567890ab";

// Finally, Create the specific conductance variable and return a pointer to it
Variable* analogEc_spcond = new Variable(
    calculateAnalogSpCond, analogSpCondResolution, analogSpCondName,
    analogSpCondUnit, analogSpCondCode, analogSpCondUUID);
/** End [analog_elec_conductivity] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y504
// ==========================================================================
//  Yosemitech Y504 Dissolved Oxygen Sensor
// ==========================================================================
/** Start [yosemitech_y504] */
#include <sensors/YosemitechY504.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y504ModbusAddress  = 0x04;  // The modbus address of the Y504
const int8_t  y504AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y504SensorPower    = A3;              // Sensor power pin
const int8_t  y504EnablePin      = -1;              // Adapter RE/DE pin
const uint8_t y504NumberReadings = 5;
// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
// power consumption

// Create a Yosemitech Y504 dissolved oxygen sensor object
YosemitechY504 y504(y504ModbusAddress, modbusSerial, y504AdapterPower,
                    y504SensorPower, y504EnablePin, y504NumberReadings);

// Create the dissolved oxygen percent, dissolved oxygen concentration, and
// temperature variable pointers for the Y504
Variable* y504DOpct =
    new YosemitechY504_DOpct(&y504, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y504DOmgL =
    new YosemitechY504_DOmgL(&y504, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y504Temp =
    new YosemitechY504_Temp(&y504, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y504] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y510
// ==========================================================================
//  Yosemitech Y510 Turbidity Sensor
// ==========================================================================
/** Start [yosemitech_y510] */
#include <sensors/YosemitechY510.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y510ModbusAddress  = 0x0B;  // The modbus address of the Y510
const int8_t  y510AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y510SensorPower    = A3;              // Sensor power pin
const int8_t  y510EnablePin      = -1;              // Adapter RE/DE pin
const uint8_t y510NumberReadings = 5;
// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
// power consumption

// Create a Y510-B Turbidity sensor object
YosemitechY510 y510(y510ModbusAddress, modbusSerial, y510AdapterPower,
                    y510SensorPower, y510EnablePin, y510NumberReadings);

// Create turbidity and temperature variable pointers for the Y510
Variable* y510Turb =
    new YosemitechY510_Turbidity(&y510, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y510Temp =
    new YosemitechY510_Temp(&y510, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y510] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y511
// ==========================================================================
//  Yosemitech Y511 Turbidity Sensor with Wiper
// ==========================================================================
/** Start [yosemitech_y511] */
#include <sensors/YosemitechY511.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y511ModbusAddress  = 0x1A;  // The modbus address of the Y511
const int8_t  y511AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y511SensorPower    = A3;              // Sensor power pin
const int8_t  y511EnablePin      = -1;              // Adapter RE/DE pin
const uint8_t y511NumberReadings = 5;
// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
// power consumption

// Create a Y511-A Turbidity sensor object
YosemitechY511 y511(y511ModbusAddress, modbusSerial, y511AdapterPower,
                    y511SensorPower, y511EnablePin, y511NumberReadings);

// Create turbidity and temperature variable pointers for the Y511
Variable* y511Turb =
    new YosemitechY511_Turbidity(&y511, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y511Temp =
    new YosemitechY511_Temp(&y511, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y511] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y514
// ==========================================================================
//  Yosemitech Y514 Chlorophyll Sensor
// ==========================================================================
/** Start [yosemitech_y514] */
#include <sensors/YosemitechY514.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y514ModbusAddress  = 0x14;  // The modbus address of the Y514
const int8_t  y514AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y514SensorPower    = A3;              // Sensor power pin
const int8_t  y514EnablePin      = -1;              // Adapter RE/DE pin
const uint8_t y514NumberReadings = 5;
// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
// power consumption

// Create a Y514 chlorophyll sensor object
YosemitechY514 y514(y514ModbusAddress, modbusSerial, y514AdapterPower,
                    y514SensorPower, y514EnablePin, y514NumberReadings);

// Create chlorophyll concentration and temperature variable pointers for the
// Y514
Variable* y514Chloro = new YosemitechY514_Chlorophyll(
    &y514, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y514Temp =
    new YosemitechY514_Temp(&y514, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y514] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y520
// ==========================================================================
//  Yosemitech Y520 Conductivity Sensor
// ==========================================================================
/** Start [yosemitech_y520] */
#include <sensors/YosemitechY520.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y520ModbusAddress  = 0x20;  // The modbus address of the Y520
const int8_t  y520AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y520SensorPower    = A3;              // Sensor power pin
const int8_t  y520EnablePin      = -1;              // Adapter RE/DE pin
const uint8_t y520NumberReadings = 5;
// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
// power consumption

// Create a Y520 conductivity sensor object
YosemitechY520 y520(y520ModbusAddress, modbusSerial, y520AdapterPower,
                    y520SensorPower, y520EnablePin, y520NumberReadings);

// Create specific conductance and temperature variable pointers for the Y520
Variable* y520Cond =
    new YosemitechY520_Cond(&y520, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y520Temp =
    new YosemitechY520_Temp(&y520, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y520] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y532
// ==========================================================================
//  Yosemitech Y532 pH
// ==========================================================================
/** Start [yosemitech_y532] */
#include <sensors/YosemitechY532.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y532ModbusAddress  = 0x32;  // The modbus address of the Y532
const int8_t  y532AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y532SensorPower    = A3;              // Sensor power pin
const int8_t  y532EnablePin      = 4;               // Adapter RE/DE pin
const uint8_t y532NumberReadings = 1;
// The manufacturer actually doesn't mention averaging for this one

// Create a Yosemitech Y532 pH sensor object
YosemitechY532 y532(y532ModbusAddress, modbusSerial, y532AdapterPower,
                    y532SensorPower, y532EnablePin, y532NumberReadings);

// Create pH, electrical potential, and temperature variable pointers for the
// Y532
Variable* y532Voltage =
    new YosemitechY532_Voltage(&y532, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y532pH =
    new YosemitechY532_pH(&y532, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y532Temp =
    new YosemitechY532_Temp(&y532, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y532] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y533
// ==========================================================================
//  Yosemitech Y533 Oxidation Reduction Potential (ORP)
// ==========================================================================
/** Start [yosemitech_y533] */
#include <sensors/YosemitechY533.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y533ModbusAddress  = 0x32;  // The modbus address of the Y533
const int8_t  y533AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y533SensorPower    = A3;              // Sensor power pin
const int8_t  y533EnablePin      = 4;               // Adapter RE/DE pin
const uint8_t y533NumberReadings = 1;
// The manufacturer actually doesn't mention averaging for this one

// Create a Yosemitech Y533 ORP sensor object
YosemitechY533 y533(y533ModbusAddress, modbusSerial, y533AdapterPower,
                    y533SensorPower, y533EnablePin, y533NumberReadings);

// Create ORP and temperature variable pointers for the Y533
Variable* y533ORP =
    new YosemitechY533_ORP(&y533, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y533Temp =
    new YosemitechY533_Temp(&y533, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y533] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y551
// ==========================================================================
//  Yosemitech Y551 COD Sensor with Wiper
// ==========================================================================
/** Start [yosemitech_y551] */
#include <sensors/YosemitechY551.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y551ModbusAddress  = 0x50;  // The modbus address of the Y551
const int8_t  y551AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y551SensorPower    = A3;              // Sensor power pin
const int8_t  y551EnablePin      = -1;              // Adapter RE/DE pin
const uint8_t y551NumberReadings = 5;
// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
// power consumption

// Create a Y551 chemical oxygen demand sensor object
YosemitechY551 y551(y551ModbusAddress, modbusSerial, y551AdapterPower,
                    y551SensorPower, y551EnablePin, y551NumberReadings);

// Create COD, turbidity, and temperature variable pointers for the Y551
Variable* y551COD =
    new YosemitechY551_COD(&y551, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y551Turbid =
    new YosemitechY551_Turbidity(&y551, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y551Temp =
    new YosemitechY551_Temp(&y551, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y551] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y560
// ==========================================================================
//  Yosemitech Y560 Ammonium Probe with Wiper
// ==========================================================================
/** Start [yosemitech_y560] */
#include <sensors/YosemitechY560.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte y560ModbusAddress =
    0x60;  // The modbus address of the Y560.
           // NOTE: Hexidecimal 0x60 = 96 decimal used by Yosemitech SmartPC
const int8_t  y560AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y560SensorPower    = A3;              // Sensor power pin
const int8_t  y560EnablePin      = -1;              // Adapter RE/DE pin
const uint8_t y560NumberReadings = 3;
// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
// power consumption

// Create a Y560 Ammonium Probe object
YosemitechY560 y560(y560ModbusAddress, modbusSerial, y560AdapterPower,
                    y560SensorPower, y560EnablePin, y560NumberReadings);

// Create COD, turbidity, and temperature variable pointers for the Y560
Variable* y560NH4_N =
    new YosemitechY560_NH4_N(&y560, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y560pH =
    new YosemitechY560_pH(&y560, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y560Temp =
    new YosemitechY560_Temp(&y560, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y560] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y700
// ==========================================================================
//  Yosemitech Y700 Pressure Sensor
// ==========================================================================
/** Start [yosemitech_y700] */
#include <sensors/YosemitechY700.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y700ModbusAddress  = 0x70;  // The modbus address of the Y700
const int8_t  y700AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y700SensorPower    = A3;              // Sensor power pin
const int8_t  y700EnablePin      = -1;              // Adapter RE/DE pin
const uint8_t y700NumberReadings = 5;
// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
// power consumption

// Create a Y700 pressure sensor object
YosemitechY700 y700(y700ModbusAddress, modbusSerial, y700AdapterPower,
                    y700SensorPower, y700EnablePin, y700NumberReadings);

// Create pressure and temperature variable pointers for the Y700
Variable* y700Pres =
    new YosemitechY700_Pressure(&y700, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y700Temp =
    new YosemitechY700_Temp(&y700, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y700] */
#endif


#if defined BUILD_SENSOR_YOSEMITECH_Y4000
// ==========================================================================
//  Yosemitech Y4000 Multiparameter Sonde (DOmgL, Turbidity, Cond, pH, Temp,
//    ORP, Chlorophyll, BGA)
// ==========================================================================
/** Start [yosemitech_y4000] */
#include <sensors/YosemitechY4000.h>

// NOTE: Extra hardware and software serial ports are created in the "Settings
// for Additional Serial Ports" section

// NOTE: Use -1 for any pins that don't apply or aren't being used.
byte          y4000ModbusAddress  = 0x05;  // The modbus address of the Y4000
const int8_t  y4000AdapterPower   = sensorPowerPin;  // RS485 adapter power pin
const int8_t  y4000SensorPower    = A3;              // Sensor power pin
const int8_t  y4000EnablePin      = -1;              // Adapter RE/DE pin
const uint8_t y4000NumberReadings = 5;
// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
// power consumption

// Create a Yosemitech Y4000 multi-parameter sensor object
YosemitechY4000 y4000(y4000ModbusAddress, modbusSerial, y4000AdapterPower,
                      y4000SensorPower, y4000EnablePin, y4000NumberReadings);

// Create all of the variable pointers for the Y4000
Variable* y4000DO =
    new YosemitechY4000_DOmgL(&y4000, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y4000Turb = new YosemitechY4000_Turbidity(
    &y4000, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y4000Cond =
    new YosemitechY4000_Cond(&y4000, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y4000pH =
    new YosemitechY4000_pH(&y4000, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y4000Temp =
    new YosemitechY4000_Temp(&y4000, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y4000ORP =
    new YosemitechY4000_ORP(&y4000, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y4000Chloro = new YosemitechY4000_Chlorophyll(
    &y4000, "12345678-abcd-1234-ef00-1234567890ab");
Variable* y4000BGA =
    new YosemitechY4000_BGA(&y4000, "12345678-abcd-1234-ef00-1234567890ab");
/** End [yosemitech_y4000] */
#endif


#if defined BUILD_SENSOR_ZEBRA_TECH_D_OPTO
// ==========================================================================
//  Zebra Tech D-Opto Dissolved Oxygen Sensor
// ==========================================================================
/** Start [zebra_tech_d_opto] */
#include <sensors/ZebraTechDOpto.h>

// NOTE: Use -1 for any pins that don't apply or aren't being used.
const char*  DOptoSDI12address = "5";  // The SDI-12 Address of the D-Opto
const int8_t ZTPower           = sensorPowerPin;  // Power pin
const int8_t ZTData            = 7;               // The SDI-12 data pin

// Create a Zebra Tech DOpto dissolved oxygen sensor object
ZebraTechDOpto dopto(*DOptoSDI12address, ZTPower, ZTData);

// Create dissolved oxygen percent, dissolved oxygen concentration, and
// temperature variable pointers for the Zebra Tech
Variable* dOptoDOpct =
    new ZebraTechDOpto_DOpct(&dopto, "12345678-abcd-1234-ef00-1234567890ab");
Variable* dOptoDOmgL =
    new ZebraTechDOpto_DOmgL(&dopto, "12345678-abcd-1234-ef00-1234567890ab");
Variable* dOptoTemp =
    new ZebraTechDOpto_Temp(&dopto, "12345678-abcd-1234-ef00-1234567890ab");
/** End [zebra_tech_d_opto] */
#endif


// ==========================================================================
//  Calculated Variable[s]
// ==========================================================================
/** Start [calculated_variables] */
// Create the function to give your calculated result.
// The function should take no input (void) and return a float.
// You can use any named variable pointers to access values by way of
// variable->getValue()

float calculateVariableValue(void) {
    float calculatedResult = -9999;  // Always safest to start with a bad value
    // float inputVar1 = variable1->getValue();
    // float inputVar2 = variable2->getValue();
    // make sure both inputs are good
    // if (inputVar1 != -9999 && inputVar2 != -9999) {
    //     calculatedResult = inputVar1 + inputVar2;
    // }
    return calculatedResult;
}

// Properties of the calculated variable
// The number of digits after the decimal place
const uint8_t calculatedVarResolution = 3;
// This must be a value from http://vocabulary.odm2.org/variablename/
const char* calculatedVarName = "varName";
// This must be a value from http://vocabulary.odm2.org/units/
const char* calculatedVarUnit = "varUnit";
// A short code for the variable
const char* calculatedVarCode = "calcVar";
// The (optional) universallly unique identifier
const char* calculatedVarUUID = "12345678-abcd-1234-ef00-1234567890ab";

// Finally, Create a calculated variable and return a variable pointer to it
Variable* calculatedVar = new Variable(
    calculateVariableValue, calculatedVarResolution, calculatedVarName,
    calculatedVarUnit, calculatedVarCode, calculatedVarUUID);
/** End [calculated_variables] */


#if defined BUILD_TEST_CREATE_IN_ARRAY
// ==========================================================================
//  Creating the Variable Array[s] and Filling with Variable Objects
//  NOTE:  This shows three differnt ways of creating the same variable array
//         and filling it with variables
// ==========================================================================
/** Start [variables_create_in_array] */
// Version 1: Create pointers for all of the variables from the sensors,
// at the same time putting them into an array
Variable* variableList[] = {
    new ProcessorStats_SampleNumber(&mcuBoard,
                                    "12345678-abcd-1234-ef00-1234567890ab"),
    new ProcessorStats_FreeRam(&mcuBoard,
                               "12345678-abcd-1234-ef00-1234567890ab"),
    new ProcessorStats_Battery(&mcuBoard,
                               "12345678-abcd-1234-ef00-1234567890ab"),
    // new MaximDS3231_Temp(&ds3231, "12345678-abcd-1234-ef00-1234567890ab"),
    //  ... Add more variables as needed!
    new Modem_RSSI(&modem, "12345678-abcd-1234-ef00-1234567890ab"),
    new Modem_SignalPercent(&modem, "12345678-abcd-1234-ef00-1234567890ab"),
    new Modem_Temp(&modem, "12345678-abcd-1234-ef00-1234567890ab"),
    new Variable(calculateVariableValue, calculatedVarResolution,
                 calculatedVarName, calculatedVarUnit, calculatedVarCode,
                 calculatedVarUUID),
};
// Count up the number of pointers in the array
int variableCount = sizeof(variableList) / sizeof(variableList[0]);
// Create the VariableArray object
VariableArray varArray(variableCount, variableList);
/** End [variables_create_in_array] */
// ==========================================================================


#elif defined BUILD_TEST_SEPARATE_UUIDS
/** Start [variables_separate_uuids] */
// Version 2: Create two separate arrays, on for the variables and a separate
// one for the UUID's, then give both as input to the variable array
// constructor.  Be cautious when doing this though because order is CRUCIAL!
Variable* variableList[] = {
    new ProcessorStats_SampleNumber(&mcuBoard),
    new ProcessorStats_FreeRam(&mcuBoard),
    new ProcessorStats_Battery(&mcuBoard),
    // new MaximDS3231_Temp(&ds3231),
    //  ... Add all of your variables!
    new Modem_RSSI(&modem),
    new Modem_SignalPercent(&modem),
    new Modem_Temp(&modem),
    new Variable(calculateVariableValue, calculatedVarResolution,
                 calculatedVarName, calculatedVarUnit, calculatedVarCode),
};
const char* UUIDs[] = {
    "12345678-abcd-1234-ef00-1234567890ab",
    "12345678-abcd-1234-ef00-1234567890ab",
    "12345678-abcd-1234-ef00-1234567890ab",
    "12345678-abcd-1234-ef00-1234567890ab",
    //  ... The number of UUID's must match the number of variables!
    "12345678-abcd-1234-ef00-1234567890ab",
    "12345678-abcd-1234-ef00-1234567890ab",
    "12345678-abcd-1234-ef00-1234567890ab",
    "12345678-abcd-1234-ef00-1234567890ab",
};
// Count up the number of pointers in the array
int variableCount = sizeof(variableList) / sizeof(variableList[0]);
// Create the VariableArray object and attach the UUID's
VariableArray varArray(variableCount, variableList, UUIDs);
/** End [variables_separate_uuids] */
// ==========================================================================


#else  // BUILD_TEST_PRE_NAMED_VARS
/** Start [variables_pre_named] */
// Version 3: Fill array with already created and named variable pointers
Variable* variableList[] = {
    mcuBoardSampNo,
    mcuBoardAvailableRAM,
    mcuBoardBatt,
    calculatedVar,
#if defined(ARDUINO_ARCH_AVR) || defined(MS_SAMD_DS3231)
    ds3231Temp,
#endif
#if defined BUILD_SENSOR_AO_SONG_AM2315
    am2315Humid,
    am2315Temp,
#endif
#if defined BUILD_SENSOR_AO_SONG_DHT
    dhtHumid,
    dhtTemp,
    dhtHI,
#endif
#if defined BUILD_SENSOR_APOGEE_SQ212
    sq212PAR,
    sq212voltage,
#endif
#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_CO2
    atlasCO2CO2,
    atlasCO2Temp,
#endif
#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_DO
    atlasDOconc,
    atlasDOpct,
#endif
#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_ORP
    atlasORPot,
#endif
#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_PH
    atlaspHpH,
#endif
#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_RTD
    atlasTemp,
#endif
#if defined BUILD_SENSOR_ATLAS_SCIENTIFIC_EC
    atlasCond,
    atlasTDS,
    atlasSal,
    atlasGrav,
    atlasSpCond,
#endif
#if defined BUILD_SENSOR_BOSCH_BME280
    bme280Temp,
    bme280Humid,
    bme280Press,
    bme280Alt,
#endif
#if defined BUILD_SENSOR_BOSCH_BMP3XX
    bmp3xxTemp,
    bmp3xxPress,
    bmp3xxAlt,
#endif
#if defined BUILD_SENSOR_CAMPBELL_CLARI_VUE10
    clarivueTurbidity,
    clarivueTemp,
    clarivueError,
#endif
#if defined BUILD_SENSOR_CAMPBELL_OBS3
    obs3TurbLow,
    obs3VoltLow,
    obs3TurbHigh,
    obs3VoltHigh,
#endif
#if defined BUILD_SENSOR_CAMPBELL_RAIN_VUE10
    rainvuePrecipitation,
    rainvueTips,
    rainvueRainRateAve,
    rainvueRainRateMax,
#endif
#if defined BUILD_SENSOR_DECAGON_CTD
    ctdCond,
    ctdTemp,
    ctdDepth,
#endif
#if defined BUILD_SENSOR_DECAGON_ES2
    es2Cond,
    es2Temp,
#endif
#if defined BUILD_SENSOR_EVERLIGHT_ALSPT19
    alsPt19Volt,
    alsPt19Current,
    alsPt19Lux,
#endif
#if defined BUILD_SENSOR_TIADS1X15
    ads1x15Volt,
#endif
#if defined BUILD_SENSOR_FREESCALE_MPL115A2
    mplTemp,
    mplPress,
#endif
#if defined BUILD_SENSOR_IN_SITU_RDO
    rdoTemp,
    rdoDOpct,
    rdoDOmgL,
    rdoO2pp,
#endif
#if defined BUILD_SENSOR_IN_SITU_TROLL_SDI12A
    trollPressure,
    trollTemp,
    trollDepth,
#endif
#if defined BUILD_SENSOR_KELLER_ACCULEVEL
    acculevPress,
    acculevTemp,
    acculevHeight,
#endif
#if defined BUILD_SENSOR_KELLER_NANOLEVEL
    nanolevPress,
    nanolevTemp,
    nanolevHeight,
#endif
#if defined BUILD_SENSOR_MAX_BOTIX_SONAR
    sonar1Range,
#endif
#if defined BUILD_SENSOR_MAXIM_DS18
    ds18Temp,
#endif
#if defined BUILD_SENSOR_MEA_SPEC_MS5803
    ms5803Temp,
    ms5803Press,
#endif
#if defined BUILD_SENSOR_DECAGON_5TM
    fivetmEa,
    fivetmVWC,
    fivetmTemp,
#endif
#if defined BUILD_SENSOR_METER_HYDROS21
    hydros21Cond,
    hydros21Temp,
    hydros21Depth,
#endif
#if defined BUILD_SENSOR_METER_TEROS11
    teros11Ea,
    teros11Temp,
    teros11VWC,
    teros11Count,
#endif
#if defined BUILD_SENSOR_PALEO_TERRA_REDOX
    ptVolt,
#endif
#if defined BUILD_SENSOR_RAIN_COUNTER_I2C
    tbi2cTips,
    tbi2cDepth,
#endif
#if defined BUILD_SENSOR_SENSIRION_SHT4X
    sht4xHumid,
    sht4xTemp,
#endif
#if defined BUILD_SENSOR_TALLY_COUNTER_I2C
    tallyEvents,
#endif
#if defined BUILD_SENSOR_TI_INA219
    inaVolt,
    inaCurrent,
    inaPower,
#endif
#if defined BUILD_SENSOR_TURNER_CYCLOPS
    cyclopsVoltage,
    cyclopsChloro,
    cyclopsRWT,
    cyclopsFluoroscein,
    cyclopsPhycocyanin,
    cyclopsPhycoerythrin,
    cyclopsCDOM,
    cyclopsCrudeOil,
    cyclopsBrighteners,
    cyclopsTurbidity,
    cyclopsPTSA,
    cyclopsBTEX,
    cyclopsTryptophan,
    cyclopsRedChloro,
#endif
#if defined BUILD_SENSOR_ANALOG_ELEC_CONDUCTIVITY
    analogEc_cond,
    analogEc_spcond,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y504
    y504DOpct,
    y504DOmgL,
    y504Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y510
    y510Turb,
    y510Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y511
    y511Turb,
    y511Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y514
    y514Chloro,
    y514Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y520
    y520Cond,
    y520Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y532
    y532Voltage,
    y532pH,
    y532Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y533
    y533ORP,
    y533Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y551
    y551COD,
    y551Turbid,
    y551Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y560
    y560NH4_N,
    y560pH,
    y560Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y700
    y700Pres,
    y700Temp,
#endif
#if defined BUILD_SENSOR_YOSEMITECH_Y4000
    y4000DO,
    y4000Turb,
    y4000Cond,
    y4000pH,
    y4000Temp,
    y4000ORP,
    y4000Chloro,
    y4000BGA,
#endif
#if defined BUILD_SENSOR_ZEBRA_TECH_D_OPTO
    dOptoDOpct,
    dOptoDOmgL,
    dOptoTemp,
#endif
    modemRSSI,
    modemSignalPct,
#ifdef TINY_GSM_MODEM_HAS_BATTERY
    modemBatteryState,
    modemBatteryPct,
    modemBatteryVoltage,
#endif
#ifdef TINY_GSM_MODEM_HAS_TEMPERATURE
    modemTemperature,
#endif
};
// Count up the number of pointers in the array
int variableCount = sizeof(variableList) / sizeof(variableList[0]);
// Create the VariableArray object
VariableArray varArray(variableCount, variableList);
/** End [variables_pre_named] */
#endif


// ==========================================================================
//  The Logger Object[s]
// ==========================================================================
/** Start [loggers] */
// Create a new logger instance
Logger dataLogger(LoggerID, loggingInterval, &varArray);
/** End [loggers] */


#if defined BUILD_PUB_ENVIRO_DIY_PUBLISHER
// ==========================================================================
//  A Publisher to Monitor My Watershed / EnviroDIY Data Sharing Portal
// ==========================================================================
/** Start [enviro_diy_publisher] */
// Device registration and sampling feature information can be obtained after
// registration at https://monitormywatershed.org or https://data.envirodiy.org
const char* registrationToken =
    "12345678-abcd-1234-ef00-1234567890ab";  // Device registration token
const char* samplingFeature =
    "12345678-abcd-1234-ef00-1234567890ab";  // Sampling feature UUID

// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint
#include <publishers/EnviroDIYPublisher.h>
EnviroDIYPublisher EnviroDIYPOST(dataLogger, &modem.gsmClient,
                                 registrationToken, samplingFeature);
/** End [enviro_diy_publisher] */
#endif


#if defined BUILD_PUB_DREAM_HOST_PUBLISHER
// ==========================================================================
//  A Publisher to DreamHost
// ==========================================================================
/** Start [dream_host_publisher] */
// NOTE:  This is an outdated data collection tool used by the Stroud Center.
// It very, very unlikely that you will use this.

const char* DreamHostPortalRX = "xxxx";

// Create a data publisher to DreamHost
#include <publishers/DreamHostPublisher.h>
DreamHostPublisher DreamHostGET(dataLogger, &modem.gsmClient,
                                DreamHostPortalRX);
/** End [dream_host_publisher] */
#endif


#if defined BUILD_PUB_THING_SPEAK_PUBLISHER
// ==========================================================================
//  ThingSpeak Data Publisher
// ==========================================================================
/** Start [thing_speak_publisher] */
// Create a channel with fields on ThingSpeak in advance.
// The fields will be sent in exactly the order they are in the variable array.
// Any custom name or identifier given to the field on ThingSpeak is irrelevant.
// No more than 8 fields of data can go to any one channel.  Any fields beyond
// the eighth in the array will be ignored.
const char* thingSpeakMQTTKey =
    "XXXXXXXXXXXXXXXX";  // Your MQTT API Key from Account > MyProfile.
const char* thingSpeakChannelID =
    "######";  // The numeric channel id for your channel
const char* thingSpeakChannelKey =
    "XXXXXXXXXXXXXXXX";  // The Write API Key for your channel

// Create a data publisher for ThingSpeak
#include <publishers/ThingSpeakPublisher.h>
ThingSpeakPublisher TsMqtt(dataLogger, &modem.gsmClient, thingSpeakMQTTKey,
                           thingSpeakChannelID, thingSpeakChannelKey);
/** End [thing_speak_publisher] */
#endif


#if defined BUILD_PUB_UBIDOTS_PUBLISHER
// ==========================================================================
//  Ubidots Data Publisher
// ==========================================================================
/** Start [ubidots_publisher] */
// The authentication token from Ubdots, either the Organization's Integration
// Token (under Users > Organization menu,visible by Admin only) OR the STEM
// User's Device Token (under the specific evice's setup panel).
const char* ubidotsToken = "XXXXXXXXXXXXXXXX";
// The device API Label from Ubidots, derived from the user-specified device
// name.
const char* ubidotsDeviceID = "######";

// Create a data publisher for ThingSpeak
#include <publishers/UbidotsPublisher.h>
UbidotsPublisher ubidots(dataLogger, &modem.gsmClient, ubidotsToken,
                         ubidotsDeviceID);
/** End [ubidots_publisher] */
#endif


// ==========================================================================
//  Working Functions
// ==========================================================================
/** Start [working_functions] */
// Flashes the LED's on the primary board
void greenredflash(uint8_t numFlash = 4, uint8_t rate = 75) {
    for (uint8_t i = 0; i < numFlash; i++) {
        digitalWrite(greenLED, HIGH);
        digitalWrite(redLED, LOW);
        delay(rate);
        digitalWrite(greenLED, LOW);
        digitalWrite(redLED, HIGH);
        delay(rate);
    }
    digitalWrite(redLED, LOW);
}

// Uses the processor sensor object to read the battery voltage
// NOTE: This will actually return the battery level from the previous update!
float getBatteryVoltage() {
    if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update();
    return mcuBoard.sensorValues[0];
}
/** End [working_functions] */


// ==========================================================================
//  Arduino Setup Function
// ==========================================================================
void setup() {
/** Start [setup_wait] */
// Wait for USB connection to be established by PC
// NOTE:  Only use this when debugging - if not connected to a PC, this
// could prevent the script from starting
#if defined SERIAL_PORT_USBVIRTUAL
    while (!SERIAL_PORT_USBVIRTUAL && (millis() < 10000L)) {
        // wait
    }
#endif
    /** End [setup_wait] */

    /** Start [setup_prints] */
    // Start the primary serial connection
    Serial.begin(serialBaud);

    // Print a start-up note to the first serial port
    Serial.print(F("\n\nNow running "));
    Serial.print(sketchName);
    Serial.print(F(" on Logger "));
    Serial.println(LoggerID);
    Serial.println();

    Serial.print(F("Using ModularSensors Library version "));
    Serial.println(MODULAR_SENSORS_VERSION);
    Serial.print(F("TinyGSM Library version "));
    Serial.println(TINYGSM_VERSION);
    Serial.println();
/** End [setup_prints] */

/** Start [setup_softserial] */
// Allow interrupts for software serial
#if defined SoftwareSerial_ExtInts_h
    enableInterrupt(softSerialRx, SoftwareSerial_ExtInts::handle_interrupt,
                    CHANGE);
#endif
#if defined NeoSWSerial_h
    enableInterrupt(neoSSerial1Rx, neoSSerial1ISR, CHANGE);
#endif
    /** End [setup_softserial] */

    /** Start [setup_serial_begins] */
    // Start the serial connection with the modem
    modemSerial.begin(modemBaud);

    // Start the stream for the modbus sensors;
    // all currently supported modbus sensors use 9600 baud
    modbusSerial.begin(9600);

#if defined BUILD_SENSOR_MAX_BOTIX_SONAR
    // Start the SoftwareSerial stream for the sonar; it will always be at 9600
    // baud
    sonarSerial.begin(9600);
#endif
/** End [setup_serial_begins] */

// Assign pins SERCOM functionality for SAMD boards
// NOTE:  This must happen *after* the various serial.begin statements
/** Start [setup_samd_pins] */
#if defined ARDUINO_ARCH_SAMD
#ifndef ENABLE_SERIAL2
    pinPeripheral(10, PIO_SERCOM);  // Serial2 Tx/Dout = SERCOM1 Pad #2
    pinPeripheral(11, PIO_SERCOM);  // Serial2 Rx/Din = SERCOM1 Pad #0
#endif
#ifndef ENABLE_SERIAL3
    pinPeripheral(2, PIO_SERCOM);  // Serial3 Tx/Dout = SERCOM2 Pad #2
    pinPeripheral(5, PIO_SERCOM);  // Serial3 Rx/Din = SERCOM2 Pad #3
#endif
#endif
    /** End [setup_samd_pins] */

    /** Start [setup_flashing_led] */
    // Set up pins for the LED's
    pinMode(greenLED, OUTPUT);
    digitalWrite(greenLED, LOW);
    pinMode(redLED, OUTPUT);
    digitalWrite(redLED, LOW);
    // Blink the LEDs to show the board is on and starting up
    greenredflash();
    /** End [setup_flashing_led] */

    /** Start [setup_logger] */
    // Set the timezones for the logger/data and the RTC
    // Logging in the given time zone
    Logger::setLoggerTimeZone(timeZone);
    // It is STRONGLY RECOMMENDED that you set the RTC to be in UTC (UTC+0)
    Logger::setRTCTimeZone(0);

    // Attach the modem and information pins to the logger
    dataLogger.attachModem(modem);
    modem.setModemLED(modemLEDPin);
    dataLogger.setLoggerPins(wakePin, sdCardSSPin, sdCardPwrPin, buttonPin,
                             greenLED);

    // Begin the logger
    dataLogger.begin();
    /** End [setup_logger] */

    /** Start [setup_sensors] */
    // Note:  Please change these battery voltages to match your battery
    // Set up the sensors, except at lowest battery level
    if (getBatteryVoltage() > 3.4) {
        Serial.println(F("Setting up sensors..."));
        varArray.setupSensors();
    }
    /** End [setup_sensors] */

#if (defined BUILD_MODEM_ESPRESSIF_ESP8266 || \
     defined BUILD_MODEM_ESPRESSIF_ESP32) &&  \
    F_CPU == 8000000L
    /** Start [setup_esp] */
    if (modemBaud > 57600) {
        modem.modemWake();  // NOTE:  This will also set up the modem
        modemSerial.begin(modemBaud);
        modem.gsmModem.sendAT(GF("+UART_DEF=9600,8,1,0,0"));
        modem.gsmModem.waitResponse();
        modemSerial.end();
        modemSerial.begin(9600);
    }
/** End [setup_esp] */
#endif

#if defined BUILD_TEST_SKYWIRE
    /** Start [setup_skywire] */
    modem.setModemStatusLevel(LOW);  // If using CTS, LOW
    modem.setModemWakeLevel(HIGH);   // Skywire dev board inverts the signal
    modem.setModemResetLevel(HIGH);  // Skywire dev board inverts the signal
    /** End [setup_skywire] */
#endif

#if defined BUILD_MODEM_SIM_COM_SIM7080
    /** Start [setup_sim7080] */
    modem.setModemWakeLevel(HIGH);   // ModuleFun Bee inverts the signal
    modem.setModemResetLevel(HIGH);  // ModuleFun Bee inverts the signal
    Serial.println(F("Waking modem and setting Cellular Carrier Options..."));
    modem.modemWake();  // NOTE:  This will also set up the modem
    modem.gsmModem.setBaud(modemBaud);   // Make sure we're *NOT* auto-bauding!
    modem.gsmModem.setNetworkMode(38);   // set to LTE only
                                         // 2 Automatic
                                         // 13 GSM only
                                         // 38 LTE only
                                         // 51 GSM and LTE only
    modem.gsmModem.setPreferredMode(1);  // set to CAT-M
                                         // 1 CAT-M
                                         // 2 NB-IoT
                                         // 3 CAT-M and NB-IoT
    /** End [setup_sim7080] */
#endif

#if defined BUILD_MODEM_DIGI_XBEE_CELLULAR_TRANSPARENT
    /** Start [setup_xbeec_carrier] */
    // Extra modem set-up
    Serial.println(F("Waking modem and setting Cellular Carrier Options..."));
    modem.modemWake();  // NOTE:  This will also set up the modem
    // Go back to command mode to set carrier options
    modem.gsmModem.commandMode();
    // Carrier Profile - 0 = Automatic selection
    //                 - 1 = No profile/SIM ICCID selected
    //                 - 2 = AT&T
    //                 - 3 = Verizon
    // NOTE:  To select T-Mobile, you must enter bypass mode!
    modem.gsmModem.sendAT(GF("CP"), 2);
    modem.gsmModem.waitResponse();
    // Cellular network technology - 0 = LTE-M with NB-IoT fallback
    //                             - 1 = NB-IoT with LTE-M fallback
    //                             - 2 = LTE-M only
    //                             - 3 = NB-IoT only
    // NOTE:  As of 2020 in the USA, AT&T and Verizon only use LTE-M
    // T-Mobile uses NB-IOT
    modem.gsmModem.sendAT(GF("N#"), 2);
    modem.gsmModem.waitResponse();
    // Write changes to flash and apply them
    Serial.println(F("Wait while applying changes..."));
    // Write changes to flash
    modem.gsmModem.writeChanges();
    // Reset the cellular component to ensure network settings are changed
    modem.gsmModem.sendAT(GF("!R"));
    modem.gsmModem.waitResponse(30000L);
    // Force reset of the Digi component as well
    // This effectively exits command mode
    modem.gsmModem.sendAT(GF("FR"));
    modem.gsmModem.waitResponse(5000L);
/** End [setup_xbeec_carrier] */
#endif


#if defined BUILD_MODEM_DIGI_XBEE_LTE_BYPASS
    /** Start [setup_r4_carrrier] */
    // Extra modem set-up
    Serial.println(F("Waking modem and setting Cellular Carrier Options..."));
    modem.modemWake();  // NOTE:  This will also set up the modem
    // Turn off the cellular radio while making network changes
    modem.gsmModem.sendAT(GF("+CFUN=0"));
    modem.gsmModem.waitResponse();
    // Mobile Network Operator Profile - 0 = SW default
    //                                 - 1 = SIM ICCID selected
    //                                 - 2: ATT
    //                                 - 6: China Telecom
    //                                 - 100: Standard Europe
    //                                 - 4: Telstra
    //                                 - 5: T-Mobile US
    //                                 - 19: Vodafone
    //                                 - 3: Verizon
    //                                 - 31: Deutsche Telekom
    modem.gsmModem.sendAT(GF("+UMNOPROF="), 2);
    modem.gsmModem.waitResponse();
    // Selected network technology - 7: LTE Cat.M1
    //                             - 8: LTE Cat.NB1
    // Fallback network technology - 7: LTE Cat.M1
    //                              - 8: LTE Cat.NB1
    // NOTE:  As of 2020 in the USA, AT&T and Verizon only use LTE-M
    // T-Mobile uses NB-IOT
    modem.gsmModem.sendAT(GF("+URAT="), 7, ',', 8);
    modem.gsmModem.waitResponse();
    // Restart the module to apply changes
    modem.gsmModem.sendAT(GF("+CFUN=1,1"));
    modem.gsmModem.waitResponse(10000L);
/** End [setup_r4_carrrier] */
#endif

    /** Start [setup_clock] */
    // Sync the clock if it isn't valid or we have battery to spare
    if (getBatteryVoltage() > 3.55 || !dataLogger.isRTCSane()) {
        // Synchronize the RTC with NIST
        // This will also set up the modem
        dataLogger.syncRTC();
    }
    /** End [setup_clock] */

    /** Start [setup_file] */
    // Create the log file, adding the default header to it
    // Do this last so we have the best chance of getting the time correct and
    // all sensor names correct
    // Writing to the SD card can be power intensive, so if we're skipping
    // the sensor setup we'll skip this too.
    if (getBatteryVoltage() > 3.4) {
        Serial.println(F("Setting up file on SD card"));
        dataLogger.turnOnSDcard(true);
        // true = wait for card to settle after power up
        dataLogger.createLogFile(true);  // true = write a new header
        dataLogger.turnOffSDcard(true);
        // true = wait for internal housekeeping after write
    }
    /** End [setup_file] */

    /** Start [setup_sleep] */
    // Call the processor sleep
    Serial.println(F("Putting processor to sleep\n"));
    dataLogger.systemSleep();
    /** End [setup_sleep] */
}


// ==========================================================================
//  Arduino Loop Function
// ==========================================================================
#ifndef BUILD_TEST_COMPLEX_LOOP
// Use this short loop for simple data logging and sending
/** Start [simple_loop] */
void loop() {
    // Note:  Please change these battery voltages to match your battery
    // At very low battery, just go back to sleep
    if (getBatteryVoltage() < 3.4) {
        dataLogger.systemSleep();
    } else if (getBatteryVoltage() < 3.55) {
        // At moderate voltage, log data but don't send it over the modem
        dataLogger.logData();
    } else {
        // If the battery is good, send the data to the world
        dataLogger.logDataAndPublish();
    }
}

/** End [simple_loop] */

#else
/** Start [complex_loop] */
// Use this long loop when you want to do something special
// Because of the way alarms work on the RTC, it will wake the processor and
// start the loop every minute exactly on the minute.
// The processor may also be woken up by another interrupt or level change on a
// pin - from a button or some other input.
// The "if" statements in the loop determine what will happen - whether the
// sensors update, testing mode starts, or it goes back to sleep.
void loop() {
    // Reset the watchdog
    dataLogger.watchDogTimer.resetWatchDog();

    // Assuming we were woken up by the clock, check if the current time is an
    // even interval of the logging interval
    // We're only doing anything at all if the battery is above 3.4V
    if (dataLogger.checkInterval() && getBatteryVoltage() > 3.4) {
        // Flag to notify that we're in already awake and logging a point
        Logger::isLoggingNow = true;
        dataLogger.watchDogTimer.resetWatchDog();

        // Print a line to show new reading
        Serial.println(F("------------------------------------------"));
        // Turn on the LED to show we're taking a reading
        dataLogger.alertOn();
        // Power up the SD Card, but skip any waits after power up
        dataLogger.turnOnSDcard(false);
        dataLogger.watchDogTimer.resetWatchDog();

        // Turn on the modem to let it start searching for the network
        // Only turn the modem on if the battery at the last interval was high
        // enough
        // NOTE:  if the modemPowerUp function is not run before the
        // completeUpdate
        // function is run, the modem will not be powered and will not
        // return a signal strength reading.
        if (getBatteryVoltage() > 3.6) modem.modemPowerUp();

#ifdef BUILD_TEST_ALTSOFTSERIAL
        // Start the stream for the modbus sensors, if your RS485 adapter bleeds
        // current from data pins when powered off & you stop modbus serial
        // connection with digitalWrite(5, LOW), below.
        // https://github.com/EnviroDIY/ModularSensors/issues/140#issuecomment-389380833
        altSoftSerial.begin(9600);
#endif

        // Do a complete update on the variable array.
        // This this includes powering all of the sensors, getting updated
        // values, and turing them back off.
        // NOTE:  The wake function for each sensor should force sensor setup
        // to run if the sensor was not previously set up.
        varArray.completeUpdate();

        dataLogger.watchDogTimer.resetWatchDog();

#ifdef BUILD_TEST_ALTSOFTSERIAL
        // Reset modbus serial pins to LOW, if your RS485 adapter bleeds power
        // on sleep, because Modbus Stop bit leaves these pins HIGH.
        // https://github.com/EnviroDIY/ModularSensors/issues/140#issuecomment-389380833
        digitalWrite(5, LOW);  // Reset AltSoftSerial Tx pin to LOW
        digitalWrite(6, LOW);  // Reset AltSoftSerial Rx pin to LOW
#endif

        // Create a csv data record and save it to the log file
        dataLogger.logToSD();
        dataLogger.watchDogTimer.resetWatchDog();

        // Connect to the network
        // Again, we're only doing this if the battery is doing well
        if (getBatteryVoltage() > 3.55) {
            dataLogger.watchDogTimer.resetWatchDog();
            if (modem.connectInternet()) {
                dataLogger.watchDogTimer.resetWatchDog();
                // Publish data to remotes
                Serial.println(F("Modem connected to internet."));
                dataLogger.publishDataToRemotes();

                // Sync the clock at noon
                dataLogger.watchDogTimer.resetWatchDog();
                if (Logger::markedLocalEpochTime != 0 &&
                    Logger::markedLocalEpochTime % 86400 == 43200) {
                    Serial.println(F("Running a daily clock sync..."));
                    dataLogger.setRTClock(modem.getNISTTime());
                    dataLogger.watchDogTimer.resetWatchDog();
                    modem.updateModemMetadata();
                    dataLogger.watchDogTimer.resetWatchDog();
                }

                // Disconnect from the network
                modem.disconnectInternet();
                dataLogger.watchDogTimer.resetWatchDog();
            }
            // Turn the modem off
            modem.modemSleepPowerDown();
            dataLogger.watchDogTimer.resetWatchDog();
        }

        // Cut power from the SD card - without additional housekeeping wait
        dataLogger.turnOffSDcard(false);
        dataLogger.watchDogTimer.resetWatchDog();
        // Turn off the LED
        dataLogger.alertOff();
        // Print a line to show reading ended
        Serial.println(F("------------------------------------------\n"));

        // Unset flag
        Logger::isLoggingNow = false;
    }

    // Check if it was instead the testing interrupt that woke us up
    if (Logger::startTesting) {
#ifdef BUILD_TEST_ALTSOFTSERIAL
        // Start the stream for the modbus sensors, if your RS485 adapter bleeds
        // current from data pins when powered off & you stop modbus serial
        // connection with digitalWrite(5, LOW), below.
        // https://github.com/EnviroDIY/ModularSensors/issues/140#issuecomment-389380833
        altSoftSerial.begin(9600);
#endif

        dataLogger.testingMode();
    }

#ifdef BUILD_TEST_ALTSOFTSERIAL
    // Reset modbus serial pins to LOW, if your RS485 adapter bleeds power
    // on sleep, because Modbus Stop bit leaves these pins HIGH.
    // https://github.com/EnviroDIY/ModularSensors/issues/140#issuecomment-389380833
    digitalWrite(5, LOW);  // Reset AltSoftSerial Tx pin to LOW
    digitalWrite(6, LOW);  // Reset AltSoftSerial Rx pin to LOW
#endif

    // Call the processor sleep
    dataLogger.systemSleep();
}
#endif
/** End [complex_loop] */