seabeanのアイコン画像
seabean 2021年05月14日作成 (2021年05月16日更新)
製作品 製作品 閲覧数 2840
seabean 2021年05月14日作成 (2021年05月16日更新) 製作品 製作品 閲覧数 2840

プラレールの遠隔操作

プラレールの遠隔操作

概要

子供が小さい頃に遊んでいたプラレールを押入れで発見。
子供といっしょに線路をたくさん繋げて遊んだことを思い出しました。

プラレールはSWをONにしたら一定の速度でひたすら走る電車のおもちゃ。
これをobnizでいろいろ遠隔操作できれば大人でも楽しめそう。と思い製作。

前進、後進、スピードコントロール、自動運転、ポイントコントロールが出来るようにしました。
また、操作性を良くするため、使わなくなったビデオデッキのリコモンを使った操作も出来るようにしました。

obnizは2台使用。
一台は車両コントロールに、もう一台はポイントコントロール、リモコン操作に使っています。

obniz #1
obniz #2

デモ

ここに動画が表示されます

ここに動画が表示されます

車両

プラレールのDCモーターとobnizのI/Oを接続してコントロールします。
モバイルバッテリーから電源供給をさせるため、車両の屋根におんぶ状態で装着しました。
ファスナーテープ、LEGOブロックで固定しているので自由に取り外せます。
プラレールは1.5Vの乾電池で動くおもちゃですが、obnizからは5Vなのでプログラムで出力を抑えています。
そのまま5Vで動かしてしまいますと速過ぎて脱線してしまいますから。
DCモーターやギヤの破損も考えられますので。

車両
車両

ポイント

プラレールの手で切り換えるポイント線路にサーボモーターを取り付けてコントロールします。
黄色のポイント部分をサーボモーターで押したり引いたり。
サーボモーターを少しずつ調整しながらベストな回転角度をみつけてあります。

ポイント

ポイント切り換えの動きです。

ここに動画が表示されます

手動運転

画面のボタンを操作して、前進、後進、スピードコントロールができます。
スピードコントロールは画面上のスライダーを使って行います。
ポイントコントロールでは駅ホームの線路、駅通過の線路の切り換えができます。
駅ホームへ切り換えた時はLEDが”緑”、駅通過へ切り換えた時はLEDが”赤”となります。

画面 初期
画面 スピード変更

スピードコントロールの動きです。

ここに動画が表示されます

前進、後進の動きです。

ここに動画が表示されます

自動運転

画面のAUTOボタンを押すことで自動運転がスタートします。
何周駅を通過するか乱数で決め車両を走らせます。
車両の周回は車両下に取り付けたリードスイッチ(磁石に反応する)を使って検出します。
ポイント近くの線路上に磁石を貼り付けて車両が通過するごとに回数をカウントしています。
駅での発車、停車はスピードコントロールして徐々に加速、徐々に減速させています。

リードスイッチ
磁石

画面 AUTO走行

自動運転時の画面の動きです。

ここに動画が表示されます

リモコン操作

使わなくなったビデオデッキのリモコンがあったので、リモコンでの操作も出来るようにしました。
リモコンから送信される赤外線信号を赤外線受信モジュールで受信して、どのボタンが押されたか判定しています。
判定用の各ボタンの赤外線信号パターンは別プログラムを作成して拾い集めました。
リモコンにはジョグシャトル(くるくる回すやつ)が付いていましたので、これでスピードコントロールを出来るようにしました。
車両を見ながら操作できるようになり操作性UPです。

リモコン
赤外線受信モジュール

画面 リモコン操作

リモコン操作時の画面の動きです。

ここに動画が表示されます

部品

  • obniz Board 1Y
  • obniz Board
  • ブレッドボード
  • ジャンパーケーブル 数本
  • サーボモーター SG92R
  • 赤外線受信モジュール OSRB38C9AA
  • リードスイッチ MKA-10110
  • 磁石
  • 抵抗付きLED 緑
  • 抵抗付きLED 赤
  • モバイルバッテリー
  • USB ACアダプター
  • USBケーブル Type-B
  • USBケーブル Type-C
  • プラレール 車両 一編成
  • プラレール 線路 数本
  • プラレール 駅
  • LEGOブロック 数個
  • obniz車両への取り付け具 ※3Dプリンタでオリジナル製作
  • サーボモーター線路への取り付け具 ※3Dプリンタでオリジナル製作
  • obnizケース ※LEGOブロックでオリジナル製作

配線図

車両コントロール側
ポイントコントロール側

ソースコード

<html>

<!-- ---------------------------------------------------------------------------------------------------- -->

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
	<script src="https://unpkg.com/obniz@3.14.0/obniz.js" crossorigin="anonymous"></script>

	<link href="https://obniz.com/users/1656/repo/roundslider.min.css" rel="stylesheet" />
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
	<script src="https://obniz.com/users/1656/repo/roundslider.min.js"></script>
</head>

<!-- ---------------------------------------------------------------------------------------------------- -->

<style type="text/css">
	.rslider {
		display: inline-block;
		margin-top: 0px;
		margin-left: 0px;
	}
</style>

<!-- ---------------------------------------------------------------------------------------------------- -->

<body>
	<div></div>
	<h1></h1>
	<table>
		<tr>
			<td>
				<div id="sld_pwr" class="rslider"></div>
			</td>

			<td width="30">
			</td>

			<td>
				<div>
					<center>
						<button id="btn_mae" style="width:100px;height:50px" disabled></button>
					</center>
				</div>
				<div>
					<center>
						<button id="btn_usiro" style="width:100px;height:50px" disabled></button>
					</center>
				</div>
				<br>
				<div>
					<center>
						<button id="btn_auto" style="width:100px;height:50px" disabled>AUTO</button>
					</center>
				</div>
				<br>
				<div>
					<center>
						<button id="btn_tomaru" style="width:100px;height:50px;background-color:#FF6928" disabled>止まる</button>
					</center>
				</div>
			</td>

			<td width="100">
			</td>

      <td>
				<div>
					<center>
						<button id="btn_pointeki" style="width:200px;height:50px" disabled>ポイント・駅</button>
					</center>
				</div>
				<div>
					<center>
						<button id="btn_pointtuuka" style="width:200px;height:50px" disabled>ポイント・通過</button>
					</center>
				</div>
      </td>
		</tr>
	</table>

	<!-- ---------------------------------------------------------------------- -->

	<script>
	let obniz1 = new Obniz("9999-9999"); // obniz 車両   ←IDは消してあります
	let obniz2 = new Obniz("9999-9999"); // obniz ポイント ←IDは消してあります

    let motor1; // モーター 駆動
    let motor1_pwrmax = 35;
    let motor1_pwrrate = 100;
    let motor1_pwrrate_back = 60;
    let motor1_pwr = motor1_pwrmax * motor1_pwrrate * 0.01;

    let flg_move = 0; // 動作フラグ(0:停止、1:前、2:後、3::AUTO)
    let flg_point = 0; // ポイントフラグ(0:駅、1:1:通過)

    let rdsw; // リードスイッチ
    let rdsw_count = 0; // リードスイッチカウンタ
    let rdsw_loop; // リードスイッチループ

    let servo1; // サーボモーター  ポイント
    let servo1_pointeki = 64;
    let servo1_pointtuuka = 107;

    let led_green; // LED ポイント 緑
    let led_red; // LED ポイント 赤

    let irsens; // 赤外線受信センサー
    let data = new Array(); // 取得データ
    let data_ptn; // パターンデータ

    // --------------------------------------------------

    const ir_vhs = "11110110101011010110110101011010110110101101111011010101101011011010101101011011010110111101101010110101101101010110101101101011011110110101011010110110101011010110110101101111011010101101011011010101";
    const ir_vd8 = "11110101101011010110110101011010110110101101111010110101101011011010101101011011010110111101011010110101101101010110101101101011011110101101011010110110101011010110110101101111010110101101011011010101";
    const ir_rgt = "11110101011011011010101011010101011110101011011011010101011010101011110101011011011010101011010101011110101011011011010101011010101011110101011011011010101011010101011110101011011011010101011010101011";
    const ir_lft = "11110110110101101101010101101010101111011011010110110101010110101010111101101101011011010101011010101011110110110101101101010101101010101111011011010110110101010110101010111101101101011011010101011010";
    const ir_jgr = "111101011010110101101010101101011011010110111101011010110101101010101101011011010110111101011010110101101010101101011011010110";
    const ir_jgl = "111101101101011010110101010110101101101011011110110110101101011010101011010110110101101111011011010110101101010101101011011010";
    const ir_ply = "111101011010110110101010110101010111101011010110110101010110101010111101011010110110101010110101010111101011010110110101010110101010111101011010110110101010110101010";
    const ir_stp = "1111010101011011010101011010101011110101010110110101010110101010111101010101101101010101101010101111010101011011010101011010101011110101010110110101010110101010";
    const ir_out = "111101011011010110101010110101010111101011011010110101010110101010111101011011010110101010110101010111101011011010110101010110101010111101011011010110101010110101010111101011011010110101010110101010111101011011010110101010110101010";

    // --------------------------------------------------

    // スライダー・パワーを準備する
    $("#sld_pwr").roundSlider({
      sliderType: "min-range",
      disabled: true,
      value: motor1_pwrrate
    });

    // --------------------------------------------------

    // obniz1に接続する
    obniz1.onconnect = async function () {
      // モーターを準備する
      motor1 = obniz1.wired("DCMotor", {forward:0, back:1}); // モーター 駆動
      motor1.power(motor1_pwrmax);

      // リードスイッチを準備する
      rdsw = obniz1.wired("Button", {signal:2, gnd:3});

      // コントロールを有効化する
      control_enabled();

      // リードスイッチが変化したときのcallback関数を定義する
      rdsw.onchange = function(pressed) {
      
        if (pressed == true) {
          console.log("RDSWがON");

          // AUTOモードの時
          if (flg_move == 3) {
            // リードスイッチカウンタをインクリメントする
            rdsw_count++;

            if (rdsw_count + 1 == rdsw_loop) {
              // ポイントを駅に切り替える
              auto_pointeki();
            } else if (rdsw_count >= rdsw_loop) {
              // 駅に停車させる
              auto_station();
            }
          }
        } else {
          console.log("RDSWがOFF");
        }
      }

      console.log("obniz1 準備完了");
    }

    // --------------------------------------------------

    // obniz2に接続する
    obniz2.onconnect = async function () {
      // サーボモーターを準備する
      servo1 = obniz2.wired("ServoMotor",  {gnd:0, vcc:1, signal:2}); // サーボモーター  ポイント
      servo1.angle(servo1_pointeki);

      // LEDを準備する
      led_green = obniz2.wired("LED", {anode:3, cathode:5}); // LED 緑
      led_red = obniz2.wired("LED", {anode:4, cathode:5}); // LED 赤
      led_green.on();
      led_red.off();

      // 赤外線受信センサーを準備する
      irsens = obniz2.wired("IRSensor", {vcc:8, gnd:7, output:6}); // 赤外線受信センサー OSRB38C9AA

      // 赤外線信号を検出したときのcallback関数を定義する
      irsens.start(function(arr) {

        // 取得データ配列に取得データをセットする
        data = arr;
        
        // 取得データをパターン文字列に変換する
        data_ptn = change_bin2ptn().substr(0, 150);
        console.log(data_ptn);

        // 80文字以下のパターン文字列は無効とする
        if (data_ptn.length >= 80) {
          if (data_ptn.indexOf(ir_out) > -1 || ir_out.indexOf(data_ptn) > -1) {
            console.log("リモコンのカセット取出しボタンを検出しました");
            
            document.getElementById("btn_auto").click();
          } else if (data_ptn.indexOf(ir_ply) > -1 || ir_ply.indexOf(data_ptn) > -1) {
            console.log("リモコンのカセット再生ボタンを検出しました");

            document.getElementById("btn_mae").click();
          } else if (data_ptn.indexOf(ir_stp) > -1 || ir_stp.indexOf(data_ptn) > -1) {
            console.log("リモコンのカセット停止ボタンを検出しました");

            document.getElementById("btn_tomaru").click();
          } else if (data_ptn.indexOf(ir_vhs) > -1 || ir_vhs.indexOf(data_ptn) > -1) {
            console.log("リモコンのVHSボタンを検出しました");

            document.getElementById("btn_pointeki").click();
          } else if (data_ptn.indexOf(ir_vd8) > -1 || ir_vd8.indexOf(data_ptn) > -1) {
            console.log("リモコンのVideo8ボタンを検出しました");

            document.getElementById("btn_pointtuuka").click();
          } else if (data_ptn.indexOf(ir_rgt) > -1 || ir_rgt.indexOf(data_ptn) > -1) {
            console.log("リモコンの>>ボタンを検出しました");

            document.getElementById("btn_mae").click();
          } else if (data_ptn.indexOf(ir_lft) > -1 || ir_lft.indexOf(data_ptn) > -1) {
              console.log("リモコンの<<ボタンを検出しました");

              document.getElementById("btn_usiro").click();

          } else if (data_ptn.indexOf(ir_jgr) > -1 || ir_jgr.indexOf(data_ptn) > -1) {
              console.log("リモコンのジョグ右回転を検出しました");

              // AUTOモード以外の時
              if (flg_move != 3) {
                motor1_pwrrate += 5;
                motor1_power(motor1_pwrrate);
              }
          } else if (data_ptn.indexOf(ir_jgl) > -1 || ir_jgl.indexOf(data_ptn) > -1) {
              console.log("リモコンのジョグ左回転を検出しました");

              // AUTOモード以外の時
              if (flg_move != 3) {
                motor1_pwrrate -= 5;
                motor1_power(motor1_pwrrate);
              }
          }
        }

      })

      console.log("obniz2 準備完了");
    }

	</script>

	<script>
	// --------------------------------------------------
    // スライダー・パワーが変更されたら
    // --------------------------------------------------

    $("#sld_pwr").change(function() {

      motor1_pwrrate = $("#sld_pwr").roundSlider("getValue");
      motor1_pwr = parseInt(motor1_pwrmax * motor1_pwrrate * 0.01);

      console.log("パワーが変更された " + motor1_pwr);

      motor1.power(motor1_pwr);

    });

    // --------------------------------------------------
    // ボタン・前がクリックされたら
    // --------------------------------------------------

    $('#btn_mae').click(function () {

      console.log("前が押された");

      // 動作フラグをセットする
      flg_move = 1;

      // 使用できないコントロールを無効化する
      control_disabled();

      // モーターを回転させる
      motor1.forward();

    });

    // --------------------------------------------------
    // ボタン・後がクリックされたら
    // --------------------------------------------------

    $('#btn_usiro').click(function () {

      console.log("後が押された");

      // 動作フラグをセットする
      flg_move = 2;

      // 使用できないコントロールを無効化する
      control_disabled();
      $("#sld_pwr").roundSlider({
        disabled: true
      });

      // モーターパワーを変更する
      motor1_pwrrate = motor1_pwrrate_back;
      motor1_pwr = parseInt(motor1_pwrmax * motor1_pwrrate * 0.01);
      // モーターパワーを変更する
      motor1.power(motor1_pwr);

      // スライダー・パワーを変更する
      $("#sld_pwr").roundSlider({
        value: motor1_pwrrate
      });

      // モーターを逆回転させる
      motor1.reverse();

    });

    // --------------------------------------------------
    // ボタン・AUTOがクリックされたら
    // --------------------------------------------------

    $('#btn_auto').click(function () {

      console.log("AUTOが押された");

      // 動作フラグをセットする
      flg_move = 3;

      // リードスイッチカウンタを初期化する
      rdsw_count = 0;

      // 使用できないコントロールを無効化する
      control_disabled();
      $("#sld_pwr").roundSlider({
        disabled: true
      });

      // 発車させる
      auto_start();

    });

    // --------------------------------------------------
    // ボタン・止まるがクリックされたら
    // --------------------------------------------------

    $('#btn_tomaru').click(function () {

      console.log("止まるが押された");

      // 動作フラグをセットする
      flg_move = 0;

      // モーターを停止させる
      motor1.stop();

      // コントロールを有効化する
      control_enabled();

    });

    // --------------------------------------------------
    // ボタン・ポイント・駅がクリックされたら
    // --------------------------------------------------

    $('#btn_pointeki').click(function () {

      console.log("ポイント・駅が押された");

      // ポイントフラグをセットする
      flg_point = 0;

      // サーボモーターを動かす
      servo1.angle(servo1_pointeki);

      // LEDを緑で点灯する
      led_green.on();
      led_red.off();

    });

    // --------------------------------------------------
    // ボタン・ポイント・通過がクリックされたら
    // --------------------------------------------------

    $('#btn_pointtuuka').click(function () {

      console.log("ポイント・通過が押された");

      // ポイントフラグをセットする
      flg_point = 1;

      // サーボモーターを動かす
      servo1.angle(servo1_pointtuuka);

      // LEDを赤で点灯する
      led_green.off();
      led_red.on();

    });

    // --------------------------------------------------
    // AUTOでスタートさせる
    // --------------------------------------------------

    async function auto_start() {

      var mtrrate;

      console.log("発車します");

      // リードスイッチのループ数をランダムで決める 
      rdsw_loop = Math.floor(Math.random() * 3) + 1;
      console.log("ループ数は" + rdsw_loop);

      // ポイントフラグをセットする
      flg_point = 1;
      // ポイントを通過に切り替える
      servo1.angle(servo1_pointtuuka);
      // LEDを赤で点灯する
      led_green.off();
      led_red.on();

      // 次は通過か?
      if (rdsw_loop == 1) {
        // ポイントフラグをセットする
        flg_point = 0;
        // ポイントを駅に切り替える
        servo1.angle(servo1_pointeki);
        // LEDを緑で点灯する
        led_green.on();
        led_red.off();
      }

      // スタートは0からモーターを回転させる
      mtrrate = 0;
      motor1_forward(mtrrate);

      // 10回に別けて加速させる
      for (count = 0; count < 10; count++) {
        // レートをアップする
        mtrrate += 10;
        // レートを変更する   
        motor1_power(mtrrate);

        // 待ち
        await obniz1.wait(300);

        // 動作フラグを確認する
        if (flg_move == 0) {
          return;
        }
      }

    }

    // --------------------------------------------------
    // AUTOでストップさせる
    // --------------------------------------------------

    async function auto_stop() {

      var mtrrate;

      console.log("停車します");

      // スタートは100からモーターを回転させる
      mtrrate = 100;
      motor1_forward(mtrrate);

      // 10回に別けて加速させる
      for (count = 0; count < 10; count++) {
        // レートをアップする
        mtrrate -= 10;
        // レートを変更する   
        motor1_power(mtrrate);

        // 待ち
        await obniz1.wait(500);

        // 動作フラグを確認する
        if (flg_move == 0) {
          return;
        }
      }

    }

    // --------------------------------------------------
    // AUTOで駅にストップさせる
    // --------------------------------------------------

    async function auto_station() {

      var mtrrate;

      console.log("駅に停車します");

      // スタートは100からモーターを回転させる
      mtrrate = 100;
      motor1_forward(mtrrate);

      // 10回に別けて減速させる
      for (count = 0; count < 10; count++) {
        // レートをアップする
        mtrrate -= 10;
        // レートを変更する   
        motor1_power(mtrrate);

        // 待ち
        await obniz1.wait(550);

        // 動作フラグを確認する
        if (flg_move == 0) {
          return;
        }
      }

      // 待ち
      await obniz1.wait(5000);

      // 動作フラグを確認する
      if (flg_move == 0) {
        return;
      }

      // リードスイッチカウンタを初期化する
      rdsw_count = 0;

      // 発車させる
      auto_start();
    }

    // --------------------------------------------------
    // AUTOでポイントを駅に切り替える
    // --------------------------------------------------

    async function auto_pointeki() {

      var mtrrate;

      console.log("ポイントを駅に切り替えます");

      // ポイントフラグをセットする
      flg_point = 0;

      // 待ち
      await obniz1.wait(3000);

      // ポイントを駅に切り替える
      servo1.angle(servo1_pointeki);

      // LEDを緑で点灯する
      led_green.on();
      led_red.off();
    }

    // --------------------------------------------------
    // 指定レートでモーターを回転させる
    // --------------------------------------------------

    function motor1_forward(pwrrate) {

      // モーターパワーを変更する
      motor1_power(pwrrate);

      // モーターを回転させる
      motor1.forward();

    }

    // --------------------------------------------------
    // 指定レートに変更する
    // --------------------------------------------------

    function motor1_power(pwrrate) {

      // モーターパワーを変更する
      if (pwrrate > 100) {
        pwrrate = 100;
      } else if (pwrrate < 0) {
        pwrrate = 0;
      }
      motor1_pwrrate = pwrrate;
      motor1_pwr = parseInt(motor1_pwrmax * motor1_pwrrate * 0.01);
      // モーターパワーを変更する
      motor1.power(motor1_pwr);

      // スライダー・パワーを変更する
      $("#sld_pwr").roundSlider({
        value: motor1_pwrrate
      });

    }

    // --------------------------------------------------
    // 取得データをパターン文字列に変換する
    // --------------------------------------------------

    function change_bin2ptn() {

      var strptn = ""; // パターン文字列
      var chrbit = ""; // ビットデータ
      var count; // カウント

      strptn = "";
      for (var k = 0; k < data.length; k++) {
        if (data[k].toString(10) != chrbit) {
          // 受信データから1ビット分のデータを取得する
          chrbit = data[k].toString(10);

          // パターン文字列を組み立てる
          strptn += chrbit;
          count = 1;
        } else {
          // 10文字単位に文字を追加する
          if (count == 10 && chrbit == "1") {
            // パターン文字列を組み立てる
            strptn += chrbit;
            count = 0;
          }

          // カウントをインクリメントする
          count++;
        }
      }

      return strptn; // ----- (RET)
    }

    // --------------------------------------------------
    // コントロールを有効化する
    // --------------------------------------------------

    function control_enabled() {

      $("#sld_pwr").roundSlider({
        disabled: false
      });

      document.getElementById("btn_mae").disabled = false;
      document.getElementById("btn_usiro").disabled = false;
      document.getElementById("btn_auto").disabled = false;
      document.getElementById("btn_tomaru").disabled = false;

      document.getElementById("btn_pointeki").disabled = false;
      document.getElementById("btn_pointtuuka").disabled = false;

    }

    // --------------------------------------------------
    // コントロールを無効化する
    // --------------------------------------------------

    function control_disabled() {

      document.getElementById("btn_mae").disabled = true;
      document.getElementById("btn_usiro").disabled = true;
      document.getElementById("btn_auto").disabled = true;

      if (flg_move == 3) {
        document.getElementById("btn_pointeki").disabled = true;
        document.getElementById("btn_pointtuuka").disabled = true;
      }

    }

	</script>
</body>

</html>

最後に

遠隔操作できる車両、ポイントなど増やして、運行管理をしながら複数の車両を同時に走らせれるなどやってみたいと思っています。
また、今回の製作ではobniz Board 1Yの低電圧で動かすことやスリープ機能などは利用しませんでした。
これらの機能を使ったアイデアも考え試してみたいと思っています。

obnizは他のマイコンボードに比べて手軽にIoT化することが出来るツールだと感じました。
WiFiに接続すればどこででも作成、実行が出来ますからね。
閃いたらすぐ形に出来ます。

自分で考えたものを形にしていく「ものづくり」は楽しいですね。
私がやっているこういった製作を見て、子供たちも興味を示してくれるとうれしいのですが...

ログインしてコメントを投稿する