-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for CO2 measurements via Senseair S8
- Loading branch information
Showing
8 changed files
with
251 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
#include "co2.hh" | ||
#include "crc16.hh" | ||
#include "driver/uart.h" | ||
#include "esp_err.h" | ||
#include "measurement.hh" | ||
#include "time.hh" | ||
#include <cstdio> | ||
#include <esp_log.h> | ||
#include <sys/cdefs.h> | ||
|
||
static const char *const kTag = "co2"; | ||
|
||
namespace co2::cmd { | ||
|
||
static Command initCmd(Command &&cmd) { | ||
const auto *const bytes = reinterpret_cast<const uint8_t *>(&cmd); | ||
cmd.crc = crc16(bytes, sizeof(cmd) - sizeof(cmd.crc)); | ||
return cmd; | ||
} | ||
|
||
static const Command kCmdReadCo2Level = initCmd({ | ||
.address = kAddressAny, | ||
.functionCode = kReadInputRegisters, | ||
.startingAddress = PP_HTONS(3), | ||
.quantityOfRegisters = PP_HTONS(1), | ||
}); | ||
|
||
} // namespace co2::cmd | ||
|
||
namespace co2 { | ||
|
||
[[noreturn]] void Sensor::collectionTask(void *const arg) { | ||
Sensor &sensor{*reinterpret_cast<Sensor *>(arg)}; | ||
Measurement ms{.type = MeasurementType::CO2, .sensor = sensor.name}; | ||
|
||
TickType_t lastWake = xTaskGetTickCount(); | ||
|
||
while (true) { | ||
const auto written = sensor.writeCommand(cmd::kCmdReadCo2Level); | ||
|
||
if (written != sizeof(cmd::kCmdReadCo2Level)) { | ||
ESP_LOGE(kTag, "could not send command (written %d bytes)", written); | ||
vTaskDelay(secToTicks(5)); | ||
sensor.flushInput(); | ||
sensor.flushOutput(secToTicks(1)); | ||
continue; | ||
} | ||
|
||
const std::optional<Co2Level> co2 = sensor.readCo2(); | ||
|
||
if (co2.has_value()) { | ||
ms.time = getTimestamp(); | ||
ms.co2 = co2.value(); | ||
sensor.queue->put(ms, portMAX_DELAY); | ||
|
||
ESP_LOGI(kTag, "CO2 concentration: %d PPM", ms.co2); | ||
} else { | ||
ESP_LOGE(kTag, "could not receive CO2 data from sensor"); | ||
} | ||
|
||
vTaskDelayUntil(&lastWake, secToTicks(4)); | ||
} | ||
} | ||
|
||
int Sensor::writeCommand(const cmd::Command &cmd) { | ||
return uart_write_bytes(port, &cmd, sizeof(cmd)); | ||
} | ||
|
||
esp_err_t Sensor::flushInput() { return uart_flush_input(port); } | ||
|
||
esp_err_t Sensor::flushOutput(const TickType_t wait) { | ||
return uart_wait_tx_done(port, wait); | ||
} | ||
|
||
void Sensor::start(Queue<Measurement> &msQueue) { | ||
queue = &msQueue; | ||
|
||
constexpr uart_config_t conf{.baud_rate = 9600, | ||
.data_bits = UART_DATA_8_BITS, | ||
.parity = UART_PARITY_DISABLE, | ||
.stop_bits = UART_STOP_BITS_1, | ||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, | ||
.source_clk = UART_SCLK_APB}; | ||
|
||
constexpr size_t rxBuf = sizeof(cmd::Co2Response) * 24; | ||
static_assert(rxBuf >= UART_FIFO_LEN); | ||
|
||
ESP_ERROR_CHECK(uart_driver_install(port, rxBuf, 0, 0, nullptr, 0)); | ||
ESP_ERROR_CHECK(uart_param_config(port, &conf)); | ||
ESP_ERROR_CHECK( | ||
uart_set_pin(port, txPin, rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); | ||
|
||
char buf[32]; | ||
snprintf(buf, sizeof(buf), "co2_%s", name); | ||
|
||
xTaskCreate(collectionTask, buf, KiB(2), this, 4, nullptr); | ||
} | ||
|
||
[[nodiscard]] std::optional<Co2Level> Sensor::readCo2() { | ||
cmd::Co2Response response{}; | ||
|
||
const auto read = | ||
uart_read_bytes(port, &response, sizeof(response), portMAX_DELAY); | ||
|
||
if (read != sizeof(cmd::Co2Response)) { | ||
ESP_LOGE(kTag, "invalid response length %d bytes", read); | ||
return std::nullopt; | ||
} | ||
|
||
const uint16_t crc = crc16(reinterpret_cast<const uint8_t *>(&response), | ||
sizeof(response) - sizeof(response.crc)); | ||
|
||
if (crc != response.crc) { | ||
ESP_LOGE(kTag, "invalid CRC 0x%x (expected 0x%x)", crc, response.crc); | ||
return std::nullopt; | ||
} | ||
|
||
const auto level = static_cast<Co2Level>(PP_NTOHS(response.co2)); | ||
|
||
return std::make_optional(level); | ||
} | ||
|
||
} // namespace co2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
#pragma once | ||
|
||
#include "measurement.hh" | ||
#include "queue.hh" | ||
#include <optional> | ||
|
||
namespace co2 { | ||
|
||
using Co2Level = uint16_t; | ||
|
||
namespace cmd { | ||
|
||
using Address = uint8_t; | ||
using FunctionCode = uint8_t; | ||
|
||
static constexpr Address kAddressAny = 0xfe; | ||
static constexpr FunctionCode kReadInputRegisters = 0x04; | ||
|
||
struct Command { | ||
Address address; | ||
FunctionCode functionCode; | ||
uint16_t startingAddress; | ||
uint16_t quantityOfRegisters; | ||
uint16_t crc; | ||
} __attribute__((packed)); | ||
|
||
struct Co2Response { | ||
Address address; | ||
FunctionCode functionCode; | ||
uint8_t len; | ||
uint16_t co2; | ||
uint16_t crc; | ||
} __attribute__((packed)); | ||
|
||
static_assert(sizeof(Co2Response) == 7); | ||
|
||
} // namespace cmd | ||
|
||
struct Sensor { | ||
const char *name; | ||
const uart_port_t port; | ||
const gpio_num_t rxPin; | ||
const gpio_num_t txPin; | ||
Queue<Measurement> *queue; | ||
|
||
void start(Queue<Measurement> &msQueue); | ||
|
||
private: | ||
[[noreturn]] static void collectionTask(void *arg); | ||
|
||
[[nodiscard]] std::optional<Co2Level> readCo2(); | ||
|
||
int writeCommand(const cmd::Command &cmd); | ||
esp_err_t flushInput(); | ||
esp_err_t flushOutput(TickType_t wait); | ||
}; | ||
|
||
} // namespace co2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
#include "crc16.hh" | ||
#include <cassert> | ||
|
||
static constexpr uint16_t kCrcTable[] = { | ||
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, | ||
0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01, 0x0cc0, | ||
0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, | ||
0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0, 0x1980, 0xd941, | ||
0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, | ||
0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, | ||
0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, | ||
0x1040, 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, | ||
0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, | ||
0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, | ||
0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, | ||
0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, | ||
0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, | ||
0xe7c1, 0xe681, 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, | ||
0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, | ||
0x6240, 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, | ||
0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01, | ||
0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, | ||
0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0, 0x7f80, | ||
0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, | ||
0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, | ||
0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, | ||
0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, | ||
0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, | ||
0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, 0x8801, | ||
0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, | ||
0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, | ||
0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, | ||
0x4100, 0x81c1, 0x8081, 0x4040}; | ||
|
||
uint16_t crc16(const uint8_t *data, size_t len) { | ||
assert(data); | ||
assert(len > 0); | ||
|
||
uint8_t temp = 0; | ||
uint16_t crc = 0xffff; | ||
|
||
while (len--) { | ||
temp = *data++ ^ crc; | ||
crc >>= 8; | ||
crc ^= kCrcTable[temp]; | ||
} | ||
|
||
return crc; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#include <cstddef> | ||
#include <cstdint> | ||
|
||
extern uint16_t crc16(const uint8_t *data, size_t len); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters