Merge commit '383f0c179303e58d859ffe4fb4c05170bed3b1ed' into V2

This commit is contained in:
Pascal Serrarens 2025-02-19 16:51:51 +01:00
commit 4eb65f1312
25 changed files with 521 additions and 204 deletions

2
.gitignore vendored
View File

@ -4,4 +4,4 @@ test/bin
test/obj test/obj
/bin /bin
/obj /obj
*.meta **.meta

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 7f964f406734cf74097d61697e5cafc9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -47,10 +47,7 @@ namespace Passer.RoboidControl {
this.thingId = thing.id; this.thingId = thing.id;
this.bytes = System.Array.Empty<byte>(); this.bytes = System.Array.Empty<byte>();
} }
/// <summary> /// @copydoc Passer::RoboidControl::IMessage::IMessage(byte[] buffer)
/// Create the message for receiving
/// </summary>
/// <param name="buffer">The byte array to parse</param>
public BinaryMsg(byte[] buffer) { public BinaryMsg(byte[] buffer) {
byte ix = 1; byte ix = 1;
this.networkId = buffer[ix++]; this.networkId = buffer[ix++];
@ -61,13 +58,9 @@ namespace Passer.RoboidControl {
this.bytes[bytesIx] = buffer[ix++]; this.bytes[bytesIx] = buffer[ix++];
} }
/// <summary> /// @copydoc Passer::RoboidControl::IMessage::Serialize
/// Serialize the message into a byte array for sending
/// </summary>
/// <param name="buffer">The buffer to serilize into</param>
/// <returns>The length of the message in the buffer</returns>
public override byte Serialize(ref byte[] buffer) { public override byte Serialize(ref byte[] buffer) {
if (buffer.Length < BinaryMsg.length + buffer.Length || bytes.Length == 0) if (buffer.Length < BinaryMsg.length + this.bytes.Length || this.bytes.Length == 0)
return 0; return 0;
byte ix = 0; byte ix = 0;

View File

@ -3,29 +3,52 @@ using System.Threading.Tasks;
namespace Passer.RoboidControl { namespace Passer.RoboidControl {
/// <summary>
/// Message notifiying that a Thing no longer exists
/// </summary>
public class DestroyMsg : IMessage { public class DestroyMsg : IMessage {
/// <summary>
/// The message ID
/// </summary>
public const byte Id = 0x20; public const byte Id = 0x20;
/// <summary>
/// The length of the message
/// </summary>
public const byte length = 3; public const byte length = 3;
/// <summary>
/// The network ID of the thing
/// </summary>
public byte networkId; public byte networkId;
/// <summary>
/// The ID of the thing
/// </summary>
public byte thingId; public byte thingId;
/// <summary>
/// Create a message for sending
/// </summary>
/// <param name="networkId">The network ID of the thing</param>
/// <param name="thingId">The ID of the thing</param>
public DestroyMsg(byte networkId, byte thingId) {
this.networkId = networkId;
this.thingId = thingId;
}
/// @copydoc Passer::RoboidControl::IMessage::IMessage(byte[] buffer)
public DestroyMsg(byte[] buffer) : base(buffer) { } public DestroyMsg(byte[] buffer) : base(buffer) { }
public override void Deserialize(byte[] buffer) { /// @copydoc Passer::RoboidControl::IMessage::Serialize
this.networkId = buffer[0]; public override byte Serialize(ref byte[] buffer) {
this.thingId = buffer[1]; if (buffer.Length < DestroyMsg.length)
return 0;
byte ix = 0;
buffer[ix++] = DestroyMsg.Id;
buffer[ix++] = this.networkId;
buffer[ix++] = this.thingId;
return ix;
} }
public static async Task<bool> Receive(Stream dataStream, Participant client, byte packetSize) {
if (packetSize != length)
return false;
byte[] buffer = await Receive(dataStream, packetSize);
DestroyMsg msg = new DestroyMsg(buffer);
client.messageQueue.Enqueue(msg);
return true;
}
} }
} }

View File

@ -3,18 +3,40 @@ using System.Threading.Tasks;
namespace Passer.RoboidControl { namespace Passer.RoboidControl {
/// <summary>
/// Message to request details for a Thing
/// </summary>
public class InvestigateMsg : IMessage { public class InvestigateMsg : IMessage {
/// <summary>
/// The message Id
/// </summary>
public const byte Id = 0x81; public const byte Id = 0x81;
/// <summary>
/// The length of the message
/// </summary>
public const byte length = 3; public const byte length = 3;
/// <summary>
/// The network ID of the thing
/// </summary>
public byte networkId; public byte networkId;
/// <summary>
/// The ID of the thing
/// </summary>
public byte thingId; public byte thingId;
/// <summary>
/// Create a new message for sending
/// </summary>
/// <param name="networkId">The network ID for the thing</param>
/// <param name="thingId">The ID of the thing</param>
public InvestigateMsg(byte networkId, byte thingId) { public InvestigateMsg(byte networkId, byte thingId) {
this.networkId = networkId; this.networkId = networkId;
this.thingId = thingId; this.thingId = thingId;
} }
/// @copydoc Passer::RoboidControl::IMessage::IMessage(byte[] buffer)
public InvestigateMsg(byte[] buffer) : base(buffer) { } public InvestigateMsg(byte[] buffer) : base(buffer) { }
/// @copydoc Passer::RoboidControl::IMessage::Serialize
public override byte Serialize(ref byte[] buffer) { public override byte Serialize(ref byte[] buffer) {
byte ix = 0; byte ix = 0;
buffer[ix++] = InvestigateMsg.Id; buffer[ix++] = InvestigateMsg.Id;
@ -22,28 +44,28 @@ namespace Passer.RoboidControl {
buffer[ix++] = this.thingId; buffer[ix++] = this.thingId;
return ix; return ix;
} }
public override void Deserialize(byte[] buffer) { // public override void Deserialize(byte[] buffer) {
uint ix = 0; // uint ix = 0;
this.networkId = buffer[ix++]; // this.networkId = buffer[ix++];
this.thingId = buffer[ix++]; // this.thingId = buffer[ix++];
} // }
//public static bool Send(Participant client, CoreThing thing) { //public static bool Send(Participant client, CoreThing thing) {
// InvestigateMsg msg = new(thing.networkId, thing.id); // InvestigateMsg msg = new(thing.networkId, thing.id);
// return SendMsg(client, msg); // return SendMsg(client, msg);
//} //}
public static async Task<bool> Receive(Stream dataStream, Participant client, byte packetSize) { // public static async Task<bool> Receive(Stream dataStream, Participant client, byte packetSize) {
if (packetSize != length) // if (packetSize != length)
return false; // return false;
byte[] buffer = await Receive(dataStream, packetSize); // byte[] buffer = await Receive(dataStream, packetSize);
InvestigateMsg msg = new InvestigateMsg(buffer); // InvestigateMsg msg = new InvestigateMsg(buffer);
//UnityEngine.Debug.Log($"Receive investigate [{msg.networkId}/{msg.thingId}]"); // //UnityEngine.Debug.Log($"Receive investigate [{msg.networkId}/{msg.thingId}]");
client.messageQueue.Enqueue(msg); // client.messageQueue.Enqueue(msg);
return true; // return true;
} // }
} }
} }

View File

@ -3,40 +3,54 @@ using System.IO;
namespace Passer.RoboidControl { namespace Passer.RoboidControl {
/// <summary>
/// Root structure for all communcation messages
/// </summary>
public class IMessage { public class IMessage {
public IMessage() { } public IMessage() { }
/// <summary>
/// Create a message for receiving
/// </summary>
/// <param name="buffer">The byte array to parse</param>
public IMessage(byte[] buffer) { public IMessage(byte[] buffer) {
Deserialize(buffer); //Deserialize(buffer);
} }
/// <summary>
/// Serialize the message into a byte array for sending
/// </summary>
/// <param name="buffer">The buffer to serilize into</param>
/// <returns>The length of the message in the buffer</returns>
public virtual byte Serialize(ref byte[] buffer) { return 0; } public virtual byte Serialize(ref byte[] buffer) { return 0; }
public virtual void Deserialize(byte[] buffer) { }
public bool SendTo(Participant client) { //public virtual void Deserialize(byte[] buffer) { }
Serialize(ref client.buffer);
return client.SendBuffer(client.buffer.Length);
}
public static bool SendMsg(Participant client, IMessage msg) { // public bool SendTo(Participant client) {
msg.Serialize(ref client.buffer); // Serialize(ref client.buffer);
return client.SendBuffer(client.buffer.Length); // return client.SendBuffer(client.buffer.Length);
} // }
public static bool PublishMsg(Participant client, IMessage msg) { // public static bool SendMsg(Participant client, IMessage msg) {
msg.Serialize(ref client.buffer); // msg.Serialize(ref client.buffer);
return client.PublishBuffer(client.buffer.Length); // return client.SendBuffer(client.buffer.Length);
} // }
public static async Task<byte[]> Receive(Stream dataStream, byte packetSize) { // public static bool PublishMsg(Participant client, IMessage msg) {
byte[] buffer = new byte[packetSize - 1]; // without msgId // msg.Serialize(ref client.buffer);
int byteCount = dataStream.Read(buffer, 0, packetSize - 1); // return client.PublishBuffer(client.buffer.Length);
while (byteCount < packetSize - 1) { // }
// not all bytes have been read, wait and try again
await Task.Delay(1); // public static async Task<byte[]> Receive(Stream dataStream, byte packetSize) {
byteCount += dataStream.Read(buffer, byteCount, packetSize - 1 - byteCount); // byte[] buffer = new byte[packetSize - 1]; // without msgId
} // int byteCount = dataStream.Read(buffer, 0, packetSize - 1);
return buffer; // while (byteCount < packetSize - 1) {
} // // not all bytes have been read, wait and try again
// await Task.Delay(1);
// byteCount += dataStream.Read(buffer, byteCount, packetSize - 1 - byteCount);
// }
// return buffer;
// }
} }
} }

View File

@ -1,23 +1,56 @@
namespace Passer.RoboidControl { namespace Passer.RoboidControl {
/// <summary>
/// Message for communicating the name of a thing
/// </summary>
public class NameMsg : IMessage { public class NameMsg : IMessage {
/// <summary>
/// The message ID
/// </summary>
public const byte Id = 0x91; // 145 public const byte Id = 0x91; // 145
/// <summary>
/// The length of the message
/// </summary>
public const byte length = 4; public const byte length = 4;
/// <summary>
/// The network ID of the thing
/// </summary>
public byte networkId; public byte networkId;
/// <summary>
/// The ID of the thing
/// </summary>
public byte thingId; public byte thingId;
/// <summary>
/// The length of the name, excluding null terminator
/// </summary>
public byte len; public byte len;
/// <summary>
/// The name of the thing, not terminated with a null character
/// </summary>
public string name = ""; public string name = "";
/// <summary>
/// Create a new message for sending
/// </summary>
/// <param name="networkId">The network ID of the thing</param>
/// <param name="thing">The thing</param>
public NameMsg(byte networkId, Thing thing) { public NameMsg(byte networkId, Thing thing) {
this.networkId = networkId; this.networkId = networkId;
this.thingId = thing.id; this.thingId = thing.id;
this.name = thing.name; this.name = thing.name;
} }
/// <summary>
/// Create a new message for sending
/// </summary>
/// <param name="networkId">The network ID of the thing</param>
/// <param name="thingId">The ID of the thing</param>
/// <param name="name">The name of the thing</param>
public NameMsg(byte networkId, byte thingId, string name) { public NameMsg(byte networkId, byte thingId, string name) {
this.networkId = networkId; this.networkId = networkId;
this.thingId = thingId; this.thingId = thingId;
this.name = name; this.name = name;
} }
/// @copydoc Passer::RoboidControl::IMessage::IMessage(byte[] buffer)
public NameMsg(byte[] buffer) { public NameMsg(byte[] buffer) {
byte ix = 1; byte ix = 1;
this.networkId = buffer[ix++]; this.networkId = buffer[ix++];
@ -26,8 +59,9 @@ namespace Passer.RoboidControl {
this.name = System.Text.Encoding.UTF8.GetString(buffer, (int)ix, strlen); this.name = System.Text.Encoding.UTF8.GetString(buffer, (int)ix, strlen);
} }
/// @copydoc Passer::RoboidControl::IMessage::Serialize
public override byte Serialize(ref byte[] buffer) { public override byte Serialize(ref byte[] buffer) {
if (this.name.Length == 0) if (buffer.Length < NameMsg.length + this.name.Length || this.name.Length == 0)
return 0; return 0;
byte ix = 0; byte ix = 0;
@ -40,6 +74,7 @@ namespace Passer.RoboidControl {
buffer[ix++] = (byte)nameLength; buffer[ix++] = (byte)nameLength;
for (int nameIx = 0; nameIx < nameLength; nameIx++) for (int nameIx = 0; nameIx < nameLength; nameIx++)
buffer[ix++] = (byte)this.name[nameIx]; buffer[ix++] = (byte)this.name[nameIx];
return ix; return ix;
} }
} }

View File

@ -1,18 +1,39 @@
namespace Passer.RoboidControl { namespace Passer.RoboidControl {
/// <summary>
/// A message communicating the network ID for that participant
/// </summary>
public class NetworkIdMsg : IMessage { public class NetworkIdMsg : IMessage {
/// <summary>
/// The message ID
/// </summary>
public const byte Id = 0xA1; public const byte Id = 0xA1;
/// <summary>
/// The length of the message
/// </summary>
public const byte length = 2; public const byte length = 2;
/// <summary>
/// The network ID for the participant
/// </summary>
public byte networkId; public byte networkId;
/// <summary>
/// Create a new message for sending
/// </summary>
/// <param name="networkId">The network ID for the participant</param>
public NetworkIdMsg(byte networkId) { public NetworkIdMsg(byte networkId) {
this.networkId = networkId; this.networkId = networkId;
} }
/// @copydoc Passer::RoboidControl::IMessage::IMessage(byte[] buffer)
public NetworkIdMsg(byte[] buffer) { public NetworkIdMsg(byte[] buffer) {
this.networkId = buffer[1]; this.networkId = buffer[1];
} }
/// @copydoc Passer::RoboidControl::IMessage::Serialize
public override byte Serialize(ref byte[] buffer) { public override byte Serialize(ref byte[] buffer) {
if (buffer.Length < NetworkIdMsg.length)
return 0;
buffer[0] = NetworkIdMsg.Id; buffer[0] = NetworkIdMsg.Id;
buffer[1] = this.networkId; buffer[1] = this.networkId;
return NetworkIdMsg.length; return NetworkIdMsg.length;

View File

@ -4,23 +4,73 @@ using Passer.LinearAlgebra;
namespace Passer.RoboidControl { namespace Passer.RoboidControl {
/// <summary>
/// Message to communicate the pose of the thing
/// </summary>
/// The pose is in local space relative to the parent. If there is not parent (the thing is a root thing), the pose will be in world space.
public class PoseMsg : IMessage { public class PoseMsg : IMessage {
/// <summary>
/// The message ID
/// </summary>
public const byte Id = 0x10; public const byte Id = 0x10;
/// <summary>
/// The length of the message
/// </summary>
public const byte length = 4 + 4 + 4; public const byte length = 4 + 4 + 4;
/// <summary>
/// The network ID of the thing
/// </summary>
public byte networkId; public byte networkId;
/// <summary>
/// The ID of the thing
/// </summary>
public byte thingId; public byte thingId;
/// <summary>
/// bitpattern stating which pose components are available
/// </summary>
public byte poseType; public byte poseType;
/// <summary>
/// Bit pattern for a pose with position
/// </summary>
public const byte Pose_Position = 0x01; public const byte Pose_Position = 0x01;
/// <summary>
/// Bit pattern for a pose with orientation
/// </summary>
public const byte Pose_Orientation = 0x02; public const byte Pose_Orientation = 0x02;
/// <summary>
/// Bit pattern for a pose with linear velocity
/// </summary>
public const byte Pose_LinearVelocity = 0x04; public const byte Pose_LinearVelocity = 0x04;
/// <summary>
/// Bit pattern for a pose with angular velocity
/// </summary>
public const byte Pose_AngularVelocity = 0x08; public const byte Pose_AngularVelocity = 0x08;
/// <summary>
/// The position of the thing in local space in meters
/// </summary>
public Spherical position = Spherical.zero; public Spherical position = Spherical.zero;
/// <summary>
/// The orientation of the thing in local space
/// </summary>
public SwingTwist orientation = SwingTwist.zero; public SwingTwist orientation = SwingTwist.zero;
/// <summary>
/// The linear velocity of the thing in local space in meters per second
/// </summary>
public Spherical linearVelocity = Spherical.zero; public Spherical linearVelocity = Spherical.zero;
/// <summary>
/// The angular veloicty of the thing in local space
/// </summary>
public Spherical angularVelocity = Spherical.zero; public Spherical angularVelocity = Spherical.zero;
/// <summary>
/// Create a new message for sending
/// </summary>
/// <param name="networkId">The network ID of the thing</param>
/// <param name="thingId">The ID of the thing</param>
/// <param name="position">The position of the thing in local space in meters</param>
/// <param name="orientation">The orientation of the thing in local space</param>
public PoseMsg(byte networkId, byte thingId, Spherical position, SwingTwist orientation) { public PoseMsg(byte networkId, byte thingId, Spherical position, SwingTwist orientation) {
this.networkId = networkId; this.networkId = networkId;
this.thingId = thingId; this.thingId = thingId;
@ -34,8 +84,10 @@ namespace Passer.RoboidControl {
if (this.orientation != null) if (this.orientation != null)
this.poseType |= Pose_Orientation; this.poseType |= Pose_Orientation;
} }
/// @copydoc Passer::RoboidControl::IMessage::IMessage(byte[] buffer)
public PoseMsg(byte[] buffer) : base(buffer) { } public PoseMsg(byte[] buffer) : base(buffer) { }
/// @copydoc Passer::RoboidControl::IMessage::Serialize
public override byte Serialize(ref byte[] buffer) { public override byte Serialize(ref byte[] buffer) {
byte ix = 0; byte ix = 0;
buffer[ix++] = PoseMsg.Id; buffer[ix++] = PoseMsg.Id;
@ -53,59 +105,59 @@ namespace Passer.RoboidControl {
LowLevelMessages.SendSpherical(buffer, ref ix, this.angularVelocity); LowLevelMessages.SendSpherical(buffer, ref ix, this.angularVelocity);
return ix; return ix;
} }
public override void Deserialize(byte[] buffer) { // public override void Deserialize(byte[] buffer) {
byte ix = 0; // byte ix = 0;
this.networkId = buffer[ix++]; // this.networkId = buffer[ix++];
this.thingId = buffer[ix++]; // this.thingId = buffer[ix++];
this.poseType = buffer[ix++]; // this.poseType = buffer[ix++];
if ((poseType & Pose_Position) != 0) // if ((poseType & Pose_Position) != 0)
this.position = LowLevelMessages.ReceiveSpherical(buffer, ref ix); // this.position = LowLevelMessages.ReceiveSpherical(buffer, ref ix);
if ((poseType & Pose_Orientation) != 0) // if ((poseType & Pose_Orientation) != 0)
this.orientation = LowLevelMessages.ReceiveSwingTwist(buffer, ref ix); // this.orientation = LowLevelMessages.ReceiveSwingTwist(buffer, ref ix);
if ((poseType & Pose_LinearVelocity) != 0) // if ((poseType & Pose_LinearVelocity) != 0)
this.linearVelocity = LowLevelMessages.ReceiveSpherical(buffer, ref ix); // this.linearVelocity = LowLevelMessages.ReceiveSpherical(buffer, ref ix);
if ((poseType & Pose_AngularVelocity) != 0) // if ((poseType & Pose_AngularVelocity) != 0)
this.angularVelocity = LowLevelMessages.ReceiveSpherical(buffer, ref ix); // this.angularVelocity = LowLevelMessages.ReceiveSpherical(buffer, ref ix);
} // }
public static bool Send(Participant client, byte thingId, Spherical position, SwingTwist orientation) { // public static bool Send(Participant client, byte thingId, Spherical position, SwingTwist orientation) {
PoseMsg msg = new PoseMsg(client.networkId, thingId, position, orientation); // PoseMsg msg = new PoseMsg(client.networkId, thingId, position, orientation);
return SendMsg(client, msg); // return SendMsg(client, msg);
} // }
public static async Task<bool> Receive(Stream dataStream, Participant client, byte packetSize) { // public static async Task<bool> Receive(Stream dataStream, Participant client, byte packetSize) {
byte[] buffer = await Receive(dataStream, packetSize); // byte[] buffer = await Receive(dataStream, packetSize);
PoseMsg msg = new PoseMsg(buffer); // PoseMsg msg = new PoseMsg(buffer);
// Do no process poses with nwid == 0 (== local) // // Do no process poses with nwid == 0 (== local)
if (msg.networkId == 0) // if (msg.networkId == 0)
return true; // return true;
client.messageQueue.Enqueue(msg); // client.messageQueue.Enqueue(msg);
return true; // return true;
} // }
public static bool SendTo(Participant participant, Thing thing) { // public static bool SendTo(Participant participant, Thing thing) {
if (participant == null || thing == null) // if (participant == null || thing == null)
return false; // return false;
byte ix = 0; // byte ix = 0;
participant.buffer[ix++] = PoseMsg.Id; // participant.buffer[ix++] = PoseMsg.Id;
participant.buffer[ix++] = participant.networkId; // participant.buffer[ix++] = participant.networkId;
participant.buffer[ix++] = thing.id; // participant.buffer[ix++] = thing.id;
participant.buffer[ix++] = thing.poseUpdated; // participant.buffer[ix++] = thing.poseUpdated;
if ((thing.poseUpdated & Pose_Position) != 0 && thing.position != null) // if ((thing.poseUpdated & Pose_Position) != 0 && thing.position != null)
LowLevelMessages.SendSpherical(participant.buffer, ref ix, thing.position); // LowLevelMessages.SendSpherical(participant.buffer, ref ix, thing.position);
if ((thing.poseUpdated & Pose_Orientation) != 0 && thing.orientation != null) // if ((thing.poseUpdated & Pose_Orientation) != 0 && thing.orientation != null)
LowLevelMessages.SendQuat32(participant.buffer, ref ix, thing.orientation); // LowLevelMessages.SendQuat32(participant.buffer, ref ix, thing.orientation);
if ((thing.poseUpdated & Pose_LinearVelocity) != 0 && thing.linearVelocity != null) // if ((thing.poseUpdated & Pose_LinearVelocity) != 0 && thing.linearVelocity != null)
LowLevelMessages.SendSpherical(participant.buffer, ref ix, thing.linearVelocity); // LowLevelMessages.SendSpherical(participant.buffer, ref ix, thing.linearVelocity);
if ((thing.poseUpdated & Pose_AngularVelocity) != 0 && thing.angularVelocity != null) // if ((thing.poseUpdated & Pose_AngularVelocity) != 0 && thing.angularVelocity != null)
LowLevelMessages.SendSpherical(participant.buffer, ref ix, thing.angularVelocity); // LowLevelMessages.SendSpherical(participant.buffer, ref ix, thing.angularVelocity);
return participant.SendBuffer(ix); // return participant.SendBuffer(ix);
} // }
} }
} }

View File

@ -3,24 +3,59 @@ using System.Threading.Tasks;
namespace Passer.RoboidControl { namespace Passer.RoboidControl {
/// <summary>
/// Message for sending generic text.
/// </summary>
public class TextMsg : IMessage { public class TextMsg : IMessage {
public TextMsg(byte[] buffer) : base(buffer) {} /// <summary>
/// The message ID
/// </summary>
public const byte Id = 0xB0; public const byte Id = 0xB0;
/// <summary>
/// The length of the message without the text itself
/// </summary>
public const byte length = 2;
/// <summary>
/// The text
/// </summary>
public string text = ""; public string text = "";
public override void Deserialize(byte[] buffer) { /// <summary>
uint ix = 0; /// Create a new message for sending
uint strlen = buffer[ix++]; /// </summary>
this.text = System.Text.Encoding.UTF8.GetString(buffer, (int)ix, (int)strlen); /// <param name="text">The text to send</param>
public TextMsg(string text) {
this.text = text;
} }
public static async Task<bool> Receive(Stream dataStream, Participant client, byte packetSize) { /// @copydoc Passer::RoboidControl::IMessage::IMessage(byte[] buffer)
byte[] buffer = await Receive(dataStream, packetSize); public TextMsg(byte[] buffer) : base(buffer) { }
TextMsg msg = new TextMsg(buffer);
client.messageQueue.Enqueue(msg); /// @copydoc Passer::RoboidControl::IMessage::Serialize
return true; public override byte Serialize(ref byte[] buffer) {
if (buffer.Length < TextMsg.length + this.text.Length || this.text.Length == 0)
return 0;
byte ix = 0;
buffer[ix++] = TextMsg.Id;
buffer[ix++] = (byte)this.text.Length;
for (int textIx = 0; textIx < this.text.Length; textIx++)
buffer[ix++] = (byte)this.text[textIx];
return ix;
} }
// public override void Deserialize(byte[] buffer) {
// uint ix = 0;
// uint strlen = buffer[ix++];
// this.text = System.Text.Encoding.UTF8.GetString(buffer, (int)ix, (int)strlen);
// }
// public static async Task<bool> Receive(Stream dataStream, Participant client, byte packetSize) {
// byte[] buffer = await Receive(dataStream, packetSize);
// TextMsg msg = new TextMsg(buffer);
// client.messageQueue.Enqueue(msg);
// return true;
// }
} }
} }

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 711bdb0248c9f6848a6b4da15cc05db5

View File

@ -318,7 +318,9 @@ namespace Passer.RoboidControl {
if (client == this) if (client == this)
continue; continue;
//UnityEngine.Debug.Log($"---> {client.ipAddress}"); //UnityEngine.Debug.Log($"---> {client.ipAddress}");
IMessage.SendMsg(client, msg); //IMessage.SendMsg(client, msg);
msg.Serialize(ref client.buffer);
client.SendBuffer(client.buffer.Length);
} }
} }

View File

@ -1,3 +1,10 @@
\mainpage Control Core for C# \mainpage Roboid Control for C#
Control Core contains generic functionality for Controlling Things. Roboid Control support for C# applications.
Includes support for the Unity game engine.
# Basic components
- Passer::RoboidControl::Thing
- Passer::RoboidControl::Participant
- Passer::RoboidControl::SiteServer

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a5a7a42365df0d0459195576ad3bb50b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,22 +1,39 @@
using System; namespace Passer.RoboidControl {
namespace Passer.RoboidControl.Core {
/// <summary>
/// A sensor measuring the distance in the forward direction
/// </summary>
public class DistanceSensor : Thing { public class DistanceSensor : Thing {
/// <summary>
/// The current measured distance
/// </summary>
public float distance = 0; public float distance = 0;
/// <summary>
/// Constructor for a new distance sensor
/// </summary>
/// <param name="participant">The participant for which the sensor is needed</param>
public DistanceSensor(RemoteParticipant participant) : base(participant, true) { } public DistanceSensor(RemoteParticipant participant) : base(participant, true) { }
/// <summary>
/// Create a distance sensor with the given ID
/// </summary>
/// <param name="participant">The participant for with the sensor is needed</param>
/// <param name="networkId">The network ID of the sensor</param>
/// <param name="thingId">The ID of the thing</param>
public DistanceSensor(RemoteParticipant participant, byte networkId, byte thingId) : base(participant, networkId, thingId, (byte)Type.TemperatureSensor) { public DistanceSensor(RemoteParticipant participant, byte networkId, byte thingId) : base(participant, networkId, thingId, (byte)Type.TemperatureSensor) {
} }
#if UNITY_5_3_OR_NEWER #if UNITY_5_3_OR_NEWER
/// @copydoc Passer::RoboidControl::Thing::CreateComponent
public override void CreateComponent() { public override void CreateComponent() {
this.component = Unity.DistanceSensor.Create(this.parent); this.component = Unity.DistanceSensor.Create(this.parent);
this.component.core = this; this.component.core = this;
} }
#endif #endif
/// <summary>
/// Function to extract the distance received in the binary message
/// </summary>
/// <param name="bytes">The byte array</param>
public override void ProcessBinary(byte[] bytes) { public override void ProcessBinary(byte[] bytes) {
byte ix = 0; byte ix = 0;
this.distance = LowLevelMessages.ReceiveFloat16(bytes, ref ix); this.distance = LowLevelMessages.ReceiveFloat16(bytes, ref ix);

View File

@ -1,16 +1,32 @@
using System; //using System;
namespace Passer.RoboidControl.Core { namespace Passer.RoboidControl {
/// <summary>
/// A temperature sensor
/// </summary>
public class TemperatureSensor : Thing { public class TemperatureSensor : Thing {
public float temp = 0; /// <summary>
/// The measured temperature
/// </summary>
public float temperature = 0;
/// <summary>
/// Create a temperature sensor with the given ID
/// </summary>
/// <param name="participant">The participant for with the sensor is needed</param>
/// <param name="networkId">The network ID of the sensor</param>
/// <param name="thingId">The ID of the thing</param>
public TemperatureSensor(Participant participant, byte networkId, byte thingId) : base(participant, networkId, thingId, (byte)Type.TemperatureSensor) {} public TemperatureSensor(Participant participant, byte networkId, byte thingId) : base(participant, networkId, thingId, (byte)Type.TemperatureSensor) {}
/// <summary>
/// Function to extract the temperature received in the binary message
/// </summary>
/// <param name="bytes">The byte array</param>
public override void ProcessBinary(byte[] bytes) { public override void ProcessBinary(byte[] bytes) {
byte ix = 0; byte ix = 0;
this.temp = LowLevelMessages.ReceiveFloat16(bytes, ref ix); this.temperature = LowLevelMessages.ReceiveFloat16(bytes, ref ix);
Console.WriteLine($"temperature {this.name} = {this.temp} C"); //Console.WriteLine($"temperature {this.name} = {this.temperature} C");
} }
} }

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5cd9e2534695ec844bd23b7a48ad498a

View File

@ -1,12 +1,25 @@
namespace Passer.RoboidControl { namespace Passer.RoboidControl {
/// <summary>
/// A sensor which can detect touches
/// </summary>
public class TouchSensor : Thing { public class TouchSensor : Thing {
/// <summary>
/// Value which is true when the sensor is touching something, false otherwise
/// </summary>
public bool touchedSomething = false; public bool touchedSomething = false;
/// <summary>
/// Create a touch sensor
/// </summary>
/// <param name="participant">The participant for with the sensor is needed</param>
/// <param name="invokeEvent">True when the creation should trigger an event</param>
public TouchSensor(RemoteParticipant participant, bool invokeEvent = true) : base(participant, invokeEvent) { public TouchSensor(RemoteParticipant participant, bool invokeEvent = true) : base(participant, invokeEvent) {
touchedSomething = false; touchedSomething = false;
} }
#if UNITY_5_3_OR_NEWER #if UNITY_5_3_OR_NEWER
/// @copydoc Passer::RoboidControl::Thing::CreateComponent
public override void CreateComponent() { public override void CreateComponent() {
this.component = Unity.TouchSensor.Create(this); this.component = Unity.TouchSensor.Create(this);
this.component.core = this; this.component.core = this;

155
Thing.cs
View File

@ -31,21 +31,41 @@ namespace Passer.RoboidControl {
Humanoid, Humanoid,
ExternalSensor ExternalSensor
}; };
/// <summary>
/// The type of this thing. This can be either a Thing::Type (needs casting)
/// or a byte value for custom types.
/// </summary>
public byte type;
#endregion Types #endregion Types
#region Properties #region Properties
/// <summary>
/// The participant to which this thing belongs
/// </summary>
public RemoteParticipant participant; public RemoteParticipant participant;
public delegate void ChangeHandler(); public delegate void ChangeHandler();
public delegate void SphericalHandler(Spherical v); public delegate void SphericalHandler(Spherical v);
/// <summary>
/// The network ID of this thing.
/// </summary>
public byte networkId; public byte networkId;
/// <summary>
/// The ID of this thing
/// </summary>
public byte id; public byte id;
public event ChangeHandler OnParentChanged = delegate {}; /// <summary>
/// Event which is triggered when the parent changes
/// </summary>
public event ChangeHandler OnParentChanged = delegate { };
private Thing _parent; private Thing _parent;
/// <summary>
/// The parent of this thing
/// </summary>
public Thing parent { public Thing parent {
get => _parent; get => _parent;
set { set {
@ -63,22 +83,38 @@ namespace Passer.RoboidControl {
} }
} }
/// <summary>
/// Attach a thing as a child of this thing
/// </summary>
/// <param name="child">The thing to attach as a child</param>
public void AddChild(Thing child) { public void AddChild(Thing child) {
if (children.Find(thing => thing == child) != null) if (children.Find(thing => thing == child) != null)
return; return;
child._parent = this; child._parent = this;
children.Add(child); children.Add(child);
} }
/// <summary>
/// Remove the given thing as a child of this thing
/// </summary>
/// <param name="child"></param>
public void RemoveChild(Thing child) { public void RemoveChild(Thing child) {
children.Remove(child); children.Remove(child);
} }
[System.NonSerialized] /// <summary>
/// The list of children of this thing
/// </summary>
[NonSerialized]
public List<Thing> children = new List<Thing>(); public List<Thing> children = new List<Thing>();
public byte type;
public event ChangeHandler OnNameChanged = delegate {}; /// <summary>
/// Event which is triggered when the name changes
/// </summary>
public event ChangeHandler OnNameChanged = delegate { };
private string _name = ""; private string _name = "";
/// <summary>
/// The name of the thing
/// </summary>
public virtual string name { public virtual string name {
get => _name; get => _name;
set { set {
@ -89,11 +125,19 @@ namespace Passer.RoboidControl {
} }
} }
/// <summary>
/// An URL pointing to the location where a model of the thing can be found
/// </summary>
public string modelUrl = ""; public string modelUrl = "";
public byte poseUpdated = 0x00; /// <summary>
public event ChangeHandler OnPositionChanged = delegate {}; /// Event triggered when the position has changed
/// </summary>
public event ChangeHandler OnPositionChanged = delegate { };
private Spherical _position = Spherical.zero; private Spherical _position = Spherical.zero;
/// <summary>
/// The position of the thing in local space, in meters.
/// </summary>
public Spherical position { public Spherical position {
get { return _position; } get { return _position; }
set { set {
@ -104,8 +148,14 @@ namespace Passer.RoboidControl {
} }
} }
public event ChangeHandler OnOrientationChanged = delegate {}; /// <summary>
/// Event triggered when the orientation has changed
/// </summary>
public event ChangeHandler OnOrientationChanged = delegate { };
private SwingTwist _orientation = SwingTwist.zero; private SwingTwist _orientation = SwingTwist.zero;
/// <summary>
/// The orientation of the thing in local space
/// </summary>
public SwingTwist orientation { public SwingTwist orientation {
get { return _orientation; } get { return _orientation; }
set { set {
@ -116,8 +166,14 @@ namespace Passer.RoboidControl {
} }
} }
public event SphericalHandler OnLinearVelocityChanged = delegate {}; /// <summary>
/// Event triggered when the linear velocity has changed
/// </summary>
public event SphericalHandler OnLinearVelocityChanged = delegate { };
private Spherical _linearVelocity = Spherical.zero; private Spherical _linearVelocity = Spherical.zero;
/// <summary>
/// The linear velocity of the thing in local space in meters per second
/// </summary>
public Spherical linearVelocity { public Spherical linearVelocity {
get => _linearVelocity; get => _linearVelocity;
set { set {
@ -127,9 +183,15 @@ namespace Passer.RoboidControl {
} }
} }
} }
/// <summary>
/// The angular velocity of the thing in local space
/// </summary>
public Spherical angularVelocity = Spherical.zero; public Spherical angularVelocity = Spherical.zero;
#if UNITY_5_3_OR_NEWER #if UNITY_5_3_OR_NEWER
/// <summary>
/// A reference to the representation of the thing in Unity
/// </summary>
[NonSerialized] [NonSerialized]
public Unity.Thing component = null; public Unity.Thing component = null;
#endif #endif
@ -138,67 +200,92 @@ namespace Passer.RoboidControl {
#region Init #region Init
/// <summary>
/// Create a new thing for the given participant
/// </summary>
/// <param name="participant">The participant for which this thing is created</param>
/// <param name="invokeEvent">True when a new thing event should be triggered</param>
public Thing(RemoteParticipant participant, bool invokeEvent = false) { public Thing(RemoteParticipant participant, bool invokeEvent = false) {
this.participant = participant; this.participant = participant;
if (invokeEvent) if (invokeEvent)
InvokeNewThing(this); InvokeNewThing(this);
} }
public Thing(RemoteParticipant sender, byte networkId, byte thingId, byte thingType = 0) { /// <summary>
this.participant = sender; /// Create a new thing for the given participant
/// </summary>
/// <param name="participant">The participant for which this thing is created</param>
/// <param name="networkId">The network ID of the thing</param>
/// <param name="thingId">The ID of the thing</param>
/// <param name="thingType">The type of thing</param>
public Thing(RemoteParticipant participant, byte networkId, byte thingId, byte thingType = 0) {
this.participant = participant;
this.id = thingId; this.id = thingId;
this.type = thingType; this.type = thingType;
this.networkId = networkId; this.networkId = networkId;
} }
public virtual void CreateComponent() {} /// <summary>
/// Function which can be used to create components in external engines.
/// </summary>
/// Currently this is used to create GameObjects in Unity
public virtual void CreateComponent() { }
#endregion Init #endregion Init
#region Update #region Update
#if UNITY_5_3_OR_NEWER #if UNITY_5_3_OR_NEWER
/// <summary>
/// Convience function for use in Unity which removes the need for a currentTime argument
/// </summary>
public void Update() { public void Update() {
Update((ulong)UnityEngine.Time.time * 1000); Update((ulong)UnityEngine.Time.time * 1000);
} }
#endif #endif
/// <summary>
/// Update this thing
/// </summary>
/// <param name="currentTime">The current time in milliseconds</param>
public virtual void Update(ulong currentTime) { public virtual void Update(ulong currentTime) {
// should recurse over children... // should recurse over children...
} }
/// <summary>
/// Function used to generate binary data for this thing
/// </summary>
/// <returns>a byte array with the binary data</returns>
/// @sa Passer::RoboidControl::BinaryMsg
public virtual byte[] GenerateBinary() { return new byte[0]; } public virtual byte[] GenerateBinary() { return new byte[0]; }
/// <summary>
/// Function used to process binary data received for this thing
/// </summary>
/// <param name="bytes">The binary data</param>
public virtual void ProcessBinary(byte[] bytes) { public virtual void ProcessBinary(byte[] bytes) {
//if (sensor != null)
// sensor.ProcessBytes(bytes);
} }
#endregion Update #endregion Update
// Experimental
// public float stressLevel = 0;
// protected delegate void ReceptorFunc(Sensor sensor);
// protected void SetupReceptor(Sensor sensor, ReceptorFunc receptor) {
// sensor.Signaller += (sensor => Receptor(receptor, sensor));
// }
// protected void Receptor(ReceptorFunc receptor, Sensor sensor) {
// if (sensor.signalStrength <= stressLevel)
// return;
// receptor(sensor);
// }
//---------- All Things
// private static readonly List<Thing> allThings = new();
public delegate void ThingHandler(Thing t); public delegate void ThingHandler(Thing t);
public static event ThingHandler OnNewThing = delegate {}; /// <summary>
/// Event triggered when a new thing has been created
/// </summary>
public static event ThingHandler OnNewThing = delegate { };
/// <summary>
/// Trigger the creation for the given thing
/// </summary>
/// <param name="thing">The created thing</param>
public static void InvokeNewThing(Thing thing) { public static void InvokeNewThing(Thing thing) {
OnNewThing?.Invoke(thing); OnNewThing?.Invoke(thing);
} }
/// <summary>
/// Check if the thing has the given properaties
/// </summary>
/// <param name="thing">The thing to check</param>
/// <param name="networkId">The network ID to compare to</param>
/// <param name="thingId">The thing ID to compare to</param>
/// <returns>True when the thing has the given properties</returns>
public static bool IsThing(Thing thing, byte networkId, byte thingId) { public static bool IsThing(Thing thing, byte networkId, byte thingId) {
if (thing == null) if (thing == null)
return false; return false;

View File

@ -6,15 +6,15 @@ namespace Passer.RoboidControl.Unity {
public class DistanceSensor : Thing { public class DistanceSensor : Thing {
public new Core.DistanceSensor core { public new RoboidControl.DistanceSensor core {
get => (Core.DistanceSensor)base.core; get => (RoboidControl.DistanceSensor)base.core;
set => base.core = value; set => base.core = value;
} }
protected virtual void Start() { protected virtual void Start() {
if (core == null) { if (core == null) {
SiteServer siteServer = FindAnyObjectByType<SiteServer>(); SiteServer siteServer = FindAnyObjectByType<SiteServer>();
SetCoreThing(new Core.DistanceSensor(siteServer.site)); SetCoreThing(new RoboidControl.DistanceSensor(siteServer.site));
} }
StartCoroutine(MeasureDistance()); StartCoroutine(MeasureDistance());

View File

@ -2,12 +2,21 @@
using UnityEngine; using UnityEngine;
namespace Passer.RoboidControl.Unity { namespace Passer.RoboidControl.Unity {
/// <summary>
/// The representation of a Thing in Unity
/// </summary>
public class Thing : MonoBehaviour { public class Thing : MonoBehaviour {
/// <summary>
/// The core C# thing
/// </summary>
[field: SerializeField] [field: SerializeField]
public RoboidControl.Thing core {get; set; } public RoboidControl.Thing core {get; set; }
/// <summary>
/// Set the core C# thing
/// </summary>
protected void SetCoreThing(RoboidControl.Thing thing) { protected void SetCoreThing(RoboidControl.Thing thing) {
core = thing; core = thing;
core.component = this; core.component = this;
@ -20,6 +29,9 @@ namespace Passer.RoboidControl.Unity {
siteServer.site.Add(thing); siteServer.site.Add(thing);
} }
/// <summary>
/// Update the Unity representation
/// </summary>
protected virtual void Update() { protected virtual void Update() {
if (core == null) if (core == null)
return; return;

View File

@ -3,6 +3,9 @@ using UnityEngine;
namespace Passer.RoboidControl.Unity { namespace Passer.RoboidControl.Unity {
/// <summary>
/// The Unity representation of the TouchSensor
/// </summary>
public class TouchSensor : Thing { public class TouchSensor : Thing {
public RoboidControl.TouchSensor coreSensor { public RoboidControl.TouchSensor coreSensor {

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: d5d87461365fd8a4da528aa84a49e62c
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 743b128a79ef8414fa29d7bb3b9e2ac8

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 92729868a8379c04197dcb80d0276a63
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: