Last-Modified: Thu, 30 Jul 2026 19:01:36 GMT Expires: Sun, 27 Jul 2036 19:01:36 GMT Commands « Tango.Core « Visual_Studio « Software - Tango - Twine softwares
aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.Core/Commands
ModeNameSize
-rw-r--r--RelayCommand.cs7261logstatsplain
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;

namespace Tango.Integration.ExternalBridge
{
    public class ExternalBridgeService : BasicTransporter, IExternalBridgeService
    {
        private UdpDiscoveryService<ExternalBridgeUdpDiscoveryPacket> _discoverySevice;
        private TcpServer _tcpServer;
        private bool _send_app_logs;
        private String _app_logs_token;
        private Dictionary<MessageType, Action<MessageContainer>> _messageHandlers;

        #region Events

        /// <summary>
        /// Occurs when a new client is waiting for authentication.
        /// </summary>
        public event EventHandler<ExternalBridgeClientConnectedEventArgs> ConnectionRequest;

        /// <summary>
        /// Occurs when the last client has been disconnected.
        /// </summary>
        public event EventHandler ClientDisconnected;

        #endregion

        #region Properties

        private IMachineOperator _machineOperator;
        /// <summary>
        /// Gets or sets the machine operator.
        /// </summary>
        public IMachineOperator MachineOperator
        {
            get { return _machineOperator; }
            set { _machineOperator = value; OnMachineOperatorChanged(); }
        }

        private Machine _machine;
        /// <summary>
        /// Gets or sets the machine.
        /// </summary>
        public Machine Machine
        {
            get { return _machine; }
            set { _machine = value; OnMachineChanged(); }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is started.
        /// </summary>
        public bool IsStarted { get; private set; }

        private bool _enabled;
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="IExternalBridgeService" /> is enabled.
        /// </summary>
        public bool Enabled
        {
            get { return _enabled; }
            set
            {
                _enabled = value;

                if (_enabled)
                {
                    Start();
                }
                else
                {
                    Stop();
                }

                RaisePropertyChangedAuto();
            }
        }

        private bool _isInSession;
        /// <summary>
        /// Gets a value indicating whether a remote client is authenticated and in session.
        /// </summary>
        public bool IsInSession
        {
            get { return _isInSession; }
            private set { _isInSession = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Gets the current session login intent.
        /// </summary>
        public ExternalBridgeLoginIntent SessionIntent { get; private set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ExternalBridgeService"/> class.
        /// </summary>
        public ExternalBridgeService()
        {
            _messageHandlers = new Dictionary<MessageType, Action<MessageContainer>>();
            RegisterMessageHandlers();

            _tcpServer = new TcpServer(1984);
            _tcpServer.ClientConnected += _tcpServer_ClientConnected;

            LogManager.NewLog += LogManager_NewLog;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ExternalBridgeService"/> class.
        /// </summary>
        /// <param name="machineOperator">The machine operator.</param>
        /// <param name="machine">The machine.</param>
        public ExternalBridgeService(IMachineOperator machineOperator, Machine machine) : this()
        {
            Machine = machine;
            MachineOperator = machineOperator;
        }

        #endregion

        #region Properties Changes

        /// <summary>
        /// Called when the machine has been changed
        /// </summary>
        protected virtual void OnMachineChanged()
        {
            if (_discoverySevice != null && _discoverySevice.IsStarted)
            {
                _discoverySevice.Stop();
            }

            _discoverySevice = new UdpDiscoveryService<ExternalBridgeUdpDiscoveryPacket>(8888, new ExternalBridgeUdpDiscoveryPacket()
            {
                SerialNumber = Machine.SerialNumber,
            });

            _discoverySevice.BeforeBroadcasting -= _discoverySevice_BeforeBroadcasting;
            _discoverySevice.BeforeBroadcasting += _discoverySevice_BeforeBroadcasting;
        }

        private void _discoverySevice_BeforeBroadcasting(object sender, ExternalBridgeUdpDiscoveryPacket e)
        {
            e.Time = DateTime.Now.ToString();
        }

        /// <summary>
        /// Called when the machine operator has been changed
        /// </summary>
        protected virtual void OnMachineOperatorChanged()
        {
            if (MachineOperator != null)
            {
                MachineOperator.StateChanged -= MachineOperator_StateChanged;
                MachineOperator.StateChanged += MachineOperator_StateChanged;
            }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the ClientConnected event of the _tcpServer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="ClientConnectedEventArgs"/> instance containing the event data.</param>
        private async void _tcpServer_ClientConnected(object sender, ClientConnectedEventArgs e)
        {
            LogManager.Log("External bridge client connected from: " + e.Socket.GetIPAddress());

            Adapter = new TcpTransportAdapter(e.Socket);
            await Connect();
        }

        /// <summary>
        /// Machines the operator state changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void MachineOperator_StateChanged(object sender, TransportComponentState e)
        {
            //Do nothing right now.
        }

        private void LogManager_NewLog(object sender, Tango.Logging.LogItemBase e)
        {
            if (State == TransportComponentState.Connected && _send_app_logs)
            {
                try
                {
                    SendResponse<StartApplicationLogsResponse>(new StartApplicationLogsResponse()
                    {
                        LogItem = ByteString.CopyFrom(e.Serialize())
                    }, _app_logs_token);
                }
                catch { }
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public void Start()
        {
            if (!IsStarted)
            {
                _tcpServer.Start();
                _discoverySevice.Start();

                IsStarted = true;
                _enabled = true;
                RaisePropertyChanged(nameof(Enabled));
            }
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public async void Stop()
        {
            if (IsStarted)
            {
                _tcpServer.Stop();
                _discoverySevice.Stop();

                IsStarted = false;
                IsInSession = false;
                _enabled = false;
                RaisePropertyChanged(nameof(Enabled));

                try
                {
                    await Disconnect();
                }
                catch { }
            }
        }

        /// <summary>
        /// Disconnects the current remote client session.
        /// </summary>
        public async void DisconnectSession()
        {
            await Disconnect();
        }

        #endregion

        #region Override Methods

        protected override void OnRequestReceived(MessageContainer container)
        {
            base.OnRequestReceived(container);

            try
            {
                if (Enabled)
                {
                    if (container.Type == MessageType.ConnectRequest || container.Type == MessageType.DisconnectRequest)
                    {
                        //Do nothing !
                    }
                    else
                    {
                        if (IsInSession)
                        {
                            if (container.Type == MessageType.ExternalBridgeLoginRequest)
                            {
                                SendErrorResponse(new AuthenticationException("Machine is already in session."), container.Token);
                                return;
                            }

                            if (_messageHandlers.ContainsKey(container.Type))
                            {
                                try
                                {
                                    try
                                    {
                                        _messageHandlers[container.Type](container);
                                    }
                                    catch (Exception ex)
                                    {
                                        SendErrorResponse(ex, container.Token);
                                    }
                                }
                                catch (Exception ex)
                                {
                                    if (ex is ResponseErrorException)
                                    {
                                        SendResponse((ex as ResponseErrorException).Container);
                                    }
                                    else
                                    {
                                        SendErrorResponse(ex, container.Token);
                                    }
                                }
                            }
                            else
                            {
                                OnAnyRequest(container);
                            }
                        }
                        else
                        {
                            if (container.Type == MessageType.ExternalBridgeLoginRequest)
                            {
                                OnExternalBridgeLoginRequest(container);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, String.Format("An error occurred while processing a request message '{0}' from the remote host.", container.Type));
            }
        }

        public async override Task Disconnect()
        {
            _send_app_logs = false;

            try
            {
                if (IsInSession)
                {
                    await SendRequest<ExternalBridgeLogoutRequest, ExternalBridgeLogoutResponse>(new ExternalBridgeLogoutRequest(), TimeSpan.FromSeconds(0.5));
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error sending an external bridge log out request.");
            }
            finally
            {
                ClearQueues();
            }

            OnClientDisconnected();
        }

        #endregion

        #region Virtual Methods

        protected async virtual void OnClientDisconnected()
        {
            IsInSession = false;
            _send_app_logs = false;

            if (MachineOperator.State == TransportComponentState.Connected)
            {
                try
                {
                    await MachineOperator.SendRequest(new StopDiagnosticsRequest());
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex);
                }

                try
                {
                    await MachineOperator.SendRequest(new StopDebugLogRequest());
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex);
                }
            }

            ClientDisconnected?.Invoke(this, new EventArgs());

            try
            {
                await base.Disconnect();
            }
            catch (Exception ex)
            {
                LogManager.Log(ex);
            }

            LogManager.Log("External bridge client disconnected.");
        }

        #endregion

        #region Message Handlers

        private void RegisterMessageHandlers()
        {
            _messageHandlers.Add(MessageType.ExternalBridgeLogoutRequest, OnExternalBridgeLogoutRequest);

            _messageHandlers.Add(MessageType.StartApplicationLogsRequest, OnStartApplicationLogsRequest);
            _messageHandlers.Add(MessageType.StopApplicationLogsRequest, OnStopApplicationLogsRequest);

            _messageHandlers.Add(MessageType.JobRequest, OnJobRequest);
        }

        protected virtual async void OnAnyRequest(MessageContainer container)
        {
            if (SessionIntent == ExternalBridgeLoginIntent.ColorProfile)
            {
                await SendErrorResponse(new AuthenticationException("The specified intent does not grant the specified action."), container.Token);
                return;
            }

            if (!container.Continuous)
            {
                try
                {
                    var response = await MachineOperator.SendRequest(container);
                    await SendResponse(response);
                }
                //catch (TimeoutException)
                //{

                //}
                catch (Exception ex)
                {
                    await SendErrorResponse(ex, container.Token);
                }
            }
            else
            {
                try
                {
                    MachineOperator.SendContinuousRequest(container).Subscribe((response) =>
                    {
                        if (Enabled && IsInSession)
                        {
                            SendResponse(response);
                        }

                    }, (ex) =>
                    {
                        if (Enabled)
                        {
                            if (ex is ResponseErrorException)
                            {
                                SendResponse((ex as ResponseErrorException).Container);
                            }
                        }
                    });
                }
                catch (Exception ex)
                {
                    await SendErrorResponse(ex, container.Token);
                }
            }
        }

        protected async virtual void OnExternalBridgeLoginRequest(MessageContainer container)
        {

            var request = MessageFactory.ParseTangoMessageFromContainer<ExternalBridgeLoginRequest>(container);

            LogManager.Log($"External bridge login attempt:\nIntent: {request.Message.Intent}\nMessage:\n{request.Message.ToJsonString()}");

            if (MachineOperator.Status != MachineStatuses.Printing || request.Message.Intent != ExternalBridgeLoginIntent.FullControl)
            {
                ExternalBridgeClientConnectedEventArgs args = new ExternalBridgeClientConnectedEventArgs();
                args.Request = request;
                args.IpAddress = Adapter.Address;
                ConnectionRequest?.Invoke(this, args);

                var response = new ExternalBridgeLoginResponse();
                response.Authenticated = args.Confirmed;
                response.SerialNumber = Machine.SerialNumber;
                response.DeviceInformation = MachineOperator.DeviceInformation;

                IsInSession = args.Confirmed;

                if (IsInSession)
                {
                    LogManager.Log("External bridge client has logged-in successfully.");
                    MachineOperator.EnableDiagnostics = false;
                    MachineOperator.EnableEmbeddedDebugging = false;
                    SessionIntent = request.Message.Intent;
                }
                else
                {
                    LogManager.Log("External bridge client login failed, invalid password.");
                }

                await SendResponse<ExternalBridgeLoginResponse>(response, container.Token, null, null, IsInSession ? null : "Invalid password or intent.");
            }
            else
            {
                LogManager.Log($"External bridge client login failed because the machine is currently printing and '{ExternalBridgeLoginIntent.FullControl}' intent was requested.");
                await SendResponse<ExternalBridgeLoginResponse>(new ExternalBridgeLoginResponse(), container.Token, false, ErrorCode.GeneralError, $"Machine connection with '{ExternalBridgeLoginIntent.FullControl}' intent is not permitted while printing.");
            }
        }

        protected async virtual void OnExternalBridgeLogoutRequest(MessageContainer container)
        {
            try
            {
                await SendResponse<ExternalBridgeLogoutResponse>(new ExternalBridgeLogoutResponse(), container.Token);
            }
            catch (Exception ex)
            {
                LogManager.Log(ex);
            }
            finally
            {
                ClearQueues();
            }

            OnClientDisconnected();
        }

        protected virtual void OnStartApplicationLogsRequest(MessageContainer container)
        {
            if (SessionIntent == ExternalBridgeLoginIntent.Diagnostics || SessionIntent == ExternalBridgeLoginIntent.FullControl)
            {
                _app_logs_token = container.Token;
                _send_app_logs = true;
                SendResponse<StartApplicationLogsResponse>(new StartApplicationLogsResponse(), container.Token);
            }
            else
            {
                throw new AuthenticationException("The specified intent does not grant the specified action.");
            }
        }

        protected virtual void OnStopApplicationLogsRequest(MessageContainer container)
        {
            if (SessionIntent == ExternalBridgeLoginIntent.Diagnostics || SessionIntent == ExternalBridgeLoginIntent.FullControl)
            {
                _send_app_logs = false;
                SendResponse<StopApplicationLogsResponse>(new StopApplicationLogsResponse() { }, container.Token);
                SendResponse<StartApplicationLogsResponse>(new StartApplicationLogsResponse() { }, _app_logs_token, true);
            }
            else
            {
                throw new AuthenticationException("The specified intent does not grant the specified action.");
            }
        }

        protected virtual void OnJobRequest(MessageContainer container)
        {
            if (SessionIntent != ExternalBridgeLoginIntent.FullControl)
            {
                throw new InvalidOperationException($"Job execution is disabled while session intent is '{SessionIntent}'.");
            }
            if (MachineOperator.Status != MachineStatuses.ReadyToDye)
            {
                throw new InvalidOperationException($"Could not execute job while machine operator status is '{MachineOperator.Status}'.");
            }
            else
            {
                OnAnyRequest(container);
            }
        }

        #endregion
    }
}