work1

Dependencies:   mbed MAX44009 mbed-os Si7021

Revision:
1:3656b45f17a8
Parent:
0:d3e390d62607
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/AppMain.cpp	Wed May 27 12:34:37 2020 +0000
@@ -0,0 +1,831 @@
+#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;
+}
+
+