Event handling

This commit is contained in:
Pascal Serrarens 2025-01-20 19:26:30 +01:00
parent d9108a418b
commit e218e0ea51
16 changed files with 127 additions and 61 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto

5
.gitignore vendored
View File

@ -1,2 +1,5 @@
__pycache__/* __pycache__/*
test/__pycache__/* test/__pycache__/*
.vscode/*
DoxyGen/DoxyWarnLogfile.txt
**/__pycache__

27
BinaryMsg.py Normal file
View File

@ -0,0 +1,27 @@
from .Messages import IMessage
from .Thing import Thing
class BinaryMsg(IMessage):
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

View File

@ -1,4 +1,4 @@
from Messages import IMessage from .Messages import IMessage
## A client message announces the presence of a participant ## A client message announces the presence of a participant
# #

View File

@ -1,5 +1,5 @@
import numpy as np import numpy as np
from SwingTwist import SwingTwist from .SwingTwist import SwingTwist
def SendAngle8(buffer, ix_ref, angle): def SendAngle8(buffer, ix_ref, angle):
# Normalize angle # Normalize angle
@ -24,7 +24,10 @@ def SendFloat16(buffer, ix_ref, value):
def ReceiveFloat16(buffer, ix_ref) -> float: def ReceiveFloat16(buffer, ix_ref) -> float:
ix = ix_ref[0] ix = ix_ref[0]
# if ix < len(buffer) - 1:
binary = (buffer[ix] << 8) + buffer[ix+1] binary = (buffer[ix] << 8) + buffer[ix+1]
# else:
# binary = 0
value16 = np.uint16(binary).view(np.float16) value16 = np.uint16(binary).view(np.float16)
ix_ref[0] += 2 ix_ref[0] += 2
return float(value16) return float(value16)

View File

@ -1,5 +1,5 @@
import LowLevelMessages from . import LowLevelMessages
from Thing import Thing from .Thing import Thing
class IMessage: class IMessage:
id = 0x00 id = 0x00
@ -59,28 +59,3 @@ class PoseMsg(IMessage):
if self.thing.pose_updated & Thing.AngularVelocity: if self.thing.pose_updated & Thing.AngularVelocity:
LowLevelMessages.SendSpherical(buffer, ix, self.thing.angularVelocity) LowLevelMessages.SendSpherical(buffer, ix, self.thing.angularVelocity)
return ix[0] 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

View File

@ -1,4 +1,4 @@
from Messages import IMessage from .Messages import IMessage
class ModelUrlMsg(IMessage): class ModelUrlMsg(IMessage):
id = 0x90 id = 0x90

View File

@ -1,4 +1,4 @@
from Messages import IMessage from .Messages import IMessage
class NameMsg(IMessage): class NameMsg(IMessage):
id = 0x91 id = 0x91

View File

@ -1,4 +1,4 @@
from Messages import IMessage from .Messages import IMessage
## A network id message invites another participant to a site ## A network id message invites another participant to a site
# #

View File

@ -1,12 +1,14 @@
import socket import socket
import threading import threading
import time
from ClientMsg import ClientMsg from .ClientMsg import ClientMsg
from NetworkIdMsg import NetworkIdMsg from .NetworkIdMsg import NetworkIdMsg
from ThingMsg import ThingMsg from .ThingMsg import ThingMsg
from NameMsg import NameMsg from .NameMsg import NameMsg
from ModelUrlMsg import ModelUrlMsg from .ModelUrlMsg import ModelUrlMsg
from Thing import Thing from .BinaryMsg import BinaryMsg
from .Thing import Thing
## A participant is device which can communicate with other participants ## A participant is device which can communicate with other participants
# #
@ -27,11 +29,13 @@ class Participant:
self.others = [] self.others = []
self.network_id = 0 self.network_id = 0
self.buffer = bytearray(256) self.buffer = bytearray(256)
self.thing_msg_processors = {}
self.new_thing_handlers = []
if remote == False: if remote == False:
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 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.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.udp_socket.bind(("0.0.0.0", 0)) self.udp_socket.bind(("0.0.0.0", port))
self.AddParticipant(self.ip_address, self.port) self.AddParticipant(self.ip_address, self.port)
self.thread = threading.Thread(target = self.Receiver) self.thread = threading.Thread(target = self.Receiver)
@ -59,7 +63,10 @@ class Participant:
self.others.append(remote_participant) self.others.append(remote_participant)
return remote_participant return remote_participant
def Update(self, currentTimeMs): def Update(self, currentTimeMs = None):
if currentTimeMs is None:
currentTimeMs = time.time() * 1000
if self.publishInterval > 0 and currentTimeMs > self.nextPublishMe: if self.publishInterval > 0 and currentTimeMs > self.nextPublishMe:
self.Publish(ClientMsg(self.network_id)) self.Publish(ClientMsg(self.network_id))
print(f'Publish ClientMsg {self.network_id}') print(f'Publish ClientMsg {self.network_id}')
@ -126,9 +133,11 @@ class Participant:
self.ProcessNameMsg(NameMsg(data)) self.ProcessNameMsg(NameMsg(data))
case ModelUrlMsg.id: case ModelUrlMsg.id:
self.ProcessModelUrlMsg(ModelUrlMsg(data)) self.ProcessModelUrlMsg(ModelUrlMsg(data))
# case Messages.BinaryMsg.id: case BinaryMsg.id:
# msg = Messages.BinaryMsg(data) self.ProcessBinary(BinaryMsg(data))
# msg.thing.ProcessBinary(msg.data) # msg = BinaryMsg(data)
# if msg.thing != None:
# msg.thing.ProcessBinary(msg.data)
def ProcessClientMsg(self, sender, msg: ClientMsg): def ProcessClientMsg(self, sender, msg: ClientMsg):
pass pass
@ -146,7 +155,16 @@ class Participant:
pass pass
def ProcessThingMsg(self, msg: ThingMsg): def ProcessThingMsg(self, msg: ThingMsg):
print(f'received thing {msg.thing_id}') 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): def ProcessNameMsg(self, msg: NameMsg):
print(f'received name {msg.name}') print(f'received name {msg.name}')
@ -154,4 +172,22 @@ class Participant:
def ProcessModelUrlMsg(self, msg: ModelUrlMsg): def ProcessModelUrlMsg(self, msg: ModelUrlMsg):
print(f'received model url: {msg.url}') 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 #endregion

View File

@ -0,0 +1,19 @@
from ..Thing import Thing
from .. import LowLevelMessages
class TemperatureSensor(Thing):
def __init__(self, network_id, thing_id):
super().__init__(network_id, thing_id, type = Thing.Type.TemperatureSensor)
self.temp = 0
self._watchers = []
def OnUpdate(self, handler):
self._watchers.append(handler)
def CancelOnUpdate(self, handler):
self._watchers.remove(handler)
def ProcessBinary(self, data):
ix = 0
self.temp = LowLevelMessages.ReceiveFloat16(data, [ix])
for watcher in self._watchers:
watcher(self.temp)

View File

@ -1,6 +1,6 @@
from Participant import Participant from .Participant import Participant
from ClientMsg import ClientMsg from .ClientMsg import ClientMsg
from NetworkIdMsg import NetworkIdMsg from .NetworkIdMsg import NetworkIdMsg
import socket import socket
import threading import threading
@ -19,7 +19,7 @@ class SiteServer(Participant):
if remote == False: if remote == False:
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 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.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.udp_socket.bind(("0.0.0.0", 7681)) self.udp_socket.bind(("0.0.0.0", port))
self.AddParticipant(self.ip_address, self.port) self.AddParticipant(self.ip_address, self.port)
self.thread = threading.Thread(target = self.Receiver) self.thread = threading.Thread(target = self.Receiver)

View File

@ -1,5 +1,5 @@
import math import math
from Direction import Direction from .Direction import Direction
class Spherical: class Spherical:
def __init__(self, distance, direction): def __init__(self, distance, direction):

View File

@ -1,5 +1,5 @@
from Direction import Direction from .Direction import Direction
from Quaternion import Quaternion from .Quaternion import Quaternion
class SwingTwist: class SwingTwist:
def __init__(self, swing: Direction, twist: float): def __init__(self, swing: Direction, twist: float):

View File

@ -1,5 +1,5 @@
from Spherical import Spherical from .Spherical import Spherical
from Quaternion import Quaternion from .Quaternion import Quaternion
## A thing is the basic building block ## A thing is the basic building block
# #
@ -10,6 +10,7 @@ class Thing:
Switch = 0x01 Switch = 0x01
DistanceSensor = 0x02 DistanceSensor = 0x02
DirectionalSensor = 0x03 DirectionalSensor = 0x03
TemperatureSensor = 0x04
Animator = 0x40 Animator = 0x40
Position = 0x01 Position = 0x01
@ -17,9 +18,9 @@ class Thing:
LinearVelocity = 0x04 LinearVelocity = 0x04
AngularVelocity = 0x08 AngularVelocity = 0x08
def __init__(self, type=Type.Undetermined, parent=None, name=None): def __init__(self, network_id = 0, thing_id = 0, type=Type.Undetermined, parent=None, name=None):
self.networkId = 0 self.network_id = network_id
self.id = 0 self.id = thing_id
self.type = type self.type = type
if parent is None: if parent is None:
self.parent_id = 0 self.parent_id = 0
@ -42,6 +43,7 @@ class Thing:
pass pass
def ProcessBinary(self, data): def ProcessBinary(self, data):
print('default binary processor')
pass pass
allThings = set({ None }) allThings = set({ None })
@ -52,16 +54,16 @@ class Thing:
Thing.allThings.add(thing) Thing.allThings.add(thing)
@staticmethod @staticmethod
def Get(networkId, thingId): def Get(network_id, thing_id):
for thing in Thing.allThings: for thing in Thing.allThings:
if thing is not None: if thing is not None:
if thing.networkId == networkId and thing.id == thingId: if thing.network_id == network_id and thing.id == thing_id:
return thing return thing
return None return None
## Update all things ## Update all things
@staticmethod @staticmethod
def UpdateAll(currentTime): def UpdateAll(currentTime):
for thing in Thing.allThings: for thing in list(Thing.allThings):
if thing is not None: if thing is not None:
thing.update(currentTime) thing.update(currentTime)

View File

@ -1,4 +1,4 @@
from Messages import IMessage from .Messages import IMessage
class ThingMsg(IMessage): class ThingMsg(IMessage):
id = 0x80 id = 0x80