20191024のNode.jsに関する記事は12件です。

NodeCGで配信を華やかに

Splatoon2の大会で運営としてOBSを使って運営放送(配信)をする際に放送をかっこよくするために何か良いものはないかと探しているうちにNodeCGというものを見つけました。

NodeCGで何ができる?

NodeCGを使うとOBS組み込みブラウザ(ブラウザソース)に用意したHTMLを表示させて、それを外部から操作することができます。
文字じゃよくわからないと思いますが使ってみるとおおすげぇってなりますたぶん
自分の場合は以前ブラウザに表示させてそれをキャプチャして表示させていたんですが、書き換えている間は違うシーンに変えなきゃいけないし、誤ってウィンドウ閉じたりすると余計なものが映り込んだりしてしまっていたので、その方法もまた別に良いやり方があるのかもしれないが書き換えが必要なものをブラウザソースで表示させることができるのは凄い便利でした。

導入方法

についてはcma2819氏Hoishin氏が詳しく説明してくださっているのでそちらを参考にしてみてください。

実際につかってみる

NodeCGのbundlesフォルダの中にフォルダを作ります。以後ここで作ったフォルダ名をexampleとして進めていきます。
その中に

package.json
{
  "name": "example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",

  "nodecg": {
    "compatibleRange": "^1.0.0"
  }
}

を置きます(NodeCGを動かす際に必要です)。
加えて、exampleフォルダの中にdashboardとgraphicsというフォルダを作っておいてください。

コメント表示

まずはdashboardフォルダの中に

test.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
    <input id="comment" type="text">
    <button id="submitButton">反映</button>
</body>
<script>
    const com = document.getElementById("comment");
    const testRep = nodecg.Replicant("test");
    submitButton.onclick = () => {
        testRep.value = com.value;
    }
</script>
</html>

次にgraphicsフォルダの中に

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <div id="simple" style="font-size: 100px; color: white"></div>
</body>

<script>
  const simple = document.getElementById("simple");
  const testRep = nodecg.Replicant("test");
  testRep.on("change", newValue => {
    simple.innerText = newValue;
  });
</script>
</html>

を作成します。

できたら先程exampleフォルダに置いたpackage.jsonに書き加えます。

package.json
{
  "name": "example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",

  "nodecg": {
    "compatibleRange": "^1.0.0",
    "dashboardPanels": [
      {
        "width": "1",
        "name": "test",
        "title": "コメント",
        "file": "test.html"
      }
    ],
    "graphics": [
      {
        "file": "index.html",
        "width": 800,
        "height": 600
      }
    ]
  }
}

これで完成です。動かしてみましょう。

コマンドプロンプトを開いてNodeCGのディレクトリ(bundlesフォルダのが入っているディレクトリ)に移動して実行します。

cd ここにNodeCGのディレクトリのアドレス
node .

しばらくすると

NodeCG running on http://localhost:9090

みたいに表示されるのでそのURL(この場合はhttp://localhost:9090)をブラウザで開きます。
すると
dashboard
何かかっこいいのが表示されました。これがダッシュボードで、ここで操作します。
このままだと表示するやつがないので右上のGRAPHICSと書いてある目のマークをクリックしてください。
すると
image.png
こんな感じの画面が出てくるのでCOPY URLをクリックし、OBSを起動してブラウザソースを追加してください。
obs
追加した状態だと値が入っていないので何も表示されていません。
ひとまず先程開いたダッシュボードのWORKSPACEをクリックして操作画面に戻ります。
コメントと書いてある所に適当な文字を入力して反映ボタンをクリックしてみてください。
result
変わった!!!!!
と、こんな感じで動かせます。

もっといろいろやってみましょう。

試合経過表示

HTMLやCSS部分はあんまりわかってないので参考にせずに、どんな感じに動かすかを見ていただければ…
まずはdashboardフォルダの中に

panel.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
  <style>
    * {
    font-family: "コーポレート・ロゴ(ラウンド)";
        font-size: 20px;
  }
    .team, .member {
        width: 370px;
    }
    .content {
        margin-bottom: 10px
    }
  </style>
</head>
<body>
    <div id="folder">
    <div class="content">
            <label>チームA</label>
            <input id="team1" class="team" type="text" placeholder="チーム名" onchange="teamSet()">
            <textarea id="member1" class="member" placeholder="メンバー"></textarea>
    </div>
    <div class="content">
            <label>チームB</label>
            <input id="team2" class="team" type="text" placeholder="チーム名" onchange="teamSet()">
          <textarea id="member2" class="member" placeholder="メンバー"></textarea>
    </div>
  </div>
    <label>ルール&ステージ</label>
    <div id="folder">
    <div class="content">
            <select id="result1">
                <option selected>-</option>
          </select><br>
            <select id="rule1">
            <option selected>? ? ?</option>
            <option>ナワバリ</option>
            <option>ガチエリア</option>
            <option>ガチヤグラ</option>
            <option>ガチホコ</option>
            <option>ガチアサリ</option>
          </select>
            <select id="stage1">
                <option value="none" selected>? ? ?</option>
                <option value="0">バッテラストリート</option>
                <option value="1">フジツボスポーツクラブ</option>
                <option value="2">ガンガゼ野外音楽堂</option>
                <option value="3">チョウザメ造船</option>
                <option value="4">海女美術大学</option>
                <option value="5">コンブトラック</option>
                <option value="6">マンタマリア号</option>
                <option value="7">ホッケふ頭</option>
                <option value="8">タチウオパーキング</option>
                <option value="9">エンガワ河川敷</option>
                <option value="10">モズク農園</option>
                <option value="11">Bバスパーク</option>
                <option value="12">デボン海洋博物館</option>
                <option value="13">ザトウマーケット</option>
                <option value="14">ハコフグ倉庫</option>
                <option value="15">アロワナモール</option>
                <option value="16">モンガラキャンプ場</option>
                <option value="17">ショッツル鉱山</option>
                <option value="18">アジフライスタジアム</option>
                <option value="19">ホテルニューオートロ</option>
                <option value="20">スメーシーワールド</option>
                <option value="21">アンチョビットゲームズ</option>
                <option value="22">ムツゴ楼</option>
                <option value="x">DEAR SENPAI</option>
            </select>
    </div>
        <div class="content">
            <select id="result2">
                <option selected>-</option>
          </select><br>
            <select id="rule2">
            <option selected>? ? ?</option>
            <option>ナワバリ</option>
            <option>ガチエリア</option>
            <option>ガチヤグラ</option>
            <option>ガチホコ</option>
            <option>ガチアサリ</option>
          </select>
            <select id="stage2">
                <option value="none" selected>? ? ?</option>
                <option value="0">バッテラストリート</option>
                <option value="1">フジツボスポーツクラブ</option>
                <option value="2">ガンガゼ野外音楽堂</option>
                <option value="3">チョウザメ造船</option>
                <option value="4">海女美術大学</option>
                <option value="5">コンブトラック</option>
                <option value="6">マンタマリア号</option>
                <option value="7">ホッケふ頭</option>
                <option value="8">タチウオパーキング</option>
                <option value="9">エンガワ河川敷</option>
                <option value="10">モズク農園</option>
                <option value="11">Bバスパーク</option>
                <option value="12">デボン海洋博物館</option>
                <option value="13">ザトウマーケット</option>
                <option value="14">ハコフグ倉庫</option>
                <option value="15">アロワナモール</option>
                <option value="16">モンガラキャンプ場</option>
                <option value="17">ショッツル鉱山</option>
                <option value="18">アジフライスタジアム</option>
                <option value="19">ホテルニューオートロ</option>
                <option value="20">スメーシーワールド</option>
                <option value="21">アンチョビットゲームズ</option>
                <option value="22">ムツゴ楼</option>
                <option value="x">DEAR SENPAI</option>
            </select>
    </div>
        <div class="content">
            <select id="result3">
                <option selected>-</option>
          </select><br>
            <select id="rule3">
            <option selected>? ? ?</option>
            <option>ナワバリ</option>
            <option>ガチエリア</option>
            <option>ガチヤグラ</option>
            <option>ガチホコ</option>
            <option>ガチアサリ</option>
          </select>
            <select id="stage3">
                <option value="none" selected>? ? ?</option>
                <option value="0">バッテラストリート</option>
                <option value="1">フジツボスポーツクラブ</option>
                <option value="2">ガンガゼ野外音楽堂</option>
                <option value="3">チョウザメ造船</option>
                <option value="4">海女美術大学</option>
                <option value="5">コンブトラック</option>
                <option value="6">マンタマリア号</option>
                <option value="7">ホッケふ頭</option>
                <option value="8">タチウオパーキング</option>
                <option value="9">エンガワ河川敷</option>
                <option value="10">モズク農園</option>
                <option value="11">Bバスパーク</option>
                <option value="12">デボン海洋博物館</option>
                <option value="13">ザトウマーケット</option>
                <option value="14">ハコフグ倉庫</option>
                <option value="15">アロワナモール</option>
                <option value="16">モンガラキャンプ場</option>
                <option value="17">ショッツル鉱山</option>
                <option value="18">アジフライスタジアム</option>
                <option value="19">ホテルニューオートロ</option>
                <option value="20">スメーシーワールド</option>
                <option value="21">アンチョビットゲームズ</option>
                <option value="22">ムツゴ楼</option>
                <option value="x">DEAR SENPAI</option>
            </select>
    </div>
        <div class="content">
            <select id="result4">
                <option selected>-</option>
          </select><br>
            <select id="rule4">
            <option selected>? ? ?</option>
            <option>ナワバリ</option>
            <option>ガチエリア</option>
            <option>ガチヤグラ</option>
            <option>ガチホコ</option>
            <option>ガチアサリ</option>
          </select>
            <select id="stage4">
                <option value="none" selected>? ? ?</option>
                <option value="0">バッテラストリート</option>
                <option value="1">フジツボスポーツクラブ</option>
                <option value="2">ガンガゼ野外音楽堂</option>
                <option value="3">チョウザメ造船</option>
                <option value="4">海女美術大学</option>
                <option value="5">コンブトラック</option>
                <option value="6">マンタマリア号</option>
                <option value="7">ホッケふ頭</option>
                <option value="8">タチウオパーキング</option>
                <option value="9">エンガワ河川敷</option>
                <option value="10">モズク農園</option>
                <option value="11">Bバスパーク</option>
                <option value="12">デボン海洋博物館</option>
                <option value="13">ザトウマーケット</option>
                <option value="14">ハコフグ倉庫</option>
                <option value="15">アロワナモール</option>
                <option value="16">モンガラキャンプ場</option>
                <option value="17">ショッツル鉱山</option>
                <option value="18">アジフライスタジアム</option>
                <option value="19">ホテルニューオートロ</option>
                <option value="20">スメーシーワールド</option>
                <option value="21">アンチョビットゲームズ</option>
                <option value="22">ムツゴ楼</option>
                <option value="x">DEAR SENPAI</option>
            </select>
    </div>
        <div class="content">
            <select id="result5">
                <option selected>-</option>
          </select><br>
            <select id="rule5">
            <option selected>? ? ?</option>
            <option>ナワバリ</option>
            <option>ガチエリア</option>
            <option>ガチヤグラ</option>
            <option>ガチホコ</option>
            <option>ガチアサリ</option>
          </select>
            <select id="stage5">
                <option value="none" selected>? ? ?</option>
                <option value="0">バッテラストリート</option>
                <option value="1">フジツボスポーツクラブ</option>
                <option value="2">ガンガゼ野外音楽堂</option>
                <option value="3">チョウザメ造船</option>
                <option value="4">海女美術大学</option>
                <option value="5">コンブトラック</option>
                <option value="6">マンタマリア号</option>
                <option value="7">ホッケふ頭</option>
                <option value="8">タチウオパーキング</option>
                <option value="9">エンガワ河川敷</option>
                <option value="10">モズク農園</option>
                <option value="11">Bバスパーク</option>
                <option value="12">デボン海洋博物館</option>
                <option value="13">ザトウマーケット</option>
                <option value="14">ハコフグ倉庫</option>
                <option value="15">アロワナモール</option>
                <option value="16">モンガラキャンプ場</option>
                <option value="17">ショッツル鉱山</option>
                <option value="18">アジフライスタジアム</option>
                <option value="19">ホテルニューオートロ</option>
                <option value="20">スメーシーワールド</option>
                <option value="21">アンチョビットゲームズ</option>
                <option value="22">ムツゴ楼</option>
                <option value="x">DEAR SENPAI</option>
            </select>
    </div>
  </div>
    <button id="submitButton">反映</button>
</body>
<script>
    const team1 = document.getElementById("team1");
    const team2 = document.getElementById("team2");
    const member1 = document.getElementById("member1");
    const member2 = document.getElementById("member2");
    const result1 = document.getElementById("result1");
    const result2 = document.getElementById("result2");
    const result3 = document.getElementById("result3");
    const result4 = document.getElementById("result4");
    const result5 = document.getElementById("result5");
    const rule1 = document.getElementById("rule1");
    const rule2 = document.getElementById("rule2");
    const rule3 = document.getElementById("rule3");
    const rule4 = document.getElementById("rule4");
    const rule5 = document.getElementById("rule5");
    const stage1 = document.getElementById("stage1");
    const stage2 = document.getElementById("stage2");
    const stage3 = document.getElementById("stage3");
    const stage4 = document.getElementById("stage4");
    const stage5 = document.getElementById("stage5");
    const submitButton = document.getElementById("submitButton");

    function teamSet() {
        let teams = [document.getElementById("team1").value, document.getElementById("team2").value];
        teams.forEach((team, index) => {
            for (var i = 1; i <= 5; i++) {
                let team_select = document.getElementById("result" + i)
                team_select.options[index+1] = new Option(team, team);
            }
        });
    }

    const dataRep = nodecg.Replicant("data", {persistent: false});

    submitButton.onclick = () => {
        let winArray = [result1.value,result2.value,result3.value,result4.value,result5.value];
        let winA = 0;
        let winB = 0;
        for (var i = 0; i < winArray.length; i++) {
            if (winArray[i] === team1.value) winA++;
            if (winArray[i] === team2.value) winB++;
        }
        dataRep.value = {
            "counter1": winA,
            "counter2": winB,
            "team1": team1.value,
            "team2": team2.value,
            "member1": member1.value,
            "member2": member2.value,
            "result1": result1.value,
            "result2": result2.value,
            "result3": result3.value,
            "result4": result4.value,
            "result5": result5.value,
            "rule1": rule1.value,
            "rule2": rule2.value,
            "rule3": rule3.value,
            "rule4": rule4.value,
            "rule5": rule5.value,
            "stage1": stage1.value,
            "stage2": stage2.value,
            "stage3": stage3.value,
            "stage4": stage4.value,
            "stage5": stage5.value
        };
    }
</script>
</html>

こんな感じで複数の値を送ることもできちゃいます。

次にgraphicsフォルダの中に

3games.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
  * {
    font-family: "コーポレート・ロゴ(ラウンド)";
  }
  p {
    text-align: center;
    font-family: "Splatoon2";
  }
  .counter {
    text-align: center;
    font-family: "Splatoon2";
    font-size: 120px;
  }
  .team {
    text-align: center;
    font-size: 60px;
  }
  .member {
    overflow: hidden;
    height: 3.0em;
    line-height: 1.5;
    text-align: center;
    font-size: 40px;
  }
  .result, .rule, .stage {
    font-size: 40px;
  }
  #teaminfo {
    width: 100%;
    margin: 3% 0 0;
  }
  #teamprof {
    width: 50%;
  }
  #folder {
    width: 80%;
    margin: 30px auto;
    display: -webkit-box;
    -webkit-box-pack: justify;
  }
  .content {
    width: 31%;
    height: auto;
    margin: 0 0 0 0;
  }
  .stage {
    width: 100%;
    height: auto;
  }
  .image {
    position: relative;
  }
  .image p {
    position: absolute;
    top: 0;
    left: 0;
    margin: 0;
    color: white;
    background: rgba(0, 0, 0, 0.6);;
    font-size: 30px;
    line-height: 1;
    padding: 5px 20px;
  }
  </style>
</head>
<body bgcolor="2C2F33" text="#FFFFFF" link="#00AEEF" vlink="#7289DA" alink="#FC0000">
  <center>
    <div id="teaminfo" style="display:inline-flex">
      <div id="teamprof">
        <div id="counter1" class="counter">0</div>
        <div id="team1" class="team">チーム名</div>
        <div id="member1" class="member">メンバー</div>
      </div>
      <p style="font-size: 100px">VS</p>
      <div id="teamprof">
        <div id="counter2" class="counter">0</div>
        <div id="team2" class="team">チーム名</div>
        <div id="member2" class="member">メンバー</div>
      </div>
    </div>
  </center>
  <div id="folder">
    <div class="content">
      <div id="result1" class="result">-</div>
      <div class="image">
        <img id="image1" src="./image/stage/none.png" class="stage">
        <p>1</p>
      </div>
      <div id="rule1" class="result">? ? ?</div>
      <div id="stage1" class="result">? ? ?</div>
    </div>
    <div class="content">
      <div id="result2" class="result">-</div>
      <div class="image">
        <img id="image2" src="./image/stage/none.png" class="stage">
        <p>2</p>
      </div>
      <div id="rule2" class="result">? ? ?</div>
      <div id="stage2" class="result">? ? ?</div>
    </div>
    <div class="content">
      <div id="result3" class="result">-</div>
      <div class="image">
        <img id="image3" src="./image/stage/none.png" class="stage">
        <p>3</p>
      </div>
      <div id="rule3" class="result">? ? ?</div>
      <div id="stage3" class="result">? ? ?</div>
    </div>
  </div>
</body>

<script>
  const counter1 = document.getElementById("counter1");
  const counter2 = document.getElementById("counter2");
  const team1 = document.getElementById("team1");
  const team2 = document.getElementById("team2");
  const member1 = document.getElementById("member1");
  const member2 = document.getElementById("member2");
  const result1 = document.getElementById("result1");
  const result2 = document.getElementById("result2");
  const result3 = document.getElementById("result3");
  const image1 = document.getElementById("image1");
  const image2 = document.getElementById("image2");
  const image3 = document.getElementById("image3");
  const rule1 = document.getElementById("rule1");
  const rule2 = document.getElementById("rule2");
  const rule3 = document.getElementById("rule3");
  const stage1 = document.getElementById("stage1");
  const stage2 = document.getElementById("stage2");
  const stage3 = document.getElementById("stage3");

  const dataRep = nodecg.Replicant("data", {persistent: false});

  const stageList = {"none":"? ? ?","0":"バッテラストリート","1":"フジツボスポーツクラブ","2":"ガンガゼ野外音楽堂","3":"チョウザメ造船","4":"海女美術大学","5":"コンブトラック","6":"マンタマリア号","7":"ホッケふ頭","8":"タチウオパーキング","9":"エンガワ河川敷","10":"モズク農園","11":"Bバスパーク","12":"デボン海洋博物館","13":"ザトウマーケット","14":"ハコフグ倉庫","15":"アロワナモール","16":"モンガラキャンプ場","17":"ショッツル鉱山","18":"アジフライスタジアム","19":"ホテルニューオートロ","20":"スメーシーワールド","21":"アンチョビットゲームズ","22":"ムツゴ楼","x":"DEAR SENPAI"};

  dataRep.on("change", newValue => {
    counter1.innerText = newValue.counter1;
    counter2.innerText = newValue.counter2;
    team1.innerText = newValue.team1;
    team2.innerText = newValue.team2;
    member1.innerText = newValue.member1;
    member2.innerText = newValue.member2;
    result1.innerText = newValue.result1;
    result2.innerText = newValue.result2;
    result3.innerText = newValue.result3;
    image1.src = "./image/stage/" + newValue.stage1 + ".png";
    image2.src = "./image/stage/" + newValue.stage2 + ".png";
    image3.src = "./image/stage/" + newValue.stage3 + ".png";
    rule1.innerText = newValue.rule1;
    rule2.innerText = newValue.rule2;
    rule3.innerText = newValue.rule3;
    stage1.innerText = stageList[newValue.stage1];
    stage2.innerText = stageList[newValue.stage2];
    stage3.innerText = stageList[newValue.stage3];
  });
</script>
</html>

みたいに作っていきます。
初期状態が映っても良いように適当な値を既に入れています。
また、persistent: falseにすると前回分のデータが残らなくなるみたいです。
画像とかはまあ適当に用意してください。
上でも述べましたが、綺麗なコードではないので動かし方だけ参考にしていただければ…

先程と同様にpackage.jsonに書き加えていきます。
ここには書きませんが3games.htmlと同じように5games.htmlも作りました。

package.json
{
  "name": "example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",

  "nodecg": {
    "compatibleRange": "^1.0.0",
    "dashboardPanels": [
      {
        "width": "3",
        "name": "panel",
        "title": "試合経過",
        "file": "panel.html"
      },
      {
        "width": "1",
        "name": "test",
        "title": "コメント",
        "file": "test.html"
      }
    ],
    "graphics": [
      {
        "file": "3games.html",
        "width": 1920,
        "height": 1080
      },
      {
        "file": "5games.html",
        "width": 1920,
        "height": 1080
      },
      {
        "file": "index.html",
        "width": 800,
        "height": 600
      }
    ]
  }
}

これでもう一度実行します。
すると
image.png
image.png
項目が増えました。

またURLをコピーしてOBSにブラウザソースとして追加するんですが、今回は背景も含めたいのでカスタムCSSの部分を空白にします。サイズも数値を変更しておいてください。
image.png

入力欄に入力して反映ボタンをクリックすれば画面内でも変わります。
image.png
同じように作った5games.htmlも同じ操作パネルで同時に変更できます。

感想とか

自分は作っていませんが、チーム名と勝数カウンターの部分を使って試合中に何対何なのかを表示できるようにしても面白そうです。あとは表示する部分にアニメーションとかも付けてみたいですね。上手くやれば書き換え自体も演出にできそう(?)。
間違っている部分とかあれば教えて下さい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超音波センサーで近点距離を計測し、老眼か判定するLINE Bot×Iotの作成

概要

以前、老眼とモスキート音による加齢性難聴をチェックするLINE Bot×Iotを作成しました。
老眼と加齢性難聴のチェックができるLINE Bot×Iotの作成

前回は近点距離(人差し指の指紋が一番鮮明に見える距離)は実際にメジャーで計測してLINEに数値を入力していましたが、今回は、超音波センサーで近点距離を自動計測しました。機能も老眼判定のみに絞ったBotをobnizと組み合わせて作成しました。

実装

LINE BOtとobnizの連携。近点(人差し指の指紋が一番鮮明に見える)でLINEに入力すると自分が老眼かどうかが分かるBOT。

概念図

node.js expressでLINE bot APIとobnizを連携しました。
rougann.jpg

作成方法

1.obnizの準備
obnizをPCと接続します。
obniz公式サイト
Node.jsのダウンロード

obnizを使えるようにWifiに接続し、画面上にQRコードと8桁の数字が表れるようにします。

超音波センサーを準備し、obnizのio0・io1・i02にさします。
image.png
今回は、超音波距離センサー HC-SR04 [101-60-142 / 20-019-100-A]を使用しました。

超音波センサーのドキュメント

2. Botアカウントを作成する

3. Node.jsでBot開発

4. ngrokでトンネリング

上記の2~4を以下の参考記事の通りに行います。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017

5.obnizとの連携

コードを以下のように書き換えます。

'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: '自分のchannelSecretを入力',
    channelAccessToken: '自分のchannelAccessTokenを入力'
};

// Obnizの準備
var Obniz = require("obniz");
var obniz = new Obniz("自分のObniz ID を入力");  // Obniz ID を入力
//超音波センサー
var hcsr04;//全体に使えるようにするスコープ
obniz.onconnect = async function () {
  hcsr04 = obniz.wired("HC-SR04", { gnd: 0, echo: 1, trigger: 2, vcc: 3 });


}

console.log("hcsr04 sample!");


const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
  console.log(req.body.events);

  //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
  if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
    res.send('Hello LINE BOT!(POST)');
    console.log('疎通確認用');
    return; 
  }

  Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});



const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  // LINE botのプログラム
// 聞いてくる間はHTTP APIで取得するように取得待ちが生じる
getDistance(event.source.userId);

return client.replyMessage(event.replyToken, {
  type: 'text',
  text: 'ちょっとまってね。いま距離調べてる。'
});
}

const getDistance = async (userId) => {

  // 処理が待てるmeasureWaitのほうを使う
  let res;
  const dis_mm = await hcsr04.measureWait();
  const dis_cm = dis_mm / 10;
  const distance = dis_cm.toFixed(); 

  if (distance>=80) {
    res =" 近点距離は" + distance + "cm です。ピント調整力は61歳以上相当です。老眼の可能性が高いです。老眼度数は3以上と思われます。";
  }else if (distance >= 60){
    res = "近点距離は" + distance + "cm です。ピント調整力は56~60歳相当です。老眼の可能性が高いです。老眼度数は2.5程度と思われます。。";
  }else if (distance >= 50){
    res = "近点距離は" + distance + "cm です。ピント調整力は51~55歳相当です。老眼の可能性が高いです。老眼度数は2程度と思われます。";
  }else if(distance >= 40){
    res = "近点距離は" + distance + "cm です。ピント調整力は46~50歳相当です。老眼の可能性が高いです。老眼度数は1.5程度と思われます。";
     }else if(distance >= 30){
    res = "近点距離は" + distance + "cm です。ピント調整力は41~45歳相当です。老眼の可能性が高いです。老眼度数は1程度と思われます。";
  } else if(distance<30) {
    res = "近点距離は" + distance + "cm です。ピント調整力は40歳未満相当です。老眼の可能性は低いです。";
  }else {
    res = "指先にピントが合い、人差し指の指紋がはっきりと見えたところでLINEに何かメッセージを送ってください。";
  }


  await client.pushMessage(userId, {
      type: 'text',
      text: res,
  });
}


app.listen(PORT);
console.log(`Server running at ${PORT}`);

動作確認

ちゃんと動いています。
image.png

考察

超音波センサーは対象が柔らかいと正確に距離を測定できないということだったので心配でしたが、メジャーで計測して比較すると誤差は数㎝ほどでした。スマートフォンのカメラで距離が取得できるとUIがずっと良くなるので何か方法はないか調べてみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超音波センサーで近点距離を計測し、老眼か判定するLINE Bot×IoTの作成

概要

以前、老眼とモスキート音による加齢性難聴をチェックするLINE Bot×Iotを作成しました。
老眼と加齢性難聴のチェックができるLINE Bot×Iotの作成

前回は近点距離(人差し指の指紋が一番鮮明に見える距離)は実際にメジャーで計測してLINEに数値を入力していましたが、今回は、超音波センサーで近点距離を自動計測しました。機能も老眼判定のみに絞ったBotをobnizと組み合わせて作成しました。

実装

LINE BOtとobnizの連携。近点(人差し指の指紋が一番鮮明に見える)でLINEに入力すると自分が老眼かどうかが分かるBOT。

概念図

node.js expressでLINE bot APIとobnizを連携しました。
rougann.jpg

作成方法

1.obnizの準備
obnizをPCと接続します。
obniz公式サイト
Node.jsのダウンロード

obnizを使えるようにWifiに接続し、画面上にQRコードと8桁の数字が表れるようにします。

超音波センサーを準備し、obnizのio0・io1・i02にさします。
image.png
今回は、超音波距離センサー HC-SR04 [101-60-142 / 20-019-100-A]を使用しました。

超音波センサーのドキュメント

2. Botアカウントを作成する

3. Node.jsでBot開発

4. ngrokでトンネリング

上記の2~4を以下の参考記事の通りに行います。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017

5.obnizとの連携

コードを以下のように書き換えます。

'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: '自分のchannelSecretを入力',
    channelAccessToken: '自分のchannelAccessTokenを入力'
};

// Obnizの準備
var Obniz = require("obniz");
var obniz = new Obniz("自分のObniz ID を入力");  // Obniz ID を入力
//超音波センサー
var hcsr04;//全体に使えるようにするスコープ
obniz.onconnect = async function () {
  hcsr04 = obniz.wired("HC-SR04", { gnd: 0, echo: 1, trigger: 2, vcc: 3 });


}

console.log("hcsr04 sample!");


const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
  console.log(req.body.events);

  //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
  if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
    res.send('Hello LINE BOT!(POST)');
    console.log('疎通確認用');
    return; 
  }

  Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});



const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  // LINE botのプログラム
// 聞いてくる間はHTTP APIで取得するように取得待ちが生じる
getDistance(event.source.userId);

return client.replyMessage(event.replyToken, {
  type: 'text',
  text: 'ちょっとまってね。いま距離調べてる。'
});
}

const getDistance = async (userId) => {

  // 処理が待てるmeasureWaitのほうを使う
  let res;
  const dis_mm = await hcsr04.measureWait();
  const dis_cm = dis_mm / 10;
  const distance = dis_cm.toFixed(); 

  if (distance>=80) {
    res =" 近点距離は" + distance + "cm です。ピント調整力は61歳以上相当です。老眼の可能性が高いです。老眼度数は3以上と思われます。";
  }else if (distance >= 60){
    res = "近点距離は" + distance + "cm です。ピント調整力は56~60歳相当です。老眼の可能性が高いです。老眼度数は2.5程度と思われます。。";
  }else if (distance >= 50){
    res = "近点距離は" + distance + "cm です。ピント調整力は51~55歳相当です。老眼の可能性が高いです。老眼度数は2程度と思われます。";
  }else if(distance >= 40){
    res = "近点距離は" + distance + "cm です。ピント調整力は46~50歳相当です。老眼の可能性が高いです。老眼度数は1.5程度と思われます。";
     }else if(distance >= 30){
    res = "近点距離は" + distance + "cm です。ピント調整力は41~45歳相当です。老眼の可能性が高いです。老眼度数は1程度と思われます。";
  } else if(distance<30) {
    res = "近点距離は" + distance + "cm です。ピント調整力は40歳未満相当です。老眼の可能性は低いです。";
  }else {
    res = "指先にピントが合い、人差し指の指紋がはっきりと見えたところでLINEに何かメッセージを送ってください。";
  }


  await client.pushMessage(userId, {
      type: 'text',
      text: res,
  });
}


app.listen(PORT);
console.log(`Server running at ${PORT}`);

動作確認

ちゃんと動いています。
image.png

考察

超音波センサーは対象が柔らかいと正確に距離を測定できないということだったので心配でしたが、メジャーで計測して比較すると誤差は数㎝ほどでした。スマートフォンのカメラで距離が取得できるとUIがずっと良くなるので何か方法はないか調べてみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

http モジュール(node.js)で、ローカル画像ファイルを使用する。

この記事は、httpモジュールで、ローカル画像ファイルを使う方法を書き込んだ
個人的なメモです。:point_up:

httpモジュール内で、画像を表示させる一番簡単な方法として、

画像アドレスを使用する

//html
<img src="http://○○○○○○">

//pug
img(src="http://○○○○○○")

が、あります。が、この方法は任意の画像をどこぞにアップロードして画像アドレスを
取得しなければなりません。
例えば私がN予備校の授業で作ったアンケートページでは、
グーグルドライブに画像をアップロードして、そこからアドレスを取得して使っています。:cloud:

尚、グーグルドライブの画像を使うにはちょっとクセがあります。

:writing_hand: グーグルドライブの画像を使う場合
グーグルドライブにアップロードした任意の画像→
右クリック→「共有可能なリンクを取得」で取得するURL ・・ではエラーになってしまいます。

//「共有可能なリンクを取得」でコピーできるURL
https://drive.google.com/open?id=○○○○○○○○○○

利用するには、このアドレスの「id=」の前を入れ替えます。

//表示用のアドレス
http://drive.google.com/uc?export=view&id=○○○○○○○○○○

↑表示用のアドレスでidを入れ替えればOK。

これでも十分なんですけどね。

httpモジュール内のWebページでローカル画像を読み込ませる。

本題です。やり方としては、上に書いた通り、画像URL(画像が表示されるアドレス)があれば
画像が使えるので、画像を表示させるアドレス(パス)を作ります。
使用してる画像ファイル(参考
buta.png buta.png
ファイル構成
index.js  (http.createServerしてるファイル)
views   (ページをレイアウトするpugファイルなどなどを入れたディレクトリ)
 ┣ pugファイル
 ┗ images  (画像ファイルを入れているディレクトリ)
   ┣ buta.png

const fs=require("fs"); // ← 超重要(忘れやすい)

  switch (req.url) {
    case '/index':
      ${indexにリクエストが来た時に動く関数}(req, res);
      break;
    case '/images/buta.pug':  // ← アドレスは任意。本当はuuidとか使うのがいいのかもしれませんが。
      res.writeHead(200, {
      'Content-Type': `image/png; charset=utf-8`  // ← ここがキモ!
      });
      var image = fs.readFileSync("./views/images/buta.png", "binary"); // ← ファイルpathはその環境に合わせてください
      res.end(image, "binary");
      break;
    default:
      break;
  }

switch文で画像アドレスを作り、そこにリクエストがきたらfsモジュールで画像を読み込んで表示させるというただそれだけです。
readFileはSyncにしないとエラーがでます。(私の環境だと)
※ jpg画像の場合は'Content-Type': `image/jpeg;です。(jpgではない)
これでローカル画像が使えます:v_tone1:
例)
<img src="/images/buta.pug">

まとめ
Expressフレームワークは偉大

:star2:参考にさせてもらったContent-Typeまとめページ:star2:
:point_right: https://qiita.com/AkihiroTakamura/items/b93fbe511465f52bffaa

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Intellij/WebStormでts-nodeデバッグ設定

デバッグ環境がわからなくなったのでメモ

Webアプリの起動ファイルはsrc/main.tsにしています。
node --inspect --require ts-node/register src/main.ts
nodemonを入れているなら

nodemon --inspect --require ts-node/register src/main.ts

これで

$ node --inspect --require ts-node/register src/main.ts
Debugger listening on ws://127.0.0.1:9229/977cea48-33da-4f31-86ad-9f7a465a4429

Server listening at port 3000

などと表示され、9229ポートでデバッガーが動くようです。

このあと、爆速で設定

Intellij/WebStormで

  1. 上部のパネルから'Edit Configuration'を選ぶ。

191024-0002.png

2.左にTemplatesの選択があるので、そこで"Attach to Node.js/Chrome"を選択

191024-0003.png

  1. このままの設定でOKなのでデバッグ設定に追加.

191024-0004.png

  • アプリを起動した状態でデバッガーボタン(虫の形ボタン)を押すと、Intellij上でブレークポイントが設定できデバッグできます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでWebSocketサーバをUnixドメインソケットで立てる

Node.jsでwsパッケージのWebSocketサーバをUnixドメインソケットでlistenする方法です。サンプルコードはTypeScriptです。

Unixドメインソケットを使ったWebSocketサーバを立てるコード

Serverクラスのコンストラクタのserverオプションにhttp.Serverのインスタンスを渡すのがミソです。

server.ts
import websocket from 'ws'
import http from 'http'
import fs from 'fs'

// UnixドメインソケットでWebSocketを待ち受けるHTTPサーバー
const hs: http.Server = http.createServer()

// WebSocketサーバー (いわゆるechoサーバーです)
const wss = new websocket.Server({server: hs}) // 最重要
wss.on('connection', ws => {
  ws.send('hello, this is websocket echo server.')
  ws.on('message', message => {
    ws.send(message) // オオム返し
  })
})

// Unixドメインソケットのファイル名
const socketPath = '/tmp/server.sock'

// UnixドメインソケットのファイルがあるとHTTPサーバが起動しないので必ず削除する
fs.existsSync(socketPath) && fs.unlinkSync(socketPath)

// HTTPサーバのlistenを開始する
hs.listen(socketPath, () => {
  console.log(`websocket server listening on ${socketPath}`)
})

WebSocketサーバーと話してみる

WebSocketサーバと話すには、wscatというWebSocket CLIクライアントが便利です。

Unixドメインソケットではない、普通のTCPのWebSocketと疎通するには、npx wscat -c ws://echo.websocket.orgのように、URLをws://で始めますが、Unixドメインソケットでは、URLスキーマの部分がws+unix://になります:

$ npx wscat -c ws+unix:///tmp/server.sock
Connected (press CTRL+C to quit)
< hello, this is websocket echo server.
> てすと
< てすと
>

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【未完】LINE Botにobnizを組み合わせて感情をもたせることに挑戦した

はじめに 

LINE Botでオウム返しやAPIと連携させていたりしたが、今回はLINE botとobnizを連携させることでbotの機械的な表現に感情があるように表現できないかを検証したく作成しました。
結論、タイトルにある通り完成はできていないので、今後も作成は継続していきます。

概要

システムと違い人間は同じ言葉を話していても、その時の感情は発言と反していることがあります。
例えば「好き」言われて本当はうれしいのに「ふーん」とそっけない返事をしてしまうようなこととかがあります。
このツンデレな感じをBotで表現できないか、というのが今回の作成コンセプトです。

仕様はLINE Botに「好き」以外の言葉だと「ふーん」とそっけない返事のみだけですが
「好き」という言葉を送ると、返事は「ふーん」なのですが
obnizのLEDが赤く発行します。

似ているものとして、嘘発見機をイメージしていただけるとわかりやすいかもしれません。

環境

Node.js v10.16.3
Windows 10 pro
Visual Studio Code v1.39.1

作り方・参照サイト

まず必要な素材ですが下記のURLを参照ください。

obniz LEDパーツ

また、実際のLINE Botとobnizの連携についてですが

老眼と加齢性難聴のチェックができるLINE Bot×Iotの作成

こちらの記事がわかりやすく参考になりました。
node.jsを使用してAPI連携をしております。

使用したコード

'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: '作成したBOTのチャンネルシークレット',
    channelAccessToken: '作成したBOTのチャンネルアクセストークン'
};

// Obnizの準備
var Obniz = require("obniz");
var obniz = new Obniz("");  // Obniz ID を入力
var leds; // 全体で使えるようにするスコープ
obniz.onconnect = async function () {
  var leds = obniz.wired("WS2811", {gnd:0, vcc: 1, din: 2});
}

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);

    //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
    if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
        res.send('Hello LINE BOT!(POST)');
        console.log('疎通確認用');
        return; 
    }

    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  let ans = "";
  let question = event.message.text;
  let color;//RGB値

  if (question == "好き") {
    ans = "ふーん";
    color = [255, 0, 0];
  } else {
    ans = "ふーん";

  }

  led.rgbs(color);

  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: ans //実際に返信の言葉を入れる箇所
  });
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);

結果

IMG_4820 (1).jpg
S__58744842 (2).jpg

...不発

全く振り向いてくれません(笑)
どうやらLINE自体が応答してくれないようです。webhookの接続は問題ないのでコードに問題がありそうです。
あとはobnizが常時発光している状態なのでこれもなんとかしないといけませんね。。

考察 次回に向けて

いろんな記事を参考にしていますがやはりコピペだけだと限界がきますね。node.jsなのでjavascriptのコードの理解が早急に必要だと感じました。
それでも、今ある材料の中から作品をどう表現していくのかというコンセプト、企画の点でとても楽しく作成できました。
プログラミングの知識と合わせて企画力も意識して制作していこうと意欲的になりました。

今回は未完の状態で終了しておりますが、引き続き継続して作品を完成させたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Node] 標準入力を扱うコードをテストする

ユーザからの入力を受け付けるCUIのようなコードのテストサンプル。

テスト対象コード

prompt.js
const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

module.exports = function prompt(question) {
  return new Promise((resolve, reject) => {
    rl.question(question, answer => {
      rl.close();
      resolve(answer);
    });
  })
};

使う側はこんな感じ。

main.js
const prompt = require('./prompt');

prompt('Enter something: ').then(answer => {
  console.log(answer);
});

実行結果

% node ./main.js
Enter something: abc
abc

テストコード

spec/prompt-spec.js
const prompt = require('../prompt');

describe('prompt function', () => {
  it('returns answer', done => {
    prompt('question: ').then(answer => {
      expect(answer).toEqual('abc');
      done();
    });
    process.stdin.emit('data', 'abc\n');
  });
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ts-node-devで起動されたかを調べる方法

プロセスがts-node-devで起動されたかを調べる方法です。

const isTsNodeDev = Object.keys(require.cache).some(path => /\/ts-node-dev\//.test(path))

ts-node-devで起動すると、nodeの-rオプションにts-node-devが渡されるため、ロード済みのモジュールに'ts-node-dev'の文字列があるかを調べれば良いわけです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINE BOTとlotで、いつでもレベルアップできるようになった!

はじめに

アニメ・ゲーム・漫画を愛するタレント、喜屋武ちあきです。

2019年7月から16年間いた事務所から独立し、フリーランスとして活動しはじめました。
色々新しいことに自由に挑戦できる環境の中、もともとオタクとして興味があったプログラミングにもチャレンジしています。

今月からnodeでjavasqriptを始めたばかりの全くの素人で、まだ学校で言われたことをそのままトレースしている段階ですが、少しずつコードを読めるようになりたいです。

obnizをゲット

「obniz」という、IOTの基盤があります。この基盤をインターネットとつなげると、それだけでnodeから色々な指示を送ることができて、すごく面白いんです!!!

LEDライトやスピーカー、超音波センサーなど色々差し込んで遊ぶ中で、私が一番気になったのは音の出る「スピーカー」でした。

正直、まだ、プログラミングは「辛い」

これ、動機の話なんですが。
本当にまだ何も分からない中で色々なことにチャレンジしていますが、意味不明のエラーは出るし、新品のLEDなのに光らないし(これはモノの問題かもですが)、なかなか心をへし折られることばかり。

でも、ちょっとずつ成長していることを、誰かに認めてもらいたいし、成長しているということをもっと感じたい!!!

そう思ったので、LINEbotとobnizをつなげて、頑張りを認めてレベルアップさせてくれるbotを作ることにしました。

私の目的とするbotは

「今日は、朝5時起きでお仕事に行って頑張ったよ!」
など、”頑張った”という文字に反応して
「よく頑張ったね!勇者きゃんちはレベルが1上がりました。」
と褒めて、さらに某国民的RPGのレベルアップ音を鳴らしてくれるbotです。

でっきるっかな、でっきるっかな♪

開発環境と用意したもの

環境
node.js v11.10.0
mac os 10.15
Visual Studio Code v1.39.1

用意
Obniz ボード
圧電スピーカー(圧電サウンダ)(13mm)PKM13EPYH4000-A0
USB2.0ケーブル(A-microBタイプ)50cm

じゃ、やってみようか

参考にした記事

「1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest」

「老眼と加齢性難聴のチェックができるLINE Bot×Iotの作成」

「超初心者がLineBot×Obnizでスピーカから音を出してみた。」

まず、一番上の記事を参考に、LINE BOTの設定を行います。
何回か同じことを繰り返しているうちに、LINE BOT自体作るのは慣れが出てスムーズに行くようになりました。個人的にはこれがすでに奇跡

続いて、2番目の記事を参考に、Aコードを埋め込み、スピーカーとLINEbotを繋ぎます。

シンプルに、「頑張った」というワードに文字と音を同時に送るということがしたかったので、3番目の記事からコードをお借りして、文字を変更。

さらに、一番時間をかけた、レベルアップ音の指定を入力します。

以下、functionからのコードです。
頑張ったレベルアップ音の指定を!!!見て!!!

function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  var hz = Number(event.message.text);
  speaker.play(1396); // ファ
  obniz.wait(130); 
  speaker.play(0); // 休符
  obniz.wait(10);
  speaker.play(1396);// ファ
  obniz.wait(130);
  speaker.play(0); // 休符
  obniz.wait(10);
  speaker.play(1396);// ファ
  obniz.wait(130);
  speaker.play(0); // 休符
  obniz.wait(10);
  speaker.play(1396); // ファ
  obniz.wait(130);
  speaker.play(0); // 休符
  obniz.wait(70);
  speaker.play(1244);// ミ
  obniz.wait(210);
  speaker.play(0); // 休符
  obniz.wait(50);
  speaker.play(1567);// ソ
  obniz.wait(200);
  speaker.play(50); // 休符
  obniz.wait(30);
  speaker.play(1396); // ファ
  obniz.wait(1000);
  speaker.stop();
  let replyText = '';
  if(event.message.text === '頑張った'){
    replyText = 'よく頑張ったね!勇者きゃんちはレベルが1上がりました。';
  }else{
    replyText = hz;
  }


  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: replyText
  });
}

音階はともかく、スタッカート機能なんてついているのかわからないので、音の長さと休符(音のならない0ヘルツ)の設定だけでレベルアップ音を作りました。

そして、早速Webhookを使ってトンネリング。
LINEbotに話しかけたところ・・・

S__64856132.jpg

不思議なことに、どんな言葉にも、レベルアップ音で返すようになりました。

そして、シンプルに"頑張った"と打つと、

「よく頑張ったね!勇者きゃんちはレベルが1上がりました。」
と褒めてくれるようになりました。

IMG_0498.jpg
画像、おっきいな・・・;

ま、何を打ってもレベルが上がる音がするなら、それはそれでいっか!!!と思います。
この後、「頑張った」というワードが言葉のどこかに入っていれば自動で返信できるようにと頑張ったのですがうまくいかず、何を送っても返信もなく、レベルアップ音が虚しく鳴り響くという形になってしまったので、元に戻しました。

レベルアップ音の動画をUPしました!こちらからどうぞ。

まとめ

電脳の世界のみで完結するというのがまだわかりづらく、こうしてIOTとして繋げてみると、工作感が出てきてわかりやすく楽しいと感じました。

スピーカーから発せられるレベルアップ音もゲームっぽくて可愛いーーと思いましたが、元祖1ってすでに単音じゃないので、30年も後なのに・・・!?!?と思うとエモいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

面倒なので`npm init`を実行しない

Node.jsのモジュールを使おうとWebで検索すると大抵のページでよく「npm initを実行する」と書いてある。

しかしいろいろ聞かれてめんどくさい。ただclaspとかtscとかを使いたいだけなのに…
※Enterキーを押し続ければいいだけなのは分かった上で。

ということでnpm initを実行しないでできないか試してみた。

TL;DR

echo {"private":true}> package.json

ただし、Windowsのコマンドプロンプト上で。Linux/MacOSなら

echo '{"private":true}' > package.json

作成したモジュールを公開するつもりがないなら
npm initを実行しなくてもこれだけで十分(多分)。

何もしない

まずはnpm initの実行も何もしないで、いきなりnpm installしてみる。

> npm install typescript 
npm WARN saveError ENOENT: no such file or directory, open '~\package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '~\package.json'
npm WARN ~ No description
npm WARN ~ No repository field.
npm WARN ~ No README data
npm WARN ~ No license field.

+ typescript@3.6.4
added 1 package from 1 contributor and audited 1 package in 0.619s
found 0 vulnerabilities

当然のことながらpackage.jsonがないのでエラーが出ている(警告扱いになっているけど)ものの
インストール自体は成功したようだ。

しかし、このままだとほかのモジュールをインストールしたときに、また同じ警告が出ることになる。

結論: ボツ

空のpackage.json

次に、空のpackage.jsonを作成してからnpm installしてみる。

> echo;> package.json
> npm install typescript
npm ERR! code EJSONPARSE
npm ERR! file ~\package.json
npm ERR! JSON.parse Failed to parse json
npm ERR! JSON.parse Unexpected end of JSON input while parsing near ''
npm ERR! JSON.parse Failed to parse package.json data.
npm ERR! JSON.parse package.json must be actual JSON, not just JavaScript.

npm ERR! A complete log of this run can be found in:
npm ERR!     ~-debug.log

空っぽだとJSONのシンタックス的におかしいのでエラーが出ている。
このためエラーという扱いでインストールにも失敗している。

結論: ボツ

空のオブジェクトのpackage.json

JSONのシンタックス的にOKな状態にすれば良いのかということで{}にしてみる。

> echo {}> package.json
> npm install typescript
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN ~ No description
npm WARN ~ No repository field.
npm WARN ~ No license field.

+ typescript@3.6.4
added 1 package from 1 contributor and audited 1 package in 0.662s
found 0 vulnerabilities

package.jsonがないときにも出ていた警告がいくつか出てる。
指定するべきフィールドが不足しているようだ。

npm package.json 取扱説明書より

  • description
    説明を文字列で記述します。
    npm search で表示されるので、人々があなたの作ったパッケージを見つけ、理解するのに役立ちます。
  • repositry
    ソースコードが管理されている場所を指定します。ソースコードに関与したい人々に とってそれは助けとなるでしょう。
    もし github 上の git リポジトリであれば npm docs コマンドでそのパッケージが発見可能になります。
  • license
    パッケージユーザーが使用許諾とあなたの指定している禁止事項を知る為にパッケージのライセンス情報を指定します。

やはり、このままだとほかのモジュールをインストールしたときに、また同じ警告が出ることになる。

結論: ボツ

"private": trueを指定する

警告が出ているフィールドはどれもパッケージを公開するつもりがなければ必要がない。
ということでprivatetrueを指定してみる。

npm package.json 取扱説明書より

  • private
    "private": true を package.json に設定しておくと、publish コマンドを拒否します。

…あまり警告が出なくなることについての説明になってなかった。

descriptionほかはpublishコマンドによってパッケージを公開する際に必要になるフィールドなので
その他のコマンドでもこれらのフィールドについてのチェックがスキップされるらしい。

> echo {"private":true}> package.json
> npm install typescript
npm notice created a lockfile as package-lock.json. You should commit this file.
+ typescript@3.6.4
added 1 package from 1 contributor and audited 1 package in 0.642s
found 0 vulnerabilities

警告が出なくなった。
モジュールをnpmで公開するつもりがなければ、これで十分なんじゃないだろうか?

というわけでこれを採用!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Node.js(Express4)】POSTデータの受け取り

はじめに

Express4.16以降からPOSTデータの受け取りにbody-parserをわざわざ使わなくてもよくなったらしい。
本記事では、フォームからのPOSTデータの送信を想定している。

body-parser使う版

command
$ npm install body-parser --save
index.js
var express = require('express');
var bodyParser = require('body-parser');
var app = express();

app.use(bodyParser.urlencoded({ extended: false }));

app.post('/', function (req, res) {} =>{
  res.json({
    msg: 'Hello World!',
    data: req.body
  });
});

Express4.16以降のやつ

index.js
var express = require('express');
var app = express();

app.use(express.urlencoded({extended: true}));

app.post('/', function (req, res) {
  res.json({
    msg: 'Hello',
    data: req.body
  })
})

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む