private void DataChannel_Connected(object sender, EventArgs e) { try { // lock to prevent multiple simultaneous calls to the data channel from different threads lock (_locker) { _logger.Trace("Received Connected event from data channel"); // first thing to do is to send the info about this controller var controllerInfo = DataSource.GetControllerInfoData(); _dataChannel.Send(controllerInfo); // check state if (_dataChannel.IsConnected && _controlChannel.IsConnected) { State = PhoneControllerState.Ready; } } } catch (Exception ex) { State = PhoneControllerState.Error; RaiseErrorEvent("Error while sending controller info to server: " + ex.Message, ex); } }
private void DataChannel_Error(object sender, NetworkErrorEventArgs e) { _logger.Trace("Received Error event from data channel"); var state = State; if (state == PhoneControllerState.Closed || state == PhoneControllerState.Closing || state == PhoneControllerState.Error) { // ignore follow-up errors after we're closed or already in error state return; } State = PhoneControllerState.Error; RaiseErrorEvent(e); }
private void ControlChannel_ControlCommandReceived(object sender, ControlCommandReceivedEventArgs e) { _logger.Trace("Control command received: {0}, {1}", e.ControlCommand.DataType, e.ControlCommand.Action); if (e.ControlCommand.DataType == DataType.Configuration) { var command = e.ControlCommand as ConfigurationControlCommand; if (command != null && command.Configuration != null) { Configure(command.Configuration); } } else { try { // lock to prevent multiple simultaneous calls to the data source and channel from different threads lock (_locker) { if (e.ControlCommand.Action == ControlCommandAction.Start) { DataSource.StartAcquisition(e.ControlCommand.DataType); } else { DataSource.StopAcquisition(e.ControlCommand.DataType); _dataChannel.PurgeFromQueue(e.ControlCommand.DataType); } } } catch (Exception ex) { State = PhoneControllerState.Error; RaiseErrorEvent("Error while processing start/stop control command: " + ex.Message, ex); } } }
private void CreatePhoneControllerClient(IDataSource dataSource) { if (dataSource == null) { throw new ArgumentNullException("dataSource"); } // capture the current synchronization context _synchronizationContext = SynchronizationContext.Current; if (_synchronizationContext == null) { // create a default implementation _synchronizationContext = new SynchronizationContext(); } // store and set up data source DataSource = dataSource; DataSource.DataAcquired += DataSource_DataAcquired; // create all required networking objects _broadCaster = new MulticastClient(); _broadCaster.ServerDiscovered += BroadCaster_ServerDiscovered; _broadCaster.TimeoutElapsed += BroadCaster_TimeoutElapsed; _controlChannel = _communicationFactory.CreateControlChannel(); _controlChannel.Error += ControlChannel_Error; _controlChannel.ControlCommandReceived += ControlChannel_ControlCommandReceived; _controlChannel.Connected += ControlChannel_Connected; _controlChannel.ConnectionClosed += ControlChannel_ConnectionClosed; _dataChannel = _communicationFactory.CreateDataChannel(); _dataChannel.Error += DataChannel_Error; _dataChannel.Connected += DataChannel_Connected; _dataChannel.ConnectionClosed += DataChannel_ConnectionClosed; // configure some default values for the configuration here // these typically are overridden by a configuration sent down from the server side var configuration = new ControllerConfiguration(); configuration.AutoReconnectOnActivation = false; configuration.TouchInputMargin = new Thickness(90, 20, 20, 20); configuration.MinAccelerometerDataRate = 0; configuration.MinCompassDataRate = 0; configuration.MinGyroscopeDataRate = 0; configuration.MinMotionDataRate = 0; configuration.MinMillisecondsBetweenMessages = 0; // as fast as possible configuration.EnableTracing = false; configuration.TracingEndpointAddress = null; Configure(configuration); State = PhoneControllerState.Created; }
private void BroadCaster_TimeoutElapsed(object sender, EventArgs e) { lock (_locker) { _logger.Trace("Received TimeoutElapsed event from broadcaster"); var state = State; // broadcasting is also possible from the closed state (during re-initialization) // so we only check for the error state here if (state == PhoneControllerState.Error) { // ignore follow-up errors after we're closed or already in error state return; } State = PhoneControllerState.Error; // create new error event args var eventArgs = new NetworkErrorEventArgs("Timeout while broadcasting.", "TimeOut", null); RaiseErrorEvent(eventArgs); } }
private void ControlChannel_Connected(object sender, EventArgs e) { // lock to prevent overlapping operations on the channels lock (_locker) { _logger.Trace("Received Connected event from the control channel"); // check state if (_dataChannel.IsConnected && _controlChannel.IsConnected) { State = PhoneControllerState.Ready; } } }
/// <summary> /// Shuts down the controller. After a successful shutdown, the controller transitions to the <c>Closed</c> /// state and can be re-initialized later (e.g. re-connected by using the respective <c>ConnectAsync</c> methods). /// </summary> public void Shutdown() { try { // lock to prevent multiple simultaneous calls to any of the involved objects from different threads lock (_locker) { var state = State; // only proceed when valid (we allow calling shutdown when the state is "Closed" or "Created" etc. // because certain actions like broadcasting are allowed in these states too) if (state == PhoneControllerState.Closing) { return; } _logger.Trace("Shutting down"); // transition from closing to close, shutting down everything State = PhoneControllerState.Closing; if (DataSource != null) { DataSource.Shutdown(); } if (_broadCaster != null) { _broadCaster.Shutdown(); } if (_dataChannel != null) { _dataChannel.Shutdown(); } if (_controlChannel != null) { _controlChannel.Shutdown(); } State = PhoneControllerState.Closed; } } catch (Exception ex) { throw new PhoneControllerException("Error while shutting down the phone controller client: " + ex.Message, ex); } }
/// <summary> /// Shuts down the controller. After a successful shutdown, the controller transitions to the <c>Closed</c> /// state and can be re-initialized later. /// </summary> public void Shutdown() { try { // avoid that multiple threads invoke this in parallel lock (_locker) { var state = State; // shortcut if (state == PhoneControllerState.Created) { State = PhoneControllerState.Closed; return; } // only proceed when valid if (state == PhoneControllerState.Closing || state == PhoneControllerState.Closed) { return; } _logger.Trace("Shutting down"); State = PhoneControllerState.Closing; if (_broadCastListener != null) { _broadCastListener.Shutdown(); } if (_dataChannel != null) { _dataChannel.Shutdown(); } if (_controlChannel != null) { _controlChannel.Shutdown(); } State = PhoneControllerState.Closed; } } catch (Exception ex) { throw new PhoneControllerException("Error while shutting down the phone controller client: " + ex.Message, ex); } }
private void DataChannel_DataMessageReceived(object sender, DataMessageEventArgs e) { // do not report data if we are not ready // (e.g. data may still drop in if the client version does not match the // expected version and we've transitioned to Error state already, // and until the consumer of the library does something like shutting down). if (State != PhoneControllerState.Ready) { return; } _logger.Trace("Received DataMessageReceived event from data channel"); // check if we have a controller info data message if (e.DataMessage.DataType == DataType.ControllerInfo) { // make sure that the version information fits what we expect var controllerInfoData = e.DataMessage as ControllerInfoData; if (controllerInfoData.ClientVersion != Constants.ControllerVersion) { _logger.Trace("Client controller version {0} does not match expected version {1}.", controllerInfoData.ClientVersion, Constants.ControllerVersion); State = PhoneControllerState.Error; var error = new ControllerVersionMismatchException(Constants.ControllerVersion, controllerInfoData.ClientVersion); RaiseErrorEvent(error); return; } } // simply pass through RaiseDataMessageReceivedEvent(e.DataMessage); }
/// <summary> /// Asynchronously connects to a server using the given server address. /// </summary> /// <param name="ipAddress">The IP address of the server to connect to.</param> public void ConnectAsync(IPAddress ipAddress) { try { // lock to prevent multiple simultaneous calls to the channels from different threads lock (_locker) { var state = State; if (state != PhoneControllerState.Created && state != PhoneControllerState.Closed) { throw new InvalidOperationException("Can only initialize from Created or Closed state (current state " + State + ")."); } _logger.Trace("Trying to connect to server using IP address {0}", ipAddress); ServerAddress = ipAddress; _controlChannel.ConnectAsync(ServerAddress.ToString(), Constants.CommunicationControlChannelPort); _dataChannel.ConnectAsync(ServerAddress.ToString(), Constants.CommunicationDataChannelPort); State = PhoneControllerState.Initialized; } } catch (Exception ex) { throw new PhoneControllerException("Error while starting async connection: " + ex.Message, ex); } }
private void DataChannel_ClientAccepted(object sender, EventArgs e) { // avoid multiple threads accessing the channels lock (_locker) { _logger.Trace("Received ClientAccepted event from data channel"); if (_dataChannel.IsConnected && _controlChannel.IsConnected) { State = PhoneControllerState.Ready; } } }
private void CreatePhoneControllerServer() { // capture the current synchronization context _synchronizationContext = SynchronizationContext.Current; if (_synchronizationContext == null) { // create a default implementation _synchronizationContext = new SynchronizationContext(); } // create all required networking objects _broadCastListener = new MulticastListener(); _controlChannel = _communicationFactory.CreateControlChannel(); _controlChannel.ClientAccepted += ControlChannel_ClientAccepted; _controlChannel.ConnectionClosed += ControlChannel_ConnectionClosed; _controlChannel.ControlCommandSent += ControlChannel_ControlCommandSent; _controlChannel.Error += ControlChannel_Error; _dataChannel = _communicationFactory.CreateDataChannel(); _dataChannel.ClientAccepted += DataChannel_ClientAccepted; _dataChannel.ConnectionClosed += DataChannel_ConnectionClosed; _dataChannel.Error += DataChannel_Error; _dataChannel.DataMessageReceived += DataChannel_DataMessageReceived; // set initial state State = PhoneControllerState.Created; }
private void ControlChannel_ClientAccepted(object sender, EventArgs e) { lock (_locker) { _logger.Trace("Received ClientAccepted event from control channel"); if (_dataChannel.IsConnected && _controlChannel.IsConnected) { State = PhoneControllerState.Ready; } } }
private void DataSource_DataAcquired(object sender, DataMessageEventArgs e) { try { // lock to prevent multiple simultaneous calls to the data channel from different threads lock (_locker) { _logger.Trace("Received data from the data source: {0}", e.DataMessage.DataType); if (_dataChannel != null && _dataChannel.IsConnected) { // for some messages, we need to do some clean-up here. // explanation: we use two separate sockets to transfer data: TCP and UDP. // some data messages require a logical sequence, e.g. PinchComplete must always occur after the last Pinch. // the problem is that for performance reasons, data messages like Pinch are sent using UDP, whereas // all "Complete" messages are sent using TCP (because their delivery must be guaranteed). This can lead // to the situation that Pinch messages arrive _after_ PinchComplete messages on the server side. // we avoid this here by purging all queued messages of a certain kind if we are about to send a corresponding // "Complete" message. switch (e.DataMessage.DataType) { case DataType.CustomDragComplete: _dataChannel.PurgeFromQueue(DataType.CustomDrag); break; case DataType.DragComplete: _dataChannel.PurgeFromQueue(DataType.FreeDrag); _dataChannel.PurgeFromQueue(DataType.HorizontalDrag); _dataChannel.PurgeFromQueue(DataType.VerticalDrag); break; case DataType.PinchComplete: _dataChannel.PurgeFromQueue(DataType.Pinch); break; } // now actually send the message _dataChannel.Send(e.DataMessage); } } } catch (Exception ex) { State = PhoneControllerState.Error; RaiseErrorEvent("Error while processing and sending acquired data: " + ex.Message, ex); } }
private void BroadCaster_ServerDiscovered(object sender, ServerDiscoveredEventArgs e) { try { // lock to prevent multiple simultaneous calls to the channels from different threads lock (_locker) { _logger.Trace("Discovered a server at {0}", e.RemoteAddress); _controlChannel.ConnectAsync(e.RemoteAddress, Constants.CommunicationControlChannelPort); _dataChannel.ConnectAsync(e.RemoteAddress, Constants.CommunicationDataChannelPort); ServerAddress = IPAddress.Parse(e.RemoteAddress); State = PhoneControllerState.Initialized; } } catch (Exception ex) { State = PhoneControllerState.Error; RaiseErrorEvent("Error while connecting control and data channels: " + ex.Message, ex); } }
/// <summary> /// Initializes a new instance of the <see cref="PhoneControllerStateEventArgs"/> class. /// </summary> /// <param name="state">The new state of the controller.</param> public PhoneControllerStateEventArgs(PhoneControllerState state) { State = state; }
/// <summary> /// Initializes the controller with the given local IP address and starts waiting for incoming client connections. /// </summary> /// <param name="localAddress">The local IP address to use for the controller.</param> public void Initialize(IPAddress localAddress) { try { // make sure the state transitioning happens atomically lock (_locker) { var state = State; if (state != PhoneControllerState.Created && state != PhoneControllerState.Closed) { throw new InvalidOperationException("Can only initialize from Created or Closed state (current state " + State + ")."); } _logger.Trace("Initializing with local address {0}", localAddress); _broadCastListener.StartListening(); _controlChannel.ListenAsync(localAddress.ToString(), Constants.CommunicationControlChannelPort); _dataChannel.ListenAsync(localAddress.ToString(), Constants.CommunicationDataChannelPort); State = PhoneControllerState.Initialized; } } catch (Exception ex) { throw new PhoneControllerException("Error while starting the control and data channels: " + ex.Message, ex); } }