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-09-11 13:39:39 +00:00
# include "MQTTDataStreamer.hpp"
# 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-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
/* 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 < std : : string > & 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 < int > ( match [ 1 ] ) ) ) ;
} else {
throw validation_error ( validation_error : : invalid_option_value ) ;
2022-09-20 19:11:42 +00:00
}
2022-09-13 19:45:36 +00:00
}
void validate ( boost : : any & v ,
const std : : vector < std : : string > & 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 ) ) {
2022-09-20 19:11:42 +00:00
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 ) ) ) ;
2022-09-13 19:45:36 +00:00
} else {
throw validation_error ( validation_error : : invalid_option_value ) ;
2022-09-20 19:11:42 +00:00
}
}
void validate ( boost : : any & v ,
const std : : vector < std : : string > & 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 < std : : string > & 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 ) ;
}
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...
}
// application entry point
int main ( int argc , char * argv [ ] )
{
osjob_t initjob ;
2022-09-27 19:25:14 +00:00
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 < std : : string > ( ) - > default_value ( " INFO " ) , " set verbosity: DEBUG, INFO, WARN, ERROR, FATAL " )
( " hostname " , boost : : program_options : : value < std : : string > ( & hostname ) ) ;
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 " ) ;
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-09-13 19:45:36 +00:00
( " config,c " , po : : value < std : : string > ( ) - > default_value ( " default.conf " ) , " configuration file " )
( " 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
// set options allowed in config file
po : : options_description 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-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 ) ;
auto config_file = variable_map . at ( " config " ) . as < std : : string > ( ) ;
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 ;
}
}
}