using Assets.Domain.Agents; using Assets.GAMA.net.Message.Jobs; using Assets.Message.Decoding; using M2MqttUnity; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using uPLibrary.Networking.M2Mqtt.Messages; public class MqttController : M2MqttUnityClient { private bool m_isConnected; public bool isConnected { get { return m_isConnected; } set { if (m_isConnected == value) return; m_isConnected = value; OnConnectionSucceeded?.Invoke(isConnected); } } public MqttControllerStatistics MqttControllerStatistics = new MqttControllerStatistics(); public event OnConnectionSucceededDelegate OnConnectionSucceeded; public delegate void OnConnectionSucceededDelegate(bool isConnected); public Dictionary> ActiveAgentsPerTopic = new Dictionary>(); public Dictionary ActiveDecodingJobs = new Dictionary(); private Dictionary TopicsWithTransforms = new Dictionary(); private Dictionary TopicEncoderName = new Dictionary(); private bool IsFeedbackTopic(string topic) => topic.EndsWith("_FB", StringComparison.InvariantCultureIgnoreCase); private string ToFeedbackTopic(string topic) => $"{topic}_FB"; public delegate void AgentInfoReceivedEvent(object source, AgentInfoReceivedEventArgs args); public event AgentInfoReceivedEvent OnAgentInfoReceived; public class AgentInfoReceivedEventArgs : EventArgs { public List Agents { get; set; } public long ExperimentCycle { get; set; } public string Topic { get; set; } } //-------------------------------------------------------------------------------------------------- public void Subscribe(string topic, string encoderName, CartesianTransform cartesianTransform) { TopicsWithTransforms.Add(topic, cartesianTransform); ActiveDecodingJobs.Add(topic, null); ActiveAgentsPerTopic.Add(topic, new Dictionary()); TopicEncoderName.Add(topic, encoderName); if (isConnected) { Sub(topic); } } //-------------------------------------------------------------------------------------------------- private void Sub(string topic) { client.Subscribe(new string[] { topic }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE }); client.Subscribe(new string[] { ToFeedbackTopic(topic) }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE }); } public CartesianTransform GetCartesianTransform(string topic) => TopicsWithTransforms[topic]; public void Publish(byte[] message, string topic) { if (!TopicsWithTransforms.ContainsKey(topic)) throw new Exception($"Tried to publish a message to a non-subscribed topic {topic}"); client.Publish(ToFeedbackTopic(topic), message); } //-------------------------------------------------------------------------------------------------- public void Unsubscribe(string topic) { TopicsWithTransforms.Remove(topic); TopicEncoderName.Remove(topic); client.Unsubscribe(new string[] { topic }); } //-------------------------------------------------------------------------------------------------- protected override void DecodeMessage(string topic, byte[] message) { if (IsFeedbackTopic(topic)) return; if (!IsProcessingMessage(topic)) ProcessMessage(message, topic); } private void HarvestCurrentJob(string topic) { if (ActiveDecodingJobs[topic] is null) return; if (!ActiveDecodingJobs[topic].IsCompleted()) return; if (ActiveDecodingJobs[topic].Harvesting) return; DecodedPublishedMessage result = ActiveDecodingJobs[topic].Harvest(); MqttControllerStatistics.AddMessage(result.TimestampTicks); List updatedAgents = UpdateAgents(result.Agents, ActiveDecodingJobs[topic].Topic); OnAgentInfoReceived(this, new AgentInfoReceivedEventArgs() { Agents = updatedAgents, ExperimentCycle = result.ExperimentCycle, Topic = topic }); ActiveDecodingJobs[topic] = null; } private bool IsProcessingMessage(string topic) { return ActiveDecodingJobs[topic] != null; } //-------------------------------------------------------------------------------------------------- private void ProcessMessage(byte[] message, string topic) { if (IsProcessingMessage(topic)) throw new ApplicationException($"Tried to process a new message while a previous message is being processed. topic={topic}"); if (!TopicEncoderName.ContainsKey(topic)) throw new ApplicationException($"MessageEncoder not set. topic={topic}"); if (TopicEncoderName[topic]=="SUMO.net") ActiveDecodingJobs[topic] = new SumoMessageDecodingJobHandler(message, topic); else if (TopicEncoderName[topic] == "GAMA.net") ActiveDecodingJobs[topic] = new GamaMessageDecodingJobHandler(message, topic); else throw new ApplicationException($"MessageEncoder Unknown. topic='{topic}' encoder='{TopicEncoderName[topic]}'"); ActiveDecodingJobs[topic].Start(); } //-------------------------------------------------------------------------------------------------- private List UpdateAgents(List agents, string topic) { List updatedAgents = new List(); foreach (SimulationAgent agent in agents) { updatedAgents.Add(UpdateAgent(agent, topic)); } return updatedAgents; } private SimulationAgent UpdateAgent(SimulationAgent agent, string topic) { /*if (!ActiveAgentsPerTopic[topic].ContainsKey(agent.Name)) ActiveAgentsPerTopic[topic].Add(agent.Name, agent);*/ //SimulationAgent existingAgent = ActiveAgentsPerTopic[topic][agent.Name]; if (agent.GetType() == typeof(PeopleSimulationAgent)) { PeopleSimulationAgent peopleAgent = (PeopleSimulationAgent)agent; Vector2 location = TopicsWithTransforms[topic].ToDestination(new Vector2(agent.Position.x, agent.Position.y)); peopleAgent.Position = new Vector3(location.x, location.y, peopleAgent.Position.z); peopleAgent.Heading += TopicsWithTransforms[topic].angle * Mathf.Rad2Deg; } else if (agent.GetType() == typeof(RoadVehicleSimulationAgent)) { RoadVehicleSimulationAgent vehicleAgent = (RoadVehicleSimulationAgent)agent; Vector2 location = TopicsWithTransforms[topic].ToDestination(new Vector2(agent.Position.x, agent.Position.y)); vehicleAgent.Position = new Vector3(location.x, location.y, vehicleAgent.Position.z); vehicleAgent.Heading += TopicsWithTransforms[topic].angle * Mathf.Rad2Deg; } ActiveAgentsPerTopic[topic][agent.Name] = agent; return agent; } protected override void OnConnecting() { base.OnConnecting(); } protected override void OnConnected() { base.OnConnected(); isConnected = true; } protected override void OnConnectionFailed(string errorMessage) { Debug.Log("CONNECTION FAILED! " + errorMessage); } protected override void OnDisconnected() { Debug.Log("Disconnected."); isConnected = false; } protected override void OnConnectionLost() { Debug.Log("CONNECTION LOST!"); } protected override void SubscribeTopics() { foreach(string topic in TopicsWithTransforms.Select(e => e.Key)) { Sub(topic); } } protected override void UnsubscribeTopics() { client.Unsubscribe(TopicsWithTransforms.Select(e => e.Key).ToArray()); } protected override void Awake() { base.Awake(); } protected override void Start() { base.Start(); } protected override void Update() { base.Update(); foreach (string activeJobTopic in ActiveDecodingJobs.Keys.ToArray()) { HarvestCurrentJob(activeJobTopic); } } private void OnDestroy() { Disconnect(); } }