2

I am following on from this question here and I am trying to get back the contents of the DataLogger that I have subscribed to for Meas/HR.

I am doing this using the Sensor Simulator running on my Windows 10 PC.

I setup the DataLogger and request the "Meas/HR" as follows:

WB_RES::DataEntry entry;
entry.path = "/Meas/HR";

WB_RES::DataLoggerConfig logConfig;
logConfig.dataEntries.dataEntry = {entry};

whiteboard::Result result = asyncPut(WB_RES::LOCAL::MEM_DATALOGGER_CONFIG(), AsyncRequestOptions::Empty, logConfig);

if (result == whiteboard::HTTP_CODE_OK) {
    this->asyncPut(WB_RES::LOCAL::MEM_DATALOGGER_STATE(), AsyncRequestOptions::Empty,
                   WB_RES::DataLoggerStateValues::Type::DATALOGGER_LOGGING);
} else {
    DEBUGLOG("asyncPut::datalogger config threw error: %u", result);
}

And I get back as expected a result code of 200 and then data logger config object with 1 entry in the onPutResult(...) function.

Then using the onTimer function (just for testing purposes) I try to get the logbook entries using the following code:

 this->asyncGet(WB_RES::LOCAL::MEM_LOGBOOK_ENTRIES(), AsyncRequestOptions::Empty);

It runs and enters the

onGetResult(wb::RequestId requestId, wb::ResourceId resourceId, wb::Result resultCode,
                           const wb::Value &result)

Call back function where I get a resultCode of HTTP_CODE_OK (200) but the result value always comes back as:

result = {mDataTypeId=13313 mSenderDataType=1 '\x1' mReceiverDataType=1 '\x1' ..., mpData = 0x0456f8ac}

Why does it always come back as MEM_LOGBOOK_ISFULL_LID = 13313 and never MEM_LOGBOOK_ENTRIES_LID = 13312?

It is because I am running this is simulator mode? I also ran this command in case it is needed to populate the DataLogger:

wbcmd.exe --port TCP127.0.0.1:7044 --path Meas/HR --op SUBSCRIBE

But it still does not work....

I do not have a JIG, so I need some way to make sure this works on the PC simulator as the next step for me is to use the movesense sensor to log a few days worth of results BEFORE I send the results back using GATT. I am trying to avoid using the whiteboard and the android mobile software.

Is there some way I can properly test this using the simulator because it is going to be total hell to even try to do it any other way. Do I need Manually put data into the DataLogger?

HELP! I have spent SO MUCH time on this and it is driving me crazy...

Thanks! Helic

@Morten: Here is the minimal working sample. Thanks very much!:

the .cpp file

#include "movesense.h"

#include "DataLoggerClient.h"
#include "common/core/debug.h"

#include "ui_ind/resources.h"
#include "mem_datalogger/resources.h"
#include "mem_logbook/resources.h"
#include "whiteboard/Value.h"
#include <string>

const size_t BLINK_PERIOD_MS = 3500;

const char *const DataLoggerClient::LAUNCHABLE_NAME = "DataLogger";

DataLoggerClient::DataLoggerClient()
        : ResourceClient(WBDEBUG_NAME(__FUNCTION__), WB_EXEC_CTX_APPLICATION),
          LaunchableModule(LAUNCHABLE_NAME, WB_EXEC_CTX_APPLICATION),
          mTimer(wb::ID_INVALID_TIMER) {
}

DataLoggerClient::~DataLoggerClient() {
}

bool DataLoggerClient::initModule() {
    mModuleState = WB_RES::ModuleStateValues::INITIALIZED;
    return true;
}

void DataLoggerClient::deinitModule() {
    mModuleState = WB_RES::ModuleStateValues::UNINITIALIZED;
}

bool DataLoggerClient::startModule() {
    mModuleState = WB_RES::ModuleStateValues::STARTED;

    // Configure the DataLogger to start recording the data on startup
    startDataLogger();

    mTimer = startTimer(BLINK_PERIOD_MS, true); // Start LED timer. true = trigger repeatedly

    return true;
}

// Use the Datalogger to store movesense device data (in this case the HeartRate information)
// Look here for more details on DataLogger and Logbook: http://www.movesense.com/docs/mobile/android/datalogger_logbook/
void DataLoggerClient::startDataLogger() {
    // We want to record the heart rate measurement - using the API
    WB_RES::DataEntry entry;
    entry.path = "/Meas/HR";

    //DataLoggerConfig contains the current configuration of the logger (what data to log)
    WB_RES::DataLoggerConfig logConfig;

    logConfig.dataEntries.dataEntry = whiteboard::MakeArray<WB_RES::DataEntry>(&entry, 1);

    whiteboard::Result result = this->asyncPut(WB_RES::LOCAL::MEM_DATALOGGER_CONFIG(), AsyncRequestOptions::Empty,
                                               logConfig);
    if (result == whiteboard::HTTP_CODE_OK) {
        //To start logging, PUT value DATALOGGER_LOGGING (=3) to Mem/DataLogger/State resource
        this->asyncPut(WB_RES::LOCAL::MEM_DATALOGGER_STATE(), AsyncRequestOptions::Empty,
                       WB_RES::DataLoggerStateValues::Type::DATALOGGER_LOGGING);
    } else {
        DEBUGLOG("asyncPut::datalogger config threw error: %u", result);
    }
}

void DataLoggerClient::stopModule() {
    stopTimer(mTimer); // Stop LED timer
    mTimer = wb::ID_INVALID_TIMER;

    // stop the DataLogger as we are shutting down the service
    stopDataLogger();

    mModuleState = WB_RES::ModuleStateValues::STOPPED;
}

void DataLoggerClient::stopDataLogger() {
    //To stop logging, PUT value DATALOGGER_READY (=2) to Mem/DataLogger/State resource
    asyncPut(WB_RES::LOCAL::MEM_DATALOGGER_STATE(), AsyncRequestOptions::Empty,
             WB_RES::DataLoggerStateValues::Type::DATALOGGER_READY);
}

void DataLoggerClient::onTimer(wb::TimerId) {
    // todo: if only one timer started, no need to check the ID
    const WB_RES::VisualIndType type = WB_RES::VisualIndTypeValues::SHORT_VISUAL_INDICATION; // defined in ui/ind.yaml
    asyncPut(WB_RES::LOCAL::UI_IND_VISUAL(), AsyncRequestOptions::Empty, type); // PUT request to trigger led blink
    // ignore the immediate and asynchronous Results as this is not critical

    //Fetch all entries / logs from the device
    getLogbookEntries();
}

// Use the Logbook to retrieve all the data we have stored using the Datalogger
// Look here for more details on DataLogger and Logbook: http://www.movesense.com/docs/mobile/android/datalogger_logbook/
void DataLoggerClient::getLogbookEntries() {
    //Get all the entries stored by data logger using the logbook, Use Empty because (or null) so the list of the items starts from beginning
    whiteboard::Result result = this->asyncGet(WB_RES::LOCAL::MEM_LOGBOOK_ENTRIES(), AsyncRequestOptions::Empty);
    DEBUGLOG("asyncGet::logbook get entries result: %u", result);
}

// Use the Logbook to retrieve 1 set of entries we have stored using the Datalogger
void DataLoggerClient::getLogbookEntry() {
    //Get one set of entries stored by data logger using the logbook,
    //this is not working...
    //    const uint32 p1 = 1;
//    const uint32 p2 = 0;
//    const whiteboard::ParameterList parameterList;
//    parameterList[0].convertTo<p1>();
//    parameterList[1].convertTo<p2>();

//    whiteboard::Result result = this->asyncGet(WB_RES::LOCAL::MEM_LOGBOOK_BYID_LOGID_DATA(), AsyncRequestOptions::Empty, parameterList);
//    DEBUGLOG("asyncGet::logbook get entry result: %u", result);
}


void DataLoggerClient::deleteLogbookEntries() {
    //Delete all the entries stored by data logger using the logbook
    whiteboard::Result result = this->asyncDelete(WB_RES::LOCAL::MEM_LOGBOOK_ENTRIES(), AsyncRequestOptions::Empty);
    DEBUGLOG("asyncDelete::logbook delete all entries result: %u", result);
}

void
DataLoggerClient::onNotify(wb::ResourceId resourceId, const wb::Value &value, const wb::ParameterList &parameters) {
    switch (resourceId.localResourceId) {
        case WB_RES::LOCAL::MEM_LOGBOOK_ENTRIES::LID: {
            DEBUGLOG("onSubscribeResult success resource: %u, value: %u, parameter list: %u",
                     resourceId.localResourceId, &value, &parameters);
            break;
        }
        default: {
            //do nothing
        }
    }
}

void DataLoggerClient::onSubscribeResult(wb::RequestId requestId, wb::ResourceId resourceId, wb::Result resultCode,
                                         const wb::Value &result) {
    // If error result, just log and do nothing
    if (wb::IsErrorResult(resultCode)) {
        DEBUGLOG("onSubscribeResult error: resource: %u, resultCode: %u", resourceId.localResourceId, resultCode);
        return;
    }

    switch (resourceId.localResourceId) {
        case WB_RES::LOCAL::MEM_LOGBOOK_ENTRIES::LID:
        case WB_RES::LOCAL::MEM_DATALOGGER_CONFIG::LID: {
            DEBUGLOG("onSubscribeResult success requestId: %u, resource: %u, resultCode: %u, result addr: %u",
                     requestId, resourceId.localResourceId, resultCode, &result);
            break;
        }
        default: {
            //do nothing
        }
    }
}

void DataLoggerClient::onGetResult(wb::RequestId requestId, wb::ResourceId resourceId, wb::Result resultCode,
                                   const wb::Value &result) {
    if (wb::IsErrorResult(resultCode)) {
        DEBUGLOG("onGetResult failed! resource: %u, result: %u", resourceId.localResourceId, resultCode);
        return;
    }
    switch (resourceId.localResourceId) {
        case WB_RES::LOCAL::MEM_DATALOGGER_CONFIG::LID: {
            DEBUGLOG("onGetResult success requestId: %u, resource: %u, resultCode: %u, result addr: %u", requestId,
                     resourceId.localResourceId, resultCode, &result);
            break;
        }
        case WB_RES::LOCAL::MEM_LOGBOOK_ENTRIES::LID: {
            DEBUGLOG("onGetResult success requestId: %u, resource: %u, resultCode: %u, result addr: %u", requestId,
                     resourceId.localResourceId, resultCode, &result);
            const WB_RES::LogEntries &logEntries = result.convertTo<const WB_RES::LogEntries &>();
//          DEBUGLOG("HRValue: %f", hrValue.average);
            DEBUGLOG("LE: %u", logEntries.elements.size());
            break;
        }
        default: {
            DEBUGLOG("resource id: %u", resourceId.localResourceId);
            //do nothing
        }
    }
}

void DataLoggerClient::onPutResult(wb::RequestId requestId,
                                   wb::ResourceId resourceId,
                                   wb::Result resultCode,
                                   const wb::Value &result) {
    if (wb::IsErrorResult(resultCode)) {
        DEBUGLOG("onPutResult failed! resource: %u, result: %u", resourceId.localResourceId, resultCode);
        return;
    }
    switch (resourceId.localResourceId) {
        case WB_RES::LOCAL::MEM_DATALOGGER_CONFIG::LID: {
            const WB_RES::DataLoggerConfig &dataLoggerConfig = result.convertTo<const WB_RES::DataLoggerConfig &>();
            DEBUGLOG("LE: %u", dataLoggerConfig.dataEntries.dataEntry.size());
        }
        case WB_RES::LOCAL::MEM_DATALOGGER_STATE::LID:
        case WB_RES::LOCAL::UI_IND_VISUAL::LID: {
            DEBUGLOG("onPutResult success requestId: %u, resource: %u, resultCode: %u, result addr: %u", requestId,
                     resourceId.localResourceId, resultCode, &result);
            break;
        }
        default: {
            //do nothing
        }
    }
}

void DataLoggerClient::onDeleteResult(whiteboard::RequestId requestId, whiteboard::ResourceId resourceId,
                                      whiteboard::Result resultCode, const whiteboard::Value &rResultData) {
    if (wb::IsErrorResult(resultCode)) {
        DEBUGLOG("onPutResult failed! resource: %u, result: %u", resourceId.localResourceId, resultCode);
        return;
    }

}


and the .h file:

#pragma once

#include <whiteboard/LaunchableModule.h>
#include <whiteboard/ResourceClient.h>

class DataLoggerClient FINAL : private wb::ResourceClient, public wb::LaunchableModule {
public:
    /** Name of this class. Used in StartupProvider list. */
    static const char *const LAUNCHABLE_NAME;

    DataLoggerClient();

    ~DataLoggerClient();

private:
    /** @see whiteboard::ILaunchableModule::initModule */
    virtual bool initModule() OVERRIDE;

    /** @see whiteboard::ILaunchableModule::deinitModule */
    virtual void deinitModule() OVERRIDE;

    /** @see whiteboard::ILaunchableModule::startModule */
    virtual bool startModule() OVERRIDE;

    /** @see whiteboard::ILaunchableModule::stopModule */
    virtual void stopModule() OVERRIDE;

protected:
    /** @see whiteboard::ResourceClient::onTimer */
    virtual void onTimer(wb::TimerId timerId) OVERRIDE;

    /** @see whiteboard::ResourceClient::onGetResult */
    virtual void onGetResult(wb::RequestId requestId,
                             wb::ResourceId resourceId,
                             wb::Result resultCode,
                             const wb::Value &result) OVERRIDE;

    /** @see whiteboard::ResourceClient::onPutResult */
    virtual void onPutResult(wb::RequestId requestId,
                             wb::ResourceId resourceId,
                             wb::Result resultCode,
                             const wb::Value &result) OVERRIDE;

    /** @see whiteboard::ResourceClient::onSubscribeResult */
    virtual void onSubscribeResult(wb::RequestId requestId,
                                   wb::ResourceId resourceId,
                                   wb::Result resultCode,
                                   const wb::Value &result) OVERRIDE;

    /** @see whiteboard::ResourceClient::onNotify */
    virtual void onNotify(wb::ResourceId resourceId,
                          const wb::Value &value,
                          const wb::ParameterList &parameters) OVERRIDE;


private:
    void startDataLogger();

    void getLogbookEntries();

    wb::TimerId mTimer;

    void stopDataLogger();

    void deleteLogbookEntries();

    void onDeleteResult(whiteboard::RequestId requestId, whiteboard::ResourceId resourceId, whiteboard::Result resultCode,
                   const whiteboard::Value &rResultData) override;

    void getLogbookEntry();
};

and the App.cpp file:

#include "DataLoggerClient.h"
#include "movesense.h"

MOVESENSE_APPLICATION_STACKSIZE(1024)

MOVESENSE_PROVIDERS_BEGIN(1)

MOVESENSE_PROVIDER_DEF(DataLoggerClient)

MOVESENSE_PROVIDERS_END(1)

MOVESENSE_FEATURES_BEGIN()
// Explicitly enable or disable Movesense framework core modules.
// List of modules and their default state is found in documentation
OPTIONAL_CORE_MODULE(DataLogger, true)
OPTIONAL_CORE_MODULE(Logbook, true)
OPTIONAL_CORE_MODULE(LedService, true)
OPTIONAL_CORE_MODULE(IndicationService, true)
OPTIONAL_CORE_MODULE(BleService, true)
OPTIONAL_CORE_MODULE(EepromService, true)
OPTIONAL_CORE_MODULE(BypassService, false)
OPTIONAL_CORE_MODULE(SystemMemoryService, true)
OPTIONAL_CORE_MODULE(DebugService, true)
OPTIONAL_CORE_MODULE(BleStandardHRS, false)
OPTIONAL_CORE_MODULE(BleNordicUART, false)
OPTIONAL_CORE_MODULE(CustomGattService, false)

// NOTE: It is inadvisable to enable both Logbook/DataLogger and EepromService without
// explicit definition of Logbook memory area (see LOGBOOK_MEMORY_AREA macro in movesense.h and eeprom_logbook_app).
// Default setting is for Logbook to use the whole EEPROM memory area.

// NOTE: If building a simulator build, these macros are obligatory!
DEBUGSERVICE_BUFFER_SIZE(6, 120); // 6 lines, 120 characters total
DEBUG_EEPROM_MEMORY_AREA(true, 0, 16384)
// Rest of the EEPROM is for Logbook
LOGBOOK_MEMORY_AREA(16384, (384 * 1024)-16384);

APPINFO_NAME("DataLogger");
APPINFO_VERSION("1.1.0");
APPINFO_COMPANY("Movesense");

// NOTE: SERIAL_COMMUNICATION macro has been DEPRECATED
BLE_COMMUNICATION(true)
MOVESENSE_FEATURES_END()


  • Strange, might be a problem with the simulator? I have tried it on an earlier version of the Movesense SDK and a switch on resourceId.localResourceId have got into the case matching WB_RES::LOCAL::MEM_LOGBOOK_ENTRIES::LID If you have a minimal working sample I could flash a sensor and take a look at the output from a jig. Also remember that this will only give you the number of logbook entries and not the actual data. – Morten Jan 17 '20 at 16:01
  • @Morten. Thanks for you response! I actually want to get the number of entries and then the next step is the actual data so that I can pass it back to the subscriber using GATT. I have added the minimal working sample code to the question. Thanks! Helic – Helic Opter Jan 19 '20 at 10:09

1 Answers1

1

Your issue is that the .dataEntry is a wb::Array<> and not a C-array. To set it correctly, use the code:

config.dataEntries.dataEntry = whiteboard::MakeArray<WB_RES::DataEntry>(& entry, 1);

(the &entry above is pointer to the first entry in the C-array of type WB_RES::DataEntry[] and "1" is the count.)

For more info look in the "generated" -subfolder in your build directory where the WB_RES structs are defined.

Full Disclosure: I work for the Movesense team

PetriL
  • 1,211
  • 1
  • 7
  • 11