updated firmware

This commit is contained in:
2021-08-23 17:12:02 +02:00
parent 5a5f977a5f
commit 0c79764922
13 changed files with 45 additions and 1678 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -14,5 +14,7 @@ board = esp12e
framework = arduino framework = arduino
monitor_speed = 115200 monitor_speed = 115200
lib_deps = lib_deps =
glmnet/Dsmr@^0.3
lib_ldf_mode = deep+ lib_ldf_mode = deep+
compile_flags =
-std=c++11

View File

@@ -5,6 +5,28 @@ bool initOK = false;
OLEDDisplayUi ui(&display); 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) void msOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{ {
@@ -74,7 +96,7 @@ void initDisplay(void)
// but that won't give you much time for anything else // but that won't give you much time for anything else
// run it in 160Mhz mode or just set it to 30 fps // run it in 160Mhz mode or just set it to 30 fps
ui.setTargetFPS(30); ui.setTargetFPS(30);
display.init();
// Customize the active and inactive symbol // Customize the active and inactive symbol
//ui.setActiveSymbol(activeSymbol); //ui.setActiveSymbol(activeSymbol);
//ui.setInactiveSymbol(inactiveSymbol); //ui.setInactiveSymbol(inactiveSymbol);

View File

@@ -73,7 +73,7 @@ void handleDSMR(void)
String getElecDelivered(void) String getElecDelivered(void)
{ {
String tmpstr("Cons: "); String tmpstr("Cons: ");
tmpstr += data.power_delivered; tmpstr += data.power_delivered.int_val();
tmpstr += " kwh"; tmpstr += " kwh";
return tmpstr; return tmpstr;
} }
@@ -81,7 +81,7 @@ String getElecDelivered(void)
String getElecReturned(void) String getElecReturned(void)
{ {
String tmpstr("Prod: "); String tmpstr("Prod: ");
tmpstr += data.power_returned; tmpstr += data.power_returned.int_val();
tmpstr += " kwh"; tmpstr += " kwh";
return tmpstr; return tmpstr;
} }
@@ -89,7 +89,7 @@ String getElecReturned(void)
String getGasDelivered(void) String getGasDelivered(void)
{ {
String tmpstr("Cons: "); String tmpstr("Cons: ");
tmpstr += data.gas_delivered; tmpstr += data.gas_delivered.int_val();
tmpstr += " m3"; tmpstr += " m3";
return tmpstr; return tmpstr;
} }

View File

@@ -65,11 +65,12 @@ using DSMRData = ParsedData<
/* uint16_t */ water_device_type, /* uint16_t */ water_device_type,
/* String */ water_equipment_id, /* String */ water_equipment_id,
/* uint8_t */ water_valve_position, /* uint8_t */ water_valve_position,
/* TimestampedFixedValue */ water_delivered, /* TimestampedFixedValue */ water_delivered
/* uint16_t */ slave_device_type, // /* uint16_t */ slave_device_type,
/* String */ slave_equipment_id, // /* String */ slave_equipment_id,
/* uint8_t */ slave_valve_position, // /* uint8_t */ slave_valve_position,
/* TimestampedFixedValue */ slave_delivered>; // /* TimestampedFixedValue */ slave_delivered>
>;
struct Printer struct Printer
{ {

View File

@@ -1,7 +1,9 @@
#pragma once
#include "Arduino.h" #include "Arduino.h"
#define WiFi_Logo_width 60 #define WiFi_Logo_width 60
#define WiFi_Logo_height 36 #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, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}; };
const uint8_t activeSymbol[] PROGMEM = { const uint8_t activeSymbol[] = {
B00000000, B00000000,
B00000000, B00000000,
B00011000, B00011000,
@@ -39,7 +41,7 @@ const uint8_t activeSymbol[] PROGMEM = {
B00011000 B00011000
}; };
const uint8_t inactiveSymbol[] PROGMEM = { const uint8_t inactiveSymbol[] = {
B00000000, B00000000,
B00000000, B00000000,
B00000000, B00000000,

View File

@@ -4,69 +4,21 @@
#include "connection.h" #include "connection.h"
void setup() { void setup() {
// put your setup code here, to run once: // put your setup code here, to run once:
Serial.begin(115200); Serial.begin(115200);
delay(500); delay(1000);
Serial.print("hello ESP8266");
delay(1000);
initDisplay(); initDisplay();
initConnection(); initConnection();
initDSMR(); 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() { void loop() {
// put your main code here, to run repeatedly: // put your main code here, to run repeatedly:
Serial.print("."); Serial.print(".|.ß");
handleDisplay(); handleDisplay();
handleConnection(); handleConnection();
} }