kopia lustrzana https://github.com/weetmuts/wmbusmeters
				
				
				
			Initial support for loadable drivers.
							rodzic
							
								
									6e3bac97d4
								
							
						
					
					
						commit
						f017694d78
					
				
							
								
								
									
										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; | ||||
|         } | ||||
|  |  | |||
|  | @ -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
	
	 Fredrik Öhrström
						Fredrik Öhrström