wmbusmeters/src/main.cc

339 wiersze
12 KiB
C++
Czysty Zwykły widok Historia

2018-04-01 06:53:37 +00:00
/*
2019-02-23 17:30:16 +00:00
Copyright (C) 2017-2019 Fredrik Öhrström
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/>.
*/
#include"cmdline.h"
#include"config.h"
#include"meters.h"
2017-09-02 21:26:57 +00:00
#include"printer.h"
2017-08-09 10:00:11 +00:00
#include"serial.h"
#include"util.h"
2017-08-09 10:00:11 +00:00
#include"wmbus.h"
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
2019-02-23 20:36:00 +00:00
#include <sys/errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
2017-08-09 10:00:11 +00:00
using namespace std;
2019-02-24 13:08:51 +00:00
void oneshotCheck(Configuration *cmdline, SerialCommunicationManager *manager, Meter *meter, vector<unique_ptr<Meter>> &meters);
void startUsingCommandline(Configuration *cmdline);
void startUsingConfigFiles(string root, bool is_daemon);
void startDaemon(string pid_file); // Will use config files.
2017-08-09 10:00:11 +00:00
int main(int argc, char **argv)
{
auto cmdline = parseCommandLine(argc, argv);
if (cmdline->need_help) {
printf("wmbusmeters version: " WMBUSMETERS_VERSION "\n");
printf("Usage: wmbusmeters [options] (auto | /dev/ttyUSBx) { [meter_name] [meter_type] [meter_id] [meter_key] }* \n\n");
printf("Add more meter quadruplets to listen to more meters.\n");
printf("Add --verbose for more detailed information on communication.\n");
2018-03-05 10:39:30 +00:00
printf(" --robot or --robot=json for json output.\n");
printf(" --robot=fields for semicolon separated fields.\n");
printf(" --separator=X change field separator to X.\n");
2019-02-24 14:20:55 +00:00
printf(" --logfile=file\n");
2018-11-01 16:17:23 +00:00
printf(" --meterfiles=dir to create status files below dir,\n"
" named dir/meter_name, containing the latest reading.\n");
printf(" --meterfiles defaults dir to /tmp.\n");
printf(" --shell=cmd invokes cmd with env variables containing the latest reading.\n");
printf(" --shellenvs list the env variables available for the meter.\n");
2018-11-01 16:17:23 +00:00
printf(" --oneshot wait for an update from each meter, then quit.\n\n");
printf(" --exitafter=20h program exits after running for twenty hoursh\n"
" or 10m for ten minutes or 5s for five seconds.\n");
printf(" --useconfig read from /etc/wmbusmeters.conf and /etc/wmbusmeters.d\n");
2019-02-23 17:30:16 +00:00
printf(" check the man page for how to write the config files.\n");
printf(" --reload signals a running wmbusmeters daemon to reload the configuration,\n");
printf(" when you have modified config files and/or usb dongles.\n\n");
printf("Specifying auto as the device will automatically look for usb\n");
printf("wmbus dongles on /dev/im871a and /dev/amb8465\n\n");
2018-11-23 08:04:31 +00:00
printf("The meter types: multical21,flowiq3100,supercom587,iperl (water meters) are supported.\n"
"The meter types: multical302 (heat) and omnipower (electricity) qcaloric (heat cost)\n"
"are work in progress.\n\n");
}
2019-02-23 17:30:16 +00:00
else
if (cmdline->daemon) {
startDaemon(cmdline->pid_file);
exit(0);
}
2019-02-23 17:30:16 +00:00
else
if (cmdline->useconfig) {
2019-02-23 20:21:17 +00:00
// This is primarily used for testing.
const char *r = getenv("WMBUSMETERS_CONFIG_ROOT");
string root = "";
if (r != NULL) {
root = r;
}
startUsingConfigFiles(root, false);
exit(0);
}
2019-02-23 17:30:16 +00:00
else {
// We want the data visible in the log file asap!
setbuf(stdout, NULL);
startUsingCommandline(cmdline.get());
}
}
void startUsingCommandline(Configuration *config)
{
warningSilenced(config->silence);
verboseEnabled(config->verbose);
logTelegramsEnabled(config->logtelegrams);
debugEnabled(config->debug);
if (config->exitafter != 0) {
verbose("(config) wmbusmeters will exit after %d seconds\n", config->exitafter);
}
if (config->meterfiles) {
verbose("(config) store meter files in: \"%s\"\n", config->meterfiles_dir.c_str());
}
verbose("(config) using usb device: %s\n", config->usb_device.c_str());
verbose("(config) number of meters: %d\n", config->meters.size());
auto manager = createSerialCommunicationManager(config->exitafter);
2017-08-09 10:00:11 +00:00
onExit(call(manager.get(),stop));
2017-08-09 10:00:11 +00:00
unique_ptr<WMBus> wmbus;
2017-08-09 10:00:11 +00:00
auto type_and_device = detectMBusDevice(config->usb_device, manager.get());
switch (type_and_device.first) {
case DEVICE_IM871A:
verbose("(im871a) detected on %s\n", type_and_device.second.c_str());
wmbus = openIM871A(type_and_device.second, manager.get());
break;
case DEVICE_AMB8465:
verbose("(amb8465) detected on %s\n", type_and_device.second.c_str());
wmbus = openAMB8465(type_and_device.second, manager.get());
break;
case DEVICE_SIMULATOR:
verbose("(simulator) found %s\n", type_and_device.second.c_str());
wmbus = openSimulator(type_and_device.second, manager.get());
break;
case DEVICE_UNKNOWN:
warning("No wmbus device found! Exiting!\n");
if (config->daemon) {
// If starting as a daemon, wait a bit so that systemd have time to catch up.
sleep(1);
}
exit(1);
break;
}
if (!config->link_mode_set) {
2018-11-01 16:17:23 +00:00
// The link mode is not explicitly set. Examine the meters to see which
// link mode to use.
for (auto &m : config->meters) {
if (!config->link_mode_set) {
config->link_mode = toMeterLinkMode(m.type);
config->link_mode_set = true;
2018-11-01 16:17:23 +00:00
} else {
if (config->link_mode != toMeterLinkMode(m.type)) {
2018-11-01 16:17:23 +00:00
error("A different link mode has been set already.\n");
}
}
}
}
if (!config->link_mode_set) {
2018-11-01 16:17:23 +00:00
error("If you specify no meters, you have to specify the link mode: --c1 or --t1\n");
}
wmbus->setLinkMode(config->link_mode);
2018-11-01 16:17:23 +00:00
string using_link_mode = linkModeName(wmbus->getLinkMode());
verbose("(config) using link mode: %s\n", using_link_mode.c_str());
2017-09-02 21:26:57 +00:00
auto output = unique_ptr<Printer>(new Printer(config->json, config->fields,
config->separator, config->meterfiles, config->meterfiles_dir,
config->shells));
vector<unique_ptr<Meter>> meters;
if (config->meters.size() > 0) {
for (auto &m : config->meters) {
2018-11-25 11:33:14 +00:00
const char *keymsg = (m.key[0] == 0) ? "not-encrypted" : "encrypted";
switch (toMeterType(m.type)) {
case MULTICAL21_METER:
2019-02-23 13:08:56 +00:00
meters.push_back(createMultical21(wmbus.get(), m.name, m.id, m.key));
2019-02-23 12:59:47 +00:00
verbose("(multical21) configured \"%s\" \"multical21\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg);
break;
case FLOWIQ3100_METER:
2019-02-23 13:08:56 +00:00
meters.push_back(createMultical21(wmbus.get(), m.name, m.id, m.key));
2019-02-23 12:59:47 +00:00
verbose("(flowiq3100) configured \"%s\" \"flowiq3100\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg);
break;
case MULTICAL302_METER:
meters.push_back(createMultical302(wmbus.get(), m.name, m.id, m.key));
2019-02-23 12:59:47 +00:00
verbose("(multical302) configured \"%s\" \"multical302\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg);
break;
case OMNIPOWER_METER:
meters.push_back(createOmnipower(wmbus.get(), m.name, m.id, m.key));
2019-02-23 12:59:47 +00:00
verbose("(omnipower) configured \"%s\" \"omnipower\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg);
break;
2018-11-01 16:17:23 +00:00
case SUPERCOM587_METER:
meters.push_back(createSupercom587(wmbus.get(), m.name, m.id, m.key));
2019-02-23 12:59:47 +00:00
verbose("(supercom587) configured \"%s\" \"supercom587\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg);
2018-11-01 16:17:23 +00:00
break;
2018-11-23 08:04:31 +00:00
case IPERL_METER:
meters.push_back(createIperl(wmbus.get(), m.name, m.id, m.key));
2019-02-23 12:59:47 +00:00
verbose("(iperl) configured \"%s\" \"iperl\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg);
2018-11-23 08:04:31 +00:00
break;
case QCALORIC_METER:
meters.push_back(createQCaloric(wmbus.get(), m.name, m.id, m.key));
2019-02-23 12:59:47 +00:00
verbose("(qcaloric) configured \"%s\" \"qcaloric\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg);
break;
case UNKNOWN_METER:
2019-02-23 13:01:04 +00:00
error("No such meter type \"%s\"\n", m.type.c_str());
break;
}
if (config->list_shell_envs) {
string ignore1, ignore2, ignore3;
vector<string> envs;
meters.back()->printMeter(&ignore1,
&ignore2, config->separator,
&ignore3,
&envs);
printf("Environment variables provided to shell for meter %s:\n", m.type.c_str());
for (auto &e : envs) {
int p = e.find('=');
string key = e.substr(0,p);
printf("%s\n", key.c_str());
}
exit(0);
}
meters.back()->onUpdate(calll(output.get(),print,Meter*));
meters.back()->onUpdate([&](Meter*meter) { oneshotCheck(config, manager.get(), meter, meters); });
}
} else {
printf("No meters configured. Printing id:s of all telegrams heard!\n\n");
2017-09-02 21:26:57 +00:00
wmbus->onTelegram([](Telegram *t){t->print();});
}
if (type_and_device.first == DEVICE_SIMULATOR) {
wmbus->simulate();
}
if (config->daemon || config->use_logfile) {
notice("(wmbusmeters) waiting for telegrams\n");
}
manager->waitForStop();
if (config->daemon || config->use_logfile) {
notice("(wmbusmeters) shutting down\n");
}
2017-08-09 10:00:11 +00:00
}
2017-09-02 21:26:57 +00:00
void oneshotCheck(Configuration *config, SerialCommunicationManager *manager, Meter *meter, vector<unique_ptr<Meter>> &meters)
2017-09-02 21:26:57 +00:00
{
if (!config->oneshot) return;
for (auto &m : meters) {
if (m->numUpdates() == 0) return;
2017-09-02 21:26:57 +00:00
}
// All meters have received at least one update! Stop!
manager->stop();
}
void writePid(string pid_file, int pid)
{
FILE *pidf = fopen(pid_file.c_str(), "w");
if (!pidf) {
error("Could not open pid file \"%s\" for writing!\n", pid_file.c_str());
}
if (pid > 0) {
int n = fprintf(pidf, "%d\n", pid);
if (!n) {
error("Could not write pid (%d) to file \"%s\"!\n", pid, pid_file.c_str());
}
notice("(wmbusmeters) started %s\n", pid_file.c_str());
}
fclose(pidf);
return;
}
void startDaemon(string pid_file)
{
setlogmask(LOG_UPTO (LOG_INFO));
openlog("wmbusmetersd", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
enableSyslog();
// Pre check that the pid file can be writte to.
// Exit before fork, if it fails.
writePid(pid_file, 0);
pid_t pid = fork();
if (pid < 0)
{
error("Could not fork.\n");
}
if (pid > 0)
{
// Success! The parent stores the pid and exits.
writePid(pid_file, pid);
return;
}
// Change the file mode mask
umask(0);
// Create a new SID for the daemon
pid_t sid = setsid();
if (sid < 0) {
// log
exit(-1);
}
if ((chdir("/")) < 0) {
error("Could not change to root as current working directory.");
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
startUsingConfigFiles("", true);
}
void startUsingConfigFiles(string root, bool is_daemon)
{
2019-02-24 14:20:55 +00:00
unique_ptr<Configuration> config = loadConfiguration(root);
config->daemon = is_daemon;
if (config->use_logfile) {
bool ok = enableLogfile(config->logfile);
if (!ok) {
if (is_daemon) {
warning("Could not open log file, will use syslog instead.\n");
} else {
error("Could not open log file.\n");
}
}
}
2019-02-24 14:20:55 +00:00
startUsingCommandline(config.get());
}