/*
 * Copyright 2015-2021 CEVA, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License and 
 * any applicable agreements you may have with CEVA, Inc.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file sh2.c
 * @author David Wheeler
 * @date 22 Sept 2015
 * @brief API Definition for SH-2 Sensor Hub.
 *
 * The sh2 API provides functions for opening a session with
 * the sensor hub and performing all supported operations with it.
 * This includes enabling sensors and reading events as well as
 * other housekeeping functions.
 *
 */

//Suppress warning about fopen and strcpy safety under MSVC
#ifdef _MSC_VER
#   ifndef _CRT_SECURE_NO_WARNINGS
#       define _CRT_SECURE_NO_WARNINGS
#   endif
#endif

#include "sh2.h"
#include "sh2_err.h"
#include "shtp.h"
#include "sh2_util.h"

#include <string.h>
#include <stdio.h>

// ------------------------------------------------------------------------
// Private type definitions

#define CHAN_EXECUTABLE_DEVICE    (1)
#define CHAN_SENSORHUB_CONTROL    (2)
#define CHAN_SENSORHUB_INPUT      (3)
#define CHAN_SENSORHUB_INPUT_WAKE (4)
#define CHAN_SENSORHUB_INPUT_GIRV (5)

// executable/device channel responses
#define EXECUTABLE_DEVICE_CMD_RESET (1)
#define EXECUTABLE_DEVICE_CMD_ON    (2)
#define EXECUTABLE_DEVICE_CMD_SLEEP (3)

// executable/device channel responses
#define EXECUTABLE_DEVICE_RESP_RESET_COMPLETE (1)

// Tags for sensorhub app advertisements.
#define TAG_SH2_VERSION (0x80)
#define TAG_SH2_REPORT_LENGTHS (0x81)

// Max length of sensorhub version string.
#define MAX_VER_LEN (16)

// Max number of report ids supported
#define SH2_MAX_REPORT_IDS (64)

#if defined(_MSC_VER)
#define PACKED_STRUCT struct
#pragma pack(push, 1)
#elif defined(__GNUC__)
#define PACKED_STRUCT struct __attribute__((packed))
#else 
#define PACKED_STRUCT __packed struct
#endif

#define ADVERT_TIMEOUT_US (200000)

// Command and Subcommand values
#define SH2_CMD_ERRORS                 1
#define SH2_CMD_COUNTS                 2
#define     SH2_COUNTS_GET_COUNTS          0
#define     SH2_COUNTS_CLEAR_COUNTS        1
#define SH2_CMD_TARE                   3
#define     SH2_TARE_TARE_NOW              0
#define     SH2_TARE_PERSIST_TARE          1
#define     SH2_TARE_SET_REORIENTATION     2
#define SH2_CMD_INITIALIZE             4
#define     SH2_INIT_SYSTEM                1
#define     SH2_INIT_UNSOLICITED           0x80
// #define SH2_CMD_FRS                    5 /* Depreciated */
#define SH2_CMD_DCD                    6
#define SH2_CMD_ME_CAL                 7
#define SH2_CMD_DCD_SAVE               9
#define SH2_CMD_GET_OSC_TYPE           0x0A
#define SH2_CMD_CLEAR_DCD_AND_RESET    0x0B
#define SH2_CMD_CAL                    0x0C
#define     SH2_CAL_START                   0
#define     SH2_CAL_FINISH                  1
#define SH2_CMD_BOOTLOADER             0x0D     /* SH-2 Reference Manual 6.4.12 */
#define     SH2_BL_MODE_REQ                 0
#define     SH2_BL_STATUS_REQ               1
#define SH2_CMD_INTERACTIVE_ZRO        0x0E     /* SH-2 Reference Manual 6.4.13 */
#define SH2_CMD_WHEEL_REQ              0x0F
#define SH2_CMD_DR_CAL_SAVE            0x10

// SENSORHUB_COMMAND_REQ
#define SENSORHUB_COMMAND_REQ        (0xF2)
#define COMMAND_PARAMS (9)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t seq;
    uint8_t command;
    uint8_t p[COMMAND_PARAMS];
} CommandReq_t;

// SENSORHUB_COMMAND_RESP
#define SENSORHUB_COMMAND_RESP       (0xF1)
#define RESPONSE_VALUES (11)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t seq;
    uint8_t command;
    uint8_t commandSeq;
    uint8_t respSeq;
    uint8_t r[RESPONSE_VALUES];
} CommandResp_t;

// SENSORHUB_PROD_ID_REQ
#define SENSORHUB_PROD_ID_REQ        (0xF9)
typedef PACKED_STRUCT {
    uint8_t reportId;  
    uint8_t reserved;
} ProdIdReq_t;

// SENSORHUB_PROD_ID_RESP
#define SENSORHUB_PROD_ID_RESP       (0xF8)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t resetCause;
    uint8_t swVerMajor;
    uint8_t swVerMinor;
    uint32_t swPartNumber;
    uint32_t swBuildNumber;
    uint16_t swVerPatch;
    uint8_t reserved0;
    uint8_t reserved1;
} ProdIdResp_t;

// Report definitions
// Bit fields for Feature Report flags
#define FEAT_CHANGE_SENSITIVITY_MODE_BIT   0
#define FEAT_CHANGE_SENSITIVITY_ENABLE_BIT 1
#define FEAT_WAKEUP_ENABLE_BIT             2
#define FEAT_ALWAYS_ON_ENABLE_BIT          3
#define FEAT_SNIFF_ENABLE_BIT              4
#define FEAT_CHANGE_SENSITIVITY_RELATIVE   (1 << FEAT_CHANGE_SENSITIVITY_MODE_BIT)
#define FEAT_CHANGE_SENSITIVITY_ABSOLUTE   (0 << FEAT_CHANGE_SENSITIVITY_MODE_BIT)
#define FEAT_CHANGE_SENSITIVITY_ENABLED    (1 << FEAT_CHANGE_SENSITIVITY_ENABLE_BIT)
#define FEAT_CHANGE_SENSITIVITY_DISABLED   (0 << FEAT_CHANGE_SENSITIVITY_ENABLE_BIT)
#define FEAT_WAKE_ENABLED                  (1 << FEAT_WAKEUP_ENABLE_BIT)
#define FEAT_WAKE_DISABLED                 (0 << FEAT_WAKEUP_ENABLE_BIT)
#define FEAT_ALWAYS_ON_ENABLED             (1 << FEAT_ALWAYS_ON_ENABLE_BIT)
#define FEAT_ALWAYS_ON_DISABLED            (0 << FEAT_ALWAYS_ON_ENABLE_BIT)
#define FEAT_SNIFF_ENABLED                 (1 << FEAT_SNIFF_ENABLE_BIT)
#define FEAT_SNIFF_DISABLED                (0 << FEAT_SNIFF_ENABLE_BIT)


// GET_FEATURE_REQ
#define SENSORHUB_GET_FEATURE_REQ    (0xFE)
typedef PACKED_STRUCT{
    uint8_t reportId;
    uint8_t featureReportId;
} GetFeatureReq_t;

// SENSORHUB_GET_FEATURE_RESP
#define SENSORHUB_GET_FEATURE_RESP   (0xFC)
typedef PACKED_STRUCT{
    uint8_t reportId;
    uint8_t featureReportId;      // sensor id
    uint8_t flags;                // FEAT_... values
    uint16_t changeSensitivity;
    uint32_t reportInterval_uS;
    uint32_t batchInterval_uS;
    uint32_t sensorSpecific;
} GetFeatureResp_t;


typedef struct sh2_s sh2_t;

typedef int (sh2_OpStart_t)(sh2_t *pSh2);
typedef void (sh2_OpRx_t)(sh2_t *pSh2, const uint8_t *payload, uint16_t len);

typedef struct sh2_Op_s {
    uint32_t timeout_us;
    sh2_OpStart_t *start;
    sh2_OpRx_t *rx;
} sh2_Op_t;

// Parameters and state information for the operation in progress
typedef union {
    struct {
        CommandReq_t req;
    } sendCmd;
    struct {
        sh2_ProductIds_t *pProdIds;
        uint8_t nextEntry;
        uint8_t expectedEntries;
    } getProdIds;
    struct {
        sh2_SensorConfig_t *pConfig;
        sh2_SensorId_t sensorId;
    } getSensorConfig;
    struct {
        const sh2_SensorConfig_t *pConfig;
        sh2_SensorId_t sensorId;
    } setSensorConfig;
    struct {
        uint16_t frsType;
        uint32_t *pData;
        uint16_t *pWords;
        uint16_t nextOffset;
    } getFrs;
    struct {
        uint16_t frsType;
        uint32_t *pData;
        uint16_t words;
        uint16_t offset;
    } setFrs;
    struct {
        uint8_t severity;
        sh2_ErrorRecord_t *pErrors;
        uint16_t *pNumErrors;
        uint16_t errsRead;
    } getErrors;
    struct {
        sh2_SensorId_t sensorId;
        sh2_Counts_t *pCounts;
    } getCounts;
    struct {
        uint8_t sensors;
    } calConfig;
    struct {
        uint8_t *pSensors;
    } getCalConfig;
    struct {
        sh2_SensorId_t sensorId;
    } forceFlush;
    struct {
        sh2_OscType_t *pOscType;
    } getOscType;
    struct {
        uint32_t interval_us;
    } startCal;
    struct {
        sh2_CalStatus_t status;
    } finishCal;
    struct {
        uint8_t wheelIndex;
        uint32_t timestamp;
        int16_t wheelData;
        uint8_t dataType;
    } wheelRequest;
} sh2_OpData_t;

// Max length of an FRS record, words.
#define MAX_FRS_WORDS (72)

struct sh2_s {
    // Pointer to the SHTP HAL
    sh2_Hal_t *pHal;

    // associated SHTP instance
    void *pShtp;
    
    volatile bool resetComplete;
    char version[MAX_VER_LEN+1];

    // Multi-step operation support
    const sh2_Op_t *pOp;
    int opStatus;
    sh2_OpData_t opData;
    uint8_t lastCmdId;
    uint8_t cmdSeq;
    uint8_t nextCmdSeq;
    
    // Event callback and it's cookie
    sh2_EventCallback_t *eventCallback;
    void * eventCookie;

    // Sensor callback and it's cookie
    sh2_SensorCallback_t *sensorCallback;
    void * sensorCookie;

    // Storage space for reading sensor metadata
    uint32_t frsData[MAX_FRS_WORDS];
    uint16_t frsDataLen;

    // Stats
    uint32_t execBadPayload;
    uint32_t emptyPayloads;
    uint32_t unknownReportIds;

};

#define SENSORHUB_BASE_TIMESTAMP_REF (0xFB)
typedef PACKED_STRUCT {
    uint8_t reportId;
    int32_t timebase;
} BaseTimestampRef_t;

#define SENSORHUB_TIMESTAMP_REBASE   (0xFA)
typedef PACKED_STRUCT {
    uint8_t reportId;
    int32_t timebase;
} TimestampRebase_t;

// SENSORHUB_FORCE_SENSOR_FLUSH
#define SENSORHUB_FORCE_SENSOR_FLUSH (0xF0)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t sensorId;
} ForceFlushReq_t;

// SENSORHUB_FLUSH_COMPLETED    
#define SENSORHUB_FLUSH_COMPLETED    (0xEF)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t sensorId;
} ForceFlushResp_t;

typedef struct sh2_ReportLen_s {
    uint8_t id;
    uint8_t len;
} sh2_ReportLen_t;

// SENSORHUB_FRS_WRITE_REQ
#define SENSORHUB_FRS_WRITE_REQ      (0xF7)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t reserved;
    uint16_t length;
    uint16_t frsType;
} FrsWriteReq_t;

// SENSORHUB_FRS_WRITE_DATA_REQ
#define SENSORHUB_FRS_WRITE_DATA_REQ (0xF6)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t reserved;
    uint16_t offset;
    uint32_t data0;
    uint32_t data1;
} FrsWriteDataReq_t;

// FRS write status values
#define FRS_WRITE_STATUS_RECEIVED (0)
#define FRS_WRITE_STATUS_UNRECOGNIZED_FRS_TYPE (1)
#define FRS_WRITE_STATUS_BUSY (2)
#define FRS_WRITE_STATUS_WRITE_COMPLETED (3)
#define FRS_WRITE_STATUS_READY (4)
#define FRS_WRITE_STATUS_FAILED (5)
#define FRS_WRITE_STATUS_NOT_READY (6) // data received when not in write mode
#define FRS_WRITE_STATUS_INVALID_LENGTH (7)
#define FRS_WRITE_STATUS_RECORD_VALID (8)
#define FRS_WRITE_STATUS_INVALID_RECORD (9)
#define FRS_WRITE_STATUS_DEVICE_ERROR (10)
#define FRS_WRITE_STATUS_READ_ONLY (11)

// SENSORHUB_FRS_WRITE_RESP
#define SENSORHUB_FRS_WRITE_RESP     (0xF5)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t status;
    uint16_t wordOffset;
} FrsWriteResp_t;

// RESP_FRS_READ_REQ
#define SENSORHUB_FRS_READ_REQ       (0xF4)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t reserved;
    uint16_t readOffset;
    uint16_t frsType;
    uint16_t blockSize;
} FrsReadReq_t;

// Get Datalen portion of len_status field
#define FRS_READ_DATALEN(x) ((x >> 4) & 0x0F)

// Get status portion of len_status field
#define FRS_READ_STATUS(x) ((x) & 0x0F)

// Status values
#define FRS_READ_STATUS_NO_ERROR                        0
#define FRS_READ_STATUS_UNRECOGNIZED_FRS_TYPE           1
#define FRS_READ_STATUS_BUSY                            2
#define FRS_READ_STATUS_READ_RECORD_COMPLETED           3
#define FRS_READ_STATUS_OFFSET_OUT_OF_RANGE             4
#define FRS_READ_STATUS_RECORD_EMPTY                    5
#define FRS_READ_STATUS_READ_BLOCK_COMPLETED            6
#define FRS_READ_STATUS_READ_BLOCK_AND_RECORD_COMPLETED 7
#define FRS_READ_STATUS_DEVICE_ERROR                    8

// SENSORHUB_FRS_READ_RESP
#define SENSORHUB_FRS_READ_RESP      (0xF3)
typedef PACKED_STRUCT {
    uint8_t reportId;
    uint8_t len_status;  // See FRS_READ... macros above
    uint16_t wordOffset;
    uint32_t data0;
    uint32_t data1;
    uint16_t frsType;
    uint8_t reserved0;
    uint8_t reserved1;
} FrsReadResp_t;

// ------------------------------------------------------------------------
// Private data

// SH2 state
sh2_t _sh2;

// SH2 Async Event Message
static sh2_AsyncEvent_t sh2AsyncEvent;

// Lengths of reports by report id.
static const sh2_ReportLen_t sh2ReportLens[] = {
    // Sensor reports
    {.id = SH2_ACCELEROMETER,                .len = 10},  
    {.id = SH2_GYROSCOPE_CALIBRATED,         .len = 10},
    {.id = SH2_MAGNETIC_FIELD_CALIBRATED,    .len = 10},
    {.id = SH2_LINEAR_ACCELERATION,          .len = 10},
    {.id = SH2_ROTATION_VECTOR,              .len = 14},
    {.id = SH2_GRAVITY,                      .len = 10},
    {.id = SH2_GYROSCOPE_UNCALIBRATED,       .len = 16},
    {.id = SH2_GAME_ROTATION_VECTOR,         .len = 12},
    {.id = SH2_GEOMAGNETIC_ROTATION_VECTOR,  .len = 14},
    {.id = SH2_PRESSURE,                     .len =  8},
    {.id = SH2_AMBIENT_LIGHT,                .len =  8},
    {.id = SH2_HUMIDITY,                     .len =  6},
    {.id = SH2_PROXIMITY,                    .len =  6},
    {.id = SH2_TEMPERATURE,                  .len =  6},
    {.id = SH2_MAGNETIC_FIELD_UNCALIBRATED,  .len = 16},
    {.id = SH2_TAP_DETECTOR,                 .len =  5},
    {.id = SH2_STEP_COUNTER,                 .len = 12},
    {.id = SH2_SIGNIFICANT_MOTION,           .len =  6},
    {.id = SH2_STABILITY_CLASSIFIER,         .len =  6},
    {.id = SH2_RAW_ACCELEROMETER,            .len = 16},
    {.id = SH2_RAW_GYROSCOPE,                .len = 16},
    {.id = SH2_RAW_MAGNETOMETER,             .len = 16},
    {.id = SH2_STEP_DETECTOR,                .len =  8},
    {.id = SH2_SHAKE_DETECTOR,               .len =  6},
    {.id = SH2_FLIP_DETECTOR,                .len =  6},
    {.id = SH2_PICKUP_DETECTOR,              .len =  8},
    {.id = SH2_STABILITY_DETECTOR,           .len =  6},
    {.id = SH2_PERSONAL_ACTIVITY_CLASSIFIER, .len = 16},
    {.id = SH2_SLEEP_DETECTOR,               .len =  6},
    {.id = SH2_TILT_DETECTOR,                .len =  6},
    {.id = SH2_POCKET_DETECTOR,              .len =  6},
    {.id = SH2_CIRCLE_DETECTOR,              .len =  6},
    {.id = SH2_HEART_RATE_MONITOR,           .len =  6},
    {.id = SH2_ARVR_STABILIZED_RV,           .len = 14},
    {.id = SH2_ARVR_STABILIZED_GRV,          .len = 12},
    {.id = SH2_GYRO_INTEGRATED_RV,           .len = 14},
    {.id = SH2_IZRO_MOTION_REQUEST,          .len =  6},
    {.id = SH2_RAW_OPTICAL_FLOW,             .len = 24},
    {.id = SH2_DEAD_RECKONING_POSE,          .len = 60},
    {.id = SH2_WHEEL_ENCODER,                .len = 12},

    // Other response types
    {.id = SENSORHUB_FLUSH_COMPLETED,        .len =  2},
    {.id = SENSORHUB_COMMAND_RESP,           .len = 16},
    {.id = SENSORHUB_FRS_READ_RESP,          .len = 16},
    {.id = SENSORHUB_FRS_WRITE_RESP,         .len =  4},
    {.id = SENSORHUB_PROD_ID_RESP,           .len = 16},
    {.id = SENSORHUB_TIMESTAMP_REBASE,       .len =  5},
    {.id = SENSORHUB_BASE_TIMESTAMP_REF,     .len =  5},
    {.id = SENSORHUB_GET_FEATURE_RESP,       .len = 17},
};

// ------------------------------------------------------------------------
// Private functions

// SH-2 transaction phases
static int opStart(sh2_t *pSh2, const sh2_Op_t *pOp)
{
    // return error if another operation already in progress
    if (pSh2->pOp) return SH2_ERR_OP_IN_PROGRESS;

    // Establish this operation as the new operation in progress
    pSh2->pOp = pOp;
    pSh2->opStatus = SH2_OK;
    int rc = pOp->start(pSh2);  // Call start method
    if (rc != SH2_OK) {
        // Unregister this operation
        pSh2->opStatus = rc;
        pSh2->pOp = 0;
    }

    return rc;
}

static void opRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{ 
    if ((pSh2->pOp != 0) &&                      // An operation is in progress
        (pSh2->pOp->rx != 0)) {                  // and it has an rx method
        pSh2->pOp->rx(pSh2, payload, len);  // Call receive method
    }
}

static uint8_t getReportLen(uint8_t reportId)
{
    for (unsigned n = 0; n < ARRAY_LEN(sh2ReportLens); n++) {
        if (sh2ReportLens[n].id == reportId) {
            return sh2ReportLens[n].len;
        }
    }

    return 0;
}

static void sensorhubControlHdlr(void *cookie, uint8_t *payload, uint16_t len, uint32_t timestamp)
{
    (void)timestamp;  // unused.
    
    sh2_t *pSh2 = (sh2_t *)cookie;

    uint16_t cursor = 0;
    uint32_t count = 0;
    CommandResp_t * pResp = 0;

    if (len == 0) {
        pSh2->emptyPayloads++;
        return;
    }

    while (cursor < len) {
        // Get next report id
        count++;
        uint8_t reportId = payload[cursor];

        // Determine report length
        uint8_t reportLen = getReportLen(reportId);
        if (reportLen == 0) {
            // An unrecognized report id
            pSh2->unknownReportIds++;
            return;
        }
        else {
            // Check for unsolicited initialize response
            if (reportId == SENSORHUB_COMMAND_RESP) {
                pResp = (CommandResp_t *)(payload+cursor);
                if ((pResp->command == (SH2_CMD_INITIALIZE | SH2_INIT_UNSOLICITED)) &&
                    (pResp->r[1] == SH2_INIT_SYSTEM)) {
                    // This is an unsolicited INIT message.
                    // Ignore this.  EXECUTABLE_DEVICE_RESP_RESET_COMPLETE makes it redundant.
                }

            } // Check for Get Feature Response
            else if (reportId == SENSORHUB_GET_FEATURE_RESP) {
                if (pSh2->eventCallback) {
                    GetFeatureResp_t * pGetFeatureResp;
                    pGetFeatureResp = (GetFeatureResp_t *)(payload + cursor);

                    sh2AsyncEvent.eventId = SH2_GET_FEATURE_RESP;
                    sh2AsyncEvent.sh2SensorConfigResp.sensorId = pGetFeatureResp->featureReportId;
                    sh2AsyncEvent.sh2SensorConfigResp.sensorConfig.changeSensitivityEnabled =
                        ((pGetFeatureResp->flags & FEAT_CHANGE_SENSITIVITY_ENABLED) != 0);
                    sh2AsyncEvent.sh2SensorConfigResp.sensorConfig.changeSensitivityRelative =
                        ((pGetFeatureResp->flags & FEAT_CHANGE_SENSITIVITY_RELATIVE) != 0);
                    sh2AsyncEvent.sh2SensorConfigResp.sensorConfig.wakeupEnabled =
                        ((pGetFeatureResp->flags & FEAT_WAKE_ENABLED) != 0);
                    sh2AsyncEvent.sh2SensorConfigResp.sensorConfig.alwaysOnEnabled =
                        ((pGetFeatureResp->flags & FEAT_ALWAYS_ON_ENABLED) != 0);
                    sh2AsyncEvent.sh2SensorConfigResp.sensorConfig.changeSensitivity =
                        pGetFeatureResp->changeSensitivity;
                    sh2AsyncEvent.sh2SensorConfigResp.sensorConfig.reportInterval_us =
                        pGetFeatureResp->reportInterval_uS;
                    sh2AsyncEvent.sh2SensorConfigResp.sensorConfig.batchInterval_us =
                        pGetFeatureResp->batchInterval_uS;
                    sh2AsyncEvent.sh2SensorConfigResp.sensorConfig.sensorSpecific =
                        pGetFeatureResp->sensorSpecific;

                    pSh2->eventCallback(pSh2->eventCookie, &sh2AsyncEvent);
                }
            }

            // Hand off to operation in progress, if any
            opRx(pSh2, payload+cursor, reportLen);
            cursor += reportLen;
        }
    }
}

static int opCompleted(sh2_t *pSh2, int status)
{
    // Record status
    pSh2->opStatus = status;

    // Signal that op is done.
    pSh2->pOp = 0;

    return SH2_OK;
}

static int opProcess(sh2_t *pSh2, const sh2_Op_t *pOp)
{
    int status = SH2_OK;
    uint32_t start_us = 0;

    start_us = pSh2->pHal->getTimeUs(pSh2->pHal);
    
    status = opStart(pSh2, pOp);
    if (status != SH2_OK) {
        return status;
    }

    uint32_t now_us = start_us;
    // While op not complete and not timed out.
    while ((pSh2->pOp != 0) &&
           ((pOp->timeout_us == 0) ||
            ((now_us-start_us) < pOp->timeout_us))) {
        // Service SHTP to poll the device.
        shtp_service(pSh2->pShtp);

        // Update the time
        now_us = pSh2->pHal->getTimeUs(pSh2->pHal);
    }

    if (pSh2->pOp != 0) {
        // Operation has timed out.  Clean up.
        pSh2->pOp = 0;
        pSh2->opStatus = SH2_ERR_TIMEOUT;
    }

    return pSh2->opStatus;
}

// Produce 64-bit microsecond timestamp for a sensor event
static uint64_t touSTimestamp(uint32_t hostInt, int32_t referenceDelta, uint16_t delay)
{
    static uint32_t lastHostInt = 0;
    static uint32_t rollovers = 0;
    uint64_t timestamp;

    // Count times hostInt timestamps rolled over to produce upper bits
    if (hostInt < lastHostInt) {
        rollovers++;
    }
    lastHostInt = hostInt;
    
    timestamp = ((uint64_t)rollovers << 32);
    timestamp += hostInt + (referenceDelta + delay) * 100;

    return timestamp;
}

static void sensorhubInputHdlr(sh2_t *pSh2, uint8_t *payload, uint16_t len, uint32_t timestamp)
{
    sh2_SensorEvent_t event;
    uint16_t cursor = 0;

    int32_t referenceDelta = 0;

    while (cursor < len) {
        // Get next report id
        uint8_t reportId = payload[cursor];

        // Determine report length
        uint8_t reportLen = getReportLen(reportId);
        if (reportLen == 0) {
            // An unrecognized report id
            pSh2->unknownReportIds++;
            return;
        }
        else {
            if (reportId == SENSORHUB_BASE_TIMESTAMP_REF) {
                const BaseTimestampRef_t *rpt = (const BaseTimestampRef_t *)(payload+cursor);
                
                // store base timestamp reference
                referenceDelta = -rpt->timebase;
            }
            else if (reportId == SENSORHUB_TIMESTAMP_REBASE) {
                const TimestampRebase_t *rpt = (const TimestampRebase_t *)(payload+cursor);

                referenceDelta += rpt->timebase;
            }
            else if (reportId == SENSORHUB_FLUSH_COMPLETED) {
                // Route this as if it arrived on command channel.
                opRx(pSh2, payload+cursor, reportLen);
            }
            else {
                // Sensor event.  Call callback
                uint8_t *pReport = payload+cursor;
                uint16_t delay = ((pReport[2] & 0xFC) << 6) + pReport[3];
                event.timestamp_uS = touSTimestamp(timestamp, referenceDelta, delay);
                event.delay_uS = (referenceDelta + delay) * 100;
                event.reportId = reportId;
                memcpy(event.report, pReport, reportLen);
                event.len = reportLen;
                if (pSh2->sensorCallback != 0) {
                    pSh2->sensorCallback(pSh2->sensorCookie, &event);
                }
            }
            
            // Move to next report in the payload
            cursor += reportLen;
        }
    }
}

static void sensorhubInputNormalHdlr(void *cookie, uint8_t *payload, uint16_t len, uint32_t timestamp)
{
    sh2_t *pSh2 = (sh2_t *)cookie;

    sensorhubInputHdlr(pSh2, payload, len, timestamp);
}

static void sensorhubInputWakeHdlr(void *cookie, uint8_t *payload, uint16_t len, uint32_t timestamp)
{
    sh2_t *pSh2 = (sh2_t *)cookie;
    
    sensorhubInputHdlr(pSh2, payload, len, timestamp);
}

static void sensorhubInputGyroRvHdlr(void *cookie, uint8_t *payload, uint16_t len, uint32_t timestamp)
{
    sh2_t *pSh2 = (sh2_t *)cookie;
    sh2_SensorEvent_t event;
    uint16_t cursor = 0;

    uint8_t reportId = SH2_GYRO_INTEGRATED_RV;
    uint8_t reportLen = getReportLen(reportId);

    while (cursor < len) {
        event.timestamp_uS = timestamp;
        event.reportId = reportId;
        memcpy(event.report, payload+cursor, reportLen);
        event.len = reportLen;

        if (pSh2->sensorCallback != 0) {
            pSh2->sensorCallback(pSh2->sensorCookie, &event);
        }

        cursor += reportLen;
    }
}

static void executableDeviceHdlr(void *cookie, uint8_t *payload, uint16_t len, uint32_t timestamp)
{
    (void)timestamp;  // unused
    
    sh2_t *pSh2 = (sh2_t *)cookie;

    // Discard if length is bad
    if (len != 1) {
        pSh2->execBadPayload++;
        return;
    }
    
    switch (payload[0]) {
        case EXECUTABLE_DEVICE_RESP_RESET_COMPLETE:
            // reset process is now done.
            pSh2->resetComplete = true;
            
            // Notify client that reset is complete.
            sh2AsyncEvent.eventId = SH2_RESET;
            if (pSh2->eventCallback) {
                pSh2->eventCallback(pSh2->eventCookie, &sh2AsyncEvent);
            }
            break;
        default:
            pSh2->execBadPayload++;
            break;
    }
}

static int sendExecutable(sh2_t *pSh2, uint8_t cmd)
{
    return shtp_send(pSh2->pShtp, CHAN_EXECUTABLE_DEVICE, &cmd, 1);
}

static int sendCtrl(sh2_t *pSh2, const uint8_t *data, uint16_t len)
{
    return shtp_send(pSh2->pShtp, CHAN_SENSORHUB_CONTROL, data, len);
}

static int16_t toQ14(double x)
{
    int16_t retval = (int16_t)(x * (1<<14));
    
    return retval;
}

// ------------------------------------------------------------------------
// Get Product ID support

// Get Product ID Op handler
static int getProdIdStart(sh2_t *pSh2)
{
    int rc = SH2_OK;
    ProdIdReq_t req;
    
    pSh2->opData.getProdIds.nextEntry = 0;
    pSh2->opData.getProdIds.expectedEntries = 4;  // Most products supply 4 product ids.
                                                // When the first arrives, we'll know if
                                                // we need to adjust this.
    
    // Set up request to issue
    memset(&req, 0, sizeof(req));
    req.reportId = SENSORHUB_PROD_ID_REQ;
    rc = sendCtrl(pSh2, (uint8_t *)&req, sizeof(req));

    return rc;
}

static void getProdIdRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len;  // unused
    
    ProdIdResp_t *resp = (ProdIdResp_t *)payload;
    
    // skip this if it isn't the product id response.
    if (resp->reportId != SENSORHUB_PROD_ID_RESP) return;

    // Store this product id, if we can
    sh2_ProductIds_t *pProdIds = pSh2->opData.getProdIds.pProdIds;
    
    if (pProdIds) {
        // Store the product id response
        if (pSh2->opData.getProdIds.nextEntry < pSh2->opData.getProdIds.expectedEntries) {
            sh2_ProductId_t *pProdId = &pProdIds->entry[pSh2->opData.getProdIds.nextEntry];
            
            pProdId->resetCause = resp->resetCause;
            pProdId->swVersionMajor = resp->swVerMajor;
            pProdId->swVersionMinor = resp->swVerMinor;
            pProdId->swPartNumber = resp->swPartNumber;
            pProdId->swBuildNumber = resp->swBuildNumber;
            pProdId->swVersionPatch = resp->swVerPatch;
            pProdId->reserved0 = resp->reserved0;
            pProdId->reserved1 = resp->reserved1;

            if (pProdId->swPartNumber == 10004095) {
                // FSP200 has 5 product id entries
                pSh2->opData.getProdIds.expectedEntries = 5;
            }


            pSh2->opData.getProdIds.nextEntry++;
        }
    }

    // Complete this operation if there is no storage for more product ids
    if ((pSh2->opData.getProdIds.pProdIds == 0) ||
        (pSh2->opData.getProdIds.nextEntry >= pSh2->opData.getProdIds.expectedEntries)) {
        
        pSh2->opData.getProdIds.pProdIds->numEntries = pSh2->opData.getProdIds.nextEntry;
        opCompleted(pSh2, SH2_OK);
    }

    return;
}

const sh2_Op_t getProdIdOp = {
    .start = getProdIdStart,
    .rx = getProdIdRx,
};

// ------------------------------------------------------------------------
// Set Sensor Config

static int getSensorConfigStart(sh2_t *pSh2)
{
    int rc = SH2_OK;
    GetFeatureReq_t req;
    
    // set up request to issue
    memset(&req, 0, sizeof(req));
    req.reportId = SENSORHUB_GET_FEATURE_REQ;
    req.featureReportId = pSh2->opData.getSensorConfig.sensorId;
    rc = sendCtrl(pSh2, (uint8_t *)&req, sizeof(req));

    return rc;
}

static void getSensorConfigRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    
    GetFeatureResp_t *resp = (GetFeatureResp_t *)payload;
    sh2_SensorConfig_t *pConfig;
    
    // skip this if it isn't the response we're waiting for.
    if (resp->reportId != SENSORHUB_GET_FEATURE_RESP) return;
    if (resp->featureReportId != pSh2->opData.getSensorConfig.sensorId) return;

    // Copy out data
    pConfig = pSh2->opData.getSensorConfig.pConfig;
    
    pConfig->changeSensitivityEnabled = ((resp->flags & FEAT_CHANGE_SENSITIVITY_ENABLED) != 0);
    pConfig->changeSensitivityRelative = ((resp->flags & FEAT_CHANGE_SENSITIVITY_RELATIVE) != 0);
    pConfig->wakeupEnabled = ((resp->flags & FEAT_WAKE_ENABLED) != 0);
    pConfig->alwaysOnEnabled = ((resp->flags & FEAT_ALWAYS_ON_ENABLED) != 0);
    pConfig->sniffEnabled = ((resp->flags & FEAT_SNIFF_ENABLED) !=0);
    pConfig->changeSensitivity = resp->changeSensitivity;
    pConfig->reportInterval_us = resp->reportInterval_uS;
    pConfig->batchInterval_us = resp->batchInterval_uS;
    pConfig->sensorSpecific = resp->sensorSpecific;

    // Complete this operation
    opCompleted(pSh2, SH2_OK);

    return;
}

const sh2_Op_t getSensorConfigOp = {
    .start = getSensorConfigStart,
    .rx = getSensorConfigRx,
};

// ------------------------------------------------------------------------
// Set Sensor Config

// SENSORHUB_SET_FEATURE_CMD
#define SENSORHUB_SET_FEATURE_CMD    (0xFD)
typedef PACKED_STRUCT {
    uint8_t reportId;             // 0xFD
    uint8_t featureReportId;      // sensor id
    uint8_t flags;                // FEAT_... values
    uint16_t changeSensitivity;
    uint32_t reportInterval_uS;
    uint32_t batchInterval_uS;
    uint32_t sensorSpecific;
} SetFeatureReport_t;

static int setSensorConfigStart(sh2_t *pSh2)
{
    SetFeatureReport_t req;
    uint8_t flags = 0;
    int rc;
    sh2_SensorConfig_t *pConfig = pSh2->opData.getSensorConfig.pConfig;
    
    if (pConfig->changeSensitivityEnabled)  flags |= FEAT_CHANGE_SENSITIVITY_ENABLED;
    if (pConfig->changeSensitivityRelative) flags |= FEAT_CHANGE_SENSITIVITY_RELATIVE;
    if (pConfig->wakeupEnabled)             flags |= FEAT_WAKE_ENABLED;
    if (pConfig->alwaysOnEnabled)           flags |= FEAT_ALWAYS_ON_ENABLED;
    if (pConfig->sniffEnabled)              flags |= FEAT_SNIFF_ENABLED;

    memset(&req, 0, sizeof(req));
    req.reportId = SENSORHUB_SET_FEATURE_CMD;
    req.featureReportId = pSh2->opData.setSensorConfig.sensorId;
    req.flags = flags;
    req.changeSensitivity = pConfig->changeSensitivity;
    req.reportInterval_uS = pConfig->reportInterval_us;
    req.batchInterval_uS = pConfig->batchInterval_us;
    req.sensorSpecific = pConfig->sensorSpecific;

    rc = sendCtrl(pSh2, (uint8_t *)&req, sizeof(req));
    opCompleted(pSh2, rc);

    return rc;
}

const sh2_Op_t setSensorConfigOp = {
    .start = setSensorConfigStart,
};

// ------------------------------------------------------------------------
// Get FRS.


static int getFrsStart(sh2_t *pSh2)
{
    int rc = SH2_OK;
    FrsReadReq_t req;

    pSh2->opData.getFrs.nextOffset = 0;
    
    // set up request to issue
    memset(&req, 0, sizeof(req));
    req.reportId = SENSORHUB_FRS_READ_REQ;
    req.reserved = 0;
    req.readOffset = 0; // read from start
    req.frsType = pSh2->opData.getFrs.frsType;
    req.blockSize = 0;  // read all avail data

    rc = sendCtrl(pSh2, (uint8_t *)&req, sizeof(req));

    return rc;
}

static void getFrsRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    
    FrsReadResp_t *resp = (FrsReadResp_t *)payload;
    uint8_t status;

    // skip this if it isn't the response we're looking for
    if (resp->reportId != SENSORHUB_FRS_READ_RESP) return;

    // Check for errors: Unrecognized FRS type, Busy, Out of range, Device error
    status = FRS_READ_STATUS(resp->len_status);
    if ((status == FRS_READ_STATUS_UNRECOGNIZED_FRS_TYPE) ||
        (status == FRS_READ_STATUS_BUSY) ||
        (status == FRS_READ_STATUS_OFFSET_OUT_OF_RANGE) ||
        (status == FRS_READ_STATUS_DEVICE_ERROR)
        ) {
        // Operation failed
        opCompleted(pSh2, SH2_ERR_HUB);
        return;
    }

    if (status == FRS_READ_STATUS_RECORD_EMPTY) {
        // Empty record, return zero length.
        *(pSh2->opData.getFrs.pWords) = 0;
        opCompleted(pSh2, SH2_OK);
    }

    // Store the contents from this response
    uint16_t offset = resp->wordOffset;

    // check for missed offsets, resulting in error.
    if (offset != pSh2->opData.getFrs.nextOffset) {
        // Some data was dropped.
        *(pSh2->opData.getFrs.pWords) = 0;
        opCompleted(pSh2, SH2_ERR_IO);
    }
    
    // store first word, if we have room
    if ((*(pSh2->opData.getFrs.pWords) == 0) ||
        (offset <= *(pSh2->opData.getFrs.pWords))) {
        pSh2->opData.getFrs.pData[offset] = resp->data0;
        pSh2->opData.getFrs.nextOffset = offset+1;
    }

    // store second word if there is one and we have room
    if ((FRS_READ_DATALEN(resp->len_status) == 2)  &&
        ((*(pSh2->opData.getFrs.pWords) == 0) ||
         (offset <= *(pSh2->opData.getFrs.pWords)))) {
        pSh2->opData.getFrs.pData[offset+1] = resp->data1;
        pSh2->opData.getFrs.nextOffset = offset+2;
    }

    // If read is done, complete the operation
    if ((status == FRS_READ_STATUS_READ_RECORD_COMPLETED) ||
        (status == FRS_READ_STATUS_READ_BLOCK_COMPLETED) ||
        (status == FRS_READ_STATUS_READ_BLOCK_AND_RECORD_COMPLETED)) {
        *(pSh2->opData.getFrs.pWords) = pSh2->opData.getFrs.nextOffset;

        opCompleted(pSh2, SH2_OK);
    }

    return;
}

const sh2_Op_t getFrsOp = {
    .start = getFrsStart,
    .rx = getFrsRx,
};

// ------------------------------------------------------------------------
// Support for sh2_getMetadata

static const struct {
    sh2_SensorId_t sensorId;
    uint16_t recordId;
} sensorToRecordMap[] = {
    { SH2_RAW_ACCELEROMETER,            FRS_ID_META_RAW_ACCELEROMETER },
    { SH2_ACCELEROMETER,                FRS_ID_META_ACCELEROMETER },
    { SH2_LINEAR_ACCELERATION,          FRS_ID_META_LINEAR_ACCELERATION },
    { SH2_GRAVITY,                      FRS_ID_META_GRAVITY },
    { SH2_RAW_GYROSCOPE,                FRS_ID_META_RAW_GYROSCOPE },
    { SH2_GYROSCOPE_CALIBRATED,         FRS_ID_META_GYROSCOPE_CALIBRATED },
    { SH2_GYROSCOPE_UNCALIBRATED,       FRS_ID_META_GYROSCOPE_UNCALIBRATED },
    { SH2_RAW_MAGNETOMETER,             FRS_ID_META_RAW_MAGNETOMETER },
    { SH2_MAGNETIC_FIELD_CALIBRATED,    FRS_ID_META_MAGNETIC_FIELD_CALIBRATED },
    { SH2_MAGNETIC_FIELD_UNCALIBRATED,  FRS_ID_META_MAGNETIC_FIELD_UNCALIBRATED },
    { SH2_ROTATION_VECTOR,              FRS_ID_META_ROTATION_VECTOR },
    { SH2_GAME_ROTATION_VECTOR,         FRS_ID_META_GAME_ROTATION_VECTOR },
    { SH2_GEOMAGNETIC_ROTATION_VECTOR,  FRS_ID_META_GEOMAGNETIC_ROTATION_VECTOR },
    { SH2_PRESSURE,                     FRS_ID_META_PRESSURE },
    { SH2_AMBIENT_LIGHT,                FRS_ID_META_AMBIENT_LIGHT },
    { SH2_HUMIDITY,                     FRS_ID_META_HUMIDITY },
    { SH2_PROXIMITY,                    FRS_ID_META_PROXIMITY },
    { SH2_TEMPERATURE,                  FRS_ID_META_TEMPERATURE },
    { SH2_TAP_DETECTOR,                 FRS_ID_META_TAP_DETECTOR },
    { SH2_STEP_DETECTOR,                FRS_ID_META_STEP_DETECTOR },
    { SH2_STEP_COUNTER,                 FRS_ID_META_STEP_COUNTER },
    { SH2_SIGNIFICANT_MOTION,           FRS_ID_META_SIGNIFICANT_MOTION },
    { SH2_STABILITY_CLASSIFIER,         FRS_ID_META_STABILITY_CLASSIFIER },
    { SH2_SHAKE_DETECTOR,               FRS_ID_META_SHAKE_DETECTOR },
    { SH2_FLIP_DETECTOR,                FRS_ID_META_FLIP_DETECTOR },
    { SH2_PICKUP_DETECTOR,              FRS_ID_META_PICKUP_DETECTOR },
    { SH2_STABILITY_DETECTOR,           FRS_ID_META_STABILITY_DETECTOR },
    { SH2_PERSONAL_ACTIVITY_CLASSIFIER, FRS_ID_META_PERSONAL_ACTIVITY_CLASSIFIER },
    { SH2_SLEEP_DETECTOR,               FRS_ID_META_SLEEP_DETECTOR },
    { SH2_TILT_DETECTOR,                FRS_ID_META_TILT_DETECTOR },
    { SH2_POCKET_DETECTOR,              FRS_ID_META_POCKET_DETECTOR },
    { SH2_CIRCLE_DETECTOR,              FRS_ID_META_CIRCLE_DETECTOR },
};

static void stuffMetadata(sh2_SensorMetadata_t *pData, uint32_t *frsData)
{
    // Populate the sensorMetadata structure with results
    pData->meVersion        = (frsData[0] >> 0) & 0xFF;
    pData->mhVersion        = (frsData[0] >> 8) & 0xFF;
    pData->shVersion        = (frsData[0] >> 16) & 0xFF;
    pData->range            = frsData[1];
    pData->resolution       = frsData[2];
    pData->power_mA         = (frsData[3] >> 0) & 0xFFFF;    // 16.10 format
    pData->revision         = (frsData[3] >> 16) & 0xFFFF;
    pData->minPeriod_uS     = frsData[4];
    pData->maxPeriod_uS     = 0;  // ...unless reading format 4 metadata below
    pData->fifoMax          = (frsData[5] >> 0) & 0xFFFF;
    pData->fifoReserved     = (frsData[5] >> 16) & 0xFFFF;
    pData->batchBufferBytes = (frsData[6] >> 0) & 0xFFFF;;
    pData->vendorIdLen      = (frsData[6] >> 16) & 0xFFFF;

    // Init fields that may not be present, depending on metadata revision
    pData->qPoint1           = 0;
    pData->qPoint2           = 0;
    pData->qPoint3           = 0;
    pData->sensorSpecificLen = 0;
    strcpy(pData->vendorId, ""); // init with empty string in case vendorIdLen == 0

    int vendorIdOffset = 8;
    // Get revision-specific fields
    if (pData->revision == 0) {
        // No fixed fields, vendor id copied after if-else block
    }
    else if (pData->revision == 1) {
        pData->qPoint1        = (frsData[7] >> 0) & 0xFFFF;
        pData->qPoint2        = (frsData[7] >> 16) & 0xFFFF;
    }
    else if (pData->revision == 2) {
        pData->qPoint1        = (frsData[7] >> 0) & 0xFFFF;
        pData->qPoint2        = (frsData[7] >> 16) & 0xFFFF;
        pData->sensorSpecificLen = (frsData[8] >> 0) & 0xFFFF;
        memcpy(pData->sensorSpecific, (uint8_t *)&frsData[9], pData->sensorSpecificLen);
        vendorIdOffset = 9 + ((pData->sensorSpecificLen+3)/4); // 9 + one word for every 4 bytes of SS data
    }
    else if (pData->revision == 3) {
        pData->qPoint1        = (frsData[7] >> 0) & 0xFFFF;
        pData->qPoint2        = (frsData[7] >> 16) & 0xFFFF;
        pData->sensorSpecificLen = (frsData[8] >> 0) & 0xFFFF;
        pData->qPoint3        = (frsData[8] >> 16) & 0xFFFF;
        memcpy(pData->sensorSpecific, (uint8_t *)&frsData[9], pData->sensorSpecificLen);
        vendorIdOffset = 9 + ((pData->sensorSpecificLen+3)/4); // 9 + one word for every 4 bytes of SS data
    }
    else if (pData->revision == 4) {
        pData->qPoint1        = (frsData[7] >> 0) & 0xFFFF;
        pData->qPoint2        = (frsData[7] >> 16) & 0xFFFF;
        pData->sensorSpecificLen = (frsData[8] >> 0) & 0xFFFF;
        pData->qPoint3        = (frsData[8] >> 16) & 0xFFFF;
        pData->maxPeriod_uS   = frsData[9];
        memcpy(pData->sensorSpecific, (uint8_t *)&frsData[10], pData->sensorSpecificLen);
        vendorIdOffset = 10 + ((pData->sensorSpecificLen+3)/4); // 9 + one word for every 4 bytes of SS data
    }
    else {
        // Unrecognized revision!
    }

    // Copy vendor id
    memcpy(pData->vendorId, (uint8_t *)&frsData[vendorIdOffset],
           pData->vendorIdLen);
}

// ------------------------------------------------------------------------
// Set FRS.

static int setFrsStart(sh2_t *pSh2)
{
    int rc = SH2_OK;
    FrsWriteReq_t req;

    pSh2->opData.setFrs.offset = 0;
    
    // set up request to issue
    memset(&req, 0, sizeof(req));
    req.reportId = SENSORHUB_FRS_WRITE_REQ;
    req.reserved = 0;
    req.length = pSh2->opData.setFrs.words;
    req.frsType = pSh2->opData.getFrs.frsType;

    rc = sendCtrl(pSh2, (uint8_t *)&req, sizeof(req));

    return rc;
}

static void setFrsRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    
    FrsWriteResp_t *resp = (FrsWriteResp_t *)payload;
    FrsWriteDataReq_t req;
    uint8_t status;
    bool sendMoreData = false;
    bool completed = false;
    int rc = SH2_OK;

    // skip this if it isn't the response we're looking for.
    if (resp->reportId != SENSORHUB_FRS_WRITE_RESP) return;

    // Check for errors: Unrecognized FRS type, Busy, Out of range, Device error
    status = resp->status;
    switch(status) {
        case FRS_WRITE_STATUS_RECEIVED:
        case FRS_WRITE_STATUS_READY:
            sendMoreData = true;
            break;
        case FRS_WRITE_STATUS_UNRECOGNIZED_FRS_TYPE:
        case FRS_WRITE_STATUS_BUSY:
        case FRS_WRITE_STATUS_FAILED:
        case FRS_WRITE_STATUS_NOT_READY:
        case FRS_WRITE_STATUS_INVALID_LENGTH:
        case FRS_WRITE_STATUS_INVALID_RECORD:
        case FRS_WRITE_STATUS_DEVICE_ERROR:
        case FRS_WRITE_STATUS_READ_ONLY:
            rc = SH2_ERR_HUB;
            completed = true;
            break;
        case FRS_WRITE_STATUS_WRITE_COMPLETED:
            // Successful completion
            rc = SH2_OK;
            completed = true;
            break;
        case FRS_WRITE_STATUS_RECORD_VALID:
            // That's nice, keep waiting
            break;
    }

    // if we should send more data, do it.
    if (sendMoreData &&
        (pSh2->opData.setFrs.offset < pSh2->opData.setFrs.words)) {
        uint16_t offset = pSh2->opData.setFrs.offset;
        
        memset(&req, 0, sizeof(req));
        req.reportId = SENSORHUB_FRS_WRITE_DATA_REQ;
        req.reserved = 0;
        req.offset = offset;
        req.data0 = pSh2->opData.setFrs.pData[offset++];
        if (offset < pSh2->opData.setFrs.words) {
            req.data1 = pSh2->opData.setFrs.pData[offset++];
        } else {
            req.data1 = 0;
        }
        pSh2->opData.setFrs.offset = offset;
        
        rc = sendCtrl(pSh2, (uint8_t *)&req, sizeof(req));
    }

    // if the operation is done or has to be aborted, complete it
    if (completed) {
        opCompleted(pSh2, rc);
    }

    return;
}

const sh2_Op_t setFrsOp = {
    .start = setFrsStart,
    .rx = setFrsRx,
};

// ------------------------------------------------------------------------
// Support for sending commands

static int sendCmd(sh2_t *pSh2, uint8_t cmd, uint8_t p[COMMAND_PARAMS])
{
    int rc = SH2_OK;
    CommandReq_t req;

    // Clear request structure
    memset(&req, 0, sizeof(req));
    
    // Create a command sequence number for this command
    pSh2->lastCmdId = cmd;
    pSh2->cmdSeq = pSh2->nextCmdSeq++;
    
    // set up request to issue
    req.reportId = SENSORHUB_COMMAND_REQ;
    req.seq = pSh2->cmdSeq;
    req.command = cmd;
    for (int n = 0; n < COMMAND_PARAMS; n++) {
        req.p[n] = p[n];
    }
    rc = sendCtrl(pSh2, (uint8_t *)&req, sizeof(req));
    
    return rc;
}

// Send a command with 0 parameters
static int sendCmd0(sh2_t *pSh2, uint8_t cmd)
{
    uint8_t p[COMMAND_PARAMS];

    memset(p, 0, COMMAND_PARAMS);

    return sendCmd(pSh2, cmd, p);
}

// Send a command with 1 parameter
static int sendCmd1(sh2_t *pSh2, uint8_t cmd, uint8_t p0)
{
    uint8_t p[COMMAND_PARAMS];

    memset(p, 0, COMMAND_PARAMS);

    p[0] = p0;
    return sendCmd(pSh2, cmd, p);
}

// Send a command with 2 parameters
static int sendCmd2(sh2_t *pSh2, uint8_t cmd, uint8_t p0, uint8_t p1)
{
    uint8_t p[COMMAND_PARAMS];

    memset(p, 0, COMMAND_PARAMS);
    
    p[0] = p0;
    p[1] = p1;
    return sendCmd(pSh2, cmd, p);
}

static bool wrongResponse(sh2_t *pSh2, CommandResp_t *resp)
{
    if (resp->reportId != SENSORHUB_COMMAND_RESP) return true;
    if (resp->command != pSh2->lastCmdId) return true;
    if (resp->commandSeq != pSh2->cmdSeq) return true;

    return false;
}

// ------------------------------------------------------------------------
// Get Errors

static int getErrorsStart(sh2_t *pSh2)
{
    // Initialize state
    pSh2->opData.getErrors.errsRead = 0;

    return sendCmd1(pSh2, SH2_CMD_ERRORS, pSh2->opData.getErrors.severity);
}

static void getErrorsRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    
    CommandResp_t *resp = (CommandResp_t *)payload;
    
    // skip this if it isn't the right response
    if (wrongResponse(pSh2, resp)) return;

    if (resp->r[2] == 255) {
        // No error to report, operation is complete
        *(pSh2->opData.getErrors.pNumErrors) = pSh2->opData.getErrors.errsRead;
        opCompleted(pSh2, SH2_OK);
    } else {
        // Copy data for invoker.
        unsigned int index = pSh2->opData.getErrors.errsRead;
        if (index < *(pSh2->opData.getErrors.pNumErrors)) {
            // We have room for this one.
            pSh2->opData.getErrors.pErrors[index].severity = resp->r[0];
            pSh2->opData.getErrors.pErrors[index].sequence = resp->r[1];
            pSh2->opData.getErrors.pErrors[index].source = resp->r[2];
            pSh2->opData.getErrors.pErrors[index].error = resp->r[3];
            pSh2->opData.getErrors.pErrors[index].module = resp->r[4];
            pSh2->opData.getErrors.pErrors[index].code = resp->r[5];

            pSh2->opData.getErrors.errsRead++;
        }
    }

    return;
}

const sh2_Op_t getErrorsOp = {
    .start = getErrorsStart,
    .rx = getErrorsRx,
};

// ------------------------------------------------------------------------
// Get Counts

static int getCountsStart(sh2_t *pSh2)
{
    return sendCmd2(pSh2, SH2_CMD_COUNTS, SH2_COUNTS_GET_COUNTS, pSh2->opData.getCounts.sensorId);
}

static void getCountsRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    
    CommandResp_t *resp = (CommandResp_t *)payload;

    if (wrongResponse(pSh2, resp)) return;
    
    // Store results
    if (resp->respSeq == 0) {
        pSh2->opData.getCounts.pCounts->offered = readu32(&resp->r[3]);
        pSh2->opData.getCounts.pCounts->accepted = readu32(&resp->r[7]);
    }
    else {
        pSh2->opData.getCounts.pCounts->on = readu32(&resp->r[3]);
        pSh2->opData.getCounts.pCounts->attempted = readu32(&resp->r[7]);
    }
    
    // Complete this operation if we've received last response
    if (resp->respSeq == 1) {
        opCompleted(pSh2, SH2_OK);
    }

    return;
}

const sh2_Op_t getCountsOp = {
    .start = getCountsStart,
    .rx = getCountsRx,
};

// ------------------------------------------------------------------------
// Generic Send Command

static int sendCmdStart(sh2_t *pSh2)
{
    int status = sendCmd(pSh2, pSh2->opData.sendCmd.req.command,
                     pSh2->opData.sendCmd.req.p);

    opCompleted(pSh2, status);

    return status;
}

const sh2_Op_t sendCmdOp = {
    .start = sendCmdStart,
};

// ------------------------------------------------------------------------
// Reinit

static int reinitStart(sh2_t *pSh2)
{
    int status = sendCmd1(pSh2, SH2_CMD_INITIALIZE, SH2_INIT_SYSTEM);

    return status;
}

static void reinitRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    CommandResp_t *resp = (CommandResp_t *)payload;

    // Ignore message if it doesn't pertain to this operation
    if (wrongResponse(pSh2, resp)) return;

    // Get return status
    int status = SH2_OK;
    if (resp->r[0] != 0) {
        status = SH2_ERR_HUB;
    }

    opCompleted(pSh2, status);
}

const sh2_Op_t reinitOp = {
    .start = reinitStart,
    .rx = reinitRx,
};

// ------------------------------------------------------------------------
// Save DCD Now

static int saveDcdNowStart(sh2_t *pSh2)
{
    int status = sendCmd0(pSh2, SH2_CMD_DCD);

    return status;
}

static void saveDcdNowRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    CommandResp_t *resp = (CommandResp_t *)payload;

    // Ignore message if it doesn't pertain to this operation
    if (wrongResponse(pSh2, resp)) return;

    // Get return status
    int status = SH2_OK;
    if (resp->r[0] != 0) {
        status = SH2_ERR_HUB;
    }

    opCompleted(pSh2, status);
}

const sh2_Op_t saveDcdNowOp = {
    .start = saveDcdNowStart,
    .rx = saveDcdNowRx,
};

// ------------------------------------------------------------------------
// Get Osc Type

static int getOscTypeStart(sh2_t *pSh2)
{
    return sendCmd0(pSh2, SH2_CMD_GET_OSC_TYPE);
}

static void getOscTypeRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    CommandResp_t *resp = (CommandResp_t *)payload;
    sh2_OscType_t *pOscType;
    
    // Ignore message if it doesn't pertain to this operation
    if (wrongResponse(pSh2, resp)) return;

    // Read out data
    pOscType = pSh2->opData.getOscType.pOscType;
    *pOscType = (sh2_OscType_t)resp->r[0];

    // Complete this operation
    opCompleted(pSh2, SH2_OK);
}

const sh2_Op_t getOscTypeOp = {
    .start = getOscTypeStart,
    .rx = getOscTypeRx,
};

// ------------------------------------------------------------------------
// Set Cal Config

static int setCalConfigStart(sh2_t *pSh2)
{
    uint8_t p[COMMAND_PARAMS];

    // Clear p.  (Importantly, set subcommand in p[3] to 0, CONFIGURE)
    memset(p, 0, COMMAND_PARAMS);
    
    // Which cal processes to enable/disable
    p[0] = (pSh2->opData.calConfig.sensors & SH2_CAL_ACCEL) ? 1 : 0; // accel cal
    p[1] = (pSh2->opData.calConfig.sensors & SH2_CAL_GYRO)  ? 1 : 0; // gyro cal
    p[2] = (pSh2->opData.calConfig.sensors & SH2_CAL_MAG)   ? 1 : 0; // mag cal
    p[4] = (pSh2->opData.calConfig.sensors & SH2_CAL_PLANAR) ? 1 : 0; // planar cal
    p[5] = (pSh2->opData.calConfig.sensors & SH2_CAL_ON_TABLE) ? 1 : 0; // on-table cal
    
    return sendCmd(pSh2, SH2_CMD_ME_CAL, p);
}

static void setCalConfigRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    
    CommandResp_t *resp = (CommandResp_t *)payload;
    
    // Ignore message if it doesn't pertain to this operation
    if (wrongResponse(pSh2, resp)) return;

    // Read out data
    int status = SH2_OK;
    if (resp->r[0] != 0) {
        status = SH2_ERR_HUB;
    }

    // Complete this operation
    opCompleted(pSh2, status);
}

const sh2_Op_t setCalConfigOp = {
    .start = setCalConfigStart,
    .rx = setCalConfigRx,
};

// ------------------------------------------------------------------------
// Get Cal Config

static int getCalConfigStart(sh2_t *pSh2)
{
    uint8_t p[COMMAND_PARAMS];

    // Clear p.  (Importantly, set subcommand in p[3] to 0, CONFIGURE)
    memset(p, 0, COMMAND_PARAMS);
    
    // Subcommand: Get ME Calibration
    p[3] = 0x01;
    
    return sendCmd(pSh2, SH2_CMD_ME_CAL, p);
}

static void getCalConfigRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    CommandResp_t *resp = (CommandResp_t *)payload;
    
    // Ignore message if it doesn't pertain to this operation
    if (wrongResponse(pSh2, resp)) return;

    // Read out data
    int status = SH2_OK;
    if (resp->r[0] != 0) {
        status = SH2_ERR_HUB;
    }
    else {
        // Unload results into pSensors
        uint8_t sensors = 0;
        if (resp->r[1]) sensors |= SH2_CAL_ACCEL;
        if (resp->r[2]) sensors |= SH2_CAL_GYRO;
        if (resp->r[3]) sensors |= SH2_CAL_MAG;
        if (resp->r[4]) sensors |= SH2_CAL_PLANAR;
        *(pSh2->opData.getCalConfig.pSensors) = sensors;
    }
    
    // Complete this operation
    opCompleted(pSh2, status);
}


const sh2_Op_t getCalConfigOp = {
    .start = getCalConfigStart,
    .rx = getCalConfigRx,
};

// ------------------------------------------------------------------------
// Force Flush

static int forceFlushStart(sh2_t *pSh2)
{
    ForceFlushReq_t req;

    memset(&req, 0, sizeof(req));
    req.reportId = SENSORHUB_FORCE_SENSOR_FLUSH;
    req.sensorId = pSh2->opData.forceFlush.sensorId;
    
    return sendCtrl(pSh2, (uint8_t *)&req, sizeof(req));
}

static void forceFlushRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    
    ForceFlushResp_t *resp = (ForceFlushResp_t *)payload;
    
    // Ignore message if it doesn't pertain to this operation
    if (resp->reportId != SENSORHUB_FLUSH_COMPLETED) return;
    if (resp->sensorId != pSh2->opData.forceFlush.sensorId) return;

    // Complete this operation
    opCompleted(pSh2, SH2_OK);
}

const sh2_Op_t forceFlushOp = {
    .start = forceFlushStart,
    .rx = forceFlushRx,
};

// ------------------------------------------------------------------------
// Clear DCD And Reset

static int clearDcdAndResetStart(sh2_t *pSh2)
{
    pSh2->resetComplete = false;
    int status = sendCmd0(pSh2, SH2_CMD_CLEAR_DCD_AND_RESET);

    return status;
}

static void clearDcdAndResetRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)payload; // unused
    (void)len;     // unused
    
    // Ignore messages until reset cycle is complete.
    if (!pSh2->resetComplete) return;

    // Complete this operation
    opCompleted(pSh2, SH2_OK);
}

const sh2_Op_t clearDcdAndResetOp = {
    .start = clearDcdAndResetStart,
    .rx = clearDcdAndResetRx,
};

// ------------------------------------------------------------------------
// Start Cal

static int startCalStart(sh2_t *pSh2)
{
    uint8_t p[COMMAND_PARAMS];

    // Clear p.  (Importantly, set subcommand in p[3] to 0, CONFIGURE)
    memset(p, 0, COMMAND_PARAMS);
    
    // Subcommand: Get ME Calibration
    p[0] = SH2_CAL_START;
    p[1] = pSh2->opData.startCal.interval_us & 0xFF;          // LSB
    p[2] = (pSh2->opData.startCal.interval_us >> 8) & 0xFF;
    p[3] = (pSh2->opData.startCal.interval_us >> 16) & 0xFF;
    p[4] = (pSh2->opData.startCal.interval_us >> 24) & 0xFF;  // MSB
    
    return sendCmd(pSh2, SH2_CMD_CAL, p);
}

static void startCalRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    
    CommandResp_t *resp = (CommandResp_t *)payload;
    
    // Ignore message if it doesn't pertain to this operation
    if (wrongResponse(pSh2, resp)) return;

    // Complete this operation
    opCompleted(pSh2, SH2_OK);
}

const sh2_Op_t startCalOp = {
    .start = startCalStart,
    .rx = startCalRx,
};

// ------------------------------------------------------------------------
// Start Cal

static int finishCalStart(sh2_t *pSh2)
{
    return sendCmd1(pSh2, SH2_CMD_CAL, SH2_CAL_FINISH);
}

static void finishCalRx(sh2_t *pSh2, const uint8_t *payload, uint16_t len)
{
    (void)len; // unused
    
    CommandResp_t *resp = (CommandResp_t *)payload;
    
    // Ignore message if it doesn't pertain to this operation
    if (wrongResponse(pSh2, resp)) return;

    pSh2->opData.finishCal.status = (sh2_CalStatus_t)resp->r[1];

    // Complete this operation
    if (pSh2->opData.finishCal.status == SH2_CAL_SUCCESS) {
        opCompleted(pSh2, SH2_OK);
    }
    else {
        opCompleted(pSh2, SH2_ERR_HUB);
    }
}

const sh2_Op_t finishCalOp = {
    .start = finishCalStart,
    .rx = finishCalRx,
};

// -----------------------------------------------------------------------
static int sendWheelOpStart(sh2_t *pSh2)
{
    uint8_t p[COMMAND_PARAMS];
    memset(p, 0, COMMAND_PARAMS);
    p[0] = pSh2->opData.wheelRequest.wheelIndex;
    p[1] = (pSh2->opData.wheelRequest.timestamp >> 0) & 0xFF; 
    p[2] = (pSh2->opData.wheelRequest.timestamp >> 8) & 0xFF;
    p[3] = (pSh2->opData.wheelRequest.timestamp >> 16) & 0xFF;
    p[4] = (pSh2->opData.wheelRequest.timestamp >> 24) & 0xFF;
    p[5] = (pSh2->opData.wheelRequest.wheelData >> 0) & 0xFF;
    p[6] = (pSh2->opData.wheelRequest.wheelData >> 8) & 0xFF;
    p[7] = pSh2->opData.wheelRequest.dataType;
    int status = sendCmd(pSh2, SH2_CMD_WHEEL_REQ, p);
    opCompleted(pSh2, status);
    return status;
}


const sh2_Op_t sendWheelOp = {
    .start = sendWheelOpStart,
    .timeout_us = 5000000,
};


// ------------------------------------------------------------------------
// SHTP Event Callback

static void shtpEventCallback(void *cookie, shtp_Event_t shtpEvent) {
    (void)cookie; // unused
    
    sh2_t *pSh2 = &_sh2;

    sh2AsyncEvent.eventId = SH2_SHTP_EVENT;
    sh2AsyncEvent.shtpEvent = shtpEvent;
    if (pSh2->eventCallback) {
        pSh2->eventCallback(pSh2->eventCookie, &sh2AsyncEvent);
    }
}

// ------------------------------------------------------------------------
// Public functions

/**
 * @brief Open a session with a sensor hub.
 *
 * This function should be called before others in this API.
 * An instance of an SH2 HAL should be passed in.
 * This call will result in the open() function of the HAL being called.
 *
 * As part of the initialization process, a callback function is registered that will
 * be invoked when the device generates certain events.  (See sh2_AsyncEventId)
 *
 * @param pHal Pointer to an SH2 HAL instance, provided by the target system.
 * @param  eventCallback Will be called when events, such as reset complete, occur.
 * @param  eventCookie Will be passed to eventCallback.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_open(sh2_Hal_t *pHal,
             sh2_EventCallback_t *eventCallback, void *eventCookie)
{
    sh2_t *pSh2 = &_sh2;
    
    // Validate parameters
    if (pHal == 0) return SH2_ERR_BAD_PARAM;

    // Clear everything in sh2 structure.
    memset(pSh2, 0, sizeof(sh2_t));

    // will go true after reset response from SH.
    pSh2->resetComplete = false;
    
    // Store reference to HAL for future use.
    pSh2->pHal = pHal;
    pSh2->eventCallback = eventCallback;
    pSh2->eventCookie = eventCookie;
    pSh2->sensorCallback = 0;
    pSh2->sensorCookie = 0;

    // Open SHTP layer
    pSh2->pShtp = shtp_open(pSh2->pHal);
    if (pSh2->pShtp == 0) {
        // Error opening SHTP
        return SH2_ERR;
    }

    // Register SHTP event callback
    shtp_setEventCallback(pSh2->pShtp, shtpEventCallback, pSh2);

    // Register with SHTP
    // Register SH2 handlers
    shtp_listenChan(pSh2->pShtp, CHAN_SENSORHUB_CONTROL, sensorhubControlHdlr, pSh2);
    shtp_listenChan(pSh2->pShtp, CHAN_SENSORHUB_INPUT, sensorhubInputNormalHdlr, pSh2);
    shtp_listenChan(pSh2->pShtp, CHAN_SENSORHUB_INPUT_WAKE, sensorhubInputWakeHdlr, pSh2);
    shtp_listenChan(pSh2->pShtp, CHAN_SENSORHUB_INPUT_GIRV, sensorhubInputGyroRvHdlr, pSh2);

    // Register EXECUTABLE handlers
    shtp_listenChan(pSh2->pShtp, CHAN_EXECUTABLE_DEVICE, executableDeviceHdlr, pSh2);

    // Wait for reset notifications to arrive.
    // The client can't talk to the sensor hub until that happens.
    uint32_t start_us = pSh2->pHal->getTimeUs(pSh2->pHal);
    uint32_t now_us = start_us;
    while (((now_us - start_us) < ADVERT_TIMEOUT_US) &&
           (!pSh2->resetComplete))
    {
        shtp_service(pSh2->pShtp);
        now_us = pSh2->pHal->getTimeUs(pSh2->pHal);
    }
    
    // No errors.
    return SH2_OK;
}

/**
 * @brief Close a session with a sensor hub.
 *
 * This should be called at the end of a sensor hub session.  
 * The underlying SHTP and HAL instances will be closed.
 */
void sh2_close(void)
{
    sh2_t *pSh2 = &_sh2;
    
    shtp_close(pSh2->pShtp);

    // Clear everything in sh2 structure.
    memset(pSh2, 0, sizeof(sh2_t));
}

/**
 * @brief Service the SH2 device, reading any data that is available and dispatching callbacks.
 *
 * This function should be called periodically by the host system to service an open sensor hub.
 */
void sh2_service(void)
{
    sh2_t *pSh2 = &_sh2;
    
    shtp_service(pSh2->pShtp);
}

/**
 * @brief Register a function to receive sensor events.
 *
 * @param  callback A function that will be called each time a sensor event is received.
 * @param  cookie  A value that will be passed to the sensor callback function.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_setSensorCallback(sh2_SensorCallback_t *callback, void *cookie)
{
    sh2_t *pSh2 = &_sh2;
    
    pSh2->sensorCallback = callback;
    pSh2->sensorCookie = cookie;

    return SH2_OK;
}

/**
 * @brief Reset the sensor hub device by sending RESET (1) command on "device" channel.
 *
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_devReset(void)
{
    sh2_t *pSh2 = &_sh2;

    return sendExecutable(pSh2, EXECUTABLE_DEVICE_CMD_RESET);
}

/**
 * @brief Turn sensor hub on by sending ON (2) command on "device" channel.
 *
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_devOn(void)
{
    sh2_t *pSh2 = &_sh2;

    return sendExecutable(pSh2, EXECUTABLE_DEVICE_CMD_ON);
}

/**
 * @brief Put sensor hub in sleep state by sending SLEEP (3) command on "device" channel.
 *
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_devSleep(void)
{
    sh2_t *pSh2 = &_sh2;

    return sendExecutable(pSh2, EXECUTABLE_DEVICE_CMD_SLEEP);
}

/**
 * @brief Get Product ID information from Sensorhub.
 *
 * @param  prodIds Pointer to structure that will receive results.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_getProdIds(sh2_ProductIds_t *prodIds)
{
    sh2_t *pSh2 = &_sh2;
    
    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    pSh2->opData.getProdIds.pProdIds = prodIds;

    return opProcess(pSh2, &getProdIdOp);
}

/**
 * @brief Get sensor configuration.
 *
 * @param  sensorId Which sensor to query.
 * @param  config SensorConfig structure to store results.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_getSensorConfig(sh2_SensorId_t sensorId, sh2_SensorConfig_t *pConfig)
{
    sh2_t *pSh2 = &_sh2;
    
    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    // Set up operation
    pSh2->opData.getSensorConfig.sensorId = sensorId;
    pSh2->opData.getSensorConfig.pConfig = pConfig;

    return opProcess(pSh2, &getSensorConfigOp);
}

/**
 * @brief Set sensor configuration. (e.g enable a sensor at a particular rate.)
 *
 * @param  sensorId Which sensor to configure.
 * @param  pConfig Pointer to structure holding sensor configuration.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_setSensorConfig(sh2_SensorId_t sensorId, const sh2_SensorConfig_t *pConfig)
{
    sh2_t *pSh2 = &_sh2;
    
    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    // Set up operation
    pSh2->opData.setSensorConfig.sensorId = sensorId;
    pSh2->opData.setSensorConfig.pConfig = pConfig;

    return opProcess(pSh2, &setSensorConfigOp);
}

/**
 * @brief Get metadata related to a sensor.
 *
 * @param  sensorId Which sensor to query.
 * @param  pData Pointer to structure to receive the results.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_getMetadata(sh2_SensorId_t sensorId, sh2_SensorMetadata_t *pData)
{
    sh2_t *pSh2 = &_sh2;
    
    // pData must be non-null
    if (pData == 0) return SH2_ERR_BAD_PARAM;
  
    // Convert sensorId to metadata recordId
    unsigned i;
    for (i = 0; i < ARRAY_LEN(sensorToRecordMap); i++) {
        if (sensorToRecordMap[i].sensorId == sensorId) {
            break;
        }
    }
    if (i >= ARRAY_LEN(sensorToRecordMap)) {
        // no match was found
        return SH2_ERR_BAD_PARAM;
    }
    uint16_t recordId = sensorToRecordMap[i].recordId;
    
    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    // Set up an FRS read operation
    pSh2->opData.getFrs.frsType = recordId;
    pSh2->opData.getFrs.pData = pSh2->frsData;
    pSh2->frsDataLen = ARRAY_LEN(pSh2->frsData);
    pSh2->opData.getFrs.pWords = &pSh2->frsDataLen;

    // Read an FRS record
    int status = opProcess(pSh2, &getFrsOp);
    
    // Copy the results into pData
    if (status == SH2_OK) {
        stuffMetadata(pData, pSh2->frsData);
    }

    return status;
}

/**
 * @brief Get an FRS record.
 *
 * @param  recordId Which FRS Record to retrieve.
 * @param  pData pointer to buffer to receive the results
 * @param[in] words Size of pData buffer, in 32-bit words.
 * @param[out] words Number of 32-bit words retrieved.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_getFrs(uint16_t recordId, uint32_t *pData, uint16_t *words)
{
    sh2_t *pSh2 = &_sh2;
    
    if ((pData == 0) || (words == 0)) {
        return SH2_ERR_BAD_PARAM;
    }
    
    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    // Store params for this op
    pSh2->opData.getFrs.frsType = recordId;
    pSh2->opData.getFrs.pData = pData;
    pSh2->opData.getFrs.pWords = words;

    return opProcess(pSh2, &getFrsOp);
}

/**
 * @brief Set an FRS record
 *
 * @param  recordId Which FRS Record to set.
 * @param  pData pointer to buffer containing the new data.
 * @param  words number of 32-bit words to write.  (0 to delete record.)
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_setFrs(uint16_t recordId, uint32_t *pData, uint16_t words)
{
    sh2_t *pSh2 = &_sh2;
    
    if ((pData == 0) && (words != 0)) {
        return SH2_ERR_BAD_PARAM;
    }
    
    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    pSh2->opData.setFrs.frsType = recordId;
    pSh2->opData.setFrs.pData = pData;
    pSh2->opData.setFrs.words = words;

    return opProcess(pSh2, &setFrsOp);
}

/**
 * @brief Get error counts.
 *
 * @param  severity Only errors of this severity or greater are returned.
 * @param  pErrors Buffer to receive error codes.
 * @param  numErrors size of pErrors array
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_getErrors(uint8_t severity, sh2_ErrorRecord_t *pErrors, uint16_t *numErrors)
{
    sh2_t *pSh2 = &_sh2;
    
    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    pSh2->opData.getErrors.severity = severity;
    pSh2->opData.getErrors.pErrors = pErrors;
    pSh2->opData.getErrors.pNumErrors = numErrors;
    
    return opProcess(pSh2, &getErrorsOp);
}

/**
 * @brief Read counters related to a sensor.
 *
 * @param  sensorId Which sensor to operate on.
 * @param  pCounts Pointer to Counts structure that will receive data.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_getCounts(sh2_SensorId_t sensorId, sh2_Counts_t *pCounts)
{
    sh2_t *pSh2 = &_sh2;
    
    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    pSh2->opData.getCounts.sensorId = sensorId;
    pSh2->opData.getCounts.pCounts = pCounts;
    
    return opProcess(pSh2, &getCountsOp);
}

/**
 * @brief Clear counters related to a sensor.
 *
 * @param  sensorId which sensor to operate on.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_clearCounts(sh2_SensorId_t sensorId)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    
    pSh2->opData.sendCmd.req.command = SH2_CMD_COUNTS;
    pSh2->opData.sendCmd.req.p[0] = SH2_COUNTS_CLEAR_COUNTS;
    pSh2->opData.sendCmd.req.p[1] = sensorId;

    return opProcess(pSh2, &sendCmdOp);
}

/**
 * @brief Perform a tare operation on one or more axes.
 *
 * @param  axes Bit mask specifying which axes should be tared.
 * @param  basis Which rotation vector to use as the basis for Tare adjustment.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_setTareNow(uint8_t axes,    // SH2_TARE_X | SH2_TARE_Y | SH2_TARE_Z
                   sh2_TareBasis_t basis)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    
    pSh2->opData.sendCmd.req.command = SH2_CMD_TARE;
    pSh2->opData.sendCmd.req.p[0] = SH2_TARE_TARE_NOW;
    pSh2->opData.sendCmd.req.p[1] = axes;
    pSh2->opData.sendCmd.req.p[2] = basis;

    return opProcess(pSh2, &sendCmdOp);
}

/**
 * @brief Clears the previously applied tare operation.
 *
 * @return SH2_OK \n");
 */
int sh2_clearTare(void)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    
    pSh2->opData.sendCmd.req.command = SH2_CMD_TARE;
    pSh2->opData.sendCmd.req.p[0] = SH2_TARE_SET_REORIENTATION;

    return opProcess(pSh2, &sendCmdOp);
}

/**
 * @brief Persist the results of last tare operation to flash.
 *
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_persistTare(void)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    
    pSh2->opData.sendCmd.req.command = SH2_CMD_TARE;
    pSh2->opData.sendCmd.req.p[0] = SH2_TARE_PERSIST_TARE;

    return opProcess(pSh2, &sendCmdOp);
}

/**
 * @brief Set the current run-time sensor reorientation. (Set to zero to clear tare.)
 *
 * @param  orientation Quaternion rotation vector to apply as new tare.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_setReorientation(sh2_Quaternion_t *orientation)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    
    pSh2->opData.sendCmd.req.command = SH2_CMD_TARE;
    pSh2->opData.sendCmd.req.p[0] = SH2_TARE_SET_REORIENTATION;

    // save me a lot of typing and you a lot of reading
    uint8_t *p = pSh2->opData.sendCmd.req.p;

    // Put new orientation in command parameters
    writeu16(&p[1], toQ14(orientation->x));
    writeu16(&p[3], toQ14(orientation->y));
    writeu16(&p[5], toQ14(orientation->z));
    writeu16(&p[7], toQ14(orientation->w));

    return opProcess(pSh2, &sendCmdOp);
}

/**
 * @brief Command the sensorhub to reset.
 *
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_reinitialize(void)
{
    sh2_t *pSh2 = &_sh2;

    return opProcess(pSh2, &reinitOp);
}

/**
 * @brief Save Dynamic Calibration Data to flash.
 *
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_saveDcdNow(void)
{
    sh2_t *pSh2 = &_sh2;

    return opProcess(pSh2, &saveDcdNowOp);
}

/**
 * @brief Get Oscillator type.
 *
 * @param  pOscType pointer to data structure to receive results.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_getOscType(sh2_OscType_t *pOscType)
{
    sh2_t *pSh2 = &_sh2;

    pSh2->opData.getOscType.pOscType = pOscType;

    return opProcess(pSh2, &getOscTypeOp);
}

/**
 * @brief Enable/Disable dynamic calibration for certain sensors
 *
 * @param  sensors Bit mask to configure which sensors are affected.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_setCalConfig(uint8_t sensors)
{
    sh2_t *pSh2 = &_sh2;

    pSh2->opData.calConfig.sensors = sensors;

    return opProcess(pSh2, &setCalConfigOp);
}

/**
 * @brief Get dynamic calibration configuration settings.
 *
 * @param  pSensors pointer to Bit mask, set on return.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_getCalConfig(uint8_t *pSensors)
{
    sh2_t *pSh2 = &_sh2;

    pSh2->opData.getCalConfig.pSensors = pSensors;

    return opProcess(pSh2, &getCalConfigOp);
}

/**
 * @brief Configure automatic saving of dynamic calibration data.
 *
 * @param  enabled Enable or Disable DCD auto-save.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_setDcdAutoSave(bool enabled)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    pSh2->opData.sendCmd.req.command = SH2_CMD_DCD_SAVE;
    pSh2->opData.sendCmd.req.p[0] = enabled ? 0 : 1;

    return opProcess(pSh2, &sendCmdOp);
}

/**
 * @brief Immediately issue all buffered sensor reports from a given sensor.
 *
 * @param  sensorId Which sensor reports to flush.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_flush(sh2_SensorId_t sensorId)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    pSh2->opData.forceFlush.sensorId = sensorId;

    return opProcess(pSh2, &forceFlushOp);
}

/**
 * @brief Command clear DCD in RAM, then reset sensor hub.
 *
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_clearDcdAndReset(void)
{
    sh2_t *pSh2 = &_sh2;

    return opProcess(pSh2, &clearDcdAndResetOp);
}

/**
 * @brief Start simple self-calibration procedure.
 *
 * @parameter interval_us sensor report interval, uS.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_startCal(uint32_t interval_us)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    pSh2->opData.startCal.interval_us = interval_us;

    return opProcess(pSh2, &startCalOp);
}

/**
 * @brief Finish simple self-calibration procedure.
 *
 * @parameter status contains calibration status code on return.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_finishCal(sh2_CalStatus_t *status)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    
    int retval = opProcess(pSh2, &finishCalOp);
    if (status != NULL) {
        *status = pSh2->opData.finishCal.status;
    }

    return retval;
}

/**
 * @brief send Interactive ZRO Request.
 *
 * @parameter intent Inform the sensor hub what sort of motion should be in progress.
 * @return SH2_OK (0), on success.  Negative value from sh2_err.h on error.
 */
int sh2_setIZro(sh2_IZroMotionIntent_t intent)
{
    sh2_t *pSh2 = &_sh2;

    // clear opData
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));

    // set up opData for iZRO request
    pSh2->opData.sendCmd.req.command = SH2_CMD_INTERACTIVE_ZRO;
    pSh2->opData.sendCmd.req.p[0] = intent;

    // Send command
    return opProcess(pSh2, &sendCmdOp);
}


int sh2_reportWheelEncoder(uint8_t wheelIndex, uint32_t timestamp, int16_t wheelData, uint8_t dataType){
    sh2_t *pSh2 = &_sh2;
    //No callback (am i doing this right?)
    pSh2->pOp = 0;
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    pSh2->opData.wheelRequest.wheelIndex = wheelIndex;
    pSh2->opData.wheelRequest.timestamp = timestamp;
    pSh2->opData.wheelRequest.wheelData = wheelData;
    pSh2->opData.wheelRequest.dataType = dataType;
    int rc = opProcess(pSh2, &sendWheelOp);
    return rc;
}

int sh2_saveDeadReckoningCalNow(void){
    sh2_t *pSh2 = &_sh2;
    memset(&pSh2->opData, 0, sizeof(sh2_OpData_t));
    pSh2->opData.sendCmd.req.command = SH2_CMD_DR_CAL_SAVE;

    return opProcess(pSh2, &sendCmdOp);
}
