Files
P1-wifi-esp12e/P1_gateway_FW/lib/dsmr/reader.h
2021-06-14 08:24:14 +02:00

243 lines
7.5 KiB
C++

/**
* Arduino DSMR parser.
*
* This software is licensed under the MIT License.
*
* Copyright (c) 2015 Matthijs Kooijman <matthijs@stdin.nl>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* P1 reader, that takes care of toggling a request pin, reading data
* from a serial port and parsing it.
*/
#ifndef DSMR_INCLUDE_READER_H
#define DSMR_INCLUDE_READER_H
#include <Arduino.h>
#include "crc16.h"
#include "parser.h"
namespace dsmr {
/**
* Controls the request pin on the P1 port to enable (periodic)
* transmission of messages and reads those messages.
*
* To enable the request pin, call enable(). This lets the Smart Meter
* start periodically sending messages. While the request pin is
* enabled, loop() should be regularly called to read pending bytes.
*
* Once a full and correct message is received, loop() (and available())
* start returning true, until the message is cleared. You can then
* either read the raw message using raw(), or parse it using parse().
*
* The message is cleared when:
* - clear() is called
* - parse() is called
* - loop() is called and the start of a new message is available
*
* When disable is called, the request pin is disabled again and any
* partial message is discarded. Any bytes received while disabled are
* dropped.
*/
class P1Reader {
public:
/**
* Create a new P1Reader. The stream passed should be the serial
* port to which the P1 TX pin is connected. The req_pin is the
* pin connected to the request pin. The pin is configured as an
* output, the Stream is assumed to be already set up (e.g. baud
* rate configured).
*/
P1Reader(Stream *stream, uint8_t req_pin)
: stream(stream), req_pin(req_pin), once(false), state(State::DISABLED_STATE) {
pinMode(req_pin, OUTPUT);
digitalWrite(req_pin, LOW);
}
/**
* Enable the request pin, to request data on the P1 port.
* @param once When true, the request pin is automatically
* disabled once a complete and correct message was
* receivedc. When false, the request pin stays
* enabled, so messages will continue to be sent
* periodically.
*/
void enable(bool once) {
digitalWrite(this->req_pin, HIGH);
this->state = State::WAITING_STATE;
this->once = once;
}
/* Disable the request pin again, to stop data from being sent on
* the P1 port. This will also clear any incomplete data that was
* previously received, but a complete message will be kept until
* clear() is called.
*/
void disable() {
digitalWrite(this->req_pin, LOW);
this->state = State::DISABLED_STATE;
if (!this->_available)
this->buffer = "";
// Clear any pending bytes
while(this->stream->read() >= 0) /* nothing */;
}
/**
* Returns true when a complete and correct message was received,
* until it is cleared.
*/
bool available() {
return this->_available;
}
/**
* Check for new data to read. Should be called regularly, such as
* once every loop. Returns true if a complete message is available
* (just like available).
*/
bool loop() {
while(true) {
if (state == State::CHECKSUM_STATE) {
// Let the Stream buffer the CRC bytes. Convert to size_t to
// prevent unsigned vs signed comparison
if ((size_t)this->stream->available() < CrcParser::CRC_LEN)
return false;
char buf[CrcParser::CRC_LEN];
for (uint8_t i = 0; i < CrcParser::CRC_LEN; ++i)
buf[i] = this->stream->read();
ParseResult<uint16_t> crc = CrcParser::parse(buf, buf + lengthof(buf));
// Prepare for next message
state = State::WAITING_STATE;
if (!crc.err && crc.result == this->crc) {
// Message complete, checksum correct
this->_available = true;
if (once)
this->disable();
return true;
}
} else {
// For other states, read bytes one by one
int c = this->stream->read();
if (c < 0)
return false;
switch (this->state) {
case State::DISABLED_STATE:
// Where did this byte come from? Just toss it
break;
case State::WAITING_STATE:
if (c == '/') {
this->state = State::READING_STATE;
// Include the / in the CRC
this->crc = _crc16_update(0, c);
this->clear();
}
break;
case State::READING_STATE:
// Include the ! in the CRC
this->crc = _crc16_update(this->crc, c);
if (c == '!')
this->state = State::CHECKSUM_STATE;
else
buffer.concat((char)c);
break;
case State::CHECKSUM_STATE:
// This cannot happen (given the surrounding if), but the
// compiler is not smart enough to see this, so list this
// case to prevent a warning.
abort();
break;
}
}
}
return false;
}
/**
* Returns the data read so far.
*/
const String &raw() {
return buffer;
}
/**
* If a complete message has been received, parse it and store the
* result into the ParsedData object passed.
*
* After parsing, the message is cleared.
*
* If parsing fails, false is returned. If err is passed, the error
* message is appended to that string.
*/
template<typename... Ts>
bool parse(ParsedData<Ts...> *data, String *err) {
const char *str = buffer.c_str(), *end = buffer.c_str() + buffer.length();
ParseResult<void> res = P1Parser::parse_data(data, str, end);
if (res.err && err)
*err = res.fullError(str, end);
// Clear the message
this->clear();
return res.err == NULL;
}
/**
* Clear any complete message from the buffer.
*/
void clear() {
if (_available) {
buffer = "";
_available = false;
}
}
protected:
Stream *stream;
uint8_t req_pin;
enum class State : uint8_t {
DISABLED_STATE,
WAITING_STATE,
READING_STATE,
CHECKSUM_STATE,
};
bool _available;
bool once;
State state;
String buffer;
uint16_t crc;
};
} // namespace dsmr
#endif // DSMR_INCLUDE_READER_H