kopia lustrzana https://github.com/weetmuts/wmbusmeters
Merge remote-tracking branch 'origin/master' into fb_add_itron_ultramaxx
commit
00039bbdb8
3
CHANGES
3
CHANGES
|
@ -1,4 +1,7 @@
|
||||||
|
|
||||||
|
ATTENTION! The hydrus driver could report the wrong value for total_at_date_m3
|
||||||
|
if an at_date had not been reached yet. This is fixed.
|
||||||
|
|
||||||
Added initial support for drivers that can be loaded from config files.
|
Added initial support for drivers that can be loaded from config files.
|
||||||
Properly receive telegrams from amb8465 which is in command mode.
|
Properly receive telegrams from amb8465 which is in command mode.
|
||||||
Chris Bednarczyk improved the build process for Darwin/MacOS platform. Thanks Chris!
|
Chris Bednarczyk improved the build process for Darwin/MacOS platform. Thanks Chris!
|
||||||
|
|
|
@ -55,4 +55,4 @@ telegram=|494468509494949495377286868686A85CFE07A90030052F2F_0413100000000F52FCF
|
||||||
|
|
||||||
# Test Zenner Minomess C1 water meter
|
# Test Zenner Minomess C1 water meter
|
||||||
telegram=|6644496A1064035514377251345015496A0007EE0050052F2F_0C1359000000026CBE2B82046CA12B8C0413FFFFFFFF8D0493132CFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02FD1700002F2F|
|
telegram=|6644496A1064035514377251345015496A0007EE0050052F2F_0C1359000000026CBE2B82046CA12B8C0413FFFFFFFF8D0493132CFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02FD1700002F2F|
|
||||||
{"media":"water","meter":"minomess","name":"Mino","id":"15503451","meter_date":"2021-11-30","total_m3":0.059,"target_m3":244444.442,"target_date":"2021-11-01","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
{"media":"water","meter":"minomess","name":"Mino","id":"15503451","meter_date":"2021-11-30","total_m3":0.059,"target_date":"2021-11-01","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||||
|
|
|
@ -122,13 +122,13 @@ telegram=|5744b40988227711101b7ab20800000265a00842658f088201659f08226589081265a0
|
||||||
|
|
||||||
# Test Hydrus water meter telegram
|
# Test Hydrus water meter telegram
|
||||||
telegram=|4E44A5116464646470077AED004005_2F2F01FD08300C13741100007C1300000000FC101300000000FC201300000000726C00000B3B00000002FD748713025A6800C4016D3B177F2ACC011300020000|
|
telegram=|4E44A5116464646470077AED004005_2F2F01FD08300C13741100007C1300000000FC101300000000FC201300000000726C00000B3B00000002FD748713025A6800C4016D3B177F2ACC011300020000|
|
||||||
{"at_datetime": "2019-10-31 23:59","flow_m3h": 0,"flow_temperature_c": 10.4,"id": "64646464","media": "water","meter": "hydrus","name": "HydrusWater","remaining_battery_life_y": 13.686797,"status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_date_m3": 0.2,"total_m3": 1.174}
|
{"at_datetime": "2019-10-31 23:59","flow_m3h": 0,"flow_temperature_c": 10.4,"id": "64646464","media": "water","meter": "hydrus","name": "HydrusWater","remaining_battery_life_y": 13.686797,"status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_woot_m3": 0.2,"total_m3": 1.174}
|
||||||
|HydrusWater;64646464;1.174;0.2;OK;1111-11-11 11:11.11
|
|HydrusWater;64646464;1.174;null;OK;1111-11-11 11:11.11
|
||||||
|
|
||||||
# Test Hydrus new version water meter telegram
|
# Test Hydrus new version water meter telegram
|
||||||
telegram=|3E44A5116565656570067AFB0030052F2F_0C13503400000DFD110A383731303134423032410B3B00000002FD74DC15C4016D3B178D29CC0113313400002F2F|
|
telegram=|3E44A5116565656570067AFB0030052F2F_0C13503400000DFD110A383731303134423032410B3B00000002FD74DC15C4016D3B178D29CC0113313400002F2F|
|
||||||
{"at_datetime": "2020-09-13 23:59","customer": "A20B410178","flow_m3h": 0,"id": "65656565","media": "warm water","meter": "hydrus","name": "HydrusVater","remaining_battery_life_y": 15.321328,"status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_date_m3": 3.431,"total_m3": 3.45}
|
{"at_datetime": "2020-09-13 23:59","customer": "A20B410178","flow_m3h": 0,"id": "65656565","media": "warm water","meter": "hydrus","name": "HydrusVater","remaining_battery_life_y": 15.321328,"status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_woot_m3": 3.431,"total_m3": 3.45}
|
||||||
|HydrusVater;65656565;3.45;3.431;OK;1111-11-11 11:11.11
|
|HydrusVater;65656565;3.45;null;OK;1111-11-11 11:11.11
|
||||||
|
|
||||||
# Test Hydrus with default AES encryption
|
# Test Hydrus with default AES encryption
|
||||||
telegram=||6644242328001081640E7266567464A51170071F0050052C411A08674048DD6BA82A0DF79FFD401309179A893A1BE3CE8EDC50C2A45CD7AFEC3B4CE765820BE8056C124A17416C3722985FFFF7FCEB7094901AB3A16294B511B9A740C9F9911352B42A72FB3B0C|
|
telegram=||6644242328001081640E7266567464A51170071F0050052C411A08674048DD6BA82A0DF79FFD401309179A893A1BE3CE8EDC50C2A45CD7AFEC3B4CE765820BE8056C124A17416C3722985FFFF7FCEB7094901AB3A16294B511B9A740C9F9911352B42A72FB3B0C|
|
||||||
|
|
|
@ -20,6 +20,24 @@
|
||||||
#include"driver_dynamic.h"
|
#include"driver_dynamic.h"
|
||||||
#include"xmq.h"
|
#include"xmq.h"
|
||||||
|
|
||||||
|
string check_driver_name(const char *name, string file);
|
||||||
|
MeterType check_meter_type(const char *meter_type_s, string file);
|
||||||
|
string check_default_fields(const char *fields, string file);
|
||||||
|
void check_detection_triplets(DriverInfo *di, string file);
|
||||||
|
|
||||||
|
string check_field_name(const char *name, DriverDynamic *dd);
|
||||||
|
Quantity check_field_quantity(const char *quantity_s, DriverDynamic *dd);
|
||||||
|
VifScaling check_vif_scaling(const char *vif_scaling_s, DriverDynamic *dd);
|
||||||
|
PrintProperties check_print_properties(const char *print_properties_s, DriverDynamic *dd);
|
||||||
|
string get_translation(XMQDoc *doc, XMQNode *node, string name, string lang);
|
||||||
|
string check_calculate(const char *formula, DriverDynamic *dd);
|
||||||
|
Unit check_display_unit(const char *display_unit, DriverDynamic *dd);
|
||||||
|
|
||||||
|
void check_set_measurement_type(const char *measurement_type_s, FieldMatcher *fm, DriverDynamic *dd);
|
||||||
|
void check_set_vif_range(const char *vif_range_s, FieldMatcher *fm, DriverDynamic *dd);
|
||||||
|
|
||||||
|
const char *line = "-------------------------------------------------------------------------------";
|
||||||
|
|
||||||
bool DriverDynamic::load(DriverInfo *di, const string &file)
|
bool DriverDynamic::load(DriverInfo *di, const string &file)
|
||||||
{
|
{
|
||||||
if (!endsWith(file, ".xmq")) return false;
|
if (!endsWith(file, ".xmq")) return false;
|
||||||
|
@ -30,85 +48,84 @@ bool DriverDynamic::load(DriverInfo *di, const string &file)
|
||||||
bool ok = xmqParseFile(doc, file.c_str(), "config");
|
bool ok = xmqParseFile(doc, file.c_str(), "config");
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
error("Error loading wmbusmeters driver file %s\n%s",
|
warning("(driver) error loading wmbusmeters driver file %s\n%s\n%s\n",
|
||||||
file.c_str(),
|
file.c_str(),
|
||||||
xmqDocError(doc));
|
xmqDocError(doc),
|
||||||
|
line);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *name = xmqGetString(doc, NULL, "/driver/name");
|
try
|
||||||
if (!name) error("(dynamic) Error in %s cannot find driver/name\n"
|
{
|
||||||
" A driver file looks like: driver { name = abc123 ... }\n", file.c_str());
|
string name = check_driver_name(xmqGetString(doc, NULL, "/driver/name"), file);
|
||||||
|
di->setName(name);
|
||||||
|
|
||||||
if (!is_lowercase_alnum_text(name)) error("(dynamic) Error in %s invalid driver name \"%s\"\n"
|
MeterType meter_type = check_meter_type(xmqGetString(doc, NULL, "/driver/meter_type"), file);
|
||||||
" The driver name must consist of lower case ascii a-z and digits 0-9.\n",
|
di->setMeterType(meter_type);
|
||||||
file.c_str(), name);
|
|
||||||
|
|
||||||
const char *meter_type_s = xmqGetString(doc, NULL, "/driver/meter_type");
|
string default_fields = check_default_fields(xmqGetString(doc, NULL, "/driver/default_fields"), file);
|
||||||
if (!meter_type_s) error("(dynamic) Error in %s cannot find driver/meter_type\n"
|
di->setDefaultFields(default_fields);
|
||||||
" Remember to add: meter_type = ...\n", file.c_str());
|
|
||||||
|
|
||||||
MeterType meter_type = toMeterType(meter_type_s);
|
verbose("(driver) loading driver %s\n", name.c_str());
|
||||||
|
|
||||||
if (meter_type == MeterType::UnknownMeter) error("(dynamic) Error in %s unknown meter type %s\n"
|
di->setDynamic(file, doc);
|
||||||
"Available meter types are:\n%s\n"
|
|
||||||
,
|
|
||||||
file.c_str(), meter_type_s, availableMeterTypes());
|
|
||||||
|
|
||||||
const char *default_fields = xmqGetString(doc, NULL, "/driver/default_fields");
|
xmqForeach(doc, NULL, "/driver/detect/mvt", (XMQNodeCallback)add_detect, di);
|
||||||
if (!default_fields) error("(dynamic) Error in %s cannot find driver/default_fields\n"
|
|
||||||
" Remember to add: default_fields = name,id,total_m3,timestamp\n"
|
|
||||||
" Where you change total_m3 to your meters most important field.\n", file.c_str());
|
|
||||||
|
|
||||||
|
check_detection_triplets(di, file);
|
||||||
|
|
||||||
verbose("(dynamic) loading %s %s\n", meter_type_s, name);
|
di->setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new DriverDynamic(mi, di)); });
|
||||||
|
|
||||||
di->setName(name);
|
return true;
|
||||||
di->setDefaultFields(default_fields);
|
}
|
||||||
di->setMeterType(meter_type);
|
catch (...)
|
||||||
di->setDynamic(file, doc);
|
{
|
||||||
|
return false;
|
||||||
xmqForeach(doc, NULL, "/driver/detect/mvt", add_detect, di);
|
}
|
||||||
|
|
||||||
if (di->detect().size() == 0) error("(dynamic) Error in %s cannot find any driver/detect/mvt triplets\n"
|
|
||||||
" Remember to add: detect { mvt = AAA,05,07 mvt = AAA,06,07 ... }\n"
|
|
||||||
" The triplets contain MANUFACTURER,VERSION,TYPE\n"
|
|
||||||
" and you can see these values when listening to all meters.\n", file.c_str());
|
|
||||||
|
|
||||||
di->setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new DriverDynamic(mi, di)); });
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DriverDynamic::DriverDynamic(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
DriverDynamic::DriverDynamic(MeterInfo &mi, DriverInfo &di) :
|
||||||
|
MeterCommonImplementation(mi, di), file_name_(di.getDynamicFileName())
|
||||||
{
|
{
|
||||||
string file = di.getDynamicFileName();
|
|
||||||
XMQDoc *doc = di.getDynamicDriver();
|
XMQDoc *doc = di.getDynamicDriver();
|
||||||
|
|
||||||
verbose("(dynamic) Constructing driver %s from file %s\n",
|
verbose("(driver) constructing driver %s from file %s\n",
|
||||||
di.name().str().c_str(),
|
di.name().str().c_str(),
|
||||||
file.c_str());
|
fileName().c_str());
|
||||||
|
|
||||||
xmqForeach(doc, NULL, "/driver/field", add_field, this);
|
try
|
||||||
|
{
|
||||||
|
xmqForeach(doc, NULL, "/driver/field", (XMQNodeCallback)add_field, this);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, void *dd)
|
XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, DriverInfo *di)
|
||||||
{
|
{
|
||||||
DriverInfo *di = (DriverInfo*)dd;
|
|
||||||
|
|
||||||
string mvt = xmqGetString(doc, detect, ".");
|
string mvt = xmqGetString(doc, detect, ".");
|
||||||
|
|
||||||
auto fields = splitString(mvt, ',');
|
auto fields = splitString(mvt, ',');
|
||||||
if (fields.size() != 3) error("(dynamic) Error in %s, wrong number of fields in mvt triple: mvt = %s\n"
|
if (fields.size() != 3)
|
||||||
" There should be three fields, for example: mvt = AAA,07,05\n",
|
{
|
||||||
di->getDynamicFileName().c_str(),
|
warning("(driver) error in %s, wrong number of fields in mvt triple: mvt = %s\n"
|
||||||
mvt.c_str());
|
"%s\n"
|
||||||
|
"There should be three fields, for example: mvt = AAA,07,05\n"
|
||||||
|
"%s\n",
|
||||||
|
di->getDynamicFileName().c_str(),
|
||||||
|
mvt.c_str(),
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
string mfct = fields[0];
|
string mfct = fields[0];
|
||||||
int mfct_code = 0;
|
int mfct_code = 0;
|
||||||
long version = strtol(fields[1].c_str(), NULL, 16);
|
long version = strtol(fields[1].c_str(), NULL, 16);
|
||||||
long type = strtol(fields[2].c_str(), NULL, 16);
|
long type = strtol(fields[2].c_str(), NULL, 16);
|
||||||
|
|
||||||
if (mfct.length() == 3)
|
if (mfct.length() == 3)
|
||||||
{
|
{
|
||||||
char a = mfct[0];
|
char a = mfct[0];
|
||||||
|
@ -116,29 +133,67 @@ XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, void *dd)
|
||||||
char c = mfct[2];
|
char c = mfct[2];
|
||||||
if (!(a >= 'A' && a < 'Z' &&
|
if (!(a >= 'A' && a < 'Z' &&
|
||||||
b >= 'A' && b < 'Z' &&
|
b >= 'A' && b < 'Z' &&
|
||||||
c >= 'A' && c < 'Z')) error("(dynamic) %s bad mfct \"%s\" in mvt detect: should be 3 uppercase characters A-Z\n",
|
c >= 'A' && c < 'Z'))
|
||||||
di->name().str().c_str(),
|
{
|
||||||
mfct.c_str());
|
warning("(driver) error in %s, bad manufacturer in mvt triplet: %s\n"
|
||||||
|
"%s\n"
|
||||||
|
"Use 3 uppercase characters A-Z or 4 lowercase hex chars.\n"
|
||||||
|
"%s\n",
|
||||||
|
di->getDynamicFileName().c_str(),
|
||||||
|
mfct.c_str(),
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
mfct_code = toMfctCode(a, b, c);
|
mfct_code = toMfctCode(a, b, c);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
char *eptr;
|
char *eptr;
|
||||||
mfct_code = strtol(mfct.c_str(), &eptr, 16);
|
mfct_code = strtol(mfct.c_str(), &eptr, 16);
|
||||||
if (*eptr) error("(dynmic) Error in %s: mfct must be either version must be in range 0..ff\n", di->name().str().c_str());
|
if (*eptr)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, bad manufacturer in mvt triplet: %s\n"
|
||||||
|
"%s\n"
|
||||||
|
"Use 3 uppercase characters A-Z or 4 lowercase hex chars.\n"
|
||||||
|
"%s\n",
|
||||||
|
di->getDynamicFileName().c_str(),
|
||||||
|
mfct.c_str(),
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version > 255 || version < 0)
|
if (version > 255 || version < 0)
|
||||||
{
|
{
|
||||||
error("(dynmic) Error in %s: version must be in range 0..ff\n", di->name().str().c_str());
|
warning("(driver) error in %s, bad version in mvt triplet: %02x\n"
|
||||||
|
"%s\n"
|
||||||
|
"The version must be a hex value from 00 to ff.\n"
|
||||||
|
"%s\n",
|
||||||
|
di->getDynamicFileName().c_str(),
|
||||||
|
version,
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type > 255 || type < 0)
|
if (type > 255 || type < 0)
|
||||||
{
|
{
|
||||||
error("(dynmic) Error in %s: type must be in range 0..ff\n", di->name().str().c_str());
|
warning("(driver) error in %s, bad type in mvt triplet: %02x\n"
|
||||||
|
"%s\n"
|
||||||
|
"The type must be a hex value from 00 to ff.\n"
|
||||||
|
"%s\n",
|
||||||
|
di->getDynamicFileName().c_str(),
|
||||||
|
type,
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
string mfct_flag = manufacturerFlag(mfct_code);
|
string mfct_flag = manufacturerFlag(mfct_code);
|
||||||
debug("(dynamic) register detection %s %s %2x %02x\n",
|
debug("(driver) register detection %s %s %2x %02x\n",
|
||||||
di->name().str().c_str(),
|
di->getDynamicFileName().c_str(),
|
||||||
mfct_flag.c_str(),
|
mfct_flag.c_str(),
|
||||||
version,
|
version,
|
||||||
type);
|
type);
|
||||||
|
@ -147,70 +202,456 @@ XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, void *dd)
|
||||||
return XMQ_CONTINUE;
|
return XMQ_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
XMQProceed DriverDynamic::add_field(XMQDoc *doc, XMQNode *field, void *d)
|
XMQProceed DriverDynamic::add_field(XMQDoc *doc, XMQNode *field, DriverDynamic *dd)
|
||||||
{
|
{
|
||||||
DriverDynamic *dd = (DriverDynamic*)d;
|
// The field name must be supplied without a unit ie total (not total_m3) since units are managed by wmbusmeters.
|
||||||
const char *driver_name = dd->name().c_str();
|
string name = check_field_name(xmqGetString(doc, field, "name"), dd);
|
||||||
|
|
||||||
const char *name = xmqGetString(doc, field, "name");
|
// The quantity ie Volume, gives the default unit (m3) for the field. The unit can be overriden with display_unit.
|
||||||
const char *quantity_s = xmqGetString(doc, field, "quantity");
|
Quantity quantity = check_field_quantity(xmqGetString(doc, field, "quantity"), dd);
|
||||||
const char *type_s = xmqGetString(doc, field, "type");
|
|
||||||
const char *info_s = xmqGetString(doc, field, "info");
|
|
||||||
const char *attributes_s = xmqGetString(doc, field, "attributes");
|
|
||||||
const char *vif_scaling_s = xmqGetString(doc, field, "vif_scaling");
|
|
||||||
|
|
||||||
debug("(dynamic) field %s %s %s %s\n", name, quantity_s, vif_scaling_s, attributes_s);
|
// Text fields are either version strings or lookups from status bits.
|
||||||
|
// All other fields are numeric, ie they have a unit. This also includes date and datetime.
|
||||||
|
bool is_numeric = quantity != Quantity::Text;
|
||||||
|
|
||||||
FieldType type = toFieldType(type_s);
|
// The vif scaling is by default Auto but can be overriden for pesky fields.
|
||||||
if (type == FieldType::Unknown) error("(dynamic) Error in %s unknown field type %s\n", driver_name, type_s);
|
VifScaling vif_scaling = check_vif_scaling(xmqGetString(doc, field, "vif_scaling"), dd);
|
||||||
|
|
||||||
Quantity quantity = toQuantity(quantity_s);
|
// The properties are by default empty but can be specified for specific fields.
|
||||||
if (quantity == Quantity::Unknown) error("(dynamic) Error in %s unknown quantity %s\n", driver_name, quantity);
|
PrintProperties properties = check_print_properties(xmqGetString(doc, field, "attributes"), dd);
|
||||||
|
|
||||||
VifScaling vif_scaling = toVifScaling(vif_scaling_s);
|
// The about fields explains what the value is for. Ie. is storage 1 the previous day or month value etc.
|
||||||
if (vif_scaling == VifScaling::Unknown) error("(dynamic) Error in %s unknown vif scaling %s\n", driver_name, vif_scaling_s);
|
string info = get_translation(doc, field, "about", language());
|
||||||
|
|
||||||
PrintProperties properties = toPrintProperties(attributes_s);
|
// The calculate formula is optional.
|
||||||
if (properties.hasUnknown()) error("(dynamic) Error in %s unknown attributes %s\n", driver_name, attributes_s);
|
string calculate = check_calculate(xmqGetString(doc, field, "calculate"), dd);
|
||||||
|
|
||||||
|
// The display unit is usually based on the quantity. But you can override it.
|
||||||
|
Unit display_unit = check_display_unit(xmqGetString(doc, field, "display_unit"), dd);
|
||||||
|
|
||||||
|
// Now find all matchers.
|
||||||
FieldMatcher match = FieldMatcher::build();
|
FieldMatcher match = FieldMatcher::build();
|
||||||
|
dd->tmp_matcher_ = &match;
|
||||||
|
int num_matches = xmqForeach(doc, field, "match", (XMQNodeCallback)add_match, dd);
|
||||||
|
// Check if there were any matches at all, if not, then disable the matcher.
|
||||||
|
match.active = num_matches > 0;
|
||||||
|
|
||||||
xmqForeach(doc, field, "match", add_match, &match);
|
if (is_numeric)
|
||||||
|
{
|
||||||
dd->addNumericFieldWithExtractor(
|
if (calculate == "")
|
||||||
|
{
|
||||||
|
dd->addNumericFieldWithExtractor(
|
||||||
|
name,
|
||||||
|
info,
|
||||||
|
properties,
|
||||||
|
quantity,
|
||||||
|
vif_scaling,
|
||||||
|
match,
|
||||||
|
display_unit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (match.active)
|
||||||
|
{
|
||||||
|
dd->addNumericFieldWithCalculator(
|
||||||
|
name,
|
||||||
|
info,
|
||||||
|
properties,
|
||||||
|
quantity,
|
||||||
|
calculate,
|
||||||
|
display_unit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dd->addNumericFieldWithCalculatorAndMatcher(
|
||||||
|
name,
|
||||||
|
info,
|
||||||
|
properties,
|
||||||
|
quantity,
|
||||||
|
calculate,
|
||||||
|
match,
|
||||||
|
display_unit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dd->addStringFieldWithExtractor(
|
||||||
name,
|
name,
|
||||||
info_s,
|
info,
|
||||||
properties,
|
properties,
|
||||||
quantity,
|
|
||||||
vif_scaling,
|
|
||||||
match
|
match
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return XMQ_CONTINUE;
|
return XMQ_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
XMQProceed DriverDynamic::add_match(XMQDoc *doc, XMQNode *match, void *m)
|
XMQProceed DriverDynamic::add_match(XMQDoc *doc, XMQNode *match, DriverDynamic *dd)
|
||||||
{
|
{
|
||||||
FieldMatcher *fm = (FieldMatcher*)m;
|
FieldMatcher *fm = dd->tmp_matcher_;
|
||||||
|
|
||||||
|
check_set_measurement_type(xmqGetString(doc, match, "measurement_type"), fm, dd);
|
||||||
|
|
||||||
|
check_set_vif_range(xmqGetString(doc, match, "vif_range"), fm, dd);
|
||||||
|
|
||||||
const char *measurement_type_s = xmqGetString(doc, match, "measurement_type");
|
|
||||||
const char *vif_range_s = xmqGetString(doc, match, "vif_range");
|
const char *vif_range_s = xmqGetString(doc, match, "vif_range");
|
||||||
|
|
||||||
if (measurement_type_s)
|
|
||||||
{
|
|
||||||
MeasurementType measurement_type = toMeasurementType(measurement_type_s);
|
|
||||||
if (measurement_type == MeasurementType::Unknown)
|
|
||||||
error("(dynamic) Error unknown measurement type %s\n", measurement_type_s);
|
|
||||||
fm->set(measurement_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vif_range_s)
|
if (vif_range_s)
|
||||||
{
|
{
|
||||||
VIFRange vif_range = toVIFRange(vif_range_s);
|
VIFRange vif_range = toVIFRange(vif_range_s);
|
||||||
if (vif_range == VIFRange::None)
|
if (vif_range == VIFRange::None)
|
||||||
error("(dynamic) Error unknown measurement type %s\n", vif_range_s);
|
{
|
||||||
|
warning("(driver) error unknown measurement type %s\n", vif_range_s);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
fm->set(vif_range);
|
fm->set(vif_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
return XMQ_CONTINUE;
|
return XMQ_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string check_driver_name(const char *name, string file)
|
||||||
|
{
|
||||||
|
if (!name)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, cannot find: driver/name\n"
|
||||||
|
"%s\n"
|
||||||
|
"A driver file looks like this: driver { name = abc123 ... }\n"
|
||||||
|
"%s\n",
|
||||||
|
file.c_str(),
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_lowercase_alnum_text(name))
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, bad driver name: %s\n"
|
||||||
|
"%s\n"
|
||||||
|
"The driver name must consist of lower case ascii a-z and digits 0-9.\n"
|
||||||
|
"%s\n",
|
||||||
|
file.c_str(),
|
||||||
|
name,
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeterType check_meter_type(const char *meter_type_s, string file)
|
||||||
|
{
|
||||||
|
if (!meter_type_s)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, cannot find: driver/meter_type\n"
|
||||||
|
"%s\n"
|
||||||
|
"Remember to add: meter_type = ...\n"
|
||||||
|
"Available meter types are:\n%s\n"
|
||||||
|
"%s\n",
|
||||||
|
file.c_str(),
|
||||||
|
line,
|
||||||
|
availableMeterTypes(),
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeterType meter_type = toMeterType(meter_type_s);
|
||||||
|
|
||||||
|
if (meter_type == MeterType::UnknownMeter)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, unknown meter type: %s\n"
|
||||||
|
"%s\n"
|
||||||
|
"Available meter types are:\n%s\n"
|
||||||
|
"%s\n",
|
||||||
|
file.c_str(),
|
||||||
|
meter_type_s,
|
||||||
|
line,
|
||||||
|
availableMeterTypes(),
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return meter_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
string check_default_fields(const char *default_fields, string file)
|
||||||
|
{
|
||||||
|
if (!default_fields)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, cannot find: driver/default_fields\n"
|
||||||
|
"%s\n"
|
||||||
|
"Remember to add for example: default_fields = name,id,total_m3,timestamp\n"
|
||||||
|
"Where you change total_m3 to your meters most important field.\n"
|
||||||
|
"%s\n",
|
||||||
|
file.c_str(),
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default_fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_detection_triplets(DriverInfo *di, string file)
|
||||||
|
{
|
||||||
|
if (di->detect().size() == 0)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, cannot find any detection triplets: driver/detect/mvt\n"
|
||||||
|
"%s\n"
|
||||||
|
"Remember to add: detect { mvt = AAA,05,07 mvt = AAA,06,07 ... }\n"
|
||||||
|
"The triplets consists of MANUFACTURER,VERSION,TYPE\n"
|
||||||
|
"You can see these values when listening to all meters.\n"
|
||||||
|
"The manufacturer can be given as three uppercase characters A-Z\n"
|
||||||
|
"or as 4 lower case hex digits.\n"
|
||||||
|
"%s\n",
|
||||||
|
file.c_str(),
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string check_field_name(const char *name, DriverDynamic *dd)
|
||||||
|
{
|
||||||
|
if (!name)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, cannot find: driver/field/name\n"
|
||||||
|
"%s\n"
|
||||||
|
"Remember to add for example: field { name = total ... }\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
string vname;
|
||||||
|
Unit u;
|
||||||
|
if (extractUnit(string(name), &vname, &u))
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, bad field name %s (field names should not have units)\n"
|
||||||
|
"%s\n"
|
||||||
|
"The field name should not have a unit since units are added automatically.\n"
|
||||||
|
"Either indirectly based on the quantity or directly based on the display_unit.\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
name,
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quantity check_field_quantity(const char *quantity_s, DriverDynamic *dd)
|
||||||
|
{
|
||||||
|
if (!quantity_s)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, cannot find: driver/field/quantity\n"
|
||||||
|
"%s\n"
|
||||||
|
"Remember to add for example: field { quantity = Volume ... }\n"
|
||||||
|
"Available quantities:\n%s\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
line,
|
||||||
|
availableQuantities(),
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quantity quantity = toQuantity(quantity_s);
|
||||||
|
|
||||||
|
if (quantity == Quantity::Unknown)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, bad quantity: %s\n"
|
||||||
|
"%s\n"
|
||||||
|
"Available quantities:\n"
|
||||||
|
"%s\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
quantity_s,
|
||||||
|
line,
|
||||||
|
availableQuantities(),
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
VifScaling check_vif_scaling(const char *vif_scaling_s, DriverDynamic *dd)
|
||||||
|
{
|
||||||
|
if (!vif_scaling_s)
|
||||||
|
{
|
||||||
|
return VifScaling::Auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
VifScaling vif_scaling = toVifScaling(vif_scaling_s);
|
||||||
|
|
||||||
|
if (vif_scaling == VifScaling::Unknown)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, bad vif scaling: %s\n",
|
||||||
|
"%s\n"
|
||||||
|
"Available vif scalings:\n"
|
||||||
|
"%s\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
vif_scaling_s,
|
||||||
|
line,
|
||||||
|
"???",
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vif_scaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintProperties check_print_properties(const char *print_properties_s, DriverDynamic *dd)
|
||||||
|
{
|
||||||
|
if (!print_properties_s)
|
||||||
|
{
|
||||||
|
return PrintProperties(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintProperties print_properties = toPrintProperties(print_properties_s);
|
||||||
|
if (print_properties.hasUnknown())
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, unknown attributes: %s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
print_properties_s);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return print_properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
string get_translation(XMQDoc *doc, XMQNode *node, string name, string lang)
|
||||||
|
{
|
||||||
|
string xpath = name+"/"+lang;
|
||||||
|
const char *txt = xmqGetString(doc, node, xpath.c_str());
|
||||||
|
if (!txt)
|
||||||
|
{
|
||||||
|
xpath = name+"/en";
|
||||||
|
txt = xmqGetString(doc, node, xpath.c_str());
|
||||||
|
if (!txt)
|
||||||
|
{
|
||||||
|
txt = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
string check_calculate(const char *formula, DriverDynamic *dd)
|
||||||
|
{
|
||||||
|
if (!formula) return "";
|
||||||
|
|
||||||
|
return formula;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit check_display_unit(const char *display_unit_s, DriverDynamic *dd)
|
||||||
|
{
|
||||||
|
if (!display_unit_s)
|
||||||
|
{
|
||||||
|
return Unit::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit u = toUnit(display_unit_s);
|
||||||
|
if (u == Unit::Unknown)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, unknown display unit: %s\n"
|
||||||
|
"Available units:\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
display_unit_s,
|
||||||
|
availableUnits());
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_set_measurement_type(const char *measurement_type_s, FieldMatcher *fm, DriverDynamic *dd)
|
||||||
|
{
|
||||||
|
if (!measurement_type_s)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, cannot find: driver/field/match/measurement_type\n"
|
||||||
|
"%s\n"
|
||||||
|
"Remember to add for example: match { measurement_type = Instantaneous ... }\n"
|
||||||
|
"Available measurement types:\n"
|
||||||
|
"Instantaneous\n"
|
||||||
|
"Minimum\n"
|
||||||
|
"Maximum\n"
|
||||||
|
"AtError\n"
|
||||||
|
"Any\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeasurementType measurement_type = toMeasurementType(measurement_type_s);
|
||||||
|
|
||||||
|
if (measurement_type == MeasurementType::Unknown)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, bad measurement_type: %s\n"
|
||||||
|
"%s\n"
|
||||||
|
"Available measurement types:\n"
|
||||||
|
"Instantaneous\n"
|
||||||
|
"Minimum\n"
|
||||||
|
"Maximum\n"
|
||||||
|
"AtError\n"
|
||||||
|
"Any\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
measurement_type_s,
|
||||||
|
line,
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fm->set(measurement_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_set_vif_range(const char *vif_range_s, FieldMatcher *fm, DriverDynamic *dd)
|
||||||
|
{
|
||||||
|
if (!vif_range_s)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, cannot find: driver/field/match/vif_range\n"
|
||||||
|
"%s\n"
|
||||||
|
"Remember to add for example: match { ... vif_range = ReturnTemperature ... }\n"
|
||||||
|
"Available vif ranges:\n"
|
||||||
|
"%s\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
line,
|
||||||
|
availableVIFRanges().c_str(),
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
VIFRange vif_range = toVIFRange(vif_range_s);
|
||||||
|
|
||||||
|
if (vif_range == VIFRange::None)
|
||||||
|
{
|
||||||
|
warning("(driver) error in %s, bad vif_range: %s\n"
|
||||||
|
"%s\n"
|
||||||
|
"Available vif ranges:\n"
|
||||||
|
"%s\n"
|
||||||
|
"%s\n",
|
||||||
|
dd->fileName().c_str(),
|
||||||
|
vif_range_s,
|
||||||
|
line,
|
||||||
|
availableVIFRanges().c_str(),
|
||||||
|
line);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fm->set(vif_range);
|
||||||
|
}
|
||||||
|
|
|
@ -24,9 +24,16 @@ struct DriverDynamic : public virtual MeterCommonImplementation
|
||||||
{
|
{
|
||||||
DriverDynamic(MeterInfo &mi, DriverInfo &di);
|
DriverDynamic(MeterInfo &mi, DriverInfo &di);
|
||||||
static bool load(DriverInfo *di, const string &name);
|
static bool load(DriverInfo *di, const string &name);
|
||||||
static XMQProceed add_detect(XMQDoc *doc, XMQNode *detect, void *dd);
|
static XMQProceed add_detect(XMQDoc *doc, XMQNode *detect, DriverInfo *di);
|
||||||
static XMQProceed add_field(XMQDoc *doc, XMQNode *field, void *dd);
|
static XMQProceed add_field(XMQDoc *doc, XMQNode *field, DriverDynamic *dd);
|
||||||
static XMQProceed add_match(XMQDoc *doc, XMQNode *match, void *dd);
|
static XMQProceed add_match(XMQDoc *doc, XMQNode *match, DriverDynamic *dd);
|
||||||
|
|
||||||
|
const string &fileName() { return file_name_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
string file_name_;
|
||||||
|
FieldMatcher *tmp_matcher_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -124,7 +124,7 @@ namespace
|
||||||
);
|
);
|
||||||
|
|
||||||
addNumericFieldWithExtractor(
|
addNumericFieldWithExtractor(
|
||||||
"total_at_date",
|
"total_at_woot",
|
||||||
"Fix this! The total water consumption recorded at last day. Perhaps?",
|
"Fix this! The total water consumption recorded at last day. Perhaps?",
|
||||||
DEFAULT_PRINT_PROPERTIES,
|
DEFAULT_PRINT_PROPERTIES,
|
||||||
Quantity::Volume,
|
Quantity::Volume,
|
||||||
|
@ -163,14 +163,14 @@ namespace
|
||||||
// Test: HydrusWater hydrus 64646464 NOKEY
|
// Test: HydrusWater hydrus 64646464 NOKEY
|
||||||
// Comment:
|
// Comment:
|
||||||
// telegram=|4E44A5116464646470077AED004005_2F2F01FD08300C13741100007C1300000000FC101300000000FC201300000000726C00000B3B00000002FD748713025A6800C4016D3B177F2ACC011300020000|
|
// telegram=|4E44A5116464646470077AED004005_2F2F01FD08300C13741100007C1300000000FC101300000000FC201300000000726C00000B3B00000002FD748713025A6800C4016D3B177F2ACC011300020000|
|
||||||
// {"media":"water","meter":"hydrus","name":"HydrusWater","id":"64646464","total_m3":1.174,"flow_m3h":0,"flow_temperature_c":10.4,"remaining_battery_life_y":13.686797,"status":"OK","at_datetime":"2019-10-31 23:59","total_at_date_m3": 0.2,"timestamp":"1111-11-11T11:11:11Z"}
|
// {"media":"water","meter":"hydrus","name":"HydrusWater","id":"64646464","total_m3":1.174,"flow_m3h":0,"flow_temperature_c":10.4,"remaining_battery_life_y":13.686797,"status":"OK","at_datetime":"2019-10-31 23:59","total_at_woot_m3": 0.2,"timestamp":"1111-11-11T11:11:11Z"}
|
||||||
// |HydrusWater;64646464;1.174;0.2;OK;1111-11-11 11:11.11
|
// |HydrusWater;64646464;1.174;null;OK;1111-11-11 11:11.11
|
||||||
|
|
||||||
// Test: HydrusVater hydrus 65656565 NOKEY
|
// Test: HydrusVater hydrus 65656565 NOKEY
|
||||||
// Comment:
|
// Comment:
|
||||||
// telegram=|3E44A5116565656570067AFB0030052F2F_0C13503400000DFD110A383731303134423032410B3B00000002FD74DC15C4016D3B178D29CC0113313400002F2F|
|
// telegram=|3E44A5116565656570067AFB0030052F2F_0C13503400000DFD110A383731303134423032410B3B00000002FD74DC15C4016D3B178D29CC0113313400002F2F|
|
||||||
// {"media":"warm water","meter":"hydrus","name":"HydrusVater","id":"65656565","flow_m3h":0,"customer": "A20B410178","total_m3":3.45,"total_at_date_m3":3.431,"remaining_battery_life_y":15.321328,"at_datetime":"2020-09-13 23:59","total_at_date_m3": 3.431,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
// {"media":"warm water","meter":"hydrus","name":"HydrusVater","id":"65656565","flow_m3h":0,"customer": "A20B410178","total_m3":3.45,"remaining_battery_life_y":15.321328,"at_datetime":"2020-09-13 23:59","total_at_woot_m3": 3.431,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||||
// |HydrusVater;65656565;3.45;3.431;OK;1111-11-11 11:11.11
|
// |HydrusVater;65656565;3.45;null;OK;1111-11-11 11:11.11
|
||||||
|
|
||||||
// Test: HydrusAES hydrus 64745666 NOKEY
|
// Test: HydrusAES hydrus 64745666 NOKEY
|
||||||
// Comment:
|
// Comment:
|
||||||
|
@ -189,3 +189,9 @@ namespace
|
||||||
// telegram=|1E4424238B06204790607A2A0010D8_0413DDC00000426CBF23441382BB0000|
|
// telegram=|1E4424238B06204790607A2A0010D8_0413DDC00000426CBF23441382BB0000|
|
||||||
// {"media":"warm water","meter":"hydrus","name":"HydrusIzarRSWarm","id":"60904720","total_m3":49.373,"total_at_date_m3":48.002,"at_date":"2021-03-31","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
// {"media":"warm water","meter":"hydrus","name":"HydrusIzarRSWarm","id":"60904720","total_m3":49.373,"total_at_date_m3":48.002,"at_date":"2021-03-31","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||||
// |HydrusIzarRSWarm;60904720;49.373;48.002;OK;1111-11-11 11:11.11
|
// |HydrusIzarRSWarm;60904720;49.373;48.002;OK;1111-11-11 11:11.11
|
||||||
|
|
||||||
|
// Test: HydrusFoo hydrus 64641820 NOKEY
|
||||||
|
// Comment: Negative power values.
|
||||||
|
// telegram=|6344A5112018646470078C00D7900F002C256AB59B00F0F13032019092DE7A6A004007102F2F0C13896729004C1323462400CC101300000000CC201323462400426CDF2C0B3B0200F002FD742F0D025AC100C4016D3B17FE29CC01132841290001FD089F|
|
||||||
|
// {"at_date": "2022-12-31","at_datetime": "2023-09-30 23:59","flow_m3h": -0.002,"flow_temperature_c": 19.3,"id": "64641820","media": "water","meter": "hydrus","name": "HydrusFoo","remaining_battery_life_y": 9.240436,"status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_date_m3":244.623,"total_at_woot_m3": 294.128,"total_m3": 296.789,"total_tariff1_at_date_m3": 0,"total_tariff2_at_date_m3": 244.623}
|
||||||
|
// |HydrusFoo;64641820;296.789;244.623;OK;1111-11-11 11:11.11
|
||||||
|
|
|
@ -33,6 +33,7 @@ namespace
|
||||||
di.addDetection(MANUFACTURER_ITW, 0x07, 0x00);
|
di.addDetection(MANUFACTURER_ITW, 0x07, 0x00);
|
||||||
di.addDetection(MANUFACTURER_ITW, 0x07, 0x03);
|
di.addDetection(MANUFACTURER_ITW, 0x07, 0x03);
|
||||||
di.addDetection(MANUFACTURER_ITW, 0x07, 0x33);
|
di.addDetection(MANUFACTURER_ITW, 0x07, 0x33);
|
||||||
|
di.addDetection(MANUFACTURER_ITW, 0x16, 0x00);
|
||||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -149,3 +150,10 @@ namespace
|
||||||
// telegram=|384497265909312000077a930000a0041360B50100066d101295f427004413ac570100426cdf2c047f0000060c027f6c2a0e79000000000000|
|
// telegram=|384497265909312000077a930000a0041360B50100066d101295f427004413ac570100426cdf2c047f0000060c027f6c2a0e79000000000000|
|
||||||
// {"enhanced_id": "000000000000","id": "20310959","media": "water","meter": "itron","meter_datetime": "2023-07-20 21:18:16","name": "AnyWater","status": "OK","target_date": "2022-12-31","target_m3": 87.98,"timestamp": "1111-11-11T11:11:11Z","total_m3": 111.968,"unknown_a": "WOOTA_C060000","unknown_b": "WOOTB_2A6C"}
|
// {"enhanced_id": "000000000000","id": "20310959","media": "water","meter": "itron","meter_datetime": "2023-07-20 21:18:16","name": "AnyWater","status": "OK","target_date": "2022-12-31","target_m3": 87.98,"timestamp": "1111-11-11T11:11:11Z","total_m3": 111.968,"unknown_a": "WOOTA_C060000","unknown_b": "WOOTB_2A6C"}
|
||||||
// |AnyWater;20310959;111.968;87.98;1111-11-11 11:11.11
|
// |AnyWater;20310959;111.968;87.98;1111-11-11 11:11.11
|
||||||
|
|
||||||
|
// Test: ColdWaterMeter itron 23362098 NOKEY
|
||||||
|
// Comment: Allmess cold water with Itron Module programmed with type 0x16
|
||||||
|
// telegram=|3A4497269820362300167AF60020A52F2F_04132E100000066D03260DE12B007413FEFEFEFE426C1F01047F1600060C027F9A2A0E79187103002300|
|
||||||
|
// {"enhanced_id": "002300037118", "id": "23362098", "media": "cold water", "meter": "itron", "meter_datetime": "2023-11-01 13:38:03", "name": "ColdWaterMeter", "status": "OK", "target_date": "2000-01-31", "timestamp": "1111-11-11T11:11:11Z", "total_m3": 4.142,"unknown_a": "WOOTA_C060016","unknown_b": "WOOTB_2A9A" }
|
||||||
|
// |ColdWaterMeter;23362098;4.142;null;1111-11-11 11:11.11
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2021 Olli Salonen (gpl-3.0-or-later)
|
Copyright (C) 2021 Olli Salonen (gpl-3.0-or-later)
|
||||||
Copyright (C) 2022 Fredrik Öhrström (gpl-3.0-or-later)
|
Copyright (C) 2022-2023 Fredrik Öhrström (gpl-3.0-or-later)
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -207,8 +207,8 @@ namespace
|
||||||
|
|
||||||
// Test: Mino minomess 15503451 NOKEY
|
// Test: Mino minomess 15503451 NOKEY
|
||||||
// telegram=|6644496A1064035514377251345015496A0007EE0050052F2F#0C1359000000026CBE2B82046CA12B8C0413FFFFFFFF8D0493132CFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02FD1700002F2F|
|
// telegram=|6644496A1064035514377251345015496A0007EE0050052F2F#0C1359000000026CBE2B82046CA12B8C0413FFFFFFFF8D0493132CFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02FD1700002F2F|
|
||||||
// {"media":"water","meter":"minomess","name":"Mino","id":"15503451","meter_date":"2021-11-30","total_m3":0.059,"target_m3":244444.442,"target_date":"2021-11-01","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
// {"media":"water","meter":"minomess","name":"Mino","id":"15503451","meter_date":"2021-11-30","total_m3":0.059,"target_date":"2021-11-01","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||||
// |Mino;15503451;0.059;244444.442;OK;1111-11-11 11:11.11
|
// |Mino;15503451;0.059;null;OK;1111-11-11 11:11.11
|
||||||
|
|
||||||
// Test: Minowired minomess 57575757 NOKEY
|
// Test: Minowired minomess 57575757 NOKEY
|
||||||
// telegram=|6874746808007257575757496A000712000000_0C7857575757046D2414DE280413000000000C943C000000004413FFFFFFFF426CFFFF840113FFFFFFFF82016CFFFFC40113FFFFFFFFC2016CFFFF840213FFFFFFFF82026CFFFF043B000000000422E62F000004260000000034220000000002FD1700001F5716|
|
// telegram=|6874746808007257575757496A000712000000_0C7857575757046D2414DE280413000000000C943C000000004413FFFFFFFF426CFFFF840113FFFFFFFF82016CFFFFC40113FFFFFFFFC2016CFFFF840213FFFFFFFF82026CFFFF043B000000000422E62F000004260000000034220000000002FD1700001F5716|
|
||||||
|
@ -223,4 +223,4 @@ namespace
|
||||||
// Test: Zenner_warm minomess 51413121 NOKEY
|
// Test: Zenner_warm minomess 51413121 NOKEY
|
||||||
// telegram=|6644496A8753155518377221314151496A0106300050052F2F_0C1357000000026CEC2182046CE1218C0413000000808D0493132C33FE00008000008000008000008000008000008000008000008000008000008000008000008000008000008002FD1700002F2F|
|
// telegram=|6644496A8753155518377221314151496A0106300050052F2F_0C1357000000026CEC2182046CE1218C0413000000808D0493132C33FE00008000008000008000008000008000008000008000008000008000008000008000008000008000008002FD1700002F2F|
|
||||||
// {"media":"warm water","meter":"minomess","name":"Zenner_warm","id":"51413121","meter_date":"2023-01-12","total_m3":0.057,"target_m3":80000,"target_date":"2023-01-01","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
// {"media":"warm water","meter":"minomess","name":"Zenner_warm","id":"51413121","meter_date":"2023-01-12","total_m3":0.057,"target_m3":80000,"target_date":"2023-01-01","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||||
// |Zenner_warm;51413121;0.057;80000;OK;1111-11-11 11:11.11
|
// |Zenner_warm;51413121;0.057;80000;OK;1111-11-11 11:11.11
|
||||||
|
|
|
@ -222,8 +222,8 @@ namespace
|
||||||
|
|
||||||
// Test: zenner_heat qcaloric 25932395 NOKEY
|
// Test: zenner_heat qcaloric 25932395 NOKEY
|
||||||
// telegram=|5E44496A95239325FD087A2CC050052F2F_0B6E030100426CDF2C4B6EFFFFFF82046CE1228B046E6200008D04EE132C3BFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F2F2F2F|
|
// telegram=|5E44496A95239325FD087A2CC050052F2F_0B6E030100426CDF2C4B6EFFFFFF82046CE1228B046E6200008D04EE132C3BFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F2F2F2F|
|
||||||
// {"media":"heat cost allocation","meter":"qcaloric","name":"zenner_heat","id":"25932395","status":"UNKNOWN_C0","current_consumption_hca":103,"set_date":"2022-12-31","consumption_at_set_date_hca":2444442,"set_date_1":"2022-12-31","consumption_at_set_date_1_hca":2444442,"set_date_8":"2023-02-01","consumption_at_set_date_8_hca":62,"timestamp":"1111-11-11T11:11:11Z"}
|
// {"media":"heat cost allocation","meter":"qcaloric","name":"zenner_heat","id":"25932395","status":"UNKNOWN_C0","current_consumption_hca":103,"set_date":"2022-12-31","set_date_1":"2022-12-31","set_date_8":"2023-02-01","consumption_at_set_date_8_hca":62,"timestamp":"1111-11-11T11:11:11Z"}
|
||||||
// |zenner_heat;25932395;103;2022-12-31;2444442;1111-11-11 11:11.11
|
// |zenner_heat;25932395;103;2022-12-31;null;1111-11-11 11:11.11
|
||||||
|
|
||||||
// Comment: Normal telegram that fills in values.
|
// Comment: Normal telegram that fills in values.
|
||||||
// telegram=|314493449392919034087a520000200b6e9700004b6e700200426c9f2ccb086e970000c2086cbe26326cffff046d2d16a227|
|
// telegram=|314493449392919034087a520000200b6e9700004b6e700200426c9f2ccb086e970000c2086cbe26326cffff046d2d16a227|
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
#include"util.h"
|
#include"util.h"
|
||||||
|
|
||||||
#include<assert.h>
|
#include<assert.h>
|
||||||
|
#include<cmath>
|
||||||
|
#include<math.h>
|
||||||
#include<memory.h>
|
#include<memory.h>
|
||||||
#include<limits>
|
#include<limits>
|
||||||
|
|
||||||
|
@ -760,6 +762,15 @@ bool checkSizeHex(size_t expected_len, DifVifKey &dvk, string &v)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_all_F(string &v)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < v.length(); ++i)
|
||||||
|
{
|
||||||
|
if (v[i] != 'F') return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool DVEntry::extractDouble(double *out, bool auto_scale, bool assume_signed)
|
bool DVEntry::extractDouble(double *out, bool auto_scale, bool assume_signed)
|
||||||
{
|
{
|
||||||
int t = dif_vif_key.dif() & 0xf;
|
int t = dif_vif_key.dif() & 0xf;
|
||||||
|
@ -847,10 +858,18 @@ bool DVEntry::extractDouble(double *out, bool auto_scale, bool assume_signed)
|
||||||
t == 0xC || // 8 digit BCD
|
t == 0xC || // 8 digit BCD
|
||||||
t == 0xE) // 12 digit BCD
|
t == 0xE) // 12 digit BCD
|
||||||
{
|
{
|
||||||
|
// Signed BCD values are always visible in bcd! Top nybble is f. We can force assume_signed to true.
|
||||||
|
assume_signed = true;
|
||||||
// 74140000 -> 00001474
|
// 74140000 -> 00001474
|
||||||
string& v = value;
|
string& v = value;
|
||||||
uint64_t raw = 0;
|
uint64_t raw = 0;
|
||||||
bool negate = false;
|
bool negate = false;
|
||||||
|
|
||||||
|
if (is_all_F(v))
|
||||||
|
{
|
||||||
|
*out = std::nan("");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (t == 0x9) {
|
if (t == 0x9) {
|
||||||
if (!checkSizeHex(2, dif_vif_key, v)) return false;
|
if (!checkSizeHex(2, dif_vif_key, v)) return false;
|
||||||
if (assume_signed && v[0] == 'F') { negate = true; v[0] = '0'; }
|
if (assume_signed && v[0] == 'F') { negate = true; v[0] = '0'; }
|
||||||
|
@ -1009,24 +1028,33 @@ bool DVEntry::extractLong(uint64_t *out)
|
||||||
{
|
{
|
||||||
// 74140000 -> 00001474
|
// 74140000 -> 00001474
|
||||||
string& v = value;
|
string& v = value;
|
||||||
|
if (is_all_F(v))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
uint64_t raw = 0;
|
uint64_t raw = 0;
|
||||||
|
bool negate = false;
|
||||||
if (t == 0x9) {
|
if (t == 0x9) {
|
||||||
if (!checkSizeHex(2, dif_vif_key, value)) return false;
|
if (!checkSizeHex(2, dif_vif_key, value)) return false;
|
||||||
|
if (v[0] == 'F') { negate = true; v[0] = '0'; }
|
||||||
assert(v.size() == 2);
|
assert(v.size() == 2);
|
||||||
raw = (v[0]-'0')*10 + (v[1]-'0');
|
raw = (v[0]-'0')*10 + (v[1]-'0');
|
||||||
} else if (t == 0xA) {
|
} else if (t == 0xA) {
|
||||||
if (!checkSizeHex(4, dif_vif_key, value)) return false;
|
if (!checkSizeHex(4, dif_vif_key, value)) return false;
|
||||||
|
if (v[2] == 'F') { negate = true; v[2] = '0'; }
|
||||||
assert(v.size() == 4);
|
assert(v.size() == 4);
|
||||||
raw = (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10
|
raw = (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10
|
||||||
+ (v[0]-'0')*10 + (v[1]-'0');
|
+ (v[0]-'0')*10 + (v[1]-'0');
|
||||||
} else if (t == 0xB) {
|
} else if (t == 0xB) {
|
||||||
if (!checkSizeHex(6, dif_vif_key, value)) return false;
|
if (!checkSizeHex(6, dif_vif_key, value)) return false;
|
||||||
|
if (v[4] == 'F') { negate = true; v[4] = '0'; }
|
||||||
assert(v.size() == 6);
|
assert(v.size() == 6);
|
||||||
raw = (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10
|
raw = (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10
|
||||||
+ (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10
|
+ (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10
|
||||||
+ (v[0]-'0')*10 + (v[1]-'0');
|
+ (v[0]-'0')*10 + (v[1]-'0');
|
||||||
} else if (t == 0xC) {
|
} else if (t == 0xC) {
|
||||||
if (!checkSizeHex(8, dif_vif_key, value)) return false;
|
if (!checkSizeHex(8, dif_vif_key, value)) return false;
|
||||||
|
if (v[6] == 'F') { negate = true; v[6] = '0'; }
|
||||||
assert(v.size() == 8);
|
assert(v.size() == 8);
|
||||||
raw = (v[6]-'0')*10*10*10*10*10*10*10 + (v[7]-'0')*10*10*10*10*10*10
|
raw = (v[6]-'0')*10*10*10*10*10*10*10 + (v[7]-'0')*10*10*10*10*10*10
|
||||||
+ (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10
|
+ (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10
|
||||||
|
@ -1034,6 +1062,7 @@ bool DVEntry::extractLong(uint64_t *out)
|
||||||
+ (v[0]-'0')*10 + (v[1]-'0');
|
+ (v[0]-'0')*10 + (v[1]-'0');
|
||||||
} else if (t == 0xE) {
|
} else if (t == 0xE) {
|
||||||
if (!checkSizeHex(12, dif_vif_key, value)) return false;
|
if (!checkSizeHex(12, dif_vif_key, value)) return false;
|
||||||
|
if (v[10] == 'F') { negate = true; v[10] = '0'; }
|
||||||
assert(v.size() == 12);
|
assert(v.size() == 12);
|
||||||
raw =(v[10]-'0')*10*10*10*10*10*10*10*10*10*10*10 + (v[11]-'0')*10*10*10*10*10*10*10*10*10*10
|
raw =(v[10]-'0')*10*10*10*10*10*10*10*10*10*10*10 + (v[11]-'0')*10*10*10*10*10*10*10*10*10*10
|
||||||
+ (v[8]-'0')*10*10*10*10*10*10*10*10*10 + (v[9]-'0')*10*10*10*10*10*10*10*10
|
+ (v[8]-'0')*10*10*10*10*10*10*10*10*10 + (v[9]-'0')*10*10*10*10*10*10*10*10
|
||||||
|
@ -1043,6 +1072,11 @@ bool DVEntry::extractLong(uint64_t *out)
|
||||||
+ (v[0]-'0')*10 + (v[1]-'0');
|
+ (v[0]-'0')*10 + (v[1]-'0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (negate)
|
||||||
|
{
|
||||||
|
raw = (uint64_t)(((int64_t)raw)*-1);
|
||||||
|
}
|
||||||
|
|
||||||
*out = raw;
|
*out = raw;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1441,3 +1475,18 @@ const char *toString(DVEntryCounterType ct)
|
||||||
|
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string available_vif_ranges_;
|
||||||
|
|
||||||
|
const string &availableVIFRanges()
|
||||||
|
{
|
||||||
|
if (available_vif_ranges_ != "") return available_vif_ranges_;
|
||||||
|
|
||||||
|
#define X(n,from,to,q,u) available_vif_ranges_ += string(#n) + "\n";
|
||||||
|
LIST_OF_VIF_RANGES
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
// Remove last newline
|
||||||
|
available_vif_ranges_.pop_back();
|
||||||
|
return available_vif_ranges_;
|
||||||
|
}
|
||||||
|
|
|
@ -584,4 +584,6 @@ bool extractDVdate(std::map<std::string,std::pair<int,DVEntry>> *values,
|
||||||
struct tm *value);
|
struct tm *value);
|
||||||
|
|
||||||
|
|
||||||
|
const std::string &availableVIFRanges();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -298,6 +298,16 @@ public:
|
||||||
{
|
{
|
||||||
string best_driver = "";
|
string best_driver = "";
|
||||||
|
|
||||||
|
if (only != "")
|
||||||
|
{
|
||||||
|
DriverInfo di;
|
||||||
|
if (!lookupDriverInfo(only, &di))
|
||||||
|
{
|
||||||
|
error("No such driver %s\n", only.c_str());
|
||||||
|
}
|
||||||
|
only = di.name().str();
|
||||||
|
}
|
||||||
|
|
||||||
for (DriverInfo *ndr : allDrivers())
|
for (DriverInfo *ndr : allDrivers())
|
||||||
{
|
{
|
||||||
string driver_name = toString(*ndr);
|
string driver_name = toString(*ndr);
|
||||||
|
|
|
@ -195,9 +195,10 @@ string loadDriver(const string &file)
|
||||||
// Check that the driver name has not been registered before!
|
// Check that the driver name has not been registered before!
|
||||||
if (lookupDriver(di.name().str()) != NULL)
|
if (lookupDriver(di.name().str()) != NULL)
|
||||||
{
|
{
|
||||||
error("Cannot load driver %s %s since it is already registered!\n",
|
debug("Ignoring loaded driver %s %s since it is already registered!\n",
|
||||||
di.name().str().c_str(),
|
di.name().str().c_str(),
|
||||||
file.c_str());
|
file.c_str());
|
||||||
|
return di.name().str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that no other driver also triggers on the same detection values.
|
// Check that no other driver also triggers on the same detection values.
|
||||||
|
@ -3016,36 +3017,6 @@ VifScaling toVifScaling(const char *s)
|
||||||
return VifScaling::Unknown;
|
return VifScaling::Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldType toFieldType(const char *s)
|
|
||||||
{
|
|
||||||
if (!strcmp(s, "NumericFieldWithExtractor")) return FieldType::NumericFieldWithExtractor;
|
|
||||||
if (!strcmp(s, "NumericFieldWithCalculator")) return FieldType::NumericFieldWithCalculator;
|
|
||||||
if (!strcmp(s, "NumericFieldWithCalculatorAndMatcher")) return FieldType::NumericFieldWithCalculatorAndMatcher;
|
|
||||||
if (!strcmp(s, "NumericField")) return FieldType::NumericField;
|
|
||||||
|
|
||||||
if (!strcmp(s, "StringFieldWithExtractor")) return FieldType::StringFieldWithExtractor;
|
|
||||||
if (!strcmp(s, "StringFieldWithExtractorAndLookup")) return FieldType::StringFieldWithExtractorAndLookup;
|
|
||||||
if (!strcmp(s, "StringField")) return FieldType::StringField;
|
|
||||||
|
|
||||||
return FieldType::Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *toString(FieldType ft)
|
|
||||||
{
|
|
||||||
switch (ft) {
|
|
||||||
case FieldType::NumericFieldWithExtractor: return "NumericFieldWithExtractor";
|
|
||||||
case FieldType::NumericFieldWithCalculator: return "NumericFieldWithCalculator";
|
|
||||||
case FieldType::NumericFieldWithCalculatorAndMatcher: return "NumericFieldWithCalculatorAndMatcher";
|
|
||||||
case FieldType::NumericField: return "NumericField";
|
|
||||||
case FieldType::StringFieldWithExtractor: return "StringFieldWithExtractor";
|
|
||||||
case FieldType::StringFieldWithExtractorAndLookup: return "StringFieldWithExtractorAndLookup";
|
|
||||||
case FieldType::StringField: return "StringField";
|
|
||||||
case FieldType::Unknown: return "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* toString(PrintProperty p)
|
const char* toString(PrintProperty p)
|
||||||
{
|
{
|
||||||
switch(p)
|
switch(p)
|
||||||
|
|
|
@ -54,21 +54,6 @@ struct StringField
|
||||||
StringField(std::string v, FieldInfo *f) : value(v), field_info(f) {}
|
StringField(std::string v, FieldInfo *f) : value(v), field_info(f) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class FieldType
|
|
||||||
{
|
|
||||||
NumericFieldWithExtractor,
|
|
||||||
NumericFieldWithCalculator,
|
|
||||||
NumericFieldWithCalculatorAndMatcher,
|
|
||||||
NumericField,
|
|
||||||
StringFieldWithExtractor,
|
|
||||||
StringFieldWithExtractorAndLookup,
|
|
||||||
StringField,
|
|
||||||
Unknown
|
|
||||||
};
|
|
||||||
|
|
||||||
FieldType toFieldType(const char *s);
|
|
||||||
const char *toString(FieldType ft);
|
|
||||||
|
|
||||||
struct MeterCommonImplementation : public virtual Meter
|
struct MeterCommonImplementation : public virtual Meter
|
||||||
{
|
{
|
||||||
int index();
|
int index();
|
||||||
|
|
|
@ -589,7 +589,10 @@ bool SerialDeviceFile::open(bool fail_if_not_ok)
|
||||||
fd_ = ::open(file_.c_str(), O_RDONLY | O_NONBLOCK);
|
fd_ = ::open(file_.c_str(), O_RDONLY | O_NONBLOCK);
|
||||||
if (fd_ == -1)
|
if (fd_ == -1)
|
||||||
{
|
{
|
||||||
if (fail_if_not_ok) error("Could not open file %s for reading.\n", file_.c_str());
|
if (fail_if_not_ok)
|
||||||
|
{
|
||||||
|
error("Could not open file %s for reading.\n", file_.c_str());
|
||||||
|
}
|
||||||
verbose("(serialdevicefile) could not open file %s for reading.\n", file_.c_str());
|
verbose("(serialdevicefile) could not open file %s for reading.\n", file_.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
34
src/units.cc
34
src/units.cc
|
@ -805,3 +805,37 @@ string SIExp::str() const
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char available_quantities_[2048];
|
||||||
|
|
||||||
|
const char *availableQuantities()
|
||||||
|
{
|
||||||
|
if (available_quantities_[0]) return available_quantities_;
|
||||||
|
|
||||||
|
#define X(q,u) if (Quantity::q != Quantity::Unknown) { \
|
||||||
|
strcat(available_quantities_, #q); strcat(available_quantities_, "\n"); \
|
||||||
|
assert(strlen(available_quantities_) < 1024); }
|
||||||
|
LIST_OF_QUANTITIES
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
// Remove last newline
|
||||||
|
available_quantities_[strlen(available_quantities_)-1] = 0;
|
||||||
|
return available_quantities_;
|
||||||
|
}
|
||||||
|
|
||||||
|
char available_units_[2048];
|
||||||
|
|
||||||
|
const char *availableUnits()
|
||||||
|
{
|
||||||
|
if (available_units_[0]) return available_units_;
|
||||||
|
|
||||||
|
#define X(n,suffix,sn,q,ln) if (Unit::n != Unit::Unknown) { \
|
||||||
|
strcat(available_units_, #suffix); strcat(available_units_, "\n"); \
|
||||||
|
assert(strlen(available_units_) < 1024); }
|
||||||
|
LIST_OF_UNITS
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
// Remove last newline
|
||||||
|
available_units_[strlen(available_units_)-1] = 0;
|
||||||
|
return available_units_;
|
||||||
|
}
|
||||||
|
|
|
@ -295,4 +295,7 @@ bool extractUnit(const std::string &s, std::string *vname, Unit *u);
|
||||||
LIST_OF_UNITS
|
LIST_OF_UNITS
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
const char *availableQuantities();
|
||||||
|
const char *availableUnits();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
32
src/util.cc
32
src/util.cc
|
@ -587,7 +587,7 @@ void warning(const char* fmt, ...) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void verbose(const char* fmt, ...) {
|
void verbose_int(const char* fmt, ...) {
|
||||||
if (verbose_enabled_) {
|
if (verbose_enabled_) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
@ -596,7 +596,7 @@ void verbose(const char* fmt, ...) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void debug(const char* fmt, ...) {
|
void debug_int(const char* fmt, ...) {
|
||||||
if (debug_enabled_) {
|
if (debug_enabled_) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
@ -605,7 +605,7 @@ void debug(const char* fmt, ...) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void trace(const char* fmt, ...) {
|
void trace_int(const char* fmt, ...) {
|
||||||
if (trace_enabled_) {
|
if (trace_enabled_) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
@ -2457,3 +2457,29 @@ bool endsWith(const std::string& str, const std::string& suffix)
|
||||||
{
|
{
|
||||||
return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
|
return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string lang_;
|
||||||
|
|
||||||
|
const std::string &language()
|
||||||
|
{
|
||||||
|
if (lang_.length() > 0) return lang_;
|
||||||
|
|
||||||
|
const char *la = getenv("LANG");
|
||||||
|
if (!la || strlen(la) < 2)
|
||||||
|
{
|
||||||
|
lang_ = "en";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (la[2] == '_' || la[2] == 0)
|
||||||
|
{
|
||||||
|
lang_ = string(la, la+2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lang_ = "en";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lang_;
|
||||||
|
}
|
||||||
|
|
16
src/util.h
16
src/util.h
|
@ -100,9 +100,16 @@ bool enableLogfile(const std::string& logfile, bool daemon);
|
||||||
void disableLogfile();
|
void disableLogfile();
|
||||||
void enableSyslog();
|
void enableSyslog();
|
||||||
void error(const char* fmt, ...);
|
void error(const char* fmt, ...);
|
||||||
void verbose(const char* fmt, ...);
|
|
||||||
void trace(const char* fmt, ...);
|
#define verbose(...) { if (isVerboseEnabled()) { verbose_int(__VA_ARGS__); } }
|
||||||
void debug(const char* fmt, ...);
|
void verbose_int(const char* fmt, ...);
|
||||||
|
|
||||||
|
#define trace(...) { if (isTraceEnabled()) { trace_int(__VA_ARGS__); } }
|
||||||
|
void trace_int(const char* fmt, ...);
|
||||||
|
|
||||||
|
#define debug(...) { if (isDebugEnabled()) { debug_int(__VA_ARGS__); } }
|
||||||
|
void debug_int(const char* fmt, ...);
|
||||||
|
|
||||||
void warning(const char* fmt, ...);
|
void warning(const char* fmt, ...);
|
||||||
void info(const char* fmt, ...);
|
void info(const char* fmt, ...);
|
||||||
void notice(const char* fmt, ...);
|
void notice(const char* fmt, ...);
|
||||||
|
@ -304,6 +311,9 @@ int toMfctCode(char a, char b, char c);
|
||||||
|
|
||||||
bool is_lowercase_alnum_text(const char *text);
|
bool is_lowercase_alnum_text(const char *text);
|
||||||
|
|
||||||
|
// The language that the user expects driver and other messages in.
|
||||||
|
const std::string &language();
|
||||||
|
|
||||||
#ifndef FUZZING
|
#ifndef FUZZING
|
||||||
#define FUZZING false
|
#define FUZZING false
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1532,8 +1532,12 @@ bool Telegram::checkMAC(std::vector<uchar> &frame,
|
||||||
debug("(wmbus) received mac %s\n", received.c_str());
|
debug("(wmbus) received mac %s\n", received.c_str());
|
||||||
string truncated = calculated.substr(0, received.length());
|
string truncated = calculated.substr(0, received.length());
|
||||||
bool ok = truncated == received;
|
bool ok = truncated == received;
|
||||||
if (ok) debug("(wmbus) mac ok!\n");
|
if (ok)
|
||||||
else {
|
{
|
||||||
|
debug("(wmbus) mac ok!\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
debug("(wmbus) mac NOT ok!\n");
|
debug("(wmbus) mac NOT ok!\n");
|
||||||
explainParse("BADMAC", 0);
|
explainParse("BADMAC", 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6369,7 +6369,7 @@ bool has_attributes(xmlNodePtr node)
|
||||||
return NULL == xml_first_attribute(node);
|
return NULL == xml_first_attribute(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
int xmqForeach(XMQDoc *doq, XMQNode *xmq_node, const char *xpath, NodeCallback cb, void *user_data)
|
int xmqForeach(XMQDoc *doq, XMQNode *xmq_node, const char *xpath, XMQNodeCallback cb, void *user_data)
|
||||||
{
|
{
|
||||||
xmlDocPtr doc = (xmlDocPtr)xmqGetImplementationDoc(doq);
|
xmlDocPtr doc = (xmlDocPtr)xmqGetImplementationDoc(doq);
|
||||||
xmlXPathContextPtr ctx = xmlXPathNewContext(doc);
|
xmlXPathContextPtr ctx = xmlXPathNewContext(doc);
|
||||||
|
|
|
@ -344,12 +344,12 @@ typedef enum
|
||||||
} XMQProceed;
|
} XMQProceed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
NodeCallback: The function type which is called by foreach functions.
|
XMQNodeCallback: The function type which is called by foreach functions.
|
||||||
@doc: The document being processed.
|
@doc: The document being processed.
|
||||||
@node: The node triggering the callback.
|
@node: The node triggering the callback.
|
||||||
@user_data: The user data supplied to for_each.
|
@user_data: The user data supplied to for_each.
|
||||||
*/
|
*/
|
||||||
typedef XMQProceed (*NodeCallback)(XMQDoc *doc, XMQNode *node, void *user_data);
|
typedef XMQProceed (*XMQNodeCallback)(XMQDoc *doc, XMQNode *node, void *user_data);
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -660,7 +660,7 @@ const char *xmqGetString(XMQDoc *doc, XMQNode *node, const char *xpath);
|
||||||
|
|
||||||
Returns the number of hits.
|
Returns the number of hits.
|
||||||
*/
|
*/
|
||||||
int xmqForeach(XMQDoc *doq, XMQNode *node, const char *xpath, NodeCallback cb, void *user_data);
|
int xmqForeach(XMQDoc *doq, XMQNode *node, const char *xpath, XMQNodeCallback cb, void *user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,11 @@ ddriver { name = Iffo }
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
(dynamic) Error in testoutput/driver.xmq cannot find driver/name
|
(driver) error in testoutput/driver.xmq, cannot find: driver/name
|
||||||
A driver file looks like: driver { name = abc123 ... }
|
-------------------------------------------------------------------------------
|
||||||
|
A driver file looks like this: driver { name = abc123 ... }
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Failed to load driver from file: testoutput/driver.xmq
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
@ -48,12 +51,15 @@ performCheck
|
||||||
TESTNAME="Test bad driver name"
|
TESTNAME="Test bad driver name"
|
||||||
TESTRESULT="ERROR"
|
TESTRESULT="ERROR"
|
||||||
cat > $TEST/driver.xmq <<EOF
|
cat > $TEST/driver.xmq <<EOF
|
||||||
driver { name = Iffo }
|
driver { name = 'a b' }
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
(dynamic) Error in testoutput/driver.xmq invalid driver name "Iffo"
|
(driver) error in testoutput/driver.xmq, bad driver name: a b
|
||||||
The driver name must consist of lower case ascii a-z and digits 0-9.
|
-------------------------------------------------------------------------------
|
||||||
|
The driver name must consist of lower case ascii a-z and digits 0-9.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Failed to load driver from file: testoutput/driver.xmq
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
@ -67,8 +73,23 @@ driver { name = iffo }
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
(dynamic) Error in testoutput/driver.xmq cannot find driver/meter_type
|
(driver) error in testoutput/driver.xmq, cannot find: driver/meter_type
|
||||||
Remember to add: meter_type = ...
|
-------------------------------------------------------------------------------
|
||||||
|
Remember to add: meter_type = ...
|
||||||
|
Available meter types are:
|
||||||
|
DoorWindowDetector
|
||||||
|
ElectricityMeter
|
||||||
|
GasMeter
|
||||||
|
HeatCostAllocationMeter
|
||||||
|
HeatMeter
|
||||||
|
HeatCoolingMeter
|
||||||
|
PulseCounter
|
||||||
|
SmokeDetector
|
||||||
|
TempHygroMeter
|
||||||
|
WaterMeter
|
||||||
|
PressureSensor
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Failed to load driver from file: testoutput/driver.xmq
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
@ -82,7 +103,8 @@ driver { name = iffo meter_type = gurka }
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
(dynamic) Error in testoutput/driver.xmq unknown meter type gurka
|
(driver) error in testoutput/driver.xmq, unknown meter type: gurka
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
Available meter types are:
|
Available meter types are:
|
||||||
DoorWindowDetector
|
DoorWindowDetector
|
||||||
ElectricityMeter
|
ElectricityMeter
|
||||||
|
@ -95,6 +117,8 @@ SmokeDetector
|
||||||
TempHygroMeter
|
TempHygroMeter
|
||||||
WaterMeter
|
WaterMeter
|
||||||
PressureSensor
|
PressureSensor
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Failed to load driver from file: testoutput/driver.xmq
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
@ -108,9 +132,12 @@ driver { name = iffo meter_type = WaterMeter }
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
(dynamic) Error in testoutput/driver.xmq cannot find driver/default_fields
|
(driver) error in testoutput/driver.xmq, cannot find: driver/default_fields
|
||||||
Remember to add: default_fields = name,id,total_m3,timestamp
|
-------------------------------------------------------------------------------
|
||||||
Where you change total_m3 to your meters most important field.
|
Remember to add for example: default_fields = name,id,total_m3,timestamp
|
||||||
|
Where you change total_m3 to your meters most important field.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Failed to load driver from file: testoutput/driver.xmq
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
@ -124,10 +151,15 @@ driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,t
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
(dynamic) Error in testoutput/driver.xmq cannot find any driver/detect/mvt triplets
|
(driver) error in testoutput/driver.xmq, cannot find any detection triplets: driver/detect/mvt
|
||||||
Remember to add: detect { mvt = AAA,05,07 mvt = AAA,06,07 ... }
|
-------------------------------------------------------------------------------
|
||||||
The triplets contain MANUFACTURER,VERSION,TYPE
|
Remember to add: detect { mvt = AAA,05,07 mvt = AAA,06,07 ... }
|
||||||
and you can see these values when listening to all meters.
|
The triplets consists of MANUFACTURER,VERSION,TYPE
|
||||||
|
You can see these values when listening to all meters.
|
||||||
|
The manufacturer can be given as three uppercase characters A-Z
|
||||||
|
or as 4 lower case hex digits.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Failed to load driver from file: testoutput/driver.xmq
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
@ -143,10 +175,312 @@ driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,t
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
(dynamic) Error in testoutput/driver.xmq, wrong number of fields in mvt triple: mvt = alfa
|
(driver) error in testoutput/driver.xmq, wrong number of fields in mvt triple: mvt = alfa
|
||||||
There should be three fields, for example: mvt = AAA,07,05
|
-------------------------------------------------------------------------------
|
||||||
|
There should be three fields, for example: mvt = AAA,07,05
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Failed to load driver from file: testoutput/driver.xmq
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
performCheck
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test no field name"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { namee = total }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
(driver) error in testoutput/driver.xmq, cannot find: driver/field/name
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Remember to add for example: field { name = total ... }
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Hej;?total_m3?
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=fields --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test bad field name with unit"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { name = total_m3 }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
(driver) error in testoutput/driver.xmq, bad field name total_m3 (field names should not have units)
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
The field name should not have a unit since units are added automatically.
|
||||||
|
Either indirectly based on the quantity or directly based on the display_unit.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Hej;?total_m3?
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=fields --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test field missing quantity"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { name = total }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
(driver) error in testoutput/driver.xmq, cannot find: driver/field/quantity
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Remember to add for example: field { quantity = Volume ... }
|
||||||
|
Available quantities:
|
||||||
|
Time
|
||||||
|
Length
|
||||||
|
Mass
|
||||||
|
Amperage
|
||||||
|
Temperature
|
||||||
|
AmountOfSubstance
|
||||||
|
LuminousIntensity
|
||||||
|
Energy
|
||||||
|
Reactive_Energy
|
||||||
|
Apparent_Energy
|
||||||
|
Power
|
||||||
|
Volume
|
||||||
|
Flow
|
||||||
|
Voltage
|
||||||
|
Frequency
|
||||||
|
Pressure
|
||||||
|
PointInTime
|
||||||
|
RelativeHumidity
|
||||||
|
HCA
|
||||||
|
Text
|
||||||
|
Angle
|
||||||
|
Dimensionless
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Hej;?total_m3?
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=fields --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test field bad quantity"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { name = total quantity = gurka }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
(driver) error in testoutput/driver.xmq, bad quantity: gurka
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Available quantities:
|
||||||
|
Time
|
||||||
|
Length
|
||||||
|
Mass
|
||||||
|
Amperage
|
||||||
|
Temperature
|
||||||
|
AmountOfSubstance
|
||||||
|
LuminousIntensity
|
||||||
|
Energy
|
||||||
|
Reactive_Energy
|
||||||
|
Apparent_Energy
|
||||||
|
Power
|
||||||
|
Volume
|
||||||
|
Flow
|
||||||
|
Voltage
|
||||||
|
Frequency
|
||||||
|
Pressure
|
||||||
|
PointInTime
|
||||||
|
RelativeHumidity
|
||||||
|
HCA
|
||||||
|
Text
|
||||||
|
Angle
|
||||||
|
Dimensionless
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Hej;?total_m3?
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=fields --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test field with no matcher and null output"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { name = total quantity = Volume }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# There is no calculate nor match in the driver. Thus no value is inserted to it remains null.
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
Hej;null
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=fields --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test field with calculate"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { name = total quantity = Volume calculate = '4711.23m3 + 0.9m3' }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
Hej 4712.13 m³
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=hr --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test field matcher without measurement_type"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { name = total quantity = Volume match { } }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
(driver) error in testoutput/driver.xmq, cannot find: driver/field/match/measurement_type
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Remember to add for example: match { measurement_type = Instantaneous ... }
|
||||||
|
Available measurement types:
|
||||||
|
Instantaneous
|
||||||
|
Minimum
|
||||||
|
Maximum
|
||||||
|
AtError
|
||||||
|
Any
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Hej ?total_m3?
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=hr --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test field matcher with bad measurement_type "
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { name = total quantity = Volume match { measurement_type = sdfInstantaneous } }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
(driver) error in testoutput/driver.xmq, bad measurement_type: sdfInstantaneous
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Available measurement types:
|
||||||
|
Instantaneous
|
||||||
|
Minimum
|
||||||
|
Maximum
|
||||||
|
AtError
|
||||||
|
Any
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Hej ?total_m3?
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=hr --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test field matcher without vif_range"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { name = total quantity = Volume match { measurement_type = Instantaneous } }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
(driver) error in testoutput/driver.xmq, cannot find: driver/field/match/vif_range
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Remember to add for example: match { ... vif_range = ReturnTemperature ... }
|
||||||
|
Available vif ranges:
|
||||||
|
Volume
|
||||||
|
OnTime
|
||||||
|
OperatingTime
|
||||||
|
VolumeFlow
|
||||||
|
FlowTemperature
|
||||||
|
ReturnTemperature
|
||||||
|
TemperatureDifference
|
||||||
|
ExternalTemperature
|
||||||
|
Pressure
|
||||||
|
HeatCostAllocation
|
||||||
|
Date
|
||||||
|
DateTime
|
||||||
|
EnergyMJ
|
||||||
|
EnergyWh
|
||||||
|
PowerW
|
||||||
|
ActualityDuration
|
||||||
|
FabricationNo
|
||||||
|
EnhancedIdentification
|
||||||
|
RelativeHumidity
|
||||||
|
AccessNumber
|
||||||
|
ParameterSet
|
||||||
|
ModelVersion
|
||||||
|
HardwareVersion
|
||||||
|
FirmwareVersion
|
||||||
|
SoftwareVersion
|
||||||
|
Location
|
||||||
|
Customer
|
||||||
|
ErrorFlags
|
||||||
|
DigitalOutput
|
||||||
|
DigitalInput
|
||||||
|
DurationSinceReadout
|
||||||
|
DurationOfTariff
|
||||||
|
Dimensionless
|
||||||
|
Voltage
|
||||||
|
Amperage
|
||||||
|
ResetCounter
|
||||||
|
CumulationCounter
|
||||||
|
SpecialSupplierInformation
|
||||||
|
RemainingBattery
|
||||||
|
AnyVolumeVIF
|
||||||
|
AnyEnergyVIF
|
||||||
|
AnyPowerVIF
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Hej ?total_m3?
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=hr --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
||||||
|
TESTNAME="Test proper field matcher"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
cat > $TEST/driver.xmq <<EOF
|
||||||
|
driver { name = iffo meter_type = WaterMeter default_fields = name,id,total_m3,timestamp
|
||||||
|
detect { mvt = SEN,99,07 }
|
||||||
|
field { name = total quantity = Volume match { measurement_type = Instantaneous vif_range = Volume } }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
|
Hej 123.529 m³
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$PROG --format=hr --selectfields=name,total_m3 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
|
||||||
|
|
||||||
|
performCheck
|
||||||
|
|
|
@ -37,33 +37,30 @@ driver {
|
||||||
name = iporl
|
name = iporl
|
||||||
meter_type = WaterMeter
|
meter_type = WaterMeter
|
||||||
default_fields = name,id,total_m3,max_flow_m3h,timestamp
|
default_fields = name,id,total_m3,max_flow_m3h,timestamp
|
||||||
link_modes = T1
|
|
||||||
detect {
|
detect {
|
||||||
mvt = SEN,99,07
|
mvt = SEN,99,07
|
||||||
}
|
}
|
||||||
field {
|
field {
|
||||||
name = totalitator
|
name = totalitator
|
||||||
quantity = Volume
|
quantity = Volume
|
||||||
type = NumericFieldWithExtractor
|
|
||||||
info = 'The total water consumption recorded by this meter.'
|
|
||||||
vif_scaling = Auto
|
|
||||||
attributes = ''
|
|
||||||
match {
|
match {
|
||||||
measurement_type = Instantaneous
|
measurement_type = Instantaneous
|
||||||
vif_range = Volume
|
vif_range = Volume
|
||||||
}
|
}
|
||||||
|
about {
|
||||||
|
en = 'The total water consumption recorded by this meter.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
field {
|
field {
|
||||||
name = max_flowwor
|
name = max_flowwor
|
||||||
quantity = Flow
|
quantity = Flow
|
||||||
type = NumericFieldWithExtractor
|
|
||||||
info = 'The maximum flow recorded during previous period.'
|
|
||||||
vif_scaling = Auto
|
|
||||||
attributes = ''
|
|
||||||
match {
|
match {
|
||||||
measurement_type = Instantaneous
|
measurement_type = Instantaneous
|
||||||
vif_range = VolumeFlow
|
vif_range = VolumeFlow
|
||||||
}
|
}
|
||||||
|
about {
|
||||||
|
en = 'The maximum flow recorded during previous period.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
Ładowanie…
Reference in New Issue