Sending and reception of big data file (1kB example)

Dependencies:   modem_ref_helper CRC DebouncedInterrupt

main.cpp

Committer:
Jeej
Date:
2018-10-11
Revision:
10:d6c37bf9bfb5
Parent:
9:26cd994b5303
Child:
11:dc0e19b2d4a0

File content as of revision 10:d6c37bf9bfb5:

// @autor: jeremie@wizzilab.com
// @date: 2017-12-14

#include "DebouncedInterrupt.h"
#include "modem_ref_helper.h"
#include "modem_callbacks.h"
#include "files.h"
#include "crc.h"

#define MIN(a,b)                ((a<b)?a:b)

#define CHUNK_SIZE              128

Semaphore button_user(0);
Semaphore modem_ready(0);
Queue<void, 8> modem_resp[MAX_USER_NB];
Queue<touch_t, 8> g_file_modified;

enum {
    MODEM_RESP_NO,
    MODEM_RESP_TERMINAL,
    MODEM_RESP_ERROR,
    MODEM_RESP_ACK,
    MODEM_RESP_TIMEOUT,
};

// This describe the upload interface
// Do not modify uncommented parameters
alp_d7a_itf_t my_itf = {
    .type                           = ALP_ITF_TYPE_D7A,
    .cfg.to                         = 0,
    .cfg.te                         = 0,
    .cfg.qos.bf.resp                = D7A_RESP_PREFERRED,
    .cfg.qos.bf.retry               = ALP_RPOL_ONESHOT,
    .cfg.addressee.ctrl.bf.nls      = D7A_NLS_AES_CCM_64, // Security level
    .cfg.addressee.ctrl.bf.idf      = D7A_ID_NBID,
    .cfg.addressee.xcl.bf           = {.s = 2, .m = 0x1}, // Gateway access class
    .cfg.addressee.id[0]            = D7A_CTF_ENCODE(4),
};

// Response Callback
void my_response_callback(uint8_t terminal, int8_t err, uint8_t id)
{    
    if (ALP_ERR_NONE != err)
    {
        modem_print_error(ALP_ITF_TYPE_D7A, err);
    }
    
    if (terminal)
    {
        modem_resp[id].put((void*)MODEM_RESP_TERMINAL);
    }
    else
    {
        if (ALP_ERR_NONE == err)
        {
            modem_resp[id].put((void*)MODEM_RESP_ACK);
        }
        else
        {
            modem_resp[id].put((void*)MODEM_RESP_ERROR);
        }
    }
}

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

// Upload thread
void button_user_thread()
{
    osEvent evt;
    uint32_t resp;
    d7a_sp_res_t istat;
    output_file_t* output;
    uint32_t sent = 0;

    uint8_t id = modem_get_id(my_response_callback);
        
    memset(&istat, 0, sizeof(d7a_sp_res_t));
    
    // Directly get data pointer to avoid reading file
    output = (output_file_t*)ram_fs_get_data(FID_OUTPUT_FILE);
    
    // Update file CRC (Calculate CRC on string without end of string)
    output->crc = crc32((char*)output->data, strlen((char*)output->data));
    
    while (true)
    {
        Timer tim;
        bool is_ok;
        uint32_t output_length = strlen((char*)output->data) + 1; // +1 as data is a string, send end of string

        // Wait for button press
        PRINT("Press user button to send file. (%d bytes)\n", output_length);
        button_user.wait();
        
        // Update file CRC (Calculate CRC on string without end of string)
        output->crc = crc32((char*)output->data, output_length - 1);
        
        sent = 0;
        tim.start();
        
        // Send chunks
        while (sent < output_length)
        {
            is_ok = false;
            uint32_t chunk_size = MIN(CHUNK_SIZE, output_length - sent);
            uint32_t chunk_offset = sent;
            
            PRINT("Sending chunk %4d/%4d (%3d bytes)... ", chunk_offset, output_length, chunk_size);
            FLUSH();
            modem_send_file_content((uint8_t*)&my_itf, D7_ITF_SIZE(&my_itf), (void*)&istat, FID_OUTPUT_FILE, &(output->data[chunk_offset]), chunk_offset, chunk_size, id);
            
            do
            {
                evt = modem_resp[id].get(3000);
                resp = (evt.status == osEventMessage)? (uint32_t)evt.value.p : MODEM_RESP_TIMEOUT;
                
                if (MODEM_RESP_ACK == resp)
                {
                    //PRINT_DATA("ACK UID:", "%02X", istat.addressee.id, 8, " ");
                    //PRINT("SNR: %ddB RXLEV: %ddBm LB: %ddB\n", istat.snr, -istat.rxlev, istat.lb);
                    
                    //PRINT("ACK.\n");
                    is_ok = true;
                }
                else if (MODEM_RESP_TIMEOUT == resp)
                {
                    PRINT("WAITING...\n");
                }
                else if (MODEM_RESP_ERROR == resp)
                {
                    //PRINT("ERROR.\n");
                    break;
                }
                else if (MODEM_RESP_TERMINAL == resp)
                {
                    //PRINT("DONE.\n");
                }
                
                memset(&istat, 0, sizeof(d7a_sp_res_t));
            } while (MODEM_RESP_TERMINAL != resp);
            
            if (is_ok)
            {
                sent += chunk_size;
                PRINT("OK.\n");
            }
            else
            {
                PRINT("FAILED.\n");
                break;
            }
        }
        
        double time_s = tim.read();
        if (sent)
        {
            PRINT("%d bytes sent in %.3fs (%d B/s)\n", sent, time_s, (int)((double)sent/time_s));
        }
        
        if (is_ok)
        {
            // Send CRC
            PRINT("Sendind CRC 0x%08X\n", output->crc);
            modem_send_file_content((uint8_t*)&my_itf, D7_ITF_SIZE(&my_itf), (void*)&istat, FID_OUTPUT_FILE, &(output->crc), offsetof(output_file_t, crc), sizeof_field(output_file_t, crc), id);
            
            do
            {
                evt = modem_resp[id].get(3000);
                resp = (evt.status == osEventMessage)? (uint32_t)evt.value.p : MODEM_RESP_TIMEOUT;
                
                if (MODEM_RESP_ACK == resp)
                {
                    PRINT_DATA("ACK UID:", "%02X", istat.addressee.id, 8, " ");
                    PRINT("SNR: %ddB RXLEV: %ddBm LB: %ddB\n", istat.snr, -istat.rxlev, istat.lb);
                }
                else if (MODEM_RESP_TIMEOUT == resp)
                {
                    PRINT("WAITING...\n");
                }
                else if (MODEM_RESP_ERROR == resp)
                {
                    PRINT("ERROR.\n");
                    break;
                }
                else if (MODEM_RESP_TERMINAL == resp)
                {
                    PRINT("DONE.\n");
                }
                
                memset(&istat, 0, sizeof(d7a_sp_res_t));
            } while (MODEM_RESP_TERMINAL != resp);
        }
    }
}

void thread_file_modified()
{
    touch_t* touch;
    osEvent evt;
    uint32_t resp;
    d7a_sp_res_t istat;
    uint8_t chunk[CHUNK_SIZE];
    
    uint8_t id = modem_get_id(my_response_callback);
    
    while (true)
    {
        evt = g_file_modified.get();
        touch = (evt.status == osEventMessage)? (touch_t*)evt.value.p : NULL;
        ASSERT(touch != NULL, "NULL touch pointer!\n");
        
        switch (touch->fid)
        {
            case FID_OUTPUT_FILE:
                output_file_t* output;
                uint32_t output_length;
                
                // Directly get data pointer to avoid reading file
                output = (output_file_t*)ram_fs_get_data(FID_OUTPUT_FILE);
                
                output_length = strlen((char*)output->data);
                
                // Update file CRC (Calculate CRC on string without end of string)
                output->crc = crc32((char*)output->data, output_length);
                
                PRINT("NEW OUTPUT CRC 0x%08X (on %d bytes)\n", output->crc, output_length);
                break;
            case FID_INPUT_FILE:
                input_file_t* input;
                uint32_t input_length;
                uint32_t crc;
                
                // Directly get data pointer to avoid reading file
                input = (input_file_t*)ram_fs_get_data(FID_INPUT_FILE);
                
                input_length = strlen((char*)input->data);
                
                // Calculate CRC
                crc = crc32((char*)input->data, input_length);
                
                // Check if CRC has been updated
                if ((offsetof(input_file_t, crc) == touch->offset) && (sizeof_field(input_file_t, crc) == touch->length))
                {
                    PRINT("INPUT CRC 0x%08X CRC 0x%08X (on %d bytes)\n", input->crc, crc, input_length);
                    
                    if (input->crc != crc)
                    {
                        // delete CRC
                        input->crc = 0;
                    }
                    
                    // Send CRC as confirmation
                    PRINT("COMFIRM CRC 0x%08X\n", input->crc);
                    modem_send_file_content((uint8_t*)&my_itf, D7_ITF_SIZE(&my_itf), (void*)&istat, FID_INPUT_FILE, &(input->crc), offsetof(input_file_t, crc), sizeof_field(input_file_t, crc), id);
                    
                    do
                    {
                        evt = modem_resp[id].get(3000);
                        resp = (evt.status == osEventMessage)? (uint32_t)evt.value.p : MODEM_RESP_TIMEOUT;
                        
                        if (MODEM_RESP_ACK == resp)
                        {
                            PRINT_DATA("ACK UID:", "%02X", istat.addressee.id, 8, " ");
                            PRINT("SNR: %ddB RXLEV: %ddBm LB: %ddB\n", istat.snr, -istat.rxlev, istat.lb);
                        }
                        else if (MODEM_RESP_TIMEOUT == resp)
                        {
                            PRINT("WAITING...\n");
                        }
                        else if (MODEM_RESP_ERROR == resp)
                        {
                            PRINT("ERROR.\n");
                            break;
                        }
                        else if (MODEM_RESP_TERMINAL == resp)
                        {
                            PRINT("DONE.\n");
                        }
                        
                        memset(&istat, 0, sizeof(d7a_sp_res_t));
                    } while (MODEM_RESP_TERMINAL != resp);
                }
                else
                {                    
                    PRINT("Got chunk %4d/%4d (%3d bytes)\n", touch->offset, sizeof_field(input_file_t, data), touch->length);
                    ram_fs_read(touch->fid, touch->offset, touch->length, chunk);
                    //PRINT("%s\n", chunk);
                    //PRINT_DATA("chunk end:", "%02X", &(chunk[touch->length-4]), 4, "\n");
                
                    PRINT("NEW INPUT CRC 0x%08X (on %d bytes)\n", crc, input_length);
                }
                break;
            default:
                PRINT("TOUCH FID %d OFF %d LEN %d\n", touch->fid, touch->offset, touch->length);
                break;
        }
        
        FREE(touch);
    }
}

modem_callbacks_t callbacks = {
    .read       = my_read,
    .write      = my_write,
    .read_fprop = my_read_fprop,
    .flush      = my_flush,
    .remove     = my_delete,
    .udata      = my_udata,
    .lqual      = my_lqual,
    .ldown      = my_ldown,
    .reset      = my_reset,
    .boot       = my_boot,
    .busy       = my_busy,
};

// Callback
void my_main_callback(uint8_t terminal, int8_t err, uint8_t id)
{    
    if (ALP_ERR_NONE != err)
    {
        modem_print_error(ALP_ITF_TYPE_D7A, err);
    }
    
    if (terminal)
    {
        modem_ready.release();
    }
}

/*** Main function ------------------------------------------------------------- ***/
int main() {
    // Start & initialize
#ifdef DEBUG_LED
    DBG_OPEN(DEBUG_LED);
#else
    DBG_OPEN(NC);
#endif
    PRINT("\n"
          "-----------------------------------------\n"
          "------------- Demo Big File -------------\n"
          "-----------------------------------------\n");
              
    modem_helper_open(&callbacks);
    
    uint8_t id = modem_get_id(my_main_callback);
    
    // Put modem to listen to downlink access class
    d7a_xcl_t xcl = { .bf.s = 0, .bf.m = 0x1 };
    modem_write_file(D7A_FID_DLL_CFG, (void*)&xcl, offsetof(d7a_dll_cfg_t, xcl), sizeof(d7a_xcl_t), id);
    modem_ready.wait();
    
    PRINT("Register Files\n");
    modem_update_file(FID_OUTPUT_FILE, (alp_file_header_t*)&h_output_file, (uint8_t*)&f_output_file);
    modem_update_file(FID_INPUT_FILE, (alp_file_header_t*)&h_input_file, (uint8_t*)&f_input_file);
    
    PRINT("Start D7A Stack\n");
    modem_activate_itf(ALP_ITF_TYPE_D7A, 24, 0, ALP_D7A_ISTAT_RESP | ALP_D7A_ISTAT_UNS | ALP_D7A_ISTAT_EOP, true, id);
    modem_ready.wait();
    
    PRINT("Notify Modem Version\n");
    modem_notify_file(D7A_FID_FIRMWARE_VERSION, 0, SIZE_HOST_REV, id);
    modem_ready.wait();
    
    PRINT("Notify Host Version\n");
    uint8_t default_root_key[] = DEFAULT_ROOT_KEY;
    modem_notify_host_rev(&f_rev, &h_rev, default_root_key);
    
    // id no longer needed
    modem_free_id(id);
    
    // Start file modified thread
    Thread th_file_modified(osPriorityNormal, 1024, NULL);
    osStatus status = th_file_modified.start(thread_file_modified);
    ASSERT(status == osOK, "Failed to start thread_file_modified (err: %d)\r\n", status);

#ifdef DEBUG_BUTTON
    DebouncedInterrupt user_interrupt(DEBUG_BUTTON);
    user_interrupt.attach(button_push_isr, IRQ_FALL, 500, true);
    
    Thread but_th(osPriorityNormal, 4096, NULL);
    status = but_th.start(button_user_thread);
    ASSERT(status == osOK, "Failed to start but thread (err: %d)\r\n", status);
#else
    #error You need a button to use this APP as is
#endif

#ifdef DEBUG_LED
    DigitalOut my_led(DEBUG_LED);
#endif
    
    // Set main task to lowest priority
    osThreadSetPriority(osThreadGetId(), osPriorityIdle);
    while(true)
    {
        Thread::wait(500);
#ifdef DEBUG_LED
        my_led = !my_led;
#endif
    }
}