kopia lustrzana https://github.com/weetmuts/wmbusmeters
Merge pull request #1052 from wmbusmeters/AddConfigFiles
Initial support for loadable drivers.pull/1090/head
commit
872bc53ace
9
CHANGES
9
CHANGES
|
@ -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.
|
||||
|
||||
|
|
11
Makefile
11
Makefile
|
@ -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* *~
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
Copyright (C) 2023 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"meters_common_implementation.h"
|
||||
|
||||
#include"driver_dynamic.h"
|
||||
#include"xmq.h"
|
||||
|
||||
bool DriverDynamic::load(DriverInfo *di, const string &file)
|
||||
{
|
||||
XMQDoc *doc = xmqNewDoc();
|
||||
|
||||
bool ok = xmqParseFile(doc, file.c_str(), "config");
|
||||
|
||||
if (!ok) {
|
||||
error("Error loading wmbusmeters driver file %s\n%s",
|
||||
file.c_str(),
|
||||
xmqDocError(doc));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *name = xmqGetString(doc, NULL, "/driver/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);
|
||||
|
||||
const char *default_fields = xmqGetString(doc, NULL, "/driver/default_fields");
|
||||
|
||||
const char *meter_type_s = xmqGetString(doc, NULL, "/driver/meter_type");
|
||||
MeterType meter_type = toMeterType(meter_type_s);
|
||||
|
||||
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());
|
||||
|
||||
verbose("(dynamic) loading %s %s\n", meter_type_s, name);
|
||||
|
||||
di->setName(name);
|
||||
di->setDefaultFields(default_fields);
|
||||
di->setMeterType(meter_type);
|
||||
di->setDynamic(file, doc);
|
||||
|
||||
xmqForeach(doc, NULL, "/driver/detect/mvt", add_detect, di);
|
||||
|
||||
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)
|
||||
{
|
||||
string file = di.getDynamicFileName();
|
||||
XMQDoc *doc = di.getDynamicDriver();
|
||||
|
||||
verbose("(dynamic) Constructing driver %s from file %s\n",
|
||||
di.name().str().c_str(),
|
||||
file.c_str());
|
||||
|
||||
xmqForeach(doc, NULL, "/driver/field", add_field, this);
|
||||
}
|
||||
|
||||
XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, void *dd)
|
||||
{
|
||||
DriverInfo *di = (DriverInfo*)dd;
|
||||
|
||||
string mvt = xmqGetString(doc, detect, ".");
|
||||
|
||||
auto fields = splitString(mvt, ',');
|
||||
if (fields.size() != 3) error("Cannot load driver %s, wrong number of fields in mvt triple.\n", di->name().str().c_str());
|
||||
|
||||
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];
|
||||
char b = mfct[1];
|
||||
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());
|
||||
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 (version > 255 || version < 0)
|
||||
{
|
||||
error("(dynmic) Error in %s: version must be in range 0..ff\n", di->name().str().c_str());
|
||||
}
|
||||
if (type > 255 || type < 0)
|
||||
{
|
||||
error("(dynmic) Error in %s: type must be in range 0..ff\n", di->name().str().c_str());
|
||||
}
|
||||
|
||||
string mfct_flag = manufacturerFlag(mfct_code);
|
||||
debug("(dynamic) register detection %s %s %2x %02x\n",
|
||||
di->name().str().c_str(),
|
||||
mfct_flag.c_str(),
|
||||
version,
|
||||
type);
|
||||
|
||||
di->addDetection(mfct_code, type, version);
|
||||
return XMQ_CONTINUE;
|
||||
}
|
||||
|
||||
XMQProceed DriverDynamic::add_field(XMQDoc *doc, XMQNode *field, void *d)
|
||||
{
|
||||
DriverDynamic *dd = (DriverDynamic*)d;
|
||||
const char *driver_name = dd->name().c_str();
|
||||
|
||||
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");
|
||||
|
||||
debug("(dynamic) field %s %s %s %s\n", name, quantity_s, vif_scaling_s, attributes_s);
|
||||
|
||||
FieldType type = toFieldType(type_s);
|
||||
if (type == FieldType::Unknown) error("(dynamic) Error in %s unknown field type %s\n", driver_name, type_s);
|
||||
|
||||
Quantity quantity = toQuantity(quantity_s);
|
||||
if (quantity == Quantity::Unknown) error("(dynamic) Error in %s unknown quantity %s\n", driver_name, quantity);
|
||||
|
||||
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);
|
||||
|
||||
PrintProperties properties = toPrintProperties(attributes_s);
|
||||
if (properties.hasUnknown()) error("(dynamic) Error in %s unknown attributes %s\n", driver_name, attributes_s);
|
||||
|
||||
FieldMatcher match = FieldMatcher::build();
|
||||
|
||||
xmqForeach(doc, field, "match", add_match, &match);
|
||||
|
||||
dd->addNumericFieldWithExtractor(
|
||||
name,
|
||||
info_s,
|
||||
properties,
|
||||
quantity,
|
||||
vif_scaling,
|
||||
match
|
||||
);
|
||||
|
||||
return XMQ_CONTINUE;
|
||||
}
|
||||
|
||||
XMQProceed DriverDynamic::add_match(XMQDoc *doc, XMQNode *match, void *m)
|
||||
{
|
||||
FieldMatcher *fm = (FieldMatcher*)m;
|
||||
|
||||
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);
|
||||
fm->set(vif_range);
|
||||
}
|
||||
|
||||
return XMQ_CONTINUE;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright (C) 2023 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DRIVER_LOADER_H_
|
||||
#define DRIVER_LOADER_H_
|
||||
|
||||
#include "meters_common_implementation.h"
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -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)
|
||||
|
|
|
@ -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 = "";
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
222
src/meters.cc
222
src/meters.cc
|
@ -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_;
|
||||
}
|
||||
|
|
27
src/meters.h
27
src/meters.h
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
|
21
src/util.cc
21
src/util.cc
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -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  	 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 = ( ' x ' )
|
||||
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
|
||||
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 this is implied if compact is set.
|
||||
@escape_non_7bit: Replace all chars above 126 with char entities, ie
|
||||
@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
|
3
test.sh
3
test.sh
|
@ -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)
|
||||
|
|
|
@ -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
|
Ładowanie…
Reference in New Issue