Funcionamento do Sistema multi-agente
Sistema multi-agente
São quatro classes que compõem o Core do sistema multi-agente: Input, Data-Manager, Analysis e Utils. A classe Core é a principal responsável pelo orquestramento das demais classes agentes.
Uma das vantagens dessa utilização é conseguir fazer com que dispositivos que funcionem em sistemas operacionais diferentes se comuniquem utilizando socket. Assim, é possível que um dispositivo como o BSN que funciona no Android, envie as informações coletadas pelo sensor para um computador rodando Windows. Permitindo uma interoperabilidade que não seria possível sem o sistema multi-agente. Viabiliza também que de posse das informações coletadas pelo Core, o mesmo pode delegar instruções para as demais classes orquestradas.
Como base para comunicação foi usada a API (Application Programming Interface) Ruffles que é uma biblioteca UDP (User Datagram Protocol) totalmente gerenciada e feita para que tenha alto desempenho com latência baixa. A primeira etapa é definir o UUID (Universally Unique Identifier) da aplicação, deve ser igual no cliente que são os agentes e no servidor que é o Core, serve para a identificação na rede, a porta 5557 foi a definida para comunicação.
readonly string uuid =
" b6af0cc1 -4 e8d -43 cd - b771 -321125895433 " ;
readonly int port = 5557;
Depois na classe orquestrada devemos iniciar o socket, configurar os canais para comunicação e iniciar as co-rotinas.
void Start()
{
// Initializes the client
socket = new RuffleSocket(new Ruffles.Configuration.SocketConfig()
{
ChallengeDifficulty = 20, // Difficulty 20 is fairly hard
ChannelTypes = new[]
{
ChannelType.Reliable,
ChannelType.ReliableSequenced,
ChannelType.Unreliable,
ChannelType.UnreliableOrdered,
},
AllowBroadcasts = true,
AllowUnconnectedMessages = true,
});
socket.Start();
StartCoroutine(Discover());
StartCoroutine(SendBroadcast());
}
O próximo passo é mandar na rede um broadcast com a porta e UUID determinados.
IEnumerator SendBroadcast()
{
while (IsDiscovering)
{
ArraySegment<byte> data = new ArraySegment<byte>(Encoding.UTF8.
GetBytes(uuid), 0, Encoding.UTF8.GetBytes(uuid).Length);
socket.SendBroadcast(data, port);
yield return new WaitForSeconds(2);
}
}
Posteriormente ao broadcast é o discover, ou seja, a conexão só é feita com sucesso se for recebido o mesmo UUID enviado. Enquanto não fechar a conexão nas duas pontas o método Recycle é chamado, para não ter estouro de memória, ou seja, não vai abrindo conexões de forma indeterminada. Quando concluir a conexão, ou seja, quando o UUID é encontrado, então define isDiscovering como false e isConnected como true.
IEnumerator Discover()
{
while (IsDiscovering)
{
yield return new WaitForSeconds(1);
@event = socket.Poll();
if (@event.Type == NetworkEventType.Nothing)
{
@event.Recycle();
continue;
}
if (@event.Type == NetworkEventType.UnconnectedData)
{
string message = Encoding.UTF8.GetString(@event.
Data.Array, @event.Data.Offset, @event.Data.Count);
if (message.Equals(uuid))
{
isDiscovering = false;
serverConnection = socket.Connect(@event.EndPoint);
isConnected = true;
StartCoroutine(SendData());
yield return new WaitForSeconds(5);
//Connection ok
break;
}
}
}
}
Para o envio de mensagem ao Core a co-rotina SendData só inicia o envio após a variável isConnected for true, note que o envio é configurado com o tempo de espera em 1/30f, ou seja, 30 quadros por segundo.
Existem vários tipos de canais na API Ruffles: 0 – Reliable que garante a entrega mas não a ordem; 1 – ReliableSequenced consegue garantir a entrega e a ordem; 2 – Unreliable não garante a entrega nem a ordem, pacotes duplicados são descartados; 3 – UnreliableOrdered a entrega não é garantida, mas a ordem sim, pacotes duplicados e antigos são descartados. A mensagem é enviada em forma de ArraySegment pelo método SendMessageToCore, passando como parâmetro a mensagem, o canal escolhido foi o ReliableSequenced no qual o envio será feito com garantia de entrega e ordem.
public void SendMessageToCore(string message, byte channel = 1)
{
ArraySegment<byte> data =
new ArraySegment<byte>(Encoding.UTF8.
GetBytes(message), 0, Encoding.UTF8.GetBytes(message).Length);
serverConnection?.Send(data, channel, false, (ulong)messagesSent);
messagesSent++;
}
IEnumerator SendData()
{
while (isConnected)
{
SendQuaternionList();
yield return new WaitForSeconds(1 / 30f);
}
}
O módulo Core pode enviar mensagem para o módulo orquestrado, essa mensagem é recebida pelo método OnMessageReceived.
void OnMessageReceived(string messageCore)
{
// do something
}
Com o UUID e portas já definidas, devemos iniciar também o socket no módulo do Core.
private void StartServer()
{
IsConnected = false;
socket = new RuffleSocket(new Ruffles.Configuration.SocketConfig()
{
ChallengeDifficulty = 20, // Difficulty 20 is fairly hard
ChannelTypes = new ChannelType[]
{
ChannelType.ReliableSequenced,
},
AllowBroadcasts = true,
AllowUnconnectedMessages = true,
DualListenPort = port
});
socket.Start();
}
}
No método Update o Core faz diversas tratativas, se não recebeu nenhuma mensagem chama o método Recycle para não ter estouro de pilha, se recebeu o broadcast com o UUID correspondente ele responde a classe orquestrada, se fechou a conexão nas duas pontas coloca a variável IsConnected como true, e se recebeu dados da classe orquestrada encaminha para o método OnMessageReceived.
void Update()
{
@event = socket.Poll();
if (@event.Type == NetworkEventType.Nothing) //nothing received
{
@event.Recycle();
return;
}
else if
(@event.Type == NetworkEventType.BroadcastData) //received broadcast, if the uuid matches, respond to client
{
string message = Encoding.UTF8.GetString(@event.Data.Array, @event.Data.Offset, @event.Data.Count);
if (message.Equals(uuid))
{
socket.SendUnconnected(@event.Data, @event.EndPoint);
}
Debug.Log("Broadcast: " + @event.EndPoint.AddressFamily.ToString() + " send " +
System.Text.Encoding.
UTF8.GetString(@event.Data.Array));
// We got a broadcast. Reply to them with the same token they used
}
else if (@event.Type == NetworkEventType.Connect) //received connection request
{
clientConnection = @event.Connection;
Debug.Log(@event.Connection.State + "to " + @event.EndPoint.AddressFamily.ToString());
IsConnected = true;
}
else if (@event.Type == NetworkEventType.Data) //the client's commands go here
{
string message = Encoding.UTF8.GetString(@event.Data.Array, @event.Data.Offset, @event.Data.Count);
OnMessageReceived(message);
}
// Recycle the event
@event.Recycle();
}
O módulo Core, pode enviar mensagens para os agentes utilizando o método SendMessageToAgent.
\textbf{\emph{Core}}},captionpos=b,label={lst:sa9_label}]
public void SendMessageToAgent(string message, byte channel = 1)
{
ArraySegment<byte> data =
new ArraySegment<byte>(Encoding.UTF8.
GetBytes(message), 0, Encoding.UTF8.GetBytes(message).Length);
clientConnection?.Send(data, channel, false, (ulong)messagesSent);
messagesSent++;
}
Existe uma particularidade nos agentes, além de trocar mensagens com o Core utilizando o método SendMessageToCore, foi criado um DTO (Data Transfer Object) para que se possa enviar dados dos sensores.

O DTO envia o quaternion com o respectivo nome da rotação.
[System.Serializable]
public class RotationDTO
{
public string name;
public QuaternionDTO rotation;
public RotationDTO(string name, QuaternionDTO rotation)
{
this.name = name;
this.rotation = rotation;
}
}
[System.Serializable]
public class QuaternionDTO
{
public float x, y, z, w;
public QuaternionDTO(float x, float y, float z, float w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
}
Dessa forma, podemos receber os dados da BSN mediante uso do sistema multi-agente, basta utilizar o método SendQuaternionList para enviar e QuaternionListFromAgent para receber os dados, o quaternion já é atribuído ao GameObject da rotação, pois o nome é enviado junto ao quaternion.
public void SendQuaternionList()
{
string a = "Quaternions/{\"rotations\":[";
List<RotationDTO> rt = new List<RotationDTO>();
var rotatableObjects = FindObjectsOfType<RotatableObject>().Where(ro => ro.bsnDevice != null);
foreach (RotatableObject ro in rotatableObjects)
{
var transform1 = ro.transform;
var rotation = transform1.rotation;
var quaternion = new QuaternionDTO(rotation.x, rotation.y,
rotation.z, rotation.w);
rt.Add(new RotationDTO(ro.SimplifiedName, quaternion));
}
rt.ForEach(rotation => { a += JsonUtility.ToJson(rotation) + ","; });
a = a.TrimEnd(',') + "]}";
SendMessageToCore(a, 1);
}
public void QuaternionListFromAgent(string message)
{
if (message.StartsWith("Quaternions/"))
{
string json = message.Substring(12);
JsonUtility.FromJsonOverwrite(json, this);
foreach (RotationDTO rdto in rotations)
{
Quaternion q = new Quaternion(rdto.rotation.x, rdto.rotation.y, rdto.rotation.z, rdto.rotation.w);
GameObject.Find(rdto.name).transform.rotation = q;
}
}
}
How can we help?
A premium WordPress theme with an integrated Knowledge Base,
providing 24/7 community-based support.



