mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-07-21 04:32:09 +02:00
Fix capitalize folder and file name ignored
This commit is contained in:
822
src/S8/S8.cpp
Normal file
822
src/S8/S8.cpp
Normal file
@ -0,0 +1,822 @@
|
||||
#include "S8.h"
|
||||
#include "mb_crc.h"
|
||||
#if defined(ESP8266)
|
||||
#include <SoftwareSerial.h>
|
||||
#else
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Construct a new Sense Air S8:: Sense Air S8 object
|
||||
*
|
||||
* @param def
|
||||
*/
|
||||
S8::S8(BoardType def) : _boardDef(def) {}
|
||||
|
||||
#if defined(ESP8266)
|
||||
/**
|
||||
* @brief Init sensor
|
||||
* @return true = success, otherwise is failure
|
||||
*/
|
||||
bool S8::begin(void) {
|
||||
if (this->_isBegin) {
|
||||
AgLog("Initialized, Call end() then try again");
|
||||
return true;
|
||||
}
|
||||
|
||||
return this->_begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init S8 sensor, this methos should be call before other, if not it's
|
||||
* always return the failure status
|
||||
*
|
||||
* @param _debugStream Serial print debug log, NULL if don't use
|
||||
* @return true = success, otherwise is failure
|
||||
*/
|
||||
bool S8::begin(Stream *_debugStream) {
|
||||
this->_debugStream = _debugStream;
|
||||
return this->begin();
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* @brief Init sensor
|
||||
*
|
||||
* @param serial Target Serial use for communication with sensor
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool S8::begin(HardwareSerial &serial) {
|
||||
this->_serial = &serial;
|
||||
return this->_begin();
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief De-Initialize sensor and release peripheral resource
|
||||
*
|
||||
*/
|
||||
void S8::end(void) {
|
||||
if (this->_isBegin == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deinit
|
||||
AgLog("De-Inititlize");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sensor firmware version
|
||||
*
|
||||
* @param firmver String buffer, len = 10 char
|
||||
*/
|
||||
void S8::getFirmwareVersion(char firmver[]) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (firmver == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
strcpy(firmver, "");
|
||||
|
||||
// Ask software version
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR29, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
snprintf(firmver, S8_LEN_FIRMVER, "%0u.%0u", buf_msg[3], buf_msg[4]);
|
||||
AgLog("Firmware version: %s", firmver);
|
||||
} else {
|
||||
AgLog("Firmware version not available!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sensor type ID
|
||||
*
|
||||
* @return int32_t Return ID
|
||||
*/
|
||||
int32_t S8::getSensorTypeId(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t sensorType = 0;
|
||||
|
||||
// Ask sensor type ID (high)
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR26, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
|
||||
// Save sensor type ID (high)
|
||||
sensorType = (((int32_t)buf_msg[4] << 16) & 0x00FF0000);
|
||||
|
||||
// Ask sensor type ID (low)
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR27, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
sensorType |=
|
||||
((buf_msg[3] << 8) & 0x0000FF00) | (buf_msg[4] & 0x000000FF);
|
||||
} else {
|
||||
AgLog("Error getting sensor type ID (low)!");
|
||||
}
|
||||
} else {
|
||||
AgLog("Error getting sensor type ID (high)!");
|
||||
}
|
||||
|
||||
return sensorType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sensor ID
|
||||
*
|
||||
* @return int32_t ID
|
||||
*/
|
||||
int32_t S8::getSensorId(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t sensorID = 0;
|
||||
|
||||
// Ask sensor ID (high)
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR30, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
|
||||
// Save sensor ID (high)
|
||||
sensorID = (((int32_t)buf_msg[3] << 24) & 0xFF000000) |
|
||||
(((int32_t)buf_msg[4] << 16) & 0x00FF0000);
|
||||
|
||||
// Ask sensor ID (low)
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR31, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
sensorID |= ((buf_msg[3] << 8) & 0x0000FF00) | (buf_msg[4] & 0x000000FF);
|
||||
} else {
|
||||
AgLog("Error getting sensor ID (low)!");
|
||||
}
|
||||
} else {
|
||||
AgLog("Error getting sensor ID (high)!");
|
||||
}
|
||||
|
||||
return sensorID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get memory map version
|
||||
*
|
||||
* @return int16_t
|
||||
*/
|
||||
int16_t S8::getMemoryMapVersion(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t mmVersion = 0;
|
||||
|
||||
// Ask memory map version
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR28, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
mmVersion = ((buf_msg[3] << 8) & 0xFF00) | (buf_msg[4] & 0x00FF);
|
||||
AgLog("Memory map version = %d", mmVersion);
|
||||
} else {
|
||||
AgLog("Error getting memory map version!");
|
||||
}
|
||||
|
||||
return mmVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get CO2
|
||||
*
|
||||
* @return int16_t (PPM), -1 if invalid.
|
||||
*/
|
||||
int16_t S8::getCo2(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t co2 = 0;
|
||||
|
||||
// Ask CO2 value
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR4, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
co2 = ((buf_msg[3] << 8) & 0xFF00) | (buf_msg[4] & 0x00FF);
|
||||
AgLog("CO2 value = %d ppm", co2);
|
||||
} else {
|
||||
AgLog("Error getting CO2 value!");
|
||||
}
|
||||
|
||||
return co2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calibration CO2 value to baseline 400(PPM)
|
||||
*
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
|
||||
bool S8::setBaselineCalibration(void) {
|
||||
if (this->manualCalib()) {
|
||||
isCalib = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait for background calibration done
|
||||
*
|
||||
* @return true Done
|
||||
* @return false On calib
|
||||
*/
|
||||
bool S8::isBaseLineCalibrationDone(void) {
|
||||
if (isCalib == false) {
|
||||
return true;
|
||||
}
|
||||
if (getAcknowledgement() & S8_MASK_CO2_BACKGROUND_CALIBRATION) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get output PWM value
|
||||
*
|
||||
* @return int16_t PWM
|
||||
*/
|
||||
int16_t S8::getOutputPWM(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t pwm = 0;
|
||||
|
||||
// Ask PWM output
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR22, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
pwm = ((buf_msg[3] << 8) & 0xFF00) | (buf_msg[4] & 0x00FF);
|
||||
AgLog("PWM output (raw) = %d", pwm);
|
||||
AgLog("PWM output (to ppm, normal version) = %d PPM",
|
||||
(pwm / 16383.0) * 2000.0);
|
||||
} else {
|
||||
AgLog("Error getting PWM output!");
|
||||
}
|
||||
|
||||
return pwm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get ABC calibration period
|
||||
*
|
||||
* @return int16_t Hour
|
||||
*/
|
||||
int16_t S8::getCalibPeriodABC(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t period = 0;
|
||||
|
||||
// Ask ABC period
|
||||
sendCommand(MODBUS_FUNC_READ_HOLDING_REGISTERS, MODBUS_HR32, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_HOLDING_REGISTERS, nb, 7)) {
|
||||
period = ((buf_msg[3] << 8) & 0xFF00) | (buf_msg[4] & 0x00FF);
|
||||
AgLog("ABC period: %d hour", period);
|
||||
} else {
|
||||
AgLog("Error getting ABC period!");
|
||||
}
|
||||
|
||||
return period;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set ABC calibration period
|
||||
*
|
||||
* @param period Hour, 4 - 4800 hour, 0 to disable
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool S8::setCalibPeriodABC(int16_t period) {
|
||||
if (this->isBegin() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buf_msg_sent[8];
|
||||
bool result = false;
|
||||
|
||||
if (period >= 0 && period <= 4800) { // 0 = disable ABC algorithm
|
||||
|
||||
// Ask set ABC period
|
||||
sendCommand(MODBUS_FUNC_WRITE_SINGLE_REGISTER, MODBUS_HR32, period);
|
||||
|
||||
// Save bytes sent
|
||||
memcpy(buf_msg_sent, buf_msg, 8);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uartReadBytes(8, S8_TIMEOUT);
|
||||
|
||||
// Check response
|
||||
if (memcmp(buf_msg_sent, buf_msg, 8) == 0) {
|
||||
result = true;
|
||||
AgLog("Successful setting of ABC period");
|
||||
} else {
|
||||
AgLog("Error in setting of ABC period!");
|
||||
}
|
||||
} else {
|
||||
AgLog("Invalid ABC period!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Manual calibration sensor, must be place sensor at clean environment
|
||||
* for 5 minute before calib
|
||||
*
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool S8::manualCalib(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = clearAcknowledgement();
|
||||
|
||||
if (result) {
|
||||
result = sendSpecialCommand(S8_CO2_BACKGROUND_CALIBRATION);
|
||||
|
||||
if (result) {
|
||||
AgLog("Manual calibration in background has started");
|
||||
} else {
|
||||
AgLog("Error starting manual calibration!");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sensor acknowlegement flags
|
||||
*
|
||||
* @return int16_t Flags
|
||||
*/
|
||||
int16_t S8::getAcknowledgement(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t flags = 0;
|
||||
|
||||
// Ask acknowledgement flags
|
||||
sendCommand(MODBUS_FUNC_READ_HOLDING_REGISTERS, MODBUS_HR1, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_HOLDING_REGISTERS, nb, 7)) {
|
||||
flags = ((buf_msg[3] << 8) & 0xFF00) | (buf_msg[4] & 0x00FF);
|
||||
} else {
|
||||
AgLog("Error getting acknowledgement flags!");
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clea acknowlegement flags
|
||||
*
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool S8::clearAcknowledgement(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buf_msg_sent[8];
|
||||
bool result = false;
|
||||
|
||||
// Ask clear acknowledgement flags
|
||||
sendCommand(MODBUS_FUNC_WRITE_SINGLE_REGISTER, MODBUS_HR1, 0x0000);
|
||||
|
||||
// Save bytes sent
|
||||
memcpy(buf_msg_sent, buf_msg, 8);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uartReadBytes(8, S8_TIMEOUT);
|
||||
|
||||
// Check response
|
||||
if (memcmp(buf_msg_sent, buf_msg, 8) == 0) {
|
||||
result = true;
|
||||
AgLog("Successful clearing acknowledgement flags");
|
||||
} else {
|
||||
AgLog("Error clearing acknowledgement flags!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sensor alarm status
|
||||
*
|
||||
* @return int16_t Alarm status
|
||||
*/
|
||||
int16_t S8::getAlarmStatus(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t status = 0;
|
||||
|
||||
// Ask alarm status
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR2, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
status = ((buf_msg[3] << 8) & 0xFF00) | (buf_msg[4] & 0x00FF);
|
||||
} else {
|
||||
AgLog("Error getting alarm status!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sensor status
|
||||
*
|
||||
* @return S8::Status Sensor status
|
||||
*/
|
||||
S8::Status S8::getStatus(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return (Status)0;
|
||||
}
|
||||
|
||||
int16_t status = 0;
|
||||
|
||||
// Ask meter status
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR1, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
status = ((buf_msg[3] << 8) & 0xFF00) | (buf_msg[4] & 0x00FF);
|
||||
} else {
|
||||
AgLog("Error getting meter status!");
|
||||
}
|
||||
|
||||
return (Status)status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sensor output status
|
||||
*
|
||||
* @return int16_t Output status
|
||||
*/
|
||||
int16_t S8::getOutputStatus(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t status = 0;
|
||||
|
||||
// Ask output status
|
||||
sendCommand(MODBUS_FUNC_READ_INPUT_REGISTERS, MODBUS_IR3, 0x0001);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uint8_t nb = uartReadBytes(7, S8_TIMEOUT);
|
||||
|
||||
// Check response and get data
|
||||
if (validResponseLenght(MODBUS_FUNC_READ_INPUT_REGISTERS, nb, 7)) {
|
||||
status = ((buf_msg[3] << 8) & 0xFF00) | (buf_msg[4] & 0x00FF);
|
||||
} else {
|
||||
AgLog("Error getting output status!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send special command to sensor, example manual calibration
|
||||
*
|
||||
* @param command Command
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool S8::sendSpecialCommand(CalibrationSpecialComamnd command) {
|
||||
if (this->isBegin() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buf_msg_sent[8];
|
||||
bool result = false;
|
||||
|
||||
// Ask set user special command
|
||||
sendCommand(MODBUS_FUNC_WRITE_SINGLE_REGISTER, MODBUS_HR2, command);
|
||||
|
||||
// Save bytes sent
|
||||
memcpy(buf_msg_sent, buf_msg, 8);
|
||||
|
||||
// Wait response
|
||||
memset(buf_msg, 0, S8_LEN_BUF_MSG);
|
||||
uartReadBytes(8, S8_TIMEOUT);
|
||||
|
||||
// Check response
|
||||
if (memcmp(buf_msg_sent, buf_msg, 8) == 0) {
|
||||
result = true;
|
||||
AgLog("Successful setting user special command");
|
||||
} else {
|
||||
AgLog("Error in setting user special command!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init sensor with BSP
|
||||
*
|
||||
* @param bsp AirGradient BSP @ref BoardDef
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool S8::init(const BoardDef *bsp) {
|
||||
return this->init(bsp->SenseAirS8.uart_tx_pin, bsp->SenseAirS8.uart_rx_pin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init sensor
|
||||
*
|
||||
* @param txPin UART TX pin
|
||||
* @param rxPin UART RX pin
|
||||
* @return true = success, otherwise failure
|
||||
*/
|
||||
bool S8::init(int txPin, int rxPin) { return this->init(txPin, rxPin, 9600); }
|
||||
|
||||
bool S8::_begin(void) {
|
||||
const BoardDef *bsp = getBoardDef(this->_boardDef);
|
||||
if (bsp == NULL) {
|
||||
AgLog("Board Type not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bsp->SenseAirS8.supported == false) {
|
||||
AgLog("Board is not support this sensor");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init sensor
|
||||
if (this->init(bsp) == false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init sensor
|
||||
*
|
||||
* @param txPin UART TX pin
|
||||
* @param rxPin UART RX pin
|
||||
* @param baud UART speed
|
||||
* @return true = success, otherwise failure
|
||||
*/
|
||||
bool S8::init(int txPin, int rxPin, uint32_t baud) {
|
||||
#if defined(ESP8266)
|
||||
SoftwareSerial *uart = new SoftwareSerial(txPin, rxPin);
|
||||
uart->begin(baud);
|
||||
this->_uartStream = uart;
|
||||
#else
|
||||
this->_serial->begin(baud, SERIAL_8N1, rxPin, txPin);
|
||||
this->_uartStream = this->_serial;
|
||||
#endif
|
||||
|
||||
/** Check communication by get firmware version */
|
||||
char fwVers[11];
|
||||
this->_isBegin = true;
|
||||
this->getFirmwareVersion(fwVers);
|
||||
if (strlen(fwVers) == 0) {
|
||||
this->_isBegin = false;
|
||||
return false;
|
||||
}
|
||||
AgLog("Firmware version: %s", fwVers);
|
||||
|
||||
AgLog("Sensor successfully initialized. Heating up for 10s");
|
||||
this->_isBegin = true;
|
||||
this->_lastInitTime = millis();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check that sensor is initialized
|
||||
*
|
||||
* @return true Initialized
|
||||
* @return false No-Initialized
|
||||
*/
|
||||
bool S8::isBegin(void) {
|
||||
if (this->_isBegin) {
|
||||
return true;
|
||||
}
|
||||
AgLog("Sensor not-initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UART write
|
||||
*
|
||||
* @param size Number of bytes
|
||||
*/
|
||||
void S8::uartWriteBytes(uint8_t size) {
|
||||
this->_uartStream->write(buf_msg, size);
|
||||
this->_uartStream->flush();
|
||||
}
|
||||
|
||||
uint8_t S8::uartReadBytes(uint8_t max_bytes, uint32_t timeout_ms) {
|
||||
uint8_t nb = 0;
|
||||
uint32_t stime = millis();
|
||||
while (1) {
|
||||
if (this->_uartStream->available()) {
|
||||
buf_msg[nb] = this->_uartStream->read();
|
||||
nb++;
|
||||
if (nb >= max_bytes) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ms = (uint32_t)(millis() - stime);
|
||||
if (ms >= timeout_ms) {
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
// Relax 5ms to avoid watchdog reset
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
#endif
|
||||
}
|
||||
return nb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check reponse
|
||||
*
|
||||
* @param func Modbus function code
|
||||
* @param nb Number of byte
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool S8::validResponse(uint8_t func, uint8_t nb) {
|
||||
uint16_t crc16;
|
||||
bool result = false;
|
||||
|
||||
if (nb >= 7) {
|
||||
crc16 = AgMb16Crc(buf_msg, nb - 2);
|
||||
if ((buf_msg[nb - 2] == (crc16 & 0x00FF)) &&
|
||||
(buf_msg[nb - 1] == ((crc16 >> 8) & 0x00FF))) {
|
||||
|
||||
if (buf_msg[0] == MODBUS_ANY_ADDRESS &&
|
||||
(buf_msg[1] == MODBUS_FUNC_READ_HOLDING_REGISTERS ||
|
||||
buf_msg[1] == MODBUS_FUNC_READ_INPUT_REGISTERS) &&
|
||||
buf_msg[2] == nb - 5) {
|
||||
AgLog("Valid response");
|
||||
result = true;
|
||||
} else {
|
||||
AgLog("Err: Unexpected response!");
|
||||
}
|
||||
} else {
|
||||
AgLog("Err: Checksum/length is invalid!");
|
||||
}
|
||||
} else {
|
||||
AgLog("Err: Invalid length!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check response length
|
||||
*
|
||||
* @param func Modbus function code
|
||||
* @param nb Number of bytes
|
||||
* @param len Length
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool S8::validResponseLenght(uint8_t func, uint8_t nb, uint8_t len) {
|
||||
bool result = false;
|
||||
if (nb == len) {
|
||||
result = validResponse(func, nb);
|
||||
} else {
|
||||
AgLog("Err: Unexpected length!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void S8::sendCommand(uint8_t func, uint16_t reg, uint16_t value) {
|
||||
uint16_t crc16;
|
||||
|
||||
if (((func == MODBUS_FUNC_READ_HOLDING_REGISTERS ||
|
||||
func == MODBUS_FUNC_READ_INPUT_REGISTERS) &&
|
||||
value >= 1) ||
|
||||
(func == MODBUS_FUNC_WRITE_SINGLE_REGISTER)) {
|
||||
buf_msg[0] = MODBUS_ANY_ADDRESS; // Address
|
||||
buf_msg[1] = func; // Function
|
||||
buf_msg[2] = (reg >> 8) & 0x00FF; // High-input register
|
||||
buf_msg[3] = reg & 0x00FF; // Low-input register
|
||||
buf_msg[4] = (value >> 8) & 0x00FF; // High-word to read or setup
|
||||
buf_msg[5] = value & 0x00FF; // Low-word to read or setup
|
||||
crc16 = AgMb16Crc(buf_msg, 6);
|
||||
buf_msg[6] = crc16 & 0x00FF;
|
||||
buf_msg[7] = (crc16 >> 8) & 0x00FF;
|
||||
uartWriteBytes(8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set Auto calib baseline period
|
||||
*
|
||||
* @param hours Number of hour will trigger auto calib: [0, 4800], 0: disable
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool S8::setAbcPeriod(int hours) {
|
||||
if (isBegin() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int curCalib = getCalibPeriodABC();
|
||||
if (curCalib == hours) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return setCalibPeriodABC(hours);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current 'ABC' calib period
|
||||
*
|
||||
* @return int Hour
|
||||
*/
|
||||
int S8::getAbcPeriod(void) { return getCalibPeriodABC(); }
|
134
src/S8/S8.h
Normal file
134
src/S8/S8.h
Normal file
@ -0,0 +1,134 @@
|
||||
#ifndef _S8_H_
|
||||
#define _S8_H_
|
||||
|
||||
#include "../main/BoardDef.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
/**
|
||||
* @brief The class define how to handle the senseair S8 sensor (CO2 sensor)
|
||||
*/
|
||||
class S8 {
|
||||
public:
|
||||
const int S8_BAUDRATE =
|
||||
9600; // Device to S8 Serial baudrate (should not be changed)
|
||||
const int S8_TIMEOUT = 5000ul; // Timeout for communication in milliseconds
|
||||
const int S8_LEN_BUF_MSG =
|
||||
20; // Max length of buffer for communication with the sensor
|
||||
const int S8_LEN_FIRMVER = 10; // Length of software version
|
||||
|
||||
enum ModbusAddr {
|
||||
MODBUS_ANY_ADDRESS = 0XFE, // S8 uses any address
|
||||
MODBUS_FUNC_READ_HOLDING_REGISTERS = 0X03, // Read holding registers (HR)
|
||||
MODBUS_FUNC_READ_INPUT_REGISTERS = 0x04, // Read input registers (IR)
|
||||
MODBUS_FUNC_WRITE_SINGLE_REGISTER = 0x06, // Write single register (SR)
|
||||
};
|
||||
enum ModbusRegInput {
|
||||
MODBUS_IR1 = 0x0000, // MeterStatus
|
||||
MODBUS_IR2 = 0x0001, // AlarmStatus
|
||||
MODBUS_IR3 = 0x0002, // OutputStatus
|
||||
MODBUS_IR4 = 0x0003, // Space CO2
|
||||
MODBUS_IR22 = 0x0015, // PWM Output
|
||||
MODBUS_IR26 = 0x0019, // Sensor Type ID High
|
||||
MODBUS_IR27 = 0x001A, // Sensor Type ID Low
|
||||
MODBUS_IR28 = 0x001B, // Memory Map version
|
||||
MODBUS_IR29 = 0x001C, // FW version Main.Sub
|
||||
MODBUS_IR30 = 0x001D, // Sensor ID High
|
||||
MODBUS_IR31 = 0x001E, // Sensor ID Low
|
||||
};
|
||||
enum ModbusRegHolding {
|
||||
MODBUS_HR1 = 0x0000, // Acknowledgement Register
|
||||
MODBUS_HR2 = 0x0001, // Special Command Register
|
||||
MODBUS_HR32 = 0x001F, // ABC Period
|
||||
};
|
||||
enum Status {
|
||||
S8_MASK_METER_FATAL_ERROR = 0x0001, // Fatal error
|
||||
S8_MASK_METER_OFFSET_REGULATION_ERROR = 0x0002, // Offset regulation error
|
||||
S8_MASK_METER_ALGORITHM_ERROR = 0x0004, // Algorithm error
|
||||
S8_MASK_METER_OUTPUT_ERROR = 0x0008, // Output error
|
||||
S8_MASK_METER_SELF_DIAG_ERROR = 0x0010, // Self diagnostics error
|
||||
S8_MASK_METER_OUT_OF_RANGE = 0x0020, // Out of range
|
||||
S8_MASK_METER_MEMORY_ERROR = 0x0040, // Memory error
|
||||
S8_MASK_METER_ANY_ERROR = 0x007F, // Mask to detect the previous errors
|
||||
// (fatal error ... memory error)
|
||||
};
|
||||
enum OutputStatus {
|
||||
S8_MASK_OUTPUT_ALARM =
|
||||
0x0001, // Alarm output status (inverted due to Open Collector)
|
||||
S8_MASK_OUTPUT_PWM = 0x0002, // PWM output status (=1 -> full output)
|
||||
};
|
||||
enum AcknowledgementFLags {
|
||||
S8_MASK_CO2_BACKGROUND_CALIBRATION =
|
||||
0x0020, // CO2 Background calibration performed = 1
|
||||
S8_MASK_CO2_NITROGEN_CALIBRATION =
|
||||
0x0040, // CO2 Nitrogen calibration performed = 1
|
||||
};
|
||||
enum CalibrationSpecialComamnd {
|
||||
S8_CO2_BACKGROUND_CALIBRATION = 0x7C06, // CO2 Background calibration
|
||||
S8_CO2_ZERO_CALIBRATION = 0x7C07, // CO2 Zero calibration
|
||||
};
|
||||
|
||||
S8(BoardType def);
|
||||
#if defined(ESP8266)
|
||||
bool begin(void);
|
||||
bool begin(Stream *_serialDebug);
|
||||
#else
|
||||
bool begin(HardwareSerial &serial);
|
||||
#endif
|
||||
void end(void);
|
||||
int16_t getCo2(void);
|
||||
bool setBaselineCalibration(void);
|
||||
bool isBaseLineCalibrationDone(void);
|
||||
bool setAbcPeriod(int hours);
|
||||
int getAbcPeriod(void);
|
||||
|
||||
private:
|
||||
/** Variables */
|
||||
const char *TAG = "S8";
|
||||
uint8_t buf_msg[20];
|
||||
Stream *_debugStream;
|
||||
BoardType _boardDef;
|
||||
Stream *_uartStream;
|
||||
#if defined(ESP32)
|
||||
HardwareSerial *_serial;
|
||||
#endif
|
||||
bool _isBegin = false;
|
||||
uint32_t _lastInitTime;
|
||||
bool isCalib = false;
|
||||
|
||||
/** Functions */
|
||||
bool _begin(void);
|
||||
bool init(const BoardDef *bsp);
|
||||
bool init(int txPin, int rxPin);
|
||||
bool init(int txPin, int rxPin, uint32_t baud);
|
||||
bool isBegin(void);
|
||||
|
||||
void uartWriteBytes(uint8_t size); // Send bytes to sensor
|
||||
uint8_t
|
||||
uartReadBytes(uint8_t max_bytes,
|
||||
uint32_t timeout_seconds); // Read received bytes from sensor
|
||||
bool validResponse(
|
||||
uint8_t func,
|
||||
uint8_t nb); // Check if response is valid according to sent command
|
||||
bool validResponseLenght(
|
||||
uint8_t func, uint8_t nb,
|
||||
uint8_t len); // Check if response is valid according to sent command and
|
||||
// checking expected total length
|
||||
void sendCommand(uint8_t func, uint16_t reg, uint16_t value); // Send command
|
||||
|
||||
void getFirmwareVersion(char firmwver[]);
|
||||
int32_t getSensorTypeId(void);
|
||||
int32_t getSensorId(void);
|
||||
int16_t getMemoryMapVersion(void);
|
||||
int16_t getOutputPWM(void);
|
||||
int16_t getCalibPeriodABC(void);
|
||||
bool setCalibPeriodABC(int16_t period);
|
||||
bool manualCalib(void);
|
||||
int16_t getAcknowledgement(void);
|
||||
bool clearAcknowledgement(void);
|
||||
Status getStatus(void);
|
||||
int16_t getAlarmStatus(void);
|
||||
int16_t getOutputStatus(void);
|
||||
bool sendSpecialCommand(CalibrationSpecialComamnd command);
|
||||
};
|
||||
|
||||
#endif
|
73
src/S8/mb_crc.cpp
Normal file
73
src/S8/mb_crc.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include "mb_crc.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/* ModBus CRC routine extracted from
|
||||
* https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf */
|
||||
|
||||
/* Table of CRC values for high–order byte */
|
||||
static const uint8_t auchCRCHi[] = {
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
||||
0x00, 0xC1, 0x81, 0x40};
|
||||
|
||||
/* Table of CRC values for low–order byte */
|
||||
static const uint8_t auchCRCLo[] = {
|
||||
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
|
||||
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
|
||||
0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
|
||||
0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
|
||||
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
|
||||
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
|
||||
0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
|
||||
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
|
||||
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
|
||||
0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
|
||||
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
|
||||
0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
|
||||
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
|
||||
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
|
||||
0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
|
||||
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
|
||||
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
|
||||
0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
|
||||
0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
|
||||
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
||||
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
|
||||
0x41, 0x81, 0x80, 0x40};
|
||||
|
||||
uint16_t AgMb16Crc(uint8_t *puchMsg, uint16_t usDataLen) {
|
||||
/*
|
||||
puchMsg -> message to calculate CRC upon
|
||||
usDataLen -> quantity of bytes in message
|
||||
*/
|
||||
uint8_t uchCRCHi = 0xFF; /* high byte of CRC initialized */
|
||||
uint8_t uchCRCLo = 0xFF; /* low byte of CRC initialized */
|
||||
uint16_t uIndex; /* will index into CRC lookup table */
|
||||
|
||||
while (usDataLen--) /* pass through message buffer */
|
||||
{
|
||||
uIndex = uchCRCLo ^ *puchMsg++; /* calculate the CRC */
|
||||
uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex];
|
||||
uchCRCHi = auchCRCLo[uIndex];
|
||||
}
|
||||
return (uchCRCHi << 8 | uchCRCLo);
|
||||
}
|
8
src/S8/mb_crc.h
Normal file
8
src/S8/mb_crc.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef _AIR_GRADIENT_MODBUS_CRC_H_
|
||||
#define _AIR_GRADIENT_MODBUS_CRC_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint16_t AgMb16Crc(uint8_t *buf, uint16_t len);
|
||||
|
||||
#endif /** _AIR_GRADIENT_MODBUS_CRC_H_ */
|
Reference in New Issue
Block a user