protected ADMRequest GetADMRequest(ArduinoDeviceManager adm, byte tag, bool remove = true)
        {
            ADMRequest req2return = null;

            foreach (ADMRequest req in _admRequests)
            {
                if (req.ADM == adm && req.Tag == tag)
                {
                    req2return = req;
                    break;
                }
            }

            if (req2return != null)
            {
                _admRequests.Remove(req2return);
            }

            return(req2return);
        }
        virtual protected void HandleADMMessage(ADMMessage message, ArduinoDeviceManager adm)
        {
            switch (message.Type)
            {
            case MessageType.ERROR:
                ErrorCode errCode = message.HasValue("ErrorCode") ? message.GetEnum <ErrorCode>("ErrorCode") : ErrorCode.ERROR_UNKNOWN;
                Tracing?.TraceEvent(TraceEventType.Error, 100, "ADM {0} produced error: {1}", adm.BoardID, errCode);
                switch (errCode)
                {
                case ErrorCode.ERROR_ADM_NOT_INITIALISED:
                    ReconnectADM(adm);
                    break;

                default:
                    //currently do nothing
                    break;
                }
                break;

            case MessageType.WARNING:
                Tracing?.TraceEvent(TraceEventType.Warning, 100, "ADM {0} produced warning: {1}", adm.BoardID, message.Value);
                break;

            case MessageType.STATUS_RESPONSE:
                if (!PortSharing && !IsRequiredBoard(adm.BoardID))
                {
                    Tracing?.TraceEvent(TraceEventType.Warning, 100, "ADM {0} is not one of the required boards ({1}).  Disconnecting from port {2}...", adm.BoardID, RequiredBoards, adm.Port);
                    DisconnectADM(adm.Port);
                    break;
                }

                if (adm.State == ADMState.DEVICE_READY)
                {
                    Tracing?.TraceEvent(TraceEventType.Verbose, 100, "ADM: Ready to add devices to {0} on port {1} ...", adm.BoardID, adm.PortAndNodeID);
                    AddADMDevices(adm, message);
                    Tracing?.TraceEvent(TraceEventType.Verbose, 100, "ADM: {0} devices added to {1} on port {2}. Now configure board", adm.DeviceCount, adm.BoardID, adm.PortAndNodeID);
                    adm.Configure();
                }
                else if (adm.State == ADMState.DEVICE_CONNECTED)
                {
                    if (message.HasValue("Initialised") && !message.GetBool("Initialised"))
                    {
                        //Console.WriteLine("Whoa ... {0} has reset without us knowing", adm.PortAndNodeID);
                    }
                }
                break;

            case MessageType.CONFIGURE_RESPONSE:
                String key = adm.PortAndNodeID;
                if (adm.State == ADMState.DEVICE_CONNECTED && !_devicesConnected[key])
                {
                    Tracing?.TraceEvent(TraceEventType.Verbose, 100, "ADM: All {0} devices now configured and connected to board {1} on port {2}", adm.DeviceCount, adm.BoardID, adm.PortAndNodeID);
                    OnADMDevicesConnected(adm, message);
                }
                break;

            case MessageType.PING_RESPONSE:
                break;
            }

            if (message.Tag > 0)
            {
                ADMRequest req = GetADMRequest(adm, message.Tag);
                if (req != null)
                {
                    if (req.HasExpired())
                    {
                        Tracing?.TraceEvent(TraceEventType.Warning, 0, "ADM request for tag {0} and target {1} has expired so not returning message of type {2}", message.Tag, req.Target, message.Type);
                        return;
                    }
                    else
                    {
                        message.Target = req.Target;
                    }
                }
            }

            var schema = new ADMService.MessageSchema(message);

            schema.PrepareForBroadcast(adm);

            //notify other clients listening to this client
            Broadcast(ADMEvent.MESSAGE, message);
        }