work1

Dependencies:   mbed MAX44009 mbed-os Si7021

app/AppMain.cpp

Committer:
danaeb
Date:
2020-05-28
Revision:
2:25f27478fdf9
Parent:
1:3656b45f17a8

File content as of revision 2:25f27478fdf9:

#include "mbed.h"
#include "mbed_mktime.h"
#include "rtc_api_hal.h"
#include "tools.h"
#include "AppMain.h"
#include "AppLora.h"
#include "Button.h"
#include "LedBlinker.h"
//#include "SensorTag.h"
#include "Sensor.h"
#include "SensorManager.h"
#include "CommandSample.h"
#include "CommandSampleSelector.h"
#include "CommandSampleBoard.h"
#include "AppCommand.h"
//#include "Latch.h"
#include "MemoryDeviceEeprom.h"
#include "Calendar.h"
#include "mbed-trace/mbed_trace.h"
#include "board/SensorBoard.h"
#include "board/PowerManager.h"
#include "rtc.h"
#include "Si7021.h"
#include "ProfileManager.h"
#define MS_PER_SEC 1000
#define MINUTES_PER_DAY 1440

#define JOIN_TRIES 3

#define LED_RUN_PERIOD 1000
#define LED_RUN_COUNT 2

// no need to define join failed because
// after all join tries failed the device go in stop mode
// U
// and advertise it
//#define LED_JOIN_FAILED_PERIOD 1000
//#define LED_JOIN_FAILED_COUNT 3
//
#define SENSOR_GROUP_MAIN 0x1
#define SENSOR_GROUP_ALTERNATE 0x2

#define LED_JOIN_JOINING_PERIOD 100
#define JOIN_RETRY_DELAY_MS 3000

#define LED_STOP_PERIOD 1000
#define LED_STOP_COUNT  5
#define COMMAND_TIMESTAMP_PORT 0x80

#define RUN_MODE_STANDARD 0x01
#define RUN_MODE_TEST 0x02
#define RUN_MODE_PRODUCTION_TEST 0x03

#ifdef MBED_CONF_APP_RUN_MODE
#endif
// TEST
#if MBED_CONF_APP_RUN_MODE == RUN_MODE_STANDARD
    #define SAMPLING_PERIOD 5
    #define NUMBER_OF_SAMPLE_BEFORE_SEND 1
    #define NUMBER_OF_TRANSMIT_BEFORE_ALERNATE 1
#elif MBED_CONF_APP_RUN_MODE == RUN_MODE_PRODUCTION_TEST
    #define SKIP_JOIN
    #define SAMPLING_PERIOD 1
    #define NUMBER_OF_SAMPLE_BEFORE_SEND 255
    #define NUMBER_OF_TRANSMIT_BEFORE_ALERNATE 1
#elif MBED_CONF_APP_RUN_MODE == RUN_MODE_TEST
    #define SAMPLING_PERIOD 2
    #define NUMBER_OF_SAMPLE_BEFORE_SEND 1
    #define NUMBER_OF_TRANSMIT_BEFORE_ALERNATE 1
#endif
#define MAX_SEND_DELAY (SAMPLING_PERIOD*60-15)

#define SOFTWARE_WATCHDOG_TIMEOUT 12


#define CALENDAR_CONFIGURATION_SIZE 1 // to save the start time offset
#define CALENDAR_DAY_COUNT 32
#define CALENDAR_TIME_SLOT_SIZE 5 // in minutes
#define CALENDAR_TIME_SLOT_PER_DAY ((MINUTES_PER_DAY) / (CALENDAR_TIME_SLOT_SIZE))

#define CALENDAR_BIT_COUNT ((CALENDAR_TIME_SLOT_PER_DAY)*(CALENDAR_DAY_COUNT) + \
        CALENDAR_CONFIGURATION_SIZE*8)

// +7 for ceiling
#define CALENDAR_BYTES_COUNT ((CALENDAR_BIT_COUNT+7)/8)


#define LATCH_TRIGGER_VOLTAGE 12000 // 12 volt
#define TRACE_GROUP "sm"


using namespace events;
using namespace liboo;

// TODO Too much code in this modules, it will need some refactoring


Sensor *AppMain::_sensor_array[CONFIG_SENSOR_COUNT] = {0};
SensorManager *AppMain::_sensor_manager = NULL;
EventQueue *AppMain::_queue = NULL;
LedBlinker *AppMain::_led_blinker = NULL;
Button *AppMain::_button = NULL;
//Calendar *AppMain::_calendar = NULL;
//MemoryDeviceEeprom *AppMain::_memory_device_calendar = NULL;
//Latch *AppMain::_latch = NULL;

I2C i2c(I2C_SDA, I2C_SCL);
SensorData AppMain::_sensor_data;

AppMain::GLOBAL_STATE AppMain::_global_state;
AppMain::RUN_STATE AppMain::_run_state;

u32 AppMain::_daily_send_s;
u8 AppMain::_remaining_join_tries;
u8 AppMain::_remaining_sample_before_send;
u8 AppMain::_transmit_count_before_alternate;
u8 AppMain::_send_delay;
u8 AppMain::_history_size;
//bool AppMain::_forced_timestamp;
//bool AppMain::_next_frame_timestamped;

// buffer for uplink commands
u8 AppMain::_command_payload[COMMAND_PAYLOAD_SIZE];
char AppMain::_time_buffer[24];

u8 AppMain::_software_watchdog_time;
bool AppMain::_software_watchdog_is_enable;

void AppMain::_software_watchdog_enable(){
    _software_watchdog_reset();
    _software_watchdog_is_enable = true;
}

void AppMain::_software_watchdog_disable(){
    _software_watchdog_is_enable = false;

}

void AppMain::_software_watchdog_reset(){
    _software_watchdog_time = 0;
}

void AppMain::_software_watchdog_increment(){
    if(!_software_watchdog_is_enable){
        return;
    }
    ++_software_watchdog_time;
    if(_software_watchdog_time >= SOFTWARE_WATCHDOG_TIMEOUT){
        tr_error("watchdog triggered, the system will restart");
        NVIC_SystemReset();
        //mbed_reset();
    }
}

CommandSample *AppMain::_command_sample_array[COMMAND_COUNT] = {
    new CommandSampleCapacitive1(),
    new CommandSampleCapacitive2(),
    new CommandSampleCapacitive3(),
    new CommandSampleCapacitive4(),
    new CommandSampleCapacitive5(),
    //new CommandSampleTest(),
    /*
    new CommandSampleGenericTag1(),
    new CommandSample1Tag1(),
    new CommandSample2Tag1(),
    new CommandSample3Tag1(),
    new CommandSample4Tag1(),
    new CommandSample5Tag1(),
    new CommandSample6Tag1(),
    new CommandSample7Tag1(),
    new CommandSampleGenericAlternativeTag1(),
    new CommandSample1AlternativeTag1(),
    new CommandSample2AlternativeTag1(),
    new CommandSample3AlternativeTag1(),
    new CommandSample4AlternativeTag1(),
    new CommandSample5AlternativeTag1(),
    new CommandSample6AlternativeTag1(),
    new CommandSample7AlternativeTag1(),
    */

};

CommandSampleSelector AppMain::_command_selector(_command_sample_array, COMMAND_COUNT,
                                                &_sensor_data);

void AppMain::set_history_size(u8 history_size){
    if(history_size < 9){
        _history_size = history_size;
        tr_info("setting history size to %d", _history_size);
    }
}

void AppMain::_minute_elapsed_callback_handler(void){
    tr_debug("one minute elapsed ");

    Rtc::get_instance()->get_formatted_time(_time_buffer);
    //_write_time(_time_buffer);
    tr_info("time: %s", _time_buffer);
    _software_watchdog_increment();
    _queue->call(callback(_global_event_handler), EVENT_MINUTE_ELAPSED);
}

void AppMain::_daily_send_callback_handler(void){
    tr_info("daily callback");
    Rtc::get_instance()->get_formatted_time(_time_buffer);
    //_write_time(_time_buffer);
    tr_debug("time: %s", _time_buffer);
    _queue->call(callback(_global_event_handler), EVENT_DAILY_SEND);
}

void AppMain::_button_callback_handler(Button::ACTION action){
    tr_debug("button handler: ");
    switch(action){
        case Button::ACTION_SHORT:
            tr_debug("\tshort");
            _queue->call(callback(&AppMain::_global_event_handler),
                    AppMain::EVENT_SHORT_PUSH);
            break;
        case Button::ACTION_LONG:
            tr_debug("\tshort");
            _queue->call(callback(&AppMain::_global_event_handler),
                    AppMain::EVENT_LONG_PUSH);
            break;
    }
}


void AppMain::_lora_callback_handler(AppLora::EVENT lora_event){
    switch(lora_event){
        case AppLora::EVENT_CONNECTED:
            _global_event_handler(AppMain::EVENT_JOIN_SUCCESS);
            break;
        case AppLora::EVENT_DISCONNECTED:
            _global_event_handler(AppMain::EVENT_DISCONNECTED);
            break;
        case AppLora::EVENT_CONNECTION_ERROR:
            _global_event_handler(AppMain::EVENT_CONNECTION_ERROR);
            break;
        case AppLora::EVENT_JOIN_FAILURE:
            _global_event_handler(AppMain::EVENT_JOIN_FAILED);
            break;
        case AppLora::EVENT_TX_DONE:
            _global_event_handler(AppMain::EVENT_TX_DONE);
            tr_info("tx done");
            break;
        case AppLora::EVENT_TX_ERROR:
            _global_event_handler(AppMain::EVENT_TX_ERROR);
            tr_err("tx error");
            break;
        case AppLora::EVENT_RX_ERROR:
            _global_event_handler(AppMain::EVENT_RX_ERROR);
            tr_err("rx error");
            break;
        default:
            // nothing to do for now
            break;
    }
}
void AppMain::_global_event_handler_from_interrupt(AppMain::EVENT event){
    _queue->call(callback(&AppMain::_global_event_handler), event);
}

void AppMain::_enter_global_state_stop(void){
    tr_info("enter stop mode");
    _software_watchdog_disable();
    _global_state = AppMain::GLOBAL_STATE_STOP;
    _remaining_join_tries = JOIN_TRIES;

    // sensor manager allocated in create_sensors
    Rtc *rtc = Rtc::get_instance();
    // disable minutes callback
    rtc->disable_alarm(Rtc::ALARM_A);
    // disable daily callback
    rtc->disable_alarm(Rtc::ALARM_B);
    //_calendar->disable();
    PowerManager *pm= PowerManager::get_instance();
    pm->sleep_mode();

    _led_blinker->blink(LED_STOP_PERIOD, LED_STOP_COUNT);


    // unlock latch
}

void AppMain::_enter_global_state_disconnecting(void){
    tr_info("enter disconnecting mode");
    _global_state = AppMain::GLOBAL_STATE_DISCONNECTING;
    _queue->call(AppLora::disconnect);
}

void AppMain::_enter_global_state_join(void){
    tr_info("enter join mode");
    _global_state = AppMain::GLOBAL_STATE_JOIN;
    _queue->call(AppLora::connect);

    // this call can potentially make the rx window drifting
    // because the led is blinked through I2C, this takes time
    //_led_blinker->blink_from_interrupt(LED_JOIN_JOINING_PERIOD,
    //LedBlinker::COUNT_INFINITE);
}
#if 0
void AppMain::_write_time(char* buffer){
    time_t time;
    tm time_info;
    Rtc &rtc = Rtc::get_instance();
    time = rtc.get_time();
    // epoch timestamp the 1 January 2018 0h00
    time += 1514764800;
    _rtc_localtime(time, &time_info, RTC_4_YEAR_LEAP_YEAR_SUPPORT);
    sprintf(buffer, "%d-%d-%d %d:%d:%d", time_info.tm_mday,
            time_info.tm_mon+1, time_info.tm_year+1900, time_info.tm_hour,
            time_info.tm_min, time_info.tm_sec);
}
#endif

void AppMain::_enter_global_state_run(void){
    tr_info("enter run mode");
    _software_watchdog_enable();
    _global_state = AppMain::GLOBAL_STATE_RUN;
    _transmit_count_before_alternate = 1; // first packet use alternate

    // random number are recomputed at each restart
    AppMain::_compute_random_number();

    Rtc *rtc = Rtc::get_instance();
    time_t time = _daily_send_s;
    tm time_info;
    _rtc_localtime(time, &time_info, RTC_4_YEAR_LEAP_YEAR_SUPPORT);

    // schedule sampling, sending and calendar check
    rtc->set_alarm(Rtc::ALARM_A, Rtc::ALARM_FIELD_UNUSED,
            Rtc::ALARM_FIELD_UNUSED, 0x00);


    tr_info("setting daily send to %d:%d:%d", time_info.tm_hour,
            time_info.tm_min, time_info.tm_sec);
    //rtc.set_alarm(Rtc::ALARM_B, Rtc::ALARM_FIELD_UNUSED,
    //Rtc::ALARM_FIELD_UNUSED, time_info.tm_sec);
    rtc->set_alarm(Rtc::ALARM_B, time_info.tm_hour,
            time_info.tm_min, time_info.tm_sec);

    _led_blinker->blink(LED_RUN_PERIOD, LED_RUN_COUNT);

    // when start run mode check calendar
    // to check the latch is in correct state
#if 0
    tr_debug("enable calendar");
    //_calendar->enable();
    tr_debug("checking calendar");
    //_calendar->check(rtc->get_time());
    tr_debug("check calendar done");
    //wait(5);
#endif
    //_forced_timestamp = true;

    // erase all old samples
    _sensor_data.reset();
    // send timestamped packet until answer
    //_forced_timestamp = true;

    // send a first message at boot
    // It enable to get the next packet size
#if 0
    if(_send_empty_timestamped() < 0){
        _run_state = AppMain::RUN_STATE_WAIT_SEND_COMPLETE;
    }
    else{
        _run_state = AppMain::RUN_STATE_READY;
    }
#endif

}

void AppMain::_global_event_handler(AppMain::EVENT event){
    tr_debug("global_event_handler");
    tr_debug("\tglobal_state: %d", _global_state);
    tr_debug("\tevent : %d", event);
    switch(_global_state){
        case GLOBAL_STATE_STOP:
            if(event == AppMain::EVENT_SHORT_PUSH){
                _enter_global_state_join();
            }
            break;
        case GLOBAL_STATE_JOIN:
            if(event == AppMain::EVENT_JOIN_SUCCESS){
                _led_blinker->stop();
                _enter_global_state_run();
            }
            if(event == AppMain::EVENT_CONNECTION_ERROR ||
                    event == AppMain::EVENT_TX_ERROR ||
                    event == AppMain::EVENT_JOIN_FAILED){
                --_remaining_join_tries;
                if(_remaining_join_tries == 0){
                    tr_err("join failed after %d tries", JOIN_TRIES);
                    _enter_global_state_stop();
                }
                else{
                    //retry join
                    tr_warn("join failed, retry join in %dms", JOIN_RETRY_DELAY_MS);
                    _led_blinker->stop();
                    //wait_ms(JOIN_RETRY_DELAY_MS);
                    //ThisThread::sleep_for(JOIN_RETRY_DELAY_MS);
                    wait_us(JOIN_RETRY_DELAY_MS*1000);
                    _enter_global_state_join();
                }
            }

            break;
        case GLOBAL_STATE_RUN:
            if(event == AppMain::EVENT_SHORT_PUSH){
                _send_empty();
                //_write_time(_time_buffer);
                Rtc::get_instance()->get_formatted_time(_time_buffer);
                tr_info("time: %s", _time_buffer);
                //Rtc &rtc = Rtc::get_instance();
                //rtc.set_time(9537861);

            }
            else if(event == AppMain::EVENT_LONG_PUSH){
                _enter_global_state_disconnecting();
            }
            else{
                _run_event_handler(event);
            }
            break;
        case GLOBAL_STATE_DISCONNECTING:
            if(event == AppMain::EVENT_DISCONNECTED){
                _enter_global_state_stop();
            }
    }
}


void AppMain::_run_event_handler(AppMain::EVENT event){
    switch(_run_state){
        case RUN_STATE_READY:
            if(event == AppMain::EVENT_MINUTE_ELAPSED){
                Rtc *rtc = Rtc::get_instance();
                time_t time = rtc->get_time();
                tm time_info;
                _rtc_localtime(time, &time_info, RTC_4_YEAR_LEAP_YEAR_SUPPORT);

                if((time_info.tm_min % SAMPLING_PERIOD) == 0) // if minute is pair
                {
                    bool is_hourly_send = false;
                    // forbid latch operation to avoid wrong measure or degraded RF
                    // this cannot protect against a latch which is already charging
                    // it will forbid only trigger latch
                    // TODO lock latch
                    _sensor_manager->sample_all();
                    --_remaining_sample_before_send;

                    tr_debug("remaining sampling before transmit: %d",
                                _remaining_sample_before_send);
                    if(_remaining_sample_before_send == 0){
                        /*
                        static u8 sample_id = 0;
                        ++sample_id;
                        */
                        _remaining_sample_before_send = NUMBER_OF_SAMPLE_BEFORE_SEND;
                        --_transmit_count_before_alternate;
                        if(_transmit_count_before_alternate == 0){
                            _transmit_count_before_alternate = NUMBER_OF_TRANSMIT_BEFORE_ALERNATE;
                            is_hourly_send = 1;
                        }

                        _sensor_manager->record_samples();
                        if(!_sensor_manager->are_all_sensor_disabled()){
                            tr_info(" Scheduling transmit in %d secondes", _send_delay);
                            //_write_time(_time_buffer);
                            Rtc::get_instance()->get_formatted_time(_time_buffer);
                            tr_debug("time: %s", _time_buffer);
                            _queue->call_in(_send_delay*1000,
                                    callback(_scheduled_send), is_hourly_send);
                        }
                    }
                    else{
                        // [TODO]unlock latch
                    }
                    //check calendar every minnutes
#if 0
                    Rtc *rtc = Rtc::get_instance();
                    u32 time = rtc->get_time();
                    _calendar->check(time);
#endif
                }

#if MBED_CONF_APP_RUN_MODE == RUN_MODE_PRODUCTION_TEST
                tr_info("turn on pump");
                // toggle latch 10s
                PowerManager *pm= PowerManager::get_instance();
                pm->lock_and_turn_on_motor();
                wait_us(10*1000*1000);
                pm->unlock_and_turn_off_motor();
                tr_info("turn off pump");
#endif
            }
            if(event == AppMain::EVENT_DAILY_SEND){
                // at least one packet is timestamped every days
                //_next_frame_timestamped = true;
                if(_sensor_manager->are_all_sensor_disabled()){
                    if(_send_empty() < 0){
                        _run_state = RUN_STATE_WAIT_SEND_COMPLETE;
                    }
                }
            }
            break;
        case RUN_STATE_WAIT_SEND_COMPLETE:
            // other event like daily send or event minutes elapsed
            // doesnt need to be process together because
            // theses event are generated in two different mode.
            // One is when sensor are enable the other is when all sensor are disabled
            if( event == AppMain::EVENT_TX_DONE ||
                    event == AppMain::EVENT_RX_ERROR ||
                    event == AppMain::EVENT_TX_ERROR){
                // TODO unlock latch
                _run_state = AppMain::RUN_STATE_READY;
            }
            break;
    }
}

void AppMain::_compute_random_number(void){
    u32 rand;
    //value between 0 and 4 which is the number of samples sinces the last
    //send
    rand = AppLora::get_random();
    _remaining_sample_before_send = (rand % NUMBER_OF_SAMPLE_BEFORE_SEND) + 1;
    //_remaining_sample_before_send  = 1;
    tr_info("random: Transmit in %d sample", _remaining_sample_before_send);

    // delay between the 5th sample and the send action
    // value between 0 and 90 seconds. The actual range could be 0-120 seconds
    // but in order to send the data before the next sample 30 secondes are removed.
    rand = AppLora::get_random();
    _send_delay = rand % MAX_SEND_DELAY;
    tr_info("random: Send delay %ds", _send_delay);

    // hours in seconds for the dayli sent
    // this sending is done only when all sensors are deactivated
    rand = AppLora::get_random();
    _daily_send_s = rand % 86400; // 86400 number of seconds in one day
    tr_info("random: Daily send %lus", _daily_send_s);

}

// this function is called only once at start
void AppMain::initialize(EventQueue *queue){
    tr_info("Main initialization");
    _queue = queue;

    ProfileManager* profile_manager = ProfileManager::get_instance();

    //CommandSample::set_endianness(CommandSample::EndiannessBig);

    AppLora::set_lora_callback(_lora_callback_handler);
    //wait(0.05);
    wait_us(50000);

    // sensor manager allocated in create_sensors
    Rtc *rtc = Rtc::get_instance();
    rtc->set_alarm_callback(Rtc::ALARM_B, _daily_send_callback_handler);
    rtc->set_alarm_callback(Rtc::ALARM_A, _minute_elapsed_callback_handler);

    // set default time
    // set default time to delivery day: 2018/09/12 00:00
    rtc->set_rtc_time(22032000);

    //pcal_i2c.frequency(100000);
    //_gpio_exp = new PCAL6416(pcal_i2c, 0x42);
    _led_blinker = new LedBlinker(AppMain::_queue, LED1);
    _button = new Button(_led_blinker, USER_BUTTON);

    _history_size = 1;
    _create_sensors();
    _button->set_action_callback(_button_callback_handler);
    // sensor manager allocated in create_sensors
    //_memory_device_calendar = new MemoryDeviceEeprom(0, CALENDAR_BYTES_COUNT);
    //_latch = new Latch(LATCH_TRIGGER_VOLTAGE, _gpio_exp);
#if 0
    _calendar = new Calendar(_memory_device_calendar,
            CALENDAR_DAY_COUNT,
            CALENDAR_TIME_SLOT_PER_DAY,
            _trigger_latch);
#endif
    AppCommand::configure(_sensor_manager, NULL /* _calendar */);

    // short wait to charge all capacitor before triggering latch
    //wait_ms(20);
    wait_us(20000);
    //_enter_global_state_stop();
#ifdef SKIP_JOIN
    _enter_global_state_run();
#else
    _enter_global_state_join();
#endif

}

void AppMain::_trigger_latch(bool sens){
    if(sens){
        tr_debug("trigger latch forward");
        //_latch->trigger(Latch::TRIGGER_SENS_FORWARD);
    }
    else{
        tr_debug("trigger latch reverse");
        //_latch->trigger(Latch::TRIGGER_SENS_REVERSE);
    }
}

void AppMain::_create_sensors(void){

    PowerManager *pm= PowerManager::get_instance();
    pm->run_mode();

    //TPS62740 *tps = TPS62740::get_instance();
    //tps->set_voltage(TPS62740::V3_0);

    tr_info("initialize sensors");
    tr_debug("initialize battery");
    _sensor_array[0] = new SensorBoardBattery(SENSOR_ID_BOARD_BAT, SENSOR_GROUP_MAIN);

    tr_debug("initialize SI7021");
    Si7021 *si = new Si7021(I2C_SDA, I2C_SCL);
    bool ping_success = si->check();
    if(ping_success){
        tr_debug("SI7021 detected");
    }
    else{
        tr_warn("couldn't communicate with SI7021");
    }

    _sensor_array[1] = new SensorBoardTempRH(SENSOR_ID_BOARD_TEMPERATURE,
                                             SENSOR_GROUP_MAIN, si);
    _sensor_array[2] = new SensorBoardTempRH(SENSOR_ID_BOARD_HUMIDITY,
                                              SENSOR_GROUP_MAIN, si);

    tr_debug("initialize MAX44009");
    MAX44009 *max44009 = new MAX44009(&i2c, 0x94);
    _sensor_array[3] = new SensorBoardIllumination(SENSOR_ID_BOARD_ILLUMINATION,
                                                    SENSOR_GROUP_MAIN, max44009);
    tr_debug("initialize sensor board 1");
    _sensor_array[4] = new SensorBoardCapacitive(SENSOR_ID_BOARD_CAPACITIVE_0, SENSOR_GROUP_MAIN);
    tr_debug("initialize sensor board 2");
    _sensor_array[5] = new SensorBoardCapacitive(SENSOR_ID_BOARD_CAPACITIVE_1, SENSOR_GROUP_MAIN);
    tr_debug("initialize sensor board 3");
    _sensor_array[6] = new SensorBoardCapacitive(SENSOR_ID_BOARD_CAPACITIVE_2, SENSOR_GROUP_MAIN);
    tr_debug("initialize sensor board 4");
    _sensor_array[7] = new SensorBoardCapacitive(SENSOR_ID_BOARD_CAPACITIVE_3, SENSOR_GROUP_MAIN);

    //_sensor_array[11] = new SensorImpulse(SENSOR_ID_IMPULSE);

    //_sensor_array[11] = new SensorImpulse(SENSOR_ID_IMPULSE);
    //_sensor_data = new SensorData();
    //_sensor_manager = new SensorManager(_sensor_data, _sensor_array, 11);
    tr_debug("initialize sensor manager");
    _sensor_manager = SensorManager::get_instance();
    _sensor_manager->initialize(&_sensor_data, _sensor_array, CONFIG_SENSOR_COUNT);
    tr_info("initialization done");
}

void AppMain::incoming_lora_message_callback(void){
    u8 app_port;
    u8 data[256];
    s16 received_size;
    u16 data_size = 256;

    //_forced_timestamp = false;
    received_size = AppLora::get_rx_message(data, data_size, app_port);
    if(received_size >= 0){
        AppCommand::process_command(app_port, data, received_size);
    }
    // else it an error
}

void AppMain::_scheduled_send(bool is_hourly_send){
    tr_info("proceed scheduled send");
    Rtc::get_instance()->get_formatted_time(_time_buffer);

    //_write_time(_time_buffer);
    tr_debug("time: %s", _time_buffer);
    if(_send_samples(is_hourly_send) >= 0){
        _run_state = RUN_STATE_WAIT_SEND_COMPLETE;
    }
}

s8 AppMain::_send_samples(bool is_hourly_send){
    CommandSample *selected_command;
    //command_field_t field;
    u32 timestamp;
    s16 retcode;
    u8 max_lora_payload;
    u8 command_payload_size = 0;
    u8 command_port = 0;
    //u8 history_size = _history_size;
    u8 history_size;
    u8 command_group = SENSOR_GROUP_MAIN;
    //bool _add_timestamp = false;

    // select if the frame must be timestamped
    // not generic
    //if(_next_frame_timestamped || _forced_timestamp){
        //_add_timestamp = true;
    //}

    // not generic
    if(is_hourly_send){
        // do nothing for now
        //history_size = _history_size;
        history_size = 1;
        command_group = SENSOR_GROUP_MAIN;
    }
    else {
        history_size = _history_size;
    }
    // get mask, it tells what are the sensor used which have set values
    // in memory for a given  history size

    //_command_selector.compute_requiered_field(&_sensor_data, history_size, &field);

#if 0
    if(!is_hourly_send){
        //these value are only set one time by hours
        field.battery = 0;
        field.board_temperature = 0;
        field.board_humidity = 0;
    }
#endif
    /*
       else{
    // it is the hourly send
    // do not use history
    history_size = 1;
    }
    */

    //tr_debug("requested fields: 0x%02u", field.bits);
    // the the mask is used to select the smallest command
    //selected_command = _command_selector.get_command_sample(&_sensor_data, &field, history_size);
    selected_command = _command_selector.get_command_sample(history_size, command_group);


    bool is_pump_on = AppCommand::get_pump_state();
    //tr_debug("requested fields: 0x%02u", field.bits);
    if(selected_command == NULL){
        tr_err("no matching command");
        return -1;
    }
    else{
        tr_info("command %d selected", selected_command->get_command_id(false,
                                                    is_pump_on/*_calendar->is_enable()*/ ));
    }
    // get maxlora payload
    max_lora_payload = AppLora::get_next_transmission_max_size();
    tr_info("LoRa max packet %d", max_lora_payload);

    // Decrement the size of history while the command is bigger than
    // the max lora payload size (for the current bandwith and sf)

    // not completly optmised, changing the history size can also
    // change the command used. Here we keep using the same command
    tr_info("expected history size %d", history_size);
    while(selected_command->get_command_size(history_size, false) >
            max_lora_payload){
        --history_size;

        // not sure it can happen but just in case
        // Doesnt try to send packet for history size of 0, this doesnt make sense
        tr_warn("history size too, using : %d instead", history_size);
        if(history_size == 0){
            tr_err("history size: 0\r\n");
            return -1;
        }
    }
    tr_info("selected history size %d", history_size);
    //
    // command id is different if timestamp is used
    command_port = selected_command->get_command_id(false,
                                                    is_pump_on /* _calendar->is_enable() */);

    tr_debug("using command port: %d", command_port);
    // get the timestamp at the last moment
    Rtc *rtc = Rtc::get_instance();
    timestamp = rtc->get_time();

    command_payload_size = selected_command->generate_command(
                                &_sensor_data, history_size,
                                false, timestamp,
                                _command_payload, COMMAND_PAYLOAD_SIZE);

    tr_info("payload size : %d", command_payload_size);
    if(_global_state == GLOBAL_STATE_RUN){
        tr_info("sending samples");
        // acknowledged -> false
        retcode = AppLora::send_message(_command_payload,
                                         command_payload_size, command_port, false);
        if(retcode < 0){
            tr_err("send error %d\r\n", retcode);
            return -1;
        }
        else{
            _software_watchdog_reset();
            //_next_frame_timestamped = false;
        }
    }
    return 0;
}

s8 AppMain::_send_empty(void){
    u16 command_size;
    u32 time;

    Rtc *rtc = Rtc::get_instance();
    time = rtc->get_time();
    command_size = CommandSample::generate_raw_command(true, time, NULL, 0,
            _command_payload, COMMAND_PAYLOAD_SIZE);
    if(_global_state == GLOBAL_STATE_RUN){
        s16 retcode;
        tr_info("sending empty frame");
        // acknowledged -> false
        retcode = AppLora::send_message(_command_payload, command_size,
                COMMAND_TIMESTAMP_PORT, false);
        if(retcode < 0){
            tr_err("send error %d\r\n", retcode);
            return -1;
        }
    }
    return 0;
}