#include #include #include #include #include "POHelperClasses.hpp" #include "MQTTDataStreamer.hpp" #include #include #include namespace po = boost::program_options; extern "C" { #include "lmic.h" #include "debug.h" } using namespace std; const std::string DFLT_SERVER_ADDRESS{"tcp://localhost:1883"}; const std::string CLIENT_ID{"paho_cpp_async_publish"}; const std::string PERSIST_DIR{"./persist"}; const char *LWT_PAYLOAD = "Last will and testament."; uint8_t QOS = 1; const auto TIMEOUT = std::chrono::seconds(1); // po::variables_map variable_map; ////////////////////////////////////////////////// // CONFIGURATION (FOR APPLICATION CALLBACKS BELOW) ////////////////////////////////////////////////// // application router ID (LSBF) static u1_t APPEUI[8]; // unique device ID (LSBF) static u1_t DEVEUI[8]; // device-specific AES key (derived from device EUI) static u1_t DEVKEY[16]; ////////////////////////////////////////////////// // LMIC APPLICATION CALLBACKS ////////////////////////////////////////////////// // provide application router ID (8 bytes, LSBF) void os_getArtEui(u1_t *buf) { memcpy(buf, APPEUI, 8); } // provide device ID (8 bytes, LSBF) void os_getDevEui(u1_t *buf) { memcpy(buf, DEVEUI, 8); } // provide device key (16 bytes) void os_getDevKey(u1_t *buf) { memcpy(buf, DEVKEY, 16); } ////////////////////////////////////////////////// // MQTT APPLICATION CALLBACKS ///////////////////////////////////////////////// void handleTopics(std::shared_ptr streamer_obj, const std::vector> &topics_to_handle, std::mutex *mut) { /* * This function runs in a separate thread. Here you can * write down the logic to be executed when a message is sent over a * subscribed topic. Subscription happens via Callback described in * HelperClasses.hh file in MQTTDataStreamer library. * Messages over subscribed topics are mainly supposed * to act as a trigger. If you want to perform * actions on the sent msg, it is currently only possible in the HelperClasses.hh * This boundary is rather limiting and it require a rewrite of MQTTDataStreamer * library(which is highly needed in my(Nirmal) opinion) */ while (true) { for (const auto &topic : topics_to_handle) { if (topic->name == "LoRa_test/transmitPacket/") { if (topic->message_received) { topic->message_received = false; } } else { std::cout << "\tTopic '" << topic->name << "' not handled\n"; exit(1); } } } } class DataTransmitTopic : public virtual TopicsToHandle { /* * This class is used provided as a means to do something * with the messages sent over subscribed messages. It requires * changing processMessage() to processMessage(const_message_ptr). * This can be theoretically done and boundary between MQTTDataStreamer * and main() can be broken by declaring static variables in this * Translation Unit. */ public: DataTransmitTopic(const std::string &name, uint8_t QoS = 1) : TopicsToHandle(name, QoS) {} void processMessage(mqtt::const_message_ptr msg_) override { message_received = true; std::size_t msg_len = msg_->get_payload().size(); char *msg = new char[msg_len + 1]; std::memcpy(msg, msg_->get_payload().data(), msg_len + 1); std::cout << "sending packet ..." << std::endl; for (int i = 0; i < msg_len; i++) { std::printf("%02hhX", msg[i]); } std::cout << std::endl; for (int i = 0; i < msg_len; i++) { LMIC.frame[i] = msg[i]; } LMIC_setTxData2(1, LMIC.frame, msg_len, 0); // (port 1, 2 bytes, unconfirmed) } }; /* Overload the 'validate' function for the user-defined class. It makes sure that value is of form XXX-XXX where X are digits and converts the second group to an integer. This has no practical meaning, meant only to show how regex can be used to validate values. */ void validate(boost::any& v, const std::vector& values, magic_number*, int) { static boost::regex r("\\d\\d\\d-(\\d\\d\\d)"); using namespace boost::program_options; // Make sure no previous assignment to 'a' was made. validators::check_first_occurrence(v); // Extract the first string from 'values'. If there is more than // one string, it's an error, and exception will be thrown. const std::string& s = validators::get_single_string(values); // Do regex match and convert the interesting part to // int. boost::smatch match; if (regex_match(s, match, r)) { v = boost::any(magic_number(boost::lexical_cast(match[1]))); } else { throw validation_error(validation_error::invalid_option_value); } } void validate(boost::any& v, const std::vector& values, appeui*, int) { static boost::regex r("^([[:xdigit:]]{2}[:.-]?){7}[[:xdigit:]]{2}$"); boost::regex re("[:.-]"); using namespace boost::program_options; // Make sure no previous assignment to 'a' was made. validators::check_first_occurrence(v); // Extract the first string from 'values'. If there is more than // one string, it's an error, and exception will be thrown. const std::string& s = validators::get_single_string(values); // Do regex match and convert the interesting part to // int. boost::smatch match; if (regex_match(s, match, r)) { std::string unfilterd_string = match[0]; std::string filterd_string = boost::regex_replace( unfilterd_string, re, "" ); std::cout << "regex funktioniert .... " << filterd_string << std::endl; std::cout << "boost algorithm ... " << boost::algorithm::unhex(filterd_string) << std::endl; v = boost::any( appeui( boost::algorithm::unhex(filterd_string) ) ); } else { throw validation_error(validation_error::invalid_option_value); } } void validate(boost::any& v, const std::vector& values, deveui*, int) { static boost::regex r("^([[:xdigit:]]{2}[:.-]?){7}[[:xdigit:]]{2}$"); boost::regex re("[:.-]"); using namespace boost::program_options; // Make sure no previous assignment to 'a' was made. validators::check_first_occurrence(v); // Extract the first string from 'values'. If there is more than // one string, it's an error, and exception will be thrown. const std::string& s = validators::get_single_string(values); // Do regex match and convert the interesting part to // int. boost::smatch match; if (regex_match(s, match, r)) { std::string unfilterd_string = match[0]; std::string filterd_string = boost::regex_replace( unfilterd_string, re, "" ); std::cout << "regex funktioniert .... " << filterd_string << std::endl; std::cout << "boost algorithm ... " << boost::algorithm::unhex(filterd_string) << std::endl; v = boost::any( deveui( boost::algorithm::unhex(filterd_string) ) ); } else { throw validation_error(validation_error::invalid_option_value); } } void validate(boost::any& v, const std::vector& values, devkey*, int) { static boost::regex r("^([[:xdigit:]]{2}[:.-]?){15}[[:xdigit:]]{2}$"); boost::regex re("[:.-]"); using namespace boost::program_options; // Make sure no previous assignment to 'a' was made. validators::check_first_occurrence(v); // Extract the first string from 'values'. If there is more than // one string, it's an error, and exception will be thrown. const std::string& s = validators::get_single_string(values); // Do regex match and convert the interesting part to // int. boost::smatch match; if (regex_match(s, match, r)) { std::string unfilterd_string = match[0]; std::string filterd_string = boost::regex_replace( unfilterd_string, re, "" ); std::cout << "regex funktioniert .... " << filterd_string << std::endl; std::cout << "boost algorithm ... " << boost::algorithm::unhex(filterd_string) << std::endl; v = boost::any( devkey( boost::algorithm::unhex(filterd_string) ) ); } else { throw validation_error(validation_error::invalid_option_value); } } std::string mapper(std::string env_var) { // ensure the env_var is all caps std::transform(env_var.begin(), env_var.end(), env_var.begin(), ::toupper); if (env_var == "PATH") return "path"; if (env_var == "EXAMPLE_VERBOSE") return "verbosity"; if (env_var == "HOSTNAME") return "hostname"; if (env_var == "HOME") return "home"; return ""; } ////////////////////////////////////////////////// // MAIN - INITIALIZATION AND STARTUP ////////////////////////////////////////////////// // initial job static void initfunc(osjob_t *j) { // reset MAC state LMIC_reset(); // start joining LMIC_startJoining(); // init done - onEvent() callback will be invoked... } // application entry point int main(int argc, char *argv[]) { osjob_t initjob; std::string hostname; boost::program_options::options_description desc_env; desc_env.add_options() ("path", "the execution path") ("home", "the home directory of the executing user") ("verbosity", po::value()->default_value("INFO"), "set verbosity: DEBUG, INFO, WARN, ERROR, FATAL") ("hostname", boost::program_options::value(&hostname)); boost::program_options::variables_map vm_env; boost::program_options::store(boost::program_options::parse_environment(desc_env, boost::function1(mapper)), vm_env); boost::program_options::notify(vm_env); if (vm_env.count("home")) { std::cout << "home path: " << vm_env["home"].as() << std::endl; } if (vm_env.count("path")) { std::cout << "First 75 chars of the system path: \n"; std::cout << vm_env["path"].as().substr(0, 75) << std::endl; } std::cout << "Verbosity: " << vm_env["verbosity"].as() << std::endl; if ( vm_env.count("hostname")) { std::cout << "hostname: " << vm_env["hostname"].as() << std::endl; // correct value of HOSTNAME environent variable } // set options allowed by the command line po::options_description command_line_options("Allowed options"); command_line_options.add_options() ("help", "produce help message") ("version,v", "print the version number") ("hostname,h", po::value()->default_value("localhost"), "Hostname") ("port,p", po::value()->default_value(1883), "Port") ("config,c", po::value(), "configuration file") ("magic,m", po::value(), "magic value (in NNN-NNN format)") ("appeui", po::value(), "APPEUI") ("deveui", po::value(), "DEVEUI") ("devkey", po::value(), "DEVKEY"); // set options allowed in config file po::options_description config_file_options; config_file_options.add_options() ("APPEUI", po::value(), "APPEUI") ("DEVEUI", po::value(), "DEVEUI") ("DEVKEY", po::value(), "DEVKEY"); po::variables_map variable_map; po::store(po::parse_command_line(argc, argv, command_line_options), variable_map); po::notify(variable_map); std::string config_file; if( variable_map.count("config") ){ config_file = variable_map.at("config").as(); }else if( vm_env.count("home") ){ if(std::filesystem::exists( vm_env["home"].as() + "/.config/mqtt2LoRaWAN/default.conf" )){ config_file = vm_env["home"].as() + "/.config/mqtt2LoRaWAN/default.conf"; }else if(std::filesystem::exists("/etc/mqtt2LoRaWAN/default.conf")){ config_file = "/etc/mqtt2LoRaWAN/default.conf"; } }else{ if(std::filesystem::exists("/etc/mqtt2LoRaWAN/default.conf")){ config_file = "/etc/mqtt2LoRaWAN/default.conf"; } } std::ifstream ifs(config_file.c_str()); if (!ifs) { std::cout << "can not open configuration file: " << config_file << "\n"; } else { po::store(parse_config_file(ifs, config_file_options), variable_map); po::notify(variable_map); } po::notify(variable_map); std::cout << config_file << " was the config file\n"; if (variable_map.count("help")) { std::cout << command_line_options << std::endl; return 0; } if (variable_map.count("version")) { cout << "Version 1.\n"; return 0; } if (variable_map.count("magic")) { cout << "The magic is \"" << variable_map["magic"].as().n << "\"\n"; return 0; } if (variable_map.count("DEVEUI")) { std::cout << "DEVEUI: " << std::endl; for( int i = 0; i < 8; i++ ){ std::printf("%#02x", variable_map["DEVEUI"].as().device_eui64.e8[i] ); } std::cout << std::endl; std::memcpy(DEVEUI, variable_map["DEVEUI"].as().device_eui64.e8, 8); }else{ std::cout << "No DEVEUI found in the config file. Add a 8 bytes long EUI to config file or specify an EUI as an option via the command line." << std::endl; return 1; } if (variable_map.count("APPEUI")) { std::cout << "APPEUI: " << std::endl; for( int i = 0; i < 8; i++ ){ std::printf("%#02x", variable_map["APPEUI"].as().application_eui64.e8[i] ); } std::cout << std::endl; std::memcpy(APPEUI, variable_map["APPEUI"].as().application_eui64.e8, 8); }else{ std::cout << "No APPEUI found in the config file. Add a 8 bytes long EUI to config file or specify an EUI as an option via the command line." << std::endl; return 1; } if (variable_map.count("DEVKEY")) { std::cout << "DEVKEY: " << std::endl; for( int i = 0; i < 16; i++ ){ std::printf("%#02x", variable_map["DEVKEY"].as().device_key[i] ); } std::cout << std::endl; std::memcpy(DEVKEY, variable_map["DEVKEY"].as().device_key, 16); }else{ std::cout << "No DEVKEY found in the config file. Add a 16 bytes long key to config file or specify a key as an option via the command line." << std::endl; return 1; } std::cout << "Initializing and connecting for server '" << variable_map["hostname"].as() << "'..." << std::endl; std::vector> topics_to_handle; topics_to_handle.push_back(std::make_shared( "LoRa_test/transmitPacket/")); auto mqtt_async_client = std::make_shared((string) "tcp://" + variable_map["hostname"].as() + (string) ":" + to_string(variable_map["port"].as()), CLIENT_ID); auto callback = std::make_shared(mqtt_async_client, topics_to_handle); auto streamer_obj = std::make_shared( std::make_tuple(mqtt_async_client, callback)); std::mutex mut; std::cout << " ...OK" << endl; // initialize runtime env os_init(); // initialize debug library debug_init(); // setup initial job os_setCallback(&initjob, initfunc); // execute scheduled jobs and events os_runloop(); // (not reached) return 0; } ////////////////////////////////////////////////// // UTILITY JOB ////////////////////////////////////////////////// static osjob_t reportjob; // report sensor value every minute static void reportfunc(osjob_t *j) { // read sensor u2_t val = 543; debug_val("val = ", val); // prepare and schedule data for transmission LMIC.frame[0] = val >> 8; LMIC.frame[1] = val; LMIC_setTxData2(1, LMIC.frame, 2, 0); // (port 1, 2 bytes, unconfirmed) // reschedule job in 60 seconds os_setTimedCallback(j, os_getTime() + sec2osticks(60), reportfunc); } ////////////////////////////////////////////////// // LMIC EVENT CALLBACK ////////////////////////////////////////////////// extern "C" { void onEvent(ev_t ev) { debug_event(ev); switch (ev) { // network joined, session established case EV_JOINED: break; // data frame received case EV_RXCOMPLETE: // log frame data debug_buf(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); /* if(LMIC.dataLen == 1) { // set LED state if exactly one byte is received debug_led(LMIC.frame[LMIC.dataBeg] & 0x01); } */ break; // scheduled data sent (optionally data received) case EV_TXCOMPLETE: if (LMIC.dataLen) { // data received in rx slot after tx debug_buf(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); } break; } } }