Ich habe in einem LOG-File eine Aufzeichnung von CAN-Nachrichten. Diese tragen einen relativen Zeitstempel. Ich möchte diese Botschaften nun in echtzeit wiedergeben. Zusätzlich möchte ich noch selbst generierte Botschaften in einem bestimmten Intervall senden. Das kleinste Intervall sind theoretisch 10 ms, am häufigsten aber 50 oder 100 ms. Mir fehlt noch der Ansatz wie ich das programmtechnisch umsetzen soll. Ich denke ich brauche irgendeine Art Scheduler. Mache ich sowas mit einem Timer der dann immer ausrechnen oder nachprüfen muss ob gerade etwas gesendet werden soll? Oder mache ich pro Nachricht einen Timer und durchs Log wühle ich mich von Zeitstempel zu Zeitstempel? Theoretisch sind alle Zeitpunkte durch 10 ms teilbar. Als Hardware nutze ich einen CAN-Adapter auf STM32-Basis mit einer 2MB VCP-USB-Verbindung. Aber ich habe auch schon überlegt ob es nicht sinnvoller ist, die Daten und ihre Intervalle an den STM32 zu übertragen und dieser übernimmt das Scheduling und senden, sodass ich von meiner Software aus nur steuere? Vielleicht habt Ihr einen Tipp wie man sowas am besten angeht :-) Danke für die Unterstützung.
Olli Z. schrieb: > Mir fehlt noch der Ansatz wie ich das programmtechnisch umsetzen soll. > Ich denke ich brauche irgendeine Art Scheduler. > Mache ich sowas mit einem Timer der dann immer ausrechnen oder > nachprüfen muss ob gerade etwas gesendet werden soll? > Oder mache ich pro Nachricht einen Timer und durchs Log wühle ich mich > von Zeitstempel zu Zeitstempel? Ich würde das als eine Art Scheduler machen, vermutlich multithreaded, einen Thread der die Daten Sendet, und einen zweiten Thread welcher die Daten Sortiert in eine art Verkettete liste einfügt, C++ Fanboys hier bitte das Passende C++ Pattern eintragen ;). (vielleicht std::vector ) In der Liste sind die Daten dann nach Fälligkeit Sortiert so das das erste Element immer das ist welches als nächstes gesendet werden muss damit kann der Sende Threat warten bis das fällig wird und ein zweiter Thread sortiert die "Nachrichten richtig ein.
Ich würde einen Timer laufen lassen. Nach jeweis 10ms schaust du in deinem sortierten Nachrichtenprotokoll anhand des Zeitstempels nach, ob etwas zum Senden da ist. Falls ja, ab damit auf den Bus. Olli Z. schrieb: > Als Hardware nutze ich einen CAN-Adapter auf STM32-Basis mit einer 2MB > VCP-USB-Verbindung. Aber ich habe auch schon überlegt ob es nicht > sinnvoller ist, die Daten und ihre Intervalle an den STM32 zu übertragen > und dieser übernimmt das Scheduling und senden, sodass ich von meiner > Software aus nur steuere? Kannst du denn auf die Software deines CAN-Adapters zugreifen und diese abändern? Falls ja, dort alles zeitkritische reinpacken und und die zu sendenden Nachrichten frühzeitig beim PC anfordern. imonbln schrieb: > vermutlich multithreaded, Halte ich für einen Overkill. Die Daten werden so schnell abgearbeitet sein, dass man das ruhig nacheinander ausführen kann. imonbln schrieb: > C++ Fanboys hier bitte das Passende C++ Pattern eintragen ;). std::queue, aber nur wenns nötig ist. Ist das gesamte Protokoll so oder so im Speicher, reicht auch ein Index auf das nächste Element.
Der PC kann das so nicht. Der Controller auf dem board muss das alles machen. Ist aber relativ trivial. Vorne beginnen, und jeweils die Differenz zum naechsten Zeitpunkt rechnen, solange warten und dann lossenden. Dasselbe wie das Log auf Jetzt beziehen.
B. S. schrieb: > Ich würde einen Timer laufen lassen. Nach jeweis 10ms schaust du in > deinem sortierten Nachrichtenprotokoll anhand des Zeitstempels nach, ob > etwas zum Senden da ist. Falls ja, ab damit auf den Bus. So würde ich es auch machen, sofern diese zeitliche Auflösung ausreicht. Sofern auf dem System Windows oder ein nicht echtzeiterweitertes Linux läuft, muss das nicht unbedingt zuverlässig laufen. Mit einem PREEMPT-Linux kiegt man aber auch 1ms locker hin. Vermutlich auch noch schneller, aber damit habe ich keine praktische Erfahrung. Aber wenn man schon einen STM32 zur Verfügung hat, könnte man auch da alle Echtzeit-Dinge erledigen. Das entspannt die Lage auf dem PC deutlich.
FreeRTOS bietet sich sicherlich als Task Scheduler dafür an
Ich würde vermutlich das Command-Pattern verwenden. Ein Command zum warten (sleep() o.ä.), ein Command zum Senden von Nachricht A, ein Command zum Senden von Nachricht B etc. Die Commands in der richtigen Reihenfolge in einen Container packen (Queue o.ä.) und dann stur nacheinander abarbeiten. merciless
Dirk K. schrieb: > Ich würde vermutlich das Command-Pattern verwenden. > Ein Command zum warten (sleep() o.ä.), ein Command > zum Senden von Nachricht A, ein Command zum Senden > von Nachricht B etc. Die Commands in der richtigen > Reihenfolge in einen Container packen (Queue o.ä.) > und dann stur nacheinander abarbeiten. > > merciless Ist es nicht ein Wahnsinn, dass für jede noch so einfache Funktionalität ein Pattern her muss??? while( queue not empty ) { 1. take one CAN frame from the queue 2. sleep for frame delta time 3. send the frame }
BobbyX schrieb: > Dirk K. schrieb: >> Ich würde vermutlich das Command-Pattern verwenden. >> Ein Command zum warten (sleep() o.ä.), ein Command >> zum Senden von Nachricht A, ein Command zum Senden >> von Nachricht B etc. Die Commands in der richtigen >> Reihenfolge in einen Container packen (Queue o.ä.) >> und dann stur nacheinander abarbeiten. >> >> merciless > > Ist es nicht ein Wahnsinn, dass für jede noch so einfache Funktionalität > ein Pattern her muss??? > > while( queue not empty ) > { > 1. take one CAN frame from the queue > 2. sleep for frame delta time > 3. send the frame > } Lol, und worin unterscheidet sich meine Lösung von deiner? Richtig, ich habe bessere Kapselung. merciless
> Lol, und worin unterscheidet sich meine Lösung von deiner? > Richtig, ich habe bessere Kapselung. > > merciless Welchen Sinn soll angesichts der Aufgabe ( eine Liste von CAN Frames abspielen ) die "Kapselung" haben? In diesem Zusammenhang klingen die Begriffe "Pattern" und "Kapselung" wie Buzzwords eines (Over)Enginners....
Naja wenn schon C++, dann doch richtig. Sonst hätte er ja nach einer Lsg in C fragen können/sollen/wollen
:
Bearbeitet durch User
Joe J. schrieb: > Naja wenn schon C++, dann doch richtig. Genau. Vielleicht noch ein Template hier und da? Wegen solchen Sch... habe ich schon Projekte scheitern sehen.....
BobbyX schrieb: >> Lol, und worin unterscheidet sich meine Lösung von deiner? >> Richtig, ich habe bessere Kapselung. >> >> merciless > > Welchen Sinn soll angesichts der Aufgabe ( eine Liste von CAN Frames > abspielen ) die "Kapselung" haben? In diesem Zusammenhang klingen die > Begriffe "Pattern" und "Kapselung" wie Buzzwords eines > (Over)Enginners.... Ich hatte geschrieben: "Ich würde vermutlich...". Das bedeutet nicht, dass es die perfekte Lösung für das Problem/den Entwickler ist. Ich gebe mal zu Bedenken: Du gehst davon aus, dass nur 2 Tasks gemacht werden müssen: Warten und einen Frame senden. Wenn morgen mehr Tasks dazukommen, bleibt mein Client-Code ein 3-Zeiler, du baust einen switch/case ein und nach 3 Jahren hat man hunderte Zeilen Code dastehen: Das habe ich nämlich schon gesehen/erlebt und würde deswegen immer an dieser Stelle ein Pattern vorziehen. merciless
Template ? da sollten mindestens 'Concepts' angewendet werden... :-)
Joe J. schrieb: > Naja wenn schon C++, dann doch richtig. Sonst hätte er ja nach einer Lsg > in C fragen können/sollen/wollen Ich verwende C++ jeden Tag und C++ ist für mich das "bessere" Werkzeug. Aber welchen Vorteil sollte den jetzt ein Command Pattern hier haben? Ein "Undo" wird offensichtlich nicht gebraucht, das wäre das "übliche" Argument für dieses Pattern. C++ "richtig" zu verwenden, bedeutet ja nicht, den gesunden Menschenverstand auszuschalten und sinnfrei irgend welche Pattern anzuwenden. Vielleicht noch aus der Queue ein Singleton machen (gibt ja nur eine Liste)? Die Reihenfolge könnte man schön über das Strategy-Pattern definieren (vielleicht will man später mal in umgekehrter Reihenfolge senden). Die `can_command` Objekte könnte man natürlich ganz direkt von einer factory erzeugen lassen. Wenn man aber später einmal andere Objekte erzeugen möchte, dann sollte man lieber gleich das abstract factory pattern einsetzen und das Erzeugen des Objekt-Erzeugers schön und "sauber" kapseln. Für den CAN-Adapter bietet sich eine einfache Factory an, die verschiedene `can_command_sink` Implementierung erzeugen kann. Den konkret eingesetzten Adapter kann man dann einfach über das Facade Pattern adapterieren. Über das Proxy pattern könnte man dann sogar CAN-ADapter auf der anderen Seite der Erde benutzen! Wenn man den verwendeneten Timer noch vernünfitg abstrahiert, kann man natürlich ... Mein lieblings-Pattern: KISS ;-)
Torsten R. schrieb: > // sinnloses Gebashe gelöscht Du musst kein Pattern verwenden. > Mein lieblings-Pattern: KISS ;-) KISS ist ein Best Practice im Rahmen von Clean Code, kein Pattern. merciless
Ihr habt leider einen ganz wesentlichen Punkt meiner Aufgabe übersehen! Ich möchte nicht nur stumpf ein Log wiedergeben, sondern auch eigene Messages senden, welche ich on-the-fly zu- und abschalten möchte. Das abspielen eines Log soll nur eine Funktionalität sein. Daher weiss ich nicht ob es wirklich eine gute Idee ist, dafür tausende von Messages erzeugen und dann abspielen zu müssen. Daher war meine Überlegung das erst garnicht zu vermischen, sondern zwei unabhängige Prozesse draus zu machen. Also soll sich der Player, welcher ein Log abspielt einfach von Nachricht zu Nachricht hangeln? Also, Nachricht lesen und einen Timer auf die Zeit stellen in der die MSG gesendet werden muss, zusammen mit den zu sendenden Daten. Wird der ausgelöst, gehen die Daten in die Sendequeue und es wird die nächste Nachricht geladen und wieder ein neuer Timer erzeugt. So würde das meiner Meinung nach am wenigsten Ressourcen verbrauchen. Der Generator, welcher eigene Nachrichten versendet könnte so ähnlich arbeiten, nur hat er als Führungsgröße keine Log-Queue sondern einfach eine als Repeat gekennzeichnete Nachricht die dann aber wieder genauso behandelt wird. War jetzt vielleicht zu kompliziert beschrieben?!
Olli Z. schrieb: > Ich möchte nicht nur stumpf ein Log wiedergeben, sondern auch eigene > Messages senden, welche ich on-the-fly zu- und abschalten möchte. Das > abspielen eines Log soll nur eine Funktionalität sein. Du hast eine Liste mit Einträgen mit relativen Zeiten und Du hast Ereignisse, die sich regelmäßig wiederholen. Für beide (oder auch n) Quellen kannst Du eine absolute Zeit ermitteln, auf den frühesten Zeitpunkt wartest Du (z.B. mit https://en.cppreference.com/w/cpp/thread/sleep_until). Dann versendest Du für alle Quellen, die zu diesem Zeitpunkt ein Telegramm versenden müssen das entsprechende Telegram. Hier mal eine Skitze, für ein mögliches Design:
1 | class can_adapter; |
2 | |
3 | class can_source |
4 | { |
5 | public: |
6 | virtual std::chrono::system_clock::time_point next_time() const = 0; |
7 | virtual void send(std::chrono::system_clock::time_point, can_adapter&) = 0; |
8 | |
9 | ~can_source() = default; |
10 | }; |
11 | |
12 | class log_source : public can_source |
13 | { |
14 | public: |
15 | log_source(const std::string& file_name, std::chrono::system_clock::time_point start_time); |
16 | |
17 | std::chrono::system_clock::time_point next_time() const override; |
18 | void send(std::chrono::system_clock::time_point, can_adapter&) override; |
19 | |
20 | private: |
21 | struct row { |
22 | std::chrono::microseconds delta; |
23 | std::vector< std::uint8_t > content; |
24 | }; |
25 | |
26 | static std::vector< row > read_rows(const std::string& file_name); |
27 | |
28 | const std::vector< row > rows_; |
29 | std::chrono::system_clock::time_point next_time_; |
30 | std::vector< row >::const_iterator next_; |
31 | }; |
32 | |
33 | class periodic_source : public can_source |
34 | { |
35 | public: |
36 | periodic_source(const std::vector< std::uint8_t >& content, std::chrono::microseconds period, std::chrono::system_clock::time_point start_time); |
37 | |
38 | std::chrono::system_clock::time_point next_time() const override; |
39 | void send(std::chrono::system_clock::time_point, can_adapter&) override; |
40 | |
41 | private: |
42 | std::chrono::system_clock::time_point next_time_; |
43 | const std::chrono::microseconds period_; |
44 | const std::vector< std::uint8_t > content_; |
45 | }; |
46 | |
47 | int main() |
48 | { |
49 | const auto now = std::chrono::system_clock::now(); |
50 | log_source files( "log.log", now ); |
51 | periodic_source periodic_100ms( { 0x01, 0x02 }, std::chrono::microseconds( 100 ), now ); |
52 | periodic_source periodic_150ms( { 0x01, 0x02 }, std::chrono::microseconds( 150 ), now ); |
53 | |
54 | std::vector< can_source* > sources = { &log_source, &periodic_100ms, &periodic_150ms }; |
55 | can_adapter can; |
56 | |
57 | for ( ; ; ) |
58 | { |
59 | const auto next = std::min_element( sources.begin(), sources.end(), |
60 | []( can_source* s ) -> std::chrono::system_clock::time_point { |
61 | s->next_time(); |
62 | } |
63 | ); |
64 | |
65 | std::sleep_until( next ); |
66 | |
67 | std::for_each( sources.begin(), sources.end(), |
68 | [ next ](const can_source* s){ |
69 | s->send( next, can ); |
70 | } |
71 | ); |
72 | } |
73 | } |
:
Bearbeitet durch User
Wow, so weit bin ich in C++ noch nicht, hab erst vor ein paar Tagen damit begonnen... aber ich verstehe den Code einigermaßen. Das ist wie mit natürlichen Sprachen, man versteht schnell aber selbst sprechen ist eine ganz andere Nummer ;-)
:
Bearbeitet durch User
Olli Z. schrieb: > Also soll sich der Player, welcher ein Log abspielt einfach von > Nachricht zu Nachricht hangeln? Also, Nachricht lesen und einen Timer > auf die Zeit stellen in der die MSG gesendet werden muss, zusammen mit > den zu sendenden Daten. Wird der ausgelöst, gehen die Daten in die > Sendequeue und es wird die nächste Nachricht geladen und wieder ein > neuer Timer erzeugt. So würde das meiner Meinung nach am wenigsten > Ressourcen verbrauchen. Es wurden hier ja schon zwei Varianten genennt, also neben dieser hier noch, dass dein Player zyklisch aufgerufen wird und schaut, ob der Sendezeitpunkt der naechsten Nachricht schon erreicht ist. Diese Variante wuerde ich vorziehen.
Ja, och glaube auch das ich eher den einfach Ansatz wählen muss, nachdem ich mich mit Multithreading etwas näher auseinandergesetzt hab. Ich würde einen Thread bauen der sowohl das abzuspielende Log, als auch die zyklisch zu sendenden Nachrichten. Die Main im Thread muss dann immer die Zeit bis zum nächsten Ereignis berechnen und so lange warten bis wieder etwas zu senden ist und dann die entsprechende Nachricht direkt auf den Port schreiben. Dann muss ich nichts vorberechnen und kann zur Laufzeit auch Signale hinzunehmen oder stornieren.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.