From 246a2b9a3acc1e8b0be4dad8991455ed93b043d1 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Tue, 4 Feb 2025 16:28:20 +0100 Subject: [PATCH] Simulartion env setup (WIP) --- Participant.cs | 142 +++++++++++++++++++++++------------ RemoteParticipant.cs | 18 +++++ Sensors/TemperatureSensor.cs | 2 + SiteServer.cs | 51 ++++++------- Thing.cs | 2 +- 5 files changed, 137 insertions(+), 78 deletions(-) create mode 100644 RemoteParticipant.cs diff --git a/Participant.cs b/Participant.cs index 8856bda..7168ba7 100644 --- a/Participant.cs +++ b/Participant.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Collections.Concurrent; @@ -8,63 +6,96 @@ using System.Net.Sockets; namespace Passer.Control.Core { - public class Participant { - //public ConnectionMethod connection; - public IPEndPoint? endPoint = null; - public UdpClient? udpClient = null; - public string ipAddress = "0.0.0.0"; - public string broadcastIpAddress = "255.255.255.255"; - public int port; + public class Participant : RemoteParticipant { + public byte[] buffer = new byte[1024]; + public ulong publishInterval = 3000; // = 3 seconds - public byte[] buffer = new byte[256]; - - public byte networkId = 0; + //public byte networkId = 0; public string name = "Participant"; + public IPEndPoint endPoint = null; + public UdpClient udpClient = null; + public string broadcastIpAddress = "255.255.255.255"; + public readonly ConcurrentQueue messageQueue = new(); - public List others = new List(); - - public Participant? GetParticipant(string ipAddress, int port) { - foreach (Participant c in others) { - if (c.ipAddress == ipAddress && c.port == port) - return c; - } - return null; - } - public Participant AddParticipant(string ipAddress, int port) { - Participant participant = new Participant(ipAddress, port); - participant.networkId = (byte)this.others.Count; - //others.Add(); - return participant; - } - #region Init + /// + /// Create a porticiapnt + /// public Participant() { others.Add(this); } /// - /// Create a new participant + /// Create a participant with the give UDP port + /// + /// The port number on which to communicate + public Participant(int port) : this() { + this.port = port; + } + + /// + /// Create a new participant for a site at the given address and port /// /// The ip address of the site server /// The port number of the site server public Participant(string ipAddress = "0.0.0.0", int port = 7681) : this() { this.ipAddress = ipAddress; this.port = port; + this.endPoint = new IPEndPoint(IPAddress.Parse(ipAddress), port); // for sending - this.udpClient = new UdpClient(); // for receiving - this.udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, 0)); + + this.udpClient = new UdpClient(port); // for receiving + this.udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port)); this.udpClient.BeginReceive(new AsyncCallback(result => ReceiveUDP(result)), null); } + /// + /// Create a participant using the given udp client + /// + /// UDP client to use for communication + /// The port number on which to communicate public Participant(UdpClient udpClient, int port) : this() { this.udpClient = udpClient; this.port = port; - //clients.Add(this); } + public List others = new List(); + + public RemoteParticipant GetParticipant(string ipAddress, int port) { + foreach (RemoteParticipant c in others) { + if (c.ipAddress == ipAddress && c.port == port) + return c; + } + return null; + } + public RemoteParticipant AddParticipant(string ipAddress, int port) { + RemoteParticipant participant = new(ipAddress, port); + participant.networkId = (byte)this.others.Count; + //others.Add(); + return participant; + } + + protected readonly Dictionary> thingMsgProcessors = new(); + + public delegate Thing ThingConstructor(byte networkId, byte thingId); + public void Register(byte thingType, ThingConstructor constr) { + thingMsgProcessors[thingType] = new Func(constr); + } + + public void Register(Thing.Type thingType) where ThingClass : Thing { + Register((byte)thingType); + } + + public void Register(byte thingType) where ThingClass : Thing { + thingMsgProcessors[thingType] = (byte networkId, byte thingId) => + Activator.CreateInstance(typeof(ThingClass), networkId, thingId) as ThingClass; + Console.WriteLine($"Registering {typeof(ThingClass)} for thing type {thingType}"); + } + + #endregion Init #region Update @@ -76,24 +107,29 @@ namespace Passer.Control.Core { byte[] data = udpClient.EndReceive(result, ref this.endPoint); // This does not yet take multi-packet messages into account! - Participant? remoteParticipant = this.GetParticipant(endPoint.Address.ToString(), endPoint.Port); - if (remoteParticipant == null) { + RemoteParticipant remoteParticipant = this.GetParticipant(endPoint.Address.ToString(), endPoint.Port); + if (remoteParticipant == null) remoteParticipant = this.AddParticipant(endPoint.Address.ToString(), endPoint.Port); - } + ReceiveData(data, remoteParticipant); udpClient.BeginReceive(new AsyncCallback(result => ReceiveUDP(result)), null); } - private static readonly ulong publishInterval = 3000; private ulong nextPublishMe = 0; - public virtual void Update(ulong currentTimeMs) { - if (currentTimeMs > this.nextPublishMe) { + public virtual void Update(ulong currentTimeMs = 0) { + if (currentTimeMs == 0) { +#if UNITY_5_3_OR_NEWER + currentTimeMs = (ulong)(UnityEngine.Time.time * 1000); +#endif + } + + if (this.publishInterval > 0 && currentTimeMs > this.nextPublishMe) { this.Publish(new ClientMsg(this.networkId)); // Console.WriteLine($"{this.name} Sent ClientMsg {this.networkId}"); - this.nextPublishMe = currentTimeMs + Participant.publishInterval; + this.nextPublishMe = currentTimeMs + this.publishInterval; } // for (int ix = 0; ix < this.others.Count; ix++) { @@ -110,11 +146,11 @@ namespace Passer.Control.Core { #region Send - public void SendThingInfo(Thing thing) { + public void SendThingInfo(RemoteParticipant remoteParticipant, Thing thing) { Console.WriteLine("Send thing info"); - this.Send(new ThingMsg(this.networkId, thing)); - this.Send(new NameMsg(this.networkId, thing)); - this.Send(new ModelUrlMsg(this.networkId, thing)); + this.Send(remoteParticipant, new ThingMsg(this.networkId, thing)); + this.Send(remoteParticipant, new NameMsg(this.networkId, thing)); + this.Send(remoteParticipant, new ModelUrlMsg(this.networkId, thing)); } public bool Send(IMessage msg) { @@ -127,6 +163,17 @@ namespace Passer.Control.Core { return true; } + public bool Send(RemoteParticipant remoteParticipant, IMessage msg) { + int bufferSize = msg.Serialize(ref this.buffer); + if (bufferSize <= 0) + return true; + + IPEndPoint participantEndpoint = new(IPAddress.Parse(remoteParticipant.ipAddress), remoteParticipant.port); + Console.WriteLine($"msg to {participantEndpoint.Address.ToString()} {participantEndpoint.Port}"); + this.udpClient?.Send(this.buffer, bufferSize, participantEndpoint); + return true; + } + public bool Publish(IMessage msg) { int bufferSize = msg.Serialize(ref this.buffer); if (bufferSize <= 0) @@ -159,8 +206,9 @@ namespace Passer.Control.Core { #region Receive - public void ReceiveData(byte[] data, Participant remoteParticipant) { + public void ReceiveData(byte[] data, RemoteParticipant remoteParticipant) { byte msgId = data[0]; + Console.Write($"received msg {msgId}"); if (msgId == 0xFF) { // Timeout return; @@ -206,14 +254,14 @@ namespace Passer.Control.Core { #region Process - protected virtual void Process(Participant sender, ClientMsg msg) { } + protected virtual void Process(RemoteParticipant sender, ClientMsg msg) { } - protected virtual void Process(Participant sender, NetworkIdMsg msg) { + protected virtual void Process(RemoteParticipant sender, NetworkIdMsg msg) { Console.WriteLine($"{this.name} receive network id {this.networkId} {msg.networkId}"); if (this.networkId != msg.networkId) { this.networkId = msg.networkId; foreach (Thing thing in Thing.GetAllThings()) - sender.SendThingInfo(thing); + this.SendThingInfo(sender, thing); } } diff --git a/RemoteParticipant.cs b/RemoteParticipant.cs new file mode 100644 index 0000000..3477067 --- /dev/null +++ b/RemoteParticipant.cs @@ -0,0 +1,18 @@ +namespace Passer.Control.Core { + + public class RemoteParticipant { + public string ipAddress = "0.0.0.0"; + public int port = 0; + + public byte networkId; + + public RemoteParticipant() { + + } + public RemoteParticipant(string ipAddress, int port) { + this.ipAddress = ipAddress; + this.port = port; + } + } + +} \ No newline at end of file diff --git a/Sensors/TemperatureSensor.cs b/Sensors/TemperatureSensor.cs index 22356fa..9c16629 100644 --- a/Sensors/TemperatureSensor.cs +++ b/Sensors/TemperatureSensor.cs @@ -3,6 +3,8 @@ using System; namespace Passer.Control.Core { public class TemperatureSensor : Thing { + public float temp = 0; + public TemperatureSensor(byte networkId, byte thingId) : base(null, networkId, thingId, (byte)Type.TemperatureSensor) { } diff --git a/SiteServer.cs b/SiteServer.cs index 09a51d8..77d8fce 100644 --- a/SiteServer.cs +++ b/SiteServer.cs @@ -8,50 +8,41 @@ namespace Passer.Control.Core { public class SiteServer : Participant { public SiteServer(int port = 7681) { + this.name = "Site Server"; + this.publishInterval = 0; + this.ipAddress = "0.0.0.0"; this.port = port; - this.endPoint = new IPEndPoint(IPAddress.Parse(ipAddress), 0); // for sending, but should not be used really... - this.udpClient = new UdpClient(); // for receiving - this.udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port)); - this.udpClient.BeginReceive(new AsyncCallback(result => ReceiveUDP(result)), null); - this.name = "Site Server"; - Register((byte)Thing.Type.TemperatureSensor); + this.endPoint = new IPEndPoint(IPAddress.Parse(ipAddress), port); // for sending + + Console.Write($"Prepare receive on port {port}"); + this.udpClient = new UdpClient(port); // for receiving + //this.udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port)); + IPEndPoint receiveEndpoint = new(IPAddress.Any, port); + this.udpClient.BeginReceive( + new AsyncCallback(result => ReceiveUDP(result)), + new Tuple(this.udpClient, receiveEndpoint)); } - public override void Update(ulong currentTimeMs) { - Thing.UpdateAll(currentTimeMs); - } - - protected override void Process(Participant sender, ClientMsg msg) { + protected override void Process(RemoteParticipant sender, ClientMsg msg) { if (msg.networkId == 0) { Console.WriteLine($"{this.name} received New Client -> {sender.networkId}"); - sender.Send(new NetworkIdMsg(sender.networkId)); + this.Send(sender, new NetworkIdMsg(sender.networkId)); } } - protected override void Process(Participant sender, NetworkIdMsg msg) { - } - - - delegate Thing ThingConstructor(byte networkId, byte thingId); - readonly Dictionary thingMsgProcessors = new(); - - public void Register(byte thingType) where ThingClass : Thing { - thingMsgProcessors[thingType] = (byte networkId, byte thingId) => { - if (Activator.CreateInstance(typeof(Thing), networkId, thingId) is not ThingClass instance) - throw new InvalidOperationException($"Could not created an instance of {(typeof(ThingClass))}."); - return instance; - }; - Console.WriteLine($"Registering {typeof(ThingClass)} for thing type {thingType}"); - } + protected override void Process(RemoteParticipant sender, NetworkIdMsg msg) { } protected override void Process(ThingMsg msg) { + Console.Write("SiteServer: Processing thing msg"); Thing thing = Thing.Get(msg.networkId, msg.thingId); if (thing == null) { - if (thingMsgProcessors.TryGetValue(msg.thingType, out ThingConstructor thingConstructor)) { - thingConstructor(msg.networkId, msg.thingId); - } + if (thingMsgProcessors.ContainsKey(msg.thingType)) + thingMsgProcessors[msg.thingType](msg.networkId, msg.thingId); + // if (thingMsgProcessors.TryGetValue(msg.thingType, out ThingConstructor thingConstructor)) { + // thingConstructor(msg.networkId, msg.thingId); + // } else { new Thing(this, msg.networkId, msg.thingId, msg.thingType); } diff --git a/Thing.cs b/Thing.cs index e3d1b5d..a51e7c1 100644 --- a/Thing.cs +++ b/Thing.cs @@ -75,7 +75,7 @@ namespace Passer.Control.Core { public event ChangeHandler OnNameChanged; private string _name; - public string name { + public virtual string name { get => _name; set { if (_name != value) {