From b1957089b9cfe37ff8fa102862f25b7ac2b9e79e Mon Sep 17 00:00:00 2001 From: AshwiniCUB Date: Wed, 24 Apr 2024 01:50:05 -0600 Subject: [PATCH] WiFi_Reconnecting & reset function added --- xpod_V3.1.9/Adafruit_PM25AQI.cpp | 133 +++++ xpod_V3.1.9/Adafruit_PM25AQI.h | 65 +++ xpod_V3.1.9/My_BlynkSimpleShieldEsp8266.h | 231 ++++++++ xpod_V3.1.9/OPC.cpp | 185 +++++++ xpod_V3.1.9/OPC.h | 54 ++ xpod_V3.1.9/ads_module.cpp | 273 ++++++++++ xpod_V3.1.9/ads_module.h | 61 +++ xpod_V3.1.9/blynk.h | 186 +++++++ xpod_V3.1.9/bme_module.cpp | 62 +++ xpod_V3.1.9/bme_module.h | 30 + xpod_V3.1.9/digipot.cpp | 97 ++++ xpod_V3.1.9/digipot.h | 23 + xpod_V3.1.9/gps_module.cpp | 194 +++++++ xpod_V3.1.9/gps_module.h | 46 ++ xpod_V3.1.9/mq131_module.cpp | 158 ++++++ xpod_V3.1.9/mq131_module.h | 48 ++ xpod_V3.1.9/pms_module.cpp | 78 +++ xpod_V3.1.9/pms_module.h | 30 + xpod_V3.1.9/quad_module.cpp | 88 +++ xpod_V3.1.9/quad_module.h | 32 ++ xpod_V3.1.9/s300i2c.cpp | 70 +++ xpod_V3.1.9/s300i2c.h | 43 ++ xpod_V3.1.9/wind_vane.cpp | 75 +++ xpod_V3.1.9/wind_vane.h | 28 + xpod_V3.1.9/xpod_node.h | 40 ++ xpod_V3.1.9/xpod_node.ino | 636 ++++++++++++++++++++++ xpod_node/Adafruit_PM25AQI.cpp | 133 +++++ xpod_node/Adafruit_PM25AQI.h | 65 +++ xpod_node/My_BlynkSimpleShieldEsp8266.h | 231 ++++++++ xpod_node/OPC.cpp | 185 +++++++ xpod_node/OPC.h | 54 ++ xpod_node/blynk.h | 186 +++++++ xpod_node/digipot.cpp | 97 ++++ xpod_node/digipot.h | 23 + xpod_node/gps_module.cpp | 194 +++++++ xpod_node/gps_module.h | 46 ++ xpod_node/mq131_module.cpp | 158 ++++++ xpod_node/mq131_module.h | 48 ++ xpod_node/pms_module.cpp | 78 +++ xpod_node/pms_module.h | 30 + xpod_node/quad_module.cpp | 88 +++ xpod_node/quad_module.h | 32 ++ xpod_node/s300i2c.cpp | 70 +++ xpod_node/s300i2c.h | 43 ++ xpod_node/wind_vane.cpp | 75 +++ xpod_node/wind_vane.h | 28 + 46 files changed, 4830 insertions(+) create mode 100644 xpod_V3.1.9/Adafruit_PM25AQI.cpp create mode 100644 xpod_V3.1.9/Adafruit_PM25AQI.h create mode 100644 xpod_V3.1.9/My_BlynkSimpleShieldEsp8266.h create mode 100644 xpod_V3.1.9/OPC.cpp create mode 100644 xpod_V3.1.9/OPC.h create mode 100644 xpod_V3.1.9/ads_module.cpp create mode 100644 xpod_V3.1.9/ads_module.h create mode 100644 xpod_V3.1.9/blynk.h create mode 100644 xpod_V3.1.9/bme_module.cpp create mode 100644 xpod_V3.1.9/bme_module.h create mode 100644 xpod_V3.1.9/digipot.cpp create mode 100644 xpod_V3.1.9/digipot.h create mode 100644 xpod_V3.1.9/gps_module.cpp create mode 100644 xpod_V3.1.9/gps_module.h create mode 100644 xpod_V3.1.9/mq131_module.cpp create mode 100644 xpod_V3.1.9/mq131_module.h create mode 100644 xpod_V3.1.9/pms_module.cpp create mode 100644 xpod_V3.1.9/pms_module.h create mode 100644 xpod_V3.1.9/quad_module.cpp create mode 100644 xpod_V3.1.9/quad_module.h create mode 100644 xpod_V3.1.9/s300i2c.cpp create mode 100644 xpod_V3.1.9/s300i2c.h create mode 100644 xpod_V3.1.9/wind_vane.cpp create mode 100644 xpod_V3.1.9/wind_vane.h create mode 100644 xpod_V3.1.9/xpod_node.h create mode 100644 xpod_V3.1.9/xpod_node.ino create mode 100644 xpod_node/Adafruit_PM25AQI.cpp create mode 100644 xpod_node/Adafruit_PM25AQI.h create mode 100644 xpod_node/My_BlynkSimpleShieldEsp8266.h create mode 100644 xpod_node/OPC.cpp create mode 100644 xpod_node/OPC.h create mode 100644 xpod_node/blynk.h create mode 100644 xpod_node/digipot.cpp create mode 100644 xpod_node/digipot.h create mode 100644 xpod_node/gps_module.cpp create mode 100644 xpod_node/gps_module.h create mode 100644 xpod_node/mq131_module.cpp create mode 100644 xpod_node/mq131_module.h create mode 100644 xpod_node/pms_module.cpp create mode 100644 xpod_node/pms_module.h create mode 100644 xpod_node/quad_module.cpp create mode 100644 xpod_node/quad_module.h create mode 100644 xpod_node/s300i2c.cpp create mode 100644 xpod_node/s300i2c.h create mode 100644 xpod_node/wind_vane.cpp create mode 100644 xpod_node/wind_vane.h diff --git a/xpod_V3.1.9/Adafruit_PM25AQI.cpp b/xpod_V3.1.9/Adafruit_PM25AQI.cpp new file mode 100644 index 0000000..a6aee3b --- /dev/null +++ b/xpod_V3.1.9/Adafruit_PM25AQI.cpp @@ -0,0 +1,133 @@ +/*! + * @file Adafruit_PM25AQI.cpp + * + * @mainpage Adafruit PM2.5 air quality sensor driver + * + * @section intro_sec Introduction + * + * This is the documentation for Adafruit's PM2.5 AQI driver for the + * Arduino platform. It is designed specifically to work with the + * Adafruit PM2.5 Air quality sensors: http://www.adafruit.com/products/4632 + * + * These sensors use I2C or UART to communicate. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * + * @section author Author + * Written by Ladyada for Adafruit Industries. + * + * @section license License + * BSD license, all text here must be included in any redistribution. + * + */ + +#include "Adafruit_PM25AQI.h" + +/*! + * @brief Instantiates a new PM25AQI class + */ +Adafruit_PM25AQI::Adafruit_PM25AQI() {} + +/*! + * @brief Setups the hardware and detects a valid PMSA003I. Initializes I2C. + * @param theWire + * Optional pointer to I2C interface, otherwise use Wire + * @return True if PMSA003I found on I2C, False if something went wrong! + */ +bool Adafruit_PM25AQI::begin_I2C(TwoWire *theWire) { + if (!i2c_dev) { + i2c_dev = new Adafruit_I2CDevice(PMSA003I_I2CADDR_DEFAULT, theWire); + } + + if (!i2c_dev->begin()) { + return false; + } + + return true; +} + +/*! + * @brief Setups the hardware and detects a valid UART PM2.5 + * @param theSerial + * Pointer to Stream (HardwareSerial/SoftwareSerial) interface + * @return True + */ +bool Adafruit_PM25AQI::begin_UART(Stream *theSerial) { + serial_dev = theSerial; + + return true; +} + +/*! + * @brief Setups the hardware and detects a valid UART PM2.5 + * @param data + * Pointer to PM25_AQI_Data that will be filled by read()ing + * @return True on successful read, false if timed out or bad data + */ +bool Adafruit_PM25AQI::read(PM25_AQI_Data *data) { + uint8_t buffer[32]; + uint16_t sum = 0; + + if (!data) { + return false; + } + + if (i2c_dev) { // ok using i2c? + if (!i2c_dev->read(buffer, 32)) { + return false; + } + } else if (serial_dev) { // ok using uart + if (!serial_dev->available()) { + return false; + } + int skipped = 0; + while ((skipped < 32) && (serial_dev->peek() != 0x42)) { + serial_dev->read(); + skipped++; + if (!serial_dev->available()) { + return false; + } + } + if (serial_dev->peek() != 0x42) { + serial_dev->read(); + return false; + } + // Now read all 32 bytes + if (serial_dev->available() < 32) { + return false; + } + serial_dev->readBytes(buffer, 32); + } else { + return false; + } + + // Check that start byte is correct! + if (buffer[0] != 0x42) { + return false; + } + + // get checksum ready + for (uint8_t i = 0; i < 30; i++) { + sum += buffer[i]; + } + + // The data comes in endian'd, this solves it so it works on all platforms + uint16_t buffer_u16[15]; + for (uint8_t i = 0; i < 15; i++) { + buffer_u16[i] = buffer[2 + i * 2 + 1]; + buffer_u16[i] += (buffer[2 + i * 2] << 8); + } + + // put it into a nice struct :) + memcpy((void *)data, (void *)buffer_u16, 30); + + if (sum != data->checksum) { + return false; + } + + // success! + return true; +} diff --git a/xpod_V3.1.9/Adafruit_PM25AQI.h b/xpod_V3.1.9/Adafruit_PM25AQI.h new file mode 100644 index 0000000..cd304b3 --- /dev/null +++ b/xpod_V3.1.9/Adafruit_PM25AQI.h @@ -0,0 +1,65 @@ +/*! + * @file Adafruit_PM25AQI.h + * + * This is the documentation for Adafruit's PM25 AQI driver for the + * Arduino platform. It is designed specifically to work with the + * Adafruit PM25 air quality sensors: http://www.adafruit.com/products/4632 + * + * These sensors use I2C or UART to communicate. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Ladyada for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#ifndef ADAFRUIT_PM25AQI_H +#define ADAFRUIT_PM25AQI_H + +#include "Arduino.h" +#include + +// the i2c address +#define PMSA003I_I2CADDR_DEFAULT 0x12 ///< PMSA003I has only one I2C address + +/**! Structure holding Plantower's standard packet **/ +typedef struct PMSAQIdata { + uint16_t framelen; ///< How long this data chunk is + uint16_t pm10_standard, ///< Standard PM1.0 + pm25_standard, ///< Standard PM2.5 + pm100_standard; ///< Standard PM10.0 + uint16_t pm10_env, ///< Environmental PM1.0 + pm25_env, ///< Environmental PM2.5 + pm100_env; ///< Environmental PM10.0 + uint16_t particles_03um, ///< 0.3um Particle Count + particles_05um, ///< 0.5um Particle Count + particles_10um, ///< 1.0um Particle Count + particles_25um, ///< 2.5um Particle Count + particles_50um, ///< 5.0um Particle Count + particles_100um; ///< 10.0um Particle Count + uint16_t unused; ///< Unused + uint16_t checksum; ///< Packet checksum +} PM25_AQI_Data; + +/*! + * @brief Class that stores state and functions for interacting with + * PM2.5 Air Quality Sensor + */ +class Adafruit_PM25AQI { +public: + Adafruit_PM25AQI(); + bool begin_I2C(TwoWire *theWire = &Wire); + bool begin_UART(Stream *theStream); + bool read(PM25_AQI_Data *data); + +private: + Adafruit_I2CDevice *i2c_dev = NULL; + Stream *serial_dev = NULL; + uint8_t _readbuffer[32]; +}; + +#endif diff --git a/xpod_V3.1.9/My_BlynkSimpleShieldEsp8266.h b/xpod_V3.1.9/My_BlynkSimpleShieldEsp8266.h new file mode 100644 index 0000000..1ebe773 --- /dev/null +++ b/xpod_V3.1.9/My_BlynkSimpleShieldEsp8266.h @@ -0,0 +1,231 @@ +/** + * @file My_BlynkSimpleShieldEsp8266.h + * @author Volodymyr Shymanskyy + * @license This project is released under the MIT License (MIT) + * @copyright Copyright (c) 2015 Volodymyr Shymanskyy + * @date Jun 2015 + * @brief + * + */ + +#ifndef My_BlynkSimpleShieldEsp8266_h +#define My_BlynkSimpleShieldEsp8266_h + +#ifdef ESP8266 +#error This code is not intended to run on the ESP8266 platform! Please check your Tools->Board setting. +#endif + +#ifndef BLYNK_INFO_CONNECTION +#define BLYNK_INFO_CONNECTION "ESP8266" +#endif + +#ifndef BLYNK_ESP8266_MUX +#define BLYNK_ESP8266_MUX 1 +#endif + +#define BLYNK_SEND_ATOMIC +#define BLYNK_SEND_CHUNK 40 + +#include +#include +#include +#include + +class BlynkTransportShieldEsp8266 +{ + static void onData(uint8_t mux_id, uint32_t len, void* ptr) { + ((BlynkTransportShieldEsp8266*)ptr)->onData(mux_id, len); + } + + void onData(uint8_t mux_id, int32_t len) { + if (mux_id != BLYNK_ESP8266_MUX) { + return; + } + //BLYNK_LOG4("Got: ", len, ", Free: ", buffer.free()); + if (buffer.free() < len) { + BLYNK_LOG1(BLYNK_F("Buffer overflow")); + return; + } + while (len) { + if (client->getUart()->available()) { + uint8_t b = client->getUart()->read(); + buffer.put(b); + len--; + } + } + } + +public: + BlynkTransportShieldEsp8266() + : client(NULL) + , status(false) + , domain(NULL) + , port(0) + {} + + void setEsp8266(ESP8266* esp8266) { + client = esp8266; + client->setOnData(onData, this); + } + + //TODO: IPAddress + + void begin(const char* d, uint16_t p) { + domain = d; + port = p; + } + + bool connect() { + if (!domain || !port) + return false; + status = client->createTCP(BLYNK_ESP8266_MUX, domain, port); + return status; + } + + void disconnect() { + status = false; + buffer.clear(); + client->releaseTCP(BLYNK_ESP8266_MUX); + } + + size_t read(void* buf, size_t len) { + millis_time_t start = BlynkMillis(); + //BLYNK_LOG4("Waiting: ", len, " Buffer: ", buffer.size()); + while ((buffer.size() < len) && (BlynkMillis() - start < 1500)) { + client->run(); + } + return buffer.get((uint8_t*)buf, len); + } + size_t write(const void* buf, size_t len) { + if (client->send(BLYNK_ESP8266_MUX, (const uint8_t*)buf, len)) { + return len; + } + return 0; + } + + bool connected() { return status; } + + int available() { + client->run(); + //BLYNK_LOG2("Still: ", buffer.size()); + return buffer.size(); + } + +private: + ESP8266* client; + bool status; + BlynkFifo buffer; + const char* domain; + uint16_t port; +}; + +class BlynkWifi + : public BlynkProtocol +{ + typedef BlynkProtocol Base; +public: + BlynkWifi(BlynkTransportShieldEsp8266& transp) + : Base(transp) + , wifi(NULL) + {} + + bool connectWiFi(const char* ssid, const char* pass) + { + BlynkDelay(500); + BLYNK_LOG2(BLYNK_F("Connecting to "), ssid); + /*if (!wifi->restart()) { + BLYNK_LOG1(BLYNK_F("Failed to restart")); + return false; + }*/ + if (!wifi->kick()) { + BLYNK_LOG1(BLYNK_F("ESP is not responding")); + //TODO: BLYNK_LOG_TROUBLE(BLYNK_F("esp8266-not-responding")); + return false; + } + if (!wifi->setEcho(0)) { + BLYNK_LOG1(BLYNK_F("Failed to disable Echo")); + return false; + } + String ver = wifi->ESP8266::getVersion(); + BLYNK_LOG1(ver); + if (!wifi->enableMUX()) { + BLYNK_LOG1(BLYNK_F("Failed to enable MUX")); + } + if (!wifi->setOprToStation()) { + BLYNK_LOG1(BLYNK_F("Failed to set STA mode")); + return false; + } + if (wifi->joinAP(ssid, pass)) { + String my_ip = wifi->getLocalIP(); + BLYNK_LOG1(my_ip); + } else { + BLYNK_LOG1(BLYNK_F("Failed to connect WiFi")); + return false; + } + BLYNK_LOG1(BLYNK_F("Connected to WiFi")); + return true; + } + + void config(ESP8266& esp8266, + const char* auth, + const char* domain = BLYNK_DEFAULT_DOMAIN, + uint16_t port = BLYNK_DEFAULT_PORT) + { + Base::begin(auth); + wifi = &esp8266; + this->conn.setEsp8266(wifi); + this->conn.begin(domain, port); + } + + // void begin(const char* auth, + // ESP8266& esp8266, + // const char* ssid, + // const char* pass, + // const char* domain = BLYNK_DEFAULT_DOMAIN, + // uint16_t port = BLYNK_DEFAULT_PORT) + // { + // config(esp8266, auth, domain, port); + // connectWiFi(ssid, pass); + // while(this->connect() != true) {} + // } + void begin(const char* auth, + ESP8266& esp8266, + const char* ssid, + const char* pass, + const char* domain = BLYNK_DEFAULT_DOMAIN, + uint16_t port = BLYNK_DEFAULT_PORT + ) + { + static unsigned long timeout = 3000; // Timeout in milliseconds (default: 5 seconds) + static unsigned int maxAttempts = 10; // Maximum number of connection attempts (default: 10) + config(esp8266, auth, domain, port); // Configure Blynk with provided parameters + connectWiFi(ssid, pass); // Connect to Wi-Fi network + + unsigned int attempts = 0; + unsigned long startTime = millis(); + + // Keep trying to connect to Blynk server until successful or timeout + while (!this->connect()) { + if (attempts++ >= maxAttempts || millis() - startTime >= timeout) { + // Maximum attempts reached or timeout occurred + // You can add error handling or retry logic here + break; + } + delay(1000); // Wait for 1 second before retrying + } + } + +private: + ESP8266* wifi; +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_BLYNK) + static BlynkTransportShieldEsp8266 _blynkTransport; + BlynkWifi Blynk(_blynkTransport); +#else + extern BlynkWifi Blynk; +#endif + +#include + +#endif diff --git a/xpod_V3.1.9/OPC.cpp b/xpod_V3.1.9/OPC.cpp new file mode 100644 index 0000000..dd7393a --- /dev/null +++ b/xpod_V3.1.9/OPC.cpp @@ -0,0 +1,185 @@ +/* +OPC.cpp + +Written by Joseph Habeck (habec021@umn.edu) on 6/24/18. (https://github.com/JHabeck/Alphasense-OPC-N2/tree/master) +Edited using Marcel Oliveira's code from github (https://github.com/shyney7/OPC-R2_ESP32/tree/main) +Put together by Aidan Mobley + + */ +#include +#include "OPC.h" + +// Combine two bytes into a 16-bit unsigned int +uint16_t OPC::twoBytes2int(byte LSB, byte MSB){ + uint16_t int_val = ((MSB << 8) | LSB); + return int_val; +} + +// Return an IEEE754 float from an array of 4 bytes +float OPC::fourBytes2float(byte val0, byte val1, byte val2, byte val3) { + uint8_t bytes[4] = {uint8_t(val0), uint8_t(val1), uint8_t(val2), uint8_t(val3)}; + float result; + memcpy(&result, bytes, 4); + return result; +} + +// Gets OPC ready to do something +bool OPC::getReady(const byte command){ + byte inData; + SPI.beginTransaction(SPISettings(300000, MSBFIRST, SPI_MODE1)); + int tries = 0; + int total_tries = 0; + while(inData != OPC_ready & total_tries++ < 20){ + for(int i = 0; i < 10; i++){ + inData = SPI.transfer(0x01); // Try reading some bytes here to clear out anything remnant of other SPI activity + delayMicroseconds(10); + } + delay(10); + digitalWrite(CSpin, LOW); + while(inData != OPC_ready & tries++ < 20) + { + inData = SPI.transfer(command); + delay(5); + } + if(inData != OPC_ready){ + if(inData == OPC_busy){ // waiting 2 seconds because opc is busy + digitalWrite(CSpin, HIGH); + delay(2000); + } + else{ // resetting spi because different byte is returned + digitalWrite(CSpin, HIGH); + SPI.endTransaction(); + delay(6000); + SPI.beginTransaction(SPISettings(300000, MSBFIRST, SPI_MODE1)); + } + } + } + delay(10); + if(inData == OPC_ready) + return true; + else + return false; +} + +OPC::OPC(){ + CSpin = 49; +} + +bool OPC::begin(){ + SPI.begin(); + pinMode(CSpin, OUTPUT); + digitalWrite(CSpin, HIGH); + delay(1000); + return on(); +} + +bool OPC::on(){ + bool on = getReady(0x03); + SPI.transfer(0x03); + digitalWrite(CSpin, HIGH); + SPI.endTransaction(); + delay(2000); + return on; +} + +bool OPC::off(){ + bool off = getReady(0x03); + SPI.transfer(0x00); + digitalWrite(CSpin, HIGH); + SPI.endTransaction(); + return off; +} + +particleData OPC::getData(){ + particleData data; + double conv; + byte vals[64]; + byte command[] = {0x30, 0x01}; // command bytes to request histogram + + // SPI transaction + getReady(command[0]); + delay(100); + + // read all bits available + for (int i=0; i<64; ++i){ + vals[i] = SPI.transfer(command[1]); + delayMicroseconds(10); + } + + digitalWrite(CSpin, HIGH); + + // sample period [s] + float sp = fourBytes2float(vals[44], vals[45], vals[46], vals[47]); + // sample flow rate [ml/s] + float sfr = fourBytes2float(vals[36], vals[37], vals[38], vals[39]); + + // conversion to concentration [particles/ml] + if (CONVERT){ + conv = sp * sfr; + } + else{ // keep as particle-count/sec + conv = 1; + } + for(int i = 0; i < 16; i++){ + data.bin[i] = twoBytes2int(vals[i*2], vals[(i*2)+1]) / conv; + } + + #if PM_COUNT + // The below code just gives the raw counts for pm1.0, pm2.5, and pm10.0 + float PM10 = data.bin[0] + data.bin[1] + data.bin[2]; + float PM25 = 0; + for(int i = 0; i <= 5; i++){ + PM25 += data.bin[i]; + } + PM25 += data.bin[6] / 2; + float PM100 = 0; + for(int i = 0; i <= 11; i++){ + PM100 += data.bin[i]; + } + #else + float PM10 = fourBytes2float(vals[50], vals[51], vals[52], vals[53]); + float PM25 = fourBytes2float(vals[54], vals[55], vals[56], vals[57]); + float PM100 = fourBytes2float(vals[58], vals[59], vals[60], vals[61]); + #endif + + data.sp = sp; // This seems to always read 5.20 + data.sfr = sfr; + data.PM10 = PM10; + data.PM25 = PM25; + data.PM100 = PM100; + + return data; +} + +String OPC::read4sd(particleData data){ + String out_str = ""; + #if PRINT_BINS + for(int i = 0; i < 16; i++){ + out_str += String(data.bin[i]) + ","; + } + #else + //out_str = ",,,,,,,,,,,,,,,,"; + #endif + out_str += String(data.sp) + ","; + out_str += String(data.sfr) + ","; + out_str += String(data.PM10) + ","; + out_str += String(data.PM25) + ","; + out_str += String(data.PM100) + ","; + + return out_str; +} +String OPC::read4print(particleData data){ + String out_str = ""; + #if PRINT_BINS + for(int i = 0; i < 16; i++){ + out_str += "Bin " + String(i) + ": " + String(data.bin[i]) + ","; + } + #endif + out_str += "Sample Period: " + String(data.sp) + ","; + out_str += "Sample Flow Rate: " + String(data.sfr) + ","; + out_str += "PM1.0: " + String(data.PM10) + ","; + out_str += "PM2.5: " + String(data.PM25) + ","; + out_str += "PM10.0: " + String(data.PM100) + ","; + + return out_str; +} diff --git a/xpod_V3.1.9/OPC.h b/xpod_V3.1.9/OPC.h new file mode 100644 index 0000000..bd85836 --- /dev/null +++ b/xpod_V3.1.9/OPC.h @@ -0,0 +1,54 @@ +/* +OPC.h - library for operating optical particle counter OPC-R2 from Alphasense using an Arduino Mega. + + Written by Joseph Habeck (habec021@umn.edu) on 6/24/18. (https://github.com/JHabeck/Alphasense-OPC-N2/tree/master) + Edited using Marcel Oliveira's code from github (https://github.com/shyney7/OPC-R2_ESP32/tree/main) + Put together by Aidan Mobley +*/ +// White = MISO, purple = CLK, blue = MOSI, green = D49 +// Do not plug green(CS) into the CS pin on rev4 + +#ifndef OPC_h +#define OPC_h + +// include Arduino SPI library +#include + +#define PRINT_BINS 0 // Prints the amount of particles in each of the 16 bins +#define PM_COUNT 0 // Returns the PM measurements in particle count rather than ug/m3 +#define CONVERT 0 // Returns the bin measurements in particles/ml rather than particle count + + +const byte OPC_ready = 0xF3; +const byte OPC_busy = 0x31; + +// particle data structure +struct particleData{ + int bin[16]; // bin 0-2: pm 1.0, bin 0-5/6: pm 2.5 bin 0-11: pm 10.0 bin 5: 1.6-2.1, bin 6: 2.1-3.0 + float sp; + float sfr; + float PM10; + float PM25; + float PM100; +}; + +// define class +class OPC{ + public: + OPC(); + bool begin(); + bool on(); + bool off(); + particleData getData(); + String read4sd(particleData data); + String read4print(particleData data); + + private: + uint16_t twoBytes2int(byte LSB, byte MSB); + float fourBytes2float(byte val0, byte val1, byte val2, byte val3); + bool getReady(const byte command); + int CSpin; +}; + + +#endif /* OPC_h */ diff --git a/xpod_V3.1.9/ads_module.cpp b/xpod_V3.1.9/ads_module.cpp new file mode 100644 index 0000000..1488d83 --- /dev/null +++ b/xpod_V3.1.9/ads_module.cpp @@ -0,0 +1,273 @@ +/******************************************************************************* + * @file ads_module.cpp + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 18 2023 + ******************************************************************************/ +#include "ads_module.h" + +ADS_Module::ADS_Module() +{ + ads_module[ADS_SENSOR_FIG2600].addr = 0x48; + ads_module[ADS_SENSOR_FIG2600].channel = 3; + + ads_module[ADS_SENSOR_FIG2602].addr = 0x49; + ads_module[ADS_SENSOR_FIG2602].channel = 2; + +#if FIGARO3_ENABLED + ads_module[ADS_SENSOR_FIG3].addr = 0x48; + ads_module[ADS_SENSOR_FIG3].channel = 0; + + ads_module[ADS_HEATER_FIG3].addr = 0x48; + ads_module[ADS_HEATER_FIG3].channel = 1; +#endif + +#if FIGARO4_ENABLED + ads_module[ADS_SENSOR_FIG4].addr = 0x49; + ads_module[ADS_SENSOR_FIG4].channel = 0; + + ads_module[ADS_HEATER_FIG4].addr = 0x49; + ads_module[ADS_HEATER_FIG4].channel = 1; +#endif + + ads_module[ADS_SENSOR_PID].addr = 0x48; + ads_module[ADS_SENSOR_PID].channel = 2; + + ads_module[ADS_SENSOR_E2V].addr = 0x4B; + ads_module[ADS_SENSOR_E2V].channel = 0; + + ads_module[ADS_SENSOR_CO].addr = 0x4A; + ads_module[ADS_SENSOR_CO].channel = -1; + + for (int i = 0; i < ADS_SENSOR_COUNT; i++) + ads_module[i].status = false; +} + +bool ADS_Module::begin() +{ + for (int i = 0; i < ADS_SENSOR_COUNT; i++) + { + if (ads_module[i].module.begin(ads_module[i].addr)) + ads_module[i].status = true; + } + + for (int i = 0; i < ADS_SENSOR_COUNT; i++) + { + if (ads_module[i].status == false) + return false; + } + + return true; +} + +float ADS_Module::read_figaro(ads_sensor_id_e ads_sensor_id) +{ + ads_module_t *sensor = &ads_module[ads_sensor_id]; + + const int samples = 20; + float volts = 0.0; + float contaminants = 0.0; + float v_sum = 0.0; + float c_sum = 0.0; + int16_t adc = 0; + + if (!sensor->status) + return -999; + + for (int i = 0; i < samples; i++) + { + adc = sensor->module.readADC_SingleEnded(sensor->channel); + volts = sensor->module.computeVolts(adc); + + // rs/ro, change 0.1 to voltage in clean air, (5/voltage_dirty) / (5/Voltage_clean) + contaminants = ((5.000 / volts) - 1) / ((5.000 / 0.1) - 1); + + c_sum += contaminants; + v_sum += volts; + } + + contaminants = c_sum / samples; + volts = v_sum / samples; + + if (contaminants > 1.000) + contaminants = 1.000; + + // return adc value, rs/ro, heater resistance. + // Use other code to calc rs/ro, calc heater resistance, + return volts; +} + +float ADS_Module::read_heater(ads_sensor_id_e ads_sensor_id) +{ + ads_module_t *sensor = &ads_module[ads_sensor_id]; + + const int samples = 20; + float volts = 0.0; + float raw = 0.0; + float r_sum = 0.0; + int adc = 1; + + for(int i = 0; i < samples; i++){ + raw = sensor->module.readADC_SingleEnded(adc); + r_sum += raw; + } + raw = r_sum / samples; + + volts = (raw - 0) * (0 - 5) / (0 - 27000); + + return volts; +} + +float ADS_Module::read_co() +{ + ads_module_t *sensor = &ads_module[ADS_SENSOR_CO]; + + float val; + const float multiplier = 0.1875F; // ADS1115 @ +/- 6.144V gain (16-bit results) + + if (!sensor->status) + return -999; + + return (sensor->module.readADC_Differential_0_1() - sensor->module.readADC_Differential_2_3()); +} + +float ADS_Module::read_co_aux() +{ + ads_module_t *sensor = &ads_module[ADS_SENSOR_CO]; + + float val; + const float multiplier = 0.1875F; // ADS1115 @ +/- 6.144V gain (16-bit results) + + if (!sensor->status) + return -999; + + return (sensor->module.readADC_Differential_0_1()); +} + +float ADS_Module::read_co_main() +{ + ads_module_t *sensor = &ads_module[ADS_SENSOR_CO]; + + float val; + const float multiplier = 0.1875F; // ADS1115 @ +/- 6.144V gain (16-bit results) + + if (!sensor->status) + return -999; + + return (sensor->module.readADC_Differential_2_3()); +} + +uint16_t ADS_Module::read_raw(ads_sensor_id_e ads_sensor_id) +{ + ads_module_t *sensor = &ads_module[ads_sensor_id]; + + if (!sensor->status) + return -999; + + return sensor->module.readADC_SingleEnded(sensor->channel); +} + + +String ADS_Module::read4sd() +{ + String out_str = ""; + + out_str += String(read_figaro(ADS_SENSOR_FIG2600)) + ","; + out_str += String(read_figaro(ADS_SENSOR_FIG2602)) + ","; +#if FIGARO3_ENABLED + out_str += String(read_figaro(ADS_SENSOR_FIG3)) + ","; +#endif +#if FIGARO4_ENABLED + out_str += String(read_figaro(ADS_SENSOR_FIG4)) + ","; +#endif + out_str += String(read_raw(ADS_SENSOR_PID)) + ","; + out_str += String(read_raw(ADS_SENSOR_E2V)) + ","; + out_str += String(read_co_aux()) + "," + String(read_co_main()); + + return out_str; +} + +String ADS_Module::read4print() +{ + String out_str = ""; + + out_str += "FIG2600:" + String(read_figaro(ADS_SENSOR_FIG2600)) + ","; + out_str += "FIG2602:" + String(read_figaro(ADS_SENSOR_FIG2602)) + ","; +#if FIGARO3_ENABLED + out_str += "FIG3:" + String(read_figaro(ADS_SENSOR_FIG3)) + ","; +#endif +#if FIGARO4_ENABLED + out_str += "FIG4:" + String(read_figaro(ADS_SENSOR_FIG4)) + ","; +#endif + out_str += "PID:" + String(read_raw(ADS_SENSOR_PID)) + ","; + out_str += "E2V:" + String(read_raw(ADS_SENSOR_E2V)) + ","; + out_str += "CO:" + String(read_co()); + + return out_str; +} + +String ADS_Module::read4sd_raw() +{ + String out_str = ""; + + // out_str += String(read_figaro(ADS_SENSOR_FIG2600)) +"\t"; + out_str += String(read_raw(ADS_SENSOR_FIG2600)) + ","; + + // out_str += String(read_figaro(ADS_SENSOR_FIG2602)) + "\t"; + out_str += String(read_raw(ADS_SENSOR_FIG2602)) + ","; + + #if FIGARO3_ENABLED + // out_str += String(read_figaro(ADS_SENSOR_FIG3)) + "\t"; + out_str += String(read_raw(ADS_SENSOR_FIG3)) + ","; + + out_str += String(read_raw(ADS_HEATER_FIG3)) + ","; + #endif + + #if FIGARO4_ENABLED + // out_str += String(read_figaro(ADS_SENSOR_FIG4)) + "\t"; + out_str += String(read_raw(ADS_SENSOR_FIG4)) + ","; + + out_str += String(read_raw(ADS_HEATER_FIG4)) + ","; + #endif + + out_str += String(read_raw(ADS_SENSOR_PID)) + ","; + out_str += String(read_raw(ADS_SENSOR_E2V)) + ","; + + out_str += String(read_co_aux()) + "," + String(read_co_main()); + return out_str; +} + +String ADS_Module::read4print_raw() +{ + String out_str = ""; + + out_str += "FIG2600:" + String(read_figaro(ADS_SENSOR_FIG2600)); + out_str += "(" + String(read_raw(ADS_SENSOR_FIG2600)) + "),"; + + out_str += "FIG2602:" + String(read_figaro(ADS_SENSOR_FIG2602)); + out_str += "(" + String(read_raw(ADS_SENSOR_FIG2602)) + "),"; + + #if FIGARO3_ENABLED + out_str += "FIG3:" + String(read_figaro(ADS_SENSOR_FIG3)) + ","; + out_str += "(" + String(read_raw(ADS_SENSOR_FIG3)) + "),"; + + out_str += "FIG3_volts:" + String(read_heater(ADS_HEATER_FIG3)) + ","; + out_str += "(" + String(read_raw(ADS_HEATER_FIG3)) + "),"; + #endif + + #if FIGARO4_ENABLED + out_str += "FIG4:" + String(read_figaro(ADS_SENSOR_FIG4)) + ","; + out_str += "(" + String(read_raw(ADS_SENSOR_FIG4)) + "),"; + + out_str += "FIG4_volts:" + String(read_heater(ADS_HEATER_FIG4)) + ","; + out_str += "(" + String(read_raw(ADS_HEATER_FIG4)) + "),"; + #endif + + out_str += "PID:" + String(read_raw(ADS_SENSOR_PID)) + ","; + out_str += "E2V:" + String(read_raw(ADS_SENSOR_E2V)) + ","; + out_str += "CO:" + String(read_co_aux()) + "," + String(read_co_main());; + + + return out_str; +} diff --git a/xpod_V3.1.9/ads_module.h b/xpod_V3.1.9/ads_module.h new file mode 100644 index 0000000..47e30b8 --- /dev/null +++ b/xpod_V3.1.9/ads_module.h @@ -0,0 +1,61 @@ +/******************************************************************************* + * @file ads_module.h + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 18 2023 + ******************************************************************************/ +#ifndef _ADS_MODULE_H +#define _ADS_MODULE_H + +#include +#include + +#define FIGARO3_ENABLED 1 +#define FIGARO4_ENABLED 1 + +enum ads_sensor_id_e +{ + ADS_SENSOR_FIG2600 = 0, + ADS_SENSOR_FIG2602, + ADS_SENSOR_FIG3, + ADS_SENSOR_FIG4, + ADS_HEATER_FIG3, + ADS_HEATER_FIG4, + ADS_SENSOR_PID, + ADS_SENSOR_E2V, + ADS_SENSOR_CO, + ADS_SENSOR_COUNT +}; + +struct ads_module_t +{ + uint8_t addr; + int8_t channel; + bool status; + Adafruit_ADS1115 module; +}; + +class ADS_Module { + public: + ADS_Module(); + bool begin(); + + float read_figaro(ads_sensor_id_e ads_sensor_id); + float read_heater(ads_sensor_id_e ads_sensor_id); + float read_co(); + float read_co_aux(); + float read_co_main(); + uint16_t read_raw(ads_sensor_id_e ads_sensor_id); + + String read4sd(); + String read4print(); + String read4sd_raw(); + String read4print_raw(); + String read4blynk(); + + private: + ads_module_t ads_module[ADS_SENSOR_COUNT]; +}; + +#endif //_ADS_MODULE_H diff --git a/xpod_V3.1.9/blynk.h b/xpod_V3.1.9/blynk.h new file mode 100644 index 0000000..8747539 --- /dev/null +++ b/xpod_V3.1.9/blynk.h @@ -0,0 +1,186 @@ +#define BLYNK_TEMPLATE_ID "TMPL2-_U7f2uX" +#define BLYNK_TEMPLATE_NAME "Simman3" +#define BLYNK_AUTH_TOKEN "sKPYgn3jcuimveDEcJSmUGmeBFd-LgG5" + +#include +#include "SoftwareSerial.h" +#include +#include "My_BlynkSimpleShieldEsp8266.h" +#include "bme_module.h" +#include "ads_module.h" +#include "gps_module.h" +#include "mq131_module.h" +#include "pms_module.h" +#include "s300i2c.h" + +// char ssid[] = "Pls_no_hack"; +// char pass[] = "BMTC_8922"; + +char ssid[] = "Samsung"; +char pass[] = "WoofWoof"; +extern gps; +#define BLYNK_PRINT Serial + +#define EspSerial Serial3 + +// Your ESP8266 baud rate: +#define ESP8266_BAUD 115200 + +#define DEBUG true + +ESP8266 wifi(&EspSerial); + +#define esp8266 Serial3 +#define CH_PD 4 +#define speed8266 115200 + +// Send AT commands to module with timeout handling +String sendDataWithTimeout(String command, const int timeout, boolean debug) { + String response = ""; + EspSerial.print(command); + long int startTime = millis(); + if (millis() - startTime < timeout) { // changed loop to condition + if (EspSerial.available()) { // changed loop to condition + char c = EspSerial.read(); + response += c; + } + if (response.endsWith("OK\r\n")) { + //break; // Command succeeded + } + } + if (debug) { + Serial.print("Wifi debug Response :"); + Serial.print(response); + Serial.println(); + } + return response; +} + +void InitWifiModule() { + sendDataWithTimeout("AT+RST\r\n", 2000, DEBUG); // reset + sendDataWithTimeout("AT+CWJAP=\"OnePlus Nord\",\"Jagatguru\"\r\n", 5000, DEBUG); // Connect network (increased timeout) 5sec + delay(3000); + sendDataWithTimeout("AT+CWMODE=1\r\n", 1000, DEBUG); // Set the module's operating mode to station mode + sendDataWithTimeout("AT+CIFSR\r\n", 1000, DEBUG); // Show IP Address + sendDataWithTimeout("AT+CIPMUX=1\r\n", 1000, DEBUG); // Multiple connections + sendDataWithTimeout("AT+CIPSERVER=1,80\r\n", 1000, DEBUG); // Start comm port 80 + +} + + +String BME_Module::read4blynk() +{ + String bms_data_str; + + if (!status) + return ""; + + Blynk.virtualWrite(V0, String(bme_sensor.temperature)); + Blynk.virtualWrite(V1, String(bme_sensor.pressure / 100.0)); + Blynk.virtualWrite(V2, String(bme_sensor.humidity)); + Blynk.virtualWrite(V3, String(bme_sensor.gas_resistance / 1000.0)); + Blynk.virtualWrite(V4, String(bme_sensor.readAltitude(SEALEVELPRESSURE_HPA))); + + Blynk.virtualWrite(V0, String(bme_sensor.temperature)); + Blynk.virtualWrite(V1, String(bme_sensor.pressure / 100.0)); + Blynk.virtualWrite(V2, String(bme_sensor.humidity)); + Blynk.virtualWrite(V3, String(bme_sensor.gas_resistance / 1000.0)); + Blynk.virtualWrite(V4, String(bme_sensor.readAltitude(SEALEVELPRESSURE_HPA))); + + return bms_data_str; +} + +String ADS_Module::read4blynk() +{ + String out_str = ""; + + Blynk.virtualWrite(V5, String(read_figaro(ADS_SENSOR_FIG2600))); + Blynk.virtualWrite(V6, String(read_figaro(ADS_SENSOR_FIG2602))); +#if FIGARO3_ENABELD + Blynk.virtualWrite(V7, String(read_figaro(ADS_SENSOR_FIG3))); +#endif +#if FIGARO4_ENABELD + Blynk.virtualWrite(V8, String(read_figaro(ADS_SENSOR_FIG4))); +#endif + Blynk.virtualWrite(V9, String(read_raw(ADS_SENSOR_PID))); + Blynk.virtualWrite(V10, String(read_raw(ADS_SENSOR_E2V))); + Blynk.virtualWrite(V11, String(read_co())); + + return out_str; +} +String S300I2C::read4blynk() +{ + String Co2data = ""; + Blynk.virtualWrite(V12,String(getCO2ppm())); + return Co2data; +} + + +String GPSModule::read4blynk() +{ + String gpsdata = ""; + #if GPS_ENABLED + String out = readData(); + // Blynk.virtualWrite(V5, out); + gps val; + Blynk.virtualWrite(V13, String(val.latitude)); + Blynk.virtualWrite(V14, String(val.longitude)); + Blynk.virtualWrite(V15, String(val.altitude)); + Blynk.virtualWrite(V16, String(val.course)); + Blynk.virtualWrite(V17, String(val.speed)); + #endif + return gpsdata; +} + +String MQ131_Module::read4blynk() +{ + String mq_data; + + if (!status) + return ""; + + Blynk.virtualWrite(V18, String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL))); + +// #if READ_JUST_RAW +// // mq_data = String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL)); +// Blynk.virtualWrite(V17, String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL))); +// #else +// // mq_data = String(this->read()) + "," + String(raw_data); +// // Blynk.virtualWrite(V18, String(this->read()) + "," + String(raw_data)); +// #endif + + return mq_data; +} + +String PMS_Module::read4blynk() +{ + String pms_data_str; + PM25_AQI_Data data; + int read_tries = 3; + + if (!status) + return ""; + + // pms_data_str = String(data.pm10_env) + ","; + Blynk.virtualWrite(V19, String(data.pm10_env)); + // pms_data_str += String(data.pm25_env) + ","; + Blynk.virtualWrite(V20, String(data.pm25_env)); + // pms_data_str += String(data.pm100_env) + ","; + Blynk.virtualWrite(V21, String(data.pm100_env)); + + // pms_data_str += String(data.particles_03um) + ","; + Blynk.virtualWrite(V22, String(data.particles_03um)); + // pms_data_str += String(data.particles_05um) + ","; + Blynk.virtualWrite(V23, String(data.particles_05um)); + // pms_data_str += String(data.particles_10um) + ","; + Blynk.virtualWrite(V24, String(data.particles_10um)); + // pms_data_str += String(data.particles_25um) + ","; + Blynk.virtualWrite(V25, String(data.particles_25um)); + // pms_data_str += String(data.particles_50um) + ","; + Blynk.virtualWrite(V26, String(data.particles_50um)); + // pms_data_str += String(data.particles_100um); + Blynk.virtualWrite(V27, String(data.particles_100um)); + + return pms_data_str; +} + diff --git a/xpod_V3.1.9/bme_module.cpp b/xpod_V3.1.9/bme_module.cpp new file mode 100644 index 0000000..4985be2 --- /dev/null +++ b/xpod_V3.1.9/bme_module.cpp @@ -0,0 +1,62 @@ +/******************************************************************************* + * @file bme_module.cpp + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 18 2023 + ******************************************************************************/ +#include +#include "bme_module.h" + +BME_Module::BME_Module() +{ + status = false; +} + +bool BME_Module::begin() +{ + if (bme_sensor.begin(BME_SENSOR_ADDR)) + { + status = true; + + // Set up oversampling and filter initialization + bme_sensor.setTemperatureOversampling(BME680_OS_8X); + bme_sensor.setHumidityOversampling(BME680_OS_2X); + bme_sensor.setPressureOversampling(BME680_OS_4X); + bme_sensor.setIIRFilterSize(BME680_FILTER_SIZE_3); + bme_sensor.setGasHeater(320, 150); + } + + return status; +} + +String BME_Module::read4sd() +{ + String bms_data_str; + + + + bms_data_str = String(bme_sensor.temperature) + ","; + bms_data_str += String(bme_sensor.pressure / 100.0) + ","; + bms_data_str += String(bme_sensor.humidity) ; + // bms_data_str += String(bme_sensor.gas_resistance / 1000.0) + ","; + // bms_data_str += String(bme_sensor.readAltitude(SEALEVELPRESSURE_HPA)); + + return bms_data_str; +} + +String BME_Module::read4print() +{ + String bms_data_str; + + // if (!status) + // return ""; + + bms_data_str = "Temp:" + String(bme_sensor.temperature) + " C,"; + bms_data_str += "Pressure:" + String(bme_sensor.pressure / 100.0) + " hPa,"; + bms_data_str += "Humidity:" + String(bme_sensor.humidity) + " %,"; + bms_data_str += "Gas:" + String(bme_sensor.gas_resistance / 1000.0) + " KOhms,"; + bms_data_str += "Altitude:" + String(bme_sensor.readAltitude(SEALEVELPRESSURE_HPA)) + " m"; + + return bms_data_str; +} \ No newline at end of file diff --git a/xpod_V3.1.9/bme_module.h b/xpod_V3.1.9/bme_module.h new file mode 100644 index 0000000..d37da06 --- /dev/null +++ b/xpod_V3.1.9/bme_module.h @@ -0,0 +1,30 @@ +/******************************************************************************* + * @file bme_module.h + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 18 2023 + ******************************************************************************/ +#ifndef _BME_MODULE_H +#define _BME_MODULE_H + +#include + +#define BME_SENSOR_ADDR (0x76) +#define SEALEVELPRESSURE_HPA (1013.25) + +class BME_Module +{ + public: + BME_Module(); + bool begin(); + String read4sd(); + String read4print(); + String read4blynk(); + + private: + Adafruit_BME680 bme_sensor; + bool status; +}; + +#endif //_BME_MODULE_H \ No newline at end of file diff --git a/xpod_V3.1.9/digipot.cpp b/xpod_V3.1.9/digipot.cpp new file mode 100644 index 0000000..61b3245 --- /dev/null +++ b/xpod_V3.1.9/digipot.cpp @@ -0,0 +1,97 @@ +/******************************************************************************* + * @file digipot.cpp + * @brief Configures the values of the digital potentiometers + * + * @author Rohan Jha + * @date July 20, 2023 + ******************************************************************************/ + +#include "digipot.h" + +pot potvar[NUM_POTS]; + +//Configuring digital potentiometers + void initpots() + { + potvar[0].clk = 4; + potvar[0].ud = 5; + potvar[0].cs = 6; + + potvar[1].clk = 27; + potvar[1].ud = 25; + potvar[1].cs = 23; + + potvar[2].clk = 29; + potvar[2].ud = 31; + potvar[2].cs = 33; + + pinMode(potvar[0].clk, OUTPUT); + pinMode(potvar[1].clk, OUTPUT); + pinMode(potvar[2].clk, OUTPUT); + + pinMode(potvar[0].ud, OUTPUT); + pinMode(potvar[1].ud, OUTPUT); + pinMode(potvar[2].ud, OUTPUT); + + pinMode(potvar[0].cs, OUTPUT); + pinMode(potvar[1].cs, OUTPUT); + pinMode(potvar[2].cs, OUTPUT); + + digitalWrite(potvar[0].cs, HIGH); + digitalWrite(potvar[1].cs, HIGH); + digitalWrite(potvar[2].cs, HIGH); + + digitalWrite(potvar[0].clk, HIGH); + digitalWrite(potvar[1].clk, HIGH); + digitalWrite(potvar[2].clk, HIGH); +} +/* +Function : Set digipot value +Args: Potentiometer number +Working : . Thet hree inputs are clock (CLK), CS and UP/DOWN (U/D).The negative-edge sensitive CLK input +requires clean transitions to avoid clocking multiple pulses into the internal UP/DOWNcounter register. +When CS is taken active low the clock begins to incre-ment or decrement the internal UP/DOWN counter dependent +upon the state of the U/D control pin. The UP/DOWN countervalue (D) starts at 40H at system power ON. +Each new CLKpulse will increment the value of the internal counter by one LSB until the full scale value of 3FH is + reached as long as theU/D pin is logic high. If the U/D pin is taken to logic low thecounter will count down stopping at code 00H (zero-scale). +*/ +void DownPot(int Pot_Num){ + digitalWrite(potvar[Pot_Num].cs, LOW); + digitalWrite(potvar[Pot_Num].ud, LOW); + // digitalWrite(num.clk, HIGH); + for(int i = 0; i < 128; i++){ + digitalWrite(potvar[Pot_Num].clk, LOW); + delay(1); + digitalWrite(potvar[Pot_Num].clk, HIGH); + delay(1); + } + digitalWrite(potvar[Pot_Num].cs, HIGH); +} + +void UpPot(int Pot_Num){ + digitalWrite(potvar[Pot_Num].cs, LOW); + digitalWrite(potvar[Pot_Num].ud, HIGH); + // digitalWrite(num.clk, HIGH); + for(int i = 0; i < 128; i++){ + digitalWrite(potvar[Pot_Num].clk, LOW); + delay(1); + digitalWrite(potvar[Pot_Num].clk, HIGH); + delay(1); + } + digitalWrite(potvar[Pot_Num].cs, HIGH); +} + +//Level could be anywhere between 0-128 based on the value of resistance required upto 10k +void SetPotLevel(int Pot_Num,int level){ + DownPot(Pot_Num); + digitalWrite(potvar[Pot_Num].cs, LOW); + digitalWrite(potvar[Pot_Num].ud, HIGH); + // digitalWrite(num.clk, HIGH); + for(int i = 0; i < level; i++){ + digitalWrite(potvar[Pot_Num].clk, LOW); + delay(1); + digitalWrite(potvar[Pot_Num].clk, HIGH); + delay(1); + } + digitalWrite(potvar[Pot_Num].cs, HIGH); +} diff --git a/xpod_V3.1.9/digipot.h b/xpod_V3.1.9/digipot.h new file mode 100644 index 0000000..53eef95 --- /dev/null +++ b/xpod_V3.1.9/digipot.h @@ -0,0 +1,23 @@ +/******************************************************************************* + * @file digipot.h + * @brief Contains funtions and structures for digital potentiometers + * + * @author Rohan Jha + * @date July 20, 2023 + ******************************************************************************/ +#include +#define NUM_POTS 3 + +struct pot{ + int clk; + int ud; + int cs; +}; + +void initpots(); + +void DownPot(int Pot_Num); + +void UpPot(int Pot_Num); + +void SetPotLevel(int Pot_Num,int level); \ No newline at end of file diff --git a/xpod_V3.1.9/gps_module.cpp b/xpod_V3.1.9/gps_module.cpp new file mode 100644 index 0000000..90a4a55 --- /dev/null +++ b/xpod_V3.1.9/gps_module.cpp @@ -0,0 +1,194 @@ +/******************************************************************************* + * @file gps_module.cpp + * @brief Defines the GPS functions + * + * @author Malola Simman Srinivasan Kannan , masr4788@colorado.edu + * @date September 8 2023 + ******************************************************************************/ +#include "gps_module.h" +#include +#include + +#define ARDUINO_GPS_RX 11 +#define ARDUINO_GPS_TX 10 + +#define GPS_BAUDRATE 9600 + +GPSModule gpsModule1; // Create an instance of the GPSModule class + +GPSModule::GPSModule() : gpsSerial(ARDUINO_GPS_RX, ARDUINO_GPS_TX) { + gps_status = false; +} + +bool GPSModule::begin() { + gpsSerial.begin(GPS_BAUDRATE); + + gps_status = true; + return gps_status; + +} + +bool GPSModule::isDataAvailable() { + return gpsSerial.available(); +} + +String GPSModule::readData() { + String data; + + while (gpsSerial.available()) { + char c = gpsSerial.read(); + static int i = 0; // Use static to retain i between loop iterations + + if (c == '$') { + i = 0; + } + + if (i < sizeof(strGPSData) - 1) { + strGPSData[i++] = c; + strGPSData[i] = '\0'; // Null-terminate the string + } + + if (c == '\n') { + data = parseNMEASentence(strGPSData); + } + } + + return data; +} + +String GPSModule::parseNMEASentence(const char* sentence) +{ + + // Check if the sentence starts with "$GPRMC" or "$GPGGA" + if (strncmp(sentence, "$GPRMC", 6) == 0) + { + // Ensure the sentence length is within a reasonable range + int sentenceLen = strlen(sentence); + char* values[12]; + int valueIndex = 0; + + if (sentenceLen >= 50 && sentenceLen <= 80) { + char* sentenceCopy = strdup(sentence); // Create a copy to avoid modifying the original string + char* token = strtok(sentenceCopy, ","); + while (token != NULL && valueIndex < 12) { + values[valueIndex++] = token; + token = strtok(NULL, ","); + } + free(sentenceCopy); // Free the copied string when done + + if (valueIndex >= 7) { + char* latitudeValue = values[3]; + char* latitudeDirection = values[4]; + char* longitudeValue = values[5]; + char* longitudeDirection = values[6]; + char* speedValue = values[7]; // Speed over ground in knots + + // Convert latitude and longitude to decimal degrees based on direction + latitude = atof(latitudeValue) /100; + longitude = atof(longitudeValue)/100; + + // Adjust latitude and longitude based on direction + if (latitudeDirection[0] == 'S') { + latitude = -latitude; + } + if (longitudeDirection[0] == 'W') { + longitude = -longitude; + } + + // Extract and validate course + if (valueIndex >= 8) { + char* courseValue = values[8]; + course = atof(courseValue); + + // Ensure course is within the valid range of 0 to 360 degrees + while (course < 0.0) { + course += 360.0; + } + while (course >= 360.0) { + course -= 360.0; + } + + // Extract and print speed + speed = atof(speedValue); + + } + + + // Extract and print date and time if available + if (valueIndex >= 10) { + String dateValue = values[8]; // Date in DDMMYY format + char* time_c = values[1]; + char time[7]; + + memcpy(time, time_c ,6); + time[6] = '\0'; + String timeValue ; + for(int i=0;i<7;i++) + { + timeValue.concat(time[i]); + } + + if (dateValue.length()== 6 && timeValue.length()>=7 ) { + + day = (dateValue.substring(0, 2)).toInt(); + month = (dateValue.substring(2, 4)).toInt(); + year = (dateValue.substring(4, 6)).toInt(); + hours=0; + if(timeValue.substring(0,2).toInt() == 0){ + hours = (timeValue.substring(1, 2)).toInt(); + } + else{ + hours = (timeValue.substring(0, 2)).toInt(); + } + + min = (timeValue.substring(2, 4)).toInt(); + sec = (timeValue.substring(4, 6)).toInt(); + + } + else { + Serial.println("GPS Invalid date or time format."); + } + } + else { + Serial.println("GPS Date and time information missing."); + } + } + } + } + else if (strncmp(sentence, "$GPGGA", 6) == 0) { + // Parse "$GPGGA" sentence for altitude + int sentenceLen = strlen(sentence); + char* values[15]; + int valueIndex = 0; + + if (sentenceLen >= 50 && sentenceLen <= 80) { + char* sentenceCopy = strdup(sentence); // Create a copy to avoid modifying the original string + char* token = strtok(sentenceCopy, ","); + while (token != NULL && valueIndex < 15) { + values[valueIndex++] = token; + token = strtok(NULL, ","); + } + free(sentenceCopy); // Free the copied string when done + + if (valueIndex >= 15) { + char* altitudeValue = values[9]; // Altitude above sea level (in meters) + altitude = atof(altitudeValue); + } + } + } + String gpsDataString = "Lat: " + String(latitude) ; + gpsDataString += ",Long: " + String(longitude); + gpsDataString += ",Course(degrees): " + String(course, 2) ; + gpsDataString += ",Speed(knots): " + String(speed, 2) ; + gpsDataString += ",Altitude(meters): " + String(altitude, 2) ; + gpsDataString += ",Date(DD/MM/YYYY): " + String(day) + "/" + String(month) + "/" + String(year) ; + gpsDataString += ",Time(HH:MM:SS): " + String(hours) + ":" + String(min) + ":" + String(sec) ; + gps gpsval={0}; + gpsval.altitude=altitude; + gpsval.longitude=longitude; + gpsval.course=course; + gpsval.latitude = latitude; + gpsval.speed = speed; + + return gpsDataString; +} diff --git a/xpod_V3.1.9/gps_module.h b/xpod_V3.1.9/gps_module.h new file mode 100644 index 0000000..a27c20f --- /dev/null +++ b/xpod_V3.1.9/gps_module.h @@ -0,0 +1,46 @@ +/******************************************************************************* + * @file gps_module.h + * @brief GPS Header file declares required class methods and variables + * + * @author Malola Simman Srinivasan Kannan , masr4788@colorado.edu + * @date September 8 2023 + ******************************************************************************/ +#ifndef GPSMODULE_H +#define GPSMODULE_H + +#include +#include +typedef struct gps_val{ + double latitude; + double longitude; + double course; + double speed; + double altitude; +}gps; +class GPSModule { +public: + GPSModule(); + bool begin(); // Remove the parameter from begin method + String readData(); + String parseNMEASentence(const char* sentence); + bool isDataAvailable(); + String read4blynk(); + double latitude; + double longitude; + double course; + double speed; + double altitude; + int day; + int month; + int year; + int hours; + int min; + int sec; + +private: + SoftwareSerial gpsSerial; + char strGPSData[1024]; + bool gps_status; +}; + +#endif \ No newline at end of file diff --git a/xpod_V3.1.9/mq131_module.cpp b/xpod_V3.1.9/mq131_module.cpp new file mode 100644 index 0000000..f3f2d78 --- /dev/null +++ b/xpod_V3.1.9/mq131_module.cpp @@ -0,0 +1,158 @@ +/******************************************************************************* + * @file mq131_module.cpp + * @brief + * + * @cite miguel5612, https://github.com/miguel5612/MQSensorsLib + * + * @editor Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 21 2023 + ******************************************************************************/ +#include +#include "mq131_module.h" + +MQ131_Module::MQ131_Module() +{ + heater_R0 = 0; + status = false; +} + +bool MQ131_Module::begin() +{ + if (!ads_module.begin(MQ131_I2C_ADDR)) + return false; + else + status = true; + +#if !READ_JUST_RAW + float calcR0 = 0; + for(int i = 1; i<=10; i++) + { + calcR0 += this->calibrate(); + } + + heater_R0 = calcR0 / 10; +#endif + + return status; +} + + +String MQ131_Module::read4sd() +{ + String mq_data; + + if (!status) + return ""; + +#if READ_JUST_RAW + mq_data = String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL)); +#else + mq_data = String(this->read()) + "," + String(raw_data); +#endif + + return mq_data; +} + +String MQ131_Module::read4print() +{ + String mq_data; + + if (!status) + return ","; + +#if READ_JUST_RAW + mq_data = " MQ131: " + String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL)); +#else + mq_data = " MQ131: " + String(this->read()) + "," + String(raw_data); +#endif + + return mq_data; +} + +float MQ131_Module::read() +{ + float rs_calc, ratio, PPM; + float sensor_volt = this->update(); + + //More explained in: https://jayconsystems.com/blog/understanding-a-gas-sensor + rs_calc = ((VOLT_RESOLUTION * O3_EXP_REG_RL) / sensor_volt) - O3_EXP_REG_RL; //Get value of RS in a gas + + //No negative values accepted. + if(rs_calc < 0) + rs_calc = 0; + + // Get ratio RS_air/RS_gas <- INVERTED for MQ-131 issue 28 https://github.com/miguel5612/MQSensorsLib/issues/28 + ratio = heater_R0 / rs_calc; + + //No negative values accepted or upper datasheet recomendation. + if(ratio <= 0) + ratio = 0; + + // <- Source excel analisis https://github.com/miguel5612/MQSensorsLib_Docs/tree/master/Internal_design_documents + if(REG_METHOD == 1) + { + PPM = O3_EXP_REG_A * pow(ratio, O3_EXP_REG_B); + } + else + { + // https://jayconsystems.com/blog/understanding-a-gas-sensor <- Source of linear ecuation + double ppm_log = (log10(ratio) - O3_EXP_REG_B) / O3_EXP_REG_A; //Get ppm value in linear scale according to the the ratio value + PPM = pow(10, ppm_log); //Convert ppm value to log scale + } + + //No negative values accepted or upper datasheet recomendation. + if(PPM < 0) + PPM = 0; + + //if(_PPM > 10000) _PPM = 99999999; //No negative values accepted or upper datasheet recomendation. + return PPM; +} + +float MQ131_Module::calibrate() +{ + //More explained in: https://jayconsystems.com/blog/understanding-a-gas-sensor + /* + V = I x R + VRL = [VC / (RS + RL)] x RL + VRL = (VC x RL) / (RS + RL) + Así que ahora resolvemos para RS: + VRL x (RS + RL) = VC x RL + (VRL x RS) + (VRL x RL) = VC x RL + (VRL x RS) = (VC x RL) - (VRL x RL) + RS = [(VC x RL) - (VRL x RL)] / VRL + RS = [(VC x RL) / VRL] - RL + */ + float R0; //Define variable for R0 + float sensor_volt = this->update(); + + float RS_air = ((VOLT_RESOLUTION * O3_EXP_REG_RL) / sensor_volt) - O3_EXP_REG_RL; //Calculate RS in fresh air + + if(RS_air < 0) + RS_air = 0; //No negative values accepted. + + R0 = RS_air / RATIO_CLEAN_AIR; //Calculate R0 + + if(R0 < 0) + R0 = 0; //No negative values accepted. + + return R0; +} + +float MQ131_Module::update() +{ + int retries = 2; + float avg = 0.0; + uint32_t adc = 0; + + for (int i = 0; i < retries; i++) + { + avg += ads_module.readADC_SingleEnded(MQ131_I2C_CHL); + delay(20); + } + + avg = avg / retries; + + raw_data = avg; + + return ((avg * VOLT_RESOLUTION) / ((pow(2, ADC_RESOLUTION) - 1))); +} diff --git a/xpod_V3.1.9/mq131_module.h b/xpod_V3.1.9/mq131_module.h new file mode 100644 index 0000000..6388959 --- /dev/null +++ b/xpod_V3.1.9/mq131_module.h @@ -0,0 +1,48 @@ +/******************************************************************************* + * @file mq131_module.ch + * @brief + * + * @cite miguel5612, https://github.com/miguel5612/MQSensorsLib + * + * @editor Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 21 2023 + ******************************************************************************/ +#ifndef _MQ131_Module_H +#define _MQ131_Module_H + +#include + +#define READ_JUST_RAW 1 + +#define ADC_RESOLUTION 16 +#define VOLT_RESOLUTION 5 +#define RATIO_CLEAN_AIR 15 +#define O3_EXP_REG_A 23.943 +#define O3_EXP_REG_B -1.11 +#define O3_EXP_REG_RL 10 +#define REG_METHOD 1 +#define MQ131_I2C_ADDR 0x4B +#define MQ131_I2C_CHL 1 + +class MQ131_Module +{ + public: + MQ131_Module(); + bool begin(); + + float read(); + String read4sd(); + String read4print(); + String read4blynk(); + + private: + float calibrate(); + float update(); + + Adafruit_ADS1115 ads_module; + float heater_R0; + uint16_t raw_data; + bool status; +}; + +#endif //_MQ131_Module_H \ No newline at end of file diff --git a/xpod_V3.1.9/pms_module.cpp b/xpod_V3.1.9/pms_module.cpp new file mode 100644 index 0000000..7bd896d --- /dev/null +++ b/xpod_V3.1.9/pms_module.cpp @@ -0,0 +1,78 @@ +/******************************************************************************* + * @file bme_module.cpp + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 18 2023 + ******************************************************************************/ +#include +#include "pms_module.h" +PMS_Module::PMS_Module() +{ + status = false; +} + +bool PMS_Module::begin() +{ + PMS_SERIAL.begin(9600); + + if (!pms_sensor.begin_UART(&PMS_SERIAL)) + status = false; + else + status = true; + + return status; +} + +String PMS_Module::read4sd() +{ + String pms_data_str; + PM25_AQI_Data data; + int read_tries = 20; + + + while(!pms_sensor.read(&data) && read_tries) { + read_tries--; + delay(10); + } + + + pms_data_str = String(data.pm10_env) + ","; + pms_data_str += String(data.pm25_env) + ","; + pms_data_str += String(data.pm100_env) + ","; + + pms_data_str += String(data.particles_03um) + ","; + pms_data_str += String(data.particles_05um) + ","; + pms_data_str += String(data.particles_10um) + ","; + pms_data_str += String(data.particles_25um) + ","; + pms_data_str += String(data.particles_50um) + ","; + pms_data_str += String(data.particles_100um); + delay(100); + return pms_data_str; +} + +String PMS_Module::read4print() +{ + String pms_data_str; + PM25_AQI_Data data; + int read_tries = 3; + + if (!status) + return ""; + while(!pms_sensor.read(&data) && read_tries) { + read_tries--; + } + + pms_data_str = "PM10_ENV:" + String(data.pm10_env) + ","; + pms_data_str += "PM10_ENV:" + String(data.pm25_env) + ","; + pms_data_str += "PM10_ENV:" + String(data.pm100_env) + ","; + + pms_data_str += "PM_03um:" + String(data.particles_03um) + ","; + pms_data_str += "PM_05um:" + String(data.particles_05um) + ","; + pms_data_str += "PM_10um:" + String(data.particles_10um) + ","; + pms_data_str += "PM_25um:" + String(data.particles_25um) + ","; + pms_data_str += "PM_30um:" + String(data.particles_50um) + ","; + pms_data_str += "PM_100um:" + String(data.particles_100um); + delay(100); + return pms_data_str; +} diff --git a/xpod_V3.1.9/pms_module.h b/xpod_V3.1.9/pms_module.h new file mode 100644 index 0000000..a6e5c4e --- /dev/null +++ b/xpod_V3.1.9/pms_module.h @@ -0,0 +1,30 @@ +/******************************************************************************* + * @file pms_module.h + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 18 2023 + ******************************************************************************/ +#ifndef _PMS_MODULE_H +#define _PMS_MODULE_H + +#include "Adafruit_PM25AQI.h" + +#define PMS_SERIAL (Serial1) +#define PMS_SERIAL_BR (9600) + +class PMS_Module +{ + public: + PMS_Module(); + bool begin(); + String read4sd(); + String read4print(); + String read4blynk(); + + private: + Adafruit_PM25AQI pms_sensor; + bool status; +}; + +#endif //_PMS_MODULE_H \ No newline at end of file diff --git a/xpod_V3.1.9/quad_module.cpp b/xpod_V3.1.9/quad_module.cpp new file mode 100644 index 0000000..ccffb6c --- /dev/null +++ b/xpod_V3.1.9/quad_module.cpp @@ -0,0 +1,88 @@ +/******************************************************************************* + * @file quad_module.cpp + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 20 2023 + ******************************************************************************/ +#include +#include +#include "quad_module.h" + +QUAD_Module::QUAD_Module() +{ + status = true; +} + +bool QUAD_Module::begin() +{ + alpha_one = MCP342x(APLHA_ONE_ADDR); + alpha_two = MCP342x(APLHA_TWO_ADDR); + + MCP342x::generalCallReset(); + delay(1); + + // Wire.requestFrom(APLHA_ONE_ADDR, (uint8_t)1); + + // if (!Wire.available()) + // status = false; + + // Wire.requestFrom(APLHA_TWO_ADDR, (uint8_t)1); + + // if (!Wire.available()) + // status = false; + + return status; +} + +String QUAD_Module::read() +{ + MCP342x::Config status; + String quad_data; + long value = 0; + + // Initiate a conversion; convertAndRead() will wait until it can be read + alpha_one.convertAndRead(MCP342x::channel1, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_one.convertAndRead(MCP342x::channel2, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_one.convertAndRead(MCP342x::channel3, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_one.convertAndRead(MCP342x::channel4, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + + alpha_two.convertAndRead(MCP342x::channel1, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_two.convertAndRead(MCP342x::channel2, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_two.convertAndRead(MCP342x::channel3, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_two.convertAndRead(MCP342x::channel4, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value); + + // //Read ADCs on-board and on-Quadstat + // for (int i = 1; i <= 16; i++) + // { + // if (i <= 4) + // quad_data += alpha_one.GetValue(i) + ","; + // else if (i <= 8) + // quad_data += alpha_two.GetValue(i - 4) + ","; + // } + + return quad_data; +} \ No newline at end of file diff --git a/xpod_V3.1.9/quad_module.h b/xpod_V3.1.9/quad_module.h new file mode 100644 index 0000000..5eeb6f8 --- /dev/null +++ b/xpod_V3.1.9/quad_module.h @@ -0,0 +1,32 @@ +/******************************************************************************* + * @file quad_module.h + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 20 2023 + ******************************************************************************/ +#ifndef _QUAD_Module_H +#define _QUAD_Module_H + +#include + +#define BME_SENSOR_ADDR (0x76) +#define SEALEVELPRESSURE_HPA (1013.25) + +#define APLHA_ONE_ADDR (0x69) +#define APLHA_TWO_ADDR (0x6E) + +class QUAD_Module +{ + public: + QUAD_Module(); + bool begin(); + + String read(); + private: + MCP342x alpha_one; + MCP342x alpha_two; + bool status; +}; + +#endif //_QUAD_Module_H \ No newline at end of file diff --git a/xpod_V3.1.9/s300i2c.cpp b/xpod_V3.1.9/s300i2c.cpp new file mode 100644 index 0000000..d96cc4a --- /dev/null +++ b/xpod_V3.1.9/s300i2c.cpp @@ -0,0 +1,70 @@ +#include "s300i2c.h" +#include "Arduino.h" + +S300I2C::S300I2C(TwoWire &w) { + wire = &w; + co2i = 0; +} + +boolean S300I2C::begin(uint8_t i2caddr = S300I2C_ADDR) { + _i2caddr = i2caddr; + return true; +} + +void S300I2C::writeCommand(uint8_t cmd) { + wire->beginTransmission(_i2caddr); + wire->write(cmd); + wire->endTransmission(); +} + +unsigned int S300I2C::getCO2ppm(void) { + writeCommand('R'); + wire->requestFrom((int)_i2caddr,(int)7); + for (int i=0; wire->available(); i++) { + _tmpBuf[i] = wire->read(); + delay(10); + } + if (_tmpBuf[0] != 0x08 || + _tmpBuf[3] == 0xff || + _tmpBuf[4] == 0xff || + _tmpBuf[5] == 0xff || + _tmpBuf[6] == 0xff) { + return 0; + } + return (_tmpBuf[1] << 8) | _tmpBuf[2]; +} + +void S300I2C::sleep(void) { + writeCommand('S'); + delay(4000); +} + +void S300I2C::wakeup(void) { + writeCommand('W'); + delay(6000); +} + +void S300I2C::clear_recalib(void) { + writeCommand('C'); + delay(6000); +} + +void S300I2C::start_mcdl(void) { + writeCommand('M'); + delay(2000); +} + +void S300I2C::end_mcdl(void) { + writeCommand('E'); + delay(2000); +} + +void S300I2C::start_acdl(void) { + writeCommand('A'); + delay(2000); +} + +void S300I2C::end_acdl(void) { + writeCommand('E'); + delay(2000); +} diff --git a/xpod_V3.1.9/s300i2c.h b/xpod_V3.1.9/s300i2c.h new file mode 100644 index 0000000..a547395 --- /dev/null +++ b/xpod_V3.1.9/s300i2c.h @@ -0,0 +1,43 @@ +/* + * ELT S300 I2C library + * s300.h + */ + +#ifndef ELT_S300_I2C_HOLLY +#define ELT_S300_I2C_HOLLY + +#include "Arduino.h" +#include "Wire.h" + +#define S300I2C_ADDR 0x31 + +class S300I2C { + public: + S300I2C(TwoWire &w); + boolean begin(uint8_t i2caddr); + void sleep(void); // Sleep command + void wakeup(void); // Wake up command + void clear_recalib(void); // Clear Recalibtation Factor Command + void start_mcdl(void); // Start Manual Calibration + void end_mcdl(void); // End Manual Calibration + void start_acdl(void); // Start Auto-Calibration + void end_acdl(void); // End Auto Calibration + unsigned int getCO2ppm(void); // get CO2 value + String read4blynk(); + + private: + TwoWire *wire; + unsigned int co2i; + uint8_t _i2caddr; + uint8_t _tmpBuf[7]; + + /** + * Internal function to perform and I2C write. + * + * @param cmd The 8-bit command ID to send. + */ + void writeCommand(uint8_t cmd); + + +}; +#endif diff --git a/xpod_V3.1.9/wind_vane.cpp b/xpod_V3.1.9/wind_vane.cpp new file mode 100644 index 0000000..924acf9 --- /dev/null +++ b/xpod_V3.1.9/wind_vane.cpp @@ -0,0 +1,75 @@ +/******************************************************************************* + * @file wind_vane.cpp + * @brief + * + * @cite Modest Maker (https://www.youtube.com/watch?v=KHrTqdmYoAk) + * + * @editor Percy Smith, percy.smith@colorado.edu + * @date August 23, 2023 + ******************************************************************************/ +#include "wind_vane.h" + +// Here we're defining the wind vane "object" +wind_vane::wind_vane() +{ + status = false; +} + +// This retrieves voltage reading and turns it into a voltage +float wind_vane::get_direction() +{ + int sensorValue = analogRead(WINDVANE_PIN); + float voltage = sensorValue * (5.0 / 1023.0); + + return (voltage); +} + +// This translates the directional voltage into a cardinal direction +String wind_vane::cardinal_direction(float directionVoltage) +{ + float windVane = directionVoltage; + String compass = ""; + if(windVane > 4.61) compass = "W"; //W + else if(windVane > 4.33) compass = "NW"; //NW + else if(windVane > 4.03) compass = "WNW"; //WNW + else if(windVane > 3.84) compass = "N"; //N + else if(windVane > 3.43) compass = "NNW"; //NNW + else if(windVane > 3.06) compass = "SW"; //SW + else if(windVane > 2.92) compass = "WSW"; //WSW + else if(windVane > 2.23) compass = "NE"; //NE + else if(windVane > 1.96) compass = "NNE"; //NNE + else if(windVane > 1.38) compass = "S"; //S + else if(windVane > 1.17) compass = "SSW"; //SSW + else if(windVane > 0.88) compass = "SE"; //SE + else if(windVane > 0.60) compass = "SSE"; //SSE + else if(windVane > 0.43) compass = "E"; //E + else if(windVane > 0.39) compass = "ENE"; //ENE + else compass = "ESE"; //ESE + + return(compass); +} + +// This will translate the directional voltage into the degrees of the direction +float wind_vane::degree_direction(float directionVoltage) +{ + float windVane = directionVoltage; + float degrees; + if(windVane > 4.61) degrees = 270; //W + else if(windVane > 4.33) degrees = 315; //NW + else if(windVane > 4.03) degrees = 282.5; //WNW + else if(windVane > 3.84) degrees = 0; //N + else if(windVane > 3.43) degrees = 337.5; //NNW + else if(windVane > 3.06) degrees = 225; //SW + else if(windVane > 2.92) degrees = 247.5; //WSW + else if(windVane > 2.23) degrees = 45; //NE + else if(windVane > 1.96) degrees = 22.5; //NNE + else if(windVane > 1.38) degrees = 180; //S + else if(windVane > 1.17) degrees = 202.5; //SSW + else if(windVane > 0.88) degrees = 135; //SE + else if(windVane > 0.60) degrees = 157.5; //SSE + else if(windVane > 0.43) degrees = 90; //E + else if(windVane > 0.39) degrees = 67.5; //ENE + else degrees = 112.5; //ESE + + return(degrees); +} diff --git a/xpod_V3.1.9/wind_vane.h b/xpod_V3.1.9/wind_vane.h new file mode 100644 index 0000000..a7287db --- /dev/null +++ b/xpod_V3.1.9/wind_vane.h @@ -0,0 +1,28 @@ +/******************************************************************************* + * @file wind_vane.h + * @brief + * + * @cite Modest Maker (https://www.youtube.com/watch?v=KHrTqdmYoAk) + * + * @editor Percy Smith, percy.smith@colorado.edu + * @date August 23, 2023 + ******************************************************************************/ +#ifndef wind_vane_h +#define wind_vane_h + +#include + +#define WINDVANE_PIN A15 + +class wind_vane +{ + public: + wind_vane(); + float get_direction(); + String cardinal_direction(float directionVoltage); + float degree_direction(float directionVoltage); + private: + bool status; +}; + +#endif /* wind_vane.h */ \ No newline at end of file diff --git a/xpod_V3.1.9/xpod_node.h b/xpod_V3.1.9/xpod_node.h new file mode 100644 index 0000000..1a505dc --- /dev/null +++ b/xpod_V3.1.9/xpod_node.h @@ -0,0 +1,40 @@ +/******************************************************************************* + * @project Hannigan Lab's Next Gen. Air Quality Pods + * + * @file xpod_node.h + * @brief Collects data from sensors over ADC-16bit module and logs the data + * on both serial monitor and the SD card. + * + * @author Ajay Kandagal + Malola Simman Srinivasan Kannan + * @date Following features are present: + * - Sensors Figaro 2600, Fiagaro 2602, CO, PID, E2V, CO, CO2 and BME + * - RTC time and GPS time stamping + * - SD Card logging + * - Motor control + ******************************************************************************/ +#ifndef _XPOD_NODE_H +#define _XPOD_NODE_H + +#define SAMPLE_TIME (15) +#define SDCARD_LOG_ENABLED (1) +#define SERIAL_LOG_ENABLED (1) +#define RTC_ENABLED (1) +#define GPS_ENABLED (0) +#define PMS_ENABLED (0) +#define QUAD_ENABLED (0) +#define MQ131_ENABLED (0) +#define MOTOR_ENABLED (0) +#define MET_STATION (0) +#define OPC_ENABLED (0) +#define MOTOR_CTRL_IN_PIN (A14) +#define MOTOR_CTRL_OUT_PIN (2) +#define CO2_I2C_ADDR (0x31) +#define IN_VOLT_PIN (A0) +#define SD_CARD_CS_PIN (53) +#define STATUS_RUNNING (12) +#define STATUS_ERROR (14) +#define STATUS_HALTED (13) +#define WIFI_ENABLED (1) // set to 0 if wifi not available, set to 1 if wifi is available + +#endif // _XPOD_NODE_H diff --git a/xpod_V3.1.9/xpod_node.ino b/xpod_V3.1.9/xpod_node.ino new file mode 100644 index 0000000..c0d158b --- /dev/null +++ b/xpod_V3.1.9/xpod_node.ino @@ -0,0 +1,636 @@ +/******************************************************************************* + * @project Hannigan Lab's Next Gen. Air Quality Pods + * + * @file xpod_node.cpp + * @brief Collects data from sensors over ADC-16bit module and logs the data + * on both serial monitor and the SD card. + * + * @author 1. Rohan Jha + * @author 2. Malola Simman + * @date Following features are present: + * - Sensors Figaro 2600, Fiagaro 2602, CO, PID, E2V, CO, CO2 and BME + * - RTC time and GPS time stamping + * - SD Card logging + * - Motor control + ******************************************************************************/ + +#include "xpod_node.h" +#include +#include +#include +#include +#include +#include "s300i2c.h" +#include "digipot.h" +#include "ads_module.h" +#include "bme_module.h" +#include + +/************* Global Declarations *************/ +unsigned int CO2=0; + +// Modules +S300I2C co2_sensor(Wire); +ADS_Module ads_module; +BME_Module bme_module; + +// xpod id +String xpodID = "XPODv4"; +String fileName; +SdFat sd; +SdFile file; + +#if WIFI_ENABLED + #include "blynk.h" +#endif + +#if GPS_ENABLED + #include "gps_module.h" + GPSModule gpsModule; +#endif + +#if RTC_ENABLED + #include + RTC_DS3231 rtc; + DateTime rtc_date_time; +#endif + + +#if QUAD_ENABLED +#include "quad_module.h" +QUAD_Module quad_module; +#endif + +#if MQ131_ENABLED +#include "mq131_module.h" +MQ131_Module mq131_module; +#endif + +#if PMS_ENABLED +#include "pms_module.h" +PMS_Module pms_module; +#endif + +#if OPC_ENABLED +#include "OPC.h" +OPC opc; +particleData opcData; +#endif + + +/****************** Functions ******************/ +#if MET_STATION +#include "wind_vane.h" +//Wind direction sensor(Potentiometer) on analog pin 15 +const byte WDIR = A15; +//Wind speed variables +long lastWindCheck = 0; +volatile long lastWindIRQ = 0; +volatile byte windClicks = 0; +void wspeedIRQ() { + if (millis() - lastWindIRQ > 10) { // Ignore switch-bounce glitches less than 10ms (142MPH max reading) after the reed switch closes + lastWindIRQ = millis(); //Grab the current time + windClicks++; //There is 1.492MPH for each click per second. + } +} +#endif + + + +void logDataToSdCard() +{ + + char fileNameArray[fileName.length()+1]; + fileName.toCharArray(fileNameArray, sizeof(fileNameArray)); + bme_module.read4print(); // This is necessary to make the bme read for sd logging for some reason + + float in_volt_val; + + #if OPC_ENABLED + opcData = opc.getData(); + #endif + + in_volt_val = (analogRead(IN_VOLT_PIN) * 5.02 *5 ) / 1023.0; + #if SERIAL_LOG_ENABLED + if(!Serial) + { + //check if Serial is available... if not, + Serial.end(); // close serial port + delay(100); //wait 100 millis + Serial.begin(9600); // reenable serial again + } + + #if RTC_ENABLED + { + rtc_date_time = rtc.now(); + + if((rtc_date_time.year() <= 2023) || (rtc_date_time.year() > 2030)) + { + Serial.print("Invalid Date reinitialize the date and time"); + Serial.print(","); + digitalWrite(STATUS_HALTED,HIGH); + delay(300); + digitalWrite(STATUS_HALTED,LOW); + delay(300); + } + else + { + Serial.print(rtc_date_time.timestamp()); + Serial.print(","); + } + } + #endif + + Serial.print("Volt:"); + Serial.print(in_volt_val); + Serial.print(","); + + Serial.print(ads_module.read4print_raw()); + + #if WIFI_ENABLED + ads_module.read4blynk(); + #endif + + Serial.print(","); + + Serial.print("CO2:"); + Serial.print(co2_sensor.getCO2ppm()); + + #if WIFI_ENABLED + co2_sensor.read4blynk(); + #endif + + Serial.print(","); + + Serial.print(bme_module.read4print()); + + #if WIFI_ENABLED + bme_module.read4blynk(); + #endif + + Serial.print(","); + + #if QUAD_ENABLED + Serial.print(quad_module.read()); + Serial.print(","); + #endif + + #if MQ131_ENABLED + Serial.print(mq131_module.read4print()); + Serial.print(","); + + #if WIFI_ENABLED + mq131_module.read4blynk(); + #endif + + #endif + + #if PMS_ENABLED + Serial.print(pms_module.read4print()); + + #if WIFI_ENABLED + pms_module.read4blynk(); + #endif + + Serial.print(","); + + #endif + + #if OPC_ENABLED + Serial.print(opc.read4print(opcData)); + Serial.print(","); + + #endif + + #if GPS_ENABLED + if(gpsModule.isDataAvailable()){ + Serial.print(gpsModule.readData()); + Serial.print(","); + #if WIFI_ENABLED + gpsModule.read4blynk(); + #endif + } + else{ + Serial.print("GPS connection fails"); + Serial.print(","); + } + + #endif + + #if MET_STATION + String data = "Wind Speed"+String(get_wind_speed()) + "," + String(analogRead(A15)) ; + Serial.print(data); + #endif + + #endif //SERIAL_LOG_ENABLED + + #if SDCARD_LOG_ENABLED + digitalWrite(SD_CARD_CS_PIN,LOW); + while(!sd.begin(SD_CARD_CS_PIN)) + { + #if SERIAL_LOG_ENABLED + Serial.println("error: SD card init failed in loop"); + #endif + sd.begin(SD_CARD_CS_PIN); + // just to indicates sd card fail to init + digitalWrite(STATUS_HALTED,HIGH); + delay(100); + digitalWrite(STATUS_HALTED,LOW); + delay(100); + } + + + if (sd.begin(SD_CARD_CS_PIN) && file.open(fileNameArray, O_CREAT | O_APPEND | O_WRITE)) + { + #if RTC_ENABLED + { + rtc_date_time = rtc.now(); + if((rtc_date_time.year() == 2000) || (rtc_date_time.year() == 2099)) + { + file.print("\r\n"); + file.print("Invalid Date reinitialize the date and time"); + file.print(","); + digitalWrite(STATUS_HALTED,HIGH); + delay(300); + digitalWrite(STATUS_HALTED,LOW); + delay(300); + } + else{ + file.print("\r\n"); + file.print(rtc_date_time.timestamp()); + file.print(","); + } + } + #endif + + file.print(in_volt_val); + file.print(","); + + file.print(ads_module.read4sd_raw()); + file.print(","); + + file.print(co2_sensor.getCO2ppm()); + file.print(","); + + file.print(bme_module.read4sd()); + file.print(","); + + + #if QUAD_ENABLED + file.print(quad_module.read()); + file.print(","); + #else + file.print(",,,,,,,,"); + #endif + + #if MQ131_ENABLED + file.print(mq131_module.read4sd()); + file.print(","); + #else + file.print(",,"); + #endif + + + #if PMS_ENABLED + file.print(pms_module.read4sd()); + #else + file.print(",,,,,,,,,"); + #endif + + #if OPC_ENABLED + file.print(opc.read4sd(opcData)); + #else + file.print(",,,,,"); + #endif + + #if GPS_ENABLED + if(gpsModule.isDataAvailable()){ + file.print(gpsModule.readData()); + } + else{ + file.print(",,,,,"); + } + + + #else + file.print(",,,,,,,,"); + #endif + + + #if MET_STATION + data = String(get_wind_speed()) + "," + String(analogRead(A15)) ; + file.print(data); + #else + file.print(",,"); + #endif + //just indicate SD CARD writing + digitalWrite(STATUS_RUNNING,HIGH); + _delay_ms(100); + digitalWrite(STATUS_RUNNING,LOW); + + file.close(); + } + else + { + #if SERIAL_LOG_ENABLED + Serial.println("Failed to open SD CARD"); + #endif + digitalWrite(SD_CARD_CS_PIN,HIGH); + + //just to indicate SD CARD not writing + digitalWrite(STATUS_HALTED,HIGH); + delay(200); + digitalWrite(STATUS_HALTED,LOW); + delay(1000); + } + #endif //SDCARD_LOG_ENABLED +} + + +// blynk task checks for connection, if fails try to connect +void blynktask(){ + #if WIFI_ENABLED + bool status = Blynk.run(); + if(!status) + { + // Set ESP8266 baud rate + EspSerial.begin(ESP8266_BAUD); + + if (WiFiDisconnected()) { + // Reconnect to WiFi + InitWifiModule(); + } + + delay(10); + Blynk.begin(BLYNK_AUTH_TOKEN, wifi, ssid, pass); + status = Blynk.run(); + } + #endif +} + +bool WiFiDisconnected() { + esp8266.println("AT+CWJAP?"); // Check if ESP8266 is connected to WiFi + delay(500); + + while (esp8266.available()) { + String response = esp8266.readStringUntil('\n'); + if (response.indexOf("No AP available") != -1) { // If not connected to any WiFi + return true; + } + } + return false; +} + +void reset8266 () +{ + pinMode(CH_PD, OUTPUT); + digitalWrite(CH_PD, LOW); + delay(300); + digitalWrite(CH_PD, HIGH); +} + +// Blynk task +SchedTask blynkTask(2000, 3000, blynktask); // define the blynkTask task (dispatch in 2 sec, every 3 sec) + +// sdcard task +SchedTask sdCardTask(0, 3000, logDataToSdCard); // define the sdCardTask task (dispatch now, every 3 sec) + +void setup() +{ + #if SERIAL_LOG_ENABLED + Serial.begin(9600); + Serial.println(); + #endif + + #if WIFI_ENABLED + { + // Set ESP8266 baud rate + EspSerial.begin(ESP8266_BAUD); + InitWifiModule(); + delay(10); + Blynk.begin(BLYNK_AUTH_TOKEN, wifi, ssid, pass); + } + #endif + + // In voltage + pinMode(IN_VOLT_PIN, INPUT); + + // Motor control + pinMode(MOTOR_CTRL_IN_PIN, INPUT); + pinMode(MOTOR_CTRL_OUT_PIN, OUTPUT); + pinMode(SD_CARD_CS_PIN, OUTPUT); + + // status LEDs + pinMode(STATUS_RUNNING, OUTPUT); + // pinMode(STATUS_ERROR, OUTPUT); // pin needs to be changed + pinMode(STATUS_HALTED, OUTPUT); + + Wire.begin(); + + #if RTC_ENABLED + if (!rtc.begin()) + { + #if SERIAL_LOG_ENABLED + Serial.println("Error: Failed to initialize RTC module"); + #endif + //just to indicate RTC not initialized + digitalWrite(STATUS_HALTED,HIGH); + delay(1000); + digitalWrite(STATUS_HALTED,LOW); + delay(1000); + + } + else + { + // uncomment this line give current date and time if needed + rtc.adjust(DateTime(F(__DATE__),F(__TIME__))); // Only run uncommented once to initialize RTC + rtc_date_time = rtc.now(); + + } + if(rtc.lostPower()) + { + #if SERIAL_LOG_ENABLED + Serial.println("Error: RTC module power lost, change battery"); + #endif + //just to indicate rtc power lost + digitalWrite(STATUS_HALTED,HIGH); + delay(300); //delay for 300msec + digitalWrite(STATUS_HALTED,LOW); + delay(300); + } + + #endif + + #if SDCARD_LOG_ENABLED + digitalWrite(SD_CARD_CS_PIN,LOW); + + DateTime now = rtc.now(); + fileName = xpodID + "_" + String(now.month()) + "_" + String(now.day()) + "_" + String(now.year()) + ".txt"; + char fileNameArray[fileName.length()+1]; + fileName.toCharArray(fileNameArray, sizeof(fileNameArray)); //Well damn, that function is nice. + + + if(!sd.begin(SD_CARD_CS_PIN)) + { + #if SERIAL_LOG_ENABLED + Serial.println("insert sd card to begin"); + #endif + sd.begin(SD_CARD_CS_PIN); + // just to indicate sd card fail to init + digitalWrite(STATUS_HALTED,HIGH); + delay(1000); + digitalWrite(STATUS_HALTED,LOW); + delay(1000); + } + digitalWrite(SD_CARD_CS_PIN,HIGH); + SPI.transfer(0); + + #endif + + if (!co2_sensor.begin(CO2_I2C_ADDR)) + { + #if SERIAL_LOG_ENABLED + Serial.println("Error: Failed to initialize CO2 sensor!"); + #endif + } + + + if (!bme_module.begin()) + { + #if SERIAL_LOG_ENABLED + Serial.println("Error: Failed to initialize BME sensor!"); + #endif + } + + if (!ads_module.begin()) + { + #if SERIAL_LOG_ENABLED + Serial.println("Error: Failed to initialize one of the ADS1115 module!"); + #endif + } + + #if QUAD_ENABLED + if (!quad_module.begin()) + { + #if SERIAL_LOG_ENABLED + Serial.println("Error: Failed to initialize Quad Stat!"); + #endif + } + #endif + + #if MQ131_ENABLED + if (!mq131_module.begin()) + { + #if SERIAL_LOG_ENABLED + Serial.println("Error: Failed to initialize MQ131 sensor!"); + #endif + + } + #endif + + #if PMS_ENABLED + if (!pms_module.begin()) + { + #if SERIAL_LOG_ENABLED + Serial.println("Error: Failed to initialize PM sensor!"); + #endif + } + #endif + + #if OPC_ENABLED + if(!opc.begin()){ + #if SERIAL_LOG_ENABLED + Serial.println("Error: Failed to initialize OPC!"); + #endif + } + #endif + + #if GPS_ENABLED + if (!gpsModule.begin()) + { + #if SERIAL_LOG_ENABLED + Serial.println("Error: Failed to initialize GPS module!"); + #endif + } + #endif + + #if MET_STATION + attachInterrupt(digitalPinToInterrupt(3), wspeedIRQ, FALLING); // attaching wind speed interrupt to pin 3 + #endif + + initpots(); + DownPot(0); + DownPot(1); + //DownPot(2); + SetPotLevel(2, 80); + + delay(1000); + + delay(10000); + wdt_enable(WDTO_8S); + + +} + + +void loop() +{ + wdt_reset(); + unsigned long long int startLoop = millis(); + + // Dispatches the tasl to log data to sd card and blynk server + SchedBase::dispatcher(); + + // Motor control + int motor_ctrl_val = analogRead(MOTOR_CTRL_IN_PIN); + motor_ctrl_val = (((float)motor_ctrl_val / 1024) * 255); + + //motor_ctrl_val = 200; // use this to hardcode the motor speed; the number ranges from 0-255 + analogWrite(MOTOR_CTRL_OUT_PIN, motor_ctrl_val); + + // This all controls how long the loop lasts + if((SAMPLE_TIME * 1000) - (millis() - startLoop) >= 0) + { + int n = floor(((SAMPLE_TIME * 1000.0) - (millis() - startLoop)) / 6000.0); + for(int i = 0; i < n; i++) + { + wdt_reset(); + delay(6000); + } + wdt_reset(); + delay((SAMPLE_TIME * 1000) - (millis() - startLoop)); + } + else + { + delay(100); + } + // digitalWrite(STATUS_RUNNING, LOW); + Serial.print("\n"); + + #if WIFI_ENABLED + if (WiFiDisconnected()) { + // Reconnect to WiFi + reset8266(); + InitWifiModule(); + } + #endif +} + + +#if MET_STATION +//Returns the instataneous wind speed +float get_wind_speed(){ + float deltaTime = millis() - lastWindCheck; //750ms + + deltaTime /= 1000.0; //Covert to seconds + + float windSpeed = (float)windClicks / deltaTime; //3 / 0.750s = 4 + + windClicks = 0; //Reset and start watching for new wind + lastWindCheck = millis(); + + windSpeed *= 1.492; //4 * 1.492 = 5.968MPH + + return (windSpeed); +} +#endif diff --git a/xpod_node/Adafruit_PM25AQI.cpp b/xpod_node/Adafruit_PM25AQI.cpp new file mode 100644 index 0000000..a6aee3b --- /dev/null +++ b/xpod_node/Adafruit_PM25AQI.cpp @@ -0,0 +1,133 @@ +/*! + * @file Adafruit_PM25AQI.cpp + * + * @mainpage Adafruit PM2.5 air quality sensor driver + * + * @section intro_sec Introduction + * + * This is the documentation for Adafruit's PM2.5 AQI driver for the + * Arduino platform. It is designed specifically to work with the + * Adafruit PM2.5 Air quality sensors: http://www.adafruit.com/products/4632 + * + * These sensors use I2C or UART to communicate. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * + * @section author Author + * Written by Ladyada for Adafruit Industries. + * + * @section license License + * BSD license, all text here must be included in any redistribution. + * + */ + +#include "Adafruit_PM25AQI.h" + +/*! + * @brief Instantiates a new PM25AQI class + */ +Adafruit_PM25AQI::Adafruit_PM25AQI() {} + +/*! + * @brief Setups the hardware and detects a valid PMSA003I. Initializes I2C. + * @param theWire + * Optional pointer to I2C interface, otherwise use Wire + * @return True if PMSA003I found on I2C, False if something went wrong! + */ +bool Adafruit_PM25AQI::begin_I2C(TwoWire *theWire) { + if (!i2c_dev) { + i2c_dev = new Adafruit_I2CDevice(PMSA003I_I2CADDR_DEFAULT, theWire); + } + + if (!i2c_dev->begin()) { + return false; + } + + return true; +} + +/*! + * @brief Setups the hardware and detects a valid UART PM2.5 + * @param theSerial + * Pointer to Stream (HardwareSerial/SoftwareSerial) interface + * @return True + */ +bool Adafruit_PM25AQI::begin_UART(Stream *theSerial) { + serial_dev = theSerial; + + return true; +} + +/*! + * @brief Setups the hardware and detects a valid UART PM2.5 + * @param data + * Pointer to PM25_AQI_Data that will be filled by read()ing + * @return True on successful read, false if timed out or bad data + */ +bool Adafruit_PM25AQI::read(PM25_AQI_Data *data) { + uint8_t buffer[32]; + uint16_t sum = 0; + + if (!data) { + return false; + } + + if (i2c_dev) { // ok using i2c? + if (!i2c_dev->read(buffer, 32)) { + return false; + } + } else if (serial_dev) { // ok using uart + if (!serial_dev->available()) { + return false; + } + int skipped = 0; + while ((skipped < 32) && (serial_dev->peek() != 0x42)) { + serial_dev->read(); + skipped++; + if (!serial_dev->available()) { + return false; + } + } + if (serial_dev->peek() != 0x42) { + serial_dev->read(); + return false; + } + // Now read all 32 bytes + if (serial_dev->available() < 32) { + return false; + } + serial_dev->readBytes(buffer, 32); + } else { + return false; + } + + // Check that start byte is correct! + if (buffer[0] != 0x42) { + return false; + } + + // get checksum ready + for (uint8_t i = 0; i < 30; i++) { + sum += buffer[i]; + } + + // The data comes in endian'd, this solves it so it works on all platforms + uint16_t buffer_u16[15]; + for (uint8_t i = 0; i < 15; i++) { + buffer_u16[i] = buffer[2 + i * 2 + 1]; + buffer_u16[i] += (buffer[2 + i * 2] << 8); + } + + // put it into a nice struct :) + memcpy((void *)data, (void *)buffer_u16, 30); + + if (sum != data->checksum) { + return false; + } + + // success! + return true; +} diff --git a/xpod_node/Adafruit_PM25AQI.h b/xpod_node/Adafruit_PM25AQI.h new file mode 100644 index 0000000..cd304b3 --- /dev/null +++ b/xpod_node/Adafruit_PM25AQI.h @@ -0,0 +1,65 @@ +/*! + * @file Adafruit_PM25AQI.h + * + * This is the documentation for Adafruit's PM25 AQI driver for the + * Arduino platform. It is designed specifically to work with the + * Adafruit PM25 air quality sensors: http://www.adafruit.com/products/4632 + * + * These sensors use I2C or UART to communicate. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Ladyada for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#ifndef ADAFRUIT_PM25AQI_H +#define ADAFRUIT_PM25AQI_H + +#include "Arduino.h" +#include + +// the i2c address +#define PMSA003I_I2CADDR_DEFAULT 0x12 ///< PMSA003I has only one I2C address + +/**! Structure holding Plantower's standard packet **/ +typedef struct PMSAQIdata { + uint16_t framelen; ///< How long this data chunk is + uint16_t pm10_standard, ///< Standard PM1.0 + pm25_standard, ///< Standard PM2.5 + pm100_standard; ///< Standard PM10.0 + uint16_t pm10_env, ///< Environmental PM1.0 + pm25_env, ///< Environmental PM2.5 + pm100_env; ///< Environmental PM10.0 + uint16_t particles_03um, ///< 0.3um Particle Count + particles_05um, ///< 0.5um Particle Count + particles_10um, ///< 1.0um Particle Count + particles_25um, ///< 2.5um Particle Count + particles_50um, ///< 5.0um Particle Count + particles_100um; ///< 10.0um Particle Count + uint16_t unused; ///< Unused + uint16_t checksum; ///< Packet checksum +} PM25_AQI_Data; + +/*! + * @brief Class that stores state and functions for interacting with + * PM2.5 Air Quality Sensor + */ +class Adafruit_PM25AQI { +public: + Adafruit_PM25AQI(); + bool begin_I2C(TwoWire *theWire = &Wire); + bool begin_UART(Stream *theStream); + bool read(PM25_AQI_Data *data); + +private: + Adafruit_I2CDevice *i2c_dev = NULL; + Stream *serial_dev = NULL; + uint8_t _readbuffer[32]; +}; + +#endif diff --git a/xpod_node/My_BlynkSimpleShieldEsp8266.h b/xpod_node/My_BlynkSimpleShieldEsp8266.h new file mode 100644 index 0000000..1ebe773 --- /dev/null +++ b/xpod_node/My_BlynkSimpleShieldEsp8266.h @@ -0,0 +1,231 @@ +/** + * @file My_BlynkSimpleShieldEsp8266.h + * @author Volodymyr Shymanskyy + * @license This project is released under the MIT License (MIT) + * @copyright Copyright (c) 2015 Volodymyr Shymanskyy + * @date Jun 2015 + * @brief + * + */ + +#ifndef My_BlynkSimpleShieldEsp8266_h +#define My_BlynkSimpleShieldEsp8266_h + +#ifdef ESP8266 +#error This code is not intended to run on the ESP8266 platform! Please check your Tools->Board setting. +#endif + +#ifndef BLYNK_INFO_CONNECTION +#define BLYNK_INFO_CONNECTION "ESP8266" +#endif + +#ifndef BLYNK_ESP8266_MUX +#define BLYNK_ESP8266_MUX 1 +#endif + +#define BLYNK_SEND_ATOMIC +#define BLYNK_SEND_CHUNK 40 + +#include +#include +#include +#include + +class BlynkTransportShieldEsp8266 +{ + static void onData(uint8_t mux_id, uint32_t len, void* ptr) { + ((BlynkTransportShieldEsp8266*)ptr)->onData(mux_id, len); + } + + void onData(uint8_t mux_id, int32_t len) { + if (mux_id != BLYNK_ESP8266_MUX) { + return; + } + //BLYNK_LOG4("Got: ", len, ", Free: ", buffer.free()); + if (buffer.free() < len) { + BLYNK_LOG1(BLYNK_F("Buffer overflow")); + return; + } + while (len) { + if (client->getUart()->available()) { + uint8_t b = client->getUart()->read(); + buffer.put(b); + len--; + } + } + } + +public: + BlynkTransportShieldEsp8266() + : client(NULL) + , status(false) + , domain(NULL) + , port(0) + {} + + void setEsp8266(ESP8266* esp8266) { + client = esp8266; + client->setOnData(onData, this); + } + + //TODO: IPAddress + + void begin(const char* d, uint16_t p) { + domain = d; + port = p; + } + + bool connect() { + if (!domain || !port) + return false; + status = client->createTCP(BLYNK_ESP8266_MUX, domain, port); + return status; + } + + void disconnect() { + status = false; + buffer.clear(); + client->releaseTCP(BLYNK_ESP8266_MUX); + } + + size_t read(void* buf, size_t len) { + millis_time_t start = BlynkMillis(); + //BLYNK_LOG4("Waiting: ", len, " Buffer: ", buffer.size()); + while ((buffer.size() < len) && (BlynkMillis() - start < 1500)) { + client->run(); + } + return buffer.get((uint8_t*)buf, len); + } + size_t write(const void* buf, size_t len) { + if (client->send(BLYNK_ESP8266_MUX, (const uint8_t*)buf, len)) { + return len; + } + return 0; + } + + bool connected() { return status; } + + int available() { + client->run(); + //BLYNK_LOG2("Still: ", buffer.size()); + return buffer.size(); + } + +private: + ESP8266* client; + bool status; + BlynkFifo buffer; + const char* domain; + uint16_t port; +}; + +class BlynkWifi + : public BlynkProtocol +{ + typedef BlynkProtocol Base; +public: + BlynkWifi(BlynkTransportShieldEsp8266& transp) + : Base(transp) + , wifi(NULL) + {} + + bool connectWiFi(const char* ssid, const char* pass) + { + BlynkDelay(500); + BLYNK_LOG2(BLYNK_F("Connecting to "), ssid); + /*if (!wifi->restart()) { + BLYNK_LOG1(BLYNK_F("Failed to restart")); + return false; + }*/ + if (!wifi->kick()) { + BLYNK_LOG1(BLYNK_F("ESP is not responding")); + //TODO: BLYNK_LOG_TROUBLE(BLYNK_F("esp8266-not-responding")); + return false; + } + if (!wifi->setEcho(0)) { + BLYNK_LOG1(BLYNK_F("Failed to disable Echo")); + return false; + } + String ver = wifi->ESP8266::getVersion(); + BLYNK_LOG1(ver); + if (!wifi->enableMUX()) { + BLYNK_LOG1(BLYNK_F("Failed to enable MUX")); + } + if (!wifi->setOprToStation()) { + BLYNK_LOG1(BLYNK_F("Failed to set STA mode")); + return false; + } + if (wifi->joinAP(ssid, pass)) { + String my_ip = wifi->getLocalIP(); + BLYNK_LOG1(my_ip); + } else { + BLYNK_LOG1(BLYNK_F("Failed to connect WiFi")); + return false; + } + BLYNK_LOG1(BLYNK_F("Connected to WiFi")); + return true; + } + + void config(ESP8266& esp8266, + const char* auth, + const char* domain = BLYNK_DEFAULT_DOMAIN, + uint16_t port = BLYNK_DEFAULT_PORT) + { + Base::begin(auth); + wifi = &esp8266; + this->conn.setEsp8266(wifi); + this->conn.begin(domain, port); + } + + // void begin(const char* auth, + // ESP8266& esp8266, + // const char* ssid, + // const char* pass, + // const char* domain = BLYNK_DEFAULT_DOMAIN, + // uint16_t port = BLYNK_DEFAULT_PORT) + // { + // config(esp8266, auth, domain, port); + // connectWiFi(ssid, pass); + // while(this->connect() != true) {} + // } + void begin(const char* auth, + ESP8266& esp8266, + const char* ssid, + const char* pass, + const char* domain = BLYNK_DEFAULT_DOMAIN, + uint16_t port = BLYNK_DEFAULT_PORT + ) + { + static unsigned long timeout = 3000; // Timeout in milliseconds (default: 5 seconds) + static unsigned int maxAttempts = 10; // Maximum number of connection attempts (default: 10) + config(esp8266, auth, domain, port); // Configure Blynk with provided parameters + connectWiFi(ssid, pass); // Connect to Wi-Fi network + + unsigned int attempts = 0; + unsigned long startTime = millis(); + + // Keep trying to connect to Blynk server until successful or timeout + while (!this->connect()) { + if (attempts++ >= maxAttempts || millis() - startTime >= timeout) { + // Maximum attempts reached or timeout occurred + // You can add error handling or retry logic here + break; + } + delay(1000); // Wait for 1 second before retrying + } + } + +private: + ESP8266* wifi; +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_BLYNK) + static BlynkTransportShieldEsp8266 _blynkTransport; + BlynkWifi Blynk(_blynkTransport); +#else + extern BlynkWifi Blynk; +#endif + +#include + +#endif diff --git a/xpod_node/OPC.cpp b/xpod_node/OPC.cpp new file mode 100644 index 0000000..dd7393a --- /dev/null +++ b/xpod_node/OPC.cpp @@ -0,0 +1,185 @@ +/* +OPC.cpp + +Written by Joseph Habeck (habec021@umn.edu) on 6/24/18. (https://github.com/JHabeck/Alphasense-OPC-N2/tree/master) +Edited using Marcel Oliveira's code from github (https://github.com/shyney7/OPC-R2_ESP32/tree/main) +Put together by Aidan Mobley + + */ +#include +#include "OPC.h" + +// Combine two bytes into a 16-bit unsigned int +uint16_t OPC::twoBytes2int(byte LSB, byte MSB){ + uint16_t int_val = ((MSB << 8) | LSB); + return int_val; +} + +// Return an IEEE754 float from an array of 4 bytes +float OPC::fourBytes2float(byte val0, byte val1, byte val2, byte val3) { + uint8_t bytes[4] = {uint8_t(val0), uint8_t(val1), uint8_t(val2), uint8_t(val3)}; + float result; + memcpy(&result, bytes, 4); + return result; +} + +// Gets OPC ready to do something +bool OPC::getReady(const byte command){ + byte inData; + SPI.beginTransaction(SPISettings(300000, MSBFIRST, SPI_MODE1)); + int tries = 0; + int total_tries = 0; + while(inData != OPC_ready & total_tries++ < 20){ + for(int i = 0; i < 10; i++){ + inData = SPI.transfer(0x01); // Try reading some bytes here to clear out anything remnant of other SPI activity + delayMicroseconds(10); + } + delay(10); + digitalWrite(CSpin, LOW); + while(inData != OPC_ready & tries++ < 20) + { + inData = SPI.transfer(command); + delay(5); + } + if(inData != OPC_ready){ + if(inData == OPC_busy){ // waiting 2 seconds because opc is busy + digitalWrite(CSpin, HIGH); + delay(2000); + } + else{ // resetting spi because different byte is returned + digitalWrite(CSpin, HIGH); + SPI.endTransaction(); + delay(6000); + SPI.beginTransaction(SPISettings(300000, MSBFIRST, SPI_MODE1)); + } + } + } + delay(10); + if(inData == OPC_ready) + return true; + else + return false; +} + +OPC::OPC(){ + CSpin = 49; +} + +bool OPC::begin(){ + SPI.begin(); + pinMode(CSpin, OUTPUT); + digitalWrite(CSpin, HIGH); + delay(1000); + return on(); +} + +bool OPC::on(){ + bool on = getReady(0x03); + SPI.transfer(0x03); + digitalWrite(CSpin, HIGH); + SPI.endTransaction(); + delay(2000); + return on; +} + +bool OPC::off(){ + bool off = getReady(0x03); + SPI.transfer(0x00); + digitalWrite(CSpin, HIGH); + SPI.endTransaction(); + return off; +} + +particleData OPC::getData(){ + particleData data; + double conv; + byte vals[64]; + byte command[] = {0x30, 0x01}; // command bytes to request histogram + + // SPI transaction + getReady(command[0]); + delay(100); + + // read all bits available + for (int i=0; i<64; ++i){ + vals[i] = SPI.transfer(command[1]); + delayMicroseconds(10); + } + + digitalWrite(CSpin, HIGH); + + // sample period [s] + float sp = fourBytes2float(vals[44], vals[45], vals[46], vals[47]); + // sample flow rate [ml/s] + float sfr = fourBytes2float(vals[36], vals[37], vals[38], vals[39]); + + // conversion to concentration [particles/ml] + if (CONVERT){ + conv = sp * sfr; + } + else{ // keep as particle-count/sec + conv = 1; + } + for(int i = 0; i < 16; i++){ + data.bin[i] = twoBytes2int(vals[i*2], vals[(i*2)+1]) / conv; + } + + #if PM_COUNT + // The below code just gives the raw counts for pm1.0, pm2.5, and pm10.0 + float PM10 = data.bin[0] + data.bin[1] + data.bin[2]; + float PM25 = 0; + for(int i = 0; i <= 5; i++){ + PM25 += data.bin[i]; + } + PM25 += data.bin[6] / 2; + float PM100 = 0; + for(int i = 0; i <= 11; i++){ + PM100 += data.bin[i]; + } + #else + float PM10 = fourBytes2float(vals[50], vals[51], vals[52], vals[53]); + float PM25 = fourBytes2float(vals[54], vals[55], vals[56], vals[57]); + float PM100 = fourBytes2float(vals[58], vals[59], vals[60], vals[61]); + #endif + + data.sp = sp; // This seems to always read 5.20 + data.sfr = sfr; + data.PM10 = PM10; + data.PM25 = PM25; + data.PM100 = PM100; + + return data; +} + +String OPC::read4sd(particleData data){ + String out_str = ""; + #if PRINT_BINS + for(int i = 0; i < 16; i++){ + out_str += String(data.bin[i]) + ","; + } + #else + //out_str = ",,,,,,,,,,,,,,,,"; + #endif + out_str += String(data.sp) + ","; + out_str += String(data.sfr) + ","; + out_str += String(data.PM10) + ","; + out_str += String(data.PM25) + ","; + out_str += String(data.PM100) + ","; + + return out_str; +} +String OPC::read4print(particleData data){ + String out_str = ""; + #if PRINT_BINS + for(int i = 0; i < 16; i++){ + out_str += "Bin " + String(i) + ": " + String(data.bin[i]) + ","; + } + #endif + out_str += "Sample Period: " + String(data.sp) + ","; + out_str += "Sample Flow Rate: " + String(data.sfr) + ","; + out_str += "PM1.0: " + String(data.PM10) + ","; + out_str += "PM2.5: " + String(data.PM25) + ","; + out_str += "PM10.0: " + String(data.PM100) + ","; + + return out_str; +} diff --git a/xpod_node/OPC.h b/xpod_node/OPC.h new file mode 100644 index 0000000..bd85836 --- /dev/null +++ b/xpod_node/OPC.h @@ -0,0 +1,54 @@ +/* +OPC.h - library for operating optical particle counter OPC-R2 from Alphasense using an Arduino Mega. + + Written by Joseph Habeck (habec021@umn.edu) on 6/24/18. (https://github.com/JHabeck/Alphasense-OPC-N2/tree/master) + Edited using Marcel Oliveira's code from github (https://github.com/shyney7/OPC-R2_ESP32/tree/main) + Put together by Aidan Mobley +*/ +// White = MISO, purple = CLK, blue = MOSI, green = D49 +// Do not plug green(CS) into the CS pin on rev4 + +#ifndef OPC_h +#define OPC_h + +// include Arduino SPI library +#include + +#define PRINT_BINS 0 // Prints the amount of particles in each of the 16 bins +#define PM_COUNT 0 // Returns the PM measurements in particle count rather than ug/m3 +#define CONVERT 0 // Returns the bin measurements in particles/ml rather than particle count + + +const byte OPC_ready = 0xF3; +const byte OPC_busy = 0x31; + +// particle data structure +struct particleData{ + int bin[16]; // bin 0-2: pm 1.0, bin 0-5/6: pm 2.5 bin 0-11: pm 10.0 bin 5: 1.6-2.1, bin 6: 2.1-3.0 + float sp; + float sfr; + float PM10; + float PM25; + float PM100; +}; + +// define class +class OPC{ + public: + OPC(); + bool begin(); + bool on(); + bool off(); + particleData getData(); + String read4sd(particleData data); + String read4print(particleData data); + + private: + uint16_t twoBytes2int(byte LSB, byte MSB); + float fourBytes2float(byte val0, byte val1, byte val2, byte val3); + bool getReady(const byte command); + int CSpin; +}; + + +#endif /* OPC_h */ diff --git a/xpod_node/blynk.h b/xpod_node/blynk.h new file mode 100644 index 0000000..8747539 --- /dev/null +++ b/xpod_node/blynk.h @@ -0,0 +1,186 @@ +#define BLYNK_TEMPLATE_ID "TMPL2-_U7f2uX" +#define BLYNK_TEMPLATE_NAME "Simman3" +#define BLYNK_AUTH_TOKEN "sKPYgn3jcuimveDEcJSmUGmeBFd-LgG5" + +#include +#include "SoftwareSerial.h" +#include +#include "My_BlynkSimpleShieldEsp8266.h" +#include "bme_module.h" +#include "ads_module.h" +#include "gps_module.h" +#include "mq131_module.h" +#include "pms_module.h" +#include "s300i2c.h" + +// char ssid[] = "Pls_no_hack"; +// char pass[] = "BMTC_8922"; + +char ssid[] = "Samsung"; +char pass[] = "WoofWoof"; +extern gps; +#define BLYNK_PRINT Serial + +#define EspSerial Serial3 + +// Your ESP8266 baud rate: +#define ESP8266_BAUD 115200 + +#define DEBUG true + +ESP8266 wifi(&EspSerial); + +#define esp8266 Serial3 +#define CH_PD 4 +#define speed8266 115200 + +// Send AT commands to module with timeout handling +String sendDataWithTimeout(String command, const int timeout, boolean debug) { + String response = ""; + EspSerial.print(command); + long int startTime = millis(); + if (millis() - startTime < timeout) { // changed loop to condition + if (EspSerial.available()) { // changed loop to condition + char c = EspSerial.read(); + response += c; + } + if (response.endsWith("OK\r\n")) { + //break; // Command succeeded + } + } + if (debug) { + Serial.print("Wifi debug Response :"); + Serial.print(response); + Serial.println(); + } + return response; +} + +void InitWifiModule() { + sendDataWithTimeout("AT+RST\r\n", 2000, DEBUG); // reset + sendDataWithTimeout("AT+CWJAP=\"OnePlus Nord\",\"Jagatguru\"\r\n", 5000, DEBUG); // Connect network (increased timeout) 5sec + delay(3000); + sendDataWithTimeout("AT+CWMODE=1\r\n", 1000, DEBUG); // Set the module's operating mode to station mode + sendDataWithTimeout("AT+CIFSR\r\n", 1000, DEBUG); // Show IP Address + sendDataWithTimeout("AT+CIPMUX=1\r\n", 1000, DEBUG); // Multiple connections + sendDataWithTimeout("AT+CIPSERVER=1,80\r\n", 1000, DEBUG); // Start comm port 80 + +} + + +String BME_Module::read4blynk() +{ + String bms_data_str; + + if (!status) + return ""; + + Blynk.virtualWrite(V0, String(bme_sensor.temperature)); + Blynk.virtualWrite(V1, String(bme_sensor.pressure / 100.0)); + Blynk.virtualWrite(V2, String(bme_sensor.humidity)); + Blynk.virtualWrite(V3, String(bme_sensor.gas_resistance / 1000.0)); + Blynk.virtualWrite(V4, String(bme_sensor.readAltitude(SEALEVELPRESSURE_HPA))); + + Blynk.virtualWrite(V0, String(bme_sensor.temperature)); + Blynk.virtualWrite(V1, String(bme_sensor.pressure / 100.0)); + Blynk.virtualWrite(V2, String(bme_sensor.humidity)); + Blynk.virtualWrite(V3, String(bme_sensor.gas_resistance / 1000.0)); + Blynk.virtualWrite(V4, String(bme_sensor.readAltitude(SEALEVELPRESSURE_HPA))); + + return bms_data_str; +} + +String ADS_Module::read4blynk() +{ + String out_str = ""; + + Blynk.virtualWrite(V5, String(read_figaro(ADS_SENSOR_FIG2600))); + Blynk.virtualWrite(V6, String(read_figaro(ADS_SENSOR_FIG2602))); +#if FIGARO3_ENABELD + Blynk.virtualWrite(V7, String(read_figaro(ADS_SENSOR_FIG3))); +#endif +#if FIGARO4_ENABELD + Blynk.virtualWrite(V8, String(read_figaro(ADS_SENSOR_FIG4))); +#endif + Blynk.virtualWrite(V9, String(read_raw(ADS_SENSOR_PID))); + Blynk.virtualWrite(V10, String(read_raw(ADS_SENSOR_E2V))); + Blynk.virtualWrite(V11, String(read_co())); + + return out_str; +} +String S300I2C::read4blynk() +{ + String Co2data = ""; + Blynk.virtualWrite(V12,String(getCO2ppm())); + return Co2data; +} + + +String GPSModule::read4blynk() +{ + String gpsdata = ""; + #if GPS_ENABLED + String out = readData(); + // Blynk.virtualWrite(V5, out); + gps val; + Blynk.virtualWrite(V13, String(val.latitude)); + Blynk.virtualWrite(V14, String(val.longitude)); + Blynk.virtualWrite(V15, String(val.altitude)); + Blynk.virtualWrite(V16, String(val.course)); + Blynk.virtualWrite(V17, String(val.speed)); + #endif + return gpsdata; +} + +String MQ131_Module::read4blynk() +{ + String mq_data; + + if (!status) + return ""; + + Blynk.virtualWrite(V18, String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL))); + +// #if READ_JUST_RAW +// // mq_data = String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL)); +// Blynk.virtualWrite(V17, String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL))); +// #else +// // mq_data = String(this->read()) + "," + String(raw_data); +// // Blynk.virtualWrite(V18, String(this->read()) + "," + String(raw_data)); +// #endif + + return mq_data; +} + +String PMS_Module::read4blynk() +{ + String pms_data_str; + PM25_AQI_Data data; + int read_tries = 3; + + if (!status) + return ""; + + // pms_data_str = String(data.pm10_env) + ","; + Blynk.virtualWrite(V19, String(data.pm10_env)); + // pms_data_str += String(data.pm25_env) + ","; + Blynk.virtualWrite(V20, String(data.pm25_env)); + // pms_data_str += String(data.pm100_env) + ","; + Blynk.virtualWrite(V21, String(data.pm100_env)); + + // pms_data_str += String(data.particles_03um) + ","; + Blynk.virtualWrite(V22, String(data.particles_03um)); + // pms_data_str += String(data.particles_05um) + ","; + Blynk.virtualWrite(V23, String(data.particles_05um)); + // pms_data_str += String(data.particles_10um) + ","; + Blynk.virtualWrite(V24, String(data.particles_10um)); + // pms_data_str += String(data.particles_25um) + ","; + Blynk.virtualWrite(V25, String(data.particles_25um)); + // pms_data_str += String(data.particles_50um) + ","; + Blynk.virtualWrite(V26, String(data.particles_50um)); + // pms_data_str += String(data.particles_100um); + Blynk.virtualWrite(V27, String(data.particles_100um)); + + return pms_data_str; +} + diff --git a/xpod_node/digipot.cpp b/xpod_node/digipot.cpp new file mode 100644 index 0000000..61b3245 --- /dev/null +++ b/xpod_node/digipot.cpp @@ -0,0 +1,97 @@ +/******************************************************************************* + * @file digipot.cpp + * @brief Configures the values of the digital potentiometers + * + * @author Rohan Jha + * @date July 20, 2023 + ******************************************************************************/ + +#include "digipot.h" + +pot potvar[NUM_POTS]; + +//Configuring digital potentiometers + void initpots() + { + potvar[0].clk = 4; + potvar[0].ud = 5; + potvar[0].cs = 6; + + potvar[1].clk = 27; + potvar[1].ud = 25; + potvar[1].cs = 23; + + potvar[2].clk = 29; + potvar[2].ud = 31; + potvar[2].cs = 33; + + pinMode(potvar[0].clk, OUTPUT); + pinMode(potvar[1].clk, OUTPUT); + pinMode(potvar[2].clk, OUTPUT); + + pinMode(potvar[0].ud, OUTPUT); + pinMode(potvar[1].ud, OUTPUT); + pinMode(potvar[2].ud, OUTPUT); + + pinMode(potvar[0].cs, OUTPUT); + pinMode(potvar[1].cs, OUTPUT); + pinMode(potvar[2].cs, OUTPUT); + + digitalWrite(potvar[0].cs, HIGH); + digitalWrite(potvar[1].cs, HIGH); + digitalWrite(potvar[2].cs, HIGH); + + digitalWrite(potvar[0].clk, HIGH); + digitalWrite(potvar[1].clk, HIGH); + digitalWrite(potvar[2].clk, HIGH); +} +/* +Function : Set digipot value +Args: Potentiometer number +Working : . Thet hree inputs are clock (CLK), CS and UP/DOWN (U/D).The negative-edge sensitive CLK input +requires clean transitions to avoid clocking multiple pulses into the internal UP/DOWNcounter register. +When CS is taken active low the clock begins to incre-ment or decrement the internal UP/DOWN counter dependent +upon the state of the U/D control pin. The UP/DOWN countervalue (D) starts at 40H at system power ON. +Each new CLKpulse will increment the value of the internal counter by one LSB until the full scale value of 3FH is + reached as long as theU/D pin is logic high. If the U/D pin is taken to logic low thecounter will count down stopping at code 00H (zero-scale). +*/ +void DownPot(int Pot_Num){ + digitalWrite(potvar[Pot_Num].cs, LOW); + digitalWrite(potvar[Pot_Num].ud, LOW); + // digitalWrite(num.clk, HIGH); + for(int i = 0; i < 128; i++){ + digitalWrite(potvar[Pot_Num].clk, LOW); + delay(1); + digitalWrite(potvar[Pot_Num].clk, HIGH); + delay(1); + } + digitalWrite(potvar[Pot_Num].cs, HIGH); +} + +void UpPot(int Pot_Num){ + digitalWrite(potvar[Pot_Num].cs, LOW); + digitalWrite(potvar[Pot_Num].ud, HIGH); + // digitalWrite(num.clk, HIGH); + for(int i = 0; i < 128; i++){ + digitalWrite(potvar[Pot_Num].clk, LOW); + delay(1); + digitalWrite(potvar[Pot_Num].clk, HIGH); + delay(1); + } + digitalWrite(potvar[Pot_Num].cs, HIGH); +} + +//Level could be anywhere between 0-128 based on the value of resistance required upto 10k +void SetPotLevel(int Pot_Num,int level){ + DownPot(Pot_Num); + digitalWrite(potvar[Pot_Num].cs, LOW); + digitalWrite(potvar[Pot_Num].ud, HIGH); + // digitalWrite(num.clk, HIGH); + for(int i = 0; i < level; i++){ + digitalWrite(potvar[Pot_Num].clk, LOW); + delay(1); + digitalWrite(potvar[Pot_Num].clk, HIGH); + delay(1); + } + digitalWrite(potvar[Pot_Num].cs, HIGH); +} diff --git a/xpod_node/digipot.h b/xpod_node/digipot.h new file mode 100644 index 0000000..53eef95 --- /dev/null +++ b/xpod_node/digipot.h @@ -0,0 +1,23 @@ +/******************************************************************************* + * @file digipot.h + * @brief Contains funtions and structures for digital potentiometers + * + * @author Rohan Jha + * @date July 20, 2023 + ******************************************************************************/ +#include +#define NUM_POTS 3 + +struct pot{ + int clk; + int ud; + int cs; +}; + +void initpots(); + +void DownPot(int Pot_Num); + +void UpPot(int Pot_Num); + +void SetPotLevel(int Pot_Num,int level); \ No newline at end of file diff --git a/xpod_node/gps_module.cpp b/xpod_node/gps_module.cpp new file mode 100644 index 0000000..90a4a55 --- /dev/null +++ b/xpod_node/gps_module.cpp @@ -0,0 +1,194 @@ +/******************************************************************************* + * @file gps_module.cpp + * @brief Defines the GPS functions + * + * @author Malola Simman Srinivasan Kannan , masr4788@colorado.edu + * @date September 8 2023 + ******************************************************************************/ +#include "gps_module.h" +#include +#include + +#define ARDUINO_GPS_RX 11 +#define ARDUINO_GPS_TX 10 + +#define GPS_BAUDRATE 9600 + +GPSModule gpsModule1; // Create an instance of the GPSModule class + +GPSModule::GPSModule() : gpsSerial(ARDUINO_GPS_RX, ARDUINO_GPS_TX) { + gps_status = false; +} + +bool GPSModule::begin() { + gpsSerial.begin(GPS_BAUDRATE); + + gps_status = true; + return gps_status; + +} + +bool GPSModule::isDataAvailable() { + return gpsSerial.available(); +} + +String GPSModule::readData() { + String data; + + while (gpsSerial.available()) { + char c = gpsSerial.read(); + static int i = 0; // Use static to retain i between loop iterations + + if (c == '$') { + i = 0; + } + + if (i < sizeof(strGPSData) - 1) { + strGPSData[i++] = c; + strGPSData[i] = '\0'; // Null-terminate the string + } + + if (c == '\n') { + data = parseNMEASentence(strGPSData); + } + } + + return data; +} + +String GPSModule::parseNMEASentence(const char* sentence) +{ + + // Check if the sentence starts with "$GPRMC" or "$GPGGA" + if (strncmp(sentence, "$GPRMC", 6) == 0) + { + // Ensure the sentence length is within a reasonable range + int sentenceLen = strlen(sentence); + char* values[12]; + int valueIndex = 0; + + if (sentenceLen >= 50 && sentenceLen <= 80) { + char* sentenceCopy = strdup(sentence); // Create a copy to avoid modifying the original string + char* token = strtok(sentenceCopy, ","); + while (token != NULL && valueIndex < 12) { + values[valueIndex++] = token; + token = strtok(NULL, ","); + } + free(sentenceCopy); // Free the copied string when done + + if (valueIndex >= 7) { + char* latitudeValue = values[3]; + char* latitudeDirection = values[4]; + char* longitudeValue = values[5]; + char* longitudeDirection = values[6]; + char* speedValue = values[7]; // Speed over ground in knots + + // Convert latitude and longitude to decimal degrees based on direction + latitude = atof(latitudeValue) /100; + longitude = atof(longitudeValue)/100; + + // Adjust latitude and longitude based on direction + if (latitudeDirection[0] == 'S') { + latitude = -latitude; + } + if (longitudeDirection[0] == 'W') { + longitude = -longitude; + } + + // Extract and validate course + if (valueIndex >= 8) { + char* courseValue = values[8]; + course = atof(courseValue); + + // Ensure course is within the valid range of 0 to 360 degrees + while (course < 0.0) { + course += 360.0; + } + while (course >= 360.0) { + course -= 360.0; + } + + // Extract and print speed + speed = atof(speedValue); + + } + + + // Extract and print date and time if available + if (valueIndex >= 10) { + String dateValue = values[8]; // Date in DDMMYY format + char* time_c = values[1]; + char time[7]; + + memcpy(time, time_c ,6); + time[6] = '\0'; + String timeValue ; + for(int i=0;i<7;i++) + { + timeValue.concat(time[i]); + } + + if (dateValue.length()== 6 && timeValue.length()>=7 ) { + + day = (dateValue.substring(0, 2)).toInt(); + month = (dateValue.substring(2, 4)).toInt(); + year = (dateValue.substring(4, 6)).toInt(); + hours=0; + if(timeValue.substring(0,2).toInt() == 0){ + hours = (timeValue.substring(1, 2)).toInt(); + } + else{ + hours = (timeValue.substring(0, 2)).toInt(); + } + + min = (timeValue.substring(2, 4)).toInt(); + sec = (timeValue.substring(4, 6)).toInt(); + + } + else { + Serial.println("GPS Invalid date or time format."); + } + } + else { + Serial.println("GPS Date and time information missing."); + } + } + } + } + else if (strncmp(sentence, "$GPGGA", 6) == 0) { + // Parse "$GPGGA" sentence for altitude + int sentenceLen = strlen(sentence); + char* values[15]; + int valueIndex = 0; + + if (sentenceLen >= 50 && sentenceLen <= 80) { + char* sentenceCopy = strdup(sentence); // Create a copy to avoid modifying the original string + char* token = strtok(sentenceCopy, ","); + while (token != NULL && valueIndex < 15) { + values[valueIndex++] = token; + token = strtok(NULL, ","); + } + free(sentenceCopy); // Free the copied string when done + + if (valueIndex >= 15) { + char* altitudeValue = values[9]; // Altitude above sea level (in meters) + altitude = atof(altitudeValue); + } + } + } + String gpsDataString = "Lat: " + String(latitude) ; + gpsDataString += ",Long: " + String(longitude); + gpsDataString += ",Course(degrees): " + String(course, 2) ; + gpsDataString += ",Speed(knots): " + String(speed, 2) ; + gpsDataString += ",Altitude(meters): " + String(altitude, 2) ; + gpsDataString += ",Date(DD/MM/YYYY): " + String(day) + "/" + String(month) + "/" + String(year) ; + gpsDataString += ",Time(HH:MM:SS): " + String(hours) + ":" + String(min) + ":" + String(sec) ; + gps gpsval={0}; + gpsval.altitude=altitude; + gpsval.longitude=longitude; + gpsval.course=course; + gpsval.latitude = latitude; + gpsval.speed = speed; + + return gpsDataString; +} diff --git a/xpod_node/gps_module.h b/xpod_node/gps_module.h new file mode 100644 index 0000000..a27c20f --- /dev/null +++ b/xpod_node/gps_module.h @@ -0,0 +1,46 @@ +/******************************************************************************* + * @file gps_module.h + * @brief GPS Header file declares required class methods and variables + * + * @author Malola Simman Srinivasan Kannan , masr4788@colorado.edu + * @date September 8 2023 + ******************************************************************************/ +#ifndef GPSMODULE_H +#define GPSMODULE_H + +#include +#include +typedef struct gps_val{ + double latitude; + double longitude; + double course; + double speed; + double altitude; +}gps; +class GPSModule { +public: + GPSModule(); + bool begin(); // Remove the parameter from begin method + String readData(); + String parseNMEASentence(const char* sentence); + bool isDataAvailable(); + String read4blynk(); + double latitude; + double longitude; + double course; + double speed; + double altitude; + int day; + int month; + int year; + int hours; + int min; + int sec; + +private: + SoftwareSerial gpsSerial; + char strGPSData[1024]; + bool gps_status; +}; + +#endif \ No newline at end of file diff --git a/xpod_node/mq131_module.cpp b/xpod_node/mq131_module.cpp new file mode 100644 index 0000000..f3f2d78 --- /dev/null +++ b/xpod_node/mq131_module.cpp @@ -0,0 +1,158 @@ +/******************************************************************************* + * @file mq131_module.cpp + * @brief + * + * @cite miguel5612, https://github.com/miguel5612/MQSensorsLib + * + * @editor Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 21 2023 + ******************************************************************************/ +#include +#include "mq131_module.h" + +MQ131_Module::MQ131_Module() +{ + heater_R0 = 0; + status = false; +} + +bool MQ131_Module::begin() +{ + if (!ads_module.begin(MQ131_I2C_ADDR)) + return false; + else + status = true; + +#if !READ_JUST_RAW + float calcR0 = 0; + for(int i = 1; i<=10; i++) + { + calcR0 += this->calibrate(); + } + + heater_R0 = calcR0 / 10; +#endif + + return status; +} + + +String MQ131_Module::read4sd() +{ + String mq_data; + + if (!status) + return ""; + +#if READ_JUST_RAW + mq_data = String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL)); +#else + mq_data = String(this->read()) + "," + String(raw_data); +#endif + + return mq_data; +} + +String MQ131_Module::read4print() +{ + String mq_data; + + if (!status) + return ","; + +#if READ_JUST_RAW + mq_data = " MQ131: " + String(ads_module.readADC_SingleEnded(MQ131_I2C_CHL)); +#else + mq_data = " MQ131: " + String(this->read()) + "," + String(raw_data); +#endif + + return mq_data; +} + +float MQ131_Module::read() +{ + float rs_calc, ratio, PPM; + float sensor_volt = this->update(); + + //More explained in: https://jayconsystems.com/blog/understanding-a-gas-sensor + rs_calc = ((VOLT_RESOLUTION * O3_EXP_REG_RL) / sensor_volt) - O3_EXP_REG_RL; //Get value of RS in a gas + + //No negative values accepted. + if(rs_calc < 0) + rs_calc = 0; + + // Get ratio RS_air/RS_gas <- INVERTED for MQ-131 issue 28 https://github.com/miguel5612/MQSensorsLib/issues/28 + ratio = heater_R0 / rs_calc; + + //No negative values accepted or upper datasheet recomendation. + if(ratio <= 0) + ratio = 0; + + // <- Source excel analisis https://github.com/miguel5612/MQSensorsLib_Docs/tree/master/Internal_design_documents + if(REG_METHOD == 1) + { + PPM = O3_EXP_REG_A * pow(ratio, O3_EXP_REG_B); + } + else + { + // https://jayconsystems.com/blog/understanding-a-gas-sensor <- Source of linear ecuation + double ppm_log = (log10(ratio) - O3_EXP_REG_B) / O3_EXP_REG_A; //Get ppm value in linear scale according to the the ratio value + PPM = pow(10, ppm_log); //Convert ppm value to log scale + } + + //No negative values accepted or upper datasheet recomendation. + if(PPM < 0) + PPM = 0; + + //if(_PPM > 10000) _PPM = 99999999; //No negative values accepted or upper datasheet recomendation. + return PPM; +} + +float MQ131_Module::calibrate() +{ + //More explained in: https://jayconsystems.com/blog/understanding-a-gas-sensor + /* + V = I x R + VRL = [VC / (RS + RL)] x RL + VRL = (VC x RL) / (RS + RL) + Así que ahora resolvemos para RS: + VRL x (RS + RL) = VC x RL + (VRL x RS) + (VRL x RL) = VC x RL + (VRL x RS) = (VC x RL) - (VRL x RL) + RS = [(VC x RL) - (VRL x RL)] / VRL + RS = [(VC x RL) / VRL] - RL + */ + float R0; //Define variable for R0 + float sensor_volt = this->update(); + + float RS_air = ((VOLT_RESOLUTION * O3_EXP_REG_RL) / sensor_volt) - O3_EXP_REG_RL; //Calculate RS in fresh air + + if(RS_air < 0) + RS_air = 0; //No negative values accepted. + + R0 = RS_air / RATIO_CLEAN_AIR; //Calculate R0 + + if(R0 < 0) + R0 = 0; //No negative values accepted. + + return R0; +} + +float MQ131_Module::update() +{ + int retries = 2; + float avg = 0.0; + uint32_t adc = 0; + + for (int i = 0; i < retries; i++) + { + avg += ads_module.readADC_SingleEnded(MQ131_I2C_CHL); + delay(20); + } + + avg = avg / retries; + + raw_data = avg; + + return ((avg * VOLT_RESOLUTION) / ((pow(2, ADC_RESOLUTION) - 1))); +} diff --git a/xpod_node/mq131_module.h b/xpod_node/mq131_module.h new file mode 100644 index 0000000..6388959 --- /dev/null +++ b/xpod_node/mq131_module.h @@ -0,0 +1,48 @@ +/******************************************************************************* + * @file mq131_module.ch + * @brief + * + * @cite miguel5612, https://github.com/miguel5612/MQSensorsLib + * + * @editor Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 21 2023 + ******************************************************************************/ +#ifndef _MQ131_Module_H +#define _MQ131_Module_H + +#include + +#define READ_JUST_RAW 1 + +#define ADC_RESOLUTION 16 +#define VOLT_RESOLUTION 5 +#define RATIO_CLEAN_AIR 15 +#define O3_EXP_REG_A 23.943 +#define O3_EXP_REG_B -1.11 +#define O3_EXP_REG_RL 10 +#define REG_METHOD 1 +#define MQ131_I2C_ADDR 0x4B +#define MQ131_I2C_CHL 1 + +class MQ131_Module +{ + public: + MQ131_Module(); + bool begin(); + + float read(); + String read4sd(); + String read4print(); + String read4blynk(); + + private: + float calibrate(); + float update(); + + Adafruit_ADS1115 ads_module; + float heater_R0; + uint16_t raw_data; + bool status; +}; + +#endif //_MQ131_Module_H \ No newline at end of file diff --git a/xpod_node/pms_module.cpp b/xpod_node/pms_module.cpp new file mode 100644 index 0000000..7bd896d --- /dev/null +++ b/xpod_node/pms_module.cpp @@ -0,0 +1,78 @@ +/******************************************************************************* + * @file bme_module.cpp + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 18 2023 + ******************************************************************************/ +#include +#include "pms_module.h" +PMS_Module::PMS_Module() +{ + status = false; +} + +bool PMS_Module::begin() +{ + PMS_SERIAL.begin(9600); + + if (!pms_sensor.begin_UART(&PMS_SERIAL)) + status = false; + else + status = true; + + return status; +} + +String PMS_Module::read4sd() +{ + String pms_data_str; + PM25_AQI_Data data; + int read_tries = 20; + + + while(!pms_sensor.read(&data) && read_tries) { + read_tries--; + delay(10); + } + + + pms_data_str = String(data.pm10_env) + ","; + pms_data_str += String(data.pm25_env) + ","; + pms_data_str += String(data.pm100_env) + ","; + + pms_data_str += String(data.particles_03um) + ","; + pms_data_str += String(data.particles_05um) + ","; + pms_data_str += String(data.particles_10um) + ","; + pms_data_str += String(data.particles_25um) + ","; + pms_data_str += String(data.particles_50um) + ","; + pms_data_str += String(data.particles_100um); + delay(100); + return pms_data_str; +} + +String PMS_Module::read4print() +{ + String pms_data_str; + PM25_AQI_Data data; + int read_tries = 3; + + if (!status) + return ""; + while(!pms_sensor.read(&data) && read_tries) { + read_tries--; + } + + pms_data_str = "PM10_ENV:" + String(data.pm10_env) + ","; + pms_data_str += "PM10_ENV:" + String(data.pm25_env) + ","; + pms_data_str += "PM10_ENV:" + String(data.pm100_env) + ","; + + pms_data_str += "PM_03um:" + String(data.particles_03um) + ","; + pms_data_str += "PM_05um:" + String(data.particles_05um) + ","; + pms_data_str += "PM_10um:" + String(data.particles_10um) + ","; + pms_data_str += "PM_25um:" + String(data.particles_25um) + ","; + pms_data_str += "PM_30um:" + String(data.particles_50um) + ","; + pms_data_str += "PM_100um:" + String(data.particles_100um); + delay(100); + return pms_data_str; +} diff --git a/xpod_node/pms_module.h b/xpod_node/pms_module.h new file mode 100644 index 0000000..a6e5c4e --- /dev/null +++ b/xpod_node/pms_module.h @@ -0,0 +1,30 @@ +/******************************************************************************* + * @file pms_module.h + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 18 2023 + ******************************************************************************/ +#ifndef _PMS_MODULE_H +#define _PMS_MODULE_H + +#include "Adafruit_PM25AQI.h" + +#define PMS_SERIAL (Serial1) +#define PMS_SERIAL_BR (9600) + +class PMS_Module +{ + public: + PMS_Module(); + bool begin(); + String read4sd(); + String read4print(); + String read4blynk(); + + private: + Adafruit_PM25AQI pms_sensor; + bool status; +}; + +#endif //_PMS_MODULE_H \ No newline at end of file diff --git a/xpod_node/quad_module.cpp b/xpod_node/quad_module.cpp new file mode 100644 index 0000000..ccffb6c --- /dev/null +++ b/xpod_node/quad_module.cpp @@ -0,0 +1,88 @@ +/******************************************************************************* + * @file quad_module.cpp + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 20 2023 + ******************************************************************************/ +#include +#include +#include "quad_module.h" + +QUAD_Module::QUAD_Module() +{ + status = true; +} + +bool QUAD_Module::begin() +{ + alpha_one = MCP342x(APLHA_ONE_ADDR); + alpha_two = MCP342x(APLHA_TWO_ADDR); + + MCP342x::generalCallReset(); + delay(1); + + // Wire.requestFrom(APLHA_ONE_ADDR, (uint8_t)1); + + // if (!Wire.available()) + // status = false; + + // Wire.requestFrom(APLHA_TWO_ADDR, (uint8_t)1); + + // if (!Wire.available()) + // status = false; + + return status; +} + +String QUAD_Module::read() +{ + MCP342x::Config status; + String quad_data; + long value = 0; + + // Initiate a conversion; convertAndRead() will wait until it can be read + alpha_one.convertAndRead(MCP342x::channel1, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_one.convertAndRead(MCP342x::channel2, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_one.convertAndRead(MCP342x::channel3, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_one.convertAndRead(MCP342x::channel4, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + + alpha_two.convertAndRead(MCP342x::channel1, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_two.convertAndRead(MCP342x::channel2, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_two.convertAndRead(MCP342x::channel3, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value) + ","; + + alpha_two.convertAndRead(MCP342x::channel4, MCP342x::oneShot, MCP342x::resolution16, + MCP342x::gain1, 1000000, value, status); + quad_data += String(value); + + // //Read ADCs on-board and on-Quadstat + // for (int i = 1; i <= 16; i++) + // { + // if (i <= 4) + // quad_data += alpha_one.GetValue(i) + ","; + // else if (i <= 8) + // quad_data += alpha_two.GetValue(i - 4) + ","; + // } + + return quad_data; +} \ No newline at end of file diff --git a/xpod_node/quad_module.h b/xpod_node/quad_module.h new file mode 100644 index 0000000..5eeb6f8 --- /dev/null +++ b/xpod_node/quad_module.h @@ -0,0 +1,32 @@ +/******************************************************************************* + * @file quad_module.h + * @brief + * + * @author Ajay Kandagal, ajka9053@colorado.edu + * @date Feb 20 2023 + ******************************************************************************/ +#ifndef _QUAD_Module_H +#define _QUAD_Module_H + +#include + +#define BME_SENSOR_ADDR (0x76) +#define SEALEVELPRESSURE_HPA (1013.25) + +#define APLHA_ONE_ADDR (0x69) +#define APLHA_TWO_ADDR (0x6E) + +class QUAD_Module +{ + public: + QUAD_Module(); + bool begin(); + + String read(); + private: + MCP342x alpha_one; + MCP342x alpha_two; + bool status; +}; + +#endif //_QUAD_Module_H \ No newline at end of file diff --git a/xpod_node/s300i2c.cpp b/xpod_node/s300i2c.cpp new file mode 100644 index 0000000..d96cc4a --- /dev/null +++ b/xpod_node/s300i2c.cpp @@ -0,0 +1,70 @@ +#include "s300i2c.h" +#include "Arduino.h" + +S300I2C::S300I2C(TwoWire &w) { + wire = &w; + co2i = 0; +} + +boolean S300I2C::begin(uint8_t i2caddr = S300I2C_ADDR) { + _i2caddr = i2caddr; + return true; +} + +void S300I2C::writeCommand(uint8_t cmd) { + wire->beginTransmission(_i2caddr); + wire->write(cmd); + wire->endTransmission(); +} + +unsigned int S300I2C::getCO2ppm(void) { + writeCommand('R'); + wire->requestFrom((int)_i2caddr,(int)7); + for (int i=0; wire->available(); i++) { + _tmpBuf[i] = wire->read(); + delay(10); + } + if (_tmpBuf[0] != 0x08 || + _tmpBuf[3] == 0xff || + _tmpBuf[4] == 0xff || + _tmpBuf[5] == 0xff || + _tmpBuf[6] == 0xff) { + return 0; + } + return (_tmpBuf[1] << 8) | _tmpBuf[2]; +} + +void S300I2C::sleep(void) { + writeCommand('S'); + delay(4000); +} + +void S300I2C::wakeup(void) { + writeCommand('W'); + delay(6000); +} + +void S300I2C::clear_recalib(void) { + writeCommand('C'); + delay(6000); +} + +void S300I2C::start_mcdl(void) { + writeCommand('M'); + delay(2000); +} + +void S300I2C::end_mcdl(void) { + writeCommand('E'); + delay(2000); +} + +void S300I2C::start_acdl(void) { + writeCommand('A'); + delay(2000); +} + +void S300I2C::end_acdl(void) { + writeCommand('E'); + delay(2000); +} diff --git a/xpod_node/s300i2c.h b/xpod_node/s300i2c.h new file mode 100644 index 0000000..a547395 --- /dev/null +++ b/xpod_node/s300i2c.h @@ -0,0 +1,43 @@ +/* + * ELT S300 I2C library + * s300.h + */ + +#ifndef ELT_S300_I2C_HOLLY +#define ELT_S300_I2C_HOLLY + +#include "Arduino.h" +#include "Wire.h" + +#define S300I2C_ADDR 0x31 + +class S300I2C { + public: + S300I2C(TwoWire &w); + boolean begin(uint8_t i2caddr); + void sleep(void); // Sleep command + void wakeup(void); // Wake up command + void clear_recalib(void); // Clear Recalibtation Factor Command + void start_mcdl(void); // Start Manual Calibration + void end_mcdl(void); // End Manual Calibration + void start_acdl(void); // Start Auto-Calibration + void end_acdl(void); // End Auto Calibration + unsigned int getCO2ppm(void); // get CO2 value + String read4blynk(); + + private: + TwoWire *wire; + unsigned int co2i; + uint8_t _i2caddr; + uint8_t _tmpBuf[7]; + + /** + * Internal function to perform and I2C write. + * + * @param cmd The 8-bit command ID to send. + */ + void writeCommand(uint8_t cmd); + + +}; +#endif diff --git a/xpod_node/wind_vane.cpp b/xpod_node/wind_vane.cpp new file mode 100644 index 0000000..924acf9 --- /dev/null +++ b/xpod_node/wind_vane.cpp @@ -0,0 +1,75 @@ +/******************************************************************************* + * @file wind_vane.cpp + * @brief + * + * @cite Modest Maker (https://www.youtube.com/watch?v=KHrTqdmYoAk) + * + * @editor Percy Smith, percy.smith@colorado.edu + * @date August 23, 2023 + ******************************************************************************/ +#include "wind_vane.h" + +// Here we're defining the wind vane "object" +wind_vane::wind_vane() +{ + status = false; +} + +// This retrieves voltage reading and turns it into a voltage +float wind_vane::get_direction() +{ + int sensorValue = analogRead(WINDVANE_PIN); + float voltage = sensorValue * (5.0 / 1023.0); + + return (voltage); +} + +// This translates the directional voltage into a cardinal direction +String wind_vane::cardinal_direction(float directionVoltage) +{ + float windVane = directionVoltage; + String compass = ""; + if(windVane > 4.61) compass = "W"; //W + else if(windVane > 4.33) compass = "NW"; //NW + else if(windVane > 4.03) compass = "WNW"; //WNW + else if(windVane > 3.84) compass = "N"; //N + else if(windVane > 3.43) compass = "NNW"; //NNW + else if(windVane > 3.06) compass = "SW"; //SW + else if(windVane > 2.92) compass = "WSW"; //WSW + else if(windVane > 2.23) compass = "NE"; //NE + else if(windVane > 1.96) compass = "NNE"; //NNE + else if(windVane > 1.38) compass = "S"; //S + else if(windVane > 1.17) compass = "SSW"; //SSW + else if(windVane > 0.88) compass = "SE"; //SE + else if(windVane > 0.60) compass = "SSE"; //SSE + else if(windVane > 0.43) compass = "E"; //E + else if(windVane > 0.39) compass = "ENE"; //ENE + else compass = "ESE"; //ESE + + return(compass); +} + +// This will translate the directional voltage into the degrees of the direction +float wind_vane::degree_direction(float directionVoltage) +{ + float windVane = directionVoltage; + float degrees; + if(windVane > 4.61) degrees = 270; //W + else if(windVane > 4.33) degrees = 315; //NW + else if(windVane > 4.03) degrees = 282.5; //WNW + else if(windVane > 3.84) degrees = 0; //N + else if(windVane > 3.43) degrees = 337.5; //NNW + else if(windVane > 3.06) degrees = 225; //SW + else if(windVane > 2.92) degrees = 247.5; //WSW + else if(windVane > 2.23) degrees = 45; //NE + else if(windVane > 1.96) degrees = 22.5; //NNE + else if(windVane > 1.38) degrees = 180; //S + else if(windVane > 1.17) degrees = 202.5; //SSW + else if(windVane > 0.88) degrees = 135; //SE + else if(windVane > 0.60) degrees = 157.5; //SSE + else if(windVane > 0.43) degrees = 90; //E + else if(windVane > 0.39) degrees = 67.5; //ENE + else degrees = 112.5; //ESE + + return(degrees); +} diff --git a/xpod_node/wind_vane.h b/xpod_node/wind_vane.h new file mode 100644 index 0000000..a7287db --- /dev/null +++ b/xpod_node/wind_vane.h @@ -0,0 +1,28 @@ +/******************************************************************************* + * @file wind_vane.h + * @brief + * + * @cite Modest Maker (https://www.youtube.com/watch?v=KHrTqdmYoAk) + * + * @editor Percy Smith, percy.smith@colorado.edu + * @date August 23, 2023 + ******************************************************************************/ +#ifndef wind_vane_h +#define wind_vane_h + +#include + +#define WINDVANE_PIN A15 + +class wind_vane +{ + public: + wind_vane(); + float get_direction(); + String cardinal_direction(float directionVoltage); + float degree_direction(float directionVoltage); + private: + bool status; +}; + +#endif /* wind_vane.h */ \ No newline at end of file