20200729のNode.jsに関する記事は19件です。

【初学者向け】package.jsonについて調べてみた

業務でnpmライブラリのアップデートを行うにあたって、package.jsonについてちゃんと調べてみたので忘備録としてまとめました。

まずnpmとは

意味としては大きく2種類あると思います。
①npm.Inc社のサービス
https://www.npmjs.com/
npm.Incという会社が提供している上記のサービスことをnpmといいます。今年3月にGithubに買収された会社ですね。このサービス内に、Javascriptで書かれたライブラリが豊富に存在しています。ここに存在するライブラリを、次に説明するnpmコマンドを使って操作している訳です。

②パッケージを操作するためのCLI
Node.jsのライブラリを管理するためのコマンドで、ライブラリを生成したり、npm上のライブラリをローカルにインストールしたりするツールです。

ライブラリとは

ライブラリを使えば、自分で一からコードを書かなくても簡単に機能を実現することができます。

外部ライブラリは自分のプロジェクトに含めるのではなく、「このプロジェクトはnpmのこのライブラリを使っている」という情報だけを「宣言」します。これを宣言しているのがpackage.jsonです。

例えばRailsでは、昔はvendorというディレクトリの配下にに外部パッケージをコピペして使用していましたが、それだとバージョンを管理するのが面倒になるため、package.jsonで宣言する方法が主流になってきているそうです。

ライブラリをインストールする

npmでライブラリをインストールすると、それらはルートディレクトリ直下のnode_modulesディレクトリに仮置場としてダウンロードされます。package.jsonがあるディレクトリで以下のコマンドを実行すると生成されるのが確認できると思います。

npm install

このファイルは基本的に.gitignoreで除外されるため、編集する機会はほぼ無いと思います。

package-lock.jsonとは

package.jsonに記載された、それぞれライブラリの依存先が宣言されています。
インストールされるべき厳密なバージョン番号、モジュールの場所、必要とされるライブラリのリストなどが書かれており、かなり膨大な行になりがちです。

ライブラリをインストールする際は、package-lock.jsonに書き込まれたバージョンのパッケージがインストールされます。

わかりやすい例があったので紹介します。

- package.jsonは買うものリスト
100円のりんごを買いたいと書かれている

- package-lock.jsonは実際に買ってきた領収書
実際には120円のりんごを買いましたと書かれている

devDependencesとdevendencesについて

devDependences

開発に必要なライブラリのみを記述します。
開発者がこのpackage.jsonがあるディレクトリでnpm installを行うと、dependenciesに書かれているライブラリのみではなく、devDependenciesに書かれているライブラリもインストールされます。

dependences

ライブラリを使いたい人がnpm installしたときにはdependenciesに書かれているライブラリのみがインストールされます。

バージョン表記について

バージョン表記は以下のようになっています。左から順に、メジャーバージョン、マイナーバージョン、パッチバージョンと呼びます。

0.1.2
名前 内容
メジャー 仕様そのものの変更。upgradeする場合はアプリケーションの改修が必要になる可能性あり。
マイナー 仕様を維持した状態での機能追加。upgradeしてもアプリケーションは壊れない。
パッチ バグ修正。upgradeしてもアプリケーションは壊れない。

まとめ

  • npmはオンライン上のライブラリレジストリと、ライブラリを操作するためのCLIの2つを指す。
  • package.jsonとはライブラリをアプリで使用するときに、その依存関係を管理するファイル package-lock.jsonで使用するバージョンを固定できる。
  • バージョンは3種類あり、左から順にメジャー、マイナー、パッチという。

今回はじめてpackage.jsonのライブラリのバージョンアップをした新米なので、もし間違っている箇所等あればコメントよろしくお願いします!

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

ラジコンをリッチメニューから遠隔操作する。自動ブレーキ付き

やったこと

前回作成した【obniz×LINE Messaging API】音と光を使い侵入防止システムを製作するの時に使用したタッパを乗せて走らせてみました。

できたもの

LINEのリッチメニューから操作でき、10㎝以内に障害物があると自動で停止します。

構成

全体像

今回製作したシステムの全体像です。
Image from Gyazo

配線図

obnizの配線図です。
Untitled Sketch2_ブレッドボード.png

使用部品

環境

  • node.js v14.5.0
  • @line/bot-sdk 7.0.0
  • express 4.17.1
  • obniz 3.7.0

コード

index.js
'use strict';

// obniz呼び出し
const Obniz = require('obniz');
const obniz = new Obniz('××××-××××'); // Obniz_IDに自分のIDを入れます
const line = require("@line/bot-sdk");
const express = require("express");
const PORT = process.env.PORT || 3030;

const config = {
  channelSecret: '{チャネルシークレット}',
  channelAccessToken: '{アクセストークン}'
};
const userId = '{ユーザーid}';


const app = express();
obniz.onconnect = async function () {
  obniz.display.clear();
  obniz.display.print('obniz meets LINE Bot!');
// モーターを呼び出す
  const motorRight = obniz.wired("DCMotor", { forward: 1, back: 0 });
  const motorLeft = obniz.wired("DCMotor", { forward: 8, back: 11 });
    // 距離センサーを呼び出す
  const GP2Y0 = obniz.wired("GP2Y0A21YK0F", {vcc:4, gnd:5, signal:6 });
  setInterval(async function () {
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

function forward_command() {
    motorLeft.forward();
    motorRight.forward();
  }
  function stop_command() {
    motorLeft.stop();
    motorRight.stop();
  }
  function reverse_command() {
    motorLeft.reverse();
    motorRight.reverse();
  }
  function turning() {
    motorLeft.forward();
    motorRight.reverse();
  }

  const distance = await GP2Y0.getWait();
  console.log(distance + ' mm');

  function handleEvent(event) {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return Promise.resolve(null);
    }
    switch(event.message.text) {
      case '前進':
        client.replyMessage(event.replyToken, {
          type: 'text',
          text: '前進します'
        });
        forward_command();
      break;

      case '停止':
        client.replyMessage(event.replyToken, {
          type: 'text',
          text: '停止しました'
        });
        stop_command();
        break;

        case '後退':
          client.replyMessage(event.replyToken, {
            type: 'text',
            text: '後退します'
          });
          reverse_command();
          break;

        case 'ダンス':
          client.replyMessage(event.replyToken, {
            type: 'text',
            text: '踊ります'
          });
          turning();
          break;
      }


  }


  if(distance < 100){
    // setTimeout(function () {
    //   client.pushMessage(userId, {
    //   type: 'text',
    //   text: '障害物を検知しました',
    //   });
    // },1000);
    stop_command();
  }
}, 1000);
}
app.listen(PORT);
console.log(`Server running at ${PORT}`);

参考にした記事

LINEmessagingAPIの基礎部分で参考にしました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest

終わりに

公式サイトのラジコン類はスマートに動いていて次回の目標となります、できるかどうかはわかりませんが、次に作るときはカメラで画像認識を使用して走行させてみたいと思います。今回購入したキットは、格安品でまっすぐ進むよう調整するのが大変でした。

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

obniz×LINE Messaging API】ラジコンをリッチメニューから遠隔操作する。自動ブレーキ付き

やったこと

前回作成した【obniz×LINE Messaging API】音と光を使い侵入防止システムを製作するの時に使用したタッパを乗せて走らせてみました。

できたもの

LINEのリッチメニューから操作でき、10㎝以内に障害物があると自動で停止します。

構成

全体像

今回製作したシステムの全体像です。
Image from Gyazo

配線図

obnizの配線図です。
Untitled Sketch2_ブレッドボード.png

使用部品

環境

  • node.js v14.5.0
  • @line/bot-sdk 7.0.0
  • express 4.17.1
  • obniz 3.7.0

コード

index.js
'use strict';

// obniz呼び出し
const Obniz = require('obniz');
const obniz = new Obniz('××××-××××'); // Obniz_IDに自分のIDを入れます
const line = require("@line/bot-sdk");
const express = require("express");
const PORT = process.env.PORT || 3030;

const config = {
  channelSecret: '{チャネルシークレット}',
  channelAccessToken: '{アクセストークン}'
};
const userId = '{ユーザーid}';


const app = express();
obniz.onconnect = async function () {
  obniz.display.clear();
  obniz.display.print('obniz meets LINE Bot!');
// モーターを呼び出す
  const motorRight = obniz.wired("DCMotor", { forward: 1, back: 0 });
  const motorLeft = obniz.wired("DCMotor", { forward: 8, back: 11 });
    // 距離センサーを呼び出す
  const GP2Y0 = obniz.wired("GP2Y0A21YK0F", {vcc:4, gnd:5, signal:6 });
  setInterval(async function () {
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

function forward_command() {
    motorLeft.forward();
    motorRight.forward();
  }
  function stop_command() {
    motorLeft.stop();
    motorRight.stop();
  }
  function reverse_command() {
    motorLeft.reverse();
    motorRight.reverse();
  }
  function turning() {
    motorLeft.forward();
    motorRight.reverse();
  }

  const distance = await GP2Y0.getWait();
  console.log(distance + ' mm');

  function handleEvent(event) {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return Promise.resolve(null);
    }
    switch(event.message.text) {
      case '前進':
        client.replyMessage(event.replyToken, {
          type: 'text',
          text: '前進します'
        });
        forward_command();
      break;

      case '停止':
        client.replyMessage(event.replyToken, {
          type: 'text',
          text: '停止しました'
        });
        stop_command();
        break;

        case '後退':
          client.replyMessage(event.replyToken, {
            type: 'text',
            text: '後退します'
          });
          reverse_command();
          break;

        case 'ダンス':
          client.replyMessage(event.replyToken, {
            type: 'text',
            text: '踊ります'
          });
          turning();
          break;
      }


  }


  if(distance < 100){
    // setTimeout(function () {
    //   client.pushMessage(userId, {
    //   type: 'text',
    //   text: '障害物を検知しました',
    //   });
    // },1000);
    stop_command();
  }
}, 1000);
}
app.listen(PORT);
console.log(`Server running at ${PORT}`);

参考にした記事

LINEmessagingAPIの基礎部分で参考にしました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest

終わりに

公式サイトのラジコン類はスマートに動いていて次回の目標となります、できるかどうかはわかりませんが、次に作るときはカメラで画像認識を使用して走行させてみたいと思います。今回購入したキットは、格安品でまっすぐ進むよう調整するのが大変でした。

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

【obniz×LINE Messaging API】ラジコンをリッチメニューから遠隔操作する。自動ブレーキ付き

やったこと

前回作成した【obniz×LINE Messaging API】音と光を使い侵入防止システムを製作するの時に使用したタッパを乗せて走らせてみました。

できたもの

LINEのリッチメニューから操作でき、10㎝以内に障害物があると自動で停止します。

構成

全体像

今回製作したシステムの全体像です。
Image from Gyazo

配線図

obnizの配線図です。
Untitled Sketch2_ブレッドボード.png

使用部品

環境

  • node.js v14.5.0
  • @line/bot-sdk 7.0.0
  • express 4.17.1
  • obniz 3.7.0

コード

index.js
'use strict';

// obniz呼び出し
const Obniz = require('obniz');
const obniz = new Obniz('××××-××××'); // Obniz_IDに自分のIDを入れます
const line = require("@line/bot-sdk");
const express = require("express");
const PORT = process.env.PORT || 3030;

const config = {
  channelSecret: '{チャネルシークレット}',
  channelAccessToken: '{アクセストークン}'
};
const userId = '{ユーザーid}';


const app = express();
obniz.onconnect = async function () {
  obniz.display.clear();
  obniz.display.print('obniz meets LINE Bot!');
// モーターを呼び出す
  const motorRight = obniz.wired("DCMotor", { forward: 1, back: 0 });
  const motorLeft = obniz.wired("DCMotor", { forward: 8, back: 11 });
    // 距離センサーを呼び出す
  const GP2Y0 = obniz.wired("GP2Y0A21YK0F", {vcc:4, gnd:5, signal:6 });
  setInterval(async function () {
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

function forward_command() {
    motorLeft.forward();
    motorRight.forward();
  }
  function stop_command() {
    motorLeft.stop();
    motorRight.stop();
  }
  function reverse_command() {
    motorLeft.reverse();
    motorRight.reverse();
  }
  function turning() {
    motorLeft.forward();
    motorRight.reverse();
  }

  const distance = await GP2Y0.getWait();
  console.log(distance + ' mm');

  function handleEvent(event) {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return Promise.resolve(null);
    }
    switch(event.message.text) {
      case '前進':
        client.replyMessage(event.replyToken, {
          type: 'text',
          text: '前進します'
        });
        forward_command();
      break;

      case '停止':
        client.replyMessage(event.replyToken, {
          type: 'text',
          text: '停止しました'
        });
        stop_command();
        break;

        case '後退':
          client.replyMessage(event.replyToken, {
            type: 'text',
            text: '後退します'
          });
          reverse_command();
          break;

        case 'ダンス':
          client.replyMessage(event.replyToken, {
            type: 'text',
            text: '踊ります'
          });
          turning();
          break;
      }


  }


  if(distance < 100){
    // setTimeout(function () {
    //   client.pushMessage(userId, {
    //   type: 'text',
    //   text: '障害物を検知しました',
    //   });
    // },1000);
    stop_command();
  }
}, 1000);
}
app.listen(PORT);
console.log(`Server running at ${PORT}`);

参考にした記事

LINEmessagingAPIの基礎部分で参考にしました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest

終わりに

公式サイトのラジコン類はスマートに動いていて次回の目標となります、できるかどうかはわかりませんが、次に作るときはカメラで画像認識を使用して走行させてみたいと思います。今回購入したキットは、格安品でまっすぐ進むよう調整するのが大変でした。

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

【obniz】IoTでSpotifyライフを充実させよう【node.js】

はじめに

あなたの人生を変えたWebサービスを教えてください
と問われたらこの三つを答えます。
メルカリ
Twitter

そしてSpotifyです。

Spotifyとは

Spotify_Logo_RGB_Green.png

スウェーデン発のサブスクリプション型音楽ストリーミングサービスです。
有料会員数1億800万人、配信曲5000万曲以上、無料〜1480円のプランが用意されていています。

ランニングやジムなど運動中のBGMにしたり、近所の肉のハナマサ(髭男率高め)
でかかっていた印象的な曲を調べたり、レコメンドででた曲が良かったのでライブに足を運んだり、学生時代にCDで聴いていた曲を見つけて胸が熱くなったり、
私の音楽生活を一つ上のステージに連れて行ってくれた素敵なサービスです。

Spotify Connect

曲数やプランそれぞれのメリットはAppleMusicやLINE MUSICなど様々あるとおもいますがSpotifyで特徴的なのがSpotify Connectです。
これは例えばスマートフォンからPCなど他端末のSpotifyを操作することできます。

【Spotify Connectのイメージ】
spotify_connect.jpg
【iPadからPCのSpotifyを操作する】


デバイスをリモコン化するイメージだとお考えください。

obnizとは

ダウンロード.jpeg
obnizとは
マイコンボードの一種でJavaScriptで動かすことができます。
LEDライトやスピーカー、温度センサーといったパーツと組み合わせて簡単にIoTデバイスを
作ることができます。

構成図

Untitled Diagram.jpg
obnizのスイッチングのイベントを受けてnode.jsからSpotifyのAPIを叩いてプレーヤーを操作をします。
Requestで曲の再生/停止、巻き戻し/早送り、Responseで曲の情報を取得してobnizの画面に
曲の情報を表示させます。

機能

まずこのリモコンでは曲の選択機能はないのであらかじめスマホやPCのプレーヤーで曲を選んでおいてください。
プレイリストを作成しておくのがいいですね。

再生/停止

再開

巻き戻し/早送り

曲情報の切り替え

今日も生きたね

楽観の深奥で燻る魔(ry

実装

https://gist.github.com/UhRhythm/5e208cce89f3e845ba942f5f081fc7f9
ものすごく単純です。
obnizのスイッチのイベントに合わせて各々のAPIを呼び出すだけです。
各動作に割り当てられたAPIは下記のようになっております。

obniz 動作 httpメソッド エンドポイントURL
push 再生 PUT https://api.spotify.com/v1/me/player/play
push 停止 PUT https://api.spotify.com/v1/me/player/pause
right 早送り POST https://api.spotify.com/v1/me/player/next
left 巻き戻し POST https://api.spotify.com/v1/me/player/previous
none 再生中の曲を取得 GET https://api.spotify.com/v1/me/player/currently-playing

【参考】Spotify for Developers

【コード】

spobniz.js
'use strict'
require('dotenv').config();
const request = require('request');
const sync_request = require('sync-request');
// obniz認証情報
const obniz_id = process.env.OBNIZ_ID;
const Obniz = require('obniz');
const obniz = new Obniz(obniz_id);
// spotify認証情報(.envから読み込む)
const access_token = process.env.ACCESS_TOKEN;
const client_id = process.env.CLIENT_ID; // Your client id
const client_secret = process.env.CLIENT_SECRET; // Your secret
const redirect_uri = process.env.REDIRECT_URI; // Your redirect urico
const { createCanvas } = require('canvas');
// ヘッダー情報
var headers = {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${access_token}`
};

obniz.onconnect = async function () {
    // RGB LEDを呼び出す
    var rgbled = obniz.wired('WS2811', { gnd: 0, vcc: 1, din: 2 });
    // ディスプレイ処理
    obniz.display.clear();  // 一旦クリアする
    obniz.display.print('Hello obniz-spotify!');
    var state = await obniz.switch.getWait();
    // スイッチの反応を常時監視
    obniz.switch.onchange = function (state) {
        var result = getTrackInfo();
        if (state === 'push') {
            var is_playing = result['is_playing'];
            // 再生
            if (!is_playing) {
                console.log('play');
                playTrack();
                // 停止
            } else {
                console.log('stop');
                pauseTrack();
            }
            // ディスプレイ処理
            obniz.display.clear();
            const r = random(0, 255);
            const g = random(0, 255);
            const b = random(0, 255);
            console.log(r, g, b);
            rgbled.rgb(r, g, b);
        } else if (state == 'none') {
            var info = getDispInfo(result);
            obniz.display.clear();
        } else if (state === 'right') {
            // 右にスイッチを倒したとき
            console.log('skip');
            rgbled.rgb(0, 255, 0);
            skipTrack();
            // ディスプレイ処理
            obniz.display.clear();  // 一旦クリアする
            obniz.display.print('skip');  // pressed right という文字を出す       
        } else if (state === 'left') {
            // 左にスイッチを倒したとき(やさしく)
            console.log('back');
            rgbled.rgb(255, 0, 255);
            backTrack();
            // ディスプレイ処理
            obniz.display.clear();  // 一旦クリアする
            obniz.display.print('back');
        }
    }
    setInterval(async function () {
        var result = getTrackInfo();
        if (state == 'none' && result['is_playing']) {
            rgbled.rgb(random(0, 255), random(0, 255), random(0, 255));
        } else {
            rgbled.rgb(0, 0, 0);
        }
    }, 1200);
    setInterval(async function () {
        if (state == 'none') {
            var result = getTrackInfo();
            var info = getDispInfo(result);
            console.log(info);
            const canvas = createCanvas(128, 64);
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = "white";
            ctx.font = "15px Avenir";
            ctx.fillText(info, 0, 40);
            obniz.display.draw(ctx);
        }
    }, 3000);
}
// RGBをランダムで
var random = function (min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
}
// 再生
function playTrack() {
    console.log("play func");
    var options = {
        url: 'https://api.spotify.com/v1/me/player/play',
        method: 'PUT',
        headers: headers,
    };

    function callback(error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log("success");
            return body;
        }
    }
    request(options, callback);
}

// 巻き戻し
function backTrack() {
    console.log("back func");
    var options = {
        url: 'https://api.spotify.com/v1/me/player/previous',
        method: 'POST',
        headers: headers
    };

    function callback(error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log(body);
        }
    }
    request(options, callback);
}

// 早送り
function skipTrack() {
    console.log("skip func");
    var options = {
        url: 'https://api.spotify.com/v1/me/player/next',
        method: 'POST',
        headers: headers
    };

    function callback(error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log(body);
        }
    }
    request(options, callback);
}

//  再生中の曲
function getTrackInfo() {
    var response = sync_request(
        'GET',
        'https://api.spotify.com/v1/me/player/currently-playing', {
        headers: headers,
    });
    return JSON.parse(response.getBody('utf8'));
}

// ディスプレイ表示用の曲情報
function getDispInfo(data) {
    var title = data["item"]["name"];
    var name = data["item"]["artists"]["0"]["name"];
    return title + "\n" + name;
}

// 停止
function pauseTrack() {
    console.log("stop func");
    var options = {
        url: 'https://api.spotify.com/v1/me/player/pause',
        method: 'PUT',
        headers: headers
    };

    function callback(error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log(body);
        }
    }
    request(options, callback);
}

非同期処理の理解が甘いのと雑なコーディングなのは自覚しているので、これからリファクタリングしていく所存であります。
工夫したところはsetIntervalのところでライトがランダムに光るところです。
イケていないところは再生中にずっとライトが点滅していて結構目障りなところです。
暗闇で眺めている分にはすごく良いのですが。
さらイケてないところはsetIntervalのところで曲情報を取得するリクエストを三秒に一回
投げているところです。
もっと適した方法はあったはずです・・・。

感想

APIドキュメントをじっくり読んだことがあまりなかったので、実現したい機能を実装するために
必要なパラメータだったり書き方だったりを英語と格闘しながら調べました。
おかげでAPIドキュメントの勘所を少しは抑えれるようになりました。
日本語表示も対応できたし、なにより大好きなサービスを使って物づくりができるというのは
ここまで楽しいことなのかと思いました。

前回の課題では本来の理想であったnode.jsでの実装をほぼほぼGASでしてしまった後悔があったのですが
今回はnode.jsでゴリゴリ書けたしobnizの関数も上手く盛り込むことができ、バランスよく作れたため達成感があります。

人参は生産できないけど八百屋さんで購入して美味しいカレーがつくれるように、世の中に提供されている便利なAPIを使用してちょっとだけSpotifyライフの質を向上させることができました。
クックパッドを見て料理を作るようにドキュメントを読んで楽しく課題ができたと思います。

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

TypeScriptを始めよう ~ すぐにできる実行環境構築 ~

TypeScriptとは?

TypeScriptとは、マイクロソフトによって開発された、JavaScripで型定義を可能にできるように拡張した言語(AltJS)です。

AltJS、つまりJavaScriptの代替言語であり、コンパイル時にJavaScriptに変換されます。そして、互換性を持つスーパーセットであるため、JavaScriptと同様の記法が使えます。そのためJavaScriptの知識があればTypeScriptの学習コストはそこまで高くなく、スムーズに理解することができるでしょう。

また、TypeScriptは漸進的型付き言語(gradually typed language)であるので、プログラム内の全ての型を指定する必要はありません。これは既存のJavaScriptコードをTypeScriptに以降する際に非常に役に立ちますが、基本的には全ての型の指定をすることをおすすめします。

開発環境を整えよう

TypeScriptを試すために、まず開発環境を最低限整えましょう。

プラウザで手軽に試したい人は、こちらを使ってみてください。[https://www.typescriptlang.org/play]

VSCodeのインストール

https://code.visualstudio.com/

上記のURLから、それぞれのOSに合わせてDownloadしてください。VSCodeはTypeSciptの拡張機能が豊富なので、エディタに何を使うか迷っていたらおすすめです。

もちろんお好きなエディタを使っていただいて構いません。

Node.jsのインスール

Node.jsをインストールしていなければインストールしましょう。

Macの場合

# Nodebrewのインストール
$ brew install nodebrew
# インストールできたか確認
$ nodebrew -v
# LTSの最新版(https://nodejs.org/ja/)をインストール
$ nodebrew install-binary v12.18.3
# バージョンを指定
$ nodebrew use v12.18.3

# pathを通す(bashの場合)
$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile
$ source ~/.bash_profile
# Node.jsのバージョン確認
$ node -v

Windowsの場合

この記事を参考にしてください。

https://qiita.com/maecho/items/ae71da38c88418b806ff

TypeScriptのインストール

フォルダを作って、その中でnpmプロジェクトを初期化しましょう。その後npmを使ってTSCとTSLintをインストールします。

以下のコマンドを順に実行してください。

# 好きな場所でフォルダを作ってください
$ mkdir typescript-start
$ cd typescript-start
# npmプロジェクトを初期化(全てEnterで大丈夫)
$ npm init

# TSC, TSLint, Node.js用の型宣言をインストール
$ npm install --save-dev typescript tslint @types/node

tsconfig.jsonの作成

ここまで順調にできたら、typescript-start ディレクトリ直下にtsconfig.json というファイルを作りましょう。

このファイルはどのファイルをコンパイルするかや、どのモジュールシステムにコンパイルするか、コンパイル結果をどのディレクトリに格納するか、などを定義するものになります。

それでは実際に作ってみましょう。以下のコマンドを実行しtsconfig.jsonファイルを生成し、VSCodeで開きましょう。

$ touch tsconfig.json

開けたら、以下の内容をかきこんでください。

tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
        "target": "es2015",
    "outDir": "dist",
    "sourceMap": true,
        "strict": true
  },
    "include": [
        "src"
    ],
  "exclude": [
    "node_modules"
  ]
}

今回用いるtsconfig.jsonのオプションについて、以下に簡単に説明します。

オプション 説明
compilerOptions コンパイルする際のオプションで、この配下にオプションを基本的に書いていきます
module 出力するjsのモジュールで、何を使用するか指定(ex. CommonJS)
target jsのバージョンを指定(ex. ex5)
outDir jsファイルを出力するディレクトリ
sourceMap trueにすると対応するソースマップファイルが生成されるようになる。ソースマップファイルはTypeScriptコードと生成されたJavaScriptコードの対応関係が記述されている
strict 厳密な型チェックオプションを有効にする
include コンパイルに含めるファイルと一致するパターンリストを記述
exclude コンパイルから除外するファイルパターンリストを記述

tslint.jsonの作成

tsconfig.jsonファイルが作れたと思うので、次はTSLintの設定を行うことのできるtslint.json を加えましょう。

TSLintの利用は必須ではありませんが、コードスタイルの統一のために導入しておくことをおすすめします。

導入するには、以下のコマンドを実行します。

$ ./node_modules/.bin/tslint --init

無事実行できたらtslint.json ファイルが生成されているはずです。

細かいルールなど公式を参考にしてみてください。今回はデフォルトのまま利用します。

tslint: https://palantir.github.io/tslint/usage/configuration/

TypeScriptの実装!

では、TypeScriptの実装に入りましょう。srcフォルダを作成し、中にindex.tsファイルを作ります。TypeScriptファイルは拡張子が.tsになります。

$ mkdir src
$ touch src/index.ts

index.tsファイルは以下のように記述します

index.ts
const firstStep: string = "Let's start TypeScript!"
console.log(firstStep)

次にこのコードをコンパイルして実行しましょう。

# TSCでコンパイル
$ ./node_modules/.bin/tsc
# Node.jsで実行
$ node ./dist/index.js
# Let's start TypeScript!

無事「Let's start TypeScript!」という文字列が表示されたでしょうか?

また、tsconfig.jsonで設定したように、TSCでsrc以下のファイルがコンパイルされ、dist直下にjsファイルが生成されていることもわかると思います。

今回はここで終わりです。

こちらの記事でも同じ内容を記述しております。
またこちらでTypeScriptなどの情報を発信していく予定です。よろしければご覧ください。

参考 : https://www.typescriptlang.org/

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

【初心者でもできる】仕事が忙しくても、赤ちゃんの熱中症の危機にすぐに気づけるIoTをつくった

はじめに

少し前に、2才のこどもを保育園に送り忘れ、
かつ、車の中に放置してしまい、亡くなってしまう
痛ましい事件が起きました。

本当に悲しい出来事であり、あってはならないことですが、
人ごとではないような気がしました。

何かに集中してしまって、
つい何かやるべきことが抜け落ちてしまうことはある人も多いのではないでしょうか。

今回は、それと類似のシーンとして、
「仕事が忙しく、赤ちゃんの部屋が高温になっていることに気づけなかった」ということがないように、高温になればアラートが出て、気づける仕組みをIoTを使って、できないかとチャレンジしました。

作りたいイメージ

温度センサーによって、常に部屋の温度を監視し、
赤ちゃんにとって快適な温度(26-28度らしい)から外れた時に、
自動でLINEに通知がくるもの。

さらに、LINEの通知に気づかない可能性があるので、
音やLEDの光でアラートを出す。

その2つの仕組みがあれば、仕事に集中していても気づくことができ、
すぐにエアコンをつけにいくことができるはずです。

実際につくった仕組み

自動でLINEに通知する仕組みは作れなかったので、
外出中の夫or妻がLINE botで温度を確認し、
快適温度から外れた時に、
自宅にいる夫or妻にLEDで通知をする仕組みを作りました。

<STEP1>外出先からLINE botで赤ちゃん部屋の温度を確認する

まずは、LINE botに「どう?」と問いかけます。

3221879_s.jpg

<STEP2>部屋の温度に応じて、LEDの反応を変える

①もし28度を超えていた場合は?

■LINEの通知

8913.jpg

■LEDの反応

②もし26度より下回っていた場合は?

■LINEの通知
※検証した日はなかなか26度を下回らなかったため、
コードの方の数字をいじって、強引に表示させてしまいました。

8914.jpg

■LEDの反応

③もし26度〜28度の間(快適温度)だった場合は?

■LINEの通知

8915.jpg

■LEDの反応

LEDが消えます。

IMG_7587.JPG

<STEP3>エアコンをつけるor消す。

IMG_1921.JPG

LEDで通知を受けたら、家にいる方が
エアコンのスイッチを押せば、
快適な温度が保たれます。

これで一安心です。

・・・・と思ったのですが、
ここまで考えてみて、1つのobnizを使うと、
温度センサーとLEDの位置は、同じ場所でしかできないので、
赤ちゃん部屋の温度を、別部屋で仕事をする人に
LEDで通知はできなかったです。。

なので、これでできることは、
・外出先からでも赤ちゃん部屋の温度がわかる。
・外出先から家にいる夫or妻にLINEで普通に通知する。
ですね。

また発展版を考えてみようと思います。

コード

'use strict';

// obniz呼び出し
const Obniz = require('obniz');
const obniz = new Obniz('Obniz_ID');  // Obniz_ID を入力

// LINE botを使えるようにする
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');
const PORT = process.env.PORT || 3000;

const config = {
    channelAccessToken: 'channelAccessToken',// LINEの方から入力
    channelSecret: 'channelSecret'
};

const app = express();

app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

// obniz接続
obniz.onconnect = async function () {
  obniz.display.clear();
  obniz.display.print('obniz meets LINE Bot!');
}

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

    let mes = ''
    if(event.message.text === 'どう?'){
      mes = 'ちょっと調べてきますねー'; //少し時間がかかるのでメッセージを入れる
      getAskObnizTemp(event.source.userId); //プッシュメッセージの準備
    }else{
      mes = event.message.text;
    }

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

  const getAskObnizTemp = async (userId) => {
    // obniz温度センサー実行準備、差し込んだ番号を入力
    const tempsens = obniz.wired('LM60',  { gnd:0 , output:1, vcc:2});
    const led = obniz.wired('LED', {anode:4, cathode:5});
    // 部屋の温度を取得
    const temp = await tempsens.getWait();
    // 温度をコンソールに表示
    if (temp > 28) {
        console.log(temp);
        // obnizのdisplayに表示
        obniz.display.clear();
        obniz.display.print(temp + 'C');  
        // obnizにpushメッセージを送る
        await client.pushMessage(userId, {
            type: 'text',
            text: temp + '度。エアコンつけよう!',
        });
        // LEDの点滅スピードを決める(ちょい早め)
        setInterval(async function(){
            led.on();
        },2000); // 1000ミリ秒 = 1秒
        setInterval(async function(){
            // 同期で取得
            led.off();
        },1000); // 1000ミリ秒 = 1秒

    }else if(temp < 26){
        console.log(temp);
        obniz.display.clear();
        obniz.display.print(temp + 'C');  

        await client.pushMessage(userId, {
            type: 'text',
            text: temp + '度。エアコン消そう!',
        }); 
        // LEDの点滅スピードを決める(ちょい遅め)
        setInterval(async function(){
            // 同期で取得
            led.on();
        },3000); // 1000ミリ秒 = 1秒
        setInterval(async function(){
            // 同期で取得
            led.off();
        },2000); // 1000ミリ秒 = 1秒

    }else{
        console.log(temp);
        // displayに反映
        obniz.display.clear();
        obniz.display.print(temp + 'C');  // 英語が出力できる

        await client.pushMessage(userId, {
            type: 'text',
            text: temp + '度。快適です!',
        }); 
        // LEDのオフにする
        led.off(); 
    }
  }

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



まとめ

はじめてIoTつくってみました。

まだまだ狙ったものをつくるスキルはないですが、
少しずつ改良して、より良いものにしていきたい。

今後も自分にとって、誰かにとって必要なものを
色々作ってみたいと思います。

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

【JavaScript】ライブラリ化しておく便利な3つの自作共通関数を記録

前提

  • ※記載の共通関数は、各環境によって動作しない場合がある。
    →ES2015からES2019で利用できるメソッドも利用しているため

1. chunk

  • 指定の数に応じて、対象配列を分割する関数

コード

lib.js
const chunk = (arr = [], size = 3) => {
    return Array.from({
        length: Math.ceil(arr.length / size)
    }, (v, k) => arr.slice(k * size, k * size + size))
}

使い方

index.js
import { chunk } from './lib.js'

// 対象配列定義
const target = [1,2,3,4,5,6];

// 利用 : chunk(対象配列, 分割サイズ)
console.log(chunk(target,2));
console.log(chunk(target,4));

/*
出力結果: 
[ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
[ [ 1, 2, 3, 4 ], [ 5, 6 ] ]
*/

内容

  • 処理内容としては下記。
    • 1. 引数から受け取る値から、対象配列の分割数を求める。
    • 2. 求めた分割数からlengthプロパティを持つ配列風オブジェクトを作成
    • 3. 作成した配列風オブジェクトの各値に対して、from処理で反復slice処理と変換を行う。

1. diff

  • 指定の各配列の差分を求める関数
  • つまり、重複していない一意の値を求める。

コード

lib.js
const diff = (...manyArgs) => {
  const targetArr = manyArgs.flat();
  const map = new Map();
  targetArr.forEach(a => map.set(a, (map.get(a) || 0) + 1));
  return targetArr.filter(a => map.get(a) === 1);
}

使い方

index.js
import { diff } from './lib.js'

// 対象配列定義
const first = [1,2,3,4,5];
const second = [1,2,3];
const third = [5,6,7];

// 利用 : diff(対象配列1, 対象配列2, ...)
console.log(diff(first,second));
console.log(diff(first,second,third));

/*
出力結果: 
[4,5]
[4,6,7]
*/

内容

  • 処理内容としては下記。
    • 1. 可変引数で受け取った二次元配列をflat処理で一次元に変換
    • 2. map定義して、反復処理で各値に存在確認処理を行った値を格納していく。
    • 3. 格納したmapの値によって、一次元化した配列に対してfilter処理を行う。

1. filterObj

  • 指定の条件で対象のオブジェクトを選別(フィルタリング)する関数

コード

lib.js
const filterObj = (target,condition) => {
  return Object.fromEntries(Object.entries(target).filter(condition))
}

使い方

index.js
import { filterObj } from './lib.js'

// 対象オブジェクト定義
const obj = {
  id: 11,
  name: '',
  score: 89,
  hasSkill: false
}

// 利用 : filterObj(対象オブジェクト, 条件)
console.log(filterObj(obj, ([key,value]) => value === 11));
console.log(filterObj(obj, ([key,value]) => !!value)); // 値が正のものを抽出

/*
出力結果: 
{ id: 11 }
{ name: 't-o-d', score: 80 }
*/

内容

  • 記載している処理内容としては下記。
    • 1. 第一引数で受け取ったオブジェクトをentries処理で配列へ変換。
    • 2. 変換した配列に対して、第二引数で受け取った条件式でfilter処理を行う。
    • 3. fromEntries処理で、再度オブジェクトへの変換処理を行う。

参考

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

dreddで認証付きのAPIをテストする

しばらく書いてなかったけどメモ程度に

dreddとは

dreddopenapi(swagger)API Blueprintの定義を読み込んでテストを実行してくれる便利なツールです。
例えば localhost:8080 でAPIサーバを動かして

dredd openapi/schema.yaml http://localhost:8080/v1/

のようにすればschema.yamlに定義されたAPIの定義に従ってリクエストを送り、レスポンスが仕様に従っているかテストしてくれます。

が、ログインしてtokenを貰ってそのtokenでアクセスするというようなAPIはそのままではテストできません。
hooksという仕組みを使うと、レスポンスからtokenを取得して以後はそれを使う、といった動作が可能になります。

dredd hooks

ドキュメントにある通り、hooksは様々な言語で書くことができますが、とりあえずdreddと同じnode.jsでやってみます。
(なぜかと言うと、dockerのコンテナ一つで完結させたい場合に処理系を複数入れるのが面倒だから)

schema

例えばこんなschemaがあるとします。(端折っているのでこのままでは動きません)

schema.yaml
openapi: 3.0.2
info:
  title: login api sample
  version: 1.0.0
servers:
  - url: http://localhost:8080/v1
    description: development env
paths:
  /sign_in:
    post:
      description: Sign In
      operationId: signIn
      requestBody:
        required: true
        content:
          application/json; charset=utf-8:
            schema:
              $ref: '#/components/schemas/AuthRequest'
            example:
              email: "foo@bar.jp"
              password: "password"
      responses:
        '200':
          description: successfully authorized
          content:
            application/json; charset=utf-8:
              schema:
                $ref: '#/components/schemas/Auth'
components:
  schemas:
    Auth:
      type: object
      description: authorized response
      properties:
        token:
          type: string
          example: "abcdefgh12345678"
    AuthRequest:
      type: object
      properties:
        email:
          type: string
        password:
          type: string
      required:
        - email
        - password

hooks

hooksは任意のディレクトリに置けますが、 test/hooks/sign_in.js に置くものとします。

sign_in.js
var hooks = require('hooks');
var stash = {};

// hook to retrieve session on a login
hooks.after('/sign_in > POST > 200 > application/json; charset=utf-8', function (transaction) {
  stash['token'] = JSON.parse(transaction.real.body)['token'];
});

// hook to set the session cookie in all following requests
hooks.beforeEach(function (transaction) {
  if(stash['token'] != undefined){
    if (transaction.expected.statusCode != "401"){
      transaction.request.headers['Authorization'] = "Bearer " + stash['token'];
    };
  };
});

実行

dredd openapi/schema.yaml http://localhost:8080/v1/ --hookfiles="test/hooks/*.js"

すると最初に /sign_in にアクセスした時のレスポンスから token を保存し、以降はAuthorizationヘッダに埋め込んでくれます。
ただし 401 のテストの場合は除外するようにしています。

注意点

  • テストはschemaの順番に実行されるので、先頭に /sign_in を書いておかないとそれより前のテストは失敗します。
  • APIの実装によりますが、 ログアウト時にtokenを無効化するようなAPIの場合、ログアウトは最後に書かないと途中でtokenが無効化して以後は失敗します。

その他、hooksを上手く使うと404などの異常系もテストできて便利です。

おわりに

swaggerもdreddも最近使い始めたばかりなので、間違い等ありましたらご指摘ください。
他にもTIPSありましたら教えてくれると嬉しいです。

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

【初心者向け】Node.jsとは?を初心者ながらまとめてみた

1.はじめに

最近Node.jsを使って開発を始めました。

ただ、現状「とりあえず触ってみている状態」なので、今回はNode.jsとはそもそもなんたるものかということと特徴について勉強し、自分なりの解釈でアウトプットしてみます。

2.Node.jsとは

Node.jsの公式サイトによると 非同期型のイベント駆動の JavaScript 環境 と書かれています。
「非同期」と「イベント駆動型」と「JavaScript 環境」を一つずつ噛み砕いていきます。

2.1. 非同期型とは

非同期型とは、非同期処理が実行可能な環境であることを指しています。
非同期処理とは、「ある処理を実行している間に別の処理を実行できる方式」のことです。(もちろん同期処理もできます)

言葉だけではイメージが掴みづらいので、以下図示(図2.1.1)してみました。
左側が同期処理、右側が非同期処理のイメージです。
ご覧の通り、同期処理では、処理Bが実行している間は処理Aが停止して、処理Bが終わると処理Aが再開されます。
対して非同期処理では、処理Bが実行されても処理Aは継続するため、処理Aと処理Bは並行して実行し続けます。
処理Aと処理Bが同一サーバ内での処理であった場合、Node.jsはイベントループという仕組みの元、特殊な動きをします。詳細に書いてくださっているこちらのサイトがわかりやすかったので、気になる方はご覧ください。

非同期処理とは.png
図2.1.1. 非同期処理とは

非同期処理がどんなところに使われているのか考えてみましょう。
例えば、住所を入力するwebフォームの場合を考えてみます。
郵便番号を入力すると自動で郵便番号から住所を出力してくれる機能をご覧になったことがあるかと思います。この処理も非同期処理が裏でなされています。

非同期処理は処理完了までの時間短縮ができることが便利な反面、処理が終わる順番を約束されません。
そのため、個人的に同期処理との使い分けがとても難しく、かつ意図しない動作を生み出す危険性も兼ね備えているため、重要になるという印象を受けました。

2.2.イベント駆動型とは

イベント駆動型とは、何かしらの「イベント」が生じた際に動作する環境のことを指します。
イベントとは以下のようなものです。

  • マウスでクリック
  • キーボードで入力
  • 通信
  • センサーの入出力
    etc...

要するに、何かの入力によって動作する環境です。

2.3.JavaScript環境とは

Javascript環境という言葉を聞いただけでなんとなく想像はできるかと思いますが、簡潔に言うと「Javascriptが動作する環境」です。

もう少し詳細に言うと、インタプリタ(通訳者)がJavascriptのコードをコンピュータの実行可能なコマンドに変換し、コンピュータがそのコマンドを実行しています。

ちなみにNode.jsが使っているJavascript環境は、GoogleのV8 Javascriptエンジンを使用しているそうです。

本来このV8 Javascriptエンジンはブラウザで使用されていましたが、Node.jsではブラウザでなくても使用できるようなったため、サーバサイドアプリケーションの開発が可能になりました。

V8_engine.png
図2.3.1.V8エンジンのロゴ

3.Node.jsの特徴

次はNode.jsの特徴をみていきます。
Node.jsはシングルスレッドで動作しています。
シングルスレッドでの動作であるにもかかわらず、効率的にタスクの実行管理をするためにイベントループという仕組みが使われています。
イベントループではノンブロッキングI/Oという処理ができるようになっています。

横文字をたくさん並べたので、「シングルスレッド」と「イベントループ」と「ノンブロッキングI/O」のそれぞれの意味を解説してみます。

3.1.シングルスレッド

スレッドとは、日本語にすると「糸」や「道筋」なんて意味を持ちます。
つまりシングルスレッドとは、タスクを一つの道筋で処理をしていくことです。

こちらも言葉だけではイメージが掴みづらいので、例え話でイメージしてみます。
今回は、味噌汁を作るタスクでイメージしてみます。

味噌汁を作るためには、以下のタスクがあるとします。

  • お湯を沸かす
  • 具材の準備(豆腐切る等)
  • 具材をお湯に入れて混ぜる

上記タスクの場合、以下の図3.1.1.のように「お湯を沸かす処理」と「具材を準備」を一つずつ実行するパターンと、並行処理するパターンの二つの進め方ができます。

一つずつ処理していくパターンがシングルスレッドです。

シングルスレッド.png
図3.1.1. シングルスレッドとマルチスレッド違い

3.2.イベントループ

イベントループとはNode.jsがシングルスレッドで動作していても効率的に処理を進めるための仕組みのことです。
イメージとしては、プロジェクトマネージャーのような役割をしています。
タスク管理やメンバーへのタスク割り当て、打ち合わせの調整など、各タスクを進めるために様々な調整ごとをしてくれます。(ざっくりとしたイメージのため、詳細は異なります。)

こちらの詳細を話出すと、あまり直感的に理解できる内容ではない、かつあまり意識しなくてもプログラミングができるので、今回は割愛します。
私自身も正しく理解ができている自信がないので、引き続き勉強していこうと思います。
以下参考になるサイトのURLを貼っておきます。

3.3.ノンブロッキングI/O

I/Oとは、input/outputの略であり、入出力のことを指しています。
コンピュータの処理はinputした情報を元に計算等の処理をし、何かしらoutputします。
ブロッキングI/Oでは、このinputからoutputまでの処理を待ってから出力します。
ノンブロッキングI/Oでは、inputした情報からoutputの処理を待たずに、呼び出しに反応があります。
outputまでの処理を待たずして、別の処理を実行することができるという処理です。
イメージとしては、非同期処理の一種として捉えられます。

公式ドキュメントのコードを追っていくとわかりやすいので、詳細を知りたい方は是非ご覧ください。

この特徴があるので、Node.jsは大量のリクエストに強くなります。

4.まとめ

「とりあえず触ってみている状態」だったNode.jsについて学んだことをまとめました。
こうやってアウトプットしてみると、まだまだ正しい理解ができていない部分が多いと分かるので、勉強意欲がさらに出てきます。
引き続き実際にNode.jsに触りながら学んでいきたいと思っています。

まだまだNode初心者なため、認識が違うところがあるかもしれません。
認識齟齬あれば是非コメントください。

参考資料

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

Lambda(node.js)とJavaScriptのタイムゾーン

Lambda(node.js)で日付の計算をしたら思ったより面倒だったのでまとめる。

やりたかったこと

日本標準時(JST)で先週/先月の範囲を、世界標準時(UTC)で取得したい。
例えば、今日が 2020-07-29 とすると先週の範囲は日曜日始まりでこんな感じ。

タイムゾーン 開始 終了
JST 2020-07-19T00:00:00+0900 2020-07-26T00:00:00+0900
UTC 2020-07-18T15:00:00Z 2020-07-25T15:00:00Z

やりたくなかったこと

大した処理じゃないので余計なライブラリは使いたくない。

結論

  • タイムゾーンの設定はそのままにプログラム内で対応する
  • タイムゾーンオフセット値からJSTを自力で設定する
  • UTCへの変換も自力でやる

それぞれのタイムゾーン

Lambdaのタイムゾーン

UTCに固定されている模様。

ググるとTZ環境変数を設定すればいいという記事が大量にヒットする。
ただ、TZ環境変数はAWSに予約されているので、それを変更するのはよろしくなさそう。
Lambda のタイムゾーンを環境変数TZで指定してはいけないっていう話 | Serverworks ENGINEER BLOG

他の利用しているAWSサービスでUTCで動作するものもあったので、プログラム内で対応することにした。

JavaScriptのタイムゾーン

JavaScriptのDateクラスにはタイムゾーンを設定する方法はないようだった。
ただ getTimezoneOffset で分単位の時差は取得できた。JSTは+9時間なので、なぜか-540分が返ってくる。取得した値を+しとけばUTCに変更できそう。

toLocaleString でタイムゾーン指定の日付文字列を取得して、新しくDateオブジェクトを作る方法もあるようだが、Lambdaでは動作しないのとDateクラスのコンストラクタに日付文字列を渡すのは非推奨だったのでやめた。

最終的にJavaScriptのタイムゾーンは無視して getTimezoneOffset を使って自力で日時を計算することにした。

オレのMacのタイムゾーン

JSTだった。
AWS SAMを使ってローカルで開発していると、ローカルではJSTでLambda上ではUTCになる。
タイムゾーンを変更してテストしたい場合は、TZ環境変数を設定することで変更できる。

実装

日本標準時で現在日時を取得

考慮するタイムゾーンはJSTかUTCの2つという前提で、タイムゾーン設定を無視して日本標準時での現在日時を取得することにした。

const jstOffset = 9 * 60;
const now = new Date();
const offset = now.getTimezoneOffset() + jstOffset;
now.setTime(date.getTime() + offset * 60 * 1000);

先週の開始/終了日時を取得

日本標準時で先週の日曜日を取得。

const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
start.setDate(start.getDate() - 7 - start.getDay());

同様に今週の日曜日を取得。

const end = new Date(now.getFullYear(), now.getMonth(), now.getDate());
end.setDate(end.getDate() - end.getDay());

先月の開始/終了日時を取得

先月1日を取得。

const start = new Date(now.getFullYear(), now.getMonth() - 1, 1);

今月1日を取得。

const end = new Date(now.getFullYear(), now.getMonth(), 1);

JST→UTCの変換

それぞれの日付をUTCに変換。
実際はタイムゾーンを変換してないので getUTCFULLYear などは使えない。

const jstOffset = 9 * 60;
date.setTime(date.getTime() - jstOffset * 60 * 1000);

date.getFullYear();
date.getMonth() + 1;
date.getDate();
date.getHours();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node 12 以降の環境で Gulp 3系を動かす

Gulp 3系がサポートされなくなってから結構たちましたが、3系環境の案件を保守する事例はまだ少なくないです。ただ、 Node 12 以降では正常に動作しないので、ほとんどの場合エラーになるかと思います。
なので単純にマシンに Node 11 を入れ直せば一応は動くんですが、代わりに最近の NPM が動かなくなってしまいます。(案件ごとに Node を都度入れ直さないといけない感じになってしまいます。)

バージョンを切り替えられるようにする

Node を都度入れ直すのはかなり手間だし複数案件やってるとき作業止まってしまうので、自分は Node バージョンを切り替える様にしています。ちなみに以前は n を使用していたんですが、Catarina から $PATH env がおかしくなって Node 自体が使い物にならなくなりました。
/usr/local/bin/n のシンボリックリンクと、 /usr/local/lib/node_modules/n/bin/n を削除して対処するはめになりました。

これはイシューにも上がってて、まだ解消されてないので、いったん n はもうやめて別のにしました。
切り替えツール系に、 nodebrew と nodenv があって(他にもあるかもですが)、nodenv を入れます。前にも使ったことはあるんですがちょっと面倒だったので、今回手順をのこしています。
※ちなみに違いとしては、どちらもプロジェクトごとに切り替えられるには切り替えられるんですが、nodebrew は nodebrew use v11.0.0 みたいな感じで都度都度コマンド叩く必要があるのに対して、nodenv は .node-version ファイルをプロジェクトフォルダに生成でき、切り替えコマンド不要で Node バージョン切り替えできるという感じです。(グローバルも変えられます)

手順

nodenv を ~(ホームディレクトリ) にクローン

% git clone git://github.com/nodenv/nodenv.git ~/.nodenv

※ nodenv 自体は本来、マシンの Node 環境構築に利用するものらしいです。

nodenv にパスを通す

% echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.zshrc
% echo 'eval "$(nodenv init -)"' >> ~/.zshrc

※ bash の場合(Catarina未満)は下記

$ echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(nodenv init -)"' >> ~/.bash_profile

node-build をクローン

% git clone https://github.com/nodenv/node-build.git ~/.nodenv/plugins/node-build

※ node-build は Node の各バージョンのビルダーらしいです。

ちなみに自分はここでパーミッションで弾かれてしまいました。
なので sudo つけて再度。

% sudo git clone https://github.com/nodenv/node-build.git ~/.nodenv/plugins/node-build

※ 記事によっては /usr/local/src にクローンしてるケースも有りました。OS古い場合は下記コマンドが良いかもしれません(未検証)。

% git clone https://github.com/nodenv/node-build.git /usr/local/src
% nodenv init
% PREFIX=/usr/local /usr/local/src/node-build/install.sh

※ nodenv init はシェルの再起動→nodenv再ロード

使用する Nodeバージョンを選んで利用可能にしておく

11.15.0(Node 11系の最新)と、12.14.0(執筆時点の LTS)をインストールしておく。
~/.node-gyp にインストールされる)

% nodenv install 11.15.0
% nodenv install 12.14.0

ちなみにインストール可能なバージョンのリストを下記コマンドで表示できる

% nodenv install -l

実際に切り替える

グローバルのバージョン指定をする場合は下記コマンド。
ただ、すでにLTSを入れているので自分は特にしなかった。

% nodenv global 12.14.0

ローカルのバージョン指定は下記コマンド。

% nodenv local 11.15.0

-> .node-version ファイルがプロジェクトに作成される

Gulp 3系を走らせてみる

そのまえに、一回 npm i してしまってるプロジェクトの場合は、(cd でプロジェクトに移動した上で)node_modules と NPM キャッシュの削除をしておく。

% rm -rf node_modules
% npm cache clean --force

ローカルの Node バージョンを nodenv local 11.15.0 などで Node 12 以前にしている状態でモジュールのインストールを実施。

% npm i

package.json の start スクリプトを追記。(すでにあったら何もしない)

"scripts": {
  "start": "gulp"
}

npm start で、正常にGulpが走れば、一通り成功。

% npm start

参考

nodenv を使って Mac に Node.js の環境を構築する

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

Node.jsのDockerイメージのマルチコア対応をがんばってみた

いろいろ頑張ってみたものの、Docker本家とかその他の情報によると、Dockerfile内部でマルチコア対応は頑張らない方が良いらしい。せっかくがんばったので供養のためにまとめておきます。

マルチコア対応したい!

Node.jsはシングルコアで動くのでマルチコア対応したいですよね?コア数が生かせないのはかっこ悪いですよね?サーバーにそのままデプロイする場合はpm2とかのプロセスマネージャを使います。pm2のウェブサイトをみると、Docker用のpm2があるじゃないですね。

というわけでそれを使うようにしてみました。

  • package.jsonのdependenciesにはpm2のみ
  • package.jsonのその他の依存はdevDependenciesに
  • npm run buildでサーバーコードはdist/index.jsというシングルjsファイルになる(@zeit/ncc 利用)

package.jsonは以下の通り(ESLintとかビルドに不要なものは省いた)。

package.json
{
  "name": "webserver",
  "version": "1.0.0",
  "scripts": {
    "build": "ncc build src/main.ts"
  },
  "dependencies": {
    "pm2": "^4.4.0"
  },
  "devDependencies": {
    "@types/body-parser": "^1.19.0",
    "@types/compression": "^1.7.0",
    "@types/express": "^4.17.7",
    "@zeit/ncc": "^0.22.3",
    "body-parser": "^1.19.0",
    "compression": "^1.7.4",
    "express": "^4.17.1",
    "http-graceful-shutdown": "^2.3.2",
    "typescript": "^3.9.7"
  }
}

サンプルのウェブアプリはこんな感じで作ってみました。大事なのはシグナルを受け取って終了するということです。

src/main.ts
import express, { Request, Response } from "express";
import compression from "compression";
import bodyParser from "body-parser";
import gracefulShutdown from "http-graceful-shutdown";

const app = express();
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.get("/", (req: Request, res: Response) => {
    res.json({
        message: `hello ${req.headers["user-agent"]}`,
    });
});

const host = process.env.HOST || "0.0.0.0";
const port = process.env.PORT || 3000;

const server = app.listen(port, () => {
    console.log("Server is running at http://%s:%d", host, port);
    console.log("  Press CTRL-C to stop\n");
});

gracefulShutdown(server, {
    signals: "SIGINT SIGTERM",
    timeout: 30000,
    development: false,
    onShutdown: async (signal: string) => {
        console.log("... called signal: " + signal);
        console.log("... in cleanup");
        // shutdown DB or something
    },
    finally: () => {
        console.log("Server gracefulls shutted down.....");
    },
});

こちらがDockerfileです。pm2とpm2-runtimeはだけはパスを通すようにしています。

Dockerfile
# ここから下がビルド用イメージ

FROM node:12-buster AS builder

WORKDIR app
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build

# ここから下が実行用イメージ

FROM node:12-buster-slim AS runner
WORKDIR /opt/app
COPY package.json package-lock.json ./
RUN npm ci --prod
RUN ln -s /opt/app/node_modules/.bin/pm2 /usr/local/bin/pm2
RUN ln -s /opt/app/node_modules/.bin/pm2-runtime /usr/local/bin/pm2-runtime
COPY ecosystem.config.js ecosystem.config.js
COPY --from=builder /app/dist ./
USER node
EXPOSE 3000
CMD ["pm2-runtime", "start", "ecosystem.config.js"]

PM2の設定ファイルは次の通り。instances: "max"が勇者の証。

ecosystem.config.js
module.exports = {
    apps: [
        {
            name: "greeting-server",
            script: "/opt/app/index.js",
            env: {
                NODE_ENV: "development",
            },
            env_production: {
                NODE_ENV: "production",
            },
            instances: "max",
            exec_mode: "cluster",
        },
    ],
};

これでビルドして実行すると、コア数分プロセスが立ち上がっていることがわかります(Dockerは4コア使うように設定してあります)。

$ docker build -t webserver .
$ docker --name webserver --rm -it webserver
$ docker exec webserver pm2 list
┌─────┬────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id  │ name               │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├─────┼────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0   │ greeting-server    │ default     │ 1.0.0   │ cluster │ 18       │ 55s    │ 0    │ online    │ 0%       │ 40.8mb   │ node     │ disabled │
│ 1   │ greeting-server    │ default     │ 1.0.0   │ cluster │ 25       │ 55s    │ 0    │ online    │ 0%       │ 41.3mb   │ node     │ disabled │
│ 2   │ greeting-server    │ default     │ 1.0.0   │ cluster │ 32       │ 55s    │ 0    │ online    │ 0%       │ 41.2mb   │ node     │ disabled │
│ 3   │ greeting-server    │ default     │ 1.0.0   │ cluster │ 39       │ 55s    │ 0    │ online    │ 0%       │ 41.2mb   │ node     │ disabled │
└─────┴────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

pm2-runtimeはpm2 --no-daemon相当のツールで、フォアグラウンドで動作し、シグナルなどはDockerの作法に従ってきちんと動作するように作られています。

めでたしめでたし・・・ではなかった

Dockerのベストプラクティスを諸々調べると、どれもnode スクリプトで起動せよ、プロセスマネージャとかランチャーは挟むな、と書かれています。ランチャー(npm run)はシグナルを適切に伝達しないということで、pm2-runtimeはそこはきちんとしているので、停止できないとかクリティカル無問題はないです。とはいえ、オーケストレーションツール側でオートスケール、みたいな話とちょっと喧嘩する可能性があるので、そこはもっと深く検証が必要なのかもしれません。

便利なところといえば、ロードバランサー的なものをおかなくても手っ取り早くパフォーマンスはあげられる・・・ぐらいですかね。

pm2はSaaSでObservabilityなサービスをしているようですね。pm2-runtimeはそこにつなげるためのエージェントという色合いが強いのかも・・・という気もしました。npm install pm2すると30MBぐらいどかっとイメージが大きくなってしまうのもいまいちだし、とりあえずこの努力はこのエントリーで供養します。

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

【はじめてのIoT】サイリウムを自動的に光らせてみた【ラブライブ】                                                                                                      

はじめに

 「推しが複数いるとき、どの色にすればいいか悩む・・・」
 「サイリウムで盛り上げたいけど、もっとライブに集中したい」
 「夢中になりすぎて、サイリウムの色を変えるのを忘れてしまう」

ライブに行った時、このように思ったことはありませんか?
私はあります。

私はラブライバーなのですが、箱推し(みんな好き)です。
なので全色光らせたいけど、持つのは2本が限界だし・・・
「次はオレンジだ!」とか考えてるとライブに集中できなかったり・・・

ということで、今回は自動的に光るサイリウムを作ってみました!

理想イメージ

ライブと言っても、今はコロナの影響もあって、無観客ライブが増えてきました。
今回もYoutubeのライブをイメージして、曲が始まるとサイリウムが自動的に光ることをゴールにします!
Image from Gyazo
Youtube IFrame Player APIを使って、node.jsでYoutubeを操作できるようにします。
そしてobnizでサイリウムを繋ぎ、Youtubeの再生をトリガーにobnizが動いてサイリウムが光るようにします!

実際にできたもの

動画をご覧ください。

色々ツッコミどころはあるかと思いますが、
まず、サイリウムといえば、こんなイメージですよね。
image.png
すみません、イメージ通りのサイリウムをObnizに繋げるのはハードルが高すぎました。
image.png
ということで、フルカラーLED。小っちゃくても明るく光ります。

さすがにこれを振るには小さすぎるので、私の分身を作りました。
(工作がとても苦手です。)
image.png

見た目こそアレですが、色はちゃんと思い通りに光っています!!!
歌っているメンバーの色が光るようにしていて、
複数メンバーで歌ってる場合は、その子たちの色を交互に点灯させました。
この曲の見せ場!センター真姫ちゃんのソロでは、ばっちり赤く光っております・・・(´;ω;`)

動画ではうまくいったかのように見えるのですが、実はYoutube IFrame Player APIをうまく使うことができませんでした。
その結果、、、
Obnizにだけプログラムを仕込み、Youtubeの再生ボタンとObnizのボタンを同時に押すというアナログ戦法となりました。
変な工作しちゃったので、Obnizのボタンがスーパー押しづらかったです。(自業自得)

コード

失敗したけど、アナログ版の自動サイリウムのソースコード貼っておきます!
▼秒単位で色指定しているため、かなり長いです。興味がある方はリンクからどうぞ。
https://gist.github.com/twtjudy1128/796cb4666e7cccc6bad327f76987dc11

個人的に、関数にメンバー名を使ったのがテンションあがりました。

      //メンバーごとに色を設定する
      // 他のカラーコード参考 http://www.netyasun.com/home/color.html

      function maki(){ //まきちゃん赤
       rgbled.rgb(255, 0, 0);
       }   
      function eri(){ //エリーチカ水色
        rgbled.rgb(0, 186, 255);
       }
      function honoka(){ //ほのかオレンジ
        rgbled.rgb(255, 174, 0);
       }
       function umi(){ //うみちゃん青
        rgbled.rgb(0, 0, 255);
        }   
       function rin(){ //凜ちゃんと言えばイエローだよおおおおお
         rgbled.rgb(255, 255, 0);
        }
       function hanayo(){ //花陽グリーン
        rgbled.rgb(0, 255, 0);
         }
        function nozomi(){ //希バイオレット
         rgbled.rgb(166, 0, 255);
        }   
        function kotori(){ //ことり白
        rgbled.rgb(255, 255, 255);
        }
        function nico(){ //にこピンク
         rgbled.rgb(255, 95, 219);
         }

おわりに

最後までご覧いただき、ありがとうございます!

前回書いた妄想記事がまさかのトレンド入り・・・!
(参照:「ジェイソン・ステイサムで妄想するのが日課になっていたので、いっそBOTにしてみた。」https://qiita.com/twtjudy1128/items/88f3e8f09c449f49456c

「好きこそ物の上手なれ」ということで、今回も自分の好きな「ラブライブ!」を題材に取り組んでみました。

そもそも工作も苦手意識があり、途中で何度も心折れかけたのですが、動画の中で歌うみんなを見ていると不思議と頑張れました。「好きドリブン」大事ですね。

プログラミングの勉強を始めて3週間のひよっこちゃんなのですが、LGTMをいただくと非常に励みになります。
Qiitaにいる皆さん、あったかくて幸せです!

最後に、ことりちゃんから一言。
:hatched_chick:「ラブライバーのみんなぁ~!LGTMくれないと、ことりのおやつにしちゃうぞぉ~!!!」

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

【はじめてのIoT】サイリウムを自動的に光らせてみた【ラブライブ】

はじめに

 「推しが複数いるとき、どの色にすればいいか悩む・・・」
 「サイリウムで盛り上げたいけど、もっとライブに集中したい」
 「夢中になりすぎて、サイリウムの色を変えるのを忘れてしまう」

ライブに行った時、このように思ったことはありませんか?
私はあります。

私はラブライバーなのですが、箱推し(みんな好き)です。
なので全色光らせたいけど、持つのは2本が限界だし・・・
「次はオレンジだ!」とか考えてるとライブに集中できなかったり・・・

ということで、今回は自動的に光るサイリウムを作ってみました!

理想イメージ

ライブと言っても、今はコロナの影響もあって、無観客ライブが増えてきました。
今回もYoutubeのライブをイメージして、曲が始まるとサイリウムが自動的に光ることをゴールにします!
Image from Gyazo
Youtube IFrame Player APIを使って、node.jsでYoutubeを操作できるようにします。
そしてobnizでサイリウムを繋ぎ、Youtubeの再生をトリガーにobnizが動いてサイリウムが光るようにします!

実際にできたもの

動画をご覧ください。

色々ツッコミどころはあるかと思いますが、
まず、サイリウムといえば、こんなイメージですよね。
image.png
すみません、イメージ通りのサイリウムをObnizに繋げるのはハードルが高すぎました。
image.png
ということで、フルカラーLED。小っちゃくても明るく光ります。

さすがにこれを振るには小さすぎるので、私の分身を作りました。
(工作がとても苦手です。)
image.png

見た目こそアレですが、色はちゃんと思い通りに光っています!!!
歌っているメンバーの色が光るようにしていて、
複数メンバーで歌ってる場合は、その子たちの色を交互に点灯させました。
この曲の見せ場!センター真姫ちゃんのソロでは、ばっちり赤く光っております・・・(´;ω;`)

動画ではうまくいったかのように見えるのですが、実はYoutube IFrame Player APIをうまく使うことができませんでした。
その結果、、、
Obnizにだけプログラムを仕込み、Youtubeの再生ボタンとObnizのボタンを同時に押すというアナログ戦法となりました。
変な工作しちゃったので、Obnizのボタンがスーパー押しづらかったです。(自業自得)

コード

失敗したけど、アナログ版の自動サイリウムのソースコード貼っておきます!
▼秒単位で色指定しているため、かなり長いです。興味がある方はリンクからどうぞ。
https://gist.github.com/twtjudy1128/796cb4666e7cccc6bad327f76987dc11

個人的に、関数にメンバー名を使ったのがテンションあがりました。

      //メンバーごとに色を設定する
      // 他のカラーコード参考 http://www.netyasun.com/home/color.html

      function maki(){ //まきちゃん赤
       rgbled.rgb(255, 0, 0);
       }   
      function eri(){ //エリーチカ水色
        rgbled.rgb(0, 186, 255);
       }
      function honoka(){ //ほのかオレンジ
        rgbled.rgb(255, 174, 0);
       }
       function umi(){ //うみちゃん青
        rgbled.rgb(0, 0, 255);
        }   
       function rin(){ //凜ちゃんと言えばイエローだよおおおおお
         rgbled.rgb(255, 255, 0);
        }
       function hanayo(){ //花陽グリーン
        rgbled.rgb(0, 255, 0);
         }
        function nozomi(){ //希バイオレット
         rgbled.rgb(166, 0, 255);
        }   
        function kotori(){ //ことり白
        rgbled.rgb(255, 255, 255);
        }
        function nico(){ //にこピンク
         rgbled.rgb(255, 95, 219);
         }

おわりに

最後までご覧いただき、ありがとうございます!

前回書いた妄想記事がまさかのトレンド入り・・・!
(参照:「ジェイソン・ステイサムで妄想するのが日課になっていたので、いっそBOTにしてみた。」https://qiita.com/twtjudy1128/items/88f3e8f09c449f49456c

「好きこそ物の上手なれ」ということで、今回も自分の好きな「ラブライブ!」を題材に取り組んでみました。

そもそも工作も苦手意識があり、途中で何度も心折れかけたのですが、動画の中で歌うみんなを見ていると不思議と頑張れました。「好きドリブン」大事ですね。

プログラミングの勉強を始めて3週間のひよっこちゃんなのですが、LGTMをいただくと非常に励みになります。
Qiitaにいる皆さん、あったかくて幸せです!

最後に、ことりちゃんから一言。
:hatched_chick:「ラブライバーのみんなぁ~!LGTMくれないと、ことりのおやつにしちゃうぞぉ~!!!」

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

好きなキー好きなBPMでパプれるスピーカーをline botとobnizで作ってみた

はじめに

好きな曲とか聞いていて「あーこれ音域低めだし歌えるんじゃね?ヨユウッショ」って思ってカラオケ行ったら全然音域足らなかったみたいな経験ありませんか?
僕はかの有名なパプリカで実際ありました、裏切られた気分になりますよね。
原曲キーで歌わないにしても、どれぐらいのキーが程よいのか分かりませんよね。

そこで、自宅でも自由にパプってあらかじめ自分にあったキーを探しておけるスピーカーをobnizとline botで作ってみました。
コードはGistに載せてあるので、是非コピーしてハンズオンしてエビバディパプろうぜ!
(Gist: https://gist.github.com/canonno/2209c7d68870b99d256cb4bac110b37d)

完成デモ

メロディの送信

obnizの圧電スピーカは周波数を引数として関数を実行する必要がありますメンドクサイィィィ
ということで、lineから「ドレミ」のような身近な表記でobnizに音を送信できるようにしました。
ピアノの白鍵が「ドレミ」、オクターブは数字を付けて「ド1レ1ミ1」、半音はシャープsかフラットfをつけて「ドs」「ミf」と表記できます。
オクターブ高めの半音も「ドs1」といった表記で対応できます。

Screenshot_20200728-214218.png

obnizで再生

送った楽譜はnode.jsで周波数に変換されobnizに送られます。
画面に[play]が出ているところでスイッチを押すと再生できます。

キー選択・BPM選択

モードを[play][key][BPM]の3つ用意しました。

[key]を変えると曲の音程が変わります。

[BPM]を変えると曲の速さが変わります。

さあ自分好みの自分だけのパプリカを探せ!(大袈裟)

環境

node v12.18.2
Visual Studio Code 1.47.1
obniz

手順

linebotを作る

他記事を参考にlinebotを作成しました。
(参考:https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)
Screenshot_20200728-213845.png

linebotへ投稿した「ドレミ」の楽譜をMessaging API経由で送信し、Node.jsで実装した処理を経てobnizの圧電スピーカへ送るという手順で実装しました。
Node.jsは今回ngrokを使って接続しました。

「ドレミ」を周波数に変換するまで

「ドレミ」から周波数へ変換するにはどうするか。
オクターブ表記を含めると「ド」や「ドs」や「ド1」や「ドs1」といった様々なバリエーションに対応する必要があります。
そこで、3段階の表記変換を挟む方針にしました。
image.png
一つずつ見ていきますね。

①「ドレミ」から三文字表記へ

まずはドレミから三文字表記に直す処理を考えました。
三文字表記のお作法は
 一文字目:ドレミ
 二文字目:ナチュラルn、シャープs、フラットf
 三文字目:オクターブ0,1,2
「ファ」のみ二文字なので最初に「フ」に置き換え、lineテキストの左から一文字ずつひたすら判定と変換作業を行います。

//lineからのテキストには、ただの「ド」から「ドs1」まで一音につき1文字から3文字まで表記がある
//それらを「ド」なら「ドn0」といった、半音・全音やオクターブ情報を付与した3文字コードに置き換える
function score_to_name(line_text){
    //ファが2文字なので一文字に置き換え、最後にeeを付与し、最後であることを明示する
    score = line_text.replace(/ファ/g,"") +"ee"

    node_list = []
    //左から一文字ずつ読んでいく
    for (i = 0; i < score.length-2;i++){
        node = score.slice(i,i+3);

        //ドレミで始まっているか
        type1 = /^[ドレミファソラシー]/g
        //ドレミの次はsか数字か
        type2 = /^[ドレミファソラシー][fs\d]/g
        //ドレミの次にsがつくか
        type3 = /^[ドレミファソラシー][fs]/g
        //ドレミの後にsと数字の両方がつくか
        type4 = /^[ドレミファソラシー][fs]\d/g

        if (type1.test(node)){
            if(type2.test(node)){
                if(type3.test(node)){
                    if(type4.test(node)){
                        //ドs1みたいな表記>>そのまま格納
                        node_list.push(node);                    
                    }else{
                        //ドsみたいな表記>>オクターブ情報を付与
                        node_list.push(node[0]+node[1]+"0");
                    }
                }else{
                    //ド1みたいな表記>>ナチュラルであるnを付与
                    node_list.push(node[0]+"n"+node[1]);
                }
            }else{
                //ドみたいな表記>>ナチュラルnとオクターブ情報を付与
                node_list.push(node[0]+"n"+"0");
            }
        }else{
            //音階から始まってない>>無視して次の文字へ
            ;
        }
    }
    return node_list
}

②三文字表記から数値表記へ

人によっては「三文字表記から周波数表記に直接変換すればいいんじゃない?」と思われる方もいるかもしれません。
ここであえて数値表記を挟むことで、keyを変えることができるようになります。
数値表記でいったん保持すれば、obniz側のkey+1や+2というキー操作を、ただ数値に+1や+2するだけでキー調整ができるようになります。
なので、少々面倒ですが数値表記に一度変換しております。

//上記で3文字コードになったものを、数値に置き換える
//一番低い音から1、2、3、と半音ごとに1ずつ上がる数字に置き換える
function node_to_int(node_list){
    //3文字コードと数字の対応表をインポート
    const dict = JSON.parse(fs.readFileSync('node_dict.txt', 'utf8'));
    name2int_dict = dict["name_to_int"]

    //ひたすら数値化
    int_list = []
    for (i=0;i<node_list.length;i++){
        int_list.push(name2int_dict[node_list[i]]);
    }
    return int_list;
}

ここでインポートしている対応表は、三文字表記⇔数値表記⇔周波数表記を行うために作ったdictになっています。
こちらの記事を参考に作りました。
dictはGistに置いておきますので使われる方はぜひ使ってみてくださいね。
(Gist: https://gist.github.com/canonno/2209c7d68870b99d256cb4bac110b37d)

{"name_to_int":{"ドn-3":1,"ドs-3":2,"レn-3":3, ... "シf3":83},
 "int_to_hertz":{"1":32.703,"2":34.648,"3":36.708, ..., "84":3951.066}}

③数値表記から周波数表記

数値表記にした後は、obniz側の操作で変化するkeyを足し引きし、周波数表記にします。
「ドー」の「ー」は数値表記で100としており、周波数表記にする場合は「ひとつ前の音の長さを1のばす」という処理にしています。
こうすれば「ドーーー」でも「ドーーーーーー」でも対応可能になります。

//上記で数値情報になったものを周波数と音の長さに置き換える
//key情報を引数に入れ、keyの数値分音をずらす処理も行う
function int_to_fre(int_list,key){
    //数値と周波数との対応表のインポート
    const dict = JSON.parse(fs.readFileSync('node_dict.txt', 'utf8'));
    int2fre_dict = dict["int_to_hertz"]

    //ひたすら置き換える
    fre_list = []
    for (i=0;i<int_list.length;i++){
        //「ー」の場合(数値を100としている)、ひとつ前の音のlengthを1伸ばす
        if (int_list[i]==100){
            last_length = fre_list[fre_list.length-1]["length"]
            fre_list[fre_list.length-1]["length"] = last_length + 1
        //普通の音の場合そのまま変換
        }else{
            fre_list.push({"frequency":int2fre_dict[int_list[i]+key],"length":1});
        }
    }
    return fre_list;
}

Obniz側での操作の処理

あとはobniz側での操作を実装します。
まずはobnizと接続する処理。
こちらの記事をかなり参考にしました。
(参考:https://qiita.com/Naru0607/items/b523cf9f67fa18bcf7d7)

obniz側で操作するとobniz.switch.onchange以降の処理が走るので、keyやBPMといった変数をその範囲外で宣言する必要がある点に注意です。
最初はonchange以降で宣言し、keyやBPMを変えても反映されずかなり苦戦しました。
obniz側で操作したらどこがどう走るかはしっかり理解しないとだめですね・・・。

//obnizで出力
function sound_with_obniz(line_text){
    //もろもろ初期設定
    const Obniz = require('obniz');
    const { text } = require("express");
    const obniz = new Obniz('Obniz_ID');  // Obniz_IDに自分のIDを入れます

    //obnizと接続
    obniz.onconnect = async function () {
    const speaker = obniz.wired('Speaker', {signal:0, gnd:1});

    //obniz上での設定パラメータ
    key = 0;
    BPM = 180;
    mode_list = ['play','key','BPM']
    mode_idx = 300
    mode = mode_list[mode_idx%3]

    // ディスプレイ処理
    obniz.display.clear();  // 一旦クリアする
    obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);

    // スイッチの反応を常時監視
    obniz.switch.onchange = async function(state) {
        //表示がplayの時の操作
        if (mode == 'play'){
            //押したら音が鳴る
            if (state === 'push') {
                one_tempo = Math.round(60/2/BPM*1000);
                node_list = score_to_name(line_text);
                int_list = node_to_int(node_list);
                fre_list = int_to_fre(int_list,key)
                for (i=0;i<fre_list.length;i++){
                    sound(fre_list[i]["frequency"],fre_list[i]["length"]);
                }
                obniz.display.clear();
                obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);
            //離せば何も起こらない
            } else if (state === 'none') {
                speaker.stop();
            //右にすればモードが変わる
            } else if (state === 'right'){
                mode_idx += 1;
                mode = mode_list[mode_idx%3]
                obniz.display.clear()
                obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);
            //左にすればモードが変わる
            } else if (state === "left"){
                mode_idx -= 1;
                mode = mode_list[mode_idx%3]
                obniz.display.clear()
                obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);
            }
        //表示がkeyの時の操作
        } else if (mode == 'key'){
            //押したらkeyの変更画面へ
            if (state === 'push') {
                mode = 'key_select'
                obniz.display.clear();
                obniz.display.print(mode+"\nkey:"+key);
            //離せば何も起こらない
            } else if (state === 'none') {
                speaker.stop();
            //右に倒せばモードが変わる
            } else if (state === 'right'){
                mode_idx += 1;
                mode = mode_list[mode_idx%3];
                obniz.display.clear();
                obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);
            //左に倒してもモードが変わる
            } else if (state === "left"){
                mode_idx -= 1;
                mode = mode_list[mode_idx%3];
                obniz.display.clear();
                obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);
            }
        //表示がBPMの時の操作
        } else if (mode == 'BPM'){
            //押せばBPMの設定画面になる
            if (state === 'push') {
                mode = 'BPM_select'
                obniz.display.clear()
                obniz.display.print(mode+"\nBPM:"+BPM)
            //離せば何も起こらない
            } else if (state === 'none') {
                speaker.stop();
            //右に倒せばモードが変わる
            } else if (state === 'right'){
                mode_idx += 1;
                mode = mode_list[mode_idx%3]
                obniz.display.clear()
                obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);
            //左に倒してもモードが変わる
            } else if (state === "left"){
                mode_idx -= 1;
                mode = mode_list[mode_idx%3]
                obniz.display.clear()
                obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);
            }
        //キーセレクト画面での操作
        }else if (mode == 'key_select'){
            //押せば元の画面に戻る
            if (state === 'push') {
                mode = mode_list[mode_idx%3]
                obniz.display.clear()
                obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);
            //離せば何も起こらない
            } else if (state === 'none') {
                speaker.stop();
            //右に倒せばキーが上がる
            } else if (state === 'right'){
                key += 1;
                obniz.display.clear()
                obniz.display.print(mode+"\nkey:"+key)
            //左に倒せばキーが下がる
            } else if (state === "left"){
                key -= 1;
                obniz.display.clear()
                obniz.display.print(mode+"\nkey:"+key)
            }
        //BPM設定画面での操作
        } else if (mode == 'BPM_select'){
            //押せば元の画面に戻る
            if (state === 'push') {
                mode = mode_list[mode_idx%3]
                obniz.display.clear()
                obniz.display.print(mode + "\nkey:"+ key+" BPM:"+BPM);
            //離せば何も起こらない
            }else if (state === 'none') {
                speaker.stop();
            //右に倒せばBPMが上がる
            } else if (state === 'right'){
                BPM += 20;
                obniz.display.clear()
                obniz.display.print(mode+"\nBPM:"+BPM)
            //左に倒せばBPMが下がる
            } else if (state === "left"){
                BPM -= 20;
                obniz.display.clear()
                obniz.display.print(mode+"\nBPM:"+BPM)
            }

        }
    }

    //一つ一つの音を出力する処理
    function sound(fre,length){
        speaker.play(fre);
        obniz.wait(length*one_tempo);
        speaker.stop();
        obniz.wait(100);
    }
    }
}

最後にlineと接続!

以上がすべてfunctionで実装できたので、あとは順番に実行していくだけ!

'use strict';

const fs = require("fs");
const express = require('express');
const line = require('@line/bot-sdk');
const { compileFunction } = require('vm');
const PORT = process.env.PORT || 3000;

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

const app = express();

//lineからくるテキスト初期値
var line_text = "";

//リクエストによる処理
app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用
app.post('/webhook', line.middleware(config), (req, res) => {
    events = req.body.events
    line_text = events[0].message.text
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

async function handleEvent(event) {
  //テキストでない場合は無視
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
  //lineテキストをobnizへ流し込む
  sound_with_obniz(line_text)

  //lineへの返信
  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: 'obnizでの操作をお楽しみください' //実際に返信の言葉を入れる箇所
  });
}

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

今後やりたいこと

パプリカ演奏できたことはできたけれど、低音がないと少し味気ないですよね。
次は和音に挑戦して、低音付きのガチパプリカを演奏できるか検証してみたい。
そしてあわよくばバズって米津玄師に会いた

プラス今回の実装だと、lineから楽譜を一度インポートすると、関数を一度止めるまで楽譜が変更できない実装になっています。
関数が動いている状態でlineから別の楽譜を送信しても上書きすることができません。
関数のon/offをline側でできるようになったり、楽譜管理・保存とかできたら良いなあとか思ったりしています。

いつできるやらという感じですが、生暖かい目で見守っていただけると嬉しいです!
最後までご覧いただきありがとうございました!

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

【obniz×LINE Messaging API】音と光を使い侵入防止システムを製作する

目的

私の実家では、夏場に玄関を開放し風通しを良くしていると、野良猫が侵入し、食べ物が被害にあいます、そこで今回はobnizで侵入防止システムを製作したいと思います。(効果は保証できません)

できたもの

距離センサーの50㎝以内に入り5秒経過したら、LEDとスピーカーで警告し、システムが作動したことをLINE Messaging APIを使用してプッシュ通知します。映像ではわかりませんが、スピーカーから11khzの音を鳴らしてます。

構成

全体像

今回製作したシステムの全体像です。
Image from Gyazo

配線図

obnizの配線図です。
Untitled Sketch_ブレッドボード.png

使用部品

環境

  • node.js v14.5.0
  • @line/bot-sdk 7.0.0
  • express 4.17.1
  • obniz 3.7.0

コード

index.js
'use strcit';

const Obniz = require('obniz');
const obniz = new Obniz('××××-××××'); // Obniz_IDに自分のIDを入れます
const line = require("@line/bot-sdk");
const express = require("express");
const PORT = process.env.PORT || 3030;

const config = {
  channelSecret: '{チャネルシークレット}',
  channelAccessToken: '{アクセストークン}'
};
const userId = '{ユーザーid}';
const app = express();

app.post('/webhook', line.middleware(config), (req, res) => {
  console.log(req.body.events);
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result));
});

const client = new line.Client(config);


obniz.onconnect = async function () {
  // 各センサーを呼び出す
  // 距離センサーを呼び出す
  const GP2Y0 = obniz.wired("GP2Y0A21YK0F", {
    vcc:0,
    gnd:1, 
    signal:2
    });
  // 指す場所を変えているので対応した数字に変更
  // LEDを呼び出す
  const led = obniz.wired('LED', {
    anode:5,
    cathode:6
    });
  // スピーカーを呼び出す
  const speaker = obniz.wired('Speaker', {
    signal:10,
    gnd:11
    });
  obniz.display.clear(); // 一旦クリアする
  obniz.display.print('Hello obniz!'); // Hello obniz!という文字を出す

  // setIntervalで間隔を作る
  setInterval(async function () {
    const distance = await GP2Y0.getWait();
    // 距離(mm)をコンソールに表示
    console.log(distance + ' mm');
    // displayに反映
    obniz.display.clear(); // 一旦クリアする
    obniz.display.print(distance + ' mm'); // 英語が出力できる
    // 近づいてきたら判定する
    if (distance < 500) count++;
    else count = 0;
      switch(count) {
        case 5:
          obniz.display.clear(); // 一旦クリアする
          speaker.play(11000); // スピーカー On 11khz
          led.on();// LED ON
          //LINEにメッセージ送信
          await client.pushMessage(userId, {
            type: 'text',
            text: '侵入防止システムを発動しました',
            });
        break;
      default:
        speaker.stop();// スピーカー OFF
        led.off();// LED OFF
      }
  }, 1000); // 1000ミリ秒 = 1秒
};

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

参考にした記事

LINEmessagingAPIの基礎部分で参考にしました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest

Push通知の部分を参考にしました。
LINE BOTでラズパイから部屋の温度を教えてもらう【Push API】【Node.js】

失敗

LEDはフルカラーを使用し色を使い分ける予定でしたが、途中でショートさせて焼いてしまいました。ですので、今回は青色のLEDを使用しました。

終わりに

今回は距離センサを使ったので遮るものすべてにはんのうしてしまいます。
公式サイトでは、カメラで画像認識を使用している作例もあり、特定の対象物にのみ反応するようにAI機能を搭載できたらいいなと思いました。

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

(Express)HTMLからの処理をNode.jsで同期/非同期処理した時に教わった注意点

連投すみません。お引越し中です…。

結論から言うと、
Expressのミドルウェアには通常のコールバック関数を与えること!

らしいです…

これだけでは理解できなかったのでまとめます。

ミドルウェアとは

コンピューターは「ハードウェア」「OS」「ミドルウェア」「アプリケーション」の4要素で構成されています。

ミドルウェアは「OS」と「アプリケーション」の間にあるような、「OS」でも「ミドルウェア」でもないもの。

機械とアプリの橋渡しをするようなものは全て「ミドルウェア」のようです…。

例えば、windows10が「OS」ブラウザが「アプリケーション」、
「Webサーバ」(ブラウザからのリクエストに応じてWebページを送信する)が「ミドルウェア」です。

わかりずらい…!

expressとNode.jsでいうと、router.all()関数は、expressからNodeに渡している関数だから、ミドルウェアらしいです。

つまり、router.all()やrouter.post()のコールバック関数を標準にすればいいのですね!

コールバック関数とは

関数の引数として呼び出す関数です。

(例) function( 10, timer() )

これは、function関数の引数として呼び出されているtimer()がコールバック関数。

Express+Node.jsの場合、router('/', function(){})のfunction()関数を、通常の関数にしなければならない。

つまり、asyncはコールバック関数にはつけてはいけません!(つけてた)

まとめ

だめ↓

index.js
router.all('/', async function(){
 //中身
})

いい↓

index.js
router.all('/', function(){
 main(){
  //中身
 }

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

【同期処理】async/awaitできない(待ってもらえない)問題【Node.js】

Node.jsが親切設計すぎて、時間がかかる処理をしている間に勝手にその後の処理に進んでしまって困りました。

そこでasync/awaitしたのですがなかなか待ってもらえず…

こちらの回答者さんの回答を拝見し、解決しました。→(https://teratail.com/questions/131373)

記録します。

できなかった時のプログラム

index.js
()
async function a(){
  //ちょっと時間がかかる処理

  return '2'
}

async function main(){
  const number = await a()
  console.log(number)
  console.log("numberを表示した後")
}

実行結果です↓

numberを表示した後
2

実行結果がかわってないー(「2→numberを表示した後」、という順番にしたい)

できた時のプログラム

index.js
()
//--------returnで囲っちゃった!------------
function a(){
  return new Promise((resolve, reject) => {
    //ちょっと時間がかかる処理
    resolve('2')
  }
}

async function main(){
  const number = await a()
  console.log(number)
  console.log("numberを表示した後")
}
main()

実行結果です↓

2
numberを表示した後

やったー!

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