Forum: Compiler & IDEs avr-gcc: __DATE__ formatieren / "initializer element is not constant"


von Kirsche (Gast)


Lesenswert?

Hi,

ich möchte das Build-Datum anders formatieren und in den Flashspeicher 
packen und versuche dazu folgendes:
1
const __flash char BUILD[] = {__DATE__[4], __DATE__[5], ' ', __DATE__[0], __DATE__[1], __DATE__[2], ' ', __DATE__[9], __DATE__[10], '\0'}; // DD MMM YY

Leider erzeugt das o.g. Fehlermeldung. Wie mache ich es richtig?

von Frank (Gast)


Lesenswert?

Noch ein Casting auf const davor?

von Dr. Sommer (Gast)


Lesenswert?

Einfach als C++ kompilieren (mit g++), da geht das. Noch 'extern "C"' 
davorschreiben, damit du von C Code aus drauf zugreifen kannst.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Kirsche schrieb:
> Wie mache ich es richtig?
1
const __flash char BUILD[] = __DATE__;

Und BUILD dann zur Laufzeit aufdröseln.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Einfach als C++ kompilieren (mit g++), da geht das. Noch 'extern "C"'
> davorschreiben, damit du von C Code aus drauf zugreifen kannst.

Nein.  C++ unterstützt kein __flash.

von Kirsche (Gast)


Lesenswert?

Gibt es keine Möglichkeit, das zur Compilezeit mit dem normalen C zu 
machen???

von Dr. Sommer (Gast)


Lesenswert?

Johann L. schrieb:
> Nein.  C++ unterstützt kein __flash.
Na sowas. Und man kann nicht einfach PROGMEM stattdessen nehmen?

von Joachim B. (jar)


Lesenswert?

?
kann man doch, vielleicht verstehe ich auch nur falsch

so meldet sich mein nano nach dem Reset:

File:       Au10a1rc2_NEUER_nano328p_LCD5110_RTC_RC433_ok.ino
kompiliert: 2017/04/22_17:53:34

wurde aus _DATE_ zusammengebaut

ist C weil cpp kann ich nicht.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Johann L. schrieb:
>> Nein.  C++ unterstützt kein __flash.
> Na sowas. Und man kann nicht einfach PROGMEM stattdessen nehmen?

Wenn man alle Zugriffe anpasst und pgm_read_xxx einfügt.  Oder eben C++ 
Feenstaub nach belieben.

Joachim B. schrieb:
> ?
> kann man doch, vielleicht verstehe ich auch nur falsch
>
> so meldet sich mein nano nach dem Reset:
>
> File:       Au10a1rc2_NEUER_nano328p_LCD5110_RTC_RC433_ok.ino
> kompiliert: 2017/04/22_17:53:34
>
> wurde aus __DATE__ zusammengebaut
>
> ist C weil cpp kann ich nicht.

Auch wenn du es nicht kannst ist es trotzdem C++ (Arduino).

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Joachim B. schrieb:
> ist C weil cpp kann ich nicht.

Hmm, sicher? Das da:

> Au10a1rc2_NEUER_nano328p_LCD5110_RTC_RC433_ok.ino

klingt eigentlich sehr nach C++. Dieses ganze Arduino-Zeug setzt doch 
auf C++ auf und funktioniert gar nicht mit C, oder täusche ich mich da?

von Joachim B. (jar)


Lesenswert?

ich nutze die LIBs programmiere aber C

der gcc kann beides.

Johann L. schrieb:
> Auch wenn du es nicht kannst ist es trotzdem C++ (Arduino).

???
ich bringe meinen puren C Code dort ein und es läuft.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Joachim B. schrieb:
> ich nutze die LIBs programmiere aber C
>
> der gcc kann beides.
>
> Johann L. schrieb:
>> Auch wenn du es nicht kannst ist es trotzdem C++ (Arduino).
>
> ???
> ich bringe meinen puren C Code dort ein und es läuft.

Wenn es mit einem C++ Compiler übersetzt wird dann ist es C++-Code und 
hat folglich auch dessen Semantik.

Du bekommst also folgende Zeile übersetzt?
1
static const __flash int X = 1;

von Joachim B. (jar)


Lesenswert?

Johann L. schrieb:
>
> Du bekommst also folgende Zeile übersetzt?
>
>
1
static const __flash int X = 1;


weiss ich nicht, muss ich denn?

ich weiss das ich jetzt const char var[] PROGMEM = "text"; // (o.ä.)
übersetzen kann :)

Ich hoffe niemand mit meiner manchmal flapsigen Art wieder zu 
brüskieren.

von Kirsche (Gast)


Lesenswert?

Kann jemand schlüssig erklären, warum _DATE_ eine Konstante ist/sein 
kann und __DATE__[3] nicht? Ich komme nicht drauf.

von N. M. (mani)


Lesenswert?


von Kirsche (Gast)


Lesenswert?

Danke. Aber je mehr ich darüber lese, desto deutlicher wird mir, dass 
das doch eine arg künstliche und unnötige Einschränkung ist.

Ein an einer konstanten Position befindliches Element eines konstanten 
Arrays ist doch folgerichtig ebenfalls konstant.

von Rolf M. (rmagnus)


Lesenswert?

Kirsche schrieb:
> Danke. Aber je mehr ich darüber lese, desto deutlicher wird mir, dass
> das doch eine arg künstliche und unnötige Einschränkung ist.

Ja. Das ist historisch bedingt so. Auf der anderen Seite ist dein 
Anwendungsfall ja auch eher speziell.

> Ein an einer konstanten Position befindliches Element eines konstanten
> Arrays ist doch folgerichtig ebenfalls konstant.

Nur dass das Array in C eben nicht als konstant gilt. Außer direkt 
hingeschriebenen Zahlen wie der 3 gibt es in C gar keine echten 
Konstanten. Man muss auch bedenken, dass "const" nicht von Anfang an in 
C enthalten war, sondern erst später so in die Sprache integriert werden 
musste, dass dabei möglichst kein bestehender Code inkompatibel wird.
In C++ hat man das dann geändert, weil man dort schon alleine für die 
Templates echte Konstanten brauchte.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Joachim B. schrieb:
> Johann L. schrieb:
>>
>> Du bekommst also folgende Zeile übersetzt?
>>
>>
1
static const __flash int X = 1;
>
>
> weiss ich nicht, muss ich denn?

Weil du behauptet hast, der Code sei C und nicht C++, und die Zeile für 
C++ nicht übersetzt.

Weil du ja noch nichtmal weißt, welche Kommandozeilenoptionen in deiner 
tumben IDE verwendet werden oder wie man die ändert, etc. ist es die 
einfachste Möglichkeit, zwischen C und C++ zu unterscheiden, indem man 
Code übersetzt, der beide Sprachen trennt.  Z.B. wird
1
static const int a = 1;
2
int b[a];
in C++ übersetzt, in C hingegen nicht.

Ist aber eigentlich egal, denn die Frage des TO ist ganz klar bezüglich 
C.

Alles Gelaber über C++ ist also hinfällig und kann aussortiert werden 
:-)

von Joachim B. (jar)


Lesenswert?

Johann L. schrieb:
> Ist aber eigentlich egal, denn die Frage des TO ist ganz klar bezüglich
> C.
>
> Alles Gelaber über C++ ist also hinfällig und kann aussortiert werden

OK

ich weiss das ich meinen (auch alten C Code) in der Arduino IDE 
einsetzen konnte und das er wie gewünscht funktioniert. Der QuellCode 
ist zum Teil auf dem atariST geschrieben und getestet worden, im PC 
unter DOS mit QC kompiliert und dann in Prüfgeräten eingesetzt worden.

Einige Anpassungen waren sinnvoll und wurden so nach und nach 
integriert.

Hatte ich (wir) früher #defines für char, unsigned char, int unsigned 
int usw. und weil das undurchsichtig für uns war, hatte unser 
Informatiker dieses eingeführt:

#define BYTE char
#define UBYTE unsigned char usw.

nun schreibe ich halt alles zu int8_t und uint8_t usw. um.

Ist ja auch sinnvoll.
Das mit dem const habe ich ja auch schon eingefügt,

aus der symbolischen "prog_char var[] PROGMEM ="
geändert in "const char var[]PROGMEM =" und funktioniert nun ohne 
Warnungen und Fehler.

Ob ich damit zu c++ wechsel weiss ich nicht, für mich siehts aus wie C 
und der gcc kompiliert auch c in einer cpp Umgebung.

danke an alle die helfen meinen Knoten etwas zu lösen.

Johann L. schrieb:
> Weil du ja noch nichtmal weißt, welche Kommandozeilenoptionen in deiner
> tumben IDE verwendet werden

da sind leider keine erreichbaren Kommandozeilenoptionen, die IDE wird 
von der GUI gesteuert, so intransparent weil der geneigte User nicht mit 
Details verwirrt werden soll.

PS Kommandozeilenoption, man muss schon sehr an der alten CMD hängen um 
damit über Jahrzehnte glücklich zu sein, ich sehe es ja ein unter CP/M, 
DOS, Unix, LINUX usw. war es Standard, aber seit GEM und Win bin ich 
froh über GUI.
Optionen werden in besseren GUIs eingetragen und dann nur -> click
Wer will denn immer Optionen ändern?

von Wilhelm M. (wimalopaan)


Lesenswert?

In C++ besteht keine Notwendigkeit, solche Konstanten ins (kostbare) 
Flash abzulegen:
1
constexpr DateTime::CompilationDate cdate;

Die Klasse CompilationDate hat einen constexpr Konstruktor, der den 
C-String _DATE_ parsed und in einen Tag (Julian Day) umwandelt. Das 
verbraucht weder RAM noch Flash und kostet auch zur Laufzeit nichts.

Wenn Interesse besteht, stelle ich das auch gern zur Verfügung ...

: Bearbeitet durch User
von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Joachim B. schrieb:
> ich weiss das ich meinen (auch alten C Code) in der Arduino IDE
> einsetzen konnte

Dann wird er von einem C++-Compiler als C++-Code angesehen und 
entsprechend übersetzt.

Woran soll die Arduino-IDE erkennen, daß von Dir eingefügter Code C sein 
soll?

Oder wirfst Du der Arduino-IDE komplette Quellcodedateien vor, deren 
Name auf *.c endet?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Joachim B. schrieb:
> PS Kommandozeilenoption, man muss schon sehr an der alten CMD hängen um
> damit über Jahrzehnte glücklich zu sein, ich sehe es ja ein unter CP/M,
> DOS, Unix, LINUX usw. war es Standard, aber seit GEM und Win bin ich
> froh über GUI.

Das hat doch mit GUI oder CMD überhaupt nix zu tun.  GCC ist nun mal ein 
Kommandozeilen-Tool und wird es immer bleiben, ditto Binutils.  Die 
gesamte Steuerung der Tools geschieht also über Command Line (und zu 
einem kleinen Teil über Umgebungsvariablen, aber dazu muss man erst mal 
eine "Umgebung" haben).

von Joachim B. (jar)


Lesenswert?

Rufus Τ. F. schrieb:
> Woran soll die Arduino-IDE erkennen, daß von Dir eingefügter Code C sein
> soll?

übersetzt wird es ja vom Compiler also wird er es erkennen :)

> Oder wirfst Du der Arduino-IDE komplette Quellcodedateien vor, deren
> Name auf *.c endet?

ja verschiedene Methoden, klappte irgendwie unbefriedigend, dann habe 
ich die *.c Files in *.ino geändert und nun klappt es auch mit der IDE.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Joachim B. schrieb:
> Rufus Τ. F. schrieb:
>> Woran soll die Arduino-IDE erkennen, daß von Dir eingefügter Code C sein
>> soll?
>
> übersetzt wird es ja vom Compiler also wird er es erkennen :)

Ja, von einem C++ Compiler :-)

> ja verschiedene Methoden, klappte irgendwie unbefriedigend,

Methoden in C :-)

von Jürgen S. (jurs)


Lesenswert?

Kirsche schrieb:
> Danke. Aber je mehr ich darüber lese, desto deutlicher wird mir, dass
> das doch eine arg künstliche und unnötige Einschränkung ist.
>
> Ein an einer konstanten Position befindliches Element eines konstanten
> Arrays ist doch folgerichtig ebenfalls konstant.

Ja.

Und zum Lesen von Konstanten aus dem PROGMEM kannst Du NICHT einfach per 
Array-Index zugreifen, sondern Du brauchst die Funktion pgm_read_byte() 
dazu.


Kleines Programmierbeispiel, das ich eben mit der Arduino-IDE für den 
AVR-GCC gemacht habe:
1
static const char PROGMEM compileDate[] = __DATE__;
2
static const char PROGMEM compileTime[9]  = __TIME__;
3
static const char PROGMEM compilerVerP[6]  = __VERSION__;
4
5
void setup() 
6
{
7
  Serial.begin(9600);
8
  Serial.print(F("Date of Compilation:"));
9
  
10
  for (int i=0;i<sizeof(compileDate);i++) Serial.write(pgm_read_byte(&compileDate[i]));
11
  Serial.println();  
12
}
13
14
void loop() {
15
  // put your main code here, to run repeatedly:
16
17
}

von Joachim B. (jar)


Lesenswert?

klasse, danke dafür, spart vieleicht MEM muss ich mal probieren und 
meine Version rauswerfen.

von Joachim B. (jar)


Lesenswert?

hast du den /0 Terminator vergessen?
"Date of Compilation:May  5 2017" hinter 2017 taucht ein [] auf

kann am Serial.write liegen, ich nehme ja lieber Serial.print

von Joachim B. (jar)


Lesenswert?

wie wäre es mit:
1
static const char PROGMEM compileDate[] = __DATE__;
2
static const char PROGMEM compileTime[9]  = __TIME__;
3
static const char PROGMEM compilerVerP[6]  = __VERSION__;
4
  Serial.print(F("Date of Compilation:"));
5
  for (int i=0;i<sizeof(compileDate);i++) 
6
  { if(isprint(pgm_read_byte(&compileDate[i])))
7
      Serial.write(pgm_read_byte(&compileDate[i]));
8
  }
9
  Serial.println();

um aber aus "May"

"May  5 2017"

wieder 5 zu machen muss ich den doch durch die Texterkennung jagen :)

: Bearbeitet durch User
von Jürgen S. (jurs)


Lesenswert?

Joachim B. schrieb:
> wie wäre es mit:
>
1
> static const char PROGMEM compileDate[] = __DATE__;
2
> static const char PROGMEM compileTime[9]  = __TIME__;
3
> static const char PROGMEM compilerVerP[6]  = __VERSION__;
4
>   Serial.print(F("Date of Compilation:"));
5
>   for (int i=0;i<sizeof(compileDate);i++)
6
>   { if(isprint(pgm_read_byte(&compileDate[i])))
7
>       Serial.write(pgm_read_byte(&compileDate[i]));
8
>   }
9
>   Serial.println();
10
>
>
> um aber aus "May"
>
> "May  5 2017"
>
> wieder 5 zu machen muss ich den doch durch die Texterkennung jagen :)

Was für eine Formatierung möchtest Du denn haben?

05.05.2017 vielleicht?

Oder lieber in Richtung ISO-Format:
2017-05-05

Oder nur an numerische Variablen zuweisen?

Und dürfen bei der Verarbeitung der Monatsnamen auch nur 
PROGMEM-Konstanten verwendet werden?

Wobei der Compiler wohl nur die englischen 3-Buchstaben-Kurznamen 
verwendet, so daß man sich die Monate im Programm einfach 
speichersparend unterbringen kann als:

const char PROGMEM MonthNames[] 
="JanFebMarApr,MayJunJulAug,SepOctNovDec";

Wenn Du eine fertige Funktionfür AVR-GCC/Arduino zum Decodieren von 
_DATE_ und/oder _TIME_ brauchst, sag Bescheid, dann kann ich Dir was 
basteln und hier posten.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

Jürgen S. schrieb:
> const char PROGMEM MonthNames[]
> ="JanFebMarApr,MayJunJulAug,SepOctNovDec";
>
> Wenn Du eine fertige Funktionfür AVR-GCC/Arduino zum Decodieren von
> DATE und/oder TIME brauchst, sag Bescheid, dann kann ich Dir was
> basteln und hier posten.

hört sich prima an, könnte ich gebrauchen um meinen Code zu kürzen.

Ich erstelle ja immer einen Standardstring mit folgendem Format

sprintf_P(c_str, PSTR("%04d/%02d/%02d_%02d:%02d:%02d"), year, month, 
day, hour, minute, second);

nicht besser?

="   JanFebMarAprMayJunJulAugSepOctNovDec";

oder
="ErrJanFebMarAprMayJunJulAugSepOctNovDec";

: Bearbeitet durch User
von Jürgen S. (jurs)


Lesenswert?

Joachim B. schrieb:
> hört sich prima an, könnte ich gebrauchen um meinen Code zu kürzen.
>
> Ich erstelle ja immer einen Standardstring mit folgendem Format
>
> sprintf_P(c_str, PSTR("%04d/%02d/%02d_%02d:%02d:%02d"), year, month,
> day, hour, minute, second);


Anbei mal ein Arduino-Beispiel-Sketch, der Datum und Zeit 
derKompilierung ausliest so wie von Dir gewünscht umformatiert, und auf 
Serial ausgibt.
1
static const char PROGMEM compileDate[] = __DATE__;
2
static const char PROGMEM compileTime[]  = __TIME__;
3
static const char PROGMEM compileVersion[]  = __VERSION__;
4
const char MONTHNAMES[]PROGMEM ="JanFebMarAprMayJunJulAug,SepOctNovDec";
5
6
const int BUILD_YEAR()
7
{
8
  int year=1000*(pgm_read_byte(&compileDate[7])-'0');
9
  year+=100*(pgm_read_byte(&compileDate[8])-'0');
10
  year+=10*(pgm_read_byte(&compileDate[9])-'0');
11
  year+=pgm_read_byte(&compileDate[10])-'0';
12
  return year;
13
}
14
15
byte BUILD_MONTH()
16
{
17
18
 for(byte month=0;month<12;month++)
19
 {
20
  byte matching=0;
21
   for(byte b=0;b<3;b++)
22
  if(pgm_read_byte(&compileDate[b]) ==pgm_read_byte(&MONTHNAMES[3*month+b])) matching++;
23
  if (matching==3) return month+1;
24
 }
25
 return 0;
26
}
27
28
byte BUILD_DAY()
29
{
30
  
31
32
  byte day= pgm_read_byte(&compileDate[4]);
33
  if(day<'1') day=0; else day-='0';
34
  day=10*day+pgm_read_byte(&compileDate[5])-'0';
35
return day;    
36
}
37
38
byte BUILD_HOUR()
39
{
40
  return 10*(pgm_read_byte(&compileTime[0])-'0')+pgm_read_byte(&compileTime[1])-'0';;
41
}
42
43
byte BUILD_MINUTE()
44
{
45
  return 10*(pgm_read_byte(&compileTime[3])-'0')+pgm_read_byte(&compileTime[4])-'0';;
46
}
47
48
byte BUILD_SECOND()
49
{
50
  return 10*(pgm_read_byte(&compileTime[3])-'0')+pgm_read_byte(&compileTime[4])-'0';;
51
}
52
53
54
55
void setup() 
56
{
57
  Serial.begin(9600);
58
  Serial.print(F("Date of Compilation:"));
59
  for (int i=0;i<sizeof(compileDate);i++) Serial.write(pgm_read_byte(&compileDate[i]));
60
  Serial.println();  
61
62
  Serial.print(F("Nach der Umformatierung:"));
63
  char buf[20];
64
  sprintf_P(buf, PSTR("%04d/%02d/%02d_%02d:%02d:%02d"), BUILD_YEAR(), BUILD_MONTH(), 
65
BUILD_DAY(), BUILD_HOUR(), BUILD_MINUTE(), BUILD_SECOND());
66
  
67
Serial.println(buf);
68
}
69
70
void loop() {
71
  // put your main code here, to run repeatedly:
72
73
}

von Joachim B. (jar)


Lesenswert?

wow, Klasse
Rückmeldungen konstruktiver Natur sind ja selten,

ich bin gespannt ob das meinen Code kürzt, ich hatte es ja auch schon 
geschafft auf anderem Wege.

Danke dafür, nun beide Routinen gegeneinander antreten lassen, ich melde 
mich zurück.

Ich "kämpfe" ja bis jetzt erfolgreich Arduino 1.8.2 mit gcc 4.9.2 gleich 
auf win und PI3(Linux) laufen zu lassen, nach anfänglichen 
Schwierigkeiten LIBs und Einstellungen für beide unter einem Hut zu 
bringen ist es nun für eines meiner Testprogramme geschafft, nun "nur" 
noch 5 weitere Rechner umbauen, von PCs mit winXP/7 bis PI B alt.

ich hoffe du hast nichts gegen Vorschäge?

int sollte doch unsigned int oder besser uint16_t sein

negative Jahre kann es nicht geben und Fehler für nicht erkanntes Jahr 
mit negativem int wird nicht erstellt.

  int year=1000*(pgm_read_byte(&compileDate[7])-'0');
  year+=100*(pgm_read_byte(&compileDate[8])-'0');
  year+=10*(pgm_read_byte(&compileDate[9])-'0');
  year+=pgm_read_byte(&compileDate[10])-'0';

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

so fertig, abgesehen von deinem Sekunden Fehler

hat Monatsermittelung nicht geklappt aber dein Code braucht weniger SRAM

als Mix mit meinem schon eine echte Hilfe!
1
// -------------- Compile -------------------------------------
2
// alt
3
// Der Sketch verwendet 24952 Bytes (81%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes.
4
// Globale Variablen verwenden 1754 Bytes (85%) des dynamischen Speichers, 294 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.
5
// Ausgabe:
6
7
// RESET
8
// -----
9
// File: /mnt/Qnap453a/atmel/arduino/nan_RTC_OLE_PT2258_NOK_World8p_ic_OK/nan_RTC_OLE_PT2258_NOK_World8p_ic_OK.ino
10
// kompiliert: 2017/05/06_12:38:49
11
12
// neu
13
// hat nicht geklappt
14
// Der Sketch verwendet 24954 Bytes (81%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes.
15
// Globale Variablen verwenden 1709 Bytes (83%) des dynamischen Speichers, 339 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.
16
17
// hat geklappt
18
// Der Sketch verwendet 24946 Bytes (81%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes.
19
// Globale Variablen verwenden 1699 Bytes (82%) des dynamischen Speichers, 349 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.
20
// Ausgabe:
21
22
// RESET
23
// -----
24
// File: /mnt/Qnap453a/atmel/arduino/nan_RTC_OLE_PT2258_NOK_World8p_ic_OK/nan_RTC_OLE_PT2258_NOK_World8p_ic_OK.ino
25
// kompiliert: 2017/05/06_12:58:47
26
27
28
29
30
31
#define NEU_COMPILE
32
boolean compile_time(void)
33
{ uint8_t mo=0, ta=0, hh=0, mm=0, ss=0;
34
  uint16_t jjjj=0;
35
36
#ifndef NEU_COMPILE
37
  uint8_t cc =0;
38
  char buffer_l[15];
39
  char buf[4];
40
  strcpy(buffer_l, __DATE__);
41
  jjjj=atoi(rights(buffer_l, 4));
42
  ta=atoi(mids(buffer_l, 5, 2));
43
  for(cc=0; cc<13; cc++)
44
  { strcpy(buf, (char*)pgm_read_word(&(mo_table[cc]))); // Necessary casts and dereferencing, just copy. 
45
    if(!strcmp(lefts(buffer_l, 3), buf))
46
    { mo=cc; break; }
47
  } // for(cc=1; cc<13; cc++)
48
  strcpy(buffer_l, __TIME__);
49
  hh=atoi((char *)lefts(buffer_l, 2));
50
  mm=atoi((char *)mids(buffer_l, 4, 2));
51
  ss=atoi((char *)rights(buffer_l, 2));
52
53
#else
54
  char buf[4];
55
  static const char PROGMEM compileDate[] = __DATE__;
56
  static const char PROGMEM compileTime[]  = __TIME__;
57
//  static const char PROGMEM compileVersion[]  = __VERSION__;
58
  const char MONTHNAMES[]PROGMEM ="JanFebMarAprMayJunJulAug,SepOctNovDec";
59
  
60
  jjjj=1000*(pgm_read_byte(&compileDate[7])-'0');
61
  jjjj+=100*(pgm_read_byte(&compileDate[8])-'0');
62
  jjjj+=10*(pgm_read_byte(&compileDate[9])-'0');
63
  jjjj+=pgm_read_byte(&compileDate[10])-'0';
64
65
  for(mo=0; mo<12; mo++)
66
  { strcpy(buf, (char*)pgm_read_word(&(mo_table[mo]))); // Necessary casts and dereferencing, just copy. 
67
    if(!strcmp(lefts(__DATE__, 3), buf))
68
    { mo++; break; }
69
  } // for(cc=1; cc<13; cc++)
70
71
/*
72
  for(mo=0;mo<12;mo++)
73
  { char matching=0;
74
    for(char b=0;b<3;b++)
75
    if(pgm_read_byte(&compileDate[b]) ==pgm_read_byte(&MONTHNAMES[3*mo+b])) 
76
      matching++;
77
    if (matching==3) 
78
      mo++;
79
  }
80
*/
81
  ta = pgm_read_byte(&compileDate[4]);
82
  (ta<'1') ? ta=0 : ta-='0';
83
  ta =10*ta+pgm_read_byte(&compileDate[5])-'0';
84
85
  hh = 10*(pgm_read_byte(&compileTime[0])-'0')+pgm_read_byte(&compileTime[1])-'0';
86
  mm = 10*(pgm_read_byte(&compileTime[3])-'0')+pgm_read_byte(&compileTime[4])-'0';;
87
  ss = 10*(pgm_read_byte(&compileTime[6])-'0')+pgm_read_byte(&compileTime[7])-'0';;
88
#endif
89
90
  sprintf((char *)c_str,"%04d/%02d/%02d_%02d:%02d:%02d", jjjj, mo, ta, hh, mm, ss);
91
  if(jjjj>0 && mo>0 && ta>0)
92
    return true;
93
  else
94
    return false;
95
} // unsigned char compile_time(void)

: Bearbeitet durch User
von Jürgen S. (jurs)


Lesenswert?

Joachim B. schrieb:
> so fertig, abgesehen von deinem Sekunden Fehler
>
> hat Monatsermittelung nicht geklappt aber dein Code braucht weniger SRAM

Danke für das Feedback zu meinem Beispielcode!

Ich habe es gerade nochmal getestet.
Die BUILD_SECOND() Funktion hatte tatsächlich einen Fehler, ich habe die 
Sekunden beim falschen Offset im _TIME_ Zeitstempel ausgelesen.
Richtig ist diese Version für die Sekunden:
1
byte BUILD_SECOND()
2
{
3
  return 10*(pgm_read_byte(&compileTime[6])-'0')+pgm_read_byte(&compileTime[7])-'0';;
4
}

Bei der Monatsermittlung konnte ich in meinem Beispielcode allerdings 
keinen Fehler entdecken, für ein heute kompiliertes Programm wird für 
'May' der korrekte Monat 5 zurückgeliefert und in der umformatierten 
Datumsausgabe als Monat "05" angezeigt.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

Jürgen S. schrieb:
>
1
> byte BUILD_SECOND()
2
> {
3
>   return 
4
> 10*(pgm_read_byte(&compileTime[6])-'0')+pgm_read_byte(&compileTime[7])-'0';;
5
> }
6
>

den Fehler hatte ich ja leicht beheben können und nach deiner Methode 
eingearbeitet, siehe Code
1
  hh = 10*(pgm_read_byte(&compileTime[0])-'0')+pgm_read_byte(&compileTime[1])-'0';
2
  mm = 10*(pgm_read_byte(&compileTime[3])-'0')+pgm_read_byte(&compileTime[4])-'0';;
3
  ss = 10*(pgm_read_byte(&compileTime[6])-'0')+pgm_read_byte(&compileTime[7])-'0';;

> Bei der Monatsermittlung konnte ich in meinem Beispielcode allerdings
> keinen Fehler entdecken, für ein heute kompiliertes Programm wird für
> 'May' der korrekte Monat 5 zurückgeliefert und in der umformatierten
> Datumsausgabe als Monat "05" angezeigt.

da muss ich dann bei der Umwandlung zu meinem Code Fehler eingebaut 
haben, schaust du noch mal?
1
  for(mo=0;mo<12;mo++)
2
  { char matching=0;
3
    for(char b=0;b<3;b++)
4
    if(pgm_read_byte(&compileDate[b]) ==pgm_read_byte(&MONTHNAMES[3*mo+b])) 
5
      matching++;
6
    if (matching==3) 
7
      mo++;

kommt immer 12 raus!

: Bearbeitet durch User
von Jürgen S. (jurs)


Lesenswert?

Joachim B. schrieb:
> Jürgen S. schrieb:
> kommt immer 12 raus!

Ja, Du hast Dir eine for-Schleife gemacht, die immer(!) zwölfmal läuft.
for(mo=0;mo<12;mo++)

Tatsächlich musst Du die Schleife breaken und vorzeitig verlassen, wenn 
der gesuchte Monat gefunden wurde!

In der von mir geposteten BUILD_MONTH() Funktion wird die Schleife durch 
die vorzeitige Rückkehr aus der Funktion mittels return month+1; nicht 
bis zuende ausgeführt. Und so einen vorzeitigen Schleifenabbruch nach 
dem Finden von drei passenden Buchstaben für den Monatsnamen brauchst Du 
auch.
Oder Du nimmst meine Funktion und schreibst mo=BUILD_MONTH();

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Um nochmal eine völlig unorthodoxe Lösungsmöglichkeit anzubieten:

Man könnte auch einen Makro aus dem Makefile heraus setzen, und dort
dann extern das date-Kommando aufrufen.  Damit kann man sich eine
beliebige Formatierung erzeugen, und das dann im Flash ablegen.

Ich habe allerdings keine Ahnung, inwiefern man sowas der Arduino-IDE
beigebogen bekäme.

von Kirsche (Gast)


Lesenswert?

Das ist nicht plattformunabhängig. Der Windows-Date-Befehl kennt z.B. 
keine Parameter für die Formatierung.

von Wilhelm M. (wimalopaan)


Lesenswert?

Kirsche schrieb:
> Das ist nicht plattformunabhängig. Der Windows-Date-Befehl kennt z.B.
> keine Parameter für die Formatierung.

Na, dann schreibt man sich das halt grad und ruft das eigene tool auf 
;-)

(Anm: wie oben schon mal erwähnt, kann man solche Sachen in C++ elegant 
mit constexpr-Funktionen machen. Dann braucht man kein externes tool, 
der Compiler ist es dann.)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kirsche schrieb:
> Das ist nicht plattformunabhängig.

Plattformunabhängigkeit stand allerdings nicht im Anforderungsprofil.

Je nach Umgebung hat man gemeinsam mit dem AVR-GCC allerdings auch
im Windows-Umfeld ein unixoides "date"-Kommando zur Verfügung, beim
Atmel Studio beispielsweise unter "shellutils".

Wenn man Python installiert hat, geht es außerdem plattformunabhängig
damit:
1
python -c "from time import *;print(strftime('%Y-%m-%dT%H:%M:%S', localtime(time())))"

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> kann man solche Sachen in C++ elegant mit constexpr-Funktionen machen

Aber eben auch nur in C++.  Dort hat man das ursächliche Problem
aber gar nicht, da __DATE__[2] bereits ein konstanter Ausdruck ist,
mit dem man einen String initialisieren darf – anders als in C.

von Joachim B. (jar)


Lesenswert?

Wilhelm M. schrieb:
> In C++ besteht keine Notwendigkeit, solche Konstanten ins (kostbare)
> Flash abzulegen:

wo bitte soll das kompilierte _DATE_ sonst liegen wenn nicht im flash?

von Wilhelm M. (wimalopaan)


Lesenswert?

Joachim B. schrieb:
> Wilhelm M. schrieb:
>> In C++ besteht keine Notwendigkeit, solche Konstanten ins (kostbare)
>> Flash abzulegen:
>
> wo bitte soll das kompilierte _DATE_ sonst liegen wenn nicht im flash?

Wenn ein Ausdruck constexpr ist (wie oben geschrieben der Zeitstempel), 
dann kann der Compiler dort direkt im Code der Wert als Konstante 
einsetzen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> dann kann der Compiler dort direkt im Code der Wert als Konstante
> einsetzen.

Ja, und?

Irgendwo im Flash steht's am Ende trotzdem.  Ob nun geschlossen
als String oder in Form von Direktoperanden einzelner CPU-Befehle,
das bleibt sich wohl gleich.

von Joachim B. (jar)


Lesenswert?

Jörg W. schrieb:
> Ja, und?
>
> Irgendwo im Flash steht's am Ende trotzdem.

danke genau das war die richtige Antwort an diese Nebelkerze!

Ich verstehe auch nicht warum solche Aussagen kommen?

Wilhelm M. schrieb:
> In C++ besteht keine Notwendigkeit, solche Konstanten ins (kostbare)
> Flash abzulegen:

von Wilhelm M. (wimalopaan)


Lesenswert?

Jörg W. schrieb:
> Wilhelm M. schrieb:
>> dann kann der Compiler dort direkt im Code der Wert als Konstante
>> einsetzen.
>
> Ja, und?
>
> Irgendwo im Flash steht's am Ende trotzdem.  Ob nun geschlossen
> als String oder in Form von Direktoperanden einzelner CPU-Befehle,
> das bleibt sich wohl gleich.

Allerdings spare ich den Zugriff via pgm_read_...()

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Allerdings spare ich den Zugriff via pgm_read_...()

Den sparst du bei C auch: __flash erledigt das transparent für den 
Nutzer. :)

Ob's am Ende wirklich eine Einsparung ist, bleibt ohnehin dahingestellt: 
Direktoperanden sind nur dann effizienter, wenn man sehr wenige Elemente 
des Strings benötigt.  Das sollte der Compiler aber ohnehin selbst 
wissen.

von Kirsche (Gast)


Lesenswert?

Die Wurzel allen Übels ist, dass z.B. __DATE__[2] nicht als Konstante 
gilt, obwohl es eine ist. IMHO ist das sogar ein Bug im gcc...

von Joachim B. (jar)


Lesenswert?

Wilhelm M. schrieb:
> Allerdings spare ich den Zugriff via pgm_read_...()

Die Konstante _DATE_ sollte anders formatiert im Flash liegen, so 
verstand ich den Wunsch vom TO.

Wie soll der Zugriff ohne read aus dem Flash sonst laufen zur Laufzeit?

Ich glaube ich verstehe deine Gedanken gerade nicht,

ich habe den Wunsch das mir ein Programm im Flash auf Wunsch sein build 
Datum und seinen Namen mitteilt, das geht nur über read aus dem flash, 
egal wie man das nun nennt, wie also soll es

ohne pgm_read_...()
ohne strcpy(buffer_l, _DATE_);
 o.ä gehen?

in jeden Fall wird aus dem flash GELESEN

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Jörg W. schrieb:
> Wilhelm M. schrieb:
>> Allerdings spare ich den Zugriff via pgm_read_...()
>
> Den sparst du bei C auch: __flash erledigt das transparent für den
> Nutzer. :)

Stimmt. Aber der generierte Code ist ist trotzdem kürzer, da der "Umweg" 
über [r30, r31] fehlt.

von Wilhelm M. (wimalopaan)


Lesenswert?

Joachim B. schrieb:
> Wilhelm M. schrieb:
>> Allerdings spare ich den Zugriff via pgm_read_...()
>
> Die Konstante _DATE_ sollte anders formatiert im Flash liegen, so
> verstand ich den Wunsch vom TO.
>
> Wie soll der Zugriff ohne read aus dem Flash sonst laufen zur Laufzeit?
>
> Ich glaube ich verstehe deine Gedanken gerade nicht,

Natürlich stehen solche Konstante immer irgendwie im Flash.

Mein ursprünglicher Hinweis zielte ja auch nur darauf ab, dass man 
jegliche Umformung von _DATE_ durch den Compiler zur Compilezeit 
machen lassen kann. Alle anderen Lösungen die hier auftauchten, waren 
Laufzeitlösungen (mit Ausnahme ggf. zur Compilerzeit andere tools 
aufzurufen).

von Joachim B. (jar)


Lesenswert?

Wilhelm M. schrieb:
> Natürlich stehen solche Konstante immer irgendwie im Flash.

genau, dann war nur dein Satz missverständlich, aber darauf hatte man ja 
schon hingewiesen.

Klaro den String schon passend im Flash umgeformt zu haben wäre schön, 
vielleicht findet sich noch eine Lösung für den gcc.

von nobody expects the spanish inquisition (Gast)


Lesenswert?

Kaum ein Projektchen kommt mit nur 1 Quelldatei aus, nichtmal ein .ino:
die (GUI)IDE (oder Make) macht daraus mehrere gcc Aufrufe.
Niemand ist brav und artig, also auch nicht vor 23:59 im Bettchen.

Also läuft irgendwann ein Buildvorgang über den Zeitpunkt der 
Datumsänderung: ein gcc Aufruf davor, ein gcc Aufruf danach.

Schwupps geht der Build in die Hose! Oder besser: der Build klappt zwar, 
aber das Binary legt ein unerklärliches Laufzeitverhalten an den Tag...

> Wenn man Python installiert hat, geht es außerdem plattformunabhängig damit:
>
1
python -c "from time import *; print(strftime('%Y-%m-%dT%H:%M:%S', localtime(time())))"

Sowas, einmalig bei Start des Builds ermittelt und per Umgebungsvariable 
identisch an jeden Toolaufruf mitgegeben (e.g. gcc... -D.... ), ist 
definitiv die sauberste Lösung.

Bei _TIME_ ist man sich ja auch einig dass es bei "jedem" Zugriff 
darauf einen anderen Wert rausrückt..

NB: das standard Trennzeichen bei den Datumswerte ist '-', zumindest 
nach ISO. (nicht '/').

von Kirsche (Gast)


Lesenswert?

Sind letztendlich alles nur Krücken, quasi ein externer Präprozessor. Es 
würde schon helfen, wenn der Präprozessor im gcc so schlau wäre, z.B. 
__DATE__[2] selbst aufzulösen. _DATE_ kommt ohnehin vom Präprozessor. 
Also könnte er eigentlich auch einzelne Zeichen rausfischen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kirsche schrieb:
> Sind letztendlich alles nur Krücken, quasi ein externer Präprozessor.

Du hast seinen Einwand nicht einmal ansatzweise verstanden.

> Es
> würde schon helfen, wenn der Präprozessor im gcc so schlau wäre

Du hast auch nicht verstanden, was die Aufgabe des Präprozessors ist
und warum das, was du vom Compiler erwartest, in C einfach mal gar
nicht machbar sein darf.  Der Standard verbietet es schlicht, und
wenn GCC es anders handhaben würde, wäre er nicht mit dem Standard
konform.

: Bearbeitet durch Moderator
von Kirsche (Gast)


Lesenswert?

Zeig mir mal den Absatz in der Definition des Standards, der das sagt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kirsche schrieb:
> Zeig mir mal den Absatz in der Definition des Standards, der das sagt.

6.6 Absatz 8

Ich nehme die strikte Aussage, dass er es gar nicht darf, zurück.
Könnte sein, dass eine solche Ausnahme unter 6.6 Absatz 11 machbar
wäre.

Das müsstest du dann allerdings mit den GCC-Leuten diskutieren.

: Bearbeitet durch Moderator
von Rolf M. (rmagnus)


Lesenswert?

Kirsche schrieb:
> Sind letztendlich alles nur Krücken, quasi ein externer Präprozessor. Es
> würde schon helfen, wenn der Präprozessor im gcc so schlau wäre, z.B.
> __DATE__[2] selbst aufzulösen. DATE kommt ohnehin vom Präprozessor.
> Also könnte er eigentlich auch einzelne Zeichen rausfischen.

Dazu müsste man aber dem Präprozessor erstmal beibringen, was Arrays 
sind. Der kennt sowas nämlich nicht. Strings oder Datentypen kennt er 
auch nicht. Damit er __DATE__[2] auflösen kann, müsste man quasi einen 
halben Compiler in den Präprozessor rein implementieren.

Jörg W. schrieb:
> Ich nehme die strikte Aussage, dass er es gar nicht darf, zurück.
> Könnte sein, dass eine solche Ausnahme unter 6.6 Absatz 11 machbar
> wäre.

Das wird sie ja sowieso schon mit Absatz 10.

nobody expects the spanish inquisition schrieb:
> Bei TIME ist man sich ja auch einig dass es bei "jedem" Zugriff
> darauf einen anderen Wert rausrückt..

Darf es nicht. Es muss beim Übersetzen einer "translation unit" konstant 
sein. Wenn ich also ein C-File schreibe, das 20 Sekunden zum Übersetzen 
braucht und in dem 10 mal _TIME_ benutzt wird, muss alle 10 mal der 
selbe Wert zurück kommen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Es muss beim Übersetzen einer "translation unit" konstant sein.

Ihm ging's ja darum, dass ein Projekt aus mehreren translation units
eben keine einheitliche Notation von _DATE__ oder __TIME_ mehr hat.

>> Ich nehme die strikte Aussage, dass er es gar nicht darf, zurück.
>> Könnte sein, dass eine solche Ausnahme unter 6.6 Absatz 11 machbar
>> wäre.
>
> Das wird sie ja sowieso schon mit Absatz 10.

Mein Draft war wohl etwas neuer, dort war diese Klausel nach Abschnitt
11 gerutscht, die bei dir (und bei einem älteren Draft) noch in 10
stand.

“An implementation may accept other forms of constant expressions.”

Aber wie schon gesagt, darüber müsste man wenn schon mit den Entwicklern
des GCC verhandeln.  Er müsste ja dann in der Lage sein, ein const
char [] Objekt fiktiv anzulegen, aus dem sich die einzelnen Zeichen
als Initialisierer für ein anderes Objekt extrahieren lassen, aber
welches anschließend im Zuge der Optimierung infolge Nichtverwendung
wieder weggeworfen werden kann.

: Bearbeitet durch Moderator
von Rolf M. (rmagnus)


Lesenswert?

Jörg W. schrieb:
>>> Ich nehme die strikte Aussage, dass er es gar nicht darf, zurück.

War vielleicht voreilig :)

>>> Könnte sein, dass eine solche Ausnahme unter 6.6 Absatz 11 machbar
>>> wäre.
>>
>> Das wird sie ja sowieso schon mit Absatz 10.
>
> Mein Draft war wohl etwas neuer, dort war diese Klausel nach Abschnitt
> 11 gerutscht, die bei dir (und bei einem älteren Draft) noch in 10
> stand.

Meins ist kein Draft, dafür allerdings noch C99.

> “An implementation may accept other forms of constant expressions.”

Ja, den meinte ich. Dachte, du meintest das, was bei mir als 11 steht.
Was ich aber noch in (bei mir) Absatz 9 gefunden habe:

"The array-subscript [] and member-access . and -> operators, the 
address & and indirection * unary operators, and pointer casts may be 
used in the creation of an address constant, but the value of an object 
shall not be accessed by use of these operators."

Da steht eigentlich explizit, dass es verboten ist. Ich denke, dass das 
dann auch Absatz 11 nicht wieder aufheben kann.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Da steht eigentlich explizit, dass es verboten ist.

Man muss es dreimal lesen, aber ich stimme dir am Ende zu.

von Kirsche (Gast)


Lesenswert?

"This Is Why We Can't Have Nice Things"

:(

von Schweineprediger (Gast)


Lesenswert?

Kirsche schrieb:
> "This Is Why We Can't Have Nice Things"
>
> :(

Die haben wir doch (C++), aber die will ja dann keiner.

von Wilhelm M. (wimalopaan)


Lesenswert?

Schweineprediger schrieb:
> Kirsche schrieb:
>> "This Is Why We Can't Have Nice Things"
>>
>> :(
>
> Die haben wir doch (C++), aber die will ja dann keiner.

In C ist auch das hier (static storage) illegal (in C++ ist es ok):
1
// global
2
const int x = 3;
3
const int y = x; // initializer element is not constant

Dafür bekommt man in C++ eine sinnvolle Warnung:
1
char* text1 = "abc"; // Warnung: ISO-C++ forbids converting ...
2
const char* text1 = "abc";

Demzufolge geht in C++ auch:
1
const char date[] PROGMEM = {__DATE__[0], __DATE__[1]}; // ok

Nur mal so am Rande ...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Demzufolge geht in C++ auch:

Ja, und?

Das hat doch absolut niemand in diesem Thread in Frage gestellt,
das war von Anfang an klar.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Wäre es nicht vielleicht einfacher, anstatt das Datum aus dem String 
herauszufrickeln, beim Aufruf des Compilers sich eigene numerische(!) 
Konstanten zu definieren und diese per -D dem Compiler zu übergeben?

-DTAG=9 -DMONAT=5 -DJAHR=2017

Die Werte für diese Konstanten können im Makefile auf welche auch immer 
gewünschte Art und Weise gebildet werden.

Das mag dann zwar vom jeweiligen Betriebssystem abhängig sein, aber es 
belegt exakt 0 Bytes vom kostbaren ROM im jeweiligen µC.


Man könnte natürlich auch versuchen anzuregen, daß die gcc-Bauer 
entsprechende Erweiterungen von __TIME__ und __DATE__ aufnehmen, 
aber da dürfte der Widerstand größer sein als der, der nötig ist, um 
sich ein Skript/Macro/whatever für sein Makefile zu basteln.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rufus Τ. F. schrieb:
> Wäre es nicht vielleicht einfacher, anstatt das Datum aus dem String
> herauszufrickeln, beim Aufruf des Compilers sich eigene numerische(!)
> Konstanten zu definieren und diese per -D dem Compiler zu übergeben?
>
> -DTAG=9 -DMONAT=5 -DJAHR=2017
>
> Die Werte für diese Konstanten können im Makefile auf welche auch immer
> gewünschte Art und Weise gebildet werden.
>
> Das mag dann zwar vom jeweiligen Betriebssystem abhängig sein, aber es
> belegt exakt 0 Bytes vom kostbaren ROM im jeweiligen µC.

Das ist dann genau wie meine Anregung (s.o.) es als constexpr im 
Programm selbst zur Compilezeit parsen zu lassen. Da gab es den Einwand, 
das natürlich auch diese Konstanten als Argumente von z.B. LD (asm) im 
Flash stehen. Belegt also auch Platz im Flash ... ;-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rufus Τ. F. schrieb:
> Die Werte für diese Konstanten können im Makefile auf welche auch immer
> gewünschte Art und Weise gebildet werden.

Das war ja mein Hinweis, es per externem Kommando im Makefile (oder
wie auch immer) zu produzieren, bspw. eben mit Python.

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.