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

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