/**
 * @file    GoSensor.c
 *
 * @internal
 * Copyright (C) 2016-2022 by LMI Technologies Inc.
 * Licensed under the MIT License.
 * Redistributed files must retain the above copyright notice.
 */
#include <GoSdk/GoSensor.h>
#include <GoSdk/GoSystem.h>
#include <GoSdk/GoUtils.h>
#include <GoSdk/GoSdkDef.h>
#include <GoSdk/Messages/GoHealth.h>
#include <kApi/Io/kFile.h>
#include <kApi/Io/kPath.h>
#include <kApi/Utils/kUtils.h>
#include <kApi/Data/kImage.h>

kBeginClassEx(Go, GoSensor)
    kAddVMethod(GoSensor, kObject, VRelease)
kEndClassEx()

GoFx(kStatus) GoSensor_Construct(GoSensor* sensor, kPointer system, const GoDiscoveryInfo* discoveryInfo, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status;

    kCheck(kAlloc_GetObject(alloc, kTypeOf(GoSensor), sensor));
    if (!kSuccess(status = GoSensor_Init(*sensor, kTypeOf(GoSensor), system, discoveryInfo, alloc)))
    {
        kAlloc_FreeRef(alloc, sensor);
    }

    return status;
}

GoFx(kStatus) GoSensor_Init(GoSensor sensor, kType type, kPointer system, const GoDiscoveryInfo* discoveryInfo, kAlloc alloc)
{
    kObjR(GoSensor, sensor);
    kStatus status;


    kCheck(kObject_Init(sensor, type, alloc));
    obj->deviceId = 0;
    kZero(obj->protocolVersion);
    kZero(obj->discoveryHistory);
    obj->discoveryCount = 0;
    obj->healthCheckEnabled = kFALSE;
    kZero(obj->healthHistory);
    obj->healthCheckCount = 0;
    obj->healthMsgCount = 0;
    obj->previousHealthMsgCount = 0;
    obj->sensorInfoTime = k64U_NULL;
    kZero(obj->address);
    obj->role = GO_ROLE_INVALID;
    obj->buddyId = 0;
    obj->alignmentState = GO_ALIGNMENT_STATE_NOT_ALIGNED;
    obj->runState = GO_STATE_ONLINE;
    obj->user = GO_USER_NONE;
    obj->isResetting = kFALSE;
    obj->isCancelled = kFALSE;
    obj->isConnected = kFALSE;
    obj->isCompatible = kFALSE;
    obj->isUpgrading = kFALSE;
    kZero(obj->fileList);
    obj->fileListValid = kFALSE;
    kZero(obj->directoryList);
    obj->isDirectoryListValid = kFALSE;
    kZero(obj->partModelList);
    obj->isSyncPartModels = kFALSE;
    kZero(obj->configXml);
    kZero(obj->configXmlItem);
    obj->configValid = kFALSE;
    obj->configModified = kFALSE;
    obj->isSyncConfig = kFALSE;
    obj->isFlushConfig = kFALSE;
    kZero(obj->transformXml);
    kZero(obj->transformXmlItem);
    obj->transformValid = kFALSE;
    obj->transformModified = kFALSE;
    obj->isSyncTransform = kFALSE;
    kZero(obj->timer);
    obj->localSensorInfo = kNULL;
    kZero(obj->remoteSensorInfo);
    obj->infoValid = kFALSE;
    kZero(obj->buddySensorInfo);
    kZero(obj->resetTimer);
    obj->control = kNULL;
    obj->data = kNULL;
    obj->dataPort = GO_SENSOR_DATA_PORT;
    obj->onDataSet = kNULL;
    obj->onDataSetContext = kNULL;
    obj->health = kNULL;
    obj->healthPort = GO_SENSOR_HEALTH_PORT;
    obj->controlPort = GO_CONTROL_PORT_CONTROL;
    obj->upgradePort = GO_CONTROL_PORT_UPGRADE;
    obj->setup = kNULL;
    obj->tools = kNULL;
    obj->output = kNULL;
    obj->transform = kNULL;
    obj->replay = kNULL;
    kZero(obj->accelInfo);
    kZero(obj->discoveryInfo);

    obj->system = system;
    if (discoveryInfo != kNULL)
    {
        obj->deviceId = discoveryInfo->id;
        obj->address = discoveryInfo->address;
        obj->discoveryInfo.firmwareVersion = discoveryInfo->version;
    }

    kTry
    {
        kTest(GoSensorInfo_Construct(&obj->localSensorInfo, alloc));

        kTest(kArrayList_Construct(&obj->fileList, kTypeOf(kText64), 0, alloc));
        kTest(kArrayList_Construct(&obj->directoryList, kTypeOf(kText64), 0, alloc));
        kTest(kArrayList_Construct(&obj->partModelList, kTypeOf(GoPartModel), 0, alloc));
        kTest(kTimer_Construct(&obj->timer, alloc));
        kTest(kPeriodic_Construct(&obj->resetTimer, alloc));

        kTest(kArrayList_Construct(&obj->remoteSensorInfo, kTypeOf(GoSensorInfo), 0, alloc));
        kTest(kArrayList_Construct(&obj->buddySensorInfo, kTypeOf(GoBuddyInfo), 0, alloc));

        kTest(GoControl_Construct(&obj->control, alloc));
        if (discoveryInfo != kNULL)
        {
            kTest(GoControl_SetRemoteAddress(obj->control, discoveryInfo->address.address));
        }
        kTest(GoControl_SetCancelHandler(obj->control, GoSensor_OnCancelQuery, sensor));
    }
    kCatch(&status)
    {
        GoSensor_VRelease(sensor);
        kEndCatch(status);
    }

    return kOK;
}

GoFx(kStatus) GoSensor_VRelease(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_Disconnect(sensor));
    kCheck(kDestroyRef(&obj->control));

    kCheck(kPeriodic_Stop(obj->resetTimer));
    kCheck(kDestroyRef(&obj->resetTimer));
    kCheck(kDisposeRef(&obj->remoteSensorInfo));
    kCheck(kDisposeRef(&obj->buddySensorInfo));
    kCheck(kDestroyRef(&obj->localSensorInfo));
    kCheck(kDisposeRef(&obj->partModelList));
    kCheck(kDisposeRef(&obj->directoryList));
    kCheck(kDisposeRef(&obj->fileList));
    kCheck(kDestroyRef(&obj->timer));

    kCheck(kDestroyRef(&obj->configXml));
    kCheck(kDestroyRef(&obj->transformXml));

    kCheck(kObject_VRelease(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_SetAddress(GoSensor sensor, const GoAddressInfo* info, kBool wait)
{
    kObj(GoSensor, sensor);
    GoDiscovery discovery = kNULL;
    kBool wasConnected = GoSensor_IsConnected(sensor);

    kTry
    {
        if (wasConnected)
        {
            kTest(GoSensor_Flush(sensor));
        }

        kTest(GoSensor_BeginReset(sensor));
        kTest(GoSensor_Disconnect(sensor));

        kTest(GoDiscovery_Construct(&discovery, kTRUE, kObject_Alloc(sensor)));
        kTest(GoDiscovery_SetAddress(discovery, obj->deviceId, info));

        if (wait)
        {
            if (wasConnected)
            {
                kTest(GoSensor_WaitForReconnect(sensor, GO_SYSTEM_RESET_TIMEOUT));
            }
            else
            {
                kTest(GoSensor_WaitForReboot(sensor, GO_SYSTEM_RESET_TIMEOUT));
            }
        }
    }
    kFinally
    {
        kDestroyRef(&discovery);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_Address(GoSensor sensor, GoAddressInfo* info)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_LockState(sensor));
    {
        *info = obj->address;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_StartConnection(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kAlloc alloc = kObject_Alloc(sensor);
    kBool isCompatible = kFALSE;
    kStatus status = kERROR;

    kCheck(GoSensor_Disconnect(sensor));

    kCheckState(GoSensor_State(sensor) == GO_STATE_ONLINE);

    kTry
    {
        kTest(GoControl_Open(obj->control, obj->address.address, obj->controlPort, obj->upgradePort));

        isCompatible = GoControl_IsCompatible(obj->control);

        kTest(GoSensor_SetConnected(sensor, kTRUE, isCompatible));

        if (isCompatible)
        {
            kTest(GoSensor_EnableHealth(sensor, kTRUE));
        }

        kTest(GoSetup_Construct(&obj->setup, sensor, alloc));
        kTest(GoTools_Construct(&obj->tools, sensor, alloc));
        kTest(GoOutput_Construct(&obj->output, sensor, alloc));
        kTest(GoTransform_Construct(&obj->transform, sensor, alloc));
        kTest(GoReplay_Construct(&obj->replay, sensor, alloc));
    }
    kCatch(&status)
    {
        kCheck(GoSensor_SetConnected(sensor, kFALSE, isCompatible));
        kEndCatch(status);
    }
    return kOK;
}

GoFx(kStatus) GoSensor_CompleteConnection(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kBool isCompatible = kFALSE;
    kStatus status = kERROR;

    isCompatible = GoControl_IsCompatible(obj->control);

    kTry
    {
        if (isCompatible)
        {
            kTest(GoSensor_ReadInfo(sensor));
            obj->deviceId = GoSensorInfo_Id(obj->localSensorInfo);
            kTest(GoSensor_ReadConfig(sensor));
            kTest(GoSensor_ReadPartModels(sensor));
            kTest(GoSensor_ReadTransform(sensor));
            kTest(GoSensor_SetTimeDate(sensor));
        }
    }
    kCatch(&status)
    {
        kCheck(GoSensor_SetConnected(sensor, kFALSE, isCompatible));
        kEndCatch(status);
    }
    return kOK;
}

GoFx(kStatus) GoSensor_Connect(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kStatus status = kERROR;
    GoStates states;

    kTry
    {
        kTest(GoSensor_StartConnection(sensor));

        kTest(GoSensor_States(sensor, &states));

        if (GO_SECURITY_NONE != states.security && GO_USER_NONE == states.loginType)
        {
            kTest(GO_ERROR_AUTHENTICATION);
        }

        kTest(GoSensor_CompleteConnection(sensor));
    }
    kCatch(&status)
    {
        GoSensor_Disconnect(sensor);
        kEndCatch(status);
    }

    return kOK;
}

GoFx(kStatus) GoSensor_ConnectAndLogin(GoSensor sensor, GoUser user, const kChar* password)
{
    kObj(GoSensor, sensor);
    kStatus status = kERROR;

    kTry
    {
        kTest(GoSensor_StartConnection(sensor));

        kTest(GoControl_Login(obj->control, user, password));

        kTest(GoSensor_CompleteConnection(sensor));

        kTest(GoSensor_InvalidateInfo(sensor));
    }
    kCatch(&status)
    {
        GoSensor_Disconnect(sensor);
        kEndCatch(status);
    }

    return kOK;
}

GoFx(kStatus) GoSensor_Disconnect(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (GoSensor_IsConnected(sensor) && GoSensor_IsConfigurable(sensor))
    {
        GoSensor_Flush(sensor);
    }

    kTry
    {
        kTest(GoSensor_Cancel(sensor));
        kTest(GoControl_Close(obj->control));
    }
    kFinally
    {
        kCheck(kDisposeRef(&obj->replay));
        kCheck(kDisposeRef(&obj->transform));
        kCheck(kDisposeRef(&obj->output));
        kCheck(kDisposeRef(&obj->tools));
        kCheck(kDisposeRef(&obj->setup));
        kCheck(kDisposeRef(&obj->data));
        kCheck(kDisposeRef(&obj->health));

        kCheck(GoSensor_SetConnected(sensor, kFALSE, kFALSE));
        kCheck(GoSensor_Invalidate(sensor));

        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_SetConnected(GoSensor sensor, kBool isConnected, kBool isCompatible)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_LockState(sensor));
    {
        obj->isConnected = isConnected;
        obj->isCompatible = isCompatible;

        if (!isConnected)
        {
            obj->isCancelled = kFALSE;
        }
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_Flush(GoSensor sensor)
{
    kCheck(GoSensor_FlushConfig(sensor));
    kCheck(GoSensor_FlushTransform(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_Invalidate(GoSensor sensor)
{
    kCheck(GoSensor_InvalidateRole(sensor));
    kCheck(GoSensor_InvalidateInfo(sensor));
    kCheck(GoSensor_InvalidateConfig(sensor));
    kCheck(GoSensor_InvalidateTransform(sensor));
    kCheck(GoSensor_InvalidateFileList(sensor));
    kCheck(GoSensor_InvalidateDirectoryList(sensor));
    kCheck(GoSensor_InvalidateBuddyId(sensor));
    kCheck(GoSensor_InvalidateBuddyInfoList(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_SyncConfig(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!obj->isSyncConfig && !obj->isFlushConfig)
    {
        kTry
        {
            obj->isSyncConfig = kTRUE;
            kTest(GoSensor_FlushConfig(sensor));
            kTest(GoSensor_CacheConfig(sensor));
        }
        kFinally
        {
            obj->isSyncConfig = kFALSE;
            kEndFinally();
        }
    }

    return kOK;
}

GoFx(kStatus) GoSensor_SyncPartModels(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!obj->isSyncPartModels)
    {
        kTry
        {
            obj->isSyncPartModels = kTRUE;
            kTest(GoSensor_FlushPartModels(sensor));
            kTest(GoSensor_CachePartModels(sensor));
        }
        kFinally
        {
            obj->isSyncPartModels = kFALSE;
            kEndFinally();
        }
    }

    return kOK;
}

GoFx(kStatus) GoSensor_FlushPartModels(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kSize i;

    for (i = 0; i < kArrayList_Count(obj->partModelList); i++)
    {
        kObjN(GoPartModel, modelObj, *kArrayList_AtT(obj->partModelList, i, GoPartModel));

        if (modelObj->isModified)
        {
            kCheck(GoSensor_WritePartModel(sensor, modelObj));
        }
    }

    return kOK;
}

GoFx(kStatus) GoSensor_CachePartModels(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kSize i, j;
    kText64 tempText = "";
    kStatus exception = kOK;

    // Remove part models that are no longer present in the live job
    for (i = 0; i < kArrayList_Count(obj->partModelList); )
    {
        kObjN(GoPartModel, modelObj, *kArrayList_AtT(obj->partModelList, i, GoPartModel));
        kBool foundFile = kFALSE;
        GoPartModel partModel = kNULL;

        for (j = 0; j < GoSensor_DirectoryFileCount(sensor, "mdl", GO_SENSOR_LIVE_JOB_NAME, kTRUE); j++)
        {
            kCheck(GoSensor_DirectoryFileNameAt(sensor, "mdl", GO_SENSOR_LIVE_JOB_NAME, kTRUE, j, tempText, 64));
            strtok(tempText, ".");

            if (strcmp(tempText, modelObj->name) == 0)
            {
                foundFile = kTRUE;
            }
        }

        if (!foundFile)
        {
            kCheck(kArrayList_RemoveT(obj->partModelList, i, &partModel));
            kDestroyRef(&partModel);
        }
        else
        {
            i++;
        }
    }

    // Add or update part models in the live job
    for (i = 0; i < GoSensor_DirectoryFileCount(sensor, "mdl", GO_SENSOR_LIVE_JOB_NAME, kTRUE); i++)
    {
        kBool foundModel = kFALSE;

        kCheck(GoSensor_DirectoryFileNameAt(sensor, "mdl", GO_SENSOR_LIVE_JOB_NAME, kTRUE, i, tempText, 64));
        strtok(tempText, "."); //stripping out .mdl extension

        for (j = 0; j < kArrayList_Count(obj->partModelList); j++)
        {
            kObjN(GoPartModel, modelObj, *kArrayList_AtT(obj->partModelList, j, GoPartModel));

            if (strcmp(tempText, modelObj->name) == 0)
            {
                foundModel = kTRUE;

                if (!modelObj->isValid)
                {
                    kCheck(GoSensor_ReadPartModel(sensor, modelObj));
                    break;
                }
            }
        }

        if (!foundModel)
        {
            GoPartModel modelToAdd = kNULL;

            kTry
            {
                kTest(GoPartModel_Construct(&modelToAdd, sensor, tempText, kObject_Alloc(sensor)));
                kTest(GoSensor_ReadPartModel(sensor, modelToAdd));
                kTest(kArrayList_AddT(obj->partModelList, &modelToAdd));
            }
            kCatch(&exception)
            {
                kDestroyRef(&modelToAdd);
                kEndCatch(exception);
            }
        }
    }

    return kOK;
}

GoFx(kStatus) GoSensor_InvalidatePartModels(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kSize i;

    for (i = 0; i < kArrayList_Count(obj->partModelList); i++)
    {
        kObjNR(GoPartModel, modelObj, *kArrayList_AtT(obj->partModelList, i, GoPartModel));

        modelObj->isValid = kFALSE;
    }


    return kOK;
}

GoFx(kStatus) GoSensor_GetLivePartModel(GoSensor sensor, const kChar* name, kXml* xml, kAlloc allocator)
{
    kObj(GoSensor, sensor);
    kAlloc tempAlloc = kObject_Alloc(sensor);
    kByte* fileData = kNULL;
    kSize fileSize = 0;
    kText256 configurationPath = "";

    kCheckState(GoSensor_IsReadable(sensor));

    kTry
    {
        kStrCopy(configurationPath, 256, "_live.job/");
        kStrCat(configurationPath, 256, name);
        kStrCat(configurationPath, 256, ".mdl/config.xml");
        kTest(GoControl_ReadFile(obj->control, configurationPath, &fileData, &fileSize, tempAlloc));

        // Uncomment to save config to file (useful for debugging config problems)
        //kTest(kFile_Save("GoSensor_GetLivePartModel-Debug.cfg", fileData, fileSize));

        kTest(kXml_LoadBytes(xml, fileData, fileSize, allocator));
    }
    kFinally
    {
        kAlloc_Free(tempAlloc, fileData);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_SetLivePartModel(GoSensor sensor, const kChar* name, kXml xml)
{
    kObj(GoSensor, sensor);
    kAlloc alloc = kObject_Alloc(sensor);
    kByte* fileData = kNULL;
    kSize fileSize = 0;
    kText256 configurationPath = "";

    kCheckState(GoSensor_IsConfigurable(sensor));

    kTry
    {
        kTest(kXml_SaveBytes(xml, &fileData, &fileSize, alloc));

        // Uncomment to save config to file (useful for debugging config problems)
        //kTest(kFile_Save("GoSensor_SetLivePartModel-Debug.xml", fileData, fileSize));

        kStrCopy(configurationPath, 256, "_live.job/");
        kStrCat(configurationPath, 256, name);
        kStrCat(configurationPath, 256, ".mdl/config.xml");
        kTest(GoControl_WriteFile(obj->control, configurationPath, fileData, fileSize));
    }
    kFinally
    {
        kAlloc_Free(alloc, fileData);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_ReadPartModels(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kSize i;

    for (i = 0; i < kArrayList_Count(obj->partModelList); i++)
    {
        GoPartModel model = *kArrayList_AtT(obj->partModelList, i, GoPartModel);

        kCheck(GoSensor_ReadPartModel(sensor, model));
    }

    return kOK;
}

GoFx(kStatus) GoSensor_ReadPartModel(GoSensor sensor, GoPartModel model)
{
    kObj(GoSensor, sensor);
    kXml xml = kNULL;
    kXml root = kNULL;

    kCheck(GoSensor_GetLivePartModel(sensor, GoPartModel_Name(model), &xml, kObject_Alloc(sensor)));
    kCheck(!kIsNull(root = kXml_Root(xml)));

    kCheck(GoPartModel_Read(model, xml, root));

    return kOK;
}

GoFx(kStatus) GoSensor_WritePartModel(GoSensor sensor, GoPartModel model)
{
    kObj(GoSensor, sensor);
    kSize i;
    kText64 tempName = "";
    kXml xml = kNULL;
    kXmlItem root = kNULL;
    kByte* fileData = kNULL;
    kSize fileSize = 0;
    kText128 modelConfigString = "";

    kCheckState(GoSensor_IsConfigurable(sensor));

    for (i = 0; i < GoSensor_DirectoryFileCount(sensor, "mdl", GO_SENSOR_LIVE_JOB_NAME, kTRUE); i++)
    {
        kCheck(GoSensor_DirectoryFileNameAt(sensor, "mdl", GO_SENSOR_LIVE_JOB_NAME, kTRUE, i, tempName, 64));
        strtok(tempName, ".");

        if (strcmp(tempName, GoPartModel_Name(model)) == 0)
        {
            kTry
            {
                kObjN(GoPartModel, modelObj, model);

                kTest(kXml_Construct(&xml, kObject_Alloc(sensor)));
                kTest(kXml_AddItem(xml, kXml_Root(xml), "Configuration", &root));
                kTest(kXml_SetAttr32u(xml, root, "version", GO_PART_MODEL_CONFIG_VERSION));
                kTest(GoPartModel_Write(model, xml, root));
                kTest(kXml_SaveBytes(xml, &fileData, &fileSize, kObject_Alloc(sensor)));

                // Uncomment to save config to file (useful for debugging config problems)
                //kTest(kFile_Save("GoSensor_SetModel-Debug.xml", fileData, fileSize));

                kTest(kStrCat(modelConfigString, 256, "_live.job/"));
                kTest(kStrCat(modelConfigString, 256, GoPartModel_Name(model)));
                kTest(kStrCat(modelConfigString, 256, ".mdl/config.xml"));
                kTest(GoControl_WriteFile(obj->control, modelConfigString, fileData, fileSize));

                modelObj->isModified = kFALSE;
                modelObj->isValid = kFALSE;
            }
            kFinally
            {
                kDestroyRef(&xml);
                kAlloc_Free(kObject_Alloc(sensor), fileData);
                kEndFinally();
            }

            return kOK;
        }
    }

    //see if the model exists
    //if exists, set the live model

    return kERROR_NOT_FOUND;
}

GoFx(kSize) GoSensor_PartMatchModelCount(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!GoSensor_IsReadable(sensor))
    {
        return kSIZE_NULL;
    }

    if (!kSuccess(GoSensor_SyncPartModels(sensor)))
    {
        return 0;
    }

    return kArrayList_Count(obj->partModelList);
}

GoFx(GoPartModel) GoSensor_PartMatchModelAt(GoSensor sensor, kSize index)
{
    kObj(GoSensor, sensor);
    GoPartModel model = kNULL;

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoSensor_SyncPartModels(sensor))
        && index < kArrayList_Count(obj->partModelList))
    {
        model = *kArrayList_AtT(obj->partModelList, index, GoPartModel);
    }

    return model;
}

GoFx(GoPartModel) GoSensor_PartMatchModel(GoSensor sensor, const kChar* name)
{
    kObj(GoSensor, sensor);
    GoPartModel model = kNULL;
    kSize i;

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoSensor_SyncPartModels(sensor)))
    {
        for (i = 0; i < kArrayList_Count(obj->partModelList); i++)
        {
            GoPartModel partModel = *kArrayList_AtT(obj->partModelList, i, GoPartModel);

            if (strcmp(name, GoPartModel_Name(partModel)) == 0)
            {
                model = partModel;
            }
        }
    }

    return model;
}

GoFx(kStatus) GoSensor_FlushConfig(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (obj->configModified && !obj->isFlushConfig)
    {
        kTry
        {
            obj->isFlushConfig = kTRUE;
            kTest(GoSensor_WriteConfig(sensor));
        }
        kFinally
        {
            obj->isFlushConfig = kFALSE;
            kEndFinally();
        }
        obj->configModified = kFALSE;
        obj->configValid = kFALSE;
    }

    return kOK;
}

GoFx(kStatus) GoSensor_CacheConfig(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!obj->configValid)
    {
        kCheck(GoSensor_ReadConfig(sensor));
        obj->configModified = kFALSE;
        obj->configValid = kTRUE;
    }

    return kOK;
}

GoFx(kStatus) GoSensor_InvalidateConfig(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    obj->configValid = kFALSE;

    return kOK;
}

GoFx(kBool) GoSensor_ConfigValid(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->configValid;
}

GoFx(kStatus) GoSensor_SetConfigModified(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    obj->configModified = kTRUE;

    return kOK;
}

GoFx(kBool) GoSensor_ConfigModified(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->configModified;
}

GoFx(kStatus) GoSensor_ReadConfig(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kXml xml = kNULL;
    kXml root = kNULL;
    k32u version;

    kCheck(GoSensor_GetLiveConfig(sensor, &xml, kObject_Alloc(sensor)));
    kCheck(!kIsNull(root = kXml_Root(xml)));

    kCheck(kDestroyRef(&obj->configXml));
    obj->configXml = xml;
    obj->configXmlItem = root;

    kCheck(kXml_Attr32u(xml, root, "version", &version));
    if (version != GO_SENSOR_CONFIG_SCHEMA_VERSION)
    {
        return kERROR_VERSION;
    }

    kCheck(GoSetup_ReadConfig(obj->setup, xml, kXml_Child(xml, root, "Setup")));

    // Don't read the "Configuration/Streams" section. This section
    // is only used by the GUI client.

    kCheck(GoTools_Read(obj->tools, xml, kXml_Child(xml, root, "Tools"), kXml_Child(xml, root, "ToolOptions")));
    kCheck(GoOutput_Read(obj->output, xml, kXml_Child(xml, root, "Output")));

    if (kXml_ChildExists(xml, root, "Replay"))
    {
        kCheck(GoReplay_Read(obj->replay, xml, kXml_Child(xml, root, "Replay")));
    }

    return kOK;
}

GoFx(kStatus) GoSensor_WriteConfig(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kXml xml = kNULL;
    kXml root = kNULL;
    kXml item = kNULL;

    kCheckState(GoSensor_IsConfigurable(sensor));

    kTry
    {
        kTest(kXml_Construct(&xml, kObject_Alloc(sensor)));

        kTest(kXml_AddItem(xml, kNULL, "Configuration", &root));
        kTest(kXml_SetAttr32u(xml, root, "version", GO_SENSOR_CONFIG_SCHEMA_VERSION));

        kTest(kXml_AddItem(xml, root, "Setup", &item));
        kTest(GoSetup_WriteConfig(obj->setup, xml, item));

        // The "Configuration/Streams" section is sent only from sensor
        // to client, never from client to sensor. So don't write that
        // section.

        kTest(kXml_AddItem(xml, root, "Tools", &item));
        kTest(GoTools_Write(obj->tools, xml, item));

        kTest(kXml_AddItem(xml, root, "Output", &item));
        kTest(GoOutput_Write(obj->output, xml, item));

        kTest(kXml_AddItem(xml, root, "Replay", &item));
        kTest(GoReplay_Write(obj->replay, xml, item));

        kTest(GoUtils_XmlMerge(obj->configXml, obj->configXmlItem, xml, root));

        kTest(GoSensor_SetLiveConfig(sensor, xml));
    }
    kFinally
    {
        kCheck(kDestroyRef(&xml));
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_GetLiveConfig(GoSensor sensor, kXml* xml, kAlloc allocator)
{
    kObj(GoSensor, sensor);
    kAlloc tempAlloc = kObject_Alloc(sensor);
    kByte* fileData = kNULL;
    kSize fileSize = 0;

    kCheckState(GoSensor_IsReadable(sensor));

    kTry
    {
        kTest(GoControl_ReadFile(obj->control, GO_SENSOR_LIVE_CONFIG_NAME, &fileData, &fileSize, tempAlloc));

        // Uncomment to save config to file (useful for debugging config problems)
        //kTest(kFile_Save("GoSensor_GetLiveConfig-Debug.cfg", fileData, fileSize));

        kTest(kXml_LoadBytes(xml, fileData, fileSize, allocator));
    }
    kFinally
    {
        kAlloc_Free(tempAlloc, fileData);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_SetLiveConfig(GoSensor sensor, kXml xml)
{
    kObj(GoSensor, sensor);
    kAlloc alloc = kObject_Alloc(sensor);
    kByte* fileData = kNULL;
    kSize fileSize = 0;

    kCheckState(GoSensor_IsConfigurable(sensor));

    kTry
    {
        kTest(kXml_SaveBytes(xml, &fileData, &fileSize, alloc));

        // Uncomment to save config to file (useful for debugging config problems)
        //kTest(kFile_Save("GoSensor_SetLiveConfig-Debug.cfg", fileData, fileSize));

        kTest(GoControl_WriteFile(obj->control, GO_SENSOR_LIVE_CONFIG_NAME, fileData, fileSize));
    }
    kFinally
    {
        kAlloc_Free(alloc, fileData);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_SyncTransform(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!obj->isSyncTransform)
    {
        kTry
        {
            obj->isSyncTransform = kTRUE;
            kTest(GoSensor_FlushTransform(sensor));
            kTest(GoSensor_CacheTransform(sensor));
        }
        kFinally
        {
            obj->isSyncTransform = kFALSE;
            kEndFinally();
        }
    }

    return kOK;
}

GoFx(kStatus) GoSensor_FlushTransform(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (obj->transformModified)
    {
        kCheck(GoSensor_WriteTransform(sensor));
        obj->transformModified = kFALSE;
        obj->transformValid = kFALSE;
    }

    return kOK;
}

GoFx(kStatus) GoSensor_CacheTransform(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!obj->transformValid)
    {
        kCheck(GoSensor_ReadTransform(sensor));
        obj->transformModified = kFALSE;
        obj->transformValid = kTRUE;
    }

    return kOK;
}

GoFx(kStatus) GoSensor_InvalidateTransform(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    obj->transformValid = kFALSE;

    return kOK;
}

GoFx(kBool) GoSensor_TransformValid(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->transformValid;
}

GoFx(kStatus) GoSensor_SetTransformModified(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    obj->transformModified = kTRUE;

    return kOK;
}

GoFx(kBool) GoSensor_TransformModified(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->transformModified;
}

GoFx(kStatus) GoSensor_ReadTransform(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kXml xml = kNULL;
    kXml root = kNULL;
    k32u version;

    kCheck(GoSensor_GetLiveTransform(sensor, &xml, kObject_Alloc(sensor)));
    kCheck(!kIsNull(root = kXml_Root(xml)));

    kCheck(kXml_Attr32u(xml, root, "version", &version));
    if (version != GO_SENSOR_TRANSFORM_SCHEMA_VERSION)
    {
        return kERROR_VERSION;
    }

    kCheck(GoTransform_Read(obj->transform, xml, root));

    return kOK;
}

GoFx(kStatus) GoSensor_WriteTransform(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kXml xml = kNULL;
    kXml root = kNULL;

    kCheckState(GoSensor_IsConfigurable(sensor));

    kTry
    {
        kTest(kXml_Construct(&xml, kObject_Alloc(sensor)));

        kTest(kXml_AddItem(xml, kNULL, "Transform", &root));
        kTest(kXml_SetAttr32u(xml, root, "version", GO_SENSOR_TRANSFORM_SCHEMA_VERSION));

        kTest(GoTransform_Write(obj->transform, xml, root));

        kTest(GoSensor_SetLiveTransform(sensor, xml));
    }
    kFinally
    {
        kDestroyRef(&xml);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_GetLiveTransform(GoSensor sensor, kXml* xml, kAlloc allocator)
{
    kObj(GoSensor, sensor);
    kAlloc tempAlloc = kObject_Alloc(sensor);
    kByte* fileData = kNULL;
    kSize fileSize = 0;

    kCheckState(GoSensor_IsReadable(sensor));

    kTry
    {
        kTest(GoControl_ReadFile(obj->control, GO_SENSOR_LIVE_TRANSFORM_NAME, &fileData, &fileSize, tempAlloc));

        // Uncomment to save transform to file (useful for debugging transform problems)
        //kTest(kFile_Save("GoSensor_GetLiveTransform-Debug.tfm", fileData, fileSize));

        kTest(kXml_LoadBytes(xml, fileData, fileSize, allocator));
    }
    kFinally
    {
        kAlloc_Free(tempAlloc, fileData);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_SetLiveTransform(GoSensor sensor, kXml xml)
{
    kObj(GoSensor, sensor);
    kAlloc alloc = kObject_Alloc(sensor);
    kByte* fileData = kNULL;
    kSize fileSize = 0;

    kCheckState(GoSensor_IsConfigurable(sensor));

    kTry
    {
        kTest(kXml_SaveBytes(xml, &fileData, &fileSize, alloc));

        // Uncomment to save transform to file (useful for debugging transform problems)
        //kTest(kFile_Save("GoSensor_SetLiveTransform-Debug.cfg", fileData, fileSize));

        kTest(GoControl_WriteFile(obj->control, GO_SENSOR_LIVE_TRANSFORM_NAME, fileData, fileSize));
    }
    kFinally
    {
        kAlloc_Free(alloc, fileData);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_CacheFileList(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!obj->fileListValid)
    {
        kCheck(GoSensor_ReadFileList(sensor));
        obj->fileListValid = kTRUE;
    }

    return kOK;
}

GoFx(kStatus) GoSensor_InvalidateFileList(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    obj->fileListValid = kFALSE;

    return kOK;
}

GoFx(kBool) GoSensor_FileListValid(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->fileListValid;
}

GoFx(kStatus) GoSensor_ReadFileList(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReadable(sensor));

    kCheck(GoControl_ReadFileList(obj->control, obj->fileList, kNULL));

    return kOK;
}

GoFx(kStatus) GoSensor_ClearLog(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReadable(sensor));

    kCheck(GoControl_ClearLog(obj->control));

    return kOK;
}

GoFx(kStatus) GoSensor_CacheInfo(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!obj->infoValid)
    {
        kCheck(GoSensor_ReadInfo(sensor));
        obj->infoValid = kTRUE;
    }

    return kOK;
}

GoFx(kStatus) GoSensor_InvalidateInfo(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    obj->infoValid = kFALSE;

    kCheck(GoSensor_LockState(sensor));
    {
        obj->sensorInfoTime = k64U_NULL;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kBool) GoSensor_InfoValid(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->infoValid;
}

GoFx(kStatus) GoSensor_ReadInfo(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    GoState state = GoSensor_KnownState(sensor);        //avoids communication
    k64u now = kTimer_Now();
    GoStates states;
    kVersion sensorInfoVersion = kVersion_Create(101, 9, 0, 0);
    kVersion currentSensorInfoVersion;

    kCheckState(GoState_IsResponsive(state));


    kCheck(GoControl_GetProtocolVersion(obj->control, &currentSensorInfoVersion));

    // Determine which method to use to get system info.
    if (kVersion_Compare(sensorInfoVersion, currentSensorInfoVersion) > 0)
    {
        kCheck(GoControl_GetSensorInfo(obj->control, obj->localSensorInfo, obj->remoteSensorInfo));
    }
    else
    {
        kCheck(GoControl_GetSensorInfoV2(obj->control, obj->localSensorInfo, obj->remoteSensorInfo, obj->buddySensorInfo));
    }

    if (kArrayList_Count(obj->buddySensorInfo) > 0 )
    {
        GoBuddyInfo* buddyInfo = kArrayList_AtT(obj->buddySensorInfo, 0, GoBuddyInfo);
        kObjN(GoSensorInfo, tempObj, obj->localSensorInfo);
        tempObj->buddyId = buddyInfo->id;
    }

    kCheck(GoControl_GetStates(obj->control, &states));

    kCheck(GoSensor_LockState(sensor));

    kTry
    {
        obj->user = states.loginType;
        obj->runState = GoSensorInfo_State(obj->localSensorInfo);
        obj->alignmentState = states.alignmentState;

        if (obj->role == GO_ROLE_INVALID)
        {
            obj->role = GoSensorInfo_Role(obj->localSensorInfo);
            obj->buddyId = GoSensorInfo_HasBuddy(obj->localSensorInfo) ? GoSensorInfo_BuddyId(obj->localSensorInfo) : k32U_NULL;
        }

        obj->sensorInfoTime = now;
    }
    kFinally
    {
        kCheck(GoSensor_UnlockState(sensor));
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_InvalidateRole(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_LockState(sensor));
    {
        obj->role = GO_ROLE_INVALID;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_InvalidateBuddyId(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kObjN(GoSensorInfo, tempObj, obj->localSensorInfo);

    kCheck(GoSensor_LockState(sensor));
    {
        obj->buddyId = 0;
        tempObj->buddyId = 0;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_InvalidateBuddyInfoList(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_LockState(sensor));
    {
        kArrayList_Clear(obj->buddySensorInfo);
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_Refresh(GoSensor sensor)
{
    GoState state = GoSensor_State(sensor);

    if (state == GO_STATE_CANCELLED)
    {
        kCheck(GoSensor_Connect(sensor));
    }
    else if (state == GO_STATE_UNRESPONSIVE)
    {
        kCheck(GoSensor_Disconnect(sensor));
    }
    else
    {
        kCheck(GoSensor_Invalidate(sensor));
    }

    return kOK;
}

GoFx(kStatus) GoSensor_RefreshSystem(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    GoSystem system = kNULL;

    if (kIsNull(obj->system)) { return kOK; } // no-op

    system = (GoSystem)obj->system;
    return GoSystem_Refresh(system);
}

GoFx(kStatus) GoSensor_CheckHealth(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (obj->healthCheckEnabled)
    {
        kSize historyMask = kCountOf(obj->healthHistory) - 1;
        k64u diff = obj->healthMsgCount - obj->previousHealthMsgCount;

        obj->healthHistory[obj->healthCheckCount & historyMask] = (diff > 0);
        obj->healthCheckCount++;

        obj->previousHealthMsgCount = obj->healthMsgCount;
    }

    return kOK;
}

GoFx(GoState) GoSensor_KnownState(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    GoState state;

    kCheck(GoSensor_LockState(sensor));
    {
        if (obj->isCancelled)
        {
            state = GO_STATE_CANCELLED;
        }
        else if (obj->isUpgrading)
        {
            state = GO_STATE_UPGRADING;
        }
        else if (obj->isResetting)
        {
            state = GO_STATE_RESETTING;
        }
        else if (!obj->isConnected)
        {
            state = GoSensor_IsDiscoveryOnline(sensor) ? GO_STATE_ONLINE : GO_STATE_OFFLINE;
        }
        else if (!GoSensor_IsDiscoveryOnline(sensor) && !GoSensor_IsHealthOnline(sensor))
        {
            state = GO_STATE_UNRESPONSIVE;
        }
        else if (!obj->isCompatible)
        {
            state = GO_STATE_INCOMPATIBLE;
        }
        else if ((obj->sensorInfoTime != k64U_NULL) && (obj->role == GO_ROLE_BUDDY))
        {
            state = GO_STATE_BUSY;
        }
        else if (obj->sensorInfoTime != k64U_NULL)
        {
            state = obj->runState;
        }
        else
        {
            state = GO_STATE_CONNECTED;
        }
    }
    kCheck(GoSensor_UnlockState(sensor));

    return state;
}

GoFx(kBool) GoSensor_IsDiscoveryOnline(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kSize discoveryMask = kCountOf(obj->discoveryHistory) - 1;
    kSize discoveryTotal = (kSize) obj->discoveryCount;
    kSize discoveryCount = (kSize) kMin_(obj->discoveryCount, GO_SENSOR_DISCOVERY_CHECK_COUNT);
    kBool discoveryOnline = (discoveryCount < GO_SENSOR_DISCOVERY_CHECK_COUNT);
    kSize i;

    for (i = 0; i < discoveryCount; ++i)
    {
        discoveryOnline = discoveryOnline || obj->discoveryHistory[(discoveryTotal-i-1) & discoveryMask];
    }

    return discoveryOnline;
}

GoFx(kBool) GoSensor_IsHealthOnline(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kSize healthMask = kCountOf(obj->healthHistory) - 1;
    kSize healthTotal = (kSize) obj->healthCheckCount;
    kSize healthCount = (kSize) kMin_(obj->healthCheckCount, GO_SENSOR_HEALTH_CHECK_COUNT);
    kBool healthOnline = (healthCount < GO_SENSOR_HEALTH_CHECK_COUNT);
    kSize i;

    for (i = 0; i < healthCount; ++i)
    {
        healthOnline = healthOnline || obj->healthHistory[(healthTotal-i-1) & healthMask];
    }

    return healthOnline;
}

GoFx(kBool) GoSensor_IsConnected(GoSensor sensor)
{
    GoState state = GoSensor_KnownState(sensor);

    return GoState_IsConnected(state);
}

GoFx(kBool) GoSensor_IsResponsive(GoSensor sensor)
{
    GoState state = GoSensor_KnownState(sensor);

    return GoState_IsResponsive(state);
}

GoFx(kBool) GoSensor_IsReadable(GoSensor sensor)
{
    GoState state = GoSensor_State(sensor);

    return GoState_IsReadable(state);
}

GoFx(kBool) GoSensor_IsConfigurable(GoSensor sensor)
{
    GoState state = GoSensor_State(sensor);

    return GoState_IsConfigurable(state);
}

GoFx(kBool) GoSensor_IsReady(GoSensor sensor)
{
    GoState state = GoSensor_State(sensor);

    return (state == GO_STATE_READY);
}

GoFx(kBool) GoSensor_IsRunning(GoSensor sensor)
{
    GoState state = GoSensor_State(sensor);

    return (state == GO_STATE_RUNNING);
}

GoFx(kBool) GoSensor_IsNormal(GoSensor sensor)
{
    GoState state = GoSensor_State(sensor);

    return GoState_IsNormal(state);
}

GoFx(kBool) GoSensor_IsCancelled(GoSensor sensor)
{
    GoState state = GoSensor_KnownState(sensor);

    return (state == GO_STATE_CANCELLED);
}

GoFx(kBool) GoSensor_ConsistencyIntervalElapsed(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    k64u now = kTimer_Now();

    return (obj->sensorInfoTime != k64U_NULL) && ((now - obj->sensorInfoTime) > GO_SENSOR_CONSISTENCY_INTERVAL);
}

GoFx(kStatus) GoSensor_Cancel(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_LockState(sensor));
    {
        obj->isCancelled = kTRUE;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_OnCancelQuery(GoSensor sensor, kObject sender, kPointer args)
{
    GoState state = GoSensor_KnownState(sensor);        //use known state; avoids communication
    kBool isResponsive = GoState_IsResponsive(state);
    kBool isUpgrading = GoState_IsUpgrading(state);

    return (isResponsive || isUpgrading) ? kOK : kERROR_ABORT;
}

GoFx(kStatus) GoSensor_LockState(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    return GoSystem_LockState(obj->system);
}

GoFx(kStatus) GoSensor_UnlockState(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    return GoSystem_UnlockState(obj->system);
}

GoFx(kStatus) GoSensor_BeginReset(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_LockState(sensor));
    {
        obj->isResetting = kTRUE;
    }
    kCheck(GoSensor_UnlockState(sensor));

    kCheck(kPeriodic_Start(obj->resetTimer, GO_SYSTEM_RESET_HOLD_INTERVAL, GoSensor_OnResetHoldComplete, sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_OnResetHoldComplete(GoSensor sensor, kPeriodic timer)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_LockState(sensor));
    {
        obj->isResetting = kFALSE;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_WaitForReboot(GoSensor sensor, k64u timeout)
{
    kObj(GoSensor, sensor);

    kCheck(kTimer_Start(obj->timer, timeout));

    while ((GoSensor_State(sensor) != GO_STATE_ONLINE) && !kTimer_IsExpired(obj->timer))
    {
        kCheck(kThread_Sleep(GO_SYSTEM_RESET_QUERY_INTERVAL));
    }

    return (GoSensor_State(sensor) == GO_STATE_ONLINE) ? kOK : kERROR_TIMEOUT;
}

GoFx(kStatus) GoSensor_WaitForReconnect(GoSensor sensor, k64u timeout)
{
    kObj(GoSensor, sensor);
    GoState state;

    kCheck(kTimer_Start(obj->timer, timeout));

    //wait for reconnection
    while (!kSuccess(GoSensor_Connect(sensor)) && !kTimer_IsExpired(obj->timer))
    {
        kCheck(kThread_Sleep(GO_SYSTEM_RESET_QUERY_INTERVAL));
    }

    kCheck(kTimer_Start(obj->timer, kMin_(kTimer_Remaining(obj->timer), GO_SYSTEM_RESET_INCOMPLETE_TIMEOUT)));

    //wait for incomplete status to resolve (main booted, but buddy not yet detected)
    state = GoSensor_State(sensor);

    while (((state == GO_STATE_INCOMPLETE) || (state == GO_STATE_INCONSISTENT)) && !kTimer_IsExpired(obj->timer))
    {
        kCheck(kThread_Sleep(GO_SYSTEM_RESET_QUERY_INTERVAL));

        kCheck(GoSensor_Refresh(sensor));

        state = GoSensor_State(sensor);
    }

    return GoSensor_IsConnected(sensor) ? kOK : kERROR_TIMEOUT;
}

GoFx(kStatus) GoSensor_AddBuddy(GoSensor sensor, GoSensor buddy)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsConfigurable(sensor));
    kCheckState(GoSensor_IsConfigurable(buddy));
    kCheckState(!GoSensor_HasBuddy(buddy));

    kCheck(GoSensor_Flush(sensor));
    kCheck(GoSensor_Flush(buddy));

    kCheck(GoSensor_AppendBuddy(sensor, buddy));

    kCheck(GoSensor_Invalidate(sensor));
    kCheck(GoSensor_Invalidate(buddy));
    kCheck(GoSensor_SetConnected(buddy, kFALSE, kTRUE));

    return kOK;
}

// This works around command blocking on sensor during buddy connection.
GoFx(kStatus) GoSensor_WaitForCacheinfo(GoSensor sensor, k64u expiryTime)
{
    kCheck(GoSensor_InvalidateInfo(sensor));

    while (kTimer_Now() < expiryTime)
    {
        kStatus status = GoSensor_CacheInfo(sensor);

        if (status != kERROR_TIMEOUT)
        {
            return status;
        }
    }

    return kERROR_TIMEOUT;
}

GoFx(kStatus) GoSensor_WaitForBuddies(GoSensor sensor, k64u timeout)
{
    kObj(GoSensor, sensor);

    kSize i;
    kBool stillWaiting = kFALSE;
    const GoBuddyInfo* buddyInfo = kNULL;
    const k64u start = kTimer_Now();

    do
    {
        // if just checking with no timeout, need to pass small timeout into GoSensor_WaitForCacheinfo to succeed
        kCheck(GoSensor_WaitForCacheinfo(sensor, start + ((timeout ? timeout : 100)*1000)));

        if (!GoSensor_HasBuddies(sensor))
        {
            // no need to loop and check for buddies if system got no buddies
            return kOK;
        }

        for (i = 0; i < GoSensor_BuddiesCount(sensor); ++i)
        {
            buddyInfo = kArrayList_AtT(obj->buddySensorInfo, i, GoBuddyInfo);

            if (buddyInfo->state != GO_BUDDY_STATE_CONNECTED)
            {
                stillWaiting = kTRUE;
                break;
            }
        }
        if (!stillWaiting)
        {
            return kOK;
        }
        kCheck(kThread_Sleep(10000));

    } while (kTimer_Now() - start < (1000*timeout));

    return kERROR_TIMEOUT;
}

GoFx(kStatus) GoSensor_WaitForBuddyConnection(GoSensor sensor, k32u buddyId, k64u expiryTime)
{
    kObj(GoSensor, sensor);

    while (kTimer_Now() < expiryTime)
    {
        kSize i;
        const GoBuddyInfo* foundBuddyInfo = kNULL;

        kCheck(GoSensor_WaitForCacheinfo(sensor, expiryTime));

        for (i = 0; i < GoSensor_BuddiesCount(sensor); ++i)
        {
            const GoBuddyInfo* buddyInfo = kArrayList_AtT(obj->buddySensorInfo, i, GoBuddyInfo);

            if (buddyInfo->id == buddyId)
            {
                foundBuddyInfo = buddyInfo;
                break;
            }
        }

        kCheck(foundBuddyInfo != kNULL);

        if (foundBuddyInfo->state == GO_BUDDY_STATE_CONNECTED)
        {
            return kOK;
        }

        kCheck(kThread_Sleep(100000));
    }

    return kERROR_TIMEOUT;
}

GoFx(kStatus) GoSensor_AddBuddyBlocking(GoSensor sensor, GoSensor buddy)
{
    kObj(GoSensor, sensor);
    k32u buddyId = GoSensor_Id(buddy);
    static const k64u MAX_WAIT = 30 * 1000000;
    k64u expiryTime;

    kCheck(GoSensor_AddBuddy(sensor, buddy));

    expiryTime = kTimer_Now() + MAX_WAIT;

    // Special case: for older firmware without GetSystemInfov2,
    // there is no buddySensorInfo list. This also means the AddBuddy
    // command is synchronous so there's no need to check.
    // TODO: GoSensor_ReadInfo() should abstract away this difference.
    // Once that is done, we can remove this code.
    kCheck(GoSensor_WaitForCacheinfo(sensor, expiryTime));
    if (kArrayList_Count(obj->buddySensorInfo) == 0)
    {
        return kOK;
    }

    kCheck(GoSensor_WaitForBuddyConnection(sensor, buddyId, expiryTime));

    return kOK;
}

GoFx(kStatus) GoSensor_RemoveBuddy(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    GoSensor buddy = GoSensor_Buddy(sensor);

    kCheckState(GoSensor_IsConfigurable(sensor));
    kCheckState(GoSensor_Role(sensor) == GO_ROLE_MAIN);

    if (GoSensor_HasBuddy(sensor))
    {
        kCheck(GoSensor_Flush(sensor));

        k32u buddyIndex = (k32u)(GoSensor_BuddiesCount(sensor) - 1);
        kCheck(GoControl_RemoveBuddies(obj->control, &buddyIndex, 1));

        kCheck(GoSensor_Invalidate(sensor));

        if (!kIsNull(buddy))
        {
            kCheck(GoSensor_Invalidate(buddy));
        }
    }

    return kOK;
}

GoFx(kBool) GoSensor_HasBuddy(GoSensor sensor)
{
    // Caching is done inside these functions so isn't needed here.
    if (GoSensor_Role(sensor) == GO_ROLE_MAIN)
    {
        return (GoSensor_HasBuddies(sensor));
    }
    else
    {
        return kFALSE;
    }
}

//gets last buddy
GoFx(GoSensor) GoSensor_Buddy(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    GoSensor buddy = kNULL;

    if (GoSensor_HasBuddy(sensor))
    {
        k32u buddyId = GoSensor_BuddyId(sensor);

        if (kSuccess(GoSystem_FindSensorById(obj->system, buddyId, &buddy)))
        {
            return buddy;
        }
    }

    return kNULL;
}

GoFx(k32u) GoSensor_BuddyId(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    k32u id = 0;

    kCheck(GoSensor_CacheInfo(sensor));

    kCheck(GoSensor_LockState(sensor));
    {
        if (kArrayList_Count(obj->buddySensorInfo) > 0)
        {
            GoBuddyInfo* buddyInfo = kArrayList_LastT(obj->buddySensorInfo, GoBuddyInfo);
            if (!kIsNull(buddyInfo))
            {
                id = buddyInfo->id;
            }
        }
    }
    kCheck(GoSensor_UnlockState(sensor));

    return id;
}

GoFx(kStatus) GoSensor_EndAlign(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoControl_EndAlignment(obj->control));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_BeginAlign(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kLogf("IsReady");

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_BeginAlignment(obj->control));

    return kOK;
}

GoFx(kStatus) GoSensor_Align(GoSensor sensor)
{
    kCheck(GoSensor_BeginAlign(sensor));
    kLogf("MidAlign");
    kCheck(GoSensor_EndAlign(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_ClearAlignment(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoControl_ClearAlignment(obj->control));

    return kOK;
}

GoFx(kStatus) GoSensor_SetAlignmentReference(GoSensor sensor, GoAlignmentRef reference)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_SetAlignmentReference(obj->control, reference));

    return kOK;
}

GoFx(kStatus) GoSensor_AlignmentReference(GoSensor sensor, GoAlignmentRef* reference)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReadable(sensor));
    kCheck(GoControl_GetAlignmentReference(obj->control, reference));

    return kOK;
}

GoFx(kStatus) GoSensor_EndExposureAutoSet(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoControl_EndExposureAutoSet(obj->control));

    kCheck(GoSensor_InvalidateInfo(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_BeginExposureAutoSet(GoSensor sensor, GoRole role)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_Refresh(sensor));
    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_BeginExposureAutoSet(obj->control, role));

    return kOK;
}

GoFx(kStatus) GoSensor_ExposureAutoSet(GoSensor sensor, GoRole role)
{
    kCheck(GoSensor_BeginExposureAutoSet(sensor, role));
    kCheck(GoSensor_EndExposureAutoSet(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_BeginStart(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_BeginStart(obj->control));

    return kOK;
}

GoFx(kStatus) GoSensor_EndStart(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoControl_EndStart(obj->control));

    kCheck(GoSensor_InvalidateInfo(sensor));

    return kOK;
}

GoFx(kBool) GoSensor_CanStart(GoSensor sensor)
{
    return (kIsError(GoSensor_WaitForBuddies(sensor, 0))) ? kFALSE : kTRUE;
}

GoFx(kStatus) GoSensor_Start(GoSensor sensor)
{
    kCheck(GoSensor_BeginStart(sensor));
    kCheck(GoSensor_EndStart(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_BeginScheduledStart(GoSensor sensor, k64s value)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_BeginScheduledStart(obj->control, value));

    return kOK;
}

GoFx(kStatus) GoSensor_EndScheduledStart(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoControl_EndScheduledStart(obj->control));

    kCheck(GoSensor_InvalidateInfo(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_ScheduledStart(GoSensor sensor, k64s value)
{
    kCheck(GoSensor_BeginScheduledStart(sensor, value));
    kCheck(GoSensor_EndScheduledStart(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_BeginStop(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_BeginStop(obj->control));

    return kOK;
}

GoFx(kStatus) GoSensor_EndStop(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoControl_EndStop(obj->control));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_Stop(GoSensor sensor)
{
    kCheck(GoSensor_BeginStop(sensor));
    kCheck(GoSensor_EndStop(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_BeginSnapshot(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_BeginSnapshot(obj->control));

    return kOK;
}

GoFx(kStatus) GoSensor_EndSnapshot(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoControl_EndSnapshot(obj->control));

    kCheck(GoSensor_InvalidateInfo(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_Snapshot(GoSensor sensor)
{
    kCheck(GoSensor_BeginSnapshot(sensor));
    kCheck(GoSensor_EndSnapshot(sensor));

    return kOK;
}

GoFx(GoState) GoSensor_State(GoSensor sensor)
{
    //return value intentionally not checked; success not required
    GoSensor_CacheInfo(sensor);

    return GoSensor_KnownState(sensor);
}

GoFx(kStatus) GoSensor_States(GoSensor sensor, GoStates* states)
{
    kObj(GoSensor, sensor);
    GoMode mode = GO_MODE_UNKNOWN;

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoControl_GetStates(obj->control, states)))
    {
        return kOK;
    }

    return kERROR;
}

GoFx(GoRole) GoSensor_Role(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_CacheInfo(sensor));

    return obj->role;
}

GoFx(GoMode) GoSensor_ScanMode(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    GoMode mode = GO_MODE_UNKNOWN;

    kCheck(GoSensor_Flush(sensor));

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoControl_GetScanMode(obj->control, &mode)))
    {
        return mode;
    }

    return GO_MODE_UNKNOWN;
}

GoFx(kStatus) GoSensor_EmitDigital(GoSensor sensor, k16u index, k64s target, k8u value)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsRunning(sensor));

    kCheck(GoControl_ScheduleDigital(obj->control, index, target, value));

    return kOK;
}

GoFx(kStatus) GoSensor_EmitAnalog(GoSensor sensor, k16u index, k64s target, k32s value)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsRunning(sensor));

    kCheck(GoControl_ScheduleAnalog(obj->control, index, target, value));

    return kOK;
}

GoFx(kStatus) GoSensor_Reset(GoSensor sensor, kBool wait)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoSensor_Flush(sensor));
    kCheck(GoSensor_BeginReset(sensor));
    kCheck(GoControl_Reset(obj->control));
    kCheck(GoSensor_Disconnect(sensor));

    if (wait)
    {
        kCheck(GoSensor_WaitForReconnect(sensor, GO_SYSTEM_RESET_TIMEOUT));
    }

    return kOK;
}

GoFx(kStatus) GoSensor_ResetEncoder(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoSensor_Flush(sensor));
    kCheck(GoControl_ResetEncoder(obj->control));

    return kOK;
}

GoFx(k32u) GoSensor_Id(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->deviceId;
}

// DEPRECATED
GoFx(kStatus) GoSensor_Model(GoSensor sensor, kChar* model, kSize capacity)
{
    kObj(GoSensor, sensor);
    // modelName is deprecated (since it was ambiguous), provide the original behavior which was to return partNumber.
    return GoSensor_PartNumber(sensor, model, capacity);
}

GoFx(kStatus) GoSensor_PartNumber(GoSensor sensor, kChar* partNumber, kSize capacity)
{
    kObj(GoSensor, sensor);

    // If the value is still unknown, it means that the PartNumber was not in the discovery info, 
    // which means old sensor firmware that is older than ~2015, or really old Id.xml schemaVersion=1
    // that does not have PartNumber in the first place.
    // Fallback to the partNumber received via GetSystemInfo().
    if (kStrLength(obj->discoveryInfo.partNumber) == 0)
    {
        if (GoSensor_InfoValid(sensor))
        {
            kCheck(GoSensorInfo_PartNumber(obj->localSensorInfo, partNumber, capacity));
        }
        else
        {
            kStrCopy(partNumber, capacity, "");
        }
    }
    else
    { 
        // PartNumber learned via discovery.
        kCheck(kStrCopy(partNumber, capacity, obj->discoveryInfo.partNumber));
    }

    return kOK;
}

GoFx(kStatus) GoSensor_ModelNumber(GoSensor sensor, kChar* modelNumber, kSize capacity)
{
    kObj(GoSensor, sensor);
    // NOTE: ModelNumber is learned via discovery.
    return kStrCopy(modelNumber, capacity, obj->discoveryInfo.modelNumber);
}

GoFx(kStatus) GoSensor_ModelDisplayName(GoSensor sensor, kChar* modelDisplayName, kSize capacity)
{
    kObj(GoSensor, sensor);
    // NOTE: ModelDisplayName is learned via discovery.
    return kStrCopy(modelDisplayName, capacity, obj->discoveryInfo.modelDisplayName);
}

GoFx(GoUser) GoSensor_User(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!kSuccess(GoSensor_CacheInfo(sensor)))
    {
        return GO_USER_NONE;
    }

    return obj->user;
}

GoFx(kVersion) GoSensor_ProtocolVersion(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!GoSensor_IsConnected(sensor))
    {
        return 0;
    }

    return GoControl_ProtocolVersion(obj->control);
}

GoFx(kVersion) GoSensor_FirmwareVersion(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    // NOTE: Firmware version is learned via discovery.
    return obj->discoveryInfo.firmwareVersion;
}

GoFx(GoAlignmentState) GoSensor_AlignmentState(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!kSuccess(GoSensor_CacheInfo(sensor)))
    {
        return GO_ALIGNMENT_STATE_NOT_ALIGNED;
    }

    return obj->alignmentState;
}

GoFx(kStatus) GoSensor_Timestamp(GoSensor sensor, k64u* time)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsNormal(sensor));

    kCheck(GoControl_GetTimestamp(obj->control, time));

    return kOK;
}

GoFx(kStatus) GoSensor_Encoder(GoSensor sensor, k64s* encoder)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsNormal(sensor));

    kCheck(GoControl_GetEncoder(obj->control, encoder));

    return kOK;
}

GoFx(kStatus) GoSensor_Trigger(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsRunning(sensor));

    kCheck(GoControl_Trigger(obj->control));

    return kOK;
}

GoFx(kSize) GoSensor_FileCount(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    if (!kSuccess(GoSensor_CacheFileList(sensor)))
    {
        return 0;
    }

    return kArrayList_Count(obj->fileList);
}

GoFx(kStatus) GoSensor_FileNameAt(GoSensor sensor, kSize index, kChar* name, kSize capacity)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_CacheFileList(sensor));

    kCheckArgs(index < kArrayList_Count(obj->fileList));

    kCheck(kStrCopy(name, capacity, kArrayList_AtT(obj->fileList, index, kChar)));

    return kOK;
}

GoFx(kStatus) GoSensor_UploadFile(GoSensor sensor, const kChar* sourcePath, const kChar* destName)
{
    kObj(GoSensor, sensor);
    kAlloc alloc = kObject_Alloc(sensor);
    kByte* data = kNULL;
    kSize size = 0;

    kCheckState(GoSensor_IsNormal(sensor));

    kCheck(GoSensor_Flush(sensor));

    kTry
    {
        kTest(kFile_Load(sourcePath, &data, &size, alloc));

        kTest(GoControl_WriteFile(obj->control, destName, data, size));

        kTest(GoSensor_Invalidate(sensor));
    }
    kFinally
    {
        kAlloc_Free(alloc, data);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_DownloadFile(GoSensor sensor, const kChar* sourceName, const kChar* destPath)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsNormal(sensor));
    kCheck(GoControl_ReadFileStreamed(obj->control, sourceName, destPath, kFALSE));

    return kOK;
}

GoFx(kStatus) GoSensor_CopyFile(GoSensor sensor, const kChar* sourceName, const kChar* destName)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsNormal(sensor));

    kCheck(GoSensor_Flush(sensor));
    kCheck(GoControl_CopyFile(obj->control, sourceName, destName));
    kCheck(GoSensor_Invalidate(sensor));
    kCheck(GoSensor_SyncConfig(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_DeleteFile(GoSensor sensor, const kChar* name)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsNormal(sensor));

    kCheck(GoSensor_Flush(sensor));
    kCheck(GoControl_DeleteFile(obj->control, name));
    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kBool) GoSensor_FileExists(GoSensor sensor, const kChar* name)
{
    kSize fileCount = GoSensor_DirectoryFileCount(sensor, kNULL, "", kTRUE);
    kText128 fileName;
    kSize i;

    for (i = 0; i < fileCount; ++i)
    {
        kCheck(GoSensor_DirectoryFileNameAt(sensor, kNULL, "", kTRUE, i, fileName, kCountOf(fileName)));

        if (kStrEquals(fileName, name))
        {
            return kTRUE;
        }
    }

    return kFALSE;
}

GoFx(k64u) GoSensor_UserStorageFree(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    k64u storageFree = 0;

    if (GoSensor_IsReadable(sensor))
    {
        GoControl_GetUserStorageFree(obj->control, &storageFree);
    }

    return storageFree;
}

GoFx(k64u) GoSensor_UserStorageUsed(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    k64u storageUsed = 0;

    if (GoSensor_IsReadable(sensor))
    {
        GoControl_GetUserStorageUsed(obj->control, &storageUsed);
    }

    return storageUsed;
}

GoFx(kStatus) GoSensor_SetDefaultJob(GoSensor sensor, const kChar* fileName)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_SetDefaultJob(obj->control, fileName));

    return kOK;
}

GoFx(kStatus) GoSensor_DefaultJob(GoSensor sensor, kChar* fileName, kSize capacity)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_GetDefaultJob(obj->control, fileName, capacity));

    return kOK;
}

GoFx(kStatus) GoSensor_LoadedJob(GoSensor sensor, kChar* fileName, kSize capacity, kBool* changed)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_GetLoadedJob(obj->control, fileName, capacity, changed));

    return kOK;
}

GoFx(kStatus) GoSensor_LogIn(GoSensor sensor, GoUser user, const kChar* password)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_Login(obj->control, user, password));

    kCheck(GoSensor_InvalidateInfo(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_ChangePassword(GoSensor sensor, GoUser user, const kChar* password)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_ChangePassword(obj->control, user, password));

    return kOK;
}

GoFx(kStatus) GoSensor_BeginUpgrade(GoSensor sensor, const kChar* sourcePath)
{
    kObj(GoSensor, sensor);
    kAlloc alloc = kObject_Alloc(sensor);
    kByte* data = kNULL;
    kSize size = 0;
    kStatus status;

    kCheck(GoSensor_LockState(sensor));
    {
        obj->isUpgrading = kTRUE;
    }
    kCheck(GoSensor_UnlockState(sensor));

    kTry
    {
        kTest(kFile_Load(sourcePath, &data, &size, alloc));

        kTest(GoControl_BeginUpgrade(obj->control, data, size));
    }
    kCatchEx(&status)
    {
        GoSensor_EndUpgrade(sensor);
        kEndCatchEx(status);
    }
    kFinallyEx
    {
        kAlloc_Free(alloc, data);
        kEndFinallyEx();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_EndUpgrade(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_LockState(sensor));
    {
        obj->isUpgrading = kFALSE;
        obj->isCancelled = kFALSE;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_PostUpgrade(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_BeginReset(sensor));
    kCheck(GoSensor_WaitForReconnect(sensor, GO_SYSTEM_RESET_TIMEOUT));

    return kOK;
}

GoFx(kStatus) GoSensor_Upgrade(GoSensor sensor, const kChar* sourcePath, GoUpgradeFx callback, kPointer receiver)
{
    kObj(GoSensor, sensor);
    k32s progress, responseStage;
    GoUpgradeFxArgs args;
    kStatus status;
    kBool upgradeComplete = kFALSE;
    kBool upgradeSucceeded = kFALSE;
    kBool upgradeReapply = kFALSE;

    //success optional
    GoSensor_Flush(sensor);

    //disable health; health service may not be reliable during upgrade
    kCheck(GoSensor_EnableHealth(sensor, kFALSE));

    kCheck(kTimer_Start(obj->timer, GO_SENSOR_UPGRADE_TIMEOUT));

    kCheck(GoSensor_BeginUpgrade(sensor, sourcePath));
    {
        do
        {
            kTry
            {
                kTest(kThread_Sleep(GO_SYSTEM_QUIT_QUERY_INTERVAL));

                kTest(GoControl_GetUpgradeStatusEx(obj->control, &responseStage, &progress));

                if (callback)
                {
                    args.progress = (k64f) progress;
                    kTest(callback(receiver, sensor, &args));
                }

                upgradeComplete = (responseStage < 1 || responseStage == 2);
                upgradeSucceeded = (responseStage == 0);
                upgradeReapply = (responseStage == 2);
            }
            kCatch(&status)
            {
                upgradeComplete = kTRUE;
                upgradeSucceeded = kTRUE; /* speculative */
                kEndCatch(kOK);
            }
        }
        while (!upgradeComplete && !obj->isCancelled && !kTimer_IsExpired(obj->timer));
    }
    kCheck(GoSensor_EndUpgrade(sensor));

    if (!upgradeComplete)
    {
        return kERROR_TIMEOUT;
    }

    if (upgradeReapply)
    {
        return kERROR_INCOMPLETE;
    }

    if (!upgradeSucceeded)
    {
        return kERROR;
    }

    return GoSensor_PostUpgrade(sensor);
}

GoFx(kStatus) GoSensor_Backup(GoSensor sensor, const kChar* destPath)
{
    kObj(GoSensor, sensor);
    kAlloc alloc = kObject_Alloc(sensor);
    kByte* data = kNULL;
    kSize size = 0;

    kCheckState(GoSensor_IsReady(sensor));

    kTry
    {
        kTest(GoControl_Backup(obj->control, &data, &size, alloc));
        kTest(kFile_Save(destPath, data, size));
    }
    kFinally
    {
        kAlloc_Free(alloc, data);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_Restore(GoSensor sensor, const kChar* sourcePath)
{
    kObj(GoSensor, sensor);
    kAlloc alloc = kObject_Alloc(sensor);
    kByte* data = kNULL;
    kSize size = 0;

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Invalidate(sensor));

    kTry
    {
        kTest(kFile_Load(sourcePath, &data, &size, alloc));
        kTest(GoControl_Restore(obj->control, data, size));
    }
    kFinally
    {
        kAlloc_Free(alloc, data);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_RestoreDefaults(GoSensor sensor, kBool restoreAddress)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsConnected(sensor));

    kCheck(GoSensor_Invalidate(sensor));

    kCheck(GoControl_RestoreFactory(obj->control, restoreAddress));

    kCheck(GoSensor_CacheInfo(sensor));
    kCheck(GoSensor_ReadConfig(sensor));
    kCheck(GoSensor_ReadTransform(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_EnableData(GoSensor sensor, kBool enable)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsNormal(sensor));

    kCheck(kDestroyRef(&obj->data));

    if (enable)
    {
        kCheck(GoReceiver_Construct(&obj->data, kObject_Alloc(sensor)));
        kCheck(GoReceiver_SetBuffers(obj->data, GO_SENSOR_DATA_SOCKET_BUFFER, GO_SENSOR_DATA_STREAM_BUFFER));
        kCheck(GoReceiver_SetCancelHandler(obj->data, GoSensor_OnCancelQuery, sensor));
        kCheck(GoReceiver_SetMessageHandler(obj->data, GoSensor_OnData, sensor));

        kCheck(GoReceiver_Open(obj->data, obj->address.address, obj->dataPort));
    }

    return kOK;
}

GoFx(kBool) GoSensor_DataEnabled(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    return !kIsNull(obj->data);
}

GoFx(kStatus) GoSensor_OnData(GoSensor sensor, GoReceiver receiver, kSerializer reader)
{
    kObj(GoSensor, sensor);
    kAlloc msgAlloc = kAlloc_App();    //use the default system allocator for now; revisit later (performance)
    GoDataSet dataSet = kNULL;
    kStatus status = kNULL;

    kTry
    {
        kTest(kSerializer_ReadObject(reader, &dataSet, msgAlloc));
        kTest(GoDataSet_SetSenderId_(dataSet, obj->deviceId));
    }
    kCatch(&status)
    {
        kObject_Dispose(dataSet);
        kEndCatch(status);
    }

    if (obj->onDataSet == kNULL)
    {
        kCheck(GoSystem_OnData(obj->system, sensor, dataSet));
    }
    else
    {
        kCheck(obj->onDataSet(obj->onDataSetContext, sensor, dataSet));
    }

    return kOK;
}

GoFx(kStatus) GoSensor_EnableHealth(GoSensor sensor, kBool enable)
{
    kObj(GoSensor, sensor);

    kCheck(kDestroyRef(&obj->health));

    if (enable)
    {
        kCheck(GoReceiver_Construct(&obj->health, kObject_Alloc(sensor)));
        kCheck(GoReceiver_SetBuffers(obj->health, GO_SENSOR_HEALTH_SOCKET_BUFFER, GO_SENSOR_HEALTH_STREAM_BUFFER));
        kCheck(GoReceiver_SetCancelHandler(obj->health, GoSensor_OnCancelQuery, sensor));
        kCheck(GoReceiver_SetMessageHandler(obj->health, GoSensor_OnHealth, sensor));

        kCheck(GoReceiver_Open(obj->health, obj->address.address, obj->healthPort));
    }

    kCheck(GoSensor_LockState(sensor));
    {
        obj->healthCheckEnabled = enable;
        obj->previousHealthMsgCount = 0;
        obj->healthCheckCount = 0;
        obj->healthMsgCount = 0;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_OnHealth(GoSensor sensor, GoReceiver receiver, kSerializer reader)
{
    kObj(GoSensor, sensor);
    kAlloc msgAlloc = kAlloc_App();
    GoDataSet healthSet = kNULL;
    kStatus status = kNULL;

    kTry
    {
        kTest(kSerializer_ReadObject(reader, &healthSet, msgAlloc));
        kTest(GoDataSet_SetSenderId_(healthSet, obj->deviceId));

        kTest(GoSensor_UpdateHealthInfo(sensor, healthSet));
    }
    kCatch(&status)
    {
        kObject_Dispose(healthSet);
        kEndCatch(status);
    }

    kCheck(GoSystem_OnHealth(obj->system, sensor, healthSet));

    return kOK;
}

GoFx(kStatus) GoSensor_UpdateHealthInfo(GoSensor sensor, GoDataSet healthSet)
{
    kObj(GoSensor, sensor);
    kSize i, j;

    kCheck(GoSensor_LockState(sensor));
    {
        kBool consistencyIntervalElapsed = GoSensor_ConsistencyIntervalElapsed(sensor);
        kSize deviceCount = GoDataSet_Count(healthSet);

        for (i = 0; i < deviceCount; ++i)
        {
            GoHealthMsg health = GoDataSet_At(healthSet, i);
            kSize indicatorCount = GoHealthMsg_Count(health);
            k32u source = GoDataSet_SenderId(healthSet);

            if (source == GoSensor_Id(sensor))
            {
                for (j = 0; j < indicatorCount; ++j)
                {
                    const GoIndicator* indicator = GoHealthMsg_At(health, j);

                    switch (indicator->id)
                    {
                    case GO_HEALTH_STATE:
                        {
                            if (consistencyIntervalElapsed)
                            {
                                switch (indicator->value)
                                {
                                case -1:    obj->runState = GO_STATE_INCOMPLETE;       break;
                                case 0:     obj->runState = GO_STATE_READY;            break;
                                case 1:     obj->runState = GO_STATE_RUNNING;          break;
                                default:    obj->runState = GO_STATE_CONNECTED;        break;
                                }
                            }
                            break;
                        }

                    default:
                        break;
                    }

                }
            }
        }

        obj->healthMsgCount++;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(GoSetup) GoSensor_Setup(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->setup;
}

GoFx(GoTools) GoSensor_Tools(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->tools;
}

GoFx(GoOutput) GoSensor_Output(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->output;
}

GoFx(kStatus) GoSensor_EnableRecording(GoSensor sensor, kBool enable)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_SetRecordingEnabled(obj->control, enable));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kBool) GoSensor_RecordingEnabled(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kBool enabled = kFALSE;

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoControl_GetRecordingEnabled(obj->control, &enabled)))
    {
        return enabled;
    }

    return enabled;
}

GoFx(kStatus) GoSensor_SetInputSource(GoSensor sensor, GoInputSource source)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_SetInputSource(obj->control, source));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(GoInputSource) GoSensor_InputSource(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    GoInputSource source = GO_INPUT_SOURCE_LIVE;

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoControl_GetInputSource(obj->control, &source)))
    {
        return source;
    }

    return source;
}

GoFx(kStatus) GoSensor_Simulate(GoSensor sensor, kBool* isBufferValid)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_Simulate(obj->control, isBufferValid));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_PlaybackStep(GoSensor sensor, GoSeekDirection direction)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_PlaybackStep(obj->control, direction));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_PlaybackSeek(GoSensor sensor, kSize position)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_PlaybackSeek(obj->control, position));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_PlaybackPosition(GoSensor sensor, kSize* position, kSize* count)
{
    kObj(GoSensor, sensor);

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoControl_PlaybackPosition(obj->control, position, count)))
    {
        return kOK;
    }

    return kERROR;
}

GoFx(kStatus) GoSensor_ClearReplayData(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_ClearReplayData(obj->control));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_ClearMeasurementStats(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_ClearMeasurementStats(obj->control));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_ExportBitmap(GoSensor sensor,
                                    GoReplayExportSourceType type,
                                    GoDataSource source,
                                    const kChar* dstFileName)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_ExportBitmap(obj->control,
                                 type,
                                 source,
                                 dstFileName));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}


GoFx(kStatus) GoSensor_ExportCsv(GoSensor sensor, const kChar* dstFileName)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_ExportCsv(obj->control, dstFileName));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(GoFamily) GoSensor_Family(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    GoFamily returnType = GO_FAMILY_UNKNOWN;

    if (kStrLength(obj->discoveryInfo.family) == 0)
    {
        // If the value is still unknown, it means that the Family was not in the discovery info
        // so try to fall back to other sources.
        returnType = GoSensor_FillFamily(sensor);
    }
    // The following code mimics what is filled in for "Family" in GsDevice_FillExtendedIdGaps().
    else if (kStrCompareLower(obj->discoveryInfo.family, "G1x00") == 0)
    {
        returnType = GO_FAMILY_1000;
    }
    else if (kStrCompareLower(obj->discoveryInfo.family, "G2x00") == 0)
    {
        returnType = GO_FAMILY_2000;
    }
    else if (kStrCompareLower(obj->discoveryInfo.family, "G3x00") == 0)
    {
        returnType = GO_FAMILY_3000;
    }
    else
    {
        // NOTE: GoSdk currently does not handle "G2XX".
        // Wait until customer requests support for it.
        // Original implementation simply returns GO_FAMILY_2000 for it.
        kLogf("%s: Warning: could not determine family of '%s'. Falling back to G2x00.", 
              __FUNCTION__, obj->discoveryInfo.family);
        returnType = GO_FAMILY_2000; 
    }

    return returnType;
}

GoFx(GoFamily) GoSensor_FillFamily(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    GoFamily returnType = GO_FAMILY_UNKNOWN;

    kText32 partNumber;
    const char* pos = kNULL;
    kString tempString = kNULL;
    kArrayList tokens = kNULL;

    kTry
    {
        // If the value is still unknown, it means that the Family was not in the discovery info, 
        // which means old sensor firmware older than ~2015, so attempt to fallback 
        // to parsing the partNumber received via GetSystemInfo().
        if (GoSensor_InfoValid(sensor))
        {
            kTest(GoSensorInfo_PartNumber(obj->localSensorInfo, partNumber, kCountOf(partNumber)));
            kTest(kString_Construct(&tempString, partNumber, kObject_Alloc(sensor)));

            kTest(kString_Split(tempString, "-", &tokens, kObject_Alloc(sensor)));

            if(kArrayList_Count(tokens) == 0)
            { 
                returnType = GO_FAMILY_2000;
            }
            else
            {
                switch(atoi(kString_Chars(kArrayList_AsT(tokens, 0, kString)) + 2) / 1000)
                {
                    case 1:     returnType = GO_FAMILY_1000; break;
                    case 2:     returnType = GO_FAMILY_2000; break;
                    case 3:     returnType = GO_FAMILY_3000; break;
                    default:    returnType = GO_FAMILY_2000; break;
                }
            }
        }
        else
        {
            // NOTE: If localSensorInfo is not available, we do not explicitly trigger another 
            // GoSensor_CacheInfo() since the normal operation of this function should be able 
            // to obtain family via discovery.
            returnType = GO_FAMILY_UNKNOWN;
        }
    }
    kFinally
    {
        kDestroyRef(&tempString);
        kDisposeRef(&tokens);
        kEndFinally();
    }

    return returnType;
}

GoFx(GoTransform) GoSensor_Transform(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->transform;
}

GoFx(GoReplay) GoSensor_Replay(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->replay;
}

GoFx(kStatus) GoSensor_EnableAutoStart(GoSensor sensor, kBool enable)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_SetAutoStartEnabled(obj->control, enable));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kBool) GoSensor_AutoStartEnabled(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kBool enabled = kFALSE;

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoControl_GetAutoStartEnabled(obj->control, &enabled)))
    {
        return enabled;
    }

    return enabled;
}

GoFx(kStatus) GoSensor_SetVoltage(GoSensor sensor, GoVoltageSetting voltage, k64f cableLength)
{
    kObj(GoSensor, sensor);
    k32u cableLength_mm = 0;

    if (voltage != GO_VOLTAGE_48 && voltage != GO_VOLTAGE_24) return kERROR_PARAMETER;
    if (voltage == GO_VOLTAGE_24 && (cableLength < 0.0 || cableLength > 25.0)) return kERROR_PARAMETER;

    cableLength_mm = (k32u)(cableLength * 1000);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_SetVoltage(obj->control, voltage, cableLength_mm));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_GetVoltage(GoSensor sensor, GoVoltageSetting *voltage, k64f *cableLength)
{
    kObj(GoSensor, sensor);

    if (GoSensor_IsReadable(sensor))
    {
        k32u cableLength_mm = 0;
        kCheck(GoControl_GetVoltage(obj->control, voltage, &cableLength_mm));
        if (cableLength) *cableLength = (k64f)cableLength_mm / 1000.0;
    }
    else
    {
        return kERROR_COMMAND;
    }

    return kOK;
}

GoFx(kStatus) GoSensor_EnableQuickEdit(GoSensor sensor, kBool enable)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_SetQuickEditEnabled(obj->control, enable));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kBool) GoSensor_QuickEditEnabled(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kBool enabled = kFALSE;

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoControl_GetQuickEditEnabled(obj->control, &enabled)))
    {
        return enabled;
    }

    return enabled;
}

GoFx(kSize) GoSensor_RemoteInfoCount(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return kArrayList_Count(obj->remoteSensorInfo);
}

GoFx(GoSensorInfo) GoSensor_RemoteInfoAt(GoSensor sensor, kSize index)
{
    kObj(GoSensor, sensor);

    kAssert(index < kArrayList_Count(obj->remoteSensorInfo));

    return kArrayList_AsT(obj->remoteSensorInfo, index, GoSensorInfo);
}

GoFx(kStatus) GoSensor_PartMatchCreateModel(GoSensor sensor, const kChar* name)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_CreateModel(obj->control, name));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_PartMatchDetectModelEdges(GoSensor sensor, const kChar* name, k16u sensitivity)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_DetectModelEdges(obj->control, name, sensitivity));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_CacheDirectoryList(GoSensor sensor, const kChar* extensionFilter, const kChar* path, kBool isRecursive)
{
    kObj(GoSensor, sensor);

    if (!obj->isDirectoryListValid)
    {
        kCheck(GoSensor_ListDirectory(sensor, extensionFilter, path, isRecursive, obj->directoryList));
        obj->isDirectoryListValid = kTRUE;
    }

    return kOK;
}

GoFx(kStatus) GoSensor_ListDirectory(GoSensor sensor, const kChar* extension, const kChar* root, kBool isRecursive, kArrayList fileList)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReadable(sensor));

    kCheck(GoControl_ListDirectory(obj->control, extension, root, isRecursive, fileList));

    return kOK;
}

GoFx(kSize) GoSensor_DirectoryFileCount(GoSensor sensor, const kChar* extensionFilter, const kChar* path, kBool isRecursive)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_CacheDirectoryList(sensor, extensionFilter, path, isRecursive));

    return kArrayList_Count(obj->directoryList);
}

GoFx(kStatus) GoSensor_DirectoryFileNameAt(GoSensor sensor, const kChar* extensionFilter, const kChar* path, kBool isRecursive, kSize index, kChar* fileName, kSize capacity)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_CacheDirectoryList(sensor, extensionFilter, path, isRecursive));

    kCheckArgs(index < kArrayList_Count(obj->directoryList));

    kCheck(kStrCopy(fileName, capacity, kArrayList_AtT(obj->directoryList, index, kChar)));

    return kOK;
}

GoFx(kStatus) GoSensor_InvalidateDirectoryList(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    obj->isDirectoryListValid = kFALSE;

    return kOK;
}

GoFx(kBool) GoSensor_DirectoryListValid(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->isDirectoryListValid;
}

GoFx(kStatus) GoSensor_SetDataHandler(GoSensor sensor, GoSensorDataSetFx function, kPointer context)
{
    kObj(GoSensor, sensor);

    obj->onDataSet = function;
    obj->onDataSetContext = context;

    return kOK;
}

GoFx(kStatus) GoSensor_AddTool(GoSensor sensor, const kChar* type, const kChar* name)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_AddTool(obj->control, type, name));

    kCheck(GoSensor_Invalidate(sensor));
    kCheck(GoSensor_SyncConfig(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_AddMeasurement(GoSensor sensor, kSize index, const kChar* type, const kChar* name)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_AddMeasurement(obj->control, index, type, name));

    kCheck(GoSensor_Invalidate(sensor));
    kCheck(GoSensor_SyncConfig(sensor));

    return kOK;
}

GoFx(kBool) GoSensor_IsCompatible(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return GoControl_IsCompatible(obj->control);
}

GoFx(kStatus) GoSensor_EnableRamImage(GoSensor sensor, kBool enable)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsReady(sensor));

    kCheck(GoSensor_Flush(sensor));

    kCheck(GoControl_SetRamImageEnabled(obj->control, enable));

    kCheck(GoSensor_Invalidate(sensor));

    return kOK;
}

GoFx(kBool) GoSensor_RamImageEnabled(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kBool enabled = kFALSE;

    if (GoSensor_IsReadable(sensor)
        && kSuccess(GoControl_GetRamImageEnabled(obj->control, &enabled)))
    {
        return enabled;
    }

    return enabled;
}

GoFx(kStatus) GoSensor_WriteRamImage(GoSensor sensor, GoRole role, kBool resizeActiveArea, kSize cameraIndex, kSize stateIndex, kSize frameIndex, const kChar* imagePath)
{
    kObj(GoSensor, sensor);
    kImage tempImage = kNULL;
    k64f xRes = 0.0, yRes = 0.0, interval = 0.05;

    kCheckState(GoSensor_IsConfigurable(sensor));
    kCheckState(kFile_Exists(imagePath));

    kTry
    {
        kTest(kImage_Import(&tempImage, imagePath, kObject_Alloc(sensor)));

        if (resizeActiveArea)
        {
            xRes = GoSetup_FrontCameraWidth(obj->setup, role) / GoSetup_ActiveAreaWidth(obj->setup, role);
            yRes = GoSetup_FrontCameraHeight(obj->setup, role) / GoSetup_ActiveAreaHeight(obj->setup, role);
            kTest(GoSetup_SetActiveAreaHeight(obj->setup, role, kImage_Height(tempImage) / yRes));
            kTest(GoSetup_SetActiveAreaWidth(obj->setup, role, kImage_Width(tempImage) / xRes));

            kTest(GoSensor_Flush(sensor));

            while (GoSetup_FrontCameraWidth(obj->setup, role) > kImage_Width(tempImage))
            {
                kTest(GoSetup_SetActiveAreaWidth(obj->setup, role, GoSetup_ActiveAreaWidth(obj->setup, role) - interval));
            }

            while (GoSetup_FrontCameraHeight(obj->setup, role) > kImage_Height(tempImage))
            {
                kTest(GoSetup_SetActiveAreaHeight(obj->setup, role, GoSetup_ActiveAreaHeight(obj->setup, role) - interval));
            }

            //kTest(GoSetup_SetActiveAreaHeight(obj->setup, role, GoSetup_ActiveAreaHeight(obj->setup, role) + interval)); //we want the smallest possible active area that results in a matching pixel height
            //kTest(GoSetup_SetActiveAreaHeight(obj->setup, role, interval));
            kTest(GoSetup_SetActiveAreaZ(obj->setup, role, GoSetup_ActiveAreaZLimitMin(obj->setup, role)));

            kTest(GoSensor_Start(sensor));
            kTest(GoSensor_Stop(sensor));
        }

        kTest(GoControl_WriteRamImage(obj->control, role, cameraIndex, stateIndex, frameIndex, tempImage));
    }
    kFinally
    {
        kDisposeRef(&tempImage);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_SetRuntimeVariables(GoSensor sensor, kSize startIndex, kSize length, k32s* values)
{
    kObj(GoSensor, sensor);

    //no state check or flush is done, as runtime variables can be set at any time
    kCheckArgs(!kIsNull(values));

    kCheck(GoControl_SetRuntimeVariables(obj->control, startIndex, length, values));

    return kOK;
}

GoFx(kSize) GoSensor_GetRuntimeVariableCount(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kSize value;

    if (!kSuccess(GoControl_RuntimeVariableCount(obj->control, &value)))
    {
        return 0;
    }

    return value;
}

GoFx(kStatus) GoSensor_GetRuntimeVariables(GoSensor sensor, kSize startIndex, kSize length, k32s* values)
{
    kObj(GoSensor, sensor);

    kCheckArgs(!kIsNull(values));

    kCheck(GoControl_GetRuntimeVariables(obj->control, startIndex, length, values));

    return kOK;
}

GoFx(kStatus) GoSensor_GetRuntimeVariableAt(GoSensor sensor, kSize index, k32s* value)
{
    kCheckArgs(!kIsNull(value));

    kCheck(GoSensor_GetRuntimeVariables(sensor, index, 1, value));

    return kOK;
}

// For virtual and accelerated sensors, the hosted sensor's discovery server returns
// the loopback address (127.0.0.1). This limits connectivity to the
// hosted sensor by local SDK application. Remote SDK application
// cannot use this address to connect to the sensor.
// To support remote SDK applications connecting to accelerated (but not virtual) sensors,
// replace the sensor object's loopback address value with the IP of the
// discovery server  that is running in the hosted sensor.
//
// Virtual sensors are never seen outside the platform on which they run so they are
// not connectable by a remote SDK application. However, virtual sensor MUST
// be connectable by a local SDK application.
// Virtual sensors created by the GoEmulator program binds sockets to the
// loopback address. Therefore these virtual sensor object's address must
// remain the loopback address.
// Virtual sensors created by developers using kFramework.exe directly
// binds to the 0.0.0.0 address. Therefore these virtual sensor object's address
// can be changed to the discovery server's address, or left as the loopback
// address.
//
// NOTE:
// Before release 5.1, the discovery server for virtual sensor always
// returned the loopback address.
// For 5.1 and newer releases, the discovery server returns interface addresses
// for all sensors (virtual or standalone or accelerated).
// The 5.1 and newer releases also support the sensor OpMode field which can be used to see
// if the sensor is a virtual sensor or not.
GoFx(kBool) GoSensor_UseDiscoveryServerAddr(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    kBool useServerAddr = kTRUE;

    if (GoSensor_AccelOpMode(sensor) == GO_DISCOVERY_OP_MODE_VIRTUAL)
    {
        // 5.1 and newer sensor software's virtual sensor. Keep the virtual
        // sensor loopback address as is.
        useServerAddr = kFALSE;
    }
    else if (!kIpAddress_IsLoopback(obj->address.address))
    {
        // Not a loopback address. Might have been set by SDK client, so
        // don't change it.
        useServerAddr = kFALSE;
    }

    return useServerAddr;
}

GoFx(kStatus) GoSensor_UpdateDiscoveryInfo(GoSensor sensor, const GoDiscoveryInfo* info)
{
    kObj(GoSensor, sensor);
    kSize historyMask;
    kSize historyIndex;
    GoPortInfo portInfo;

    // Just an efficient way to do modulus
    historyMask = kCountOf(obj->discoveryHistory) - 1;
    historyIndex = obj->discoveryCount & historyMask;

    // There is technically a race condition here i.e. the parameters are modified
    // while they are used in Connect. However the values almost never change
    // so it's unlikely to be a problem. It has never been observed to be a problem
    // before this refactoring.
    if (info != kNULL)
    {
        portInfo = GoDiscoveryInfo_Ports(info);
        obj->address = GoDiscoveryInfo_Address(info);

        if (info->ports.controlPort != k16U_NULL)
        {
            kCheck(GoSensor_SetControlPort(sensor, portInfo.controlPort));
        }

        if (info->ports.upgradePort != k16U_NULL)
        {
            kCheck(GoSensor_SetUpgradePort(sensor, portInfo.upgradePort));
        }

        if (info->ports.healthPort != k16U_NULL)
        {
            kCheck(GoSensor_SetHealthPort(sensor, portInfo.healthPort));
        }

        if (info->ports.dataPort != k16U_NULL)
        {
            kCheck(GoSensor_SetDataPort(sensor, portInfo.dataPort));
        }

        kCheck(GoSensor_UpdateAccelInfo(sensor, info));

        // Update basic sensor information learned via discovery.
        obj->discoveryInfo.firmwareVersion = GoDiscoveryInfo_Version(info);
        kCheck(GoDiscoveryInfo_PartNumber(info, obj->discoveryInfo.partNumber, kCountOf(obj->discoveryInfo.partNumber)));
        kCheck(GoDiscoveryInfo_ModelNumber(info, obj->discoveryInfo.modelNumber, kCountOf(obj->discoveryInfo.modelNumber)));
        kCheck(GoDiscoveryInfo_ModelDisplayName(info, obj->discoveryInfo.modelDisplayName, kCountOf(obj->discoveryInfo.modelDisplayName)));
        kCheck(GoDiscoveryInfo_Family(info, obj->discoveryInfo.family, kCountOf(obj->discoveryInfo.family)));

        // Do this only after the sensor acceleraton information has been
        // updated so that the acceleraton op mode value is valid.
        if (GoSensor_UseDiscoveryServerAddr(sensor))
        {
            // Make sensor accessible from local and remote SDK clients.
            obj->address.address = GoDiscoveryInfo_DiscoveryServerAddress(info);
        }

        obj->discoveryHistory[historyIndex] = kTRUE;
    }
    else
    {
        obj->discoveryHistory[historyIndex] = kFALSE;
    }

    return kOK;
}

GoFx(kStatus) GoSensor_UpdateDiscoveryCycle(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    obj->discoveryCount++;

    return kOK;
}

GoFx(GoAddressInfo*) GoSensor_AddressInfo(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return &obj->address;
}

GoFx(kStatus) GoSensor_SetUpgradePort(GoSensor sensor, k32u port)
{
    kObj(GoSensor, sensor);

    obj->upgradePort = port;

    return kOK;
}

GoFx(k32u) GoSensor_UpgradePort(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->upgradePort;
}

GoFx(kStatus) GoSensor_SetControlPort(GoSensor sensor, k32u port)
{
    kObj(GoSensor, sensor);

    obj->controlPort = port;

    return kOK;
}

GoFx(k32u) GoSensor_ControlPort(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->controlPort;
}

GoFx(GoControl) GoSensor_Control(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->control;
}

GoFx(kStatus) GoSensor_SetDataPort(GoSensor sensor, k32u port)
{
    kObj(GoSensor, sensor);

    obj->dataPort = port;

    return kOK;
}

GoFx(k32u) GoSensor_DataPort(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->dataPort;
}

GoFx(kStatus) GoSensor_SetHealthPort(GoSensor sensor, k32u port)
{
    kObj(GoSensor, sensor);

    obj->healthPort = port;

    return kOK;
}

GoFx(k32u) GoSensor_HealthPort(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->healthPort;
}

GoFx(kStatus) GoSensor_StartRecordingStream(GoSensor sensor, kChar* destPath)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsNormal(sensor));
    kCheck(GoControl_ReadFileStreamed(obj->control, GO_SENSOR_LIVE_REPLAY_STREAM, destPath, kTRUE));

    return kOK;
}

GoFx(kStatus) GoSensor_StopRecordingStream(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kCheck(GoControl_StopStreaming(obj->control));

    // disconnect and reconnect to re-establish the control connection
    kCheck(GoSensor_Disconnect(sensor));
    kCheck(GoSensor_Connect(sensor));

    return kOK;
}

GoFx(kBool) GoSensor_IsRecordingStreaming(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return GoControl_IsStreamingStopped(obj->control);
}

GoFx(GoSensorInfo) GoSensor_BuddiesAt(GoSensor sensor, k32u index)
{
    kObj(GoSensor, sensor);

    if (index >= kArrayList_Count(obj->buddySensorInfo))
    {
        return kNULL;
    }

    return *kArrayList_AtT(obj->remoteSensorInfo, index, GoSensorInfo);
}

GoFx(kSize) GoSensor_BuddiesCount(GoSensor sensor)
{
    kObj(GoSensor, sensor);
    // Should we set to something "invalid" here?
    kSize buddyCount = 0;

    kCheck(GoSensor_LockState(sensor));
    if (kSuccess(GoSensor_CacheInfo(sensor)))
    {
        if (GoSensor_Role(sensor) == GO_ROLE_MAIN)
        {
            buddyCount = kArrayList_Count(obj->buddySensorInfo);
        }
    }
    kCheck(GoSensor_UnlockState(sensor));

    return buddyCount;
}

GoFx(kBool) GoSensor_HasBuddies(GoSensor sensor)
{
    return GoSensor_BuddiesCount(sensor) > 0;
}

GoFx(kStatus) GoSensor_GeoCal(GoSensor sensor, GoGeoCal *geoCal)
{
    kObj(GoSensor, sensor);
    kAlloc alloc = kObject_Alloc(sensor);
    kByte* fileData = kNULL;
    kSize fileSize = 0;
    kXml xml = kNULL;
    GoGeoCal newGeoCal = kNULL;
    kStatus exception, readOK;

    readOK = GoControl_ReadFile(obj->control, GO_FILE_GEOCAL, &fileData, &fileSize, alloc);

    if (kOK != readOK)
    {
        //Couldn't read the file, probably because it doesnt exist on this sensor.
        *geoCal = kNULL;
        return kOK;
    }

    kTry
    {
        kTest(kXml_LoadBytes(&xml, fileData, fileSize, kNULL));

        kTest(GoGeoCal_Construct(&newGeoCal, xml, kNULL));

        *geoCal = newGeoCal;
    }
    kCatchEx(&exception)
    {
        //Read the geocal from the filesystem but couldn't parse it properly?
        kObject_Destroy(newGeoCal);
        *geoCal = kNULL;
        kEndCatchEx(exception);
    }
    kFinallyEx
    {
        kAlloc_Free(alloc, fileData);
        kObject_Destroy(xml);
        kEndFinallyEx();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_UpdateAccelState(GoSensor sensor, const GoDiscoveryInfo* info, GoDiscoveryOpMode opMode)
{
    kObj(GoSensor, sensor);
    GoSensorAccelState accelState;

    // Accelerator (SDK) software version must match exactly with
    // sensor/accelerator host software version.
    // This also handles the case where the operational mode is unknown, because this
    // means the sensor/accelerator host software pre-dates this current SDK code.
    if (kVersion_Compare(GoDiscoveryInfo_Version(info), GoSdk_Version()) != 0)
    {
        accelState = GO_SENSOR_ACCEL_STATE_FW_MISMATCH;

        // Don't need to fill out the rest of the field since this sensor
        // cannot be accelerated.
    }
    else
    {
        // Discovered sensor is either standalone or accelerated, and has the same
        // software version as this current SDK code.
        if (opMode == GO_DISCOVERY_OP_MODE_STANDALONE)
        {
            accelState = GO_SENSOR_ACCEL_STATE_AVAILABLE;
        }
        else
        {
            // Main controller responding must be the sensor's accelerator host.
            // From the given information, sensor class cannot determine at this
            // point whether sensor is accelerated by this host or another host.
            // Leave this decision up to the accelerator code to fine tune the
            // acceleration state.
            accelState = GO_SENSOR_ACCEL_STATE_ACCELERATED;
        }
    }

    kCheck(GoSensor_SetAccelState(sensor, accelState));

    return kOK;
}

// This function stores the accelerated sensor's IP address. This address is
// only available if the sensor is being accelerated, regardless of which
// host is accelerating it.
GoFx(kStatus) GoSensor_UpdateAccelSensorIpAddr(GoSensor sensor, const GoDiscoveryInfo* info, GoDiscoveryOpMode opMode)
{
    kObj(GoSensor, sensor);
    kIpAddress ipAddress;

    // Accelerated sensor IP address is meaningful only if sensor is accelerated,
    // regardless of host. So copy/set the address only if sensor is accelerated.
    if (opMode == GO_DISCOVERY_OP_MODE_ACCELERATOR)
    {
        ipAddress = GoDiscoveryInfo_AccelSensorIpAddress(info);
    }
    else
    {
        ipAddress = GoDiscoveryInfo_Address(info).address;
    }

    kCheck(GoSensor_SetAccelSensorIpAddress(sensor, &ipAddress));

    return kOK;
}

GoFx(kStatus) GoSensor_UpdateAccelInfo(GoSensor sensor, const GoDiscoveryInfo* info)
{
    kObj(GoSensor, sensor);
    GoDiscoveryOpMode opMode;
    GoPortInfo portInfo;

    kCheck(GoSensor_LockState(sensor));

    kTry
    {
        // Save the operational mode always, and do it first so it is set even
        // if mode is virtual or unknown.
        obj->accelInfo.opMode = GoDiscoveryInfo_OpMode(info);
        opMode = GoSensor_AccelOpMode(sensor);

        // Ignore virtual sensors because they cannot be accelerated.
        // Only track standalone or accelerated sensors.
        // For older sensor software, the operational mode is unknown, so
        // accept those. However, these sensors would be marked as firmware mismatch.
        if (opMode != GO_DISCOVERY_OP_MODE_VIRTUAL)
        {
            kTest(GoSensor_UpdateAccelState(sensor, info, opMode));

            // Ports used by sensor is always valid.
            portInfo = GoDiscoveryInfo_Ports(info);
            kTest(GoSensor_SetAccelPortInfo(sensor, &portInfo));

            // Get accelerated sensor IP only after determining the acceleration state.
            // Want to filter out software version incompatible sensors and unaccelerated sensor.
            kTest(GoSensor_UpdateAccelSensorIpAddr(sensor, info, GoSensor_AccelOpMode(sensor)));
        }

    }
    kFinally
    {
        kCheck(GoSensor_UnlockState(sensor));

        kEndFinally();
    }

    return kOK;
}

GoFx(GoSensorAccelState) GoSensor_AccelState(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->accelInfo.state;
}

GoFx(kStatus) GoSensor_SetAccelState(GoSensor sensor, GoSensorAccelState accelState)
{
    kObj(GoSensor, sensor);

    obj->accelInfo.state = accelState;

    return kOK;
}

GoFx(GoPortInfo) GoSensor_AccelPortInfo(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->accelInfo.portInfo;
}

GoFx(kStatus) GoSensor_SetAccelPortInfo(GoSensor sensor, GoPortInfo* portInfo)
{
    kObj(GoSensor, sensor);

    obj->accelInfo.portInfo = *portInfo;

    return kOK;
}

GoFx(GoDiscoveryOpMode) GoSensor_AccelOpMode(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    return obj->accelInfo.opMode;
}

GoFx(kStatus) GoSensor_SetAccelOpMode(GoSensor sensor, GoDiscoveryOpMode opMode)
{
    kObj(GoSensor, sensor);

    obj->accelInfo.opMode = opMode;

    return kOK;
}

GoFx(kStatus) GoSensor_AccelSensorIpAddress(GoSensor sensor, kIpAddress* ipAddress)
{
    kObj(GoSensor, sensor);

    kCheck(GoSensor_LockState(sensor));
    {
        *ipAddress = obj->accelInfo.accelSensorIp;
    }
    kCheck(GoSensor_UnlockState(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_SetAccelSensorIpAddress(GoSensor sensor, kIpAddress* ipAddress)
{
    kObj(GoSensor, sensor);

    obj->accelInfo.accelSensorIp = *ipAddress;

    return kOK;
}

GoFx(kStatus) GoSensor_SetSecurityLevel(GoSensor sensor, GoSecurityLevel security)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_SetSecurityLevel(obj->control, security));

    kCheck(GoSensor_InvalidateInfo(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_SetFlag(GoSensor sensor, const kChar* name, const kChar* value)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_SetFlag(obj->control, name, value));

    kCheck(GoSensor_InvalidateInfo(sensor));

    return kOK;
}

GoFx(kStatus) GoSensor_GetFlag(GoSensor sensor, const kChar* name, kString value)
{
    kObj(GoSensor, sensor);

    kCheckState(GoSensor_IsResponsive(sensor));

    kCheck(GoControl_GetFlag(obj->control, name, value));

    return kOK;
}

GoFx(kStatus) GoSensor_AppendBuddy(GoSensor sensor, GoSensor buddy)
{
    kObj(GoSensor, sensor);
    kAlloc alloc = kObject_Alloc(sensor);
    kArrayList buddyIds = kNULL;

    kCheck(kArrayList_Construct(&buddyIds, kTypeOf(k32u), 16, alloc));

    kTry
    {
        kTest(GoSensor_CreateBuddyList(sensor, &buddyIds));

        kSize buddyCount = GoSensor_BuddiesCount(sensor);

        k32u buddyId = GoSensor_Id(buddy);
        kTest(kArrayList_AddT(buddyIds, &buddyId));
        buddyCount++;

        kTest(GoControl_AssignBuddies(obj->control, kArrayList_DataT(buddyIds, k32u), buddyCount));
    }
    kFinally
    {
        kDestroyRef(&buddyIds);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoSensor_CreateBuddyList(GoSensor sensor, kArrayList* buddyIds)
{
    kObj(GoSensor, sensor);
    kSize buddyCount = kArrayList_Count(obj->buddySensorInfo);

    for (kSize i = 0; i < buddyCount; ++i)
    {
        kCheck(kArrayList_AddT(*buddyIds, &kArrayList_AtT(obj->buddySensorInfo, i, GoBuddyInfo)->id));
    }

    return kOK;
}

GoFx(kStatus) GoSensor_SetTimeDate(GoSensor sensor)
{
    kObj(GoSensor, sensor);

    kDateTime dateTime = kDateTime_Now();
    GoControl_SetDataTime(obj->control, dateTime);

    return kOK;
}