Forum: PC-Programmierung Wie aufgezeichnete Signale in "Echtzeit" senden (C++)


von Olli Z. (z80freak)


Lesenswert?

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.

von imonbln (Gast)


Lesenswert?

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.

von B. S. (bestucki)


Lesenswert?

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.

von Pandur S. (jetztnicht)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Torben (Gast)


Lesenswert?

FreeRTOS bietet sich sicherlich als Task Scheduler dafür an

von Dirk K. (merciless)


Lesenswert?

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

von BobbyX (Gast)


Lesenswert?

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
}

von Dirk K. (merciless)


Lesenswert?

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

von BobbyX (Gast)


Lesenswert?

> 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....

von Joe J. (j_955)


Lesenswert?

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
von BobbyX (Gast)


Lesenswert?

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.....

von Dirk K. (merciless)


Lesenswert?

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

von Thomas W. (goaty)


Lesenswert?

Template ? da sollten mindestens 'Concepts' angewendet werden... :-)

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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 ;-)

von Dirk K. (merciless)


Lesenswert?

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

von Olli Z. (z80freak)


Lesenswert?

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?!

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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
von Olli Z. (z80freak)


Lesenswert?

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
von Maxe (Gast)


Lesenswert?

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.

von Olli Z. (z80freak)


Lesenswert?

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
Noch kein Account? Hier anmelden.