updated firmware
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Main included file. If you include this, you'll get everything,
|
||||
* imported into global scope
|
||||
*/
|
||||
|
||||
#ifndef DSMR_INCLUDE_DSMR_H
|
||||
#define DSMR_INCLUDE_DSMR_H
|
||||
|
||||
#include "../lib/dsmr/parser.h"
|
||||
#include "../lib/dsmr/reader.h"
|
||||
#include "../lib/dsmr/fields.h"
|
||||
|
||||
// Allow using everything without the namespace prefixes
|
||||
using namespace dsmr;
|
||||
using namespace dsmr::fields;
|
||||
|
||||
#endif // DSMR_INCLUDE_DSMR_H
|
||||
@@ -1,102 +0,0 @@
|
||||
/* CRC compatibility, adapted from the Teensy 3 core at:
|
||||
https://github.com/PaulStoffregen/cores/tree/master/teensy3
|
||||
which was in turn adapted by Paul Stoffregen from the C-only comments here:
|
||||
http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/include/util/crc16.h?revision=933&root=avr-libc&view=markup */
|
||||
|
||||
/* Copyright (c) 2002, 2003, 2004 Marek Michalkiewicz
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the name of the copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE. */
|
||||
|
||||
#ifndef _UTIL_CRC16_H_
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
#include <util/crc16.h>
|
||||
#else
|
||||
#define _UTIL_CRC16_H_
|
||||
#include <stdint.h>
|
||||
|
||||
static inline uint16_t _crc16_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused));
|
||||
static inline uint16_t _crc16_update(uint16_t crc, uint8_t data)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
crc ^= data;
|
||||
for (i = 0; i < 8; ++i) {
|
||||
if (crc & 1) {
|
||||
crc = (crc >> 1) ^ 0xA001;
|
||||
} else {
|
||||
crc = (crc >> 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static inline uint16_t _crc_xmodem_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused));
|
||||
static inline uint16_t _crc_xmodem_update(uint16_t crc, uint8_t data)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
crc = crc ^ ((uint16_t)data << 8);
|
||||
for (i=0; i<8; i++) {
|
||||
if (crc & 0x8000) {
|
||||
crc = (crc << 1) ^ 0x1021;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static inline uint16_t _crc_ccitt_update (uint16_t crc, uint8_t data) __attribute__((always_inline, unused));
|
||||
static inline uint16_t _crc_ccitt_update (uint16_t crc, uint8_t data)
|
||||
{
|
||||
data ^= (crc & 255);
|
||||
data ^= data << 4;
|
||||
|
||||
return ((((uint16_t)data << 8) | (crc >> 8)) ^ (uint8_t)(data >> 4)
|
||||
^ ((uint16_t)data << 3));
|
||||
}
|
||||
|
||||
static inline uint8_t _crc_ibutton_update(uint8_t crc, uint8_t data) __attribute__((always_inline, unused));
|
||||
static inline uint8_t _crc_ibutton_update(uint8_t crc, uint8_t data)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
crc = crc ^ data;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (crc & 0x01) {
|
||||
crc = (crc >> 1) ^ 0x8C;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,262 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Field parsing functions
|
||||
*/
|
||||
|
||||
#include "fields.h"
|
||||
|
||||
|
||||
using namespace dsmr;
|
||||
using namespace dsmr::fields;
|
||||
|
||||
// Since C++11 it is possible to define the initial values for static
|
||||
// const members in the class declaration, but if their address is
|
||||
// taken, they still need a normal definition somewhere (to allocate
|
||||
// storage).
|
||||
constexpr char units::none[];
|
||||
constexpr char units::kWh[];
|
||||
constexpr char units::Wh[];
|
||||
constexpr char units::kW[];
|
||||
constexpr char units::W[];
|
||||
constexpr char units::V[];
|
||||
constexpr char units::mV[];
|
||||
constexpr char units::A[];
|
||||
constexpr char units::mA[];
|
||||
constexpr char units::m3[];
|
||||
constexpr char units::dm3[];
|
||||
constexpr char units::GJ[];
|
||||
constexpr char units::MJ[];
|
||||
|
||||
constexpr ObisId identification::id;
|
||||
constexpr char identification::name_progmem[];
|
||||
constexpr const __FlashStringHelper *identification::name;
|
||||
|
||||
constexpr ObisId p1_version::id;
|
||||
constexpr char p1_version::name_progmem[];
|
||||
constexpr const __FlashStringHelper *p1_version::name;
|
||||
|
||||
constexpr ObisId timestamp::id;
|
||||
constexpr char timestamp::name_progmem[];
|
||||
constexpr const __FlashStringHelper *timestamp::name;
|
||||
|
||||
constexpr ObisId equipment_id::id;
|
||||
constexpr char equipment_id::name_progmem[];
|
||||
constexpr const __FlashStringHelper *equipment_id::name;
|
||||
|
||||
constexpr ObisId energy_delivered_tariff1::id;
|
||||
constexpr char energy_delivered_tariff1::name_progmem[];
|
||||
constexpr const __FlashStringHelper *energy_delivered_tariff1::name;
|
||||
|
||||
constexpr ObisId energy_delivered_tariff2::id;
|
||||
constexpr char energy_delivered_tariff2::name_progmem[];
|
||||
constexpr const __FlashStringHelper *energy_delivered_tariff2::name;
|
||||
|
||||
constexpr ObisId energy_returned_tariff1::id;
|
||||
constexpr char energy_returned_tariff1::name_progmem[];
|
||||
constexpr const __FlashStringHelper *energy_returned_tariff1::name;
|
||||
|
||||
constexpr ObisId energy_returned_tariff2::id;
|
||||
constexpr char energy_returned_tariff2::name_progmem[];
|
||||
constexpr const __FlashStringHelper *energy_returned_tariff2::name;
|
||||
|
||||
constexpr ObisId electricity_tariff::id;
|
||||
constexpr char electricity_tariff::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_tariff::name;
|
||||
|
||||
constexpr ObisId power_delivered::id;
|
||||
constexpr char power_delivered::name_progmem[];
|
||||
constexpr const __FlashStringHelper *power_delivered::name;
|
||||
|
||||
constexpr ObisId power_returned::id;
|
||||
constexpr char power_returned::name_progmem[];
|
||||
constexpr const __FlashStringHelper *power_returned::name;
|
||||
|
||||
constexpr ObisId electricity_threshold::id;
|
||||
constexpr char electricity_threshold::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_threshold::name;
|
||||
|
||||
constexpr ObisId electricity_switch_position::id;
|
||||
constexpr char electricity_switch_position::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_switch_position::name;
|
||||
|
||||
constexpr ObisId electricity_failures::id;
|
||||
constexpr char electricity_failures::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_failures::name;
|
||||
|
||||
constexpr ObisId electricity_long_failures::id;
|
||||
constexpr char electricity_long_failures::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_long_failures::name;
|
||||
|
||||
constexpr ObisId electricity_failure_log::id;
|
||||
constexpr char electricity_failure_log::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_failure_log::name;
|
||||
|
||||
constexpr ObisId electricity_sags_l1::id;
|
||||
constexpr char electricity_sags_l1::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_sags_l1::name;
|
||||
|
||||
constexpr ObisId electricity_sags_l2::id;
|
||||
constexpr char electricity_sags_l2::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_sags_l2::name;
|
||||
|
||||
constexpr ObisId electricity_sags_l3::id;
|
||||
constexpr char electricity_sags_l3::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_sags_l3::name;
|
||||
|
||||
constexpr ObisId electricity_swells_l1::id;
|
||||
constexpr char electricity_swells_l1::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_swells_l1::name;
|
||||
|
||||
constexpr ObisId electricity_swells_l2::id;
|
||||
constexpr char electricity_swells_l2::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_swells_l2::name;
|
||||
|
||||
constexpr ObisId electricity_swells_l3::id;
|
||||
constexpr char electricity_swells_l3::name_progmem[];
|
||||
constexpr const __FlashStringHelper *electricity_swells_l3::name;
|
||||
|
||||
constexpr ObisId message_short::id;
|
||||
constexpr char message_short::name_progmem[];
|
||||
constexpr const __FlashStringHelper *message_short::name;
|
||||
|
||||
constexpr ObisId message_long::id;
|
||||
constexpr char message_long::name_progmem[];
|
||||
constexpr const __FlashStringHelper *message_long::name;
|
||||
|
||||
constexpr ObisId voltage_l1::id;
|
||||
constexpr char voltage_l1::name_progmem[];
|
||||
constexpr const __FlashStringHelper *voltage_l1::name;
|
||||
|
||||
constexpr ObisId voltage_l2::id;
|
||||
constexpr char voltage_l2::name_progmem[];
|
||||
constexpr const __FlashStringHelper *voltage_l2::name;
|
||||
|
||||
constexpr ObisId voltage_l3::id;
|
||||
constexpr char voltage_l3::name_progmem[];
|
||||
constexpr const __FlashStringHelper *voltage_l3::name;
|
||||
|
||||
constexpr ObisId current_l1::id;
|
||||
constexpr char current_l1::name_progmem[];
|
||||
constexpr const __FlashStringHelper *current_l1::name;
|
||||
|
||||
constexpr ObisId current_l2::id;
|
||||
constexpr char current_l2::name_progmem[];
|
||||
constexpr const __FlashStringHelper *current_l2::name;
|
||||
|
||||
constexpr ObisId current_l3::id;
|
||||
constexpr char current_l3::name_progmem[];
|
||||
constexpr const __FlashStringHelper *current_l3::name;
|
||||
|
||||
constexpr ObisId power_delivered_l1::id;
|
||||
constexpr char power_delivered_l1::name_progmem[];
|
||||
constexpr const __FlashStringHelper *power_delivered_l1::name;
|
||||
|
||||
constexpr ObisId power_delivered_l2::id;
|
||||
constexpr char power_delivered_l2::name_progmem[];
|
||||
constexpr const __FlashStringHelper *power_delivered_l2::name;
|
||||
|
||||
constexpr ObisId power_delivered_l3::id;
|
||||
constexpr char power_delivered_l3::name_progmem[];
|
||||
constexpr const __FlashStringHelper *power_delivered_l3::name;
|
||||
|
||||
constexpr ObisId power_returned_l1::id;
|
||||
constexpr char power_returned_l1::name_progmem[];
|
||||
constexpr const __FlashStringHelper *power_returned_l1::name;
|
||||
|
||||
constexpr ObisId power_returned_l2::id;
|
||||
constexpr char power_returned_l2::name_progmem[];
|
||||
constexpr const __FlashStringHelper *power_returned_l2::name;
|
||||
|
||||
constexpr ObisId power_returned_l3::id;
|
||||
constexpr char power_returned_l3::name_progmem[];
|
||||
constexpr const __FlashStringHelper *power_returned_l3::name;
|
||||
|
||||
constexpr ObisId gas_device_type::id;
|
||||
constexpr char gas_device_type::name_progmem[];
|
||||
constexpr const __FlashStringHelper *gas_device_type::name;
|
||||
|
||||
constexpr ObisId gas_equipment_id::id;
|
||||
constexpr char gas_equipment_id::name_progmem[];
|
||||
constexpr const __FlashStringHelper *gas_equipment_id::name;
|
||||
|
||||
constexpr ObisId gas_valve_position::id;
|
||||
constexpr char gas_valve_position::name_progmem[];
|
||||
constexpr const __FlashStringHelper *gas_valve_position::name;
|
||||
|
||||
constexpr ObisId gas_delivered::id;
|
||||
constexpr char gas_delivered::name_progmem[];
|
||||
constexpr const __FlashStringHelper *gas_delivered::name;
|
||||
|
||||
constexpr ObisId thermal_device_type::id;
|
||||
constexpr char thermal_device_type::name_progmem[];
|
||||
constexpr const __FlashStringHelper *thermal_device_type::name;
|
||||
|
||||
constexpr ObisId thermal_equipment_id::id;
|
||||
constexpr char thermal_equipment_id::name_progmem[];
|
||||
constexpr const __FlashStringHelper *thermal_equipment_id::name;
|
||||
|
||||
constexpr ObisId thermal_valve_position::id;
|
||||
constexpr char thermal_valve_position::name_progmem[];
|
||||
constexpr const __FlashStringHelper *thermal_valve_position::name;
|
||||
|
||||
constexpr ObisId thermal_delivered::id;
|
||||
constexpr char thermal_delivered::name_progmem[];
|
||||
constexpr const __FlashStringHelper *thermal_delivered::name;
|
||||
|
||||
constexpr ObisId water_device_type::id;
|
||||
constexpr char water_device_type::name_progmem[];
|
||||
constexpr const __FlashStringHelper *water_device_type::name;
|
||||
|
||||
constexpr ObisId water_equipment_id::id;
|
||||
constexpr char water_equipment_id::name_progmem[];
|
||||
constexpr const __FlashStringHelper *water_equipment_id::name;
|
||||
|
||||
constexpr ObisId water_valve_position::id;
|
||||
constexpr char water_valve_position::name_progmem[];
|
||||
constexpr const __FlashStringHelper *water_valve_position::name;
|
||||
|
||||
constexpr ObisId water_delivered::id;
|
||||
constexpr char water_delivered::name_progmem[];
|
||||
constexpr const __FlashStringHelper *water_delivered::name;
|
||||
|
||||
constexpr ObisId slave_device_type::id;
|
||||
constexpr char slave_device_type::name_progmem[];
|
||||
constexpr const __FlashStringHelper *slave_device_type::name;
|
||||
|
||||
constexpr ObisId slave_equipment_id::id;
|
||||
constexpr char slave_equipment_id::name_progmem[];
|
||||
constexpr const __FlashStringHelper *slave_equipment_id::name;
|
||||
|
||||
constexpr ObisId slave_valve_position::id;
|
||||
constexpr char slave_valve_position::name_progmem[];
|
||||
constexpr const __FlashStringHelper *slave_valve_position::name;
|
||||
|
||||
constexpr ObisId slave_delivered::id;
|
||||
constexpr char slave_delivered::name_progmem[];
|
||||
constexpr const __FlashStringHelper *slave_delivered::name;
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Field parsing functions
|
||||
*/
|
||||
|
||||
#ifndef DSMR_INCLUDE_FIELDS_H
|
||||
#define DSMR_INCLUDE_FIELDS_H
|
||||
|
||||
#include "util.h"
|
||||
#include "parser.h"
|
||||
|
||||
namespace dsmr {
|
||||
|
||||
/**
|
||||
* Superclass for data items in a P1 message.
|
||||
*/
|
||||
template <typename T>
|
||||
struct ParsedField {
|
||||
template <typename F>
|
||||
void apply(F& f) {
|
||||
f.apply(*static_cast<T*>(this));
|
||||
}
|
||||
// By defaults, fields have no unit
|
||||
static const char *unit() { return ""; }
|
||||
};
|
||||
|
||||
template <typename T, size_t minlen, size_t maxlen>
|
||||
struct StringField : ParsedField<T> {
|
||||
ParseResult<void> parse(const char *str, const char *end) {
|
||||
ParseResult<String> res = StringParser::parse_string(minlen, maxlen, str, end);
|
||||
if (!res.err)
|
||||
static_cast<T*>(this)->val() = res.result;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
// A timestamp is essentially a string using YYMMDDhhmmssX format (where
|
||||
// X is W or S for wintertime or summertime). Parsing this into a proper
|
||||
// (UNIX) timestamp is hard to do generically. Parsing it into a
|
||||
// single integer needs > 4 bytes top fit and isn't very useful (you
|
||||
// cannot really do any calculation with those values). So we just parse
|
||||
// into a string for now.
|
||||
template <typename T>
|
||||
struct TimestampField : StringField<T, 13, 13> { };
|
||||
|
||||
// Value that is parsed as a three-decimal float, but stored as an
|
||||
// integer (by multiplying by 1000). Supports val() (or implicit cast to
|
||||
// float) to get the original value, and int_val() to get the more
|
||||
// efficient integer value. The unit() and int_unit() methods on
|
||||
// FixedField return the corresponding units for these values.
|
||||
struct FixedValue {
|
||||
operator float() { return val();}
|
||||
float val() { return _value / 1000.0;}
|
||||
uint32_t int_val() { return _value; }
|
||||
|
||||
uint32_t _value;
|
||||
};
|
||||
|
||||
// Floating point numbers in the message never have more than 3 decimal
|
||||
// digits. To prevent inefficient floating point operations, we store
|
||||
// them as a fixed-point number: an integer that stores the value in
|
||||
// thousands. For example, a value of 1.234 kWh is stored as 1234. This
|
||||
// effectively means that the integer value is het value in Wh. To allow
|
||||
// automatic printing of these values, both the original unit and the
|
||||
// integer unit is passed as a template argument.
|
||||
template <typename T, const char *_unit, const char *_int_unit>
|
||||
struct FixedField : ParsedField<T> {
|
||||
ParseResult<void> parse(const char *str, const char *end) {
|
||||
ParseResult<uint32_t> res = NumParser::parse(3, _unit, str, end);
|
||||
if (!res.err)
|
||||
static_cast<T*>(this)->val()._value = res.result;
|
||||
return res;
|
||||
}
|
||||
|
||||
static const char *unit() { return _unit; }
|
||||
static const char *int_unit() { return _int_unit; }
|
||||
};
|
||||
|
||||
struct TimestampedFixedValue : public FixedValue {
|
||||
String timestamp;
|
||||
};
|
||||
|
||||
// Some numerical values are prefixed with a timestamp. This is simply
|
||||
// both of them concatenated, e.g. 0-1:24.2.1(150117180000W)(00473.789*m3)
|
||||
template <typename T, const char *_unit, const char *_int_unit>
|
||||
struct TimestampedFixedField : public FixedField<T, _unit, _int_unit> {
|
||||
ParseResult<void> parse(const char *str, const char *end) {
|
||||
// First, parse timestamp
|
||||
ParseResult<String> res = StringParser::parse_string(13, 13, str, end);
|
||||
if (res.err)
|
||||
return res;
|
||||
|
||||
static_cast<T*>(this)->val().timestamp = res.result;
|
||||
|
||||
// Which is immediately followed by the numerical value
|
||||
return FixedField<T, _unit, _int_unit>::parse(res.next, end);
|
||||
}
|
||||
};
|
||||
|
||||
// A integer number is just represented as an integer.
|
||||
template <typename T, const char *_unit>
|
||||
struct IntField : ParsedField<T> {
|
||||
ParseResult<void> parse(const char *str, const char *end) {
|
||||
ParseResult<uint32_t> res = NumParser::parse(0, _unit, str, end);
|
||||
if (!res.err)
|
||||
static_cast<T*>(this)->val() = res.result;
|
||||
return res;
|
||||
}
|
||||
|
||||
static const char *unit() { return _unit; }
|
||||
};
|
||||
|
||||
// A RawField is not parsed, the entire value (including any
|
||||
// parenthesis around it) is returned as a string.
|
||||
template <typename T>
|
||||
struct RawField : ParsedField<T> {
|
||||
ParseResult<void> parse(const char *str, const char *end) {
|
||||
// Just copy the string verbatim value without any parsing
|
||||
concat_hack(static_cast<T*>(this)->val(), str, end - str);
|
||||
return ParseResult<void>().until(end);
|
||||
}
|
||||
};
|
||||
|
||||
namespace fields {
|
||||
|
||||
struct units {
|
||||
// These variables are inside a struct, since that allows us to make
|
||||
// them constexpr and define their values here, but define the storage
|
||||
// in a cpp file. Global const(expr) variables have implicitly
|
||||
// internal linkage, meaning each cpp file that includes us will have
|
||||
// its own copy of the variable. Since we take the address of these
|
||||
// variables (passing it as a template argument), this would cause a
|
||||
// compiler warning. By putting these in a struct, this is prevented.
|
||||
static constexpr char none[] = "";
|
||||
static constexpr char kWh[] = "kWh";
|
||||
static constexpr char Wh[] = "Wh";
|
||||
static constexpr char kW[] = "kW";
|
||||
static constexpr char W[] = "W";
|
||||
static constexpr char V[] = "V";
|
||||
static constexpr char mV[] = "mV";
|
||||
static constexpr char A[] = "A";
|
||||
static constexpr char mA[] = "mA";
|
||||
static constexpr char m3[] = "m3";
|
||||
static constexpr char dm3[] = "dm3";
|
||||
static constexpr char GJ[] = "GJ";
|
||||
static constexpr char MJ[] = "MJ";
|
||||
};
|
||||
|
||||
const uint8_t GAS_MBUS_ID = 1;
|
||||
const uint8_t WATER_MBUS_ID = 2;
|
||||
const uint8_t THERMAL_MBUS_ID = 3;
|
||||
const uint8_t SLAVE_MBUS_ID = 4;
|
||||
|
||||
#define DEFINE_FIELD(fieldname, value_t, obis, field_t, field_args...) \
|
||||
struct fieldname : field_t<fieldname, ##field_args> { \
|
||||
value_t fieldname; \
|
||||
bool fieldname ## _present = false; \
|
||||
static constexpr ObisId id = obis; \
|
||||
static constexpr char name_progmem[] DSMR_PROGMEM = #fieldname; \
|
||||
static constexpr const __FlashStringHelper *name = reinterpret_cast<const __FlashStringHelper*>(&name_progmem); \
|
||||
value_t& val() { return fieldname; } \
|
||||
bool& present() { return fieldname ## _present; } \
|
||||
}
|
||||
|
||||
/* Meter identification. This is not a normal field, but a
|
||||
* specially-formatted first line of the message */
|
||||
DEFINE_FIELD(identification, String, ObisId(255, 255, 255, 255, 255, 255), RawField);
|
||||
|
||||
/* Version information for P1 output */
|
||||
DEFINE_FIELD(p1_version, String, ObisId(1, 3, 0, 2, 8), StringField, 2, 2);
|
||||
|
||||
/* Date-time stamp of the P1 message */
|
||||
DEFINE_FIELD(timestamp, String, ObisId(0, 0, 1, 0, 0), TimestampField);
|
||||
|
||||
/* Equipment identifier */
|
||||
DEFINE_FIELD(equipment_id, String, ObisId(0, 0, 96, 1, 1), StringField, 0, 96);
|
||||
|
||||
/* Meter Reading electricity delivered to client (Tariff 1) in 0,001 kWh */
|
||||
DEFINE_FIELD(energy_delivered_tariff1, FixedValue, ObisId(1, 0, 1, 8, 1), FixedField, units::kWh, units::Wh);
|
||||
/* Meter Reading electricity delivered to client (Tariff 2) in 0,001 kWh */
|
||||
DEFINE_FIELD(energy_delivered_tariff2, FixedValue, ObisId(1, 0, 1, 8, 2), FixedField, units::kWh, units::Wh);
|
||||
/* Meter Reading electricity delivered by client (Tariff 1) in 0,001 kWh */
|
||||
DEFINE_FIELD(energy_returned_tariff1, FixedValue, ObisId(1, 0, 2, 8, 1), FixedField, units::kWh, units::Wh);
|
||||
/* Meter Reading electricity delivered by client (Tariff 2) in 0,001 kWh */
|
||||
DEFINE_FIELD(energy_returned_tariff2, FixedValue, ObisId(1, 0, 2, 8, 2), FixedField, units::kWh, units::Wh);
|
||||
|
||||
/* Tariff indicator electricity. The tariff indicator can also be used
|
||||
* to switch tariff dependent loads e.g boilers. This is the
|
||||
* responsibility of the P1 user */
|
||||
DEFINE_FIELD(electricity_tariff, String, ObisId(0, 0, 96, 14, 0), StringField, 4, 4);
|
||||
|
||||
/* Actual electricity power delivered (+P) in 1 Watt resolution */
|
||||
DEFINE_FIELD(power_delivered, FixedValue, ObisId(1, 0, 1, 7, 0), FixedField, units::kW, units::W);
|
||||
/* Actual electricity power received (-P) in 1 Watt resolution */
|
||||
DEFINE_FIELD(power_returned, FixedValue, ObisId(1, 0, 2, 7, 0), FixedField, units::kW, units::W);
|
||||
|
||||
/* The actual threshold Electricity in kW. Removed in 4.0.7 / 4.2.2 / 5.0 */
|
||||
DEFINE_FIELD(electricity_threshold, FixedValue, ObisId(0, 0, 17, 0, 0), FixedField, units::kW, units::W);
|
||||
|
||||
/* Switch position Electricity (in/out/enabled). Removed in 4.0.7 / 4.2.2 / 5.0 */
|
||||
DEFINE_FIELD(electricity_switch_position, uint8_t, ObisId(0, 0, 96, 3, 10), IntField, units::none);
|
||||
|
||||
/* Number of power failures in any phase */
|
||||
DEFINE_FIELD(electricity_failures, uint32_t, ObisId(0, 0, 96, 7, 21), IntField, units::none);
|
||||
/* Number of long power failures in any phase */
|
||||
DEFINE_FIELD(electricity_long_failures, uint32_t, ObisId(0, 0, 96, 7, 9), IntField, units::none);
|
||||
|
||||
/* Power Failure Event Log (long power failures) */
|
||||
DEFINE_FIELD(electricity_failure_log, String, ObisId(1, 0, 99, 97, 0), RawField);
|
||||
|
||||
/* Number of voltage sags in phase L1 */
|
||||
DEFINE_FIELD(electricity_sags_l1, uint32_t, ObisId(1, 0, 32, 32, 0), IntField, units::none);
|
||||
/* Number of voltage sags in phase L2 (polyphase meters only) */
|
||||
DEFINE_FIELD(electricity_sags_l2, uint32_t, ObisId(1, 0, 52, 32, 0), IntField, units::none);
|
||||
/* Number of voltage sags in phase L3 (polyphase meters only) */
|
||||
DEFINE_FIELD(electricity_sags_l3, uint32_t, ObisId(1, 0, 72, 32, 0), IntField, units::none);
|
||||
|
||||
/* Number of voltage swells in phase L1 */
|
||||
DEFINE_FIELD(electricity_swells_l1, uint32_t, ObisId(1, 0, 32, 36, 0), IntField, units::none);
|
||||
/* Number of voltage swells in phase L2 (polyphase meters only) */
|
||||
DEFINE_FIELD(electricity_swells_l2, uint32_t, ObisId(1, 0, 52, 36, 0), IntField, units::none);
|
||||
/* Number of voltage swells in phase L3 (polyphase meters only) */
|
||||
DEFINE_FIELD(electricity_swells_l3, uint32_t, ObisId(1, 0, 72, 36, 0), IntField, units::none);
|
||||
|
||||
/* Text message codes: numeric 8 digits (Note: Missing from 5.0 spec)
|
||||
* */
|
||||
DEFINE_FIELD(message_short, String, ObisId(0, 0, 96, 13, 1), StringField, 0, 16);
|
||||
/* Text message max 2048 characters (Note: Spec says 1024 in comment and
|
||||
* 2048 in format spec, so we stick to 2048). */
|
||||
DEFINE_FIELD(message_long, String, ObisId(0, 0, 96, 13, 0), StringField, 0, 2048);
|
||||
|
||||
/* Instantaneous voltage L1 in 0.1V resolution (Note: Spec says V
|
||||
* resolution in comment, but 0.1V resolution in format spec. Added in
|
||||
* 5.0) */
|
||||
DEFINE_FIELD(voltage_l1, FixedValue, ObisId(1, 0, 32, 7, 0), FixedField, units::V, units::mV);
|
||||
/* Instantaneous voltage L2 in 0.1V resolution (Note: Spec says V
|
||||
* resolution in comment, but 0.1V resolution in format spec. Added in
|
||||
* 5.0) */
|
||||
DEFINE_FIELD(voltage_l2, FixedValue, ObisId(1, 0, 52, 7, 0), FixedField, units::V, units::mV);
|
||||
/* Instantaneous voltage L3 in 0.1V resolution (Note: Spec says V
|
||||
* resolution in comment, but 0.1V resolution in format spec. Added in
|
||||
* 5.0) */
|
||||
DEFINE_FIELD(voltage_l3, FixedValue, ObisId(1, 0, 72, 7, 0), FixedField, units::V, units::mV);
|
||||
|
||||
/* Instantaneous current L1 in A resolution */
|
||||
DEFINE_FIELD(current_l1, uint16_t, ObisId(1, 0, 31, 7, 0), IntField, units::A);
|
||||
/* Instantaneous current L2 in A resolution */
|
||||
DEFINE_FIELD(current_l2, uint16_t, ObisId(1, 0, 51, 7, 0), IntField, units::A);
|
||||
/* Instantaneous current L3 in A resolution */
|
||||
DEFINE_FIELD(current_l3, uint16_t, ObisId(1, 0, 71, 7, 0), IntField, units::A);
|
||||
|
||||
/* Instantaneous active power L1 (+P) in W resolution */
|
||||
DEFINE_FIELD(power_delivered_l1, FixedValue, ObisId(1, 0, 21, 7, 0), FixedField, units::kW, units::W);
|
||||
/* Instantaneous active power L2 (+P) in W resolution */
|
||||
DEFINE_FIELD(power_delivered_l2, FixedValue, ObisId(1, 0, 41, 7, 0), FixedField, units::kW, units::W);
|
||||
/* Instantaneous active power L3 (+P) in W resolution */
|
||||
DEFINE_FIELD(power_delivered_l3, FixedValue, ObisId(1, 0, 61, 7, 0), FixedField, units::kW, units::W);
|
||||
|
||||
/* Instantaneous active power L1 (-P) in W resolution */
|
||||
DEFINE_FIELD(power_returned_l1, FixedValue, ObisId(1, 0, 22, 7, 0), FixedField, units::kW, units::W);
|
||||
/* Instantaneous active power L2 (-P) in W resolution */
|
||||
DEFINE_FIELD(power_returned_l2, FixedValue, ObisId(1, 0, 42, 7, 0), FixedField, units::kW, units::W);
|
||||
/* Instantaneous active power L3 (-P) in W resolution */
|
||||
DEFINE_FIELD(power_returned_l3, FixedValue, ObisId(1, 0, 62, 7, 0), FixedField, units::kW, units::W);
|
||||
|
||||
|
||||
/* Device-Type */
|
||||
DEFINE_FIELD(gas_device_type, uint16_t, ObisId(0, GAS_MBUS_ID, 24, 1, 0), IntField, units::none);
|
||||
|
||||
/* Equipment identifier (Gas) */
|
||||
DEFINE_FIELD(gas_equipment_id, String, ObisId(0, GAS_MBUS_ID, 96, 1, 0), StringField, 0, 96);
|
||||
|
||||
/* Valve position Gas (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
|
||||
DEFINE_FIELD(gas_valve_position, uint8_t, ObisId(0, GAS_MBUS_ID, 24, 4, 0), IntField, units::none);
|
||||
|
||||
/* Last 5-minute value (temperature converted), gas delivered to client
|
||||
* in m3, including decimal values and capture time (Note: 4.x spec has
|
||||
* "hourly value") */
|
||||
DEFINE_FIELD(gas_delivered, TimestampedFixedValue, ObisId(0, GAS_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::m3, units::dm3);
|
||||
|
||||
|
||||
/* Device-Type */
|
||||
DEFINE_FIELD(thermal_device_type, uint16_t, ObisId(0, THERMAL_MBUS_ID, 24, 1, 0), IntField, units::none);
|
||||
|
||||
/* Equipment identifier (Thermal: heat or cold) */
|
||||
DEFINE_FIELD(thermal_equipment_id, String, ObisId(0, THERMAL_MBUS_ID, 96, 1, 0), StringField, 0, 96);
|
||||
|
||||
/* Valve position (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
|
||||
DEFINE_FIELD(thermal_valve_position, uint8_t, ObisId(0, THERMAL_MBUS_ID, 24, 4, 0), IntField, units::none);
|
||||
|
||||
/* Last 5-minute Meter reading Heat or Cold in 0,01 GJ and capture time
|
||||
* (Note: 4.x spec has "hourly meter reading") */
|
||||
DEFINE_FIELD(thermal_delivered, TimestampedFixedValue, ObisId(0, THERMAL_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::GJ, units::MJ);
|
||||
|
||||
|
||||
/* Device-Type */
|
||||
DEFINE_FIELD(water_device_type, uint16_t, ObisId(0, WATER_MBUS_ID, 24, 1, 0), IntField, units::none);
|
||||
|
||||
/* Equipment identifier (Thermal: heat or cold) */
|
||||
DEFINE_FIELD(water_equipment_id, String, ObisId(0, WATER_MBUS_ID, 96, 1, 0), StringField, 0, 96);
|
||||
|
||||
/* Valve position (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
|
||||
DEFINE_FIELD(water_valve_position, uint8_t, ObisId(0, WATER_MBUS_ID, 24, 4, 0), IntField, units::none);
|
||||
|
||||
/* Last 5-minute Meter reading in 0,001 m3 and capture time
|
||||
* (Note: 4.x spec has "hourly meter reading") */
|
||||
DEFINE_FIELD(water_delivered, TimestampedFixedValue, ObisId(0, WATER_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::m3, units::dm3);
|
||||
|
||||
|
||||
/* Device-Type */
|
||||
DEFINE_FIELD(slave_device_type, uint16_t, ObisId(0, SLAVE_MBUS_ID, 24, 1, 0), IntField, units::none);
|
||||
|
||||
/* Equipment identifier (Thermal: heat or cold) */
|
||||
DEFINE_FIELD(slave_equipment_id, String, ObisId(0, SLAVE_MBUS_ID, 96, 1, 0), StringField, 0, 96);
|
||||
|
||||
/* Valve position (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
|
||||
DEFINE_FIELD(slave_valve_position, uint8_t, ObisId(0, SLAVE_MBUS_ID, 24, 4, 0), IntField, units::none);
|
||||
|
||||
/* Last 5-minute Meter reading Heat or Cold and capture time (e.g. slave
|
||||
* E meter) (Note: 4.x spec has "hourly meter reading") */
|
||||
DEFINE_FIELD(slave_delivered, TimestampedFixedValue, ObisId(0, SLAVE_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::m3, units::dm3);
|
||||
|
||||
} // namespace fields
|
||||
|
||||
} // namespace dsmr
|
||||
|
||||
#endif // DSMR_INCLUDE_FIELDS_H
|
||||
@@ -1,425 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Message parsing core
|
||||
*/
|
||||
|
||||
#ifndef DSMR_INCLUDE_PARSER_H
|
||||
#define DSMR_INCLUDE_PARSER_H
|
||||
|
||||
#include "crc16.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace dsmr {
|
||||
|
||||
/**
|
||||
* ParsedData is a template for the result of parsing a Dsmr P1 message.
|
||||
* You pass the fields you want to add to it as template arguments.
|
||||
*
|
||||
* This template will then generate a class that extends all the fields
|
||||
* passed (the fields really are classes themselves). Since each field
|
||||
* class has a single member variable, with the same name as the field
|
||||
* class, all of these fields will be available on the generated class.
|
||||
*
|
||||
* In other words, if I have:
|
||||
*
|
||||
* using MyData = ParsedData<
|
||||
* identification,
|
||||
* equipment_id
|
||||
* >;
|
||||
*
|
||||
* MyData data;
|
||||
*
|
||||
* then I can refer to the fields like data.identification and
|
||||
* data.equipment_id normally.
|
||||
*
|
||||
* Furthermore, this class offers some helper methods that can be used
|
||||
* to loop over all the fields inside it.
|
||||
*/
|
||||
template<typename... Ts>
|
||||
struct ParsedData;
|
||||
|
||||
/**
|
||||
* Base case: No fields present.
|
||||
*/
|
||||
template<>
|
||||
struct ParsedData<> {
|
||||
ParseResult<void> __attribute__((__always_inline__)) parse_line_inlined(const ObisId& /* id */, const char *str, const char * /* end */) {
|
||||
// Parsing succeeded, but found no matching handler (so return
|
||||
// set the next pointer to show nothing was parsed).
|
||||
return ParseResult<void>().until(str);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void __attribute__((__always_inline__)) applyEach_inlined(F&& /* f */) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
bool all_present_inlined() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Do not use F() for multiply-used strings (including strings used from
|
||||
// multiple template instantiations), that would result in multiple
|
||||
// instances of the string in the binary
|
||||
static constexpr char DUPLICATE_FIELD[] DSMR_PROGMEM = "Duplicate field";
|
||||
|
||||
/**
|
||||
* General case: At least one typename is passed.
|
||||
*/
|
||||
template<typename T, typename... Ts>
|
||||
struct ParsedData<T, Ts...> : public T, ParsedData<Ts...> {
|
||||
/**
|
||||
* This method is used by the parser to parse a single line. The
|
||||
* OBIS id of the line is passed, and this method recursively finds a
|
||||
* field with a matching id. If any, it calls it's parse method, which
|
||||
* parses the value and stores it in the field.
|
||||
*/
|
||||
ParseResult<void> parse_line(const ObisId& id, const char *str, const char *end) {
|
||||
return parse_line_inlined(id, str, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* always_inline version of parse_line. This is a separate method, to
|
||||
* allow recursively inlining all calls, but still have a non-inlined
|
||||
* top-level parse_line method.
|
||||
*/
|
||||
ParseResult<void> __attribute__((__always_inline__)) parse_line_inlined(const ObisId& id, const char *str, const char *end) {
|
||||
if (id == T::id) {
|
||||
if (T::present())
|
||||
return ParseResult<void>().fail((const __FlashStringHelper*)DUPLICATE_FIELD, str);
|
||||
T::present() = true;
|
||||
return T::parse(str, end);
|
||||
}
|
||||
return ParsedData<Ts...>::parse_line_inlined(id, str, end);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void applyEach(F&& f) {
|
||||
applyEach_inlined(f);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void __attribute__((__always_inline__)) applyEach_inlined(F&& f) {
|
||||
T::apply(f);
|
||||
return ParsedData<Ts...>::applyEach_inlined(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when all defined fields are present.
|
||||
*/
|
||||
bool all_present() {
|
||||
return all_present_inlined();
|
||||
}
|
||||
|
||||
bool all_present_inlined() {
|
||||
return T::present() && ParsedData<Ts...>::all_present_inlined();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct StringParser {
|
||||
static ParseResult<String> parse_string(size_t min, size_t max, const char *str, const char *end) {
|
||||
ParseResult<String> res;
|
||||
if (str >= end || *str != '(')
|
||||
return res.fail(F("Missing ("), str);
|
||||
|
||||
const char *str_start = str + 1; // Skip (
|
||||
const char *str_end = str_start;
|
||||
|
||||
while(str_end < end && *str_end != ')')
|
||||
++str_end;
|
||||
|
||||
if (str_end == end)
|
||||
return res.fail(F("Missing )"), str_end);
|
||||
|
||||
size_t len = str_end - str_start;
|
||||
if (len < min || len > max)
|
||||
return res.fail(F("Invalid string length"), str_start);
|
||||
|
||||
concat_hack(res.result, str_start, len);
|
||||
|
||||
return res.until(str_end + 1); // Skip )
|
||||
}
|
||||
};
|
||||
|
||||
// Do not use F() for multiply-used strings (including strings used from
|
||||
// multiple template instantiations), that would result in multiple
|
||||
// instances of the string in the binary
|
||||
static constexpr char INVALID_NUMBER[] DSMR_PROGMEM = "Invalid number";
|
||||
static constexpr char INVALID_UNIT[] DSMR_PROGMEM = "Invalid unit";
|
||||
|
||||
struct NumParser {
|
||||
static ParseResult<uint32_t> parse(size_t max_decimals, const char* unit, const char *str, const char *end) {
|
||||
ParseResult<uint32_t> res;
|
||||
if (str >= end || *str != '(')
|
||||
return res.fail(F("Missing ("), str);
|
||||
|
||||
const char *num_start = str + 1; // Skip (
|
||||
const char *num_end = num_start;
|
||||
|
||||
uint32_t value = 0;
|
||||
|
||||
// Parse integer part
|
||||
while(num_end < end && !strchr("*.)", *num_end)) {
|
||||
if (*num_end < '0' || *num_end > '9')
|
||||
return res.fail((const __FlashStringHelper*)INVALID_NUMBER, num_end);
|
||||
value *= 10;
|
||||
value += *num_end - '0';
|
||||
++num_end;
|
||||
}
|
||||
|
||||
// Parse decimal part, if any
|
||||
if (max_decimals && num_end < end && *num_end == '.') {
|
||||
++num_end;
|
||||
|
||||
while(num_end < end && !strchr("*)", *num_end) && max_decimals--) {
|
||||
if (*num_end < '0' || *num_end > '9')
|
||||
return res.fail((const __FlashStringHelper*)INVALID_NUMBER, num_end);
|
||||
value *= 10;
|
||||
value += *num_end - '0';
|
||||
++num_end;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in missing decimals with zeroes
|
||||
while(max_decimals--)
|
||||
value *= 10;
|
||||
|
||||
if (unit && *unit) {
|
||||
if (num_end >= end || *num_end != '*')
|
||||
return res.fail(F("Missing unit"), num_end);
|
||||
const char *unit_start = ++num_end; // skip *
|
||||
while(num_end < end && *num_end != ')' && *unit) {
|
||||
if (*num_end++ != *unit++)
|
||||
return res.fail((const __FlashStringHelper*)INVALID_UNIT, unit_start);
|
||||
}
|
||||
if (*unit)
|
||||
return res.fail((const __FlashStringHelper*)INVALID_UNIT, unit_start);
|
||||
}
|
||||
|
||||
if (num_end >= end || *num_end != ')')
|
||||
return res.fail(F("Extra data"), num_end);
|
||||
|
||||
return res.succeed(value).until(num_end + 1); // Skip )
|
||||
}
|
||||
};
|
||||
|
||||
struct ObisIdParser {
|
||||
static ParseResult<ObisId> parse(const char *str, const char *end) {
|
||||
// Parse a Obis ID of the form 1-2:3.4.5.6
|
||||
// Stops parsing on the first unrecognized character. Any unparsed
|
||||
// parts are set to 255.
|
||||
ParseResult<ObisId> res;
|
||||
ObisId& id = res.result;
|
||||
res.next = str;
|
||||
uint8_t part = 0;
|
||||
while (res.next < end) {
|
||||
char c = *res.next;
|
||||
|
||||
if (c >= '0' && c <= '9') {
|
||||
uint8_t digit = c - '0';
|
||||
if (id.v[part] > 25 || (id.v[part] == 25 && digit > 5))
|
||||
return res.fail(F("Obis ID has number over 255"), res.next);
|
||||
id.v[part] = id.v[part] * 10 + digit;
|
||||
} else if (part == 0 && c == '-') {
|
||||
part++;
|
||||
} else if (part == 1 && c == ':') {
|
||||
part++;
|
||||
} else if (part > 1 && part < 5 && c == '.') {
|
||||
part++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
++res.next;
|
||||
}
|
||||
|
||||
if (res.next == str)
|
||||
return res.fail(F("OBIS id Empty"), str);
|
||||
|
||||
for (++part; part < 6; ++part)
|
||||
id.v[part] = 255;
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
struct CrcParser {
|
||||
static const size_t CRC_LEN = 4;
|
||||
|
||||
// Parse a crc value. str must point to the first of the four hex
|
||||
// bytes in the CRC.
|
||||
static ParseResult<uint16_t> parse(const char *str, const char *end) {
|
||||
ParseResult<uint16_t> res;
|
||||
// This should never happen with the code in this library, but
|
||||
// check anyway
|
||||
if (str + CRC_LEN > end)
|
||||
return res.fail(F("No checksum found"), str);
|
||||
|
||||
// A bit of a messy way to parse the checksum, but all
|
||||
// integer-parse functions assume nul-termination
|
||||
char buf[CRC_LEN + 1];
|
||||
memcpy(buf, str, CRC_LEN);
|
||||
buf[CRC_LEN] = '\0';
|
||||
char *endp;
|
||||
uint16_t check = strtoul(buf, &endp, 16);
|
||||
|
||||
// See if all four bytes formed a valid number
|
||||
if (endp != buf + CRC_LEN)
|
||||
return res.fail(F("Incomplete or malformed checksum"), str);
|
||||
|
||||
res.next = str + CRC_LEN;
|
||||
return res.succeed(check);
|
||||
}
|
||||
};
|
||||
|
||||
struct P1Parser {
|
||||
/**
|
||||
* Parse a complete P1 telegram. The string passed should start
|
||||
* with '/' and run up to and including the ! and the following
|
||||
* four byte checksum. It's ok if the string is longer, the .next
|
||||
* pointer in the result will indicate the next unprocessed byte.
|
||||
*/
|
||||
template <typename... Ts>
|
||||
static ParseResult<void> parse(ParsedData<Ts...> *data, const char *str, size_t n, bool unknown_error = false) {
|
||||
ParseResult<void> res;
|
||||
if (!n || str[0] != '/')
|
||||
return res.fail(F("Data should start with /"), str);
|
||||
|
||||
// Skip /
|
||||
const char *data_start = str + 1;
|
||||
|
||||
// Look for ! that terminates the data
|
||||
const char *data_end = data_start;
|
||||
uint16_t crc = _crc16_update(0, *str); // Include the / in CRC
|
||||
while (data_end < str + n && *data_end != '!') {
|
||||
crc = _crc16_update(crc, *data_end);
|
||||
++data_end;
|
||||
}
|
||||
|
||||
if (data_end >= str + n)
|
||||
return res.fail(F("No checksum found"), data_end);
|
||||
|
||||
crc = _crc16_update(crc, *data_end); // Include the ! in CRC
|
||||
|
||||
ParseResult<uint16_t> check_res = CrcParser::parse(data_end + 1, str + n);
|
||||
if (check_res.err)
|
||||
return check_res;
|
||||
|
||||
// Check CRC
|
||||
if (check_res.result != crc)
|
||||
return res.fail(F("Checksum mismatch"), data_end + 1);
|
||||
|
||||
res = parse_data(data, data_start, data_end, unknown_error);
|
||||
res.next = check_res.next;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the data part of a message. Str should point to the first
|
||||
* character after the leading /, end should point to the ! before the
|
||||
* checksum. Does not verify the checksum.
|
||||
*/
|
||||
template <typename... Ts>
|
||||
static ParseResult<void> parse_data(ParsedData<Ts...> *data, const char *str, const char *end, bool unknown_error = false) {
|
||||
ParseResult<void> res;
|
||||
// Split into lines and parse those
|
||||
const char *line_end = str, *line_start = str;
|
||||
|
||||
// Parse ID line
|
||||
while (line_end < end) {
|
||||
if (*line_end == '\r' || *line_end == '\n') {
|
||||
// The first identification line looks like:
|
||||
// XXX5<id string>
|
||||
// The DSMR spec is vague on details, but in 62056-21, the X's
|
||||
// are a three-leter (registerd) manufacturer ID, the id
|
||||
// string is up to 16 chars of arbitrary characters and the
|
||||
// '5' is a baud rate indication. 5 apparently means 9600,
|
||||
// which DSMR 3.x and below used. It seems that DSMR 2.x
|
||||
// passed '3' here (which is mandatory for "mode D"
|
||||
// communication according to 62956-21), so we also allow
|
||||
// that.
|
||||
if (line_start + 3 >= line_end || (line_start[3] != '5' && line_start[3] != '3'))
|
||||
return res.fail(F("Invalid identification string"), line_start);
|
||||
// Offer it for processing using the all-ones Obis ID, which
|
||||
// is not otherwise valid.
|
||||
ParseResult<void> tmp = data->parse_line(ObisId(255, 255, 255, 255, 255, 255), line_start, line_end);
|
||||
if (tmp.err)
|
||||
return tmp;
|
||||
line_start = ++line_end;
|
||||
break;
|
||||
}
|
||||
++line_end;
|
||||
}
|
||||
|
||||
// Parse data lines
|
||||
while (line_end < end) {
|
||||
if (*line_end == '\r' || *line_end == '\n') {
|
||||
ParseResult<void> tmp = parse_line(data, line_start, line_end, unknown_error);
|
||||
if (tmp.err)
|
||||
return tmp;
|
||||
line_start = line_end + 1;
|
||||
}
|
||||
line_end++;
|
||||
}
|
||||
|
||||
if (line_end != line_start)
|
||||
return res.fail(F("Last dataline not CRLF terminated"), line_end);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename Data>
|
||||
static ParseResult<void> parse_line(Data *data, const char *line, const char *end, bool unknown_error) {
|
||||
ParseResult<void> res;
|
||||
if (line == end)
|
||||
return res;
|
||||
|
||||
ParseResult<ObisId> idres = ObisIdParser::parse(line, end);
|
||||
if (idres.err)
|
||||
return idres;
|
||||
|
||||
ParseResult<void> datares = data->parse_line(idres.result, idres.next, end);
|
||||
if (datares.err)
|
||||
return datares;
|
||||
|
||||
// If datares.next didn't move at all, there was no parser for
|
||||
// this field, that's ok. But if it did move, but not all the way
|
||||
// to the end, that's an error.
|
||||
if (datares.next != idres.next && datares.next != end)
|
||||
return res.fail(F("Trailing characters on data line"), datares.next);
|
||||
else if (datares.next == idres.next && unknown_error)
|
||||
return res.fail(F("Unknown field"), line);
|
||||
|
||||
return res.until(end);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dsmr
|
||||
|
||||
#endif // DSMR_INCLUDE_PARSER_H
|
||||
@@ -1,242 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
@@ -1,186 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
@@ -14,5 +14,7 @@ board = esp12e
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
glmnet/Dsmr@^0.3
|
||||
lib_ldf_mode = deep+
|
||||
|
||||
compile_flags =
|
||||
-std=c++11
|
||||
|
||||
@@ -5,6 +5,28 @@ bool initOK = false;
|
||||
|
||||
OLEDDisplayUi ui(&display);
|
||||
|
||||
|
||||
|
||||
int screenW = 128;
|
||||
int screenH = 64;
|
||||
int clockCenterX = screenW / 2;
|
||||
int clockCenterY = ((screenH - 16) / 2) + 16; // top yellow part is 16 px height
|
||||
int clockRadius = 23;
|
||||
|
||||
// utility function for digital clock display: prints leading 0
|
||||
String twoDigits(int digits)
|
||||
{
|
||||
if (digits < 10)
|
||||
{
|
||||
String i = '0' + String(digits);
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{
|
||||
return String(digits);
|
||||
}
|
||||
}
|
||||
|
||||
void msOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||
{
|
||||
|
||||
@@ -74,7 +96,7 @@ void initDisplay(void)
|
||||
// but that won't give you much time for anything else
|
||||
// run it in 160Mhz mode or just set it to 30 fps
|
||||
ui.setTargetFPS(30);
|
||||
|
||||
display.init();
|
||||
// Customize the active and inactive symbol
|
||||
//ui.setActiveSymbol(activeSymbol);
|
||||
//ui.setInactiveSymbol(inactiveSymbol);
|
||||
|
||||
@@ -73,7 +73,7 @@ void handleDSMR(void)
|
||||
String getElecDelivered(void)
|
||||
{
|
||||
String tmpstr("Cons: ");
|
||||
tmpstr += data.power_delivered;
|
||||
tmpstr += data.power_delivered.int_val();
|
||||
tmpstr += " kwh";
|
||||
return tmpstr;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ String getElecDelivered(void)
|
||||
String getElecReturned(void)
|
||||
{
|
||||
String tmpstr("Prod: ");
|
||||
tmpstr += data.power_returned;
|
||||
tmpstr += data.power_returned.int_val();
|
||||
tmpstr += " kwh";
|
||||
return tmpstr;
|
||||
}
|
||||
@@ -89,7 +89,7 @@ String getElecReturned(void)
|
||||
String getGasDelivered(void)
|
||||
{
|
||||
String tmpstr("Cons: ");
|
||||
tmpstr += data.gas_delivered;
|
||||
tmpstr += data.gas_delivered.int_val();
|
||||
tmpstr += " m3";
|
||||
return tmpstr;
|
||||
}
|
||||
|
||||
@@ -65,11 +65,12 @@ using DSMRData = ParsedData<
|
||||
/* uint16_t */ water_device_type,
|
||||
/* String */ water_equipment_id,
|
||||
/* uint8_t */ water_valve_position,
|
||||
/* TimestampedFixedValue */ water_delivered,
|
||||
/* uint16_t */ slave_device_type,
|
||||
/* String */ slave_equipment_id,
|
||||
/* uint8_t */ slave_valve_position,
|
||||
/* TimestampedFixedValue */ slave_delivered>;
|
||||
/* TimestampedFixedValue */ water_delivered
|
||||
// /* uint16_t */ slave_device_type,
|
||||
// /* String */ slave_equipment_id,
|
||||
// /* uint8_t */ slave_valve_position,
|
||||
// /* TimestampedFixedValue */ slave_delivered>
|
||||
>;
|
||||
|
||||
struct Printer
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#define WiFi_Logo_width 60
|
||||
#define WiFi_Logo_height 36
|
||||
const uint8_t WiFi_Logo_bits[] PROGMEM = {
|
||||
const uint8_t WiFi_Logo_bits[] {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
|
||||
@@ -28,7 +30,7 @@ const uint8_t WiFi_Logo_bits[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
const uint8_t activeSymbol[] PROGMEM = {
|
||||
const uint8_t activeSymbol[] = {
|
||||
B00000000,
|
||||
B00000000,
|
||||
B00011000,
|
||||
@@ -39,7 +41,7 @@ const uint8_t activeSymbol[] PROGMEM = {
|
||||
B00011000
|
||||
};
|
||||
|
||||
const uint8_t inactiveSymbol[] PROGMEM = {
|
||||
const uint8_t inactiveSymbol[] = {
|
||||
B00000000,
|
||||
B00000000,
|
||||
B00000000,
|
||||
|
||||
@@ -4,69 +4,21 @@
|
||||
#include "connection.h"
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
delay(1000);
|
||||
Serial.print("hello ESP8266");
|
||||
delay(1000);
|
||||
initDisplay();
|
||||
initConnection();
|
||||
initDSMR();
|
||||
|
||||
// Include custom images
|
||||
#include "images.h"
|
||||
|
||||
// Use the corresponding display class:
|
||||
|
||||
// Initialize the OLED display using SPI
|
||||
// D5 -> CLK
|
||||
// D7 -> MOSI (DOUT)
|
||||
// D0 -> RES
|
||||
// D2 -> DC
|
||||
// D8 -> CS
|
||||
// SSD1306Spi display(D0, D2, D8);
|
||||
// or
|
||||
// SH1106Spi display(D0, D2);
|
||||
|
||||
// Initialize the OLED display using brzo_i2c
|
||||
// D3 -> SDA
|
||||
// D5 -> SCL
|
||||
// SSD1306Brzo display(0x3c, D3, D5);
|
||||
// or
|
||||
// SH1106Brzo display(0x3c, D3, D5);
|
||||
|
||||
// Initialize the OLED display using Wire library
|
||||
SSD1306Wire display(0x3c, SDA, SCL);
|
||||
// SH1106 display(0x3c, D3, D5);
|
||||
|
||||
OLEDDisplayUi ui(&display);
|
||||
|
||||
int screenW = 128;
|
||||
int screenH = 64;
|
||||
int clockCenterX = screenW / 2;
|
||||
int clockCenterY = ((screenH - 16) / 2) + 16; // top yellow part is 16 px height
|
||||
int clockRadius = 23;
|
||||
|
||||
// utility function for digital clock display: prints leading 0
|
||||
String twoDigits(int digits)
|
||||
{
|
||||
if (digits < 10)
|
||||
{
|
||||
String i = '0' + String(digits);
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{
|
||||
return String(digits);
|
||||
}
|
||||
}
|
||||
|
||||
void clockOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||
{
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
Serial.print(".");
|
||||
Serial.print(".|.ß");
|
||||
handleDisplay();
|
||||
handleConnection();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user