$Date: 2026/09/29 11:39:12 $
$Revision: 1.4 $
$Revision: 1.4 $
この世界ではソースコードと言わずにスケッチというそうですHi。
スケッチのダウンロード
このままでは動作しません。個別のハードウェア環境、APRS環境、ネットワーク環境に合わせたスケッチの修正が必要です
スケッチの最初の部分に個別設定がありますので、その部分を各自の環境に合わせて修正を行ってください。
最初に #include "jf6lze-13.h"などの部分をコメントアウトしてください。私の個人的設定を読み込むようにしていますが、これは不要です。リリースタイミングによっては jg6ycl-13.h, jg6ycl-15.hを読み込むようにしているかもしれません。
現時点での問題点 / 既知のバグ
本来はマルチタスクにして、いくつかのタスクに分けて処理すべきなのですが、現時点では、シングルタスクです。(システムとしては既にマルチタスクですが、私が作成した部分がすべて同じタスクで動いている状態です。後日、改修予定)[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