ubuntuusers.de

C-Programmierung / frage zu "static volatile int"

Status: Gelöst | Ubuntu-Version: Ubuntu 16.04 (Xenial Xerus)
Antworten |

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4686

Wohnort: Berlin

@sveni-lee: Nee das funktioniert immer noch nicht. Wenn, dann nur zufällig und wahrscheinlich auch nur einmal. Es hat sich seit dem ich angefangen habe diese Antwort zu schreiben zwar verbessert, aber nicht genug. Erklär mal wie Du auf die 50 kommst.

Wenn es so funktionieren würde, dann ist Dir hoffentlich klar das in der Datei eine andere (kleinere) Zahl landen könnte als an den Server gesendet wird‽ Ich vermute mal das soll nicht so sein…

In der Dokumentation zu curl_easy_init() sehe ich Sachen, die Du nicht berücksichtigt hast. Der letzte Satz im ersten Absatz der Beschreibung ist wichtig. Und auf Fehler testen ebenfalls.

Ist jetzt nicht unbedingt ein Fehler, aber Netzwerkverbindungen können auch mal länger dauern oder hängen bleiben. Ist das okay das Dein Programm dann einfach so lange nichts in die Datei schreibt?

Weisst Du ob die Plattform auf der das läuft garantiert, dass ein erhöhen einer int-Variable atomar ist? Ich glaube ich würde selbst wenn ich das wüsste, die entsprechenden kritischen Abschnitte schützen, oder das sehr deutlich in den Code schreiben, dass der nur dann funktioniert wenn das garantiert ist. Und zwar an allen Stellen die davon betroffen sind. wiringpi bietet für kritische Abschnitte auch Funktionen.

Man mus bei C ja nicht mehr alle Variablen am Anfang eines Blocks deklarieren, aber man sollte trotzdem aufpassen, dass man Deklarationen und Code nicht so vermischt, dass es unübersichtlich wird. Also nicht error deklarieren und einen Wert zuweisen, dann zwei davon völlig unabhängige Deklarationen dazwischen schieben, und dann error auswerten.

Ich würde das versenden des Wertes ins Netz ja in eine eigene Funktion schreiben. Dann kann man das auch unabhängig vom ganzen Rest entwickeln und testen.

Ich kenne die curl_easy*-API nicht, aber ich vermute mal Du musst eine Rückruffunktion zum schreiben von Daten registrieren die die Daten dann einfach ignoriert, falls die sonst auf der Standardausgabe landen. Man kann das gleiche CURL*-Handle laut Dokumentation übrigens für mehrere aufeinanderfolgende curl_easy_perform()-Aufrufe verwenden. Man muss da nicht immer neue erstellen.

sveni-lee

(Themenstarter)

Anmeldungsdatum:
28. Mai 2013

Beiträge: 258

Marc_BlackJack_Rintsch schrieb:

@sveni-lee: Nee das funktioniert immer noch nicht. Wenn, dann nur zufällig und wahrscheinlich auch nur einmal. Es hat sich seit dem ich angefangen habe diese Antwort zu schreiben zwar verbessert, aber nicht genug. Erklär mal wie Du auf die 50 kommst.

ich hatte gelesen, dass damit angegeben wird wieviele Zeichen das Array speichern kann

Wenn es so funktionieren würde, dann ist Dir hoffentlich klar das in der Datei eine andere (kleinere) Zahl landen könnte als an den Server gesendet wird‽ Ich vermute mal das soll nicht so sein…

nein ist es nicht

In der Dokumentation zu curl_easy_init() sehe ich Sachen, die Du nicht berücksichtigt hast. Der letzte Satz im ersten Absatz der Beschreibung ist wichtig. Und auf Fehler testen ebenfalls.

okay, das eine habe ich verstanden, der cleanup ist zwingend erforderlich.

Ist jetzt nicht unbedingt ein Fehler, aber Netzwerkverbindungen können auch mal länger dauern oder hängen bleiben. Ist das okay das Dein Programm dann einfach so lange nichts in die Datei schreibt?

nein, der zähler sollte dann schon weiter laufen...

Weisst Du ob die Plattform auf der das läuft garantiert, dass ein erhöhen einer int-Variable atomar ist? Ich glaube ich würde selbst wenn ich das wüsste, die entsprechenden kritischen Abschnitte schützen, oder das sehr deutlich in den Code schreiben, dass der nur dann funktioniert wenn das garantiert ist. Und zwar an allen Stellen die davon betroffen sind. wiringpi bietet für kritische Abschnitte auch Funktionen.

das verstehe ich nicht, der Abschnitt mit wiringpi ist aus dem example, welches bei dem Paket dabei war

Man mus bei C ja nicht mehr alle Variablen am Anfang eines Blocks deklarieren, aber man sollte trotzdem aufpassen, dass man Deklarationen und Code nicht so vermischt, dass es unübersichtlich wird. Also nicht error deklarieren und einen Wert zuweisen, dann zwei davon völlig unabhängige Deklarationen dazwischen schieben, und dann error auswerten. Ich würde das versenden des Wertes ins Netz ja in eine eigene Funktion schreiben. Dann kann man das auch unabhängig vom ganzen Rest entwickeln und testen.

also so wie es bei der error abfrage gemacht wurde?

Ich kenne die curl_easy*-API nicht, aber ich vermute mal Du musst eine Rückruffunktion zum schreiben von Daten registrieren die die Daten dann einfach ignoriert, falls die sonst auf der Standardausgabe landen. Man kann das gleiche CURL*-Handle laut Dokumentation übrigens für mehrere aufeinanderfolgende curl_easy_perform()-Aufrufe verwenden. Man muss da nicht immer neue erstellen.

das verstehe ich auch wieder nicht

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11260

Wohnort: München

Bevor man sich in die Untiefen der asynchronen Ausführung von Code begibt, könnte man sich auch überlegen, ob man nicht der Einfachheit halber den C-Teil so klein wie möglich hält - die Verarbeitung eines ermittelten Zählerwertes ist ja etwas, das man leicht in der Shell lösen kann, wenn man die Ausgabe des Programms auf stdout per Pipe weiterreicht.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <wiringPi.h>


// What GPIO input are we using?
//   This is a wiringPi pin number

#define   BUTTON_PIN   9

// globalCounter:
//   Global variable to count interrupts
//   Should be declared volatile to make sure the compiler doesn't cache it.

static volatile int globalCounter = 0 ;


/*
 * myInterrupt:
 *********************************************************************************
 */

void myInterrupt (void)
{
  ++globalCounter ;
}

int parse_value_from_file(const char *filename, int *value)
{
    FILE *fd = fopen(filename, "r");
    if (fd == NULL) {
        fprintf(stderr, "could not open %s\n", filename);
        return 1;
    }
    int count = fscanf(fd, "%d", value);
    fclose(fd);
    if (count != 1) {
        fprintf(stderr, "could not parse %s successfully\n", filename);
        return 2;
    }
    return 0;
}


/*
 *********************************************************************************
 * main
 *********************************************************************************
 */

int main (void)
{
  const char filename[] = "/var/strom/stromzähler1";
  int myCounter;
  int error = parse_value_from_file(filename, &myCounter);

  if (error) {
      myCounter = 0;
      fprintf(stderr, "unable to read initial value from %s, starting at %d", filename, myCounter);
  }

  globalCounter = myCounter;

  if (wiringPiSetup () < 0)
  {
    fprintf (stderr, "Unable to setup wiringPi: %s\n", strerror (errno)) ;
    return 1 ;
  }

  if (wiringPiISR (BUTTON_PIN, INT_EDGE_FALLING, &myInterrupt) < 0)
  {
    fprintf (stderr, "Unable to setup ISR: %s\n", strerror (errno)) ;
    return 1 ;
  }


  for (;;)
  {
    fprintf(stderr, "Waiting ... ");

    while (myCounter == globalCounter)
      delay (100) ;
    fprintf (stdout, "%d\n", globalCounter);
    myCounter = globalCounter ;
  }

  return 0 ;
}
gpio_counter | while read -r value; do
    echo "$value" > /var/strom/stromzähler1
    curl "http://192.168.178.38:8082/set/javascript.0.Stromzähler.Normalstrom.Zählerstand_input?value=${value}"
done 

Dann wäre das schlimmste, was einem passieren könnte, dass man nicht jede Werteveränderung sieht, aber da die zeitliche Auflösung anscheinend nicht besonders kritisch ist (der Delay wartet ja auch schon 100 ms) sollte das für eine Stromzähler vermutlich völlig ausreichend sein.

sveni-lee

(Themenstarter)

Anmeldungsdatum:
28. Mai 2013

Beiträge: 258

für mich zum besseren Verständnis. das schreiben der in die Datei übernimmt jetzt

1
echo "$value" > /var/strom/stromzähler1

richtig? und danach wird der Wert dann übertragen...

den zweiten Teil packe ich dann in ein Bash script? das bedeutet, dass das zweite skript immer vor dem ersten gestartet sein muß, damit der Wert auch korrekt hoch gesetzt wird... aber woher kommt gpio_counter?

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11260

Wohnort: München

sveni-lee schrieb:

für mich zum besseren Verständnis. das schreiben der in die Datei übernimmt jetzt

1
echo "$value" > /var/strom/stromzähler1

richtig? und danach wird der Wert dann übertragen...

Ja, in der Schleife kannst du mit der Zahl, die das Programm, das die GPIO-Interrupts zählt und dann auf stdout ausgibt, bequem weiterarbeiten. Das ist natürlich nicht so effizient wie wenn man das komplett in C schreiben würde, aber dafür vermutlich deutlich weniger Aufwand für dich...

den zweiten Teil packe ich dann in ein Bash script?

Es darf auch ein Skript für andere Shell sein, in der read verfügbar ist.

das bedeutet, dass das zweite skript immer vor dem ersten gestartet sein muß, damit der Wert auch korrekt hoch gesetzt wird... aber woher kommt gpio_counter?

gpio_counter ist das in C geschriebene Programm. Wenn es nicht im PATH ist, muss man es natürlich mit einem passenden Pfad aufrufen.

sveni-lee

(Themenstarter)

Anmeldungsdatum:
28. Mai 2013

Beiträge: 258

klappt leider so nicht...

was geht:

1
2
3
#!/bin/bash
COUNTER=$(cat /var/strom/stromcounter1)
curl http://192.168.1.142:8082/set/javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input?value={$COUNTER}

was nicht klappt:

1
2
3
4
#!/bin/bash
/home/strompi/WiringOP-Zero/examples/test3 | while read -r counter; do
    curl http://192.168.1.142:8082/set/javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input?value={counter}
done

ich weiß nicht so recht wo der unterschied liegt...

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11260

Wohnort: München

Die Variable counter wird in der URL nicht ersetzt, wenn du kein Dollar-Zeichen davor stellst - und du solltest den String quoten. Wie muss die URL denn aussehen?

http://192.168.1.142:8082/set/javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input?value={1}

oder

http://192.168.1.142:8082/set/javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input?value=1

?

sveni-lee

(Themenstarter)

Anmeldungsdatum:
28. Mai 2013

Beiträge: 258

es gehen beide varianten auf Komandozeilen Ebene

1
2
3
4
5
strompi@strompi:~$ curl http://192.168.1.142:8082/set/javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input?value={1}
{"id":"javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input","value":1,"val":1}strompi@strompi:~$
strompi@strompi:~$ curl http://192.168.1.142:8082/set/javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input?value=1
{"id":"javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input","value":1,"val":1}strompi@strompi:~$
strompi@strompi:~$

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11260

Wohnort: München

Und was genau funktioniert an der Variante, die ich oben gepostet hatte nicht? So kannst du sehen, was die Shell daraus macht:

1
2
3
4
5
#!/bin/bash
/home/strompi/WiringOP-Zero/examples/test3 | while read -r counter; do
    echo "http://192.168.1.142:8082/set/javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input?value=${counter}"
    curl "http://192.168.1.142:8082/set/javascript.0.Stromzaehler.Normalstrom.Zaehlerstand_input?value=${counter}"
done

sveni-lee

(Themenstarter)

Anmeldungsdatum:
28. Mai 2013

Beiträge: 258

es gibt gar nichts aus...

das C-Program läuft aber

1
2
3
4
5
6
7
8
9
strompi@strompi:~/WiringOP-Zero/examples$ sudo /home/strompi/WiringOP-Zero/examples/test3
436
437
438
439
440
441
442
443
1
2
strompi@strompi:~$ sudo ./stromexport.sh
[sudo] password for strompi:

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4686

Wohnort: Berlin

Ich vermute mal da fehlt ein fflush() im C-Programm wenn man nicht möchte das die Ausgaben gepuffert werden.

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11260

Wohnort: München

Ok, dann muss man das vermutlich nach Zeile 85 einfügen:

1
fflush(stdout);

sveni-lee

(Themenstarter)

Anmeldungsdatum:
28. Mai 2013

Beiträge: 258

ja also... das funktioniert jetzt mit der Übertragung schon mal ABER die beiden Skripte laufen asynchron. die übergebenen Werte passen nicht zusammen...

okay, habs rausgefunden fflush(stdout) muss hinter fprintf (stdout, "%d\n", globalCounter); stehen. Das zweite was ich rausgefunden habe mit dem Bash-Skript wird das C-Programm automatisch gestartet...was schon mal super ist... der export an Simple-API funktioniert so auch...

jetzt müsste das Bash-Skript noch in den Autostart ich dachte an so etwas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#! /bin/sh
 
# Aktionen
case "$1" in
    start)
        ./usr/local/bin/stromexport.sh &
#        /opt/beispiel start
        ;;
    stop)
        killall stromexport.sh
#        /opt/beispiel stop
        ;;
    restart)
        killall stromexport.sh
        ./usr/local/bin/stromexport.sh &
#        /opt/beispiel restart
        ;;
esac
 
exit 0

und das ganze in die /etc/init.d/stromexport packen

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11260

Wohnort: München

Ubuntu 16.04 bringt Systemd als Init-System mit - du könntest z.B. eine /etc/systemd/system/electric-meter.service anlegen:

1
2
3
4
5
6
7
8
9
[Unit]
Description=electric meter
After=network-online.target

[Service]
ExecStart=/usr/local/bin/stromexport.sh

[Install]
WantedBy=multi-user.target

Und die dann mit diesen Befehlen aktiveren bzw. starten:

sudo systemctl daemon-reload # immer nach Änderungen an der service-Datei ausführen, damit Systemd die neu einliest
sudo systemctl enable electric-meter.service # damit der Dienst beim Booten gestartet wird
sudo systemctl start electric-meter.service # startet den Dienst sofort 

network-online.target hängt von der Methode zur Netzwerkkonfiguration ab - falls das nicht wie erwartet funktionieren sollte, zeig mal bitte die Ausgabe von

systemctl is-enabled NetworkManager-wait-online.service
systemctl is-enabled  systemd-networkd-wait-online.service 

da müsstest du ggf. noch den passenden Dienst aktivieren.

sveni-lee

(Themenstarter)

Anmeldungsdatum:
28. Mai 2013

Beiträge: 258

was soll ich sagen, klappt genau so wie es soll...

Vielen Dankk für Deine Mühe und vor allem für deine Geduld!

Ich lasse den globalCounter zusätzlich noch in die Datei schreiben. Sollte das Frontend (iobroker) mal abgeschaltet werden, werden die Werte trotzdem noch weiter gesammelt. Und ein zweiter Vorteil, ich kann in der Datei /var/strom/stromcounter1 auch noch händisch korrekturen vornehmen...

Antworten |