diff --git a/P1_gateway_FW/include/dsmr.h b/P1_gateway_FW/include/dsmr.h deleted file mode 100644 index 9361c19..0000000 --- a/P1_gateway_FW/include/dsmr.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Arduino DSMR parser. - * - * This software is licensed under the MIT License. - * - * Copyright (c) 2015 Matthijs Kooijman - * - * 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 diff --git a/P1_gateway_FW/lib/dsmr/crc16.h b/P1_gateway_FW/lib/dsmr/crc16.h deleted file mode 100644 index 1967044..0000000 --- a/P1_gateway_FW/lib/dsmr/crc16.h +++ /dev/null @@ -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 -#else -#define _UTIL_CRC16_H_ -#include - -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 diff --git a/P1_gateway_FW/lib/dsmr/fields.cpp b/P1_gateway_FW/lib/dsmr/fields.cpp deleted file mode 100644 index 02781c4..0000000 --- a/P1_gateway_FW/lib/dsmr/fields.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/** - * Arduino DSMR parser. - * - * This software is licensed under the MIT License. - * - * Copyright (c) 2015 Matthijs Kooijman - * - * 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; - diff --git a/P1_gateway_FW/lib/dsmr/fields.h b/P1_gateway_FW/lib/dsmr/fields.h deleted file mode 100644 index a6a10f5..0000000 --- a/P1_gateway_FW/lib/dsmr/fields.h +++ /dev/null @@ -1,352 +0,0 @@ -/** - * Arduino DSMR parser. - * - * This software is licensed under the MIT License. - * - * Copyright (c) 2015 Matthijs Kooijman - * - * 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 -struct ParsedField { - template - void apply(F& f) { - f.apply(*static_cast(this)); - } - // By defaults, fields have no unit - static const char *unit() { return ""; } -}; - -template -struct StringField : ParsedField { - ParseResult parse(const char *str, const char *end) { - ParseResult res = StringParser::parse_string(minlen, maxlen, str, end); - if (!res.err) - static_cast(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 -struct TimestampField : StringField { }; - -// 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 -struct FixedField : ParsedField { - ParseResult parse(const char *str, const char *end) { - ParseResult res = NumParser::parse(3, _unit, str, end); - if (!res.err) - static_cast(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 -struct TimestampedFixedField : public FixedField { - ParseResult parse(const char *str, const char *end) { - // First, parse timestamp - ParseResult res = StringParser::parse_string(13, 13, str, end); - if (res.err) - return res; - - static_cast(this)->val().timestamp = res.result; - - // Which is immediately followed by the numerical value - return FixedField::parse(res.next, end); - } -}; - -// A integer number is just represented as an integer. -template -struct IntField : ParsedField { - ParseResult parse(const char *str, const char *end) { - ParseResult res = NumParser::parse(0, _unit, str, end); - if (!res.err) - static_cast(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 -struct RawField : ParsedField { - ParseResult parse(const char *str, const char *end) { - // Just copy the string verbatim value without any parsing - concat_hack(static_cast(this)->val(), str, end - str); - return ParseResult().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 { \ - 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(&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 diff --git a/P1_gateway_FW/lib/dsmr/parser.h b/P1_gateway_FW/lib/dsmr/parser.h deleted file mode 100644 index 68805a6..0000000 --- a/P1_gateway_FW/lib/dsmr/parser.h +++ /dev/null @@ -1,425 +0,0 @@ -/** - * Arduino DSMR parser. - * - * This software is licensed under the MIT License. - * - * Copyright (c) 2015 Matthijs Kooijman - * - * 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 -struct ParsedData; - -/** - * Base case: No fields present. - */ -template<> -struct ParsedData<> { - ParseResult __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().until(str); - } - - template - 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 -struct ParsedData : public T, ParsedData { - /** - * 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 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 __attribute__((__always_inline__)) parse_line_inlined(const ObisId& id, const char *str, const char *end) { - if (id == T::id) { - if (T::present()) - return ParseResult().fail((const __FlashStringHelper*)DUPLICATE_FIELD, str); - T::present() = true; - return T::parse(str, end); - } - return ParsedData::parse_line_inlined(id, str, end); - } - - template - void applyEach(F&& f) { - applyEach_inlined(f); - } - - template - void __attribute__((__always_inline__)) applyEach_inlined(F&& f) { - T::apply(f); - return ParsedData::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::all_present_inlined(); - } -}; - - -struct StringParser { - static ParseResult parse_string(size_t min, size_t max, const char *str, const char *end) { - ParseResult 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 parse(size_t max_decimals, const char* unit, const char *str, const char *end) { - ParseResult 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 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 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 parse(const char *str, const char *end) { - ParseResult 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 - static ParseResult parse(ParsedData *data, const char *str, size_t n, bool unknown_error = false) { - ParseResult 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 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 - static ParseResult parse_data(ParsedData *data, const char *str, const char *end, bool unknown_error = false) { - ParseResult 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 - // 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 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 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 - static ParseResult parse_line(Data *data, const char *line, const char *end, bool unknown_error) { - ParseResult res; - if (line == end) - return res; - - ParseResult idres = ObisIdParser::parse(line, end); - if (idres.err) - return idres; - - ParseResult 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 diff --git a/P1_gateway_FW/lib/dsmr/reader.h b/P1_gateway_FW/lib/dsmr/reader.h deleted file mode 100644 index 61b8742..0000000 --- a/P1_gateway_FW/lib/dsmr/reader.h +++ /dev/null @@ -1,242 +0,0 @@ -/** - * Arduino DSMR parser. - * - * This software is licensed under the MIT License. - * - * Copyright (c) 2015 Matthijs Kooijman - * - * 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 -#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 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 - bool parse(ParsedData *data, String *err) { - const char *str = buffer.c_str(), *end = buffer.c_str() + buffer.length(); - ParseResult 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 diff --git a/P1_gateway_FW/lib/dsmr/util.h b/P1_gateway_FW/lib/dsmr/util.h deleted file mode 100644 index c52ca6c..0000000 --- a/P1_gateway_FW/lib/dsmr/util.h +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Arduino DSMR parser. - * - * This software is licensed under the MIT License. - * - * Copyright (c) 2015 Matthijs Kooijman - * - * 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 - -namespace dsmr { - -/** - * Small utility to get the length of an array at compiletime. - */ -template -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 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 -struct _ParseResult { - T result; - - P& succeed(T& result) { - this->result = result; return *static_cast(this); - } - P& succeed(T&& result) { - this->result = result; - return *static_cast(this); - } -}; - -// partial specialization for void result -template -struct _ParseResult { -}; - -// Actual ParseResult class -template -struct ParseResult : public _ParseResult, 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 - ParseResult(const ParseResult& 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 diff --git a/P1_gateway_FW/platformio.ini b/P1_gateway_FW/platformio.ini index 5936c90..5acf842 100644 --- a/P1_gateway_FW/platformio.ini +++ b/P1_gateway_FW/platformio.ini @@ -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 diff --git a/P1_gateway_FW/src/display.cpp b/P1_gateway_FW/src/display.cpp index 60e3380..8760e95 100644 --- a/P1_gateway_FW/src/display.cpp +++ b/P1_gateway_FW/src/display.cpp @@ -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); diff --git a/P1_gateway_FW/src/dsmrhandler.cpp b/P1_gateway_FW/src/dsmrhandler.cpp index f2e2b23..8df1717 100644 --- a/P1_gateway_FW/src/dsmrhandler.cpp +++ b/P1_gateway_FW/src/dsmrhandler.cpp @@ -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; } diff --git a/P1_gateway_FW/src/dsmrhandler.h b/P1_gateway_FW/src/dsmrhandler.h index 04133a0..f50cca6 100644 --- a/P1_gateway_FW/src/dsmrhandler.h +++ b/P1_gateway_FW/src/dsmrhandler.h @@ -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 { diff --git a/P1_gateway_FW/src/images.h b/P1_gateway_FW/src/images.h index 030dd35..affb92e 100644 --- a/P1_gateway_FW/src/images.h +++ b/P1_gateway_FW/src/images.h @@ -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, diff --git a/P1_gateway_FW/src/main.cpp b/P1_gateway_FW/src/main.cpp index 47e1486..444908c 100644 --- a/P1_gateway_FW/src/main.cpp +++ b/P1_gateway_FW/src/main.cpp @@ -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(); }