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

Dependencies:   modem_ref_helper CRC DebouncedInterrupt

main.cpp

Committer:
Jeej
Date:
2021-02-17
Revision:
16:2990b7d21ca2
Parent:
15:9a9cfb0c3c4e

File content as of revision 16:2990b7d21ca2:

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

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

#define CHUNK_SIZE              128

Semaphore button_user(0);
Semaphore modem_ready(0);
Queue<touch_t, 8> g_file_modified;

// This describe the upload interface
// Do not modify uncommented parameters
alp_itf_d7a_cfg_t report_itf = {
    .type                           = ALP_ITF_TYPE_D7A,
    .cfg.to.byte                    = D7A_CTF_ENCODE(0),
    .cfg.te.byte                    = D7A_CTF_ENCODE(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),
};

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

// Upload thread
void button_user_thread()
{
    osEvent evt;
    output_file_t* output;
    uint32_t sent = 0;
    int err;
    alp_payload_t* alp;
    alp_payload_t* rsp;
    
    PRINT("Register Files\n");
    ram_fs_new(FID_OUTPUT_FILE, (uint8_t*)&h_output_file, (uint8_t*)&f_output_file);
    ram_fs_new(FID_INPUT_FILE, (uint8_t*)&h_input_file, (uint8_t*)&f_input_file);
    
    modem_declare_file(FID_OUTPUT_FILE, (alp_file_header_t*)&h_output_file);
    modem_declare_file(FID_INPUT_FILE, (alp_file_header_t*)&h_input_file);
    
    PRINT("Enable D7A interface\n");
    modem_d7a_enable_itf();
    
    // Host revision file is in the modem. Update it.
    PRINT("Update host revision\n");
    modem_write_file(FID_HOST_REV, &f_rev, 0, sizeof(revision_t));
    
    // Retrieve modem revision
    PRINT("Send revision\n");
    revision_t rev;
    modem_read_file(FID_WM_REV, &rev, 0, sizeof(revision_t));
    
    // Send both to the server
    // Build payload
    alp = NULL;
    alp = alp_payload_rsp_f_data(alp, FID_WM_REV, &rev, 0, sizeof(revision_t));
    alp = alp_payload_rsp_f_data(alp, FID_HOST_REV, &f_rev, 0, sizeof(revision_t));
    // Send
    modem_remote_raw_alp((void*)&report_itf, alp, NULL, 10000);
        
    // 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.acquire();
        
        // 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_u32(CHUNK_SIZE, output_length - sent);
            uint32_t chunk_offset = sent;
            
            PRINT("Sending chunk %4d/%4d (%3d bytes)... ", chunk_offset, output_length, chunk_size);
            FLUSH();
            
            // Build payload
            alp = NULL;
            alp = alp_payload_rsp_f_data(alp, FID_OUTPUT_FILE, &(output->data[chunk_offset]), chunk_offset, chunk_size);
            
            // Send
            err = modem_remote_raw_alp((void*)&report_itf, alp, &rsp, 60000);
        
            if (ALP_ERR_UNKNOWN == err)
            {
                PRINT("TIMEOUT.\n");
            }
            else
            {
                err = alp_payload_get_err(rsp);
                if (ALP_ERR_NONE <= err)
                {
                    sent += chunk_size;
                    is_ok = true;
                    PRINT("OK.\n");
                }
                else
                {
                    modem_print_error(report_itf.type, err);
                }
            }
            
            alp_payload_free(rsp);
        }
        
        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... ", output->crc);
            
            alp = NULL;
            alp = alp_payload_rsp_f_data(alp, FID_OUTPUT_FILE, &(output->crc), offsetof(output_file_t, crc), sizeof_field(output_file_t, crc));
            
            // Send
            err = modem_remote_raw_alp((void*)&report_itf, alp, &rsp, 60000);
            
            if (ALP_ERR_UNKNOWN == err)
            {
                PRINT("TIMEOUT.\n");
            }
            else
            {
                err = alp_payload_get_err(rsp);
                if (ALP_ERR_NONE <= err)
                {
                    PRINT("OK.\n");
                }
                else
                {
                    modem_print_error(report_itf.type, err);
                }
            }
            
            alp_payload_free(rsp);
        }
    }
}

void thread_file_modified()
{
    touch_t* touch;
    osEvent evt;
    uint8_t chunk[CHUNK_SIZE];
    int err;
    alp_payload_t* alp;
    
    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);
                    
                    alp = NULL;
                    alp = alp_payload_rsp_f_data(alp, FID_INPUT_FILE, &(input->crc), offsetof(input_file_t, crc), sizeof_field(input_file_t, crc));
                    
                    // Send
                    err = modem_remote_raw_alp((void*)&report_itf, alp, NULL, 3000);
                    
                    if (ALP_ERR_NONE <= err)
                    {
                        PRINT("OK.\n");
                    }
                    else
                    {
                        modem_print_error(report_itf.type, err);
                    }
                }
                else
                {                    
                    PRINT("Got chunk %4d/%4d (%3d bytes)\n", touch->offset, sizeof_field(input_file_t, data), touch->length);
                    ram_fs_read(touch->fid, chunk, touch->offset, touch->length);
                    //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_ref_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,
};


/*** 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_open(&callbacks);
    
    // Start file modified thread
    Thread th_file_modified(osPriorityNormal, 4096, 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(), osPriorityLow);
    while(true)
    {
        ThisThread::sleep_for(500);
#ifdef DEBUG_LED
        my_led = !my_led;
#endif
    }
}