3
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?

More than 3 years have passed since last update.

obnizAdvent Calendar 2021

Day 2

Obniz Cloudとnipple.jsを連携したらメカ操作が楽になった

Last updated at Posted at 2021-12-01

はじめに

Obnizを使ったラジコンカーを微調整操作をする際にいいUIがなくて困っていたところ、nipple.jsというイマドキなジョイスティックっぽい操作ができるパッケージを知った。そんなわけで連携してみた話です。

作ったもの(結果)

nipple.jsを用いて実装されたUIによってゆるゆるバギーが走行しています。
ポイントとしては、速度の強弱や角度の微調整が行えているところです。

ゆるゆるバギー作成に使ったもの

ちなみに動きとしてはこんなかんじです。

nipple.jsについて

公式ページはこちら:https://yoannmoi.net/nipplejs/

nipple.jsとは、yoann moinet氏が主体になって作成された、タッチ操作を考慮した仮想ジョイスティックインターフェースです。

公式サイトで示すとおり、比較的文量の少ないコードでタッチ圧力などの細かい操作まで取得することができます。マルチタップもできるので非常に高機能です。

ちなみにnipple.jsはCDNでも提供されています。Obniz Cloudを利用する都合から、今回はCDNを使用しました。
https://cdnjs.com/libraries/nipplejs

今回の実装について

実装したコードは主に三つに分けられます。

  • nipple.js使用領域の用意
  • nipple.jsの初期化&イベント取得処理の実装
  • スティック位置を取得&モーター駆動コードを書く。

nipple.js使用領域の用意

nipple.jsを使うためには、さきに画面をタッチ(クリック)した際にジョイスティックを表示・操作する領域を作成する必要があります。今回の実装では左右で別々のジョイスティックを使いたかったため、このような画面を作成しました。
(注意:Obniz Cloud上で実装した都合、bootstrap 4.3.1を使用しています。)

ui
  <div class="container">
    <div class="row">
      <div id="zone_joystick_r" style="width:100%;height:600px;background-color:skyblue;" class="rounded col">
        dynamicR<br>アクセル</div>
      <div id="zone_joystick_l" style="width:100%;height:600px;background-color:pink;" class="rounded col">dynamicL<br>ステア
      </div>
    </div>
  </div>
画面領域の例

nipple.jsの初期化&イベント取得処理の実装

nipple.js初期化時に上記領域のid(zone_joystick_r,zone_joystick_l)を引数zoneに指定することで、ジョイスティックを表示する領域を指定することができます。
左右でジョイスティックの色を変える際には、colorを引数にわたすことで指定できます。

init
var dynamicR = nipplejs.create({
  zone: document.getElementById("zone_joystick_r"),
  color: "blue",
});
var dynamicL = nipplejs.create({
  zone: document.getElementById("zone_joystick_l"),
  color: "red",
});

大抵の実装では、joystickが操作された際になにかの処理が動いてほしいと考えるかと思います。今回は公式サンプルのcodepenの内容を参考にして下記コードを実装しました。

joystickevent
  bindNipple();

  function bindNipple() {
    dynamicR
      .on("start end", function (evt, data) {
        // joystick操作開始・終了時呼び出し
      })
      .on("move", function (evt, data) {
        // joystickを動かしたとき呼び出し
      })
      .on(
        "dir:up plain:up dir:left plain:left dir:down " +
          "plain:down dir:right plain:right",
        function (evt, data) {
          // joystick指はなし時呼び出し
        }
      )
      .on("pressure", function (evt, data) {
        //  joystickに圧がかかっているとき呼び出し
      });
  }

onメソッドに任意のイベントを入れることで、細かいイベント別の処理を走らせることが可能です。
今回のObnizゆるバギーの走行にはジョイスティックのスティック移動と開始・終了時の処理呼び出しを使います。

スティック位置を取得&モーター駆動コードを書く。

joystickの情報は上記イベントメソッドの引数evtから取得可能です。
今回はジョイスティックの位置情報がほしかったため、このようにデータを取得しました。

stickmove
evt.target.nipples[0].frontPosition.x
evt.target.nipples[0].frontPosition.y

evt.target.nipples[0].frontPosition.xは水平方向のジョイスティック位置であり、無操作時を0として左一杯にうごかしたときに+50,右一杯にうごかして-50という値が入ります。同様に、evt.target.nipples[0].frontPosition.yは垂直方向のジョイスティック位置で、無操作時に0、上一杯操作で+50,下一杯操作で-50という値が入ります。

ちなみに同じ画面領域で複数のジョイスティックを出すことができるため、2つ以上のジョイスティックを操作する際にはnipples[1]のようにインデックスを増やすことで解決できます。

値の正負をモーターの制御に用いるべく、ゆるバギーの前後動作にはこのような関数を実装しました。powにはevt.target.nipples[0].frontPosition.yがそのまま入ります。
(pwm1はモーター正転側、pwm2はモーター逆転側の強弱付けのためのpwmオブジェクトです。)

front-back
  function flipPWM(pow) {
    if (pow >= 0) {
      pwm1.duty(pow);
      pwm2.duty(0);
      return;
    }
    pwm1.duty(0);
    pwm2.duty(-pow);
  }

最後に、サーボの操作はこのように実装しました。ちなみに95.0はサーボの中央位置です。

servo
servo.angle(95.0 + evt.target.nipples[0].frontPosition.x / 2);

コード全体

ボタン操作はデバッグ用です。

allcode.html
<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" />
  <link rel="stylesheet" href="/css/starter-sample.css" />
  <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
  <script src="https://unpkg.com/obniz@3.6.1/obniz.js" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/nipplejs/0.9.0/nipplejs.min.js"
    integrity="sha512-7PRZndBOTMkYqbGwO6dvNDozKEwaJYu3zPLoLv0rzOLMPQ2PVh6yaevCZOUL8+/n+A16fnLYEBKAlYXgMBn54w=="
    crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>

<body>
  <div class="container">
    <div class="row">
      <div id="zone_joystick_r" style="width:100%;height:600px;background-color:skyblue;" class="rounded col">
        dynamicR<br>アクセル</div>
      <div id="zone_joystick_l" style="width:100%;height:600px;background-color:pink;" class="rounded col">dynamicL<br>ステア
      </div>
    </div>
  </div>


  <div class="led">
    <button class="btn btn-primary" id="F">Forward</button>
    <button class="btn btn-primary" id="B">Back</button>
    <button class="btn btn-primary" id="R">Right</button>
    <button class="btn btn-primary" id="L">Left</button>
  </div>

  <script>
    var dynamicR = nipplejs.create({
  zone: document.getElementById("zone_joystick_r"),
  color: "blue",
});
var dynamicL = nipplejs.create({
  zone: document.getElementById("zone_joystick_l"),
  color: "red",
});

var obniz = new Obniz("YOUR OBNIZ ID");

obniz.onconnect = async function () {
  var pwm2 = obniz.getFreePwm();
  pwm2.start({ io: 11 });
  pwm2.freq(10000); // 10k hz
  pwm2.duty(0);

  var pwm1 = obniz.getFreePwm();
  pwm1.start({ io: 10 });
  pwm1.freq(10000); // 10k hz
  pwm1.duty(0);

  var servo = obniz.wired("ServoMotor", { gnd: 4, vcc: 5, signal: 6 });
  servo.angle(95.0);

  bindNipple();

  function bindNipple() {
    dynamicR
      .on("start end", function (evt, data) {
        // console.log("evt st:",JSON.stringify(evt))
        flipPWM(0);
      })
      .on("move", function (evt, data) {
        // console.log("evt move:",JSON.stringify(evt))
        console.log("evt move:", evt.target.nipples[0].frontPosition.y);
        flipPWM(evt.target.nipples[0].frontPosition.y * 2);
      })
      .on(
        "dir:up plain:up dir:left plain:left dir:down " +
          "plain:down dir:right plain:right",
        function (evt, data) {
          // console.log("evt up:",JSON.stringify(evt))
        }
      )
      .on("pressure", function (evt, data) {
        //  console.log("evt press:",JSON.stringify(evt))
      });

    dynamicL
      .on("start end", function (evt, data) {
        // console.log("evt st:",JSON.stringify(evt))
        servo.angle(95.0);
      })
      .on("move", function (evt, data) {
        // console.log("evt move:",JSON.stringify(evt))
        console.log("evt move:", evt.target.nipples[0].frontPosition.x);
        servo.angle(95.0 + evt.target.nipples[0].frontPosition.x / 2);
      })
      .on(
        "dir:up plain:up dir:left plain:left dir:down " +
          "plain:down dir:right plain:right",
        function (evt, data) {
          // console.log("evt up:",JSON.stringify(evt))
        }
      )
      .on("pressure", function (evt, data) {
        //  console.log("evt press:",JSON.stringify(evt))
      });
  }

  function flipPWM(pow) {
    if (pow >= 0) {
      pwm1.duty(pow);
      pwm2.duty(0);
      return;
    }
    pwm1.duty(0);
    pwm2.duty(-pow);
  }

  await $("#F").on("touchstart mousedown", async () => {
    pwm1.duty(0);
    pwm2.duty(100);
  });
  await $("#F").on("touchstart mouseup", async () => {
    pwm1.duty(0);
    pwm2.duty(0);
  });
  await $("#B").on("touchstart mousedown", async () => {
    pwm1.duty(100);
    pwm2.duty(0);
  });
  await $("#B").on("touchstart mouseup", async () => {
    pwm1.duty(0);
    pwm2.duty(0);
  });

  await $("#R").on("touchstart mousedown", async () => {
    servo.angle(105.0 + 10);
  });
  await $("#R").on("touchstart mouseup", async () => {
    servo.angle(95.0);
  });
  await $("#L").on("touchstart mousedown", async () => {
    servo.angle(85.0 - 10);
  });
  await $("#L").on("touchstart mouseup", async () => {
    servo.angle(95.0);
  });
};

  </script>
</body>

</html>

まとめ

nipple.jsを使うことで、メカ操作を微調整できるようになった。ハードウェアをAPI的につかえるObnizはモダンなWebUIとすぐ組み合わせられて良いですね。

3
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
3
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?