read and push

Dependencies:   X_NUCLEO_IKS01A1 MLX90614 d7a_1x wizzi-utils

Fork of D7A_1x_demo_sensors_OS5 by WizziLab

main.cpp

Committer:
shawe
Date:
2017-02-27
Revision:
16:ad7842fcd376
Parent:
15:f8e5805c696c

File content as of revision 16:ad7842fcd376:

// This project is a demo of the DASH7 1.x stack
// @autor: jeremie@wizzilab.com
// @date: 2016-12-20
//
// ----- SETUP -----
// This programm has been tested with the following hardware:
// --> NUCLEO-L152RE + DASH7 enabled SHIELD_SP1ML_v1.0
// For this demo to work, you need the previously described hardware.
// A DASH7 1.x gateway have to be available in your area in order to retreive the data.
//
// ----- FILE SYSTEM -----
// DASH7 is a file system based protocol. You read and write in files that are present on your device or your modem.
// Some callbacks can be implemented by the user for allowing the stack to acceed the local file system. (See description of the callbacks for more details)
// The file system is user defined and can be changed to fit your needs.
// This demo uses a simple RAM file system.
// The files are defined in files.h
// The files values are initialized in files.cpp
// 
// ----- SENSORS -----
// The sensors can be simulated (random data).
//
// ----- DEMO -----
// This demo starts 7 threads (one for each sensor value) that will periodically read the sensor value
// and depending on the sensor configuration file, will send the data to the DASH7 network (notification)
// You can also report the alarm status by pushing the board's button (blue)
// 
// ----- DEBUG -----
// Several debugging options are available in wizzi-utils/dbg/dbg.h
// An ASSERT is a fatal error. By default, this error will reboot the device.
#include "mbed.h"
#include "rtos.h"
#include "rtos_idle.h"
#include "d7a.h"
#include "sensors.h"
#include "simul.h"
#include "dbg.h"
#include "DebouncedInterrupt.h"
#include "files.h"
#include "MLX90614.h"
#include "DevI2C.h"

#if defined(TARGET_STM32L152RE)
    #define D7A_PIN_TX              (D10)
    #define D7A_PIN_RX              (D2)
    #define D7A_PIN_RTS             (D13)
    #define D7A_PIN_CTS             (D9)
    #define D7A_PIN_RESET           (A3)
    #define DEBUG_LED               (LED1)
    #define DEBUG_BUTTON            (USER_BUTTON)
    #define SENSOR_I2C_SDA          (D14)
    #define SENSOR_I2C_SCL          (D15) 
    #define VOLT_PIN                (A0)
    
#elif defined(TARGET_STM32L432KC)
    // -----------------------------------------------
    // Hardware configuration for sh2050
    // -----------------------------------------------
    #define D7A_PIN_TX              (D5)
    #define D7A_PIN_RX              (D4)
    #define D7A_PIN_RTS             (D11)
    #define D7A_PIN_CTS             (D10)
    #define D7A_PIN_RESET           (D12)
    #define DEBUG_LED               (D13) // LED1
    #define DEBUG_BUTTON            (D9)
    #define SENSOR_I2C_SDA          (D0)
    #define SENSOR_I2C_SCL          (D1)
    // TODO Check which pin to use on the small board for voltage stuff
    #define VOLT_PIN                (A0)
    
#else
    #error "Please choose or add the right platform."
#endif

#define DGB_LED_BLINK_PERIOD        (500)
#define _TEM1_EN_           (1)
#define _VOLTAGE_EN_        (1)
#if (_VOLTAGE_EN_ == 1)
AnalogIn volt_pin(VOLT_PIN);
#endif

MLX90614 *mlxSensor;
Semaphore button_user(0);
Semaphore thread_ready(0);

// Interrupt Service Routine on button press.
void button_push_isr( void )
{
    button_user.release();
}

// file modified queue
Queue<void, 8> fm_queue;

// -----------------------------------------------
// callbacks
// -----------------------------------------------
/**
    Write data into a file.

    @param const uint8_t            File ID
    @param const uint16_t           Offset from start of data
    @param const uint16_t           Size of data to be written
    @param const uint8_t* const     Pointer to the data to write
    @return uint32_t                Size of written data
*/
uint32_t write_file_callback(const uint8_t file_id,
                             const uint16_t offset,
                             const uint16_t size,
                             const uint8_t* const content)
{
    uint32_t ret = 0;
    
    IPRINT("Write file %d offset:%d size:%d\r\n", file_id, offset, size);
    
    ret = fs_write_file(file_id, offset, size, content);

    WARNING(ret, "File %d not found\r\n", file_id);
    
    // Indicate that the file has been modified
    fm_queue.put((void*)file_id);
    
    return ret;
}

/**
    Read data from a file.

    @param const uint8_t            File ID
    @param const uint16_t           Offset from start of data
    @param const uint16_t           Size of data to be read
    @param const uint8_t* const     Pointer to the reading buffer
    @return uint32_t                Size of read data
*/
uint32_t read_file_callback(const uint8_t file_id,
                            const uint16_t offset,
                            const uint16_t size,
                            uint8_t* buf)
{
    PRINT("Read file %d offset:%d size:%d\r\n", file_id, offset, size);
    uint32_t ret = fs_read_file(file_id, offset, size, buf);
    WARNING(ret, "File %d not found\r\n", file_id);
    return ret;
}

/**
    Called when a notification is finished

    @param const uint8_t            File ID
    @param const uint8_t            Error code
    @return void
*/
void notif_done_callback(const uint8_t file_id, const uint8_t error)
{
    PRINT("Notif FID %d done. err %d\r\n", file_id, error);
}

void unsolicited_callback(d7a_msg_t** uns)
{
    PRINT("UNSOLICITED MESSAGE:\r\n");
    d7a_print_msg(uns);
    d7a_free_msg(uns);
}

// callbacks structure
const d7a_callbacks_t callbacks = {
    .write_file = write_file_callback,   // If NULL you cannot declare files
    .read_file = read_file_callback,     // If NULL you cannot declare files
    .notif_done = notif_done_callback,
    .unsolicited_msg = unsolicited_callback,
};

// Com configuration for the DASH7 shield
const d7a_com_config_t shield_config = {
    .tx  = D7A_PIN_TX,
    .rx  = D7A_PIN_RX,
    .rts = D7A_PIN_RTS,
    .cts = D7A_PIN_CTS,
};

// Check parameters to see if data should be send
static bool report_needed(sensor_config_t* cfg, int32_t value, int32_t last_value, uint32_t last_report_time)
{
    switch (cfg->report_type)
    {
        case REPORT_ALWAYS:
            // Send a report at each measure
            IPRINT("Notif cause 1\r\n");
            return true;
        case REPORT_ON_DIFFERENCE:
            // Send a report when the difference between the last reported measure and the current mesure is greater than max_diff
            if (abs(last_value - value) >= cfg->max_diff)
            {
                IPRINT("Notif cause 2 last:%d new:%d max_diff:%d\r\n", last_value, value, cfg->max_diff);
                return true;
            }
            break;
        case REPORT_ON_THRESHOLD:
            // Send a report when crossing a threshold
            if (   (value >= cfg->threshold_high && last_value < cfg->threshold_high)
                || (value <= cfg->threshold_low  && last_value > cfg->threshold_low)
                || (value < cfg->threshold_high  && last_value >= cfg->threshold_high)
                || (value > cfg->threshold_low   && last_value <= cfg->threshold_low))
            {
                IPRINT("Notif cause 3 last:%d new:%d th:%d tl:%d\r\n", last_value, value, cfg->threshold_high, cfg->threshold_low);
                return true;
            }
            break;
        default:
            break;
    }
    
    // Send a report if it's been more than max_period since the last report
    if (((last_report_time/1000) >= cfg->max_period) && (cfg->max_period != 0))
    {
        IPRINT("Notif cause 4 max_period:%d time:%d\r\n", cfg->max_period, last_report_time);
        return true;
    }

    return false;
}

// -----------------------------------------------
// Sensor Threads
// -----------------------------------------------
typedef struct
{
    // Number of data fields
    uint32_t nb_values;
    // Total size of data
    uint32_t data_size;
    // Read value function
    bool (*read_value)(int32_t*);
    // Last reported value
    int32_t* last_report_value;
    // Current measured value
    int32_t* current_value;
    
    // File ID of the sensor value file
    uint8_t value_file_id;
    // Sensor configuration file ID
    uint8_t cfg_file_id;
    // Sensor configuration context
    sensor_config_t cfg;
    
} sensor_thread_ctx_t;

sensor_thread_ctx_t* g_thread_ctx;

// Initialisation of the sensor thread's context
#define SENSOR_THREAD_CTX(_name,_vfid,_cfid,_read_func,_nb_values) \
    int32_t _name##_last_report[_nb_values];\
    int32_t _name##_current_value[_nb_values];\
    sensor_thread_ctx_t _name##_thread_ctx = {\
        .nb_values = _nb_values,\
        .data_size = _nb_values * sizeof(int32_t),\
        .read_value = _read_func,\
        .last_report_value = (int32_t*)&_name##_last_report,\
        .current_value = (int32_t*)&_name##_current_value,\
        .value_file_id = _vfid,\
        .cfg_file_id = _cfid\
    }

__inline int32_t float2_to_int(float v)
{
    return (int32_t)(v*100);
}

bool tem1_get_value(int32_t* buf)
{
#if (_TEM1_EN_ == 0)
    return simul_sensor_value(buf, 1, 1100, 3900);
#elif (_TEM1_EN_ == 1)
    float ambient = mlxSensor->ambientTemp();
    float object = mlxSensor->objectTemp();
    PRINT("Got %f || %f\r\n", ambient, object);
    buf[0] = float2_to_int(object);    
    buf[1] = float2_to_int(ambient);    
    return false;
#endif
}
SENSOR_THREAD_CTX(tem1, TEM1_VALUE_FILE_ID, TEM1_CFG_FILE_ID, tem1_get_value, 2);

bool volt_get_value(int32_t* buf)
{
#if (_VOLTAGE_EN_ == 0)
    return simul_sensor_value(buf, 1, 0, 1000);
#elif (_VOLTAGE_EN_ == 1)
    float voltage = volt_pin*1000;
    PRINT("Voltage value %f\r\n", voltage);
    buf[0] = float2_to_int(voltage);
    return false;
#else
    return false;
#endif
}
SENSOR_THREAD_CTX(volt, VOLT_VALUE_FILE_ID, VOLT_CFG_FILE_ID, volt_get_value,  1);


void sensor_thread()
{
    FPRINT("(id:0x%08x)\r\n", osThreadGetId());
    
    // Get thread context
    sensor_thread_ctx_t* th_ctx = g_thread_ctx;
        
    d7a_msg_t** resp = NULL;
    uint32_t last_report_time = 0;

    // Force a first notification
    bool first_notif = true;
    
    // Retrieve notification config from local file
    fs_read_file(th_ctx->cfg_file_id, 0, sizeof(sensor_config_t), (uint8_t*)&(th_ctx->cfg));

    // Declare the config file to allow it to be accessed via the modem
    d7a_declare(th_ctx->cfg_file_id, VOLATILE, RW_RW, sizeof(sensor_config_t), sizeof(sensor_config_t));
    
    // Create a file on the modem to store and notify the sensor value
    d7a_create(th_ctx->value_file_id, VOLATILE, RW_R, th_ctx->data_size, th_ctx->data_size, D7A_NOTIFICATION_FULL, D7A_ITF_REPORT_CHECKED);

    thread_ready.release();

    while (true)
    {
        // Read the sensor values
        bool err = th_ctx->read_value(th_ctx->current_value);
        
        ASSERT(!err, "Failed to read sensor value for FID: %d\r\n", th_ctx->value_file_id);

        // Check if data should be send
        for (uint8_t i = 0; i < th_ctx->nb_values; i++)
        {
            if (report_needed(&(th_ctx->cfg),
                th_ctx->current_value[i],
                th_ctx->last_report_value[i],
                last_report_time) || first_notif)
            {
                first_notif = false;
                
                PRINT("NOTIFY %3d: ", th_ctx->value_file_id);
                for (uint8_t i = 0; i < th_ctx->nb_values; i++)
                {
                    PRINT("%9ld ", (int32_t)th_ctx->current_value[i]);   
                }
                PRINT("\r\n");

                // Send data to the modem
                resp = d7a_write(th_ctx->value_file_id, 0, th_ctx->data_size, (uint8_t*)th_ctx->current_value);
                d7a_free_msg(resp);
                
                // Update last report value
                memcpy(th_ctx->last_report_value, th_ctx->current_value, th_ctx->data_size);
                // Reset last report time
                last_report_time = 0;
                break;
            }
        }
        
        // Update last report time
        last_report_time += th_ctx->cfg.period;
        
        // Wait for period
        Thread::wait(th_ctx->cfg.period);
    }
}

void file_modified_thread()
{    
    FPRINT("(id:0x%08x)\r\n", osThreadGetId());
    
    while (true)
    {
        // Wait for a file modified event
        osEvent evt = fm_queue.get(); 
        // Retrieve FID of modified file
        uint8_t fid = (uint8_t)(uint32_t)evt.value.p;
        PRINT("File %d has been modified\r\n", fid);
        
        switch (fid)
        {
            // If a configuration file has been modified, update the context
            case VOLT_CFG_FILE_ID:
                fs_read_file(fid, 0, sizeof(sensor_config_t), (uint8_t*)&(volt_thread_ctx.cfg));
                break;
            case TEM1_CFG_FILE_ID:
                fs_read_file(fid, 0, sizeof(sensor_config_t), (uint8_t*)&(tem1_thread_ctx.cfg));
                break;            
            default:
                break;
        }
    }
}

void button_user_thread()
{
    FPRINT("(id:0x%08x)\r\n", osThreadGetId());
    
    uint8_t alarm = 255;
    d7a_msg_t** resp = NULL;

    // Create the alarm file
    d7a_create(ALARM_FILE_ID, VOLATILE, RW_R, sizeof(uint8_t), sizeof(uint8_t), D7A_NOTIFICATION_FULL, D7A_ITF_SINGLE);
    
    // Update it with the default value
    resp = d7a_write(ALARM_FILE_ID, 0, sizeof(uint8_t), (uint8_t*)&alarm);
    d7a_free_msg(resp);
    
    while (true)
    {
        // Wait for button press
        button_user.wait();
        
        // Toggle alarm state
        alarm = !alarm;
        
        PRINT("BUTTON ALARM %d\r\n", alarm);
#if 1 // Switch between Notify and Distant Read/Write example
        // Send alarm state to the modem 
        resp = d7a_write(ALARM_FILE_ID, 0, sizeof(uint8_t), (uint8_t*)&alarm);
        WARNING(!resp[0]->err, "BUTTON ALARM ERR %d\r\n", resp[0]->err);
        d7a_free_msg(resp);
#else
        // Distant device that I want to acceed
#if 0 // Unicast / Broadcast
        // Unicast
        d7a_addressee_t target = {
            // Access Class
            .xcl.byte = D7A_XCL_ENDPOINT_NO,
            // Type of security you want to use
            .ctrl.bf.nls = D7A_NLS_AES_CCM_64,
            // Type of ID
            .ctrl.bf.idf = D7A_ID_UID,
            // Device ID
            .id = { 0x00, 0x1B, 0xC5, 0x0C, 0x70, 0x00, 0x07, 0xA3 },
        };
#else    
        // Broadcast
        d7a_addressee_t target = {
            // Access Class
            .xcl.byte = D7A_XCL_ENDPOINT_NO,
            // Type of security you want to use
            .ctrl.bf.nls = D7A_NLS_AES_CCM_64,
            // Type of ID
            .ctrl.bf.idf = D7A_ID_NBID,
            // Maximum number of responses (1-32)
            .id[0] = D7A_NBID(8),
        };
#endif

#if 1 // Read / Write
        // Read example
        //resp = d7a_read(MAG_CFG_FILE_ID, 12, 5, &target, D7A_ITF_ONESHOT);
        resp = d7a_read(ALARM_FILE_ID, 0, 1, NULL, &target, D7A_ITF_ONESHOT);
#else
        // Write example
        uint8_t v = 0x01;
        resp = d7a_write(ALARM_FILE_ID, 0, 1, &v, NULL, &target, D7A_ITF_ONESHOT);
#endif
        // Check received response(s)
        d7a_print_msg(resp);
        
        PRINT("Resp done.\r\n");
        d7a_free_msg(resp);
#endif
    }
}

/*** Main function ------------------------------------------------------------- ***/
int main()
{
    // Start & initialize
    DBG_OPEN(DEBUG_LED);
    PRINT("\r\n--- Starting new run ---\r\n");
    
    extern uint16_t const os_maxtaskrun;
    //IPRINT("Max user threads: %d\r\n", os_maxtaskrun-1-9);

    d7a_open(&shield_config, D7A_PIN_RESET, &callbacks);
    d7a_modem_print_infos();
    
    // Create the revision file for the Dash7board
    d7a_create(65, PERMANENT, RW_R, sizeof(d7a_revision_t), sizeof(d7a_revision_t), D7A_NOTIFICATION_FULL, D7A_ITF_REPORT_CHECKED);
    // Notify revision
    d7a_msg_t** resp = d7a_write(65, 0, sizeof(d7a_revision_t), (uint8_t*)&f_dev_rev);
    d7a_free_msg(resp);

    uint32_t divider;
    
    // Retreive the simulation parameter from local file
    fs_read_file(SIMUL_FILE_ID, 0, sizeof(uint32_t), (uint8_t*)&divider);
    
    // Update the simulation parameters
    simul_update_param(divider);
    
    // Declare the simulation parameter file
    d7a_declare(SIMUL_FILE_ID, PERMANENT, RW_RW, sizeof(sensor_config_t), sizeof(sensor_config_t));

#if (_TEM1_EN_ == 1)
    // Open I2C and initialise the sensors
    DevI2C ext_i2c(SENSOR_I2C_SDA, SENSOR_I2C_SCL);
    ext_i2c.frequency(100000);
    mlxSensor = new MLX90614(&ext_i2c);
#endif

    osStatus status;

    // Start sensors threads
#define THREAD_START(_name) Thread _name##_th(osPriorityNormal, 1024, NULL);\
                            g_thread_ctx = &_name##_thread_ctx;\
                            status = _name##_th.start(sensor_thread);\
                            ASSERT(status == osOK, "Failed to start ##_name## thread (err: %d)\r\n", status);\
                            thread_ready.wait();
                            
#if (_TEM1_EN_ >= 0)
    THREAD_START(tem1);
#endif // _TEM1_EN_

#if (_VOLTAGE_EN_ >=0)
    THREAD_START(volt);
#endif

    // File modified thread
    Thread fm_th(osPriorityNormal, 512, NULL);
    status = fm_th.start(file_modified_thread);
    ASSERT(status == osOK, "Failed to start fm thread (err: %d)\r\n", status);

    // For button
#ifdef DEBUG_BUTTON
    DebouncedInterrupt user_interrupt(DEBUG_BUTTON);
    user_interrupt.attach(button_push_isr, IRQ_FALL, 500, true);

    Thread but_th(osPriorityNormal, 512, NULL);
    status = but_th.start(button_user_thread);
    ASSERT(status == osOK, "Failed to start but thread (err: %d)\r\n", status);
#endif

#ifdef DGB_LED_BLINK_PERIOD
    DigitalOut my_led(DEBUG_LED);
#endif
    
    // Set main task to lowest priority
    osThreadSetPriority(osThreadGetId(), osPriorityIdle);
    while(true)
    {
#ifdef DGB_LED_BLINK_PERIOD
        // Wait to avoid beeing stuck in loop
        Thread::wait(DGB_LED_BLINK_PERIOD);
        my_led = !my_led;
#else
        Thread::wait(osWaitForever);
#endif
    }
}