コンパイル / スケッチ

スケッチ

$Date: 2026/09/29 11:39:12 $
$Revision: 1.4 $

この世界ではソースコードと言わずにスケッチというそうですHi。

スケッチのダウンロード

スケッチの最初の部分に個別設定がありますので、その部分を各自の環境に合わせて修正を行ってください。

最初に #include "jf6lze-13.h"などの部分をコメントアウトしてください。私の個人的設定を読み込むようにしていますが、これは不要です。リリースタイミングによっては jg6ycl-13.h, jg6ycl-15.hを読み込むようにしているかもしれません。

47.4Kbyte
2026-04-22 07:46

現時点での問題点 / 既知のバグ

  • 本来はマルチタスクにして、いくつかのタスクに分けて処理すべきなのですが、現時点では、シングルタスクです。(システムとしては既にマルチタスクですが、私が作成した部分がすべて同じタスクで動いている状態です。後日、改修予定)[2019.09.20 マルチタスク化完了]
  • WiFiが切れた場合、数回リトライの後でも接続に失敗する場合、リセットをかけるようにしています。リセット後、直後にAPRS-ISに気象データを送信しますが、リセットのタイミングによっては短時間(5分以下)でAPRS-ISへの送信を行ってしまうことがあります。(解決策はあります。リセットしても消えないメモリー領域を利用してタイミングの調整を行えばいいはず...) WifiMultiを利用する方にしましたので、一応解決か....
  • display関係の割り込みの問題を抱えています。 内部処理のタイミングが悪いとピーと鳴りつづけます。 排他処理を行えばいいのはわかっているのですが、なかなか手が回りません。

その他の改修予定

  • ambientにもデータを送れるようにする
  • その前に、MQTT対応が先か....
  • DisplayなしのESP開発ボードの場合、現在の状態(温度など)をモールスでLチカする(その前にマルチタスク化をしないと実装できない)[2019.09.21 CW化完了]
クリップボードにコピーしました
/* -*- c -*- */

/*
       Temperature, Humidity, Pressure Web Server (JSON) and APRS Weather Station
          for ESP32 (ESP32-WROOM-32)
      Copyright (C) 2019,2020,2021 by JF6LZE. All Rights Reserved.
  7
  Multitask Version.. (2019.09.20-)

      $Id: esp32_bme280_webserver.ino,v 1.4 2023/11/18 06:36:03 yahiro Exp $
*/

/*
 * save to esp32-wx/public/
 */
const char * ktwx_version = "0.08.2";

/*

   https://github.com/mgo-tec/ESP32_BME280_I2C
      https://github.com/mgo-tec/ESP32_BME280_I2C/archive/master.zip
*/
#include <ESP32_BME280_I2C.h>

/*
   Copyright (c) 2015, Majenko Technologies
   All rights reserved.

   Redistribution and use in source and binary forms, with or without modification,
   are permitted provided that the following conditions are met:

 * * Redistributions of source code must retain the above copyright notice, this
     list of conditions and the following disclaimer.

 * * Redistributions in binary form must reproduce the above copyright notice, this
     list of conditions and the following disclaimer in the documentation and/or
     other materials provided with the distribution.

 * * Neither the name of Majenko Technologies nor the names of its
     contributors may be used to endorse or promote product derived from
     this software without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
   ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


//#define USE_ARDUINO

#include <WiFi.h>
#include <WiFiMulti.h>
#include <WiFiClient.h>
#ifdef USE_ARDUINO
#include <Ethernet.h>

#else

#include <WebServer.h>
#include <HTTP_Method.h>

#include <ESPmDNS.h>

#endif

#include <time.h>

/*

   https://github.com/espressif/arduino-esp32
*/

// *************************
//   Configureation Section
//
//    ※ コンパイル前に、下記数行の#includeはコメントアウトし、その下にある各種設定を行なってください。
//
// *************************
//  for TEST Configuration
//#include "jf6lze-13.h" // ESP32 DevKit SS
//#include "jg6ycl-13.h" // ESP32 DevKit Waves
//#include "jg6ycl-15.h"  // M5Stack
//#include "jg6ycl-14.h"  // 格安 ESP32  NodeE-MCU32
//#include "studyroom.h"    // 格安 ESP32  NodeE-MCU32
#include "dining_m5stack.h"  // M5Stack NO APRS  (with Network, Web Server)

//
// WiFi configuration  (2.4GHz)
//
#ifndef DEF_CONF_WIFI
#define DEF_CONF_WIFI
const char *ssid = "AP-SSID";
const char * password = "secret-key";
#endif

/*
  IP configuration
   DHCPでIPアドレスなどを取得する場合は#define USE_DHCP を、
   固定IPの場合は #define USE_DHCP をコメントアウトして、その下の情報を定義してください。
*/
//#define USE_DHCP

#if !defined( USE_DHCP ) && !defined( DEF_CONF_IP )
#define DEF_CONF_IP
IPAddress local_IP(192, 168, 1, 59);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);
IPAddress secondaryDNS(8, 8, 4, 4);
#endif

WiFiMulti wifiMulti;

//
// for APRS-IS
//     for Ham Radio
// APRS-ISに気象情報を送信する場合は、#define USE_APRSを有効にしてください。
//
//#define USE_APRS


#if defined( USE_APRS ) && !defined( DEF_CONF_APRS )
#define DEF_CONF_APRS
const char * callsign_ssid = "NOCALL-13";         // コールサイン-SSIDを忘れずに
const int passcode = 00000;                       // APRS-ISにアクセスするためのpasscode

//
// センサを室内に設置する場合、その気温と湿度をAPRS-ISに送信しても、あまり意味がありませんので
// その場合は、下記の2変数を 0 にしてください。
//  ただし、現行のAPRSでは、温度、湿度を空データにすることは仕様上できないかもしれません。
const int send_humidity = 1;
const int send_temperature = 1;

// Your Location
const float lat = 33.0000;                        // 緯度
const float lng = 130.0000;                       // 経度
const float altitude = 75;  // altitude [m]       // 標高 [m] (気圧の補正用)

// Upstream APRS-IS
const char * host = "wave.ftokai-u.ac.jp";  // Old-T2FUKUOKA or fukuoka.aprs2.net
const uint16_t port = 14580;
const int aprs_update_interval = 5;              // 更新間隔 [分]
#endif


//
//  WebServer機能を有効にする場合は #define USE_WEBSERVER を有効にしてください
//
//#define USE_WEBSERVER

//
//
//
//#define USE_DEEP_SLEEP


//
//
//
//  ESP32 and MBE280 Hardware configuration
//
#ifndef DEF_CONF_GPIO
#define DEF_CONF_GPIO
const uint8_t Address = 0x76;
//const uint8_t Address = 0x77;
//
//  BME280の SDA, SCLを接続したGPIOに合わせて変更してください
//     接続したGPIOに合わせて変更してください

//
// 利用不可能なGPIO (ESP32)
//    6,7,8,9,10,11,15 その他色々・・・
// M5Stackの場合、次のGPIOは内部で利用されているため利用できません
//    0,1,3,18,19,23,25
//    LCD関係 14,18,23,27,33
//     34-39 は INPUTのみ
//  M5Stackの場合、センサーとして、https://amzn.to/34NHbXi を使う場合は、下記がオススメ
//   BME280とM5Stackはできる限り離してください:M5Stackの熱の影響を受けて、通常より数度温度があがって計測されてしまいます
//     (ただし、Groveとは SDA, SCLが入れ替えた設定です。Groveを併用する場合は注意)
//    const uint8_t sda = 22;
//    const uint8_t scl = 21;
//  その他は、回路図を追っかけてください
// https://github.com/m5stack/M5-Schematic/blob/master/Core/Basic/M5-Core-Schematic(20171206).pdf

const uint8_t sda = 21;
const uint8_t scl = 22;

//const uint8_t sda = 32;
//const uint8_t scl = 33;

//
// LED configureation
//   LEDを接続しない場合は、0を指定する
//    M5Stackの場合は33は利用不可
const uint8_t busy_led = 0;

#endif

#if !defined( DEF_CONF_USE_ANALOG )
#define DEF_CONF_USE_ANALOG
//
//  アナログセンサを接続する場合は、#define USE_ANALOG を定義してください。
//
#define USE_ANALOG
const int analog_gpio[] = { 34, 35 };
#endif

#if !defined( DEF_CONF_MORSE_LED )
#define DEF_CONF_MORSE_LED
//
// 気温などをモールス信号(LED)で通知
#define USE_MORSE_LED
const uint8_t morse_led = busy_led;
const uint8_t morse_speed = 200;    // 数値を小さくすると速くなります
#endif
//
//  M5Stackを利用する場合は、USE_M5STACKを定義する、ESP32開発ボードの場合はコメントアウト
//#define USE_M5STACK
#define LCD_BRIGHTNESS  150
#define USE_M5STACK_POWER_CONTROL


#if !defined( DEF_CONF_USE_LCD_SSD1306 )
#define DEF_CONF_USE_LCD_SSD1306
//
//
// https://amzn.to/2ntemy9 を使う場合は、下記を有効にしてください。(テスト中)
//#define USE_LCD_SSD1306
//
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

#endif


#if !defined( DEF_CONF_USE_MQTT )
#define DEF_CONF_USE_MQTT
// MQTTに気象データを送信する場合は USE_MQTTを定義してください。
// ライブラリーは https://github.com/knolleary/pubsubclient
//
#define USE_MQTT
const char * mqtt_host = "ketaitracker.info";
const int mqtt_port = 1883;
const char * mqtt_topic_prifix = "aprs/wx";

char mqtt_topic[256]; //  = "aprs/wx/dummy"; // test now
#endif

#ifndef DEF_CONF_USE_OTA
#define DEF_CONF_USE_OTA
//
// OTAを利用する場合は#define USE_OTAを有効にしてください。
// Bluetooth経由でプログラムの更新ができるようになります。
#define USE_OTA
#endif

/////////////////////////////
//
//  以下、変更は原則不要です
//
/////////////////////////////
#ifdef USE_OTA
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#endif

#if defined( USE_APRS )
const char * mdns_name = callsign_ssid;
#else
const char * mdns_name = "esp32-wx";
#endif

#ifdef USE_DEEP_SLEEP
#undef USE_WEBSERVER
#endif

#ifdef USE_M5STACK
#include <M5Stack.h>

#endif


long timezone = 0;
byte daysavetime = 0;

#ifdef USE_WEBSERVER
#ifdef USE_ARDUINO
EthernetServer server(80);
#else
WebServer server(80);
#endif
#endif
#ifdef USE_APRS
WiFiClient aprs;
#endif


#ifdef USE_MQTT
//
// ここで MQTT_MAX_PACKET_SIZE を変更してもライブラリーに影響がありません
//  ~/Document/Arduino/libraries/pubsubclient-master/src/PubSubClient.h で
// #define MQTT_MAX_PACKET_SIZE 256 と変更してください。
//#define MQTT_MAX_PACKET_SIZE 256

#include <PubSubClient.h>
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
#endif

#define STATUS_BUSY 1
#define STATUS_IDLE 0

//
// LCD SSD1306
#if defined( USE_LCD_SSD1306 )
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#ifndef SCREEN_WIDTH
#define SCREEN_WIDTH 128
#endif
#ifndef SCREEN_HEIGHT
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#endif

#define USE_LCD

#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#endif   // end if USE_LCD_SSD1306

#if defined( USE_M5STACK )
#undef USE_LCD
#endif
//const int led = 13;

const uint32_t frequency = 30000;
ESP32_BME280_I2C bme280i2c(Address, scl, sda, frequency);

//
// mean-sea-level barometric pressure
//
double adjustPressure( double pressure, double temperature, double altitude )
{
  return pressure * pow( 1 - 0.0056 * altitude / ( temperature + 0.0065 * altitude + 273.15), 5.257 );
}



void setup(void) {

  unsigned long system_start_time = millis();
  unsigned long system_ready_time;

  Serial.begin(115200);
  Serial.println( );

#if defined( __AVR__ )
  Serial.println( "ARDUINO Enabled" );
#endif
#if defined( ESP32 )
  Serial.println( "ESP32 Enabled" );
#endif
#if defined( ESP8266 )
  Serial.printlmn( "ESP8266 Enabled" );
#endif



#if defined( USE_LCD ) || defined( USE_LCD_SSD1306 )
  Serial.println( "LCD_SSD1306 Enabled" );
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }
  display.display();
  display.clearDisplay();

  display.setTextSize( 1 );
  display.setTextColor( WHITE );
  display.setCursor( 0, 0 );
  display.printf( "%s", "KetaiTracker ESP" );
  display.setCursor( 0, 10 );
  display.setTextSize( 2 );
  display.printf( "%s", ktwx_version );

  display.display();

#endif

#ifdef USE_M5STACK
  if ( sda == 25 || scl == 25 || busy_led == 25 ) {
    // M5Stackで25に出力すると内蔵スピーカーがバチバチいうので要注意...
    Serial.println( "Error: Don't use GPIO 25" );
  }
#endif
#ifndef USE_ARDUINO
  pinMode( sda, PULLUP );
  pinMode( scl, PULLUP );
#endif
  if ( busy_led != 0 )
    pinMode( busy_led, OUTPUT );

#ifdef USE_MORSE_LED
  if ( morse_led != 0 )
    pinMode( morse_led, OUTPUT );
#endif



  Serial.println( "" );
#ifdef USE_M5STACK
  Serial.println( "M5STACK" );
  M5.begin();
  //M5.Speaker.begin();
  //M5.Speaker.end();

  M5.Lcd.println( "KetaiTracker WX" );
#ifdef USE_M5STACK_POWER_CONTROL
  if ( M5.Power.canControl()) {
    M5.Lcd.println( "Contorable Power" );
    if ( M5.Power.isChargeFull()) {
      M5.Lcd.print( "Battery: full" );
    } else {
      M5.Lcd.print( "Battery: NOT full" );
    }
    M5.Lcd.printf( ",%d%% ", M5.Power.getBatteryLevel());
    if ( M5.Power.isCharging()) {
      M5.Lcd.println( ", Charging Now" );
    } else {
      M5.Lcd.println( "" );
    }
  } else {
    M5.Lcd.println( "Power Control is disbaled (May be.. Old M5Stack)" );
  }
#endif
#ifdef USE_APRS
  M5.Lcd.printf( "Callsign: %s\n", callsign_ssid );
#endif
#endif

#ifdef USE_MQTT
  sprintf( mqtt_topic, "%s/%s", mqtt_topic_prifix, callsign_ssid );
  Serial.printf( "MQTT publish enabled  %s:%d, topic: %s\n", mqtt_host, mqtt_port, mqtt_topic );
  Serial.printf( "MQTT maximum message size is %d, keepalive interval is %d\n", MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE );
#endif

  statusLed( STATUS_BUSY );
#ifdef USE_MORSE_LED
  Serial.println( "Morse LED Enabled" );
#endif
#ifdef USE_WEBSERVER
  Serial.println( "Web Server Enabled" );
#endif
#ifdef USE_APRS
  Serial.println( "APRS Weather Station Enbaled" );
#endif

#ifdef USE_ANALOG
  Serial.println( "Analog Input Enabled" );
#endif

#ifdef USE_DHCP
  Serial.println( 'use DHCP Server' );
#else
  Serial.println( 'use Static IP Address' );
  if ( !WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("STA Failed to configure");
  }
#endif

#if 1
  initWiFi();
#else
  WiFi.mode( WIFI_STA );
  WiFi.begin( ssid, password);
#endif
  Serial.println("");
  /*
    Serial.print( "Reconned mode is " );
    Serial.println( WiFi.getAutoReconnect() );
  */
  // Wait for connection
#if 0
  keepWifi();

  Serial.println( "" );
  Serial.print( "Connected to " );
  Serial.println(ssid);
  Serial.print( "IP address: " );
  Serial.println( WiFi.localIP() );
#endif

#ifdef USE_M5STACK
  M5.Lcd.print( "IP: " );
  M5.Lcd.println( WiFi.localIP());
#endif
  if ( MDNS.begin( mdns_name )) {
    Serial.printf("MDNS responder started : %s.local\n", mdns_name );

  }


  // BME280
  //Example Indoor navigation
  uint8_t t_sb = 0; //stanby 0.5ms
  uint8_t filter = 4; //IIR filter = 16
  uint8_t osrs_t = 2; //OverSampling Temperature x2
  uint8_t osrs_p = 5; //OverSampling Pressure x16
  uint8_t osrs_h = 1; //OverSampling Humidity x1
  uint8_t Mode = 3; //Normal mode

  bme280i2c.ESP32_BME280_I2C_Init(t_sb, filter, osrs_t, osrs_p, osrs_h, Mode);

  Wire.begin();

  // I2C check
  if ( 1 ) {
    Wire.beginTransmission( Address );
    byte error = Wire.endTransmission();
    if ( error != 0 ) {
#ifdef USE_M5STACK
      M5.Lcd.printf( "Error: Not found BME280 at 0x%x\n", Address );
#endif
      Serial.printf( "Error: Not found BME280 at 0x%x\n", Address );
      msgError();
    }
  }
  syncNtp();

  struct tm tmstruct ;
  // delay(2000);
  tmstruct.tm_year = 0;
  getLocalTime(&tmstruct, 5000);
  Serial.printf("Now %d-%02d-%02d %02d:%02d:%02d\n",
                (tmstruct.tm_year) + 1900,
                ( tmstruct.tm_mon) + 1,
                tmstruct.tm_mday,
                tmstruct.tm_hour ,
                tmstruct.tm_min,
                tmstruct.tm_sec
               );
  Serial.println("");
#ifdef USE_M5STACK
  M5.Lcd.printf( "Time: %d-%02d-%02d %02d:%02d:%02d\n",
                 (tmstruct.tm_year) + 1900,
                 ( tmstruct.tm_mon) + 1,
                 tmstruct.tm_mday,
                 tmstruct.tm_hour ,
                 tmstruct.tm_min,
                 tmstruct.tm_sec
               );
#endif

#ifdef USE_WEBSERVER
  server.on( "/", putBme280Json);
  server.on( "/bme", putBme280Json );

  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");
#ifdef M5STACK
  M5.Lcd.println( "HTTP Server Started" );
#endif
#endif

  statusLed( STATUS_IDLE );



#if defined( USE_M5STACK ) || defined( USE_LCD )
  lcdWakeup();
  // M5.Lcd.println( "" );

  initLcd();
  // これ以降は、M5.Lcd.printシリーズは利用しない
  // dispLcd( x, y, size, fmt, ... );
  // dispLcdColor( x, y, size, color, fmt, ... );
#endif

#if defined( USE_OTA )
  initOta();
#endif
  Serial.print( "Setup() Core is is " );
  Serial.println( xPortGetCoreID() );

#if defined( USE_MQTT )
  //connectMqtt();
#endif

  xTaskCreatePinnedToCore(taskSystem, "taskSystem", 8192, NULL, 10 /* Priolity */ , NULL, 1 /* Core ID */);

#if defined( USE_WEBSERVER ) && !defined( USE_DEEP_SLEEP )
  xTaskCreatePinnedToCore(taskWebServer, "taskWebServer", 8192, NULL, 5 /* Priolity */ , NULL, 1 /* Core ID */);
#endif

#if defined( USE_APRS ) && !defined( USE_DEEP_SLEEP )
  xTaskCreatePinnedToCore(taskAprs, "taskAprs", 8192, NULL, 20 /* Priolity */ , NULL, 1 /* Core ID */);
#endif

#if defined( USE_MORSE_LED )
  if ( morse_led > 0 ) {
#if defined( USE_APRS )
    setMorse( callsign_ssid );
#endif
    xTaskCreatePinnedToCore(taskMorseLed, "taskMorseLed", 8192, NULL, 10 /* Priolity */ , NULL, 1 /* Core ID */);

  }
#endif

#if defined( USE_MQTT )
  //xTaskCreatePinnedToCore(taskMqtt, "taskMqtt", 8192, NULL, 40 /* Priolity */ , NULL, 1 /* Core ID */);
#endif

#ifdef USE_DEEP_SLEEP
  send_aprs();
  delay(2000);
  aprs.stop();
  Serial.println( "Goto Deep Sleep" );

  esp_deep_sleep( aprs_update_interval * 60000000 - ( millis() - system_start_time ) * 1000 );

#else
  delay(1000);
#endif
}





void loop(void) {

#ifndef USE_DEEP_SLEEP
  keepWifi();
#endif

  delay( 10000 );
  //Serial.println( "loop()" );

}




void initWiFi()
{
  int n;
  char * ssid, * password;
  char buf[256];

  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, "," );



    wifiMulti.addAP( ssid, password );

  }
  WiFi.config( local_IP, gateway, subnet, primaryDNS );

  Serial.println("Connecting Wifi...");
  if (wifiMulti.run() == WL_CONNECTED) {
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
  }
}



int readThpAvg( double * t, double * p, double * h )
{
  double temperature = 0.0, humidity = 0.0, pressure = 0.0;
  double t_t, t_h, t_p ;

  int c = 0 ;
  int n = 30; // 取得回数
  int i;

  for ( i = 0 ; i < n ; i++ ) {
    c = 0;
    do {
      bme280i2c.Read_All( &t_t, &t_p, &t_h);
      delay( 10 );
      c++;
      if ( c > 40 ) {
        ESP.restart();

      }
    } while ( t_h == 0 || t_h == 100 );
    temperature  += t_t;
    humidity     += t_h;
    pressure     += t_p;

  }
  * t =  temperature / n;
  * p =  pressure / n;
  * h =  humidity / n;

  return 0;
}

int readThp( double * t, double * p, double * h )
{
  double temperature, humidity, pressure;
  int c = 0 ;

  do {
    bme280i2c.Read_All( &temperature, &pressure, &humidity);
    delay( 250 );
    c++;
    if ( c > 40 ) {
      ESP.restart();

    }
  } while ( humidity == 0 || humidity == 100 );
  * t =  temperature;
  * p =  pressure;
  * h =  humidity;

  return 0;
}


void msgError()
{
  int cnt = 0;
  Serial.println( "abort system due to ERROR" );
#ifdef USE_M5STACK

  M5.Lcd.printf( "Abort system due to error\n" );
  while ( 1 ) {
    delay( 1000 );
  }
#else
  while ( 1 ) {
    if ( busy_led != 0 ) {
      digitalWrite( busy_led, HIGH );
      if ( morse_led != 0 && morse_led != busy_led )
        digitalWrite( morse_led, LOW );
      delay( 50 );
      digitalWrite( busy_led, LOW );
      if ( morse_led != 0 && morse_led != busy_led )
        digitalWrite( morse_led, HIGH );
      delay( 50 );
    }
    cnt++;
    if ( cnt > 100 )
      ESP.restart();
  }
#endif
}


//
// System Loop Task
//   display Clock and Tenperature, Humidity
void taskSystem( void * arg )
{
  static time_t nextMorse = 0;
  static boolean setNextMorse = true;

  Serial.print( "taskSystem() Core is is " );
  Serial.println( xPortGetCoreID() );

  while ( 1 ) {
    //Serial.println( "taskSystem() loop" );
#if defined( USE_M5STACK )
    M5.update();
    if ( M5.BtnA.isPressed() ) {
      lcdWakeup();
    }
    if ( M5.BtnC.isPressed() ) {
      lcdSleep();
    }

    dispStatus();
    lcdAutoSleep();
#endif

#if defined( USE_LCD )
    dispStatus();
#endif

#if defined( USE_MORSE_LED )
    if (setNextMorse &&  now() > nextMorse ) {
      setNextMorse = false;
      statusMorse();
    }
    if ( !setNextMorse && getMorseQueN() == 0 ) {
      // Serial.println( "set NextMorse time" );
      setNextMorse = true;
      nextMorse = now() + 30;

    }
#endif
#if defined( USE_OTA )
    ArduinoOTA.handle();
#endif
    delay( 100 );
  }
}

#if defined( USE_WEBSERVER )
//
//  WebServer Task
//
void taskWebServer( void * arg )
{
  Serial.print( "taskWebServer() Core is is " );
  Serial.println( xPortGetCoreID() );
  while ( 1 ) {
    server.handleClient();
    delay( 30 );
  }
}
#endif

#if defined( USE_APRS )
//
// APRS関係のタスク
//
void taskAprs( void * arg )
{
  static time_t packet_send_time = 0;
  int c;
  Serial.print( "taskAprs() Core is is " );
  Serial.println( xPortGetCoreID() );
  while ( 1 ) {
    while ( aprs.available()) {
      c = aprs.read();
      // Serial.print( aprs.read());
    }
    //Serial.println( "taskAprs() loop" );
    if ( now() > packet_send_time ) {
      packet_send_time = now() + 60 * aprs_update_interval; // 5min
      send_aprs();
    }
    delay( 1000 );
  }

}
#endif

#if defined( USE_MQTT )
void taskMqtt( void * arg )
{

  static time_t send_time = 0;
  delay( 1500 );
  while ( 1 ) {
    if ( now() > send_time ) {
      send_time = now() + 29; /* 30 sec interval */
      // delay( 1000 );
      sendMqtt();

    }
    delay( 1000 );
  }
}

boolean sendMqtt()
{
  double temperature, humidity, pressure;
  char buf[256];

  readThpAvg( &temperature, &pressure, &humidity);
  if ( pressure == 0 ) {
    Serial.println( "taskMqtt(): illegal data (humidity is zero)" );
  } else {

    //sprintf( buf, "{\"callsign\":\"%s\",\"temperature\":%.2lf,\"humidity\":%.2lf,\"pressure\":%.2lf,\"time\":%ld}", callsign_ssid, temperature, humidity, pressure, now() );
    sprintf( buf, "{\"callsign\":\"%s\",\"temperature\":%lf,\"humidity\":%.lf,\"pressure\":%lf,\"time\":%ld,\"latitude\":%f,\"longitude\":%f}",
             callsign_ssid, temperature, humidity, pressure, now() , lat, lng);

    if ( (strlen( buf ) + 2 + MQTT_MAX_HEADER_SIZE + strlen( mqtt_topic )) > MQTT_MAX_PACKET_SIZE ) {
      Serial.printf( "MQTT over max packet size (%d) < (%d) packet is %s\n", MQTT_MAX_PACKET_SIZE, strlen( buf ), buf );
      return false;
    }
    Serial.printf( "MQTT sending..size %d.", strlen( buf ) );
    keepWifi();
    keepMqtt();
    if ( mqttClient.publish( mqtt_topic, buf ) == false ) {
      Serial.println( "MQTT send fail" );
    }
    mqttClient.loop();

    Serial.println( "..done" );
  }
  return true;
}

void keepMqtt()
{
  if ( !mqttClient.connected())
    connectMqtt();

}

void connectMqtt()
{

  mqttClient.setServer( mqtt_host, mqtt_port );
  while ( ! mqttClient.connected() ) {
    Serial.printf("Connecting to MQTT...%s:%d  ", mqtt_host, mqtt_port );
    String clientId = "ESP32-" + String(random(0xffff), HEX);
    if ( mqttClient.connect(clientId.c_str()) ) {
      Serial.println("connected");
    }
    delay(1000);
    randomSeed(micros());
  }
}
#endif

#if defined( USE_MORSE_LED )
void taskMorseLed( void * arg )
{
  while ( 1 ) {
    doMorseLed();
    delay( 330 );
  }
}

#endif
//
// NTPサーバとの同期を試み、同期が完了するまで待つ
//
void syncNtp()
{
  time_t t;
  int cnt = 0;

  Serial.print("Contacting Time Server");
#ifdef USE_M5STACK
  M5.Lcd.println( "Connecting to Time Server..." );
#endif

  configTime(3600 * timezone, daysavetime * 3600,
             "ntp.jst.mfeed.ad.jp",
             "time.google.com",
             "ntp.nict.jp"
            );
  // configTimeは非同期で動く? ので delayなどである程度待ってあげる必要がある

  do {
    time( &t );
    Serial.print( t );
    Serial.print( ", " );
    delay( 1000 );
    cnt++;
    if ( cnt > 30 ) {
      Serial.println( "Reboot due to fail (Sync NTP)" );
      ESP.restart();
    }
  } while ( t < 1000 );  // こんなやり方が正しいのかは?

  Serial.println( " done." );
}

time_t now()
{
  time_t t;

  time( &t );
  return t;
}

void statusLed( int sw )
{
  static int cnt  = 0;
  if ( sw > 0 )
    cnt++;
  else
    cnt--;
  /*
    Serial.print( "statusLed() &c =" );
    char buf[30];
    sprintf( buf, "%ld", &cnt );
    Serial.print( buf );
    Serial.print( ", " );
    Serial.print( cnt );
    Serial.println( "" );

  */
#ifdef USE_M5STACK
  //  M5.Lcd.fillEllipse( 310, 10, 10, 10, s > 0 ? TFT_RED : TFT_GREEN );
  M5.Lcd.setTextSize( 1 );
  if ( cnt > 0 ) { /* budy */
    M5.Lcd.setTextColor( TFT_WHITE, TFT_RED );
  } else {
    M5.Lcd.setTextColor( TFT_WHITE, TFT_DARKGREEN );
  }
  M5.Lcd.setCursor( 290, 0 );
  M5.Lcd.print( cnt > 0 ? "Busy" : "Idle" );
  M5.Lcd.setTextColor( TFT_WHITE, TFT_BLACK );
#endif

  if ( busy_led == 0 )
    return;
  digitalWrite( busy_led, cnt > 0 ? HIGH : LOW );
}

void keepWifi()
{
#if 1
  int retry = 0;

  while (WiFi.status() != WL_CONNECTED) {
    delay(2000);
    wifiMulti.run();

    Serial.print( WiFi.status() );
    retry++;

    if ( retry > 10 ) {
      Serial.println( "" );
      Serial.println( "Restart system due to fail wifi connection" );
#ifdef USE_M5STACK
      M5.Lcd.printf( "Restart System due to fail Wifi connection\n" );
      delay( 3000 );

#endif
      ESP.restart();
    }
  }
#else
  int retry = 0;

  while (WiFi.status() != WL_CONNECTED) {
    delay(2000);
    WiFi.begin();
    Serial.printf(" Wifi. %s ", ssid );
    Serial.print( WiFi.status() );
    retry++;

    if ( retry > 10 ) {
      Serial.println( "" );
      Serial.println( "Restart system due to fail wifi connection" );
#ifdef USE_M5STACK
      M5.Lcd.printf( "Restart System due to fail Wifi connection\n" );
      delay( 3000 );

#endif
      ESP.restart();
    }
  }
#endif
}

#ifdef USE_APRS
void send_aprs()
{
  String packet;
  double temperature, pressure, humidity, pressure_adj;
  String msg;
  char pos[30];
  char timestr[30];
  char sendtimestr[30];
  struct tm tmstruct ;

#ifdef USE_M5STACK
  lcdWakeup();
#endif

  statusLed( STATUS_BUSY );

  getLocalTime(&tmstruct, 5000);

  sprintf( sendtimestr, "%d-%02d-%02d %02d:%02d:%02d",
           (tmstruct.tm_year) + 1900,
           ( tmstruct.tm_mon) + 1,
           tmstruct.tm_mday,
           tmstruct.tm_hour ,
           tmstruct.tm_min,
           tmstruct.tm_sec
         );

  Serial.printf("Now %s\n", sendtimestr );

  sprintf( timestr, "%02d%02d%02dz", tmstruct.tm_mday, tmstruct.tm_hour , tmstruct.tm_min );

  // connect to aprs-id
  if ( !aprs.connected()) {
#ifdef USE_M5STACK
    dispLcd( 0, 190, 1, "Connecting to APRS-IS %s:%d", host, port );
#endif
    if (!aprs.connect(host, port)) {
      Serial.println("connection failed");
      statusLed( STATUS_IDLE );
      return;
    }

    Serial.printf( "connected to APRS-IS %s:%d\n", host, port  );
#ifdef USE_M5STACK
    dispLcd( 0, 190, 1, "Connected to APRS-IS %s:%d", host, port );
#endif
    if ( aprs.connected()) {
      char cmd[256];

      sprintf( cmd, "user %s pass %d vers KetaiTrackerWX %s ESP32+BMP280",
               callsign_ssid,
               passcode,
               ktwx_version
             );
      aprs.println( cmd );
      Serial.println( cmd );
      delay( 100 );
      while ( aprs.available() == 0 ) {
        Serial.print( 'wait.' );
        delay( 100 );

      }
      String line = aprs.readStringUntil('\r');
      Serial.print(line);
    }
  }
  readThpAvg(&temperature, &pressure, &humidity);

  pressure_adj = adjustPressure(  pressure,  temperature,  altitude );
  //Serial.println ( pressure );
  //Serial.println ( pressure_adj );

  char temp_c[10], hum_c[10], pres_c[10];
  if ( send_temperature ) {
    sprintf( temp_c, "t%03.0lf", temperature * 9 / 5.0 + 32 );
  } else {
    sprintf( temp_c, "t..." );
  }
  if ( send_humidity ) {
    sprintf( hum_c,  "h%02.0lf", humidity);
  } else {
    sprintf( hum_c, "h.." );
  }
  sprintf( pres_c, "b%05.0lf", pressure_adj * 10);

  sprintf( pos,    "%02d%05.2fN/%03d%05.2fE",
           (int)lat,
           (lat - (int)lat) * 60.0,

           (int)lng,
           (lng - (int)lng) * 60.0
         );

  char p[60];

  sprintf( p, "%s>APPT30,TCPIP*:", callsign_ssid );
  packet = p;

  if ( 0 ) {
    packet += "!";
  } else {
    packet += "@";
    packet += timestr;

  }
  packet += pos;
  packet += "_";

  // packet += "000 / 000";
  packet += ".../...";
  //packet += "c...s...g..."; // Wind
  //packet += "c000s000g000";
  packet += temp_c;
  packet += hum_c;
  packet += pres_c;
  //packet += "     ESP32 + BME280";

  Serial.println( packet );
#ifdef USE_M5STACK
  M5.Lcd.setTextSize( 1 );
  M5.Lcd.setCursor( 0, 180 );
  M5.Lcd.printf( "%s UTC", sendtimestr );
  M5.Lcd.setCursor( 0, 200 );
  M5.Lcd.print( "Sending: " );
  M5.Lcd.println(  packet );
#endif
  aprs.println( packet );

  if ( 0 ) {
    //aprs.flush();  // Disconnectもしてしまうみたい
    delay(1000);
    //aprs.stop();  // Disconnect
  }
  statusLed( STATUS_IDLE );

}

#endif
//  end of #USE_APRS

#ifdef USE_ANALOG
char * getAnalogJson()
{
  // TODO: 後日 C++ っぽく書き直す
  //  違うやり方があるけど、しばらくこのまま。。
  static char buf[256];
  String msg;
  int  i, n;
  n = sizeof( analog_gpio ) / sizeof( int );


  msg = "\"analog\":[";


  for ( i = 0; i < n ; i++ ) {
    if ( i != 0 )
      msg += ",";
    msg += analogRead( analog_gpio[i] );
  }
  msg += "]";

  snprintf( buf, 256, "%s", msg.c_str() );
  //Serial.println( buf );
  return buf;
}

int * getAnalog()
{
  static int buf[10];
  // TODO: 後日 C++ っぽく書き直す
  //  違うやり方があるけど、しばらくこのまま。。
  int  i, n;
  n = sizeof( analog_gpio ) / sizeof( int );
  for ( i = 0; i < n ; i++ ) {
    buf[i] = analogRead( analog_gpio[i] );
  }
  return buf;
}
#endif

#ifdef USE_WEBSERVER


void handleNotFound() {
  //digitalWrite(led, 1);
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";

  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }

  server.send( 404, "text/plain", message);
  // digitalWrite(led, 0);
}


/*
  Web Server
  JSON形式:気圧の高度補正は行わない
*/
void putBme280Json() {
  double temperature, pressure, humidity;
  String msg;
  char temp_c[10], hum_c[10], pres_c[10];

  statusLed( STATUS_BUSY );
  server.sendHeader( "Access-Control-Allow-Origin", "*" );
  server.sendHeader( "Cache-Control", "no-cache");


  readThpAvg(&temperature, &pressure, &humidity);

  sprintf(temp_c, "%.2lf", temperature);
  sprintf(hum_c,  "%.2lf", humidity);
  sprintf(pres_c, "%.2lf", pressure);

  msg = "{\"temperature\":";
  msg +=  temp_c;
  msg += ",\"humidity\":" ;
  msg += hum_c;
  msg += ",\"pressure\":";
  msg += pres_c;
  // msg += ",\"dummy\":123";
#ifdef USE_ANALOG
  msg += ",";
  msg += getAnalogJson();
#endif
  msg += ",\"version\":";
  msg += "\"";
  msg += ktwx_version;
  msg += "\"" ;

  msg += "}";
  //msg += "\n";
  server.send( 200, "application/json", msg );
  //server.send( 200, "text/plain", msg );
  //delay( 500 );

  statusLed( STATUS_IDLE );
}
#endif

#if defined( USE_M5STACK ) || defined( USE_LCD )
void initLcd()
{
#if defined( USE_M5STACK )
  M5.Lcd.setBrightness( LCD_BRIGHTNESS );
  M5.Lcd.clear();
  dispLcd( 0, 0, 1, "KetaiTracker WX %s", ktwx_version  );
  dispLcd( 160, 0, 1, "IP: %s", WiFi.localIP().toString().c_str() );

#ifdef USE_APRS
  dispLcd( 0, 10 , 1, "Callsign: %s", callsign_ssid );
  dispLcd( 0, 18, 1, "APRS-IS: %s:%d", host, port );
#endif
  initGraph();
#endif

#if defined( USE_LCD )
  display.clearDisplay();
  display.display();
#endif
}

//
//

#ifndef SLEEP_TIME
#define SLEEP_TIME 0
#endif

time_t nextSleepTime = 0;
time_t lcdSleepTime = SLEEP_TIME;
boolean isSleep = false;

void lcdSleep()
{
#if defined( USE_M5STACK )
  if ( isSleep )
    return;

  isSleep = true;
  if ( lcdSleepTime > 5 ) {
    for ( int i = LCD_BRIGHTNESS; i > 10; i-- ) {
      M5.Lcd.setBrightness(i);
      delay( 15 );
    }
  }

  M5.Lcd.sleep();
  M5.Lcd.setBrightness(0);
#endif
  Serial.println( "LCD sleep" );
}

void lcdWakeup()
{
#if defined( USE_M5STACK )
  lcdResetSleep();
  if ( !isSleep )
    return;
  isSleep = false;

  M5.Lcd.wakeup();
  M5.Lcd.setBrightness(LCD_BRIGHTNESS);
  Serial.println( "LCD Wakeup" );
#endif
}


void lcdResetSleep()
{
  nextSleepTime = now() + lcdSleepTime;

}

void lcdAutoSleep()
{

  if ( lcdSleepTime == 0 )
    return;

  if ( now() > nextSleepTime ) {
    lcdSleep();
  }
}





void dispLcd( uint16_t x, uint16_t y, uint16_t size, const char * fmt, ... )
{
  char buf[1024];

  va_list arg;
  va_start(arg, fmt );
  vsnprintf( buf, 1024, 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 dispLcdColor( uint16_t x, uint16_t y, uint16_t size, uint16_t color, const char * fmt, ... )
{
  char buf[1024];

  va_list arg;
  va_start(arg, fmt );
  vsnprintf( buf, 1024, 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, BLACK  );
  display.setCursor( x, y );
  display.setTextSize( size );
  display.print( buf );
  display.display();
#endif

  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[1024];

  va_list arg;
  va_start(arg, fmt );
  vsnprintf( buf, 1024, 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);
}
#if !defined( USE_M5STACK )
#define TFT_RED WHITE
#define TFT_WHITE WHITE
#define TFT_GREEN WHITE
#define TFT_CYAN WHITE
#endif


#define COLOR_TEMPERATURE TFT_RED
#define COLOR_HUMIDITY    TFT_CYAN
#define COLOR_PRESSURE    TFT_GREEN

#define COLOR_INC         TFT_ORANGE
#define COLOR_DEC         TFT_CYAN

#define DATA_STACK 600

double calc_avg( double v[], int n )
{
  int i;
  double t = 0.0;

  for ( i = 0 ; i < DATA_STACK && i < n ; i++ ) {
    t += v[i];
  }

  return t / i;
}

void dispStatus()
{
  static time_t nextUpdate = 0;

  // TODO: 構造体を使ってスッキリさせる
  double temperature, pressure, humidity;
  static double  temperature_max = -99.0, pressure_max = 0.0, humidity_max = 0.0;
  static double  temperature_min = 99.9, pressure_min = 2000.0, humidity_min = 100.0;
  static double  temperature_prev = 0, pressure_prev = 0, humidity_prev = 0;
  static double  temperature_stack[DATA_STACK], pressure_stack[DATA_STACK], humidity_stack[DATA_STACK];
  static int data_stack = 0;
  static int data_stack_p ;

  uint16_t fg_t, fg_h, fg_p;
  uint16_t bg_t_max, bg_t_min, bg_p_max, bg_p_min, bg_h_max, bg_h_min;
  int x, y;

  if ( nextUpdate > now())
    return;

  nextUpdate = now() + 1;

  //  Current time
  struct tm tmstruct ;

  getLocalTime(&tmstruct, 5000);

  /*
    M5.Lcd.setCursor( 0, 140 );
    M5.Lcd.setTextSize( 1 );
    M5.Lcd.printf( "%d-%02d-%02d %02d:%02d:%02d",
                   (tmstruct.tm_year) + 1900,
                   ( tmstruct.tm_mon) + 1,
                   tmstruct.tm_mday,
                   tmstruct.tm_hour ,
                   tmstruct.tm_min,
                   tmstruct.tm_sec
                 );
  */

  dispLcdColor( 0, 140, 1, TFT_WHITE, "%d-%02d-%02d %02d:%02d:%02d UTC",
                (tmstruct.tm_year) + 1900,
                ( tmstruct.tm_mon) + 1,
                tmstruct.tm_mday,
                tmstruct.tm_hour ,
                tmstruct.tm_min,
                tmstruct.tm_sec);
  //
#if defined( USE_LCD )
  dispLcdColor( 0, 25, 1, WHITE, "%d/%02d/%02d %02d:%02d:%02d Z",
                (tmstruct.tm_year) + 1900,
                ( tmstruct.tm_mon) + 1,
                tmstruct.tm_mday,
                tmstruct.tm_hour ,
                tmstruct.tm_min,
                tmstruct.tm_sec);
#endif
  readThp(&temperature, &pressure, &humidity);


  /*
    M5.Lcd.setCursor( 0, 150 );
    M5.Lcd.setTextSize( 2 );
    M5.Lcd.printf( "T:%s H:%s P:%s", temp_c, hum_c, pres_c );
  */
  //dispLcd( 0, 150, 2, "T:%s H:%s P:%s", temp_c, hum_c, pres_c );
#if defined( USE_M5STACK )
  /*
    char temp_c[10], hum_c[10], pres_c[10];
    sprintf(temp_c, "%5.2lf", temperature);
    sprintf(hum_c, "%5.2lf", humidity);
    sprintf(pres_c, "%7.2lf", pressure);
  */

  /*
      過去1分平均
  */
  data_stack_p = data_stack % DATA_STACK;
  temperature_stack[data_stack_p] = temperature;
  humidity_stack[data_stack_p]    = humidity;
  pressure_stack[data_stack_p]    = pressure;

  data_stack++;

  fg_t = fg_h = fg_p = TFT_WHITE;

  if ( data_stack >= DATA_STACK || 1) {
    temperature_prev = calc_avg( temperature_stack, data_stack );
    humidity_prev    = calc_avg( humidity_stack, data_stack );
    pressure_prev    = calc_avg( pressure_stack, data_stack );


    if ( temperature > temperature_prev )
      fg_t = COLOR_INC;
    if ( temperature < temperature_prev )
      fg_t = COLOR_DEC;

    if ( humidity > humidity_prev )
      fg_h = COLOR_INC;
    if ( humidity < humidity_prev )
      fg_h = COLOR_DEC;

    if ( pressure > pressure_prev )
      fg_p = COLOR_INC;
    if ( pressure < pressure_prev )
      fg_p = COLOR_DEC;

  }

  y = 150;
  x = 20;
  dispLcdColor( 0, y, 1, TFT_WHITE, "Now" );

  dispLcdColor( x, y, 2, COLOR_TEMPERATURE, "T:" );
  dispLcdColor( M5.Lcd.getCursorX(), y, 2, fg_t, "%5.2lf", temperature );

  dispLcdColor( M5.Lcd.getCursorX(), y, 2, COLOR_HUMIDITY, " H:" );
  dispLcdColor( M5.Lcd.getCursorX(), y, 2, fg_h, "%5.2lf", humidity );

  dispLcdColor( M5.Lcd.getCursorX(), y, 2, COLOR_PRESSURE, " P:" );
  dispLcdColor( M5.Lcd.getCursorX(), y, 2, fg_p, "%7.2lf", pressure );

  /* 過去平均 */
  y = 170;
  dispLcdColor( 0, y, 1, TFT_WHITE, "Avg" );
  dispLcdColor( x, y, 2, COLOR_TEMPERATURE, "T:" );
  dispLcdColor( M5.Lcd.getCursorX(), y, 2, TFT_WHITE, "%5.2lf", temperature_prev );

  dispLcdColor( M5.Lcd.getCursorX(), y, 2, COLOR_HUMIDITY, " H:" );
  dispLcdColor( M5.Lcd.getCursorX(), y, 2, TFT_WHITE, "%5.2lf", humidity_prev );

  dispLcdColor( M5.Lcd.getCursorX(), y, 2, COLOR_PRESSURE, " P:" );
  dispLcdColor( M5.Lcd.getCursorX(), y, 2, TFT_WHITE, "%7.2lf", pressure_prev );

  if ( setGraphDataInterval( temperature, humidity, pressure )) {
    drawGraphAll();
  }

  /*
     最高、最低を表示(ただし表示スペースの関係でAPRSを利用しない場合に限る)
  */
#if !defined( USE_APRS )
  bg_t_max = bg_t_min = bg_p_max = bg_p_min = bg_h_max = bg_h_min = TFT_BLACK;

  if ( temperature_max < temperature ) {
    temperature_max = temperature;
    bg_t_max = TFT_RED;
  }

  if ( temperature_min > temperature ) {
    temperature_min = temperature;
    bg_t_min = TFT_BLUE;
  }

  if ( humidity_max < humidity ) {
    humidity_max = humidity;
    bg_h_max = TFT_RED;
  }

  if ( humidity_min > humidity ) {
    humidity_min = humidity;
    bg_h_min = TFT_BLUE;
  }

  if ( pressure_max < pressure ) {
    pressure_max = pressure;
    bg_p_max = TFT_RED;
  }

  if ( pressure_min > pressure ) {
    pressure_min = pressure;
    bg_p_min = TFT_BLUE;
  }

  y = 200;
  dispLcdColor( 0, y, 1, TFT_WHITE, "Max" );
  dispLcdColor( x, y, 2, COLOR_TEMPERATURE, "T:" );
  dispLcdColorBg( M5.Lcd.getCursorX(), y, 2, TFT_WHITE, bg_t_max, "%5.2lf", temperature_max );

  dispLcdColor( M5.Lcd.getCursorX(), y, 2, COLOR_HUMIDITY, " H:" );
  dispLcdColorBg( M5.Lcd.getCursorX(), y, 2, TFT_WHITE, bg_h_max, "%5.2lf", humidity_max );

  dispLcdColor( M5.Lcd.getCursorX(), y, 2, COLOR_PRESSURE, " P:" );
  dispLcdColorBg( M5.Lcd.getCursorX(), y, 2, TFT_WHITE, bg_p_max, "%7.2lf", pressure_max );

  y += 17;
  dispLcdColor( 0, y, 1, TFT_WHITE, "Min" );
  dispLcdColor( x, y, 2, COLOR_TEMPERATURE, "T:" );
  dispLcdColorBg( M5.Lcd.getCursorX(), y, 2, TFT_WHITE, bg_t_min, "%5.2lf", temperature_min );

  dispLcdColor( M5.Lcd.getCursorX(), y, 2, COLOR_HUMIDITY, " H:" );
  dispLcdColorBg( M5.Lcd.getCursorX(), y, 2, TFT_WHITE, bg_h_min, "%5.2lf", humidity_min );

  dispLcdColor( M5.Lcd.getCursorX(), y, 2, COLOR_PRESSURE, " P:" );
  dispLcdColorBg( M5.Lcd.getCursorX(), y, 2, TFT_WHITE, bg_p_min, "%7.2lf", pressure_min );
  if ( 0 ) {
    temperature_prev = temperature;
    humidity_prev    = humidity;
    pressure_prev    = pressure;
  }
#endif





#endif

#if defined( USE_LCD )

  //dispLcdColor( 0, 0, 1, WHITE, "T:%s", temp_c );
  //dispLcdColor( 0, 16, 1, WHITE, "H:%s", hum_c );
  //dispLcdColor( 64, 8, 1, WHITE, "P:%s", pres_c );
  dispLcdColor( 0, 0, 1, WHITE, "%5.2lf %5.2lf %7.2lf", temperature, humidity, pressure );
#if defined( USE_ANALOG )
  int n, i;
  n = sizeof( analog_gpio ) / sizeof( int );
  for ( i = 0 ; i < n ; i++ ) {
    dispLcdColor( i * 30 , 10, 1, WHITE, "%4d ", analogRead( analog_gpio[i] ));
  }
#endif
#endif
}

//
// グラフ
//
#define GRAPH_X 0
#define GRAPH_Y 39
#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

void initGraph()
{
#if defined( USE_M5STACK )
  M5.Lcd.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT, BLACK );
  M5.Lcd.drawRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT, WHITE );
#endif
}

void clearGraph()
{
#if defined( USE_M5STACK )
  M5.Lcd.fillRect( GRAPH_X + 1, GRAPH_Y + 1, GRAPH_WIDTH - 2, GRAPH_HEIGHT - 2, 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;

  if ( nextTime > now())
    return false;

  nextTime = now() + GRAPH_INTERVAL; // グラフの描画間隔
  setGraphData( temperature, humidity, pressure );

  return true;
}

void setGraphData( double temperature, double humidity, double pressure )
{
  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--;
  }
  //Serial.println( graphDataN  );
  graphData[graphDataN][SEED_TEMPERATURE] = temperature;
  graphData[graphDataN][SEED_HUMIDITY]    = humidity;
  graphData[graphDataN][SEED_PRESSURE]    = pressure;
  graphDataN++;

}

void drawGraphAll()
{
  clearGraph();
  drawGraph( SEED_TEMPERATURE, COLOR_TEMPERATURE );
  drawGraph( SEED_HUMIDITY,    COLOR_HUMIDITY    );
  drawGraph( SEED_PRESSURE,    COLOR_PRESSURE    );
}

void drawGraph(uint16_t seed, uint16_t color)
{
  double max = -10000.0;
  double min = 10000.0;
  int x, y;

  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++ ) {
    y = GRAPH_Y + GRAPH_HEIGHT - ((graphData[x][seed] - min) / (max - min ) * GRAPH_HEIGHT);
    //Serial.print( y ); Serial.print( "," );
#if defined( USE_M5STACK )
    M5.Lcd.drawPixel( x + 1, y, color );
#endif
  }
  //Serial.println( "" );

}
#endif



//
// Morse LED
//
#ifdef USE_MORSE_LED

void statusMorse()
{
  double temperature, pressure, humidity;
  char buf[256];

  readThp(&temperature, &pressure, &humidity);

  sprintf( buf, "T %.0f  H %2d  P %d", temperature, (int)humidity, (int)pressure );
  setMorse( buf );
}

void doMorseLed()
{
  if ( morse_led ==  0 )
    return;

  execMorseFromQue();
}




//
// Todo: 以下、そのうちクラス化しよう
//

#define MORSE_QUE_MAX 10
int morse_que_n = 0;

char * morse_que[ MORSE_QUE_MAX ];
void setMorse( const char * str )
{
  // Serial.printf( "setMorse(): %s Length=%d Que=%d\n", str, strlen( str ), morse_que_n );
  char * p;

  if ( morse_que_n >= MORSE_QUE_MAX ) {
    Serial.println( "Error: too many morse_que in setMorse()" );
    return;
  }
  if ( (p = (char *)malloc( strlen( str ) + 1 )) == NULL ) {
    Serial.println( "Can't allocate memory in setMorse()" );
    return;
  }
  // Serial.printf( "address is %ld\n", p );
  memcpy( p, str, strlen( str ) + 1 );
  morse_que[morse_que_n] = p;
  morse_que_n++;
}

int getMorseQueN()
{
  return morse_que_n;
}

void execMorseFromQue()
{
  static boolean exec = false;
  int q;

  if ( exec != false )
    return;
  if ( morse_que_n == 0 )
    return;
  exec = true;
  // Serial.printf( "execMorseFromQue(): start\n" );

  Serial.printf( "MORSE: %s\n", morse_que[0] );
  ledMorse( morse_que[0]);
  free( morse_que[0] );
  for ( q = 0 ; q < morse_que_n - 1 ; q++ ) {
    morse_que[q] = morse_que[q + 1];
  }
  exec = false;
  morse_que_n--;

  // Serial.printf( "execMorseFromQue(): done\n" );
}





//
//
//

void ledMorse( char * str )
{
  int n, i;

  n = strlen( str );
  for ( i = 0 ; i < n ; i++ ) {
    ledMorseChar( str[i] );
  }
}

const char * morse_code[] = {
  "_______", "", "", "", "", "", "", "", "", "", "", "", "", "", "010101" /* or "010". */, "", /* !-/  32 to 47 */
  "11111", "01111", "00111", "00011", "00001", "00000", "10000", "11000", "11100", "11110", // 0-9
  // "1", "01", "001", "0001", "00001", "00000", "10000", "1000", "100", "10", // 0-9 省略型
  "", "", "", "", "", "", "", // :;<=>?@  省略
  "01", "1000", "1010", "100", "0", "0010", "110", "0000", "00", "0111", "101", "0100", "11", // A-M
  "10", "111", "0110", "1101", "010", "000", "1", "001", "0001", "011", "1001", "1011", "1100", // N-Z
};

void ledMorseChar( char c )
{
  int n, i;
  int code = c - ' ';
  char s;
  /*
    Serial.printf( "sizeof(morse_code ) is %d\n", sizeof( morse_code ) / sizeof( char * ));
    return;
  */
  if ( code < 0 || code > sizeof( morse_code ) / sizeof( char * )) {
    Serial.printf( "Warning: Illegal Morse Char code, code is %d, %c\n", c, c );
    return;

  }

  // Serial.println( code );
  n = strlen( morse_code[code] );
  // Serial.println( n );
  //Serial.printf( "MORSE: %c %s\n", c, morse_code[code] );

  for ( i = 0 ; i < n ; i++ ) {
    switch (  morse_code[code][i] ) {
      case '1':
        // Serial.print( "L" );
        digitalWrite( morse_led, HIGH );
        delay( morse_speed * 3 );
        digitalWrite( morse_led, LOW );
        delay( morse_speed * 1 );
        break;

      case '0':
        // Serial.print( "S" );
        digitalWrite( morse_led, HIGH );
        delay( morse_speed * 1 );
        digitalWrite( morse_led, LOW );
        delay( morse_speed * 1 );
        break;

      case '_':
        delay( morse_speed * 1 );
        break;

    }

    // delay( morse_speed * 3 );

  }
  //Serial.println();
  delay( morse_speed * 3 );
}
#endif

#if defined( 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()
    Serial.println("Start updating " + type);
  })
  .onEnd([]() {
    Serial.println("\nEnd");
  })
  .onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  })
  .onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });

  ArduinoOTA.begin();
  Serial.println( "OTA Enabled" );
}

#endif