Skip to content

Commit

Permalink
feat(api): fully re-write implementation, add preheating logic
Browse files Browse the repository at this point in the history
BREAKING CHANGE: dropping `Mhz19MeasuringRange::Ppm_10000`.
`enableAutoCalibration()` renamed to `enableAutoBaseCalibration()`,
`disableAutoCalibration()` renamed to `disableAutoBaseCalibration()`
  • Loading branch information
malokhvii-eduard committed Nov 30, 2021
1 parent 1abfc9c commit df12e9a
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 243 deletions.
63 changes: 27 additions & 36 deletions include/Mhz19.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,69 @@

#include <stdint.h>

#ifndef ARDUINO_MHZ19_SERIAL_TIMEOUT
#define ARDUINO_MHZ19_SERIAL_TIMEOUT 500
#endif

#ifndef ARDUINO_MHZ19_UNIT_TEST
/* Arduino */
#include <Arduino.h>

#define __NOT_VIRTUAL_METHOD
#define __NOT_VIRTUAL
#else
/* Mocks */
#include <mocks/Arduino.h>

#define __NOT_VIRTUAL_METHOD virtual
#define __NOT_VIRTUAL virtual

#define protected public
#define private public
#define final
#endif

namespace internal {

enum Mhz19Command : uint8_t {
Mhz19CommandGetCarbonDioxide = 0x86,
Mhz19CommandSetMeasuringRange = 0x99,
Mhz19CommandSetAutoCalibration = 0x79,
Mhz19CommandCalibrateToZeroPoint = 0x87,
Mhz19CommandCalibrateToSpanPoint = 0x88
};

enum : size_t { Mhz19PacketLength = 9 };

}; // namespace internal
#ifndef MHZ19_PREHEATING_DURATION
#define MHZ19_PREHEATING_DURATION 180000 // 3 minutes
#endif

enum class Mhz19MeasuringRange : uint16_t {
Ppm_1000 = 1000,
Ppm_2000 = 2000,
Ppm_3000 = 3000,
Ppm_5000 = 5000,
Ppm_10000 = 10000
Ppm_5000 = 5000
};

class Mhz19 {
public:
Mhz19();

#ifdef ARDUINO_MHZ19_UNIT_TEST
virtual ~Mhz19();
__NOT_VIRTUAL ~Mhz19();
#endif

void begin(Stream *serial);
void begin(Stream* serial);

int16_t getCarbonDioxide() const;
bool isReady() const;
int getCarbonDioxide() const;

void setMeasuringRange(const Mhz19MeasuringRange measuringRange);
void enableAutoCalibration();
void disableAutoCalibration();
bool setMeasuringRange(const Mhz19MeasuringRange measuringRange);
bool enableAutoBaseCalibration();
bool disableAutoBaseCalibration();

void calibrateToZeroPoint();
bool calibrateToZeroPoint();
bool calibrateToSpanPoint(const uint16_t spanPoint);

private:
static uint8_t calculatePacketCheckSum(const uint8_t *packet);
static const size_t PacketLength = 9;
static const uint8_t CommandRead[];
static const uint8_t CommandEnableAutoBaseCalibration[];
static const uint8_t CommandDisableAutoBaseCalibration[];
static const uint8_t CommandCalibrateToZeroPoint[];

static uint8_t calculatePacketCheckSum(const uint8_t* packet);

__NOT_VIRTUAL_METHOD bool readPacket(const uint8_t command,
uint8_t *packet) const;
__NOT_VIRTUAL_METHOD void writePacket(
const uint8_t command, const uint8_t byte3, const uint8_t byte4,
const uint8_t byte5, const uint8_t byte6, const uint8_t byte7) const;
__NOT_VIRTUAL bool sendCommand(const uint8_t* command) const;

Stream *serial_;
Stream* serial_;
mutable bool isPreheatingDone_;
};

#undef __NOT_VIRTUAL_METHOD
#undef __NOT_VIRTUAL

#ifdef ARDUINO_MHZ19_UNIT_TEST
#undef protected
Expand Down
132 changes: 81 additions & 51 deletions src/Mhz19.cc
Original file line number Diff line number Diff line change
@@ -1,61 +1,107 @@
#include <Mhz19.h>

const uint8_t Mhz19::CommandRead[Mhz19::PacketLength] = {
0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
const uint8_t Mhz19::CommandEnableAutoBaseCalibration[Mhz19::PacketLength] = {
0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00, 0xE6};
const uint8_t Mhz19::CommandDisableAutoBaseCalibration[Mhz19::PacketLength] = {
0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86};
const uint8_t Mhz19::CommandCalibrateToZeroPoint[Mhz19::PacketLength] = {
0xFF, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78};

Mhz19::Mhz19() : serial_(nullptr), isPreheatingDone_(false) {}

#ifdef ARDUINO_MHZ19_UNIT_TEST

Mhz19::~Mhz19() {}

#endif

void Mhz19::begin(Stream *serial) { serial_ = serial; }
void Mhz19::begin(Stream* serial) { serial_ = serial; }

bool Mhz19::isReady() const {
if (isPreheatingDone_) {
return true;
}

if (millis() > MHZ19_PREHEATING_DURATION) {
isPreheatingDone_ = true;
return true;
}

return false;
}

int Mhz19::getCarbonDioxide() const {
assert(serial_ != nullptr);

int carbonDioxide = -1;

if (!isReady()) {
return carbonDioxide;
}

uint8_t response[PacketLength];
serial_->write(CommandRead, PacketLength);

int16_t Mhz19::getCarbonDioxide() const {
writePacket(::internal::Mhz19CommandGetCarbonDioxide, 0, 0, 0, 0, 0);
if (serial_->available()) {
serial_->readBytes(response, PacketLength);

uint8_t packet[::internal::Mhz19PacketLength];
if (!readPacket(::internal::Mhz19CommandGetCarbonDioxide, packet)) {
return -1;
auto checkSum = calculatePacketCheckSum(response);
if (response[0] == 0xFF && response[1] == CommandRead[2] &&
response[8] == checkSum) {
auto high = static_cast<unsigned int>(response[2]);
auto low = static_cast<unsigned int>(response[3]);
carbonDioxide = static_cast<int>((256 * high) + low);
}
}

return (256 * static_cast<int8_t>(packet[2])) +
static_cast<int8_t>(packet[3]);
return carbonDioxide;
}

void Mhz19::setMeasuringRange(const Mhz19MeasuringRange measuringRange) {
auto low = static_cast<uint8_t>(static_cast<uint16_t>(measuringRange) / 256);
auto high = static_cast<uint8_t>(static_cast<uint16_t>(measuringRange) % 256);
bool Mhz19::setMeasuringRange(const Mhz19MeasuringRange measuringRange) {
auto low = static_cast<uint8_t>(static_cast<uint16_t>(measuringRange) % 256);
auto high = static_cast<uint8_t>(static_cast<uint16_t>(measuringRange) / 256);

uint8_t command[PacketLength] = {0xFF, 0x01, 0x99, 0x00, 0x00,
0x00, high, low, 0x00};
command[8] = calculatePacketCheckSum(command);

writePacket(::internal::Mhz19CommandSetMeasuringRange, 0, 0, 0, low, high);
return sendCommand(command);
}

void Mhz19::enableAutoCalibration() {
writePacket(::internal::Mhz19CommandSetAutoCalibration, 0xA0, 0, 0, 0, 0);
bool Mhz19::enableAutoBaseCalibration() {
return sendCommand(CommandEnableAutoBaseCalibration);
}

void Mhz19::disableAutoCalibration() {
writePacket(::internal::Mhz19CommandSetAutoCalibration, 0, 0, 0, 0, 0);
bool Mhz19::disableAutoBaseCalibration() {
return sendCommand(CommandDisableAutoBaseCalibration);
}

void Mhz19::calibrateToZeroPoint() {
writePacket(::internal::Mhz19CommandCalibrateToZeroPoint, 0, 0, 0, 0, 0);
bool Mhz19::calibrateToZeroPoint() {
return sendCommand(CommandCalibrateToZeroPoint);
}

bool Mhz19::calibrateToSpanPoint(const uint16_t spanPoint) {
if ((spanPoint < static_cast<uint16_t>(Mhz19MeasuringRange::Ppm_1000)) ||
(spanPoint > static_cast<uint16_t>(Mhz19MeasuringRange::Ppm_10000))) {
(spanPoint > static_cast<uint16_t>(Mhz19MeasuringRange::Ppm_5000))) {
return false;
}

auto low = static_cast<uint8_t>(spanPoint / 256);
auto high = static_cast<uint8_t>(spanPoint % 256);
writePacket(::internal::Mhz19CommandCalibrateToSpanPoint, low, high, 0, 0, 0);
auto low = static_cast<uint8_t>(spanPoint % 256);
auto high = static_cast<uint8_t>(spanPoint / 256);

return true;
uint8_t command[PacketLength] = {0xFF, 0x01, 0x88, high, low,
0x00, 0x00, 0x00, 0x00};
command[8] = calculatePacketCheckSum(command);

return sendCommand(command);
}

uint8_t Mhz19::calculatePacketCheckSum(const uint8_t *packet) {
uint8_t Mhz19::calculatePacketCheckSum(const uint8_t* packet) {
uint8_t checkSum = 0;

for (size_t i = 1; i < ::internal::Mhz19PacketLength - 1; i++) {
for (size_t i = 1; i < PacketLength - 1; i++) {
checkSum += packet[i];
}

Expand All @@ -64,35 +110,19 @@ uint8_t Mhz19::calculatePacketCheckSum(const uint8_t *packet) {
return checkSum;
}

bool Mhz19::readPacket(const uint8_t command, uint8_t *packet) const {
auto duration = millis();
while (serial_->available() <= 0) {
if (millis() - duration >= ARDUINO_MHZ19_SERIAL_TIMEOUT) {
return false;
}
}
bool Mhz19::sendCommand(const uint8_t* command) const {
assert(serial_ != nullptr);

memset(packet, 0, ::internal::Mhz19PacketLength);
serial_->readBytes(packet, ::internal::Mhz19PacketLength);
uint8_t response[PacketLength];

auto checkSum = calculatePacketCheckSum(packet);
if (packet[0] != 0xFF || packet[1] != command || packet[8] != checkSum) {
return false;
}
return true;
}
serial_->write(command, PacketLength);
serial_->readBytes(response, PacketLength);

void Mhz19::writePacket(const uint8_t command, const uint8_t byte3,
const uint8_t byte4, const uint8_t byte5,
const uint8_t byte6, const uint8_t byte7) const {
uint8_t packet[::internal::Mhz19PacketLength] = {
0xFF, 0x1, command, byte3, byte4, byte5, byte6, byte7, 0};
packet[8] = calculatePacketCheckSum(packet);

while (serial_->available() > 0) {
serial_->read();
auto checkSum = calculatePacketCheckSum(response);
if (response[0] != 0xFF || response[1] != command[2] ||
response[8] != checkSum) {
return false;
}

serial_->write(packet, ::internal::Mhz19PacketLength);
serial_->flush();
return true;
}
Loading

0 comments on commit df12e9a

Please sign in to comment.