Example showing all possible functionality
-
Walking Through the Code
- Defines and Includes
- Logger Settings
-
Wifi/Cellular Modem Options
- Digi XBee Cellular - Transparent Mode
- Digi XBee3 LTE-M - Bypass Mode
- Digi XBee 3G - Bypass Mode
- Digi XBee S6B Wifi
- Espressif ESP8266
- Quectel BG96
- Sequans Monarch
- SIMCom SIM800
- SIMCom SIM7000
- SIMCom SIM7080G (EnviroDIY LTE Bee])
- Sodaq GPRSBee
- u-blox SARA R410M
- u-blox SARA U201
- Modem Measured Variables
-
Sensors and Measured Variables
- The processor as a sensor
- Maxim DS3231 RTC as a sensor
- AOSong AM2315
- AOSong DHT
- Apogee SQ-212 Quantum Light Sensor
- Atlas Scientific EZO Circuits
- Bosch BME280 Environmental Sensor
- Bosch BMP388 and BMP398 Pressure Sensors
- Campbell OBS3+ Analog Turbidity Sensor
- Decagon ES2 Conductivity and Temperature Sensor
- External Voltage via TI ADS1x15
- Freescale Semiconductor MPL115A2 Miniature I2C Digital Barometer
- Keller RS485/Modbus Water Level Sensors
- Maxbotix HRXL Ultrasonic Range Finder
- Maxim DS18 One Wire Temperature Sensor
- Measurement Specialties MS5803-14BA Pressure Sensor
- Meter SDI-12 Sensors
- PaleoTerra Redox Sensors
- Trinket-Based Tipping Bucket Rain Gauge
- Northern Widget Tally Event Counter
- TI INA219 High Side Current Sensor
- Turner Cyclops-7F Submersible Fluorometer
- Analog Electrical Conductivity using the Processor's Analog Pins
-
Yosemitech RS485/Modbus Environmental Sensors
- Yosemitech Y504 Dissolved Oxygen Sensor
- Yosemitech Y510 Turbidity Sensor
- Yosemitech Y511 Turbidity Sensor with Wiper
- Yosemitech Y514 Chlorophyll Sensor
- Yosemitech Y520 Conductivity Sensor
- Yosemitech Y532 pH Sensor
- Yosemitech Y533 Oxidation Reduction Potential (ORP) Sensor
- Yosemitech Y551 Carbon Oxygen Demand (COD) Sensor with Wiper
- Yosemitech Y560 Ammonium Sensor
- Yosemitech Y700 Pressure Sensor
- Yosemitech Y4000 Multi-Parameter Sonde
- Zebra Tech D-Opto Dissolved Oxygen Sensor
- Calculated Variables
- Creating the array, logger, publishers
- Extra Working Functions
- Arduino Setup Function
- Arduino Loop Function
- PlatformIO Configuration
- The Complete Code
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
Defines and Includes
Defines for the Arduino IDE
The top few lines of the examples set defines of buffer sizes and yields needed for the Arduino IDE. That IDE read any defines within the top few lines and applies them as build flags for the processor. This is not standard behavior for C++ (which is what Arduino code really is) - this is a unique aspect of the Arduino IDE.
#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
If you are using PlatformIO, you should instead set these as global build flags in your platformio.ini. This is standard behaviour for C++.
build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT -DMQTT_MAX_PACKET_SIZE=240 -DTINY_GSM_RX_BUFFER=64 -DTINY_GSM_YIELD_MS=2
Library Includes
Next, include the libraries needed for every program using ModularSensors.
// 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>
Logger Settings
Creating Extra Serial Ports
This section of the example has all the code to create and link to serial ports for both AVR and SAMD based boards. The EnviroDIY Mayfly, the Arduino Mega, UNO, and Leonardo are all AVR boards. The Arduino Zero, the M0 and the Sodaq Autonomo are all SAMD boards.
Many different sensors communicate using some sort of serial or transistor-transistor-logic (TTL) protocol. Among these are any sensors using RS232, RS485, RS422. Generally each serial variant (or sometimes each sensor) needs a dedicated serial "port" - its own connection to the processor. Most processors have built in dedicated wires for serial communication - "Hardware" serial. See the page on Arduino streams for much more detail about serial connections with Arduino processors.
AVR Boards
Most Arduino AVR style boards have very few (ie, one, or none) dedicated serial ports available after counting out the programming serial port. So to connect anything else, we need to try to emulate the processor serial functionality with a software library. This example shows three possible libraries that can be used to emulate a serial port on an AVR board.
AltSoftSerial
AltSoftSerial by Paul Stoffregen 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. See the processor compatibility page for more information on which pins are used on supported boards.
#include <AltSoftSerial.h> AltSoftSerial altSoftSerial;
NeoSWSerial
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 want. Each instance requires two pins, one for data in and another for data out. If you only want to use the serial line for incoming or outgoing data, set the other pin to -1. Not all AVR boards are supported by 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))); }
When using NeoSWSerial we will also have to actually set the data receiving (Rx) pin modes for interrupt in the setup function.
SoftwareSerial with External Interrupts
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 but it is still far from ideal. This should be used only use if necessary. It is not a very accurate serial port!
Accepting its poor quality, you can use as many instances of SoftwareSerial as you want. Each instance requires two pins, one for data in and another for data out. If you only want to use the serial line for incoming or outgoing data, set the other pin to -1.
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);
When using SoftwareSerial with External Interrupts we will also have to actually set the data receiving (Rx) pin modes for interrupt in the setup function.
Software I2C/Wire
This creates a software I2C (wire) instance that can be shared between multiple sensors. Only Testato's SoftwareWire library is supported.
// 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);
SAMD Boards
The SAMD21 supports up to 6 hardware serial ports, which is awesome. But, the Arduino core doesn't make use of all of them, so we have to assign them ourselves.
This section of code assigns SERCOM's 1 and 2 to act as Serial2 and Serial3 on pins 10/11 and 5/2 respectively. These pin selections are based on the Adafruit Feather M0.
// 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
In addition to creating the extra SERCOM ports here, the pins must be set up as the proper pin peripherals after the serial ports are begun. This is shown in the SAMD Pin Peripherals section of the setup function.
NOTE: The SAMD51 board has an amazing 8 available SERCOM's, but I do not have any exmple code for using them.
Assigning Serial Port Functionality
This section just assigns all the serial ports from the Creating Extra Serial Ports section above to specific functionality. For a board with the option of up to 4 hardware serial ports, like the SAMD21 or Arduino Mega, we use the Serial1 to talk to the modem, Serial2 for modbus, and Serial3 for the Maxbotix.
// 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
For an AVR board where we're relying on a mix of hardware and software ports, we use hardware Serial 1 for the modem, AltSoftSerial for modbus, and NeoSWSerial for the Maxbotix. Depending on how you rank the importance of each component, you can adjust these to your liking.
// 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
Logging Options
Here we set options for the logging and dataLogger object. This includes setting the time zone (daylight savings time is NOT applied) and setting all of the input and output pins related to the logger.
// 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
Wifi/Cellular Modem Options
This modem section is very lengthy because it contains the code with the constructor for every possible supported modem module. Do NOT try to use more than one modem at a time - it will NOT work.
To create any of the modems, we follow a similar pattern:
First, we'll create a pointer to the serial port (Arduino Stream object) that we'll use for communication between the modem and the MCU. We also assign the baud rate to a variable here. There is a table of Default baud rates of supported modules on the Notes about Modems page. The baud rate of any of the modules can be changed using AT commands or the modem.gsmModem.setBaud(uint32_t baud)
function.
Next, we'll assign all the pin numbers for all the other pins connected between the modem and the MCU. Pins that do not apply should be set as -1. There is a table of general Sleep and Reset Pin Labels and Pin Numbers to Use when Connecting to a Mayfly 0.x on the Notes about Modems page.
All the modems also need some sort of network credentials for internet access. For WiFi modems, you need the network name and password (assuming WPA2). For cellular models, you will need the APN assigned to you by the carrier you bought your SIM card from.
Digi XBee Cellular - Transparent Mode
This is the code to use for any of Digi's cellular XBee or XBee3 modules. All of them can be implented as a DigiXBeeCellularTransparent object - a subclass of DigiXBee and loggerModem. To create a DigiXBeeCellularTransparent object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the status pin,
- whether the status pin is the true status pin (
ON/SLEEP_N/DIO9
) or theCTS_N/DIO7
pin, - the MCU pin connected to the
RESET_N
pin, - the
DTR_N/SLEEP_RQ/DIO8
pin, - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1. A helpful table detailing the pins to use with the EnviroDIY Mayfly is available on the Modem Notes page.
// 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;
Depending on your cellular carrier, it is best to select the proper carrier profile and network. Setting these helps the modem to connect to network faster. This is shows in the XBee Cellular Carrier chunk of the setup function.
Digi XBee3 LTE-M - Bypass Mode
This code is for Digi's LTE-M XBee3 based on the u-blox SARA R410M - used in bypass mode. To create a DigiXBeeLTEBypass object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the status pin,
- whether the status pin is the true status pin (
ON/SLEEP_N/DIO9
) or theCTS_N/DIO7
pin, - the MCU pin connected to the
RESET_N
pin, - the
DTR_N/SLEEP_RQ/DIO8
pin, - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1. A helpful table detailing the pins to use with the EnviroDIY Mayfly is available on the Modem Notes page.
// 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;
Depending on your cellular carrier, it is best to select the proper carrier profile and network. Setting these helps the modem to connect to network faster. This is shows in the SARA R4 Cellular Carrier chunk of the setup function.
Digi XBee 3G - Bypass Mode
This code is for Digi's 3G/2G XBee based on the u-blox SARA U201 - used in bypass mode. To create a DigiXBee3GBypass object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the status pin,
- whether the status pin is the "true" status pin (
ON/SLEEP_N/DIO9
) or theCTS_N/DIO7
pin, - the MCU pin connected to the
RESET_N
pin, - the
DTR_N/SLEEP_RQ/DIO8
pin, - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1. A helpful table detailing the pins to use with the EnviroDIY Mayfly is available on the Modem Notes page.
// 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;
Digi XBee S6B Wifi
This code is for the Digi's S6B wifi module. To create a DigiXBeeWifi object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the status pin,
- whether the status pin is the "true" status pin (
ON/SLEEP_N/DIO9
) or theCTS_N/DIO7
pin, - the MCU pin connected to the
RESET_N
pin, - the
DTR_N/SLEEP_RQ/DIO8
pin, - the wifi access point name,
- and the wifi WPA2 password.
Pins that do not apply should be set as -1. A helpful table detailing the pins to use with the EnviroDIY Mayfly is available on the Modem Notes page.
// 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;
Espressif ESP8266
This code is for the Espressif ESP8266 or ESP32 operating with "AT" firmware. To create a EspressifESP8266 object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the reset pin (MCU pin connected to the ESP's
RSTB/DIO16
), - the wifi access point name,
- and the wifi WPA2 password.
Pins that do not apply should be set as -1.
// 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;
Because the ESP8266's default baud rate is too fast for an 8MHz board like the Mayfly, to use it you need to drop the baud rate down for sucessful communication. You can set the slower baud rate using some external method, or useing the code from the ESP8266 Baud Rate(ESP8266 Baud Rate) part of the setup function below.
Quectel BG96
This code is for the Dragino, Nimbelink or other boards based on the Quectel BG96. To create a QuectelBG96 object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the
STATUS
pin, - the MCU pin connected to the
RESET_N
pin, - the MCU pin connected to the
PWRKEY
pin (for sleep request), - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1.
// 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;
If you are interfacing with a Nimbelink Skywire board via the Skywire development board, you also need to handle the fact that the development board reverses the levels of the status, wake, and reset pins. Code to invert the pin levels is in the Skywire Pin Inversions part of the setup function below.
Sequans Monarch
This code is for the Nimbelink LTE-M Verizon/Sequans or other boards based on the Sequans Monarch series SoC. To create a SequansMonarch object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to either the
GPIO3/STATUS_LED
orPOWER_MON
pin, - the MCU pin connected to the
RESETN
pin, - the MCU pin connected to the
RTS
orRTS0
pin (for sleep request), - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1.
// 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;
If you are interfacing with a Nimbelink Skywire board via the Skywire development board, you also need to handle the fact that the development board reverses the levels of the status, wake, and reset pins. Code to invert the pin levels is in the Skywire Pin Inversions part of the setup function below.
The default baud rate of the SVZM20 is much too fast for almost all Arduino boards. Before attampting to connect a SVZM20 to an Arduino you should connect it to your computer and use AT commands to decrease the baud rate. The proper command to decrease the baud rate to 9600 (8N1) is: AT+IPR=9600
.
SIMCom SIM800
This code is for a SIMCom SIM800 or SIM900 or one of their many variants, including the Adafruit Fona and the Sodaq 2GBee R4. To create a SIMComSIM800 object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the
STATUS
pin, - the MCU pin connected to the
RESET
pin, - the MCU pin connected to the
PWRKEY
pin (for sleep request), - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1.
NOTE: This is NOT the correct form for a Sodaq 2GBee R6 or R7. See the section for a 2GBee R6.
// 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;
SIMCom SIM7000
This code is for a SIMCom SIM7000 or one of its variants. To create a SIMComSIM7000 object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the
STATUS
pin, - the MCU pin connected to the
RESET
pin, - the MCU pin connected to the
PWRKEY
pin (for sleep request), - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1.
// 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;
SIMCom SIM7080G (EnviroDIY LTE Bee])
This code is for a SIMCom SIM7080G or one of its variants, including the EnviroDIY LTE Bee.
To create a SIMComSIM7080 object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the
STATUS
pin, - the MCU pin connected to the
PWRKEY
pin (for sleep request), - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1. A helpful table detailing the pins to use with the EnviroDIY LTE Bee and the EnviroDIY Mayfly is available on the Modem Notes page.
// 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;
Sodaq GPRSBee
This code is for the Sodaq 2GBee R6 and R7 based on the SIMCom SIM800. To create a Sodaq2GBeeR6 object we need to know
- the serial object name,
- the MCU pin controlling modem power, (NOTE: On the GPRSBee R6 and R7 the pin labeled as ON/OFF in Sodaq's diagrams is tied to both the SIM800 power supply and the (inverted) SIM800
PWRKEY
. You should enter this pin as the power pin.) - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1. The GPRSBee R6/R7 does not expose the RESET
pin of the SIM800. The PWRKEY
is held LOW
as long as the SIM800 is powered (as mentioned above). A helpful table detailing the pins to use with the Sodaq GPRSBee and the EnviroDIY Mayfly is available on the Modem Notes page.
// 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;
u-blox SARA R410M
This code is for modules based on the 4G LTE-M u-blox SARA R410M including the Sodaq UBee. To create a SodaqUBeeR410M object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the
V_INT
pin (for status), - the MCU pin connected to the
RESET_N
pin, - the MCU pin connected to the
PWR_ON
pin (for sleep request), - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1. A helpful table detailing the pins to use with the UBee R410M and the EnviroDIY Mayfly is available on the Modem Notes page.
// 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;
Depending on your cellular carrier, it is best to select the proper carrier profile and network. Setting these helps the modem to connect to network faster. This is shows in the SARA R4 Cellular Carrier chunk of the setup function.
u-blox SARA U201
This code is for modules based on the 3G/2G u-blox SARA U201 including the Sodaq UBee or the Sodaq 3GBee. To create a SodaqUBeeU201 object we need to know
- the serial object name,
- the MCU pin controlling modem power,
- the MCU pin connected to the
V_INT
pin (for status), - the MCU pin connected to the
RESET_N
pin, - the MCU pin connected to the
PWR_ON
pin (for sleep request), - and the SIM card's cellular access point name (APN).
Pins that do not apply should be set as -1. A helpful table detailing the pins to use with the Sodaq UBee U201 and the EnviroDIY Mayfly is available on the Modem Notes page.
// 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;
Modem Measured Variables
After creating the modem object, we can create Variable objects for each of the variables the modem is capable of measuring (Modem_
// 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");
Sensors and Measured Variables
The processor as a sensor
Set options and create the objects for using the processor as a sensor to report battery level, processor free ram, and sample number.
The processor can return the number of "samples" it has taken, the amount of RAM it has available and, for some boards, the battery voltage (EnviroDIY Mayfly, Sodaq Mbili, Ndogo, Autonomo, and One, Adafruit Feathers). The version of the board is required as input (ie, for a EnviroDIY Mayfly: "v0.3" or "v0.4" or "v0.5"). Use a blank value (ie, "") for un-versioned boards. Please note that while you can opt to average more than one sample, it really makes no sense to do so for the processor. The number of "samples" taken will increase by one for each time another processor "measurement" is taken so averaging multiple measurements from the processor will result in the number of samples increasing by more than one with each loop.
#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");
Maxim DS3231 RTC as a sensor
In addition to the time, we can also use the required DS3231 real time clock to report the temperature of the circuit board. This temperature is not equivalent to an environmental temperature measurement and should only be used to as a diagnostic. As above, we create both the sensor and the variables measured by it.
#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");
AOSong AM2315
Here is the code for the AOSong AM2315 temperature and humidity sensor. This is an I2C sensor with only one possible address so the only argument required for the constructor is the pin on the MCU controlling power to the AM2315 (AM2315Power). The number of readings to average from the sensor is optional, but can be supplied as the second argument for the constructor if desired.
#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");
AOSong DHT
Here is the code for the AOSong DHT temperature and humidity sensor. To create the DHT Sensor we need the power pin, the data pin, and the DHT type. The number of readings to average from the sensor is optional, but can be supplied as the fourth argument for the constructor if desired.
#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");
Apogee SQ-212 Quantum Light Sensor
Here is the code for the Apogee SQ-212 Quantum Light Sensor. The SQ-212 is not directly connected to the MCU, but rather to an TI ADS1115 that communicates with the MCU. The Arduino pin controlling power on/off and the analog data channel on the TI ADS1115 are required for the sensor constructor. If your ADD converter is not at the standard address of 0x48, you can enter its actual address as the third argument. The number of readings to average from the sensor is optional, but can be supplied as the fourth argument for the constructor if desired.
#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");
Atlas Scientific EZO Circuits
The next several sections are for Atlas Scientific EZO circuts and sensors. The sensor class constructors for each are nearly identical, except for the class name. In the most common setup, with hardware I2C, the only required argument for the constructor is the Arduino pin controlling power on/off; the i2cAddressHex is optional as is the number of readings to average.
The default I2C addresses for the circuits are:
- CO2: 0x69 (105)
- DO: 0x61 (97)
- EC (conductivity): 0x64 (100)
- ORP (redox): 0x62 (98)
- pH: 0x63 (99)
- RTD (temperature): 0x66 (102) All of the circuits can be re-addressed to any other 8 bit number if desired. To use multiple circuits of the same type, re-address them.
Atlas Scientific EZO-CO2 Embedded NDIR Carbon Dioxide Sensor
#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");
Atlas Scientific EZO-DO Dissolved Oxygen Sensor
#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");
Atlas Scientific EZO-ORP Oxidation/Reduction Potential Sensor
#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");
Atlas Scientific EZO-pH Sensor
#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");
Atlas Scientific EZO-RTD Temperature Sensor
#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");
Atlas Scientific EZO-EC Conductivity Sensor
#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);
Bosch BME280 Environmental Sensor
Here is the code for the Bosch BME280 environmental sensor. The only input needed is the Arduino pin controlling power on/off; the i2cAddressHex is optional as is the number of readings to average. Keep in mind that the possible I2C addresses of the BME280 match those of the MS5803; when using those sensors together, make sure they are set to opposite addresses.
#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");
Bosch BMP388 and BMP398 Pressure Sensors
#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");
Campbell ClariVUE SDI-12 Turbidity Sensor
#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");
Campbell OBS3+ Analog Turbidity Sensor
This is the code for the Campbell OBS3+. The Arduino pin controlling power on/off, analog data channel on the TI ADS1115, and calibration values in Volts for Ax^2 + Bx + C are required for the sensor constructor. A custom variable code can be entered as a second argument in the variable constructors, and it is very strongly recommended that you use this otherwise it will be very difficult to determine which return is high and which is low range on the sensor. If your ADD converter is not at the standard address of 0x48, you can enter its actual address as the third argument. Do NOT forget that if you want to give a number of measurements to average, that comes after the i2c address in the constructor!
Note that to access both the high and low range returns, two instances must be created, one at the low range return pin and one at the high pin.
#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");
Campbell RainVUE SDI-12 Precipitation Sensor
#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");
Decagon CTD-10 Conductivity, Temperature, and Depth Sensor
#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");
Decagon ES2 Conductivity and Temperature Sensor
The SDI-12 address of the sensor, the Arduino pin controlling power on/off, and the Arduino pin sending and receiving data are required for the sensor constructor. Optionally, you can include a number of distinct readings to average. The data pin must be a pin that supports pin-change interrupts.
#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");
Everlight ALS-PT19 Ambient Light Sensor
#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");
External Voltage via TI ADS1x15
The Arduino pin controlling power on/off and the analog data channel on the TI ADS1115 are required for the sensor constructor. If using a voltage divider to increase the measurable voltage range, enter the gain multiplier as the third argument. If your ADD converter is not at the standard address of 0x48, you can enter its actual address as the fourth argument. The number of measurements to average, if more than one is desired, goes as the fifth argument.
#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");
Freescale Semiconductor MPL115A2 Miniature I2C Digital Barometer
The only input needed for the sensor constructor is the Arduino pin controlling power on/off and optionally the number of readings to average. Because this sensor can have only one I2C address (0x60), it is only possible to connect one of these sensors to your system.
#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");
In-Situ Aqua/Level TROLL Pressure, Temperature, and Depth Sensor
#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");
In-Situ RDO PRO-X Rugged Dissolved Oxygen Probe
#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");
Keller RS485/Modbus Water Level Sensors
The next two sections are for Keller RS485/Modbus water level sensors. The sensor class constructors for each are nearly identical, except for the class name. The sensor constructors require as input: the sensor modbus address, a stream instance for data (ie, Serial
), and one or two power pins. The Arduino pin controlling the receive and data enable on your RS485-to-TTL adapter and the number of readings to average are optional. (Use -1 for the second power pin and -1 for the enable pin if these don't apply and you want to average more than one reading.) Please see the section "[Notes on Arduino Streams and Software Serial](@ref page_arduino_streams)" for more information about what streams can be used along with this library. In tests on these sensors, SoftwareSerial_ExtInts did not work to communicate with these sensors, because it isn't stable enough. AltSoftSerial and HardwareSerial work fine.
The serial ports for this example are created in the Creating Extra Serial Ports section and then assigned to modbus functionality in the Assigning Serial Port Functionality section.
Up to two power pins are provided so that the RS485 adapter, the sensor and/or an external power relay can be controlled separately. If the power to everything is controlled by the same pin, use -1 for the second power pin or omit the argument. If they are controlled by different pins and no other sensors are dependent on power from either pin then the order of the pins doesn't matter. If the RS485 adapter, sensor, or relay are controlled by different pins and any other sensors are controlled by the same pins you should put the shared pin first and the un-shared pin second. Both pins cannot be shared pins.
Keller Acculevel High Accuracy Submersible Level Transmitter
#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");
Keller Nanolevel Level Transmitter
#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");
Maxbotix HRXL Ultrasonic Range Finder
The Arduino pin controlling power on/off, a stream instance for received data (ie, Serial
), and the Arduino pin controlling the trigger are required for the sensor constructor. (Use -1 for the trigger pin if you do not have it connected.) Please see the section "[Notes on Arduino Streams and Software Serial](@ref page_arduino_streams)" for more information about what streams can be used along with this library.
The serial ports for this example are created in the Creating Extra Serial Ports section and then assigned to the sonar functionality in the Assigning Serial Port Functionality section.
#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");
Maxim DS18 One Wire Temperature Sensor
The OneWire hex address of the sensor, the Arduino pin controlling power on/off, and the Arduino pin sending and receiving data are required for the sensor constructor, though the address can be omitted if only one sensor is used. The OneWire address is an array of 8 hex values, for example: {0x28, 0x1D, 0x39, 0x31, 0x2, 0x0, 0x0, 0xF0}. To get the address of your sensor, plug a single sensor into your device and run the oneWireSearch example or the Single example provided within the Dallas Temperature library. The sensor address is programmed at the factory and cannot be changed.
#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");
Measurement Specialties MS5803-14BA Pressure Sensor
The only input needed is the Arduino pin controlling power on/off; the i2cAddressHex and maximum pressure are optional as is the number of readings to average. Keep in mind that the possible I2C addresses of the MS5803 match those of the BME280.
#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");
Meter SDI-12 Sensors
The next few sections are for Meter SDI-12 sensors. The SDI-12 address of the sensor, the Arduino pin controlling power on/off, and the Arduino pin sending and receiving data are required for the sensor constructor. Optionally, you can include a number of distinct readings to average. The data pin must be a pin that supports pin-change interrupts.
Meter ECH2O Soil Moisture Sensor
#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");
Meter Hydros 21 Conductivity, Temperature, and Depth Sensor
#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");
Meter Teros 11 Soil Moisture Sensor
#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");
PaleoTerra Redox Sensors
Because older versions of these sensors all ship with the same I2C address, and more than one is frequently used at different soil depths in the same profile, this module has an optional dependence on Testato's SoftwareWire library for software I2C.
To use software I2C, compile with the build flag -D MS_PALEOTERRA_SOFTWAREWIRE
. See the software wire section for an example of creating a software I2C instance to share between sensors.
The constructors for the software I2C implementation requires either the SCL and SDA pin numbers or a reference to the I2C object as arguments. All variants of the constructor require the Arduino power pin. The I2C address can be given if it the sensor is not set to the default of 0x68. A number of readings to average can also be given.
#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");
Trinket-Based Tipping Bucket Rain Gauge
This is for use with a simple external I2C tipping bucket counter based on the Adafriut Trinket. All constructor arguments are optional, but the first argument is for the I2C address of the tip counter (if not 0x08) and the second is for the depth of rain (in mm) per tip event (if not 0.2mm). Most metric tipping buckets are calibrated to have 1 tip per 0.2mm of rain. Most English tipping buckets are calibrated to have 1 tip per 0.01" of rain, which is 0.254mm. Note that you cannot input a number of measurements to average because averaging does not make sense with this kind of counted variable.
#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");
Sensirion SHT4X Digital Humidity and Temperature Sensor
#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");
Northern Widget Tally Event Counter
This is for use with Northern Widget's Tally event counter
The only option for the constructor is an optional setting for the I2C address, if the counter is not set at the default of 0x33. The counter should be continuously powered.
#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
TI INA219 High Side Current Sensor
This is the code for the TI INA219 high side current and voltage sensor. The Arduino pin controlling power on/off is all that is required for the constructor. If your INA219 is not at the standard address of 0x40, you can enter its actual address as the fourth argument. The number of measurements to average, if more than one is desired, goes as the fifth argument.
#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");
Turner Cyclops-7F Submersible Fluorometer
This is the code for the Turner Cyclops-7F submersible fluorometer. The Arduino pin controlling power on/off and all calibration information is needed for the constructor. The address of the ADS1x15, if it is different than the default of 0x48, can be entered after the calibration information. The number of measurements to average, if more than one is desired, is the last argument.
The Cyclops sensors are NOT pre-calibrated and must be calibrated prior to deployment.
#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");
Analog Electrical Conductivity using the Processor's Analog Pins
This is the code for the measuring electrical conductivity using the processor's internal ADC and analog input pins. The Arduino pin controlling power on/off and the sensing pin are required for the constuctor. The power supply for the sensor absolutely must be switched on and off between readings! The resistance of your in-circuit resistor, the cell constant for your power cord, and the number of measurements to average are the optional third, fourth, and fifth arguments. If your processor has an ADS with resolution greater or less than 10-bit, compile with the build flag -D ANALOG_EC_ADC_RESOLUTION=##
. For best results, you should also connect the AREF pin of your processors ADC to the power supply for the and compile with the build flag -D ANALOG_EC_ADC_REFERENCE_MODE=EXTERNAL
.
#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);
Yosemitech RS485/Modbus Environmental Sensors
The next several sections are for Yosemitech brand sensors. The sensor class constructors for each are nearly identical, except for the class name. The sensor constructor requires as input: the sensor modbus address, a stream instance for data (ie, Serial
), and one or two power pins. The Arduino pin controlling the receive and data enable on your RS485-to-TTL adapter and the number of readings to average are optional. (Use -1 for the second power pin and -1 for the enable pin if these don't apply and you want to average more than one reading.) For most of the sensors, Yosemitech strongly recommends averaging multiple (in most cases 10) readings for each measurement. Please see the section "[Notes on Arduino Streams and Software Serial](@ref page_arduino_streams)" for more information about what streams can be used along with this library. In tests on these sensors, SoftwareSerial_ExtInts did not work to communicate with these sensors, because it isn't stable enough. AltSoftSerial and HardwareSerial work fine. NeoSWSerial is a bit hit or miss, but can be used in a pinch.
The serial ports for this example are created in the Creating Extra Serial Ports section and then assigned to modbus functionality in the Assigning Serial Port Functionality section.
Yosemitech Y504 Dissolved Oxygen Sensor
#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");
Yosemitech Y510 Turbidity Sensor
#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");
Yosemitech Y511 Turbidity Sensor with Wiper
#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");
Yosemitech Y514 Chlorophyll Sensor
#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");
Yosemitech Y520 Conductivity Sensor
#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");
Yosemitech Y532 pH Sensor
#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");
Yosemitech Y533 Oxidation Reduction Potential (ORP) Sensor
#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");
Yosemitech Y551 Carbon Oxygen Demand (COD) Sensor with Wiper
#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");
Yosemitech Y560 Ammonium Sensor
#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");
Yosemitech Y700 Pressure Sensor
#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");
Yosemitech Y4000 Multi-Parameter Sonde
#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");
Zebra Tech D-Opto Dissolved Oxygen Sensor
The SDI-12 address of the sensor, the Arduino pin controlling power on/off, and the Arduino pin sending and receiving data are required for the sensor constructor. Optionally, you can include a number of distinct readings to average. The data pin must be a pin that supports pin-change interrupts.
#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");
Calculated Variables
Create new Variable objects calculated from the measured variables. For these calculate variables, we must not only supply a function for the calculation, but also all of the metadata about the variable - like the name of the variable and its units.
// 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);
Creating the array, logger, publishers
The variable array
Create a VariableArray containing all of the Variable objects that we are logging the values of.
This shows three differnt ways of creating the same variable array and filling it with variables. You should only use ONE of these in your own code
Creating Variables within an Array
Here we use the new
keyword to create multiple variables and get pointers to them all at the same time within the arry.
// 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);
Creating Variables and Pasting UUIDs from MonitorMyWatershed
If you are sending data to monitor my watershed, it is much easier to create the variables in an array and then to paste the UUID's all together as copied from the "View Token UUID List" link for a site. If using this method, be very, very, very careful to make sure the order of your variables exactly matches the order of your UUID's.
// 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);
Creating Variables within an Array
You can also create and name variable pointer objects outside of the array (as is demonstrated in all of the code chunks here) and then reference those pointers inside of the array like so:
// 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);
The Logger Object
Now that we've created the array, we can actually create the Logger object.
// Create a new logger instance Logger dataLogger(LoggerID, loggingInterval, &varArray);
Data Publishers
Here we set up all three possible data publisers and link all of them to the same Logger object.
Monitor My Watershed
To publish data to the Monitor My Watershed / EnviroDIY Data Sharing Portal first you must register yourself as a user at https:/
// 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);
DreamHost
It is extrmemly unlikely you will use this. You should ignore this section.
// 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);
ThingSpeak
After you have set up channels on ThingSpeak, you can use this code to publish your data to it.
Keep in mind that the order of variables in the VariableArray is crucial when publishing to ThingSpeak.
// 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);
Ubidots
Use this to publish data to Ubidots.
// 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);
Extra Working Functions
Here we're creating a few extra functions on the global scope. The flash function is used at board start up just to give an indication that the board has restarted. The battery function calls the ProcessorStats sensor to check the battery level before attempting to log or publish data.
// 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]; }
Arduino Setup Function
This is our setup function. In Arduino coding, the classic "main" function is replaced by two functions: setup() and loop(). The setup() function runs once when the board boots or restarts. It usually contains many functions that set the mode of input and output pins and prints out some debugging information to the serial port. These functions are frequently named "begin". Because we have a lot of parts to set up, there's a lot going on in this function!
Let's break it down.
Starting the Function
First we just open the function definitions:
void setup() {
Wait for USB
Next we wait for the USB debugging port to initialize. This only applies to SAMD and 32U4 boards that have built-in USB support. This code should not be used for deployed loggers; it's only for using a USB for debugging.
// 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
Printing a Hello
Next we print a message out to the debugging port. This is also just for debugging - it's very helpful when connected to the logger via USB to see a clear indication that the board is starting
// 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();
Serial Interrupts
If we're using either NeoSWSerial or SoftwareSerial_ExtInts we need to assign the data receiver pins to interrupt functionality here in the setup.
The NeoSWSerial and SoftwareSerial_
NOTE: If you create more than one NeoSWSerial or Software serial object, you need to call the enableInterrupt function for each Rx pin!
For NeoSWSerial we use: enableInterrupt(neoSSerial1Rx, neoSSerial1ISR, CHANGE);
For SoftwareSerial with External interrupts we use:
enableInterrupt(softSerialRx, SoftwareSerial_ExtInts::handle_interrupt, CHANGE);
// 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
Serial Begin
Every serial port setup and used in the program must be "begun" in the setup function. This section calls the begin functions for all of the various ports defined in the Extra Serial Ports section
// 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
SAMD Pin Peripherals
After beginning all of the serial ports, we need to set the pin peripheral settings for any SERCOM's we assigned to serial functionality on the SAMD boards. These were created in the Extra Serial Ports section above. This does not need to be done for an AVR board (like the Mayfly).
#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
Flash the LEDs
Like printing debugging information to the serial port, flashing the board LED's is a very helpful indication that the board just restarted. Here we set the pin modes for the LED pins and flash them back and forth using the greenredflash() function we created back in the working functions section.
// 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();
Begin the Logger
Next get ready and begin the logger. We set the logger time zone and the clock time zone. The clock time zone is what the RTC will report; the logger time zone is what will be written to the SD card and all data publishers. The values are set with the Logger::
prefix because they are static variables of the Logger class rather than member variables. Here we also tie the logger and modem together and set all the logger pins. Then we finally run the logger's begin function.
// 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();
Setup the Sensors
After beginning the logger, we setup all the sensors. Unlike all the previous chuncks of the setup that are preparation steps only requiring the mcu processor, this might involve powering up the sensors. To prevent a low power restart loop, we put a battery voltage condition on the sensor setup. This prevents a solar powered board whose battery has died from continuously restarting as soon as it gains any power on sunrise. Without the condition the board would boot with power, try to power hungry sensors, brown out, and restart over and over.
// 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(); }
Custom Modem Setup
Next we can opt to do some special setup needed for a few of the modems. You should only use the one chunk that applies to your specific modem configuration and delete the others.
ESP8266 Baud Rate
This chunk of code reduces the baud rate of the ESP8266 from its default of 115200 to 9600. This is only needed for 8MHz boards (like the Mayfly) that cannot communicate at 115200 baud.
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); }
Skywire Pin Inversions
This chunk of code inverts the pin levels for status, wake, and reset of the modem. This is necessary for the Skywire development board and some other breakouts.
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
SimCom SIM7080G Network Mode
This chunk of code sets the network mode and preferred mode for the SIM7080G.
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
XBee Cellular Carrier
This chunk of code sets the carrier profile and network technology for a Digi XBee or XBee3. You should change the lines with the CP
and N#
commands to the proper number to match your SIM card.
// 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);
SARA R4 Cellular Carrier
This chunk of code sets the carrier profile and network technology for a u-blox SARA R4 or N4 module, including a Sodaq R410 UBee or a Digi XBee3 LTE-M in bypass mode.. You should change the lines with the UMNOPROF
and URAT
commands to the proper number to match your SIM card.
// 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);
Sync the Real Time Clock
After any special modem options, we can opt to use the modem to synchronize the real time clock with the NIST time servers. This is very helpful in keeping the clock from drifting or resetting it if it lost time due to power loss. Like the sensor setup, we also apply a battery voltage voltage condition before attempting the clock sync. (All of the supported modems are large power eaters.) Unlike the sensor setup, we have an additional check for "sanity" of the clock time. To be considered "sane" the clock has to set somewhere between 2020 and 2025. It's a broad range, but it will automatically flag values like Jan 1, 2000 - which are the default start value of the clock on power up.
// 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(); }
Setup File on the SD card
We're getting close to the end of the setup function! This section verifies that the SD card is communicating with the MCU and sets up a file on it for saved data. Like with the sensors and the modem, we check for battery level before attempting to communicate with the SD card.
// 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 }
Sleep until the First Data Collection Time
We're finally fished with setup! This chunk puts the system into low power deep sleep until the next logging interval.
// Call the processor sleep Serial.println(F("Putting processor to sleep\n")); dataLogger.systemSleep();
Setup Complete
Set up is done! This setup function is really long. But don't forget you need to close it with a final curly brace.
}
Arduino Loop Function
This is the loop function which will run repeatedly as long as the board is turned on. NOTE: This example has code for both a typical simple loop and a complex loop that calls lower level logger functions. You should only pick one loop function and delete the other.
A Typical Loop
After the incredibly long setup function, we can do the vast majority of all logger work in a very simple loop function. Every time the logger wakes we check the battery voltage and do 1 of three things:
- If the battery is very low, go immediately back to sleep and hope the sun comes back out
- If the battery is at a moderate level, attempt to collect data from sensors, but do not attempt to publish data. The modem the biggest power user of the whole system. 3. At full power, do everything.
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(); } }
A Complex Loop
If you need finer control over the steps of the logging function, this code demonstrates how the loop should be constructed.
Here are some guidelines for writing a loop function:
- If you want to log on an even interval, use
if (checkInterval())
orif (checkMarkedInterval())
to verify that the current or marked time is an even interval of the logging interval.. - Call the
markTime()
function if you want associate with a two iterations of sensor updates with the same timestamp. This allows you to usecheckMarkedInterval()
to check if an action should be preformed based on the exact time when the logger woke rather than upto several seconds later when iterating through sensors. - Either:
- Power up all of your sensors with
sensorsPowerUp()
. - Wake up all your sensors with
sensorsWake()
. - Update the values all the sensors in your VariableArray together with
updateAllSensors()
. - Immediately after running
updateAllSensors()
, put sensors to sleep to save power withsensorsSleep()
. - Power down all of your sensors with
sensorsPowerDown()
.
- Power up all of your sensors with
- Or:
- Do a full update loop of all sensors, including powering them with
completeUpdate()
. (This combines the previous 5 functions.)
- Do a full update loop of all sensors, including powering them with
- After updating the sensors, then call any functions you want to send/print/save data.
- Finish by putting the logger back to sleep, if desired, with
systemSleep()
.
All together, this gives:
// 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
If you need more help in writing a complex loop, the double_
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] */