One step to aligning with C++
This commit is contained in:
parent
9b632e5a39
commit
dfbfc0818b
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,6 +1,4 @@
|
||||
__pycache__/*
|
||||
test/__pycache__/*
|
||||
.vscode/*
|
||||
.DS_Store
|
||||
DoxyGen/DoxyWarnLogfile.txt
|
||||
**/__pycache__
|
||||
**/__pycache__/*
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
203
LocalParticipant.py
Normal file
203
LocalParticipant.py
Normal file
@ -0,0 +1,203 @@
|
||||
import socket
|
||||
import threading
|
||||
|
||||
from Participant import Participant
|
||||
from Thing import Thing
|
||||
from Messages.ParticipantMsg import ParticipantMsg
|
||||
from Messages.NetworkIdMsg import NetworkIdMsg
|
||||
from Messages.ThingMsg import ThingMsg
|
||||
from Messages.NameMsg import NameMsg
|
||||
from Messages.ModelUrlMsg import ModelUrlMsg
|
||||
from Messages import *
|
||||
|
||||
import sys
|
||||
micropython = 'micropython' in sys.modules
|
||||
|
||||
if micropython:
|
||||
from MicroPython.uPythonParticipant import Bla
|
||||
|
||||
class LocalParticipant(Participant):
|
||||
"""! A local participant is the local device which can communicate with other participants.
|
||||
|
||||
It manages all local things and communcation with other participants. Each application has a local participant which is usually explicit in the code.
|
||||
An participant can be isolated. In that case it is standalong and does not communicate with other participants.
|
||||
|
||||
Currently, only UDP communication is supported
|
||||
"""
|
||||
publishInterval = 3000 # 3 seconds
|
||||
|
||||
buffer = None
|
||||
## The network ID of the participant
|
||||
network_id = None
|
||||
nextPublishMe = 0
|
||||
others = None
|
||||
thread = None
|
||||
name = "Participant"
|
||||
|
||||
def __init__(self, port=7681, ip_address = "0.0.0.0", remote=False, udp_socket = None):
|
||||
super().__init__(ip_address, port)
|
||||
self.ip_address = ip_address
|
||||
self.port = port
|
||||
self.others = []
|
||||
self.network_id = 0
|
||||
self.buffer = bytearray(256)
|
||||
self.thing_msg_processors = {}
|
||||
self.new_thing_handlers = []
|
||||
|
||||
if remote == False:
|
||||
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
self.udp_socket.bind(("0.0.0.0", port))
|
||||
self.AddParticipant(self.ip_address, self.port)
|
||||
|
||||
self.thread = threading.Thread(target = self.Receiver)
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
else:
|
||||
self.udp_socket = udp_socket
|
||||
|
||||
#region Update
|
||||
|
||||
def GetParticipant(self, ip_address, port):
|
||||
# print(f'{self.name} Get participant {ip_address} {port}')
|
||||
# for item in self.others:
|
||||
# print(f'- {item.ip_address} {item.port}')
|
||||
|
||||
found_participants = (item for item in self.others
|
||||
if item.ip_address == ip_address and item.port == port)
|
||||
participant = next(found_participants, None)
|
||||
return participant
|
||||
|
||||
def AddParticipant(self, ip_address, port):
|
||||
# print(f'{self.name} Add participant {ip_address} {port}')
|
||||
remote_participant = LocalParticipant(ip_address, port, remote=True, udp_socket=self.udp_socket)
|
||||
remote_participant.network_id = len(self.others)
|
||||
self.others.append(remote_participant)
|
||||
return remote_participant
|
||||
|
||||
def Update(self, currentTimeMs = None):
|
||||
if currentTimeMs is None:
|
||||
currentTimeMs = time.time() * 1000
|
||||
|
||||
if self.publishInterval > 0 and currentTimeMs > self.nextPublishMe:
|
||||
self.Publish(ParticipantMsg(self.network_id))
|
||||
print(f'Publish ClientMsg {self.network_id}')
|
||||
self.nextPublishMe = currentTimeMs + self.publishInterval
|
||||
|
||||
super().Update(currentTimeMs)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
def SendThingInfo(self, thing):
|
||||
self.Send(ThingMsg(self.network_id, thing))
|
||||
self.Send(NameMsg(self.network_id, thing))
|
||||
self.Send(ModelUrlMsg(self.network_id, thing))
|
||||
|
||||
def Send(self, msg):
|
||||
buffer_size = msg.Serialize([self.buffer])
|
||||
if buffer_size <= 0:
|
||||
return True
|
||||
|
||||
# print(f'{self.name} send {self.buffer[0]} to {self.ip_address} {self.port}')
|
||||
self.udp_socket.sendto(self.buffer[:buffer_size], (self.ip_address, self.port))
|
||||
return True
|
||||
|
||||
def Publish(self, msg):
|
||||
buffer_size = msg.Serialize([self.buffer])
|
||||
if buffer_size <= 0:
|
||||
return True
|
||||
|
||||
# print(f'publish {self.buffer[0]} to {self.port}')
|
||||
self.udp_socket.sendto(self.buffer[:buffer_size], ('<broadcast>', self.port))
|
||||
return True
|
||||
|
||||
#endregion
|
||||
|
||||
#region Receive
|
||||
|
||||
def Receiver(self):
|
||||
while True:
|
||||
data, addr = self.udp_socket.recvfrom(1024)
|
||||
remote_ip_address = addr[0]
|
||||
remote_port = addr[1]
|
||||
# print(f'msg received from {remote_ip_address}:{remote_port}')
|
||||
remote_participant = self.GetParticipant(remote_ip_address, remote_port)
|
||||
if remote_participant is None:
|
||||
# print(f'new participant')
|
||||
remote_participant = self.AddParticipant(remote_ip_address, remote_port)
|
||||
self.ReceiveData(data, remote_participant)
|
||||
|
||||
def ReceiveData(self, data, remote_participant):
|
||||
msgId = data[0]
|
||||
# print(f'msg {msgId} ')
|
||||
match msgId:
|
||||
case ParticipantMsg.id:
|
||||
self.ProcessClientMsg(remote_participant, ParticipantMsg(data))
|
||||
case NetworkIdMsg.id:
|
||||
self.ProcessNetworkIdMsg(remote_participant, NetworkIdMsg(data))
|
||||
# case InvestigateMsg.id:
|
||||
# self.ProcessInvestigateMsg(InvestigateMsg(data))
|
||||
case ThingMsg.id:
|
||||
self.ProcessThingMsg(ThingMsg(data))
|
||||
case NameMsg.id:
|
||||
self.ProcessNameMsg(NameMsg(data))
|
||||
case ModelUrlMsg.id:
|
||||
self.ProcessModelUrlMsg(ModelUrlMsg(data))
|
||||
# case Messages.BinaryMsg.id:
|
||||
# msg = Messages.BinaryMsg(data)
|
||||
# msg.thing.ProcessBinary(msg.data)
|
||||
|
||||
def ProcessClientMsg(self, sender, msg: ParticipantMsg):
|
||||
pass
|
||||
|
||||
def ProcessNetworkIdMsg(self, sender, msg: NetworkIdMsg):
|
||||
print(f'{self.name} receive network id {self.network_id} -> {msg.network_id}')
|
||||
if self.network_id != msg.network_id:
|
||||
self.network_id = msg.network_id
|
||||
# print(f'sending all things {len(Thing.allThings)}')
|
||||
for thing in self.things:
|
||||
sender.SendThingInfo(thing)
|
||||
# self.Send(NameMsg(self.network_id, thing))
|
||||
|
||||
def ProcessInvestigateMsg(self, data: bytearray):
|
||||
pass
|
||||
|
||||
def ProcessThingMsg(self, msg: ThingMsg):
|
||||
print(f'received thing {msg.network_id} {msg.thing_id}')
|
||||
if msg.thing_type in self.thing_msg_processors:
|
||||
constructor = self.thing_msg_processors[msg.thing_type]
|
||||
constructor(msg.network_id, msg.thing_id)
|
||||
|
||||
# not really 'new' thing, but it is a start
|
||||
thing = Thing.Get(msg.network_id, msg.thing_id)
|
||||
if thing is not None:
|
||||
for handler in self.new_thing_handlers:
|
||||
handler(thing)
|
||||
|
||||
def ProcessNameMsg(self, msg: NameMsg):
|
||||
print(f'received name {msg.name}')
|
||||
|
||||
def ProcessModelUrlMsg(self, msg: ModelUrlMsg):
|
||||
print(f'received model url: {msg.url}')
|
||||
|
||||
def ProcessBinary(self, msg: BinaryMsg):
|
||||
print('received binary data')
|
||||
if msg.thing != None:
|
||||
msg.thing.ProcessBinary(msg.data)
|
||||
|
||||
|
||||
def Register(self, constructor, thing_type):
|
||||
self.thing_msg_processors[thing_type] = constructor
|
||||
|
||||
def OnNewThing(self, event_handler):
|
||||
self.new_thing_handlers.append(event_handler)
|
||||
|
||||
def OnNewThingType(self, thing_type, event_handler):
|
||||
def ConditionalHandler(thing):
|
||||
if thing.type == thing_type:
|
||||
event_handler(thing)
|
||||
self.new_thing_handlers.append(ConditionalHandler)
|
||||
|
||||
#endregion
|
164
Messages.py
164
Messages.py
@ -1,164 +0,0 @@
|
||||
import Messages.LowLevelMessages as LowLevelMessages
|
||||
from Thing import Thing
|
||||
|
||||
class IMessage:
|
||||
id = 0x00
|
||||
|
||||
## Serialize the message into the given buffer
|
||||
#
|
||||
## @returns: the length of the message
|
||||
def Serialize(buffer):
|
||||
return 0
|
||||
|
||||
def SendTo(self, participant):
|
||||
buffer_size = self.Serialize([participant.buffer])
|
||||
if buffer_size == 0:
|
||||
return False
|
||||
return participant.SendBuffer(buffer_size)
|
||||
|
||||
def Publish(self, participant):
|
||||
bufferSize = self.Serialize([participant.buffer])
|
||||
if bufferSize == 0:
|
||||
return False
|
||||
return participant.PublishBuffer(bufferSize)
|
||||
|
||||
class ParticipantMsg(IMessage):
|
||||
"""! A participant messages notifies other participants of its presence.
|
||||
|
||||
When received by another participant, it can be followed by a NetworkIdMsg
|
||||
to announce that participant to this client such that it can join privately
|
||||
"""
|
||||
## The message ID
|
||||
id: int = 0xA0
|
||||
## The length of the message
|
||||
length: int = 2
|
||||
## The network ID known by the participant.
|
||||
network_id: int = None
|
||||
|
||||
def __init__(self, network_id: int):
|
||||
"""! Create a new message for sending
|
||||
@param network_id The network ID known by the participant. Use 0 if it is unknown
|
||||
"""
|
||||
if isinstance(network_id, int):
|
||||
self.network_id = network_id
|
||||
elif isinstance(network_id, bytes):
|
||||
self.network_id = network_id[1]
|
||||
|
||||
def Serialize(self, buffer_ref):
|
||||
"""! Serialize the message into a byte array.
|
||||
@param buffer_ref The buffer to serialize into
|
||||
@return The length of the message in the buffer
|
||||
"""
|
||||
if buffer_ref is None or self.network_id is None:
|
||||
return 0
|
||||
|
||||
buffer: bytearray = buffer_ref[0]
|
||||
buffer[0:ParticipantMsg.length] = [
|
||||
ParticipantMsg.id,
|
||||
self.network_id
|
||||
]
|
||||
return ParticipantMsg.length
|
||||
|
||||
## A network id message invites another participant to a site
|
||||
#
|
||||
## This can be sent in response to a ClientMsg
|
||||
class NetworkIdMsg(IMessage):
|
||||
id = 0xA1
|
||||
length = 2
|
||||
|
||||
network_id = None
|
||||
|
||||
## Create a network id message
|
||||
#
|
||||
# @param network_id The network id assigned to the remote participant.
|
||||
def __init__(self, network_id):
|
||||
self.network_id = None
|
||||
if isinstance(network_id, int):
|
||||
self.network_id = network_id
|
||||
elif isinstance(network_id, bytes):
|
||||
self.network_id = network_id[1]
|
||||
|
||||
## Serialize the message into the given buffer
|
||||
#
|
||||
## @param buffer_ref A reference to the buffer to use. This should be a list with the buffer as its first and only element
|
||||
## @returns the length of the message
|
||||
def Serialize(self, buffer_ref):
|
||||
if self.network_id is None:
|
||||
return 0
|
||||
|
||||
buffer: bytearray = buffer_ref[0]
|
||||
buffer[0:NetworkIdMsg.length] = [
|
||||
NetworkIdMsg.id,
|
||||
self.network_id
|
||||
]
|
||||
return NetworkIdMsg.length
|
||||
|
||||
class InvestigateMsg():
|
||||
id = 0x81
|
||||
length = 3
|
||||
|
||||
def __init__(self, buffer):
|
||||
self.network_id = buffer[1]
|
||||
self.thing_id = buffer[2]
|
||||
|
||||
class PoseMsg(IMessage):
|
||||
"""! Message to communicate the pose of the thing
|
||||
|
||||
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.
|
||||
"""
|
||||
## The message ID
|
||||
id = 0x10
|
||||
## The length of the message
|
||||
length = 4
|
||||
|
||||
def __init__(self, network_id, thing):
|
||||
self.network_id = network_id
|
||||
self.thing = thing
|
||||
|
||||
def Serialize(self, buffer_ref):
|
||||
if (self.network_id is None) or (self.thing is None):
|
||||
return 0
|
||||
|
||||
buffer: bytearray = buffer_ref[0]
|
||||
buffer[0:PoseMsg.length] = [
|
||||
PoseMsg.id,
|
||||
self.network_id,
|
||||
self.thing.id,
|
||||
self.thing.pose_updated
|
||||
]
|
||||
ix = [4]
|
||||
if self.thing.pose_updated & Thing.Position:
|
||||
Messages.LowLevelMessages.SendSpherical(buffer, ix, self.thing.position)
|
||||
if self.thing.pose_updated & Thing.Orientation:
|
||||
Messages.LowLevelMessages.SendQuat32(buffer, ix, self.thing.orientation)
|
||||
if self.thing.pose_updated & Thing.LinearVelocity:
|
||||
Messages.LowLevelMessages.SendSpherical(buffer, ix, self.thing.linearVelocity)
|
||||
if self.thing.pose_updated & Thing.AngularVelocity:
|
||||
Messages.LowLevelMessages.SendSpherical(buffer, ix, self.thing.angularVelocity)
|
||||
return ix[0]
|
||||
|
||||
class BinaryMsg():
|
||||
id = 0xB1
|
||||
|
||||
def __init__(self, buffer):
|
||||
self.network_id = buffer[1]
|
||||
self.thing_id = buffer[2]
|
||||
self.thing: Thing = Thing.Get(self.network_id, self.thing_id)
|
||||
self.data = buffer[3:]
|
||||
|
||||
def SendTo(participant, thing, data: bytearray):
|
||||
length = 3
|
||||
|
||||
if thing.network_id is None or thing is None or data is None:
|
||||
return False
|
||||
|
||||
participant.buffer[0:length] = [
|
||||
BinaryMsg.id,
|
||||
participant.network_id,
|
||||
thing.id
|
||||
]
|
||||
full_length = length + len(data)
|
||||
participant.buffer[length:full_length] = data
|
||||
participant.SendBuffer(full_length)
|
||||
return True
|
164
Messages/Messages.py
Normal file
164
Messages/Messages.py
Normal file
@ -0,0 +1,164 @@
|
||||
import Messages.LowLevelMessages as LowLevelMessages
|
||||
from Thing import Thing
|
||||
|
||||
class IMessage:
|
||||
id = 0x00
|
||||
|
||||
## Serialize the message into the given buffer
|
||||
#
|
||||
## @returns: the length of the message
|
||||
def Serialize(buffer):
|
||||
return 0
|
||||
|
||||
def SendTo(self, participant):
|
||||
buffer_size = self.Serialize([participant.buffer])
|
||||
if buffer_size == 0:
|
||||
return False
|
||||
return participant.SendBuffer(buffer_size)
|
||||
|
||||
def Publish(self, participant):
|
||||
bufferSize = self.Serialize([participant.buffer])
|
||||
if bufferSize == 0:
|
||||
return False
|
||||
return participant.PublishBuffer(bufferSize)
|
||||
|
||||
# class ParticipantMsg(IMessage):
|
||||
# """! A participant messages notifies other participants of its presence.
|
||||
|
||||
# When received by another participant, it can be followed by a NetworkIdMsg
|
||||
# to announce that participant to this client such that it can join privately
|
||||
# """
|
||||
# ## The message ID
|
||||
# id: int = 0xA0
|
||||
# ## The length of the message
|
||||
# length: int = 2
|
||||
# ## The network ID known by the participant.
|
||||
# network_id: int = None
|
||||
|
||||
# def __init__(self, network_id: int):
|
||||
# """! Create a new message for sending
|
||||
# @param network_id The network ID known by the participant. Use 0 if it is unknown
|
||||
# """
|
||||
# if isinstance(network_id, int):
|
||||
# self.network_id = network_id
|
||||
# elif isinstance(network_id, bytes):
|
||||
# self.network_id = network_id[1]
|
||||
|
||||
# def Serialize(self, buffer_ref):
|
||||
# """! Serialize the message into a byte array.
|
||||
# @param buffer_ref The buffer to serialize into
|
||||
# @return The length of the message in the buffer
|
||||
# """
|
||||
# if buffer_ref is None or self.network_id is None:
|
||||
# return 0
|
||||
|
||||
# buffer: bytearray = buffer_ref[0]
|
||||
# buffer[0:ParticipantMsg.length] = [
|
||||
# ParticipantMsg.id,
|
||||
# self.network_id
|
||||
# ]
|
||||
# return ParticipantMsg.length
|
||||
|
||||
# ## A network id message invites another participant to a site
|
||||
# #
|
||||
# ## This can be sent in response to a ClientMsg
|
||||
# class NetworkIdMsg(IMessage):
|
||||
# id = 0xA1
|
||||
# length = 2
|
||||
|
||||
# network_id = None
|
||||
|
||||
# ## Create a network id message
|
||||
# #
|
||||
# # @param network_id The network id assigned to the remote participant.
|
||||
# def __init__(self, network_id):
|
||||
# self.network_id = None
|
||||
# if isinstance(network_id, int):
|
||||
# self.network_id = network_id
|
||||
# elif isinstance(network_id, bytes):
|
||||
# self.network_id = network_id[1]
|
||||
|
||||
# ## Serialize the message into the given buffer
|
||||
# #
|
||||
# ## @param buffer_ref A reference to the buffer to use. This should be a list with the buffer as its first and only element
|
||||
# ## @returns the length of the message
|
||||
# def Serialize(self, buffer_ref):
|
||||
# if self.network_id is None:
|
||||
# return 0
|
||||
|
||||
# buffer: bytearray = buffer_ref[0]
|
||||
# buffer[0:NetworkIdMsg.length] = [
|
||||
# NetworkIdMsg.id,
|
||||
# self.network_id
|
||||
# ]
|
||||
# return NetworkIdMsg.length
|
||||
|
||||
class InvestigateMsg():
|
||||
id = 0x81
|
||||
length = 3
|
||||
|
||||
def __init__(self, buffer):
|
||||
self.network_id = buffer[1]
|
||||
self.thing_id = buffer[2]
|
||||
|
||||
class PoseMsg(IMessage):
|
||||
"""! Message to communicate the pose of the thing
|
||||
|
||||
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.
|
||||
"""
|
||||
## The message ID
|
||||
id = 0x10
|
||||
## The length of the message
|
||||
length = 4
|
||||
|
||||
def __init__(self, network_id, thing):
|
||||
self.network_id = network_id
|
||||
self.thing = thing
|
||||
|
||||
def Serialize(self, buffer_ref):
|
||||
if (self.network_id is None) or (self.thing is None):
|
||||
return 0
|
||||
|
||||
buffer: bytearray = buffer_ref[0]
|
||||
buffer[0:PoseMsg.length] = [
|
||||
PoseMsg.id,
|
||||
self.network_id,
|
||||
self.thing.id,
|
||||
self.thing.pose_updated
|
||||
]
|
||||
ix = [4]
|
||||
if self.thing.pose_updated & Thing.Position:
|
||||
LowLevelMessages.SendSpherical(buffer, ix, self.thing.position)
|
||||
if self.thing.pose_updated & Thing.Orientation:
|
||||
LowLevelMessages.SendQuat32(buffer, ix, self.thing.orientation)
|
||||
if self.thing.pose_updated & Thing.LinearVelocity:
|
||||
LowLevelMessages.SendSpherical(buffer, ix, self.thing.linearVelocity)
|
||||
if self.thing.pose_updated & Thing.AngularVelocity:
|
||||
LowLevelMessages.SendSpherical(buffer, ix, self.thing.angularVelocity)
|
||||
return ix[0]
|
||||
|
||||
class BinaryMsg():
|
||||
id = 0xB1
|
||||
|
||||
def __init__(self, buffer):
|
||||
self.network_id = buffer[1]
|
||||
self.thing_id = buffer[2]
|
||||
self.thing = Thing.Get(self.network_id, self.thing_id)
|
||||
self.data = buffer[3:]
|
||||
|
||||
def SendTo(participant, thing, data: bytearray):
|
||||
length = 3
|
||||
|
||||
if thing.network_id is None or thing is None or data is None:
|
||||
return False
|
||||
|
||||
participant.buffer[0:length] = [
|
||||
BinaryMsg.id,
|
||||
participant.network_id,
|
||||
thing.id
|
||||
]
|
||||
full_length = length + len(data)
|
||||
participant.buffer[length:full_length] = data
|
||||
participant.SendBuffer(full_length)
|
||||
return True
|
@ -1,13 +1,36 @@
|
||||
# from Messages.Messages import IMessage
|
||||
from Messages.Messages import IMessage
|
||||
|
||||
## A network id message invites another participant to a site
|
||||
#
|
||||
## This can be sent in response to a ClientMsg
|
||||
class NetworkIdMsg(IMessage):
|
||||
id = 0xA1
|
||||
length = 2
|
||||
|
||||
# def SendTo(participant, network_id):
|
||||
# if network_id is None:
|
||||
# return 0
|
||||
network_id = None
|
||||
|
||||
## Create a network id message
|
||||
#
|
||||
# @param network_id The network id assigned to the remote participant.
|
||||
def __init__(self, network_id):
|
||||
self.network_id = None
|
||||
if isinstance(network_id, int):
|
||||
self.network_id = network_id
|
||||
elif isinstance(network_id, bytes):
|
||||
self.network_id = network_id[1]
|
||||
|
||||
## Serialize the message into the given buffer
|
||||
#
|
||||
## @param buffer_ref A reference to the buffer to use. This should be a list with the buffer as its first and only element
|
||||
## @returns the length of the message
|
||||
def Serialize(self, buffer_ref):
|
||||
if self.network_id is None:
|
||||
return 0
|
||||
|
||||
# participant.buffer[0:2] = [
|
||||
# NetworkIdMsg.id,
|
||||
# network_id
|
||||
# ]
|
||||
# return NetworkIdMsg.length
|
||||
buffer: bytearray = buffer_ref[0]
|
||||
buffer[0:NetworkIdMsg.length] = [
|
||||
NetworkIdMsg.id,
|
||||
self.network_id
|
||||
]
|
||||
return NetworkIdMsg.length
|
||||
|
||||
|
@ -1,72 +1,72 @@
|
||||
# from Messages.Messages import IMessage
|
||||
from Messages.Messages import IMessage
|
||||
|
||||
# class ParticipantMsg(IMessage):
|
||||
# """! A participant messages notifies other participants of its presence.
|
||||
class ParticipantMsg(IMessage):
|
||||
"""! A participant messages notifies other participants of its presence.
|
||||
|
||||
# When received by another participant, it can be followed by a NetworkIdMsg
|
||||
# to announce that participant to this client such that it can join privately
|
||||
# """
|
||||
# ## The message ID
|
||||
# id: int = 0xA0
|
||||
# ## The length of the message
|
||||
# length: int = 2
|
||||
# ## The network ID known by the participant.
|
||||
# network_id: int = None
|
||||
When received by another participant, it can be followed by a NetworkIdMsg
|
||||
to announce that participant to this client such that it can join privately
|
||||
"""
|
||||
## The message ID
|
||||
id: int = 0xA0
|
||||
## The length of the message
|
||||
length: int = 2
|
||||
## The network ID known by the participant.
|
||||
network_id: int = None
|
||||
|
||||
# def __init__(self, network_id: int):
|
||||
# """! Create a new message for sending
|
||||
# @param network_id The network ID known by the participant. Use 0 if it is unknown
|
||||
# """
|
||||
# if isinstance(network_id, int):
|
||||
# self.network_id = network_id
|
||||
# elif isinstance(network_id, bytes):
|
||||
# self.network_id = network_id[1]
|
||||
def __init__(self, network_id: int):
|
||||
"""! Create a new message for sending
|
||||
@param network_id The network ID known by the participant. Use 0 if it is unknown
|
||||
"""
|
||||
if isinstance(network_id, int):
|
||||
self.network_id = network_id
|
||||
elif isinstance(network_id, bytes):
|
||||
self.network_id = network_id[1]
|
||||
|
||||
# def Serialize(self, buffer_ref):
|
||||
# """! Serialize the message into a byte array.
|
||||
# @param buffer_ref The buffer to serialize into
|
||||
# @return The length of the message in the buffer
|
||||
# """
|
||||
# if buffer_ref is None or self.network_id is None:
|
||||
# return 0
|
||||
def Serialize(self, buffer_ref):
|
||||
"""! Serialize the message into a byte array.
|
||||
@param buffer_ref The buffer to serialize into
|
||||
@return The length of the message in the buffer
|
||||
"""
|
||||
if buffer_ref is None or self.network_id is None:
|
||||
return 0
|
||||
|
||||
# buffer: bytearray = buffer_ref[0]
|
||||
# buffer[0:ParticipantMsg.length] = [
|
||||
# ParticipantMsg.id,
|
||||
# self.network_id
|
||||
# ]
|
||||
# return ParticipantMsg.length
|
||||
buffer: bytearray = buffer_ref[0]
|
||||
buffer[0:ParticipantMsg.length] = [
|
||||
ParticipantMsg.id,
|
||||
self.network_id
|
||||
]
|
||||
return ParticipantMsg.length
|
||||
|
||||
# @staticmethod
|
||||
# def Serialized(buffer_ref, network_id):
|
||||
# if buffer_ref is None or network_id is None:
|
||||
# return 0
|
||||
@staticmethod
|
||||
def Serialized(buffer_ref, network_id):
|
||||
if buffer_ref is None or network_id is None:
|
||||
return 0
|
||||
|
||||
# buffer: bytearray = buffer_ref[0]
|
||||
# buffer[0:ClientMsg.length] = [
|
||||
# ClientMsg.id,
|
||||
# network_id
|
||||
# ]
|
||||
# return ClientMsg.length
|
||||
buffer: bytearray = buffer_ref[0]
|
||||
buffer[0:ParticipantMsg.length] = [
|
||||
ParticipantMsg.id,
|
||||
network_id
|
||||
]
|
||||
return ParticipantMsg.length
|
||||
|
||||
## Send the network_id to the participant
|
||||
# def SendTo(participant, network_id):
|
||||
# if participant is None or network_id is None:
|
||||
# return False
|
||||
# Send the network_id to the participant
|
||||
def SendTo(participant, network_id):
|
||||
if participant is None or network_id is None:
|
||||
return False
|
||||
|
||||
# participant.buffer[0:2] = [
|
||||
# ClientMsg.id,
|
||||
# network_id
|
||||
# ]
|
||||
# participant.SendBuffer(ClientMsg.length)
|
||||
participant.buffer[0:2] = [
|
||||
ParticipantMsg.id,
|
||||
network_id
|
||||
]
|
||||
participant.SendBuffer(ParticipantMsg.length)
|
||||
|
||||
## Publish the network_id
|
||||
# def Publish(participant, network_id):
|
||||
# if participant is None or network_id is None:
|
||||
# return False
|
||||
# Publish the network_id
|
||||
def Publish(participant, network_id):
|
||||
if participant is None or network_id is None:
|
||||
return False
|
||||
|
||||
# participant.buffer[0:2] = [
|
||||
# ClientMsg.id,
|
||||
# network_id
|
||||
# ]
|
||||
# participant.PublishBuffer(ClientMsg.length)
|
||||
participant.buffer[0:2] = [
|
||||
ParticipantMsg.id,
|
||||
network_id
|
||||
]
|
||||
participant.PublishBuffer(ParticipantMsg.length)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
210
Participant.py
210
Participant.py
@ -1,198 +1,22 @@
|
||||
import socket
|
||||
import threading
|
||||
|
||||
from Messages.ParticipantMsg import ParticipantMsg
|
||||
from Messages.NetworkIdMsg import NetworkIdMsg
|
||||
from Messages.ThingMsg import ThingMsg
|
||||
from Messages.NameMsg import NameMsg
|
||||
from Messages.ModelUrlMsg import ModelUrlMsg
|
||||
from Messages import *
|
||||
from Thing import Thing
|
||||
|
||||
import sys
|
||||
micropython = 'micropython' in sys.modules
|
||||
|
||||
if micropython:
|
||||
from MicroPython.uPythonParticipant import Bla
|
||||
|
||||
class Participant:
|
||||
"""! A participant is used for communcation between things.
|
||||
|
||||
Currently, only UDP communication is supported
|
||||
"""
|
||||
publishInterval = 3000 # 3 seconds
|
||||
|
||||
buffer = None
|
||||
## The network ID of the participant
|
||||
network_id = None
|
||||
nextPublishMe = 0
|
||||
others = None
|
||||
thread = None
|
||||
name = "Participant"
|
||||
|
||||
def __init__(self, ipAddress = "0.0.0.0", port=7681, remote=False, udp_socket =None):
|
||||
self.ip_address = ipAddress
|
||||
def __init__(self, ip_address, port):
|
||||
self.ip_addres = ip_address
|
||||
self.port = port
|
||||
self.others = []
|
||||
self.network_id = 0
|
||||
self.buffer = bytearray(256)
|
||||
self.thing_msg_processors = {}
|
||||
self.new_thing_handlers = []
|
||||
self.things = set({ None })
|
||||
|
||||
if remote == False:
|
||||
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
self.udp_socket.bind(("0.0.0.0", port))
|
||||
self.AddParticipant(self.ip_address, self.port)
|
||||
|
||||
self.thread = threading.Thread(target = self.Receiver)
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
else:
|
||||
self.udp_socket = udp_socket
|
||||
|
||||
#region Update
|
||||
|
||||
def GetParticipant(self, ip_address, port):
|
||||
# print(f'{self.name} Get participant {ip_address} {port}')
|
||||
# for item in self.others:
|
||||
# print(f'- {item.ip_address} {item.port}')
|
||||
|
||||
found_participants = (item for item in self.others
|
||||
if item.ip_address == ip_address and item.port == port)
|
||||
participant = next(found_participants, None)
|
||||
return participant
|
||||
|
||||
def AddParticipant(self, ip_address, port):
|
||||
# print(f'{self.name} Add participant {ip_address} {port}')
|
||||
remote_participant = Participant(ip_address, port, remote=True, udp_socket=self.udp_socket)
|
||||
remote_participant.network_id = len(self.others)
|
||||
self.others.append(remote_participant)
|
||||
return remote_participant
|
||||
|
||||
def Update(self, currentTimeMs = None):
|
||||
if currentTimeMs is None:
|
||||
currentTimeMs = time.time() * 1000
|
||||
|
||||
if self.publishInterval > 0 and currentTimeMs > self.nextPublishMe:
|
||||
self.Publish(ParticipantMsg(self.network_id))
|
||||
print(f'Publish ClientMsg {self.network_id}')
|
||||
self.nextPublishMe = currentTimeMs + self.publishInterval
|
||||
|
||||
Thing.UpdateAll(currentTimeMs)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send
|
||||
|
||||
def SendThingInfo(self, thing):
|
||||
self.Send(ThingMsg(self.network_id, thing))
|
||||
self.Send(NameMsg(self.network_id, thing))
|
||||
self.Send(ModelUrlMsg(self.network_id, thing))
|
||||
|
||||
def Send(self, msg):
|
||||
buffer_size = msg.Serialize([self.buffer])
|
||||
if buffer_size <= 0:
|
||||
return True
|
||||
|
||||
# print(f'{self.name} send {self.buffer[0]} to {self.ip_address} {self.port}')
|
||||
self.udp_socket.sendto(self.buffer[:buffer_size], (self.ip_address, self.port))
|
||||
return True
|
||||
|
||||
def Publish(self, msg):
|
||||
buffer_size = msg.Serialize([self.buffer])
|
||||
if buffer_size <= 0:
|
||||
return True
|
||||
|
||||
# print(f'publish {self.buffer[0]} to {self.port}')
|
||||
self.udp_socket.sendto(self.buffer[:buffer_size], ('<broadcast>', self.port))
|
||||
return True
|
||||
|
||||
#endregion
|
||||
|
||||
#region Receive
|
||||
def Add(self, thing):
|
||||
thing.id = len(self.things)
|
||||
self.things.add(thing)
|
||||
|
||||
def Receiver(self):
|
||||
while True:
|
||||
data, addr = self.udp_socket.recvfrom(1024)
|
||||
remote_ip_address = addr[0]
|
||||
remote_port = addr[1]
|
||||
# print(f'msg received from {remote_ip_address}:{remote_port}')
|
||||
remote_participant = self.GetParticipant(remote_ip_address, remote_port)
|
||||
if remote_participant is None:
|
||||
# print(f'new participant')
|
||||
remote_participant = self.AddParticipant(remote_ip_address, remote_port)
|
||||
self.ReceiveData(data, remote_participant)
|
||||
def Get(self, network_id, thing_id):
|
||||
for thing in self.things:
|
||||
if thing is not None:
|
||||
if thing.network_id == network_id and thing.id == thing_id:
|
||||
return thing
|
||||
return None
|
||||
|
||||
def ReceiveData(self, data, remote_participant):
|
||||
msgId = data[0]
|
||||
# print(f'msg {msgId} ')
|
||||
match msgId:
|
||||
case ParticipantMsg.id:
|
||||
self.ProcessClientMsg(remote_participant, ParticipantMsg(data))
|
||||
case NetworkIdMsg.id:
|
||||
self.ProcessNetworkIdMsg(remote_participant, NetworkIdMsg(data))
|
||||
# case InvestigateMsg.id:
|
||||
# self.ProcessInvestigateMsg(InvestigateMsg(data))
|
||||
case ThingMsg.id:
|
||||
self.ProcessThingMsg(ThingMsg(data))
|
||||
case NameMsg.id:
|
||||
self.ProcessNameMsg(NameMsg(data))
|
||||
case ModelUrlMsg.id:
|
||||
self.ProcessModelUrlMsg(ModelUrlMsg(data))
|
||||
# case Messages.BinaryMsg.id:
|
||||
# msg = Messages.BinaryMsg(data)
|
||||
# msg.thing.ProcessBinary(msg.data)
|
||||
|
||||
def ProcessClientMsg(self, sender, msg: ParticipantMsg):
|
||||
pass
|
||||
|
||||
def ProcessNetworkIdMsg(self, sender, msg: NetworkIdMsg):
|
||||
print(f'{self.name} receive network id {self.network_id} -> {msg.network_id}')
|
||||
if self.network_id != msg.network_id:
|
||||
self.network_id = msg.network_id
|
||||
# print(f'sending all things {len(Thing.allThings)}')
|
||||
for thing in Thing.allThings:
|
||||
sender.SendThingInfo(thing)
|
||||
# self.Send(NameMsg(self.network_id, thing))
|
||||
|
||||
def ProcessInvestigateMsg(self, data: bytearray):
|
||||
pass
|
||||
|
||||
def ProcessThingMsg(self, msg: ThingMsg):
|
||||
print(f'received thing {msg.network_id} {msg.thing_id}')
|
||||
if msg.thing_type in self.thing_msg_processors:
|
||||
constructor = self.thing_msg_processors[msg.thing_type]
|
||||
constructor(msg.network_id, msg.thing_id)
|
||||
|
||||
# not really 'new' thing, but it is a start
|
||||
thing = Thing.Get(msg.network_id, msg.thing_id)
|
||||
if thing is not None:
|
||||
for handler in self.new_thing_handlers:
|
||||
handler(thing)
|
||||
|
||||
def ProcessNameMsg(self, msg: NameMsg):
|
||||
print(f'received name {msg.name}')
|
||||
|
||||
def ProcessModelUrlMsg(self, msg: ModelUrlMsg):
|
||||
print(f'received model url: {msg.url}')
|
||||
|
||||
def ProcessBinary(self, msg: BinaryMsg):
|
||||
print('received binary data')
|
||||
if msg.thing != None:
|
||||
msg.thing.ProcessBinary(msg.data)
|
||||
|
||||
|
||||
def Register(self, constructor, thing_type):
|
||||
self.thing_msg_processors[thing_type] = constructor
|
||||
|
||||
def OnNewThing(self, event_handler):
|
||||
self.new_thing_handlers.append(event_handler)
|
||||
|
||||
def OnNewThingType(self, thing_type, event_handler):
|
||||
def ConditionalHandler(thing):
|
||||
if thing.type == thing_type:
|
||||
event_handler(thing)
|
||||
self.new_thing_handlers.append(ConditionalHandler)
|
||||
|
||||
#endregion
|
||||
## Update all things
|
||||
def Update(self, currentTimeMs):
|
||||
for thing in list(self.things):
|
||||
if thing is not None:
|
||||
thing.update(currentTimeMs)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from Participant import Participant
|
||||
from LocalParticipant import LocalParticipant
|
||||
from Messages.ParticipantMsg import ParticipantMsg
|
||||
from Messages.NetworkIdMsg import NetworkIdMsg
|
||||
from Sensors.TemperatureSensor import TemperatureSensor
|
||||
@ -7,7 +7,7 @@ from Thing import Thing
|
||||
import socket
|
||||
import threading
|
||||
|
||||
class SiteServer(Participant):
|
||||
class SiteServer(LocalParticipant):
|
||||
name = "Site Server"
|
||||
|
||||
def __init__(self, ip_address="0.0.0.0", port=7681, remote=False, udp_socket = None):
|
||||
|
36
Thing.py
36
Thing.py
@ -47,24 +47,24 @@ class Thing:
|
||||
print('default binary processor')
|
||||
pass
|
||||
|
||||
allThings = set({ None })
|
||||
# allThings = set({ None })
|
||||
|
||||
@staticmethod
|
||||
def Add(thing):
|
||||
thing.id = len(Thing.allThings)
|
||||
Thing.allThings.add(thing)
|
||||
# @staticmethod
|
||||
# def Add(thing):
|
||||
# thing.id = len(Thing.allThings)
|
||||
# Thing.allThings.add(thing)
|
||||
|
||||
@staticmethod
|
||||
def Get(network_id, thing_id):
|
||||
for thing in Thing.allThings:
|
||||
if thing is not None:
|
||||
if thing.network_id == network_id and thing.id == thing_id:
|
||||
return thing
|
||||
return None
|
||||
# @staticmethod
|
||||
# def Get(network_id, thing_id):
|
||||
# for thing in Thing.allThings:
|
||||
# if thing is not None:
|
||||
# if thing.network_id == network_id and thing.id == thing_id:
|
||||
# return thing
|
||||
# return None
|
||||
|
||||
## Update all things
|
||||
@staticmethod
|
||||
def UpdateAll(currentTime):
|
||||
for thing in list(Thing.allThings):
|
||||
if thing is not None:
|
||||
thing.update(currentTime)
|
||||
# ## Update all things
|
||||
# @staticmethod
|
||||
# def UpdateAll(currentTime):
|
||||
# for thing in list(Thing.allThings):
|
||||
# if thing is not None:
|
||||
# thing.update(currentTime)
|
||||
|
@ -1,9 +1,9 @@
|
||||
__all__ = ['Thing',
|
||||
'Participant',
|
||||
'LocalParticipant',
|
||||
'SiteServer']
|
||||
|
||||
from .LinearAlgebra.Direction import Direction
|
||||
from .Participant import Participant
|
||||
from .LocalParticipant import LocalParticipant
|
||||
from .Thing import Thing
|
||||
from .LinearAlgebra.Spherical import Spherical
|
||||
from .LinearAlgebra.SwingTwist import SwingTwist
|
||||
|
@ -8,13 +8,13 @@ sys.path.append(str(Path(__file__).resolve().parent.parent))
|
||||
import unittest
|
||||
|
||||
from Thing import Thing
|
||||
from Participant import Participant
|
||||
from LocalParticipant import LocalParticipant
|
||||
from SiteServer import SiteServer
|
||||
|
||||
class ThingTest(unittest.TestCase):
|
||||
|
||||
def test_participant(self):
|
||||
participant: Participant = Participant(ipAddress="127.0.0.1", port=7681)
|
||||
participant: LocalParticipant = LocalParticipant()
|
||||
|
||||
milliseconds = time.time() * 1000
|
||||
start_time = milliseconds
|
||||
@ -33,7 +33,7 @@ class ThingTest(unittest.TestCase):
|
||||
|
||||
def test_site_participant(self):
|
||||
site = SiteServer(7681)
|
||||
participant = Participant("127.0.0.1", 7681)
|
||||
participant = LocalParticipant("127.0.0.1", 7681)
|
||||
|
||||
milliseconds = time.time() * 1000
|
||||
start_time = milliseconds
|
||||
@ -46,7 +46,7 @@ class ThingTest(unittest.TestCase):
|
||||
def test_thing_msg(self):
|
||||
site = SiteServer()
|
||||
|
||||
participant = Participant("127.0.0.1")
|
||||
participant = LocalParticipant("127.0.0.1")
|
||||
thing = Thing()
|
||||
thing.name = "First thing"
|
||||
thing.model_url = "https://passer.life/extras/ant.jpg"
|
||||
|
Loading…
x
Reference in New Issue
Block a user