# include "tools/cabana/streams/abstractstream.h"
# include <QTimer>
AbstractStream * can = nullptr ;
StreamNotifier * StreamNotifier : : instance ( ) {
static StreamNotifier notifier ;
return & notifier ;
}
AbstractStream : : AbstractStream ( QObject * parent ) : new_msgs ( new QHash < MessageId , CanData > ( ) ) , QObject ( parent ) {
assert ( parent ! = nullptr ) ;
QObject : : connect ( this , & AbstractStream : : seekedTo , this , & AbstractStream : : updateLastMsgsTo ) ;
QObject : : connect ( & settings , & Settings : : changed , this , & AbstractStream : : updateMasks ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : DBCFileChanged , this , & AbstractStream : : updateMasks ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : maskUpdated , this , & AbstractStream : : updateMasks ) ;
QObject : : connect ( this , & AbstractStream : : streamStarted , [ this ] ( ) {
emit StreamNotifier : : instance ( ) - > changingStream ( ) ;
delete can ;
can = this ;
emit StreamNotifier : : instance ( ) - > streamStarted ( ) ;
} ) ;
}
void AbstractStream : : updateMasks ( ) {
std : : lock_guard lk ( mutex ) ;
masks . clear ( ) ;
if ( settings . suppress_defined_signals ) {
for ( auto s : sources ) {
if ( auto f = dbc ( ) - > findDBCFile ( s ) ) {
for ( const auto & [ address , m ] : f - > getMessages ( ) ) {
masks [ { . source = ( uint8_t ) s , . address = address } ] = m . mask ;
}
}
}
}
}
void AbstractStream : : updateMessages ( QHash < MessageId , CanData > * messages ) {
auto prev_src_size = sources . size ( ) ;
auto prev_msg_size = last_msgs . size ( ) ;
for ( auto it = messages - > begin ( ) ; it ! = messages - > end ( ) ; + + it ) {
const auto & id = it . key ( ) ;
last_msgs [ id ] = it . value ( ) ;
sources . insert ( id . source ) ;
}
if ( sources . size ( ) ! = prev_src_size ) {
updateMasks ( ) ;
emit sourcesUpdated ( sources ) ;
}
emit updated ( ) ;
emit msgsReceived ( messages , prev_msg_size ! = last_msgs . size ( ) ) ;
delete messages ;
processing = false ;
}
void AbstractStream : : updateEvent ( const MessageId & id , double sec , const uint8_t * data , uint8_t size ) {
std : : lock_guard lk ( mutex ) ;
auto mask_it = masks . find ( id ) ;
std : : vector < uint8_t > * mask = mask_it = = masks . end ( ) ? nullptr : & mask_it - > second ;
all_msgs [ id ] . compute ( ( const char * ) data , size , sec , getSpeed ( ) , mask ) ;
if ( ! new_msgs - > contains ( id ) ) {
new_msgs - > insert ( id , { } ) ;
}
}
bool AbstractStream : : postEvents ( ) {
// delay posting CAN message if UI thread is busy
if ( processing = = false ) {
processing = true ;
for ( auto it = new_msgs - > begin ( ) ; it ! = new_msgs - > end ( ) ; + + it ) {
it . value ( ) = all_msgs [ it . key ( ) ] ;
}
// use pointer to avoid data copy in queued connection.
QMetaObject : : invokeMethod ( this , std : : bind ( & AbstractStream : : updateMessages , this , new_msgs . release ( ) ) , Qt : : QueuedConnection ) ;
new_msgs . reset ( new QHash < MessageId , CanData > ) ;
new_msgs - > reserve ( 100 ) ;
return true ;
}
return false ;
}
const std : : vector < const CanEvent * > & AbstractStream : : events ( const MessageId & id ) const {
static std : : vector < const CanEvent * > empty_events ;
auto it = events_ . find ( id ) ;
return it ! = events_ . end ( ) ? it - > second : empty_events ;
}
const CanData & AbstractStream : : lastMessage ( const MessageId & id ) {
static CanData empty_data = { } ;
auto it = last_msgs . find ( id ) ;
return it ! = last_msgs . end ( ) ? it . value ( ) : empty_data ;
}
// it is thread safe to update data in updateLastMsgsTo.
// updateLastMsgsTo is always called in UI thread.
void AbstractStream : : updateLastMsgsTo ( double sec ) {
new_msgs . reset ( new QHash < MessageId , CanData > ) ;
all_msgs . clear ( ) ;
last_msgs . clear ( ) ;
uint64_t last_ts = ( sec + routeStartTime ( ) ) * 1e9 ;
for ( auto & [ id , ev ] : events_ ) {
auto it = std : : lower_bound ( ev . crbegin ( ) , ev . crend ( ) , last_ts , [ ] ( auto e , uint64_t ts ) {
return e - > mono_time > ts ;
} ) ;
auto mask_it = masks . find ( id ) ;
std : : vector < uint8_t > * mask = mask_it = = masks . end ( ) ? nullptr : & mask_it - > second ;
if ( it ! = ev . crend ( ) ) {
double ts = ( * it ) - > mono_time / 1e9 - routeStartTime ( ) ;
auto & m = all_msgs [ id ] ;
m . compute ( ( const char * ) ( * it ) - > dat , ( * it ) - > size , ts , getSpeed ( ) , mask ) ;
m . count = std : : distance ( it , ev . crend ( ) ) ;
m . freq = m . count / std : : max ( 1.0 , ts ) ;
}
}
// deep copy all_msgs to last_msgs to avoid multi-threading issue.
last_msgs = all_msgs ;
last_msgs . detach ( ) ;
// use a timer to prevent recursive calls
QTimer : : singleShot ( 0 , [ this ] ( ) {
emit updated ( ) ;
emit msgsReceived ( & last_msgs , true ) ;
} ) ;
}
void AbstractStream : : mergeEvents ( std : : vector < Event * > : : const_iterator first , std : : vector < Event * > : : const_iterator last ) {
size_t memory_size = 0 ;
size_t events_cnt = 0 ;
for ( auto it = first ; it ! = last ; + + it ) {
if ( ( * it ) - > which = = cereal : : Event : : Which : : CAN ) {
for ( const auto & c : ( * it ) - > event . getCan ( ) ) {
memory_size + = sizeof ( CanEvent ) + sizeof ( uint8_t ) * c . getDat ( ) . size ( ) ;
+ + events_cnt ;
}
}
}
if ( memory_size = = 0 ) return ;
char * ptr = memory_blocks . emplace_back ( new char [ memory_size ] ) . get ( ) ;
std : : unordered_map < MessageId , std : : deque < const CanEvent * > > new_events_map ;
std : : vector < const CanEvent * > new_events ;
new_events . reserve ( events_cnt ) ;
for ( auto it = first ; it ! = last ; + + it ) {
if ( ( * it ) - > which = = cereal : : Event : : Which : : CAN ) {
uint64_t ts = ( * it ) - > mono_time ;
for ( const auto & c : ( * it ) - > event . getCan ( ) ) {
CanEvent * e = ( CanEvent * ) ptr ;
e - > src = c . getSrc ( ) ;
e - > address = c . getAddress ( ) ;
e - > mono_time = ts ;
auto dat = c . getDat ( ) ;
e - > size = dat . size ( ) ;
memcpy ( e - > dat , ( uint8_t * ) dat . begin ( ) , e - > size ) ;
new_events_map [ { . source = e - > src , . address = e - > address } ] . push_back ( e ) ;
new_events . push_back ( e ) ;
ptr + = sizeof ( CanEvent ) + sizeof ( uint8_t ) * e - > size ;
}
}
}
bool append = new_events . front ( ) - > mono_time > lastest_event_ts ;
for ( auto & [ id , new_e ] : new_events_map ) {
auto & e = events_ [ id ] ;
auto pos = append ? e . end ( ) : std : : upper_bound ( e . cbegin ( ) , e . cend ( ) , new_e . front ( ) , [ ] ( const CanEvent * l , const CanEvent * r ) {
return l - > mono_time < r - > mono_time ;
} ) ;
e . insert ( pos , new_e . cbegin ( ) , new_e . cend ( ) ) ;
}
auto pos = append ? all_events_ . end ( ) : std : : upper_bound ( all_events_ . begin ( ) , all_events_ . end ( ) , new_events . front ( ) , [ ] ( auto l , auto r ) {
return l - > mono_time < r - > mono_time ;
} ) ;
all_events_ . insert ( pos , new_events . cbegin ( ) , new_events . cend ( ) ) ;
lastest_event_ts = all_events_ . back ( ) - > mono_time ;
emit eventsMerged ( ) ;
}
// CanData
constexpr int periodic_threshold = 10 ;
constexpr int start_alpha = 128 ;
constexpr float fade_time = 2.0 ;
const QColor CYAN = QColor ( 0 , 187 , 255 , start_alpha ) ;
const QColor RED = QColor ( 255 , 0 , 0 , start_alpha ) ;
const QColor GREYISH_BLUE = QColor ( 102 , 86 , 169 , start_alpha / 2 ) ;
const QColor CYAN_LIGHTER = QColor ( 0 , 187 , 255 , start_alpha ) . lighter ( 135 ) ;
const QColor RED_LIGHTER = QColor ( 255 , 0 , 0 , start_alpha ) . lighter ( 135 ) ;
const QColor GREYISH_BLUE_LIGHTER = QColor ( 102 , 86 , 169 , start_alpha / 2 ) . lighter ( 135 ) ;
static inline QColor blend ( const QColor & a , const QColor & b ) {
return QColor ( ( a . red ( ) + b . red ( ) ) / 2 , ( a . green ( ) + b . green ( ) ) / 2 , ( a . blue ( ) + b . blue ( ) ) / 2 , ( a . alpha ( ) + b . alpha ( ) ) / 2 ) ;
}
void CanData : : compute ( const char * can_data , const int size , double current_sec , double playback_speed , const std : : vector < uint8_t > * mask , uint32_t in_freq ) {
ts = current_sec ;
+ + count ;
const double sec_to_first_event = current_sec - ( can - > allEvents ( ) . front ( ) - > mono_time / 1e9 - can - > routeStartTime ( ) ) ;
freq = in_freq = = 0 ? count / std : : max ( 1.0 , sec_to_first_event ) : in_freq ;
if ( dat . size ( ) ! = size ) {
dat . resize ( size ) ;
bit_change_counts . resize ( size ) ;
colors = QVector ( size , QColor ( 0 , 0 , 0 , 0 ) ) ;
last_change_t . assign ( size , ts ) ;
last_delta . resize ( size ) ;
same_delta_counter . resize ( size ) ;
} else {
bool lighter = settings . theme = = DARK_THEME ;
const QColor & cyan = ! lighter ? CYAN : CYAN_LIGHTER ;
const QColor & red = ! lighter ? RED : RED_LIGHTER ;
const QColor & greyish_blue = ! lighter ? GREYISH_BLUE : GREYISH_BLUE_LIGHTER ;
for ( int i = 0 ; i < size ; + + i ) {
const uint8_t mask_byte = ( mask & & i < mask - > size ( ) ) ? ( ~ ( ( * mask ) [ i ] ) ) : 0xff ;
const uint8_t last = dat [ i ] & mask_byte ;
const uint8_t cur = can_data [ i ] & mask_byte ;
const int delta = cur - last ;
if ( last ! = cur ) {
double delta_t = ts - last_change_t [ i ] ;
// Keep track if signal is changing randomly, or mostly moving in the same direction
if ( std : : signbit ( delta ) = = std : : signbit ( last_delta [ i ] ) ) {
same_delta_counter [ i ] = std : : min ( 16 , same_delta_counter [ i ] + 1 ) ;
} else {
same_delta_counter [ i ] = std : : max ( 0 , same_delta_counter [ i ] - 4 ) ;
}
// Mostly moves in the same direction, color based on delta up/down
if ( delta_t * freq > periodic_threshold | | same_delta_counter [ i ] > 8 ) {
// Last change was while ago, choose color based on delta up or down
colors [ i ] = ( cur > last ) ? cyan : red ;
} else {
// Periodic changes
colors [ i ] = blend ( colors [ i ] , greyish_blue ) ;
}
// Track bit level changes
const uint8_t tmp = ( cur ^ last ) ;
for ( int bit = 0 ; bit < 8 ; bit + + ) {
if ( tmp & ( 1 < < bit ) ) {
bit_change_counts [ i ] [ bit ] + = 1 ;
}
}
last_change_t [ i ] = ts ;
last_delta [ i ] = delta ;
} else {
// Fade out
float alpha_delta = 1.0 / ( freq + 1 ) / ( fade_time * playback_speed ) ;
colors [ i ] . setAlphaF ( std : : max ( 0.0 , colors [ i ] . alphaF ( ) - alpha_delta ) ) ;
}
}
}
memcpy ( dat . data ( ) , can_data , size ) ;
}