2022-09-11 18:08:18 +00:00
# include <iostream>
# include <fstream>
# include <filesystem>
# include <string>
2022-09-11 13:39:39 +00:00
2022-09-13 19:45:36 +00:00
# include "POHelperClasses.hpp"
2022-10-10 05:04:51 +00:00
# include "POCmdlineHelperClasses.hpp"
2022-10-11 10:00:07 +00:00
# include "POConfigHelperClasses.hpp"
2022-09-11 13:39:39 +00:00
# include "MQTTDataStreamer.hpp"
2022-10-17 05:07:13 +00:00
# include "customLogger.hpp"
2022-09-11 13:39:39 +00:00
# include <boost/program_options.hpp>
2022-09-13 19:45:36 +00:00
# include <boost/regex.hpp>
2022-09-20 19:11:42 +00:00
# include <boost/algorithm/hex.hpp>
2022-10-17 05:07:13 +00:00
2022-09-11 13:39:39 +00:00
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 ) ;
2022-09-11 18:08:18 +00:00
// po::variables_map variable_map;
2022-09-11 13:39:39 +00:00
//////////////////////////////////////////////////
// CONFIGURATION (FOR APPLICATION CALLBACKS BELOW)
//////////////////////////////////////////////////
// application router ID (LSBF)
2022-09-26 13:55:51 +00:00
static u1_t APPEUI [ 8 ] ;
2022-09-11 13:39:39 +00:00
// unique device ID (LSBF)
2022-09-26 13:55:51 +00:00
static u1_t DEVEUI [ 8 ] ;
2022-09-11 13:39:39 +00:00
// device-specific AES key (derived from device EUI)
2022-09-26 13:55:51 +00:00
static u1_t DEVKEY [ 16 ] ;
2022-09-11 13:39:39 +00:00
//////////////////////////////////////////////////
// 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 < MQTTDataStreamer > streamer_obj ,
const std : : vector < std : : shared_ptr < TopicsToHandle > > & 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 < < " \t Topic ' " < < 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)
}
} ;
2022-09-13 19:45:36 +00:00
2022-09-27 19:25:14 +00:00
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 " " ;
}
2022-09-13 19:45:36 +00:00
2022-09-11 13:39:39 +00:00
//////////////////////////////////////////////////
// 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...
}
2022-10-17 05:07:13 +00:00
2022-09-11 13:39:39 +00:00
// application entry point
int main ( int argc , char * argv [ ] )
{
2022-10-17 05:07:13 +00:00
BOOST_LOG_NAMED_SCOPE ( " named_scope_logging " ) ;
LOG_TRACE < < " this is a trace message " ;
LOG_DEBUG < < " this is a debug message " ;
LOG_WARNING < < " this is a warning message " ;
LOG_ERROR < < " this is an error message " ;
LOG_FATAL < < " this is a fatal error message " ;
2022-09-11 13:39:39 +00:00
osjob_t initjob ;
2022-09-27 19:25:14 +00:00
std : : string hostname ;
2022-10-10 05:04:51 +00:00
po_cmdline_helper po_cmdline_inst ;
2022-10-11 10:00:07 +00:00
po_config_helper po_config_inst ;
2022-10-10 05:04:51 +00:00
2022-09-27 19:25:14 +00:00
boost : : program_options : : options_description desc_env ;
desc_env . add_options ( ) ( " path " , " the execution path " )
( " home " , " the home directory of the executing user " )
2022-10-04 18:48:45 +00:00
( " verbosity " , po : : value < std : : string > ( ) - > default_value ( " INFO " ) , " set verbosity: DEBUG, INFO, WARN, ERROR, FATAL " )
( " hostname " , boost : : program_options : : value < std : : string > ( & hostname ) ) ;
2022-09-27 19:25:14 +00:00
boost : : program_options : : variables_map vm_env ;
boost : : program_options : : store ( boost : : program_options : : parse_environment ( desc_env , boost : : function1 < std : : string , std : : string > ( mapper ) ) , vm_env ) ;
boost : : program_options : : notify ( vm_env ) ;
if ( vm_env . count ( " home " ) )
{
std : : cout < < " home path: " < < vm_env [ " home " ] . as < std : : string > ( ) < < std : : endl ;
}
if ( vm_env . count ( " path " ) )
{
std : : cout < < " First 75 chars of the system path: \n " ;
std : : cout < < vm_env [ " path " ] . as < std : : string > ( ) . substr ( 0 , 75 ) < < std : : endl ;
}
std : : cout < < " Verbosity: " < < vm_env [ " verbosity " ] . as < std : : string > ( ) < < std : : endl ;
if ( vm_env . count ( " hostname " ) )
{
std : : cout < < " hostname: " < < vm_env [ " hostname " ] . as < std : : string > ( ) < < std : : endl ; // correct value of HOSTNAME environent variable
}
2022-09-11 18:08:18 +00:00
// set options allowed by the command line
po : : options_description command_line_options ( " Allowed options " ) ;
2022-10-10 05:04:51 +00:00
po_cmdline_inst . init ( & command_line_options ) ;
/*
2022-09-11 18:08:18 +00:00
command_line_options . add_options ( ) ( " help " , " produce help message " )
2022-09-13 19:45:36 +00:00
( " version,v " , " print the version number " )
2022-09-11 18:08:18 +00:00
( " hostname,h " , po : : value < string > ( ) - > default_value ( " localhost " ) , " Hostname " )
( " port,p " , po : : value < int > ( ) - > default_value ( 1883 ) , " Port " )
2022-10-04 18:48:45 +00:00
( " config,c " , po : : value < std : : string > ( ) , " configuration file " )
2022-09-13 19:45:36 +00:00
( " magic,m " , po : : value < magic_number > ( ) , " magic value (in NNN-NNN format) " )
2022-09-20 19:11:42 +00:00
( " appeui " , po : : value < appeui > ( ) , " APPEUI " )
( " deveui " , po : : value < deveui > ( ) , " DEVEUI " )
2022-09-26 13:55:51 +00:00
( " devkey " , po : : value < devkey > ( ) , " DEVKEY " ) ;
2022-09-11 18:08:18 +00:00
2022-10-10 05:04:51 +00:00
*/
2022-09-11 18:08:18 +00:00
// set options allowed in config file
po : : options_description config_file_options ;
2022-10-11 10:00:07 +00:00
po_config_inst . init ( & config_file_options ) ;
/*
2022-09-20 19:11:42 +00:00
config_file_options . add_options ( ) ( " APPEUI " , po : : value < appeui > ( ) , " APPEUI " )
2022-09-26 13:55:51 +00:00
( " DEVEUI " , po : : value < deveui > ( ) , " DEVEUI " )
( " DEVKEY " , po : : value < devkey > ( ) , " DEVKEY " ) ;
2022-10-11 10:00:07 +00:00
*/
2022-09-11 18:08:18 +00:00
po : : variables_map variable_map ;
po : : store ( po : : parse_command_line ( argc , argv , command_line_options ) , variable_map ) ;
po : : notify ( variable_map ) ;
2022-10-04 18:48:45 +00:00
std : : string config_file ;
if ( variable_map . count ( " config " ) ) {
config_file = variable_map . at ( " config " ) . as < std : : string > ( ) ;
} else if ( vm_env . count ( " home " ) ) {
if ( std : : filesystem : : exists ( vm_env [ " home " ] . as < std : : string > ( ) + " /.config/mqtt2LoRaWAN/default.conf " ) ) {
config_file = vm_env [ " home " ] . as < std : : string > ( ) + " /.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 " ;
}
}
2022-09-11 18:08:18 +00:00
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 " ;
2022-09-11 13:39:39 +00:00
2022-09-11 18:08:18 +00:00
if ( variable_map . count ( " help " ) )
2022-09-11 13:39:39 +00:00
{
2022-09-11 18:08:18 +00:00
std : : cout < < command_line_options < < std : : endl ;
2022-09-11 13:39:39 +00:00
return 0 ;
}
2022-09-13 19:45:36 +00:00
if ( variable_map . count ( " version " ) ) {
cout < < " Version 1. \n " ;
return 0 ;
}
if ( variable_map . count ( " magic " ) ) {
cout < < " The magic is \" " < < variable_map [ " magic " ] . as < magic_number > ( ) . n < < " \" \n " ;
return 0 ;
}
2022-09-20 19:11:42 +00:00
if ( variable_map . count ( " DEVEUI " ) )
2022-09-13 19:45:36 +00:00
{
2022-09-20 19:11:42 +00:00
std : : cout < < " DEVEUI: " < < std : : endl ;
for ( int i = 0 ; i < 8 ; i + + ) {
2022-09-26 13:55:51 +00:00
std : : printf ( " %#02x " , variable_map [ " DEVEUI " ] . as < deveui > ( ) . device_eui64 . e8 [ i ] ) ;
}
std : : cout < < std : : endl ;
std : : memcpy ( DEVEUI , variable_map [ " DEVEUI " ] . as < deveui > ( ) . 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 ;
2022-09-13 19:45:36 +00:00
}
2022-09-11 18:08:18 +00:00
if ( variable_map . count ( " APPEUI " ) )
{
2022-09-20 19:11:42 +00:00
std : : cout < < " APPEUI: " < < std : : endl ;
for ( int i = 0 ; i < 8 ; i + + ) {
2022-09-26 13:55:51 +00:00
std : : printf ( " %#02x " , variable_map [ " APPEUI " ] . as < appeui > ( ) . application_eui64 . e8 [ i ] ) ;
}
std : : cout < < std : : endl ;
std : : memcpy ( APPEUI , variable_map [ " APPEUI " ] . as < appeui > ( ) . 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 < devkey > ( ) . device_key [ i ] ) ;
}
std : : cout < < std : : endl ;
std : : memcpy ( DEVKEY , variable_map [ " DEVKEY " ] . as < devkey > ( ) . 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 ;
2022-09-11 18:08:18 +00:00
}
std : : cout < < " Initializing and connecting for server ' " < < variable_map [ " hostname " ] . as < string > ( ) < < " '... " < < std : : endl ;
2022-09-11 13:39:39 +00:00
std : : vector < std : : shared_ptr < TopicsToHandle > > topics_to_handle ;
topics_to_handle . push_back ( std : : make_shared < DataTransmitTopic > (
" LoRa_test/transmitPacket/ " ) ) ;
2022-09-11 18:08:18 +00:00
auto mqtt_async_client = std : : make_shared < mqtt : : async_client > ( ( string ) " tcp:// " + variable_map [ " hostname " ] . as < string > ( ) +
( string ) " : " + to_string ( variable_map [ " port " ] . as < int > ( ) ) ,
2022-09-11 13:39:39 +00:00
CLIENT_ID ) ;
auto callback = std : : make_shared < MqttCallback > ( mqtt_async_client , topics_to_handle ) ;
auto streamer_obj = std : : make_shared < MQTTDataStreamer > (
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 ;
}
}
}