示例#1
0
 private async Task SetInitialControlState(TLCFIClientSession session, TLCFIClientStateManager stateManager)
 {
     _logger.Info("Setting initial application session state in TLC (with ControlState.Offline) if needed");
     try
     {
         if (stateManager.ControlSession.ReqControlState != ControlState.Offline)
         {
             await session.SetReqControlStateAsync(ControlState.Offline);
         }
         session.State.Configured = true;
         _logger.Debug("Succesfully set initial application session state in TLC");
     }
     catch (JsonRpcException e)
     {
         _logger.LogRpcException(e);
         throw new TLCFISessionException("Error while setting initial application control state in TLCFacilities.");
     }
 }
示例#2
0
 public TLCFIClientSessionJsonRpcHandler(
     TLCFIClientStateManager stateManager,
     TLCProxy tlcProxy,
     TLCFIClientSessionState sessionState,
     TwoWayTcpClient tcpClient,
     CancellationToken token)
 {
     _stateManager             = stateManager;
     _tlcProxy                 = tlcProxy;
     _sessionCancellationToken = token;
     tcpClient.DataReceived   += async(o, e) =>
     {
         if (!_jsonRpcMethodRegex.IsMatch(e))
         {
             return;
         }
         var result = _service?.HandleRpc(e);
         if (result != null)
         {
             await tcpClient.SendDataAsync(result, token);
         }
     };
     _service = JsonRpcProcedureBinder.Default.GetInstanceService(this);
 }
示例#3
0
        public TLCFIClientSession(TLCFIClientStateManager stateManager, IPEndPoint ep, CancellationToken token)
        {
            _endPoint = ep;
            _sessionCancellationToken = token;
            _stateManager             = stateManager;

            Client               = new TwoWayTcpClient();
            Client.DataSent     += (o, e) => { DataSent?.Invoke(this, e); };
            Client.Disconnected += Client_Disconnected;

            RemoteEndPoint = ep;

            State = new TLCFIClientSessionState();

            TLCProxy        = new TLCProxy(Client);
            _jsonRpcHandler = new TLCFIClientSessionJsonRpcHandler(stateManager, TLCProxy, State, Client, token);
            _jsonRpcHandler.ReceivedAlive += (o, a) =>
            {
                _aliveReceivedTimer.Stop();
                _aliveReceivedTimer.Start();
            };
            _jsonRpcHandler.UpdateStateCalled += OnUpdateStateCalled;
            _jsonRpcHandler.NotifyEventCalled += OnNotifyEventCalled;
        }
示例#4
0
        public async Task InitializeSession(TLCFIClientSession session, TLCFIClientConfig config, TLCFIClientStateManager stateManager, CancellationToken token)
        {
            if (config == null)
            {
                throw new NullReferenceException("Config is null, has SetConfig been called first?");
            }
            try
            {
                var sessionId = await RegisterAsync(session, config, token);

                if (!session.State.Registered)
                {
                    throw new TLCFISessionException("Registering with TLC failed");
                }
                ApplicationRegistered?.Invoke(this, EventArgs.Empty);
                session.StartAliveTimers();
                await GetSessionDataAsync(sessionId, session, config, stateManager, token);

                if (stateManager == null)
                {
                    return;
                }
                await ReadFacilitiesMetaAsync(session, config, stateManager, token);

                Intersection inter = null;
                if (!config.UseIdsFromTLCForSubscription)
                {
                    inter = await ReadIntersectionMetaAndSubscribeAsync(session, config, stateManager, token);
                }
                var refs = CollectAllRefs(stateManager.Facilities, inter, config);

                CheckMetaData(stateManager.Facilities, inter, config);
                await ReadAllObjectsMetaAsync(refs, session, config, stateManager, token);
                await SubscribeAllObjectsAsync(refs, session, stateManager, token);

                ApplicationConfigured?.Invoke(this, EventArgs.Empty);
                await SetInitialControlState(session, stateManager);

                _logger.Info("Client configured succesfully. Now ready to request control.");
            }
            catch (TLCFISessionException e)
            {
                _logger.Fatal(e, "Error initializing session. " + (e.Fatal ? "(FATAL!): " : ": "));
                throw new TLCFISessionException("Error initializing session. " + (e.Fatal ? "(FATAL!) " : ""), e.Fatal);
            }
        }
示例#5
0
        private async Task SubscribeAllObjectsAsync(IEnumerable <ObjectReference> refs, TLCFIClientSession session, TLCFIClientStateManager stateManager, CancellationToken token)
        {
            try
            {
                var getstatetasks = (from objref in refs
                                     where objref.Ids.Length > 0
                                     select session.TLCProxy.SubscribeAsync(objref, token)).Cast <Task>()
                                    .ToList();

                await Task.WhenAll(getstatetasks.ToArray());

                foreach (var t in getstatetasks)
                {
                    using (var task = t as Task <ObjectData>)
                    {
                        if (task == null)
                        {
                            continue;
                        }

                        var data = task.Result;

                        switch (data.Objects.Type)
                        {
                        case TLCObjectType.SignalGroup:
                            for (var i = 0; i < data.Data.Length; ++i)
                            {
                                var sg  = (SignalGroup)data.Data[i];
                                var ssg = stateManager.InternalSignalGroups.First(x => x.Id == data.Objects.Ids[i]);
                                if (ssg == null)
                                {
                                    throw new NullReferenceException();
                                }
                                // copy state
                                ssg.StateTicks  = sg.StateTicks;
                                ssg.State       = sg.State;
                                ssg.Predictions = sg.Predictions;
                            }
                            break;

                        case TLCObjectType.Detector:
                            for (var i = 0; i < data.Data.Length; ++i)
                            {
                                var d  = (Detector)data.Data[i];
                                var sd = stateManager.InternalDetectors.First(x => x.Id == data.Objects.Ids[i]);
                                if (sd == null)
                                {
                                    throw new NullReferenceException();
                                }
                                // copy state
                                sd.StateTicks = d.StateTicks;
                                sd.State      = d.State;
                                sd.FaultState = d.FaultState;
                                sd.Swico      = d.Swico;
                            }
                            break;

                        case TLCObjectType.Input:
                            for (var i = 0; i < data.Data.Length; ++i)
                            {
                                var ip  = (Input)data.Data[i];
                                var sip = stateManager.InternalInputs.First(x => x.Id == data.Objects.Ids[i]);
                                if (sip == null)
                                {
                                    throw new NullReferenceException();
                                }
                                // copy state
                                sip.StateTicks = ip.StateTicks;
                                sip.State      = ip.State;
                                sip.FaultState = ip.FaultState;
                                sip.Swico      = ip.Swico;
                            }
                            break;

                        case TLCObjectType.Output:
                            for (var i = 0; i < data.Data.Length; ++i)
                            {
                                var op  = (Output)data.Data[i];
                                var sop = stateManager.InternalOutputs.First(x => x.Id == data.Objects.Ids[i]);
                                if (sop == null)
                                {
                                    throw new NullReferenceException();
                                }
                                // copy state
                                sop.StateTicks = op.StateTicks;
                                sop.State      = op.State;
                                sop.FaultState = op.FaultState;
                            }
                            break;

                        case TLCObjectType.Intersection:
                            for (var i = 0; i < data.Data.Length; ++i)
                            {
                                var ins  = (Intersection)data.Data[i];
                                var sins = stateManager.InternalIntersections.First(x => x.Id == data.Objects.Ids[i]);
                                if (sins == null)
                                {
                                    throw new NullReferenceException();
                                }
                                // copy state
                                sins.StateTicks = ins.StateTicks;
                                sins.State      = ins.State;
                                session.State.IntersectionControl = sins.State == IntersectionControlState.Control;
                            }
                            break;

                        case TLCObjectType.SpecialVehicleEventGenerator:
                            if (data.Data.Length == 1)
                            {
                                var spv  = (SpecialVehicleEventGenerator)data.Data[0];
                                var sspv = stateManager.SpvhGenerator;
                                if (spv == null)
                                {
                                    throw new NullReferenceException();
                                }
                                // copy state
                                sspv.FaultState = spv.FaultState;
                            }
                            break;

                        case TLCObjectType.Variable:
                            for (var i = 0; i < data.Data.Length; ++i)
                            {
                                var v  = (Variable)data.Data[i];
                                var sv = stateManager.InternalVariables.First(x => x.Id == data.Objects.Ids[i]);
                                if (sv == null)
                                {
                                    throw new NullReferenceException();
                                }
                                // copy state
                                sv.Lifetime = v.Lifetime;
                                sv.Value    = v.Value;
                            }
                            break;

                        case TLCObjectType.Session:             // Already subscribed in Register()
                        case TLCObjectType.TLCFacilities:       // This object does not have a state
                            throw new NotSupportedException();

                        default:
                            throw new ArgumentOutOfRangeException();
                        }
                    }
                }
                _logger.Info("Succesfully initialized TLC state in CLA");
            }
            catch (JsonRpcException e)
            {
                _logger.LogRpcException(e);
                throw new TLCFISessionException("Error while subscribing to objects in TLCFacilities.");
            }
            catch (Exception e)
            {
                _logger.Error(e, "Error while obtaining objects from TLC");
                throw new TLCFISessionException("Error while subscribing to objects in TLCFacilities.");
            }
        }
示例#6
0
        private async Task ReadAllObjectsMetaAsync(IEnumerable <ObjectReference> refs, TLCFIClientSession session, TLCFIClientConfig config, TLCFIClientStateManager stateManager, CancellationToken token)
        {
            if (!session.State.Registered)
            {
                _logger.Warn(
                    "Error configuring application: not authorized with TLC; were Register() and ReadFacilitiesMeta() called?");
                throw new TLCFISessionException("Client is authorized with TLC; were Register() and ReadFacilitiesMeta() called?");
            }

            try
            {
                var getmetatasks = (from objref in refs
                                    where objref.Ids.Length > 0
                                    select session.TLCProxy.ReadMetaAsync(objref, token)).Cast <Task>()
                                   .ToList();

                await Task.WhenAll(getmetatasks.ToArray());

                foreach (var t in getmetatasks)
                {
                    using (var task = t as Task <ObjectMeta>)
                    {
                        if (task == null)
                        {
                            continue;
                        }

                        var meta = task.Result;

                        switch (meta.Objects.Type)
                        {
                        case TLCObjectType.SignalGroup:
                            foreach (SignalGroup sg in meta.Meta)
                            {
                                stateManager.InternalSignalGroups.Add(sg);
                            }
                            break;

                        case TLCObjectType.Detector:
                            foreach (Detector d in meta.Meta)
                            {
                                stateManager.InternalDetectors.Add(d);
                            }
                            break;

                        case TLCObjectType.Input:
                            foreach (Input ip in meta.Meta)
                            {
                                stateManager.InternalInputs.Add(ip);
                            }
                            break;

                        case TLCObjectType.Output:
                            foreach (Output op in meta.Meta)
                            {
                                stateManager.InternalOutputs.Add(op);
                            }
                            break;

                        case TLCObjectType.SpecialVehicleEventGenerator:
                            stateManager.SpvhGenerator = (SpecialVehicleEventGenerator)meta.Meta[0];
                            break;

                        case TLCObjectType.Variable:
                            foreach (Variable op in meta.Meta)
                            {
                                stateManager.InternalVariables.Add(op);
                            }
                            break;

                        case TLCObjectType.Session:       // Special kind of object; gets initialized in Register()
                        case TLCObjectType.TLCFacilities: // Got initialized in ReadFacilitiesMeta()
                        case TLCObjectType.Intersection:  // Got initialized in ReadFacilitiesMeta()
                            throw new NotSupportedException();

                        default:
                            throw new ArgumentOutOfRangeException();
                        }
                    }
                }
            }
            catch (JsonRpcException e)
            {
                _logger.LogRpcException(e);
                throw new TLCFISessionException("Error reading META from TLCFacilities.");
            }
            catch (Exception e)
            {
                _logger.Error(e, "Error while reading META data for all objects from TLC");
                throw new TLCFISessionException("Error reading META from TLCFacilities.");
            }

            try
            {
                stateManager.Initialize(config.RemoteIntersectionId);         // Check and init
            }
            catch (DuplicateNameException)
            {
                throw new TLCFISessionException("Error checking META data from TLCFacilities (FATAL!)", true);
            }
        }
示例#7
0
        private async Task <Intersection> ReadIntersectionMetaAndSubscribeAsync(TLCFIClientSession session, TLCFIClientConfig config, TLCFIClientStateManager stateManager, CancellationToken token)
        {
            ObjectMeta intersectionMeta  = null;
            ObjectData intersectionState = null;
            var        iref = new ObjectReference
            {
                Ids  = new[] { config.RemoteIntersectionId },
                Type = TLCObjectType.Intersection
            };

            try
            {
                _logger.Info("Getting Intersection META data for intersection with id {0}", config.RemoteIntersectionId);
                intersectionMeta = await session.TLCProxy.ReadMetaAsync(iref, token);
            }
            catch (JsonRpcException e)
            {
                _logger.LogRpcException(e);
            }

            if (intersectionMeta == null || intersectionMeta.Meta.Length != 1)
            {
                var exmes = $"Error reading META of Intersection: received {intersectionMeta?.Meta.Length ?? 0} objects, expected 1";
                _logger.Warn(exmes);
                throw new InvalidMetaReceivedException(exmes);
            }

            _logger.Info("Succesfully obtained Intersection META data");
            var intersectionData = (Intersection)intersectionMeta.Meta[0];

            stateManager.InternalIntersections.Add(intersectionData);

            try
            {
                intersectionState = await session.TLCProxy.SubscribeAsync(iref, token);
            }
            catch (JsonRpcException e)
            {
                _logger.LogRpcException(e);
            }

            if (intersectionState == null || intersectionState.Data.Length != 1)
            {
                var exmes = $"Error reading STATE of Intersection: received {intersectionState?.Data.Length ?? 0} objects, expected 1";
                _logger.Warn(exmes);
                throw new InvalidMetaReceivedException(exmes);
            }

            var ins  = (Intersection)intersectionState.Data[0];
            var sins = stateManager.InternalIntersections.First(x => x.Id == intersectionState.Objects.Ids[0]);

            // Copy state
            sins.StateTicks = ins.StateTicks;
            sins.State      = ins.State;
            session.State.IntersectionControl = sins.State == IntersectionControlState.Control;

            return(intersectionData);
        }
示例#8
0
        private async Task ReadFacilitiesMetaAsync(TLCFIClientSession session, TLCFIClientConfig config, TLCFIClientStateManager stateManager, CancellationToken token)
        {
            try
            {
                if (_facilitiesRef != null)
                {
                    ObjectMeta facilitiesMeta = null;
                    try
                    {
                        _logger.Info("Getting TLCFacilities META data");
                        facilitiesMeta = await session.TLCProxy.ReadMetaAsync(_facilitiesRef, token);
                    }
                    catch (JsonRpcException e)
                    {
                        _logger.LogRpcException(e);
                    }

                    if (facilitiesMeta != null && facilitiesMeta.Meta.Length == 1)
                    {
                        _logger.Info("Succesfully obtained TLCFacilities META data");
                        var facilitiesData = (TLCFacilities)facilitiesMeta.Meta[0];
                        stateManager.Facilities = facilitiesData;
                        if (!facilitiesData.Intersections.Contains(config.RemoteIntersectionId))
                        {
                            _logger.Error("Intersection with id {0} not found in TLCFacilities META data",
                                          config.RemoteIntersectionId);
                            throw new TLCObjectNotFoundException(config.RemoteIntersectionId, TLCObjectType.Intersection);
                        }
                    }
                    else
                    {
                        _logger.Fatal("Error reading META of TLCFacilities: received {0} objects, expected 1",
                                      facilitiesMeta?.Meta.Length ?? 0);
                        throw new ArgumentOutOfRangeException();
                    }
                }
                else
                {
                    _logger.Error(
                        "Error reading META of TLCFacilities: reference to facilities is null; was Register() succesfully called?");
                    throw new NullReferenceException();
                }
            }
            catch (Exception e)
            {
                _logger.Error(e, "Error reading META of TLCFacilities, canceling session; ");
                throw new TLCFISessionException("Error reading META of TLCFacilities, canceling session");
            }
        }
示例#9
0
        private async Task <TLCFIClientStateManager> GetSessionDataAsync(string sessionId, TLCFIClientSession session, TLCFIClientConfig config, TLCFIClientStateManager stateManager, CancellationToken token)
        {
            _logger.Info("Obtaining session data and subscribing to session.");
            try
            {
                // Read meta for session
                var sref = new ObjectReference()
                {
                    Ids  = new[] { sessionId },
                    Type = TLCObjectType.Session
                };
                var meta = await session.TLCProxy.ReadMetaAsync(sref, token);

                token.ThrowIfCancellationRequested();
                if (meta == null || meta.Meta.Length != 1)
                {
                    throw new InvalidMetaReceivedException("Incorrect response while reading session META. Meta received: " + meta);
                }

                // Check and store data, set state for session, and subscribe to session updates
                stateManager.Session = (TLCSessionBase)meta.Meta[0];
                ValueChecker.CheckValidObjectId(stateManager.Session.Id);
                switch (config.ApplicationType)
                {
                case ApplicationType.Consumer:
                case ApplicationType.Provider:
                    break;

                case ApplicationType.Control:
                    if (stateManager.Session is ControlApplication ct)
                    {
                        // Start with Error state: either the TLC will set it to Offline, or we will in SetInitialControlState
                        ct.ReqControlState = ControlState.Error;
                        ct.StartCapability = config.StartCapability;
                        ct.EndCapability   = config.EndCapability;
                        ct.ReqIntersection = config.RemoteIntersectionId;
                    }
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }
                if (stateManager.Session.SessionType != config.ApplicationType)
                {
                    throw new InvalidTLCObjectTypeException($"Type of Session (ApplicationType) incorrect. Expected {config.ApplicationType.ToString()}, got {stateManager.Session.SessionType.ToString()}");
                }
                var data = await session.TLCProxy.SubscribeAsync(sref, token);

                token.ThrowIfCancellationRequested();
                if (data == null || data.Data.Length != 1)
                {
                    throw new InvalidStateReceivedException("Incorrect response while reading session STATE. State received: " + data);
                }

                ApplicationRegistered?.Invoke(this, EventArgs.Empty);

                _logger.Info("Succesfully got session meta and state: registered properly.");
                return(stateManager);
            }
            catch (JsonRpcException e)
            {
                session.State.Registered = false;
                _logger.LogRpcException(e);
            }
            catch (Exception e)
            {
                session.State.Registered = false;
                _logger.Info(e, "Register failed with non-jsonrpc error.");
            }
            return(null);
        }
        public async Task <TLCFIClientSession> GetNewSession(IPEndPoint endPoint, TLCFIClientStateManager stateManager, CancellationToken token)
        {
            if (token.IsCancellationRequested)
            {
                return(null);
            }

            lock (_locker)
            {
                if (_activeSession != null && !_activeSession.Connected)
                {
                    _logger.Warn("A session with {0}:{1} already exists, but is not connected. It will be disposed of.",
                                 _activeSession.RemoteEndPoint.Address, _activeSession.RemoteEndPoint.Port);
                    DisposeActiveSession();
                    return(null);
                }
                if (_activeSession != null)
                {
                    _logger.Warn("There already is a connected session with {0}:{1}. Simultaneous sessions are not allowed.",
                                 _activeSession.RemoteEndPoint.Address, _activeSession.RemoteEndPoint.Port);
                    return(null);
                }
            }

            // Succesful registration interval
            if (DateTime.Now.Subtract(_lastSuccesfulRegister).TotalSeconds < 42)
            {
                var remaining = (int)(42 - DateTime.Now.Subtract(_lastSuccesfulRegister).TotalSeconds) + 1;
                _logger.Info("Need 42 seconds between succesful register calls. Still need to wait {0} seconds. ",
                             remaining);
                await Task.Run(async() =>
                {
                    while (remaining > 0)
                    {
                        await Task.Delay(1000, token);
                        remaining--;
                        if (remaining > 0)
                        {
                            _logger.Trace("Starting new session in {0} seconds.", remaining);
                        }
                    }
                }, token);
            }

            _tokenSource = new CancellationTokenSource();
            var session = new TLCFIClientSession(stateManager, endPoint, _tokenSource.Token);

            _activeSession                      = session;
            session.SessionEnded               += OnSessionEnded;
            session.Disconnected               += OnSessionDisconnected;
            session.ControlStateSetToError     += OnSessionControlStateSetToError;
            session.ReceiveAliveTimeoutOccured += OnSessionReceiveAliveTimeout;
            session.EventOccured               += OnSessionEventOccured;

            var watch = new Stopwatch();

            watch.Reset();

            var sesIp   = endPoint.Address.ToString();
            var sesPort = endPoint.Port.ToString();

            while (!session.Connected)
            {
                try
                {
                    var remaining = _timeout - watch.ElapsedMilliseconds;
                    if (remaining > 0)
                    {
                        await Task.Delay((int)remaining, token);
                    }
                    _tries++;
                    // Backoff procedure
                    if (_tries >= 25)
                    {
                        _timeout = 60000;
                    }
                    else if (_tries >= 21)
                    {
                        _timeout = 30000;
                    }
                    else if (_tries >= 10)
                    {
                        _timeout = 5000;
                    }
                    else if (_tries >= 5)
                    {
                        _timeout = 2000;
                    }
                    watch.Reset();
                    watch.Start();
                    _logger.Info("Connecting to {0}:{1}, try {2}", sesIp, sesPort, _tries);
                    ConnectingStarted?.Invoke(this, _tries);
                    if (token.IsCancellationRequested || _tokenSource.IsCancellationRequested)
                    {
                        return(null);
                    }

                    await session.StartSessionAsync(_timeout);
                }
                catch (TaskCanceledException)
                {
                    return(null);
                }
                catch
                {
                    _logger.Warn("Connecting to {0}:{1} try {2} failed", sesIp, sesPort, _tries);
                    ConnectingFailed?.Invoke(this, _tries);
                }
            }

            _logger.Info("TCP session with {0}:{1} started", sesIp, sesPort);
            TLCSessionStarted?.Invoke(this, session);

            return(session);
        }
示例#11
0
        private void OnUpdateStateCalled(object sender, ObjectStateUpdate objectstateupdate)
        {
            switch (objectstateupdate.Objects.Type)
            {
            case TLCObjectType.Session:
                if (objectstateupdate.Objects.Ids.Length == 1 && objectstateupdate.Objects.Ids[0] == _stateManager.Session.Id &&
                    objectstateupdate.States.Length == 1)
                {
                    var application = (ControlApplication)objectstateupdate.States[0];
                    if (application == null)
                    {
                        throw new JsonRpcException((int)ProtocolErrorCode.InvalidObjectReference,
                                                   "State could not be properly cast to type ControlApplication (object type: TLCObjectType.Session).", null);
                    }
                    if (!application.ControlState.HasValue)
                    {
                        throw new JsonRpcException((int)ProtocolErrorCode.InvalidAttributeValue,
                                                   "ControlState was not set on ControlApplication object.", null);
                    }
                    _logger.Info("TLC set Application.ControlState from {0} to {1}.",
                                 _stateManager.ControlSession.ControlState, application.ControlState);
                    if (_stateManager.ControlSession.ControlState.HasValue)
                    {
                        if (!TLCFIStateChecker.IsControlStateChangeOk(_stateManager.ControlSession.ControlState.Value,
                                                                      application.ControlState.Value))
                        {
                            _logger.Warn("Invalid ControlState transition made by TLC: from {0} to {1}. Resulting behaviour is undefined.",
                                         _stateManager.ControlSession.ControlState, application.ControlState);
                        }
                    }

                    switch (application.ControlState)
                    {
                    case ControlState.Error:
                        State.SessionControl = false;
                        _logger.Error("TLC set Application.ControlState to ControlState.Error.");
                        ControlStateSetToError?.Invoke(this, EventArgs.Empty);
                        break;

                    case ControlState.NotConfigured:
                        State.SessionControl = false;
                        // During startup, accept transition from 0 (error) to NotConfigured
                        if (_stateManager.ControlSession.ReqControlState == ControlState.Error)
                        {
                            _logger.Info("Will confirm ControlState by setting requested state to ControlState.NotConfigured.");
                            Task.Run(() => SetReqControlStateAsync(ControlState.NotConfigured), _sessionCancellationToken);
                        }
                        // Otherwise, if not requested, this transition is false
                        else if (_stateManager.ControlSession.ReqControlState != ControlState.NotConfigured)
                        {
                            _logger.Warn("TLC set Application.ControlState to ControlState.NotConfigured. " +
                                         "(Requested = {0}).", _stateManager.ControlSession.ReqControlState);
                        }
                        break;

                    case ControlState.Offline:
                        State.SessionControl = false;
                        // Accept Offline during startup
                        if ((_stateManager.ControlSession.ReqControlState == ControlState.Error ||
                             _stateManager.ControlSession.ReqControlState == ControlState.NotConfigured) &&
                            _stateManager.ControlSession.ControlState == ControlState.Offline)
                        {
                            _logger.Info("Will confirm ControlState by setting requested state from ControlState.NotConfigured to ControlState.Offline.");
                            Task.Run(() => SetReqControlStateAsync(ControlState.Offline), _sessionCancellationToken);
                        }
                        // Log Offline as false otherwise: the CLA should have requested this first
                        else if (_stateManager.ControlSession.ReqControlState != ControlState.Offline)
                        {
                            _logger.Warn(
                                "TLC set Application.ControlState to ControlState.Offline. (Requested = {0}).",
                                _stateManager.ControlSession.ReqControlState);
                        }
                        // Otherwise Offline was requested
                        else
                        {
                            _logger.Info("TLC set Application.ControlState to ControlState.Offline.");
                            if (_stateManager.ControlSession.Type == ApplicationType.Control)
                            {
                                _logger.Info("Instruction to request control may now be send using RequestSessionStartControl().");
                            }
                        }
                        break;

                    case ControlState.ReadyToControl:
                        State.SessionControl = false;
                        // Log if not as requested
                        if (_stateManager.ControlSession.ReqControlState != ControlState.ReadyToControl)
                        {
                            _logger.Error("TLC set Application.ControlState to ControlState.ReadyToControl, while requested state is {0}.", _stateManager.ControlSession.ReqControlState);
                        }
                        // Otherwise log awaiting StartControl
                        else
                        {
                            _logger.Info("TLC set Application.ControlState to ControlState.ReadyToControl. Now awaiting StartControl.");
                        }
                        break;

                    case ControlState.InControl:
                        // Log if not as requested
                        if (_stateManager.ControlSession.ReqControlState != ControlState.InControl)
                        {
                            _logger.Error("TLC set Application.ControlState to ControlState.InControl. (Requested = {0}).", _stateManager.ControlSession.ReqControlState);
                        }
                        // Otherwise log confirmation
                        else
                        {
                            _logger.Info("TLC set Application.ControlState to ControlState.InControl.");
                        }
                        break;

                    case ControlState.StartControl:
                        // If we requested control, take action to actually take it
                        if (_stateManager.ControlSession.ReqControlState == ControlState.ReadyToControl)
                        {
                            _logger.Info("Application.ControlState set to ControlState.StartControl. (Requested = {0}).", _stateManager.ControlSession.ReqControlState);
                            State.SessionControl        = true;
                            _gotIntersectionControlTime = DateTime.Now;
                            Task.Run(() => SetReqControlStateAsync(ControlState.InControl), _sessionCancellationToken);
                        }
                        // Otherwise, log the error
                        else
                        {
                            var startControlError = "Application.ControlState set to ControlState.StartControl, but application not ready.";
                            _logger.Error(startControlError);
                            throw new JsonRpcException((int)ProtocolErrorCode.Error, startControlError, null);
                        }
                        break;

                    case ControlState.EndControl:
                        // If the application is not in Control, log the error
                        if (_stateManager.ControlSession.ReqControlState != ControlState.InControl &&
                            _stateManager.ControlSession.ReqControlState != ControlState.EndControl)
                        {
                            _logger.Error("TLC set Application.ControlState to ControlState.EndControl. (Requested = {0}).", _stateManager.ControlSession.ReqControlState);
                            throw new JsonRpcException((int)ProtocolErrorCode.Error, "TLC set Application.ControlState to ControlState.EndControl, but application not in control.", null);
                        }
                        else
                        {
                            if (_stateManager.ControlSession.ReqControlState == ControlState.EndControl)
                            {
                                _logger.Info("TLC set Application.ControlState to ControlState.EndControl, which was requested.");
                            }
                            else
                            {
                                if (DateTime.Now.Subtract(_gotIntersectionControlTime) < TimeSpan.FromSeconds(180))
                                {
                                    _logger.Warn("TLC requested EndControl less than 180 after StartControl: {0} seconds.", DateTime.Now.Subtract(_gotIntersectionControlTime).TotalSeconds);
                                }
                                _logger.Info("TLC set Application.ControlState to ControlState.EndControl (outside request). " +
                                             "Confirming by setting requested state.");
                                Task.Run(() => SetReqControlStateAsync(ControlState.EndControl), _sessionCancellationToken);
                            }
                        }
                        break;

                    default:
                        var error = $"Application.ControlState cannot be set to {application.ControlState}: this state is undefined.";
                        _logger.Error(error);
                        throw new JsonRpcException((int)ProtocolErrorCode.Error, error, null);
                    }
                    _stateManager.ControlSession.ControlState = application.ControlState;
                    if (_stateManager.ControlSession.ControlState.Value != ControlState.Error)
                    {
                        _logger.Debug("Application.ControlState set to " + _stateManager.ControlSession.ControlState);
                    }
                }
                else
                {
                    var error = $"UpdateState called with type Session, but {objectstateupdate.Objects.Ids.Length} instead of 1 object provided.";
                    _logger.Error(error);
                    throw new JsonRpcException((int)ProtocolErrorCode.Error, error, null);
                }
                break;

            case TLCObjectType.Intersection:
                for (var i = 0; i < objectstateupdate.Objects.Ids.Length; ++i)
                {
                    var inter = _stateManager.InternalIntersections.FirstOrDefault(x => x.Id == objectstateupdate.Objects.Ids[i]);
                    if (inter == null)
                    {
                        throw new JsonRpcException((int)ProtocolErrorCode.InvalidObjectReference, "Object " + objectstateupdate.Objects.Ids[i] + " unknown", null);
                    }
                    var sinter = (Intersection)objectstateupdate.States[i];
                    inter.StateTicks = sinter.StateTicks;
                    if (sinter.State.HasValue)
                    {
                        inter.State = sinter.State;
                    }
                    if (sinter.ReqState.HasValue)
                    {
                        inter.ReqState = sinter.ReqState;
                    }
                    switch (sinter.State)
                    {
                    case IntersectionControlState.Error:
                    case IntersectionControlState.Dark:
                    case IntersectionControlState.Standby:
                    case IntersectionControlState.AlternativeStandby:
                    case IntersectionControlState.AllRed:
                    case IntersectionControlState.SwitchOn:
                    case IntersectionControlState.SwitchOff:
                        State.IntersectionControl = false;
                        break;

                    case IntersectionControlState.Control:
                        State.IntersectionControl = true;
                        break;

                    default:
                        var error = $"Intersection.State cannot be set to {sinter.State}: This state is undefined.";
                        _logger.Error(error);
                        throw new JsonRpcException((int)ProtocolErrorCode.Error, error, null);
                    }
                    _logger.Debug("Intersection {0} state set to {1}", objectstateupdate.Objects.Ids[i], sinter.State);
                }
                break;

            case TLCObjectType.SignalGroup:
            case TLCObjectType.Detector:
            case TLCObjectType.Output:
            case TLCObjectType.Input:
            case TLCObjectType.Variable:
                for (var i = 0; i < objectstateupdate.Objects.Ids.Length; ++i)
                {
                    var upob = _stateManager.FindObjectById(objectstateupdate.Objects.Ids[i], TLCFIClientStateManager.GetObjectTypeString(objectstateupdate.Objects.Type));
                    if (upob == null)
                    {
                        throw new JsonRpcException((int)ProtocolErrorCode.InvalidObjectReference, "Object " + objectstateupdate.Objects.Ids[i] + " unknown", null);
                    }
                    try
                    {
                        ((TLCObjectBase)upob).CopyState(objectstateupdate.States[i]);
                    }
                    catch (InvalidCastException e)
                    {
                        throw new JsonRpcException((int)ProtocolErrorCode.InvalidObjectReference, "Object " + objectstateupdate.Objects.Ids[i] + " seems not to be of type " + objectstateupdate.Objects.Type, e);
                    }
                }
                break;

            case TLCObjectType.SpecialVehicleEventGenerator:
                if (objectstateupdate.Objects.Ids.Length == 1 && objectstateupdate.Objects.Ids[0] == _stateManager.ControlSession.Id &&
                    objectstateupdate.States.Length == 1)
                {
                    _stateManager.SpvhGenerator.CopyState(objectstateupdate.States[0]);
                }
                else
                {
                    var error = $"UpdateState called with type SpecialVehicleEventGenerator, but {objectstateupdate.Objects.Ids.Length} instead of 1 object provided.";
                    _logger.Error(error);
                    throw new JsonRpcException((int)ProtocolErrorCode.Error, error, null);
                }
                break;

            case TLCObjectType.TLCFacilities:
                var tlcferror = "UpdateState called with type TLCFacilities, which has no state.";
                _logger.Error(tlcferror);
                throw new JsonRpcException((int)ProtocolErrorCode.Error, tlcferror, null);

            default:
                throw new ArgumentOutOfRangeException();
            }
        }