diff --git a/.gitignore b/.gitignore index 2a93f5c..e474614 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ -__pycache__/* -test/__pycache__/* .vscode/* .DS_Store DoxyGen/DoxyWarnLogfile.txt -**/__pycache__ \ No newline at end of file +**/__pycache__/* \ No newline at end of file diff --git a/LinearAlgebra/__pycache__/Direction.cpython-312.pyc b/LinearAlgebra/__pycache__/Direction.cpython-312.pyc index cfa819d..ee50c97 100644 Binary files a/LinearAlgebra/__pycache__/Direction.cpython-312.pyc and b/LinearAlgebra/__pycache__/Direction.cpython-312.pyc differ diff --git a/LinearAlgebra/__pycache__/Quaternion.cpython-312.pyc b/LinearAlgebra/__pycache__/Quaternion.cpython-312.pyc index 202f63c..6b95b95 100644 Binary files a/LinearAlgebra/__pycache__/Quaternion.cpython-312.pyc and b/LinearAlgebra/__pycache__/Quaternion.cpython-312.pyc differ diff --git a/LinearAlgebra/__pycache__/Spherical.cpython-312.pyc b/LinearAlgebra/__pycache__/Spherical.cpython-312.pyc index 3ae01d9..c6b4fc8 100644 Binary files a/LinearAlgebra/__pycache__/Spherical.cpython-312.pyc and b/LinearAlgebra/__pycache__/Spherical.cpython-312.pyc differ diff --git a/LinearAlgebra/__pycache__/SwingTwist.cpython-312.pyc b/LinearAlgebra/__pycache__/SwingTwist.cpython-312.pyc index 86bf59c..db17909 100644 Binary files a/LinearAlgebra/__pycache__/SwingTwist.cpython-312.pyc and b/LinearAlgebra/__pycache__/SwingTwist.cpython-312.pyc differ diff --git a/LocalParticipant.py b/LocalParticipant.py new file mode 100644 index 0000000..3caa71b --- /dev/null +++ b/LocalParticipant.py @@ -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], ('', 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 \ No newline at end of file diff --git a/Messages.py b/Messages.py deleted file mode 100644 index 0cfa123..0000000 --- a/Messages.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/Messages/Messages.py b/Messages/Messages.py new file mode 100644 index 0000000..941be44 --- /dev/null +++ b/Messages/Messages.py @@ -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 \ No newline at end of file diff --git a/Messages/NetworkIdMsg.py b/Messages/NetworkIdMsg.py index a1e6baf..bb7d7ad 100644 --- a/Messages/NetworkIdMsg.py +++ b/Messages/NetworkIdMsg.py @@ -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 diff --git a/Messages/ParticipantMsg.py b/Messages/ParticipantMsg.py index d1a5e8e..ed496bb 100644 --- a/Messages/ParticipantMsg.py +++ b/Messages/ParticipantMsg.py @@ -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) \ No newline at end of file + participant.buffer[0:2] = [ + ParticipantMsg.id, + network_id + ] + participant.PublishBuffer(ParticipantMsg.length) \ No newline at end of file diff --git a/Messages/__pycache__/LowLevelMessages.cpython-312.pyc b/Messages/__pycache__/LowLevelMessages.cpython-312.pyc index 08b4352..881dbbc 100644 Binary files a/Messages/__pycache__/LowLevelMessages.cpython-312.pyc and b/Messages/__pycache__/LowLevelMessages.cpython-312.pyc differ diff --git a/Messages/__pycache__/Messages.cpython-312.pyc b/Messages/__pycache__/Messages.cpython-312.pyc index 0fda623..4910e52 100644 Binary files a/Messages/__pycache__/Messages.cpython-312.pyc and b/Messages/__pycache__/Messages.cpython-312.pyc differ diff --git a/Messages/__pycache__/ModelUrlMsg.cpython-312.pyc b/Messages/__pycache__/ModelUrlMsg.cpython-312.pyc index 1edaf47..bf0f92e 100644 Binary files a/Messages/__pycache__/ModelUrlMsg.cpython-312.pyc and b/Messages/__pycache__/ModelUrlMsg.cpython-312.pyc differ diff --git a/Messages/__pycache__/NameMsg.cpython-312.pyc b/Messages/__pycache__/NameMsg.cpython-312.pyc index a040df3..380175f 100644 Binary files a/Messages/__pycache__/NameMsg.cpython-312.pyc and b/Messages/__pycache__/NameMsg.cpython-312.pyc differ diff --git a/Messages/__pycache__/NetworkIdMsg.cpython-312.pyc b/Messages/__pycache__/NetworkIdMsg.cpython-312.pyc index fdfd880..580a06a 100644 Binary files a/Messages/__pycache__/NetworkIdMsg.cpython-312.pyc and b/Messages/__pycache__/NetworkIdMsg.cpython-312.pyc differ diff --git a/Messages/__pycache__/ParticipantMsg.cpython-312.pyc b/Messages/__pycache__/ParticipantMsg.cpython-312.pyc index bc78bfa..fbed586 100644 Binary files a/Messages/__pycache__/ParticipantMsg.cpython-312.pyc and b/Messages/__pycache__/ParticipantMsg.cpython-312.pyc differ diff --git a/Messages/__pycache__/ThingMsg.cpython-312.pyc b/Messages/__pycache__/ThingMsg.cpython-312.pyc index c62ad79..97b68ef 100644 Binary files a/Messages/__pycache__/ThingMsg.cpython-312.pyc and b/Messages/__pycache__/ThingMsg.cpython-312.pyc differ diff --git a/Participant.py b/Participant.py index beadcab..cadb02d 100644 --- a/Participant.py +++ b/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], ('', 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 \ No newline at end of file + ## Update all things + def Update(self, currentTimeMs): + for thing in list(self.things): + if thing is not None: + thing.update(currentTimeMs) diff --git a/SiteServer.py b/SiteServer.py index f62349e..2d0ef18 100644 --- a/SiteServer.py +++ b/SiteServer.py @@ -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): diff --git a/Thing.py b/Thing.py index e7e7c9b..b01a0a1 100644 --- a/Thing.py +++ b/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) diff --git a/__init__.py b/__init__.py index 45245a9..0354ddf 100644 --- a/__init__.py +++ b/__init__.py @@ -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 diff --git a/test/thing_test.py b/test/thing_test.py index bbc7fb6..95bccfb 100644 --- a/test/thing_test.py +++ b/test/thing_test.py @@ -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"