diff --git a/examples/advanced_multivariable/src/main.cpp b/examples/advanced_multivariable/src/main.cpp index 7b7c6afd..232507f9 100644 --- a/examples/advanced_multivariable/src/main.cpp +++ b/examples/advanced_multivariable/src/main.cpp @@ -62,9 +62,9 @@ void setup() { Serial.println("\n== Sensor test setup ==\n"); Serial.println("-->[SETUP] Detecting sensors.."); - sensors.setSampleTime(10); // config sensors sample time interval + sensors.setSampleTime(10); // config sensors sample time interval sensors.setOnDataCallBack(&onSensorDataOk); // all data read callback - sensors.setDebugMode(true); // [optional] debug mode + sensors.setDebugMode(true); // [optional] debug mode sensors.detectI2COnly(false); // disable force to only i2c sensors sensors.init(); // Auto detection to UART and i2c sensors delay(1000); diff --git a/examples/radiation_CAJOE/radiation_CAJOE.cpp b/examples/radiation_CAJOE/radiation_CAJOE.cpp new file mode 100644 index 00000000..a0ae7ba8 --- /dev/null +++ b/examples/radiation_CAJOE/radiation_CAJOE.cpp @@ -0,0 +1,65 @@ +/** + * @file main.cpp + * @authors @roberbike @iw2lsi @hpsaturn + * @date June 2018 - 2023 + * @brief Radiation sensor example + * @license GPL3 + * + * Full documentation: + * https://github.com/kike-canaries/canairio_sensorlib#canairio-air-quality-sensors-library + * + * Full implementation for WiFi and Bluetooth Air Quality fixed and mobile station: + * https://github.com/kike-canaries/canairio_firmware#canairio-firmware + * + * Main pull requests and discussions: + * https://github.com/kike-canaries/canairio_sensorlib/pull/144 + * https://github.com/kike-canaries/canairio_firmware/pull/226 + * + * CanAirIO project: + * https://canair.io + * + * CanAirIO Docs: + * https://canair.io/docs + * + */ + +#include +#include + +void onSensorDataOk() { + Serial.print(" CPM: " + String(sensors.getGeigerCPM())); + Serial.print(" uSvh: " + String(sensors.getGeigerMicroSievertHour())); +} + +void onSensorDataError(const char* msg) { + Serial.println(msg); +} + +/****************************************************************************** +* M A I N +******************************************************************************/ + +void setup() { + Serial.begin(115200); + delay(1000); + + Serial.println("\n== Sensor test setup ==\n"); + Serial.println("-->[SETUP] Detecting sensors.."); + + sensors.setSampleTime(5); // config sensors sample time interval + sensors.setOnDataCallBack(&onSensorDataOk); // all data read callback + sensors.setOnErrorCallBack(&onSensorDataError); // [optional] error callback + sensors.setDebugMode(true); // [optional] debug mode + sensors.detectI2COnly(true); // [optional] skip UART detection + sensors.enableGeigerSensor(27); // Geiger in sensor pin 27 + + sensors.init(); // forced UAQ sensor. Empty for auto detection + + delay(500); +} + +void loop() { + sensors.loop(); // read sensor data and showed it +} + + diff --git a/library.json b/library.json index ddd0ec80..e9215e07 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "CanAirIO Air Quality Sensors Library", - "version": "0.6.8", + "version": "0.6.9", "homepage":"https://canair.io", "keywords": [ diff --git a/library.properties b/library.properties index ae26f78e..4cf29075 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=CanAirIO Air Quality Sensors Library -version=0.6.8 +version=0.6.9 author=@hpsaturn, CanAirIO project maintainer=Antonio Vanegas url=https://github.com/kike-canaries/canairio_sensorlib diff --git a/src/Sensors.cpp b/src/Sensors.cpp index 0b4aeedd..28ac6ce1 100644 --- a/src/Sensors.cpp +++ b/src/Sensors.cpp @@ -77,6 +77,8 @@ bool Sensors::readAllSensors() { aht10Read(); DFRobotCORead(); DFRobotNH3Read(); + geigerRead(); + #ifdef DHT11_ENABLED dhtRead(); #endif @@ -133,7 +135,7 @@ void Sensors::init(u_int pms_type, int pms_rx, int pms_tx) { #ifdef DHT11_ENABLED dhtInit(); #endif - + printSensorsRegistered(true); } @@ -332,8 +334,6 @@ float Sensors::getCO() { return co; } - - /** * @brief UART only: check if the UART sensor is registered * @return bool true if the UART sensor is registered, false otherwise. @@ -550,7 +550,11 @@ float Sensors::getUnitValue(UNIT unit) { case ALT: return alt; case GAS: - return gas; + return gas; + case CPM: + return rad->getTics(); + case RAD: + return rad->getUSvh(); case NH3: return nh3; case CO: @@ -1601,7 +1605,6 @@ void Sensors::DFRobotNH3Init() { } // Altitude compensation for CO2 sensors without Pressure atm or Altitude compensation - void Sensors::CO2correctionAlt() { DEBUG("-->[SLIB] CO2 altitud original\t:", String(CO2Val).c_str()); float CO2cor = (0.016 * ((1013.25 - hpa) /10 ) * (CO2Val - 400)) + CO2Val; // Increment of 1.6% for every hpa of difference at sea level @@ -1646,8 +1649,37 @@ void Sensors::resetAllVariables() { pres = 0.0; nh3 = 0; co = 0; + rad->clear(); } +// ######################################################################### + +void Sensors::geigerRead(){ + if(rad->read()){ + unitRegister(UNIT::CPM); + unitRegister(UNIT::RAD); + } +} +/** + * @brief Enable Geiger sensor on specific pin + * @param GPIO pin +*/ +void Sensors::enableGeigerSensor(int gpio){ + sensorAnnounce(SENSORS::SCAJOE); + rad = new GEIGER(gpio,devmode); + sensorRegister(SENSORS::SCAJOE); +} + +uint32_t Sensors::getGeigerCPM(void) { + return rad->getTics(); +} + +float Sensors::getGeigerMicroSievertHour(void) { + return rad->getUSvh(); +} + +// ######################################################################### + void Sensors::DEBUG(const char *text, const char *textb) { if (devmode) { _debugPort.print(text); diff --git a/src/Sensors.hpp b/src/Sensors.hpp index 4d37ffe4..f57500d9 100644 --- a/src/Sensors.hpp +++ b/src/Sensors.hpp @@ -16,14 +16,15 @@ #include #include #include +#include #include #ifdef DHT11_ENABLED #include #endif -#define CSL_VERSION "0.6.8" -#define CSL_REVISION 375 +#define CSL_VERSION "0.6.9" +#define CSL_REVISION 376 /*************************************************************** * S E T U P E S P 3 2 B O A R D S A N D F I E L D S @@ -97,6 +98,8 @@ X(PRESS, "hPa", "P") \ X(ALT, "m", "Alt") \ X(GAS, "Ohm", "Gas") \ + X(CPM, "CPM", "CPM") \ + X(RAD, "uSv/h", "RAD") \ X(NH3, "ppm", "NH3") \ X(CO, "ppm", "CO") \ X(UCOUNT, "COUNT", "UCOUNT") @@ -126,6 +129,7 @@ typedef enum UNIT : size_t { SENSOR_UNITS } UNIT; X(SDHTX, "DHTX", 3) \ X(SDFRCO, "DFRCO", 3) \ X(SDFRNH3, "DFRNH3", 3) \ + X(SCAJOE, "CAJOE", 3) \ X(SCOUNT, "SCOUNT", 3) #define X(utype, uname, umaintype) utype, @@ -136,7 +140,9 @@ typedef enum SENSORS : size_t { SENSORS_TYPES } SENSORS; // backward compatibil enum class SensorGroup { SENSOR_NONE, SENSOR_PM, SENSOR_CO2, - SENSOR_ENV }; + SENSOR_ENV, + SENSOR_RAD // CAJOE_GEIGER + }; typedef void (*errorCbFn)(const char *msg); typedef void (*voidCbFn)(); @@ -219,6 +225,9 @@ class Sensors { // DFRobot gravity NH3 sensor addr 0x77 DFRobot_GAS_I2C dfrNH3; + // Geiger CAJOE sensor + GEIGER *rad; + void init(u_int pms_type = 0, int pms_rx = PMS_RX, int pms_tx = PMS_TX); void loop(); @@ -267,6 +276,12 @@ class Sensors { float getCO(); + void enableGeigerSensor(int gpio); + + uint32_t getGeigerCPM(void); + + float getGeigerMicroSievertHour(void); + void setTempOffset(float offset); void setCO2AltitudeOffset(float altitude); @@ -428,6 +443,8 @@ class Sensors { void sps30Errorloop(char *mess, uint8_t r); void sps30DeviceInfo(); + void geigerRead(); + void onSensorError(const char *msg); void startI2C(); @@ -469,3 +486,4 @@ extern Sensors sensors; #endif #endif + \ No newline at end of file diff --git a/src/drivers/MovingSum.h b/src/drivers/MovingSum.h new file mode 100644 index 00000000..62cfd1c9 --- /dev/null +++ b/src/drivers/MovingSum.h @@ -0,0 +1,163 @@ + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// MovingSum - template class for a moving-sum evaluation +// ---------------------------------------------------------------------------------------------------------------------------------------- +// derived from https://github.com/alphaville/MovingAverage + +#ifndef MOVING_SUM_H +#define MOVING_SUM_H + +#define DEFAULT_FILTER_LENGTH 8 // default length of the filter + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// template types are: , +// +// : should be an integer type large enough to fit the current sample variable +// : must be large enough to contain the sum of filter_length samples, each of type +// +// example: MovingSum adc +// MovingSum adc +// MovingSum adc +// ---------------------------------------------------------------------------------------------------------------------------------------- + +template +class MovingSum +{ + +public: + + MovingSum(unsigned short _filter_length=DEFAULT_FILTER_LENGTH); + ~MovingSum(); + + void add(MA_dt x); + void clear(void); + MA_dt* getData(void); + MA_st getCurrentSum(void) const; + unsigned short getFilterLength(void) const; + unsigned short getCurrentFilterLength(void) const; + +private: + + MA_st sum; // sum of current samples + MA_dt *data; // vector with raw data + unsigned short index; // index of current sample + unsigned short filter_length; // length of the filter + bool filter_complete; // true when there are filter_length samples + + void init(); + +}; + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// constructor - Creates a new instance of MovingSum with a default or a given filter length. +// ---------------------------------------------------------------------------------------------------------------------------------------- + +template +MovingSum::MovingSum(unsigned short _filter_length) +{ + filter_length = _filter_length; + + init(); +} + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// destructor - releases the memory objects associated with the current MovingSum instance. +// ---------------------------------------------------------------------------------------------------------------------------------------- + +template +MovingSum::~MovingSum() +{ + delete[] data; +} + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// init - initialize the class and allocate the required memory space +// ---------------------------------------------------------------------------------------------------------------------------------------- + +template +void MovingSum::init(void) +{ + sum = 0; + index = -1; + filter_complete = false; + + data = new MA_dt[filter_length]; + + clear(); +} + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// clear - clears the vector of data by setting it to zero. +// ---------------------------------------------------------------------------------------------------------------------------------------- + +template +void MovingSum::clear(void) +{ + for(unsigned short i=0; i +void MovingSum::add(MA_dt x) +{ + index = (index + 1) % filter_length; + sum -= data[index]; + data[index] = x; + sum += x; + + if (!filter_complete && index==filter_length-1){ + filter_complete = true; + } + +} + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// getCurrentSum - returns the current sum as updated after the invocation of MovingSum::add(MA_dt). +// ---------------------------------------------------------------------------------------------------------------------------------------- + +template +MA_st MovingSum::getCurrentSum(void) const +{ + return sum; +} + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// getData - returns the raw data that are currently stored in an internal vector. +// ---------------------------------------------------------------------------------------------------------------------------------------- + +template +MA_dt* MovingSum::getData(void) +{ + return data; +} + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// getFilterLength - returns the Filter's Length. +// ---------------------------------------------------------------------------------------------------------------------------------------- + +template +unsigned short MovingSum::getFilterLength(void) const +{ + return filter_length; +} + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// getCurrentFilterLength - returns the current Filter's Length. +// ---------------------------------------------------------------------------------------------------------------------------------------- + +template +unsigned short MovingSum::getCurrentFilterLength(void) const +{ + return filter_complete ? filter_length : (index+1); +} + +#endif + +// ---------------------------------------------------------------------------------------------------------------------------------------- +// END +// ---------------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/drivers/geiger.cpp b/src/drivers/geiger.cpp new file mode 100644 index 00000000..4194e4d3 --- /dev/null +++ b/src/drivers/geiger.cpp @@ -0,0 +1,128 @@ +#include "geiger.h" + +#ifdef ESP32 +hw_timer_t* geiger_timer = NULL; +portMUX_TYPE* geiger_timerMux = NULL; +uint16_t tics_cnt = 0U; // tics in 1000ms +uint32_t tics_tot = 0U; // total tics since boot +MovingSum* cajoe_fms; + +// ######################################################################### +// Interrupt routine called on each click from the geiger tube +// WARNING! the ISR is actually called on both the rising and the falling edge even if configure for FALLING or RISING + +void IRAM_ATTR GeigerTicISR() { + portENTER_CRITICAL_ISR(geiger_timerMux); + tics_cnt++; // tics in 1000ms + tics_tot++; // total tics since boot + portEXIT_CRITICAL_ISR(geiger_timerMux); +} + +// ######################################################################### +// Interrupt timer routine called every 1000 ms +void IRAM_ATTR onGeigerTimer() { + portENTER_CRITICAL_ISR(geiger_timerMux); + cajoe_fms->add(tics_cnt); + tics_cnt = 0; + portEXIT_CRITICAL_ISR(geiger_timerMux); +} +#endif + +// ######################################################################### +// Initialize Geiger counter +GEIGER::GEIGER(int gpio, bool debug) { +#ifdef ESP32 + if (gpio < 0) { + if (debug) Serial.println("[E][SLIB] undefined Geiger pin"); + return; + } + devmode = debug; + tics_cnt = 0U; // tics in 1000ms + tics_tot = 0U; // total tics since boot + + geiger_timer = NULL; + geiger_timerMux = new portMUX_TYPE(portMUX_INITIALIZER_UNLOCKED); + + // moving sum for CAJOE Geiger Counter, configured for 60 samples (1 sample every 1s * 60 samples = 60s) + cajoe_fms = new MovingSum(GEIGER_BUFSIZE); + + Serial.printf("-->[SLIB] Geiger startup on pin\t: %i\r\n",gpio); + + // attach interrupt routine to the GPI connected to the Geiger counter module + pinMode(gpio, INPUT); + attachInterrupt(digitalPinToInterrupt(gpio), GeigerTicISR, FALLING); + + // attach interrupt routine to internal timer, to fire every 1000 ms + geiger_timer = timerBegin(GEIGER_TIMER, 80, true); + timerAttachInterrupt(geiger_timer, &onGeigerTimer, true); + timerAlarmWrite(geiger_timer, 1000000, true); // 1000 ms + timerAlarmEnable(geiger_timer); + + Serial.println("-->[SLIB] Geiger counter ready"); +#endif +} + +// ######################################################################### +// Geiger counts evaluation +// CAJOE kit comes with a Chinese J305 geiger tube +// Conversion factor used for conversion from CPM to uSv/h is 0.008120370 (J305 tube) +bool GEIGER::read() { +#ifdef ESP32 + if (geiger_timer == NULL) return false; + bool ready; + uint32_t tics_len; + + portENTER_CRITICAL(geiger_timerMux); + tics_cpm = cajoe_fms->getCurrentSum(); + tics_len = cajoe_fms->getCurrentFilterLength(); + portEXIT_CRITICAL(geiger_timerMux); + + // check whether the moving sum is full + ready = (tics_len == cajoe_fms->getFilterLength()); + + // convert CPM (tics in last minute) to uSv/h and put in display buffer for TFT + // moving sum buffer size is 60 (1 sample every 1000 ms * 60 samples): the complete sum cover exactly last 60s + if (ready) { + uSvh = getUSvh(); + } else { + uSvh = 0.0; + } + + if (!devmode) return true; + + Serial.print("-->[SLIB] tTOT: "); + Serial.println(tics_tot); + Serial.print("-->[SLIB] tLEN: "); + Serial.print(tics_len); + Serial.println(ready ? " (ready)" : " (not ready)"); + Serial.print("-->[SLIB] tCPM: "); + Serial.println(tics_cpm); + Serial.print("-->[SLIB] uSvh: "); + Serial.println(uSvh); + + return true; +#else + return false; +#endif +} + +/** + * Converts CPM to uSv/h units (J305 tube) +*/ +float GEIGER::getUSvh() { + return float(this->tics_cpm) * J305_CONV_FACTOR; +} +/** + * Returns CPM +*/ +uint32_t GEIGER::getTics() { + return this->tics_cpm; +} + +void GEIGER::clear() { + tics_cpm = 0; + uSvh = 0.0; +#ifdef ESP32 + if (cajoe_fms != NULL) cajoe_fms->clear(); +#endif +} diff --git a/src/drivers/geiger.h b/src/drivers/geiger.h new file mode 100644 index 00000000..72b5bef0 --- /dev/null +++ b/src/drivers/geiger.h @@ -0,0 +1,28 @@ +#include +#include "MovingSum.h" + +/************************************************************** + * GEIGER + * ************************************************************/ + +#define GEIGER_TIMER 1 // timer0 is already used (at least on TTGO-TDisplay) somewhere ??? +#define GEIGER_BUFSIZE 60 // moving sum buffer size (1 sample every 1s * 60 samples = 60s) +#define J305_CONV_FACTOR 0.008120370 // conversion factor used for conversion from CPM to uSv/h units (J305 tube) + +class GEIGER { + private: + bool devmode = false; + public: + uint32_t tics_cpm = 0U; // tics in last 60s + float uSvh = 0.0f; + /** + * @brief Constructor + * @param gpio attached pin + * @param debug debug mode enable/disable + */ + explicit GEIGER(int gpio = -1, bool debug = false); + bool read(); + void clear(); + uint32_t getTics(); + float getUSvh(); +}; \ No newline at end of file