kopia lustrzana https://github.com/weetmuts/wmbusmeters
Added --robot option to get JSON output.
rodzic
858f685c77
commit
e0f01c9c44
12
Makefile
12
Makefile
|
@ -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)
|
||||
|
|
30
README.md
30
README.md
|
@ -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
96
main.cc
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
9
meters.h
9
meters.h
|
@ -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;
|
||||
};
|
||||
|
|
Ładowanie…
Reference in New Issue