2018-04-01 06:53:37 +00:00
/*
2022-02-12 14:28:17 +00:00
Copyright ( C ) 2017 - 2022 Fredrik Ö hrstr ö m ( gpl - 3.0 - or - later )
2018-04-01 06:53:37 +00:00
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/>.
*/
2017-08-09 10:00:11 +00:00
2022-04-25 17:38:36 +00:00
# include "bus.h"
2021-02-20 10:08:23 +00:00
# include "config.h"
2017-08-09 10:00:11 +00:00
# include "meters.h"
2021-03-07 19:10:44 +00:00
# include "meter_detection.h"
2018-03-05 10:29:25 +00:00
# include "meters_common_implementation.h"
2019-05-04 11:07:37 +00:00
# include "units.h"
2019-05-04 15:38:10 +00:00
# include "wmbus.h"
# include "wmbus_utils.h"
2017-08-09 10:00:11 +00:00
2019-05-04 14:37:35 +00:00
# include <algorithm>
2022-05-13 14:20:55 +00:00
# include <cmath>
# include <limits>
2018-03-05 10:29:25 +00:00
# include <memory.h>
2021-01-30 16:58:00 +00:00
# include <numeric>
2022-05-13 14:20:55 +00:00
# include <stdexcept>
2020-04-21 16:07:46 +00:00
# include <time.h>
2022-01-02 12:08:36 +00:00
2022-06-16 14:01:31 +00:00
map < string , DriverInfo > * registered_drivers_ = NULL ;
vector < DriverInfo * > * registered_drivers_list_ = NULL ;
void verifyDriverLookupCreated ( )
{
if ( registered_drivers_ = = NULL )
{
registered_drivers_ = new map < string , DriverInfo > ;
}
if ( registered_drivers_list_ = = NULL )
{
registered_drivers_list_ = new vector < DriverInfo * > ;
}
}
DriverInfo * lookupDriver ( string name )
{
verifyDriverLookupCreated ( ) ;
if ( registered_drivers_ - > count ( name ) = = 0 ) return NULL ;
return & ( * registered_drivers_ ) [ name ] ;
}
vector < DriverInfo * > & allDrivers ( )
{
return * registered_drivers_list_ ;
}
void addRegisteredDriver ( DriverInfo di )
{
verifyDriverLookupCreated ( ) ;
( * registered_drivers_ ) [ di . name ( ) . str ( ) ] = di ;
// The list elements points into the map.
( * registered_drivers_list_ ) . push_back ( lookupDriver ( di . name ( ) . str ( ) ) ) ;
}
2022-01-02 12:08:36 +00:00
2022-01-06 17:28:22 +00:00
bool DriverInfo : : detect ( uint16_t mfct , uchar type , uchar version )
2022-01-02 12:08:36 +00:00
{
2022-01-06 17:28:22 +00:00
for ( auto & dd : detect_ )
{
if ( dd . mfct = = 0 & & dd . type = = 0 & & dd . version = = 0 ) continue ; // Ignore drivers with no detection.
if ( dd . mfct = = mfct & & dd . type = = type & & dd . version = = version ) return true ;
}
return false ;
}
2022-01-25 20:21:17 +00:00
bool DriverInfo : : isValidMedia ( uchar type )
{
for ( auto & dd : detect_ )
{
if ( dd . type = = type ) return true ;
}
return false ;
}
2022-02-08 19:51:13 +00:00
bool DriverInfo : : isCloseEnoughMedia ( uchar type )
{
for ( auto & dd : detect_ )
{
if ( isCloseEnough ( dd . type , type ) ) return true ;
}
return false ;
}
2022-01-06 17:28:22 +00:00
bool registerDriver ( function < void ( DriverInfo & ) > setup )
{
DriverInfo di ;
setup ( di ) ;
2022-06-16 14:01:31 +00:00
// Check that the driver name has not been registered before!
assert ( lookupDriver ( di . name ( ) . str ( ) ) = = NULL ) ;
2022-01-06 17:28:22 +00:00
// Check that no other driver also triggers on the same detection values.
for ( auto & d : di . detect ( ) )
{
2022-06-16 14:01:31 +00:00
for ( DriverInfo * p : allDrivers ( ) )
2022-01-06 17:28:22 +00:00
{
2022-06-16 14:01:31 +00:00
bool foo = p - > detect ( d . mfct , d . type , d . version ) ;
2022-02-09 13:52:13 +00:00
if ( foo )
{
error ( " Internal error: driver %s tried to register the same auto detect combo as driver %s alread has taken! \n " ,
2022-06-16 14:01:31 +00:00
di . name ( ) . str ( ) . c_str ( ) , p - > name ( ) . str ( ) . c_str ( ) ) ;
2022-02-09 13:52:13 +00:00
}
2022-01-06 17:28:22 +00:00
}
2022-01-25 19:42:46 +00:00
}
2022-01-06 17:28:22 +00:00
// Everything looks, good install this driver.
2022-06-16 14:01:31 +00:00
addRegisteredDriver ( di ) ;
2022-01-02 12:08:36 +00:00
// 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());
2022-01-06 17:28:22 +00:00
return true ;
}
bool lookupDriverInfo ( string & driver , DriverInfo * out_di )
{
2022-06-16 14:01:31 +00:00
DriverInfo * di = lookupDriver ( driver ) ;
if ( di = = NULL )
2022-01-06 17:28:22 +00:00
{
return false ;
}
2022-06-16 14:01:31 +00:00
* out_di = * di ;
2022-01-06 17:28:22 +00:00
return true ;
2022-01-02 12:08:36 +00:00
}
2020-09-07 08:36:39 +00:00
struct MeterManagerImplementation : public virtual MeterManager
{
2021-02-20 21:21:01 +00:00
private :
bool is_daemon_ { } ;
2021-12-07 18:51:26 +00:00
bool should_analyze_ { } ;
2021-12-07 22:56:29 +00:00
OutputFormat analyze_format_ { } ;
2022-02-06 17:49:55 +00:00
string analyze_driver_ ;
string analyze_key_ ;
bool analyze_verbose_ ;
2021-02-20 21:21:01 +00:00
vector < MeterInfo > meter_templates_ ;
vector < shared_ptr < Meter > > meters_ ;
2022-03-06 21:42:44 +00:00
vector < function < bool ( AboutTelegram & , vector < uchar > ) > > telegram_listeners_ ;
2021-02-20 21:21:01 +00:00
function < void ( Telegram * t , Meter * ) > on_meter_updated_ ;
public :
void addMeterTemplate ( MeterInfo & mi )
{
meter_templates_ . push_back ( mi ) ;
}
2020-09-21 19:55:21 +00:00
void addMeter ( shared_ptr < Meter > meter )
2020-09-07 08:36:39 +00:00
{
2020-09-21 19:55:21 +00:00
meters_ . push_back ( meter ) ;
2021-02-20 21:21:01 +00:00
meter - > setIndex ( meters_ . size ( ) ) ;
2021-03-08 16:14:03 +00:00
meter - > onUpdate ( on_meter_updated_ ) ;
2020-09-07 08:36:39 +00:00
}
2020-09-08 20:11:32 +00:00
Meter * lastAddedMeter ( )
{
return meters_ . back ( ) . get ( ) ;
}
2020-09-07 08:36:39 +00:00
void removeAllMeters ( )
{
meters_ . clear ( ) ;
}
void forEachMeter ( std : : function < void ( Meter * ) > cb )
{
for ( auto & meter : meters_ )
{
cb ( meter . get ( ) ) ;
}
}
bool hasAllMetersReceivedATelegram ( )
{
2022-02-08 21:02:46 +00:00
if ( meters_ . size ( ) < meter_templates_ . size ( ) ) return false ;
2020-09-07 08:36:39 +00:00
for ( auto & meter : meters_ )
{
if ( meter - > numUpdates ( ) = = 0 ) return false ;
}
return true ;
}
2020-09-08 12:55:01 +00:00
bool hasMeters ( )
{
2021-02-20 21:21:01 +00:00
return meters_ . size ( ) ! = 0 | | meter_templates_ . size ( ) ! = 0 ;
2020-09-08 12:55:01 +00:00
}
2021-03-07 19:10:44 +00:00
void warnForUnknownDriver ( string name , Telegram * t )
{
int mfct = t - > dll_mfct ;
int media = t - > dll_type ;
int version = t - > dll_version ;
uchar * id_b = t - > dll_id_b ;
if ( t - > tpl_id_found )
{
2021-03-07 19:40:41 +00:00
mfct = t - > tpl_mfct ;
media = t - > tpl_type ;
version = t - > tpl_version ;
2021-03-07 19:10:44 +00:00
id_b = t - > tpl_id_b ;
}
warning ( " (meter) %s: meter detection could not find driver for "
" id: %02x%02x%02x%02x mfct: (%s) %s (0x%02x) type: %s (0x%02x) ver: 0x%02x \n " ,
name . c_str ( ) ,
id_b [ 3 ] , id_b [ 2 ] , id_b [ 1 ] , id_b [ 0 ] ,
manufacturerFlag ( mfct ) . c_str ( ) ,
manufacturer ( mfct ) . c_str ( ) ,
mfct ,
mediaType ( media , mfct ) . c_str ( ) , media ,
version ) ;
warning ( " (meter) please consider opening an issue at https://github.com/weetmuts/wmbusmeters/ \n " ) ;
warning ( " (meter) to add support for this unknown mfct,media,version combination \n " ) ;
}
2021-02-20 21:21:01 +00:00
bool handleTelegram ( AboutTelegram & about , vector < uchar > input_frame , bool simulated )
2020-09-07 08:36:39 +00:00
{
2021-12-07 18:51:26 +00:00
if ( should_analyze_ )
{
analyzeTelegram ( about , input_frame , simulated ) ;
return true ;
}
2020-09-07 08:36:39 +00:00
bool handled = false ;
2021-02-20 21:21:01 +00:00
bool exact_id_match = false ;
2020-09-07 08:36:39 +00:00
2021-01-30 16:58:00 +00:00
string ids ;
2020-09-07 08:36:39 +00:00
for ( auto & m : meters_ )
{
2021-02-20 21:21:01 +00:00
bool h = m - > handleTelegram ( about , input_frame , simulated , & ids , & exact_id_match ) ;
2020-09-07 08:36:39 +00:00
if ( h ) handled = true ;
}
2021-02-20 21:21:01 +00:00
// If not properly handled, and there was no exact id match.
// then lets check if there is a template that can create a meter for it.
if ( ! handled & & ! exact_id_match )
{
debug ( " (meter) no meter handled %s checking %d templates. \n " , ids . c_str ( ) , meter_templates_ . size ( ) ) ;
// Not handled, maybe we have a template to create a new meter instance for this telegram?
Telegram t ;
t . about = about ;
bool ok = t . parseHeader ( input_frame ) ;
if ( simulated ) t . markAsSimulated ( ) ;
if ( ok )
{
ids = t . idsc ;
for ( auto & mi : meter_templates_ )
{
if ( MeterCommonImplementation : : isTelegramForMeter ( & t , NULL , & mi ) )
{
// We found a match, make a copy of the meter info.
2022-01-06 17:28:22 +00:00
MeterInfo meter_info = mi ;
2021-02-20 21:21:01 +00:00
// Overwrite the wildcard pattern with the highest level id.
// The last id in the t.ids is the highest level id.
// For example: a telegram can have dll_id,tpl_id
// This will pick the tpl_id.
// Or a telegram can have a single dll_id,
// then the dll_id will be picked.
vector < string > tmp_ids ;
tmp_ids . push_back ( t . ids . back ( ) ) ;
2022-01-06 17:28:22 +00:00
meter_info . ids = tmp_ids ;
meter_info . idsc = t . ids . back ( ) ;
2021-03-07 18:52:21 +00:00
2022-01-06 17:28:22 +00:00
if ( meter_info . driver = = MeterDriver : : AUTO )
2021-03-07 18:52:21 +00:00
{
// Look up the proper meter driver!
2022-01-06 17:28:22 +00:00
DriverInfo di = pickMeterDriver ( & t ) ;
if ( di . driver ( ) = = MeterDriver : : UNKNOWN & & di . name ( ) . str ( ) = = " " )
2021-03-07 19:10:44 +00:00
{
2021-12-07 22:56:29 +00:00
if ( should_analyze_ = = false )
{
// We are not analyzing, so warn here.
warnForUnknownDriver ( mi . name , & t ) ;
}
2021-03-07 19:10:44 +00:00
}
2022-01-06 17:28:22 +00:00
else
{
meter_info . driver = di . driver ( ) ;
meter_info . driver_name = di . name ( ) ;
}
2021-03-07 18:52:21 +00:00
}
2021-02-20 21:21:01 +00:00
// Now build a meter object with for this exact id.
2022-01-06 17:28:22 +00:00
auto meter = createMeter ( & meter_info ) ;
2021-02-20 21:21:01 +00:00
addMeter ( meter ) ;
string idsc = toIdsCommaSeparated ( t . ids ) ;
verbose ( " (meter) used meter template %s %s %s to match %s \n " ,
mi . name . c_str ( ) ,
mi . idsc . c_str ( ) ,
2021-03-13 17:35:47 +00:00
toString ( mi . driver ) . c_str ( ) ,
2021-02-20 21:21:01 +00:00
idsc . c_str ( ) ) ;
if ( is_daemon_ )
{
notice ( " (wmbusmeters) started meter %d (%s %s %s) \n " ,
meter - > index ( ) ,
mi . name . c_str ( ) ,
2022-01-06 17:28:22 +00:00
meter_info . idsc . c_str ( ) ,
2021-03-13 17:35:47 +00:00
toString ( mi . driver ) . c_str ( ) ) ;
2021-02-20 21:21:01 +00:00
}
else
{
verbose ( " (meter) started meter %d (%s %s %s) \n " ,
meter - > index ( ) ,
mi . name . c_str ( ) ,
2022-01-06 17:28:22 +00:00
meter_info . idsc . c_str ( ) ,
2021-03-13 17:35:47 +00:00
toString ( mi . driver ) . c_str ( ) ) ;
2021-02-20 21:21:01 +00:00
}
bool match = false ;
bool h = meter - > handleTelegram ( about , input_frame , simulated , & ids , & match ) ;
if ( ! match )
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not match! This is probably an error in wmbusmeters!
warning ( " (meter) newly created meter (%s %s %s) did not match telegram! " ,
" Please open an issue at https://github.com/weetmuts/wmbusmeters/ \n " ,
2022-01-08 17:52:06 +00:00
meter - > name ( ) . c_str ( ) , meter - > idsc ( ) . c_str ( ) , meter - > driverName ( ) . str ( ) . c_str ( ) ) ;
2021-02-20 21:21:01 +00:00
}
else if ( ! h )
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not handle it! This can happen if the wrong
// decryption key was used.
warning ( " (meter) newly created meter (%s %s %s) did not handle telegram! \n " ,
2022-01-08 17:52:06 +00:00
meter - > name ( ) . c_str ( ) , meter - > idsc ( ) . c_str ( ) , meter - > driverName ( ) . str ( ) . c_str ( ) ) ;
2021-02-20 21:21:01 +00:00
}
else
{
handled = true ;
}
}
}
}
}
2022-03-06 21:42:44 +00:00
for ( auto f : telegram_listeners_ )
{
f ( about , input_frame ) ;
}
2020-09-07 08:36:39 +00:00
if ( isVerboseEnabled ( ) & & ! handled )
{
2021-01-30 16:58:00 +00:00
verbose ( " (wmbus) telegram from %s ignored by all configured meters! \n " , ids . c_str ( ) ) ;
2020-09-07 08:36:39 +00:00
}
return handled ;
}
2020-09-08 12:55:01 +00:00
2022-03-06 21:42:44 +00:00
void onTelegram ( function < bool ( AboutTelegram & about , vector < uchar > ) > cb )
2020-09-08 12:55:01 +00:00
{
2022-03-06 21:42:44 +00:00
telegram_listeners_ . push_back ( cb ) ;
2020-09-08 12:55:01 +00:00
}
2021-03-08 16:14:03 +00:00
2021-02-20 21:21:01 +00:00
void whenMeterUpdated ( std : : function < void ( Telegram * t , Meter * ) > cb )
{
on_meter_updated_ = cb ;
}
2020-09-07 08:36:39 +00:00
2021-03-14 08:41:35 +00:00
void pollMeters ( shared_ptr < BusManager > bus )
2021-03-08 16:14:03 +00:00
{
for ( auto & m : meters_ )
{
2021-03-14 08:41:35 +00:00
m - > poll ( bus ) ;
2021-03-08 16:14:03 +00:00
}
}
2022-02-06 17:49:55 +00:00
void analyzeEnabled ( bool b , OutputFormat f , string force_driver , string key , bool verbose )
2021-12-07 18:51:26 +00:00
{
should_analyze_ = b ;
2021-12-07 22:56:29 +00:00
analyze_format_ = f ;
2022-03-27 20:17:28 +00:00
if ( force_driver ! = " auto " )
{
analyze_driver_ = force_driver ;
}
2022-02-06 17:49:55 +00:00
analyze_key_ = key ;
analyze_verbose_ = verbose ;
2021-12-07 18:51:26 +00:00
}
2022-02-06 17:49:55 +00:00
string findBestOldStyleDriver ( MeterInfo & mi ,
int * best_length ,
int * best_understood ,
Telegram & t ,
AboutTelegram & about ,
vector < uchar > & input_frame ,
bool simulated ,
2022-03-27 12:43:10 +00:00
string force )
2021-12-07 18:51:26 +00:00
{
2022-02-06 17:49:55 +00:00
vector < MeterDriver > old_drivers ;
# define X(mname,linkmode,info,type,cname) old_drivers.push_back(MeterDriver::type);
2021-12-07 22:56:29 +00:00
LIST_OF_METERS
# undef X
2022-02-06 17:49:55 +00:00
string best_driver = " " ;
for ( MeterDriver odr : old_drivers )
2021-12-07 22:56:29 +00:00
{
2022-02-06 17:49:55 +00:00
if ( odr = = MeterDriver : : AUTO ) continue ;
if ( odr = = MeterDriver : : UNKNOWN ) continue ;
string driver_name = toString ( odr ) ;
2022-03-27 12:43:10 +00:00
if ( force ! = " " )
{
if ( driver_name ! = force ) continue ;
return driver_name ;
}
2021-12-07 22:56:29 +00:00
2022-03-27 12:43:10 +00:00
if ( force = = " " & &
2022-03-27 10:09:18 +00:00
! isMeterDriverReasonableForMedia ( odr , " " , t . dll_type ) & &
2022-02-06 17:49:55 +00:00
! isMeterDriverReasonableForMedia ( odr , " " , t . tpl_type ) )
2022-01-25 20:21:17 +00:00
{
2022-02-06 17:49:55 +00:00
// Sanity check, skip this driver since it is not relevant for this media.
2022-01-25 20:21:17 +00:00
continue ;
}
2022-02-06 17:49:55 +00:00
debug ( " Testing old style driver %s... \n " , driver_name . c_str ( ) ) ;
mi . driver = odr ;
mi . driver_name = DriverName ( " " ) ;
2021-12-07 22:56:29 +00:00
auto meter = createMeter ( & mi ) ;
2022-02-06 17:49:55 +00:00
2021-12-07 22:56:29 +00:00
bool match = false ;
string id ;
bool h = meter - > handleTelegram ( about , input_frame , simulated , & id , & match , & t ) ;
if ( ! match )
{
2022-02-06 17:49:55 +00:00
debug ( " no match! \n " ) ;
2022-01-25 19:42:46 +00:00
}
else if ( ! h )
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not handle it! This can happen if the wrong
// decryption key was used. But it is ok if analyzing....
2022-02-06 17:49:55 +00:00
debug ( " Newly created meter (%s %s %s) did not handle telegram! \n " ,
2022-01-25 19:42:46 +00:00
meter - > name ( ) . c_str ( ) , meter - > idsc ( ) . c_str ( ) , meter - > driverName ( ) . str ( ) . c_str ( ) ) ;
}
else
{
int l = 0 ;
int u = 0 ;
t . analyzeParse ( OutputFormat : : NONE , & l , & u ) ;
2022-03-27 12:43:10 +00:00
if ( analyze_verbose_ & & force = = " " ) printf ( " (verbose) old %02d/%02d %s \n " , u , l , driver_name . c_str ( ) ) ;
2022-02-06 17:49:55 +00:00
if ( u > * best_understood )
2022-01-25 19:42:46 +00:00
{
2022-02-06 17:49:55 +00:00
* best_understood = u ;
* best_length = l ;
best_driver = driver_name ;
2022-03-27 12:43:10 +00:00
if ( analyze_verbose_ & & force = = " " ) printf ( " (verbose) old best so far: %s %02d/%02d \n " , best_driver . c_str ( ) , u , l ) ;
2022-01-25 19:42:46 +00:00
}
}
}
2022-02-06 17:49:55 +00:00
return best_driver ;
}
string findBestNewStyleDriver ( MeterInfo & mi ,
int * best_length ,
int * best_understood ,
Telegram & t ,
AboutTelegram & about ,
vector < uchar > & input_frame ,
bool simulated ,
string only )
{
string best_driver = " " ;
2022-06-16 14:01:31 +00:00
for ( DriverInfo * ndr : allDrivers ( ) )
2022-01-25 19:42:46 +00:00
{
2022-06-16 14:01:31 +00:00
string driver_name = toString ( * ndr ) ;
2022-03-27 12:43:10 +00:00
if ( only ! = " " )
{
if ( driver_name ! = only ) continue ;
return driver_name ;
}
2022-02-06 17:49:55 +00:00
2022-03-27 10:09:18 +00:00
if ( only = = " " & &
! isMeterDriverReasonableForMedia ( MeterDriver : : AUTO , driver_name , t . dll_type ) & &
! isMeterDriverReasonableForMedia ( MeterDriver : : AUTO , driver_name , t . tpl_type ) )
{
// Sanity check, skip this driver since it is not relevant for this media.
continue ;
}
2022-02-06 17:49:55 +00:00
debug ( " Testing new style driver %s... \n " , driver_name . c_str ( ) ) ;
mi . driver = MeterDriver : : UNKNOWN ;
2022-01-25 19:42:46 +00:00
mi . driver_name = driver_name ;
2022-02-06 17:49:55 +00:00
2022-01-25 19:42:46 +00:00
auto meter = createMeter ( & mi ) ;
2022-02-06 17:49:55 +00:00
2022-01-25 19:42:46 +00:00
bool match = false ;
string id ;
bool h = meter - > handleTelegram ( about , input_frame , simulated , & id , & match , & t ) ;
2022-02-06 17:49:55 +00:00
2022-01-25 19:42:46 +00:00
if ( ! match )
{
2022-02-06 17:49:55 +00:00
debug ( " no match! \n " ) ;
2021-12-07 22:56:29 +00:00
}
else if ( ! h )
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not handle it! This can happen if the wrong
2021-12-27 13:30:54 +00:00
// decryption key was used. But it is ok if analyzing....
2022-02-06 17:49:55 +00:00
debug ( " Newly created meter (%s %s %s) did not handle telegram! \n " ,
2022-01-08 17:52:06 +00:00
meter - > name ( ) . c_str ( ) , meter - > idsc ( ) . c_str ( ) , meter - > driverName ( ) . str ( ) . c_str ( ) ) ;
2021-12-07 22:56:29 +00:00
}
else
{
int l = 0 ;
int u = 0 ;
2022-01-25 19:42:46 +00:00
t . analyzeParse ( OutputFormat : : NONE , & l , & u ) ;
2022-02-06 17:49:55 +00:00
if ( analyze_verbose_ & & only = = " " ) printf ( " (verbose) new %02d/%02d %s \n " , u , l , driver_name . c_str ( ) ) ;
if ( u > * best_understood )
2021-12-07 22:56:29 +00:00
{
2022-02-06 17:49:55 +00:00
* best_understood = u ;
* best_length = l ;
2022-06-16 14:01:31 +00:00
best_driver = ndr - > name ( ) . str ( ) ;
2022-02-06 17:49:55 +00:00
if ( analyze_verbose_ & & only = = " " ) printf ( " (verbose) new best so far: %s %02d/%02d \n " , best_driver . c_str ( ) , u , l ) ;
2021-12-07 22:56:29 +00:00
}
}
}
2022-02-06 17:49:55 +00:00
return best_driver ;
}
void analyzeTelegram ( AboutTelegram & about , vector < uchar > & input_frame , bool simulated )
{
Telegram t ;
t . about = about ;
bool ok = t . parseHeader ( input_frame ) ;
if ( simulated ) t . markAsSimulated ( ) ;
t . markAsBeingAnalyzed ( ) ;
if ( ! ok )
{
printf ( " Could not even analyze header, giving up. \n " ) ;
return ;
}
if ( meter_templates_ . size ( ) > 0 )
{
error ( " You cannot specify a meter quadruple when analyzing. \n "
" Instead use --analyze=<format>:<driver>:<key> \n "
" where <formt> <driver> <key> are all optional. \n "
" E.g. --analyze=terminal:multical21:001122334455667788001122334455667788 \n "
" --analyze=001122334455667788001122334455667788 \n "
" --analyze \n " ) ;
}
// Overwrite the id with the id from the telegram to be analyzed.
MeterInfo mi ;
mi . key = analyze_key_ ;
mi . ids . clear ( ) ;
mi . ids . push_back ( t . ids . back ( ) ) ;
mi . idsc = t . ids . back ( ) ;
// This will be the driver that will actually decode and print with.
string using_driver = " " ;
int using_length = 0 ;
int using_understood = 0 ;
// Driver that understands most of the telegram content.
string best_driver = " " ;
int best_length = 0 ;
int best_understood = 0 ;
int old_best_length = 0 ;
int old_best_understood = 0 ;
string best_old_driver = findBestOldStyleDriver ( mi , & old_best_length , & old_best_understood , t , about , input_frame , simulated , " " ) ;
int new_best_length = 0 ;
int new_best_understood = 0 ;
string best_new_driver = findBestNewStyleDriver ( mi , & new_best_length , & new_best_understood , t , about , input_frame , simulated , " " ) ;
mi . driver = MeterDriver : : UNKNOWN ;
mi . driver_name = DriverName ( " " ) ;
// Use the existing mapping from mfct/media/version to driver.
DriverInfo auto_di = pickMeterDriver ( & t ) ;
string auto_driver = auto_di . name ( ) . str ( ) ;
if ( auto_driver = = " " )
{
auto_driver = toString ( auto_di . driver ( ) ) ;
2022-02-09 13:52:13 +00:00
if ( auto_driver = = " unknown " )
{
auto_driver = " " ;
}
2022-02-06 17:49:55 +00:00
}
// Will be non-empty if an explicit driver has been selected.
string force_driver = analyze_driver_ ;
int force_length = 0 ;
int force_understood = 0 ;
2022-02-09 13:52:13 +00:00
// If an auto driver is found and no other driver has been forced, use the auto driver.
if ( force_driver = = " " & & auto_driver ! = " " )
2022-02-06 17:49:55 +00:00
{
force_driver = auto_driver ;
}
if ( force_driver ! = " " )
2021-12-07 22:56:29 +00:00
{
2022-02-06 17:49:55 +00:00
using_driver = findBestOldStyleDriver ( mi , & force_length , & force_understood , t , about , input_frame , simulated ,
force_driver ) ;
if ( using_driver ! = " " )
2021-12-07 23:55:50 +00:00
{
2022-02-06 17:49:55 +00:00
mi . driver = toMeterDriver ( using_driver ) ;
mi . driver_name = DriverName ( " " ) ;
2022-03-27 10:09:18 +00:00
using_driver + = " (driver should be upgraded) " ;
2021-12-07 23:55:50 +00:00
}
else
{
2022-02-06 17:49:55 +00:00
using_driver = findBestNewStyleDriver ( mi , & force_length , & force_understood , t , about , input_frame , simulated ,
force_driver ) ;
mi . driver_name = using_driver ;
mi . driver = MeterDriver : : UNKNOWN ;
2021-12-07 23:55:50 +00:00
}
2022-02-06 17:49:55 +00:00
using_length = force_length ;
using_understood = force_understood ;
}
2021-12-07 23:55:50 +00:00
2022-02-06 17:49:55 +00:00
if ( old_best_understood > new_best_understood )
{
best_length = old_best_length ;
best_understood = old_best_understood ;
2022-03-27 10:09:18 +00:00
best_driver = best_old_driver + " (driver should be upgraded) " ;
2022-02-06 17:49:55 +00:00
if ( using_driver = = " " )
{
mi . driver = toMeterDriver ( best_old_driver ) ;
mi . driver_name = DriverName ( " " ) ;
2022-03-27 10:09:18 +00:00
using_driver = best_driver ;
2022-02-06 17:49:55 +00:00
using_length = best_length ;
using_understood = best_understood ;
}
2021-12-07 22:56:29 +00:00
}
2022-02-09 13:30:06 +00:00
else if ( new_best_understood > = old_best_understood )
2021-12-07 22:56:29 +00:00
{
2022-02-06 17:49:55 +00:00
best_length = new_best_length ;
best_understood = new_best_understood ;
best_driver = best_new_driver ;
if ( using_driver = = " " )
{
mi . driver_name = best_new_driver ;
mi . driver = MeterDriver : : UNKNOWN ;
using_driver = best_new_driver ;
using_length = best_length ;
using_understood = best_understood ;
}
2021-12-07 22:56:29 +00:00
}
2022-02-06 17:49:55 +00:00
auto meter = createMeter ( & mi ) ;
bool match = false ;
string id ;
meter - > handleTelegram ( about , input_frame , simulated , & id , & match , & t ) ;
int u = 0 ;
int l = 0 ;
string output = t . analyzeParse ( analyze_format_ , & u , & l ) ;
string hr , fields , json ;
vector < string > envs , more_json , selected_fields ;
meter - > printMeter ( & t , & hr , & fields , ' \t ' , & json ,
& envs , & more_json , & selected_fields , true ) ;
if ( auto_driver = = " " )
{
auto_driver = " not found! " ;
}
printf ( " Auto driver : %s \n " , auto_driver . c_str ( ) ) ;
printf ( " Best driver : %s %02d/%02d \n " , best_driver . c_str ( ) , best_understood , best_length ) ;
printf ( " Using driver : %s %02d/%02d \n " , using_driver . c_str ( ) , using_understood , using_length ) ;
printf ( " %s \n " , output . c_str ( ) ) ;
printf ( " %s \n " , json . c_str ( ) ) ;
2021-12-07 18:51:26 +00:00
}
2021-02-20 21:21:01 +00:00
MeterManagerImplementation ( bool daemon ) : is_daemon_ ( daemon ) { }
~ MeterManagerImplementation ( ) { }
2020-09-07 08:36:39 +00:00
} ;
2021-02-20 21:21:01 +00:00
shared_ptr < MeterManager > createMeterManager ( bool daemon )
2020-09-07 08:36:39 +00:00
{
2021-02-20 21:21:01 +00:00
return shared_ptr < MeterManager > ( new MeterManagerImplementation ( daemon ) ) ;
2020-09-07 08:36:39 +00:00
}
2020-08-30 19:33:48 +00:00
MeterCommonImplementation : : MeterCommonImplementation ( MeterInfo & mi ,
2022-01-02 12:08:36 +00:00
string driver ) :
2022-05-01 18:54:12 +00:00
driver_ ( driver ) , bus_ ( mi . bus ) , name_ ( mi . name ) , waiting_for_poll_response_sem_ ( " waiting_for_poll_response " )
2018-03-05 10:29:25 +00:00
{
2021-02-20 21:21:01 +00:00
ids_ = mi . ids ;
idsc_ = toIdsCommaSeparated ( ids_ ) ;
2022-04-27 17:18:30 +00:00
link_modes_ = mi . link_modes ;
2021-02-20 21:21:01 +00:00
2020-02-06 12:14:46 +00:00
if ( mi . key . length ( ) > 0 )
{
2020-01-27 08:29:40 +00:00
hex2bin ( mi . key , & meter_keys_ . confidentiality_key ) ;
2018-03-05 10:29:25 +00:00
}
2019-05-21 12:19:54 +00:00
for ( auto s : mi . shells ) {
addShell ( s ) ;
}
2021-08-01 22:22:13 +00:00
for ( auto j : mi . extra_constant_fields ) {
addExtraConstantField ( j ) ;
2019-10-20 17:19:17 +00:00
}
2018-03-05 10:29:25 +00:00
}
2022-01-06 17:28:22 +00:00
MeterCommonImplementation : : MeterCommonImplementation ( MeterInfo & mi ,
DriverInfo & di ) :
2022-05-21 12:22:56 +00:00
type_ ( di . type ( ) ) ,
driver_ ( di . name ( ) . str ( ) ) ,
driver_name_ ( di . name ( ) ) ,
bus_ ( mi . bus ) ,
name_ ( mi . name ) ,
mfct_tpl_status_bits_ ( di . mfctTPLStatusBits ( ) ) ,
2022-05-01 18:54:12 +00:00
waiting_for_poll_response_sem_ ( " waiting_for_poll_response " )
2022-01-06 17:28:22 +00:00
{
ids_ = mi . ids ;
idsc_ = toIdsCommaSeparated ( ids_ ) ;
2022-04-27 17:18:30 +00:00
link_modes_ = mi . link_modes ;
2022-04-27 19:09:50 +00:00
poll_interval_ = mi . poll_interval ;
2022-01-06 17:28:22 +00:00
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 ) ;
}
2022-01-08 17:52:06 +00:00
link_modes_ . unionLinkModeSet ( di . linkModes ( ) ) ;
2022-01-06 17:28:22 +00:00
}
2019-05-04 11:55:52 +00:00
void MeterCommonImplementation : : addConversions ( std : : vector < Unit > cs )
{
for ( Unit c : cs )
{
conversions_ . push_back ( c ) ;
}
}
2019-05-21 12:19:54 +00:00
void MeterCommonImplementation : : addShell ( string cmdline )
{
shell_cmdlines_ . push_back ( cmdline ) ;
}
2021-08-01 22:22:13 +00:00
void MeterCommonImplementation : : addExtraConstantField ( string ecf )
2019-10-20 17:19:17 +00:00
{
2021-08-01 22:22:13 +00:00
extra_constant_fields_ . push_back ( ecf ) ;
2019-10-20 17:19:17 +00:00
}
2019-05-21 12:19:54 +00:00
vector < string > & MeterCommonImplementation : : shellCmdlines ( )
{
return shell_cmdlines_ ;
}
2021-08-01 22:22:13 +00:00
vector < string > & MeterCommonImplementation : : meterExtraConstantFields ( )
2019-10-20 17:19:17 +00:00
{
2021-08-01 22:22:13 +00:00
return extra_constant_fields_ ;
2019-10-20 17:19:17 +00:00
}
2021-03-08 07:40:48 +00:00
MeterDriver MeterCommonImplementation : : driver ( )
2018-03-05 10:29:25 +00:00
{
2022-01-02 12:08:36 +00:00
return toMeterDriver ( driver_ ) ;
2022-01-06 17:28:22 +00:00
}
2022-01-02 12:08:36 +00:00
2022-01-06 17:28:22 +00:00
DriverName MeterCommonImplementation : : driverName ( )
{
2022-01-08 17:52:06 +00:00
if ( driver_name_ . str ( ) = = " " )
{
return DriverName ( toString ( driver ( ) ) ) ;
}
2022-01-06 17:28:22 +00:00
return driver_name_ ;
2018-03-05 10:29:25 +00:00
}
2021-12-31 11:27:57 +00:00
void MeterCommonImplementation : : setMeterType ( MeterType mt )
{
type_ = mt ;
}
2019-06-06 15:28:20 +00:00
void MeterCommonImplementation : : addLinkMode ( LinkMode lm )
{
link_modes_ . addLinkMode ( lm ) ;
}
2022-05-21 12:22:56 +00:00
void MeterCommonImplementation : : addMfctTPlStatusBits ( Translate : : Lookup lookup )
{
mfct_tpl_status_bits_ = lookup ;
}
2019-05-04 11:55:52 +00:00
void MeterCommonImplementation : : addPrint ( string vname , Quantity vquantity ,
2022-04-11 12:01:54 +00:00
function < double ( Unit ) > getValueFunc , string help , PrintProperties pprops )
2019-05-04 11:55:52 +00:00
{
2022-04-23 06:20:22 +00:00
field_infos_ . push_back (
2022-04-23 14:28:54 +00:00
FieldInfo ( field_infos_ . size ( ) ,
vname ,
2022-01-06 17:28:22 +00:00
vquantity ,
defaultUnitForQuantity ( vquantity ) ,
VifScaling : : Auto ,
2022-04-17 13:54:01 +00:00
FieldMatcher ( ) ,
2022-01-06 17:28:22 +00:00
help ,
2022-04-11 12:01:54 +00:00
pprops ,
2022-01-06 17:28:22 +00:00
getValueFunc ,
NULL ,
NULL ,
NULL ,
2022-01-08 14:50:15 +00:00
NoLookup
2022-01-06 17:28:22 +00:00
) ) ;
2019-05-04 17:56:17 +00:00
}
2020-04-22 20:38:40 +00:00
void MeterCommonImplementation : : addPrint ( string vname , Quantity vquantity , Unit unit ,
2022-04-11 12:01:54 +00:00
function < double ( Unit ) > getValueFunc , string help , PrintProperties pprops )
2020-04-22 20:38:40 +00:00
{
2022-04-23 06:20:22 +00:00
field_infos_ . push_back (
2022-04-23 14:28:54 +00:00
FieldInfo ( field_infos_ . size ( ) ,
vname ,
2022-01-06 17:28:22 +00:00
vquantity ,
unit ,
VifScaling : : Auto ,
2022-04-17 13:54:01 +00:00
FieldMatcher ( ) ,
2022-01-06 17:28:22 +00:00
help ,
2022-04-11 12:01:54 +00:00
pprops ,
2022-01-06 17:28:22 +00:00
getValueFunc ,
NULL ,
NULL ,
NULL ,
2022-01-08 14:50:15 +00:00
NoLookup
2022-01-06 17:28:22 +00:00
) ) ;
2020-04-22 20:38:40 +00:00
}
2019-05-04 17:56:17 +00:00
void MeterCommonImplementation : : addPrint ( string vname , Quantity vquantity ,
function < string ( ) > getValueFunc ,
2022-04-11 12:01:54 +00:00
string help , PrintProperties pprops )
2019-05-04 17:56:17 +00:00
{
2022-04-23 06:20:22 +00:00
field_infos_ . push_back (
2022-04-23 14:28:54 +00:00
FieldInfo ( field_infos_ . size ( ) ,
vname ,
2022-01-06 17:28:22 +00:00
vquantity ,
defaultUnitForQuantity ( vquantity ) ,
VifScaling : : Auto ,
2022-04-17 13:54:01 +00:00
FieldMatcher ( ) ,
2022-01-06 17:28:22 +00:00
help ,
2022-04-11 12:01:54 +00:00
pprops ,
2022-01-06 17:28:22 +00:00
NULL ,
getValueFunc ,
NULL ,
NULL ,
2022-01-08 14:50:15 +00:00
NoLookup
2022-01-06 17:28:22 +00:00
) ) ;
}
2022-04-16 18:23:37 +00:00
void MeterCommonImplementation : : addNumericFieldWithExtractor (
2022-01-06 17:28:22 +00:00
string vname ,
Quantity vquantity ,
DifVifKey dif_vif_key ,
VifScaling vif_scaling ,
MeasurementType mt ,
2022-04-16 15:47:20 +00:00
VIFRange vi ,
2022-01-06 17:28:22 +00:00
StorageNr s ,
TariffNr t ,
IndexNr i ,
2022-04-11 12:01:54 +00:00
PrintProperties print_properties ,
2022-01-06 17:28:22 +00:00
string help ,
function < void ( Unit , double ) > setValueFunc ,
function < double ( Unit ) > getValueFunc )
{
2022-04-23 06:20:22 +00:00
field_infos_ . push_back (
2022-04-23 14:28:54 +00:00
FieldInfo ( field_infos_ . size ( ) ,
vname ,
2022-01-06 17:28:22 +00:00
vquantity ,
defaultUnitForQuantity ( vquantity ) ,
vif_scaling ,
2022-04-19 07:10:14 +00:00
FieldMatcher : : build ( ) . set ( dif_vif_key ) . set ( mt ) . set ( vi ) . set ( s ) . set ( t ) . set ( i ) ,
2022-01-06 17:28:22 +00:00
help ,
2022-04-11 12:01:54 +00:00
print_properties ,
2022-01-06 17:28:22 +00:00
getValueFunc ,
NULL ,
setValueFunc ,
NULL ,
2022-01-08 14:50:15 +00:00
NoLookup
2022-01-06 17:28:22 +00:00
) ) ;
}
2022-04-23 14:28:54 +00:00
void MeterCommonImplementation : : addNumericFieldWithExtractor ( string vname ,
string help ,
PrintProperties print_properties ,
Quantity vquantity ,
VifScaling vif_scaling ,
FieldMatcher matcher )
2022-04-19 07:43:10 +00:00
{
2022-04-23 06:20:22 +00:00
field_infos_ . push_back (
2022-04-23 14:28:54 +00:00
FieldInfo ( field_infos_ . size ( ) ,
vname ,
2022-04-19 07:43:10 +00:00
vquantity ,
defaultUnitForQuantity ( vquantity ) ,
vif_scaling ,
matcher ,
help ,
print_properties ,
NULL ,
2022-04-23 14:28:54 +00:00
NULL ,
NULL ,
2022-04-19 07:43:10 +00:00
NULL ,
NoLookup
) ) ;
}
2022-04-16 18:23:37 +00:00
void MeterCommonImplementation : : addNumericField (
2022-02-06 17:49:55 +00:00
string vname ,
Quantity vquantity ,
2022-04-11 12:01:54 +00:00
PrintProperties print_properties ,
2022-02-06 17:49:55 +00:00
string help ,
function < void ( Unit , double ) > setValueFunc ,
function < double ( Unit ) > getValueFunc )
{
2022-04-23 06:20:22 +00:00
field_infos_ . push_back (
2022-04-23 14:28:54 +00:00
FieldInfo ( field_infos_ . size ( ) ,
vname ,
2022-02-06 17:49:55 +00:00
vquantity ,
defaultUnitForQuantity ( vquantity ) ,
VifScaling : : None ,
2022-04-17 13:54:01 +00:00
FieldMatcher ( ) ,
2022-02-06 17:49:55 +00:00
help ,
2022-04-11 12:01:54 +00:00
print_properties ,
2022-02-06 17:49:55 +00:00
getValueFunc ,
NULL ,
setValueFunc ,
NULL ,
NoLookup
) ) ;
}
2022-01-06 17:28:22 +00:00
void MeterCommonImplementation : : addStringFieldWithExtractor (
string vname ,
Quantity vquantity ,
DifVifKey dif_vif_key ,
MeasurementType mt ,
2022-04-16 15:47:20 +00:00
VIFRange vi ,
2022-01-06 17:28:22 +00:00
StorageNr s ,
TariffNr t ,
IndexNr i ,
2022-04-11 12:01:54 +00:00
PrintProperties print_properties ,
2022-01-06 17:28:22 +00:00
string help ,
function < void ( string ) > setValueFunc ,
function < string ( ) > getValueFunc )
{
2022-04-23 06:20:22 +00:00
field_infos_ . push_back (
2022-04-23 14:28:54 +00:00
FieldInfo ( field_infos_ . size ( ) ,
vname ,
2022-01-06 17:28:22 +00:00
vquantity ,
defaultUnitForQuantity ( vquantity ) ,
VifScaling : : None ,
2022-04-19 07:10:14 +00:00
FieldMatcher : : build ( ) . set ( dif_vif_key ) . set ( mt ) . set ( vi ) . set ( s ) . set ( t ) . set ( i ) ,
2022-01-06 17:28:22 +00:00
help ,
2022-04-11 12:01:54 +00:00
print_properties ,
2022-01-06 17:28:22 +00:00
NULL ,
getValueFunc ,
NULL ,
setValueFunc ,
2022-01-08 14:50:15 +00:00
NoLookup
) ) ;
}
2022-04-23 16:23:04 +00:00
void MeterCommonImplementation : : addStringFieldWithExtractor ( string vname ,
string help ,
PrintProperties print_properties ,
FieldMatcher matcher )
{
field_infos_ . push_back (
FieldInfo ( field_infos_ . size ( ) ,
vname ,
Quantity : : Text ,
defaultUnitForQuantity ( Quantity : : Text ) ,
VifScaling : : None ,
matcher ,
help ,
print_properties ,
NULL ,
NULL ,
NULL ,
NULL ,
NoLookup
) ) ;
}
2022-01-08 14:50:15 +00:00
void MeterCommonImplementation : : addStringFieldWithExtractorAndLookup (
string vname ,
Quantity vquantity ,
DifVifKey dif_vif_key ,
MeasurementType mt ,
2022-04-16 15:47:20 +00:00
VIFRange vi ,
2022-01-08 14:50:15 +00:00
StorageNr s ,
TariffNr t ,
IndexNr i ,
2022-04-11 12:01:54 +00:00
PrintProperties print_properties ,
2022-01-08 14:50:15 +00:00
string help ,
function < void ( string ) > setValueFunc ,
function < string ( ) > getValueFunc ,
Translate : : Lookup lookup )
{
2022-04-23 06:20:22 +00:00
field_infos_ . push_back (
2022-04-23 14:28:54 +00:00
FieldInfo ( field_infos_ . size ( ) ,
vname ,
2022-01-08 14:50:15 +00:00
vquantity ,
defaultUnitForQuantity ( vquantity ) ,
VifScaling : : None ,
2022-04-19 07:10:14 +00:00
FieldMatcher : : build ( ) . set ( dif_vif_key ) . set ( mt ) . set ( vi ) . set ( s ) . set ( t ) . set ( i ) ,
2022-01-08 14:50:15 +00:00
help ,
2022-04-11 12:01:54 +00:00
print_properties ,
2022-01-08 14:50:15 +00:00
NULL ,
getValueFunc ,
NULL ,
setValueFunc ,
lookup
2022-01-06 17:28:22 +00:00
) ) ;
2019-05-04 11:55:52 +00:00
}
2022-04-23 16:32:30 +00:00
void MeterCommonImplementation : : addStringFieldWithExtractorAndLookup ( string vname ,
string help ,
PrintProperties print_properties ,
FieldMatcher matcher ,
Translate : : Lookup lookup )
2022-04-23 16:23:04 +00:00
{
field_infos_ . push_back (
FieldInfo ( field_infos_ . size ( ) ,
vname ,
Quantity : : Text ,
defaultUnitForQuantity ( Quantity : : Text ) ,
VifScaling : : None ,
matcher ,
help ,
print_properties ,
NULL ,
NULL ,
NULL ,
NULL ,
lookup
) ) ;
}
2022-06-19 09:31:28 +00:00
void MeterCommonImplementation : : addStringField ( string vname ,
string help ,
PrintProperties print_properties )
{
field_infos_ . push_back (
FieldInfo ( field_infos_ . size ( ) ,
vname ,
Quantity : : Text ,
defaultUnitForQuantity ( Quantity : : Text ) ,
VifScaling : : None ,
FieldMatcher ( ) ,
help ,
print_properties ,
NULL ,
NULL ,
NULL ,
NULL ,
NoLookup
) ) ;
}
2022-04-25 17:38:36 +00:00
void MeterCommonImplementation : : poll ( shared_ptr < BusManager > bus_manager )
2021-03-08 16:14:03 +00:00
{
2022-05-01 18:54:12 +00:00
if ( usesPolling ( ) )
2022-04-25 17:38:36 +00:00
{
2022-05-01 15:05:30 +00:00
// An valid poll interval must have been set!
if ( pollInterval ( ) < = 0 ) return ;
2022-04-27 19:09:50 +00:00
time_t now = time ( NULL ) ;
time_t next_poll_time = timestampLastUpdate ( ) + pollInterval ( ) ;
if ( now < next_poll_time )
{
// Not yet time to poll this meter.
return ;
}
2022-04-25 17:38:36 +00:00
WMBus * bus_device = bus_manager - > findBus ( bus ( ) ) ;
if ( ! bus_device )
{
2022-05-01 15:05:30 +00:00
warning ( " (meter) warning! no bus specified for meter %s %s \n " , name ( ) . c_str ( ) , idsc ( ) . c_str ( ) ) ;
2022-04-25 17:38:36 +00:00
return ;
}
string id = ids ( ) . back ( ) ;
if ( id . length ( ) ! = 2 & & id . length ( ) ! = 3 & & id . length ( ) ! = 8 )
{
debug ( " (meter) not polling from bad id \" %s \" with wrong length \n " , id . c_str ( ) ) ;
return ;
}
if ( id . length ( ) = = 2 | | id . length ( ) = = 3 )
{
vector < uchar > idhex ;
int idnum = atoi ( id . c_str ( ) ) ;
if ( idnum < 0 | | idnum > 250 | | idhex . size ( ) ! = 1 )
{
debug ( " (meter) not polling from bad id \" %s \" \n " , id . c_str ( ) ) ;
return ;
}
vector < uchar > buf ;
buf . resize ( 5 ) ;
buf [ 0 ] = 0x10 ; // Start
buf [ 1 ] = 0x5b ; // REQ_UD2
buf [ 2 ] = idhex [ 0 ] ;
uchar cs = 0 ;
for ( int i = 1 ; i < 3 ; + + i ) cs + = buf [ i ] ;
buf [ 3 ] = cs ; // checksum
buf [ 4 ] = 0x16 ; // Stop
2022-05-01 15:05:30 +00:00
verbose ( " (meter) polling %s %s (primary) with req ud2 on bus %s \n " ,
name ( ) . c_str ( ) ,
id . c_str ( ) ,
bus_device - > busAlias ( ) . c_str ( ) , id . c_str ( ) ) ;
2022-04-25 17:38:36 +00:00
bus_device - > serial ( ) - > send ( buf ) ;
}
if ( id . length ( ) = = 8 )
{
// A full secondary address 12345678 was specified.
vector < uchar > idhex ;
bool ok = hex2bin ( id , & idhex ) ;
if ( ! ok | | idhex . size ( ) ! = 4 )
{
debug ( " (meter) not polling from bad id \" %s \" \n " , id . c_str ( ) ) ;
return ;
}
vector < uchar > buf ;
buf . resize ( 17 ) ;
buf [ 0 ] = 0x68 ;
buf [ 1 ] = 0x0b ;
buf [ 2 ] = 0x0b ;
buf [ 3 ] = 0x68 ;
buf [ 4 ] = 0x73 ; // SND_UD
buf [ 5 ] = 0xfd ; // address 253
buf [ 6 ] = 0x52 ; // ci 52
2022-04-27 16:51:51 +00:00
// Assuming we send id 12345678
2022-04-25 17:38:36 +00:00
buf [ 7 ] = idhex [ 3 ] ; // id 78
buf [ 8 ] = idhex [ 2 ] ; // id 56
buf [ 9 ] = idhex [ 1 ] ; // id 34
buf [ 10 ] = idhex [ 0 ] ; // id 12
2022-04-27 16:51:51 +00:00
// Use wildcards instead of exact matching here.
// TODO add selection based on these values as well.
buf [ 11 ] = 0xff ; // mfct
buf [ 12 ] = 0xff ; // mfct
buf [ 13 ] = 0xff ; // version/generation
buf [ 14 ] = 0xff ; // type/media/device
2022-04-25 17:38:36 +00:00
uchar cs = 0 ;
for ( int i = 4 ; i < 15 ; + + i ) cs + = buf [ i ] ;
buf [ 15 ] = cs ; // checksum
buf [ 16 ] = 0x16 ; // Stop
debug ( " (meter) secondary addressing bus %s to address %s \n " , bus_device - > busAlias ( ) . c_str ( ) , id . c_str ( ) ) ;
bus_device - > serial ( ) - > send ( buf ) ;
usleep ( 1000 * 500 ) ;
buf . resize ( 5 ) ;
buf [ 0 ] = 0x10 ; // Start
buf [ 1 ] = 0x5b ; // REQ_UD2
buf [ 2 ] = 0xfd ; // 00 or address 253 previously setup
cs = 0 ;
for ( int i = 1 ; i < 3 ; + + i ) cs + = buf [ i ] ;
buf [ 3 ] = cs ; // checksum
buf [ 4 ] = 0x16 ; // Stop
2022-05-01 15:05:30 +00:00
verbose ( " (meter) polling %s %s (secondary) with req ud2 bus %s \n " ,
name ( ) . c_str ( ) ,
id . c_str ( ) ,
bus_device - > busAlias ( ) . c_str ( ) ) ;
2022-04-25 17:38:36 +00:00
bus_device - > serial ( ) - > send ( buf ) ;
}
2022-05-01 18:54:12 +00:00
bool ok = waiting_for_poll_response_sem_ . wait ( ) ;
if ( ! ok )
{
warning ( " (meter) %s %s did not send a response! \n " , name ( ) . c_str ( ) , idsc ( ) . c_str ( ) ) ;
}
2022-04-25 17:38:36 +00:00
}
2021-03-08 16:14:03 +00:00
}
2021-02-20 21:21:01 +00:00
vector < string > & MeterCommonImplementation : : ids ( )
2019-03-05 20:19:05 +00:00
{
return ids_ ;
2018-03-05 10:29:25 +00:00
}
2021-02-20 21:21:01 +00:00
string MeterCommonImplementation : : idsc ( )
{
return idsc_ ;
}
2022-04-23 06:20:22 +00:00
vector < FieldInfo > & MeterCommonImplementation : : fieldInfos ( )
2020-05-09 21:43:30 +00:00
{
2022-04-23 06:20:22 +00:00
return field_infos_ ;
2020-09-08 20:11:32 +00:00
}
2018-03-05 10:29:25 +00:00
string MeterCommonImplementation : : name ( )
{
return name_ ;
}
2019-03-05 20:19:05 +00:00
void MeterCommonImplementation : : onUpdate ( function < void ( Telegram * , Meter * ) > cb )
2018-03-05 10:29:25 +00:00
{
on_update_ . push_back ( cb ) ;
}
int MeterCommonImplementation : : numUpdates ( )
{
return num_updates_ ;
}
string MeterCommonImplementation : : datetimeOfUpdateHumanReadable ( )
{
char datetime [ 40 ] ;
memset ( datetime , 0 , sizeof ( datetime ) ) ;
strftime ( datetime , 20 , " %Y-%m-%d %H:%M.%S " , localtime ( & datetime_of_update_ ) ) ;
return string ( datetime ) ;
}
string MeterCommonImplementation : : datetimeOfUpdateRobot ( )
{
char datetime [ 40 ] ;
memset ( datetime , 0 , sizeof ( datetime ) ) ;
// This is the date time in the Greenwich timezone (Zulu time), dont get surprised!
strftime ( datetime , sizeof ( datetime ) , " %FT%TZ " , gmtime ( & datetime_of_update_ ) ) ;
return string ( datetime ) ;
}
2021-08-01 16:24:19 +00:00
string MeterCommonImplementation : : unixTimestampOfUpdate ( )
{
char ut [ 40 ] ;
memset ( ut , 0 , sizeof ( ut ) ) ;
2021-08-12 17:36:26 +00:00
snprintf ( ut , sizeof ( ut ) - 1 , " %lu " , datetime_of_update_ ) ;
2021-08-01 16:24:19 +00:00
return string ( ut ) ;
}
2022-04-27 19:09:50 +00:00
time_t MeterCommonImplementation : : timestampLastUpdate ( )
{
return datetime_of_update_ ;
}
void MeterCommonImplementation : : setPollInterval ( time_t interval )
{
poll_interval_ = interval ;
2022-05-01 18:54:12 +00:00
if ( usesPolling ( ) & & poll_interval_ = = 0 )
2022-05-01 15:05:30 +00:00
{
warning ( " (meter) %s %s needs polling but has no pollinterval set! \n " , name ( ) . c_str ( ) , idsc ( ) . c_str ( ) ) ;
}
2022-04-27 19:09:50 +00:00
}
time_t MeterCommonImplementation : : pollInterval ( )
{
return poll_interval_ ;
}
2022-05-01 18:54:12 +00:00
bool MeterCommonImplementation : : usesPolling ( )
2022-05-01 15:05:30 +00:00
{
return link_modes_ . has ( LinkMode : : MBUS ) | |
link_modes_ . has ( LinkMode : : C2 ) | |
link_modes_ . has ( LinkMode : : T2 ) | |
link_modes_ . has ( LinkMode : : S2 ) ;
}
bool driverNeedsPolling ( MeterDriver d , DriverName & dn )
2021-03-08 16:14:03 +00:00
{
2022-02-17 18:59:51 +00:00
if ( d ! = MeterDriver : : UNKNOWN & & d ! = MeterDriver : : AUTO )
{
2021-03-08 16:14:03 +00:00
# define X(mname,linkmodes,info,driver,cname) if (d == MeterDriver::driver && 0 != ((linkmodes) & MBUS_bit)) return true;
LIST_OF_METERS
# undef X
return false ;
2022-02-17 18:59:51 +00:00
}
2022-06-16 14:01:31 +00:00
DriverInfo * di = lookupDriver ( dn . str ( ) ) ;
2022-02-17 18:59:51 +00:00
2022-06-16 14:01:31 +00:00
if ( di = = NULL ) return false ;
2022-02-17 18:59:51 +00:00
// Return true for MBUS,S2,C2,T2 meters. Currently only mbus is implemented.
2022-06-16 14:01:31 +00:00
return di - > linkModes ( ) . has ( LinkMode : : MBUS ) | |
di - > linkModes ( ) . has ( LinkMode : : C2 ) | |
di - > linkModes ( ) . has ( LinkMode : : T2 ) | |
di - > linkModes ( ) . has ( LinkMode : : S2 ) ;
2021-03-08 16:14:03 +00:00
}
2022-01-06 17:28:22 +00:00
const char * toString ( MeterType type )
{
# define X(tname) if (type == MeterType::tname) return #tname;
LIST_OF_METER_TYPES
# undef X
return " unknown " ;
}
2021-03-13 17:35:47 +00:00
string toString ( MeterDriver mt )
2019-05-04 08:52:18 +00:00
{
2021-03-08 07:40:48 +00:00
# define X(mname,link,info,type,cname) if (mt == MeterDriver::type) return #mname;
2019-05-04 08:52:18 +00:00
LIST_OF_METERS
# undef X
return " unknown " ;
}
2022-01-06 17:28:22 +00:00
string toString ( DriverInfo & di )
{
2022-01-08 21:04:18 +00:00
if ( di . driver ( ) ! = MeterDriver : : UNKNOWN & &
di . driver ( ) ! = MeterDriver : : AUTO ) return toString ( di . driver ( ) ) ;
2022-01-06 17:28:22 +00:00
return di . name ( ) . str ( ) ;
}
2021-03-08 07:40:48 +00:00
MeterDriver toMeterDriver ( string & t )
2019-05-04 08:52:18 +00:00
{
2021-03-08 07:40:48 +00:00
# define X(mname,linkmodes,info,type,cname) if (t == #mname) return MeterDriver::type;
2019-05-04 08:52:18 +00:00
LIST_OF_METERS
# undef X
2021-03-08 07:40:48 +00:00
return MeterDriver : : UNKNOWN ;
2018-11-01 16:17:23 +00:00
}
2019-06-06 15:28:20 +00:00
LinkModeSet toMeterLinkModeSet ( string & t )
2019-05-04 08:52:18 +00:00
{
2019-06-06 15:28:20 +00:00
# define X(mname,linkmodes,info,type,cname) if (t == #mname) return LinkModeSet(linkmodes);
2019-05-04 08:52:18 +00:00
LIST_OF_METERS
# undef X
2019-06-06 15:28:20 +00:00
return LinkModeSet ( ) ;
2019-05-04 08:52:18 +00:00
}
2018-03-05 10:29:25 +00:00
2021-03-13 17:35:47 +00:00
LinkModeSet toMeterLinkModeSet ( MeterDriver d )
{
# define X(mname,linkmodes,info,driver,cname) if (d == MeterDriver::driver) return LinkModeSet(linkmodes);
LIST_OF_METERS
# undef X
return LinkModeSet ( ) ;
}
2021-02-20 21:21:01 +00:00
bool MeterCommonImplementation : : isTelegramForMeter ( Telegram * t , Meter * meter , MeterInfo * mi )
2018-03-05 10:29:25 +00:00
{
2021-02-20 21:21:01 +00:00
string name ;
vector < string > ids ;
string idsc ;
2021-03-08 07:40:48 +00:00
MeterDriver driver ;
2021-02-20 21:21:01 +00:00
assert ( ( meter & & ! mi ) | |
( ! meter & & mi ) ) ;
if ( meter )
{
name = meter - > name ( ) ;
ids = meter - > ids ( ) ;
idsc = meter - > idsc ( ) ;
2021-03-08 07:40:48 +00:00
driver = meter - > driver ( ) ;
2021-02-20 21:21:01 +00:00
}
else
{
name = mi - > name ;
ids = mi - > ids ;
idsc = mi - > idsc ;
2021-03-08 07:40:48 +00:00
driver = mi - > driver ;
2021-02-20 21:21:01 +00:00
}
2021-03-14 11:15:49 +00:00
debug ( " (meter) %s: for me? %s in %s \n " , name . c_str ( ) , t - > idsc . c_str ( ) , idsc . c_str ( ) ) ;
2019-03-05 20:19:05 +00:00
2021-01-24 12:35:44 +00:00
bool used_wildcard = false ;
2021-02-20 21:21:01 +00:00
bool id_match = doesIdsMatchExpressions ( t - > ids , ids , & used_wildcard ) ;
2019-03-05 20:19:05 +00:00
if ( ! id_match ) {
2019-03-19 20:32:28 +00:00
// The id must match.
2021-02-20 21:21:01 +00:00
debug ( " (meter) %s: not for me: not my id \n " , name . c_str ( ) ) ;
2019-03-05 20:19:05 +00:00
return false ;
}
2021-03-08 07:40:48 +00:00
bool valid_driver = isMeterDriverValid ( driver , t - > dll_mfct , t - > dll_type , t - > dll_version ) ;
2021-01-30 16:58:00 +00:00
if ( ! valid_driver & & t - > tpl_id_found )
{
2021-03-08 07:40:48 +00:00
valid_driver = isMeterDriverValid ( driver , t - > tpl_mfct , t - > tpl_type , t - > tpl_version ) ;
2021-01-30 16:58:00 +00:00
}
2021-01-24 12:35:44 +00:00
2021-03-08 07:40:48 +00:00
if ( ! valid_driver & & driver ! = MeterDriver : : AUTO )
2020-02-23 12:55:37 +00:00
{
2020-09-04 09:31:49 +00:00
// Are we using the right driver? Perhaps not since
// this particular driver, mfct, media, version combo
// is not registered in the METER_DETECTION list in meters.h
2021-01-24 12:35:44 +00:00
if ( used_wildcard )
{
// The match for the id was not exact, thus the user is listening using a wildcard
// to many meters and some received matched meter telegrams are not from the right meter type,
// ie their driver does not match. Lets just ignore telegrams that probably cannot be decoded properly.
verbose ( " (meter) ignoring telegram from %s since it matched a wildcard id rule but driver does not match. \n " ,
2021-01-30 16:58:00 +00:00
t - > idsc . c_str ( ) ) ;
2021-01-24 12:35:44 +00:00
return false ;
}
// The match was exact, ie the user has actually specified 12345678 and foo as driver even
// though they do not match. Lets warn and then proceed. It is common that a user tries a
// new version of a meter with the old driver, thus it might not be a real error.
2021-01-24 20:44:20 +00:00
if ( isVerboseEnabled ( ) | | isDebugEnabled ( ) | | ! warned_for_telegram_before ( t , t - > dll_a ) )
2020-09-07 08:36:39 +00:00
{
2021-01-06 12:46:12 +00:00
string possible_drivers = t - > autoDetectPossibleDrivers ( ) ;
2021-12-07 22:56:29 +00:00
if ( t - > beingAnalyzed ( ) = = false )
2021-01-06 12:46:12 +00:00
{
2021-12-07 22:56:29 +00:00
warning ( " (meter) %s: meter detection did not match the selected driver %s! correct driver is: %s \n "
" (meter) Not printing this warning again for id: %02x%02x%02x%02x mfct: (%s) %s (0x%02x) type: %s (0x%02x) ver: 0x%02x \n " ,
name . c_str ( ) ,
toString ( driver ) . c_str ( ) ,
possible_drivers . c_str ( ) ,
t - > dll_id_b [ 3 ] , t - > dll_id_b [ 2 ] , t - > dll_id_b [ 1 ] , t - > dll_id_b [ 0 ] ,
manufacturerFlag ( t - > dll_mfct ) . c_str ( ) ,
manufacturer ( t - > dll_mfct ) . c_str ( ) ,
t - > dll_mfct ,
mediaType ( t - > dll_type , t - > dll_mfct ) . c_str ( ) , t - > dll_type ,
t - > dll_version ) ;
if ( possible_drivers = = " unknown! " )
{
warning ( " (meter) please consider opening an issue at https://github.com/weetmuts/wmbusmeters/ \n " ) ;
warning ( " (meter) to add support for this unknown mfct,media,version combination \n " ) ;
}
2021-01-06 12:46:12 +00:00
}
2020-09-07 08:36:39 +00:00
}
2019-03-05 17:38:54 +00:00
}
2019-03-05 20:19:05 +00:00
2021-02-20 21:21:01 +00:00
debug ( " (meter) %s: yes for me \n " , name . c_str ( ) ) ;
2019-03-05 20:19:05 +00:00
return true ;
2018-03-05 10:29:25 +00:00
}
2020-01-27 08:29:40 +00:00
MeterKeys * MeterCommonImplementation : : meterKeys ( )
2018-03-05 10:29:25 +00:00
{
2020-01-27 08:29:40 +00:00
return & meter_keys_ ;
2018-03-05 10:29:25 +00:00
}
2021-02-20 21:21:01 +00:00
int MeterCommonImplementation : : index ( )
{
return index_ ;
}
void MeterCommonImplementation : : setIndex ( int i )
{
index_ = i ;
}
2021-03-14 08:41:35 +00:00
string MeterCommonImplementation : : bus ( )
2021-03-08 16:14:03 +00:00
{
return bus_ ;
}
2018-03-05 10:29:25 +00:00
void MeterCommonImplementation : : triggerUpdate ( Telegram * t )
{
datetime_of_update_ = time ( NULL ) ;
num_updates_ + + ;
2019-03-05 20:19:05 +00:00
for ( auto & cb : on_update_ ) if ( cb ) cb ( t , this ) ;
2018-03-05 10:29:25 +00:00
t - > handled = true ;
}
2019-05-04 14:37:35 +00:00
2022-04-23 06:20:22 +00:00
string concatAllFields ( Meter * m , Telegram * t , char c , vector < FieldInfo > & fields , vector < Unit > & cs , bool hr ,
2021-08-08 15:16:52 +00:00
vector < string > * extra_constant_fields )
2019-05-04 14:37:35 +00:00
{
string s ;
s = " " ;
s + = m - > name ( ) + c ;
2021-01-30 16:58:00 +00:00
if ( t - > ids . size ( ) > 0 )
{
s + = t - > ids . back ( ) + c ;
}
else
{
s + = c ;
}
2022-04-23 06:20:22 +00:00
for ( FieldInfo & fi : fields )
2019-05-04 14:37:35 +00:00
{
2022-04-23 06:20:22 +00:00
if ( fi . printProperties ( ) . hasFIELD ( ) )
2019-05-04 14:37:35 +00:00
{
2022-04-23 08:18:04 +00:00
if ( fi . xuantity ( ) = = Quantity : : Text )
{
s + = m - > getStringValue ( & fi ) ;
}
else
2019-05-04 17:56:17 +00:00
{
2022-04-23 06:20:22 +00:00
Unit u = replaceWithConversionUnit ( fi . defaultUnit ( ) , cs ) ;
2022-04-23 08:18:04 +00:00
double v = m - > getNumericValue ( & fi , u ) ;
2019-05-04 17:56:17 +00:00
if ( hr ) {
s + = valueToString ( v , u ) ;
s + = " " + unitToStringHR ( u ) ;
} else {
s + = to_string ( v ) ;
}
}
2019-05-04 14:37:35 +00:00
s + = c ;
}
}
s + = m - > datetimeOfUpdateHumanReadable ( ) ;
return s ;
}
2021-08-08 14:50:25 +00:00
string findField ( string key , vector < string > * extra_constant_fields )
2020-05-09 21:43:30 +00:00
{
2021-08-08 14:50:25 +00:00
key = key + " = " ;
for ( string ecf : * extra_constant_fields )
2020-05-09 21:43:30 +00:00
{
2021-08-08 14:50:25 +00:00
if ( startsWith ( ecf , key ) )
{
return ecf . substr ( key . length ( ) ) ;
}
2020-05-09 21:43:30 +00:00
}
2021-08-08 14:50:25 +00:00
return " " ;
}
2020-05-09 21:43:30 +00:00
2021-08-08 14:50:25 +00:00
// Is the desired field one of the fields common to all meters and telegrams?
bool checkCommonField ( string * buf , string field , Meter * m , Telegram * t , char c )
{
if ( field = = " name " )
2020-05-09 21:43:30 +00:00
{
2021-08-08 14:50:25 +00:00
* buf + = m - > name ( ) + c ;
return true ;
}
if ( field = = " id " )
{
* buf + = t - > ids . back ( ) + c ;
return true ;
}
if ( field = = " timestamp " )
{
* buf + = m - > datetimeOfUpdateHumanReadable ( ) + c ;
return true ;
}
if ( field = = " timestamp_lt " )
{
* buf + = m - > datetimeOfUpdateHumanReadable ( ) + c ;
return true ;
}
if ( field = = " timestamp_utc " )
{
* buf + = m - > datetimeOfUpdateRobot ( ) + c ;
return true ;
}
if ( field = = " timestamp_ut " )
{
* buf + = m - > unixTimestampOfUpdate ( ) + c ;
return true ;
}
if ( field = = " device " )
{
* buf + = t - > about . device + c ;
return true ;
}
if ( field = = " rssi_dbm " )
{
* buf + = to_string ( t - > about . rssi_dbm ) + c ;
return true ;
}
return false ;
}
// Is the desired field one of the meter printable fields?
bool checkPrintableField ( string * buf , string field , Meter * m , Telegram * t , char c ,
2022-04-23 06:20:22 +00:00
vector < FieldInfo > & fields , vector < Unit > & cs )
2021-08-08 14:50:25 +00:00
{
2022-04-23 06:20:22 +00:00
for ( FieldInfo & fi : fields )
2021-08-08 14:50:25 +00:00
{
2022-04-23 08:18:04 +00:00
if ( fi . xuantity ( ) = = Quantity : : Text )
2020-11-12 08:56:51 +00:00
{
2021-08-08 14:50:25 +00:00
// Strings are simply just print them.
2022-04-23 06:20:22 +00:00
if ( field = = fi . vname ( ) )
2021-08-08 14:50:25 +00:00
{
2022-04-23 08:18:04 +00:00
* buf + = m - > getStringValue ( & fi ) + c ;
2021-08-08 14:50:25 +00:00
return true ;
}
2020-11-12 08:56:51 +00:00
}
2022-04-23 08:18:04 +00:00
else
2020-05-09 21:43:30 +00:00
{
2021-08-08 14:50:25 +00:00
// Doubles have to be converted into the proper unit.
2022-04-23 06:20:22 +00:00
string default_unit = unitToStringLowerCase ( fi . defaultUnit ( ) ) ;
string var = fi . vname ( ) + " _ " + default_unit ;
2021-08-08 14:50:25 +00:00
if ( field = = var )
2020-05-09 21:43:30 +00:00
{
2021-08-08 14:50:25 +00:00
// Default unit.
2022-04-23 08:18:04 +00:00
* buf + = valueToString ( m - > getNumericValue ( & fi , fi . defaultUnit ( ) ) , fi . defaultUnit ( ) ) + c ;
2021-08-08 14:50:25 +00:00
return true ;
2020-05-09 21:43:30 +00:00
}
2021-08-08 14:50:25 +00:00
else
2020-05-09 21:43:30 +00:00
{
2021-08-08 14:50:25 +00:00
// Added conversion unit.
2022-04-23 06:20:22 +00:00
Unit u = replaceWithConversionUnit ( fi . defaultUnit ( ) , cs ) ;
if ( u ! = fi . defaultUnit ( ) )
2020-05-09 21:43:30 +00:00
{
2021-08-08 14:50:25 +00:00
string unit = unitToStringLowerCase ( u ) ;
2022-04-23 06:20:22 +00:00
string var = fi . vname ( ) + " _ " + unit ;
2021-08-08 14:50:25 +00:00
if ( field = = var )
2020-05-09 21:43:30 +00:00
{
2022-04-23 08:18:04 +00:00
* buf + = valueToString ( m - > getNumericValue ( & fi , u ) , u ) + c ;
2021-08-08 14:50:25 +00:00
return true ;
2020-05-09 21:43:30 +00:00
}
}
}
}
2021-08-08 14:50:25 +00:00
}
return false ;
}
// Is the desired field one of the constant fields?
bool checkConstantField ( string * buf , string field , char c , vector < string > * extra_constant_fields )
{
// Ok, lets look for extra constant fields and print any such static information.
string v = findField ( field , extra_constant_fields ) ;
if ( v ! = " " )
{
* buf + = v + c ;
return true ;
}
return false ;
}
2022-01-06 17:28:22 +00:00
string concatFields ( Meter * m , Telegram * t , char c , vector < FieldInfo > & prints , vector < Unit > & cs , bool hr ,
2021-08-08 15:16:52 +00:00
vector < string > * selected_fields , vector < string > * extra_constant_fields )
2021-08-08 14:50:25 +00:00
{
if ( selected_fields = = NULL | | selected_fields - > size ( ) = = 0 )
{
2021-08-08 15:16:52 +00:00
return concatAllFields ( m , t , c , prints , cs , hr , extra_constant_fields ) ;
2021-08-08 14:50:25 +00:00
}
string buf = " " ;
for ( string field : * selected_fields )
{
bool handled = checkCommonField ( & buf , field , m , t , c ) ;
if ( handled ) continue ;
handled = checkPrintableField ( & buf , field , m , t , c , prints , cs ) ;
if ( handled ) continue ;
handled = checkConstantField ( & buf , field , c , extra_constant_fields ) ;
if ( handled ) continue ;
2020-11-12 08:56:51 +00:00
if ( ! handled )
{
2021-08-08 14:50:25 +00:00
buf + = " ? " + field + " ? " + c ;
2020-11-12 08:56:51 +00:00
}
2020-05-09 21:43:30 +00:00
}
2021-08-08 14:50:25 +00:00
if ( buf . back ( ) = = c ) buf . pop_back ( ) ;
return buf ;
2020-05-09 21:43:30 +00:00
}
2021-12-07 22:56:29 +00:00
bool MeterCommonImplementation : : handleTelegram ( AboutTelegram & about , vector < uchar > input_frame ,
bool simulated , string * ids , bool * id_match , Telegram * out_analyzed )
2019-05-04 15:38:10 +00:00
{
2020-01-27 08:29:40 +00:00
Telegram t ;
2020-10-14 18:59:14 +00:00
t . about = about ;
2020-01-27 08:29:40 +00:00
bool ok = t . parseHeader ( input_frame ) ;
2020-09-08 18:50:51 +00:00
if ( simulated ) t . markAsSimulated ( ) ;
2021-12-07 22:56:29 +00:00
if ( out_analyzed ! = NULL ) t . markAsBeingAnalyzed ( ) ;
2020-09-08 18:50:51 +00:00
2021-01-30 16:58:00 +00:00
* ids = t . idsc ;
2020-09-25 17:14:34 +00:00
2021-02-20 21:21:01 +00:00
if ( ! ok | | ! isTelegramForMeter ( & t , this , NULL ) )
2020-01-27 08:29:40 +00:00
{
2019-05-04 15:38:10 +00:00
// This telegram is not intended for this meter.
2020-01-27 08:29:40 +00:00
return false ;
2019-05-04 15:38:10 +00:00
}
2021-02-20 21:21:01 +00:00
* id_match = true ;
2022-05-01 18:54:12 +00:00
verbose ( " (meter) %s(%d) %s handling telegram from %s \n " , name ( ) . c_str ( ) , index ( ) , meterDriver ( ) . c_str ( ) , t . ids . back ( ) . c_str ( ) ) ;
2020-09-25 17:14:34 +00:00
2020-02-06 18:01:48 +00:00
if ( isDebugEnabled ( ) )
{
string msg = bin2hex ( input_frame ) ;
2021-01-30 16:58:00 +00:00
debug ( " (meter) %s %s \" %s \" \n " , name ( ) . c_str ( ) , t . ids . back ( ) . c_str ( ) , msg . c_str ( ) ) ;
2020-02-06 18:01:48 +00:00
}
2021-01-30 16:58:00 +00:00
ok = t . parse ( input_frame , & meter_keys_ , true ) ;
2020-02-03 17:39:07 +00:00
if ( ! ok )
{
2022-02-06 17:49:55 +00:00
if ( out_analyzed ! = NULL ) * out_analyzed = t ;
2020-02-03 17:39:07 +00:00
// Ignoring telegram since it could not be parsed.
return false ;
}
2019-05-04 15:38:10 +00:00
char log_prefix [ 256 ] ;
2021-02-24 19:50:45 +00:00
snprintf ( log_prefix , 255 , " (%s) log " , meterDriver ( ) . c_str ( ) ) ;
2021-02-07 22:06:44 +00:00
logTelegram ( t . original , t . frame , t . header_size , t . suffix_size ) ;
2019-05-04 15:38:10 +00:00
2022-05-01 18:54:12 +00:00
if ( usesPolling ( ) )
{
waiting_for_poll_response_sem_ . notify ( ) ;
}
2022-01-06 17:28:22 +00:00
// Invoke standardized field extractors!
processFieldExtractors ( & t ) ;
// Invoke tailor made meter specific parsing!
2020-01-27 08:29:40 +00:00
processContent ( & t ) ;
2019-05-04 15:38:10 +00:00
// All done....
if ( isDebugEnabled ( ) )
{
char log_prefix [ 256 ] ;
2021-02-24 19:50:45 +00:00
snprintf ( log_prefix , 255 , " (%s) " , meterDriver ( ) . c_str ( ) ) ;
2020-01-27 08:29:40 +00:00
t . explainParse ( log_prefix , 0 ) ;
2019-05-04 15:38:10 +00:00
}
2020-01-27 08:29:40 +00:00
triggerUpdate ( & t ) ;
2021-12-07 22:56:29 +00:00
if ( out_analyzed ! = NULL ) * out_analyzed = t ;
2020-01-27 08:29:40 +00:00
return true ;
2019-05-04 15:38:10 +00:00
}
2022-01-06 17:28:22 +00:00
void MeterCommonImplementation : : processFieldExtractors ( Telegram * t )
2021-12-31 11:27:57 +00:00
{
2022-05-21 12:22:56 +00:00
// Iterate through the dv_entries in the telegram and trigger any
// field specification with a matcher.
2022-04-23 14:28:54 +00:00
for ( auto & p : t - > dv_entries )
2022-04-17 10:47:06 +00:00
{
2022-04-23 14:28:54 +00:00
DVEntry * dve = & p . second . second ;
2022-04-23 06:20:22 +00:00
for ( FieldInfo & fi : field_infos_ )
2022-04-17 13:54:01 +00:00
{
2022-04-19 07:10:14 +00:00
if ( fi . hasMatcher ( ) & & fi . matches ( dve ) )
2022-04-17 13:54:01 +00:00
{
2022-04-23 21:02:17 +00:00
debug ( " (meters) using field info %s(%s)[%d] to extract %s \n " ,
fi . vname ( ) . c_str ( ) ,
toString ( fi . xuantity ( ) ) ,
fi . index ( ) ,
dve - > dif_vif_key . str ( ) . c_str ( ) ) ;
2022-05-22 21:24:46 +00:00
dve - > addFieldInfo ( & fi ) ;
2022-04-19 07:10:14 +00:00
fi . performExtraction ( this , t , dve ) ;
2022-04-17 13:54:01 +00:00
}
}
}
2022-04-19 07:10:14 +00:00
2022-05-21 12:22:56 +00:00
// Iterate over the fields that has no matcher rule. Ie the field
// itself does the searching and matching.
2022-04-23 06:20:22 +00:00
for ( FieldInfo & fi : field_infos_ )
2021-12-31 11:27:57 +00:00
{
2022-04-19 07:10:14 +00:00
if ( ! fi . hasMatcher ( ) )
{
fi . performExtraction ( this , t , NULL ) ;
}
2022-01-06 17:28:22 +00:00
}
}
void MeterCommonImplementation : : processContent ( Telegram * t )
{
}
2022-04-23 06:20:22 +00:00
void MeterCommonImplementation : : setNumericValue ( FieldInfo * fi , Unit u , double v )
2022-04-19 07:10:14 +00:00
{
2022-04-23 08:18:04 +00:00
if ( fi - > hasSetNumericValueOverride ( ) )
{
// Use setter function to store value somewhere.
fi - > setNumericValueOverride ( u , v ) ;
return ;
}
// Store value in default meter location for numeric values.
2022-04-23 06:20:22 +00:00
string field_name_no_unit = fi - > vname ( ) ;
2022-04-23 21:02:17 +00:00
numeric_values_ [ pair < string , Quantity > ( field_name_no_unit , fi - > xuantity ( ) ) ] = NumericField ( u , v , fi ) ;
2022-04-19 07:10:14 +00:00
}
2022-04-23 06:20:22 +00:00
double MeterCommonImplementation : : getNumericValue ( FieldInfo * fi , Unit to )
2022-04-19 07:10:14 +00:00
{
2022-04-23 08:18:04 +00:00
if ( fi - > hasGetNumericValueOverride ( ) )
{
return fi - > getNumericValueOverride ( to ) ;
}
2022-04-23 06:20:22 +00:00
string field_name_no_unit = fi - > vname ( ) ;
2022-04-23 21:02:17 +00:00
pair < string , Quantity > key ( field_name_no_unit , fi - > xuantity ( ) ) ;
if ( numeric_values_ . count ( key ) = = 0 )
2022-04-23 16:23:04 +00:00
{
return std : : numeric_limits < double > : : quiet_NaN ( ) ; // This is translated into a null in the json.
}
2022-04-23 21:02:17 +00:00
NumericField & nf = numeric_values_ [ key ] ;
2022-04-22 15:28:57 +00:00
return convert ( nf . value , nf . unit , to ) ;
2022-04-19 07:10:14 +00:00
}
2022-04-23 06:20:22 +00:00
void MeterCommonImplementation : : setStringValue ( FieldInfo * fi , string v )
2022-04-19 07:10:14 +00:00
{
2022-04-23 08:18:04 +00:00
if ( fi - > hasSetStringValueOverride ( ) )
{
fi - > setStringValueOverride ( v ) ;
return ;
}
2022-04-23 06:20:22 +00:00
string field_name_no_unit = fi - > vname ( ) ;
string_values_ [ field_name_no_unit ] = StringField ( v , fi ) ;
2022-04-19 07:10:14 +00:00
}
2022-04-23 06:20:22 +00:00
string MeterCommonImplementation : : getStringValue ( FieldInfo * fi )
2022-04-19 07:10:14 +00:00
{
2022-04-23 08:18:04 +00:00
if ( fi - > hasGetStringValueOverride ( ) )
{
2022-05-22 14:10:16 +00:00
// There is a custom getter for this field. Use this instead.
2022-04-23 08:18:04 +00:00
return fi - > getStringValueOverride ( ) ;
}
2022-05-22 14:10:16 +00:00
// Fetch the string value from the default string storage in the meter.
2022-04-23 06:20:22 +00:00
string field_name_no_unit = fi - > vname ( ) ;
2022-04-23 16:23:04 +00:00
if ( string_values_ . count ( field_name_no_unit ) = = 0 )
{
return " null " ; // This is translated to a real(non-string) null in the json.
}
2022-04-23 06:20:22 +00:00
StringField & sf = string_values_ [ field_name_no_unit ] ;
2022-05-22 14:10:16 +00:00
string value = sf . value ;
if ( fi - > printProperties ( ) . hasSTATUS ( ) )
{
// This is >THE< status field, only one is allowed.
// Look for other fields with the JOIN_INTO_STATUS marker.
// These other fields will not be printed, instead
// joined into this status field.
for ( FieldInfo & f : field_infos_ )
{
if ( f . printProperties ( ) . hasJOININTOSTATUS ( ) )
{
string more = getStringValue ( & f ) ;
string joined = joinStatusStrings ( value , more ) ;
//printf("JOINING >%s< >%s< into >%s<\n", value.c_str(), more.c_str(), joined.c_str());
value = joined ;
}
}
// Sort all found flags and remove any duplicates. A well designed meter decoder
// should not be able to generate duplicates.
value = sortStatusString ( value ) ;
}
return value ;
2022-04-19 07:10:14 +00:00
}
2022-01-06 17:28:22 +00:00
2022-05-21 12:22:56 +00:00
string MeterCommonImplementation : : decodeTPLStatusByte ( uchar sts )
{
return : : decodeTPLStatusByteWithMfct ( sts , mfct_tpl_status_bits_ ) ;
}
2022-01-06 17:28:22 +00:00
FieldInfo * MeterCommonImplementation : : findFieldInfo ( string vname )
{
FieldInfo * found = NULL ;
2022-04-23 06:20:22 +00:00
for ( FieldInfo & p : field_infos_ )
2022-01-06 17:28:22 +00:00
{
if ( p . vname ( ) = = vname )
2021-12-31 11:27:57 +00:00
{
found = & p ;
break ;
}
}
2022-01-06 17:28:22 +00:00
return found ;
}
string MeterCommonImplementation : : renderJsonOnlyDefaultUnit ( string vname )
{
FieldInfo * fi = findFieldInfo ( vname ) ;
if ( fi = = NULL ) return " unknown field " + vname ;
2022-04-23 08:18:04 +00:00
return fi - > renderJsonOnlyDefaultUnit ( this ) ;
2022-01-06 17:28:22 +00:00
}
2022-04-23 08:18:04 +00:00
string FieldInfo : : renderJsonOnlyDefaultUnit ( Meter * m )
2022-01-06 17:28:22 +00:00
{
2022-04-23 08:18:04 +00:00
return renderJson ( m , NULL ) ;
2022-01-06 17:28:22 +00:00
}
2021-12-31 11:27:57 +00:00
2022-04-23 08:18:04 +00:00
string FieldInfo : : renderJsonText ( Meter * m )
2022-01-06 17:28:22 +00:00
{
2022-04-23 08:18:04 +00:00
return renderJson ( m , NULL ) ;
2021-12-31 11:27:57 +00:00
}
2022-04-22 15:28:57 +00:00
string FieldInfo : : generateFieldName ( DVEntry * dve )
{
if ( xuantity_ = = Quantity : : Text )
{
return vname ( ) ;
}
string default_unit = unitToStringLowerCase ( defaultUnit ( ) ) ;
string var = vname ( ) ;
return vname ( ) + " _ " + default_unit ;
}
2022-04-23 08:18:04 +00:00
string FieldInfo : : renderJson ( Meter * m , vector < Unit > * conversions )
2021-12-31 11:27:57 +00:00
{
string s ;
2022-01-06 17:28:22 +00:00
string default_unit = unitToStringLowerCase ( defaultUnit ( ) ) ;
string var = vname ( ) ;
2022-04-23 08:18:04 +00:00
if ( xuantity ( ) = = Quantity : : Text )
2022-01-06 17:28:22 +00:00
{
2022-04-23 16:23:04 +00:00
string v = m - > getStringValue ( this ) ;
if ( v = = " null " )
{
// Yes, right now a meter cannot send a string value "something":"null" it will
// be translated into "something":null in the json, indicating that there is no value.
// This should not be a problem for now. Lets deal with it when a meter decides to send "null"
// as its version string for example.
s + = " \" " + var + " \" :null " ;
}
else
{
// Normally the string values are quoted in json. TODO quote the value properly.
// A well crafted meter could send a version string with " and break the json format.
s + = " \" " + var + " \" : \" " + v + " \" " ;
}
2021-12-31 11:27:57 +00:00
}
2022-04-23 08:18:04 +00:00
else
2022-01-06 17:28:22 +00:00
{
2022-04-23 08:18:04 +00:00
s + = " \" " + var + " _ " + default_unit + " \" : " + valueToString ( m - > getNumericValue ( this , defaultUnit ( ) ) , defaultUnit ( ) ) ;
2021-12-31 11:27:57 +00:00
2022-01-06 17:28:22 +00:00
if ( conversions ! = NULL )
2021-12-31 11:27:57 +00:00
{
2022-01-06 17:28:22 +00:00
Unit u = replaceWithConversionUnit ( defaultUnit ( ) , * conversions ) ;
if ( u ! = defaultUnit ( ) )
{
string unit = unitToStringLowerCase ( u ) ;
// Appending extra conversion unit.
2022-04-23 08:18:04 +00:00
s + = " , \" " + var + " _ " + unit + " \" : " + valueToString ( m - > getNumericValue ( this , u ) , u ) ;
2022-01-06 17:28:22 +00:00
}
2021-12-31 11:27:57 +00:00
}
}
2022-01-06 17:28:22 +00:00
2021-12-31 11:27:57 +00:00
return s ;
}
2019-05-04 14:37:35 +00:00
void MeterCommonImplementation : : printMeter ( Telegram * t ,
string * human_readable ,
string * fields , char separator ,
string * json ,
2019-10-20 17:19:17 +00:00
vector < string > * envs ,
2021-08-01 22:22:13 +00:00
vector < string > * extra_constant_fields ,
2022-01-25 19:10:38 +00:00
vector < string > * selected_fields ,
bool pretty_print_json )
2019-05-04 14:37:35 +00:00
{
2022-04-23 06:20:22 +00:00
* human_readable = concatFields ( this , t , ' \t ' , field_infos_ , conversions_ , true , selected_fields , extra_constant_fields ) ;
* fields = concatFields ( this , t , separator , field_infos_ , conversions_ , false , selected_fields , extra_constant_fields ) ;
2019-05-04 14:37:35 +00:00
2021-02-24 19:50:45 +00:00
string media ;
2021-01-30 16:58:00 +00:00
if ( t - > tpl_id_found )
{
2021-02-24 19:50:45 +00:00
media = mediaTypeJSON ( t - > tpl_type , t - > tpl_mfct ) ;
2021-01-30 16:58:00 +00:00
}
else if ( t - > ell_id_found )
{
2021-02-24 19:50:45 +00:00
media = mediaTypeJSON ( t - > ell_type , t - > ell_mfct ) ;
2021-01-30 16:58:00 +00:00
}
else
{
2021-02-24 19:50:45 +00:00
media = mediaTypeJSON ( t - > dll_type , t - > dll_mfct ) ;
2021-01-30 16:58:00 +00:00
}
2022-01-25 19:10:38 +00:00
string indent = " " ;
string newline = " " ;
if ( pretty_print_json )
{
indent = " " ;
newline = " \n " ;
}
2019-05-04 14:37:35 +00:00
string s ;
2022-01-25 19:10:38 +00:00
s + = " { " + newline ;
s + = indent + " \" media \" : \" " + media + " \" , " + newline ;
s + = indent + " \" meter \" : \" " + meterDriver ( ) + " \" , " + newline ;
s + = indent + " \" name \" : \" " + name ( ) + " \" , " + newline ;
2021-01-30 16:58:00 +00:00
if ( t - > ids . size ( ) > 0 )
{
2022-01-25 19:10:38 +00:00
s + = indent + " \" id \" : \" " + t - > ids . back ( ) + " \" , " + newline ;
2021-01-30 16:58:00 +00:00
}
else
{
2022-01-25 19:10:38 +00:00
s + = indent + " \" id \" : \" \" , " + newline ;
2021-01-30 16:58:00 +00:00
}
2022-04-23 14:28:54 +00:00
// Iterate over the meter field infos...
2022-04-23 06:20:22 +00:00
for ( FieldInfo & fi : field_infos_ )
2019-05-04 14:37:35 +00:00
{
2022-04-23 06:20:22 +00:00
if ( fi . printProperties ( ) . hasJSON ( ) )
2019-05-04 14:37:35 +00:00
{
2022-04-23 14:28:54 +00:00
// The field should be printed in the json. (Most usually should.)
bool found = false ;
for ( auto & i : t - > dv_entries )
{
// Check each telegram dv entry.
DVEntry * dve = & i . second . second ;
// Has the entry been matches to this field, then print it as json.
2022-05-22 21:24:46 +00:00
if ( dve - > hasFieldInfo ( & fi ) )
2022-04-23 14:28:54 +00:00
{
2022-04-23 21:02:17 +00:00
debug ( " (meters) render field %s(%s)[%d] with dventry @%d key %s data %s \n " ,
fi . vname ( ) . c_str ( ) , toString ( fi . xuantity ( ) ) , fi . index ( ) ,
dve - > offset ,
dve - > dif_vif_key . str ( ) . c_str ( ) ,
dve - > value . c_str ( ) ) ;
string out = fi . renderJson ( this , & conversions ( ) ) ;
debug ( " (meters) %s \n " , out . c_str ( ) ) ;
s + = indent + out + " , " + newline ;
2022-04-23 14:28:54 +00:00
found = true ;
}
}
if ( ! found & & ! fi . printProperties ( ) . hasOPTIONAL ( ) )
{
// No telegram entries found, but this field should be printed anyway.
// It will be printed with any value received from a previous telegram.
2022-05-22 21:24:46 +00:00
// Or if no value has been received, null.
2022-04-23 14:28:54 +00:00
s + = indent + fi . renderJson ( this , & conversions ( ) ) + " , " + newline ;
}
2019-05-04 14:37:35 +00:00
}
}
2022-04-23 14:28:54 +00:00
2022-01-25 19:10:38 +00:00
s + = indent + " \" timestamp \" : \" " + datetimeOfUpdateRobot ( ) + " \" " ;
2021-08-08 15:16:52 +00:00
2020-10-14 18:59:14 +00:00
if ( t - > about . device ! = " " )
{
2022-01-25 19:10:38 +00:00
s + = " , " + newline ;
s + = indent + " \" device \" : \" " + t - > about . device + " \" , " + newline ;
s + = indent + " \" rssi_dbm \" : " + to_string ( t - > about . rssi_dbm ) ;
2020-10-14 18:59:14 +00:00
}
2021-08-01 22:22:13 +00:00
for ( string extra_field : meterExtraConstantFields ( ) )
2019-10-20 17:19:17 +00:00
{
2022-01-25 19:10:38 +00:00
s + = " , " + newline ;
s + = indent + makeQuotedJson ( extra_field ) ;
2019-10-20 17:19:17 +00:00
}
2021-08-01 22:22:13 +00:00
for ( string extra_field : * extra_constant_fields )
2019-10-20 17:19:17 +00:00
{
2022-01-25 19:10:38 +00:00
s + = " , " + newline ;
s + = indent + makeQuotedJson ( extra_field ) ;
2019-10-20 17:19:17 +00:00
}
2022-01-25 19:10:38 +00:00
s + = newline ;
2019-05-04 14:37:35 +00:00
s + = " } " ;
* json = s ;
envs - > push_back ( string ( " METER_JSON= " ) + * json ) ;
2021-01-30 16:58:00 +00:00
if ( t - > ids . size ( ) > 0 )
{
envs - > push_back ( string ( " METER_ID= " ) + t - > ids . back ( ) ) ;
}
else
{
envs - > push_back ( string ( " METER_ID= " ) ) ;
}
2021-02-24 19:50:45 +00:00
envs - > push_back ( string ( " METER_NAME= " ) + name ( ) ) ;
envs - > push_back ( string ( " METER_MEDIA= " ) + media ) ;
envs - > push_back ( string ( " METER_TYPE= " ) + meterDriver ( ) ) ;
envs - > push_back ( string ( " METER_TIMESTAMP= " ) + datetimeOfUpdateRobot ( ) ) ;
2021-08-01 21:12:52 +00:00
envs - > push_back ( string ( " METER_TIMESTAMP_UTC= " ) + datetimeOfUpdateRobot ( ) ) ;
envs - > push_back ( string ( " METER_TIMESTAMP_UT= " ) + unixTimestampOfUpdate ( ) ) ;
envs - > push_back ( string ( " METER_TIMESTAMP_LT= " ) + datetimeOfUpdateHumanReadable ( ) ) ;
2021-02-24 19:50:45 +00:00
if ( t - > about . device ! = " " )
{
envs - > push_back ( string ( " METER_DEVICE= " ) + t - > about . device ) ;
envs - > push_back ( string ( " METER_RSSI_DBM= " ) + to_string ( t - > about . rssi_dbm ) ) ;
}
2019-05-04 14:37:35 +00:00
2022-04-23 08:18:04 +00:00
for ( FieldInfo & fi : field_infos_ )
2019-05-04 14:37:35 +00:00
{
2022-04-23 08:18:04 +00:00
if ( fi . printProperties ( ) . hasJSON ( ) )
2019-05-04 14:37:35 +00:00
{
2022-04-23 08:18:04 +00:00
string default_unit = unitToStringUpperCase ( fi . defaultUnit ( ) ) ;
string var = fi . vname ( ) ;
2019-05-04 18:52:05 +00:00
std : : transform ( var . begin ( ) , var . end ( ) , var . begin ( ) , : : toupper ) ;
2022-04-23 08:18:04 +00:00
if ( fi . xuantity ( ) = = Quantity : : Text )
{
string envvar = " METER_ " + var + " = " + getStringValue ( & fi ) ;
2019-05-04 18:52:05 +00:00
envs - > push_back ( envvar ) ;
2019-05-04 17:56:17 +00:00
}
2022-04-23 08:18:04 +00:00
else
{
string envvar = " METER_ " + var + " _ " + default_unit + " = " + valueToString ( getNumericValue ( & fi , fi . defaultUnit ( ) ) , fi . defaultUnit ( ) ) ;
2019-05-04 14:37:35 +00:00
envs - > push_back ( envvar ) ;
2019-05-04 17:56:17 +00:00
2022-04-23 08:18:04 +00:00
Unit u = replaceWithConversionUnit ( fi . defaultUnit ( ) , conversions_ ) ;
if ( u ! = fi . defaultUnit ( ) )
2019-05-04 17:56:17 +00:00
{
string unit = unitToStringUpperCase ( u ) ;
2022-04-23 08:18:04 +00:00
string envvar = " METER_ " + var + " _ " + unit + " = " + valueToString ( getNumericValue ( & fi , u ) , u ) ;
2019-05-04 17:56:17 +00:00
envs - > push_back ( envvar ) ;
}
2019-05-04 14:37:35 +00:00
}
}
}
2021-02-24 19:50:45 +00:00
2019-10-20 17:19:17 +00:00
// If the configuration has supplied json_address=Roodroad 123
// then the env variable METER_address will available and have the content "Roodroad 123"
2021-08-01 22:22:13 +00:00
for ( string add_json : meterExtraConstantFields ( ) )
2019-10-20 17:19:17 +00:00
{
envs - > push_back ( string ( " METER_ " ) + add_json ) ;
}
2021-08-01 22:22:13 +00:00
for ( string extra_field : * extra_constant_fields )
2019-10-20 17:19:17 +00:00
{
2021-08-01 22:22:13 +00:00
envs - > push_back ( string ( " METER_ " ) + extra_field ) ;
2019-10-20 17:19:17 +00:00
}
2019-05-04 14:37:35 +00:00
}
2019-05-04 15:38:10 +00:00
2020-01-27 08:29:40 +00:00
void MeterCommonImplementation : : setExpectedTPLSecurityMode ( TPLSecurityMode tsm )
{
expected_tpl_sec_mode_ = tsm ;
}
void MeterCommonImplementation : : setExpectedELLSecurityMode ( ELLSecurityMode dsm )
{
expected_ell_sec_mode_ = dsm ;
}
TPLSecurityMode MeterCommonImplementation : : expectedTPLSecurityMode ( )
2019-05-04 17:56:17 +00:00
{
2020-01-27 08:29:40 +00:00
return expected_tpl_sec_mode_ ;
2019-05-04 17:56:17 +00:00
}
2020-01-27 08:29:40 +00:00
ELLSecurityMode MeterCommonImplementation : : expectedELLSecurityMode ( )
2019-05-04 17:56:17 +00:00
{
2020-01-27 08:29:40 +00:00
return expected_ell_sec_mode_ ;
2019-05-04 17:56:17 +00:00
}
2021-03-07 18:52:21 +00:00
void detectMeterDrivers ( int manufacturer , int media , int version , vector < string > * drivers )
2019-05-04 17:56:17 +00:00
{
2021-03-13 17:35:47 +00:00
# define X(TY,MA,ME,VE) { if (manufacturer == MA && (media == ME || ME == -1) && (version == VE || VE == -1)) { drivers->push_back(toString(MeterDriver::TY)); }}
2020-09-04 09:31:49 +00:00
METER_DETECTION
# undef X
2022-01-06 17:28:22 +00:00
2022-06-16 14:01:31 +00:00
for ( DriverInfo * p : allDrivers ( ) )
2022-01-06 17:28:22 +00:00
{
2022-06-16 14:01:31 +00:00
if ( p - > detect ( manufacturer , media , version ) )
2022-01-06 17:28:22 +00:00
{
2022-06-16 14:01:31 +00:00
drivers - > push_back ( p - > name ( ) . str ( ) ) ;
2022-01-06 17:28:22 +00:00
}
}
2019-05-04 17:56:17 +00:00
}
2021-03-08 07:40:48 +00:00
bool isMeterDriverValid ( MeterDriver type , int manufacturer , int media , int version )
2019-05-04 17:56:17 +00:00
{
2021-03-08 07:40:48 +00:00
# define X(TY,MA,ME,VE) { if (type == MeterDriver::TY && manufacturer == MA && (media == ME || ME == -1) && (version == VE || VE == -1)) { return true; }}
2020-09-04 09:31:49 +00:00
METER_DETECTION
# undef X
2022-06-16 14:01:31 +00:00
for ( DriverInfo * p : allDrivers ( ) )
2022-01-06 17:28:22 +00:00
{
2022-06-16 14:01:31 +00:00
if ( p - > detect ( manufacturer , media , version ) )
2022-01-06 17:28:22 +00:00
{
return true ;
}
}
2020-09-04 09:31:49 +00:00
return false ;
2019-05-04 17:56:17 +00:00
}
2021-02-20 10:08:23 +00:00
2022-01-25 20:21:17 +00:00
bool isMeterDriverReasonableForMedia ( MeterDriver type , string driver_name , int media )
{
if ( media = = 0x37 ) return false ; // Skip converter meter side since they do not give any useful information.
2022-03-27 10:09:18 +00:00
if ( driver_name = = " " )
{
2022-02-08 19:51:13 +00:00
# define X(TY,MA,ME,VE) { if (type == MeterDriver::TY && isCloseEnough(media, ME)) { return true; }}
2022-01-25 20:21:17 +00:00
METER_DETECTION
# undef X
2022-03-27 10:09:18 +00:00
}
2022-01-25 20:21:17 +00:00
2022-06-16 14:01:31 +00:00
for ( DriverInfo * p : allDrivers ( ) )
2022-01-25 20:21:17 +00:00
{
2022-06-16 14:01:31 +00:00
if ( p - > name ( ) . str ( ) = = driver_name & & p - > isValidMedia ( media ) )
2022-01-25 20:21:17 +00:00
{
return true ;
}
}
return false ;
}
2022-01-06 17:28:22 +00:00
DriverInfo pickMeterDriver ( Telegram * t )
2021-03-07 18:52:21 +00:00
{
int manufacturer = t - > dll_mfct ;
int media = t - > dll_type ;
int version = t - > dll_version ;
if ( t - > tpl_id_found )
{
2021-03-07 19:40:41 +00:00
manufacturer = t - > tpl_mfct ;
media = t - > tpl_type ;
version = t - > tpl_version ;
2021-03-07 18:52:21 +00:00
}
2021-03-08 07:40:48 +00:00
# define X(TY,MA,ME,VE) { if (manufacturer == MA && (media == ME || ME == -1) && (version == VE || VE == -1)) { return MeterDriver::TY; }}
2021-03-07 18:52:21 +00:00
METER_DETECTION
# undef X
2022-01-06 17:28:22 +00:00
2022-06-16 14:01:31 +00:00
for ( DriverInfo * p : allDrivers ( ) )
2022-01-06 17:28:22 +00:00
{
2022-06-16 14:01:31 +00:00
if ( p - > detect ( manufacturer , media , version ) )
2022-01-06 17:28:22 +00:00
{
2022-06-16 14:01:31 +00:00
return * p ;
2022-01-06 17:28:22 +00:00
}
}
2021-03-08 07:40:48 +00:00
return MeterDriver : : UNKNOWN ;
2021-03-07 18:52:21 +00:00
}
2021-02-20 21:21:01 +00:00
shared_ptr < Meter > createMeter ( MeterInfo * mi )
2021-02-20 10:08:23 +00:00
{
shared_ptr < Meter > newm ;
const char * keymsg = ( mi - > key [ 0 ] = = 0 ) ? " not-encrypted " : " encrypted " ;
2022-06-16 14:01:31 +00:00
DriverInfo * di = lookupDriver ( mi - > driver_name . str ( ) ) ;
if ( di ! = NULL )
2022-01-06 17:28:22 +00:00
{
2022-06-16 14:01:31 +00:00
shared_ptr < Meter > newm = di - > construct ( * mi ) ;
2022-01-06 17:28:22 +00:00
newm - > addConversions ( mi - > conversions ) ;
2022-05-01 15:05:30 +00:00
newm - > setPollInterval ( mi - > poll_interval ) ;
2022-05-01 18:54:12 +00:00
verbose ( " (meter) created %s %s %s %s \n " ,
2022-01-06 17:28:22 +00:00
mi - > name . c_str ( ) ,
2022-06-16 14:01:31 +00:00
di - > name ( ) . str ( ) . c_str ( ) ,
2022-01-06 17:28:22 +00:00
mi - > idsc . c_str ( ) ,
keymsg ) ;
return newm ;
}
2021-03-08 07:40:48 +00:00
switch ( mi - > driver )
2021-02-20 10:08:23 +00:00
{
2021-03-08 07:40:48 +00:00
# define X(mname,link,info,driver,cname) \
case MeterDriver : : driver : \
2021-02-20 21:21:01 +00:00
{ \
2021-02-20 10:08:23 +00:00
newm = create # # cname ( * mi ) ; \
2021-02-20 21:21:01 +00:00
newm - > addConversions ( mi - > conversions ) ; \
2022-05-01 15:05:30 +00:00
newm - > setPollInterval ( mi - > poll_interval ) ; \
2022-05-01 18:54:12 +00:00
verbose ( " (meter) created %s " # mname " %s %s \n " , \
2021-02-20 21:21:01 +00:00
mi - > name . c_str ( ) , mi - > idsc . c_str ( ) , keymsg ) ; \
2021-02-20 10:08:23 +00:00
return newm ; \
} \
break ;
LIST_OF_METERS
# undef X
}
return newm ;
}
2021-03-13 17:35:47 +00:00
2022-04-25 17:38:36 +00:00
bool is_driver_and_extras ( string t , MeterDriver * out_driver , DriverName * out_driver_name , string * out_extras )
2021-03-13 17:35:47 +00:00
{
// piigth(jump=foo)
// multical21
2022-01-06 17:28:22 +00:00
DriverInfo di ;
2021-03-13 17:35:47 +00:00
size_t ps = t . find ( ' ( ' ) ;
size_t pe = t . find ( ' ) ' ) ;
size_t te = 0 ; // Position after type end.
bool found_parentheses = ( ps ! = string : : npos & & pe ! = string : : npos ) ;
if ( ! found_parentheses )
{
2022-01-06 17:28:22 +00:00
if ( lookupDriverInfo ( t , & di ) )
{
* out_driver_name = di . name ( ) ;
// We found a registered driver.
* out_driver = MeterDriver : : AUTO ; // To go away!
* out_extras = " " ;
return true ;
}
2021-03-13 17:35:47 +00:00
MeterDriver md = toMeterDriver ( t ) ;
if ( md = = MeterDriver : : UNKNOWN ) return false ;
* out_driver = md ;
* out_extras = " " ;
return true ;
}
// Parentheses must be last.
if ( ! ( ps > 0 & & ps < pe & & pe = = t . length ( ) - 1 ) ) return false ;
te = ps ;
string type = t . substr ( 0 , te ) ;
2022-01-06 17:28:22 +00:00
bool found = lookupDriverInfo ( type , & di ) ;
2021-03-13 17:35:47 +00:00
MeterDriver md = toMeterDriver ( type ) ;
2022-01-06 17:28:22 +00:00
if ( found )
{
* out_driver_name = di . name ( ) ;
}
else
{
if ( md = = MeterDriver : : UNKNOWN ) return false ;
}
2021-03-13 17:35:47 +00:00
* out_driver = md ;
string extras = t . substr ( ps + 1 , pe - ps - 1 ) ;
* out_extras = extras ;
return true ;
}
string MeterInfo : : str ( )
{
string r ;
r + = toString ( driver ) ;
if ( extras ! = " " )
{
r + = " ( " + extras + " ) " ;
}
r + = " : " ;
if ( bus ! = " " ) r + = bus + " : " ;
if ( bps ! = 0 ) r + = bps + " : " ;
if ( ! link_modes . empty ( ) ) r + = link_modes . hr ( ) + " : " ;
if ( r . size ( ) > 0 ) r . pop_back ( ) ;
return r ;
}
bool MeterInfo : : parse ( string n , string d , string i , string k )
{
clear ( ) ;
name = n ;
ids = splitMatchExpressions ( i ) ;
key = k ;
bool driverextras_checked = false ;
bool bus_checked = false ;
bool bps_checked = false ;
bool link_modes_checked = false ;
2022-04-11 16:53:50 +00:00
// The : colon is forbidden inside the parts.
2021-03-13 17:35:47 +00:00
vector < string > parts = splitString ( d , ' : ' ) ;
2022-04-27 17:18:30 +00:00
// Example piigth:MAIN:2400 // it is an mbus meter.
// c5isf:MAIN:2400:mbus // attached to mbus instead of t1
2021-03-13 17:35:47 +00:00
// multical21:c1
// telco:BUS2:c2
// driver ( extras ) : bus_alias : bps : linkmodes
for ( auto & p : parts )
{
2022-04-25 17:38:36 +00:00
if ( ! driverextras_checked & & is_driver_and_extras ( p , & driver , & driver_name , & extras ) )
2021-03-13 17:35:47 +00:00
{
driverextras_checked = true ;
}
else if ( ! bus_checked & & isValidAlias ( p ) & & ! isValidBps ( p ) & & ! isValidLinkModes ( p ) )
{
driverextras_checked = true ;
bus_checked = true ;
bus = p ;
}
else if ( ! bps_checked & & isValidBps ( p ) & & ! isValidLinkModes ( p ) )
{
driverextras_checked = true ;
bus_checked = true ;
bps_checked = true ;
bps = atoi ( p . c_str ( ) ) ;
}
else if ( ! link_modes_checked & & isValidLinkModes ( p ) )
{
driverextras_checked = true ;
bus_checked = true ;
bps_checked = true ;
link_modes_checked = true ;
link_modes = parseLinkModes ( p ) ;
}
else
{
// Unknown part....
return false ;
}
}
if ( ! link_modes_checked )
{
// No explicit link mode set, set to the default link modes
// that the meter can transmit on.
link_modes = toMeterLinkModeSet ( driver ) ;
}
return true ;
}
2021-03-14 08:41:35 +00:00
2022-05-01 18:54:12 +00:00
bool MeterInfo : : usesPolling ( )
2022-05-01 15:05:30 +00:00
{
return link_modes . has ( LinkMode : : MBUS ) | |
link_modes . has ( LinkMode : : C2 ) | |
link_modes . has ( LinkMode : : T2 ) | |
link_modes . has ( LinkMode : : S2 ) ;
}
2021-03-14 08:41:35 +00:00
bool isValidKey ( string & key , MeterDriver mt )
{
if ( key . length ( ) = = 0 ) return true ;
if ( key = = " NOKEY " ) {
key = " " ;
return true ;
}
if ( mt = = MeterDriver : : IZAR | |
mt = = MeterDriver : : HYDRUS )
{
// These meters can either be OMS compatible 128 bit key (32 hex).
// Or using an older proprietary encryption with 64 bit keys (16 hex)
if ( key . length ( ) ! = 16 & & key . length ( ) ! = 32 ) return false ;
}
else
{
// OMS compliant meters have 128 bit AES keys (32 hex).
// There is a deprecated DES mode, but I have not yet
// seen any telegram using that mode.
if ( key . length ( ) ! = 32 ) return false ;
}
vector < uchar > tmp ;
return hex2bin ( key , & tmp ) ;
}
2022-01-06 17:28:22 +00:00
2022-04-17 13:54:01 +00:00
void FieldInfo : : performExtraction ( Meter * m , Telegram * t , DVEntry * dve )
2022-01-06 17:28:22 +00:00
{
2022-04-16 18:23:37 +00:00
if ( xuantity_ = = Quantity : : Text )
2022-01-06 17:28:22 +00:00
{
2022-04-17 13:54:01 +00:00
extractString ( m , t , dve ) ;
2022-01-06 17:28:22 +00:00
}
2022-04-16 18:23:37 +00:00
else
2022-01-06 17:28:22 +00:00
{
2022-04-17 13:54:01 +00:00
extractNumeric ( m , t , dve ) ;
2022-01-06 17:28:22 +00:00
}
}
2022-01-08 17:52:06 +00:00
2022-04-19 07:10:14 +00:00
bool FieldInfo : : hasMatcher ( )
{
return matcher_ . active = = true ;
}
2022-04-17 13:54:01 +00:00
bool FieldInfo : : matches ( DVEntry * dve )
{
return matcher_ . matches ( * dve ) ;
}
2022-01-08 17:52:06 +00:00
DriverName MeterInfo : : driverName ( )
{
if ( driver_name . str ( ) = = " " )
{
return DriverName ( toString ( driver ) ) ;
}
return driver_name ;
}
2022-04-16 18:23:37 +00:00
2022-04-17 13:54:01 +00:00
bool FieldInfo : : extractNumeric ( Meter * m , Telegram * t , DVEntry * dve )
2022-04-16 18:23:37 +00:00
{
bool found = false ;
2022-04-19 07:10:14 +00:00
string key = matcher_ . dif_vif_key . str ( ) ;
2022-04-17 13:54:01 +00:00
2022-04-17 15:34:51 +00:00
if ( dve = = NULL )
{
if ( key = = " " )
{
// Search for key.
2022-04-19 07:10:14 +00:00
bool ok = findKeyWithNr ( matcher_ . measurement_type ,
matcher_ . vif_range ,
matcher_ . storage_nr_from . intValue ( ) ,
matcher_ . tariff_nr_from . intValue ( ) ,
matcher_ . index_nr . intValue ( ) ,
2022-04-17 15:34:51 +00:00
& key ,
& t - > dv_entries ) ;
// No entry was found.
if ( ! ok ) return false ;
}
// No entry with this key was found.
if ( t - > dv_entries . count ( key ) = = 0 ) return false ;
dve = & t - > dv_entries [ key ] . second ;
2022-04-16 18:23:37 +00:00
}
2022-04-19 07:10:14 +00:00
assert ( dve ! = NULL ) ;
assert ( key = = " " | | dve - > dif_vif_key . str ( ) = = key ) ;
2022-04-22 15:28:57 +00:00
// Generate the json field name:
string field_name = generateFieldName ( dve ) ;
2022-04-16 18:23:37 +00:00
double extracted_double_value = NAN ;
2022-04-17 15:34:51 +00:00
if ( dve - > extractDouble ( & extracted_double_value ,
vifScaling ( ) = = VifScaling : : Auto | |
vifScaling ( ) = = VifScaling : : AutoSigned ,
vifScaling ( ) = = VifScaling : : NoneSigned | |
vifScaling ( ) = = VifScaling : : AutoSigned ) )
2022-04-16 18:23:37 +00:00
{
Unit decoded_unit = defaultUnit ( ) ;
2022-04-19 07:10:14 +00:00
if ( matcher_ . vif_range ! = VIFRange : : Any & &
matcher_ . vif_range ! = VIFRange : : AnyVolumeVIF & &
matcher_ . vif_range ! = VIFRange : : AnyEnergyVIF & &
matcher_ . vif_range ! = VIFRange : : AnyPowerVIF & &
matcher_ . vif_range ! = VIFRange : : None )
2022-04-16 18:23:37 +00:00
{
2022-04-19 07:10:14 +00:00
decoded_unit = toDefaultUnit ( matcher_ . vif_range ) ;
2022-04-16 18:23:37 +00:00
}
2022-04-23 06:20:22 +00:00
m - > setNumericValue ( this , decoded_unit , extracted_double_value ) ;
2022-04-23 08:18:04 +00:00
t - > addMoreExplanation ( dve - > offset , renderJson ( m , & m - > conversions ( ) ) ) ;
2022-04-16 18:23:37 +00:00
found = true ;
}
return found ;
}
2022-04-17 13:54:01 +00:00
bool FieldInfo : : extractString ( Meter * m , Telegram * t , DVEntry * dve )
2022-04-16 18:23:37 +00:00
{
bool found = false ;
2022-04-19 07:10:14 +00:00
string key = matcher_ . dif_vif_key . str ( ) ;
2022-04-17 15:34:51 +00:00
if ( dve = = NULL )
{
if ( key = = " " )
{
// Search for key.
2022-04-19 07:10:14 +00:00
bool ok = findKeyWithNr ( matcher_ . measurement_type ,
matcher_ . vif_range ,
matcher_ . storage_nr_from . intValue ( ) ,
matcher_ . tariff_nr_from . intValue ( ) ,
matcher_ . index_nr . intValue ( ) ,
2022-04-17 15:34:51 +00:00
& key ,
& t - > dv_entries ) ;
// No entry was found.
if ( ! ok ) return false ;
}
// No entry with this key was found.
if ( t - > dv_entries . count ( key ) = = 0 ) return false ;
dve = & t - > dv_entries [ key ] . second ;
2022-04-16 18:23:37 +00:00
}
2022-04-17 15:34:51 +00:00
assert ( dve ! = NULL ) ;
2022-04-19 07:10:14 +00:00
assert ( key = = " " | | dve - > dif_vif_key . str ( ) = = key ) ;
2022-04-17 15:34:51 +00:00
2022-04-22 15:28:57 +00:00
// Generate the json field name:
string field_name = generateFieldName ( dve ) ;
2022-04-16 18:23:37 +00:00
uint64_t extracted_bits { } ;
2022-05-21 12:22:56 +00:00
if ( lookup_ . hasLookups ( ) | | ( print_properties_ . hasJOINTPLSTATUS ( ) ) )
2022-04-16 18:23:37 +00:00
{
2022-05-21 12:22:56 +00:00
string translated_bits = " " ;
// The field has lookups, or the print property JOIN_TPL_STATUS is set,
// this means that we should create a string.
if ( lookup_ . hasLookups ( ) & & dve - > extractLong ( & extracted_bits ) )
{
translated_bits = lookup ( ) . translate ( extracted_bits ) ;
found = true ;
}
if ( print_properties_ . hasJOINTPLSTATUS ( ) )
{
string status = m - > decodeTPLStatusByte ( t - > tpl_sts ) ;
t - > addMoreExplanation ( t - > tpl_sts_offset , " (%s) " , status . c_str ( ) ) ;
if ( status ! = " OK " )
{
if ( translated_bits ! = " OK " )
{
// Join the statuses.
translated_bits + = " " + status ;
}
else
{
// Overwrite the translated bits.
translated_bits = status ;
}
}
else
{
// No change to the translated_bits.
}
}
if ( found )
2022-04-16 18:23:37 +00:00
{
2022-04-23 06:20:22 +00:00
m - > setStringValue ( this , translated_bits ) ;
2022-04-23 08:18:04 +00:00
t - > addMoreExplanation ( dve - > offset , renderJsonText ( m ) ) ;
2022-04-16 18:23:37 +00:00
}
}
2022-04-19 07:10:14 +00:00
else if ( matcher_ . vif_range = = VIFRange : : DateTime )
2022-04-16 18:23:37 +00:00
{
struct tm datetime ;
2022-04-17 15:34:51 +00:00
dve - > extractDate ( & datetime ) ;
2022-04-16 18:23:37 +00:00
string extracted_device_date_time = strdatetime ( & datetime ) ;
2022-04-23 06:20:22 +00:00
m - > setStringValue ( this , extracted_device_date_time ) ;
2022-04-23 08:18:04 +00:00
t - > addMoreExplanation ( dve - > offset , renderJsonText ( m ) ) ;
2022-04-16 18:23:37 +00:00
found = true ;
}
2022-04-19 07:10:14 +00:00
else if ( matcher_ . vif_range = = VIFRange : : Date )
2022-04-16 18:23:37 +00:00
{
struct tm date ;
2022-04-17 15:34:51 +00:00
dve - > extractDate ( & date ) ;
2022-04-16 18:23:37 +00:00
string extracted_device_date = strdate ( & date ) ;
2022-04-23 06:20:22 +00:00
m - > setStringValue ( this , extracted_device_date ) ;
2022-04-23 08:18:04 +00:00
t - > addMoreExplanation ( dve - > offset , renderJsonText ( m ) ) ;
2022-04-16 18:23:37 +00:00
found = true ;
}
2022-04-19 07:10:14 +00:00
else if ( matcher_ . vif_range = = VIFRange : : EnhancedIdentification | |
2022-05-07 17:16:34 +00:00
matcher_ . vif_range = = VIFRange : : FabricationNo | |
matcher_ . vif_range = = VIFRange : : ModelVersion | |
2022-05-21 12:22:56 +00:00
matcher_ . vif_range = = VIFRange : : SoftwareVersion | |
2022-05-07 17:16:34 +00:00
matcher_ . vif_range = = VIFRange : : ParameterSet )
2022-04-16 18:23:37 +00:00
{
string extracted_id ;
2022-04-17 15:34:51 +00:00
dve - > extractReadableString ( & extracted_id ) ;
2022-04-23 06:20:22 +00:00
m - > setStringValue ( this , extracted_id ) ;
2022-04-23 08:18:04 +00:00
t - > addMoreExplanation ( dve - > offset , renderJsonText ( m ) ) ;
2022-04-16 18:23:37 +00:00
found = true ;
}
else
{
error ( " Internal error: Cannot extract text string from vif %s in %s:%d \n " ,
2022-04-19 07:10:14 +00:00
toString ( matcher_ . vif_range ) ,
2022-04-16 18:23:37 +00:00
__FILE__ , __LINE__ ) ;
}
return found ;
}
2022-04-26 09:44:00 +00:00
bool Address : : parse ( string & s )
{
// Example: 12345678
// or 12345678.M=PII.T=1B.V=01
// or 1234*
// or 1234*.PII
// or 1234*.V01
// or 12 // mbus primary
// or 0 // mbus primary
// or 250.MPII.T1B.V01 // mbus primary
id = " " ;
mbus_primary = false ;
mfct = 0 ;
type = 0 ;
version = 0 ;
if ( s . size ( ) = = 0 ) return false ;
vector < string > parts = splitString ( s , ' . ' ) ;
assert ( parts . size ( ) > 0 ) ;
if ( ! isValidMatchExpression ( parts [ 0 ] , true ) )
{
// Not a long id, so lets check if it is 0-250.
2022-04-27 17:18:30 +00:00
for ( size_t i = 0 ; i < parts [ 0 ] . length ( ) ; + + i )
2022-04-26 09:44:00 +00:00
{
if ( ! isdigit ( parts [ 0 ] [ i ] ) ) return false ;
}
// All digits good.
int v = atoi ( parts [ 0 ] . c_str ( ) ) ;
if ( v < 0 | | v > 250 ) return false ;
// It is 0-250 which means it is an mbus primary address.
mbus_primary = true ;
}
id = parts [ 0 ] ;
for ( size_t i = 1 ; i < parts [ i ] . size ( ) ; + + i )
{
if ( parts [ i ] . size ( ) = = 4 ) // V=xy or T=xy
{
if ( parts [ i ] [ 1 ] ! = ' = ' ) return false ;
vector < uchar > data ;
bool ok = hex2bin ( & parts [ i ] [ 2 ] , & data ) ;
if ( ! ok ) return false ;
if ( data . size ( ) ! = 1 ) return false ;
if ( parts [ i ] [ 0 ] = = ' V ' )
{
version = data [ 0 ] ;
}
else if ( parts [ i ] [ 0 ] = = ' T ' )
{
type = data [ 0 ] ;
}
else
{
return false ;
}
}
else if ( parts [ i ] . size ( ) = = 5 ) // M=xyz
{
if ( parts [ i ] [ 1 ] ! = ' = ' ) return false ;
if ( parts [ i ] [ 0 ] ! = ' M ' ) return false ;
bool ok = flagToManufacturer ( & parts [ i ] [ 2 ] , & mfct ) ;
if ( ! ok ) return false ;
}
else
{
return false ;
}
}
return true ;
}
2022-06-18 21:03:18 +00:00
void MeterCommonImplementation : : addOptionalCommonFields ( )
{
addStringFieldWithExtractor (
" fabrication_no " ,
" Fabrication number. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : FabricationNo )
) ;
2022-06-19 09:31:28 +00:00
addStringFieldWithExtractor (
" software_version " ,
" Software version. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : SoftwareVersion )
) ;
2022-06-18 21:03:18 +00:00
addNumericFieldWithExtractor (
" operating_time " ,
" How long the meter has been collecting data. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
Quantity : : Time ,
VifScaling : : Auto ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : OperatingTime )
) ;
addNumericFieldWithExtractor (
" on_time " ,
" How long the meter has been powered up. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
Quantity : : Time ,
VifScaling : : Auto ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : OnTime )
) ;
addStringFieldWithExtractor (
" meter_datetime " ,
" Date and time when the meter sent the telegram. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : DateTime )
) ;
addStringFieldWithExtractor (
" meter_error_datetime " ,
" Date and time when the meter was in error. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
FieldMatcher : : build ( )
. set ( MeasurementType : : AtError )
. set ( VIFRange : : DateTime )
) ;
}
void MeterCommonImplementation : : addOptionalFlowRelatedFields ( )
{
addNumericFieldWithExtractor (
" total " ,
" The total heating/cooling media volume consumption recorded by this meter. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
Quantity : : Volume ,
VifScaling : : Auto ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : Volume )
) ;
addNumericFieldWithExtractor (
" flow_temperature " ,
" Forward heat/cooling media temperature. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
Quantity : : Temperature ,
VifScaling : : Auto ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : FlowTemperature )
) ;
addNumericFieldWithExtractor (
" return_temperature " ,
" Return heat/cooling media temperature. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
Quantity : : Temperature ,
VifScaling : : Auto ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : ReturnTemperature )
) ;
addNumericFieldWithExtractor (
" flow_return_temperature_difference " ,
" The difference between flow and return media temperatures. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
Quantity : : Temperature ,
VifScaling : : AutoSigned ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : TemperatureDifference )
) ;
addNumericFieldWithExtractor (
" volume_flow " ,
" Media volume flow. " ,
PrintProperty : : JSON | PrintProperty : : OPTIONAL ,
Quantity : : Flow ,
VifScaling : : Auto ,
FieldMatcher : : build ( )
. set ( MeasurementType : : Instantaneous )
. set ( VIFRange : : VolumeFlow )
) ;
}