/**
 * @file    GoControl.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/Internal/GoControl.h>
#include <kApi/Utils/kUtils.h>
#include <kApi/Data/kImage.h>
#include <kApi/Io/kDat6Serializer.h>

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

GoFx(kStatus) GoControl_Construct(GoControl* control, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status;

    kCheck(kAlloc_GetObject(alloc, kTypeOf(GoControl), control));

    if (!kSuccess(status = GoControl_Init(*control, kTypeOf(GoControl), alloc)))
    {
        kAlloc_FreeRef(alloc, control);
    }

    return status;
}

GoFx(kStatus) GoControl_Init(GoControl control, kType type, kAlloc alloc)
{
    kObjR(GoControl, control);

    kCheck(kObject_Init(control, type, alloc));
    obj->onCancel.function = kNULL;
    obj->onCancel.receiver = kNULL;
    kZero(obj->protocolVersion);
    obj->busy = kFALSE;
    kZero(obj->channels);
    kZero(obj->streamThread);
    kAtomic32s_Init(&obj->streamIsCancelled, 0);

    obj->isCompatible = kFALSE;
    obj->remoteAddress = kIpAddress_AnyV4();

    obj->control = &obj->channels[GO_CONTROL_CHANNEL_CONTROL];
    obj->control->port = GO_CONTROL_PORT_CONTROL;
    obj->control->parent = control;

    obj->upgrade = &obj->channels[GO_CONTROL_CHANNEL_UPGRADE];
    obj->upgrade->port = GO_CONTROL_PORT_UPGRADE;
    obj->upgrade->parent = control;

    return kOK;
}

GoFx(kStatus) GoControl_VRelease(GoControl control)
{
    kCheck(GoControl_Close(control));

    kCheck(kObject_VRelease(control));

    return kOK;
}

GoFx(kStatus) GoControl_SetRemoteAddress(GoControl control, kIpAddress address)
{
    kObj(GoControl, control);

    obj->remoteAddress = address;

    return kOK;
}

GoFx(kStatus) GoControl_SetCancelHandler(GoControl control, kCallbackFx function, kPointer receiver)
{
    kObj(GoControl, control);

    obj->onCancel.function = function;
    obj->onCancel.receiver = receiver;

    return kOK;
}

GoFx(kStatus) GoControl_SetControlPort(GoControl control, k32u port)
{
    kObj(GoControl, control);

    obj->control->port = port;

    return kOK;
}

GoFx(k32u) GoControl_ControlPort(GoControl control)
{
    kObj(GoControl, control);

    return obj->control->port;
}

GoFx(kStatus) GoControl_SetUpgradePort(GoControl control, k32u port)
{
    kObj(GoControl, control);

    obj->upgrade->port = port;

    return kOK;
}

GoFx(k32u) GoControl_UpgradePort(GoControl control)
{
    kObj(GoControl, control);

    return obj->upgrade->port;
}

GoFx(kStatus) GoControl_InitChannel(GoControl control, GoControlChannel* channel)
{
    kStatus exception = kOK;

    kTry
    {
        kTest(kTcpClient_Construct(&channel->client, kIP_VERSION_4, kObject_Alloc(control)));
        kTest(kTcpClient_SetCancelHandler(channel->client, GoControl_CancelHandler, channel));
        kTest(kTcpClient_SetReadBuffers(channel->client, -1, 4096));
        kTest(kSerializer_Construct(&channel->serializer, channel->client, kTypeOf(kDat6Serializer), kObject_Alloc(control)));

        kTest(kTimer_Construct(&channel->timer, kObject_Alloc(control)));
    }
    kCatch(&exception)
    {
        GoControl_Close(control);
        kEndCatch(exception);
    }

    return kOK;
}

GoFx(kStatus) GoControl_Open(GoControl control, kIpAddress address, k32u controlPort, k32u upgradePort)
{
    kObj(GoControl, control);
    kStatus status;
    k32u i;

    kCheck(GoControl_Close(control));

    kTry
    {
        // New connection being set up, so clear history of previous transactions.
        obj->busy = kFALSE;

        obj->remoteAddress = address;

        for (i = 0; i < GO_CONTROL_CHANNEL_COUNT; ++i)
        {
            GoControlChannel* channel = &obj->channels[i];

            kTest(GoControl_InitChannel(control, channel));
        }
        //set teh ports
        obj->channels[GO_CONTROL_CHANNEL_CONTROL].port = controlPort;
        obj->channels[GO_CONTROL_CHANNEL_UPGRADE].port = upgradePort;

        kTest(GoControl_ConnectChannel(control, obj->control));

        kTest(GoControl_DetectProtocolVersion(control, &obj->protocolVersion));

        obj->isCompatible = (kVersion_Major(obj->protocolVersion) == kVersion_Major(GoSdk_ProtocolVersion()));
    }
    kCatch(&status)
    {
        GoControl_Close(control);
        kEndCatch(status);
    }

    return kOK;
}

GoFx(kStatus) GoControl_DetectProtocolVersion(GoControl control, kVersion* version)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32u size;
    k32u reserved;
    k64u commandId64u;
    k64s status64s;
    k64u major64u, minor64u;

    kCheck(kTimer_Start(channel->timer, GO_CONTROL_GET_PROTOCOL_VERSION_OLD_TIMEOUT));

    kCheck(kSerializer_BeginWrite(channel->serializer, kTypeOf(k32u), kTRUE));
    kCheck(kSerializer_Write32u(channel->serializer, 0));
    kCheck(kSerializer_Write16u(channel->serializer, GO_CONTROL_GET_PROTOCOL_VERSION_OLD));
    kCheck(kSerializer_Write16u(channel->serializer, 0));
    kCheck(kSerializer_Write16u(channel->serializer, 0));
    kCheck(kSerializer_Write16u(channel->serializer, 0));

    kCheck(kSerializer_EndWrite(channel->serializer));
    kCheck(kSerializer_Flush(channel->serializer));

    kCheck(GoControl_Wait(control, channel));

    kCheck(kSerializer_Read32u(channel->serializer, &size));

    if (size == 40)
    {
        kCheck(kSerializer_Read32u(channel->serializer, &reserved));
        kCheck(kSerializer_Read64u(channel->serializer, &commandId64u));
        kCheck(kSerializer_Read64s(channel->serializer, &status64s));
        kCheck(kSerializer_Read64u(channel->serializer, &major64u));
        kCheck(kSerializer_Read64u(channel->serializer, &minor64u));

        *version = kVersion_Create((k32u) major64u, (k32u) minor64u, 0, 0);
    }
    else
    {
        kCheck(kSerializer_AdvanceRead(channel->serializer, size - 4));

        kCheck(GoControl_GetProtocolVersion(control, version));
    }

    return kOK;
}

GoFx(kStatus) GoControl_Close(GoControl control)
{
    kObj(GoControl, control);
    k32u i;

    for (i = 0; i < GO_CONTROL_CHANNEL_COUNT; ++i)
    {
        GoControlChannel* channel = &obj->channels[i];

        kCheck(kDestroyRef(&channel->timer));

        kCheck(kDestroyRef(&channel->serializer));
        kCheck(kDestroyRef(&channel->client));

        channel->isConnected = kFALSE;
    }

    return kOK;
}

GoFx(kBool) GoControl_IsConnected(GoControl control)
{
    kObj(GoControl, control);
    k32u i;

    //GoControl is considered connected if any channel is connected
    for (i = 0; i < GO_CONTROL_CHANNEL_COUNT; ++i)
    {
        GoControlChannel* channel = &obj->channels[i];
        if(channel->isConnected)
        {
            return kTRUE;
        }
    }
    return kFALSE;
}

GoFx(kStatus) GoControl_ConnectChannel(GoControl control, GoControlChannel* channel)
{
    kObj(GoControl, control);

    if (!channel->isConnected)
    {
        kCheck(kTcpClient_Connect(channel->client, obj->remoteAddress, channel->port, GO_CONTROL_CONNECT_TIMEOUT));
        channel->isConnected = kTRUE;
    }

    return kOK;
}

GoFx(kStatus) GoControl_BeginLegacyCommand(GoControl control, GoControlChannel* channel, k16u commandId, k64u timeout)
{
    kObj(GoControl, control);

    channel->commandId = commandId;

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

    kCheck(kSerializer_Reset(channel->serializer));

    kCheck(kSerializer_BeginWrite(channel->serializer, kTypeOf(k64u), kTRUE));
    kCheck(kSerializer_Write64u(channel->serializer, commandId));

    return kOK;
}

GoFx(kStatus) GoControl_BeginCommand(GoControl control, GoControlChannel* channel, k16u commandId, k64u timeout)
{
    kObj(GoControl, control);

    if (obj->busy)
    {
        kCheck(GoControl_Receive(control, channel));
        kCheck(GoControl_EndResponse(control, channel));
    }

    channel->commandId = commandId;

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

    kCheck(kSerializer_Reset(channel->serializer));

    kCheck(kSerializer_BeginWrite(channel->serializer, kTypeOf(k32u), kTRUE));
    kCheck(kSerializer_Write16u(channel->serializer, commandId));

    return kOK;
}

GoFx(kStatus) GoControl_CancelHandler(kPointer channel, kObject sender, kPointer args)
{
    GoControlChannel* channelObj = channel;
    kObjR(GoControl, channelObj->parent);

    if (kTimer_IsExpired(channelObj->timer))
    {
        return kERROR_TIMEOUT;
    }
    else if (obj->onCancel.function)
    {
        return obj->onCancel.function(obj->onCancel.receiver, channelObj->parent, kNULL);
    }

    return kOK;
}

GoFx(kStatus) GoControl_Send(GoControl control, GoControlChannel* channel)
{
    kObj(GoControl, control);

    kCheck(kSerializer_EndWrite(channel->serializer));
    kCheck(kSerializer_Flush(channel->serializer));

    obj->busy = kTRUE;

    return kOK;
}

GoFx(kStatus) GoControl_Wait(GoControl control, GoControlChannel* channel)
{
    kObj(GoControl, control);
    k64s remaining = kTimer_Remaining(channel->timer);
    kStatus result;

    do
    {
        k64u timeout = kMin_(remaining, GO_CONTROL_CANCEL_QUERY_INTERVAL);

        if (kSuccess(result = kTcpClient_Wait(channel->client, timeout)))
        {
            return kOK;
        }
        else if ((result == kERROR_TIMEOUT) && !kIsNull(obj->onCancel.function))
        {
            kCheck(obj->onCancel.function(obj->onCancel.receiver, control, kNULL));
        }
        else if (result != kERROR_TIMEOUT)
        {
            return result;
        }
    }
    while ((remaining = kTimer_Remaining(channel->timer)) > 0);

    return kERROR_TIMEOUT;
}

GoFx(kStatus) GoControl_Receive(GoControl control, GoControlChannel* channel)
{
    kObj(GoControl, control);
    k16u responseId;
    k32s responseStatus;

    kCheck(GoControl_Wait(control, channel));

    kCheck(kSerializer_BeginRead(channel->serializer, kTypeOf(k32u), kTRUE));
    obj->busy = kFALSE;

    kCheck(kSerializer_Read16u(channel->serializer, &responseId));
    kCheck(kSerializer_Read32s(channel->serializer, &responseStatus));

    kCheck(responseId == channel->commandId);
    if (!kSuccess(responseStatus))
    {
        kCheck(GoControl_EndResponse(control, channel));    //handles situations where there may be additional fields
    }

    kCheck(responseStatus);

    return kOK;
}

GoFx(kStatus) GoControl_LegacyReceive(GoControl control, GoControlChannel* channel)
{
    kObj(GoControl, control);
    k64u responseId;
    k64u responseStatus;

    kCheck(GoControl_Wait(control, channel));

    kCheck(kSerializer_BeginRead(channel->serializer, kTypeOf(k64u), kTRUE));
    obj->busy = kFALSE;

    kCheck(kSerializer_Read64u(channel->serializer, &responseId));
    kCheck(kSerializer_Read64u(channel->serializer, &responseStatus));

    kCheck(responseId == channel->commandId);
    if (!kSuccess((kStatus)responseStatus))
    {
        kCheck(GoControl_EndResponse(control, channel));    //handles situations where there may be additional fields
    }

    kCheck(responseStatus);

    return kOK;
}

GoFx(kStatus) GoControl_LegacySendAndReceive(GoControl control, GoControlChannel* channel)
{
    kCheck(GoControl_Send(control, channel));
    kCheck(GoControl_LegacyReceive(control, channel));
    return kOK;
}

GoFx(kStatus) GoControl_SendAndReceive(GoControl control, GoControlChannel* channel)
{
    kCheck(GoControl_Send(control, channel));
    kCheck(GoControl_Receive(control, channel));
    return kOK;
}

GoFx(kStatus) GoControl_EndResponse(GoControl control, GoControlChannel* channel)
{
    kCheck(kSerializer_EndRead(channel->serializer));
    return kOK;
}

GoFx(kStatus) GoControl_GetProtocolVersion(GoControl control, kVersion* version)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k8u major, minor;

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_VERSION, GO_CONTROL_GET_VERSION_TIMEOUT));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read8u(channel->serializer, &major));
    kCheck(kSerializer_Read8u(channel->serializer, &minor));

    *version = kVersion_Create(major, minor, 0, 0);

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_Login(GoControl control, GoUser user, const kChar* password)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 passwordText = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(kStrCopy(passwordText, kCountOf(passwordText), password));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_LOG_IN, GO_CONTROL_LOG_IN_TIMEOUT));

    kCheck(kSerializer_Write32s(channel->serializer, user));
    kCheck(kSerializer_WriteCharArray(channel->serializer, passwordText, kCountOf(passwordText)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ChangePassword(GoControl control, GoUser user, const kChar* password)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 passwordText = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(kStrCopy(passwordText, kCountOf(passwordText), password));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_CHANGE_PASSWORD, GO_CONTROL_CHANGE_PASSWORD_TIMEOUT));

    kCheck(kSerializer_Write32s(channel->serializer, user));
    kCheck(kSerializer_WriteCharArray(channel->serializer, passwordText, kCountOf(passwordText)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_RestoreFactory(GoControl control, kBool restoreAddress)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_RESTORE_FACTORY, GO_CONTROL_RESTORE_FACTORY_TIMEOUT));

    kCheck(kSerializer_Write8u(channel->serializer, (k8u) restoreAddress));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_Reset(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_RESET, GO_CONTROL_RESET_TIMEOUT));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ResetEncoder(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_RESET_ENCODER, GO_CONTROL_RESET_ENCODER_TIMEOUT));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_BeginUpgrade(GoControl control, void* data, kSize size)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->upgrade;

    // upgrade channel is connected on demand
    if (!(channel->isConnected) && kIsNull(channel->client))
    {
        kCheck(GoControl_InitChannel(control, channel));
    }

    kCheck(GoControl_ConnectChannel(control, channel));
    kCheck(GoControl_BeginLegacyCommand(control, channel, GO_CONTROL_BEGIN_UPGRADE, GO_CONTROL_BEGIN_UPGRADE_TIMEOUT));

    kCheck(kSerializer_Write64s(channel->serializer, (k64s) size));
    kCheck(kSerializer_WriteByteArray(channel->serializer, data, size));
    kCheck(GoControl_LegacySendAndReceive(control, channel));
    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetUpgradeStatus(GoControl control, kBool* complete, kBool* succeeded, k32s* progress)
{
    kObj(GoControl, control);
    k32s stage, prgrss;

    kCheck(GoControl_GetUpgradeStatusEx(control, &stage, &prgrss));

    *complete = (stage < 1 || stage == 2);
    *succeeded = (stage == 0);
    *progress = (k32s) prgrss;

    return kOK;
}

GoFx(kStatus) GoControl_GetUpgradeStatusEx(GoControl control, k32s* stage, k32s* progress)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->upgrade;
    k64s stg, prgrss;

    if (!(channel->isConnected) && kIsNull(channel->client))
    {
        kCheck(GoControl_InitChannel(control, channel));
    }

    // upgrade channel is connected on demand
    kCheck(GoControl_ConnectChannel(control, channel));

    kCheck(GoControl_BeginLegacyCommand(control, channel, GO_CONTROL_GET_UPGRADE_STATUS, GO_CONTROL_GET_UPGRADE_STATUS_TIMEOUT));
    kCheck(GoControl_LegacySendAndReceive(control, channel));

    kCheck(kSerializer_Read64s(channel->serializer, &stg));
    kCheck(kSerializer_Read64s(channel->serializer, &prgrss));

    *stage = (k32s) stg;
    *progress = (k32s) prgrss;

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_BeginAlignment(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_START_ALIGNMENT, GO_CONTROL_START_ALIGNMENT_TIMEOUT));

    kCheck(GoControl_Send(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_EndAlignment(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32s opId;

    kCheck(GoControl_Receive(control, channel));

    kCheck(kSerializer_Read32s(channel->serializer, &opId));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ClearAlignment(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_CLEAR_ALIGNMENT, GO_CONTROL_CLEAR_ALIGNMENT_TIMEOUT));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetAlignmentReference(GoControl control, GoAlignmentRef reference)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_ALIGNMENT_REFERENCE, GO_CONTROL_SET_ALIGNMENT_REFERENCE_TIMEOUT));
    kCheck(kSerializer_Write32s(channel->serializer, reference));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetAlignmentReference(GoControl control, GoAlignmentRef* reference)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText16 modeBuffer = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_ALIGNMENT_REFERENCE, GO_CONTROL_GET_ALIGNMENT_REFERENCE_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read32s(channel->serializer, reference));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_BeginExposureAutoSet(GoControl control, GoRole role)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_START_EXPOSURE_AUTO_SET, GO_CONTROL_START_EXPOSURE_AUTO_SET_TIMEOUT));
    kCheck(kSerializer_Write32s(channel->serializer, (k32s)role));

    kCheck(GoControl_Send(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_EndExposureAutoSet(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32s opId;

    kCheck(GoControl_Receive(control, channel));

    kCheck(kSerializer_Read32s(channel->serializer, &opId));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_BeginStart(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_START, GO_CONTROL_START_TIMEOUT));

    kCheck(GoControl_Send(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_EndStart(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheck(GoControl_Receive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_BeginScheduledStart(GoControl control, k64s value)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SCHEDULED_START, GO_CONTROL_SCHEDULED_START_TIMEOUT));
    kCheck(kSerializer_Write64s(channel->serializer, value));

    kCheck(GoControl_Send(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_EndScheduledStart(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheck(GoControl_Receive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_BeginStop(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_STOP, GO_CONTROL_STOP_TIMEOUT));
    kCheck(GoControl_Send(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_EndStop(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheck(GoControl_Receive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_BeginSnapshot(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SNAPSHOT, GO_CONTROL_SNAPSHOT_TIMEOUT));

    kCheck(GoControl_Send(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_EndSnapshot(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheck(GoControl_Receive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ReadFileList(GoControl control, kArrayList files, const kChar* extensionFilter)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32u count;
    kText64 filter = { 0 };
    kText64 name = { 0 };
    k32u i;

    kCheckState(obj->isCompatible);

    kCheck(kArrayList_Allocate(files, kTypeOf(kText64), 0));

    if (extensionFilter)
    {
        kCheck(kStrCopy(filter, sizeof(filter), extensionFilter));
    }

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_FILE_LIST, GO_CONTROL_GET_FILE_LIST_TIMEOUT));
    kCheck(kSerializer_WriteCharArray(channel->serializer, filter, kCountOf(filter)));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(kSerializer_Read32u(channel->serializer, &count));

    for (i = 0; i < count; ++i)
    {
        kCheck(kSerializer_ReadCharArray(channel->serializer, name, kCountOf(name)));
        kCheck(kArrayList_AddT(files, &name));
    }

    kCheck(GoControl_EndResponse(control, channel));


    return kOK;
}

GoFx(kStatus) GoControl_ReadFile(GoControl control, const kChar* fileName, kByte** data, kSize* size, kAlloc allocator)
{
    kObj(GoControl, control);
    kAlloc alloc = kAlloc_Fallback(allocator);
    GoControlChannel* channel = obj->control;
    kText64 name = { 0 };
    kByte* output = kNULL;
    k32u temp;
    kSize fileSize;
    kStatus status;

    kCheckState(obj->isCompatible);

    kTry
    {
        kTest(kStrCopy(name, kCountOf(name), fileName));

        kTest(GoControl_BeginCommand(control, channel, GO_CONTROL_READ_FILE, GO_CONTROL_READ_FILE_TIMEOUT));
        kTest(kSerializer_WriteCharArray(channel->serializer, name, kCountOf(name)));

        kTest(GoControl_SendAndReceive(control, channel));

        kTest(kSerializer_Read32u(channel->serializer, &temp));
        fileSize = (kSize)temp;

        kTest(kAlloc_Get(alloc, fileSize, &output));

        kTest(kSerializer_ReadByteArray(channel->serializer, output, fileSize));

        kTest(GoControl_EndResponse(control, channel));

        *data = output;
        *size = fileSize;
    }
    kCatch(&status)
    {
        kAlloc_Free(alloc, output);
        kEndCatch(status);
    }

    return kOK;
}

GoFx(kStatus) GoControl_ClearLog(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_CLEAR_LOG, GO_CONTROL_DEFAULT_TIMEOUT));

    kCheck(GoControl_Send(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_WriteFile(GoControl control, const kChar* fileName, const kByte* data, kSize size)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 name = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(kStrCopy(name, kCountOf(name), fileName));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_WRITE_FILE, GO_CONTROL_WRITE_FILE_TIMEOUT));
    kCheck(kSerializer_WriteCharArray(channel->serializer, name, kCountOf(name)));
    kCheck(kSerializer_Write32u(channel->serializer, (k32u)size));
    kCheck(kSerializer_WriteByteArray(channel->serializer, data, size));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_CopyFile(GoControl control, const kChar* source, const kChar* destination)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 srcName = { 0 };
    kText64 destName = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(kStrCopy(srcName, kCountOf(srcName), source));
    kCheck(kStrCopy(destName, kCountOf(destName), destination));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_COPY_FILE, GO_CONTROL_COPY_FILE_TIMEOUT));
    kCheck(kSerializer_WriteCharArray(channel->serializer, srcName, kCountOf(srcName)));
    kCheck(kSerializer_WriteCharArray(channel->serializer, destName, kCountOf(destName)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_DeleteFile(GoControl control, const kChar* fileName)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 name = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(kStrCopy(name, kCountOf(name), fileName));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_DELETE_FILE, GO_CONTROL_DELETE_FILE_TIMEOUT));
    kCheck(kSerializer_WriteCharArray(channel->serializer, name, kCountOf(name)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetUserStorageFree(GoControl control, k64u* spaceAvailable)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k64u temp = 0;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_USER_STORAGE_FREE, GO_CONTROL_USER_STORAGE_FREE_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read64u(channel->serializer, &temp));
    *spaceAvailable = temp;

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetUserStorageUsed(GoControl control, k64u* spaceUsed)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k64u temp = 0;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_USER_STORAGE_USED, GO_CONTROL_USER_STORAGE_USED_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read64u(channel->serializer, &temp));
    *spaceUsed = temp;

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetDefaultJob(GoControl control, kChar* fileName, kSize capacity)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 name = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_DEFAULT_JOB, GO_CONTROL_GET_DEFAULT_JOB_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_ReadCharArray(channel->serializer, name, kCountOf(name)));
    kCheck(kStrCopy(fileName, capacity, name));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetDefaultJob(GoControl control, const kChar* fileName)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 name = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(kStrCopy(name, kCountOf(name), fileName));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_DEFAULT_JOB, GO_CONTROL_SET_DEFAULT_JOB_TIMEOUT));
    kCheck(kSerializer_WriteCharArray(channel->serializer, name, kCountOf(name)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetLoadedJob(GoControl control, kChar* fileName, kSize capacity, kBool* isModified)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 name = { 0 };
    k8u modified;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_LOADED_JOB, GO_CONTROL_GET_LOADED_FILE_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_ReadCharArray(channel->serializer, name, kCountOf(name)));
    kCheck(kStrCopy(fileName, capacity, name));

    kCheck(kSerializer_Read8u(channel->serializer, &modified));
    *isModified = (kBool) modified;

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetSensorInfo(GoControl control, GoSensorInfo localInfo, kArrayList remoteInfoList)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32s i, remoteCount;
    kStatus status = kOK;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_SYSTEM_INFO, GO_CONTROL_GET_SYSTEM_INFO_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));


    //
    kCheck(GoSensorInfo_Read(localInfo, channel->serializer));

    kCheck(kSerializer_Read32s(channel->serializer, &remoteCount));

    kCheck(kArrayList_Purge(remoteInfoList));

    for (i = 0; i < remoteCount; i++)
    {
        GoSensorInfo remoteInfo = kNULL;

        kTry
        {
            kTest(GoSensorInfo_Construct(&remoteInfo, kObject_Alloc(control)));
            kTest(GoSensorInfo_Read(remoteInfo, channel->serializer));
            kTest(kArrayList_AddT(remoteInfoList, &remoteInfo));
        }
        kCatch(&status)
        {
            kDestroyRef(&remoteInfo);
            kEndCatch(status);
        }
    }

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetSensorInfoV2(GoControl control, GoSensorInfo localInfo, kArrayList remoteInfoList, kArrayList buddyInfoList)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32u i, remoteCount, buddyCount;
    k16u localInfoSize, remoteInfoSize, buddyInfoSize;
    kStatus status = kOK;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_SYSTEM_INFO_2, GO_CONTROL_GET_SYSTEM_INFO_2_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read16u(channel->serializer, &localInfoSize));

    //read info
    kCheck(GoSensorInfo_ReadV2(localInfo, channel->serializer, localInfoSize, kTRUE));

    kCheck(kSerializer_Read32u(channel->serializer, &remoteCount));

    kCheck(kSerializer_Read16u(channel->serializer, &remoteInfoSize));

    kCheck(kArrayList_Purge(remoteInfoList));

    for (i = 0; i < remoteCount; i++)
    {
        GoSensorInfo remoteInfo = kNULL;

        kTry
        {
            kTest(GoSensorInfo_Construct(&remoteInfo, kObject_Alloc(control)));
            kTest(GoSensorInfo_ReadV2(remoteInfo, channel->serializer, remoteInfoSize, kFALSE));
            kTest(kArrayList_AddT(remoteInfoList, &remoteInfo));
        }
        kCatch(&status)
        {
            kDestroyRef(&remoteInfo);
            kEndCatch(status);
        }
    }

    kCheck(kSerializer_Read32u(channel->serializer, &buddyCount));

    kCheck(kSerializer_Read16u(channel->serializer, &buddyInfoSize));

    kCheck(kArrayList_Purge(buddyInfoList));

    if (!kIsNull(buddyInfoList) && buddyCount > 0)
    {
        GoBuddyInfo buddyInfo;
        for (i = 0; i < buddyCount; i++)
        {
            k32u bytesRead = 8;
            kCheck(kSerializer_Read32u(channel->serializer, &buddyInfo.id));
            kCheck(kSerializer_Read32s(channel->serializer, &buddyInfo.state));
            kCheck(kArrayList_AddT(buddyInfoList, &buddyInfo));

            kCheck(kSerializer_AdvanceRead(channel->serializer, buddyInfoSize - bytesRead));
        }
    }

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kVersion) GoControl_ProtocolVersion(GoControl control)
{
    kObj(GoControl, control);
    return obj->protocolVersion;
}

GoFx(kBool) GoControl_IsCompatible(GoControl control)
{
    kObj(GoControl, control);
    return obj->isCompatible;
}

GoFx(kStatus) GoControl_GetStates(GoControl control, GoStates* states)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32u itemCount;
    k32u temp32u;

    kCheckState(obj->isCompatible);

    // Important to zero because some fields may be skipped due to compat logic.
    kCheck(kMemZero(states, sizeof(GoStates)));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_STATES, GO_CONTROL_GET_STATES_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    //read states
    kCheck(kSerializer_Read32u(channel->serializer, &itemCount));     //item count
    kCheck(kSerializer_Read32s(channel->serializer, &states->sensorState));
    kCheck(kSerializer_Read32s(channel->serializer, &states->loginType));
    kCheck(kSerializer_Read32s(channel->serializer, &states->alignmentReference));
    kCheck(kSerializer_Read32s(channel->serializer, &states->alignmentState));
    kCheck(kSerializer_Read32s(channel->serializer, &states->recordingEnabled));
    kCheck(kSerializer_Read32s(channel->serializer, &states->playbackSource));
    kCheck(kSerializer_Read32u(channel->serializer, &states->uptimeSec));
    kCheck(kSerializer_Read32u(channel->serializer, &states->uptimeMicrosec));
    kCheck(kSerializer_Read32u(channel->serializer, &states->playbackPos));
    kCheck(kSerializer_Read32u(channel->serializer, &states->playbackCount));

    kCheck(kSerializer_Read32u(channel->serializer, &temp32u));
    states->autoStartEnabled = (kBool)temp32u;

    if (itemCount > 11) // backwards compatibility support
    {
        kCheck(kSerializer_Read32u(channel->serializer, &temp32u));
        states->isAccelerator = (kBool)temp32u;
    }

    if (itemCount > 12) // backwards compatibility support
    {
        kCheck(kSerializer_Read32u(channel->serializer, &temp32u));
        states->voltage = (GoVoltageSetting)temp32u;
    }

    if (itemCount > 13) // backwards compatibility support
    {
        kCheck(kSerializer_Read32u(channel->serializer, &temp32u));
        states->cableLength = temp32u;
    }

    if (itemCount > 14) // Backwards compatibility for quickedit state
    {
        kCheck(kSerializer_Read32u(channel->serializer, &temp32u));
        states->quickEditEnabled = (kBool)temp32u;
    }

    if (itemCount > 15) // Backwards compatibility for security state
    {
        kCheck(kSerializer_Read32u(channel->serializer, &temp32u));
        states->security = (k32s)temp32u;
    }

    if (itemCount > 16) // Backwards compatibility for branding type
    {
        kCheck(kSerializer_Read32u(channel->serializer, &temp32u));
        states->brandingType = (k32s)temp32u;
    }

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetScanMode(GoControl control, GoMode* mode)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText16 modeBuffer = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_MODE, GO_CONTROL_GET_MODE_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read32s(channel->serializer, mode));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ChangeBuddy(GoControl control, kBool add, k32u buddyId)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_CHANGE_BUDDY, GO_CONTROL_CHANGE_BUDDY_TIMEOUT));

    if (add)
    {
        kCheck(kSerializer_Write32u(channel->serializer, buddyId));
    }
    else
    {
        kCheck(kSerializer_Write32u(channel->serializer, 0));
    }

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_AssignBuddies(GoControl control, const k32u* buddyIds, kSize count)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kSize index;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_ASSIGN_BUDDIES, GO_CONTROL_ASSIGN_BUDDIES_TIMEOUT));

    kCheck(kSerializer_Write32u(channel->serializer, (k32u) count));

    for (index = 0; index < count; index++)
    {
        kCheck(kSerializer_Write32u(channel->serializer, buddyIds[index]));
    }

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_RemoveBuddiesById(GoControl control, const k32u* buddyIds, kSize count)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kSize index;

    kCheckState(obj->isCompatible);
    kCheckArgs(count > 0);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_REMOVE_BUDDIES_BY_ID, GO_CONTROL_REMOVE_BUDDIES_TIMEOUT));

    kCheck(kSerializer_Write32u(channel->serializer, (k32u)count));

    for (index = 0; index < count; index++)
    {
        kCheck(kSerializer_Write32u(channel->serializer, buddyIds[index]));
    }

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_RemoveBuddies(GoControl control, const k32u* buddyIndices, kSize count)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kSize index;

    kCheckState(obj->isCompatible);
    kCheckArgs(count > 0);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_REMOVE_BUDDIES, GO_CONTROL_REMOVE_BUDDIES_TIMEOUT));

    kCheck(kSerializer_Write32u(channel->serializer, (k32u) count));

    for (index = 0; index < count; index++)
    {
        kCheck(kSerializer_Write32u(channel->serializer, buddyIndices[index]));
    }

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetTimestamp(GoControl control, k64u* time)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_TIME_STAMP, GO_CONTROL_GET_TIME_STAMP_TIMEOUT));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read64u(channel->serializer, time));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetEncoder(GoControl control, k64s* encoder)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_ENCODER, GO_CONTROL_GET_ENCODER_TIMEOUT));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read64s(channel->serializer, encoder));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_Trigger(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_TRIGGER, GO_CONTROL_TRIGGER_TIMEOUT));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_Backup(GoControl control, kByte** fileData, kSize* size, kAlloc allocator)
{
    kObj(GoControl, control);
    kAlloc alloc = kAlloc_Fallback(allocator);
    GoControlChannel* channel = obj->control;
    kByte* output = kNULL;
    k32u temp;
    kSize fileSize;
    kStatus status;

    kCheckState(obj->isCompatible);

    kTry
    {
        kTest(GoControl_BeginCommand(control, channel, GO_CONTROL_BACKUP, GO_CONTROL_BACKUP_TIMEOUT));
        kTest(GoControl_SendAndReceive(control, channel));

        kTest(kSerializer_Read32u(channel->serializer, &temp));
        fileSize = (kSize)temp;

        kTest(kAlloc_Get(alloc, fileSize, &output));

        kTest(kSerializer_ReadByteArray(channel->serializer, output, fileSize));

        kTest(GoControl_EndResponse(control, channel));

        *fileData = output;
        *size = fileSize;
    }
    kCatch(&status)
    {
        kAlloc_Free(alloc, output);
        kEndCatch(status);
    }

    return kOK;
}

GoFx(kStatus) GoControl_Restore(GoControl control, const kByte* fileData, kSize size)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_RESTORE, GO_CONTROL_RESTORE_TIMEOUT));
    kCheck(kSerializer_Write32u(channel->serializer, (k32u)size));
    kCheck(kSerializer_WriteByteArray(channel->serializer, fileData, size));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ScheduleDigital(GoControl control, k16u index, k64s target, k8u value)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SCHEDULE_DIGITAL, GO_CONTROL_SCHEDULE_DIGITAL_TIMEOUT));
    kCheck(kSerializer_Write16u(channel->serializer, index));
    kCheck(kSerializer_Write64s(channel->serializer, target));
    kCheck(kSerializer_Write8u(channel->serializer, value));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ScheduleAnalog(GoControl control, k16u index, k64s target, k32s value)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SCHEDULE_ANALOG, GO_CONTROL_SCHEDULE_ANALOG_TIMEOUT));
    kCheck(kSerializer_Write16u(channel->serializer, index));
    kCheck(kSerializer_Write64s(channel->serializer, target));
    kCheck(kSerializer_Write32s(channel->serializer, value));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetRecordingEnabled(GoControl control, kBool enable)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_RECORDING_ENABLED, GO_CONTROL_DEFAULT_TIMEOUT));
    kCheck(kSerializer_Write8u(channel->serializer, (k8u)enable));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetRecordingEnabled(GoControl control, kBool* enabled)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k8u tempVal;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_RECORDING_ENABLED, GO_CONTROL_DEFAULT_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(kSerializer_Read8u(channel->serializer, &tempVal));

    kCheck(GoControl_EndResponse(control, channel));

    if (tempVal == 1)
    {
        *enabled = kTRUE;
    }
    else
    {
        *enabled = kFALSE;
    }

    return kOK;
}

GoFx(kStatus) GoControl_SetInputSource(GoControl control, GoDataSource source)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_INPUT_SOURCE, GO_CONTROL_SET_INPUT_SOURCE_TIMEOUT));
    kCheck(kSerializer_Write32s(channel->serializer, source));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetInputSource(GoControl control, GoInputSource* source)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_INPUT_SOURCE, GO_CONTROL_GET_INPUT_SOURCE_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(kSerializer_Read32s(channel->serializer, source));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ClearReplayData(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_CLEAR_REPLAY_DATA, GO_CONTROL_DEFAULT_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_Simulate(GoControl control, kBool* isBufferValid)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k8u bufferValid;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SIMULATE, GO_CONTROL_DEFAULT_TIMEOUT));
    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(kSerializer_Read8u(channel->serializer, &bufferValid));
    kCheck(GoControl_EndResponse(control, channel));

    if (bufferValid == 0)
    {
        *isBufferValid = kFALSE;
    }
    else
    {
        *isBufferValid = kTRUE;
    }

    return kOK;
}

GoFx(kStatus) GoControl_ClearMeasurementStats(GoControl control)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_CLEAR_MEASUREMENT_STATS, GO_CONTROL_DEFAULT_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_PlaybackSeek(GoControl control, kSize position)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_PLAYBACK_SEEK, GO_CONTROL_DEFAULT_TIMEOUT));
    kCheck(kSerializer_Write32u(channel->serializer, (k32u)position));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_PlaybackStep(GoControl control, GoSeekDirection direction)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_STEP_PLAYBACK, GO_CONTROL_DEFAULT_TIMEOUT));
    kCheck(kSerializer_Write32s(channel->serializer, direction));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_PlaybackPosition(GoControl control, kSize* position, kSize* count)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32u temp;
    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_PLAYBACK_POSITION, GO_CONTROL_PLAYBACK_POSITION_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(kSerializer_Read32u(channel->serializer, &temp));
    *position = (kSize)temp;

    kCheck(kSerializer_Read32u(channel->serializer, &temp));
    *count = (kSize)temp;

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ReadChunkedData(GoControl control, const kChar* dstFileName)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kMemory outputBuffer = kNULL;
    kByte* currentChunkData = kNULL;
    k32u temp, currentProgress;
    kSize chunkDataSize = 0;
    k32u chunkSize;
    k16u chunkType;
    k32s chunkStatus;
    k32u dataSize;
    kBool bufferInitialized = kFALSE;
    kFile file = kNULL;
    kBool done = kFALSE;

    kTry
    {
        kTest(kMemory_Construct(&outputBuffer, kObject_Alloc(control)));

        while (!done)
        {
            kTest(kSerializer_Read32u(channel->serializer, &chunkSize));
            kTest(kSerializer_Read16u(channel->serializer, &chunkType));
            kTest(kSerializer_Read32s(channel->serializer, &chunkStatus));
            kTest(kSerializer_Read32u(channel->serializer, &temp));
            kTest(kSerializer_Read32u(channel->serializer, &currentProgress));
            kTest(kSerializer_Read32u(channel->serializer, &dataSize));

            kTestArgs(chunkType == GO_CONTROL_CONTINUE && chunkStatus == kOK);

            if (dataSize == 0)
            {
                done = kTRUE;
                kTest(kAlloc_FreeRef(kObject_Alloc(control), &currentChunkData));
            }
            else
            {
                if (!bufferInitialized)
                {
                    chunkDataSize = (kSize)dataSize;
                    kTest(kMemory_Allocate(outputBuffer, chunkDataSize));
                    kTest(kAlloc_Get(kObject_Alloc(control), chunkDataSize, &currentChunkData));

                    bufferInitialized = kTRUE;
                }

                if (chunkDataSize != (kSize)dataSize)
                {
                    kTest(kAlloc_FreeRef(kObject_Alloc(control), &currentChunkData));
                    chunkDataSize = (kSize)dataSize;
                    kTest(kAlloc_Get(kObject_Alloc(control), chunkDataSize, &currentChunkData));
                }

                kTest(kSerializer_ReadByteArray(channel->serializer, currentChunkData, chunkDataSize));
                kTest(kStream_Write(outputBuffer, currentChunkData, chunkDataSize));
            }
        }

        kTest(kFile_Construct(&file, dstFileName, kFILE_MODE_WRITE, kObject_Alloc(control)));
        kTest(kStream_Write(file, kMemory_At(outputBuffer, 0), (kSize)kMemory_Length(outputBuffer)));
        kTest(kFile_Close(file));
    }
    kFinally
    {
        kAlloc_FreeRef(kObject_Alloc(control), &currentChunkData);
        kDestroyRef(&file);
        kDestroyRef(&outputBuffer);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoControl_ReadChunkedDataStream(kPointer context)
{
    GoControlChunkedDataStreamInfo* info = (GoControlChunkedDataStreamInfo*)context;
    GoControl control = xGoControl_Cast(info->control);
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kByte* currentChunkData = kNULL;
    k32u temp, currentProgress;
    kSize chunkDataSize = 0;
    k32u chunkSize;
    k16u chunkType;
    k32s chunkStatus;
    k32u dataSize;
    kBool bufferInitialized = kFALSE;
    kFile file = kNULL;
    kBool done = kFALSE;
    kAlloc alloc = kObject_Alloc(control);

    kTry
    {
        kTest(kFile_Construct(&file, info->destPath, kFILE_MODE_WRITE, alloc));

        while (!done)
        {
            kTest(kSerializer_Read32u(channel->serializer, &chunkSize));
            kTest(kSerializer_Read16u(channel->serializer, &chunkType));
            kTest(kSerializer_Read32s(channel->serializer, &chunkStatus));
            kTest(kSerializer_Read32u(channel->serializer, &temp));
            kTest(kSerializer_Read32u(channel->serializer, &currentProgress));
            kTest(kSerializer_Read32u(channel->serializer, &dataSize));

            kTestArgs(chunkType == GO_CONTROL_CONTINUE && chunkStatus == kOK);

            if (chunkDataSize != (kSize)dataSize)
            {
                kTest(kAlloc_FreeRef(alloc, &currentChunkData));
                chunkDataSize = (kSize)dataSize;
                kTest(kAlloc_Get(alloc, chunkDataSize, &currentChunkData));
            }

            kTest(kSerializer_ReadByteArray(channel->serializer, currentChunkData, chunkDataSize));
            kTest(kStream_Write(file, currentChunkData, chunkDataSize));

            done = GoControl_IsStreamingStopped(control);
        }

        kTest(kSerializer_Flush(channel->serializer));

        kTest(kFile_Close(file));
    }
    kFinally
    {
        kAlloc_FreeRef(alloc, &currentChunkData);
        kAlloc_Free(alloc, context);
        kDestroyRef(&file);
        kEndFinally();
    }

    return kOK;
}

GoFx(kStatus) GoControl_StartStreamingChunkedData(GoControl control, const kChar* dstFileName)
{
    kObj(GoControl, control);
    kStatus exception = kOK;
    kAlloc alloc = kObject_Alloc(control);
    GoControlChunkedDataStreamInfo* context = kNULL;

    kTry
    {
        kTest(kThread_Construct(&obj->streamThread, kObject_Alloc(control)));
        kTest(kAlloc_Get(alloc, sizeof(GoControlChunkedDataStreamInfo), &context));

        context->control = control;
        kTest(kStrCopy(context->destPath, sizeof(context->destPath), dstFileName));

        kAtomic32s_Exchange(&obj->streamIsCancelled, kFALSE);

        kTest(kThread_Start(obj->streamThread, GoControl_ReadChunkedDataStream, (kPointer)context)); // context will get freed by GoControl_ReadChunkedDataStream
    }
    kCatch(&exception)
    {
        kAtomic32s_Exchange(&obj->streamIsCancelled, kTRUE);
        if (!kIsNull(context)) { kAlloc_Free(alloc, context); }
        kDestroyRef(&obj->streamThread);
        kEndCatch(exception);
    }

    return kOK;
}

GoFx(kStatus) GoControl_StopStreaming(GoControl control)
{
    kObj(GoControl, control);
    kAlloc alloc = kObject_Alloc(control);

    kAtomic32s_Exchange(&obj->streamIsCancelled, kTRUE);

    // wait until the thread actually shuts down
    kCheck(kThread_Join(obj->streamThread, kINFINITE, kNULL));

    // clean up the thread
    kDestroyRef(&obj->streamThread);

    return kOK;
}

GoFx(kBool) GoControl_IsStreamingStopped(GoControl control)
{
    kObj(GoControl, control);

    return kAtomic32s_Get(&obj->streamIsCancelled);
}

GoFx(kStatus) GoControl_ExportBitmap(GoControl control,
                                     GoReplayExportSourceType type,
                                     GoDataSource source,
                                     const kChar* dstFileName)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32u temp, currentProgress;

    kCheckState(obj->isCompatible);

    if (type == GO_REPLAY_EXPORT_SOURCE_PRIMARY)
    {
        return kERROR;
    }

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_EXPORT_BITMAP, GO_CONTROL_EXPORT_BITMAP_TIMEOUT)); //no timeout
    kCheck(kSerializer_Write32s(channel->serializer, type));
    kCheck(kSerializer_Write32s(channel->serializer, source));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read32u(channel->serializer, &temp));
    kCheck(kSerializer_Read32u(channel->serializer, &currentProgress));

    kCheck(GoControl_EndResponse(control, channel));

    kCheck(GoControl_ReadChunkedData(control, dstFileName));

    return kOK;
}

GoFx(kStatus) GoControl_ReadFileStreamed(GoControl control, const kChar* srcFileName, const kChar* dstFileName, kBool continuous)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32u temp, currentProgress;
    kText64 name;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_READ_FILE_STREAMED, GO_CONTROL_READ_FILE_STREAMED_TIMEOUT)); //no timeout
    kCheck(kStrCopy(name, kCountOf(name), srcFileName));
    kCheck(kSerializer_WriteCharArray(channel->serializer, name, kCountOf(name)));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read32u(channel->serializer, &temp)); //progress total
    kCheck(kSerializer_Read32u(channel->serializer, &currentProgress));

    kCheck(GoControl_EndResponse(control, channel));

    if (continuous)
    {
        kCheck(GoControl_StartStreamingChunkedData(control, dstFileName));
    }
    else
    {
        kCheck(GoControl_ReadChunkedData(control, dstFileName));
    }

    return kOK;
}

GoFx(kStatus) GoControl_ExportCsv(GoControl control, const kChar* dstFileName)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32u temp, currentProgress;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_EXPORT_CSV, GO_CONTROL_EXPORT_CSV_TIMEOUT)); //no timeout
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read32u(channel->serializer, &temp)); //progress total
    kCheck(kSerializer_Read32u(channel->serializer, &currentProgress));

    kCheck(GoControl_EndResponse(control, channel));

    kCheck(GoControl_ReadChunkedData(control, dstFileName));

    return kOK;
}

GoFx(kStatus) GoControl_SetAutoStartEnabled(GoControl control, kBool enable)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_AUTOSTART_ENABLED, GO_CONTROL_SET_AUTOSTART_ENABLED_TIMEOUT));
    kCheck(kSerializer_Write8u(channel->serializer, (k8u)enable));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetAutoStartEnabled(GoControl control, kBool* enabled)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 name = { 0 };
    k8u tempEnable = kFALSE;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_AUTOSTART_ENABLED, GO_CONTROL_GET_AUTOSTART_ENABLED_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read8u(channel->serializer, &tempEnable));

    if (tempEnable != 0)
    {
        *enabled = kTRUE;
    }
    else
    {
        *enabled = kFALSE;
    }

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetVoltage(GoControl control, GoVoltageSetting voltage, k32u cableLength)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_VOLTAGE, GO_CONTROL_SET_VOLTAGE_TIMEOUT));
    kCheck(kSerializer_Write16u(channel->serializer, voltage));
    kCheck(kSerializer_Write32u(channel->serializer, cableLength));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetVoltage(GoControl control, GoVoltageSetting *voltage, k32u *cableLength)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    k16u v;
    k32u cl;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_VOLTAGE, GO_CONTROL_GET_VOLTAGE_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read16u(channel->serializer, &v));
    kCheck(kSerializer_Read32u(channel->serializer, &cl));

    kCheck(GoControl_EndResponse(control, channel));

    if (voltage) *voltage = v;
    if (cableLength) *cableLength = cl;

    return kOK;
}

GoFx(kStatus) GoControl_SetQuickEditEnabled(GoControl control, kBool enable)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_QUICK_EDIT_ENABLED, GO_CONTROL_DEFAULT_TIMEOUT));
    kCheck(kSerializer_Write8u(channel->serializer, (k8u)enable));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetQuickEditEnabled(GoControl control, kBool* enabled)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 name = { 0 };
    k8u tempEnable = kFALSE;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_QUICK_EDIT_ENABLED, GO_CONTROL_DEFAULT_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read8u(channel->serializer, &tempEnable));

    if (tempEnable != 0)
    {
        *enabled = kTRUE;
    }
    else
    {
        *enabled = kFALSE;
    }

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_CreateModel(GoControl control, const kChar* name)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 fileName = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_CREATE_MODEL, GO_CONTROL_CREATE_MODEL_TIMEOUT));

    kCheck(kStrCopy(fileName, sizeof(fileName), name));
    kCheck(kSerializer_WriteCharArray(channel->serializer, fileName, 64));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_DetectModelEdges(GoControl control, const kChar* name, k16u sensitivity)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 fileName = { 0 };

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_DETECT_MODEL_EDGES, GO_CONTROL_DETECT_MODEL_EDGES_TIMEOUT));

    kCheck(kStrCopy(fileName, sizeof(fileName), name));
    kCheck(kSerializer_WriteCharArray(channel->serializer, fileName, 64));
    kCheck(kSerializer_Write16u(channel->serializer, sensitivity));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_ListDirectory(GoControl control, const kChar* extension, const kChar* root, kBool isRecursive, kArrayList fileList)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 textString = { 0 };
    kText64 filter = { 0 };
    k32u fileNameCount;
    kSize i;

    kCheckState(obj->isCompatible);

    kCheck(kArrayList_Allocate(fileList, kTypeOf(kText64), 0));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_LIST_DIRECTORY, GO_CONTROL_LIST_DIRECTORY_TIMEOUT));

    if (extension) {
        kCheck(kStrCopy(filter, sizeof(filter), extension));
    }
    kCheck(kSerializer_WriteCharArray(channel->serializer, filter, kCountOf(filter)));

    kCheck(kStrCopy(textString, sizeof(textString), root));
    kCheck(kSerializer_WriteCharArray(channel->serializer, textString, 64));

    kCheck(kSerializer_Write8u(channel->serializer, (k8u)isRecursive));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read32u(channel->serializer, &fileNameCount));

    for (i = 0; i < fileNameCount; i++)
    {
        kCheck(kSerializer_ReadCharArray(channel->serializer, textString, 64));
        kCheck(kArrayList_AddT(fileList, &textString));
    }

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetIsEmulator(GoControl control, kBool value)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_IS_EMULATOR, GO_CONTROL_SET_IS_EMULATOR_TIMEOUT));
    kCheck(kSerializer_Write8u(channel->serializer, (k8u)value));
    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetReplayProtectionEnabled(GoControl control, kBool enable)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_REPLAY_PROTECTION_ENABLED, GO_CONTROL_SET_REPLAY_PROTECTION_ENABLED_TIMEOUT));
    kCheck(kSerializer_Write8u(channel->serializer, (k8u)enable));
    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetReplayProtectionEnabled(GoControl control, kBool* enabled)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 name = { 0 };
    k8u tempEnable = kFALSE;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_REPLAY_PROTECTION_ENABLED, GO_CONTROL_GET_REPLAY_PROTECTION_ENABLED_TIMEOUT));
    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(kSerializer_Read8u(channel->serializer, &tempEnable));
    kCheck(GoControl_EndResponse(control, channel));

    *enabled = !!tempEnable;

    return kOK;
}

GoFx(kStatus) GoControl_AddTool(GoControl control, const kChar* type, const kChar* name)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 tempText;

    kCheckState(obj->isCompatible);
    kCheck(kStrCopy(tempText, kCountOf(tempText), type));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_ADD_TOOL, GO_CONTROL_ADD_TOOL_TIMEOUT));

    kCheck(kSerializer_WriteCharArray(channel->serializer, tempText, kCountOf(tempText)));
    kCheck(kStrCopy(tempText, kCountOf(tempText), name));
    kCheck(kSerializer_WriteCharArray(channel->serializer, tempText, kCountOf(tempText)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_AddMeasurement(GoControl control, kSize index, const kChar* type, const kChar* name)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 tempText;

    kCheckState(obj->isCompatible);
    kCheck(kStrCopy(tempText, kCountOf(tempText), type));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_ADD_MEASUREMENT, GO_CONTROL_ADD_MEASUREMENT_TIMEOUT));

    kCheck(kSerializer_Write32u(channel->serializer, (k32u)index));
    kCheck(kSerializer_WriteCharArray(channel->serializer, tempText, kCountOf(tempText)));

    kCheck(kStrCopy(tempText, kCountOf(tempText), name));
    kCheck(kSerializer_WriteCharArray(channel->serializer, tempText, kCountOf(tempText)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetRamImageEnabled(GoControl control, kBool enable)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);
    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_ENABLE_RAM_IMAGE, GO_CONTROL_ENABLE_RAM_IMAGE_TIMEOUT));

    kCheck(kSerializer_Write8u(channel->serializer, (k8u)enable));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetRamImageEnabled(GoControl control, kBool* enabled)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k8u tempEnable;

    kCheckState(obj->isCompatible);
    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_RAM_IMAGE_ENABLED, GO_CONTROL_ENABLE_RAM_IMAGE_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read8u(channel->serializer, &tempEnable));

    if (tempEnable != 0)
    {
        *enabled = kTRUE;
    }
    else
    {
        *enabled = kFALSE;
    }

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_WriteRamImage(GoControl control, GoRole role, kSize cameraIndex, kSize stateIndex, kSize frameIndex, kImage image)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kImage tempImage = kNULL;
    k32s pixelType = 0;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_WRITE_RAM_IMAGE, GO_CONTROL_WRITE_RAM_IMAGE_TIMEOUT));

    kCheck(kSerializer_Write32s(channel->serializer, role));
    kCheck(kSerializer_Write32u(channel->serializer, (k32u)cameraIndex));
    kCheck(kSerializer_Write32u(channel->serializer, (k32u)stateIndex));
    kCheck(kSerializer_Write32u(channel->serializer, (k32u)frameIndex));
    kCheck(kSerializer_WriteObject(channel->serializer, image));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_RuntimeVariableCount(GoControl control, kSize* count)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    k32u varCount;

    kCheckState(obj->isCompatible);
    kCheckArgs(!kIsNull(count));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_RUNTIME_VARIABLE_COUNT, GO_CONTROL_GET_RUNTIME_VARIABLE_COUNT_TIMEOUT));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(kSerializer_Read32u(channel->serializer, &varCount));

    *count = (kSize)varCount;

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetRuntimeVariables(GoControl control, kSize startIndex, kSize length, k32s* values)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kSize i;
    k32u temp;

    kCheckState(obj->isCompatible);
    kCheckArgs(!kIsNull(values));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_RUNTIME_VARIABLES, GO_CONTROL_GET_RUNTIME_VARIABLES_TIMEOUT));

    kCheck(kSerializer_Write32u(channel->serializer, (k32u)startIndex));
    kCheck(kSerializer_Write32u(channel->serializer, (k32u)length));
    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read32u(channel->serializer, &temp));
    kCheck(kSerializer_Read32u(channel->serializer, &temp));

    for (i = 0; i < length; i++)
    {
        kCheck(kSerializer_Read32s(channel->serializer, &values[i]));
    }

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetRuntimeVariables(GoControl control, kSize startIndex, kSize length, const k32s* values)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kSize i;

    kCheckState(obj->isCompatible);
    kCheckArgs(!kIsNull(values));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_RUNTIME_VARIABLES, GO_CONTROL_SET_RUNTIME_VARIABLES_TIMEOUT));

    kCheck(kSerializer_Write32u(channel->serializer, (k32u)startIndex));
    kCheck(kSerializer_Write32u(channel->serializer, (k32u)length));

    for (i = 0; i < length; i++)
    {
        kCheck(kSerializer_Write32s(channel->serializer, values[i]));
    }

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetLeanAndMean(GoControl control, kBool leanAndMean)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);
    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_LEAN_AND_MEAN, GO_CONTROL_SET_LEAN_AND_MEAN_TIMEOUT));

    kCheck(kSerializer_Write8u(channel->serializer, (k8u)leanAndMean));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetSecurityLevel(GoControl control, GoSecurityLevel security)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_SECURITY_LEVEL, GO_CONTROL_SECURITY_LEVEL_TIMEOUT));
    kCheck(kSerializer_Write32s(channel->serializer, security));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetFlag(GoControl control, const kChar* name, const kChar* value)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText256 nameBuffer;
    k32u valueLen;

    kCheckState(obj->isCompatible);

    kCheckArgs(kStrCopy(nameBuffer, kCountOf(nameBuffer), name));
    valueLen = (k32u)kStrLength(value);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_FLAG, GO_CONTROL_SET_FLAG_TIMEOUT));

    kCheck(kSerializer_WriteCharArray(channel->serializer, nameBuffer, kCountOf(nameBuffer)));
    kCheck(kSerializer_Write32u(channel->serializer, valueLen));
    kCheck(kSerializer_WriteCharArray(channel->serializer, value, valueLen));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_GetFlag(GoControl control, const kChar* name, kString value)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText256 nameBuffer;
    k32u valueLen;

    kCheckState(obj->isCompatible);

    kCheckArgs(kStrCopy(nameBuffer, kCountOf(nameBuffer), name));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_GET_FLAG, GO_CONTROL_GET_FLAG_TIMEOUT));

    kCheck(kSerializer_WriteCharArray(channel->serializer, nameBuffer, kCountOf(nameBuffer)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(kSerializer_Read32u(channel->serializer, &valueLen));
    kCheck(kString_SetLength(value, (kSize)valueLen));
    kCheck(kSerializer_ReadCharArray(channel->serializer, kString_Chars(value), valueLen));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_AddNewTool(GoControl control, const kChar* type, const kChar* name)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 tempText;

    kCheckState(obj->isCompatible);
    kCheck(kStrCopy(tempText, kCountOf(tempText), type));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_ADD_NEW_TOOL, GO_CONTROL_ADD_TOOL_TIMEOUT));

    kCheck(kSerializer_WriteCharArray(channel->serializer, tempText, kCountOf(tempText)));
    kCheck(kStrCopy(tempText, kCountOf(tempText), name));
    kCheck(kSerializer_WriteCharArray(channel->serializer, tempText, kCountOf(tempText)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_DeleteNewTool(GoControl control, const kChar* type, const kChar* name)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;
    kText64 tempText;

    kCheckState(obj->isCompatible);
    kCheck(kStrCopy(tempText, kCountOf(tempText), type));

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_DELETE_NEW_TOOL, GO_CONTROL_ADD_TOOL_TIMEOUT));

    kCheck(kSerializer_WriteCharArray(channel->serializer, tempText, kCountOf(tempText)));
    kCheck(kStrCopy(tempText, kCountOf(tempText), name));
    kCheck(kSerializer_WriteCharArray(channel->serializer, tempText, kCountOf(tempText)));

    kCheck(GoControl_SendAndReceive(control, channel));

    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}

GoFx(kStatus) GoControl_SetDataTime(GoControl control, kDateTime dateTime)
{
    kObj(GoControl, control);
    GoControlChannel* channel = obj->control;

    kCheckState(obj->isCompatible);

    kCheck(GoControl_BeginCommand(control, channel, GO_CONTROL_SET_DATETIME, GO_CONTROL_SET_DATETIME_TIMEOUT));
    kCheck(kSerializer_Write64s(channel->serializer, dateTime));

    kCheck(GoControl_SendAndReceive(control, channel));
    kCheck(GoControl_EndResponse(control, channel));

    return kOK;
}