NOBのArduino日記!

NOBのArduino日記!

趣味は車・バイク・自転車・ラジコン・電子工作です。

【ESP32→ルーター→ngrok→colab】https通信!

【ESP32→ルーター→ngrok→colab】https通信!
NAT越えを容易にするトンネリングツール「ngrok」を使う事で、家のWiFiルーター設定を変更する事無く、クライアント「ESP32-WROOM-32E」からサーバー「colab」へのhttps通信がサクッと通ると、この先応用が拡がるかなと思います('▽')/
と言う事で【ESP32→無線LANルーター→有線WAN→ngrokcolab】のシンプルな通信実験をしてみました!(
^^)v


1. ngrokとは

ローカル環境で動いているサーバーやアプリを、インターネットからアクセス可能にするトンネリングツールです。
「ローカルと外部世界をつなぐ魔法のトンネル」 とも呼ばれる便利なツールです。開発中のAPIやWebアプリをすぐに外部公開できるので、Webhook検証やデモ、リモート共有に欠かせない存在になっています。 例えば http://localhost:5000 で動いているアプリを、https://xxxx.ngrok-free.app のような公開URLで外部から利用できるようにします。

仕組み

  • 開発PC上で ngrok コマンドを実行
  • ngrok クライアントが ngrok サーバーとセキュアなトンネルを確立
  • ngrok サーバーが一意の公開URLを発行
  • 外部からのアクセスはそのURLを経由してローカルサーバーに転送されます

用途

  • Webhookのテスト GitHubやLINE、Slackなど外部サービスからのコールバックをローカルで受け取れる
  • デモや共有 開発中のアプリをクライアントやチームに即座に見せられる
  • モバイルアプリ開発 スマホアプリからローカルAPIにアクセス可能にする
  • リモートデバッグ 外部の人と同じ環境を共有して動作確認できる

特徴

  • 数行のコマンドで簡単に公開できる
  • デフォルトで HTTPS に対応(セキュア通信)
  • 無料プランではランダムURL、有料プランでは固定ドメインやカスタムドメインが利用可能
  • HTTP/HTTPS だけでなく TCP/UDP のトンネルもサポート

2. Colabとは

Google が提供する クラウド上の Jupyter Notebook 環境です。
「ブラウザだけで使える無料のPython実行環境」とも呼ばれ、機械学習やデータ分析の学習・実験に広く利用されています。
ローカルに環境構築をしなくても、すぐに Python コードを実行でき、さらに GPU や TPU を無料で利用できるのが大きな魅力です。

仕組み

  • ブラウザからアクセスすると、Googleクラウド上に一時的な仮想マシンVM)が割り当てられる
  • ノートブック形式(セル単位)でコードやテキストを記述・実行できる
  • Google Drive と連携してノートブックやデータを保存・共有可能
  • セッションが終了すると VM はリセットされる(使い捨て環境)

用途

  • 機械学習・深層学習の実験(TensorFlow / PyTorch などをすぐ試せる)
  • データ分析・可視化(pandas, matplotlib, seaborn などを利用)
  • Python学習や教育用教材としての利用
  • チームやクライアントとのノートブック共有・共同編集
  • 環境構築不要のプロトタイピングや検証

特徴

  • 無料で GPU / TPU が利用可能(有料版 Colab Pro / Pro+ ではより高性能・長時間利用可)
  • 環境構築不要、ブラウザだけで利用可能
  • Google Drive との親和性が高く、保存・共有が容易
  • 多くのライブラリがプリインストール済みで、すぐに使える
  • 無料版はセッション時間やリソースに制限あり(数時間で切断されることもある)

3. colabのpythonコード

今回作ったプログラムの流れ

  • flask-corsライブラリをインストール
  • ngrok をダウンロード&セットアップ
  • Flask アプリ起動
  • ESP32からJSONを受信
  • 最新データを返す
  • ngrok を HTTPS モードで起動

Flask というWebフレームワークで「/data というURLに対して HTTP の POST リクエストを受け付けます。
HTTP はアプリケーション層のプロトコルで、その下のトランスポート層として TCP/IP を利用しています。

事前準備
ngrokにアクセス → Googleアカウントなどを使いユーザー登録 → ngrokにログイン → Welcome画面の「Run the following command to add your authtoken to the default ngrok.yml configuration file.」下テキストボックス記載の「ngrok config add-authtoken xxxx」の「xxxx」を控えておき、以下「★ご自身のトークンに置き換えてください」の部分に代入します。

# 実行して残っている ngrok プロセスを強制終了する
!pkill -9 ngrok

# 0. 必要ライブラリをインストール
!pip install flask-cors

# 1. ngrokプロセスを停止
print("Stopping ngrok processes (using killall)...")
get_ipython().system('killall -9 ngrok || echo "No ngrok processes found to kill"')

import time, subprocess, requests, os
time.sleep(2)

# 2. ポート5000を解放
print("\nKilling processes using port 5000...")
get_ipython().system('fuser -k 5000/tcp || echo "No process found on port 5000 to kill"')
time.sleep(2)

# 3. ngrok をダウンロード&セットアップ
print("\nDownloading and extracting ngrok...")
get_ipython().system('wget -O ngrok-v3.tgz https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz')
get_ipython().system('tar -xvzf ngrok-v3.tgz')
# ★ご自身のトークンに置き換えてください
get_ipython().system('./ngrok authtoken xxxx')
time.sleep(2)

# 4. Flask アプリ起動
print("\nStarting Flask application...")
from flask import Flask, request, jsonify
from threading import Thread
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # CORSを許可

latest_data = {"message": "No data yet"}

# ESP32からJSONを受信
@app.route('/data', methods=['POST'])
def receive_data():
    global latest_data
    latest_data = request.get_json()
    print("Received:", latest_data)
    return "OK"

# 最新データを返す
@app.route('/latest', methods=['GET'])
def get_latest():
    return jsonify(latest_data)

def run_flask():
    app.run(host="0.0.0.0", port=5000, debug=True, use_reloader=False)

Thread(target=run_flask).start()
time.sleep(5)

# 5. ngrok を HTTPS モードで起動
print("\nStarting ngrok tunnel (HTTPS mode) and getting public URL...")
ngrok_process = subprocess.Popen(
    ["/content/ngrok", "http", "5000"],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE
)

time.sleep(3)

if ngrok_process.poll() is None:
    try:
        tunnels = requests.get("http://localhost:4040/api/tunnels", timeout=30).json()['tunnels']
        https_url = next((t['public_url'] for t in tunnels if t['proto'] == 'https'), None)

        print("\n--- Colab Setup Complete ---")
        if https_url:
            print("ESP32 POST URL:", https_url + "/data")   # ESP32 用
            print("JSON Latest URL:", https_url + "/latest") # JSON確認用
        print("----------------------------")
    except Exception as e:
        print("Error getting ngrok URL:", e)
        print("stderr:")
        print(ngrok_process.stderr.read().decode())
else:
    print("ngrok failed to start")
    print(ngrok_process.stderr.read().decode())

colabで上記コードを実行すると以下の様な「…/data」と「…/latest」の2つのURLが出力されますので共に控えておきます。

Starting ngrok tunnel (HTTPS mode) and getting public URL...

--- Colab Setup Complete ---
ESP32 POST URL: https://xxxx.ngrok-free.dev/data
JSON Latest URL: https://xxxx.ngrok-free.dev/latest
----------------------------

4. ESP32(C++)コード

ArduinoIDE(ver2.3.6使用)で「esp32 by Espressif Systems ver」をhttps通信が可能なverに切り替えます。
ArduinoIDEを開き、ツール→ボード→ボードマネージャー→検索テキストボックス→”ESP32”検索→"esp32 by Espressif Systems ver"項目→プルダウンメニュー→"3.0.3"以上を選択→UPDATEクリック

私の環境で実機試験した結果、以下"〇"記載esp32 by Espressif Systems verが「https:」通信可能でした(^^)/
∟Response code: -1 Error: connection refused
 ∟2.0.11:×
 ∟2.0.14:×
 ∟3.0.0:×
 ∟3.0.2:×
∟Response code: 200 Server response: OK
 ∟3.0.3:〇
 ∟3.0.5:〇
 ∟3.3.1:〇

false: Let's Encrypt ISRG Root X1 証明書(ルートCA) を入手
ngrok の無料ドメイン.ngrok-free.app / .ngrok-free.dev)は Let's Encrypt の証明書で署名されています。
そのルート CA は ISRG Root X1 です。Let's Encrypt ISRG Root X1 の PEM 形式証明書は公式サイト「https://letsencrypt.org/certs/isrgrootx1.pem」から取得。
このPEM 形式証明書の内容を"そのまま"以下コード中の root_ca()内に貼り付けたら「Response code: 200 Server response: OK」

以下コードをArduinoIDEに貼り付け、コード中「★<-自分の環境に合わせて入力ください」4か所をコメントの通り修正しESP32へ書き込み → シリアルモニタを"115200"で開いておく。

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
// WiFi設定
const char* ssid     = "your_ssid"; //★<-自分の環境に合わせて入力ください
const char* password = "your_pass"; //★<-自分の環境に合わせて入力ください
// ngrok の HTTPS エンドポイント
const char* serverUrl = "https://your.ngrok-free.dev/data"; //★<-自分の環境に合わせて入力ください
void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
}
void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    WiFiClientSecure client;

#if false 
    /*true:証明書検証をスキップ
    最初の通信出来る事の確認時に使用、その後証明書が通れば不要*/
    client.setInsecure();
#else
    //★<-自分の環境に合わせて入力ください
    const char* root_ca PROGMEM = R"rawliteral(-----BEGIN CERTIFICATE-----
xxxx your_pem xxxx,
-----END CERTIFICATE-----
)rawliteral";
    client.setCACert(root_ca);  // ルート証明書を設定
#endif
    HTTPClient https;
    if (https.begin(client, serverUrl)) {
      https.addHeader("Content-Type", "application/json");
      https.addHeader("ngrok-skip-browser-warning", "true");
      String payload = "{\"message\":\"Hello from ESP32 (no cert)\"}";
      int httpResponseCode = https.POST(payload);
      Serial.printf("Response code: %d\n", httpResponseCode);
      if (httpResponseCode > 0) {
        Serial.println("Server response: " + https.getString());
      } else {
        Serial.printf("Error: %s\n", https.errorToString(httpResponseCode).c_str());
      }
      https.end();
    } else {
      Serial.println("Unable to connect to server");
    }
  }
  delay(5000); // 5秒ごとに送信
}
}

5. Send JSON to HTML

任意のhttps宛にテキストを送信するシンプルなウェブアプリを作りました(^^)/
クライアント(ESP32)送信機の代わり、サーバー(colabのpythonコード)側が受信できるかの確認に使用

操作方法

  • 上段textbox:4.で控えた「…/data」URLを入力→writeをクリック
  • 中段textbox:「{ "message": "Hello" }」等任意の文字列を指定のURLへ送信
  • 下段textbox:通信が成功すれば「Response 200」と表示

See the Pen Untitled by NOB-Arduino (@NOB-Arduino) on CodePen.

6. Read JSON from HTML

任意のhttpsからテキストを読み取るシンプルなウェブアプリを作りました(^^)/
クライアント(ESP32や上記アプリ)から、サーバー(colabのpythonコード)が受信した内容の確認に使用

操作方法

  • 上段textbox:3.で控えた「…/latest」URLを入力→Startをクリック
  • 中段textbox:「{ "message": "Hello" }」等任意の文字列が表示されたらOK
  • 下段textbox:通信が成功すれば「Response 200」と表示

See the Pen Receiving JSON from HTML by NOB-Arduino (@NOB-Arduino) on CodePen.


7. まとめ

ESP32を使ったhttps通信は初めてでしたので色々ハマりました( ;∀;)
特にESP32のボードVerについてはCopilotに聞いてもわからず、他含め40時間程ハマってしまいました !(*'▽')と言う事で備忘録を残しておきます!

ハマりポイント

  • ESP32のボードマネージャーVer -> 3.0.3以上でないとhttps通信不可
  • Let's Encrypt ISRG Root X1 証明書 -> root_ca変数内に"そのまま"余計なことせずに貼り付ける事!
  • colabのpythonコード -> "CORS(app)"を書くのが無難
 
イメージ 1 イメージ 3
励みになりますのでよければクリック下さい(^o^)/

↩【NOBのArduino日記!】目次に戻る


【ブロック崩し】ブラウザミニゲーム!

See the Pen ブロック崩し by NOB-Arduino (@NOB-Arduino) on CodePen.


1.ゲーム概要

このアプリケーションは、無駄に3D空間で展開されるブロック崩しゲームです。 プレイヤーはマウスでパドルを操作し、跳ね返るボールを使ってブロックを破壊していきます。 ゲームは10段階のレベルで構成されており、レベルに合わせてブロックの色が変化することで、難易度の変化を視覚的に体験できます。ブロックの数を動的に変えられる機能や、ゲームクリア時には花火の演出が表示され、最終レベル到達後には感謝のメッセージ付きエンディング演出が画面中央に表示されるようになっています。

2.操作方法

操作 内容
🖱️ マウス移動 パドルの位置を操作し、ボールの跳ね返しを制御します。
🎚️ スライダー 難易度(レベル)とブロックの行数(空間の複雑さ)を調整できます。
🔘 リスタートボタン ゲームを初期状態にリセットし、再スタートします。
⏎ Enterキー 即座にゲームクリア状態に移行し、花火演出とスコア加算が行われます。

3.まとめ

 Copilotが賢過ぎて、勢いでブロック崩しゲームを作りました。(*^^)v
暇つぶしにどうぞ!

イメージ 1 イメージ 3
励みになりますのでよければクリック下さい(^o^)/

↩【ブラウザミニゲーム!】目次に戻る


Calabi–Yau空間をイメージした円筒構造!

See the Pen Untitled by NOB-Arduino (@NOB-Arduino) on CodePen.


1.アプリケーション概要

「蟻が歩く多次元ストロー」のイメージです。
このアプリケーションは、高次元幾何(特にCalabi–Yau空間)をイメージした円筒構造を、視覚的かつインタラクティブに体験できるツールです。 そのストローの様な円筒表面を、小さな蟻が歩いている様子を通して、空間の複雑さや変化を直感的に感じられるかとおもいます。

2.背景となるアイデア

Calabi–Yau空間は、物理学(特に超弦理論)で登場する複雑な形状の高次元空間です。
このアプリでは、その断面を「花びらのように波打つ円筒」として表現し、時間とともに形が変化します。
さらに、内側にもう1本の円筒が回転しながら存在し、多重構造を表現しています。

3.蟻の役割

 表面を歩く蟻は、観察者の視点の代わりです。
蟻の動きによって、空間の形状や変化を「体験」することができます。
UIで蟻の速度や断面の複雑さ(花びらの数)を調整でき、空間のモジュライ(変形パラメータ)を操作する感覚が得られます。

4.操作方法

操作 内容
🖱️ マウスドラッグ 視点の回転
🎚️ スライダー 蟻の速度・断面の複雑さ(花びら数)を調整
🔘 ボタン 視点のリセット
 

5.まとめ

 昨日の夜、小学生の息子と9次元空間の話になり「見たい!」と言う事で、雰囲気「高次元幾何の直感的理解を促すビジュアルツール」として作成しました。(;'∀')ほぼCopilot

 動きと変形を通じて、抽象的な数学的概念を体験的に学べるかと思います。
「この蟻は、私たちの視点の代わりで歩いてくれてる」 イメージです(*^^)v

 
イメージ 1 イメージ 3
励みになりますのでよければクリック下さい(^o^)/

↩【ブラウザミニゲーム!】目次に戻る


ブラウザミニゲーム! 目次!

ブラウザミニゲーム! 目次

1)ミニゲーム

  1. 【ブロック崩し】ブラウザミニゲーム!
 

2)その他!

  1. Calabi–Yau空間をイメージした円筒構造!
 

イメージ 1 イメージ 3
励みになりますのでよければクリック下さい(^o^)/

↩【NOBのArduino日記!】目次に戻る


グリーンカーテン(2025)!

グリーンカーテン2025!

今年はゴーヤに肥料を一株一握/週あげたおかげで、グリーンカーテンの茂り具合が過去一に仕上がりました(*^^)v

1.散水機

 毎年何処かが壊れますね(;'∀')
今年はタカギのコネクタのゴムシールから水漏れしたので、グリーンカーテンを始めた当初は無かった自在コネクタに交換しました(^^)/

タカギ製自在コネクター (左スリム:G097GYSH、右通常G067CG)
ビフォーアフター

水漏れ止まりました!

2.グリーンカーテンと野菜

グリーンカーテンの他にも色々野菜を育ててみました(*^^)v

今年の仕上がり
トマト3株

オクラ
ワサビ菜

お花

ピーマン

地域🐈の家

ハイビスカス植える為に掘った穴・・・からの放置・・・。

ゴーヤ!

良い仕上がり!

3.まとめ

 2017年から9年連続ゴーヤのグリーンカーテンの土・散水・追肥を試行錯誤して落ち着いた条件は以下の通りです。
 一つでも手を抜くと一瞬で枯れます(´;ω;`)

1.土
 夏の成長したゴーヤは、大量の根を張る土を必要とするので土は多めに!
 ->750サイズのプランター・培養土40L(以上) / 2株
1.肥料

 夏の成長したゴーヤは、大量の栄養(肥料)を必要とするので追肥はこまめに!
 ->一握り / 株・週
2.水
 夏の成長したゴーヤは、大量の水を必要とするので散水はこまめに!
 ->8月の散水回数:24回/日!

#ifndef Arduino
  #include <Arduino.h>
#endif
#define DEBUG false
#include <Wire.h>
#include "DS1307.h"//https://github.com/Seeed-Studio/RTC_DS1307

class PlantWateringClass {
private:
  DS1307 clock;                             //DS1307Classのインスタンス生成
  const int SensorThreshold_Morning = 410;  //SensorReadの値が朝410を超えたら土に水をあげる
  const int SensorThreshold_Night   = 462;  //SensorReadの値が夜475を超えたら土に水をあげる
  const int Hor_Morning = 7;                //朝一の時刻(閾値)
  int SensorPower = 2;                      //土壌湿度センサー電力供給制御ピン番号
  int SolenoidPower = 8;                    //水電磁弁開閉制御ピン番号
  int SwitchIn = A1;                        //水電磁弁の手動ONスイッチ接続ピン番号
  int SensorIn = A3;                        //土壌湿度センサー値取得ピン番号

public:
  typedef struct {
    //変数
    int Yea;
    int Mon;
    int Day;
    int Wee;
    int Hor;
    int Min;
    int Sec;
    //「曜日」列挙
    #ifndef MON //※「DS1307.h」「#define MON 1・・・SUN 7」迄既に定義されているので以下参考1
      enum WEE : byte {MON = 1, TUE, WED, THU, FRI, SAT, SUN};
    #endif
    //「月」列挙
    enum MONTH : byte {
      January = 1,
      February,
      March,
      April,
      May,
      June,
      July,
      August,
      September,
      October,
      November,
      December
    };
  } YMD; YMD ymd;


  /*
  * 時刻調整関数
  * 20190624:インターネット時間-3秒に調整済み
  * 通常の書き込み時は確実にDEBUG値を「false」にする!
  */
  //例:pwc.RTC_time_initialize(true, 2024, 5, 4 ,8, 0, 0);
  void RTC_time_initialize(bool reset, int Y, int M, int D ,int h, int m, int s){
    if(reset){//年,月,日, 時,分,秒,曜日
      RTC_Write( Y, M, D, h, m, s, 0);
    }
  }


  PlantWateringClass(int _SensorPower=2, int _SolenoidPower=8, int _SwitchIn=A1, int _SensorIn=A3) {//PlantWateringClassのコンストラク
    SensorPower = _SensorPower;
    SolenoidPower = _SolenoidPower;
    SwitchIn = _SwitchIn;
    SensorIn = _SensorIn;
    pinMode(SensorPower, OUTPUT);
    pinMode(SolenoidPower, OUTPUT);
    pinMode(SwitchIn, INPUT);
    pinMode(SensorIn, INPUT);
    clock.begin();
  }


  inline void SolenoidWrite(bool argV){digitalWrite(SolenoidPower, argV);}; //TRUE:散水, FALSE:停止
  

  /*
  * 土壌湿度センサーの値を取得する
  */
  bool Sensor_Read(){
    int SensorValue = 0;                                  //SensorValueをint型の変数として宣言(毎回0にリセット)
    for(byte i=0;i<=10;i++){                              //A3ポートの電圧を10回読み平均値を出す
      digitalWrite(SensorPower, HIGH);                    //センサー電力供給開始
      delay(10);                                          //一定時間電流を流して測定値を安定させる
      SensorValue = SensorValue + analogRead(SensorIn);   //センサー電圧を読み合計する
      digitalWrite(SensorPower, LOW);                     //センサー電力供給停止
    }
    SensorValue = SensorValue / 10;                       //センサー電圧平均値を返す
    int SensorThreshold_Data;                             //朝晩によって閾値を変える
    if(ymd.Hor < Hor_Morning){ 
      SensorThreshold_Data = SensorThreshold_Morning;
    }else{
      SensorThreshold_Data = SensorThreshold_Night;
    }
    if(SensorValue > SensorThreshold_Data){               //測定値が閾値より高い場合は1を返す
      return true;
    Serial.println("Senser_ON ");
    }else{
      return false;
    }
    Serial.println("Senser_OFF");
  }


  /*
  * 手動スイッチ入力状態の測定
  */
  bool Switch_Read(){
    if(digitalRead(SwitchIn) == HIGH){
      return true;
    }else{
      return false;
    }
  }


  /* 
  * Serial受信データ【例:h0,1,2,3CRLF】をintに変換しint配列に代入する関数
  */
  inline bool Receive(int* B) {
    int receive_bytes = Serial.available(), command_length;
    static String str_sum;
    unsigned  short get_byte_count;
    if (receive_bytes) {
      str_sum = str_sum + Serial.readStringUntil(0x0A);
      int hed_posi = str_sum.indexOf("h");
      if (hed_posi != -1){
        get_byte_count = str_sum.length();
        int lf_posi = get_byte_count;
        if (lf_posi == -1){
          str_sum.remove(0, lf_posi);
        }else{
          String str_command = str_sum.substring(0, lf_posi);
          str_sum.remove(0, lf_posi + 1);
          str_command.remove(lf_posi - 1);
          str_command.remove(0, 1);
          int k = 0;
          int j = 0;
          char text[256];
          char delim = ',';
          for (int i = 0; i <= str_command.length(); i++) {
            char c = str_command.charAt(i);
            if (c == delim || i == str_command.length()) {
              text[k] = '\0';
              B[j] = atoi(text);
              j++;
              k = 0;
            } else {
              text[k] = c;
              k++;
            }
          }
          return true;
        }
      }
    }
    return false;
  }


  /* 
  * RTCの時刻を修正する関数
  * 使い方      年, 月, 日, 時, 分, 秒, 曜日
  * RTC_Write(2019,  6, 24, 13, 51, 00, MON);
  */
  void RTC_Write(int Y,int M,int D,int h,int m,int s,int W){
    clock.fillByYMD(Y,M,D);  //年,月,日
    clock.fillByHMS(h,m,s);  //時:分,秒"
    clock.fillDayOfWeek(W);   //曜日
    clock.setTime();           //RTCチップへの書き込み
  }

  /*
  * 現在の時刻を取得する
  */
  void RTC_refresh(){
    clock.getTime();
    ymd.Yea = clock.year + 2000;
    ymd.Mon = clock.month;
    ymd.Day = clock.dayOfMonth;
    ymd.Wee = clock.dayOfWeek;
    ymd.Hor = clock.hour;
    ymd.Min = clock.minute;
    ymd.Sec = clock.second;
  }


  /*
  * 現在のinformationを出力する
  */
  void information_Print(String str="\r\n"){
  //■Sencer_Print()
    Serial.flush();
    int SensorValue = 0;                                  //SensorValueをint型の変数として宣言(毎回0にリセット)
    for(byte i=0;i<=10;i++){                              //A3ポートの電圧を10回読み平均値を出す
      digitalWrite(SensorPower, HIGH);                    //センサー電力供給開始
      delay(10);                                          //一定時間電流を流して測定値を安定させる
      SensorValue = SensorValue + analogRead(SensorIn);   //センサー電圧を読み合計する
      digitalWrite(SensorPower, LOW);                     //センサー電力供給停止
    }
    SensorValue = SensorValue / 10;                       //センサー電圧平均値を返す
    Serial.print("Sensor:");Serial.print(SensorValue);Serial.print("  ");

    //■RTC_Print()
    Serial.flush();
    Serial.print(ymd.Yea, DEC);Serial.print("/");
    Serial.print(ymd.Mon, DEC);Serial.print("/");
    Serial.print(ymd.Day, DEC);
    switch (ymd.Wee){
      case MON:Serial.print("(MON)");break;
      case TUE:Serial.print("(TUE)");break;
      case WED:Serial.print("(WED)");break;
      case THU:Serial.print("(THU)");break;
      case FRI:Serial.print("(FRI)");break;
      case SAT:Serial.print("(SAT)");break;
      case SUN:Serial.print("(SUN)");break;
    }
    char Value[2];
    dtostrf(ymd.Hor,2,0,Value);Serial.print(Value);Serial.print(":");
    dtostrf(ymd.Min,2,0,Value);Serial.print(Value);Serial.print(":");
    dtostrf(ymd.Sec,2,0,Value);Serial.print(Value);

    //■出力状態も出力
    Serial.print("  Solenoid:");
    if(digitalRead(SolenoidPower)){Serial.print("ON");}else{Serial.print("OFF");} 

    //■その他information
    Serial.print(str);
  }
};



 /*
  * 機能:土壌湿度センサーの値を判定
  * Dry: (520 430]
  * Wet: (430 350]
  * Water: (350 260]
  * 
  * 土に直挿し実測値
  * 390:少し湿っている
  * 272:水没!
  * 337:水が滴っている
  * 
  * センサーの周りにスポンジセットし水散布
  * 一回目
  * 350:水あげ5min後
  * 425:水あげ10min後
  * 453:水あげ60min後
  * 
  * 二回目
  * 280:水あげ 0min後 13:09
  * 283:水あげ 1min後 13:10
  * 303:水あげ 2min後 13:11
  * 339:水あげ 3min後 13:12
  * 386:水あげ 4min後 13:13
  * 398:水あげ 5min後 13:14
  * 404:水あげ 6min後 13:15
  * 406:水あげ 7min後 13:16
  * 407:水あげ 8min後 13:17
  * 408:水あげ 9min後 13:18
  * 408:水あげ10min後 13:19
  * 409:水あげ11min後 13:20
  * 411:水あげ15min後 13:24
  * 415:水あげ20min後 13:29 水が滴っている
  * 415:水あげ25min後 13:34 水が滴っている
  * 417:水あげ30min後 13:39 水が滴っている
  * 423:水あげ50min後 13:59 水が滴っていない
  * 
  * 三回目(8/3 晴れ 11:08)
  * 443:水あげ4h後 乾燥している
  * 
  * 四回目(2023/6/25 晴れ 17:35,新)
  * 264:水あげ4h後 乾燥している
  */

/* NOBのArduino日記! プランター自動水やり機制御プログラム(20240504更新)
 * rev   :2.03
 * 変更内容:時刻と、pwc.RTC_time_initialize()の引数変更
*/
PlantWateringClass pwc;
int InComingData[255];
unsigned long StartTime, loopTime = 1000;
bool Skip = false, Skip2 = true;            //条件判定
unsigned long       On_No;                //散水時間(単位:Sec)
int WatTimParDay = 0;                     //一日当たりの散水回数合計
int OldMon = -1, OldDay = -1, OldHor = -1, OldMin = -1;//最終測定時間     



void setup() {
  Serial.begin(115200);
  //            年, 月, 日, 時, 分, 秒, 曜日
  //RTC_Write(2019,  7, 21, 12,  5,  0,  0);//前回
  //RTC_Write(2020,  6, 14,  6, 49, 55,  0);//書き込み後直ぐ出す_Test用
  //RTC_Write(2021,  6, 13, 15, 39,  0,  0);//20210614 いつでも値が確認できるようにDEBUGModeでシリアル出すが日付リセットは止めておく
  //RTC_Write(2022,  7, 2, 16, 47,  0,  0);//20220617 いつでも値が確認できるようにDEBUGModeでシリアル出すが日付リセットは止めておく
  //RTC_Write(2023,  6, 25, 17, 47,  0,  0);//20230625 いつでも値が確認できるようにDEBUGModeでシリアル出すが日付リセットは止めておく
  //pwc.RTC_time_initialize(false, 2024, 5, 4 ,9, 37, 0);
  pwc.RTC_time_initialize(false, 2025, 5, 25 ,16, 48, 0);
}


void loop() {
  //■main
  if (loopTime <= millis() - StartTime) {
    StartTime = millis();
    //■手動スイッチが押されていれば強制的に水を出す
    if(SkipCheck(false) && pwc.Switch_Read()){
      pwc.SolenoidWrite(true);
      SkipWrite(true);
    }

    //■規定時刻に土が乾いていれば水を出す
    if(SkipCheck(false) && Time_Read()){
      if(true/*Sensor_Read()*/){
        pwc.SolenoidWrite(true);
        SkipWrite(true);
      }
    }

    //■最後までSkipされていなければ水を止める、Skipトグルが「true」だったら「false」に戻す
    if(SkipCheck(false)){
      pwc.SolenoidWrite(false);
    }else{
      SkipWrite(false);
    }

    //■information出力
#if !DEBUG
      String str = "  WatTimParDay:" + String(WatTimParDay) + "  onNo:0x"+  String(On_No, HEX) + "\r\n";
      pwc.information_Print(str);
#endif
  }

#if DEBUG
  //■Debug[h,Yea, Mon, Day, Wee, Hor, Min, Sec,]: h2023,7,29,  06, 00,
  if (pwc.Receive(InComingData)) {
      pwc.ymd.Yea = InComingData[0];
      pwc.ymd.Mon = InComingData[1];
      pwc.ymd.Day = InComingData[2];
    //pwc.ymd.Wee = InComingData[3];  //省略
      pwc.ymd.Hor = InComingData[3];
      pwc.ymd.Min = InComingData[4];
    //pwc.ymd.Sec = InComingData[5];  //省略
      Time_Read();
      String str = "  WatTimParDay:" + String(WatTimParDay) + "  On_No:0x"+  String(On_No, HEX) + "\r\n";
      pwc.information_Print(str);
  }
#endif
}




/*
* リアルタイムクロックの値を判定
*/
bool Time_Read(){
  //No初期化
#if !DEBUG
  pwc.RTC_refresh();
#endif
  Skip2 = true;
  On_No = 0;
  if(pwc.ymd.Hor == 0 && pwc.ymd.Min == 0 && (OldMin != pwc.ymd.Min || OldHor != pwc.ymd.Hor || OldDay != pwc.ymd.Day || OldMon != pwc.ymd.Mon )){
    OldMin = pwc.ymd.Min;
    OldHor = pwc.ymd.Hor;
    OldDay = pwc.ymd.Day;
    OldMon = pwc.ymd.Mon;

#if false//DEBUG
        Serial.print(", WTPD:" + String(WatTimParDay));
#endif

    WatTimParDay = 0;
  };

  //規定時刻でtrueを返す処理
  switch (pwc.ymd.Mon) {
    case pwc.ymd.June:       //6月
      if(0 <= pwc.ymd.Day && pwc.ymd.Day < 7){
        // h2023,6,1,  06, 00,
        check( 6,  0, 0x060A0600);
        check(18,  0, 0x060A1800);
      } else if(7 <= pwc.ymd.Day && pwc.ymd.Day < 14){
        check( 6,  0, 0x060B0600);
        check(12,  0, 0x120B0600);
        check(18,  0, 0x180B0600);
      } else if(14 <= pwc.ymd.Day && pwc.ymd.Day < 21){
        check( 6,  0, 0x060C0600);
        check(10,  0, 0x060C1000);
        check(14,  0, 0x060C1400);
        check(18,  0, 0x060C1800);
      } else if(21 <= pwc.ymd.Day){
        check( 6,  0, 0x060D0600);
        check( 9,  0, 0x060D0900);
        check(12,  0, 0x060D1200);
        check(15,  0, 0x060D1500);
        check(18,  0, 0x060D1800);
      }
      break;
    case pwc.ymd.July:       //7月
      if(0 <= pwc.ymd.Day && pwc.ymd.Day < 7){
        check( 6,  0, 0x070A0600);
        check( 9,  0, 0x070A0900);
        check(12,  0, 0x070A1200);
        check(15,  0, 0x070A1500);
        check(18,  0, 0x070A1800);
      } else if(7 <= pwc.ymd.Day && pwc.ymd.Day < 14){
        check( 6,  0, 0x070B0600);
        check( 8,  0, 0x070B0800);
        check(10,  0, 0x070B1000);
        check(11,  0, 0x070B1100);
        check(12,  0, 0x070B1200);
        check(13,  0, 0x070B1300);
        check(14,  0, 0x070B1400);
        check(16,  0, 0x070B1600);
        check(18,  0, 0x070B1800);
      } else if(14 <= pwc.ymd.Day && pwc.ymd.Day < 21){
        check( 6,  0, 0x070C0600);
        check( 7,  0, 0x070C0700);
        check( 8,  0, 0x070C0800);
        check( 9,  0, 0x070C0900);
        check(10,  0, 0x070C1000);
        check(10, 30, 0x070C1030);
        check(11,  0, 0x070C1100);
        check(11, 30, 0x070C1130);
        check(12,  0, 0x070C1200);
        check(12, 30, 0x070C1230);
        check(13,  0, 0x070C1300);
        check(13, 30, 0x070C1330);
        check(14,  0, 0x070C1400);
        check(15,  0, 0x070C1500);
        check(16, 30, 0x070C1630);
        check(18,  0, 0x070C1800);
      } else if(21 <= pwc.ymd.Day){
        check( 6,  0, 0x070D0600);
        check( 7,  0, 0x070D0700);
        check( 8,  0, 0x070D0800);
        check( 9,  0, 0x070D0900);
        check( 9, 30, 0x070D0930);
        check(10,  0, 0x070D1000);
        check(10, 30, 0x070D1030);
        check(11,  0, 0x070D1100);
        check(11, 15, 0x070D1115);
        check(11, 30, 0x070D1130);
        check(11, 45, 0x070D1145);
        check(12,  0, 0x070D1200);
        check(12, 15, 0x070D1215);
        check(12, 30, 0x070D1230);
        check(12, 45, 0x070D1245);
        check(13,  0, 0x070D1300);
        check(13, 15, 0x070D1315);
        check(13, 30, 0x070D1330);
        check(13, 45, 0x070D1345);
        check(14,  0, 0x070D1400);
        check(15,  0, 0x070D1500);
        check(16,  0, 0x070D1600);
        check(17,  0, 0x070D1700);
        check(18,  0, 0x070D1800);
      }
      break;
    case pwc.ymd.August:     //8月
        check( 6,  0, 0x08ff0600);
        check( 7,  0, 0x08ff0700);
        check( 8,  0, 0x08ff0800);
        check( 9,  0, 0x08ff0900);
        check( 9, 30, 0x08ff0930);
        check(10,  0, 0x08ff1000);
        check(10, 30, 0x08ff1030);
        check(11,  0, 0x08ff1100);
        check(11, 15, 0x08ff1115);
        check(11, 30, 0x08ff1130);
        check(11, 45, 0x08ff1145);
        check(12,  0, 0x08ff1200);
        check(12, 15, 0x08ff1215);
        check(12, 30, 0x08ff1230);
        check(12, 45, 0x08ff1245);
        check(13,  0, 0x08ff1300);
        check(13, 15, 0x08ff1315);
        check(13, 30, 0x08ff1330);
        check(13, 45, 0x08ff1345);
        check(14,  0, 0x08ff1400);
        check(15,  0, 0x08ff1500);
        check(16,  0, 0x08ff1600);
        check(17,  0, 0x08ff1700);
        check(18,  0, 0x08ff1800);
      break;
    case pwc.ymd.September:  //9月
        check( 6,  0, 0x09ff0600);
        check( 7,  0, 0x09ff0700);
        check( 8,  0, 0x09ff0800);
        check( 9,  0, 0x09ff0900);          
        check( 9, 30, 0x09ff0930);
        check(10,  0, 0x09ff1000);
        check(10, 30, 0x09ff1030);
        check(11,  0, 0x09ff1100);
        check(11, 15, 0x09ff1115);
        check(11, 30, 0x09ff1130);
        check(11, 45, 0x09ff1145);
        check(12,  0, 0x09ff1200);
        check(12, 15, 0x09ff1215);
        check(12, 30, 0x09ff1230);
        check(12, 45, 0x09ff1245);
        check(13,  0, 0x09ff1300);
        check(13, 15, 0x09ff1315);
        check(13, 30, 0x09ff1330);
        check(13, 45, 0x09ff1345);
        check(14,  0, 0x09ff1400);
        check(15,  0, 0x09ff1500);
        check(16,  0, 0x09ff1600);
        check(17,  0, 0x09ff1700);
        check(18,  0, 0x09ff1800);
      break;
    default:   //1~5月「Jan,Feb,Mar,Apr,May」、10~12月「Oct,Nov,Dec」)
        check( 6,  0, 0x09ff1500);
        check(18,  0, 0x09ff1600);
      break;
  }
  return (0 < On_No);  //OnTimeが0を超えたら「true」を返す
}




inline void SkipWrite( bool argV ){ Skip = argV; }




inline bool SkipCheck( bool argV ){
  if(Skip == argV){
    return true;
  }else{
    return false;
  };
}




/* 20230730 TBD 次回ここから修正する!!!!★★★!!!
* 引数の時刻である場合はTrueを返す関数
* 例: check(6, 0, 0x060A0600)
*/
inline void check(int hor, int min, unsigned long on_no){
  if(Skip2){
    if(pwc.ymd.Hor == hor && pwc.ymd.Min == min){
      On_No = on_no;
  #if false//DEBUG
          Serial.print(
            " now:" + (String)pwc.ymd.Mon +
            ":" + (String)pwc.ymd.Day +
            ":" + (String)pwc.ymd.Hor +
            ":" + (String)pwc.ymd.Min +
            ",  Old:" + (String)OldMon +
            ":" + (String)OldDay +
            ":" + (String)OldHor +
            ":" + (String)OldMin +
            "\r\n"
            );
  #endif
      if(pwc.ymd.Min != OldMin || pwc.ymd.Hor != OldHor || pwc.ymd.Day != OldDay || pwc.ymd.Mon != OldMon){
        Skip2 = false;
        
        WatTimParDay++;
  #if false//DEBUG
          Serial.print(
            "  now:" + (String)pwc.ymd.Mon +
            ":" + (String)pwc.ymd.Day +
            ":" + (String)pwc.ymd.Hor +
            ":" + (String)pwc.ymd.Min +
            ",  chack:" + (String)hor +
            ":" + (String)min +
            ",  Old:" + (String)OldMon +
            ":" + (String)OldDay +
            ":" + (String)OldHor +
            ":" + (String)OldMin +
            ",  Skip2:" + (String)Skip2 +
            ", WatTimParDay:" + (String)WatTimParDay +
            ", On_No:0x" + String(On_No,HEX) +
            "\r\n"
            );
  #endif
      }
    }
    OldMin = pwc.ymd.Min;
    OldHor = pwc.ymd.Hor;
    OldDay = pwc.ymd.Day;
    OldMon = pwc.ymd.Mon;
  }
}
イメージ 1
散水機に書き込んだプログラム(2025.5.25)

イメージ 1 イメージ 3
励みになりますのでよければクリック下さい(^o^)/

↩【プランター自動水やり機!(他ガーデニング)】目次に戻る


 
 
 

秋葉原!(9)(秋月電子通商・千石電商!)

秋月電子通商

 最後に秋月電子さんに来たのは2018年2月なので実に6年ぶりに来ました(;'∀')
商品ラインナップはもちろん、PayPayが使える様に進化してました!(^^)/

1.仙谷電商

 千石電商さんも寄り道~

千石電商さん~Lパラさんも

千石電商さんはモジュール品の宝庫で店内も広くて良いですね(*^^)v

3.まとめ

 店入り口で気になった3V200mA加えるだけで点灯出来るフレキシブルLEDを取り敢えず全色「白、青、黄、緑、桃、赤」と、なんとなく理想ダイオード、室内換気の目安に?CO2ガスセンサー、アナログジョイスティック、16文字×2行LCDモジュールGET!しました!(^^)!

本日の戦利品!
イメージ 1 イメージ 3
励みになりますのでよければクリック下さい(^o^)/

↩【電子部品のお買い物!(東京秋葉原)】目次に戻る


グリーンカーテン(2024)!

グリーンカーテン(2024)!

 今年で8年連続皆勤賞ですね(*^^)v!

毎年恒例のグリーンカーテンを今年もやりました!(^^)!

 

1.準備

 グリーンカーテン以外にも色々育てたいので、今年は夏に向けて早めに庭の手入れをやりました。

■2024年3月2日

1-1.プランターの土入れ替え、重力に逆らって伸びるヤブタビラコ

■2024年5月6日

1-2.ブラックベリーの木選定・・・やり方が悪いのか、ここ数年実は成るのですが、ご覧の通り白化しカビ?て、赤黒くなりません(´;ω;`)?選定断面から菌が入っているのか?
1-3.と言う事で、植物の枯病を防ぐ殺菌剤入り「トップジンMペースト」を買って来ました
1-4.ブラックベリーの選定断面にトップジンMを塗りました!
これで来年ブラックベリーが収穫出来たらよいのですが(;'∀')肥料も追肥

1-5.家の老猫(16才)ちゃん、足腰が明らかに弱ってきたので脱走経路を塞ぎ、
1-6.今年は自動散水機プランターを、細ネギ用とリーフレタス用に2つ増設

1-7.配水1車線では長すぎ・分岐し過ぎで流量が足らなくなったので、2車線化し延長

1-8.上側:息子が小学校で貰って来たトウモロコシ(爆裂種)をポットで育てておいた苗
下側:買って来たオクラの苗

2.完成

■2024年7月15日

2-1.と言う事でトウモロコシとオクラが育ちました(^^)/
※右側オクラの写真は今日20240916撮影、まだ育ち続けてますね(*'▽')

2-2.トマト1株でここまで大きくなったのは初ですね、
葉っぱに栄養を取られているからか、思ったより実りは少ないです。

2-3.バジル

2-4.リーフレタス

2-5.細ネギ

2-6.ゴーヤ

2-7.キュウリ・・・と家のネコが見切れてます🐈

3.まとめ

 今年のゴーヤは豊作でした(^^)/来年ブラックベリーが収穫出来ればよいのですが(;^_^A

イメージ 1 イメージ 3
励みになりますのでよければクリック下さい(^o^)/

↩【プランター自動水やり機!(他ガーデニング)】目次に戻る