Added --robot option to get JSON output.

pull/5/head
weetmuts 2017-08-31 10:58:39 +02:00
rodzic 858f685c77
commit e0f01c9c44
5 zmienionych plików z 153 dodań i 75 usunięć

Wyświetl plik

@ -28,21 +28,21 @@ endif
$(shell mkdir -p $(BUILD))
CXXFLAGS := $(DEBUG_FLAGS) -Wall -fmessage-length=0 -std=c++11 -Wno-unused-function "-DWMBUSMETERS_VERSION=\"0.1\""
CXXFLAGS := $(DEBUG_FLAGS) -Wall -fmessage-length=0 -std=c++11 -Wno-unused-function "-DWMBUSMETERS_VERSION=\"0.2\""
$(BUILD)/%.o: %.cc $(wildcard %.h)
$(CXX) $(CXXFLAGS) $< -c -o $@
METERS_OBJS:=\
$(BUILD)/main.o \
$(BUILD)/util.o \
$(BUILD)/aes.o \
$(BUILD)/cmdline.o \
$(BUILD)/main.o \
$(BUILD)/meters.o \
$(BUILD)/meter_multical21.o \
$(BUILD)/serial.o \
$(BUILD)/util.o \
$(BUILD)/wmbus.o \
$(BUILD)/wmbus_im871a.o \
$(BUILD)/meters.o \
$(BUILD)/meter_multical21.o
all: $(BUILD)/wmbusmeters
$(STRIP_BINARY)

Wyświetl plik

@ -1,13 +1,12 @@
# wmbusmeters
The program receives and decodes C1 telegrams
(using the wireless mbus protocol) to acquire
utility meter readings. You configure multiple
meters for reading by supplying triplets
on the command line.
utility meter readings.
wmbusmeters [usbdevice] [meter_name] [meter_id] [meter_ke]
wmbusmeters [usbdevice] { [meter_name] [meter_id] [meter_key] }*
If you want to listen to more than one meter, simply add more triples.
If you want to listen to more than one meter, simply add more meter triplets.
Add --robot to get JSON output.
Builds and runs on GNU/Linux:
@ -17,23 +16,21 @@ make
./build/wmbusmeters --verbose /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF
./build/wmbusmeters --robot /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF
make HOST=arm
./build_arm/wmbusmeters /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF
Binary generated: ./build_arm/wmbusmeters
make DEBUG=true
./build_debug/wmbusmeters /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF
Binary generated: ./build_debug/wmbusmeters
make DEBUG=true HOST=arm
./build_arm_debug/wmbusmeters /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF
Binary generated: ./build_arm_debug/wmbusmeters
(After you insert the im871A USB stick, do:
chown me:me /dev/ttyUSB0
to avoid having to run the program as root.)
Add yourself to the dialout group to get access to the newly plugged in im87A USB stick.
Currently only supports the USB stick receiver im871A
and the water meter Multical21. The source code is modular
@ -58,7 +55,6 @@ https://github.com/ffcrg/ecpiww
https://github.com/tobiasrask/wmbus-client
Code can print total water consumption! But everything else is
missing. CRC checks anyone? :-) Don't rely on these measurements
for anything really important!
TODO: CRC checks are still missing. If the wrong AES key
is supplied you probably get zero readings and
sometimes warnings about wrong type of frames.

96
main.cc
Wyświetl plik

@ -17,12 +17,12 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include"util.h"
#include"serial.h"
#include"wmbus.h"
#include"cmdline.h"
#include"meters.h"
#include"aes.h"
#include"serial.h"
#include"util.h"
#include"wmbus.h"
#include<string.h>
@ -33,47 +33,65 @@ void printMeter(Meter *meter) {
meter->name().c_str(),
meter->id().c_str(),
meter->totalWaterConsumption(),
meter->datetimeOfUpdate().c_str(),
meter->datetimeOfUpdateHumanReadable().c_str(),
meter->targetWaterConsumption(),
meter->statusHumanReadable().c_str());
// targetWaterConsumption: The total consumption at the start of the previous 30 day period.
// statusHumanReadable: DRY,REVERSED,LEAK,BURST if that status is detected right now, followed by
// (dry 15-21 days) which means that, even it DRY is not active right now,
// DRY has been active for 15-21 days during the last 30 days.
}
#define Q(x,y) "\""#x"\":"#y","
#define QS(x,y) "\""#x"\":\""#y"\","
#define QSE(x,y) "\""#x"\":\""#y"\""
void printMeterJSON(Meter *meter) {
printf("{"
QS(name,%s)
QS(id,%s)
Q(total_m3,%.3f)
Q(target_m3,%.3f)
QS(current_status,%s)
QS(time_dry,%s)
QS(time_reversed,%s)
QS(time_leaking,%s)
QS(time_bursting,%s)
QSE(timestamp,%s)
"}\n",
meter->name().c_str(),
meter->id().c_str(),
meter->totalWaterConsumption(),
meter->targetWaterConsumption(),
meter->status().c_str(), // DRY REVERSED LEAK BURST
meter->timeDry().c_str(),
meter->timeReversed().c_str(),
meter->timeLeaking().c_str(),
meter->timeBursting().c_str(),
meter->datetimeOfUpdateRobot().c_str());
}
int main(int argc, char **argv)
{
if (argc < 2) {
printf("wmbusmeters version: " WMBUSMETERS_VERSION "\n\n");
printf("Usage: wmbusmeters [--verbose] [usbdevice] { [meter_name] [meter_id] [meter_key] }* \n");
CommandLine *c = parseCommandLine(argc, argv);
if (c->need_help) {
printf("wmbusmeters version: " WMBUSMETERS_VERSION "\n");
printf("Usage: wmbusmeters [--verbose] [--robot] [usbdevice] { [meter_name] [meter_id] [meter_key] }* \n");
printf("\nAdd more meter triplets to listen to more meters.\n");
printf("Add --verbose for detailed debug information.\n");
printf(" --robot for json output.\n");
exit(0);
}
int i=1;
if (!strcmp(argv[i], "--verbose")) {
verboseEnabled(true);
i++;
}
char *usbdevice = argv[i];
i++;
if (!usbdevice) error("You must supply the usb device to which the wmbus dongle is connected.\n");
verbose("Using usbdevice: %s\n", usbdevice);
if ((argc-i) % 3 != 0) {
error("For each meter you must supply a: name,id and key.\n");
}
int num_meters = (argc-i)/3;
verbose("Number of meters: %d\n", num_meters);
verboseEnabled(c->verbose);
auto manager = createSerialCommunicationManager();
onExit(call(manager,stop));
auto wmbus = openIM871A(usbdevice, manager);
auto wmbus = openIM871A(c->usb_device, manager);
wmbus->setLinkMode(C1a);
if (wmbus->getLinkMode()!=C1a) error("Could not set link mode to C1a\n");
@ -81,23 +99,15 @@ int main(int argc, char **argv)
// We want the data visible in the log file asap!
setbuf(stdout, NULL);
if (num_meters > 0) {
Meter *meters[num_meters];
for (int m=0; m<num_meters; ++m) {
char *name = argv[m*3+i+0];
char *id = argv[m*3+i+1];
char *key = argv[m*3+i+2];
if (!isValidId(id)) error("Not a valid meter id \"%s\"\n", id);
if (!isValidKey(key)) error("Not a valid meter key \"%s\"\n", key);
verbose("Configuring meter: \"%s\" \"%s\" \"%s\"\n", name, id, key);
meters[m] = createMultical21(wmbus, name, id, key);
meters[m]->onUpdate(printMeter);
if (c->meters.size() > 0) {
for (auto &m : c->meters) {
verbose("Configuring meter: \"%s\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
Meter *meter=createMultical21(wmbus, m.name, m.id, m.key);
if (c->robot) meter->onUpdate(printMeterJSON);
else meter->onUpdate(printMeter);
}
} else {
printf("No meters configured. Printing id:s of all telegrams heard! Add --verbose to get more data.\n");
printf("No meters configured. Printing id:s of all telegrams heard! \n");
printf("To configure a meter, add a triplet to the command line: name id key\n");
printf("Where name is your string identifying the meter.\n");
printf(" id is the 8 digits printed on the meter.\n");

Wyświetl plik

@ -51,8 +51,14 @@ struct MeterMultical21 : public Meter {
float totalWaterConsumption();
float targetWaterConsumption();
string statusHumanReadable();
string status();
string timeDry();
string timeReversed();
string timeLeaking();
string timeBursting();
string datetimeOfUpdate();
string datetimeOfUpdateHumanReadable();
string datetimeOfUpdateRobot();
void onUpdate(function<void(Meter*)> cb);
private:
@ -106,14 +112,22 @@ float MeterMultical21::targetWaterConsumption()
return target_volume_;
}
string MeterMultical21::datetimeOfUpdate()
string MeterMultical21::datetimeOfUpdateHumanReadable()
{
char datetime[20];
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 MeterMultical21::datetimeOfUpdateRobot()
{
char datetime[40];
memset(datetime, 0, sizeof(datetime));
strftime(datetime, sizeof(datetime), "%FT%TZ", gmtime(&datetime_of_update_));
return string(datetime);
}
Meter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key) {
return new MeterMultical21(bus,name,id,key);
}
@ -242,13 +256,14 @@ void MeterMultical21::processContent(vector<uchar> &c) {
// The dif=0x04 vif=0x13 means current volume with scale factor .001
total_water_consumption_ = ((float)consumption_raw) / ((float)1000);
// The short frame target volume supplies two low bytes, the remaining two hi bytes are picked from rec2.
// The short frame target volume supplies two low bytes,
// the remaining two hi bytes are >>probably<< picked from rec2!
int target_volume_raw = rec2val3*256*256*256 + rec2val2*256*256 + rec3val1*256 + rec3val0;
verbose("short rec3 (%02x %02x) %02x %02x = %d target volume\n", rec2val3, rec2val2, rec3val1, rec3val0, target_volume_raw);
target_volume_ = ((float)target_volume_raw) / ((float)1000);
// Which leaves one unknown byte.
verbose("short rec3 %02x = unknown\n", rec3val2);
// Which leaves one unknown byte. Takes on all possible values.
verbose("short rec3 %02x = unknown\n", rec3val2);
} else
if (frame_type == 0x78) {
verbose("Full frame %d bytes\n", c.size());
@ -310,12 +325,58 @@ void MeterMultical21::processContent(vector<uchar> &c) {
target_volume_ = ((float)target_volume_raw) / ((float)1000);
// To unknown bytes, seems to be very constant.
verbose("full rec4 %02x %02x = unknown\n", rec4val1, rec4val0);
} else {
fprintf(stderr, "Unknown frame %02x\n", frame_type);
}
}
string MeterMultical21::status() {
string s;
if (info_codes_ & INFO_CODE_DRY) s.append("DRY ");
if (info_codes_ & INFO_CODE_REVERSE) s.append("REVERSED ");
if (info_codes_ & INFO_CODE_LEAK) s.append("LEAK ");
if (info_codes_ & INFO_CODE_BURST) s.append("BURST ");
if (s.length() > 0) {
s.pop_back(); // Remove final space
return s;
}
return s;
}
string MeterMultical21::timeDry() {
int time_dry = (info_codes_ >> INFO_CODE_DRY_SHIFT) & 7;
if (time_dry) {
return decodeTime(time_dry);
}
return "";
}
string MeterMultical21::timeReversed() {
int time_reversed = (info_codes_ >> INFO_CODE_REVERSE_SHIFT) & 7;
if (time_reversed) {
return decodeTime(time_reversed);
}
return "";
}
string MeterMultical21::timeLeaking() {
int time_leaking = (info_codes_ >> INFO_CODE_LEAK_SHIFT) & 7;
if (time_leaking) {
return decodeTime(time_leaking);
}
return "";
}
string MeterMultical21::timeBursting() {
int time_bursting = (info_codes_ >> INFO_CODE_BURST_SHIFT) & 7;
if (time_bursting) {
return decodeTime(time_bursting);
}
return "";
}
string MeterMultical21::statusHumanReadable() {
string s;
bool dry = info_codes_ & INFO_CODE_DRY;
@ -324,7 +385,7 @@ string MeterMultical21::statusHumanReadable() {
if (dry) s.append("DRY");
s.append("(dry ");
s.append(decodeTime(time_dry));
s.append(")");
s.append(") ");
}
bool reversed = info_codes_ & INFO_CODE_REVERSE;
@ -353,7 +414,11 @@ string MeterMultical21::statusHumanReadable() {
s.append(decodeTime(time_burst));
s.append(") ");
}
return s;
if (s.length() > 0) {
s.pop_back();
return s;
}
return "OK";
}
string MeterMultical21::decodeTime(int time) {

Wyświetl plik

@ -37,7 +37,14 @@ struct Meter {
virtual float totalWaterConsumption() = 0;
virtual float targetWaterConsumption() = 0;
virtual string statusHumanReadable() = 0;
virtual string datetimeOfUpdate() = 0;
virtual string status() = 0;
virtual string timeDry() = 0;
virtual string timeReversed() = 0;
virtual string timeLeaking() = 0;
virtual string timeBursting() = 0;
virtual string datetimeOfUpdateHumanReadable() = 0;
virtual string datetimeOfUpdateRobot() = 0;
virtual void onUpdate(function<void(Meter*)> cb) = 0;
};