////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
// All rights reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// THIS IS AUTO-GENERATED CODE.  PLEASE DO NOT EDIT (File bug reports against tools).
///
/// Auto-generated by: Parameter Parser V7.4.1 (8cca6579792b9fcef3bc725ff070c5ec453c7c85)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file  parametereztuneworker.cpp
/// @brief Auto-generated Chromatix parameter file
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "parametertypes.h"
#include "parameterutils.h"
#include "parametereztune.h"
#include "parametereztuneworker.h"
#include <cstdio>

ParameterEzTuneWorker::ParameterEzTuneWorker(IParameterDataAPI* dataAPI)
{
    m_dataAPI        = dataAPI;
    m_ezTuneID       = reinterpret_cast<UINT64>(dataAPI);
    m_com            = PARAMETER_NEW ParameterEzTuneCom(dataAPI);
    m_exitData       = false;
    m_dataTimeout    = RECV_TIMEOUT_SEC;
    m_readThread     = nullptr;
    m_callback       = nullptr;
    m_modeTableIndex = 0;

    if (m_dataAPI == nullptr)
    {
        PARAMETER_LOG_ERROR(m_dataAPI, "ParameterEzTuneWorker constructed with a nullptr Set Manager object\n");
    }
}

ParameterEzTuneWorker::~ParameterEzTuneWorker()
{
    PARAMETER_DELETE m_com;
    m_com = nullptr;

    for (UINT32 i = 0; i < m_triggerReqs.Count(); i++)
    {
        PARAMETER_DELETE m_triggerReqs[i];
    }

    m_triggerReqs.Clear();
}

void ParameterEzTuneWorker::Start()
{
    if (m_readThread == nullptr)
    {
        m_com->Start();
        m_readThread = PARAMETER_NEW std::thread(&ReadFunction, this);
    }
}

void ParameterEzTuneWorker::Stop()
{
    m_exitData = true;

    if (m_readThread != nullptr)
    {
        m_com->Stop();

        if (m_readThread->joinable())
        {
            // Wait for the read thread to exit, this can take up to POLLING_TIMEOUT_MS milliseconds
            m_readThread->join();
        }

        PARAMETER_DELETE m_readThread;
        m_readThread = nullptr;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SetControlValues
///
/// @brief Set the control values to use in the control value response packet
///
/// @param values     List of control values
///
/// @return  False if EzTune is not enabled
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::SetControlValues(
    const PList<ParameterCTValue>& values)
{
#ifdef PARAMETER_USE_MUTEX
    ParameterMutexLocker amlock(m_ctMutex); // Unique LOCK
#endif

    m_controlValues.Clear();
    m_controlValues = values;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SetControlValue
///
/// @brief Set a controls value to use in the control value response packet
///
/// @param value     Control value
///
/// @return  False if EzTune is not enabled
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::SetControlValue(
    const ParameterCTValue& value)
{
#ifdef PARAMETER_USE_MUTEX
    ParameterMutexLocker amlock(m_ctMutex); // Unique LOCK
#endif

    INT32 insert = 0;

    for (UINT32 i = 0; i < m_controlValues.Count(); i++)
    {
        if (m_controlValues[i].EnumVal < value.EnumVal)
        {
            insert++;
        }
        else if (m_controlValues[i].EnumVal == value.EnumVal)
        {
            insert = -1;
            m_controlValues[i].Value = value.Value;
            break;
        }
    }

    if (insert >= 0)
    {
        m_controlValues.Insert(insert, value);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SetTriggerValues
///
/// @brief Set the trigger values to use in the trigger value response packet
///
/// @param modePath   Mode path string (Example: "0.0|1.2|2.0|...")
/// @param module     Module name
/// @param pipeline   Pipeline name (IPE, IFE, OFE, ect.)
/// @param id         Unique identifier (allows multiple trigger sets per module)
/// @param values     Array of trigger values
/// @param count      Trigger values count
///
/// @return  False if EzTune is not enabled, or is not waiting for this information
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool ParameterEzTuneWorker::SetTriggerValues(
    const CHAR* const modePath,
    const CHAR* const module,
    const CHAR* const pipeline,
    const CHAR* const id,
    const ParameterCTValue* const values,
    UINT32 const count)
{
    bool result = false;

    for (UINT32 i = 0; i < m_triggerReqs.Count(); i++)
    {
        ParameterTriggerValues* req = m_triggerReqs[i];
        if (req->ModePath == modePath && req->Module == module && req->Pipeline == pipeline)
        {
            result = true;
            req->Add(id, values, count);
            break;
        }
    }

    return result;
}

void ParameterEzTuneWorker::ReadFunction(void* worker)
{
    if (worker != nullptr)
    {
        ((ParameterEzTuneWorker*)worker)->ReadWorker();
    }
}

void ParameterEzTuneWorker::ReadWorker()
{
    do
    {
        SendTriggerResponses();

        ParameterEzTuneCom::Packet* request = m_com->Read();

        if (request != nullptr)
        {
            if (!m_exitData && request->Length > 0)
            {
                UINT16 cmd    = 0;
                UINT32 cmdPos = CMD_POS;
                ParameterUtils::Read(&cmd, request->Buffer, request->Length, cmdPos, 1, m_dataAPI);
                switch ((Commands)cmd)
                {
                case Commands::EZTUNE_DEVICE_INFO:
                    ProcessDeviceInfo();
                    break;
                case Commands::EZTUNE_DATA_ACTIVE_MODES:
                    ProcessActiveModes();
                    break;
                case Commands::EZTUNE_DATA_CTRL_VALUES:
                    ProcessControlValues();
                    break;
                case Commands::EZTUNE_DATA_MODES:
                    ProcessModes(request->Buffer, request->Length);
                    break;
                case Commands::EZTUNE_DATA_INFO:
                    ProcessInfo(request->Buffer, request->Length);
                    break;
                case Commands::EZTUNE_DATA_GET_VALUES:
                    ProcessGetValues(request->Buffer, request->Length);
                    break;
                case Commands::EZTUNE_DATA_SET_VALUES:
                    ProcessSetValues(request->Buffer, request->Length);
                    break;
                case Commands::EZTUNE_DATA_TRIG_VALUES:
                    ProcessTriggerValues(request->Buffer, request->Length);
                    break;
                case Commands::EZTUNE_CUSTOM:
                    ProcessCustom(request->Buffer, request->Length);
                    break;
                case Commands::EZTUNE_MODE_TABLE_INDEX:
                    ProcessModeTableIndex(request->Buffer, request->Length);
                    break;
                default:
                    break;
                }
            }

            PARAMETER_DELETE request;
        }
    } while (!m_exitData);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessDeviceInfo
///
/// @brief Process Data Device Info:
///        For sockets based comminication, file based uses a different method defined in ParameterEzTuneCom
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessDeviceInfo()
{
    const UINT32 ID_POS    = MINOR_POS + MINOR_LEN;
    const UINT32 NAME_POS  = ID_POS + EZT_ID_LEN;
    const UINT32 MAX_SIZE  = 4096;
    BYTE response[MAX_SIZE];
    UINT32 pos = 0;

    // Write the Command and Tag
    ParameterUtils::Write(ParameterEzTuneCom::TAG, response, MAX_SIZE, pos, 1, m_dataAPI);
    ParameterUtils::Write((UINT16)Commands::EZTUNE_DEVICE_INFO, response, MAX_SIZE, pos, 1, m_dataAPI);

    // Write the version
    response[MAJOR_POS] = VERSION_MAJOR;
    response[MINOR_POS] = VERSION_MINOR;

    // Write the EzTune ID
    pos = ID_POS;
    ParameterUtils::Write(m_ezTuneID, response, MAX_SIZE, pos, 1, m_dataAPI);

    // Write the Binary Tag, if not available will write the EzTune ID in hex string format
    response[NAME_POS] = 0;

    const CHAR* id = m_dataAPI->GetLoggingIdentifier();
    if (id != nullptr && id[0] != 0)
    {
        PARAMETER_STRCPY((CHAR*)&response[NAME_POS], MAX_SIZE - NAME_POS, id);
    }
    else
    {
#ifdef _WIN32
        PARAMETER_SPRINTF((CHAR*)&response[NAME_POS], MAX_SIZE - NAME_POS, "0x%016llx", reinterpret_cast<SIZE_T>(m_dataAPI));
#else
        PARAMETER_SPRINTF((CHAR*)&response[NAME_POS], MAX_SIZE - NAME_POS, "0x%016lx", reinterpret_cast<SIZE_T>(m_dataAPI));
#endif
    }

    PList<const CHAR*> names;
    m_dataAPI->GetModeTableNames(names);

    if (names.Count() > 1)
    {
        PARAMETER_STRCAT((CHAR*)&response[NAME_POS], MAX_SIZE - NAME_POS, ":");

        for (UINT32 i = 0; i < names.Count(); i++)
        {
            if (i > 0)
            {
                PARAMETER_STRCAT((CHAR*)&response[NAME_POS], MAX_SIZE - NAME_POS, ",");
            }
            PARAMETER_STRCAT((CHAR*)&response[NAME_POS], MAX_SIZE - NAME_POS, names[i]);
        }
    }

    UINT16 rspLen = (UINT16)(NAME_POS + PARAMETER_STRLEN((CHAR*)&response[NAME_POS]) + 1);

    // Write the length
    pos = LEN_POS;
    ParameterUtils::Write(rspLen, response, MAX_SIZE, pos, 1, m_dataAPI);

    m_com->Write(response, rspLen);

    constexpr INT32 MAX_PRINT = 128;
    CHAR printPayload[MAX_PRINT];
    SIZE_T len = PARAMETER_STRLEN((CHAR*)&response[NAME_POS]);
    if (len >= MAX_PRINT)
    {
        len = MAX_PRINT - 1;
    }
    PARAMETER_MEMCPY(printPayload, len, &response[NAME_POS], len);
    printPayload[len] = '\0';

    PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune queued command: Info(%d), Version: %u.%u, Name: %s\n",
        response[CMD_POS], VERSION_MAJOR, VERSION_MINOR, printPayload);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessActiveModes
///
/// @brief Process Active Modes
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessActiveModes()
{
    const UINT32 MAX_SIZE = 4096;
    BYTE response[MAX_SIZE];
    UINT32 pos = 0;

    // Write the Command and Tag
    ParameterUtils::Write(ParameterEzTuneCom::TAG, response, MAX_SIZE, pos, 1, m_dataAPI);
    ParameterUtils::Write((UINT16)Commands::EZTUNE_DATA_ACTIVE_MODES, response, MAX_SIZE, pos, 1, m_dataAPI);

    // Write the EzTune ID
    pos = EZT_ID_POS;
    ParameterUtils::Write(m_ezTuneID, response, MAX_SIZE, pos, 1, m_dataAPI);

    response[REQ_ID_POS]  = 0; // This packet does not have a Request ID, so payload starts at REQ_ID_POS
    m_dataAPI->GetActiveModes(m_modeTableIndex, (CHAR*)&response[REQ_ID_POS], MAX_SIZE - REQ_ID_POS);

    UINT16 rspLen = (UINT16)(REQ_ID_POS + PARAMETER_STRLEN((CHAR*)&response[REQ_ID_POS]) + 1);

    // Write the length
    pos = LEN_POS;
    ParameterUtils::Write(rspLen, response, MAX_SIZE, pos, 1, m_dataAPI);

    m_com->Write(response, rspLen);

    constexpr INT32 MAX_PRINT = 128;
    CHAR printPayload[MAX_PRINT];
    SIZE_T len = PARAMETER_STRLEN((CHAR*)&response[REQ_ID_POS]);
    if (len >= MAX_PRINT)
    {
        len = MAX_PRINT - 1;
    }
    PARAMETER_MEMCPY(printPayload, len, &response[REQ_ID_POS], len);
    printPayload[len] = '\0';

    PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune queued command: Active Modes(%d), Version: %u.%u, Active: %s\n",
        response[CMD_POS], VERSION_MAJOR, VERSION_MINOR, printPayload);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessControlValues
///
/// @brief Process Control Values Request
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessControlValues()
{
    const UINT32 MAX_SIZE = 4096;
    BYTE response[MAX_SIZE];
    UINT32 pos = 0;

    // Write the Command and Tag
    ParameterUtils::Write(ParameterEzTuneCom::TAG, response, MAX_SIZE, pos, 1, m_dataAPI);
    ParameterUtils::Write((UINT16)Commands::EZTUNE_DATA_CTRL_VALUES, response, MAX_SIZE, pos, 1, m_dataAPI);

    // Write the EzTune ID
    pos = EZT_ID_POS;
    ParameterUtils::Write(m_ezTuneID, response, MAX_SIZE, pos, 1, m_dataAPI);

    response[REQ_ID_POS] = 0; // This packet does not have an ID, so payload starts at REQ_ID_POS

    {
#ifdef PARAMETER_USE_MUTEX
        ParameterMutexLocker amlock(m_ctMutex); // Unique LOCK
#endif
        for (UINT32 i = 0; i < m_controlValues.Count(); i++)
        {
            constexpr SIZE_T STR_SIZE = 128;
            CHAR valueStr[STR_SIZE] = "";

            m_controlValues[i].ToString(valueStr, STR_SIZE);
            SIZE_T const valueLen = PARAMETER_STRLEN(valueStr);
            if (valueLen < MAX_SIZE - pos)
            {
                if (i > 0)
                {
                    ParameterUtils::Write(',', response, MAX_SIZE, pos, 1, m_dataAPI);
                }

                PARAMETER_STRCAT((CHAR*)&response[pos], MAX_SIZE - pos, valueStr);
                pos += (UINT32)valueLen;
            }
        }
    }

    UINT16 rspLen = (UINT16)(REQ_ID_POS + PARAMETER_STRLEN((CHAR*)&response[REQ_ID_POS]) + 1);

    // Write the length
    pos = LEN_POS;
    ParameterUtils::Write(rspLen, response, MAX_SIZE, pos, 1, m_dataAPI);

    m_com->Write(response, rspLen);

    constexpr INT32 MAX_PRINT = 128;
    CHAR printPayload[MAX_PRINT];
    SIZE_T len = PARAMETER_STRLEN((CHAR*)&response[REQ_ID_POS]);
    if (len >= MAX_PRINT)
    {
        len = MAX_PRINT - 1;
    }
    PARAMETER_MEMCPY(printPayload, len, &response[REQ_ID_POS], len);
    printPayload[len] = '\0';

    PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune queued command: Control Values(%d), Version: %u.%u, Name: %s\n",
        response[CMD_POS], VERSION_MAJOR, VERSION_MINOR, printPayload);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessTriggerValues
///
/// @brief Process Trigger Values Request
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessTriggerValues(
    BYTE* request,
    UINT32 length)
{
    UINT16 command   = 0;
    UINT64 ezTuneID  = 0;
    UINT32 requestID = 0;
    BYTE*  payload   = nullptr;
    UINT16 payLen    = 0;

    if (ParseRequest(request, length, command, ezTuneID, requestID, payload, payLen) && payload != nullptr && payLen > 0)
    {
        constexpr SIZE_T MAX_PATH_LEN = 512;
        CHAR   modePath[MAX_PATH_LEN];
        CHAR   module[MAX_PATH_LEN];
        CHAR   pipeline[MAX_PATH_LEN];
        UINT32 pos                    = 0;

        modePath[0] = 0;
        module[0]   = 0;
        pipeline[0] = 0;

        pos += ParameterUtils::GetSubString((const CHAR*)&payload[pos], ':', modePath, MAX_PATH_LEN);
        pos++;
        pos += ParameterUtils::GetSubString((const CHAR*)&payload[pos], ':', module,   MAX_PATH_LEN);
        pos++;
        pos += ParameterUtils::GetSubString((const CHAR*)&payload[pos], ':', pipeline, MAX_PATH_LEN);

#ifdef PARAMETER_USE_MUTEX
        ParameterMutexLocker amlock(m_ctMutex); // Unique LOCK
#endif

        if (!TriggerRequestExists(modePath, module, pipeline))
        {
            ParameterTriggerValues* values = PARAMETER_NEW ParameterTriggerValues(modePath, module, pipeline);
            m_triggerReqs.Add(values);
        }

        if (m_callback != nullptr)
        {
            m_callback->RequestTriggerValues(modePath, module, ParserCRC64::GetStringHash(module), pipeline);
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SendTriggerValues
///
/// @brief Send Trigger Values Request
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::SendTriggerValues(
    const ParameterTriggerValues* const value)
{
    const UINT32 MAX_SIZE = 65535;
    static constexpr UINT32 const PAYLOAD = REQ_ID_POS; // This packet does not have an ID, so payload starts at REQ_ID_POS
    BYTE* response = PARAMETER_NEW BYTE[MAX_SIZE];

    if (value != nullptr && response != nullptr)
    {
        UINT32 pos = 0;

        // Write the Command and Tag
        ParameterUtils::Write(ParameterEzTuneCom::TAG, response, MAX_SIZE, pos, 1, m_dataAPI);
        ParameterUtils::Write((UINT16)Commands::EZTUNE_DATA_TRIG_VALUES, response, MAX_SIZE, pos, 1, m_dataAPI);

        // Write the EzTune ID
        pos = EZT_ID_POS;
        ParameterUtils::Write(m_ezTuneID, response, MAX_SIZE, pos, 1, m_dataAPI);

        response[PAYLOAD] = 0; // This packet does not have an ID, so payload starts at PAYLOAD

        pos = PAYLOAD;
        if (value->ModePath.length() + value->Module.length() + value->Pipeline.length() + 3 < MAX_SIZE - pos)
        {
            // Write Mode Path
            PARAMETER_MEMCPY(&response[pos], MAX_SIZE - pos, value->ModePath.c_str(), value->ModePath.length());
            pos += (UINT32)value->ModePath.length();
            response[pos++] = ':';

            // Write Module
            PARAMETER_MEMCPY(&response[pos], MAX_SIZE - pos, value->Module.c_str(), value->Module.length());
            pos += (UINT32)value->Module.length();
            response[pos++] = ':';

            // Write Pipeline
            PARAMETER_MEMCPY(&response[pos], MAX_SIZE - pos, value->Pipeline.c_str(), value->Pipeline.length());
            pos += (UINT32)value->Pipeline.length();
            response[pos++] = '#';

            // Write the ID/Values
            for (UINT32 valIdx = 0; valIdx < value->Values.Count(); valIdx++)
            {
                ParameterTriggerIDValues* const values = value->Values[valIdx];

                if (values->ID.length() + 1 < MAX_SIZE - pos)
                {
                    UINT16 count = (UINT16)values->Count;
                    UINT32 ctLen = (UINT32)(count * sizeof(ParameterCTValue));
                    if (sizeof(UINT32) + ctLen < MAX_SIZE - pos)
                    {
                        // Write Identifier
                        PARAMETER_MEMCPY(&response[pos], MAX_SIZE - pos, values->ID.c_str(), values->ID.length());
                        pos += (UINT32)values->ID.length();
                        response[pos++] = ':';

                        // Write trigger count
                        PARAMETER_MEMCPY(&response[pos], MAX_SIZE - pos, &count, sizeof(count));
                        pos += sizeof(count);

                        // Write trigger values
                        PARAMETER_MEMCPY(&response[pos], MAX_SIZE - pos, values->Values, ctLen);
                        pos += ctLen;
                    }
                    else
                    {
                        PARAMETER_LOG_ERROR(m_dataAPI, "EzTune Trigger Values(%u) command, insufficient packet space to hold IPE instance %s\n", response[CMD_POS], values->ID.c_str());
                        break;
                    }
                }
                else
                {
                    PARAMETER_LOG_ERROR(m_dataAPI, "EzTune Trigger Values(%u) command, insufficient space to write trigger values\n", response[CMD_POS]);
                    break;
                }
            }

            // Write the length
            UINT16 rspLen = (UINT16)pos;
            pos = LEN_POS;
            ParameterUtils::Write(rspLen, response, MAX_SIZE, pos, 1, m_dataAPI);

            // Send the packet
            m_com->Write(response, rspLen);

            constexpr INT32 MAX_PRINT = 128;
            CHAR printPayload[MAX_PRINT];
            SIZE_T len = PARAMETER_STRLEN((CHAR*)&response[REQ_ID_POS]);
            if (len >= MAX_PRINT)
            {
                len = MAX_PRINT - 1;
            }
            PARAMETER_MEMCPY(printPayload, len, &response[REQ_ID_POS], len);
            printPayload[len] = 0;

            PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune queued command: Control Values(%d), Version: %u.%u, Name: %s\n",
                response[CMD_POS], VERSION_MAJOR, VERSION_MINOR, printPayload);
        }
        else
        {
            PARAMETER_LOG_ERROR(m_dataAPI, "EzTune Trigger Values(%d) command, insufficient space for mode path, module, and pipeline info\n", response[CMD_POS]);
        }
    }
    else
    {
        PARAMETER_LOG_ERROR(m_dataAPI, "EzTune Trigger Values(%d) command, unable to allocate memory\n", response[CMD_POS]);
    }

    PARAMETER_DELETE[] response;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SendTriggerResponses
///
/// @brief Send responses to any expired trigger requests
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::SendTriggerResponses()
{
#ifdef PARAMETER_USE_MUTEX
    ParameterMutexLocker amlock(m_ctMutex); // Unique LOCK
#endif

    clock_t current = clock();

    for (INT32 i = 0; (UINT32)i < m_triggerReqs.Count(); i++)
    {
        ParameterTriggerValues* const trigger = m_triggerReqs[i];
        if (current - trigger->CreateTime > MAX_TRIGGER_MS)
        {
            SendTriggerValues(trigger);
            m_triggerReqs.RemoveAt((UINT32)i);
            PARAMETER_DELETE trigger;
            i--;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// TriggerRequestExists
///
/// @brief Checks if a trigger request exists
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool ParameterEzTuneWorker::TriggerRequestExists(
    const CHAR* const modePath,
    const CHAR* const module,
    const CHAR* const pipeline)
{
    bool result = false;

#ifdef PARAMETER_USE_MUTEX
    ParameterMutexLocker amlock(m_ctMutex); // Unique LOCK
#endif

    for (UINT32 i = 0; !result && i < m_triggerReqs.Count(); i++)
    {
        ParameterTriggerValues* const req = m_triggerReqs[i];
        result = req->ModePath == modePath && req->Module == module && req->Pipeline == pipeline;
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessModes
///
/// @brief Process Modes
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessModes(
    BYTE*  request,
    UINT32 length)
{
    const UINT32 MAX_SIZE = 16384;
    const UINT32 SUB_SIZE = 512;

    if (length > PAY_POS)
    {
        CHAR   shorthand[SUB_SIZE];
        BYTE*  response = PARAMETER_NEW BYTE[MAX_SIZE];
        UINT32 id       = 0;
        UINT16 command  = 0;
        BYTE*  payload  = nullptr;
        UINT16 payLen   = 0;
        UINT64 eztID    = 0;

        (void)ParseRequest(request, length, command, eztID, id, payload, payLen);

        if (eztID != m_ezTuneID)
        {
            PARAMETER_LOG_ERROR(m_dataAPI, "Request packet EzTune ID does not match, Command: Modes(%d): Expected: %llu, Actual: %llu\n",
                Commands::EZTUNE_DATA_MODES, m_ezTuneID, eztID);
        }

        m_dataAPI->GetModeShorthand((CHAR*)payload, shorthand, SUB_SIZE);
        UINT16 rspLen = (UINT16)(m_dataAPI->GetChildModes(m_modeTableIndex, shorthand, &response[PAY_POS], MAX_SIZE - PAY_POS) + PAY_POS);

        BuildResponse(response, MAX_SIZE, rspLen, command, m_ezTuneID, id, &response[PAY_POS], rspLen);

        m_com->Write(response, rspLen);

        constexpr INT32 MAX_PRINT = 128;
        CHAR printPayload[MAX_PRINT];
        SIZE_T len = rspLen - PAY_POS + 1;

        if (len >= MAX_PRINT)
        {
            len = MAX_PRINT - 1;
        }
        PARAMETER_MEMCPY(printPayload, len, &response[PAY_POS], len);
        printPayload[len] = 0;

        PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune queued command: Modes(%d), ID: %u, Payload: %s...\n",
            response[CMD_POS], id, printPayload);

        PARAMETER_DELETE[] response;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessInfo
///
/// @brief Process Info
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessInfo(
    BYTE*  request,
    UINT32 length)
{
    const UINT32 MAX_SIZE = 65535;
    const UINT32 SUB_SIZE = 512;
    if (length > PAY_POS)
    {
        BYTE* response = PARAMETER_NEW BYTE[MAX_SIZE];

        if (response != nullptr)
        {
            CHAR* structure = nullptr;
            CHAR  mode[SUB_SIZE];
            CHAR  shorthand[SUB_SIZE];
            PList<ParameterParserInfo> info;
            UINT32 id  = 0;
            UINT32 pos = REQ_ID_POS;

            // Get ID for the log message
            ParameterUtils::Read(&id, request, length, pos, 1, m_dataAPI);

            // Copy Tag, Command, Length, and Request ID from the request, Length will be overwritten
            PARAMETER_MEMCPY(response, MAX_SIZE, request, PAY_POS);
            response[PAY_POS] = 0;

            bool result = false;
            pos = PAY_POS;
            pos = ParameterUtils::GetSubString((CHAR*)&request[pos], ':', mode, SUB_SIZE) + pos;
            pos++; // skip colon if present
            if (pos + 1 < length)
            {
                structure = (CHAR*)&request[pos];
            }

            m_dataAPI->GetModeShorthand(mode, shorthand, SUB_SIZE);

            result = m_dataAPI->GetInfo(m_modeTableIndex, shorthand, structure, info);

            pos = PAY_POS;

            if (result)
            {
                for (UINT32 i = 0; i < info.Count(); i++)
                {
                    if (i > 0)
                    {
                        ParameterUtils::Write((BYTE)';', response, MAX_SIZE, pos, 1, m_dataAPI);
                    }
                    ParameterUtils::WriteString(info[i].Name, response, MAX_SIZE, pos, false, m_dataAPI);
                    if (PARAMETER_STRLEN(info[i].Value) > 0)
                    {
                        ParameterUtils::Write((BYTE)'=', response, MAX_SIZE, pos, 1, m_dataAPI);
                        ParameterUtils::WriteString(info[i].Value, response, MAX_SIZE, pos, false, m_dataAPI);
                    }
                    ParameterUtils::Write((BYTE)',', response, MAX_SIZE, pos, 1, m_dataAPI);
                    ParameterUtils::WriteString(info[i].Type, response, MAX_SIZE, pos, false, m_dataAPI);
                    if (info[i].IsArray || info[i].IsOptional)
                    {
                        ParameterUtils::Write((BYTE)',', response, MAX_SIZE, pos, 1, m_dataAPI);
                        ParameterUtils::DoubleToString((double)info[i].Elements, (CHAR*)&response[pos], MAX_SIZE - pos, 0);
                        pos = (UINT32)(pos + PARAMETER_STRLEN((CHAR*)&response[pos]));
                    }
                }
            }

            ParameterUtils::Write((BYTE)'\0', response, MAX_SIZE, pos, 1, m_dataAPI);

            // Write the length
            UINT32 len_pos = LEN_POS;
            UINT16 rspLen = (UINT16)pos;
            ParameterUtils::Write(rspLen, response, MAX_SIZE, len_pos, 1, m_dataAPI);

            m_com->Write(response, rspLen);

            constexpr INT32 MAX_PRINT = 128;
            CHAR printPayload[MAX_PRINT];
            SIZE_T len = PARAMETER_STRLEN((CHAR*)&response[PAY_POS]);
            if (len >= MAX_PRINT)
            {
                len = MAX_PRINT - 1;
            }
            PARAMETER_MEMCPY(printPayload, len, &response[PAY_POS], len);
            printPayload[len] = '\0';

            PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune queued command: Info(%d), ID: %u, Payload: %s\n",
                response[CMD_POS], id, printPayload);

            PARAMETER_DELETE[] response;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessGetValues
///
/// @brief Process Get Values
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessGetValues(
    BYTE*  request,
    UINT32 length)
{
    const UINT32 MAX_SIZE = 65535;
    const UINT32 SUB_SIZE = 512;

    if (length > PAY_POS)
    {
        BYTE* response = PARAMETER_NEW BYTE[MAX_SIZE];

        if (response != nullptr)
        {
            CHAR  mode[SUB_SIZE];
            CHAR  path[SUB_SIZE];
            const CHAR* variable = nullptr;
            PList<ParameterParserInfo> info;
            UINT32 id   = 0;
            UINT32 pos = REQ_ID_POS;

            // Get ID for the log message
            ParameterUtils::Read(&id, request, length, pos, 1, m_dataAPI);

            // Copy Tag, Command, Length, and Request ID from the request, Length will be overwritten
            PARAMETER_MEMCPY(response, MAX_SIZE, request, PAY_POS);
            response[PAY_POS] = 0;

            pos = PAY_POS;
            pos = ParameterUtils::GetSubString((CHAR*)&request[pos], ':', mode, SUB_SIZE) + pos;
            pos++;
            if (pos < length)
            {
                CHAR  shorthand[SUB_SIZE];
                pos = ParameterUtils::GetSubString((CHAR*)&request[pos], ':', path, SUB_SIZE) + pos;
                pos++;
                if (pos < length)
                {
                    variable = (CHAR*)&request[pos];
                }
                pos = PAY_POS;

                m_dataAPI->GetModeShorthand(mode, shorthand, SUB_SIZE);

                response[pos] = 0;
                m_dataAPI->GetValue(m_modeTableIndex, shorthand, path, variable, (CHAR*)&response[pos], MAX_SIZE - pos);
            }

            // Write the length
            UINT32 len_pos = LEN_POS;
            UINT16 rspLen = (UINT16)(PAY_POS + PARAMETER_STRLEN((CHAR*)&response[PAY_POS]) + 1);
            ParameterUtils::Write(rspLen, response, MAX_SIZE, len_pos, 1, m_dataAPI);

            m_com->Write(response, rspLen);

            constexpr INT32 MAX_PRINT = 128;
            CHAR printPayload[MAX_PRINT];
            SIZE_T len = PARAMETER_STRLEN((CHAR*)&response[PAY_POS]);
            if (len >= MAX_PRINT)
            {
                len = MAX_PRINT - 1;
            }
            PARAMETER_MEMCPY(printPayload, len, &response[PAY_POS], len);
            printPayload[len] = '\0';

            PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune queued command: GetValues(%d), ID: %u, Payload: %s\n",
                response[CMD_POS], id, printPayload);

            PARAMETER_DELETE[] response;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessSetValues
///
/// @brief Process Get Values
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessSetValues(
    BYTE*  request,
    UINT32 length)
{
    const UINT32 MAX_SIZE = 65535;
    const UINT32 SUB_SIZE = 512;

    if (length > PAY_POS)
    {
        BYTE* response = PARAMETER_NEW BYTE[MAX_SIZE];

        if (response != nullptr)
        {
            CHAR  mode[SUB_SIZE];
            PList<ParameterParserInfo> info;
            UINT32 id  = 0;
            UINT32 pos = REQ_ID_POS;

            // Get ID for the log message
            ParameterUtils::Read(&id, request, length, pos, 1, m_dataAPI);

            // Copy Tag, Command, Length, and Request ID from the request, Length will be overwritten
            PARAMETER_MEMCPY(response, MAX_SIZE, request, PAY_POS);
            response[PAY_POS] = 0;

            bool result = false;
            pos = PAY_POS;
            pos = ParameterUtils::GetSubString((CHAR*)&request[pos], ':', mode, SUB_SIZE) + pos;
            pos++;
            if (pos < length)
            {
                CHAR  path[SUB_SIZE];
                pos = ParameterUtils::GetSubString((CHAR*)&request[pos], ':', path, SUB_SIZE) + pos;
                pos++;
                if (pos < length)
                {
                    CHAR  pair[SUB_SIZE];
                    CHAR  variable[SUB_SIZE];
                    CHAR  shorthand[SUB_SIZE];
                    INT32 count = 0;
                    CHAR* value = PARAMETER_NEW CHAR[MAX_SIZE];

                    if (value != nullptr)
                    {
                        value[0] = 0;
                        m_dataAPI->GetModeShorthand(mode, shorthand, SUB_SIZE);

                        do
                        {
                            pos = ParameterUtils::GetSubString((CHAR*)&request[pos], ';', pair, SUB_SIZE) + pos;
                            pos++;

                            INT32 valPos = ParameterUtils::GetSubString(pair, '=', variable, SUB_SIZE);

                            if (valPos + 1 < (INT32)PARAMETER_STRLEN(pair))
                            {
                                result = m_dataAPI->SetValue(m_modeTableIndex, shorthand, path, variable, &pair[valPos + 1]);
                                if (result)
                                {
                                    if (m_dataAPI->GetValue(m_modeTableIndex, shorthand, path, variable, value, MAX_SIZE))
                                    {
                                        if (count > 0)
                                        {
                                            PARAMETER_STRCAT((char*)&response[PAY_POS], MAX_SIZE - PAY_POS, ";");
                                        }
                                        PARAMETER_STRCAT((char*)&response[PAY_POS], MAX_SIZE - PAY_POS, value);
                                        count++;
                                    }
                                }
                                else
                                {
                                    PARAMETER_LOG_ERROR(m_dataAPI, "EzTune SetValues failed: Mode: %s, Value: %s\n", mode, &pair[valPos + 1]);
                                }
                            }
                        } while (pos < length);

                        PARAMETER_DELETE[] value;
                    }
                }
                else
                {
                    PARAMETER_LOG_ERROR(m_dataAPI, "EzTune SetValues failed: Failed to read mode\n");
                }
            }

           // Write the length
           UINT32 len_pos = LEN_POS;
           UINT16 rspLen = (UINT16)(PAY_POS + PARAMETER_STRLEN((CHAR*)&response[PAY_POS]) + 1);
           ParameterUtils::Write(rspLen, response, MAX_SIZE, len_pos, 1, m_dataAPI);

            m_com->Write(response, rspLen);

            constexpr INT32 MAX_PRINT = 128;
            CHAR printPayload[MAX_PRINT];
            SIZE_T len = PARAMETER_STRLEN((CHAR*)&request[PAY_POS]);
            if (len >= MAX_PRINT)
            {
                len = MAX_PRINT - 1;
            }
            PARAMETER_MEMCPY(printPayload, len, &request[PAY_POS], len);
            printPayload[len] = '\0';

            if (result)
            {
                PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune command succeeded: SetValues(%d), ID: %u, Payload: %s\n",
                    request[CMD_POS], id, printPayload);
            }
            else
            {
                PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune command failed: SetValues(%d), ID: %u, Payload: %s\n",
                    request[CMD_POS], id, printPayload);
            }

            PARAMETER_DELETE[] response;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessCustom
///
/// @brief Process Custom Commands
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessCustom(
    BYTE* request,
    UINT32 length)
{
    const UINT32 MAX_SIZE    = 65535;
    const UINT32 RESULT_POS  = PAY_POS;
    const UINT32 RESULT_SIZE = 2U;
    const UINT32 RSP_PKT_POS = RESULT_POS + RESULT_SIZE;
    const UINT32 MAX_RSP     = MAX_SIZE - RSP_PKT_POS;
    if (length > PAY_POS && length <= 0xFFFF)
    {
        BYTE* response = PARAMETER_NEW BYTE[MAX_SIZE];

        if (response != nullptr)
        {
            UINT32 pos = REQ_ID_POS;
            UINT32 id = 0;

            ParameterUtils::Read(&id, request, length, pos, 1, m_dataAPI);

            // Copy Tag, Command, Length, and Request ID from the request, Length will be overwritten
            PARAMETER_MEMCPY(response, MAX_SIZE, request, PAY_POS);

            UINT16 result  = 0;
            UINT16 rspSize = 0;
            if (m_callback != nullptr)
            {
                result = m_callback->CustomPacketHandler(&request[PAY_POS], (UINT16)length - PAY_POS, MAX_RSP, &response[RSP_PKT_POS], rspSize);
            }
            else
            {
                PARAMETER_LOG_ERROR(m_dataAPI, "EzTune queued Custom command received with no handler installed, sending result = 0\n");
            }

            // Write the length
            UINT32 len_pos = LEN_POS;
            UINT16 rspLen = RSP_PKT_POS + rspSize;
            ParameterUtils::Write(rspLen, response, MAX_SIZE, len_pos, 1U, m_dataAPI);

            // Write the result
            UINT32 result_pos = RESULT_POS;
            ParameterUtils::Write(result, response, MAX_SIZE, result_pos, 1U, m_dataAPI);

            m_com->Write(response, rspLen);

            constexpr INT32 MAX_PRINT = 128;
            CHAR printPayload[MAX_PRINT];
            PARAMETER_INITIALIZE_PTR(printPayload, MAX_PRINT);

            if (IsPrintable(&response[RSP_PKT_POS], rspSize))
            {
                UINT32 max = PARAMETER_MIN(rspSize, MAX_PRINT - 1);
                PARAMETER_MEMCPY(printPayload, MAX_PRINT, &response[RSP_PKT_POS], max);
            }
            else
            {
                UINT32 rspPos = 0;
                for (UINT32 printPos = 0; rspPos < rspSize && printPos < MAX_PRINT - 2; printPos += 2)
                {
                    PARAMETER_SPRINTF(&printPayload[printPos], MAX_PRINT - printPos, "%02X", response[RSP_PKT_POS + rspPos]);
                    rspPos++;
                }
            }

            PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune queued command: Custom(%d), ID: %u, Payload: %s...\n",
                response[CMD_POS], id, printPayload);

            PARAMETER_DELETE[] response;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ProcessModeTableIndex
///
/// @brief Process Mode Table Index
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneWorker::ProcessModeTableIndex(
    BYTE* request,
    UINT32 length)
{
    if (length > REQ_ID_POS)
    {
        const UINT32 MAX_SIZE = 32;
        BYTE  response[MAX_SIZE];
        BYTE* payload = &request[REQ_ID_POS];

        bool   valid = false;
        double value = ParameterUtils::StringToDouble((CHAR*)payload, valid);
        if (valid && value < m_dataAPI->GetModeTableCount())
        {
            m_modeTableIndex = (BYTE)value;
        }

        // Write the Command and Tag
        UINT32 pos = TAG_POS;
        ParameterUtils::Write(ParameterEzTuneCom::TAG, response, MAX_SIZE, pos, 1, m_dataAPI);
        ParameterUtils::Write((UINT16)Commands::EZTUNE_MODE_TABLE_INDEX, response, MAX_SIZE, pos, 1, m_dataAPI);

        // Write the EzTune ID
        pos = EZT_ID_POS;
        ParameterUtils::Write(m_ezTuneID, response, MAX_SIZE, pos, 1, m_dataAPI);

        response[REQ_ID_POS] = 0; // This packet does not have a Request ID, so payload starts at REQ_ID_POS

        PARAMETER_SPRINTF((CHAR*)&response[REQ_ID_POS], MAX_SIZE - REQ_ID_POS, "%u", m_modeTableIndex);

        UINT16 rspLen = (UINT16)(REQ_ID_POS + PARAMETER_STRLEN((CHAR*)&response[REQ_ID_POS]) + 1);

        // Write the length
        pos = LEN_POS;
        ParameterUtils::Write(rspLen, response, MAX_SIZE, pos, 1, m_dataAPI);

        m_com->Write(response, rspLen);

        PARAMETER_LOG_DEBUG(m_dataAPI, "EzTune queued command (%d): Mode Index(%s), Version: %u.%u, Index: %s\n",
            response[CMD_POS], &response[REQ_ID_POS], VERSION_MAJOR, VERSION_MINOR, (CHAR*)&response[REQ_ID_POS]);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ParseRequest
///
/// Parses an EzTune request packet
///
/// @brief Returns false if the packet is invalid or EzTune source code is not present
///
/// @param request   [in]  Request packet
/// @param reqLen    [in]  Request packet length
/// @param command   [out] Command ID
/// @param ezTuneID  [out] EzTune device ID
/// @param requestID [out] Request ID
/// @param payload   [out] Position within the request where the Payload starts
/// @param payLen    [out] Length of the payload
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool ParameterEzTuneWorker::ParseRequest(
    BYTE* const  request,
    UINT32 const reqLen,
    UINT16&      command,
    UINT64&      ezTuneID,
    UINT32&      requestID,
    BYTE*&       payload,
    UINT16&      payLen)
{
    bool result = false;

    command   = 0;
    ezTuneID  = 0;
    requestID = 0;
    payload   = nullptr;
    payLen    = 0;

    if (request != nullptr && reqLen > LEN_POS + LEN_LEN)
    {
        UINT16 tag = 0;
        UINT32 pos = TAG_POS;

        ParameterUtils::Read(&tag, request, reqLen, pos, 1, nullptr);

        if (tag == ParameterEzTuneComBase::TAG)
        {
            UINT16 length = 0;
            pos = CMD_POS;
            ParameterUtils::Read(&command,  request, reqLen, pos, 1, nullptr);
            ParameterUtils::Read(&length,   request, reqLen, pos, 1, nullptr);
            ParameterUtils::Read(&ezTuneID, request, reqLen, pos, 1, nullptr);

            result = true;
            // Commands with request IDs
            if (command == (UINT16)Commands::EZTUNE_DATA_MODES ||
                command == (UINT16)Commands::EZTUNE_DATA_INFO ||
                command == (UINT16)Commands::EZTUNE_DATA_GET_VALUES ||
                command == (UINT16)Commands::EZTUNE_DATA_SET_VALUES ||
                command == (UINT16)Commands::EZTUNE_CUSTOM)
            {
                if (reqLen > REQ_ID_POS + REQ_ID_LEN)
                {
                    ParameterUtils::Read(&requestID, request, reqLen, pos, 1, nullptr);
                }
                else
                {
                    result = false;
                }
            }

            if (result)
            {
                // Commands with payloads
                if (command == (UINT16)Commands::EZTUNE_DATA_MODES ||
                    command == (UINT16)Commands::EZTUNE_DATA_INFO ||
                    command == (UINT16)Commands::EZTUNE_DATA_GET_VALUES ||
                    command == (UINT16)Commands::EZTUNE_DATA_SET_VALUES ||
                    command == (UINT16)Commands::EZTUNE_DATA_TRIG_VALUES ||
                    command == (UINT16)Commands::EZTUNE_CUSTOM)
                {
                    if (reqLen > REQ_ID_POS + REQ_ID_LEN)
                    {
                        payLen  = (UINT16)(reqLen - pos);
                        payload = &request[pos];
                    }
                    else
                    {
                        result = false;
                    }
                }
            }
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ParseResponse
///
/// Parses an EzTune response packet
///
/// @brief Returns false if the packet is invalid or EzTune source code is not present
///
/// @param request   [in]  Request packet
/// @param reqLen    [in]  Request packet length
/// @param command   [out] Command ID
/// @param ezTuneID  [out] EzTune device ID
/// @param requestID [out] Request ID
/// @param payload   [out] Position within the request where the Payload starts
/// @param payLen    [out] Length of the payload
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool ParameterEzTuneWorker::ParseResponse(
    BYTE* const  request,
    UINT32 const reqLen,
    UINT16&      command,
    UINT64&      ezTuneID,
    UINT32&      requestID,
    BYTE*&       payload,
    UINT16&      payLen)
{
    bool result = false;

    command   = 0;
    ezTuneID  = 0;
    requestID = 0;
    payload  = nullptr;
    payLen   = 0;

    if (request != nullptr && reqLen > LEN_POS + LEN_LEN)
    {
        UINT16 tag = 0;
        UINT32 pos = TAG_POS;

        ParameterUtils::Read(&tag, request, reqLen, pos, 1, nullptr);

        if (tag == ParameterEzTuneComBase::TAG)
        {
            // Read the command ID and length
            UINT16 length = 0;
            pos = CMD_POS;
            ParameterUtils::Read(&command, request, reqLen, pos, 1, nullptr);
            ParameterUtils::Read(&length,  request, reqLen, pos, 1, nullptr);

            result = true;

            // Read the EzTune ID
            if (command == (UINT16)Commands::EZTUNE_DEVICE_INFO)
            {
                pos = MINOR_POS + MINOR_LEN;
            }
            else
            {
                pos = EZT_ID_POS;
            }
            ParameterUtils::Read(&ezTuneID, request, reqLen, pos, 1, nullptr);

            // Read the request ID
            if (
                command == (UINT16)Commands::EZTUNE_DATA_MODES      ||
                command == (UINT16)Commands::EZTUNE_DATA_INFO       ||
                command == (UINT16)Commands::EZTUNE_DATA_GET_VALUES ||
                command == (UINT16)Commands::EZTUNE_DATA_SET_VALUES ||
                command == (UINT16)Commands::EZTUNE_CUSTOM)
            {
                // Commands with request IDs
                if (reqLen > REQ_ID_POS + REQ_ID_LEN)
                {
                    ParameterUtils::Read(&requestID, request, reqLen, pos, 1, nullptr);
                }
                else
                {
                    result = false;
                }
            }

            // Read the payload
            if (result)
            {
                // Commands with payloads
                if (command == (UINT16)Commands::EZTUNE_DEVICE_INFO      ||
                    command == (UINT16)Commands::EZTUNE_DATA_MODES       ||
                    command == (UINT16)Commands::EZTUNE_DATA_INFO        ||
                    command == (UINT16)Commands::EZTUNE_DATA_GET_VALUES  ||
                    command == (UINT16)Commands::EZTUNE_DATA_SET_VALUES  ||
                    command == (UINT16)Commands::EZTUNE_DATA_TRIG_VALUES ||
                    command == (UINT16)Commands::EZTUNE_CUSTOM)
                {
                    if (reqLen > REQ_ID_POS + REQ_ID_LEN ||
                        (command == (UINT16)Commands::EZTUNE_DATA_MODES && reqLen > EZT_ID_POS + EZT_ID_LEN))
                    {
                        payLen = (UINT16)(reqLen - pos);
                        payload = &request[pos];
                    }
                    else
                    {
                        result = false;
                    }
                }
            }
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// BuildResponse
///
/// Builds an EzTune response packet
///
/// @brief Returns false if the info is not valid or EzTune source code is not present
///
/// @param response  [out] Response packet
/// @param rspSize   [in]  Maximum size of the response packet
/// @param rspLen    [out] Length of the built response packet
/// @param command   [in]  Command ID
/// @param ezTuneID  [in ] EzTune device ID
/// @param requestID [in]  Request ID
/// @param payload   [in]  Payload
/// @param payLen    [in]  Size of the payload, including the nul terminator
///
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool ParameterEzTuneWorker::BuildResponse(
    BYTE*        response,
    UINT32 const rspSize,
    UINT16&      rspLen,
    UINT16 const command,
    UINT64 const ezTuneID,
    UINT32 const requestID,
    BYTE* const  payload,
    UINT16 const payLen)
{
    bool result = false;

    rspLen = 0;

    if (command != (UINT16)Commands::EZTUNE_DEVICE_INFO && response != nullptr && rspSize > EZT_ID_POS + EZT_ID_LEN)
    {
        UINT32 pos = TAG_POS;

        bool hasID = command == (UINT16)Commands::EZTUNE_DATA_MODES ||
            command == (UINT16)Commands::EZTUNE_DATA_INFO ||
            command == (UINT16)Commands::EZTUNE_DATA_GET_VALUES ||
            command == (UINT16)Commands::EZTUNE_DATA_SET_VALUES ||
            command == (UINT16)Commands::EZTUNE_CUSTOM;

        rspLen = (UINT16)(LEN_POS + LEN_LEN + payLen + (hasID ? REQ_ID_LEN : 0));

        if (rspLen < rspSize)
        {
            // Write TAG, command, length, and EzTune ID
            result = true;

            if (result)
            {
                result = ParameterUtils::Write(ParameterEzTuneComBase::TAG, response, rspSize, pos, 1, nullptr);
            }
            if (result)
            {
                result = ParameterUtils::Write(command, response, rspSize, pos, 1, nullptr);
            }
            if (result)
            {
                result = ParameterUtils::Write(rspLen, response, rspSize, pos, 1, nullptr);
            }
            if (result)
            {
                ParameterUtils::Write(ezTuneID, response, rspSize, pos, 1, nullptr);
            }

            // Commands with request IDs
            if (hasID && result)
            {
                result = ParameterUtils::Write(requestID, response, rspSize, pos, 1, nullptr);
            }

            if (result && (
                command == (UINT16)Commands::EZTUNE_DATA_MODES ||
                command == (UINT16)Commands::EZTUNE_DATA_INFO ||
                command == (UINT16)Commands::EZTUNE_DATA_GET_VALUES ||
                command == (UINT16)Commands::EZTUNE_DATA_SET_VALUES ||
                command == (UINT16)Commands::EZTUNE_DATA_TRIG_VALUES ||
                command == (UINT16)Commands::EZTUNE_CUSTOM))
            {
                PARAMETER_MEMCPY(&response[pos], rspSize - pos, payload, payLen);
            }
        }
    }

    return result;
}

bool ParameterEzTuneWorker::IsPrintable(BYTE* bt, UINT32 len)
{
    bool result = false;
    if (bt != nullptr && len > 0)
    {
        result = true;
        for (UINT32 i = 0; result && i < len; i++)
        {
            result = IsPrintable(bt[i]);
        }
    }

    return result;
}
