/*
* 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 timeout = TIMEOUT;
const char* cmd_stats = "stats";
const char* cmd_stats_reset = "stats reset";
const char* cmd_nodelist = "node list";
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");
std::stringstream receiveStream;
const int nodeSize = 15;
const std::string NODE_ADDR("nodeAddr");
const std::string NODE_DEV_EUI("devEui");
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 getloraData(const char *command);
void printStats(void);
void printNodeList(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) {
getloraData(cmd_stats_reset);
}
if (opt_get_stats) {
getloraData(cmd_stats);
printStats();
}
if (opt_get_nodelist) {
getloraData(cmd_nodelist);
if (opt_get_json) {
printNodeList();
} else {
if (receiveStream.str().empty() ) {
return 0;
}
saveToFile(lora_network_nodelist, receiveStream.str());
std::cout << receiveStream.str();
}
}
return 0;
}
void getloraData(const char *command) {
int sockfd;
struct sockaddr_in servaddr;
int receiveBytes = 0;
char receiveMessage[MAX_RECEIVED_BYTES];
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
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;
}
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;
}
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 printNodeList() {
std::string line;
std::vector 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 == parts.size()) {
Json::Value jNode(Json::objectValue);
jNode[NODE_ADDR] = parts[0];
jNode[NODE_DEV_EUI] = parts[1];
jNode[NODE_JOINED] = parts[2];
jNode[NODE_SEQ_NUM] = atoi(parts[3].c_str());
jNode[NODE_PKTS_UP] = atoi(parts[4].c_str());
jNode[NODE_PKTS_DOWN] = atoi(parts[5].c_str());
jNode[NODE_PKTS_1ST] = atoi(parts[6].c_str());
jNode[NODE_PKTS_2ND] = atoi(parts[7].c_str());
jNode[NODE_DROPPED] = atoi(parts[8].c_str());
jNode[NODE_RSSI_MIN] = atoi(parts[9].c_str());
jNode[NODE_RSSI_MAX] = atoi(parts[10].c_str());
jNode[NODE_RSSI_AVR] = atoi(parts[11].c_str());
jNode[NODE_SNR_MIX] = atoi(parts[12].c_str());
jNode[NODE_SNR_MAX] = atoi(parts[13].c_str());
jNode[NODE_SNR_AVR] = atoi(parts[14].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 = "hvsirnjt:";
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'},
{"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 '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': {
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--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;
}