187 lines
6.0 KiB
C++
187 lines
6.0 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.
|
|
*
|
|
* Various utility functions
|
|
*/
|
|
|
|
#ifndef DSMR_INCLUDE_UTIL_H
|
|
#define DSMR_INCLUDE_UTIL_H
|
|
|
|
#ifdef ARDUINO_ARCH_ESP8266
|
|
#define DSMR_PROGMEM
|
|
#else
|
|
#define DSMR_PROGMEM PROGMEM
|
|
#endif
|
|
|
|
#include <Arduino.h>
|
|
|
|
namespace dsmr {
|
|
|
|
/**
|
|
* Small utility to get the length of an array at compiletime.
|
|
*/
|
|
template<typename T, unsigned int sz>
|
|
inline unsigned int lengthof(const T (&)[sz]) { return sz; }
|
|
|
|
// Hack until https://github.com/arduino/Arduino/pull/1936 is merged.
|
|
// This appends the given number of bytes from the given C string to the
|
|
// given Arduino string, without requiring a trailing NUL.
|
|
// Requires that there _is_ room for nul-termination
|
|
static void concat_hack(String& s, const char *append, size_t n) {
|
|
// Add null termination. Inefficient, but it works...
|
|
char buf[n + 1];
|
|
memcpy(buf, append, n);
|
|
buf[n] = 0;
|
|
s.concat(buf);
|
|
}
|
|
|
|
/**
|
|
* The ParseResult<T> class wraps the result of a parse function. The type
|
|
* of the result is passed as a template parameter and can be void to
|
|
* not return any result.
|
|
*
|
|
* A ParseResult can either:
|
|
* - Return an error. In this case, err is set to an error message, ctx
|
|
* is optionally set to where the error occurred. The result (if any)
|
|
* and the next pointer are meaningless.
|
|
* - Return succesfully. In this case, err and ctx are NULL, result
|
|
* contains the result (if any) and next points one past the last
|
|
* byte processed by the parser.
|
|
*
|
|
* The ParseResult class has some convenience functions:
|
|
* - succeed(result): sets the result to the given value and returns
|
|
* the ParseResult again.
|
|
* - fail(err): Set the err member to the error message passed,
|
|
* optionally sets the ctx and return the ParseResult again.
|
|
* - until(next): Set the next member and return the ParseResult again.
|
|
*
|
|
* Furthermore, ParseResults can be implicitely converted to other
|
|
* types. In this case, the error message, context and and next pointer are
|
|
* conserved, the return value is reset to the default value for the
|
|
* target type.
|
|
*
|
|
* Note that ctx points into the string being parsed, so it does not
|
|
* need to be freed, lives as long as the original string and is
|
|
* probably way longer that needed.
|
|
*/
|
|
|
|
// Superclass for ParseResult so we can specialize for void without
|
|
// having to duplicate all content
|
|
template <typename P, typename T>
|
|
struct _ParseResult {
|
|
T result;
|
|
|
|
P& succeed(T& result) {
|
|
this->result = result; return *static_cast<P*>(this);
|
|
}
|
|
P& succeed(T&& result) {
|
|
this->result = result;
|
|
return *static_cast<P*>(this);
|
|
}
|
|
};
|
|
|
|
// partial specialization for void result
|
|
template <typename P>
|
|
struct _ParseResult<P, void> {
|
|
};
|
|
|
|
// Actual ParseResult class
|
|
template <typename T>
|
|
struct ParseResult : public _ParseResult<ParseResult<T>, T> {
|
|
const char *next = NULL;
|
|
const __FlashStringHelper *err = NULL;
|
|
const char *ctx = NULL;
|
|
|
|
ParseResult& fail(const __FlashStringHelper *err, const char* ctx = NULL) {
|
|
this->err = err;
|
|
this->ctx = ctx;
|
|
return *this;
|
|
}
|
|
ParseResult& until(const char *next) {
|
|
this->next = next;
|
|
return *this;
|
|
}
|
|
ParseResult() = default;
|
|
ParseResult(const ParseResult& other) = default;
|
|
|
|
template <typename T2>
|
|
ParseResult(const ParseResult<T2>& other): next(other.next), err(other.err), ctx(other.ctx) { }
|
|
|
|
/**
|
|
* Returns the error, including context in a fancy multi-line format.
|
|
* The start and end passed are the first and one-past-the-end
|
|
* characters in the total parsed string. These are needed to properly
|
|
* limit the context output.
|
|
*/
|
|
String fullError(const char* start, const char* end) const {
|
|
String res;
|
|
if (this->ctx && start && end) {
|
|
// Find the entire line surrounding the context
|
|
const char *line_end = this->ctx;
|
|
while(line_end < end && line_end[0] != '\r' && line_end[0] != '\n') ++line_end;
|
|
const char *line_start = this->ctx;
|
|
while(line_start > start && line_start[-1] != '\r' && line_start[-1] != '\n') --line_start;
|
|
|
|
// We can now predict the context string length, so let String allocate
|
|
// memory in advance
|
|
res.reserve((line_end - line_start) + 2 + (this->ctx - line_start) + 1 + 2);
|
|
|
|
// Write the line
|
|
concat_hack(res, line_start, line_end - line_start);
|
|
res += "\r\n";
|
|
|
|
// Write a marker to point out ctx
|
|
while (line_start++ < this->ctx)
|
|
res += ' ';
|
|
res += '^';
|
|
res += "\r\n";
|
|
}
|
|
res += this->err;
|
|
return res;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An OBIS id is 6 bytes, usually noted as a-b:c.d.e.f. Here we put them
|
|
* in an array for easy parsing.
|
|
*/
|
|
struct ObisId {
|
|
uint8_t v[6];
|
|
|
|
constexpr ObisId(uint8_t a, uint8_t b = 255, uint8_t c = 255, uint8_t d = 255, uint8_t e = 255, uint8_t f = 255)
|
|
: v{a, b, c, d, e, f} { };
|
|
constexpr ObisId() : v() {} // Zeroes
|
|
|
|
bool operator==(const ObisId &other) const {
|
|
return memcmp(&v, &other.v, sizeof(v)) == 0;
|
|
}
|
|
};
|
|
|
|
} // namespace dsmr
|
|
|
|
#endif // DSMR_INCLUDE_UTIL_H
|