using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Google.Protobuf; using Tango.BL.Entities; using Tango.Integration.Operation; using Tango.PMR; using Tango.PMR.Common; using Tango.Transport; using Tango.Transport.Transporters; using Tango.PMR.Integration; using Tango.Transport.Discovery; using Tango.Transport.Servers; using Tango.Transport.Adapters; using Tango.PMR.Connection; using Tango.PMR.Diagnostics; using Tango.PMR.Debugging; using System.Security.Authentication; using Tango.Settings; using Tango.Core.ExtensionMethods; using Tango.PMR.MachineStatus; using Tango.PMR.Power; using Tango.Core; using Microsoft.AspNet.SignalR.Client; using Tango.Integration.ExternalBridge.Web; using Tango.Core.Threading; using System.Diagnostics; using System.Reflection; using Newtonsoft.Json; using System.Collections.ObjectModel; namespace Tango.Integration.ExternalBridge { public class ExternalBridgeService : ExtendedObject, IExternalBridgeService { private List _receivers; private UdpDiscoveryService _discoveryService; private TcpServer _tcpServer; private int _discovery_port = 8888; //Will be overridden by settings in constructor.. private int _external_bridge_port = 1984; //Will be overridden by settings in constructor.. private String _multicastAddress = "234.55.66.77"; //Will be overridden by settings in constructor.. private HubConnection _connection; private IHubProxy _proxy; private bool _isSignalRConnected; private System.Timers.Timer _signalrPingTimer; private Dictionary _requestHandlers; private static JsonSerializerSettings _genericMessageSettings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }; private class RequestHandler { public IExternalBridgeRequestHandler Handler { get; set; } public MethodInfo Method { get; set; } public RequestHandlerLoggingMode LoggingMode { get; set; } } #region Events /// /// Occurs when a new client is waiting for authentication. /// public event EventHandler ConnectionRequest; /// /// Occurs when the last client has been disconnected. /// public event EventHandler FullControlSessionDisconnected; /// /// Occurs when the service has received a new color profile request. /// public event EventHandler ColorProfileRequest; #endregion #region Properties public static HashSet SafetyLevelOperations { get; private set; } /// /// Gets the collection of logged-in receivers. /// public List ActiveReceivers { get { return _receivers.Where(x => x.IsLoggedIn).ToList(); } } /// /// Gets or sets the TCP transport adapter write mode when creating a TCP receiver. /// public TcpTransportAdapterWriteMode TcpTransportAdapterWriteMode { get; set; } /// /// Gets or sets the SignalR configuration. /// public ExternalBridgeSignalRConfiguration SignalRConfiguration { get; set; } private IMachineOperator _machineOperator; /// /// Gets or sets the machine operator. /// public IMachineOperator MachineOperator { get { return _machineOperator; } set { _machineOperator = value; OnMachineOperatorChanged(); } } private Machine _machine; /// /// Gets or sets the machine. /// public Machine Machine { get { return _machine; } set { _machine = value; OnMachineChanged(); } } /// /// Gets a value indicating whether this instance is started. /// public bool IsStarted { get; private set; } private bool _enabled; /// /// Gets or sets a value indicating whether this is enabled. /// public bool Enabled { get { return _enabled; } set { _enabled = value; if (_enabled) { Start(); } else { Stop(); } RaisePropertyChangedAuto(); } } private bool _hasSessions; /// /// Gets a value indicating whether there are any connected sessions. /// public bool HasSessions { get { return _hasSessions; } private set { _hasSessions = value; RaisePropertyChangedAuto(); } } private ExternalBridgeReceiver _fullControlSessionReceiver; /// /// Gets the current full control session receiver. /// public ExternalBridgeReceiver FullControlSessionReceiver { get { return _fullControlSessionReceiver; } private set { _fullControlSessionReceiver = value; RaisePropertyChangedAuto(); } } #endregion #region Constructors /// /// Initializes the class. /// static ExternalBridgeService() { SafetyLevelOperations = new HashSet(); } /// /// Initializes a new instance of the class. /// public ExternalBridgeService() { _requestHandlers = new Dictionary(); SignalRConfiguration = new ExternalBridgeSignalRConfiguration(); TcpTransportAdapterWriteMode = TcpTransportAdapterWriteMode.Interval; _receivers = new List(); var settings = SettingsManager.Default.GetOrCreate(); _discovery_port = settings.ExternalBridgeServiceDiscoveryPort; _external_bridge_port = settings.ExternalBridgeServicePort; _multicastAddress = settings.ExternalBridgeMulticastGroup; _tcpServer = new TcpServer(_external_bridge_port); _tcpServer.ClientConnected += _tcpServer_ClientConnected; LogManager.NewLog += LogManager_NewLog; } /// /// Initializes a new instance of the class. /// /// The machine operator. /// The machine. public ExternalBridgeService(IMachineOperator machineOperator, Machine machine) : this() { Machine = machine; MachineOperator = machineOperator; } #endregion #region Properties Changes /// /// Called when the machine has been changed /// protected virtual void OnMachineChanged() { if (_discoveryService != null && _discoveryService.IsStarted) { _discoveryService.Stop(); } _discoveryService = new UdpDiscoveryService(_discovery_port, new ExternalBridgeUdpDiscoveryPacket() { SerialNumber = Machine.SerialNumber, Guid = Machine.Guid, MachineType = (PMR.Common.MachineType)Machine.MachineType }) { MulticastGroupAddress = _multicastAddress, TcpValidationInfo = new TcpValidationInfo() { Guid = Machine.Guid, SerialNumber = Machine.SerialNumber, MachineType = Machine.MachineType } }; _discoveryService.BeforeBroadcasting -= _discoverySevice_BeforeBroadcasting; _discoveryService.BeforeBroadcasting += _discoverySevice_BeforeBroadcasting; } private void _discoverySevice_BeforeBroadcasting(object sender, ExternalBridgeUdpDiscoveryPacket e) { e.Time = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"); } /// /// Called when the machine operator has been changed /// protected virtual void OnMachineOperatorChanged() { if (MachineOperator != null) { MachineOperator.StatusChanged -= MachineOperator_StatusChanged; MachineOperator.StatusChanged += MachineOperator_StatusChanged; MachineOperator.PendingResponseReceived -= MachineOperator_PendingResponseReceived; MachineOperator.PendingResponseReceived += MachineOperator_PendingResponseReceived; MachineOperator.EventsNotification -= MachineOperator_EventsNotification; MachineOperator.EventsNotification += MachineOperator_EventsNotification; } } #endregion #region Event Handlers /// /// Handles the ClientConnected event of the _tcpServer control. /// /// The source of the event. /// The instance containing the event data. private async void _tcpServer_ClientConnected(object sender, ClientConnectedEventArgs e) { LogManager.Log("External bridge TCP client connected from: " + e.Socket.GetIPAddress()); ExternalBridgeReceiver receiver = new ExternalBridgeReceiver(e.Socket, MachineOperator); (receiver.Adapter as TcpTransportAdapter).WriteMode = TcpTransportAdapterWriteMode; receiver.LoginRequest += Receiver_LoginRequest; receiver.ColorProfileRequest += Receiver_ColorProfileRequest; receiver.Disconnected += Receiver_Disconnected; receiver.ReceiverRequestReceived += Receiver_ReceiverRequestReceived; _receivers.Add(receiver); await receiver.Connect(); RaisePropertyChanged(nameof(ActiveReceivers)); } private async void OnSignalRSessionCreated(String sessionID) { try { LogManager.Log("External bridge SignalR client connected."); var adapter = new SignalRTransportAdapter(SignalRConfiguration.Address, SignalRConfiguration.Hub, SignalRTransportAdapterMode.JoinSession, Machine.SerialNumber, sessionID); ; ExternalBridgeReceiver receiver = new ExternalBridgeReceiver(adapter, MachineOperator); receiver.LoginRequest += Receiver_LoginRequest; receiver.ColorProfileRequest += Receiver_ColorProfileRequest; receiver.Disconnected += Receiver_Disconnected; receiver.ReceiverRequestReceived += Receiver_ReceiverRequestReceived; _receivers.Add(receiver); await receiver.Connect(); RaisePropertyChanged(nameof(ActiveReceivers)); } catch (Exception ex) { LogManager.Log(ex, "Error initializing SignalR ExternalBridgeReceiver when session created."); } } private void MachineOperator_StatusChanged(object sender, MachineStatuses status) { try { _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics).ToList().ForEach(x => x.UpdateMachineOperatorStatus((UpdateStatus)status)); } catch (Exception ex) { LogManager.Log(ex, "Error updating status of external bridge service client."); } } private void LogManager_NewLog(object sender, Tango.Logging.LogItemBase e) { var toSend = _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresApplicationLogs).ToList(); if (_receivers.Count > 0) { var msg = MessageFactory.CreateTangoMessage(new StartApplicationLogsResponse() { LogItem = ByteString.CopyFrom(e.Serialize()) }); msg.ToBytes(); toSend.ToList().Where(x => x.State == TransportComponentState.Connected).ToList().ForEach(x => x.UpdateApplicationLogs(msg.Container)); } } private void MachineOperator_PendingResponseReceived(object sender, MessageContainer container) { OnOperatorResponseReceived(container); } private void MachineOperator_EventsNotification(object sender, StartEventsNotificationResponse e) { try { MessageContainer container = new MessageContainer(); container.Type = MessageType.StartEventsNotificationResponse; container.Continuous = true; container.Data = e.ToByteString(); _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresEventsNotification).ToList().ForEach(x => x.UpdateEvents(container)); } catch (Exception ex) { LogManager.Log(ex, "Error on external bridge machine events broadcast."); } } #endregion #region Receiver Events Handlers private void Receiver_Disconnected(object sender, EventArgs e) { var receiver = sender as ExternalBridgeReceiver; receiver.LoginRequest -= Receiver_LoginRequest; receiver.ColorProfileRequest -= Receiver_ColorProfileRequest; receiver.Disconnected -= Receiver_Disconnected; receiver.ReceiverRequestReceived -= Receiver_ReceiverRequestReceived; _receivers.Remove(receiver); if (receiver.LoginIntent == ExternalBridgeLoginIntent.FullControl) { FullControlSessionDisconnected?.Invoke(this, new EventArgs()); } HasSessions = _receivers.Count(x => x.IsLoggedIn) > 0; FullControlSessionReceiver = _receivers.SingleOrDefault(x => x.IsLoggedIn && x.LoginIntent == ExternalBridgeLoginIntent.FullControl); //Notify request handlers about this receiver disconnected. foreach (var requestHandler in _requestHandlers.ToList().Select(x => x.Value.Handler)) { try { requestHandler.OnReceiverDisconnected(receiver); } catch { } //Ignore exceptions on handler. } RaisePropertyChanged(nameof(ActiveReceivers)); } private void Receiver_ColorProfileRequest(object sender, ColorProfileRequestEventArgs e) { ColorProfileRequest?.Invoke(this, e); } private void Receiver_LoginRequest(object sender, ExternalBridgeReceiverLoginRequestEventArgs e) { var request = e.Request; if (request.Intent == ExternalBridgeLoginIntent.FullControl && _receivers.Any(x => x.IsLoggedIn && x.LoginIntent == ExternalBridgeLoginIntent.FullControl)) { e.Decline("Only one full control client is allowed."); return; } if (request.Intent == ExternalBridgeLoginIntent.FullControl && MachineOperator.IsPrinting) { e.Decline($"External bridge client login failed because the machine is currently printing and '{ExternalBridgeLoginIntent.FullControl}' intent was requested."); return; } ExternalBridgeClientConnectedEventArgs args = null; args = new ExternalBridgeClientConnectedEventArgs(() => { //Confirmed e.Confirm(Machine, MachineOperator.DeviceInformation, args.ApplicationInformation); HasSessions = true; if (request.Intent == ExternalBridgeLoginIntent.FullControl) { FullControlSessionReceiver = sender as ExternalBridgeReceiver; } RaisePropertyChanged(nameof(ActiveReceivers)); }, (reason) => { //Declined e.Decline(reason); }); args.Request = request; args.Address = e.Address; ConnectionRequest?.Invoke(this, args); } private void Receiver_ReceiverRequestReceived(object sender, ExternalBridgeReceiverRequestReceivedEventArgs e) { ExternalBridgeReceiver receiver = sender as ExternalBridgeReceiver; if (e.Container.Type == MessageType.GenericRequest || _requestHandlers.ContainsKey(e.Container.Type.ToString())) { e.Handled = true; ThreadFactory.StartNew(() => { try { var message = MessageFactory.ExtractMessageFromContainer(e.Container); if (e.Container.Type != MessageType.GenericRequest) //Handle standard PMR messages. { var handler = _requestHandlers[e.Container.Type.ToString()]; if (handler.Method.ReturnType == typeof(Task)) { ((Task)handler.Method.Invoke(handler.Handler, new object[] { message, e.Container.Token, sender, })).Wait(); } else { handler.Method.Invoke(handler.Handler, new object[] { message, e.Container.Token, sender, }); } } else //Handle GenericRequest with inner JSON/BSON formated generic message. { var genericType = Type.GetType((message as GenericRequest).Type); if (genericType != null) { try { if (_requestHandlers.ContainsKey(genericType.FullName)) { var innerMessage = GenericMessageSerializer.DeserializeFromByteString(genericType, (message as GenericRequest).Data, receiver.GenericProtocol); var handler = _requestHandlers[genericType.FullName]; if (handler.LoggingMode == RequestHandlerLoggingMode.LogRequestName || handler.LoggingMode == RequestHandlerLoggingMode.LogRequestNameAndContent) { String content = "."; if (handler.LoggingMode == RequestHandlerLoggingMode.LogRequestNameAndContent) { content = $":\n{innerMessage.ToJsonString()}"; } LogManager.Log($"'{innerMessage.GetType().Name}' received on '{handler.Method.DeclaringType.Name}.{handler.Method.Name}'{content}"); } if (handler.Method.ReturnType == typeof(Task)) { ((Task)handler.Method.Invoke(handler.Handler, new object[] { innerMessage, e.Container.Token, sender, })).Wait(); } else { handler.Method.Invoke(handler.Handler, new object[] { innerMessage, e.Container.Token, sender, }); } } else { receiver.SendErrorResponse(new NotSupportedException("Request message not supported on this PPC version."), e.Container.Token); } } catch (Exception ex) { LogManager.Log(ex.GetFirstIfAggregate(), $"Error invoking external bridge handler for request '{genericType.Name}'."); try { receiver.SendErrorResponse(ex.GetFirstIfAggregate(), e.Container.Token); } catch { } } } else { receiver.SendErrorResponse(new NotSupportedException("Request message not supported on this PPC version."), e.Container.Token); } } } catch (Exception ex) { LogManager.Log(ex, $"An error occurred while trying or invoking an external bridge request handler for '{e.Container.Type}'."); try { receiver.SendErrorResponse(ex.GetFirstIfAggregate(), e.Container.Token); } catch { } } }); } } #endregion #region Public Methods /// /// Starts this instance. /// public void Start() { if (!IsStarted) { LogManager.Log("Starting external bridge service..."); LogManager.Log("Starting TCP server..."); try { _tcpServer.Start(); } catch (Exception ex) { LogManager.Log(ex, "Error starting external bridge TCP server."); } LogManager.Log("Starting discovery service..."); try { _discoveryService.Start(); } catch (Exception ex) { LogManager.Log(ex, "Error starting external bridge discovery service."); } if (SignalRConfiguration.Enabled) { StartSignalR(); } IsStarted = true; _enabled = true; RaisePropertyChanged(nameof(Enabled)); } } private void StartSignalR() { if (!_enabled || _isSignalRConnected) return; try { try { if (_connection != null) { _connection.Dispose(); } } catch { } LogManager.Log("Starting SignalR service..."); _connection = new HubConnection(SignalRConfiguration.Address); _proxy = _connection.CreateHubProxy(SignalRConfiguration.Hub); _proxy.On("OnSessionCreated", OnSignalRSessionCreated); _connection.TransportConnectTimeout = TimeSpan.FromMinutes(2); _connection.Start(); _connection.StateChanged += (x) => { if (x.NewState == ConnectionState.Connected) { try { LogManager.Log("External Bridge Service SignalR Connected. Registering machine..."); _proxy.Invoke("RegisterMachine", new MachineInfo() { SerialNumber = Machine.SerialNumber, Organization = Machine.Organization.Name, MachineType = Machine.MachineType }); _isSignalRConnected = true; if (_signalrPingTimer == null) { _signalrPingTimer = new System.Timers.Timer(TimeSpan.FromMinutes(5).TotalMilliseconds); _signalrPingTimer.Elapsed += _signalrPingTimer_Elapsed; _signalrPingTimer.Start(); } } catch (Exception ex) { LogManager.Log(ex, "Error registering machine via SignalR."); } } else if (x.NewState == ConnectionState.Disconnected) { _isSignalRConnected = false; if (Enabled) { LogManager.Log("External Bridge Service SignalR Disconnected/Failed. Reconnecting in 1 minute..."); TimeoutTask.StartNew(StartSignalR, TimeSpan.FromMinutes(1)); } } else if (x.NewState == ConnectionState.Reconnecting) { LogManager.Log("External Bridge Service SignalR Connection Lost. Reconnecting..."); //Will go invoke state change again with "connected" if successful... } }; } catch (Exception ex) { LogManager.Log(ex, "Error initializing External Bridge SignalR. Will try again in 5 minutes..."); TimeoutTask.StartNew(StartSignalR, TimeSpan.FromMinutes(5)); } } private async void _signalrPingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (Enabled && SignalRConfiguration.Enabled) { if (_proxy != null) { try { var result = await _proxy.Invoke("Ping"); } catch { Debug.WriteLine("Error pinging to machine service via SignalR."); } } } } /// /// Stops this instance. /// public async void Stop() { if (IsStarted) { try { _tcpServer.Stop(); _discoveryService.Stop(); } catch (Exception ex) { LogManager.Log(ex, "Error disposing TCP discovery services."); } foreach (var receiver in _receivers.ToList()) { try { await receiver.Disconnect(); } catch (Exception ex) { LogManager.Log(ex, $"Error disconnecting receiver {receiver.ComponentName}."); } } _enabled = false; if (SignalRConfiguration.Enabled) { try { _isSignalRConnected = false; await _proxy.Invoke("UnregisterMachine"); _connection.Stop(); _connection.Dispose(); } catch (Exception ex) { LogManager.Log(ex, "Error disposing SignalR connection."); } } IsStarted = false; HasSessions = false; RaisePropertyChanged(nameof(Enabled)); } } /// /// Disconnects the current remote client session. /// public async void DisconnectFullControlSession() { var sessionReceiver = _receivers.SingleOrDefault(x => x.IsLoggedIn && x.LoginIntent == ExternalBridgeLoginIntent.FullControl); if (sessionReceiver != null) { await sessionReceiver.Disconnect(); } } #endregion #region Virtual Methods protected virtual void OnOperatorResponseReceived(MessageContainer container) { try { switch (container.Type) { case MessageType.StartDiagnosticsResponse: _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresDiagnostics).ToList().ForEach(x => x.UpdateDiagnostics(container)); break; case MessageType.StartDebugLogResponse: _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresDebugLogs).ToList().ForEach(x => x.UpdateDebugLogs(container)); break; case MessageType.StartEventsNotificationResponse: //_receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresEventsNotification).ToList().ForEach(x => x.UpdateEvents(container)); break; case MessageType.StartMachineStatusUpdateResponse: _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresMachineStatusUpdate).ToList().ForEach(x => x.UpdateMachineStatus(container)); break; case MessageType.StartInkFillingStatusResponse: _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresInkFillingStatus).ToList().ForEach(x => x.UpdateInkFillingStatus(container)); break; } } catch { } } #endregion #region Handlers Registration public void RegisterRequestHandler(IExternalBridgeRequestHandler handler) { foreach (var method in handler.GetType().GetMethods()) { var att = method.GetCustomAttribute(); if (att != null) { _requestHandlers.Add(att.Type is IMessage ? att.Type.Name : att.Type.FullName, new RequestHandler() { Handler = handler, Method = method, LoggingMode = att.LoggingMode, }); } } } public void UnregisterRequestHandler(IExternalBridgeRequestHandler handler) { foreach (var h in _requestHandlers.Where(x => x.Value.Handler == handler).ToList()) { _requestHandlers.Remove(h.Key); } } #endregion } }