/*
* Copyright (C) 2015 by Multi-Tech Systems
*
* This file is part of lora-query.
*
* lora-query 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 2 of the License, or
* (at your option) any later version.
*
* lora-query 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 lora-query. If not, see .
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Version.h"
#define INET_ADDR "127.0.0.1"
#define INET_PORT 6677
#define MAX_RECEIVED_BYTES 2000
#define TIMEOUT 100 /* By default 100 msec */
int opt_get_stats = 0;
int opt_get_nodelist = 0;
int opt_get_json = 0;
int opt_stats_reset = 0;
int opt_get_nodeconfig = 0;
int opt_add_node = 0;
int opt_delete_node = 0;
int opt_update_node = 0;
int opt_command = 0;
int opt_timeout = 0;
char* command_command;
char* node_get_addr;
char* node_delete_addr;
char* node_config_addr;
char* node_update_addr;
char* node_update_field;
char* node_update_value;
int node_add_count = 0;
char* node_add_args[6];
int command_count = 0;
char* command_args[20];
int timeout = TIMEOUT;
const char* cmd_stats = "stats";
const char* cmd_stats_reset = "stats reset";
const char* cmd_node_list = "node stats";
const char* cmd_node_config = "node config";
const char* cmd_node_update = "node update";
const char* cmd_node_delete = "node delete";
const char* cmd_nodeadd = "node add";
const std::string lora_command_output("/var/tmp/lora_command_output");
const std::string lora_network_stats_json("/var/tmp/lora_network_stats.json");
const std::string lora_network_nodelist("/var/tmp/lora_network_nodelist");
const std::string lora_network_nodelist_json("/var/tmp/lora_network_nodelist.json");
const std::string lora_network_nodeconfig("/var/tmp/lora_network_nodeconfig");
const std::string lora_network_nodeconfig_json("/var/tmp/lora_network_nodeconfig.json");
std::stringstream receiveStream;
const int nodeSize = 16;
const int configSize = 7;
const std::string NODE_ADDR("nodeAddr");
const std::string NODE_DEV_EUI("devEui");
const std::string NODE_APP_EUI("appEui");
const std::string NODE_APP_KEY("appKey");
const std::string NODE_NS_KEY("nsKey");
const std::string NODE_DS_KEY("dsKey");
const std::string NODE_CLASS("class");
const std::string NODE_JOINED("joined");
const std::string NODE_SEQ_NUM("seqNum");
const std::string NODE_PKTS_UP("pktsUp");
const std::string NODE_PKTS_DOWN("pktsDown");
const std::string NODE_PKTS_1ST("pkts1st");
const std::string NODE_PKTS_2ND("pkts2nd");
const std::string NODE_DROPPED("dropped");
const std::string NODE_RSSI_MIN("rssiMin");
const std::string NODE_RSSI_MAX("rssiMax");
const std::string NODE_RSSI_AVR("rssiAvg");
const std::string NODE_SNR_MIX("snrMin");
const std::string NODE_SNR_MAX("snrMax");
const std::string NODE_SNR_AVR("snrAvg");
void runCmd(const char *command);
void printStats(void);
void printNodeList(void);
void printNodeConfig(void);
void saveToFile(const std::string& fileName, const std::string& buffer);
void parseOptions(int argc, char** argv);
void printHelp(const std::string& sApp);
std::string trim(std::string& str);
int main(int argc, char**argv) {
parseOptions(argc, argv);
if (opt_stats_reset) {
runCmd(cmd_stats_reset);
}
if (opt_command) {
std::stringstream cmd;
for (int i = 0; i < command_count; i++) {
cmd << " " << command_args[i];
}
runCmd(cmd.str().c_str());
if (receiveStream.str().empty()) {
return 0;
}
saveToFile(lora_command_output, receiveStream.str());
std::cout << receiveStream.str();
}
if (opt_get_stats) {
runCmd(cmd_stats);
printStats();
}
if (opt_get_nodelist) {
runCmd(cmd_node_list);
if (opt_get_json) {
printNodeList();
} else {
if (receiveStream.str().empty()) {
return 0;
}
saveToFile(lora_network_nodelist, receiveStream.str());
std::cout << receiveStream.str();
}
}
if (opt_get_nodeconfig) {
char buff[256];
snprintf(buff, 256, "%s %s", cmd_node_config, node_config_addr);
runCmd(buff);
if (opt_get_json) {
printNodeConfig();
} else {
if (!receiveStream.str().empty()) {
saveToFile(lora_network_nodelist, receiveStream.str());
std::cout << receiveStream.str();
}
}
}
if (opt_add_node) {
if (node_add_count < 4) {
std::cout << "usage: --node-add [CLASS] ([APP-KEY] | [NET-SKEY] [APP-SKEY])\n";
return 0;
}
std::stringstream cmd;
cmd << cmd_nodeadd;
for (int i = 0; i < node_add_count; i++) {
cmd << " " << node_add_args[i];
}
runCmd(cmd.str().c_str());
if (!receiveStream.str().empty()) {
std::cout << receiveStream.str();
}
}
if (opt_delete_node) {
char buff[256];
snprintf(buff, 256, "%s %s", cmd_node_delete, node_delete_addr);
runCmd(buff);
if (!receiveStream.str().empty()) {
std::cout << receiveStream.str();
}
}
if (opt_update_node) {
std::stringstream cmd;
cmd << cmd_node_update;
cmd << " " << node_update_addr;
cmd << " " << node_update_field;
cmd << " " << node_update_value;
runCmd(cmd.str().c_str());
if (!receiveStream.str().empty()) {
std::cout << receiveStream.str();
}
}
return 0;
}
void runCmd(const char *command) {
int sockfd;
struct sockaddr_in servaddr;
int receiveBytes = 0;
char receiveMessage[MAX_RECEIVED_BYTES];
struct timeval tv;
if (opt_timeout) {
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
} else {
// set initial timeout to 5 seconds
tv.tv_sec = 5;
tv.tv_usec = 0;
}
receiveStream.str("");
receiveStream.clear();
if (NULL == command) {
printError("Command is null\n");
return;
}
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(INET_ADDR);
servaddr.sin_port = htons(INET_PORT);
memset(receiveMessage, 0, MAX_RECEIVED_BYTES);
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
printError("setsockopt error\n");
return;
}
// Lower timeout for subsequent reads
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
sendto(sockfd, command, strlen(command), 0, (struct sockaddr *) &servaddr, sizeof(servaddr));
while (1) {
receiveBytes = recvfrom(sockfd, receiveMessage, MAX_RECEIVED_BYTES, 0, NULL, NULL);
if (receiveBytes < 0) {
/*printf("timeout\n");*/
break;
}
// Lower timeout for subsequent reads
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
printError("setsockopt error\n");
return;
}
receiveStream << std::string(receiveMessage, receiveBytes);
}
}
void printStats() {
Json::Reader reader;
Json::Value stats;
if (receiveStream.str().empty()) {
return;
}
if (!reader.parse(receiveStream.str(), stats), false) {
printError("Error parsing JSON: [%s]", receiveStream.str().c_str());
}
// Replace underscores keys with camel-case.
if (stats.isObject()) {
Json::Value::Members keys = stats.getMemberNames();
std::string newKey;
for (Json::Value::Members::iterator it = keys.begin(); it != keys.end(); ++it) {
const std::string& key = *it;
newKey.clear();
for (size_t i = 0; i < key.length(); ++i) {
if (('_' == key[i]) || ('-' == key[i])) {
if ((i + 1) < key.length()) {
newKey.push_back(std::toupper(key[++i]));
}
} else {
newKey.push_back(key[i]);
}
}
stats[newKey] = stats[key];
stats.removeMember(key);
}
}
saveToFile(lora_network_stats_json, stats.toStyledString());
std::cout << stats.toStyledString();
}
void printNodeConfig() {
std::string line;
std::vector < std::string > parts;
Json::Value nodeList = Json::ValueType::arrayValue;
if (receiveStream.str().empty()) {
return;
}
getline(receiveStream, line);
while (1) {
getline(receiveStream, line);
if (line.size() == 0)
break;
line = trim(line);
parts = MTS::Text::split(line, " ");
if (configSize == (int)parts.size()) {
Json::Value jNode(Json::objectValue);
int index = 0;
jNode[NODE_ADDR] = parts[index++];
jNode[NODE_DEV_EUI] = parts[index++];
jNode[NODE_CLASS] = parts[index++];
jNode[NODE_APP_EUI] = parts[index++];
jNode[NODE_APP_KEY] = parts[index++];
jNode[NODE_NS_KEY] = parts[index++];
jNode[NODE_DS_KEY] = parts[index++];
nodeList.append(jNode);
}
else {
printError("Incorrect Node Data! parts.size() = [%d]\n", parts.size());
}
}
saveToFile(lora_network_nodelist_json, nodeList.toStyledString());
std::cout << nodeList.toStyledString();
}
void printNodeList() {
std::string line;
std::vector < std::string > parts;
Json::Value nodeList = Json::ValueType::arrayValue;
if (receiveStream.str().empty()) {
return;
}
getline(receiveStream, line);
while (1) {
getline(receiveStream, line);
if (line.size() == 0)
break;
line = trim(line);
parts = MTS::Text::split(line, " ");
if (nodeSize == (int)parts.size()) {
Json::Value jNode(Json::objectValue);
int index = 0;
jNode[NODE_ADDR] = parts[index++];
jNode[NODE_DEV_EUI] = parts[index++];
jNode[NODE_CLASS] = parts[index++];
jNode[NODE_JOINED] = parts[index++];
jNode[NODE_SEQ_NUM] = (uint32_t)std::strtoul(parts[index++].c_str(), nullptr, 10);
jNode[NODE_PKTS_UP] = (uint32_t)std::strtoul(parts[index++].c_str(), nullptr, 10);
jNode[NODE_PKTS_DOWN] = (uint32_t)std::strtoul(parts[index++].c_str(), nullptr, 10);
jNode[NODE_PKTS_1ST] = (uint32_t)std::strtoul(parts[index++].c_str(), nullptr, 10);
jNode[NODE_PKTS_2ND] = (uint32_t)std::strtoul(parts[index++].c_str(), nullptr, 10);
jNode[NODE_DROPPED] = (uint32_t)std::strtoul(parts[index++].c_str(), nullptr, 10);
jNode[NODE_RSSI_MIN] = atof(parts[index++].c_str());
jNode[NODE_RSSI_MAX] = atof(parts[index++].c_str());
jNode[NODE_RSSI_AVR] = atof(parts[index++].c_str());
jNode[NODE_SNR_MIX] = atof(parts[index++].c_str());
jNode[NODE_SNR_MAX] = atof(parts[index++].c_str());
jNode[NODE_SNR_AVR] = atof(parts[index++].c_str());
nodeList.append(jNode);
}
else {
printError("Incorrect Node Data! parts.size() = [%d]\n", parts.size());
}
}
saveToFile(lora_network_nodelist_json, nodeList.toStyledString());
std::cout << nodeList.toStyledString();
}
void saveToFile(const std::string& fileName, const std::string& buffer) {
std::ofstream outFile;
outFile.open(fileName);
outFile << buffer;
outFile.close();
}
void parseOptions(int argc, char** argv) {
if (argc == 1) {
printHelp(argv[0]);
exit(0);
}
const char* short_options = "hvsrnc:a:u:d:jt:x:";
const struct option long_options[] = {
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'v' },
{ "stats", no_argument, 0, 's' },
{ "stats-reset", no_argument, 0, 'r' },
{ "node-list", no_argument, 0, 'n' },
{ "command", required_argument, 0, 'x' },
{ "json", no_argument, 0, 'j' },
{ "timeout", required_argument, 0, 't' },
{ 0, 0, 0, 0 }
};
int rez;
int option_index;
while ((rez = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) {
switch (rez) {
case 'x': {
++opt_command;
command_args[command_count++] = optarg;
for (int i = optind; i < argc; i++) {
command_args[command_count++] = argv[i];
}
break;
}
case 's': {
++opt_get_stats;
break;
}
case 'r': {
++opt_stats_reset;
break;
}
case 'n': {
++opt_get_nodelist;
break;
}
case 'j': {
++opt_get_json;
break;
}
case 't': {
opt_timeout = 1;
timeout = atoi(optarg);
break;
}
case 'v':
printf("Version: %s\n", Version::version.c_str());
exit(0);
break;
case 'h':
case '?':
default: {
printHelp(argv[0]);
exit(0);
break;
}
};
};
}
void printHelp(const std::string& sApp) {
printf("Usage: %s [-t timeout] [-s] [-n]\n", sApp.c_str());
printf("\tSimple UDP client utility to pull info from LoRa Network server\n");
printf("\t--timeout (t) : UDP recv timeout, default: 100 (msecs)\n");
printf("\t--stats (s) : get LoRa Network server statistics\n");
printf("\t--stats-reset (r) : reset LoRa Network server statistics\n");
printf("\t--node-list (n) : get Node List\n");
printf("\t--command (x) : send command string to network server\n");
printf("\t\tusage: --command ...\n");
printf("\t\tex: --command device list json\n");
printf("\t\tex: -x help\n");
printf("\t--json (j) : data in json format\n");
printf("\t--help (?) : returns this message\n");
printf("\t--version (v) : print version\n");
}
std::string trim(std::string& str) {
str.erase(std::unique(str.begin(), str.end(), [](char a, char b) {return a == ' ' && b == ' ';}), str.end());
return str;
}