コンパイル / スケッチ

スケッチ

$Date: 2022/01/02 07:37:26 $
$Revision: 1.2 $

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

スケッチのダウンロード

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

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

47.4Kbyte
2022-01-02 16:37

現時点での問題点

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

その他の改修予定

  • 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.
  鐚
  Multitask Version.. (2019.09.20-)

      $Id: esp32_bme280_webserver.ino,v 1.3 2022/01/02 07:26:56 yahiro Exp $
*/

const char * ktwx_version = "0.08.0";

/*

   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
   DHCPP≪禮礫礬竇彰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磚祚禹禮asscode

//
// 祉潟泣絎ゅ荐臀禊絎聿県礙羚水墾APRS-IS蜆拭礒秕祿秕祟
// 經絲邃梧┏閻ぐ 0 礒
//  乗PRSс竊県綺羚水墾腥冴若帥礙祚粭т硯礫秕
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] 鐚羂с蒿g

// 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絋經礒

//
// 腮箝莉PIO (ESP32)
//    6,7,8,9,10,11,15 箍禹禹
// M5Stack經繙<PIO紲礑紿腮礒礬腮礑秕
//    0,1,3,18,19,23,25
//    LCD≫ 14,18,23,27,33
//     34-39 INPUT
//  M5Stack經綮祿禝禺礙礒https://amzn.to/34NHbXi 篏帥翫邃梧┴禮禮
//   BME2805Stack礑磚礒竢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經3紿腮
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 ) {
    // M5Stack5綺紲綉禮禺祀禺祟礑蒿絵.
    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 );  // 禮竊c祚

  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++ 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++ 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綵√鐚羂с蜈墾蒿c茯
*/
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: 罕篏篏帥c禮祗礫
  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);
  */

  /*
      サ鐚綛喝
  */
  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();
  }

  /*
     蕭篏茵腓削取;腓差禺禮祟蘂PRS腮礫禊絆蜿
  */
#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, "Min" );
  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, "Max" );
  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 40
#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
印刷日: 2024-12-28 03:11:49
User:
URL: https://ketaitracker.info/esp32-wx/?page=sketch