概要
事前準備
利用方法
バグと改良予定
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