wmbusmeters/src/admin.cc

653 wiersze
15 KiB
C++
Czysty Zwykły widok Historia

2020-04-03 09:58:19 +00:00
/*
Copyright (C) 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/>.
*/
#include<curses.h>
#include<menu.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
2020-06-29 14:35:58 +00:00
#include <syslog.h>
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
#include"serial.h"
2020-05-30 16:45:29 +00:00
#include"shell.h"
2020-05-09 19:56:37 +00:00
#include"wmbus.h"
2020-04-03 09:58:19 +00:00
#define BG_PAIR 1
#define WIN_PAIR 2
#define TITLE_PAIR 3
#define HILIGHT_PAIR 4
#include <menu.h>
2020-05-30 16:45:29 +00:00
bool running_as_root_ = false;
2020-05-09 19:56:37 +00:00
#define LIST_OF_MAIN_MENU \
2020-06-29 14:35:58 +00:00
X(DETECT_WMBUS_RECEIVERS, "Detect wmbus receiver") \
X(RESET_WMBUS_RECEIVERS, "Reset wmbus receiver") \
2020-05-09 19:56:37 +00:00
X(LISTEN_FOR_METERS, "Listen for meters") \
X(EDIT_CONFIG, "Edit config") \
X(EDIT_METERS, "Edit meters") \
X(STOP_DAEMON, "Stop daemon") \
X(START_DAEMON, "Start daemon") \
X(EXIT_ADMIN, "Exit")
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
enum class MainMenuType {
#define X(name,description) name,
LIST_OF_MAIN_MENU
#undef X
};
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
const char *main_menu[] = {
#define X(name,description) description,
LIST_OF_MAIN_MENU
#undef X
(char *)NULL,
};
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
#define LIST_OF_WMBUS_RECEIVERS \
X(AMB8465, "amb8465") \
X(CUL, "cul") \
X(IM871A, "im871a")
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
enum class ReceiversType {
#define X(name,description) name,
LIST_OF_WMBUS_RECEIVERS
#undef X
};
const char *receivers_menu[] = {
#define X(name,description) description,
LIST_OF_WMBUS_RECEIVERS
#undef X
(char *)NULL,
};
2020-04-03 09:58:19 +00:00
2020-05-30 16:45:29 +00:00
bool detectIfRoot();
void detectProcesses(string cmd, vector<int> *pids);
2020-06-29 14:35:58 +00:00
void detectWMBUSReceiver();
void resetWMBUSReceiver();
2020-05-09 19:56:37 +00:00
void probeFor(string type, AccessCheck(*func)(string,SerialCommunicationManager*));
void printAt(WINDOW *win, int y, int x, const char *str, chtype color);
void printMiddle(WINDOW *win, int y, int width, const char *str, chtype color);
2020-05-30 16:45:29 +00:00
void alwaysOnScreen();
2020-05-09 19:56:37 +00:00
int selectFromMenu(const char *title, const char *menu[]);
2020-06-29 14:35:58 +00:00
int selectFromMenu(const char *title, vector<string> menu);
2020-05-30 16:45:29 +00:00
void displayInformationAndWait(string title, vector<string> entries, int px=-1, int py=-1);
void displayInformationNoWait(WINDOW **win, string title, vector<string> entries, int px=-1, int py=-1);
2020-05-25 17:05:45 +00:00
2020-06-29 14:35:58 +00:00
void notImplementedYet(string msg);
2020-05-25 17:05:45 +00:00
int screen_width, screen_height;
unique_ptr<SerialCommunicationManager> handler;
2020-05-09 19:56:37 +00:00
2020-05-30 16:45:29 +00:00
WINDOW *status_window;
WINDOW *serial_ports_window;
WINDOW *processes_window;
int main(int argc, char **argv)
2020-05-09 19:56:37 +00:00
{
2020-05-30 16:45:29 +00:00
if (argc == 2 && !strcmp(argv[1], "--debug"))
{
debugEnabled(true);
2020-06-29 14:35:58 +00:00
setlogmask(LOG_UPTO (LOG_INFO));
openlog("wmbusmeters-admin", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
enableSyslog();
2020-05-30 16:45:29 +00:00
}
running_as_root_ = detectIfRoot();
2020-05-25 17:05:45 +00:00
handler = createSerialCommunicationManager(0, 0, false);
2020-04-03 09:58:19 +00:00
initscr();
2020-05-09 19:56:37 +00:00
getmaxyx(stdscr, screen_height, screen_width);
2020-04-03 09:58:19 +00:00
start_color();
cbreak();
curs_set(0);
noecho();
keypad(stdscr, TRUE);
init_pair(BG_PAIR, COLOR_WHITE, COLOR_BLUE);
init_pair(WIN_PAIR, COLOR_BLACK, COLOR_WHITE);
init_pair(TITLE_PAIR, COLOR_WHITE, COLOR_CYAN);
init_pair(HILIGHT_PAIR, COLOR_WHITE, COLOR_RED);
wbkgd(stdscr, COLOR_PAIR(BG_PAIR));
2020-05-09 19:56:37 +00:00
bool running = true;
2020-05-30 16:45:29 +00:00
alwaysOnScreen();
2020-05-09 19:56:37 +00:00
do
{
int c = selectFromMenu("wmbusmeters admin", main_menu);
switch (static_cast<MainMenuType>(c))
{
case MainMenuType::DETECT_WMBUS_RECEIVERS:
2020-06-29 14:35:58 +00:00
detectWMBUSReceiver();
break;
case MainMenuType::RESET_WMBUS_RECEIVERS:
resetWMBUSReceiver();
2020-05-09 19:56:37 +00:00
break;
case MainMenuType::LISTEN_FOR_METERS:
2020-06-29 14:35:58 +00:00
notImplementedYet("Listen for meters");
2020-05-09 19:56:37 +00:00
break;
case MainMenuType::EDIT_CONFIG:
2020-06-29 14:35:58 +00:00
notImplementedYet("Edit config");
2020-05-09 19:56:37 +00:00
break;
case MainMenuType::EDIT_METERS:
2020-06-29 14:35:58 +00:00
notImplementedYet("Edit meters");
2020-05-09 19:56:37 +00:00
break;
case MainMenuType::STOP_DAEMON:
2020-06-29 14:35:58 +00:00
notImplementedYet("Stop daemon");
2020-05-09 19:56:37 +00:00
break;
case MainMenuType::START_DAEMON:
2020-06-29 14:35:58 +00:00
notImplementedYet("Start daemon");
2020-05-09 19:56:37 +00:00
break;
case MainMenuType::EXIT_ADMIN:
running = false;
break;
}
} while (running);
endwin();
}
void printAt(WINDOW *win, int y, int x, const char *str, chtype color)
{
wattron(win, color);
mvwprintw(win, y, x, "%s", str);
wattroff(win, color);
refresh();
}
void printMiddle(WINDOW *win, int y, int width, const char *str, chtype color)
{
int len = strlen(str);
int wh, ww;
getyx(win, wh, ww);
((void)wh);
int x = (width-len)/2;
wattron(win, color);
mvwprintw(win, y, x, "%s", str);
wattroff(win, color);
refresh();
}
int countEntries(const char *entries[])
{
int i = 0;
for (; entries[i] != 0; ++i);
return i+1;
}
int maxWidth(const char *entries[])
{
int max = 0;
for (int i=0; entries[i] != 0; ++i)
{
int n = strlen(entries[i]);
if (max < n) max = n;
}
return max;
}
int maxWidth(vector<string> entries)
{
int max = 0;
for (string& s : entries)
{
int n = s.length();
if (max < n) max = n;
}
return max;
}
2020-05-30 16:45:29 +00:00
void alwaysOnScreen()
2020-05-25 17:05:45 +00:00
{
2020-05-30 17:12:05 +00:00
static uchar ticktock = 0;
2020-05-30 16:45:29 +00:00
vector<string> info;
2020-05-30 17:12:05 +00:00
ticktock++;
2020-05-30 16:45:29 +00:00
if (running_as_root_ == false)
{
info.push_back("Not running as root!");
info.push_back("Limited functionality.");
info.push_back("----------------------");
}
vector<int> daemons;
detectProcesses("wmbusmetersd", &daemons);
if (daemons.size() == 0)
{
info.push_back("No daemons running.");
}
else
{
for (int i : daemons)
{
info.push_back("Daemon "+to_string(i));
}
}
vector<int> processes;
detectProcesses("wmbusmeters", &processes);
if (processes.size() == 0)
{
}
else
{
for (int i : processes)
{
info.push_back("Process "+to_string(i));
}
}
2020-05-30 17:12:05 +00:00
displayInformationNoWait(&status_window, (ticktock%2==0)?"Status ":"Status.", info, 1, 1);
2020-05-30 16:45:29 +00:00
2020-05-25 17:05:45 +00:00
vector<string> devices = handler->listSerialDevices();
2020-05-30 16:45:29 +00:00
if (devices.size() == 0)
{
devices.push_back("No serial ports found!");
}
//info.insert(info.end(), devices.begin(), devices.end());
displayInformationNoWait(&serial_ports_window, "Serial ports", devices, 1, 15);
erase();
redrawwin(status_window);
redrawwin(serial_ports_window);
2020-05-25 17:05:45 +00:00
}
2020-05-09 19:56:37 +00:00
int selectFromMenu(const char *title, const char *entries[])
2020-06-29 14:35:58 +00:00
{
vector<string> menu;
int n_choices = countEntries(entries);
for (int i=0; i<n_choices; ++i)
{
if (entries[i] == NULL) break;
menu.push_back(entries[i]);
}
return selectFromMenu(title, menu);
}
int selectFromMenu(const char *title, vector<string> entries)
2020-05-09 19:56:37 +00:00
{
int selected = -1;
ITEM **menu_items;
int c;
MENU *menu;
WINDOW *frame_window, *menu_window;
int n_choices, i;
2020-06-29 14:35:58 +00:00
n_choices = entries.size()+1;
2020-05-09 19:56:37 +00:00
menu_items = (ITEM **)calloc(n_choices, sizeof(ITEM *));
2020-06-29 14:35:58 +00:00
for(i = 0; i < n_choices-1; ++i)
2020-04-03 09:58:19 +00:00
{
2020-06-29 14:35:58 +00:00
menu_items[i] = new_item(entries[i].c_str(), NULL);
2020-05-09 19:56:37 +00:00
}
2020-06-29 14:35:58 +00:00
menu_items[n_choices-1] = NULL;
2020-05-09 19:56:37 +00:00
menu = new_menu(menu_items);
int mw = 0;
int mh = 0;
scale_menu(menu, &mh, &mw);
int w = mw+2;
int h = mh+4;
if (w-2 < (int)strlen(title))
{
w = (int)strlen(title)+2;
2020-04-03 09:58:19 +00:00
}
2020-05-09 19:56:37 +00:00
int x = screen_width/2-w/2;
int y = screen_height/2-h/2;
frame_window = newwin(h, w, y, x);
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
int mx = (w-mw)/2;
int my = 3;
menu_window = derwin(frame_window, mh, mw, my, mx);
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
set_menu_fore(menu, COLOR_PAIR(HILIGHT_PAIR));
set_menu_back(menu, COLOR_PAIR(WIN_PAIR));
set_menu_grey(menu, COLOR_PAIR(3));
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
keypad(frame_window, TRUE);
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
set_menu_win(menu, frame_window);
set_menu_sub(menu, menu_window);
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
set_menu_mark(menu, ">");
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
box(frame_window, 0, 0);
wbkgd(frame_window, COLOR_PAIR(WIN_PAIR));
2020-04-03 09:58:19 +00:00
2020-05-09 19:56:37 +00:00
printMiddle(frame_window, 1, w, title, COLOR_PAIR(WIN_PAIR));
mvwaddch(frame_window, 2, 0, ACS_LTEE);
mvwhline(frame_window, 2, 1, ACS_HLINE, 38);
mvwaddch(frame_window, 2, w-1, ACS_RTEE);
2020-04-03 09:58:19 +00:00
refresh();
2020-05-09 19:56:37 +00:00
post_menu(menu);
wrefresh(frame_window);
2020-04-03 09:58:19 +00:00
2020-05-30 16:45:29 +00:00
alwaysOnScreen();
2020-05-25 17:05:45 +00:00
wtimeout(frame_window, 1000);
2020-04-03 09:58:19 +00:00
bool running = true;
do
{
2020-05-09 19:56:37 +00:00
c = wgetch(frame_window);
ITEM *cur = current_item(menu);
selected = item_index(cur);
2020-04-03 09:58:19 +00:00
switch(c)
{
2020-05-25 17:05:45 +00:00
case ERR:
2020-05-30 16:45:29 +00:00
alwaysOnScreen();
redrawwin(frame_window);
2020-05-25 17:05:45 +00:00
break;
2020-04-03 09:58:19 +00:00
case KEY_DOWN:
2020-05-09 19:56:37 +00:00
if (selected < n_choices-2)
{
menu_driver(menu, REQ_DOWN_ITEM);
}
else
{
menu_driver(menu, REQ_FIRST_ITEM);
}
2020-04-03 09:58:19 +00:00
break;
case KEY_UP:
2020-05-09 19:56:37 +00:00
if (selected > 0)
{
menu_driver(menu, REQ_UP_ITEM);
}
else
{
menu_driver(menu, REQ_LAST_ITEM);
}
2020-04-03 09:58:19 +00:00
break;
case '\n':
running = false;
break;
}
2020-05-09 19:56:37 +00:00
wrefresh(frame_window);
2020-04-03 09:58:19 +00:00
} while (running);
2020-05-09 19:56:37 +00:00
unpost_menu(menu);
free_menu(menu);
delwin(frame_window);
erase();
refresh();
2020-04-03 09:58:19 +00:00
for(i = 0; i < n_choices; ++i)
{
2020-05-09 19:56:37 +00:00
free_item(menu_items[i]);
2020-04-03 09:58:19 +00:00
}
2020-05-09 19:56:37 +00:00
return selected;
2020-04-03 09:58:19 +00:00
}
2020-05-30 16:45:29 +00:00
void displayInformationAndWait(string title, vector<string> entries, int px, int py)
2020-04-03 09:58:19 +00:00
{
2020-05-09 19:56:37 +00:00
WINDOW *frame_window;
2020-04-03 09:58:19 +00:00
2020-05-30 16:45:29 +00:00
alwaysOnScreen();
2020-05-09 19:56:37 +00:00
int mw = maxWidth(entries)+1;
int mh = entries.size();
int w = mw+2;
int h = mh+4;
if (w-2 < (int)title.length())
2020-04-03 09:58:19 +00:00
{
2020-05-09 19:56:37 +00:00
w = (int)title.length()+2;
2020-04-03 09:58:19 +00:00
}
2020-05-09 19:56:37 +00:00
int x = screen_width/2-w/2;
int y = screen_height/2-h/2;
2020-05-25 17:05:45 +00:00
if (px != -1)
{
x = px;
}
if (py != -1)
{
y = py;
}
2020-05-09 19:56:37 +00:00
frame_window = newwin(h, w, y, x);
keypad(frame_window, TRUE);
box(frame_window, 0, 0);
wbkgd(frame_window, COLOR_PAIR(WIN_PAIR));
printMiddle(frame_window, 1, w, title.c_str(), COLOR_PAIR(WIN_PAIR));
mvwaddch(frame_window, 2, 0, ACS_LTEE);
mvwhline(frame_window, 2, 1, ACS_HLINE, 38);
mvwaddch(frame_window, 2, w-1, ACS_RTEE);
2020-05-30 16:45:29 +00:00
//refresh();
2020-05-09 19:56:37 +00:00
int r = 3;
for (string e : entries)
2020-04-03 09:58:19 +00:00
{
2020-05-09 19:56:37 +00:00
printAt(frame_window, r, 1, e.c_str(), COLOR_PAIR(WIN_PAIR));
r++;
2020-04-03 09:58:19 +00:00
}
2020-05-09 19:56:37 +00:00
wrefresh(frame_window);
2020-05-30 16:45:29 +00:00
wtimeout(frame_window, 1000);
2020-05-09 19:56:37 +00:00
bool running = true;
do
2020-04-03 09:58:19 +00:00
{
2020-05-09 19:56:37 +00:00
int c = wgetch(frame_window);
switch(c)
{
2020-05-30 16:45:29 +00:00
case ERR:
alwaysOnScreen();
redrawwin(frame_window);
break;
2020-05-09 19:56:37 +00:00
case 27:
case '\n':
running = false;
break;
}
wrefresh(frame_window);
} while (running);
delwin(frame_window);
erase();
refresh();
}
2020-05-30 16:45:29 +00:00
void displayInformationNoWait(WINDOW **winp, string title, vector<string> entries, int px, int py)
{
WINDOW *win = *winp;
if (win != NULL)
{
delwin(win);
*winp = NULL;
}
int mw = maxWidth(entries)+1;
int mh = entries.size();
int w = mw+2;
int h = mh+4;
if (w-2 < (int)title.length())
{
w = (int)title.length()+2;
}
int x = screen_width/2-w/2;
int y = screen_height/2-h/2;
if (px != -1)
{
x = px;
}
if (py != -1)
{
y = py;
}
win = newwin(h, w, y, x);
*winp = win;
box(win, 0, 0);
wbkgd(win, COLOR_PAIR(WIN_PAIR));
printMiddle(win, 1, w, title.c_str(), COLOR_PAIR(WIN_PAIR));
mvwaddch(win, 2, 0, ACS_LTEE);
mvwhline(win, 2, 1, ACS_HLINE, 38);
mvwaddch(win, 2, w-1, ACS_RTEE);
// refresh();
int r = 3;
for (string e : entries)
{
printAt(win, r, 1, e.c_str(), COLOR_PAIR(WIN_PAIR));
r++;
}
wrefresh(win);
}
2020-06-29 14:35:58 +00:00
void detectWMBUSReceiver()
2020-05-09 19:56:37 +00:00
{
int c = selectFromMenu("Select your wmbus radio device", receivers_menu);
switch (static_cast<ReceiversType>(c))
2020-04-03 09:58:19 +00:00
{
2020-05-09 19:56:37 +00:00
case ReceiversType::AMB8465:
probeFor("amb8465", detectAMB8465);
break;
case ReceiversType::CUL:
probeFor("cul", detectCUL);
break;
case ReceiversType::IM871A:
probeFor("im871a", detectIM871A);
break;
2020-04-03 09:58:19 +00:00
}
2020-05-09 19:56:37 +00:00
}
2020-04-03 09:58:19 +00:00
2020-06-29 14:35:58 +00:00
void resetWMBUSReceiver()
{
int c = selectFromMenu("Select your wmbus radio device", receivers_menu);
switch (static_cast<ReceiversType>(c))
{
case ReceiversType::AMB8465:
{
vector<string> devices = handler->listSerialDevices();
if (devices.size() == 0)
{
vector<string> entries;
displayInformationAndWait("No serial ports!", entries);
return;
}
int c = selectFromMenu("Select device", devices);
string device = devices[c];
int was_baud = 0;
2020-07-30 09:18:44 +00:00
AccessCheck ac = factoryResetAMB8465(device, handler.get(), &was_baud);
2020-06-29 14:35:58 +00:00
if (ac == AccessCheck::AccessOK)
{
vector<string> entries;
entries.push_back("amb8465 "+device+" using "+to_string(was_baud));
displayInformationAndWait("Factory reset successful", entries);
}
else
{
vector<string> entries;
entries.push_back(device);
displayInformationAndWait("No amb8465 response from", entries);
}
break;
}
case ReceiversType::CUL:
notImplementedYet("Resetting cul");
break;
case ReceiversType::IM871A:
notImplementedYet("Resetting im871a");
break;
}
}
void notImplementedYet(string msg)
{
vector<string> entries;
entries.push_back(msg);
displayInformationAndWait("Not implemented yet", entries);
}
2020-05-09 19:56:37 +00:00
void probeFor(string type, AccessCheck (*check)(string,SerialCommunicationManager*))
{
vector<string> devices = handler->listSerialDevices();
vector<string> entries;
for (string& device : devices)
{
string tty = "?";
AccessCheck ac = checkAccessAndDetect(
handler.get(),
[=](string d, SerialCommunicationManager* m){ return check(d, m);},
type,
device);
if (ac == AccessCheck::AccessOK)
{
tty = device+" DETECTED "+type;
}
else if (ac == AccessCheck::NotThere)
{
tty = device+" nothing there";
}
else if (ac == AccessCheck::NotSameGroup)
{
tty = device+" not same group";
}
entries.push_back(tty);
}
if (entries.size() == 0)
{
entries.push_back("No serial devices found.");
}
2020-05-30 16:45:29 +00:00
displayInformationAndWait("Probed serial devices", entries);
}
bool detectIfRoot()
{
vector<string> args;
vector<string> envs;
args.push_back("-u");
string out;
invokeShellCaptureOutput("/usr/bin/id", args, envs, &out, true);
return out == "0\n";
}
void detectProcesses(string cmd, vector<int> *pids)
{
vector<string> args;
vector<string> envs;
args.push_back(cmd);
string out;
invokeShellCaptureOutput("/bin/pidof", args, envs, &out, true);
char buf[out.size()+1];
strcpy(buf, out.c_str());
char *pch;
pch = strtok (buf," \n");
while (pch != NULL)
{
pids->push_back(atoi(pch));
pch = strtok (NULL, " \n");
}
2020-04-03 09:58:19 +00:00
}