You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							260 lines
						
					
					
						
							9.4 KiB
						
					
					
				
			
		
		
	
	
							260 lines
						
					
					
						
							9.4 KiB
						
					
					
				| #include "tools/cabana/streams/abstractstream.h"
 | |
| 
 | |
| #include <QTimer>
 | |
| 
 | |
| AbstractStream *can = nullptr;
 | |
| 
 | |
| StreamNotifier *StreamNotifier::instance() {
 | |
|   static StreamNotifier notifier;
 | |
|   return ¬ifier;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| }
 | |
| 
 |