Add more loadable driver features.

pull/1095/head
Fredrik Öhrström 2023-11-12 12:36:22 +01:00
rodzic a385737984
commit c5dc2ada51
14 zmienionych plików z 989 dodań i 172 usunięć

Wyświetl plik

@ -20,6 +20,24 @@
#include"driver_dynamic.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)
{
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");
if (!ok) {
error("Error loading wmbusmeters driver file %s\n%s",
file.c_str(),
xmqDocError(doc));
warning("(driver) error loading wmbusmeters driver file %s\n%s\n%s\n",
file.c_str(),
xmqDocError(doc),
line);
return false;
}
const char *name = xmqGetString(doc, NULL, "/driver/name");
if (!name) error("(dynamic) Error in %s cannot find driver/name\n"
" A driver file looks like: driver { name = abc123 ... }\n", file.c_str());
try
{
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"
" The driver name must consist of lower case ascii a-z and digits 0-9.\n",
file.c_str(), name);
MeterType meter_type = check_meter_type(xmqGetString(doc, NULL, "/driver/meter_type"), file);
di->setMeterType(meter_type);
const char *meter_type_s = xmqGetString(doc, NULL, "/driver/meter_type");
if (!meter_type_s) error("(dynamic) Error in %s cannot find driver/meter_type\n"
" Remember to add: meter_type = ...\n", file.c_str());
string default_fields = check_default_fields(xmqGetString(doc, NULL, "/driver/default_fields"), file);
di->setDefaultFields(default_fields);
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"
"Available meter types are:\n%s\n"
,
file.c_str(), meter_type_s, availableMeterTypes());
di->setDynamic(file, doc);
const char *default_fields = xmqGetString(doc, NULL, "/driver/default_fields");
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());
xmqForeach(doc, NULL, "/driver/detect/mvt", (XMQNodeCallback)add_detect, di);
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);
di->setDefaultFields(default_fields);
di->setMeterType(meter_type);
di->setDynamic(file, doc);
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;
return true;
}
catch (...)
{
return false;
}
}
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();
verbose("(dynamic) Constructing driver %s from file %s\n",
verbose("(driver) constructing driver %s from file %s\n",
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, ".");
auto fields = splitString(mvt, ',');
if (fields.size() != 3) error("(dynamic) Error in %s, wrong number of fields in mvt triple: mvt = %s\n"
" There should be three fields, for example: mvt = AAA,07,05\n",
di->getDynamicFileName().c_str(),
mvt.c_str());
if (fields.size() != 3)
{
warning("(driver) error in %s, wrong number of fields in mvt triple: mvt = %s\n"
"%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];
int mfct_code = 0;
long version = strtol(fields[1].c_str(), NULL, 16);
long type = strtol(fields[2].c_str(), NULL, 16);
if (mfct.length() == 3)
{
char a = mfct[0];
@ -116,29 +133,67 @@ XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, void *dd)
char c = mfct[2];
if (!(a >= 'A' && a < '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",
di->name().str().c_str(),
mfct.c_str());
c >= 'A' && c < 'Z'))
{
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);
}
else
{
char *eptr;
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)
{
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)
{
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);
debug("(dynamic) register detection %s %s %2x %02x\n",
di->name().str().c_str(),
debug("(driver) register detection %s %s %2x %02x\n",
di->getDynamicFileName().c_str(),
mfct_flag.c_str(),
version,
type);
@ -147,70 +202,456 @@ XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, void *dd)
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;
const char *driver_name = dd->name().c_str();
// The field name must be supplied without a unit ie total (not total_m3) since units are managed by wmbusmeters.
string name = check_field_name(xmqGetString(doc, field, "name"), dd);
const char *name = xmqGetString(doc, field, "name");
const char *quantity_s = xmqGetString(doc, field, "quantity");
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");
// The quantity ie Volume, gives the default unit (m3) for the field. The unit can be overriden with display_unit.
Quantity quantity = check_field_quantity(xmqGetString(doc, field, "quantity"), dd);
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);
if (type == FieldType::Unknown) error("(dynamic) Error in %s unknown field type %s\n", driver_name, type_s);
// The vif scaling is by default Auto but can be overriden for pesky fields.
VifScaling vif_scaling = check_vif_scaling(xmqGetString(doc, field, "vif_scaling"), dd);
Quantity quantity = toQuantity(quantity_s);
if (quantity == Quantity::Unknown) error("(dynamic) Error in %s unknown quantity %s\n", driver_name, quantity);
// The properties are by default empty but can be specified for specific fields.
PrintProperties properties = check_print_properties(xmqGetString(doc, field, "attributes"), dd);
VifScaling vif_scaling = toVifScaling(vif_scaling_s);
if (vif_scaling == VifScaling::Unknown) error("(dynamic) Error in %s unknown vif scaling %s\n", driver_name, vif_scaling_s);
// The about fields explains what the value is for. Ie. is storage 1 the previous day or month value etc.
string info = get_translation(doc, field, "about", language());
PrintProperties properties = toPrintProperties(attributes_s);
if (properties.hasUnknown()) error("(dynamic) Error in %s unknown attributes %s\n", driver_name, attributes_s);
// The calculate formula is optional.
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();
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);
dd->addNumericFieldWithExtractor(
if (is_numeric)
{
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,
info_s,
info,
properties,
quantity,
vif_scaling,
match
);
}
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");
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)
{
VIFRange vif_range = toVIFRange(vif_range_s);
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);
}
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/measurement_type\n"
"%s\n"
"Remember to add for example: field { ... 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/vif_range\n"
"%s\n"
"Remember to add for example: field { ... 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);
}

Wyświetl plik

@ -24,9 +24,16 @@ struct DriverDynamic : public virtual MeterCommonImplementation
{
DriverDynamic(MeterInfo &mi, DriverInfo &di);
static bool load(DriverInfo *di, const string &name);
static XMQProceed add_detect(XMQDoc *doc, XMQNode *detect, void *dd);
static XMQProceed add_field(XMQDoc *doc, XMQNode *field, void *dd);
static XMQProceed add_match(XMQDoc *doc, XMQNode *match, void *dd);
static XMQProceed add_detect(XMQDoc *doc, XMQNode *detect, DriverInfo *di);
static XMQProceed add_field(XMQDoc *doc, XMQNode *field, DriverDynamic *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

Wyświetl plik

@ -1441,3 +1441,18 @@ const char *toString(DVEntryCounterType ct)
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_;
}

Wyświetl plik

@ -584,4 +584,6 @@ bool extractDVdate(std::map<std::string,std::pair<int,DVEntry>> *values,
struct tm *value);
const std::string &availableVIFRanges();
#endif

Wyświetl plik

@ -3016,36 +3016,6 @@ VifScaling toVifScaling(const char *s)
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)
{
switch(p)

Wyświetl plik

@ -54,21 +54,6 @@ struct StringField
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
{
int index();

Wyświetl plik

@ -805,3 +805,37 @@ string SIExp::str() const
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_;
}

Wyświetl plik

@ -295,4 +295,7 @@ bool extractUnit(const std::string &s, std::string *vname, Unit *u);
LIST_OF_UNITS
#undef X
const char *availableQuantities();
const char *availableUnits();
#endif

Wyświetl plik

@ -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);
}
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_;
}

Wyświetl plik

@ -304,6 +304,9 @@ int toMfctCode(char a, char b, char c);
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
#define FUZZING false
#endif

Wyświetl plik

@ -6369,7 +6369,7 @@ bool has_attributes(xmlNodePtr 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);
xmlXPathContextPtr ctx = xmlXPathNewContext(doc);

Wyświetl plik

@ -344,12 +344,12 @@ typedef enum
} 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.
@node: The node triggering the callback.
@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.
*/
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);
/**

Wyświetl plik

@ -37,8 +37,11 @@ ddriver { name = Iffo }
EOF
cat > $TEST/test_expected.txt <<EOF
(dynamic) Error in testoutput/driver.xmq cannot find driver/name
A driver file looks like: driver { name = abc123 ... }
(driver) error in testoutput/driver.xmq, cannot find: driver/name
-------------------------------------------------------------------------------
A driver file looks like this: driver { name = abc123 ... }
-------------------------------------------------------------------------------
Failed to load driver from file: testoutput/driver.xmq
EOF
$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"
TESTRESULT="ERROR"
cat > $TEST/driver.xmq <<EOF
driver { name = Iffo }
driver { name = 'a b' }
EOF
cat > $TEST/test_expected.txt <<EOF
(dynamic) Error in testoutput/driver.xmq invalid driver name "Iffo"
The driver name must consist of lower case ascii a-z and digits 0-9.
(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.
-------------------------------------------------------------------------------
Failed to load driver from file: testoutput/driver.xmq
EOF
$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
cat > $TEST/test_expected.txt <<EOF
(dynamic) Error in testoutput/driver.xmq cannot find driver/meter_type
Remember to add: meter_type = ...
(driver) error in testoutput/driver.xmq, cannot find: driver/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
$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
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:
DoorWindowDetector
ElectricityMeter
@ -95,6 +117,8 @@ SmokeDetector
TempHygroMeter
WaterMeter
PressureSensor
-------------------------------------------------------------------------------
Failed to load driver from file: testoutput/driver.xmq
EOF
$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
cat > $TEST/test_expected.txt <<EOF
(dynamic) 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.
(driver) error in testoutput/driver.xmq, cannot find: driver/default_fields
-------------------------------------------------------------------------------
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
$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
cat > $TEST/test_expected.txt <<EOF
(dynamic) Error in testoutput/driver.xmq cannot find any driver/detect/mvt triplets
Remember to add: detect { mvt = AAA,05,07 mvt = AAA,06,07 ... }
The triplets contain MANUFACTURER,VERSION,TYPE
and you can see these values when listening to all meters.
(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 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
$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
cat > $TEST/test_expected.txt <<EOF
(dynamic) 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
(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
-------------------------------------------------------------------------------
Failed to load driver from file: testoutput/driver.xmq
EOF
$PROG 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1 || true
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/measurement_type
-------------------------------------------------------------------------------
Remember to add for example: field { ... 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/vif_range
-------------------------------------------------------------------------------
Remember to add for example: field { ... 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 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 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

Wyświetl plik

@ -37,33 +37,30 @@ driver {
name = iporl
meter_type = WaterMeter
default_fields = name,id,total_m3,max_flow_m3h,timestamp
link_modes = T1
detect {
mvt = SEN,99,07
}
field {
name = totalitator
quantity = Volume
type = NumericFieldWithExtractor
info = 'The total water consumption recorded by this meter.'
vif_scaling = Auto
attributes = ''
match {
measurement_type = Instantaneous
vif_range = Volume
}
about {
en = 'The total water consumption recorded by this meter.'
}
}
field {
name = max_flowwor
quantity = Flow
type = NumericFieldWithExtractor
info = 'The maximum flow recorded during previous period.'
vif_scaling = Auto
attributes = ''
match {
measurement_type = Instantaneous
vif_range = VolumeFlow
}
about {
en = 'The maximum flow recorded during previous period.'
}
}
}
EOF