Initial support for loadable drivers.

pull/1052/head
Fredrik Öhrström 2023-10-13 16:30:29 +02:00
rodzic 6e3bac97d4
commit f017694d78
22 zmienionych plików z 8334 dodań i 38 usunięć

Wyświetl plik

@ -1,7 +1,14 @@
Added initial support for drivers that can be loaded from config files.
Properly receive telegrams from amb8465 which is in command mode.
Chris Bednarczyk improved the build process for Darwin/MacOS platform. Thanks Chris!
PovilasID added another Hydrodigit version. Thanks PovilasID!
Added new units for phase angle: deg rad.
Added more fields to the abbb23.
Version 1.14.0 2023-07-02
Version 1.14.0-RC1 2023-07-02
Added more fields to em24 driver.
Added another mfct/type/version combo to em42 driver and the power_kw field.

Wyświetl plik

@ -147,6 +147,10 @@ $(BUILD)/%.o: src/%.cc $(wildcard src/%.h)
$(CXX) $(CXXFLAGS) $< -c -E > $@.src
$(CXX) $(CXXFLAGS) $< -MMD -c -o $@
$(BUILD)/%.o: src/%.c $(wildcard src/%.h)
$(CXX) -I/usr/include/libxml2 $(CXXFLAGS) $< -c -E > $@.src
$(CXX) -I/usr/include/libxml2 -fpermissive $(CXXFLAGS) $< -MMD -c -o $@
PROG_OBJS:=\
$(BUILD)/aes.o \
$(BUILD)/aescmac.o \
@ -178,6 +182,7 @@ PROG_OBJS:=\
$(BUILD)/wmbus_rawtty.o \
$(BUILD)/wmbus_rc1180.o \
$(BUILD)/wmbus_utils.o \
$(BUILD)/xmq.o \
$(BUILD)/lora_iu880b.o \
# If you run: "make DRIVER=minomess" then only driver_minomess.cc will be compiled into wmbusmeters.
@ -255,7 +260,7 @@ $(BUILD)/authors.h:
# Build binary with debug information. ~15M size binary.
$(BUILD)/wmbusmeters.g: $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/main.o $(BUILD)/short_manual.h
$(CXX) -o $(BUILD)/wmbusmeters.g $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/main.o $(LDFLAGS) -lrtlsdr $(USBLIB) -lpthread
$(CXX) -o $(BUILD)/wmbusmeters.g $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/main.o $(LDFLAGS) -lrtlsdr -lxml2 $(USBLIB) -lpthread
# Production build will have debug information stripped. ~1.5M size binary.
# DEBUG=true builds, which has address sanitizer code, will always keep the debug information.
@ -278,10 +283,10 @@ testinternals: $(BUILD)/testinternals
$(BUILD)/testinternals.o: $(PROG_OBJS) $(DRIVER_OBJS) $(wildcard src/*.h)
$(BUILD)/testinternals: $(BUILD)/testinternals.o
$(CXX) -o $(BUILD)/testinternals $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/testinternals.o $(LDFLAGS) -lrtlsdr $(USBLIB) -lpthread
$(CXX) -o $(BUILD)/testinternals $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/testinternals.o $(LDFLAGS) -lrtlsdr -lxml2 $(USBLIB) -lpthread
$(BUILD)/fuzz: $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/fuzz.o
$(CXX) -o $(BUILD)/fuzz $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/fuzz.o $(LDFLAGS) -lrtlsdr -lpthread
$(CXX) -o $(BUILD)/fuzz $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/fuzz.o $(LDFLAGS) -lrtlsdr -lxml2 -lpthread
clean_executables:
rm -rf build/wmbusmeters* build_arm/wmbusmeters* build_debug/wmbusmeters* build_arm_debug/wmbusmeters* *~

47
configure vendored
Wyświetl plik

@ -2969,6 +2969,53 @@ else $as_nop
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for xmlFreeDoc in -lxml2" >&5
printf %s "checking for xmlFreeDoc in -lxml2... " >&6; }
if test ${ac_cv_lib_xml2_xmlFreeDoc+y}
then :
printf %s "(cached) " >&6
else $as_nop
ac_check_lib_save_LIBS=$LIBS
LIBS="-lxml2 $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
namespace conftest {
extern "C" int xmlFreeDoc ();
}
int
main (void)
{
return conftest::xmlFreeDoc ();
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_link "$LINENO"
then :
ac_cv_lib_xml2_xmlFreeDoc=yes
else $as_nop
ac_cv_lib_xml2_xmlFreeDoc=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_xml2_xmlFreeDoc" >&5
printf "%s\n" "$ac_cv_lib_xml2_xmlFreeDoc" >&6; }
if test "x$ac_cv_lib_xml2_xmlFreeDoc" = xyes
then :
printf "%s\n" "#define HAVE_LIBXML2 1" >>confdefs.h
LIBS="-lxml2 $LIBS"
else $as_nop
as_fn_error $? "Could not find libxml2 library. Try: sudo apt install libxml2-dev" "$LINENO" 5
fi
ac_config_files="$ac_config_files $OUTPUT_ROOT/spec.gmk:$SRC_ROOT/autoconf/spec.gmk.in"
ac_config_files="$ac_config_files $OUTPUT_ROOT/Makefile:$SRC_ROOT/autoconf/Makefile.in"

Wyświetl plik

@ -53,6 +53,11 @@ AC_CHECK_LIB(rtlsdr, rtlsdr_get_device_count, [],
AC_MSG_ERROR([Could not find rtlsdr library. Try: sudo apt install librtlsdr-dev])
])
AC_CHECK_LIB(xml2, xmlFreeDoc, [],
[
AC_MSG_ERROR([Could not find libxml2 library. Try: sudo apt install libxml2-dev])
])
AC_CONFIG_FILES([$OUTPUT_ROOT/spec.gmk:$SRC_ROOT/autoconf/spec.gmk.in])
AC_CONFIG_FILES([$OUTPUT_ROOT/Makefile:$SRC_ROOT/autoconf/Makefile.in])

Wyświetl plik

@ -41,6 +41,9 @@ do
if grep -q -i "gpl-3.0-or-later" $f
then
license="GPL-3+"
elif grep -q -i "MIT" $f
then
license="MIT"
elif grep -q -i "CC0" $f
then
license="CC0"
@ -70,6 +73,25 @@ License: GPL-3+
On Debian systems, the complete text of the GNU General Public License
version 3 can be found in file "/usr/share/common-licenses/GPL-3".
License: MIT
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
License: CC0
The authors, and therefore would be copyright holders, have as much
as possible relinguished their copyright to the public domain.

Wyświetl plik

@ -13,7 +13,7 @@ then
exit 1
fi
(cd src; grep -Eo "Copyright \(C\) (....-)?.... [^\(]+ \(.+\)" * | cut -f 2 -d ':' | tr -s ' ' | sed 's/(C) \([0-9][0-9][0-9][0-9]\) /(C) \1-\1 /' > $TMP)
(cd src; grep -Eo ".*Copyright \(C\) (....-)?.... [^\(]+ \(.+\)" * | cut -f 2 -d ':' | tr -s ' ' | sed 's/(C) \([0-9][0-9][0-9][0-9]\) /(C) \1-\1 /' > $TMP)
echo 'R"AUTHORS(' > $OUT

Wyświetl plik

@ -76,10 +76,12 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
if (!strcmp(argv[i], "--silent")) {
c->silent = true;
i++;
silentLogging(true);
continue;
}
if (!strcmp(argv[i], "--verbose")) {
c->verbose = true;
verboseEnabled(true);
i++;
continue;
}
@ -165,12 +167,17 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
if (!strcmp(argv[i], "--debug")) {
c->debug = true;
verboseEnabled(true);
debugEnabled(true);
i++;
continue;
}
if (!strcmp(argv[i], "--trace")) {
c->debug = true;
c->trace = true;
verboseEnabled(true);
debugEnabled(true);
traceEnabled(true);
i++;
continue;
}

Wyświetl plik

@ -40,7 +40,7 @@ namespace
addStringFieldWithExtractorAndLookup(
"current_status",
"Status of meter.",
DEFAULT_PRINT_PROPERTIES
DEFAULT_PRINT_PROPERTIES
| PrintProperty::STATUS | PrintProperty::INCLUDE_TPL_STATUS,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)

Wyświetl plik

@ -49,6 +49,17 @@ LIST_OF_VIF_RANGES
assert(0);
}
VIFRange toVIFRange(const char *s)
{
if (!strcmp(s, "None")) return VIFRange::None;
if (!strcmp(s, "Any")) return VIFRange::Any;
#define X(name,from,to,quantity,unit) if (!strcmp(s, #name)) return VIFRange::name;
LIST_OF_VIF_RANGES
#undef X
return VIFRange::None;
}
const char *toString(VIFCombinable v)
{
switch (v) {
@ -1328,10 +1339,23 @@ const char *toString(MeasurementType mt)
case MeasurementType::Minimum: return "Minimum";
case MeasurementType::Maximum: return "Maximum";
case MeasurementType::AtError: return "AtError";
case MeasurementType::Unknown: return "Unknown";
}
return "?";
}
MeasurementType toMeasurementType(const char *s)
{
if (!strcmp(s, "Any")) return MeasurementType::Any;
if (!strcmp(s, "Instantaneous")) return MeasurementType::Instantaneous;
if (!strcmp(s, "Minimum")) return MeasurementType::Minimum;
if (!strcmp(s, "Maximum")) return MeasurementType::Maximum;
if (!strcmp(s, "AtError")) return MeasurementType::AtError;
if (!strcmp(s, "Unknown")) return MeasurementType::Unknown;
return MeasurementType::Unknown;
}
string FieldMatcher::str()
{
string s = "";

Wyświetl plik

@ -88,6 +88,7 @@ struct VIFRaw {
};
const char *toString(VIFRange v);
VIFRange toVIFRange(const char *s);
Unit toDefaultUnit(VIFRange v);
VIFRange toVIFRange(int i);
bool isInsideVIFRange(int i, VIFRange range);
@ -213,10 +214,12 @@ enum class MeasurementType
Instantaneous,
Minimum,
Maximum,
AtError
AtError,
Unknown
};
const char *toString(MeasurementType mt);
MeasurementType toMeasurementType(const char *s);
void extractDV(std::string &s, uchar *dif, int *vif, bool *has_difes, bool *has_vifes);

Wyświetl plik

@ -17,6 +17,7 @@
#include"bus.h"
#include"config.h"
#include"driver_dynamic.h"
#include"meters.h"
#include"meters_common_implementation.h"
#include"units.h"
@ -49,11 +50,14 @@ void verifyDriverLookupCreated()
DriverInfo *lookupDriver(string name)
{
verifyDriverLookupCreated();
// Check if we have a compiled/loaded driver available.
if (registered_drivers_->count(name) == 1)
{
return &(*registered_drivers_)[name];
}
// No, ok lets look for driver aliases.
for (DriverInfo *di : *registered_drivers_list_)
{
for (DriverName &dn : di->nameAliases())
@ -115,6 +119,38 @@ bool DriverInfo::isCloseEnoughMedia(uchar type)
return false;
}
bool forceRegisterDriver(function<void(DriverInfo&)> setup)
{
DriverInfo di;
setup(di);
// Check that the driver name has not been registered before!
assert(lookupDriver(di.name().str()) == NULL);
// Check that no other driver also triggers on the same detection values.
for (auto &d : di.detect())
{
for (DriverInfo *p : allDrivers())
{
bool foo = p->detect(d.mfct, d.type, d.version);
if (foo)
{
error("Internal error: driver %s tried to register the same auto detect combo as driver %s alread has taken!\n",
di.name().str().c_str(), p->name().str().c_str());
}
}
}
// Everything looks, good install this driver.
addRegisteredDriver(di);
// This code is invoked from the static initializers of DriverInfos when starting
// wmbusmeters. Thus we do not yet know if the user has supplied --debug or similar setting.
// To debug this you have to uncomment the printf below.
// fprintf(stderr, "(STATIC) added driver: %s\n", n.c_str());
return true;
}
bool registerDriver(function<void(DriverInfo&)> setup)
{
DriverInfo di;
@ -147,12 +183,58 @@ bool registerDriver(function<void(DriverInfo&)> setup)
return true;
}
bool lookupDriverInfo(const string& driver, DriverInfo *out_di)
string loadDriver(const string &file)
{
DriverInfo *di = lookupDriver(driver);
DriverInfo di;
bool ok = DriverDynamic::load(&di, file);
if (!ok)
{
error("Failed to load driver from file: %s\n", file.c_str());
}
// Check that the driver name has not been registered before!
if (lookupDriver(di.name().str()) != NULL)
{
error("Cannot load driver %s %s since it is already registered!\n",
di.name().str().c_str(),
file.c_str());
}
// Check that no other driver also triggers on the same detection values.
for (auto &d : di.detect())
{
for (DriverInfo *p : allDrivers())
{
bool foo = p->detect(d.mfct, d.type, d.version);
if (foo)
{
error("Newly loaded driver %s tries to register the same auto detect combo as driver %s alread has taken!\n",
di.name().str().c_str(), p->name().str().c_str());
}
}
}
// Everything looks, good install this driver.
addRegisteredDriver(di);
return di.name().str();
}
bool lookupDriverInfo(const string& driver_name, DriverInfo *out_di)
{
DriverInfo *di = lookupDriver(driver_name);
if (di == NULL)
{
return false;
// Ok, not built in, try to load it from file.
string new_name = loadDriver(driver_name);
// Check again if it was registered.
di = lookupDriver(new_name);
if (di == NULL)
{
return false;
}
}
if (out_di != NULL)
@ -162,32 +244,7 @@ bool lookupDriverInfo(const string& driver, DriverInfo *out_di)
return true;
}
/*
MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
string driver) :
driver_name_(driver),
bus_(mi.bus),
name_(mi.name),
waiting_for_poll_response_sem_("waiting_for_poll_response")
{
ids_ = mi.ids;
idsc_ = toIdsCommaSeparated(ids_);
link_modes_ = mi.link_modes;
if (mi.key.length() > 0)
{
hex2bin(mi.key, &meter_keys_.confidentiality_key);
}
for (auto s : mi.shells)
{
addShell(s);
}
for (auto j : mi.extra_constant_fields)
{
addExtraConstantField(j);
}
}
*/
MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
DriverInfo &di) :
type_(di.type()),
@ -747,6 +804,14 @@ LIST_OF_METER_TYPES
return "unknown";
}
MeterType toMeterType(string type)
{
#define X(tname) if (type == #tname) return MeterType::tname;
LIST_OF_METER_TYPES
#undef X
return MeterType::UnknownMeter;
}
string toString(DriverInfo &di)
{
return di.name().str();
@ -2930,6 +2995,107 @@ const char *toString(VifScaling s)
case VifScaling::Auto: return "Auto";
case VifScaling::NoneSigned: return "NoneSigned";
case VifScaling::AutoSigned: return "AutoSigned";
case VifScaling::Unknown: return "Unknown";
}
return "?";
}
VifScaling toVifScaling(const char *s)
{
if (!s) return VifScaling::Unknown;
if (!strcmp(s, "None")) return VifScaling::None;
if (!strcmp(s, "Auto")) return VifScaling::Auto;
if (!strcmp(s, "NoneSigned")) return VifScaling::NoneSigned;
if (!strcmp(s, "AutoSigned")) return VifScaling::AutoSigned;
if (!strcmp(s, "Unknown")) 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)
{
switch(p)
{
case PrintProperty::REQUIRED: return "REQUIRED";
case PrintProperty::DEPRECATED: return "DEPRECATED";
case PrintProperty::STATUS: return "STATUS";
case PrintProperty::INCLUDE_TPL_STATUS: return "INCLUDE_TPL_STATUS";
case PrintProperty::INJECT_INTO_STATUS: return "INJECT_INTO_STATUS";
case PrintProperty::HIDE: return "HIDE";
case PrintProperty::Unknown: return "Unknown";
}
return "Unknown";
}
PrintProperty toPrintProperty(const char *s)
{
if (!strcmp(s, "REQUIRED")) return PrintProperty::REQUIRED;
if (!strcmp(s, "DEPRECATED")) return PrintProperty::DEPRECATED;
if (!strcmp(s, "STATUS")) return PrintProperty::STATUS;
if (!strcmp(s, "INCLUDE_TPL_STATUS")) return PrintProperty::INCLUDE_TPL_STATUS;
if (!strcmp(s, "INJECT_INTO_STATUS")) return PrintProperty::INJECT_INTO_STATUS;
if (!strcmp(s, "HIDE")) return PrintProperty::HIDE;
if (!strcmp(s, "Unknown")) return PrintProperty::Unknown;
return PrintProperty::Unknown;
}
PrintProperties toPrintProperties(string s)
{
auto fields = splitString(s, ',');
int bits = 0;
for (auto p : fields)
{
bits |= toPrintProperty(p.c_str());
}
return bits;
}
char available_meter_types_[2048];
const char *availableMeterTypes()
{
if (available_meter_types_[0]) return available_meter_types_;
#define X(m) if (MeterType::m != MeterType::AutoMeter && MeterType::m != MeterType::UnknownMeter) { \
strcat(available_meter_types_, #m); strcat(available_meter_types_, "\n"); \
assert(strlen(available_meter_types_) < 1024); }
LIST_OF_METER_TYPES
#undef X
// Remove last ,
available_meter_types_[strlen(available_meter_types_)-1] = 0;
return available_meter_types_;
}

Wyświetl plik

@ -23,6 +23,7 @@
#include"util.h"
#include"units.h"
#include"translatebits.h"
#include"xmq.h"
#include"wmbus.h"
#include<assert.h>
@ -183,6 +184,8 @@ private:
vector<string> default_fields_;
int force_mfct_index_ = -1; // Used for meters not declaring mfct specific data using the dif 0f.
bool has_process_content_ = false; // Mark this driver as having mfct specific decoding.
XMQDoc *dynamic_driver_ {}; // Configuration loaded from driver file.
string dynamic_file_name_; // Name of actual loaded driver file.
public:
DriverInfo() {};
@ -195,6 +198,9 @@ public:
void setConstructor(function<shared_ptr<Meter>(MeterInfo&,DriverInfo&)> c) { constructor_ = c; }
void addDetection(uint16_t mfct, uchar type, uchar ver) { detect_.push_back({ mfct, type, ver }); }
void usesProcessContent() { has_process_content_ = true; }
void setDynamic(const string &file_name, XMQDoc *driver) { dynamic_file_name_ = file_name; dynamic_driver_ = driver; }
XMQDoc *getDynamicDriver() { return dynamic_driver_; }
const string &getDynamicFileName() { return dynamic_file_name_; }
vector<DriverDetect> &detect() { return detect_; }
@ -219,6 +225,7 @@ public:
};
bool registerDriver(function<void(DriverInfo&di)> setup);
// Lookup (and load if necessary) driver from memory or disk.
bool lookupDriverInfo(const string& driver, DriverInfo *di = NULL);
// Return the best driver match for a telegram.
DriverInfo pickMeterDriver(Telegram *t);
@ -234,10 +241,12 @@ enum class VifScaling
None, // No auto scaling.
Auto, // Scale to normalized VIF unit (ie kwh, m3, m3h etc)
NoneSigned, // No auto scaling however assume the value is signed.
AutoSigned // Scale and assume the value is signed.
AutoSigned, // Scale and assume the value is signed.
Unknown
};
const char* toString(VifScaling s);
VifScaling toVifScaling(const char *s);
enum PrintProperty
{
@ -246,9 +255,14 @@ enum PrintProperty
STATUS = 4, // This is >the< status field and it should read OK of not error flags are set.
INCLUDE_TPL_STATUS = 8, // This text field also includes the tpl status decoding. multiple OK:s collapse to a single OK.
INJECT_INTO_STATUS = 16, // This text field is injected into the already defined status field. multiple OK:s collapse.
HIDE = 32 // This field is only used in calculations, do not print it!
HIDE = 32, // This field is only used in calculations, do not print it!
Unknown = 1024
};
int toBit(PrintProperty p);
const char* toString(PrintProperty p);
PrintProperty toPrintProperty(const char *s);
struct PrintProperties
{
PrintProperties(int x) : props_(x) {}
@ -259,11 +273,15 @@ struct PrintProperties
bool hasINCLUDETPLSTATUS() { return props_ & PrintProperty::INCLUDE_TPL_STATUS; }
bool hasINJECTINTOSTATUS() { return props_ & PrintProperty::INJECT_INTO_STATUS; }
bool hasHIDE() { return props_ & PrintProperty::HIDE; }
bool hasUnknown() { return props_ & PrintProperty::Unknown; }
private:
private:
int props_;
};
// Parse a string like DEPRECATED,HIDE into bits.
PrintProperties toPrintProperties(string s);
#define DEFAULT_PRINT_PROPERTIES 0
struct FieldInfo
@ -447,6 +465,7 @@ struct MeterManager
shared_ptr<MeterManager> createMeterManager(bool daemon);
const char *toString(MeterType type);
MeterType toMeterType(std::string type);
string toString(DriverInfo &driver);
LinkModeSet toMeterLinkModeSet(const string& driver);
@ -454,4 +473,6 @@ struct Configuration;
struct MeterInfo;
shared_ptr<Meter> createMeter(MeterInfo *mi);
const char *availableMeterTypes();
#endif

Wyświetl plik

@ -54,6 +54,21 @@ 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

@ -415,6 +415,14 @@ LIST_OF_UNITS
return Quantity::Unknown;
}
Quantity toQuantity(string q)
{
#define X(qname,qunit) if (q == #qname) return Quantity::qname;
LIST_OF_QUANTITIES
#undef X
return Quantity::Unknown;
}
void assertQuantity(Unit u, Quantity q)
{
if (!isQuantity(u, q))

Wyświetl plik

@ -277,6 +277,7 @@ bool overrideConversion(Unit from, Unit to);
// Either uppercase KWH or lowercase kwh works here.
Unit toUnit(std::string s);
Quantity toQuantity(std::string s);
const SIUnit &toSIUnit(Unit u);
const char *toString(Quantity q);
bool isQuantity(Unit u, Quantity q);

Wyświetl plik

@ -2431,3 +2431,24 @@ string strTimestampUTC(double v)
strftime(datetime, sizeof(datetime), "%FT%TZ", &ts);
return string(datetime);
}
int toMfctCode(char a, char b, char c)
{
return ((a-64)*1024+(b-64)*32+(c-64));
}
bool is_lowercase_alnum_text(const char *text)
{
const char *i = text;
while (*i)
{
char c = *i;
if (!((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'z')))
{
return false;
}
i++;
}
return true;
}

Wyświetl plik

@ -299,6 +299,10 @@ uchar *safeButUnsafeVectorPtr(std::vector<uchar> &v);
// Count utf8 unicode code points.
int strlen_utf8(const char *s);
int toMfctCode(char a, char b, char c);
bool is_lowercase_alnum_text(const char *text);
#ifndef FUZZING
#define FUZZING false
#endif

Wyświetl plik

@ -3999,6 +3999,7 @@ string measurementTypeName(MeasurementType mt)
case MeasurementType::Maximum: return "maximum";
case MeasurementType::Minimum: return "minimum";
case MeasurementType::AtError: return "aterror";
case MeasurementType::Unknown: return "unknown";
}
assert(0);
}

7125
src/xmq.c 100644

Plik diff jest za duży Load Diff

734
src/xmq.h 100644
Wyświetl plik

@ -0,0 +1,734 @@
/* libxmq - Copyright (C) 2023 Fredrik Öhrström (spdx: MIT)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef XMQ_H
#define XMQ_H
#define _hideLBfromEditor {
#define _hideRBfromEditor }
#ifdef __cplusplus
extern "C" _hideLBfromEditor
#endif
#include<stdbool.h>
#include<stdlib.h>
///////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////// TYPES and STRUCTURES ///////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
/** Opaque structure storing a loaded xmq/xml/json document.
XMQDoc:
Structure storing a loaded xmq/xml/json document.
*/
typedef struct XMQDoc XMQDoc;
/** Opaque structure referencing a node/attr in the xmq/xml/json document.
XMQNode:
Structure referencing a node/attr in the xmq/xml/json document.
*/
typedef struct XMQNode XMQNode;
/**
XMQParseState:
An opaque structure to maintain the parse state and the list of callbacks
to be invoked when parsing xmq.
*/
typedef struct XMQParseState XMQParseState;
/**
XMQParseCallbacks:
Store your own parse callbacks into this structure and register
your own callback structure with the XMQParseState. Then you will get your own
callbacks when parsing xmq and using these callbacks you can implement
your own document builder or token handler.
*/
typedef struct XMQParseCallbacks XMQParseCallbacks;
/** Specify the file/buffer content type.
XMQContentType:
@XMQ_CONTENT_UNKNOWN: a failed content detect will mark the content type as unknown
@XMQ_CONTENT_DETECT: auto detect the content type
@XMQ_CONTENT_XMQ: xmq detected
@XMQ_CONTENT_HTMQ: htmq detected
@XMQ_CONTENT_XML: xml detected
@XMQ_CONTENT_HTML: html detected
@XMQ_CONTENT_JSON: json detected
Specify the file/buffer content type.
*/
typedef enum
{
XMQ_CONTENT_UNKNOWN = 0,
XMQ_CONTENT_DETECT = 1,
XMQ_CONTENT_XMQ = 2,
XMQ_CONTENT_HTMQ = 3,
XMQ_CONTENT_XML = 4,
XMQ_CONTENT_HTML = 5,
XMQ_CONTENT_JSON = 6
} XMQContentType;
/**
XMQRenderFormat:
@XMQ_RENDER_PLAIN: normal output for data storage
@XMQ_RENDER_TERMINAL: colorize using ansi codes
@XMQ_RENDER_HTML: colorize using html tags
@XMQ_RENDER_HTMQ: colorize using htmq tags
@XMQ_RENDER_TEX: colorize using tex
The xmq output can be rendered as PLAIN, or for human consumption in TERMINAL, HTML, HTMQ or TEX.
*/
typedef enum
{
XMQ_RENDER_PLAIN,
XMQ_RENDER_TERMINAL,
XMQ_RENDER_HTML,
XMQ_RENDER_HTMQ,
XMQ_RENDER_TEX
} XMQRenderFormat;
/**
XMQTrimType:
@XMQ_TRIM_DEFAULT: Use the default, ie no-trim for xmq/json, normal-trim for xml/html.
@XMQ_TRIM_NONE: Do not trim at all. Keep unnecessary xml/html indentation and newlines.
@XMQ_TRIM_NORMAL: Normal trim heuristic. Remove leading/ending whitespace, remove incindental indentation.
@XMQ_TRIM_EXTRA: Like normal but remove all indentation (not just incindental) and collapse whitespace.
@XMQ_TRIM_RESHUFFLE: Like extra but also reflow all content at word boundaries to limit line lengths.
When loading xml/html trim the whitespace from the input to generate the most likely desired xmq output.
When loading xmq/htmq, the whitespace is never trimmed since xmq explicitly encodes all important whitespace.
If you load xml with XMQ_TRIM_NONE (--trim=none) there will be a lot of unnecessary whitespace stored in
the xmq, like &#32;&#9;&#10; etc.
You can then view the xmq with XMQ_TRIM_NORMAL (--trim=normal) to drop the whitespace.
*/
typedef enum
{
XMQ_TRIM_DEFAULT = 0,
XMQ_TRIM_NONE = 1,
XMQ_TRIM_NORMAL = 2,
XMQ_TRIM_EXTRA = 3,
XMQ_TRIM_RESHUFFLE = 4,
} XMQTrimType;
/**
XMQColorStrings:
@pre: string to inserted before the token
@post: string to inserted after the token
A color string object is stored for each type of token.
It can store the ANSI color prefix, the html span etc.
If post is NULL then when the token ends, the pre of the containing color will be reprinted.
This is used for ansi codes where there is no stack memory (pop impossible) to the previous colors.
I.e. pre = "\033[0;1;32m" which means reset;bold;green but post = NULL.
For html/tex coloring we use the stack memory (pop possible) of tags.
I.e. pre = "<span class="red">" post = "</span>"
I.e. pre = "{\color{red}" post = "}"
*/
struct XMQColorStrings
{
const char *pre;
const char *post;
};
typedef struct XMQColorStrings XMQColorStrings;
/**
XMQColoring:
The coloring struct is used to prefix/postfix ANSI/HTML/TEX strings for
XMQ tokens to colorize the printed xmq output.
*/
struct XMQColoring
{
XMQColorStrings document; // <html></html> \documentclass{...}... etc
XMQColorStrings header; // <head>..</head>
XMQColorStrings style; // Stylesheet content inside header (html) or color(tex) definitions.
XMQColorStrings body; // <body></body> \begin{document}\end{document}
XMQColorStrings content; // Wrapper around rendered code. Like <pre></pre>, \textt{...}
XMQColorStrings whitespace; // The normal whitespaces: tab=9, nl=10, cr=13, space=32. Normally not colored.
XMQColorStrings unicode_whitespace; // The remaining unicode whitespaces, like: nbsp=160 color as red underline.
XMQColorStrings indentation_whitespace; // The xmq generated indentation spaces. Normally not colored.
XMQColorStrings equals; // The key = value equal sign.
XMQColorStrings brace_left; // Left brace starting a list of childs.
XMQColorStrings brace_right; // Right brace ending a list of childs.
XMQColorStrings apar_left; // Left parentheses surrounding attributes. foo(x=1)
XMQColorStrings apar_right; // Right parentheses surrounding attributes.
XMQColorStrings cpar_left; // Left parentheses surrounding a compound value. foo = (&#10;' x '&#10;)
XMQColorStrings cpar_right; // Right parentheses surrounding a compound value.
XMQColorStrings quote; // A quote 'x y z' can be single or multiline.
XMQColorStrings entity; // A entity &#10;
XMQColorStrings comment; // A comment // foo or /* foo */
XMQColorStrings comment_continuation; // A comment containing newlines /* Hello */* there */
XMQColorStrings ns_colon; // The color of the colon separating a namespace from a name.
XMQColorStrings element_ns; // The namespace part of an element tag, i.e. the text before colon in foo:alfa.
XMQColorStrings element_name; // When an element tag has multiple children or attributes it is rendered using this color.
XMQColorStrings element_key; // When an element tag is suitable to be presented as a key value, this color is used.
XMQColorStrings element_value_text; // When an element is presented as a key and the value is presented as text, use this color.
XMQColorStrings element_value_quote; // When the value is a single quote, use this color.
XMQColorStrings element_value_entity; // When the value is a single entity, use this color.
XMQColorStrings element_value_compound_quote; // When the value is compounded and this is a quote in the compound.
XMQColorStrings element_value_compound_entity; // When the value is compounded and this is an entity in the compound.
XMQColorStrings attr_ns; // The namespace part of an attribute name, i.e. the text before colon in bar:speed.
XMQColorStrings attr_key; // The color of the attribute name, i.e. the key.
XMQColorStrings attr_value_text; // When the attribute value is text, use this color.
XMQColorStrings attr_value_quote; // When the attribute value is a quote, use this color.
XMQColorStrings attr_value_entity; // When the attribute value is an entity, use this color.
XMQColorStrings attr_value_compound_quote; // When the attribute value is a compound and this is a quote in the compound.
XMQColorStrings attr_value_compound_entity; // When the attribute value is a compound and this is an entity in the compound.
const char *indentation_space; // If NULL use " " can be replaced with any other string.
const char *explicit_space; // If NULL use " " can be replaced with any other string.
const char *explicit_tab; // If NULL use "\t" can be replaced with any other string.
const char *explicit_cr; // If NULL use "\t" can be replaced with any other string.
const char *explicit_nl; // If NULL use "\n" can be replaced with any other string.
const char *prefix_line; // If non-NULL print this as the leader before each line.
const char *postfix_line; // If non-NULL print this as the ending after each line.
};
typedef struct XMQColoring XMQColoring;
/**
XMQColor:
Map token type into color index.
*/
typedef enum XMQColor {
COLOR_none,
COLOR_whitespace,
COLOR_unicode_whitespace,
COLOR_indentation_whitespace,
COLOR_equals,
COLOR_brace_left,
COLOR_brace_right,
COLOR_apar_left,
COLOR_apar_right,
COLOR_cpar_left,
COLOR_cpar_right,
COLOR_quote,
COLOR_entity,
COLOR_comment,
COLOR_comment_continuation,
COLOR_ns_colon,
COLOR_element_ns,
COLOR_element_name,
COLOR_element_key,
COLOR_element_value_text,
COLOR_element_value_quote,
COLOR_element_value_entity,
COLOR_element_value_compound_quote,
COLOR_element_value_compound_entity,
COLOR_attr_ns,
COLOR_attr_key,
COLOR_attr_value_text,
COLOR_attr_value_quote,
COLOR_attr_value_entity,
COLOR_attr_value_compound_quote,
COLOR_attr_value_compound_entity,
} XMQColor;
/**
XMQReader:
@reader_state: points to the reader state
@read: invoked with the reader state and where to store input data.
The xmq parser uses the reader to fetch data into a buffer (start <= i < stop).
You can create your own reader with a function that takes a pointer to the reader state.
Returns the number of bytes stored in buffer, maximum stored is stop-start.
*/
struct XMQReader
{
void *reader_state;
size_t (*read)(void *reader_state, char *start, char *stop);
};
typedef struct XMQReader XMQReader;
/**
XMQWrite:
@writer_state: necessary state for writing.
@start: start of buffer to write
@stop: points to byte after buffer to write. If NULL then assume start is null terminated.
Any function implementing XMQWrite must handle stop == NULL.
*/
typedef bool (*XMQWrite)(void *writer_state, const char *start, const char *stop);
/**
XMQWriter:
@writer_state: points to the writer state
@write: invoked with the writer state to store output data. Must accept stop == NULL which assumes start is null terminated.
The xmq printer uses the writer to write data supplied from a buffer (start <= i < stop).
You can create your own writer with a function that takes a pointer to the writer state.
The writer function must return true if the writing was successful.
*/
struct XMQWriter
{
void *writer_state;
XMQWrite write;
};
typedef struct XMQWriter XMQWriter;
/**
XMQOutputSettings:
@add_indent: Default is 4. Indentation starts at 0 which means no spaces prepended.
@compact: Print on a single line limiting whitespace to a minimum.
@escape_newlines: Replace newlines with &#10; this is implied if compact is set.
@escape_non_7bit: Replace all chars above 126 with char entities, ie &#10;
@output_format: Print xmq/xml/html/json
@coloring: Print prefixes/postfixes to colorize the output for ANSI/HTML/TEX.
@render_to: Render to terminal, html, tex.
@render_raw: If true do not write surrounding html and css colors, likewise for tex.
@only_style: Print only style sheet header.
@write_content: Write content to buffer.
@buffer_content: Supplied as buffer above.
@write_error: Write error to buffer.
@buffer_error: Supplied as buffer above.
*/
struct XMQOutputSettings
{
int add_indent;
bool compact;
bool use_color;
bool escape_newlines;
bool escape_non_7bit;
XMQContentType output_format;
XMQColoring coloring;
XMQRenderFormat render_to;
bool render_raw;
bool only_style;
XMQWriter content;
XMQWriter error;
};
typedef struct XMQOutputSettings XMQOutputSettings;
/**
XMQProceed:
@XMQ_CONTINUE: Return "continue" to continue iterating over xmq nodes.
@XMQ_RETURN: Return "return" to stop and return the current node.
@XMQ_ABORT: Return "abort" to stop iterating and give an error.
*/
typedef enum
{
XMQ_CONTINUE,
XMQ_STOP,
} XMQProceed;
/**
NodeCallback: 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);
///////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////// FUNCTIONS /////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
xmqDetectContentType:
@start: points to first byte of buffer to scan for content type
@stop: points to byte after buffer
Detect the content type xmq/xml/html/json by examining a few leading
non-whitespace words/characters.
*/
XMQContentType xmqDetectContentType(const char *start, const char *stop);
/**
XMQParseError:
@XMQ_ERROR_CANNOT_READ_FILE: file not found or cannot be opened for reading.
@XMQ_ERROR_NOT_XMQ: expected xmq but auto detect sees early that it is not xmq.
@XMQ_ERROR_QUOTE_NOT_CLOSED: an xmq quote is not closed, ie single quotes are missing.
@XMQ_ERROR_ENTITY_NOT_CLOSED: an entity is missing the semicolon.
@XMQ_ERROR_COMMENT_NOT_CLOSED: a comment has not been closed.
@XMQ_ERROR_COMMENT_CLOSED_WITH_TOO_MANY_SLASHES: a comment close is not balanced.
@XMQ_ERROR_BODY_NOT_CLOSED: an body is missing a closing brace.
@XMQ_ERROR_ATTRIBUTES_NOT_CLOSED: the attribute list is missing the closing parentheses.
@XMQ_ERROR_CONTENT_NOT_CLOSED: compound content is missing the closing double parentheses.
@XMQ_ERROR_CONTENT_MAY_NOT_CONTAIN: compound content may only contains quotes and entities.
@XMQ_ERROR_QUOTE_CLOSED_WITH_TOO_MANY_QUOTES: too many closing single quotes.
@XMQ_ERROR_UNEXPECTED_CLOSING_BRACE: an unexpected closing brace.
@XMQ_ERROR_INVALID_CHAR: an invalid character found.
@XMQ_ERROR_BAD_DOCTYPE: the doctype could not be parsed.
@XMQ_ERROR_JSON_INVALID_ESCAPE: an invalid json escape sequence.
@XMQ_ERROR_JSON_INVALID_CHAR: an invalid character.
@XMQ_ERROR_CANNOT_HANDLE_XML: x
@XMQ_ERROR_CANNOT_HANDLE_HTML: x
@XMQ_ERROR_CANNOT_HANDLE_JSON: x
@XMQ_ERROR_EXPECTED_XMQ: x
@XMQ_ERROR_EXPECTED_HTMQ: x
@XMQ_ERROR_EXPECTED_XML: x
@XMQ_ERROR_EXPECTED_HTML: x
@XMQ_ERROR_EXPECTED_JSON: x
Possible parse errors.
*/
typedef enum
{
XMQ_ERROR_CANNOT_READ_FILE = 1,
XMQ_ERROR_NOT_XMQ = 2,
XMQ_ERROR_QUOTE_NOT_CLOSED = 3,
XMQ_ERROR_ENTITY_NOT_CLOSED = 4,
XMQ_ERROR_COMMENT_NOT_CLOSED = 5,
XMQ_ERROR_COMMENT_CLOSED_WITH_TOO_MANY_SLASHES = 6,
XMQ_ERROR_BODY_NOT_CLOSED = 7,
XMQ_ERROR_ATTRIBUTES_NOT_CLOSED = 8,
XMQ_ERROR_COMPOUND_NOT_CLOSED = 9,
XMQ_ERROR_COMPOUND_MAY_NOT_CONTAIN = 10,
XMQ_ERROR_QUOTE_CLOSED_WITH_TOO_MANY_QUOTES = 11,
XMQ_ERROR_UNEXPECTED_CLOSING_BRACE = 12,
XMQ_ERROR_EXPECTED_CONTENT_AFTER_EQUALS = 13,
XMQ_ERROR_INVALID_CHAR = 14,
XMQ_ERROR_BAD_DOCTYPE = 15,
XMQ_ERROR_JSON_INVALID_ESCAPE = 16,
XMQ_ERROR_JSON_INVALID_CHAR = 17,
XMQ_ERROR_CANNOT_HANDLE_XML = 18,
XMQ_ERROR_CANNOT_HANDLE_HTML = 19,
XMQ_ERROR_CANNOT_HANDLE_JSON = 20,
XMQ_ERROR_EXPECTED_XMQ = 21,
XMQ_ERROR_EXPECTED_HTMQ = 22,
XMQ_ERROR_EXPECTED_XML = 23,
XMQ_ERROR_EXPECTED_HTML = 24,
XMQ_ERROR_EXPECTED_JSON = 25
} XMQParseError;
const char *xmqParseErrorToString(XMQParseError e);
/** Allocate an empty XMQParseCallback structure. All callbacks are NULL and none will be called. */
XMQParseCallbacks *xmqNewParseCallbacks();
/** Free the XMQParseCallback structure. */
void xmqFreeParseCallbacks(XMQParseCallbacks *cb);
/**
xmqSetupParseCallbacksNoopTokens:
When tokenizing only, for coloring or debugging, you can
use the setup functions below for a few standardized handlers.
*/
void xmqSetupParseCallbacksNoopTokens(XMQParseCallbacks *state);
/**
xmqSetupParseCallbacksColorizeTokens:
Used to colorize xmq input, without building a parse tree.
*/
void xmqSetupParseCallbacksColorizeTokens(XMQParseCallbacks *state, XMQRenderFormat render_format, bool dark_mode);
/**
xmqSetupParseCallbacksDebugTokens:
Used to debug location, type of tokens.
*/
void xmqSetupParseCallbacksDebugTokens(XMQParseCallbacks *state);
/**
xmqSetupParseCallbacksDebugContent:
Used debug the decoded content.
*/
void xmqSetupParseCallbacksDebugContent(XMQParseCallbacks *state);
/** Parse a buffer with xmq content. */
bool xmqTokenizeBuffer(XMQParseState *state, const char *start, const char *stop);
/** Parse a file with xmq content. */
bool xmqTokenizeFile(XMQParseState *state, const char *file);
/** Parse a file descriptor with xmq content. */
bool xmqTokenizeFileDescriptor(XMQParseState *state, int fd);
/**
xmqNewParseState:
@callbacks: these callbacks will be invoked for each token.
@settings: these settings are available to the callbacks.
Now prepare a parse state that is used to actually parse an xmq file.
The print settings can be referenced from the callbacks for example when tokenizing.
*/
XMQParseState *xmqNewParseState(XMQParseCallbacks *callbacks, XMQOutputSettings *settings);
/**
xmqFreeParseState:
Free the memory allocated for the state.
*/
void xmqFreeParseState(XMQParseState *state);
/**
xmqSetStateSourceName:
@state: the parse state to inform.
@source_name: the name of the file being parsed.
To improve the error message the name of the file being parsed can be supplied.
*/
void xmqSetStateSourceName(XMQParseState *state, const char *source_name);
/**
xmqStateErrno:
@state: the parse state.
If the parse fails then use this function to get the integer value of XMQParseError.
*/
int xmqStateErrno(XMQParseState *state);
/**
xmqStateErrorMsg:
@state: the parse state.
If the parse fails then use this function to get a string explaining the error.
*/
const char *xmqStateErrorMsg(XMQParseState *state);
/**
xmqNewDoc:
Create an empty document object.
*/
XMQDoc *xmqNewDoc();
/**
xmqSetSourceName:
Set the source name to make error message more readable when parsing fails.
*/
void xmqSetDocSourceName(XMQDoc *doq, const char *file_name);
/**
xmqGetRootNode:
Get root node suitable for xmqForeach.
*/
XMQNode *xmqGetRootNode(XMQDoc *doq);
/**
xmqGetImplementationDoc:
Get the underlying implementation doc, could be an xmlDocPtr from libxml2 for example.
*/
void *xmqGetImplementationDoc(XMQDoc *doq);
/**
xmqFreeDoc:
Free the document object and all associated memory.
*/
void xmqFreeDoc(XMQDoc *doc);
/**
xmqParseFile:
@doc: the xmq doc object
@file: file to load from file syste, or stdin if file is NULL
@implicit_root: the implicit root
Parse a file, or if file is NULL, read from stdin.
*/
bool xmqParseFile(XMQDoc *doc, const char *file, const char *implicit_root);
/**
xmqParseBuffer:
@doc: the xmq doc object
@start: start of buffer to parse
@stop: points to byte after last byte in buffer
@implicit_root: the implicit root
Parse a buffer or a file and create a document.
The xmq format permits multiple root nodes if an implicit root is supplied.
*/
bool xmqParseBuffer(XMQDoc *doc, const char *start, const char *stop, const char *implicit_root);
/**
xmqParseReader:
@doc: the xmq doc object
@reader: use this reader to fetch input data
@implicit_root: the implicit root
Parse data fetched with a reader and create a document.
The xmq format permits multiple root nodes if an implicit root is supplied.
*/
bool xmqParseReader(XMQDoc *doc, XMQReader *reader, const char *implicit_root);
/** Allocate the print settings structure and zero it. */
XMQOutputSettings *xmqNewOutputSettings();
/** Free the print settings structure. */
void xmqFreeOutputSettings(XMQOutputSettings *ps);
/** Setup the printer to print content to stdout and errors to sderr. */
void xmqSetupPrintStdOutStdErr(XMQOutputSettings *ps);
/** Setup the printer to print to a file. */
void xmqSetupPrintFile(XMQOutputSettings *ps, const char *file);
/** Setup the printer to print to a filedescriptor. */
void xmqSetupPrintFileDescriptor(XMQOutputSettings *ps, int fd);
/** Setup the printer to print to a dynamically memory buffer. */
void xmqSetupPrintMemory(XMQOutputSettings *ps, const char **start, const char **stop);
/** Pretty print the document according to the settings. */
void xmqPrint(XMQDoc *doc, XMQOutputSettings *settings);
/** Trim xml whitespace. */
void xmqTrimWhitespace(XMQDoc *doc, XMQTrimType tt);
/** A parsing error will be described here! */
const char *xmqDocError(XMQDoc *doc);
/** The error as errno. */
XMQParseError xmqDocErrno(XMQDoc *doc);
/**
xmqGetName: get name of node
@node: Node
*/
const char *xmqGetName(XMQNode *node);
/**
xmqGetContent: get content of element node
@node: Node
*/
const char *xmqGetContent(XMQNode *node);
/**
xmqGetInt:
@doc: the xmq doc object
@xpath: the location of the content to be parsed as an 32 bit signed integer.
*/
int32_t xmqGetInt(XMQDoc *doc, XMQNode *node, const char *xpath);
/**
xmqGetLong:
@doc: the xmq doc object
@xpath: the location of the content to be parsed as an 64 bit signed integer.
*/
int64_t xmqGetLong(XMQDoc *doc, XMQNode *node, const char *xpath);
/**
xmqGetDouble:
@doc: the xmq doc object
@xpath: the location of the content to be parsed as double float.
*/
double xmqGetDouble(XMQDoc *doc, XMQNode *node, const char *xpath);
/**
xmqGetString:
@doc: the xmq doc object
@xpath: the location of the content to be parsed as string.
*/
const char *xmqGetString(XMQDoc *doc, XMQNode *node, const char *xpath);
/**
xmqqForeach: Find all locations matching the xpath.
@node: the starting node to search below.
@xpath: the xpath pattern.
@cb: the function to call for each found node. Can be NULL.
@user_data: the user_data supplied to the function.
Returns the number of hits.
*/
int xmqForeach(XMQDoc *doq, XMQNode *node, const char *xpath, NodeCallback cb, void *user_data);
/**
xmqVersion:
Return the current xmq version in this library.
*/
const char *xmqVersion();
/**
xmqCommit:
Return the git commit used to build this library.
*/
const char *xmqCommit();
/**
xmqSetVerbose:
Enable/Disable verbose logging.
*/
void xmqSetVerbose(bool e);
/**
xmqSetDebug:
Enable/Disable debugging.
*/
void xmqSetDebug(bool e);
/**
xmqDebugging:
Return whether debugging is enabled or not.
*/
bool xmqDebugging();
/**
xmqParseBufferWithType:
Parse buffer.
*/
bool xmqParseBufferWithType(XMQDoc *doc,
const char *start,
const char *stop,
const char *implicit_root,
XMQContentType ct,
XMQTrimType tt);
/**
xmqParseFileWithType:
Load and parse file. If file is NULL read from stdin.
*/
bool xmqParseFileWithType(XMQDoc *doc,
const char *file,
const char *implicit_root,
XMQContentType ct,
XMQTrimType tt);
/**
xmqSetupDefaultColors:
Set the default colors for settings based on the background color.
*/
void xmqSetupDefaultColors(XMQOutputSettings *settings, bool dark_mode);
#ifdef __cplusplus
_hideRBfromEditor
#endif
#endif

Wyświetl plik

@ -172,6 +172,9 @@ if [ "$?" != "0" ]; then RC="1"; fi
./tests/test_analyze.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi
./tests/test_loadable_drivers.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi
if [ -x ../additional_tests.sh ]
then
(cd ..; ./additional_tests.sh $PROG)

Wyświetl plik

@ -0,0 +1,77 @@
#!/bin/sh
PROG="$1"
TEST=testoutput
mkdir -p $TEST
performCheck() {
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_response.txt
diff $TEST/test_expected.txt $TEST/test_response.txt
if [ "$?" = "0" ]
then
echo "OK: $TESTNAME"
TESTRESULT="OK"
else
if [ "$USE_MELD" = "true" ]
then
meld $TEST/test_expected.txt $TEST/test_response.txt
fi
fi
else
echo "ERROR: $TESTNAME $0"
echo "wmbusmeters returned error code: $?"
cat $TEST/test_output.txt
fi
}
########################################################################################################################
########################################################################################################################
########################################################################################################################
TESTNAME="Test loadable driver 1"
TESTRESULT="ERROR"
cat > $TEST/driver.xmq <<EOF
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
}
}
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
}
}
}
EOF
cat > $TEST/test_expected.txt <<EOF
{"media":"water","meter":"iporl","name":"Hej","id":"33225544","max_flowwor_m3h":0,"totalitator_m3":123.529,"timestamp":"1111-11-11T11:11:11Z"}
EOF
$PROG --format=json 1844AE4C4455223399077A55000000_041389E20100023B0000 Hej $TEST/driver.xmq 33225544 NO_KEY > $TEST/test_output.txt 2>&1
performCheck