Parse and decode a hex string supplied as argument on command line.

pull/343/head
Fredrik Öhrström 2021-09-12 10:40:40 +02:00
rodzic 2baff2207b
commit 89cf1bad80
13 zmienionych plików z 215 dodań i 34 usunięć

Wyświetl plik

@ -1,4 +1,9 @@
You can now decode a telegram just by supplying hex on the command line:
wmbusmeters --format=json <hex_string> MyMeter auto 12345678 NOKEY
or just
wmbusmeters <hex_string>
Added --listunits to list all quantities and the units for each quantity
that wmbusmeteres knows about.

Wyświetl plik

@ -383,7 +383,7 @@ void BusManager::detectAndConfigureWmbusDevices(Configuration *config, Detection
continue;
}
}
if (specified_device.file == "" && specified_device.command == "")
if (specified_device.file == "" && specified_device.command == "" && specified_device.hex == "")
{
// File/tty/command not specified, use auto scan later to find actual device file/tty.
must_auto_find_ttys |= usesTTY(specified_device.type);
@ -404,6 +404,12 @@ void BusManager::detectAndConfigureWmbusDevices(Configuration *config, Detection
specified_device.handled = true;
openBusDeviceAndPotentiallySetLinkmodes(config, "config", &detected);
}
if (specified_device.hex != "")
{
Detected detected = detectWMBusDeviceWithFileOrHex(specified_device, config->default_device_linkmodes, serial_manager_);
openBusDeviceAndPotentiallySetLinkmodes(config, "config", &detected);
specified_device.handled = true;
}
if (specified_device.file != "")
{
shared_ptr<SerialDevice> sd = serial_manager_->lookup(specified_device.file);
@ -450,7 +456,7 @@ void BusManager::detectAndConfigureWmbusDevices(Configuration *config, Detection
continue;
}
Detected detected = detectWMBusDeviceWithFile(specified_device, config->default_device_linkmodes, serial_manager_);
Detected detected = detectWMBusDeviceWithFileOrHex(specified_device, config->default_device_linkmodes, serial_manager_);
if (detected.found_type == DEVICE_UNKNOWN)
{

Wyświetl plik

@ -539,19 +539,19 @@ shared_ptr<Configuration> parseCommandLine(int argc, char **argv) {
error("Unknown option \"%s\"\n", argv[i]);
}
bool found_at_least_one_device = false;
bool found_at_least_one_device_or_hex = false;
while (argv[i])
{
bool ok = handleDevice(c, argv[i]);
bool ok = handleDeviceOrHex(c, argv[i]);
if (ok)
{
found_at_least_one_device = true;
found_at_least_one_device_or_hex = true;
}
else
{
if (!found_at_least_one_device)
if (!found_at_least_one_device_or_hex)
{
error("At least one valid device must be supplied!\n");
error("At least one valid device (or hex) must be supplied!\n");
}
// There are more arguments...
break;

Wyświetl plik

@ -241,13 +241,23 @@ void handleResetAfter(Configuration *c, string s)
}
}
bool handleDevice(Configuration *c, string devicefile)
bool handleDeviceOrHex(Configuration *c, string devicefilehex)
{
SpecifiedDevice specified_device;
bool ok = specified_device.parse(devicefile);
if (!ok && SpecifiedDevice::isLikelyDevice(devicefile))
bool invalid_hex = false;
bool is_hex = isHexString(devicefilehex, &invalid_hex);
if (is_hex)
{
error("Not a valid device \"%s\"\n", devicefile.c_str());
if (invalid_hex)
{
error("Hex string must have an even lenght of hexadecimal characters.\n");
}
}
SpecifiedDevice specified_device;
bool ok = specified_device.parse(devicefilehex);
if (!ok && SpecifiedDevice::isLikelyDevice(devicefilehex))
{
error("Not a valid device \"%s\"\n", devicefilehex.c_str());
}
if (!ok) return false;
@ -259,7 +269,7 @@ bool handleDevice(Configuration *c, string devicefile)
{
if (!specified_device.linkmodes.empty())
{
error("An mbus device must not have linkmode set. \"%s\"\n", devicefile.c_str());
error("An mbus device must not have linkmode set. \"%s\"\n", devicefilehex.c_str());
}
}
@ -594,7 +604,7 @@ shared_ptr<Configuration> loadConfiguration(string root, string device_override,
if (p.first == "loglevel") handleLoglevel(c, p.second);
else if (p.first == "internaltesting") handleInternalTesting(c, p.second);
else if (p.first == "ignoreduplicates") handleIgnoreDuplicateTelegrams(c, p.second);
else if (p.first == "device") handleDevice(c, p.second);
else if (p.first == "device") handleDeviceOrHex(c, p.second);
else if (p.first == "donotprobe") handleDoNotProbe(c, p.second);
else if (p.first == "listento") handleListenTo(c, p.second);
else if (p.first == "logtelegrams") handleLogtelegrams(c, p.second);
@ -653,7 +663,7 @@ shared_ptr<Configuration> loadConfiguration(string root, string device_override,
device_override = "rtlwmbus";
}
debug("(config) overriding device with \"%s\"\n", device_override.c_str());
handleDevice(c, device_override);
handleDeviceOrHex(c, device_override);
}
if (listento_override != "")
{

Wyświetl plik

@ -125,7 +125,7 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file);
void handleConversions(Configuration *c, string s);
void handleSelectedFields(Configuration *c, string s);
void handleAddedFields(Configuration *c, string s);
bool handleDevice(Configuration *c, string devicefile);
bool handleDeviceOrHex(Configuration *c, string devicefilehex);
enum class LinkModeCalculationResultType
{

Wyświetl plik

@ -42,6 +42,7 @@ void test_meters();
void test_months();
void test_aes();
void test_sbc();
void test_hex();
int main(int argc, char **argv)
{
@ -71,6 +72,7 @@ int main(int argc, char **argv)
test_months();
test_aes();
test_sbc();
test_hex();
return 0;
}
@ -982,3 +984,23 @@ void test_aes()
printf("ERROR! aes encrypt decrypt (no iv) failed!\n");
}
}
void test_is_hex(const char *hex, bool expected_ok, bool expected_invalid)
{
bool got_invalid;
bool got_ok = isHexString(hex, &got_invalid);
if (got_ok != expected_ok || got_invalid != expected_invalid)
{
printf("ERROR! hex string %s was expected to be %d (invalid %d) but got %d (invalid %d)\n",
hex,
expected_ok, expected_invalid, got_ok, got_invalid);
}
}
void test_hex()
{
test_is_hex("00112233445566778899aabbccddeeff", true, false);
test_is_hex("00112233445566778899AABBCCDDEEFF", true, false);
test_is_hex("00112233445566778899AABBCCDDEEF", true, true);
test_is_hex("00112233445566778899AABBCCDDEEFG", false, false);
}

Wyświetl plik

@ -158,6 +158,30 @@ uchar reverse(uchar c)
return ((c&15)<<4) | (c>>4);
}
bool isHexString(const string &txt, bool *invalid)
{
return isHexString(txt.c_str(), invalid);
}
bool isHexString(const char* txt, bool *invalid)
{
*invalid = false;
// An empty string is not an hex string.
if (*txt == 0) return false;
const char *i = txt;
int n = 0;
for (;;)
{
char c = *i++;
if (c == 0) break;
n++;
if (char2int(c) == -1) return false;
}
if (n%2 == 1) *invalid = true;
return true;
}
bool hex2bin(const char* src, vector<uchar> *target)
{
if (!src) return false;

Wyświetl plik

@ -39,6 +39,8 @@ typedef unsigned char uchar;
uchar bcd2bin(uchar c);
uchar revbcd2bin(uchar c);
uchar reverse(uchar c);
bool isHexString(const char* txt, bool *invalid);
bool isHexString(const std::string &txt, bool *invalid);
bool hex2bin(const char* src, std::vector<uchar> *target);
bool hex2bin(std::string &src, std::vector<uchar> *target);
bool hex2bin(std::vector<uchar> &src, std::vector<uchar> *target);

Wyświetl plik

@ -4392,9 +4392,9 @@ bool is_command(string b, string *cmd)
return true;
}
bool check_file(string f, bool *is_tty, bool *is_stdin, bool *is_file, bool *is_simulation)
bool check_file(string f, bool *is_tty, bool *is_stdin, bool *is_file, bool *is_simulation, bool *is_hex_simulation)
{
*is_tty = *is_stdin = *is_file = *is_simulation = false;
*is_tty = *is_stdin = *is_file = *is_simulation = *is_hex_simulation = false;
if (f == "stdin")
{
*is_stdin = true;
@ -4407,6 +4407,16 @@ bool check_file(string f, bool *is_tty, bool *is_stdin, bool *is_file, bool *is_
// a file will confuse the user to no end....
return false;
}
// A hex string becomes a simulation file with a single line containing a telegram defined by the hex string.
bool invalid_hex = false;
if (isHexString(f.c_str(), &invalid_hex))
{
*is_simulation = true;
*is_hex_simulation = true;
assert(!invalid_hex);
return true;
}
// A command line arguments simulation_xxyyzz.txt is detected as a simulation file.
if (checkIfSimulationFile(f.c_str()))
{
*is_simulation = true;
@ -4526,6 +4536,7 @@ string SpecifiedDevice::str()
r += bus_alias+"=";
}
if (file != "") r += file+":";
if (hex != "") r += "<"+hex+">:";
if (type != WMBusDeviceType::DEVICE_UNKNOWN)
{
r += toString(type);
@ -4604,10 +4615,17 @@ bool SpecifiedDevice::parse(string &arg)
// then the specified device string is faulty.
return false;
}
if (!file_checked && check_file(p, &is_tty, &is_stdin, &is_file, &is_simulation))
if (!file_checked && check_file(p, &is_tty, &is_stdin, &is_file, &is_simulation, &is_hex_simulation))
{
file_checked = true;
file = p;
if (!is_hex_simulation)
{
file = p;
}
else
{
hex = p;
}
}
else if (!typeidextras_checked && is_type_id_extras(p, &type, &id, &extras))
{
@ -4750,16 +4768,17 @@ Detected detectWMBusDeviceOnTTY(string tty,
return detected;
}
Detected detectWMBusDeviceWithFile(SpecifiedDevice &specified_device,
Detected detectWMBusDeviceWithFileOrHex(SpecifiedDevice &specified_device,
LinkModeSet default_linkmodes,
shared_ptr<SerialCommunicationManager> handler)
{
assert(specified_device.file != "");
assert(specified_device.file != "" || specified_device.hex != "");
assert(specified_device.command == "");
debug("(lookup) with file \"%s\"\n", specified_device.file.c_str());
debug("(lookup) with file/hex \"%s%s\"\n", specified_device.file.c_str(), specified_device.hex.c_str());
Detected detected;
detected.found_file = specified_device.file;
detected.found_hex = specified_device.hex;
detected.setSpecifiedDevice(specified_device);
// If <device>:c1 is missing :c1 then use --c1.
LinkModeSet lms = specified_device.linkmodes;
@ -4769,8 +4788,8 @@ Detected detectWMBusDeviceWithFile(SpecifiedDevice &specified_device,
}
if (specified_device.is_simulation)
{
debug("(lookup) driver: simulation file\n");
// A simulation file has a lms of all by default, eg no simulation_foo.txt:t1 nor --t1
debug("(lookup) driver: simulation %s\n", specified_device.hex != "" ? "hex" : "file");
// A simulation file/hex has a lms of all by default, eg no simulation_foo.txt:t1 nor --t1
if (specified_device.linkmodes.empty()) lms.setAll();
detected.setAsFound("", DEVICE_SIMULATION, 0 , false, lms);
return detected;

Wyświetl plik

@ -157,7 +157,8 @@ struct SpecifiedDevice
std::string bus_alias; // A bus alias, necessary for C2/T2 meters and mbus.
int index; // 0,1,2,3 the order on the command line / config file.
std::string file; // simulation_meter.txt, stdin, file.raw, /dev/ttyUSB0
bool is_tty{}, is_stdin{}, is_file{}, is_simulation{};
std::string hex; // a hex string supplied on the command line becomes a hex simulation.
bool is_tty{}, is_stdin{}, is_file{}, is_simulation{}, is_hex_simulation{};
WMBusDeviceType type; // im871a, rtlwmbus
std::string id; // 12345678 for wmbus dongles or 0,1 for rtlwmbus indexes.
std::string extras; // Extra device specific settings.
@ -180,6 +181,7 @@ struct Detected
SpecifiedDevice specified_device {}; // Device as specified from the command line / config file.
string found_file; // The device file to use.
string found_hex; // An immediate hex string is supplied.
string found_device_id; // An "unique" identifier, typically the id used by the dongle as its own wmbus id, if it transmits.
WMBusDeviceType found_type {}; // IM871A, AMB8465 etc.
int found_bps {}; // Serial speed of tty.
@ -610,9 +612,9 @@ struct WMBus
virtual ~WMBus() = 0;
};
Detected detectWMBusDeviceWithFile(SpecifiedDevice &specified_device,
LinkModeSet default_linkmodes,
shared_ptr<SerialCommunicationManager> manager);
Detected detectWMBusDeviceWithFileOrHex(SpecifiedDevice &specified_device,
LinkModeSet default_linkmodes,
shared_ptr<SerialCommunicationManager> manager);
Detected detectWMBusDeviceWithCommand(SpecifiedDevice &specified_device,
LinkModeSet default_linkmodes,
shared_ptr<SerialCommunicationManager> handler);

Wyświetl plik

@ -47,7 +47,7 @@ struct WMBusSimulator : public WMBusCommonImplementation
void simulate();
string device() { return file_; }
WMBusSimulator(string alias, string file, shared_ptr<SerialCommunicationManager> manager);
WMBusSimulator(string alias, string file, string hex, shared_ptr<SerialCommunicationManager> manager);
private:
vector<uchar> received_payload_;
@ -62,15 +62,23 @@ shared_ptr<WMBus> openSimulator(Detected detected, shared_ptr<SerialCommunicatio
{
string bus_alias = detected.specified_device.bus_alias;
string device = detected.found_file;
WMBusSimulator *imp = new WMBusSimulator(bus_alias, device, manager);
string hex = detected.found_hex;
WMBusSimulator *imp = new WMBusSimulator(bus_alias, device, hex, manager);
return shared_ptr<WMBus>(imp);
}
WMBusSimulator::WMBusSimulator(string bus_alias, string file, shared_ptr<SerialCommunicationManager> manager)
WMBusSimulator::WMBusSimulator(string bus_alias, string file, string hex, shared_ptr<SerialCommunicationManager> manager)
: WMBusCommonImplementation(bus_alias, DEVICE_SIMULATION, manager, NULL, false), file_(file)
{
assert(file != "");
loadFile(file, &lines_);
assert(file != "" || hex != "");
if (hex != "")
{
lines_.push_back("telegram="+hex);
}
if (file != "")
{
loadFile(file, &lines_);
}
}
bool WMBusSimulator::ping()

Wyświetl plik

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

Wyświetl plik

@ -0,0 +1,80 @@
#!/bin/sh
PROG="$1"
TEST=testoutput
mkdir -p $TEST
TESTNAME="Test hex on commandline"
TESTRESULT="ERROR"
cat > $TEST/test_expected.txt <<EOF
No meters configured. Printing id:s of all telegrams heard!
Received telegram from: 76348799
manufacturer: (KAM) Kamstrup Energi (0x2c2d)
type: Cold water meter (0x16)
ver: 0x1b
driver: multical21
EOF
$PROG 2A442D2C998734761B168D2091D37CAC21E1D68CDAFFCD3DC452BD802913FF7B1706CA9E355D6C2701CC24 2> $TEST/test_output.txt 1>&2
if [ "$?" = "0" ]
then
diff $TEST/test_expected.txt $TEST/test_output.txt
if [ "$?" = "0" ]
then
echo OK: $TESTNAME
TESTRESULT="OK"
fi
else
echo "wmbusmeters returned error code: $?"
cat $TEST/test_output.txt
fi
cat > $TEST/test_expected.txt <<EOF
{"media":"cold water","meter":"multical21","name":"MyWater","id":"76348799","total_m3":6.408,"target_m3":6.408,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
EOF
TESTNAME="Test hex on commandline with meter"
$PROG --format=json 2A442D2C998734761B168D2091D37CAC21E1D68CDAFFCD3DC452BD802913FF7B1706CA9E355D6C2701CC24 \
MyWater auto 76348799 28F64A24988064A079AA2C807D6102AE > $TEST/test_output.txt 2> $TEST/test_stderr.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 OK: $TESTNAME
TESTRESULT="OK"
fi
else
echo "wmbusmeters returned error code: $?"
cat $TEST/test_output.txt
cat $TEST/test_stderr.txt
fi
TESTNAME="Test invalid hex on commandline"
cat > $TEST/test_expected.txt <<EOF
Hex string must have an even lenght of hexadecimal characters.
EOF
$PROG 2A442D2C998734761B168D2091D37CAC21E1D68CDAFFCD3DC452BD802913FF7B1706CA9E355D6C2701CC2 2> $TEST/test_output.txt 1>&2
if [ "$?" = "1" ]
then
diff $TEST/test_expected.txt $TEST/test_output.txt
if [ "$?" = "0" ]
then
echo OK: $TESTNAME
TESTRESULT="OK"
fi
else
echo "wmbusmeters returned error code 0 but we expected failure!"
echo "======================="
cat $TEST/test_output.txt
echo "======================="
cat $TEST/test_stderr.txt
fi