0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

A660-900T22-EV61を使ってみた

0
Posted at

はじめに

水位を測りたくてM5atomS3-liteを試しましたが無線LANでは数十メートルしか電波が届きません。そこでLorawanのGatewayを用意してエンドノードにA660-900T22-EV61を使ってみました。

ハードウェア

制御マイコン M5atomS3-lite(スイッチサイエンス)
Lorawanモジュール A660-900T22-EV61(スイッチサイエンス)
超音波距離センサPWM A0221AM(Amazon)

事前準備

The things network(TTN)のアカウントを作ってGatewayとエンドノードを登録してください。
設定したDEVEUI、APPEUI、APPKEYをメモしておいてエンドノードに設定します。

ソースコード

ソースコードの共通化のため、エンドノードごとの個別設定は本体ボタンを押しながら電源ONして手動で入力する。
現時点では距離センサとLorawanの機能確認のみ。機械的な取り付け位置が決まってないので、距離のオフセットを引くのは後で実装する。

WaterLevel.ino
// Hardware M5AtomS3 lite , A0221AM (Ultrasonic sensor Pin assign modified), LoraWan module A660-900T22-EV61
// M5AtomS3 lite G1  --> A0221AM Pin 1 Yellow(modified)
// M5AtomS3 lite G2  --> A0221AM Pin 2 White(modified)
// M5AtomS3 lite 5V  --> A0221AM Pin 3 Red(modified)
// M5AtomS3 lite GND --> A0221AM Pin 4 Black(modified)
#include <M5Unified.h>
#include <FastLED.h>
#include "HardwareSerial.h"
#include <CayenneLPP.h>
HardwareSerial mySerial(1);  // UART1 を使用

// AtomS3 LiteのGPIOの定義
#define TRIGGER_PIN 1           // 超音波距離センサー トリガー信号=G1
#define PWM_OUTPUT_PIN 2        // 超音波距離センサー 距離パルス=G2
#define SERIAL_RX_PIN 38        // AtomS3 -> A660-900T22 ATコマンド送信=G38
#define SERIAL_TX_PIN 39        // A660-900T22 -> AtomS3 レスポンス受信=G39

// FastLEDライブラリでフルカラーLEDを使用するための設定
#define NUM_LEDS 1
#define LED_DATA_PIN 35
CRGB leds[NUM_LEDS];

// 計測(送信)間隔
#define SLEEP_MINUTES 20  // 計測間隔(分単位)= deep sleepする時間 Fair Access Policy

// 水位を測定 modified by Google Gemini
float getWaterLevel() {
  long duration;
  float distance_cm;

  // 1. トリガ信号の送出 (Falling Edge)
  digitalWrite(TRIGGER_PIN, HIGH);
  delayMicroseconds(10); // 安定待ち
  digitalWrite(TRIGGER_PIN, LOW);  // 立ち下がり
  delayMicroseconds(100);          // 100us程度維持
  digitalWrite(TRIGGER_PIN, HIGH); // HIGHに戻す

  // 2. パルス幅の測定
  // タイムアウトを 100ms (100,000us) に設定(未検出時の35msをカバー)
  duration = pulseIn(PWM_OUTPUT_PIN, HIGH, 100000);

  if (duration == 0) {
    return -30.0; // 測定失敗時は-30cm
  }

  // 3. 仕様書の計算式に基づき算出 (S = T / 57.5)
  // 結果は cm 単位
  distance_cm = (float)duration / 57.5;
  return distance_cm; 
}

// CayenneLPPのバッファを16進数のStringに変換する関数 Copilot programed
String getLPPString(CayenneLPP& lpp) {
  String hexStr = "";
  uint8_t* buffer = lpp.getBuffer();
  uint8_t size = lpp.getSize();

  for (uint8_t i = 0; i < size; i++) {
    if (buffer[i] < 0x10) hexStr += "0"; // 1桁の場合は0を埋める
    hexStr += String(buffer[i], HEX);
  }
  hexStr.toUpperCase(); // 大文字に揃える(任意)
  return hexStr;
}

// ATコマンド送信関数 Google Gemini programed
String sendATCommand(String command) {
  String response = "";
  // 送信前に受信バッファに残っているゴミを掃除する
  while(mySerial.available()) mySerial.read();

  Serial.print(">> "); Serial.println(command);
  
  mySerial.println(command);

  // AT+DJOINが含まれる場合は応答待ちを30秒、それ以外は5秒に設定
  unsigned long timeoutLimit = 5000;
  if (command.indexOf("AT+DJOIN") >= 0) {
    timeoutLimit = 30000;
  }

  // 応答待ち
  unsigned long start = millis();
  while (millis() - start < timeoutLimit) {
    while (mySerial.available()) {
      response += (char)mySerial.read();
      // 連続したデータの取りこぼしを防ぐためのわずかな待ち時間
      delay(2);
    }
    // 判定条件:OK/ERRORに加え、ステータス応答があればループを抜ける
    if (response.indexOf("OK") >= 0 || 
        response.indexOf("ERROR") >= 0 ||
        response.indexOf("OK+SEND:") >= 0 ||   // 送信成功時のレスポンス
        response.indexOf("+DULSTAT:") >= 0 ||
        response.indexOf("+DJOIN:") >= 0) {
      break;
    }
  }
  
  Serial.print("<< "); Serial.println(response);
  
  return response;
}

// ------------------------------------------------------------
// Setup 関数 Setup function.
// ------------------------------------------------------------ 
void setup(){
  // M5AtomS3 lite setup
  auto cfg = M5.config();      // 設定用の構造体を代入。
  cfg.serial_baudrate = 9600;  // USB-C通信ボーレート9600bps
  cfg.output_power = true;
  M5.begin(cfg); // 本体初期設定
  // ハードウェアシリアルを UART1 に割り当て、通信速度 9600bps で初期化
  mySerial.begin(9600, SERIAL_8N1, SERIAL_RX_PIN, SERIAL_TX_PIN);

  // ピンの初期設定
  pinMode(TRIGGER_PIN, OUTPUT);
  pinMode(PWM_OUTPUT_PIN, INPUT);

  // LED string setup
  FastLED.addLeds<WS2811, LED_DATA_PIN, GRB>(leds, NUM_LEDS); //フルカラーLED初期設定
  FastLED.setBrightness(10);                                  //フルカラーLED明るさMAX20までが限界
  Serial.println("AtomS3 Lite wakeup!");  // シリアルモニタ出力

  // Push A button at boot time to A660-900T22 manual configuration
  bool doManualConfig = false;
  Serial.println("Push A button to enter A660-900T22 manual configuration");
  for(int i=0 ; i<60 ; i++) {
    M5.update();
    if (M5.BtnA.isPressed()) {
      doManualConfig = true;
      break;
    }
    delay(10);
  }
 
  // 本体のボタンを押してATコマンドでA660-900T22の初期設定をする
  //     PC -> AtomS3 lite -> A660-900T22 シリアル通信をパススルー
  // AT+CSAVEで設定を保存して電源を切る。
  // 本体のボタンを押さずに電源をONすると通常動作。
  if (doManualConfig) {
    leds[0] = CRGB::Yellow;   //フルカラーLED黄色設定
    FastLED.show();        //フルカラーLED表示
    Serial.println("Usage : A660-900T22初期設定");
    Serial.println("Usage : ATコマンドでLoraWanの通信パラメータを設定してください");
    Serial.println("Usage : ATコマンドは大文字小文字を区別します。小文字だとエラーになります。");
    Serial.println("Usage : 最後にAT+CSAVEでパラメータを保存して電源をOFFのこと");
    Serial.println("Usage : AT+CDEVEUI=<value> →A600-T920に付属の紙に書かれたDevEUI 16桁");
    Serial.println("Usage : AT+CAPPEUI=<value>");
    Serial.println("Usage : AT+CAPPKEY=<value>");
    Serial.println("Usage : AT+RREGION=2 →バンドプランをAS923-1-JPに設定");
    Serial.println("Usage : AT+CSAVE");
    return;       //loop()に移動してコマンド待ち
  } 

  // 測定開始
  leds[0] = CRGB::Green;    //フルカラーLED緑色設定
  FastLED.show();           //フルカラーLED表示

  // 水位を読む Read ultrasonic distance renger
  float WaterLevel=getWaterLevel();                 // 水位読み込み単位cm
  Serial.printf("水位=%2.1f cm\n",WaterLevel);    // シリアルモニターに水位を出力、改行
  delay(200);

  // 水温を読む
  //   温度センサは取り付けていないのでここはパスする

  //送信バッファにCayenneLPPのPayloadを積む
  CayenneLPP lpp(51);
  lpp.reset();
  lpp.addAnalogInput(1, WaterLevel); // Cayenne LPP CH1 水位データを追加 単位cm
  lpp.addTemperature(2, 0);         // Cayenne LPP CH2 温度データを追加0℃ この行はダミーデータで将来の拡張用

  // Payloadをテキストとして取得、送信
  uint8_t payloadlength = lpp.getSize();   //payloadサイズ
  String payloadText = getLPPString(lpp);  //payloadの16進数テキスト
  Serial.print("Payload (Hex Text): ");
  Serial.println(payloadText); // 例: "016700FF"

  // The Things Networks(TTN)接続開始
  leds[0] = CRGB::Red;   //フルカラーLED赤色設定
  FastLED.show();        //フルカラーLED表示

  // A660-T920のスリープ解除にダミーでATを送信
  mySerial.println("AT");
  delay(200);
//  sendATCommand("AT");

  // 1. Join状態の確認  modified by Google Gemini
  String status = sendATCommand("AT+DULSTAT?");

  // 接続状況のチェック (+DULSTAT:01-08なら接続済み、00なら未接続)
  // statusの中に ":00" が含まれているか、または正常な応答がない場合を「未接続」と判定
  if (status.indexOf(":00") >= 0 || status == "") {
    Serial.println("Not Joined. Initializing and Joining...");
    
    // Joinリクエスト送信
    sendATCommand("AT+DJOIN=1,0,8,2");
    
    // Join完了を待つ(OTAAは時間がかかるため長めに待機)
    Serial.println("Waiting for Join process...");
    delay(10000); 
  } else {
    // 01〜08のいずれかであればこちらを通る
    Serial.print("Already Joined. Current Status: ");
    Serial.println(status); 
    Serial.println("Proceeding to send data.");
  }
  // 2. データ送信 
  // 確認型送信(1), 再送3回, 8バイト
  String sendRes = sendATCommand("AT+DTRX=1,3,8,"+payloadText);
   if (sendRes.indexOf("OK+SEND") >= 0) {
    Serial.println("Data sent successfully.");
  } else {
    Serial.println("Send failed or timeout.");
  }

  // 3. Deep Sleep設定
  // LEDを消す
  FastLED.clear();       // 全LEDのデータを黒(消灯)にする
  FastLED.show();        //フルカラーLED表示
  // Deep sleep GO!
  Serial.printf("Entering Deep Sleep for %d minutes...\n", SLEEP_MINUTES);
  delay(400);
  esp_sleep_enable_timer_wakeup(SLEEP_MINUTES*60*1000*1000);
  esp_deep_sleep_start();
}

void loop(){
  // UART1 で受信して USB-C(UART0) に表示
  if (mySerial.available()) {
    String received = mySerial.readStringUntil('\n');
    Serial.println("A660-900T22 : " + received);
  }
  // USB-C からの入力を UART1 へ送信
  if (Serial.available()) {
    String outgoing = Serial.readStringUntil('\n');
    mySerial.println(outgoing);
    Serial.println("COMMAND     : " + outgoing);
  }
}

ここで困った

プログラミングの能力不足

無料のGoogle GeminiとMicrosoft Copilotに自作ソースコードと仕様書を渡して添削してもらいました。生成AIが無かったら動かせなかったという自信があります(^^)。別に検討したE220-900T22S(JP)は安価ですがエンドノードやらサーバー側も自力で構築できる上級者向けorz。初心者は業界標準(TTN)を使いましょう。

仕様書の誤記でGatewayに繋がらない

Ver1.0 AT+RREGION=3 AS923-1-JP
Ver1.1 AT+RREGION=2 AS923-1-JP
無線部の仕様が違ったらそら繋がらないわ...。仕様書に更新リストを付けて欲しい。

仕様書通りに無線出力が設定できない

AT+CTXP=13でエラー。送信出力が日本で許可された最大出力の13dBmに設定できない。テスト用でGatewayからは至近距離というこで今回の使い方は許容しました。ファームのバージョンが古いせい?最新ファームは公開されているが書き換えツールを用意するのが大変なのでパス。

超音波距離センサの仕様がわからない

Amazonの販売ページは仕様書へのリンクなし、判るのはPWM仕様ということだけ。届いた現物を見て、4本の配線があるから、どれかが電源、GND、Trigger、Echoだよねで推定。よく動いたもんだ。コネクタのピンを挿し替えてGroveポートの配列に合わせてあります。

A660-900T22-EV61のAT+DULSTAT?コマンドの返り値の解釈

+DULSTAT:00がJoinしてないが正解でした。Geminiに仕様書をアップロードして質問。仕様を把握した上で、自作ソースの修正を指示。吐き出されたコードを読んで、適正なら本番ソースに挿入。なるほど生成AIはこうやって使うのか。

これからすること

ケースに入れて屋外で耐久試験をします。超音波距離センサの防水仕様IP67は水深1mで30分の防水性能。経験上、屋根は必要です。
M5atomS3-liteだけだとニッケル水素電池4本で2ヵ月動きました。Lorawanモジュールの消費電力が増えたらどれくらい動くか確認します。
まだ実戦投入まで先は長い。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?