Edge-impulse upload training data from browser [support request]

First of all I’m not sure if it is a bug :sweat_smile: I think some programming mistakes from my side

What I’m trying to do is create a small web application for android, for a test to send phone’s accelerometer data (Use case is a bit like your phone app). So it can have training mode and classification mode. In classification mode, after successful classification, it can send some info over MQTT to my other system. That is the intention! But we are not there yet.

I’m right now programming the training phase.

Why I’m re-inventing the wheel? (you may ask…)
Although the web-app hosted by Edge-Impulse is great for quick testing but I need some extra features and a specific user experience, about training, specifically uploading data part.
Also everything will happen from browser / client side and nothing from the web-server side of the app, meaning things like encoding the data with signature, making https calls and web-socket calls need to happen from browser itself.

How far have I succeed so far I so far?

  • Hosting the webapp from a https basic python server. DONE.
  • Remote management with wss (secure websocket). DONE.
  • Sample the accelerometer sensor data. DONE.
  • Creating proper encrypted signed data format for upload. DONE (from my inspection), Requesting your insight here.
  • Uploading. FAILING! (Trying to use fetch.POST() from browser. More details below…)

Data generation and encoding in browser (My method):
PSEUDO_CODE:

when in training mode:
    Make the device available on Edge-Impulse Studio by websocket handshake
         if sampling request received from Edge-Impulse studio over websockets. 
             sample data for required length
             
             when sampling finished: 
                     [1] notify the studio through the designated websocket message 
                     [2] in the data structure (with other details) push the returned data array
                     [3] encode the data structure (SHA-256 and hmacKey using https://github.com/Caligatio/jsSHA on the browser side as crypto lib is for nodejs)
                     [4] [FAILING] Upload the data using fetch() 
                     ( I looked in to the Edge-Impulse's code for the mobile app and it was using XMLHttpRequest(). IS there there a specific reason for not using fetch()?)

So how does that look like in a bit more code? (as there might be some coding issue as well)

I have 3 scripts loaded in browser:
remote_manager.js : as the name suggests does it’s job
data_forwarder.js : mainly responsible for holding the functions for encoded data structure creation and uploading.
script.js : for harnessing phone’s sensor data

For now, to test, in the remote manager, I modified the code here a bit to test the uploading data functionality with fetch(). This script is then loaded in my HTML file and hosted by a python https server

document.addEventListener('DOMContentLoaded', function () {
    console.log("[data_forwarder_tester].js] :\t", "loaded");

        function createDataStruct(_emptySignature, _deviceName, _deviceType, _intervalHZ, _dataArrayHolder, _hmac_key, _api_key){
        return new Promise((resolve, reject) => {
            let _data = {
                protected: {
                    ver: "v1",
                    alg: "HS256",
                    iat: Math.floor(Date.now() / 1000) // epoch time, seconds since 1970 (Imp for Edge-Impulse Studio)
                },
                signature: _emptySignature,
                payload: {
                    device_name: _deviceName,
                    device_type: _deviceType,
                    interval_ms: _intervalHZ,
                    sensors: [
                        { name: "accX", units: "m/s2" },
                        { name: "accY", units: "m/s2" },
                        { name: "accZ", units: "m/s2" }
                    ],
                    values: _dataArrayHolder
                }
            };

            resolve([_data, _hmac_key, _api_key]);
        });
    }

    function encodeData([_data, _hmac_key, _api_key]){
        return new Promise((resolve, reject) => {
            let encoded = JSON.stringify(_data);
            // now calculate the HMAC and fill in the signature
            // using: https://github.com/Caligatio/jsSHA
            // load it in the html file, in the head section:  <script src="your path to sha.js"></script>
            let hmac = new jsSHA("SHA-256", "TEXT", {
                hmacKey: { 
                    value: _hmac_key, 
                    format: "TEXT" },
            });
            hmac.update(encoded);
            let signature = hmac.getHash("HEX");

            // update the signature in the message and re-encode
            _data.signature = signature;
            encoded = JSON.stringify(_data);
        
            resolve([encoded, _api_key]);
        });
    }

    function uploadData([_encoded, _api_key]){
        // console.log("Encoded data to be uploaded:\n");
        // console.log(_encoded);

        const todo = {
            title: 'Some really important work to finish'
        };

        // --- *** test code for fetch()
        // fetch('https://jsonplaceholder.typicode.com/todos', {
        //         method: 'POST',
        //         body: JSON.stringify(todo),
        //         headers: {
        //             'Content-type': 'application/json; charset=UTF-8'
        //         }
        //     })
        //     .then(response => console.log(response))
        //     .catch(err => console.error(err));

        
        // TBD: labels: training/test/validation data here are to be set dynamically later
        fetch('https://ingestion.edgeimpulse.com/api/training/data', {
                method: 'POST',
                body: _encoded,
                encoding: 'binary',
                headers: {
                    'x-api-key': _api_key,
                    'x-file-name': 'test.01',
                    'x-label': 'idle',
                    'Content-type': 'application/json; charset=UTF-8'
                }
            })
            .then(response => console.log(response))
            .catch(err => console.error(err));
    }

    var API_KEY =  <YOUR API KEY>; 
    var hmac_key = <YOUR HAMC KEY>;                   
    // empty signature (all zeros). HS256 gives 32 byte signature, and we encode in hex, so we need 64 characters here
    let emptySignature = Array(64).fill('0').join('');
    let deviceName = "Test_device_name";
    let deviceType = "Test_device_type";
    let intervalHZ = 25;
    let dataHolderArray = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];

    createDataStruct(emptySignature, deviceName, deviceType, intervalHZ, dataHolderArray, hmac_key)
        .then(encodeData)
        .then(uploadData)
        .catch(console.log);
});

I get this error:

Screenshot 2021-03-24 at 10.17.55 PM

Notes:

  • The commented out other test fetch part runs successfully.
  • The node js code from the guide runs smoothly and uploads data to the studio successfully
  • The python code from the guide runs smoothly and uploads data to the studio successfully

Am I doing any silly mistake or any key information I’m missing :sweat:? Any pointers would help :pleading_face:

Found the solution after a little bit more playing around this morning :sweat_smile:. [ Writing in the forum = Rubber ducky :baby_chick: ]

So the problem laid in my sloppy my fetch request. It was the declaration of the content type, in the request headers.

It should be :
'Content-Type': 'application/json'
and not
'Content-type': 'application/json; charset=UTF-8'

fetch(_ingestion_api + _path, {
                method: 'POST',
                body: _encoded,
                encoding: 'binary',
                headers: {
                    'x-api-key': _api_key,
                    'x-file-name': _file_name,
                    'x-label': _label,
                    'Content-type': 'application/json'
                }
            })
            .then(response => console.log(response))
            .catch(err => console.error(err));

Screenshot 2021-03-25 at 12.05.00 PM

Has been working now. :upside_down_face:

2 Likes