MightyPork / TinyFrame

A simple library for building and parsing data frames for serial interfaces (like UART / RS232)
MIT License
344 stars 111 forks source link

Maybe should add some ack mechanism #3

Closed maosuyun2009 closed 6 years ago

maosuyun2009 commented 7 years ago

Hi, thank you share this libray, I want use this libray to my OTA funcation, Maybe,receiver should response to sender the complete and correct of data

MightyPork commented 7 years ago

good to hear you find it useful!

Ack is not built-in on purpose, I decided it's better to leave that to higher protocol layers for more flexibility.

You can implement acked upload with something like this (it's not tested, just off the top of my head). Don't copy-paste this code, just look at the general idea how it could be done:

it's not re-entrant because of using a global variable, there's certainly some room for improvement (in the library and also in the uploader code)

An alternative way if you dont want to do it asynchronously is using a "synchronous query", see the readme for an example.

message_types.h - a file shared by both peers, defines your message IDs

#define MSG_UPLOAD_BEGIN    100
#define MSG_UPLOAD_ACK      101
#define MSG_UPLOAD_CHUNK    102
#define MSG_UPLOAD_COMPLETE 103

this is the uploader code

#include "message_types.h"

#define UPLOAD_CHUNK_LEN 1024
#define UPLOAD_TIMEOUT_MS 5000

struct {
    uint8_t *data;
    size_t len;
    size_t last_index;
    TF_ID listener_id;
} uploadProgress;

static bool myUploadHandler(TF_ID frame_id, TF_TYPE type, const uint8_t *data, TF_LEN len)
{
    if (type == MSG_UPLOAD_ACK) {
        // advance to account for the last chunk
        // this can overflow, that's ok - we use that to test if we're done
        uploadProgress.last_index += UPLOAD_CHUNK_LEN;

        if (uploadProgress.last_index < uploadProgress.len) {
            // more to send
            size_t chunk_siz = min(uploadProgress.len - uploadProgress.last_index, UPLOAD_CHUNK_LEN);

            TF_Respond(MSG_UPLOAD_CHUNK, 
                data+uploadProgress.last_index, 
                chunk_siz,
                frame_id);

                // todo clear the old timeout,set a new one:
                startTimeout(myUploadTimeoutCallback, UPLOAD_TIMEOUT_MS);
        } 
        else {                    
            // tell client we're done
            TF_Respond(MSG_UPLOAD_COMPLETE, 
                NULL, 0, // message is empty
                frame_id);

            // ignore further responses
            TF_RemoveIdListener(frame_id);
            // todo clear the old timeout
        }

        return true;
    } else {
        // unexpected type - not MSG_UPLOAD_ACK
        // maybe signalizing an error?

        // true if we handled the message, false to let some other handler deal with it
        return true;
    }
}

static void myUploadTimeoutCallback(void)
{
    // TODO try to resend last chunk, or abort:

    // do this only if upload is in progress
    TF_RemoveIdListener(uploadProgress.listener_id);
}

void upload_start(uint8_t *data, size_t len)
{
    uploadProgress.data = data;
    uploadProgress.len = len;
    uploadProgress.last_index = 0;

    size_t chunk_siz = min(uploadProgress.len, UPLOAD_CHUNK_LEN);

    TF_Send(MSG_UPLOAD_BEGIN, 
        data, 
        chunk_siz,
        myUploadHandler,
        &uploadProgress.listener_id);

    // Start a timeout to cancel the operation        
    // Use some system mechanism for this (or just don't do it)
    startTimeout(myUploadTimeoutCallback, UPLOAD_TIMEOUT_MS);
}

Now, the client (which receives our upload)

#include "message_types.h"

size_t upl_pos = 0;
bool upload_handler(TF_ID frame_id, TF_TYPE type, const uint8_t *data, TF_LEN len)
{
    if (type == MSG_UPLOAD_BEGIN) {
        // start of upload
        // (normally you'd also send the data length in the first frame etc,
        // so the client knows that length to expect)

        // do something with *data
        (void)data;

        upl_pos = len;

        TF_Respond(MSG_UPLOAD_ACK, 
            NULL, 0, // response message is empty
            frame_id);

        return true;
    }

    if (type == MSG_UPLOAD_CHUNK) {
        // some more data...
        // TODO discard this if an upload has not begun

        // do something with *data
        // it goes at offset {upl_pos}
        (void)data;

        upl_pos += len;

        TF_Respond(MSG_UPLOAD_ACK, 
            NULL, 0, // response message is empty
            frame_id);

        return true;
    }

    if (type == MSG_UPLOAD_COMPLETE) {
        // do something

        return true;
    }
}

void setup_listeners(void)
{
    // we use one handler for all three for simplicity
    TF_AddTypeListener(MSG_UPLOAD_BEGIN, upload_handler);
    TF_AddTypeListener(MSG_UPLOAD_CHUNK, upload_handler);
    TF_AddTypeListener(MSG_UPLOAD_COMPLETE, upload_handler);
}

you could also send the last chunk already with message type MSG_UPLOAD_COMPLETE, but then the client would have to ack that too, or you don't have the resend with timeout functionality for the last chunk.