# this file deals with the setup and interaction with the receiver thread
# interaction is done through a pipeline and a stack
# the stack is used to store the bytes of messages sent to this robot
# the pipline is used to give other instructions to the thread, things like settings updates
# both the stack and pipeline are one way, the stack is used only for sending back data to the main thread, the pipeline is used only for sending instructions to the reciever thread
#
from .sender import sendData
import yodel.globaldat as globaldat
from .classes import *
import yodel.standardformats as standardformats
from .dynamicheaders import *
from yodel.config import *
from typing import *
import sys
import multiprocessing as mp
incoming = mp.Queue()
globaldat.receiver_pipe, receiver_pipe_output = mp.Pipe()
# check to see if a given message is intended for this computer, either to
# receive or to relay
def is_recipient(
data: bytearray, radiotap_header_length: int) -> Union[bool, Tuple[bool, bool]]:
"""
check to see if a given message is intended for this computer and if the message should be relayed
Args:
data: raw bytes for incoming message
radiotap_header_length: length of radiotap header, the header is skipped over because it does not hold yodel data.
"""
# get data frame payload section
frame = data[radiotap_header_length + 26 + 5:]
pos = 0 # pos is used as a pointer to the current section of the header being decoded
"""
message id, the semi unique identifer to each message to avoid receiving
them twice
"""
message_ID = frame[pos:pos + 4]
if message_ID == globaldat.lastMid: # since messages are repeated a lot it is worth saving the previous message id so that the array does not need to be fully indexed
return (False)
if message_ID not in globaldat.lastMessages: # check if message has already been received
globaldat.lastMid = message_ID # set last mid to the current mid
globaldat.lastMessages.append(message_ID)
if len(globaldat.lastMessages) > 64:
del globaldat.lastMessages[0]
pos += 4
namelen = globaldat.getInt(frame[pos:pos + 1]) # get length of name
pos += 1
name = frame[pos:pos + namelen].decode("utf-8")
nameM = (name == globaldat.robotName or namelen == 0)
pos += namelen
gnamelen = globaldat.getInt(frame[pos:pos + 1])
pos += 1
group = frame[pos:pos + gnamelen].decode("utf-8")
pos += gnamelen
groupM = (group in globaldat.groups or gnamelen == 0)
relay = (globaldat.relay == True and not (name == globaldat.robotName))
return(nameM and groupM, relay)
return (False)
def relayFrame(frame: bytearray) -> NoReturn:
"""
used to allow receiver thread to relay messages back out
Args:
frame: bytes of message being relayed
"""
header = b"\x72\x6f\x62\x6f\x74" # yodel identifier
if globaldat.relay == True: # check if relay is enabled
# format and pass data to sender thread
sendData(header + frame, globaldat.maxRelay)
def listenrecv(pipe: mp.Pipe) -> bytearray:
"""
listen for yodel messages, check if the are meant for this computer and if so receive them
Args:
pipe: same pipe as in receiver, used to check for settings updates
"""
try:
data = globaldat.yodelSocket.recv(globaldat.ETH_FRAME_LEN)
except BaseException:
return (None)
radiolen = globaldat.getInt(data[2:3])
fcs = data[17] # fcs status flag is stored at byte 17
if fcs == 2: # if fcs flag is set then remove the fcs which is the last 4 bytes
data = data[0:-4]
payload = data[radiolen + 26:]
pos = 0
starth = (payload[pos:pos + 5])
# radio tap headers are stripped on external frames (non loopback)
if starth == b"\x72\x6f\x62\x6f\x74":
# additionally check for settings change if any yodel data is captured
# to see if anything important has changed
settings_check(pipe)
rdata = is_recipient(data, radiolen)
if rdata:
isr, dorelay = rdata
if dorelay:
relayFrame(payload[5:])
if isr:
return(payload[5:])
return(None)
def settings_check(pipe) -> NoReturn:
""" reads from pipe to detect settings updates
Args:
pipe: same settings pipe as used in receiver
"""
if pipe.poll(0): # check for new data in pipe
settings = pipe.recv() # get data
#print(settings,"setting update")
setting_update(settings[0], settings[1]) # change settings accordingly
[docs]def listen() -> Section:
"""
used to listen for data being sent to your robot, is non-blocking
Returns:
yodel.Section: contains any received message which is accessible through the .payload attribute, also contain meta data about the message
"""
global incoming
try: # make specific error
frame = incoming.get(False)
data = frame.frame # take bytes from message
# take bytes and extract header info
data = decode(data, standardformats.standard_header_format)
return(data)
except BaseException:
return(None)
def receiver(incoming: mp.Queue, pipe: mp.Pipe) -> NoReturn:
"""
this is the main function in the reciever thread. this checks for new messages and stores them into the stack so that main thread can access them
this also checks for settings updates and acts on them
Args:
incoming: queue that main thread reads from to receive new messages
pipe: settings updates
"""
globaldat.yodelSocket.settimeout(.1)
while True:
settings_check(pipe) # check for settings updates
dat = listenrecv(pipe)
if dat is not None:
formatted = FrameRecv(dat)
incoming.put(formatted)
if incoming.full():
incoming.get()