diff --git a/app/src/App.vue b/app/src/App.vue index ae6eec1..eba3e4b 100644 --- a/app/src/App.vue +++ b/app/src/App.vue @@ -1,74 +1,83 @@ diff --git a/app/src/api/core.js b/app/src/api/core.js deleted file mode 100644 index 7dd5a1c..0000000 --- a/app/src/api/core.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Mocking client-server processing - */ - -const _settings = { - // connected wifi - "wifi_ssid": "", - - // oparation mode - "device_mode": "active", - - // Set rotation setting for display - // 0 thru 3 corresponding to 4 cardinal rotations - "device_rotation": 0, - - // deep sleep timer - "cloud_refresh": 97 -} - -import axios from 'axios' - -export default { - - /** - * @param cb - * @returns {PromiseLike | Promise} - */ - getSettings(cb) { - - return _settings - - return axios - .get('/api/settings') - .then(response => cb(response.data)) - } -} diff --git a/app/src/api/device.js b/app/src/api/device.js new file mode 100644 index 0000000..60dcf56 --- /dev/null +++ b/app/src/api/device.js @@ -0,0 +1,80 @@ +/** + * Mocking client-server processing + */ + +// eslint-disable-next-line +const _settings = { + // connected wifi + "wifi_ssid": "", + + // oparation mode + "device_mode": "active", + + // Set rotation setting for display + // 0 thru 3 corresponding to 4 cardinal rotations + "device_rotation": 0, + + // clound server endpoint + "cloud_server": "" +} + +// eslint-disable-next-line +const _wifiScan = [{ "rssi": -59, "ssid": "xd-design.info", "bssid": "38:10:D5:34:80:1B", "channel": 11, "secure": 3 }, { "rssi": -75, "ssid": "FRITZ!Box 7430 JI", "bssid": "38:10:D5:5D:FE:7C", "channel": 1, "secure": 3 }, { "rssi": -87, "ssid": "Vodafone Hotspot", "bssid": "AA:0E:14:BD:50:ED", "channel": 1, "secure": 0 }, { "rssi": -88, "ssid": "WLAN-548426", "bssid": "E0:60:66:55:7F:C5", "channel": 1, "secure": 3 }, { "rssi": -89, "ssid": "Familie Kalinowski", "bssid": "C8:0E:14:BD:50:ED", "channel": 1, "secure": 3 }, { "rssi": -91, "ssid": "WLAN-507287", "bssid": "E0:60:66:48:6C:6B", "channel": 1, "secure": 3 }, { "rssi": -94, "ssid": "TP-LINK_7238", "bssid": "A4:2B:B0:D8:72:38", "channel": 3, "secure": 3 }] + +import axios from 'axios' + +export default { + + /** + * @param cb + * @returns {PromiseLike | Promise} + */ + getSettings(cb) { + return axios + .get('/api/settings') + .then(response => cb(response.data)) + }, + + + /** + * @returns {IDBRequest | Promise} + */ + putSettings(settings, cb) { + return axios + .put('/api/settings', settings, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => cb(response.data)) + }, + + + /** + * scan for wifi in range + * @param {*} cb + */ + wifiScan(cb) { + return cb(_wifiScan) + + // eslint-disable-next-line + return axios + .get('/api/wifi/scan') + .then(response => cb(response.data)) + }, + + + + wifiConnect(ssid, password, cb) { + return axios + .put('/api/wifi/connect', { + ssid: ssid, + password: password + }, { + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => cb(response.data)) + } +} diff --git a/app/src/main.js b/app/src/main.js index 87edff9..e08ab80 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -8,5 +8,5 @@ Vue.config.productionTip = false new Vue({ vuetify, router, - render: h => h(App) + render: h => h(App) }).$mount('#app') diff --git a/app/src/router/index.js b/app/src/router/index.js index 39a10f9..14c29e5 100644 --- a/app/src/router/index.js +++ b/app/src/router/index.js @@ -3,6 +3,7 @@ import VueRouter from 'vue-router' const Dashboard = () => import(/* webpackChunkName: "dashboard" */ '../views/Dashboard') const Settings = () => import(/* webpackChunkName: "settings" */ '../views/Settings') +const Wifi = () => import(/* webpackChunkName: "wifi" */ '../views/Wifi') const Sandbox = () => import(/* webpackChunkName: "sandbox" */ '../views/Sandbox') @@ -12,6 +13,7 @@ export default new VueRouter({ routes: [ { path: '/', component: Dashboard }, { path: '/settings', component: Settings }, + { path: '/wifi', component: Wifi }, { path: '/sandbox', component: Sandbox }, { path: '*', redirect: '/' } diff --git a/app/src/views/Dashboard.vue b/app/src/views/Dashboard.vue index 62a68d4..b887505 100644 --- a/app/src/views/Dashboard.vue +++ b/app/src/views/Dashboard.vue @@ -18,4 +18,4 @@ + \ No newline at end of file diff --git a/app/src/views/Settings.vue b/app/src/views/Settings.vue index ec7dabc..99e8210 100644 --- a/app/src/views/Settings.vue +++ b/app/src/views/Settings.vue @@ -1,18 +1,88 @@ diff --git a/app/src/views/Wifi.vue b/app/src/views/Wifi.vue new file mode 100644 index 0000000..0680108 --- /dev/null +++ b/app/src/views/Wifi.vue @@ -0,0 +1,156 @@ + + + + + \ No newline at end of file diff --git a/app/vue.config.js b/app/vue.config.js index 0500e4f..475a541 100644 --- a/app/vue.config.js +++ b/app/vue.config.js @@ -1,8 +1,17 @@ module.exports = { - "outputDir": "../data/dist", + "outputDir": "../data/dist", "filenameHashing": false, "productionSourceMap": false, - "transpileDependencies": [ - "vuetify" - ] + "transpileDependencies": [ + "vuetify" + ], + devServer: { + proxy: { + '^/api': { + target: 'http://192.168.178.65:80', + ws: true, + changeOrigin: true + }, + } + } } \ No newline at end of file diff --git a/src/app.cpp b/src/app.cpp index 3381271..ab72c1a 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -4,9 +4,7 @@ #include "AsyncJson.h" #include "ArduinoJson.h" #include "settings.h" - -// https://techtutorialsx.com/2018/09/17/esp32-arduino-web-server-serving-external-css-file/ -// https://docs.platformio.org/en/latest/platforms/espressif8266.html#uploading-files-to-file-system-spiffs +#include "device.h" AsyncWebServer server(80); @@ -14,17 +12,18 @@ void setupSettingsGet(); void setupSettingsPost(); void setupWifiScan(); void setupWifiConnect(); +void setupCurrentImage(); void setupApp() { Serial.println("setup configure"); - if (!SPIFFS.begin()) { + if (!SPIFFS.begin()) + { Serial.println("An Error has occurred while mounting SPIFFS"); return; } - // @see https://github.com/me-no-dev/ESPAsyncWebServer // @see https://arduinojson.org/v6/assistant/ @@ -32,27 +31,26 @@ void setupApp() server .serveStatic("/", SPIFFS, "/dist/") .setDefaultFile("index.html") - .setCacheControl("max-age=600") - ; + .setCacheControl("max-age=600"); setupSettingsGet(); setupSettingsPost(); setupWifiScan(); setupWifiConnect(); - + setupCurrentImage(); // TODO response - server.on("/test", HTTP_GET, [] (AsyncWebServerRequest *request) { + server.on("/stats", HTTP_GET, [](AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("application/json"); - DynamicJsonDocument root(1024); + root["heap"] = ESP.getFreeHeap(); - root["ssid"] = WiFi.SSID(); + root["wifi"] = WiFi.SSID(); + root["sleep"] = 97; serializeJson(root, *response); request->send(response); - }); - + }); // CORS //DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); @@ -61,120 +59,141 @@ void setupApp() Serial.println("setup configure - done"); } - void setupSettingsGet() { - server.on("/api/settings", HTTP_GET, [] (AsyncWebServerRequest *request) { + server.on("/api/settings", HTTP_GET, [](AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("application/json"); - DynamicJsonDocument root(1024); - //root["heap"] = ESP.getFreeHeap(); - // NVS.getString("wifi_ssid"); - root["wifi_ssid"] = NVS.getString("wifi_ssid"); // WiFi.SSID(); - root["device_mode"] = "active"; + + //root["wifi_ssid"] = NVS.getString("wifi_ssid"); + root["device_mode"] = NVS.getString("device_mode"); root["device_rotation"] = 0; - root["cloud_refresh"] = 97; // aktueller sleep timer + root["cloud_server"] = NVS.getString("cloud_server"); serializeJson(root, *response); request->send(response); - }); - + }); } - void setupSettingsPost() { - AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/api/settings", [](AsyncWebServerRequest *request, JsonVariant &json) { - JsonObject jsonObj = json.to(); - - Serial.println("/api/settings"); - - NVS.setString("wifi_ssid", jsonObj["wifi_ssid"], true); - - -/* - char hostname[64]; - - strlcpy(hostname, // <- destination - jsonObj["wifi_ssid"] | "example.com", // <- source - sizeof(hostname)); // <- destination's capacity -*/ - Serial.println(NVS.getString("wifi_ssid")); + server.on("/api/settings", HTTP_PUT, [](AsyncWebServerRequest *request) { /* nothing and dont remove it */ }, NULL, [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + DynamicJsonDocument doc(1024); + DeserializationError error = deserializeJson(doc, data); + if (error) { + Serial.print(F("deserializeJson() failed with code ")); + Serial.println(error.c_str()); - // ... - }); - server.addHandler(handler); + request->send(404, "text/plain", ""); + } + else + { + if (doc.containsKey("device_mode")) { + NVS.setString("device_mode", doc["device_mode"]); + } + if (doc.containsKey("cloud_server")) { + NVS.setString("cloud_server", doc["cloud_server"]); + //Serial.println(doc["cloud_server"].as()); + } + request->send(200, "application/ld+json; charset=utf-8", "{}"); + } }); } +/** + * @todo + */ void setupCurrentImage() { - server.on("/current-image", HTTP_GET, [] (AsyncWebServerRequest *request) { - + server.on("/current-image", HTTP_GET, [](AsyncWebServerRequest *request) { + Serial.println("/current-image"); + request->send(SPIFFS, "/currentImage.bin", "image/x-bmp"); + }); - AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/currentImage.bin", "image/vnd.wap.wbmp"); +/* + server.on("/current-image2", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/currentImage.bin", "image/x-bmp"); // image/x-bmp | image/vnd.wap.wbmp //response->addHeader("Content-Encoding", "gzip"); - + //response->addHeader("Content-Disposition", "inline; filename=\"image.wbmp\""); request->send(response); - }); - + }); +*/ } - +/** + * @todo + */ void setupWifiScan() { //First request will return 0 results unless you start scan from somewhere else (loop/setup) //Do not request more often than 3-5 seconds - server.on("/api/wifi/scan", HTTP_GET, [](AsyncWebServerRequest *request){ + server.on("/api/wifi/scan", HTTP_GET, [](AsyncWebServerRequest *request) { String json = "["; int n = WiFi.scanComplete(); - if(n == -2){ + if (n == -2) + { WiFi.scanNetworks(true); - } else if(n){ - for (int i = 0; i < n; ++i){ - if(i) json += ","; - json += "{"; - json += "\"rssi\":"+String(WiFi.RSSI(i)); - json += ",\"ssid\":\""+WiFi.SSID(i)+"\""; - json += ",\"bssid\":\""+WiFi.BSSIDstr(i)+"\""; - json += ",\"channel\":"+String(WiFi.channel(i)); - json += ",\"secure\":"+String(WiFi.encryptionType(i)); - json += "}"; + } + else if (n) + { + for (int i = 0; i < n; ++i) + { + if (i) + { + json += ","; + } + + json += "{"; + json += "\"rssi\":" + String(WiFi.RSSI(i)); + json += ",\"ssid\":\"" + WiFi.SSID(i) + "\""; + json += ",\"bssid\":\"" + WiFi.BSSIDstr(i) + "\""; + json += ",\"channel\":" + String(WiFi.channel(i)); + json += ",\"secure\":" + String(WiFi.encryptionType(i)); + json += "}"; } + WiFi.scanDelete(); - if(WiFi.scanComplete() == -2){ + if (WiFi.scanComplete() == -2) + { WiFi.scanNetworks(true); } } + json += "]"; request->send(200, "application/json", json); json = String(); }); } - +/** + * @todo + */ void setupWifiConnect() { - AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/api/wifi/connect", [](AsyncWebServerRequest *request, JsonVariant &json) { - //JsonObject jsonObj = json.to(); - // TODO save settings + server.on("/api/wifi/connect", HTTP_PUT, [](AsyncWebServerRequest *request) { /* nothing and dont remove it */ }, NULL, [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + DynamicJsonDocument doc(1024); + Serial.println("/api/wifi/connect"); + DeserializationError error = deserializeJson(doc, data); + if (error) { + Serial.print(F("deserializeJson() failed with code ")); + Serial.println(error.c_str()); - AsyncResponseStream *response = request->beginResponseStream("application/json"); - - DynamicJsonDocument root(1024); - root["status"] = true; - root["message"] = ""; - - serializeJson(root, *response); - request->send(response); - - // ist ein restart wichtig? - //ESP.restart(); + request->send(404, "text/plain", ""); + } + else + { + if (doc.containsKey("user")) { + //NVS.setString("device_mode", doc["device_mode"]); + } + if (doc.containsKey("password")) { + //NVS.setString("cloud_server", doc["cloud_server"]); + //Serial.println(doc["cloud_server"].as()); + } - }); - server.addHandler(handler); + request->send(200, "application/ld+json; charset=utf-8", "{}"); + } }); } \ No newline at end of file diff --git a/src/cloud.cpp b/src/cloud.cpp index c519d7a..7e692e7 100644 --- a/src/cloud.cpp +++ b/src/cloud.cpp @@ -7,15 +7,17 @@ #include "display.h" #include "device.h" - // TODO SMART SIGN CONFIG ======== -#define config_PullServer "http://paperdash.sonic.da-tom.com/gateway.php/" // pull server address -String config_UUID = "22805938-2280-8022-3822-385980225980"; // TODO +//#define config_PullServer "http://paperdash.sonic.da-tom.com/gateway.php/" // pull server address +String config_UUID = "22805938-2280-8022-3822-385980225980"; // TODO -//String config_PullServer; -//String config_UUID; +//#define config_PullServer = NVS.getString("cloud_server"); +//String config_UUID = NVS.getString("cloud_server"); // SMART SIGN CONFIG ======== +unsigned long requestInterval = 1000; // 1 sec +unsigned long previousTime = 0; + // runtime data const char *setting_HeaderKeys[] = { // update deep sleep interval @@ -23,21 +25,19 @@ const char *setting_HeaderKeys[] = { // execute firmware update url "UpdateFirmware"}; -//#include -//#define FRAME_BUFFERBUFFE_SIZE GxEPD2_750::WIDTH *GxEPD2_750::HEIGHT / 8 -//PROGMEM unsigned char displayImageBuffer[FRAME_BUFFERBUFFE_SIZE]; - HTTPClient http; -void pullData(); +void requestCloud(); +bool isCloudSetupComplete(); +void updateInterval(unsigned long interval); void setupCloud() { Serial.println("setup cloud"); - // load settings - //config_PullServer = NVS.getString("cloud_gateway"); - config_UUID = NVS.getString("cloud_uuid"); + updateInterval(10); + + SPIFFS.begin(); http.useHTTP10(true); // http1.1 chunked übertragung funktioniert irgendwie nicht http.setTimeout(7000); @@ -48,7 +48,55 @@ void setupCloud() void loopCloud() { - pullData(); + if (isCloudSetupComplete()) + { + if (NVS.getString("device_mode") == "passive") + { + Serial.println("requestCloud()"); + + Serial.println("passive, after this, we go to deep sleep and startover here again"); + } + else + { + unsigned long currentTime = millis(); + + if (currentTime - previousTime >= requestInterval) + { + /* Update the timing for the next time around */ + previousTime = currentTime; + + Serial.println("requestCloud()"); + requestCloud(); + } + } + + } +} + +bool isCloudSetupComplete() +{ + return NVS.getString("cloud_server") != "" && config_UUID != ""; +} + +/** + * interval in seconds + */ +void updateInterval(unsigned long interval) +{ + //Serial.print("updateInterval: "); + //Serial.println(interval); + + // update config + Serial.println("###### config update"); + Serial.println(" set deep sleep interval from: " + String(deviceGetSleepInterval()) + " to " + interval); + Serial.println("###### config update"); + + + // active wait state + requestInterval = interval * 1000; + + // passive deep sleep state + deviceSetSleepInterval(interval); } /** @@ -56,10 +104,11 @@ void loopCloud() * 2. neues bild vom server laden und anzeigen sofern vorhanden * @return bool true on new data to display */ -void pullData() +void requestCloud() { - String pullUrl = String(config_PullServer) + "/" + config_UUID; // + "?deep-sleep=" + String(config_DeepSleepInterval) + "&wakeup=" + getWakeupReason(); + //String pullUrl = String(config_PullServer) + "/" + config_UUID; // + "?deep-sleep=" + String(config_DeepSleepInterval) + "&wakeup=" + getWakeupReason(); + String pullUrl = NVS.getString("cloud_server") + "/" + config_UUID; // + "?deep-sleep=" + String(config_DeepSleepInterval) + "&wakeup=" + getWakeupReason(); Serial.println(pullUrl); http.begin(pullUrl); @@ -75,17 +124,12 @@ void pullData() if (false && DeepSleepInterval.toInt() == 0) { // disable deep sleep - Serial.println("###### deep sleep disabled"); - deviceSetSleepInterval(0); + //Serial.println("###### deep sleep disabled"); + //deviceSetSleepInterval(0); } else if (DeepSleepInterval.toInt() > 5 && DeepSleepInterval.toInt() != deviceGetSleepInterval()) { - // update config - Serial.println("###### config update"); - Serial.println(" set deep sleep interval from: " + String(deviceGetSleepInterval()) + " to " + DeepSleepInterval); - Serial.println("###### config update"); - - deviceSetSleepInterval(DeepSleepInterval.toInt()); + updateInterval(DeepSleepInterval.toInt()); } // update to new firmware diff --git a/src/device.cpp b/src/device.cpp index 64447fc..a35a8dd 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -1,81 +1,85 @@ #include "device.h" +#include "settings.h" #define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */ RTC_DATA_ATTR int bootCount = 0; -RTC_DATA_ATTR long config_DeepSleepInterval = 0; +RTC_DATA_ATTR long config_DeepSleepInterval = 10; // sec +unsigned long bootTime = 0; // private methods void sleepDevice(); -String getWakeupReason(); - +bool isBootTimeOver(); /** * setup deep sleep mode */ void setupDevice() { - // increment boot number and print it every reboot - bootCount++; - - // TODO muss in der cloud passieren? config wakeup timer - //deviceSetSleepInterval(300); + // increment boot number and print it every reboot + bootCount++; + bootTime = millis(); } - void loopDevice() { - if (config_DeepSleepInterval > 0) - { - sleepDevice(); - // device stop here - } + if (NVS.getString("device_mode") == "passive") + { + // TODO give user a time window to switch the mode to active + if (true) // isBootTimeOver() + { + sleepDevice(); + // device stop here + } + } } - void sleepDevice() { - Serial.println("Going to sleep now"); - Serial.flush(); + Serial.println("Going to sleep now"); + Serial.flush(); esp_sleep_enable_timer_wakeup(config_DeepSleepInterval * uS_TO_S_FACTOR); - esp_deep_sleep_start(); + esp_deep_sleep_start(); } - void deviceSetSleepInterval(long interval) { config_DeepSleepInterval = interval; } - long deviceGetSleepInterval() { return config_DeepSleepInterval; } -String getWakeupReason() +bool isBootTimeOver() { - esp_sleep_wakeup_cause_t wakeup_reason; - wakeup_reason = esp_sleep_get_wakeup_cause(); - - //return String(wakeup_reason); - - switch (wakeup_reason) - { - case 1: - return String("Wakeup caused by external signal using RTC_IO"); - case 2: - return String("Wakeup caused by external signal using RTC_CNTL"); - case 3: - return String("Wakeup caused by timer"); - case 4: - return String("Wakeup caused by touchpad"); - case 5: - return String("Wakeup caused by ULP program"); - default: - return String("Wakeup was not caused by deep sleep: " + String(wakeup_reason)); - } + return millis() - bootTime >= 60000; +} - return String("unkown"); +String getWakeupReason() +{ + esp_sleep_wakeup_cause_t wakeup_reason; + wakeup_reason = esp_sleep_get_wakeup_cause(); + + //return String(wakeup_reason); + + switch (wakeup_reason) + { + case 1: + return String("Wakeup caused by external signal using RTC_IO"); + case 2: + return String("Wakeup caused by external signal using RTC_CNTL"); + case 3: + return String("Wakeup caused by timer"); + case 4: + return String("Wakeup caused by touchpad"); + case 5: + return String("Wakeup caused by ULP program"); + default: + return String("Wakeup was not caused by deep sleep: " + String(wakeup_reason)); + } + + return String("unkown"); } \ No newline at end of file diff --git a/src/display.cpp b/src/display.cpp index d6e046a..38f112e 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -91,4 +91,4 @@ void printSplash() display.setCursor(x, y); display.print(Hello); } while (display.nextPage()); -} \ No newline at end of file +} diff --git a/src/image.cpp b/src/image.cpp new file mode 100644 index 0000000..715faaf --- /dev/null +++ b/src/image.cpp @@ -0,0 +1,298 @@ +#include +#include + +typedef struct { + uint16_t x1; + uint16_t y1; + uint16_t x2; + uint16_t y2; +} dispWin_t; + +dispWin_t dispWin = { + .x1 = 0, + .y1 = 0, + .x2 = 640, // DEFAULT_TFT_DISPLAY_WIDTH, + .y2 = 384, // DEFAULT_TFT_DISPLAY_HEIGHT, +}; +uint8_t image_debug; + +// @see https://github.com/Bodmer/TJpg_Decoder/blob/master/examples/SPIFFS_Jpg/SPIFFS_Jpg.ino + +// @see https://github.com/loboris/ESP32_TFT_library/blob/aa21772f54a71887ec08b2e8bbaef9e304009891/components/tft/tft.c +void TFT_bmp_image(int x, int y, uint8_t scale, char *fname, uint8_t *imgbuf, int size) +{ + FILE *fhndl = NULL; + struct stat sb; + int i, err=0; + int img_xsize, img_ysize, img_xstart, img_xlen, img_ystart, img_ylen; + int img_pos, img_pix_pos, scan_lines, rd_len; + uint8_t tmpc; + uint16_t wtemp; + uint32_t temp; + int disp_xstart, disp_xend, disp_ystart, disp_yend; + uint8_t buf[56]; + char err_buf[64]; + uint8_t *line_buf[2] = {NULL,NULL}; + uint8_t lb_idx = 0; + uint8_t *scale_buf = NULL; + uint8_t scale_pix; + uint16_t co[3] = {0,0,0}; // RGB sum + uint8_t npix; + + if (scale > 7) scale = 7; + scale_pix = scale+1; // scale factor ( 1~8 ) + + if (fname) { + // * File name is given, reading image from file + if (stat(fname, &sb) != 0) { + sprintf(err_buf, "opening file"); + err = -1; + goto exit; + } + size = sb.st_size; + fhndl = fopen(fname, "r"); + if (!fhndl) { + sprintf(err_buf, "opening file"); + err = -2; + goto exit; + } + + i = fread(buf, 1, 54, fhndl); // read header + } + else { + // * Reading image from buffer + if ((imgbuf) && (size > 54)) { + memcpy(buf, imgbuf, 54); + i = 54; + } + else i = 0; + } + + sprintf(err_buf, "reading header"); + if (i != 54) {err = -3; goto exit;} + + // ** Check image header and get image properties + if ((buf[0] != 'B') || (buf[1] != 'M')) {err=-4; goto exit;} // accept only images with 'BM' id + + memcpy(&temp, buf+2, 4); // file size + if (temp != size) {err=-5; goto exit;} + + memcpy(&img_pos, buf+10, 4); // start of pixel data + + memcpy(&temp, buf+14, 4); // BMP header size + if (temp != 40) {err=-6; goto exit;} + + memcpy(&wtemp, buf+26, 2); // the number of color planes + if (wtemp != 1) {err=-7; goto exit;} + + memcpy(&wtemp, buf+28, 2); // the number of bits per pixel + if (wtemp != 24) {err=-8; goto exit;} + + memcpy(&temp, buf+30, 4); // the compression method being used + if (temp != 0) {err=-9; goto exit;} + + memcpy(&img_xsize, buf+18, 4); // the bitmap width in pixels + memcpy(&img_ysize, buf+22, 4); // the bitmap height in pixels + + + // * scale image dimensions +/* + img_xlen = img_xsize / scale_pix; // image display horizontal size + img_ylen = img_ysize / scale_pix; // image display vertical size + + if (x == CENTER) x = ((dispWin.x2 - dispWin.x1 + 1 - img_xlen) / 2) + dispWin.x1; + else if (x == RIGHT) x = dispWin.x2 + 1 - img_xlen; + + if (y == CENTER) y = ((dispWin.y2 - dispWin.y1 + 1 - img_ylen) / 2) + dispWin.y1; + else if (y == BOTTOM) y = dispWin.y2 + 1 - img_ylen; +*/ + if ((x < ((dispWin.x2 + 1) * -1)) || (x > (dispWin.x2 + 1)) || (y < ((dispWin.y2 + 1) * -1)) || (y > (dispWin.y2 + 1))) { + sprintf(err_buf, "out of display area (%d,%d", x, y); + err = -10; + goto exit; + } + + // ** set display and image areas + if (x < dispWin.x1) { + disp_xstart = dispWin.x1; + img_xstart = -x; // image pixel line X offset + img_xlen += x; + } + else { + disp_xstart = x; + img_xstart = 0; + } + if (y < dispWin.y1) { + disp_ystart = dispWin.y1; + img_ystart = -y; // image pixel line Y offset + img_ylen += y; + } + else { + disp_ystart = y; + img_ystart = 0; + } + disp_xend = disp_xstart + img_xlen - 1; + disp_yend = disp_ystart + img_ylen - 1; + if (disp_xend > dispWin.x2) { + disp_xend = dispWin.x2; + img_xlen = disp_xend - disp_xstart + 1; + } + if (disp_yend > dispWin.y2) { + disp_yend = dispWin.y2; + img_ylen = disp_yend - disp_ystart + 1; + } + + if ((img_xlen < 8) || (img_ylen < 8) || (img_xstart >= (img_xsize-2)) || ((img_ysize - img_ystart) < 2)) { + sprintf(err_buf, "image too small"); + err = -11; + goto exit; + } + + // ** Allocate memory for 2 lines of image pixels + line_buf[0] = static_cast(heap_caps_malloc(img_xsize*3, MALLOC_CAP_DMA)); + if (line_buf[0] == NULL) { + sprintf(err_buf, "allocating line buffer #1"); + err=-12; + goto exit; + } + + line_buf[1] = static_cast(heap_caps_malloc(img_xsize*3, MALLOC_CAP_DMA)); + if (line_buf[1] == NULL) { + sprintf(err_buf, "allocating line buffer #2"); + err=-13; + goto exit; + } + + if (scale) { + // Allocate memory for scale buffer + rd_len = img_xlen * 3 * scale_pix; + scale_buf = static_cast(malloc(rd_len*scale_pix)); + if (scale_buf == NULL) { + sprintf(err_buf, "allocating scale buffer"); + err=-14; + goto exit; + } + } + else rd_len = img_xlen * 3; + + // ** ***************************************************** ** + // ** BMP images are stored in file from LAST to FIRST line ** + // ** ***************************************************** ** + + /* Used variables: + img_xsize horizontal image size in pixels + img_ysize number of image lines + img_xlen image display horizontal scaled size in pixels + img_ylen image display vertical scaled size in pixels + img_xstart first pixel in line to be displayed + img_ystart first image line to be displayed + img_xlen number of pixels in image line to be displayed, starting with 'img_xstart' + img_ylen number of lines in image to be displayed, starting with 'img_ystart' + rd_len length of color data which are read from image line in bytes + */ + + // Set position in image to the first color data (beginning of the LAST line) + img_pos += (img_ystart * (img_xsize*3)); + if (fhndl) { + if (fseek(fhndl, img_pos, SEEK_SET) != 0) { + sprintf(err_buf, "file seek at %d", img_pos); + err = -15; + goto exit; + } + } + + if (image_debug) printf("BMP: image size: (%d,%d) scale: %d disp size: (%d,%d) img xofs: %d img yofs: %d at: %d,%d; line buf: 2* %d scale buf: %d\r\n", + img_xsize, img_ysize, scale_pix, img_xlen, img_ylen, img_xstart, img_ystart, disp_xstart, disp_ystart, img_xsize*3, ((scale) ? (rd_len*scale_pix) : 0)); + + // * Select the display + //disp_select(); + + while ((disp_yend >= disp_ystart) && ((img_pos + (img_xsize*3)) <= size)) { + if (img_pos > size) { + sprintf(err_buf, "EOF reached: %d > %d", img_pos, size); + err = -16; + goto exit1; + } + if (scale == 0) { + // Read the line of color data into color buffer + if (fhndl) { + i = fread(line_buf[lb_idx], 1, img_xsize*3, fhndl); // read line from file + if (i != (img_xsize*3)) { + sprintf(err_buf, "file read at %d (%d<>%d)", img_pos, i, img_xsize*3); + err = -16; + goto exit1; + } + } + else memcpy(line_buf[lb_idx], imgbuf+img_pos, img_xsize*3); + + if (img_xstart > 0) memmove(line_buf[lb_idx], line_buf[lb_idx]+(img_xstart*3), rd_len); + // Convert colors BGR-888 (BMP) -> RGB-888 (DISPLAY) === + for (i=0; i < rd_len; i += 3) { + tmpc = line_buf[lb_idx][i+2] & 0xfc; // save R + line_buf[lb_idx][i+2] = line_buf[lb_idx][i] & 0xfc; // B -> R + line_buf[lb_idx][i] = tmpc; // R -> B + line_buf[lb_idx][i+1] &= 0xfc; // G + } + img_pos += (img_xsize*3); + } + else { + // scale image, read 'scale_pix' lines and find the average color + for (scan_lines=0; scan_lines size) break; + if (fhndl) { + i = fread(line_buf[lb_idx], 1, img_xsize*3, fhndl); // read line from file + if (i != (img_xsize*3)) { + sprintf(err_buf, "file read at %d (%d<>%d)", img_pos, i, img_xsize*3); + err = -17; + goto exit1; + } + } + else memcpy(line_buf[lb_idx], imgbuf+img_pos, img_xsize*3); + img_pos += (img_xsize*3); + + // copy only data which are displayed to scale buffer + memcpy(scale_buf + (rd_len * scan_lines), line_buf[lb_idx]+img_xstart, rd_len); + } + + // Populate display line buffer + for (int n=0;n<(img_xlen*3);n += 3) { + memset(co, 0, sizeof(co)); // initialize color sum + npix = 0; // initialize number of pixels in scale rectangle + + // sum all pixels in scale rectangle + for (int sc_line=0; sc_line RGB-888 (DISPLAY) + line_buf[lb_idx][n+2] = (uint8_t)(co[0] / npix); // B + line_buf[lb_idx][n+1] = (uint8_t)(co[1] / npix); // G + line_buf[lb_idx][n] = (uint8_t)(co[2] / npix); // R + } + } + + //wait_trans_finish(1); + //send_data(disp_xstart, disp_yend, disp_xend, disp_yend, img_xlen, (color_t *)line_buf[lb_idx]); + lb_idx = (lb_idx + 1) & 1; // change buffer + + disp_yend--; + } + err = 0; +exit1: + //disp_deselect(); +exit: + if (scale_buf) free(scale_buf); + if (line_buf[0]) free(line_buf[0]); + if (line_buf[1]) free(line_buf[1]); + if (fhndl) fclose(fhndl); + if ((err) && (image_debug)) printf("Error: %d [%s]\r\n", err, err_buf); + + //return err; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b077094..e25425c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,7 @@ void setup() if (wlan_isConnected()) { - //setupCloud(); + setupCloud(); } setupApp(); @@ -44,7 +44,7 @@ void loop() if (wlan_isConnected()) { - //loopCloud(); + loopCloud(); } loopDevice();