wmbusmeters/src/main.cc

1232 wiersze
41 KiB
C++
Czysty Zwykły widok Historia

2018-04-01 06:53:37 +00:00
/*
Copyright (C) 2017-2020 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"
#include"rtlsdr.h"
2017-08-09 10:00:11 +00:00
#include"serial.h"
2020-09-18 18:05:59 +00:00
#include"shell.h"
#include"threads.h"
#include"util.h"
#include"version.h"
2017-08-09 10:00:11 +00:00
#include"wmbus.h"
#include <algorithm>
2020-05-11 09:59:47 +00:00
#include <errno.h>
#include <fcntl.h>
2020-09-08 20:34:24 +00:00
#include <pthread.h>
#include <semaphore.h>
2020-09-07 08:36:39 +00:00
#include <set>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>
2017-08-09 10:00:11 +00:00
using namespace std;
int main(int argc, char **argv);
void check_if_multiple_wmbus_meters_running();
void check_for_dead_wmbus_devices(Configuration *config);
shared_ptr<Meter> create_meter(Configuration *config, MeterType type, MeterInfo *mi, const char *keymsg);
shared_ptr<Printer> create_printer(Configuration *config);
shared_ptr<WMBus> create_wmbus_object(Detected *detected, Configuration *config, shared_ptr<SerialCommunicationManager> manager);
void detect_and_configure_wmbus_devices(Configuration *config);
bool find_specified_device_and_update_detected(Configuration *c, Detected *d);
void list_fields(Configuration *config, string meter_type);
void list_shell_envs(Configuration *config, string meter_type);
void list_meters(Configuration *config);
void log_start_information(Configuration *config);
void oneshot_check(Configuration *config, Telegram *t, Meter *meter);
void open_wmbus_device_and_set_linkmodes(Configuration *config, string how, Detected *detected);
void perform_auto_scan_of_devices(Configuration *config);
void perform_auto_scan_of_serial_devices(Configuration *config);
void perform_auto_scan_of_swradio_devices(Configuration *config);
void regular_checkup(Configuration *config);
void remove_lost_serial_devices_from_ignore_list(vector<string> &devices);
void remove_lost_swradio_devices_from_ignore_list(vector<string> &devices);
bool start(Configuration *config);
void start_using_config_files(string root, bool is_daemon, string device_override, string listento_override);
void start_daemon(string pid_file, string device_override, string listento_override); // Will use config files.
void setup_log_file(Configuration *config);
void setup_meters(Configuration *config, MeterManager *manager);
void write_pid(string pid_file, int pid);
// The serial communication manager takes care of
// monitoring the file descrtiptors for the ttys,
2020-09-08 12:55:01 +00:00
// background shells, files and stdin. It also invokes
// regular callbacks used for monitoring alarms and
// detecting new devices.
shared_ptr<SerialCommunicationManager> serial_manager_;
2020-09-07 08:36:39 +00:00
// Manage registered meters to decode and relay.
shared_ptr<MeterManager> meter_manager_;
// Current active set of wmbus devices that can receive telegrams.
// This can change during runtime, plugging/unplugging wmbus dongles.
vector<shared_ptr<WMBus>> wmbus_devices_;
RecursiveMutex wmbus_devices_mutex_("wmbus_devices_mutex"); // Protected by macro LOCK_WMBUS_DEVICES
#define LOCK_WMBUS_DEVICES(where) WITH(wmbus_devices_mutex_, where)
2020-09-07 08:36:39 +00:00
// Remember devices that were not detected as wmbus devices.
// To avoid probing them again and again.
2020-09-13 13:39:09 +00:00
set<string> not_serial_wmbus_devices_;
// The software radio devices are always swradio devices
// but they might not be available for wmbusmeters.
set<string> not_swradio_wmbus_devices_;
2020-09-07 08:36:39 +00:00
2020-09-13 14:55:22 +00:00
// When manually supplying stdin or a file, then, after
// it has been read, do not open it again!
set<string> do_not_open_file_again_;
2020-09-18 18:05:59 +00:00
// Store simulation files here.
set<string> simulation_files_;
// Rendering the telegrams to json,fields or shell calls is
// done by the printer.
shared_ptr<Printer> printer_;
// Set as true when the warning for no detected wmbus devices has been printed.
bool printed_warning_ = false;
2017-08-09 10:00:11 +00:00
int main(int argc, char **argv)
{
auto config = parseCommandLine(argc, argv);
if (config->version)
{
printf("wmbusmeters: " VERSION "\n");
printf(COMMIT "\n");
exit(0);
}
if (config->license)
{
const char * license = R"LICENSE(
Copyright (C) 2017-2020 Fredrik Öhrström
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/>.
You can download the source here: https://github.com/weetmuts/wmbusmeters
But you can also request the source from the person/company that
provided you with this binary. Read the full license for all details.
)LICENSE";
puts(license);
exit(0);
}
if (config->list_shell_envs)
{
list_shell_envs(config.get(), config->list_meter);
exit(0);
}
if (config->list_fields)
{
list_fields(config.get(), config->list_meter);
exit(0);
}
if (config->list_meters)
{
list_meters(config.get());
exit(0);
}
if (config->need_help)
{
printf("wmbusmeters version: " VERSION "\n");
2019-10-24 19:06:33 +00:00
const char *short_manual =
#include"short_manual.h"
puts(short_manual);
2020-09-18 18:05:59 +00:00
exit(0);
}
2020-09-18 18:05:59 +00:00
if (config->daemon)
{
start_daemon(config->pid_file, config->device_override, config->listento_override);
exit(0);
}
2020-09-18 18:05:59 +00:00
if (config->useconfig)
{
start_using_config_files(config->config_root, false, config->device_override, config->listento_override);
exit(0);
}
else
{
2019-02-23 17:30:16 +00:00
// We want the data visible in the log file asap!
setbuf(stdout, NULL);
start(config.get());
exit(0);
2019-02-23 17:30:16 +00:00
}
error("(main) internal error\n");
}
void check_if_multiple_wmbus_meters_running()
{
pid_t my_pid = getpid();
vector<int> daemons;
detectProcesses("wmbusmetersd", &daemons);
for (int i : daemons)
{
if (i != my_pid)
{
info("Notice! Wmbusmeters daemon (pid %d) is running and it might hog any wmbus devices.\n", i);
}
}
vector<int> processes;
detectProcesses("wmbusmeters", &processes);
for (int i : processes)
{
if (i != my_pid)
{
info("Notice! Other wmbusmeters (pid %d) is running and it might hog any wmbus devices.\n", i);
}
}
}
void check_for_dead_wmbus_devices(Configuration *config)
{
LOCK_WMBUS_DEVICES(check_for_wmbus_devices);
trace("[MAIN] checking for dead wmbus devices...\n");
bool found_dead_rtlwmbus = false;
vector<WMBus*> not_working;
for (auto &w : wmbus_devices_)
{
if (!w->isWorking())
{
not_working.push_back(w.get());
if ((w->type() == DEVICE_RTLWMBUS || w->type() == DEVICE_RTL433)
&& !w->serialOverride())
{
found_dead_rtlwmbus = true;
}
2020-10-05 17:47:58 +00:00
string id = w->getDeviceId();
if (id != "") id = "["+id+"]";
notice("Lost %s closing %s%s\n",
w->device().c_str(),
toLowerCaseString(w->type()),
2020-10-05 17:47:58 +00:00
id.c_str());
}
}
if (found_dead_rtlwmbus)
{
// Ok, unfortunately the device ids returned by librtlsdr
// are reshuffled when rtlsdr devices are unplugged!
// So we close all rtlwmbus devices (not overriden)
// and re-init them if an rtlwmbus device is lost.
for (auto &w : wmbus_devices_)
{
if (w->isWorking()
&& (w->type() == DEVICE_RTLWMBUS || w->type() == DEVICE_RTL433)
&& !w->serialOverride())
{
not_working.push_back(w.get());
string id = w->getDeviceId();
if (id != "") id = "["+id+"]";
notice("Force closing %s %s%s\n",
w->device().c_str(),
toLowerCaseString(w->type()),
id.c_str());
w->close();
}
}
}
for (auto w : not_working)
{
auto i = wmbus_devices_.begin();
while (i != wmbus_devices_.end())
{
if (w == (*i).get())
{
// The erased shared_ptr will delete the WMBus object.
wmbus_devices_.erase(i);
break;
}
i++;
}
}
if (wmbus_devices_.size() == 0)
{
if (config->single_device_override)
{
if (!config->simulation_found)
{
// Expect stdin/file to work.
// Simulation is special since it will self stop the serial manager.
serial_manager_->expectDevicesToWork();
}
}
else
{
if (!printed_warning_)
{
info("No wmbus device detected, waiting for a device to be plugged in.\n");
check_if_multiple_wmbus_meters_running();
printed_warning_ = true;
}
}
}
else
{
printed_warning_ = false;
}
}
shared_ptr<Meter> create_meter(Configuration *config, MeterType type, MeterInfo *mi, const char *keymsg)
{
shared_ptr<Meter> newm;
switch (type)
{
#define X(mname,link,info,type,cname) \
case MeterType::type: \
{ \
newm = create##cname(*mi); \
newm->addConversions(config->conversions); \
verbose("(main) configured \"%s\" \"" #mname "\" \"%s\" %s\n", \
mi->name.c_str(), mi->id.c_str(), keymsg); \
return newm; \
} \
break;
LIST_OF_METERS
#undef X
case MeterType::UNKNOWN:
error("No such meter type \"%s\"\n", mi->type.c_str());
break;
}
return newm;
}
shared_ptr<WMBus> create_wmbus_object(Detected *detected, Configuration *config, shared_ptr<SerialCommunicationManager> manager)
{
shared_ptr<WMBus> wmbus;
2019-11-03 15:31:30 +00:00
shared_ptr<SerialDevice> serial_override;
2019-11-03 15:31:30 +00:00
if (detected->found_tty_override)
2019-11-03 15:31:30 +00:00
{
serial_override = manager->createSerialDeviceFile(detected->specified_device.file,
string("override ")+detected->specified_device.file.c_str());
verbose("(serial) override with devicefile: %s\n", detected->specified_device.file.c_str());
2019-11-03 15:31:30 +00:00
}
switch (detected->found_type)
2019-11-03 15:31:30 +00:00
{
case DEVICE_IM871A:
verbose("(im871a) on %s\n", detected->found_file.c_str());
wmbus = openIM871A(detected->found_file, manager, serial_override);
break;
case DEVICE_AMB8465:
verbose("(amb8465) on %s\n", detected->found_file.c_str());
wmbus = openAMB8465(detected->found_file, manager, serial_override);
break;
case DEVICE_SIMULATION:
verbose("(simulation) in %s\n", detected->found_file.c_str());
wmbus = openSimulator(detected->found_file, manager, serial_override);
break;
2019-10-15 12:32:32 +00:00
case DEVICE_RAWTTY:
verbose("(rawtty) on %s\n", detected->found_file.c_str());
wmbus = openRawTTY(detected->found_file, detected->found_bps, manager, serial_override);
2019-10-20 18:39:12 +00:00
break;
2019-02-25 21:03:20 +00:00
case DEVICE_RTLWMBUS:
{
2019-11-03 15:31:30 +00:00
string command;
string identifier = detected->found_file;
2020-10-05 17:47:58 +00:00
int id = 0;
if (!detected->found_tty_override)
2019-11-03 15:31:30 +00:00
{
id = indexFromRtlSdrName(identifier);
2020-10-05 17:47:58 +00:00
command = "";
if (detected->found_command != "")
{
command = detected->found_command;
identifier = "cmd_"+to_string(detected->specified_device.index);
}
2019-11-03 15:31:30 +00:00
string freq = "868.95M";
if (detected->specified_device.fq != "")
{
freq = detected->specified_device.fq;
2019-11-03 15:31:30 +00:00
}
string prefix = "";
if (config->daemon)
{
2019-11-03 15:31:30 +00:00
prefix = "/usr/bin/";
if (command == "")
{
// Default command is used, check that the binaries are in place.
if (!checkFileExists("/usr/bin/rtl_sdr"))
{
error("(rtlwmbus) error: when starting as daemon, wmbusmeters expects /usr/bin/rtl_sdr to exist!\n");
}
if (!checkFileExists("/usr/bin/rtl_wmbus"))
{
error("(rtlwmbus) error: when starting as daemon, wmbusmeters expects /usr/bin/rtl_wmbus to exist!\n");
}
}
2019-11-03 15:31:30 +00:00
}
if (command == "") {
2020-10-05 17:47:58 +00:00
command = prefix+"rtl_sdr -d "+to_string(id)+" -f "+freq+" -s 1.6e6 - 2>/dev/null | "+prefix+"rtl_wmbus";
2019-11-03 15:31:30 +00:00
}
verbose("(rtlwmbus) using command: %s\n", command.c_str());
2019-02-25 21:03:20 +00:00
}
wmbus = openRTLWMBUS(identifier, command, manager,
[command](){
warning("(rtlwmbus) child process exited! "
"Command was: \"%s\"\n", command.c_str());
2019-11-03 15:31:30 +00:00
},
serial_override);
2019-02-25 21:03:20 +00:00
break;
}
case DEVICE_RTL433:
{
string command;
string identifier = detected->found_file;
int id = 0;
if (!detected->found_tty_override)
{
id = indexFromRtlSdrName(identifier);
command = "";
if (detected->specified_device.command != "")
{
command = detected->specified_device.command;
identifier = "cmd_"+to_string(detected->specified_device.index);
}
string freq = "868.95M";
if (detected->specified_device.fq != "")
{
freq = detected->specified_device.fq;
}
string prefix = "";
if (config->daemon) {
prefix = "/usr/bin/";
if (command == "")
{
// Default command is used, check that the binaries are in place.
if (!checkFileExists("/usr/bin/rtl_433"))
{
error("(rtl433) error: when starting as daemon, wmbusmeters expects /usr/bin/rtl_433 to exist!\n");
}
}
}
if (command == "") {
command = prefix+"rtl_433 -d "+to_string(id)+" -F csv -f "+freq;
}
verbose("(rtl433) using command: %s\n", command.c_str());
}
wmbus = openRTL433(identifier, command, manager,
[command](){
warning("(rtl433) child process exited! "
"Command was: \"%s\"\n", command.c_str());
},
serial_override);
break;
}
2019-11-22 17:53:52 +00:00
case DEVICE_CUL:
{
verbose("(cul) on %s\n", detected->found_file.c_str());
wmbus = openCUL(detected->found_file, manager, serial_override);
2019-11-22 17:53:52 +00:00
break;
}
2019-12-28 10:19:50 +00:00
case DEVICE_D1TC:
{
verbose("(d1tc) on %s\n", detected->found_file.c_str());
wmbus = openD1TC(detected->found_file, manager, serial_override);
2019-12-28 10:19:50 +00:00
break;
}
2020-09-26 11:52:24 +00:00
case DEVICE_RC1180:
{
verbose("(rc1180) on %s\n", detected->found_file.c_str());
wmbus = openRC1180(detected->found_file, manager, serial_override);
2020-09-26 11:52:24 +00:00
break;
}
2020-03-22 13:20:47 +00:00
case DEVICE_WMB13U:
{
verbose("(wmb13u) on %s\n", detected->found_file.c_str());
wmbus = openWMB13U(detected->found_file, manager, serial_override);
2020-03-22 13:20:47 +00:00
break;
}
case DEVICE_UNKNOWN:
warning("(main) internal error! cannot create an unknown device! 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 (detected->found_device_id != "" && !detected->found_tty_override)
2020-09-02 12:36:10 +00:00
{
string did = wmbus->getDeviceId();
if (did != detected->found_device_id && detected->found_type != DEVICE_RTLWMBUS)
{
warning("Not the expected dongle (dongle said %s, you said %s!\n", did.c_str(), detected->found_device_id.c_str());
return NULL;
}
2018-11-01 16:17:23 +00:00
}
return wmbus;
}
2018-11-01 16:17:23 +00:00
shared_ptr<Printer> create_printer(Configuration *config)
{
return shared_ptr<Printer>(new Printer(config->json, config->fields,
config->separator, config->meterfiles, config->meterfiles_dir,
config->use_logfile, config->logfile,
config->telegram_shells,
config->meterfiles_action == MeterFileType::Overwrite,
config->meterfiles_naming,
config->meterfiles_timestamp));
}
void detect_and_configure_wmbus_devices(Configuration *config)
{
check_for_dead_wmbus_devices(config);
bool must_auto_find = false;
for (SpecifiedDevice &specified_device : config->supplied_wmbus_devices)
{
specified_device.handled = false;
if (specified_device.file == "" && specified_device.command == "")
{
// File/tty/command not specified, use auto scan later to find actual device file/tty.
must_auto_find = true;
continue;
}
if (specified_device.command != "")
{
string identifier = "cmd_"+to_string(specified_device.index);
shared_ptr<SerialDevice> sd = serial_manager_->lookup(identifier);
if (sd != NULL)
{
debug("(main) command %s already configured\n", identifier.c_str());
specified_device.handled = true;
continue;
}
Detected detected = detectWMBusDeviceWithCommand(specified_device, serial_manager_);
specified_device.handled = true;
open_wmbus_device_and_set_linkmodes(config, "config", &detected);
}
if (specified_device.file != "")
{
shared_ptr<SerialDevice> sd = serial_manager_->lookup(specified_device.file);
if (sd != NULL)
{
debug("(main) %s already configured\n", sd->device().c_str());
specified_device.handled = true;
continue;
}
if (simulation_files_.count(specified_device.file) > 0)
{
debug("(main) %s already configured as simulation\n", specified_device.file.c_str());
specified_device.handled = true;
continue;
}
if (do_not_open_file_again_.count(specified_device.file) > 0)
{
// This was stdin/file, it should only be opened once.
debug("(MAIN) ignoring handled file %s\n", specified_device.file.c_str());
specified_device.handled = true;
continue;
}
Detected detected = detectWMBusDeviceWithFile(specified_device, serial_manager_);
if (detected.found_type == DEVICE_UNKNOWN)
{
warning("Warning! Unknown device %s\n", specified_device.str().c_str());
}
if (detected.specified_device.is_stdin || detected.specified_device.is_file || detected.specified_device.is_simulation)
{
// Only read stdin and files once!
do_not_open_file_again_.insert(specified_device.file);
}
open_wmbus_device_and_set_linkmodes(config, "config", &detected);
}
specified_device.handled = true;
}
if (config->use_auto_detect || must_auto_find)
{
perform_auto_scan_of_devices(config);
}
for (SpecifiedDevice &specified_device : config->supplied_wmbus_devices)
{
if (!specified_device.handled)
{
time_t last_alarm = specified_device.last_alarm;
time_t now = time(NULL);
if (now - last_alarm > 10)
{
specified_device.last_alarm = now;
logAlarm("device_not_found", specified_device.str());
}
}
}
}
bool find_specified_device_and_update_detected(Configuration *c, Detected *d)
{
// First look for exact type+id match.
for (SpecifiedDevice & sd : c->supplied_wmbus_devices)
{
if (sd.file == "" && sd.id != "" && sd.id == d->found_device_id && toWMBusDeviceType(sd.type) == d->found_type)
{
sd.handled = true;
d->specified_device = sd;
debug("(main) found specified device (%s) that matches detected device (%s)\n",
sd.str().c_str(),
d->str().c_str());
return true;
}
}
for (SpecifiedDevice & sd : c->supplied_wmbus_devices)
{
if (sd.file == "" && sd.id == "" && toWMBusDeviceType(sd.type) == d->found_type)
{
sd.handled = true;
d->specified_device = sd;
debug("(main) found specified device (%s) that matches detected device (%s)\n",
sd.str().c_str(),
d->str().c_str());
return true;
}
}
return false;
}
void list_shell_envs(Configuration *config, string meter_type)
{
string ignore1, ignore2, ignore3;
vector<string> envs;
Telegram t;
MeterInfo mi;
shared_ptr<Meter> meter = create_meter(config, toMeterType(meter_type), &mi, "");
meter->printMeter(&t,
&ignore1,
&ignore2, config->separator,
&ignore3,
&envs,
&config->jsons,
&config->selected_fields);
2020-09-13 14:55:22 +00:00
for (auto &e : envs)
{
int p = e.find('=');
string key = e.substr(0,p);
printf("%s\n", key.c_str());
}
}
void list_fields(Configuration *config, string meter_type)
{
MeterInfo mi;
shared_ptr<Meter> meter = create_meter(config, toMeterType(meter_type), &mi, "");
int width = 0;
for (auto &p : meter->prints())
{
if ((int)p.field_name.size() > width) width = p.field_name.size();
}
string id = padLeft("id", width);
printf("%s The meter id number.\n", id.c_str());
string name = padLeft("name", width);
printf("%s Your name for the meter.\n", name.c_str());
string type = padLeft("type", width);
printf("%s Meter type/driver.\n", type.c_str());
string timestamp = padLeft("timestamp", width);
printf("%s Timestamp when wmbusmeters received the telegram.\n", timestamp.c_str());
for (auto &p : meter->prints())
{
if (p.vname == "") continue;
string fn = padLeft(p.field_name, width);
printf("%s %s\n", fn.c_str(), p.help.c_str());
}
}
void list_meters(Configuration *config)
{
#define X(mname,link,info,type,cname) \
if (config->list_meters_search == "" || \
stringFoundCaseIgnored(#info, config->list_meters_search) || \
stringFoundCaseIgnored(#mname, config->list_meters_search)) \
printf("%-14s %s\n", #mname, #info);
LIST_OF_METERS
#undef X
2020-09-13 13:39:09 +00:00
}
void log_start_information(Configuration *config)
2020-09-13 13:39:09 +00:00
{
verbose("(wmbusmeters) version: " VERSION "\n");
2020-09-13 13:39:09 +00:00
if (config->exitafter != 0) {
verbose("(config) wmbusmeters will exit after %d seconds\n", config->exitafter);
2020-09-13 13:39:09 +00:00
}
if (config->meterfiles) {
verbose("(config) store meter files in: \"%s\"\n", config->meterfiles_dir.c_str());
}
for (SpecifiedDevice &specified_device : config->supplied_wmbus_devices)
2020-09-13 13:39:09 +00:00
{
verbose("(config) using device: %s \n", specified_device.str().c_str());
}
verbose("(config) number of meters: %d\n", config->meters.size());
}
void oneshot_check(Configuration *config, Telegram *t, Meter *meter)
{
if (!config->oneshot) return;
if (meter_manager_->hasAllMetersReceivedATelegram())
{
// All meters have received at least one update! Stop!
verbose("(main) all meters have received at least one update, stopping.\n");
serial_manager_->stop();
}
}
void open_wmbus_device_and_set_linkmodes(Configuration *config, string how, Detected *detected)
{
LOCK_WMBUS_DEVICES(open_wmbus_device);
if (detected->found_type == DEVICE_UNKNOWN)
{
// This is a manual config, lets detect and check the device properly.
AccessCheck ac = detectDevice(detected, serial_manager_);
if (ac != AccessCheck::AccessOK)
{
error("Could not open device %s\n", detected->specified_device.str().c_str());
}
}
LinkModeSet lms {};
string linkmodes = detected->specified_device.linkmodes;
if (linkmodes != "")
{
lms = parseLinkModes(detected->specified_device.linkmodes);
debug("(main) parsed specified device link modes %s to %s\n",
linkmodes.c_str(), lms.hr().c_str());
2019-11-03 15:31:30 +00:00
}
else
{
lms = config->linkmodes;
debug("(main) using globally set link modes %s\n",
lms.hr().c_str());
}
2020-09-07 08:36:39 +00:00
assert(lms.bits() != 0);
string using_link_modes = lms.hr();
2020-10-05 17:47:58 +00:00
string id = detected->found_device_id.c_str();
if (id != "") id = "["+id+"]";
string fq = detected->specified_device.fq;
if (fq != "") fq = " using fq "+fq;
string file = detected->found_file.c_str();
if (file != "") file = " on "+file;
string cmd = detected->found_command.c_str();
if (cmd != "")
{
cmd = " using CMD("+cmd+")";
}
2020-10-05 17:47:58 +00:00
string started = tostrprintf("Started %s %s%s%s listening on %s%s%s\n",
how.c_str(),
toLowerCaseString(detected->found_type),
2020-10-05 17:47:58 +00:00
id.c_str(),
file.c_str(),
using_link_modes.c_str(),
fq.c_str(),
cmd.c_str());
2020-09-13 13:39:09 +00:00
// A newly plugged in device has been manually configured or automatically detected! Start using it!
if (config->use_auto_detect || detected->found_type != DEVICE_SIMULATION)
2020-09-13 14:55:22 +00:00
{
notice("%s", started.c_str());
2020-09-13 14:55:22 +00:00
}
else
{
// Hide the started when running simulations.
verbose("%s", started.c_str());
2020-09-13 14:55:22 +00:00
}
shared_ptr<WMBus> w = create_wmbus_object(detected, config, serial_manager_);
if (w == NULL) return;
wmbus_devices_.push_back(w);
2020-09-13 13:39:09 +00:00
WMBus *wmbus = wmbus_devices_.back().get();
2020-09-25 21:56:50 +00:00
2020-09-26 11:52:24 +00:00
// By default, reset your dongle once every 23 hours,
// so that the reset is not at the exact same time every day.
int regular_reset = 23*3600;
2020-09-25 21:56:50 +00:00
if (config->resetafter != 0) regular_reset = config->resetafter;
wmbus->setResetInterval(regular_reset);
verbose("(main) regular reset of %s %s%s will happen every %d seconds\n",
toString(detected->found_type), file.c_str(), cmd.c_str(), regular_reset);
wmbus->setLinkModes(lms);
/*
LinkModeCalculationResult lmcr = calculateLinkModes(config, wmbus.get(), link_modes_matter);
if (lmcr.type != LinkModeCalculationResultType::Success)
{
error("%s\n", lmcr.msg.c_str());
}*/
2020-09-25 21:56:50 +00:00
2020-09-18 19:22:08 +00:00
bool simulated = false;
if (detected->found_type == DEVICE_SIMULATION)
2020-09-18 18:05:59 +00:00
{
2020-09-18 19:22:08 +00:00
simulated = true;
debug("(main) added %s to files\n", detected->found_file.c_str());
simulation_files_.insert(detected->specified_device.file);
2020-09-18 18:05:59 +00:00
}
2020-09-18 19:22:08 +00:00
wmbus->onTelegram([&, simulated](vector<uchar> data){return meter_manager_->handleTelegram(data, simulated);});
wmbus->setTimeout(config->alarm_timeout, config->alarm_expected_activity);
2020-09-13 13:39:09 +00:00
}
void perform_auto_scan_of_devices(Configuration *config)
{
perform_auto_scan_of_serial_devices(config);
perform_auto_scan_of_swradio_devices(config);
}
2020-09-13 13:39:09 +00:00
void perform_auto_scan_of_serial_devices(Configuration *config)
2020-09-08 12:55:01 +00:00
{
2020-09-13 13:39:09 +00:00
// Enumerate all serial devices that might connect to a wmbus device.
vector<string> ttys = serial_manager_->listSerialTTYs();
2020-09-07 08:36:39 +00:00
2020-09-08 12:55:01 +00:00
// Did a non-wmbus-device get unplugged? Then remove it from the known-not-wmbus-device set.
remove_lost_serial_devices_from_ignore_list(ttys);
for (string& tty : ttys)
{
trace("[MAIN] serial device %s\n", tty.c_str());
if (not_serial_wmbus_devices_.count(tty) > 0)
2020-09-07 08:36:39 +00:00
{
trace("[MAIN] skipping already probed not wmbus serial device %s\n", tty.c_str());
2020-09-07 08:36:39 +00:00
continue;
}
shared_ptr<SerialDevice> sd = serial_manager_->lookup(tty);
if (!sd)
{
debug("(main) device %s not currently used, detect contents...\n", tty.c_str());
// This serial device is not in use, but is there a device on it?
Detected detected = detectWMBusDeviceOnTTY(tty, serial_manager_);
if (detected.found_type != DEVICE_UNKNOWN)
{
// See if we had a specified device without a file,
// that matches this detected device.
find_specified_device_and_update_detected(config, &detected);
open_wmbus_device_and_set_linkmodes(config, "auto", &detected);
}
else
2020-09-07 08:36:39 +00:00
{
// This serial device was something that we could not recognize.
// A modem, an android phone, a teletype Model 33, etc....
// Mark this serial device as unknown, to avoid repeated detection attempts.
not_serial_wmbus_devices_.insert(tty);
verbose("(main) ignoring %s, it does not respond as any of the supported wmbus devices.\n", tty.c_str());
2020-09-07 08:36:39 +00:00
}
}
}
}
2020-09-13 13:39:09 +00:00
void perform_auto_scan_of_swradio_devices(Configuration *config)
{
// Enumerate all swradio devices, that can be used.
2020-10-05 17:47:58 +00:00
vector<string> names = listRtlSdrDevices();
2020-09-13 13:39:09 +00:00
// Did an unavailable swradio-device get unplugged? Then remove it from the known-not-swradio-device set.
2020-10-05 17:47:58 +00:00
remove_lost_swradio_devices_from_ignore_list(names);
2020-09-13 13:39:09 +00:00
2020-10-05 17:47:58 +00:00
for (string& name : names)
2020-09-13 13:39:09 +00:00
{
debug("[MAIN] rtlsdr device %s\n", name.c_str());
2020-10-05 17:47:58 +00:00
if (not_swradio_wmbus_devices_.count(name) > 0)
2020-09-13 13:39:09 +00:00
{
debug("[MAIN] skipping already probed rtlsdr %s\n", name.c_str());
2020-09-13 13:39:09 +00:00
continue;
}
2020-10-05 17:47:58 +00:00
shared_ptr<SerialDevice> sd = serial_manager_->lookup(name);
if (!sd)
2020-09-13 13:39:09 +00:00
{
2020-10-05 17:47:58 +00:00
debug("(main) rtlsdr device %s not currently used.\n", name.c_str());
2020-09-13 13:39:09 +00:00
Detected detected;
detected.setSpecifiedDeviceAsAuto();
2020-10-05 17:47:58 +00:00
AccessCheck ac = detectRTLSDR(name, &detected);
2020-09-13 13:39:09 +00:00
if (ac != AccessCheck::AccessOK)
{
// We cannot access this swradio device.
2020-10-05 17:47:58 +00:00
not_swradio_wmbus_devices_.insert(name);
verbose("(main) ignoring rtlsdr %s since it is unavailable.\n", name.c_str());
2020-09-13 13:39:09 +00:00
}
else
{
2020-10-05 17:47:58 +00:00
// Use the name as the file.
detected.found_file = name;
find_specified_device_and_update_detected(config, &detected);
open_wmbus_device_and_set_linkmodes(config, "auto", &detected);
2020-09-13 13:39:09 +00:00
}
}
}
}
void regular_checkup(Configuration *config)
2020-09-08 12:55:01 +00:00
{
if (serial_manager_ && config)
{
detect_and_configure_wmbus_devices(config);
}
2020-09-08 12:55:01 +00:00
{
LOCK_WMBUS_DEVICES(regular_checkup);
2020-09-08 12:55:01 +00:00
for (auto &w : wmbus_devices_)
{
if (w->isWorking())
2020-09-13 14:55:22 +00:00
{
w->checkStatus();
2020-09-13 14:55:22 +00:00
}
}
}
}
void remove_lost_serial_devices_from_ignore_list(vector<string> &devices)
{
vector<string> to_be_removed;
// Iterate over the devices known to NOT be wmbus devices.
for (const string& nots : not_serial_wmbus_devices_)
{
auto i = std::find(devices.begin(), devices.end(), nots);
if (i == devices.end())
2020-09-08 12:55:01 +00:00
{
// The device has been removed, therefore
// we have to forget that the device was not a wmbus device.
// Since next time someone plugs in a device, it might be a different
// one getting the same /dev/ttyUSBxx
to_be_removed.push_back(nots);
2020-09-08 12:55:01 +00:00
}
}
for (string& r : to_be_removed)
2020-09-08 12:55:01 +00:00
{
not_serial_wmbus_devices_.erase(r);
2020-09-08 12:55:01 +00:00
}
}
void remove_lost_swradio_devices_from_ignore_list(vector<string> &devices)
{
vector<string> to_be_removed;
2019-11-03 22:43:29 +00:00
// Iterate over the devices known to NOT be wmbus devices.
for (const string& nots : not_swradio_wmbus_devices_)
{
auto i = std::find(devices.begin(), devices.end(), nots);
if (i == devices.end())
{
// The device has been removed, therefore
// we have to forget that the device was not a wmbus device.
// Since next time someone plugs in a device, it might be a different
// one getting the same /dev/ttyUSBxx
to_be_removed.push_back(nots);
}
}
for (string& r : to_be_removed)
{
not_swradio_wmbus_devices_.erase(r);
}
}
void setup_log_file(Configuration *config)
{
if (config->use_logfile)
{
verbose("(wmbusmeters) using log file %s\n", config->logfile.c_str());
bool ok = enableLogfile(config->logfile, config->daemon);
if (!ok) {
if (config->daemon) {
warning("Could not open log file, will use syslog instead.\n");
} else {
error("Could not open log file.\n");
}
}
}
else
{
disableLogfile();
}
}
void setup_meters(Configuration *config, MeterManager *manager)
{
for (auto &m : config->meters)
{
const char *keymsg = (m.key[0] == 0) ? "not-encrypted" : "encrypted";
auto meter = create_meter(config, toMeterType(m.type), &m, keymsg);
manager->addMeter(meter);
}
}
bool start(Configuration *config)
{
// Configure where the logging information should end up.
setup_log_file(config);
if (config->meters.size() == 0 && !config->linkmodes_configured)
{
error("No meters supplied. You must supply which link modes to listen to. Eg. --listento=<modes>\n");
}
// Configure settings.
warningSilenced(config->silence);
verboseEnabled(config->verbose);
logTelegramsEnabled(config->logtelegrams);
debugEnabled(config->debug);
internalTestingEnabled(config->internaltesting);
traceEnabled(config->trace);
2020-09-13 14:55:22 +00:00
stderrEnabled(config->use_stderr_for_log);
setAlarmShells(config->alarm_shells);
log_start_information(config);
// Create the manager monitoring all filedescriptors and invoking callbacks.
2020-09-25 20:27:34 +00:00
serial_manager_ = createSerialCommunicationManager(config->exitafter, true);
// If our software unexpectedly exits, then stop the manager, to try
// to achive a nice shutdown.
2020-09-07 08:36:39 +00:00
onExit(call(serial_manager_.get(),stop));
//serial_manager_->eachEventLooping([]() { check_statuses(); });
// Create the printer object that knows how to translate
// telegrams into json, fields that are written into log files
// or sent to shell invocations.
printer_ = create_printer(config);
2020-09-07 08:36:39 +00:00
meter_manager_ = createMeterManager();
// Create the Meter objects from the configuration.
setup_meters(config, meter_manager_.get());
2020-09-07 08:36:39 +00:00
// Attach a received-telegram-callback from the meter and
// attach it to the printer.
2020-09-07 08:36:39 +00:00
meter_manager_->forEachMeter(
[&](Meter *meter)
{
meter->onUpdate([&](Telegram *t,Meter *meter)
{
printer_->print(t, meter, &config->jsons, &config->selected_fields);
oneshot_check(config, t, meter);
2020-09-07 08:36:39 +00:00
});
}
);
2020-09-07 08:36:39 +00:00
serial_manager_->startEventLoop();
// Detect and initialize any devices.
// Future changes are triggered through this callback.
printed_warning_ = true;
detect_and_configure_wmbus_devices(config);
if (wmbus_devices_.size() == 0)
{
notice("No wmbus device detected, waiting for a device to be plugged in.\n");
check_if_multiple_wmbus_meters_running();
}
2020-09-06 08:41:04 +00:00
// Every 2 seconds detect any plugged in or removed wmbus devices.
2020-09-07 08:36:39 +00:00
serial_manager_->startRegularCallback("HOT_PLUG_DETECTOR",
2,
[&](){
regular_checkup(config);
});
if (config->daemon)
{
notice("(wmbusmeters) waiting for telegrams\n");
}
2020-09-08 12:55:01 +00:00
if (!meter_manager_->hasMeters())
{
2020-09-25 15:02:42 +00:00
notice("No meters configured. Printing id:s of all telegrams heard!\n");
2020-09-08 12:55:01 +00:00
meter_manager_->onTelegram([](vector<uchar> frame) {
Telegram t;
MeterKeys mk;
t.parserNoWarnings(); // Try a best effort parse, do not print any warnings.
t.parse(frame, &mk);
t.print();
t.explainParse("(wmbus)",0);
logTelegram("(wmbus)", t.frame, 0, 0);
return true;
});
}
for (auto &w : wmbus_devices_)
{
// Real devices do nothing, but the simulator device will simulate.
w->simulate();
}
// This thread now sleeps waiting for the serial communication manager to stop.
// The manager has already started one thread that performs select and then callbacks
// to decoding the telegrams, finally invoking the printer.
// The regular callback invoked to detect changes in the wmbus devices and
// the alarm checks, is started in a separate thread.
2020-09-07 08:36:39 +00:00
serial_manager_->waitForStop();
if (config->daemon)
{
notice("(wmbusmeters) shutting down\n");
}
// Destroy any remaining allocated objects.
2020-09-07 08:36:39 +00:00
wmbus_devices_.clear();
meter_manager_->removeAllMeters();
printer_.reset();
2020-09-07 08:36:39 +00:00
serial_manager_.reset();
restoreSignalHandlers();
return gotHupped();
2017-08-09 10:00:11 +00:00
}
2017-09-02 21:26:57 +00:00
void start_daemon(string pid_file, string device_override, string listento_override)
{
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.
write_pid(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.
write_pid(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);
if (open("/dev/null", O_RDONLY) == -1) {
error("Failed to reopen stdin while daemonising (errno=%d)",errno);
}
if (open("/dev/null", O_WRONLY) == -1) {
error("Failed to reopen stdout while daemonising (errno=%d)",errno);
}
if (open("/dev/null", O_RDWR) == -1) {
error("Failed to reopen stderr while daemonising (errno=%d)",errno);
}
start_using_config_files("", true, device_override, listento_override);
}
void start_using_config_files(string root, bool is_daemon, string device_override, string listento_override)
{
bool restart = false;
do
{
shared_ptr<Configuration> config = loadConfiguration(root, device_override, listento_override);
config->daemon = is_daemon;
restart = start(config.get());
if (restart)
{
notice("(wmbusmeters) HUP received, restarting and reloading config files.\n");
}
}
while (restart);
}
2020-09-18 18:05:59 +00:00
void write_pid(string pid_file, int pid)
2020-09-18 18:05:59 +00:00
{
FILE *pidf = fopen(pid_file.c_str(), "w");
if (!pidf) {
error("Could not open pid file \"%s\" for writing!\n", pid_file.c_str());
2020-09-18 18:05:59 +00:00
}
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());
2020-09-18 18:05:59 +00:00
}
notice("(wmbusmeters) started %s\n", pid_file.c_str());
2020-09-18 18:05:59 +00:00
}
fclose(pidf);
return;
2020-09-18 18:05:59 +00:00
}