Diese Seite ist statisches HTML und am besten betrachtet mit Java-Script eingeschaltet!

Mikroprozessor ESP8266 Programmierung auf einem Badgy

 ·  ☕ 10 Min. Lesen  ·  🤖 SWU

badgy front photo
badgy-front.jpg: badgy-front.jpg

badgy back photo
badgy-back.jpg: badgy-bacl.jpg

Vor ein paar Jahren hatte ich mir ein schönes Gerät gekauft. Es hat ein ca. Scheckkarten großes E-Paper und eine darunter liegende Platine mit einer ausgeklügelten Mixtur moderner Mikroprozessor Technologie, damit es seiner Aufgabe gerecht werden kann, eine elektronische Plakette auf einer Hacker Konferenz sein zu können. Erfahren hatte ich davon auf Hackaday. Ein E-Paper, das man mit eigenen Ideen befüllen kann, gleich eingebaut auf einer Platine mit Mikroprozessoren, die es ansteuern können - ein Traum. Wenig später habe ich es mir von Tindie gekauft. Das ist der Anfang meiner Mikroprozessor Erkundung geworden und liegt lange zurück, weil ich damals noch Null Kenntnisse von C-Programmierung hatte. Inzwischen ist das besser geworden. Beim Rumkramen ist mir die Schachtel wieder in die Hände gefallen und nun wollte ich mal sehen, was ich damit machen kann.

Auf der Entwicklerseite steht Opensource in Form von Beispielen bereit, die Weiterentwicklung von Softwaremodulen hat aber dazu geführt, dass das mir liebste Beispiel, die Wetter Station, nicht funktionieren wollte und am Ende ist dabei herausgekommen, dass ich das Beispiel auf die aktuellen Anforderungen und Möglichkeiten angepasst habe. Es läuft jetzt mit Version 6 der Arduinojson Bibliothek und verwendet die kostenlose One Call Api von Openweathermap.

Programmiert habe ich in der Arduino IDE auf meinem Debian Linux Rechner. Solange man nicht gemurkst hat und der Aufruf der OTA Funktion mögich ist, kann man das Kompilat USB-Anschluss schonend über die Luft, sprich über Wlan übertragen. Wenn das Gerät aber abgestürzt ist, bleibt nur noch USB. Was mich an der Arduino IDE gestört hat ist, dass Fehler mit Zeilenangaben nicht in die Zeilennummer springen, wenn man die anklickt. Deswegen werde ich wohl eine andere IDE verwenden, wenn ich damit weitermachen sollte. Ansonsten fand ich sie aber ausreichend und zielführend. Ich programmiere aber ja auch nicht jeden Tag oder viel.

Bei der Wetter Station Programmierung mit der neuen Openweathermap Api hat mir geholfen, die http-Anforderung mit wget auf einem Konsolen-Fenster durchzuführen und die Antwort als Datei zu speichern. Die Datei ist im Json-Format und kann gut im Firefox betrachtet werden. So konnte ich die Struktur gut erfassen und die neue Auswertung im Programmcode aktualisieren.

Die Dokumentation der für mich neuen ArduinoJson Bibleothek auf der Seite des Entwicklers hat mir auch gut geholfen, die Anpassung vorzunehmen.

Ich bin sehr gespannt, wie lange die Ein-Akku-Powerbank den Badgy betreiben kann. Deswegen habe ich die Angabe eines Zeitstempels mit aufgenommen. Experimente, deutsche Ausgabe hinzukriegen, waren enttäuschend, da die time Bibliothek deutsche Umlaute nicht kann. Aber so ist das wohl in der Mikroprozessorwelt, denn dort ist jedes Byte wertvoll, weil man davon nicht soviel hat. Immerhin ist die Wetter Station ja schon eigentlich eine grafische Desktop Applikation und ich finde es bemerkenswert, wie gut das läuft.

Update Dienstag, 22. Febuarar 09:15 Uhr: Die Powerbank hat ihr Ladungsende erreicht. 6 Tage hat sie gehalten. Nicht schlecht, oder?

Hier ist der Code, meine Wetter.ino:

  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
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/* e-paper display lib */
#include <GxEPD.h>
//Use the GxGDEW029T5 class if you have Badgy Rev 2C. Make sure you are on GxEPD 3.05 or above
//#include <GxGDEW029T5/GxGDEW029T5.h>
//Use the GxGDEH029A1 class if you have anything older
#include <GxGDEH029A1/GxGDEH029A1.h>
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>
/* include any other fonts you want to use https://github.com/adafruit/Adafruit-GFX-Library */
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold18pt7b.h>
#include "icons.h"
/* WiFi  libs*/
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
#include <WiFiManager.h>
/* Util libs */
#include <Time.h>
#include <TimeLib.h>
#include <ArduinoJson.h>

// Code taken from https://github.com/sqfmi/badgy - Thank you very much!
// and modified. The free account at openweathermap now allows you to get data from the onecall api, current weather and forecast included.
// AdruinoJson has a new version 6,now and the old code, well, I wasn't able to get the right side to display with v5.* versions - I always got JSON parsing failed!
// So I decided to get all of the news at once and it works like a charm!

const char* host = "api.openweathermap.org";
const char* API_KEY = "YOURKEY"; // Your API key https://home.openweathermap.org/
const String CITY_LAT = "YOURLAT"; // get yours from e. g. https://www.latlong.net/
const String CITY_LON = "YOURLON";
const String CITY_LANG = "de"; // https://openweathermap.org/api/one-call-api#multi
const int time_zone = +1; // e.g. UTC-05:00 = -5
const boolean IS_METRIC_UNITS = true; //true metrics, false imperial

/* Always include the update server, or else you won't be able to do OTA updates! */
/**/const int port = 8888;
/**/ESP8266WebServer httpServer(port);
/**/ESP8266HTTPUpdateServer httpUpdater;
/*                                                                                */

/* Configure pins for display */
GxIO_Class io(SPI, SS, 0, 2);
GxEPD_Class display(io); // default selection of D4, D2

void setup()
{  
  display.init();
  
  pinMode(1,INPUT_PULLUP); //down
  pinMode(3,INPUT_PULLUP); //left
  pinMode(5,INPUT_PULLUP); //center
  pinMode(12,INPUT_PULLUP); //right
  pinMode(10,INPUT_PULLUP); //up

  /* WiFi Manager automatically connects using the saved credentials, if that fails it will go into AP mode */
  WiFiManager wifiManager;
  wifiManager.setAPCallback(configModeCallback);
  wifiManager.autoConnect("Badgy AP");

  if(digitalRead(5) == 0) {
    /* Once connected to WiFi, startup the OTA update server if the center button is held on boot */
    httpUpdater.setup(&httpServer);
    httpServer.begin();
    showIP();
    while(1){
      httpServer.handleClient();
    }
  }
  // normal Startup
  if (getWeatherData()) {
    // Success in getting weather an forecast data.  Sleep for an 3600e6 microseconds -- an hour.
    ESP.deepSleep(3600e6, WAKE_RF_DEFAULT); 
  } else {
    // A failure of some sort.  Wait for 5 seconds and then retry.
    ESP.deepSleep(5e6, WAKE_RF_DEFAULT); 
  }
}

void loop()
{  
  // loop is never executed in this program as the setup does all the work
  // then puts the ESP into a deep sleep which will cause a reset at the
  // conclusion which runs setup again.
}

void configModeCallback (WiFiManager *myWiFiManager) {
  display.setRotation(3); //even = portrait, odd = landscape
  display.fillScreen(GxEPD_WHITE);
  const GFXfont* f = &FreeMonoBold9pt7b ;
  display.setTextColor(GxEPD_BLACK);
  display.setFont(f);
  display.setCursor(0,50);
  display.println("Connect to Badgy AP");
  display.println("to setup your WiFi!");
  display.update();  
}

void showText(char *text)
{
  display.setRotation(3); //even = portrait, odd = landscape
  display.fillScreen(GxEPD_WHITE);
  const GFXfont* f = &FreeMonoBold9pt7b ;
  display.setTextColor(GxEPD_BLACK);
  display.setFont(f);
  display.setCursor(10,70);
  display.println(text);
  display.println("Auto-retry in 5 seconds.");
  display.update();
}

void showIP() {
  display.setRotation(3); //even = portrait, odd = landscape
  display.fillScreen(GxEPD_WHITE);
  const GFXfont* f = &FreeMonoBold9pt7b ;
  display.setTextColor(GxEPD_BLACK);
  display.setFont(f);
  display.setCursor(0,10);

  String url = WiFi.localIP().toString() + ":"+String(port)+"/update";
  byte charArraySize = url.length() + 1;
  char urlCharArray[charArraySize];
  url.toCharArray(urlCharArray, charArraySize);

  display.println("You are now connected!");
  display.println("");  
  display.println("Go to:");
  display.println(urlCharArray);
  display.println("to upload a new sketch.");
  display.update();  
}

String getUnitsString() {
  if (IS_METRIC_UNITS) { 
    return "metric";
  } else {
    return "imperial";
  }
}

bool getWeatherData()
{
  String url = "/data/2.5/onecall?lat="+CITY_LAT+"&lon="+CITY_LON+"&exclude=minutely,hourly,alerts&units="+getUnitsString()+"&lang="+CITY_LANG+"&appid="+API_KEY;

  // Use WiFiClient class to create TCP connections
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    showText("connection failed");
    return false;
  }
  
  // This will send the request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");
  unsigned long timeout = millis();
  while (client.available() == 0) {
    if (millis() - timeout > 5000) {
      showText(">>> Client Timeout !");
      client.stop();
      return false;
    }
  }

  // Read response
  while(client.available()){
    char status[32] = {0};
    client.readBytesUntil('\r', status, sizeof(status));
    if(strcmp(status, "HTTP/1.1 200 OK") != 0){
      showText("HTTP Status Error!");
      return false;
    }

    /* Find the end of headers */
    char endOfHeaders[] = "\r\n\r\n";
    if (!client.find(endOfHeaders)) {
      showText("Invalid Response...");
      return false;
    }

    /* Start parsing the JSON in the response body */
    DynamicJsonDocument doc(8192); 
    // test data was 7293 told by https://arduinojson.org/v6/assistant/
    DeserializationError err = deserializeJson(doc, client);
    if (err) {
      //Serial.println(err.f_str()); 
      //Above doesn't work, output is garbage
      // Have read hints that it might be the crystal frequency
      showText("Error in deserialisation!");
      return false;
    }
    // bulding left side of the current weather display
    int time = doc["current"]["dt"];
    time = time +(time_zone*60*60);
    setTime(time);
    float vc_temp = doc["current"]["temp"];
    int c_condition = doc["current"]["weather"][0]["id"];
    String c_icon_code = doc["current"]["weather"][0]["icon"];
    int c_humidity = doc["current"]["humidity"];
    String c_dew_point = doc["current"]["dew_point"];
    float vc_wind = doc["current"]["wind_speed"];
    int c_temp = (vc_temp + 0.5);
    int c_wind = (vc_wind + 0.5);
    
    const unsigned char *icon;
    icon = getIcon(c_condition, c_icon_code, false);
    display.setRotation(3); //even = portrait, odd = landscape
    display.fillScreen(GxEPD_WHITE);
    display.drawBitmap(icon, -5, 5, 80, 80, GxEPD_WHITE);

    // Current fetching time
    const GFXfont* small = &FreeMonoBold9pt7b;
    display.setTextColor(GxEPD_BLACK);
    display.setFont(small);
    display.setCursor(50,11);
    display.print(hour());
    display.print(":");
    display.print(minute());
    
    // Current weekday, month and year
    display.setCursor(120,11);
    display.print(dayShortStr(weekday()));
    display.print(" ");
    display.print(monthShortStr(month()));
    //display.print(monthGerman[month()]);
    display.print(" ");
    display.print(day());
    display.print(" ");
    display.print(year());
    // Current Wind
    display.drawBitmap(strong_wind_small, 0, 62, 48, 48, GxEPD_WHITE);
    display.setCursor(50,92);
    if (IS_METRIC_UNITS) {
      display.print(String((int)(c_wind*3.6))+"km/h");
    } else {
      display.print(String((int)c_wind)+" mph");
    }
    // Current Humidity and dew point
    display.drawBitmap(humidity_small, 0, 97, 32, 32, GxEPD_WHITE);
    display.setCursor(42,119);
    display.print(String(c_humidity)+"%");
    display.print("/");
    if (IS_METRIC_UNITS) {
      display.print(c_dew_point + "C");
    } else {
      display.println(c_dew_point + "F");
    }
    // Current temperature
     display.setCursor(72,55);
    const GFXfont* big = &FreeMonoBold18pt7b;
    display.setFont(big);
    if (IS_METRIC_UNITS) {
      display.println(String((int)c_temp) + "C");
    } else {
      display.println(String((int)c_temp) + "F");
    }
    // building right side of the forecast display
    // reducing forecast to first 3 of 7 total days:
    for(int i=1; i<=3; i++){
      String icon_code = doc["daily"][char(i)]["weather"][0]["icon"];
      int condition =    doc["daily"][char(i)]["weather"][0]["id"];
      float vtemp =         doc["daily"][char(i)]["temp"]["max"];
      int time =         doc["daily"][char(i)]["dt"];
      int temp = (vtemp + 0.5);
      time = time + (time_zone*60*60);
      setTime(time);
      int offset = 100; //x offset for forecast block
      const unsigned char *icon;
      icon = getIcon(condition, icon_code, true);
      display.drawBitmap(icon, (offset+(i*48)), 58, 48, 48, GxEPD_WHITE); //(icon, pos_x, pos_y, size_x, size_y, bg)
      // Day of forecast
      const GFXfont* small = &FreeMonoBold9pt7b;
      display.setTextColor(GxEPD_BLACK);
      display.setFont(small);
      display.setCursor((offset+7+(i*48)),60);
      display.print(dayShortStr(weekday()));
      // Temperature of forecast
      display.setCursor((offset+10+(i*48)),115);
      display.print(String(temp));
      }
    display.update();
    return true;
  }
}

const unsigned char * getIcon(int condition, String icon_code, bool small) {
    if(condition <= 232){
      return small ? thunderstorm_small : thunderstorm;
    }else if(condition >= 300 && condition <= 321){
      return small ? showers_small : showers;
    }else if(condition >= 500 && condition <= 531){
      return small ? rain_small : rain;
    }else if(condition >= 600 && condition <= 602){
      return small ? snow_small : snow;
    }else if(condition >= 611 && condition <= 612){
      return small ? sleet_small : sleet;
    }else if(condition >= 615 && condition <= 622){
      return small ? rain_mix_small : rain_mix;
    }else if(condition == 701 || condition == 721 || condition == 741){
      return small ? fog_small : fog;
    }else if(condition == 711){
      return small ? smoke_small : smoke;
    }else if(condition == 731){
      return small ? sandstorm_small : sandstorm;
    }else if(condition == 751 || condition == 761){
      return small ? dust_small : dust;
    }else if(condition == 762){
      return small ? volcano_small : volcano;
    }else if(condition == 771){
      return small ? strong_wind_small : strong_wind;
    }else if(condition == 781){
      return small ? tornado : tornado_small;
    }else if(condition == 800){
      if(icon_code == "01d"){
        return small ? day_sunny_small : day_sunny;
      }else{
        return small ? night_clear_small : night_clear;
      }
    }else if(condition == 801 || condition == 802){
      if(icon_code == "02d" || icon_code == "03d"){
        return small ? day_cloudy_small : day_cloudy;
      }else{
        return small ? night_cloudy_small : night_cloudy;
      }
    }else if(condition == 803 || condition == 804){
      if(icon_code == "04d"){
        return small ? cloudy_small : cloudy;
      }else{
        return small ? night_cloudy_small : night_cloudy;
      }
    }
}

wüsti
Author
SWU
human