/* * 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; }