概要
事前準備
利用方法
バグと改良予定
M5Stack Core2でも動作しますが、ボタンまわりの処理(タッチスクリーンで擬似的にボタンを処理)を最適化していないため、Core2ではボタンまわりの操作で挙動不審な箇所があります。(R.1.34で修正しました)
この業界はマニュアルをよく読まない人が多いので、ここで注意喚起! 下記のスケッチだけではコンパイルできません!。 次のページで説明している個別の設定ファイルが必要です。
deserializeJson() + filterの使い方が何か間違っている気がする....メモリー関係をだましだまし使っているような....辛うじて動いている状態.. ...
255行目付近の
DynamicJsonDocument mapData(52428);
の値を少し小さめにすると安定するかもしれません。
あまり小さくすると、地図が表示されなくなります。この調整が微妙......
/** -*- c -*- Copyright (C) 2021 by Takeki Yahiro. All Rights Reserved. $Id: ktm5stack-20230214A.ino,v 1.1 2023/02/14 00:28:12 yahiro Exp $ https://ketaitracker.info/ktm5stack/ */ /* ** API Document ** https://docs.m5stack.com/en/api/system ** Core2 ** https://docs.m5stack.com/en/arduino/arduino_home_page?id=m5core2_api ** https://github.com/m5stack/M5Core2/tree/master/src */ /* 荐紂磴祀礫粹寂梧├磴祿祚祀禺礒 */ #include "N0CALL.h" //#define USE_M5STACK_CORE2 //#define USE_GPS_GROVE /** system configuration **/ const char * version = "20230214A"; #ifndef KTPOS_SERVER #define KTPOS_SERVER "https://150.95.133.242/ktm5stack/" #endif #define KTPOS_PROTOCOL_VERSION 1 char ktpos_server[] = KTPOS_SERVER; /* TODO (bugfix etc) ャ秧禮祚禹蘂綺繹。礒祟礑閻茵腓坂臀竢汐蜿寂皿秧禮祚竊≪鐚鐚 <祉若梧;腓削膂゛繹с鐚 ヨ茘絮鏗竕慨竢 USE TFT_eSprite class https://lang-ship.com/blog/work/m5stickc-display2/ <≪禺竏恭礫禹禹禹祟礑禹 */ // // 篏糸膵肬experimental鐚 //#define USE_MULTI_TASK //#define USE_MULTI_TASK_GPS #define USE_MAPDATA_CLEAR //#define USE_SPRITE #ifdef USE_M5STACK_CORE2 #include <M5Core2.h> #else #include <M5Stack.h> #endif #include <M5Display.h> /* https://github.com/mikalhart/TinyGPSPlus */ #include <TinyGPS++.h> /* Arduino Json Lib https://github.com/bblanchon/ArduinoJson https://arduinojson.org/ */ #include <ArduinoJson.h> #include <Arduino.h> #ifdef USE_SPRITE #define VRAM vram #else #define VRAM M5.Lcd #endif /* // for indoor #define COLOR_COAST TFT_GREEN #define COLOR_ROAD M5.Lcd.color565( 30, 30, 30 ) #define COLOR_RAIL M5.Lcd.color565( 60, 60, 60 ) */ // for outdoor #define COLOR_COAST TFT_GREEN //#define COLOR_ROAD M5.Lcd.color565( 170, 170, 170 ) #define COLOR_ROAD 44373 //#define COLOR_RAIL M5.Lcd.color565( 130, 130, 230 ) #define COLOR_RAIL 33820 #define COLOR_OTHER_FOOTPRINT TFT_YELLOW #define COLOR_MY_FOOTPRINT TFT_GREEN #define COLOR_MY_POSITION TFT_DARKGREEN #if 0 #define SEMA_GET getSema() #define SEMA_REL releaseSema() #else #define SEMA_GET #define SEMA_REL #endif int my_course = 0; int my_speed = 0; /* #ifndef ALERT_STATIONS #define ALERT_STATIONS "" #endif */ #ifdef ALERT_STATIONS int beep = 1; #include <map> struct Alert { public: float dist ; // 莊 unsigned long lastshow; // }; std::map<std::string, Alert> alert; #endif #include <WiFi.h> #include <WiFiMulti.h> #include <HTTPClient.h> HTTPClient http; #ifdef USE_OTA #include <WiFiUdp.h> #include <ArduinoOTA.h> #endif #define USE_M5STACK #ifndef CX #define CX 160 #endif #ifndef CY #define CY 120 #endif #ifndef UPDATE_INTERVAL #define UPDATE_INTERVAL 30 #endif // // 医活;腓冴у幻筝 // //#define NORTH_UP // // GPS脂≪違竏;腓婚祟蜈墾濶綺鏗禺禮祟祀礬 // #define COLOR_SPEED TFT_GREEN #define COLOR_ALTITUDE M5.Lcd.color565( 192, 192, 192 ) #define COLOR_ALTITUDE2 M5.Lcd.color565( 50, 50, 50 ) #define COLOR_COURSE TFT_ORANGE #define STATUS_AREA_HEIGHT 7 #define STATUS_BUSY 1 #define STATUS_IDLE -1 #define STATUS_NONE 0 unsigned long next_send_time; WiFiMulti wifiMulti; #define DISPLAY_MODE_GPS 1 #define DISPLAY_MODE_MAP 2 #define DISPLAY_MODE_RADAR 3 #define DISPLAY_MODE_LIST 4 #define DISPLAY_MODE_GOOGLEMAP 5 #define DISPLAY_MODE_N 4 #ifndef TARGET_STATIONS #define TARGET_STATIONS "*" #endif #ifndef START_NORTH_UP #define START_NORTH_UP true #endif #ifdef DEFAULT_DISPLAY_MODE int displayMode = DEFAULT_DISPLAY_MODE; #else int displayMode = DISPLAY_MODE_MAP; #endif // DISPLAY_MODE_LIST int display_list_target = 0; // 0 :all 1:mobile only // DISPLAY_MODE_MAP int zoom = 4; int mapHeading = 0; // North up bool north_up = START_NORTH_UP; // 篆<若翠 uint32_t txByte = 0; uint32_t rxByte = 0; uint16_t txRxCount = 0; int statusBusy = 0; /* This sample code demonstrates the normal use of a TinyGPS++ (TinyGPSPlus) object. It requires the use of SoftwareSerial, and assumes that you have a 4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx). */ static const uint32_t GPSBaud = 9600; #ifdef USE_GPS // The TinyGPS++ object TinyGPSPlus gps; // The serial connection to the GPS device HardwareSerial ss(2); #endif TaskHandle_t taskHandle[4]; #ifdef USE_SPRITE TFT_eSprite vram = TFT_eSprite( &M5.Lcd ); #endif //DynamicJsonDocument mapData(65536); // 64KB , *2 128KB DynamicJsonDocument mapData(52428); // 64KB , *2 128KB // //StaticJsonDocument<65536> mapData; //StaticJsonDocument<55536> mapData; void setup() { M5.begin(); #ifdef USE_M5STACK_CORE2 // M5.Axp.SetSpkEnable(true ); // 絎篏心N竢 #else M5.Power.begin(); M5.Speaker.begin(); M5.Speaker.mute(); #endif #ifdef USE_GPS #ifdef USE_GPS_GROVE ss.begin( GPSBaud, SERIAL_8N1, 33, 32 ); #else ss.begin(GPSBaud); #endif #endif #if 0 M5.Speaker.setVolume( 1 ); M5.Speaker.tone( 800, 5); delay( 100 ); M5.Speaker.mute(); #endif USE_SERIAL.begin(115200 ); USE_SERIAL.println(); USE_SERIAL.println(); USE_SERIAL.println(); #if 0 USE_SERIAL.println( M5.Lcd.color565( 130, 130, 230 )); USE_SERIAL.println( TFT_YELLOW ); #endif #ifdef USE_SPRITE USE_SERIAL.println( F("Sprite Mode") ); vram.setColorDepth( 8 ); vram.createSprite( 320, 240); //vram.setSwapBytes( true ); #endif dispAppTitle(); initWiFi(); connectWiFi(); for (uint8_t t = 4; t > 0; t--) { USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); USE_SERIAL.flush(); dispLcdColor( 300, 225, 2, TFT_DARKGREY, "%d", t ); delay(1000); } #ifdef ALERT_STATIONS #ifdef ALERT_BEEP beep = 1; #else beep = 0; #endif initAlert(); #endif /* M5.Lcd.drawPngUrl( "http://192.168.24.10/~yahiro/ktpos/m5stack.png", 0, 0 ); */ smartDelay( 2000 ); next_send_time = millis(); // ms USE_SERIAL.println( "start xTaskCreatePinnedToCore()" ); /* Priolity 0:low 25:high */ xTaskCreatePinnedToCore(taskWatchDog, "taskWatchDog", 1024, NULL, 1 /* Priolity */ , &taskHandle[3], 1 /* Core ID */); #ifdef USE_GPS #ifdef USE_MULTI_TASK_GPS xTaskCreatePinnedToCore(taskGPS, "taskGPS", 4096, NULL, 10 /* Priolity */ , &taskHandle[0], 1 /* Core ID */); #endif #endif #ifdef USE_MULTI_TASK xTaskCreatePinnedToCore(taskDisplay, "taskDisplay", 8192 , NULL, 5 /* Priolity */ , &taskHandle[1], 1 /* Core ID */); USE_SERIAL.println( "use multitask fot updatePosition()" ); xTaskCreatePinnedToCore(taskUpdatePosition, "taskUpdatePosition", 8192 /* 16384 *//* 32768 */, NULL, 20 /* Priolity */ , &taskHandle[2], 1 /* Core ID */); #endif smartDelay( 2000 ); M5.update(); initDisplay(); #ifdef USE_M5STACK_CORE2 #endif #ifdef USE_OTA initOta(); #endif } bool availableChange = true; void clearHttp() { if ( http.connected()) { USE_SERIAL.println( "call http.end()" ); http.end(); } } /* Button A */ void doBtnA() { USE_SERIAL.println( "Button A" ); clearHttp(); displayMode++; if ( displayMode > DISPLAY_MODE_N ) { #ifdef USE_GPS displayMode = 1; #else displayMode = 2; #endif } initDisplay(); } /* Button B */ void doBtnB() { USE_SERIAL.println( "Button B" ); switch ( displayMode ) { case DISPLAY_MODE_MAP: case DISPLAY_MODE_RADAR: zoom = zoom + 1; clearHttp(); initDisplay(); break; case DISPLAY_MODE_LIST: display_list_target = (display_list_target + 1) % 2; clearHttp(); initDisplay(); break; } } /* Button B 傑若 */ void doBtnBLong() { USE_SERIAL.println( "Btn B Long Pressed" ); switch ( displayMode ) { case DISPLAY_MODE_MAP: case DISPLAY_MODE_RADAR: clearHttp(); #ifndef NORTH_UP changeNorthup(); #endif break; } } /* Button C */ void doBtnC() { USE_SERIAL.println( "Button C" ); switch ( displayMode ) { case DISPLAY_MODE_MAP: case DISPLAY_MODE_RADAR: clearHttp(); zoom = zoom - 1; zoom = max( 1, zoom ); initDisplay(); break; case DISPLAY_MODE_LIST: #ifdef ALERT_STATIONS beep = beep ? 0 : 1; if ( beep ) { dispLcdColorBg(250, 220, 2, TFT_BLACK, TFT_GREEN, "O N" ); } else { dispLcdColorBg(250, 220, 2, TFT_BLACK, TFT_ORANGE, "OFF" ); } #endif break; } smartDelay( 100 ); } void loop() { //static bool btnALong = false; static bool btnBLong = false; //static bool btnCLong = false; #ifdef USE_M5STACK_CORE2 static bool btn_mapHeading = false; TouchPoint_t pos = M5.Touch.getPressPoint(); #endif M5.update(); #ifdef USE_M5STACK_CORE2 #if 1 if ( 0 <= pos.x && pos.x <= 60 && 0 <= pos.y && pos.y <= 60 && btn_mapHeading == false ) { btn_mapHeading = true; //USE_SERIAL.println( "Btn Press mapHeading" ); doBtnBLong(); } else if ( pos.x == -1 ) { btn_mapHeading = false; } #endif #if 1 static int touchBtnCurrent = 0; // 緇羲秧祿竢治秧礫祿祕蕾罩鐚 if ( 0 <= pos.x && pos.x <= 106 && 240 <= pos.y && pos.y <= 280 ) { if ( touchBtnCurrent != 1 ) { touchBtnCurrent = 1; doBtnA(); } } else if ( 107 <= pos.x && pos.x <= 213 && 240 <= pos.y && pos.y <= 280 ) { if ( touchBtnCurrent != 2 ) { touchBtnCurrent = 2; doBtnB(); } } else if ( 214 <= pos.x && pos.x <= 320 && 240 <= pos.y && pos.y <= 280 ) { if ( touchBtnCurrent != 3 ) { touchBtnCurrent = 3; doBtnC(); } } else { touchBtnCurrent = 0; } #endif #if 0 if ( M5.BtnA.wasPressed()) { doBtnA(); } if ( M5.BtnB.wasPressed()) { doBtnB(); } if ( M5.BtnC.wasPressed()) { doBtnC(); } #endif #else // normal M5STACK if ( M5.BtnA.wasReleased()) { doBtnA(); } if ( M5.BtnB.pressedFor( 1000 )) { if ( !btnBLong ) { btnBLong = true; doBtnBLong(); } } if ( M5.BtnB.wasReleased()) { if ( btnBLong ) { btnBLong = false; } else { doBtnB(); } } if ( M5.BtnC.wasReleased()) { doBtnC(); } #endif #ifndef USE_MULTI_TASK_GPS #ifdef USE_GPS doGps(); #endif #endif #ifndef USE_MULTI_TASK updatePosition(); doDisplay(); #endif #if defined( USE_OTA ) ArduinoOTA.handle(); #endif smartDelay( 1 ); } #ifndef NORTH_UP /* 若鴻≪c潟違≪綽礒 */ int changeNorthup() { north_up = (north_up ? false : true); if ( north_up ) { M5.Lcd.fillCircle( 160, 200, 15, TFT_BLACK ); drawCompassRaw( 160, 200, 15, 0 ); smartDelay( 360 ); mapHeading = 0; } else { for ( int news = 0; news <= 360; news += 10 ) { M5.Lcd.fillCircle( 160, 200, 15, TFT_BLACK ); drawCompassRaw( 160, 200, 15, news ); smartDelay( 10 ); } } initDisplay(); return 0; } #endif void dispAppTitle() { int news; dispLcdColorCenter( /*76,*/ 40, 2, TFT_GREEN, "%s", F("KetaiTracker") ); // 12 char, x = 160 - strlen( xxxx )/2 * 14 , font-size 1=6px 2=12px 2=18px;... dispLcdColorCenter( /*83,*/ 70, 2, TFT_GREEN, "%s", F("for M5Stack") ); // 11 char dispLcdColorCenter( /*41,*/ 110, 2, TFT_WHITE, version ); // 17char 160 - 17/2* 14 #ifdef DEMO dispLcdColorCenter( /* 106, */ 127, 1, TFT_RED, "%s", F("Demonstration Mode") ); #endif dispLcdColorCenter( 200, 1, TFT_WHITE, "(C) JF6LZE" ); dispLcdColorCenter( 212, 1, TFT_WHITE, "All Rights Reserved" ); dispLcdColorCenter( /* 94,*/ 160, 2, TFT_ORANGE, CALLSIGN ); #ifdef USE_M5STACK_CORE2 // dispLcdColorCenter( 0, 2, TFT_WHITE, "%fV %fW", M5.Axp.GetBatVoltage(), M5.Axp.GetBatChargeCurrent()); #endif for ( news = 0; news <= 360; news += 30 ) { M5.Lcd.fillCircle( 20, 20, 15, TFT_BLACK ); drawCompassRaw( 20, 20, 15, news ); smartDelay( 30 ); } } #ifdef USE_OTA // ref. sample sketch BasicOTA.ino void initOta() { ArduinoOTA .onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) type = "sketch"; else // U_SPIFFS type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() USE_SERIAL.println("Start updating " + type); }) .onEnd([]() { USE_SERIAL.println("\nEnd"); }) .onProgress([](unsigned int progress, unsigned int total) { USE_SERIAL.printf("Progress: %u%%\r", (progress / (total / 100))); }) .onError([](ota_error_t error) { USE_SERIAL.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) USE_SERIAL.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) USE_SERIAL.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) USE_SERIAL.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) USE_SERIAL.println("Receive Failed"); else if (error == OTA_END_ERROR) USE_SERIAL.println("End Failed"); }); ArduinoOTA.begin(); USE_SERIAL.println( "OTA Enabled" ); } #endif #ifdef ALERT_STATIONS int initAlert() { char * buf; buf = (char *)malloc( strlen( ALERT_STATIONS ) + 1 ); strcpy( buf, ALERT_STATIONS ); USE_SERIAL.println( "initAlert()" ); char * p; for ( p = strtok( buf, "," ); p != NULL ; p = strtok( NULL, "," )) { USE_SERIAL.println( p ); alert[p].dist = 100000; } return 0; } /* ヨ茘 */ void checkAlert( const String payload ) { int n; const char * callsign; float dist; USE_SERIAL.println( F("checkAlert()") ); #ifndef USE_M5STACK_CORE2 int tone_freq; #endif DynamicJsonDocument filter(200); filter["near"] = true; filter["cnt"] = true; #ifdef USE_MAPDATA_CLEAR mapData.clear(); #endif DeserializationError err = deserializeJson( mapData, payload , DeserializationOption::Filter( filter) ); // 2021.12.23 #if 1 if ( err.code() != DeserializationError::Ok ) { USE_SERIAL.println( F(" error on drawMapLocalStations") ); return; } #endif n = mapData["cnt"]; // USE_SERIAL.print( "checkAlert(); " ); for (int i = 0; i < n ; i++ ) { callsign = mapData["near"][i]["callsign"]; dist = mapData["near"][i]["dists"]; //USE_SERIAL.print( callsign ); auto itr = alert.find( callsign ); if ( itr != alert.end()) { USE_SERIAL.print( " Alert Station = " ); USE_SERIAL.println( callsign ); // 腟礑祚磴禺秧eset if ( alert[callsign].lastshow + 1800000 < (millis() )) { // 30min 篁礬磧禺秧礙祟蓙炊 alert[callsign].dist = 99999; USE_SERIAL.print( F("reset alert ") ); USE_SERIAL.print( callsign ); USE_SERIAL.print( F(" ") ); USE_SERIAL.print( alert[callsign].lastshow ); USE_SERIAL.print( F(" ") ); USE_SERIAL.print( millis()); USE_SERIAL.println( "" ); } if ( dist < alert[callsign].dist ) { // km USE_SERIAL.print( "close to you! " ); USE_SERIAL.println( dist ); alert[callsign].dist = dist; alert[callsign].lastshow = millis(); if ( beep ) { #ifdef USE_M5STACK_CORE2 #else tone_freq = 440 + 440 * ( 1 - dist / 20.0); tone_freq = max( tone_freq, 440 ); M5.Speaker.tone( tone_freq, 80 ); delay( 80 ); M5.Speaker.mute(); if ( dist < 1 ) { delay( 40 ); M5.Speaker.tone( tone_freq, 80 ); delay( 80 ); M5.Speaker.mute(); } if ( dist < 0.5 ) { delay( 40 ); M5.Speaker.tone( tone_freq, 80 ); delay( 80 ); M5.Speaker.mute(); } #endif } } } else { } } USE_SERIAL.println( "..done" ); } #endif /* GPS綽ゞ肆;腓 */ void dispGpsStatus() { #ifdef USE_GPS uint16_t bcolor; uint16_t fcolor; #endif if ( statusBusy ) return; #ifdef USE_GPS fcolor = TFT_BLACK; switch ( (int)gps.satellites.value()) { case 0: bcolor = TFT_RED; break; case 1: case 2: case 3: bcolor = TFT_ORANGE; break; default: bcolor = TFT_BLACK; fcolor = TFT_GREEN; break; } dispLcdColorBg( 272, 0, 1, fcolor, bcolor, "GPS" ); #else #endif } void taskWatchDog( void * arg ) { static int check = 0; while ( 1 ) { if ( statusBusy > 0 ) { check++; if ( check > 15 ) { USE_SERIAL.println( "watch dog 15 sec!" ); } if ( check > 20 ) { USE_SERIAL.println( "Reset by watchdog" ); #ifdef USE_M5STACK_CORE2 M5.shutdown( 1 ); #else M5.Power.reset(); #endif } } else { check = 0; } smartDelay( 1000 ); } } #ifdef USE_GPS void doGps() { if (gps.location.isValid() ) { setGraphDataInterval( gps.speed.kmph(), gps.altitude.meters(), gps.course.deg() ); } dispGpsStatus(); } void taskGPS( void * arg) { while ( 1 ) { doGps(); smartDelay( 1000 ); } } #endif void doDisplay() { switch ( displayMode ) { case DISPLAY_MODE_GPS: #ifdef USE_GPS dispGps( ); smartDelay( 500 ); #else #endif break; case DISPLAY_MODE_MAP : case DISPLAY_MODE_RADAR: showNearGps(); smartDelay( 500 ); break; #ifdef USE_GOOGLEMAP case DISPLAY_MODE_GOOGLEMAP: showGoogleMap( 0 ); smartDelay( 5000 ); break; #endif default: smartDelay( 500 ); break; } } void taskDisplay( void * arg) { while ( 1 ) { doDisplay(); } smartDelay( 500 ); } void taskUpdatePosition( void * arg ) { while ( 1 ) { updatePosition(); smartDelay( 1000 ); // 1000ms GPS Freq? } } /* 若粋;腓咲祚礫 */ void clearDisplay() { M5.Lcd.fillRect( 0, STATUS_AREA_HEIGHT, 320, 240, TFT_BLACK ); } /* ** 鴻ゃ帥拷祚礫 */ void clearStatus() { M5.Lcd.fillRect( 0, 0, 320, STATUS_AREA_HEIGHT, TFT_BLACK ); } void initDisplay() { USE_SERIAL.println( "initDisplay() start" ); clearStatus(); clearDisplay(); dispLcdColorCenter( 120, 2, TFT_YELLOW, "WAIT" ); USE_SERIAL.println( "WAIT" ); switch ( displayMode ) { case DISPLAY_MODE_GPS: USE_SERIAL.println( "DISPLAY_MODE_GPS" ); dispLcdColor( 0, 220, 2, TFT_GREEN, CALLSIGN ); initGraph(); smartDelay( 1 ); break; case DISPLAY_MODE_MAP : case DISPLAY_MODE_RADAR: USE_SERIAL.println( "DISPLAY_MODE_MAP or RADAR" ); showNearGps(); smartDelay(1); break; case DISPLAY_MODE_LIST: USE_SERIAL.println( "DISPLAY_MODE_LIST" ); switch ( display_list_target ) { case 0: //dispLcdColor( 0, 100, 2, TFT_YELLOW, "List " ); dispLcdColorCenter( 80, 2, TFT_YELLOW, "List" ); break; case 1: dispLcdColorCenter( 80, 2, TFT_YELLOW, "List (mobile)" ); break; } smartDelay( 1 ); break; #ifdef USE_GOOGLEMAP case DISPLAY_MODE_GOOGLEMAP: dispLcdColor( 0, 100, 2, TFT_GREEN, "Google Map" ); showGoogleMap( 1 ); break; #endif default: USE_SERIAL.println( "DISPLAY_MODE_DEFAULT may be error" ); break; } updateDisplay(); USE_SERIAL.println( "initDisplay() end" ); } /* 篆∞倶茵腓 */ #define COMM_BEGIN 1 #define COMM_END -1 #define COMM_NONE 0 void dispTxRxByte() { dispLcdColor( 295, 230, 1, TFT_GREEN, "%4d", txRxCount ); /* dispLcdColor( 295, 194, 1, TFT_GREEN, "%4d", uxTaskGetStackHighWaterMark(taskHandle[0])); dispLcdColor( 295, 202, 1, TFT_GREEN, "%4d", uxTaskGetStackHighWaterMark(taskHandle[1])); dispLcdColor( 295, 210, 1, TFT_GREEN, "%4d", uxTaskGetStackHighWaterMark(taskHandle[2])); */ } void dispCommStatus( int sw ) { static int busy = 0; uint32_t txRxByte ; const char * unit = "KB"; busy += sw; txRxByte = (uint16_t)((txByte + rxByte) / 1024); if ( txRxByte >= 8192 ) { txRxByte = (uint16_t) (txRxByte / 1024); unit = "MB"; } dispLcdColorBg( 231, 0, 1, (busy ? TFT_BLACK : TFT_GREEN ), (busy ? TFT_ORANGE : TFT_BLACK ), "%4d%s", txRxByte, unit ); dispTemperature(); } /* System Status (busy|idle) */ void setStatus( int sw ) { statusBusy += sw; dispLcdColorBg( 295, 0, 1, ( statusBusy > 0 ? TFT_BLACK : TFT_GREEN ) , ( statusBusy > 0 ? TFT_ORANGE : TFT_BLACK ), "%s", ( statusBusy > 0 ? F("Busy") : F("Idle") ) ); } /* 羝綺祟茵腓冴鐚蕭PU罩≪障鐚 */ void dispTemperature() { #ifdef USE_DTOSTRF char buf[16]; dtostrf( temperatureRead(), 4, 1, buf ); dispLcdColor( 200, 0, 1, TFT_WHITE, "%s" , buf); #else dispLcdColor( 200, 0, 1, TFT_WHITE, "%.1f" , temperatureRead() ); #endif //USE_SERIAL.println( temperatureRead() ); } void dispStatus() { /* common */ clearStatus(); setStatus( STATUS_NONE ); dispGpsStatus(); dispCommStatus( COMM_NONE ); //dispTemperature(); switch ( displayMode ) { case DISPLAY_MODE_GPS: break; case DISPLAY_MODE_MAP: case DISPLAY_MODE_RADAR: showNearGps(); break; case DISPLAY_MODE_LIST: break; } } int updateDisplay() { USE_SERIAL.println( F("updateDisplay() start") ); updatePositionToServer( 880 ); USE_SERIAL.println( F("updateDisplay() end") ); return 0; } void initWiFi() { int n; char * ssid, * password; char buf[256]; int ret; n = sizeof( ap_list ) / sizeof( char * ); USE_SERIAL.println( n ); for ( int i = 0 ; i < n ; i++ ) { sprintf( buf, "%s", ap_list[i] ); ssid = strtok( buf, "," ); password = strtok( NULL, "," ); USE_SERIAL.println( ssid ); USE_SERIAL.println( password ); wifiMulti.addAP( ssid, password ); } ret = wifiMulti.run(); //USE_SERIAL.println( ret ); } void connectWiFi() { switch ( wifiMulti.run()) { case WL_CONNECTED: USE_SERIAL.println( F("Wifi Connected") ); return; case WL_CONNECT_FAILED: USE_SERIAL.println( F("Wifi connect failed") ); break; case WL_DISCONNECTED: USE_SERIAL.println( F("Wifi disconnected") ); break; } } /* 320 x 240 TFT */ #ifdef USE_GPS void dispGps( ) { #ifdef USE_DTOSTRF char buf[32]; #endif if ( statusBusy ) return; #ifdef FIX_MODE dispLcdColor( 0, 30, 3, TFT_WHITE, "%9.4f", HOME_LAT ); dispLcdColor( 0, 60, 3, TFT_WHITE, "%9.4f", HOME_LNG ); #else M5.Lcd.setCursor( 0, 0 ); M5.Lcd.setTextSize( 1 ); printDateTime(gps.date, gps.time); #ifdef USE_DTOSTRF dtostrf( gps.location.lat(), 9, 4, buf ); dispLcdColor( 0, 30, 3, TFT_WHITE, "%s", buf ); dtostrf( gps.location.lng(), 9, 4, buf ); dispLcdColor( 0, 60, 3, TFT_WHITE, "%s", buf ); #else dispLcdColor( 0, 30, 3, TFT_WHITE, "%9.4f", (float)gps.location.lat() ); dispLcdColor( 0, 60, 3, TFT_WHITE, "%9.4f", (float)gps.location.lng() ); #endif dispLcdColor( 0, 90, 3, TFT_WHITE, "%4d", (int)gps.satellites.value() ); dispLcdColor( 120, 95, 2, TFT_WHITE, "Sat." ); dispLcdColor( 200, 30, 3, TFT_WHITE, "%3d", (int)gps.speed.kmph()); dispLcdColor( 255, 35, 2, COLOR_SPEED, "km/h" ); dispLcdColor( 200, 60, 3, TFT_WHITE, "%3d", (int)gps.course.deg()); dispLcdColor( 255, 65, 2, COLOR_COURSE, "deg" ); dispLcdColor( 183, 90, 3, TFT_WHITE, "%4d", (int)gps.altitude.meters() ); dispLcdColor( 255, 95, 2, COLOR_ALTITUDE, "mH" ); /* Graph */ drawGraphAll(); #endif } #endif /* 篆<帥ゃ潟 */ int updatePosition() { #ifdef USE_GPS static int lastHeading = -1; int heading = -1; int headingDiff = 0; #endif USE_SERIAL.println( F( "updatePsotin() start" )); if ( millis() >= next_send_time ) { updatePositionToServer( 440 ); next_send_time = millis() + UPDATE_INTERVAL * 1000; } else { #ifdef USE_GPS if ( gps.location.isValid()) { if ( lastHeading == -1 ) { lastHeading = (int)gps.course.deg(); } else { heading = (int)gps.course.deg(); headingDiff = abs( lastHeading - heading ) ; if ( headingDiff > 180 ) { headingDiff = 360 - headingDiff; } // debug dispLcdColor( 30, 220, 3, TFT_WHITE, "%2d", headingDiff ); if ( headingDiff > 10 ) { updatePositionToServer( 1600 ); next_send_time = millis() + UPDATE_INTERVAL * 1000; lastHeading = heading; } } } #endif } smartDelay( 100 ); USE_SERIAL.println( F("updateDisplay() end") ); return 0; } #define ACCESS_MODE_GET 1 #define ACCESS_MODE_SET 2 int updatePositionToServer( int freq ) { static int busy = 0; static int notConnect = 0; static int seq = 0; if ( statusBusy ) return 1; if ( busy ) return 1; busy = 1; #ifdef BEEP_ON_SEND int tone_freq = freq; M5.Speaker.tone( tone_freq, 80 ); delay( 40 ); M5.Speaker.mute(); #endif #ifdef DEMO displayMode++; if ( displayMode > DISPLAY_MODE_N ) { displayMode = 0; clearStatus(); clearDisplay(); dispAppTitle(); smartDelay( 5000 ); M5.Lcd.qrcode( "https://ketaitracker.info/ktm5stack/" ); smartDelay( 5000); displayMode = 1; clearStatus(); clearDisplay(); } #endif setStatus( STATUS_BUSY ); if ((wifiMulti.run() == WL_CONNECTED)) { // HTTPClient http; char params[80]; char url[1024]; #ifndef USE_GPS char alt[10]; char course[10]; char spd[10]; #endif int mode = ACCESS_MODE_GET; float lat, lng; if ( notConnect ) { //dispLcdColor( 50, 210, 2, TFT_RED, " ", notConnect ); dispLcdColor( 50, 210, 2, TFT_RED, " " ); /* M5.Lcd.setCursor( 107, 210 ); M5.Lcd.setTextColor( TFT_RED, TFT_BLACK ); M5.Lcd.setTextSize( 2 ); M5.Lcd.print( " " ); */ notConnect = 0; } // display current IP address if ( displayMode == DISPLAY_MODE_GPS ) { //noInterrupts(); M5.Lcd.setTextColor( TFT_WHITE, TFT_BLACK ); M5.Lcd.setTextSize( 2 ); M5.Lcd.setCursor( 107, 220 ); M5.Lcd.print( WiFi.localIP()); //interrupts(); } #ifdef USE_GPS if (gps.location.isValid() ) { mode = ACCESS_MODE_SET; } #else mode = ACCESS_MODE_GET; #endif txRxCount++; #ifdef FIX_MODE lat = HOME_LAT; lng = HOME_LNG; mode = ACCESS_MODE_GET; mapHeading = 0; #else #ifdef USE_GPS if ( gps.location.isValid()) { lat = gps.location.lat(); lng = gps.location.lng(); if ( lat == 0.0 || lng == 0.0 ) { mode = ACCESS_MODE_GET; } } else { lat = 0.0; lng = 0.0; mode = ACCESS_MODE_GET; } #else lat = 0.0; lng = 0.0; #endif #ifndef NORTH_UP if ( !north_up ) { #ifdef USE_GPS if ( gps.location.isValid()) { mapHeading = (int)gps.course.deg(); } else { mapHeading = 0; } #else mapHeading = 0; #endif } #endif #endif #ifdef USE_GPS USE_SERIAL.println( gps.altitude.feet() ); #ifdef USE_DTOSTRF dtostrf( gps.altitude.feet(), 0, 0, alt ); dtostrf( gps.course.deg(), 0, 0, course ); dtostrf( gps.speed.knots(), 0, 0, spd ); sprintf( params, "pos=%f,%f,%s,%s,%s", lat, lng, alt, course, spd ); #else sprintf( params, "pos=%.5f,%.5f,%d,%d,%d", lat, lng, (int)gps.altitude.feet(), (int)gps.course.deg(), (int)gps.speed.knots() ); #endif #else strcpy( alt, "0" ); strcpy( course, "0" ); strcpy( spd, "0" ); sprintf( params, "pos=%f,%f,%s,%s,%s", lat, lng, alt, course, spd ); #endif seq++; #ifdef DEMO sprintf( url, "%s?cmd=%s&%s&c=%s&p=%d&z=%d&d=%d&a=%d&t=%s&v=%d&cx=%d&cy=%d&nu=%d&demo=1&seq=%d", ktpos_server, "get", params, CALLSIGN, PASSCODE, zoom, displayMode, display_list_target, TARGET_STATIONS, KTPOS_PROTOCOL_VERSION, CX, CY, (north_up ? 1 : 0), seq ); #else sprintf( url, "%s?cmd=%s&%s&c=%s&p=%d&z=%d&d=%d&a=%d&t=%s&v=%d&cx=%d&cy=%d&nu=%d&st=%d&seq=%d", ktpos_server, ( mode == ACCESS_MODE_GET ? "get" : "set" ), params, CALLSIGN, PASSCODE, zoom, displayMode, display_list_target, TARGET_STATIONS, KTPOS_PROTOCOL_VERSION, CX, CY, (north_up ? 1 : 0), #ifdef USE_GPS (int)gps.satellites.value(), #else 0, #endif seq ); #endif //http.setTimeout( 10000 ); smartDelay( 1 ); txByte += strlen( url ); // txByte += http.getSize(); dispCommStatus( COMM_BEGIN ); http.begin( url ); // "http://192.168.24.10/"); //HTTP USE_SERIAL.print( F("http.GET() start :") ); USE_SERIAL.println( url ); int httpCode = http.GET(); USE_SERIAL.println( F("http.GET() end") ); dispCommStatus( COMM_END ); smartDelay(1 ); if (httpCode > 0) { if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); //USE_SERIAL.println(payload); rxByte += payload.length(); // 括<♂潟若 -> 医潟祿祿禮禹祟祀繹鐚鐚坂鐚 if ( mode == ACCESS_MODE_GET ) { DynamicJsonDocument my(200); StaticJsonDocument<200>filter; filter["my"] = true; DeserializationError err = deserializeJson( my, payload, DeserializationOption::Filter(filter)); #if 1 // 2021.12.23 if ( err.code() != DeserializationError::Ok ) { http.end(); return -1; } #endif #if 1 switch (err.code()) { case DeserializationError::Ok: USE_SERIAL.println(F("Deserialization succeeded on ACCESS_MODE_GET")); break; case DeserializationError::InvalidInput: dispErrorMsg( "Invalid input!" ); http.end(); return -1; break; case DeserializationError::NoMemory: dispErrorMsg( "Not enough memory" ); http.end(); return -1; break; case DeserializationError::IncompleteInput: USE_SERIAL.println(F("Deserialization ImcomplementInput")); http.end(); return -1; break; case DeserializationError::TooDeep: USE_SERIAL.println(F("Deserialization TooDeep")); http.end(); return -1; break; default: USE_SERIAL.println(F("Deserialization failed on ACCESS_MODE_GET ")); USE_SERIAL.println( err.c_str()); USE_SERIAL.println( payload ); http.end(); return -1; break; } #endif my_course = my["my"]["course"]; my_speed = my["my"]["spd"]; #ifndef NORTH_UP if ( !north_up ) { mapHeading = my["my"]["course"]; } #endif } switch ( displayMode ) { case DISPLAY_MODE_MAP: case DISPLAY_MODE_RADAR: #ifdef DEMO initDisplay(); #endif showNear( payload ); break; case DISPLAY_MODE_LIST: #ifdef DEMO initDisplay(); #endif showList( payload ); } #ifdef ALERT_STATIONS checkAlert( payload ); #endif } } else { USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); char buf[512]; sprintf( buf, "ERROR: %s\n", http.errorToString(httpCode).c_str()); M5.Lcd.setCursor( 0, 230 ); M5.Lcd.setTextColor( TFT_RED , TFT_BLACK); M5.Lcd.setTextSize( 1 ); M5.Lcd.print( buf ); } http.end(); setStatus( STATUS_IDLE ); } else { /* M5.Lcd.setCursor( 107, 210 ); M5.Lcd.setTextColor( TFT_RED, TFT_BLACK ); M5.Lcd.setTextSize( 2 ); M5.Lcd.print( "Not Connect"); */ setStatus( STATUS_IDLE ); notConnect++; dispLcdColor( 50, 210, 2, TFT_RED, "Wi-Fi NotConnect (%d)", notConnect ); smartDelay( 100 ); } dispCommStatus( COMM_NONE ); busy = 0; return 0; } void showMap() { //https://jetloger.com/m5stack-m5camera-capture-lcd/#anker12 // https://github.com/m5stack/M5Stack/blob/master/src/M5Display.h // https://github.com/m5stack/M5Stack/blob/f10648586cc4324d844cfd9505cfa47d3d61b7ca/src/M5Display.cpp // M5.Lcd.drawPngUrl(); } #ifdef USE_GOOGLEMAP int showGoogleMap( int reset ) { static int olat, olng ; int lat, lng; const char * url = "https://maps.googleapis.com/maps/api/staticmap"; char buf[256]; if ( reset ) { olat = 0; olng = 0; } USE_SERIAL.println( "showGoogelMap()" ); if ( gps.location.isValid()) { drawCurrentPositionGps(); lat = round( gps.location.lat() * 1000 ); lng = round( gps.location.lng() * 1000 ); if ( olat != lat && olng != lng ) { sprintf( buf, "%s?center=%f,%f&zoom=10&size=320x240&key=%s", url, (float)gps.location.lat(), (float)gps.location.lng(), GOOGLE_MAP_API_KEY ); USE_SERIAL.println( buf ); M5.Lcd.drawPngUrl( buf, 0, 0 ); drawCurrentPositionGps(); } else { USE_SERIAL.println( "not moved" ); } olat = lat; olng = lng; } else { M5.Lcd.drawPngUrl( "http://192.168.24.10/~yahiro/ktpos/m5stack.png", 0, 0 ); } return 0; } #endif void showNearGps() { #ifdef USE_DTOSTRF char buf[64]; #endif uint16_t color; #ifdef USE_GPS #ifdef USE_DTOSTRF dtostrf( gps.location.lat(), 8, 4, buf ); dispLcdColor( 0, 0, 1, TFT_WHITE, "%s", buf ); dtostrf( gps.location.lng(), 9, 4, buf ); dispLcdColor( 50, 0, 1, TFT_WHITE, "%s", buf ); #else dispLcdColor( 0, 0, 1, TFT_WHITE, "%8.4f", (float)gps.location.lat() ); dispLcdColor( 50, 0, 1, TFT_WHITE, "%9.4f", (float)gps.location.lng() ); #endif #endif switch ( zoom ) { case 1: case 2: case 3: case 4: // 蕭初 color = TFT_RED; break; case 5: // 井劫初 color = TFT_ORANGE; break; case 6: // 筝肢井合 color = TFT_YELLOW; break; case 7: // 絽堺 color = TFT_BLUE; break; default: // 莊 color = TFT_WHITE; break; } float range = (float)(160.0 / pow( 2, zoom)); /* MAP range */ if ( range >= 2.0 ) { dispLcdColor( 130, 0, 1, color, "%2dkm", (int)range); } else { dispLcdColor( 130, 0, 1, color, "%3dm", (int)(range * 1000.0) ); } //dispLcdColor( 160, 0, 1, TFT_WHITE, "%d", zoom ); } #define DRAW_MAP_SHAPE_DOT 0 #define DRAW_MAP_SHAPE_LINE 1 /** 紫綺ф荐膊 news 剛(0-360) dist 莊km) return dx, dy 綺ф鐚dx, dy鐚 */ void calcXY( float news, float dist, int * dx, int * dy ) { int s, x, y; #ifndef NORTH_UP if ( !north_up ) { news -= mapHeading; } #endif s = (int)((360.0 - news) + 90.0) % 360; dist = dist * pow( 2, zoom ); // x = dist * cos( s * 0.01745 ); // / 180 * PI y = dist * sin( s * 0.01745 ); // 320 x 240 * dx = CX + x; * dy = CY - y; } void calcXYforArrow( int news, int len, int bx, int by, int * dx, int * dy ) { int s, x, y; #ifndef NORTH_UP if ( !north_up ) { news -= mapHeading; } #endif s = (int)((360.0 - news) + 90.0) % 360; x = len * cos( s * 0.01745 ); // / 180 * PI y = len * sin( s * 0.01745 ); // 320 x 240 * dx = bx + x; * dy = by - y; } void calcXYforAbs( int news, int len, int bx, int by, int * dx, int * dy ) { int s, x, y; s = (int)((360.0 - news) + 90.0) % 360; x = len * cos( s * 0.01745 ); // / 180 * PI y = len * sin( s * 0.01745 ); // 320 x 240 * dx = bx + x; * dy = by - y; } // // https://arduinojson.org/v6/api/dynamicjsondocument/ // //#define DEBUG_DRAW_MAP #if 0 void drawMapShapeZ( char [] target , const String json_str, uint16_t color, int mode = DRAW_MAP_SHAPE_LINE ) { int i = 0; int odx = -1; int ody = -1; int dx, dy; //int ldx, ldy; int pack = 0; char buf[30]; //StaticJsonDocument<200> filter; DynamicJsonDocument filter(200); for ( pack = 0; pack < 100; pack++ ) { i = 0; odx = -1; ody = -1; filter.clear(); sprintf( buf, "p%d", pack ); if ( pack > 0 ) { //filter["map"][target][pack - 1] = false; } filter["map"][target][buf] = true; #ifdef USE_MAPDATA_CLEAR mapData.clear(); #endif //DeserializationError err = deserializeJson( mapData, json_str, DeserializationOption::Filter( filter) ); switch (err.code()) { case DeserializationError::Ok: //USE_SERIAL.println(F("Deserialization succeeded")); break; case DeserializationError::InvalidInput: dispErrorMsg( "Invalid input!" ); break; case DeserializationError::NoMemory: dispErrorMsg( "Not enough memory" ); break; default: USE_SERIAL.println(F("Deserialization failed")); USE_SERIAL.println( json_str ); break; } USE_SERIAL.print( target ); USE_SERIAL.print( " "); USE_SERIAL.print( pack ); USE_SERIAL.print( " = " ); USE_SERIAL.println( mapData.memoryUsage()); //serializeJsonPretty(mapData, Serial); while ( mapData["map"][target][buf][i][0] != -1 ) { dx = mapData["map"][target][buf][i][0]; dy = mapData["map"][target][buf][i][1]; #ifdef DEBUG_DRAW_MAP USE_SERIAL.print( dx ); USE_SERIAL.print( "," ); USE_SERIAL.print( dy ); USE_SERIAL.println( "" ); #endif // memory over 絋苡雫 (0,0) (0,0)礫磽礒 if ( dx == 0 && dy == 0 ) { int nx, ny; nx = mapData["map"][target][buf][i + 1][0]; ny = mapData["map"][target][buf][i + 1][1]; if ( nx == 0 && ny == 0 ) { USE_SERIAL.print( F("may be not enough memory : ") ); USE_SERIAL.println( target ); //continue; return; } } if ( dx == -2 && dy == -2 ) { // end of road odx = -1; ody = -1; } else { if ( odx == -1 && ody == -1) { #ifdef DEBUG_DRAW_MAP if ( ldx == dx && ldy == dy ) { USE_SERIAL.println( "dup draw pixel" ); } #endif VRAM.drawPixel( dx, dy, color ); #ifdef DEBUG_DRAW_MAP ldx = dx; ldy = dy; #endif } else { if ( mode == DRAW_MAP_SHAPE_LINE ) { VRAM.drawLine( odx, ody, dx, dy, color ); #ifdef DEBUG_DRAW_MAP if ( ldx == dx && ldy == dy ) { USE_SERIAL.println( "dup draw Line" ); } ldx = dx; ldy = dy; #endif } else VRAM.drawPixel( dx, dy , color ); } odx = dx; ody = dy; } i++; if ( i > 76800 ) { USE_SERIAL.println( F("Over map data self limit") ); break; } } } } #endif void drawMapShape( const char target[] , const String json_str, uint16_t color, int mode = DRAW_MAP_SHAPE_LINE ) { int i = 0; int odx = -1; int ody = -1; int dx, dy; #ifdef DEBUG_DRAW_MAP int ldx, ldy; #endif //USE_SERIAL.println( json_str ); USE_SERIAL.print( target ); USE_SERIAL.print( F(" = ") ); StaticJsonDocument<200> filter; filter["map"][target] = true; filter["map"]["use_cache"] = true; #ifdef USE_MAPDATA_CLEAR mapData.clear(); #endif DeserializationError err = deserializeJson( mapData, json_str, DeserializationOption::Filter( filter) ); #if 0 // 2021.12.23 if ( err.code() != DeserializationError::Ok ) { USE_SERIAL.println( F(" error on DrawMapShape") ); return; } #endif #if 1 switch (err.code()) { case DeserializationError::Ok: //USE_SERIAL.println(F("Deserialization succeeded")); break; case DeserializationError::InvalidInput: dispErrorMsg( "Invalid input! on drawMapShape" ); return; break; case DeserializationError::NoMemory: dispErrorMsg( "Not enough memory on drawMapShape" ); return; break; case DeserializationError::IncompleteInput: USE_SERIAL.println(F("Deserialization ImcomplementInput on drawMapShape")); return; break; case DeserializationError::TooDeep: USE_SERIAL.println(F("Deserialization TooDeep on drawMapShape")); return; break; default: USE_SERIAL.println(F("Deserialization failed on drawMapShape")); USE_SERIAL.println( err.c_str()); USE_SERIAL.println( json_str ); return; break; } #endif //#define DEBUG_DRAW_MAP // USE_SERIAL.print( F("= ") ); USE_SERIAL.println( mapData.memoryUsage()); #if 1 USE_SERIAL.print( F( "Map Data Use Cache is " )); USE_SERIAL.print((int) mapData["map"]["use_cache"] ); USE_SERIAL.println( (mapData["map"]["use_cache"] > 0) ? "USE" : "none" ); if ( mapData["map"]["use_cache"] > 0 ) { dispLcdColorBg( 0, 225, 1, TFT_WHITE, TFT_GREEN, "C" ); } else { dispLcdColorBg( 0, 225, 1, TFT_WHITE, TFT_RED, "N" ); } #endif while ( mapData["map"][target][i][0] != -1 ) { dx = mapData["map"][target][i][0]; dy = mapData["map"][target][i][1]; #ifdef DEBUG_DRAW_MAP USE_SERIAL.print( dx ); USE_SERIAL.print( "," ); USE_SERIAL.print( dy ); USE_SERIAL.println( "" ); #endif // memory over 絋苡雫 (0,0) (0,0)礫磽礒 if ( dx == 0 && dy == 0 ) { int nx, ny; nx = mapData["map"][target][i + 1][0]; ny = mapData["map"][target][i + 1][1]; if ( nx == 0 && ny == 0 ) { USE_SERIAL.print( F("may be not enough memory : ") ); USE_SERIAL.println( target ); return; } } if ( dx == -2 && dy == -2 ) { // end of road odx = -1; ody = -1; yield(); // 障ф荀礫礙... } else { if ( odx == -1 && ody == -1) { #ifdef DEBUG_DRAW_MAP if ( ldx == dx && ldy == dy ) { USE_SERIAL.println( "dup draw pixel" ); } #endif VRAM.drawPixel( dx, dy, color ); #ifdef DEBUG_DRAW_MAP ldx = dx; ldy = dy; #endif } else { if ( mode == DRAW_MAP_SHAPE_LINE ) { VRAM.drawLine( odx, ody, dx, dy, color ); #ifdef DEBUG_DRAW_MAP if ( ldx == dx && ldy == dy ) { USE_SERIAL.println( "dup draw Line" ); } ldx = dx; ldy = dy; #endif } else VRAM.drawPixel( dx, dy , color ); } odx = dx; ody = dy; } i++; if ( i > 4000 ) { USE_SERIAL.println( F("Over map data self limit") ); break; } } } int spdToLen( int spd ) { if ( spd < 1 ) return 0; return (int) log10( spd ) * 25; // 10km/h -> 25 100km/ -> 50 } /* 腥根禺禺磽秉頫腓削主鐚for Radar */ void dispCallsignWithInfo( const int x, const int y, const char * callsign, const int speed, const int alt , const int course, const int aprstype ) { int dx, dy; int dx1, dy1, dx2, dy2; int offset_x = 0, offset_y = 0; int pr = 135; // 宴茵腓差 if ( aprstype & 1 ) { M5.Lcd.fillCircle( x, y, 4, TFT_RED ); } else { M5.Lcd.drawTriangle( x, y - 2, x - 2, y + 2, x + 2, y + 2, TFT_GREEN ); } // 堺礙礫竏;腓坂順舟鏐繽汐荐絎 if ( x > CX ) { if ( y < CY ) pr = 45; else pr = 135; } else if ( y < CY ) pr = 315; else pr = 215; // 画劫禧禊絲篆罩 if ( aprstype & 1 ) { if ( 90 <= course && course <= 200 ) { pr = 45; } } // 茵腓差絆繖順舟祟礫禹竏┃ if ( pr < 90 ) { offset_y = -14; } else if ( pr < 180 ) { } else if ( pr < 270 ) { offset_x = -49; } else { offset_x = -49; offset_y = -14; } /* 腱糸絮經絲闊申絮竏;腓 */ if ( aprstype & 1 ) { calcXYforArrow( pr, 10, x, y, &dx1, &dy1 ); calcXYforArrow( pr, 20, x, y, &dx2, &dy2 ); calcXYforArrow( pr, 23, x, y, &dx, &dy ); M5.Lcd.drawLine( dx1, dy1, dx2, dy2, TFT_DARKGREEN ); } else { dx = x + 8; dy = y + 8; } dy += offset_y; dx += offset_x; // future #define RADAR_CALLSIGN_SIZE 1 #define RADAR_FIXSTATION_SIZE 1 #define RADAR_INFO_SIZE 1 //dispLcdColorTransparent( dx, dy, (((aprstype & 1 ) == 1 )? RADAR_CALLSIGN_SIZE : RADAR_FIXSTATION_SIZE ), TFT_GREEN, "%s", callsign ); #if 0 if ( aprstype & 1 ) { dispLcdColorTransparent( dx, dy, RADAR_CALLSIGN_SIZE, TFT_GREEN, "%s", callsign ); } else { dispLcdColorTransparent( dx, dy, RADAR_FIXSTATION_SIZE, TFT_GREEN, "%s", callsign ); } #else dispLcdColorTransparent( dx, dy, RADAR_CALLSIGN_SIZE, TFT_GREEN, "%s", callsign ); #endif if ( !(aprstype & 1) ) return; dy = dy + 11; dispLcdColorTransparent( dx, dy, RADAR_INFO_SIZE, TFT_GREEN, "%03d %03d", alt, speed ); } /* 莨阪窺 */ void drawMapLocalStations( const String json_str ) { int i; float news; float dist; int course, spd, alt; //int x, y; int dx, dy; int odx, ody; int aprstype; int fn; StaticJsonDocument<200> filter; filter["near"] = true; filter["footprint"] = true; filter["cnt"] = true; #ifdef USE_MAPDATA_CLEAR mapData.clear(); #endif DeserializationError err = deserializeJson( mapData, json_str, DeserializationOption::Filter( filter) ); // 2021.12.23 #if 1 if ( err.code() != DeserializationError::Ok ) { USE_SERIAL.println( F(" error on drawMapLocalStations") ); USE_SERIAL.println( json_str ); return; } #endif int n = mapData["cnt"]; USE_SERIAL.print( F("Local station N is ")); USE_SERIAL.println( n ); for ( i = 0 ; i < n ; i++ ) { const char * callsign = mapData["near"][i]["callsign"]; news = mapData["near"][i]["drcs"]; dist = mapData["near"][i]["dists"]; aprstype = mapData["near"][i]["aprstype"]; course = mapData["near"][i]["course"]; spd = mapData["near"][i]["spd"]; alt = mapData["near"][i]["alt"]; /* 莨阪窺憜粢舟祗 */ calcXY( news, dist, &dx, &dy ); switch ( displayMode ) { case DISPLAY_MODE_MAP: if ( spd > 4 ) { M5.Lcd.fillCircle( dx, dy, ( aprstype & 1 ) ? 5 : 3 /* size */ , TFT_RED ); } else { M5.Lcd.drawCircle( dx, dy, ( aprstype & 1 ) ? 5 : 3 /* size */ , TFT_RED ); } dispCallsignMap( dx + 4, dy - 3, ( aprstype & 1 ) ? 2 : 1 , TFT_WHITE, "%s", callsign ); break; case DISPLAY_MODE_RADAR: dispCallsignWithInfo( dx, dy, callsign, spd, alt, course, aprstype ); } if ( aprstype & 1 ) { /* 画 */ int dx1, dy1; int dx2, dy2, dx3, dy3; int as = 150; // 180 - 30 const int arrow_size = 10; calcXYforArrow( course, spdToLen( spd ), dx, dy, &dx1, &dy1 ); M5.Lcd.drawLine( dx, dy, dx1, dy1, TFT_RED ); if ( displayMode == DISPLAY_MODE_MAP ) { calcXYforArrow( course + as, arrow_size, dx1, dy1, &dx2, &dy2 ); calcXYforArrow( course - as, arrow_size, dx1, dy1, &dx3, &dy3 ); M5.Lcd.fillTriangle( dx1, dy1, dx2, dy2, dx3, dy3, TFT_RED ); } } /* footprint */ if ( displayMode == DISPLAY_MODE_MAP ) { fn = mapData["footprint"][i]["cnt"]; if ( aprstype & 1 ) { for ( int j = 0 ; j < fn ; j++ ) { news = mapData["footprint"][i]["data"][j]["drcs"]; dist = mapData["footprint"][i]["data"][j]["dists"]; calcXY( news, dist, &dx, &dy ); if ( j == 0 ) { M5.Lcd.drawPixel( dx, dy, COLOR_OTHER_FOOTPRINT ); } else { M5.Lcd.drawLine( odx, ody, dx, dy, COLOR_OTHER_FOOTPRINT ); } odx = dx; ody = dy; } } } } } void dispErrorMsg( const char * msg ) { USE_SERIAL.println( msg ); dispLcdColor( 0, 220, 2, TFT_RED, "%s", msg ); delay( 1000 ); } void drawArrow( int x, int y, int drc, int speed , uint16_t color ) { int dx, dy; calcXYforArrow( drc, speed, x, y, &dx, &dy ); M5.Lcd.drawLine( x, y, dx, dy, color ); } /* 上粢舟祗祺莵繽弱 */ void drawCurrentPositionNewsSpeed( int news, int spd ) { int r = 15; // 絎粢舟竍ず祟繞 int x1, y1, x2, y2; int x3, y3, x4, y4; int as = 150; // 180 - 30 // √違蒹 const int arrow_size = 15; int len = spdToLen( spd ); /* news = 219; spd = 100; */ M5.Lcd.drawCircle( CX, CY, r, COLOR_MY_POSITION ); if ( r >= 10 ) { M5.Lcd.fillCircle( CX, CY, 2, COLOR_MY_POSITION ); } // 綺 2km/h莇翫秧祚祀竏;腓 if ( spd > 2 ) { calcXYforArrow( news, r, CX, CY, &x1, &y1 ); calcXYforArrow( news, r + len, CX, CY, &x2, &y2 ); M5.Lcd.drawLine( x1, y1, x2, y2, COLOR_MY_POSITION ); calcXYforArrow( news + as, arrow_size, x2, y2, &x3, &y3 ); calcXYforArrow( news - as, arrow_size, x2, y2, &x4, &y4 ); M5.Lcd.fillTriangle( x2, y2, x3, y3, x4, y4, COLOR_MY_POSITION ); } else { M5.Lcd.fillCircle( CX, CY, (int)(r / 2), COLOR_MY_POSITION ); } //dispLcdColor( CX + 4, CY + 4, 1, TFT_WHITE, "%d %d", news, spd ); } /* */ void drawCurrentPositionGps() { #ifdef USE_GPS if ( gps.location.isValid()) { int news = gps.course.deg(); int spd = gps.speed.kmph(); drawCurrentPositionNewsSpeed( news, spd ); } else { drawCurrentPositionNewsSpeed( my_course, my_speed ); } #else drawCurrentPositionNewsSpeed( my_course, my_speed ); #endif } /* void drawCurrentPositionMyData() { drawCurrentPositionNewsSpeed( my_course, my_speed ); } */ /* 剛脾 */ void drawCompassRaw( int cx, int cy, int r, int news ) { int x, y, x1, y1, x2, y2; M5.Lcd.drawCircle( cx, cy, r, TFT_LIGHTGREY ); calcXYforAbs( news + 90, r / 3, cx, cy, &x1, &y1 ); calcXYforAbs( news - 90, r / 3, cx, cy, &x2, &y2 ); calcXYforAbs( news + 180 , r , cx, cy, &x , &y ); M5.Lcd.fillTriangle( x, y, x1, y1, x2, y2, TFT_LIGHTGREY ); calcXYforAbs( news , r , cx, cy, &x , &y ); M5.Lcd.fillTriangle( x, y, x1, y1, x2, y2, TFT_RED ); } void drawCompass() { int cx = 20; int cy = 27; int r = 15; // 剛脾紊э弱常 #ifdef NORTH_UP int news = 0; #else int news = ( north_up ? 0 : (360 - mapHeading)); #endif drawCompassRaw( cx, cy, r, news ); } /* 医潟≪若禺磚禺邃源祀禺 */ void showNear( const String str ) { // int i; float news; // int x, y; int dx, dy; int odx, ody; float dist; //float s; //int aprstype; int fn; M5.Lcd.startWrite(); clearDisplay(); dispStatus(); // 脂≫賢紊祟繙梓綛括 M5.Lcd.drawLine( CX, 0, CX, 240, M5.Lcd.color565( 70, 70, 50 )); M5.Lcd.drawLine( 0, CY, 320, CY, M5.Lcd.color565( 70, 70, 50 )); // 剛脾 drawCompass(); // drawMap Shape if ( displayMode == DISPLAY_MODE_MAP ) { #ifdef USE_SPRITE vram.fillSprite( TFT_BLACK ); #endif drawMapShape( "coast", str, COLOR_COAST, DRAW_MAP_SHAPE_DOT ); drawMapShape( "road", str, COLOR_ROAD, DRAW_MAP_SHAPE_LINE ); #ifdef USE_SPRITE //clearDisplay(); vram.pushSprite( 0, 0 ); #endif } // 菴e drawMapLocalStations( str ); // // 絎莇活 StaticJsonDocument<200> filter; filter["myfootprint"] = true; #ifdef USE_MAPDATA_CLEAR mapData.clear(); #endif DeserializationError err = deserializeJson( mapData, str, DeserializationOption::Filter( filter) ); #if 1 switch (err.code()) { case DeserializationError::Ok: USE_SERIAL.println(F("Deserialization succeeded on showNear" )); break; case DeserializationError::InvalidInput: dispErrorMsg( "Invalid input!" ); break; case DeserializationError::NoMemory: dispErrorMsg( "Not enough memory" ); break; default: USE_SERIAL.print(F("Deserialization failed")); break; } #endif USE_SERIAL.print( "myfootprint = " ); USE_SERIAL.println( mapData.memoryUsage()); /* myfootprint */ fn = mapData["myfootprint"]["cnt"]; for ( int j = 0 ; j < fn ; j++ ) { news = mapData["myfootprint"]["data"][j]["drcs"]; dist = mapData["myfootprint"]["data"][j]["dists"]; calcXY( news, dist, &dx, &dy ); if ( j == 0 ) { M5.Lcd.drawPixel( dx, dy, COLOR_MY_FOOTPRINT ); } else { M5.Lcd.drawLine( odx, ody, dx, dy, COLOR_MY_FOOTPRINT ); } odx = dx; ody = dy; } // 絎憜粢舟 drawCurrentPositionGps(); dispStatus(); M5.Lcd.endWrite(); smartDelay( 1 ); } // This custom version of delay() ensures that the gps object // is being "fed". static void smartDelay(unsigned long ms) { unsigned long start = millis(); do { #ifdef USE_GPS while (ss.available()) gps.encode(ss.read()); #endif delay( 1 ); // for task switch } while (millis() - start < ms); } #define BUFSIZE 1024 #define BUFLEN 1023 void dispLcd( uint16_t x, uint16_t y, uint16_t size, const char * fmt, ... ) { char buf[BUFSIZE]; va_list arg; va_start(arg, fmt ); vsnprintf( buf, BUFLEN, fmt, arg ); #if defined( USE_M5STACK ) M5.Lcd.setCursor( x, y ); M5.Lcd.setTextSize( size ); M5.Lcd.print( buf ); #endif #if defined( USE_LCD ) display.setCursor( x, y ); display.setTextSize( size ); display.print( buf ); display.display(); #endif va_end(arg); } void dispLcdColorTransparent( uint16_t x, uint16_t y, uint16_t size, uint16_t color, const char * fmt, ... ) { char buf[BUFSIZE]; va_list arg; va_start(arg, fmt ); vsnprintf( buf, BUFLEN, fmt, arg ); #if defined( USE_M5STACK ) M5.Lcd.setTextColor( color ); M5.Lcd.setCursor( x, y ); M5.Lcd.setTextSize( size ); M5.Lcd.setTextWrap( 0 ); M5.Lcd.print( buf ); #endif va_end(arg); } void dispLcdColor( uint16_t x, uint16_t y, uint16_t size, uint16_t color, const char * fmt, ... ) { char buf[BUFSIZE]; va_list arg; va_start(arg, fmt ); vsnprintf( buf, BUFLEN, fmt, arg ); #if defined( USE_M5STACK ) M5.Lcd.setTextColor( color, TFT_BLACK ); M5.Lcd.setCursor( x, y ); M5.Lcd.setTextSize( size ); M5.Lcd.print( buf ); #endif #if defined( USE_LCD ) display.setTextColor( color, TFFT_BLACK ); display.setCursor( x, y ); display.setTextSize( size ); display.print( buf ); display.display(); #endif va_end(arg); } void dispLcdColorCenter( uint16_t y, uint16_t size, uint16_t color, const char * fmt, ... ) { int x; char buf[BUFSIZE]; va_list arg; va_start(arg, fmt ); vsnprintf( buf, BUFLEN, fmt, arg ); x = 160 - strlen( buf ) / 2.0 * 6 * size; M5.Lcd.setTextColor( color, TFT_BLACK ); M5.Lcd.setCursor( x, y ); M5.Lcd.setTextSize( size ); M5.Lcd.print( buf ); va_end(arg); } void dispLcdColorBg( uint16_t x, uint16_t y, uint16_t size, uint16_t color, uint16_t bgcolor, const char * fmt, ... ) { char buf[BUFSIZE]; va_list arg; va_start(arg, fmt ); vsnprintf( buf, BUFLEN, fmt, arg ); #if defined( USE_M5STACK ) M5.Lcd.setTextColor( color, bgcolor ); M5.Lcd.setCursor( x, y ); M5.Lcd.setTextSize( size ); M5.Lcd.print( buf ); #endif #if defined( USE_LCD ) display.setTextColor( color, bgcolor ); display.setCursor( x, y ); display.setTextSize( size ); display.print( buf ); display.display(); #endif va_end(arg); } void dispCallsignMap( uint16_t x, uint16_t y, uint16_t size, uint16_t color, const char * fmt, ... ) { char buf[BUFSIZE]; va_list arg; va_start(arg, fmt ); vsnprintf( buf, BUFLEN, fmt, arg ); #if defined( USE_M5STACK ) M5.Lcd.setTextColor( color ); M5.Lcd.setCursor( x, y ); M5.Lcd.setTextSize( size ); M5.Lcd.setTextWrap( 0 ); M5.Lcd.print( buf ); #endif #if defined( USE_LCD ) display.setTextColor( color, TFT_BLACK ); display.setCursor( x, y ); display.setTextSize( size ); display.print( buf ); display.display(); #endif va_end(arg); } uint16_t calcColor( uint8_t r, uint8_t g, uint8_t b, int st ) { // TODO: 激違≪ゃ∽違篏帥c祉祀茵┥礑... float rate = 100.0; rate -= ( st / 600.0 ) * 100; rate = max( (int)rate, 30 ); rate /= 100; r *= rate; g *= rate; b *= rate; return M5.Lcd.color565( r, g, b ); } /* 菴e筝荀 */ void showList( const String str ) { #ifdef USE_MAPDATA_CLEAR mapData.clear(); #endif DeserializationError err = deserializeJson( mapData, str ); // 2021.12.23 #if 1 if ( err.code() != DeserializationError::Ok ) { USE_SERIAL.println( F(" error on showList()") ); return; } #endif #if defined( YOUNG_HAM ) const int lineHeight = 20; const int callsignSize = 2; const int offsetDistBar = 16; const int widthCallsign = 108; // 2 x 6 x 9 #elif defined( OLD_HAM ) const int lineHeight = 40; const int callsignSize = 4; const int offsetDistBar = 32; const int widthCallsign = 216; // 4 x 6 x 9 #else const int lineHeight = 30; const int callsignSize = 3; const int offsetDistBar = 24; const int widthCallsign = 162; // 3 x 6 x 9 #endif int n = mapData["cnt"]; int i; float news; float dist = 0; int course, spd; int aprstype; int x, y; int w; int dx1, dy1, dx2, dy2, dx3, dy3; int st; int drc_diff; uint16_t distBarColor; const int news_size = 20; USE_SERIAL.println( n ); y = 15; clearDisplay(); for ( i = 0 ; i < n ; i++ ) { const char * callsign = mapData["near"][i]["callsign"]; aprstype = mapData["near"][i]["aprstype"]; if ( display_list_target ) { if ( !(aprstype & 1)) continue; } news = mapData["near"][i]["drcs"]; dist = mapData["near"][i]["dists"]; course = mapData["near"][i]["course"]; spd = mapData["near"][i]["spd"]; st = mapData["near"][i]["st"]; // ヨ or √紊 drc_diff = ( ((int)news + 180 ) - course + 360 ) % 360; if ( drc_diff <= 60 || 300 <= drc_diff ) { // ヨ distBarColor = TFT_GREEN; } else if ( drc_diff <= 120 || 240 <= drc_diff ) { // 鐚鐚cゆ distBarColor = TFT_YELLOW; } else { // √ distBarColor = TFT_RED; } // 潟若禝磴 (SSID) // font-width 6px height 7px; x = 0; #ifdef LIST_MODE_COLOR dispLcdColor( x, y, callsignSize, (aprstype & 1 ) ? distBarColor : TFT_LIGHTGREY, "%-9s", callsign ); #else dispLcdColor( x, y, callsignSize, (aprstype & 1 ) ? TFT_WHITE : TFT_LIGHTGREY, "%-9s", callsign ); #endif x += widthCallsign; // 莊 if ( dist < 10 ) { dispLcdColor( x, y, 2, TFT_YELLOW, "%4d ", (int)(dist * 1000 )); x += 48; } else { dispLcdColor( x, y, 2, TFT_WHITE, "%4d", (int)dist ); x += 48; dispLcdColor( x, y + 10, 1, TFT_DARKGREY, "km" ); } if ( dist > 0 ) { w = 180 - log10( dist ) * 138; } else { w = 320; } w = max( 10, w ); // M5.Lcd.fillRect( w, y + 24, 320, 4, TFT_BLACK ); // 礫磚 // 莊≪腓冴 Bar #ifdef LIST_MODE_COLOR M5.Lcd.fillRect( 0, y + offsetDistBar, w, 4 , TFT_WHITE ); #else M5.Lcd.fillRect( 0, y + offsetDistBar, w, 4 , distBarColor ); #endif x += 25; calcXYforArrow( news, news_size / 2, x, y + 10, &dx1, &dy1 ); calcXYforArrow( news + 155, news_size / 2, x, y + 10, &dx2, &dy2 ); calcXYforArrow( news - 155, news_size / 2, x, y + 10, &dx3, &dy3 ); M5.Lcd.fillRect( x - news_size / 2, y , news_size, news_size, TFT_BLACK ); M5.Lcd.fillTriangle( dx1, dy1, dx2, dy2, dx3, dy3, calcColor( 255, 255, 0, st )); //M5.Lcd.drawLine( x, y + 15, dx1, dy1, TFT_YELLOW ); x += news_size; x -= 5; // // 綺竏;腓 if ( aprstype & 1 ) { // 腱糸絮秧蜈墾竏;腓 #if 0 dispLcdColor( x, y, 2, TFT_GREEN, "%3d", spd ); #endif #if 0 { const int gh = 16; const int gw = 30; float spd_rate = (float)spd / 100.0; //M5.Lcd.fillRect( x, y, gw, gh, TFT_BLACK); // 礫磚 M5.Lcd.drawLine( x, y + gh, x + gw , y + gh, TFT_DARKGREEN ); // 筝膩 M5.Lcd.fillTriangle( x, y + gh, x + gw * spd_rate, y + gh, x + gw * spd_rate, y + gh - gh * spd_rate , TFT_GREEN ); } #endif #if 0 // 胼蕾莵紲蜈墾祕礬磽祚茵腓 { const int gh = 21; const int gw = 21; int cx, cy; cx = x + gw / 2; cy = y + gh / 2; //USE_SERIAL.println( x ); //USE_SERIAL.println( x + gw ); calcXYforArrow( course, min( spd, gw / 2 ), cx, cy, &dx2, &dy2 ); calcXYforArrow( course + 180, min( spd, gw / 2 ), cx, cy, &dx1, &dy1 ); // 榊 M5.Lcd.drawLine( dx1, dy1, dx2, dy2, TFT_RED ); M5.Lcd.fillCircle( dx1, dy1, 3, TFT_RED ); //M5.Lcd.drawRect( x, y, gw, gh , TFT_GREEN ); } #endif #if 1 // 後 壕箍莵繽弱墾祕礬磽祚礑礫禺禺祠磴礑茵腓 { x -= news_size; x -= 5; const int gh = 21; const int gw = 21; int cx, cy; cx = x + gw / 2; cy = y + gh / 2; //USE_SERIAL.println( x ); //USE_SERIAL.println( x + gw ); calcXYforArrow( course, min( spd, gw ), cx, cy, &dx2, &dy2 ); // calcXYforArrow( course + 180, min( spd, gw / 2 ), cx, cy, &dx1, &dy1 ); // 榊 if ( spd >= 2 ) { M5.Lcd.drawLine( cx, cy, dx2, dy2, TFT_RED ); M5.Lcd.fillCircle( cx, cy, 3, TFT_RED ); } else { M5.Lcd.drawCircle( cx, cy, 3, TFT_RED ); } //M5.Lcd.drawRect( x, y, gw, gh , TFT_GREEN ); } #endif #if 0 // for DEBUG dispLcdColor( 260, y, 2, TFT_WHITE, "%3d", drc_diff ); #endif // 鐚絮腟 } y += lineHeight; } } /* Graph Functions */ /////////////////////// // 違 /////////////////////// #define GRAPH_X 0 #define GRAPH_Y 115 #define GRAPH_WIDTH 320 #define GRAPH_HEIGHT 100 #define GRAPH_SEED 3 #define GRAPH_DATA_MAX (GRAPH_WIDTH-2) #define GRAPH_INTERVAL 5 #define SEED_TEMPERATURE 0 #define SEED_HUMIDITY 1 #define SEED_PRESSURE 2 #define COLOR_TEMPERATURE COLOR_SPEED #define COLOR_HUMIDITY COLOR_ALTITUDE #define COLOR_PRESSURE COLOR_COURSE #define COLOR_INC TFT_ORANGE #define COLOR_DEC TFT_CYAN #define DATA_STACK 600 time_t now() { time_t t; time( &t ); return t; } void initGraph() { #if defined( USE_M5STACK ) M5.Lcd.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT, TFT_BLACK ); M5.Lcd.drawRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT, TFT_WHITE ); #endif } void clearGraph() { #if defined( USE_M5STACK ) M5.Lcd.fillRect( GRAPH_X + 1, GRAPH_Y + 1, GRAPH_WIDTH - 2, GRAPH_HEIGHT - 2, TFT_BLACK ); #endif } double graphData[GRAPH_DATA_MAX][GRAPH_SEED]; uint16_t graphDataN = 0; /* 筝絎с若帥篆絖 篆絖翫 true, ∴鐚鐚經 false菴 */ bool setGraphDataInterval( double temperature, double humidity, double pressure ) { static time_t nextTime = 0L; // mills()篏蝕竢50ャс禺祀祚 if ( nextTime > now()) return false; nextTime = now() + GRAPH_INTERVAL; // 違祟羯 setGraphData( temperature, humidity, pressure ); return true; } void setGraphData( double temperature, double humidity, double pressure ) { if ( temperature <= 1.0 ) temperature = 0.0; if ( graphDataN < GRAPH_DATA_MAX ) { } else { int i, j; for ( i = 0 ; i < (GRAPH_DATA_MAX - 1); i++ ) { for ( j = 0; j < GRAPH_SEED; j++ ) { graphData[i][j] = graphData[i + 1][j]; } } graphDataN--; } //USE_SERIAL.println( graphDataN ); graphData[graphDataN][SEED_TEMPERATURE] = temperature; graphData[graphDataN][SEED_HUMIDITY] = humidity; graphData[graphDataN][SEED_PRESSURE] = pressure; graphDataN++; } #define GRAPH_DOT 1 #define GRAPH_BAR 2 #define GRAPH_LINE 3 void drawGraphAll() { clearGraph(); /* Bar羯 */ drawGraph( SEED_HUMIDITY, COLOR_HUMIDITY , GRAPH_BAR); // Altitude drawGraph( SEED_TEMPERATURE, COLOR_TEMPERATURE , GRAPH_DOT); // Speed drawGraph( SEED_PRESSURE, COLOR_PRESSURE , GRAPH_DOT); // Course } void drawGraph(uint16_t seed, uint16_t color, int graph_type ) { double max = -10000.0; double min = 10000.0; int x, y; int h; for ( x = 0; x < graphDataN; x++ ) { if ( max < graphData[x][seed] ) max = graphData[x][seed]; if ( min > graphData[x][seed] ) min = graphData[x][seed]; } if ( max == min ) { max += 0.5; min -= 0.5; } else { max += 0.1; min -= 0.1; } for ( x = 0 ; x < graphDataN; x++ ) { h = ((graphData[x][seed] - min) / (max - min ) * GRAPH_HEIGHT); y = GRAPH_Y + GRAPH_HEIGHT - h; //USE_SERIAL.print( y ); USE_SERIAL.print( "," ); #if defined( USE_M5STACK ) switch ( graph_type ) { case GRAPH_BAR: if ( 1 ) { M5.Lcd.drawFastVLine( x + 1, y + 3, h - 3 , /*color*/ COLOR_ALTITUDE2); } case GRAPH_DOT: M5.Lcd.fillCircle( x + 1, y, 1 /* size */ , color ); if ( 0 ) { M5.Lcd.drawPixel( x + 1, y , color ); M5.Lcd.drawPixel( x + 1, y + 1, color ); M5.Lcd.drawPixel( x + 1, y + 2, color ); M5.Lcd.drawPixel( x + 2, y , color ); M5.Lcd.drawPixel( x + 2, y + 1, color ); M5.Lcd.drawPixel( x + 2, y + 2, color ); M5.Lcd.drawPixel( x , y , color ); M5.Lcd.drawPixel( x , y + 1, color ); M5.Lcd.drawPixel( x , y + 2, color ); } break; case GRAPH_LINE: break; } #endif } //USE_SERIAL.println( "" ); } #if 0 static void printFloat(float val, bool valid, int len, int prec) { if (!valid) { while (len-- > 1) M5.Lcd.print('*'); M5.Lcd.print(' '); } else { M5.Lcd.print(val, prec); int vi = abs((int)val); int flen = prec + (val < 0.0 ? 2 : 1); // . and - flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1; for (int i = flen; i < len; ++i) M5.Lcd.print(' '); } smartDelay(0); } static void printInt(unsigned long val, bool valid, int len) { char sz[32] = "*****************"; if (valid) sprintf(sz, "%ld", val); sz[len] = 0; for (int i = strlen(sz); i < len; ++i) sz[i] = ' '; if (len > 0) sz[len - 1] = ' '; M5.Lcd.print(sz); smartDelay(0); } #endif static void printDateTime(TinyGPSDate & d, TinyGPSTime & t) { M5.Lcd.setTextColor( TFT_WHITE, TFT_BLACK ); if (!d.isValid()) { M5.Lcd.print(F("********** ")); } else { char sz[32]; sprintf(sz, "%02d/%02d/%02d ", d.year(), d.month(), d.day()); M5.Lcd.print(sz); } if (!t.isValid()) { M5.Lcd.print(F("******** ")); } else { char sz[32]; sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second()); M5.Lcd.print(sz); } //printInt(d.age(), d.isValid(), 5); // smartDelay(0); } #if 0 static void printStr(const char *str, int len) { int slen = strlen(str); for (int i = 0; i < len; ++i) M5.Lcd.print(i < slen ? str[i] : ' '); smartDelay(0); } #endif