Added support for additional static json info.

pull/42/head
weetmuts 2019-10-20 19:19:17 +02:00
rodzic df790c8335
commit 30543f6217
18 zmienionych plików z 235 dodań i 20 usunięć

Wyświetl plik

@ -60,6 +60,15 @@ all meters with a given prefix. If you supply at least one positive match rule,
can add negative match rules as well. For example `id=*,!2222*`
which will match all meter ids, except those that begin with 2222.
You can add the static json data "address":"RoadenRd 456","city":"Stockholm" to every json message with the
wmbusmeters.conf setting:
```
json_address=RoadenRd 456
json_city=Stockholm
```
If you add `json_floor=5` to the meter file MyTapWater, then you can have the meter tailored
static json "floor":"5" added to telegrams handled by that particular meter.
If you are running on a Raspberry PI with flash storage and you relay the data to
another computer using a shell command (mosquitto_pub or curl or similar) then you might want to remove
`meterfiles` and `meterfilesaction` to minimize the writes to the local flash file system.
@ -97,6 +106,7 @@ As <options> you can use:
--debug for a lot of information
--exitafter=<time> exit program after time, eg 20h, 10m 5s
--format=<hr/json/fields> for human readable, json or semicolon separated fields
--json_xxx=yyy always add "xxx"="yyy" to the json output and add shell env METER_xxx=yyy
--listento=<mode> tell the wmbus dongle to listen to this single link mode where mode can be
c1,t1,s1,s1m,n1a,n1b,n1c,n1d,n1e,n1f
--listento=c1,t1,s1 tell the wmbus dongle to listen to these link modes
@ -228,6 +238,7 @@ METER_TOTAL_M3
METER_MAX_FLOW_M3H
METER_TIMESTAMP
```
(If you have supplied --json_floor=5 then you will also see METER_floor in the list)
Note that the METER_TIMESTAMP and the timestamp in the json output, is in UTC format, this is not your localtime.
However the hr and fields output will print your localtime.

Wyświetl plik

@ -0,0 +1,2 @@
telegram=|2A442D2C998734761B168D208971F81821|542F7802FF2071000413F81800004413F4180000615B05616717|
{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.392,"target_m3":6.388,"max_flow_m3h":0,"flow_temperature_c":5,"external_temperature_c":23,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z","floor":"5","address":"RoodRd 42"}

Wyświetl plik

@ -258,6 +258,19 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
i++;
continue;
}
if (!strncmp(argv[i], "--json_", 7))
{
// For example: --json_floor=42
string json = string(argv[i]+7);
if (json == "") {
error("The json command cannot be empty.\n");
}
// The extra "floor"="42" will be pushed to the json.
debug("Added json %s\n", json.c_str());
c->jsons.push_back(json);
i++;
continue;
}
if (!strncmp(argv[i], "--shellenvs", 11)) {
c->list_shell_envs = true;
i++;
@ -348,8 +361,8 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
if (mt == MeterType::UNKNOWN) error("Not a valid meter type \"%s\"\n", type.c_str());
if (!isValidMatchExpressions(id, false)) error("Not a valid id nor a valid meter match expression \"%s\"\n", id.c_str());
if (!isValidKey(key)) error("Not a valid meter key \"%s\"\n", key.c_str());
vector<string> no_meter_shells;
c->meters.push_back(MeterInfo(name, type, id, key, modes, no_meter_shells));
vector<string> no_meter_shells, no_meter_jsons;
c->meters.push_back(MeterInfo(name, type, id, key, modes, no_meter_shells, no_meter_jsons));
}
return unique_ptr<Configuration>(c);

Wyświetl plik

@ -50,10 +50,11 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
string key;
string linkmodes;
vector<string> shells;
vector<string> jsons;
debug("(config) loading meter file %s\n", file.c_str());
for (;;) {
auto p = getNextKeyValue(buf, i);
pair<string,string> p = getNextKeyValue(buf, i);
if (p.first == "") break;
@ -63,7 +64,8 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
else
if (p.first == "id") id = p.second;
else
if (p.first == "key") {
if (p.first == "key")
{
key = p.second;
debug("(config) key=<notprinted>\n");
}
@ -71,6 +73,12 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
if (p.first == "shell") {
shells.push_back(p.second);
}
else
if (startsWith(p.first, "json_"))
{
string keyvalue = p.first.substr(5)+"="+p.second;
jsons.push_back(keyvalue);
}
else
warning("Found invalid key \"%s\" in meter config file\n", p.first.c_str());
@ -125,7 +133,7 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
use = false;
}
if (use) {
c->meters.push_back(MeterInfo(name, type, id, key, modes, shells));
c->meters.push_back(MeterInfo(name, type, id, key, modes, shells, jsons));
}
return;
@ -307,6 +315,11 @@ void handleShell(Configuration *c, string cmdline)
c->shells.push_back(cmdline);
}
void handleJson(Configuration *c, string json)
{
c->jsons.push_back(json);
}
unique_ptr<Configuration> loadConfiguration(string root)
{
Configuration *c = new Configuration;
@ -339,6 +352,12 @@ unique_ptr<Configuration> loadConfiguration(string root)
else if (p.first == "separator") handleSeparator(c, p.second);
else if (p.first == "addconversions") handleConversions(c, p.second);
else if (p.first == "shell") handleShell(c, p.second);
else if (startsWith(p.first, "json_"))
{
string s = p.first.substr(5);
string keyvalue = s+"="+p.second;
handleJson(c, keyvalue);
}
else
{
warning("No such key: %s\n", p.first.c_str());

Wyświetl plik

@ -36,7 +36,8 @@ enum class MeterFileNaming
Name, Id, NameId
};
struct Configuration {
struct Configuration
{
bool daemon {};
std::string pid_file;
bool useconfig {};
@ -71,7 +72,8 @@ struct Configuration {
bool link_mode_configured {};
bool no_init {};
std::vector<Unit> conversions;
vector<MeterInfo> meters;
std::vector<MeterInfo> meters;
std::vector<std::string> jsons; // Additional jsons to always add.
~Configuration() = default;
};

Wyświetl plik

@ -88,6 +88,7 @@ As <options> you can use:
--debug for a lot of information
--exitafter=<time> exit program after time, eg 20h, 10m 5s
--format=<hr/json/fields> for human readable, json or semicolon separated fields
--json_xxx=yyy always add "xxx"="yyy" to the json output and add shell env METER_xxx=yyy
--listento=<mode> tell the wmbus dongle to listen to this single link mode where mode can be
c1,t1,s1,s1m,n1a,n1b,n1c,n1d,n1e,n1f
--listento=c1,t1,s1 tell the wmbus dongle to listen to these link modes
@ -311,7 +312,8 @@ LIST_OF_METERS
&ignore1,
&ignore2, config->separator,
&ignore3,
&envs);
&envs,
&config->jsons);
printf("Environment variables provided to shell for meter %s:\n", m.type.c_str());
for (auto &e : envs) {
int p = e.find('=');
@ -320,7 +322,7 @@ LIST_OF_METERS
}
exit(0);
}
meters.back()->onUpdate([&](Telegram*t,Meter* meter) { output->print(t,meter); });
meters.back()->onUpdate([&](Telegram*t,Meter* meter) { output->print(t,meter,&config->jsons); });
meters.back()->onUpdate([&](Telegram*t, Meter* meter) { oneshotCheck(config, manager.get(), t, meter, meters); });
}
} else {

Wyświetl plik

@ -41,6 +41,9 @@ MeterCommonImplementation::MeterCommonImplementation(WMBus *bus, MeterInfo &mi,
for (auto s : mi.shells) {
addShell(s);
}
for (auto j : mi.jsons) {
addJson(j);
}
}
void MeterCommonImplementation::addConversions(std::vector<Unit> cs)
@ -56,11 +59,21 @@ void MeterCommonImplementation::addShell(string cmdline)
shell_cmdlines_.push_back(cmdline);
}
void MeterCommonImplementation::addJson(string json)
{
jsons_.push_back(json);
}
vector<string> &MeterCommonImplementation::shellCmdlines()
{
return shell_cmdlines_;
}
vector<string> &MeterCommonImplementation::additionalJsons()
{
return jsons_;
}
MeterType MeterCommonImplementation::type()
{
return type_;
@ -323,7 +336,8 @@ void MeterCommonImplementation::printMeter(Telegram *t,
string *human_readable,
string *fields, char separator,
string *json,
vector<string> *envs)
vector<string> *envs,
vector<string> *more_json)
{
*human_readable = concatFields(this, t, '\t', prints_, conversions_, true);
*fields = concatFields(this, t, separator, prints_, conversions_, false);
@ -356,6 +370,16 @@ void MeterCommonImplementation::printMeter(Telegram *t,
}
}
s += "\"timestamp\":\""+datetimeOfUpdateRobot()+"\"";
for (string add_json : additionalJsons())
{
s += ",";
s += makeQuotedJson(add_json);
}
for (string add_json : *more_json)
{
s += ",";
s += makeQuotedJson(add_json);
}
s += "}";
*json = s;
@ -389,6 +413,16 @@ void MeterCommonImplementation::printMeter(Telegram *t,
}
}
envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot());
// If the configuration has supplied json_address=Roodroad 123
// then the env variable METER_address will available and have the content "Roodroad 123"
for (string add_json : additionalJsons())
{
envs->push_back(string("METER_")+add_json);
}
for (string add_json : *more_json)
{
envs->push_back(string("METER_")+add_json);
}
}
double WaterMeter::totalWaterConsumption(Unit u) { return -47.11; }

Wyświetl plik

@ -61,14 +61,16 @@ struct MeterInfo
string key;
LinkModeSet link_modes;
vector<string> shells;
vector<string> jsons; // Additional static jsons that are added to each message.
MeterInfo(string n, string t, string i, string k, LinkModeSet lms, vector<string> &s)
MeterInfo(string n, string t, string i, string k, LinkModeSet lms, vector<string> &s, vector<string> &j)
{
name = n;
type = t;
id = i;
key = k;
shells = s;
jsons = j;
link_modes = lms;
}
};
@ -92,7 +94,8 @@ struct Meter
string *human_readable,
string *fields, char separator,
string *json,
vector<string> *envs) = 0;
vector<string> *envs,
vector<string> *more_json) = 0;
void handleTelegram(Telegram *t);
virtual bool isTelegramForMe(Telegram *t) = 0;

Wyświetl plik

@ -73,7 +73,9 @@ protected:
int expectedVersion();
void addConversions(std::vector<Unit> cs);
void addShell(std::string cmdline);
void addJson(std::string json);
std::vector<std::string> &shellCmdlines();
std::vector<std::string> &additionalJsons();
void addMedia(int media);
void addLinkMode(LinkMode lm);
void addManufacturer(int m);
@ -88,7 +90,8 @@ protected:
string *human_readable,
string *fields, char separator,
string *json,
vector<string> *envs);
vector<string> *envs,
vector<string> *more_json); // Add this json "key"="value" strings.
virtual void processContent(Telegram *t) = 0;
@ -109,6 +112,7 @@ private:
LinkModeSet link_modes_ {};
EncryptionMode enc_mode_ {};
vector<string> shell_cmdlines_;
vector<string> jsons_;
protected:
std::map<std::string,std::pair<int,std::string>> values_;

Wyświetl plik

@ -38,13 +38,13 @@ Printer::Printer(bool json, bool fields, char separator,
naming_ = naming;
}
void Printer::print(Telegram *t, Meter *meter)
void Printer::print(Telegram *t, Meter *meter, vector<string> *more_json)
{
string human_readable, fields, json;
vector<string> envs;
bool printed = false;
meter->printMeter(t, &human_readable, &fields, separator_, &json, &envs);
meter->printMeter(t, &human_readable, &fields, separator_, &json, &envs, more_json);
if (shell_cmdlines_.size() > 0 || meter->shellCmdlines().size() > 0) {
printShells(meter, envs);

Wyświetl plik

@ -31,7 +31,7 @@ struct Printer {
bool overwrite,
MeterFileNaming naming);
void print(Telegram *t, Meter *meter);
void print(Telegram *t, Meter *meter, vector<string> *more_json);
private:

Wyświetl plik

@ -203,7 +203,7 @@ int test_linkmodes()
auto serial2 = manager->createSerialDeviceSimulator();
auto serial3 = manager->createSerialDeviceSimulator();
auto serial4 = manager->createSerialDeviceSimulator();
vector<string> no_meter_shells;
vector<string> no_meter_shells, no_meter_jsons;
unique_ptr<WMBus> wmbus_im871a = openIM871A("", manager.get(), serial1.release());
unique_ptr<WMBus> wmbus_amb8465 = openAMB8465("", manager.get(), serial2.release());
@ -233,7 +233,8 @@ int test_linkmodes()
string apator162 = "apator162";
apator_config.meters.push_back(MeterInfo("m1", apator162, "12345678", "",
toMeterLinkModeSet(apator162),
no_meter_shells));
no_meter_shells,
no_meter_jsons));
// Check that if no explicit link modes are provided to apator162, then
// automatic deduction will fail, since apator162 can be configured to transmit
@ -271,10 +272,12 @@ int test_linkmodes()
string supercom587 = "supercom587";
multical21_and_supercom587_config.meters.push_back(MeterInfo("m1", multical21, "12345678", "",
toMeterLinkModeSet(multical21),
no_meter_shells));
no_meter_shells,
no_meter_jsons));
multical21_and_supercom587_config.meters.push_back(MeterInfo("m2", supercom587, "12345678", "",
toMeterLinkModeSet(supercom587),
no_meter_shells));
no_meter_shells,
no_meter_jsons));
// Check that meters that transmit on two different link modes cannot be listened to
// at the same time using im871a.

Wyświetl plik

@ -967,3 +967,29 @@ int countSetBits(int v)
}
return n;
}
bool startsWith(string &s, const char *prefix)
{
size_t len = strlen(prefix);
if (s.length() < len) return false;
if (s.length() == len) return s == prefix;
return !strncmp(&s[0], prefix, len);
}
string makeQuotedJson(string &s)
{
size_t p = s.find('=');
string key, value;
if (p != string::npos)
{
key = s.substr(0,p);
value = s.substr(p+1);
}
else
{
key = s;
value = "";
}
return string("\"")+key+"\":\""+value+"\"";
}

Wyświetl plik

@ -119,4 +119,9 @@ AccessCheck checkIfExistsAndSameGroup(std::string device);
// Count the number of 1:s in the binary number v.
int countSetBits(int v);
bool startsWith(std::string &s, const char *prefix);
// Given alfa=beta it returns "alfa":"beta"
std::string makeQuotedJson(std::string &s);
#endif

Wyświetl plik

@ -23,4 +23,5 @@ tests/test_conversions.sh $PROG
tests/test_wrongkeys.sh $PROG
tests/test_config4.sh $PROG
tests/test_linkmodes.sh $PROG
tests/test_additional_json.sh $PROG
tests/test_rtlwmbus.sh $PROG

Wyświetl plik

@ -0,0 +1,6 @@
loglevel=debug
device=simulations/simulation_shell.txt
logtelegrams=false
format=json
shell=echo METER =="$METER_JSON"== =="$METER_address"== =="$METER_floor"== > /tmp/wmbusmeters_meter_additional_json_test
json_address=RoodRd 42

Wyświetl plik

@ -0,0 +1,5 @@
name=Water
type=supercom587
id=12345678
key=
json_floor=5

Wyświetl plik

@ -0,0 +1,79 @@
#!/bin/bash
PROG="$1"
TEST=testoutput
mkdir -p $TEST
cat simulations/simulation_additional_json.txt | grep '^{' > $TEST/test_expected.txt
$PROG --format=json --json_floor=5 --json_address="RoodRd 42" simulations/simulation_additional_json.txt \
MyTapWater multical21 76348799 "" \
> $TEST/test_output.txt
if [ "$?" == "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" == "0" ]
then
echo Additional json from cmdline OK
else
echo Failure!
exit 1
fi
else
echo Failure.
exit 1
fi
$PROG --format=json --json_floor=5 --json_house="alfa beta" --shellenvs --listento=c1 simulations/simulation_additional_json.txt \
Vatten multical21 76348799 "" | grep METER_ > $TEST/test_output.txt
ENVS=$(cat $TEST/test_output.txt | tr '\n' ' ')
cat > $TEST/test_expected.txt <<EOF
METER_JSON
METER_TYPE
METER_ID
METER_TOTAL_M3
METER_TARGET_M3
METER_MAX_FLOW_M3H
METER_FLOW_TEMPERATURE_C
METER_EXTERNAL_TEMPERATURE_C
METER_CURRENT_STATUS
METER_TIME_DRY
METER_TIME_REVERSED
METER_TIME_LEAKING
METER_TIME_BURSTING
METER_TIMESTAMP
METER_floor
METER_house
EOF
diff $TEST/test_expected.txt $TEST/test_output.txt
if [ "$?" == "0" ]
then
echo Additional json in shell envs OK
else
echo Failure!
exit 1
fi
$PROG --useconfig=tests/config6 > $TEST/test_output.txt
if [ "$?" == "0" ]
then
INFO=$(cat /tmp/wmbusmeters_meter_additional_json_test | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/')
EXPECTED=$(echo 'METER =={"media":"warm water","meter":"supercom587","name":"Water","id":"12345678","total_m3":5.548,"timestamp":"1111-11-11T11:11:11Z","floor":"5","address":"RoodRd 42"}== ==RoodRd 42== ==5==')
if [ "$INFO" = "$EXPECTED" ]
then
echo Additional json from config OK
else
echo $INFO
echo $EXPECTED
exit 1
fi
else
echo Failure.
exit 1
fi