LTE-Mボタンを押す指


その前に

hatenaと記述方法が違うのでめんどい(ぉ
写真はてきとーなので勘弁してください。

SORACOM LTE-Mボタンを買ってみた

 webで検索すると皆さん色々試しているようで、ラズパイとかFlashAirとかの初期っぽくて面白い。
 でもほぼ全(以下自粛
 買ってとりあえずAWSでメール送信を試してみたけど、さてその先どうしたものかと。なにせ「ぼっち」なので、自分で押しててもつまらない。

ざっくりボタンの概要

・単4乾電池2本で300回位は押せるらしい?
・基地局に電波が届けば大抵どこでも使える模様(例外あり?)
・「1クリック」「2クリック」「長押し」の3パターンを送信できる(1.5bit相当)
・外部入力は無い(ボタンから線を引き出すのは電波法違反なので×)

誰に押させるか

 犬とか猫とかペットを訓練して押させてもいいけどボタンが壊れそう。
 機械で押させるなら、油圧や空気圧で動くアクチュエータかソレノイド。今回はArduinoと組み合わせるのでソレノイドを選択。

とりあえず試してみる

 たまたま手元に秋月電子で買ったプッシュ型ソレノイドがあったので、ボタンの上に固定して(人力)6V位流してみるとボタンを押せたり押せなかったり。いろいろ調べてみるとソレノイドは一番伸びる瞬間に最大の力を出せるらしい。ということでLTE-Mボタンとソレノイドを適当な部材で固定して、ソレノイドの高さをストロークが最大のところでボタンが押された状態になるように調整してみるとほぼ確実に押せるようになった。すばらしい。ということでちゃんと使える形にしてみる。

指(機械部分)の製作

★材料

・SORACOM LTE-M Button 1個 
これがないと始まらない
・金折 横折T型60(ヤハタホールディングス) 2個 
コメリで購入
・L型アルミアングル 10mm×10mm×100mm(板厚1.0mm) 2本 
仙石電商で購入
・Z金具 10mm・10mm・15mm(板厚0.8mm) 1個 
改装前の仙石電商で購入したが、今は取り扱っていない模様。自作する場合はL型アングルを切り取ったものをZ字に重ねて接着などの工夫を。重要なのはソレノイドの力で歪まないようにすること。
・ソレノイド P-10761(秋月電子) 1個 
6V駆動でぎりぎりボタンを押せるが、もっと力のある製品に替えて電圧を上げたほうが動作が安定すると思う
・M3ステンレス8mmナベねじセット(ねじ・ワッシャー・ナット) 適量
アングル等の固定で使用
・ゴム板 1~2mm 適量 
ボタンとアングルの隙間を調整
・ねじロック剤
・クイックメンダー
・ソレノイド駆動用電源

★作り方

1.ソレノイドはねじ穴が小さくて固定が困難なので、クイックメンダーを使ってZ金具を接着。かなり力がかかるところなのでがっちり固定。
2.アングルに金折と組むための3mmの穴を2か所づつあける。間隔はボタンの長さより数mm程度長くして隙間をゴム板で調整。私は現物合わせで85mmであけたが3mm程度の隙間ができた。きっちりした長さにすると後の工程で調整できなくなるので注意。
3.アングルと金折を組んでボタンを囲うようにねじ止め。このときボタンとアングルの間に適当に切ったゴム板を挟んで隙間を埋め、ボタンが動かないようにする。ねじはロック剤を使って緩まないように。
4.金折の出っ張ったところとソレノイドのZ金具をねじで仮固定。ソレノイドに単3×4本の電池ボックスなどを電線でつないでボタンが押されるように高さを調整。決まったらロック剤を使って本固定。

余計なものがついてるけどそれは後述。

ソレノイドのコントロール

 これでとりあえず6V入力によってLTE-Mボタンを人間以外が押せるようになったけど、さっき概要に書いたとおり3種類の押し方を情報として送ることができる。そこでマイコンを使ってソレノイドをコントロールしてみる。
 まずは簡単な動作確認なのでお手軽に使えるDigiSparkで。これはATTINY85を使った小さなマイコンボードで、直接USBでPCと接続してarduino IDEを使ってプログラミングできる。ピン数は少ないけど今回のようなちょっとした制御ならこれで十分。

 DigiSparkでは自由に使えるIOが実質3本だけなのでトリガーとなるボタンを2つつなげて、それぞれ押すと「1クリック」「2クリック」の動作に対応。両方同時に押すと「長押し」。残った1ポートはソレノイドへ。今回使ったソレノイドは1.1A/5V定格なので小型リレーを経由してON/OFFする回路に。(回路とプログラムは省略)

お披露目

 ここまでがTwitterに上げたりSORACOMさんのイベントの懇親会でちょっとお見せしたもの。
Advent Calendarに投稿してみてはという話もあったので記憶を頼りに整理している間にもくもくと次なる野望が湧いてきて…

多ビット送信

 上で1.5bit相当と書いたけど、例えば「0」を1クリック、「1」を2クリック、ストップビットを長押しに割り当てるとデータを送ることもできそう。恐ろしく遅いし、最大1500bitしか送れないけど。実用(?)的には8bit(9回送信)が限度か。というかそこまでするならSORACOM的にはWio LTE M1モジュール買えってことになるんだろうけど、ああいうのは最大公約数的に要らんものも色々ついてるし(おっと
 ちなみに京セラからもUARTでシリアル通信に対応したユニット(LU1CK010)が出てるけど、こちらも価格面や動作環境など、まだ個人レベルではちょっと扱いにくい。3980円のボタンに(電波法に触れない範囲で)何かくっつけてもっとデータを送れないかと。

ビットデータの連続送信

 ビットデータを連続して送信するには直前の送信が完了してることを認識しなきゃならない。真面目に作るならLTE-Mボタンのインジケータにカラーセンサー貼り付けて赤(失敗)のときは再送信とかやったほうがいいけど、そこは趣味の世界なのでCdSで光ってることだけ検知。送信後に3秒以上消灯してたら送信終了ということで。

実験

 DigiSparkではI/Oが足りなくなってきたのでここからはArduino UNOを使って実験。プログラムを単純にするためにCdSと10kΩの抵抗で分圧(+側をCdSにつなぐ)して中点をD2へ。これでdigitalRead(2)がLED点灯時には1、消灯時には0になる。CdSによっては閾値が変わるので抵抗値を変えて調整。

LTE-M_Button_multi_bit_send_test.ino(抜粋)
void loop() {
//  光るまで待ち
  while(digitalRead(pushButton) == 0) delay(100);
  Serial.println("send now");

//  送信中
  while(1){
    if(digitalRead(pushButton) == 0){
      count++;
      if(count > 30)  break;
    } else count = 0;
    delay(100);
  }
  Serial.println("end");
}

CdSをインジケータに当てながらボタンを押すと「send now」→「end」と変わった。いい感じ。

サーバ側処理

 恐らくAWS上でLambda関数をいじるのが正統なやり方なのだけど、可能な限りAWSに頼りたくないのとスタティックデータの扱いが面倒そう+諸般の事情によりボタンのステータスをメールで飛ばしてメールサーバ上でprocmailを使ってデータ化するスクリプトを動かすことにする。

 ボタンからは上位ビットから順番にデータが飛んできているはずなので、保存しておいたデータをビットシフトして加算、を繰り返すだけ。長押しならデータをフラッシュ(今回はメール)してクリア。

button2data.sh(procmailで起動されるシェルスクリプト)
#!/bin/bash

export LANG=ja_JP.eucJP
add="ほげほげ"
# データの送信先アドレス

cat > ltemp

f=`cat ltemp | grep -c "SINGLE"`
if [ "$f" = "1" ] ;
then
 d=`cat ltedata`
 echo `expr $d \* 2` > ltedata
fi

f=`cat ltemp | grep -c "DOUBLE"`
if [ "$f" = "1" ] ;
then
 d=`cat ltedata`
 d=`expr $d \* 2`
 echo `expr $d + 1` > ltedata
fi

f=`cat ltemp | grep -c "LONG"`
if [ "$f" = "1" ] ;
then
 d=`cat ltedata`
 echo " LTE data = $d" | mail -s "LTE Button data" $add
 echo "0" > ltedata
fi

ボタンを押す指に目(CdS)を増設

 何となくうまくいきそうなことが分かったので改造開始。

★材料

・CdS
ボタンのLEDがちょうど隠れる位のサイズ
・基板 AE-KCB-B-UNIV(秋月電子)
たまたま手元にあった基板を使ったが、現物合わせで適当なものを使用
・L金具
Z金具と同じく同型を手に入れるのは困難と思われるがホームセンター等で適当な金折を探すかアルミ板を加工して作る
・L型ピンヘッダ

★作りかた

 ソレノイドと反対側の金折にL金具を仮止めし、基板の取り付け位置を確認。CdSがちょうどインジケータに当たるように調整し、基板にはんだ付け。L型ピンヘッダもはんだ付けし、CdSの信号を取り出せるように配線。基板に3mm穴をあけてL金具に固定。最終的な位置決めをして金折とも固定。ソレノイドと違って力はかからないのでCdSを固定できればほかの方法でもOK。

制御用Arduino基板作成

 今回、諸般の事情により最終的には消費電力を抑えて乾電池で長期運用を目指すことになったので、市販のArduinoを使わずATMEGA328Pをそのまま8MHz内部クロック・3.3Vで使用することにした。単体のATMEGA328PにArduinoのプログラムを書き込む方法などは別の方の記事等を参照されたい。Arduino UNOとジャンパー線があれば簡単。

仕様

・送信データ 接点情報2bit(2個・入力プルアップ)+バッテリー電圧レベル2bit(4段階)
 ※最後にストップビットを付加
・電源 マイコン用3.3V+リレー・ソレノイド駆動用6V
・動作モード
  1.ボタンによる送信
  2.電源投入時1回のみ送信後スリープ
  3.24時間間隔での送信(WDT使用)

回路図


フォトカプラを使わず直接デジトラを駆動しても動くこともあるけど、安定動作のためにはマイコンと電源を分離おすすめ。

リレーだけはブレッドボード基板に載らなかったので別基板に載せて2層化。

プログラム

#include  <avr/sleep.h>
#include  <avr/wdt.h>

//
//  SORACOM LTE-M Buttonを使った複数ビット送信実験
//  ON/OFFセンサー2個+バッテリー電圧(4段階)
//  ATMEGA328Pのみ使用可
//
//  送信データ
//  SW1→SW2→バッテリーステータス上位ビット→下位ビット→ストップビット

//  スリープタイマーはラジオペンチ様のdelayWDT2関数を使用
//  http://radiopench.blog96.fc2.com/blog-entry-830.html


int count=0;
int s,i;
void setup() {
                              //        ON / OFF
  pinMode(8 , INPUT_PULLUP);  //  DIP1  センサー1 ON / OFF (センサー接続時はDIPをOFF)
  pinMode(7 , INPUT_PULLUP);  //  DIP2  センサー2 ON / OFF (センサー接続時はDIPをOFF)
  pinMode(6 , INPUT_PULLUP);  //  DIP3  24時間タイマースタート / 電源投入後1回だけ実行
  pinMode(5 , INPUT_PULLUP);  //  DIP4  スタートボタンによる送信を使わない(DIP3を有効に) / ボタン使用
  pinMode(10 , INPUT_PULLUP);  //  スタートボタン
  pinMode(11 , INPUT);  //  インジケータセンサー(CdS)
  pinMode(13 , OUTPUT);  //  ソレノイド

  pinMode(12 , OUTPUT);  //  LED

  digitalWrite(12,1);
  delay(1000);
  digitalWrite(12,0);

  analogReference(INTERNAL);  //  電源電圧に依存しないようにA/Dに内部1.1Vリファレンス使用
}

//  送信完了待ちループ
int  send_waiting(){
  count = 0;
//  送信開始のインジケータが光るまで待つ 10秒待っても光らない場合はエラーを返す
  while(digitalRead(11) == 0){
    count++;
    if(count > 100)  return(-1);
    delay(100);
  }

//  インジケータが消えるまで待つ エラー処理は不要
  while(1){
    if(digitalRead(11) == 0){
      count++;
      if(count > 30)  break;
    } else count = 0;
    delay(100);
  }
  delay(7000);
  return(0);
}

//  0を送る(シングルクリック)
void send_0(){
  digitalWrite(13,1);
  delay(200);
  digitalWrite(13,0);
//  send_waiting();
}

//  1を送る(ダブルクリック)
void send_1(){
  digitalWrite(13,1);
  delay(200);
  digitalWrite(13,0);
  delay(200);
  digitalWrite(13,1);
  delay(200);
  digitalWrite(13,0);
//  send_waiting();
}

//  ストップビットを送る(長押し)
void send_end(){
  digitalWrite(13,1);
  delay(2500);
  digitalWrite(13,0);
//  send_waiting();
}

//  ビット送信
//  2はストップビット
//  ボタンが反応しない場合は成功するまでリトライする
void send_bit(int d){
  while(1){
    if(d == 0){
      send_0();
    } else if(d == 1){
      send_1();
    } else send_end();
    if(send_waiting() == 0) break;
  }
}

//  SW1→2の順に送信
void sw_send(){
  s=digitalRead(8);
  s = 1 - s;  //  PULLDOWNしているので接点ONだと0になるからビット反転
  send_bit(s);

  s=digitalRead(7);
  s = 1 - s;
  send_bit(s);
}


void  ad_send(){
  s = 0;
  i = 0;
  while(i < 20){
    s = s + analogRead(A0);
    i++;
  }
  s = s / 20;
  if(s < 463){  //  10.5V未満
    send_bit(0);
    send_bit(0);
  } else if(s < 485){ //  10.5~11.0V未満
    send_bit(0);
    send_bit(1);
  } else if(s < 529){ //  11.0~12.0V未満
    send_bit(1);
    send_bit(0);
  } else {  //  12.0V以上
    send_bit(1);
    send_bit(1);
  }
}

void mode1(){
  while(1){
    while(digitalRead(10) == 1) delay(100); //  ボタンが押されるまで待機
    digitalWrite(12,1);
    delay(400);
    digitalWrite(12,0);
    delay(400);
    digitalWrite(12,1);
    delay(400);
    digitalWrite(12,0);
    sw_send();
    ad_send();
    send_bit(2);
  }
}

void delayWDT2(unsigned long t) {       // パワーダウンモードでdelayを実行
//  Serial.flush();                       // シリアルバッファが空になるまで待つ
  delayWDT_setup(t);                    // ウォッチドッグタイマー割り込み条件設定

  // ADCを停止(消費電流 147→27μA)
  ADCSRA &= ~(1 << ADEN);

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // パワーダウンモード指定
  sleep_enable();

  // BODを停止(消費電流 27→6.5μA)
  MCUCR |= (1 << BODSE) | (1 << BODS);   // MCUCRのBODSとBODSEに1をセット
  MCUCR = (MCUCR & ~(1 << BODSE)) | (1 << BODS);  // すぐに(4クロック以内)BODSSEを0, BODSを1に設定

  asm("sleep");                         // 3クロック以内にスリープ sleep_mode();では間に合わなかった

  sleep_disable();                      // WDTがタイムアップでここから動作再開
  ADCSRA |= (1 << ADEN);                // ADCの電源をON(BODはハードウエアで自動再開される)
}

void delayWDT_setup(unsigned int ii) {  // ウォッチドッグタイマーをセット。
  // 引数はWDTCSRにセットするWDP0-WDP3の値。設定値と動作時間は概略下記
  // 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
  // 6=1sec, 7=2sec, 8=4sec, 9=8sec
  byte bb;
  if (ii > 9 ) {                        // 変な値を排除
    ii = 9;
  }
  bb = ii & 7;                          // 下位3ビットをbbに
  if (ii > 7) {                         // 7以上(7.8,9)なら
    bb |= (1 << 5);                     // bbの5ビット目(WDP3)を1にする
  }
  bb |= ( 1 << WDCE );

  MCUSR &= ~(1 << WDRF);                // MCU Status Reg. Watchdog Reset Flag ->0
  // start timed sequence
  WDTCSR |= (1 << WDCE) | (1 << WDE);   // ウォッチドッグ変更許可(WDCEは4サイクルで自動リセット)
  // set new watchdog timeout value
  WDTCSR = bb;                          // 制御レジスタを設定
  WDTCSR |= _BV(WDIE);
}

ISR(WDT_vect) {                         // WDTがタイムアップした時に実行される処理
  //  wdt_cycle++;                      // 必要ならコメントアウトを外す
}

void loop() {
  if(digitalRead(5) == 1) mode1();  //  スタートボタン使用(関数内で永久ループ)
  if(digitalRead(6) == 1){  //  起動直後に1回だけ送信モード
    sw_send();
    ad_send();
    send_bit(2);
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_mode();
  }

//  24時間タイマーモード
  sw_send();
  ad_send();
  send_bit(2);
  int k = 0;
  while(k < 10800){
    delayWDT2(9);  //  8秒×10800=24時間
    k++;
  }
  digitalWrite(12,1);
  delay(400);
  digitalWrite(12,0);
}

 24時間タイマーについてはラジオペンチさんのdelayWDT2関数を利用させていただきました。ありがとうございます。
 通常動作では3.3V側が5mA前後、24時間タイマー動作中は0.22mA程度と乾電池での長期運用も問題なさそう。
 DIP1,2をOFFでボタン押すたびに送信するモードに、DIP1がON、DIP2がOFFだと電源投入時1回だけ動作するモード(WDTのテスト用)、DIP1,2ともONで24時間ごとに自動送信するモード。

 回路図には載っていないけど、アナログ入力端子にはカーバッテリーを200kΩと10kΩで1/21に分圧したものを入力。センサー入力を減らしてA/Dレベルを多段にするのもいいし、逆にA/Dを無くしてセンサーを増やす手も。送信ビット数を増やすと時間もかかるし電気も食うのでLTE-Mボタンを使う意味が減ってしまうのでほどほどに。1契約で1年間1500回と考えれば、1500/365≒4.1回/日なので、3~4ビットが妥当かと。

最後に

人間の指は偉大です(笑