# must be built with scons
from . messaging_pyx import Context , Poller , SubSocket , PubSocket , SocketEventHandle , toggle_fake_events , \
set_fake_prefix , get_fake_prefix , delete_fake_prefix , wait_for_one_event
from . messaging_pyx import MultiplePublishersError , MessagingError
import os
import capnp
import time
from typing import Optional , List , Union , Dict , Deque
from collections import deque
from cereal import log
from cereal . services import SERVICE_LIST
assert MultiplePublishersError
assert MessagingError
assert toggle_fake_events
assert set_fake_prefix
assert get_fake_prefix
assert delete_fake_prefix
assert wait_for_one_event
NO_TRAVERSAL_LIMIT = 2 * * 64 - 1
AVG_FREQ_HISTORY = 100
context = Context ( )
def fake_event_handle ( endpoint : str , identifier : Optional [ str ] = None , override : bool = True , enable : bool = False ) - > SocketEventHandle :
identifier = identifier or get_fake_prefix ( )
handle = SocketEventHandle ( endpoint , identifier , override )
if override :
handle . enabled = enable
return handle
def log_from_bytes ( dat : bytes ) - > capnp . lib . capnp . _DynamicStructReader :
with log . Event . from_bytes ( dat , traversal_limit_in_words = NO_TRAVERSAL_LIMIT ) as msg :
return msg
def new_message ( service : Optional [ str ] = None , size : Optional [ int ] = None ) - > capnp . lib . capnp . _DynamicStructBuilder :
dat = log . Event . new_message ( )
dat . logMonoTime = int ( time . monotonic ( ) * 1e9 )
dat . valid = True
if service is not None :
if size is None :
dat . init ( service )
else :
dat . init ( service , size )
return dat
def pub_sock ( endpoint : str ) - > PubSocket :
sock = PubSocket ( )
sock . connect ( context , endpoint )
return sock
def sub_sock ( endpoint : str , poller : Optional [ Poller ] = None , addr : str = " 127.0.0.1 " ,
conflate : bool = False , timeout : Optional [ int ] = None ) - > SubSocket :
sock = SubSocket ( )
sock . connect ( context , endpoint , addr . encode ( ' utf8 ' ) , conflate )
if timeout is not None :
sock . setTimeout ( timeout )
if poller is not None :
poller . registerSocket ( sock )
return sock
def drain_sock_raw ( sock : SubSocket , wait_for_one : bool = False ) - > List [ bytes ] :
""" Receive all message currently available on the queue """
ret : List [ bytes ] = [ ]
while 1 :
if wait_for_one and len ( ret ) == 0 :
dat = sock . receive ( )
else :
dat = sock . receive ( non_blocking = True )
if dat is None :
break
ret . append ( dat )
return ret
def drain_sock ( sock : SubSocket , wait_for_one : bool = False ) - > List [ capnp . lib . capnp . _DynamicStructReader ] :
""" Receive all message currently available on the queue """
ret : List [ capnp . lib . capnp . _DynamicStructReader ] = [ ]
while 1 :
if wait_for_one and len ( ret ) == 0 :
dat = sock . receive ( )
else :
dat = sock . receive ( non_blocking = True )
if dat is None : # Timeout hit
break
dat = log_from_bytes ( dat )
ret . append ( dat )
return ret
# TODO: print when we drop packets?
def recv_sock ( sock : SubSocket , wait : bool = False ) - > Optional [ capnp . lib . capnp . _DynamicStructReader ] :
""" Same as drain sock, but only returns latest message. Consider using conflate instead. """
dat = None
while 1 :
if wait and dat is None :
rcv = sock . receive ( )
else :
rcv = sock . receive ( non_blocking = True )
if rcv is None : # Timeout hit
break
dat = rcv
if dat is not None :
dat = log_from_bytes ( dat )
return dat
def recv_one ( sock : SubSocket ) - > Optional [ capnp . lib . capnp . _DynamicStructReader ] :
dat = sock . receive ( )
if dat is not None :
dat = log_from_bytes ( dat )
return dat
def recv_one_or_none ( sock : SubSocket ) - > Optional [ capnp . lib . capnp . _DynamicStructReader ] :
dat = sock . receive ( non_blocking = True )
if dat is not None :
dat = log_from_bytes ( dat )
return dat
def recv_one_retry ( sock : SubSocket ) - > capnp . lib . capnp . _DynamicStructReader :
""" Keep receiving until we get a message """
while True :
dat = sock . receive ( )
if dat is not None :
return log_from_bytes ( dat )
class SubMaster :
def __init__ ( self , services : List [ str ] , poll : Optional [ List [ str ] ] = None ,
ignore_alive : Optional [ List [ str ] ] = None , ignore_avg_freq : Optional [ List [ str ] ] = None ,
addr : str = " 127.0.0.1 " ) :
self . frame = - 1
self . updated = { s : False for s in services }
self . rcv_time = { s : 0. for s in services }
self . rcv_frame = { s : 0 for s in services }
self . alive = { s : False for s in services }
self . freq_ok = { s : False for s in services }
self . recv_dts : Dict [ str , Deque [ float ] ] = { s : deque ( maxlen = AVG_FREQ_HISTORY ) for s in services }
self . sock = { }
self . freq = { }
self . data = { }
self . valid = { }
self . logMonoTime = { }
self . poller = Poller ( )
self . non_polled_services = [ s for s in services if poll is not None and
len ( poll ) and s not in poll ]
self . ignore_average_freq = [ ] if ignore_avg_freq is None else ignore_avg_freq
self . ignore_alive = [ ] if ignore_alive is None else ignore_alive
self . simulation = bool ( int ( os . getenv ( " SIMULATION " , " 0 " ) ) )
for s in services :
if addr is not None :
p = self . poller if s not in self . non_polled_services else None
self . sock [ s ] = sub_sock ( s , poller = p , addr = addr , conflate = True )
self . freq [ s ] = SERVICE_LIST [ s ] . frequency
try :
data = new_message ( s )
except capnp . lib . capnp . KjException : # pylint: disable=c-extension-no-member
data = new_message ( s , 0 ) # lists
self . data [ s ] = getattr ( data , s )
self . logMonoTime [ s ] = 0
self . valid [ s ] = data . valid
def __getitem__ ( self , s : str ) - > capnp . lib . capnp . _DynamicStructReader :
return self . data [ s ]
def _check_avg_freq ( self , s ) :
return self . rcv_time [ s ] > 1e-5 and self . freq [ s ] > 1e-5 and ( s not in self . non_polled_services ) \
and ( s not in self . ignore_average_freq )
def update ( self , timeout : int = 1000 ) - > None :
msgs = [ ]
for sock in self . poller . poll ( timeout ) :
msgs . append ( recv_one_or_none ( sock ) )
# non-blocking receive for non-polled sockets
for s in self . non_polled_services :
msgs . append ( recv_one_or_none ( self . sock [ s ] ) )
self . update_msgs ( time . monotonic ( ) , msgs )
def update_msgs ( self , cur_time : float , msgs : List [ capnp . lib . capnp . _DynamicStructReader ] ) - > None :
self . frame + = 1
self . updated = dict . fromkeys ( self . updated , False )
for msg in msgs :
if msg is None :
continue
s = msg . which ( )
self . updated [ s ] = True
if self . _check_avg_freq ( s ) :
self . recv_dts [ s ] . append ( cur_time - self . rcv_time [ s ] )
self . rcv_time [ s ] = cur_time
self . rcv_frame [ s ] = self . frame
self . data [ s ] = getattr ( msg , s )
self . logMonoTime [ s ] = msg . logMonoTime
self . valid [ s ] = msg . valid
if self . simulation :
self . freq_ok [ s ] = True
self . alive [ s ] = True
if not self . simulation :
for s in self . data :
# arbitrary small number to avoid float comparison. If freq is 0, we can skip the check
if self . freq [ s ] > 1e-5 :
# alive if delay is within 10x the expected frequency
self . alive [ s ] = ( cur_time - self . rcv_time [ s ] ) < ( 10. / self . freq [ s ] )
# TODO: check if update frequency is high enough to not drop messages
# freq_ok if average frequency is higher than 90% of expected frequency
if self . _check_avg_freq ( s ) :
if len ( self . recv_dts [ s ] ) > 0 :
avg_dt = sum ( self . recv_dts [ s ] ) / len ( self . recv_dts [ s ] )
expected_dt = 1 / ( self . freq [ s ] * 0.90 )
self . freq_ok [ s ] = ( avg_dt < expected_dt )
else :
self . freq_ok [ s ] = False
else :
self . freq_ok [ s ] = True
else :
self . freq_ok [ s ] = True
self . alive [ s ] = True
def all_alive ( self , service_list = None ) - > bool :
if service_list is None : # check all
service_list = self . alive . keys ( )
return all ( self . alive [ s ] for s in service_list if s not in self . ignore_alive )
def all_freq_ok ( self , service_list = None ) - > bool :
if service_list is None : # check all
service_list = self . alive . keys ( )
return all ( self . freq_ok [ s ] for s in service_list if s not in self . ignore_alive )
def all_valid ( self , service_list = None ) - > bool :
if service_list is None : # check all
service_list = self . valid . keys ( )
return all ( self . valid [ s ] for s in service_list )
def all_checks ( self , service_list = None ) - > bool :
if service_list is None : # check all
service_list = self . alive . keys ( )
return self . all_alive ( service_list = service_list ) \
and self . all_freq_ok ( service_list = service_list ) \
and self . all_valid ( service_list = service_list )
class PubMaster :
def __init__ ( self , services : List [ str ] ) :
self . sock = { }
for s in services :
self . sock [ s ] = pub_sock ( s )
def send ( self , s : str , dat : Union [ bytes , capnp . lib . capnp . _DynamicStructBuilder ] ) - > None :
if not isinstance ( dat , bytes ) :
dat = dat . to_bytes ( )
self . sock [ s ] . send ( dat )
def wait_for_readers_to_update ( self , s : str , timeout : int , dt : float = 0.05 ) - > bool :
for _ in range ( int ( timeout * ( 1. / dt ) ) ) :
if self . sock [ s ] . all_readers_updated ( ) :
return True
time . sleep ( dt )
return False
def all_readers_updated ( self , s : str ) - > bool :
return self . sock [ s ] . all_readers_updated ( ) # type: ignore