クソ雑魚エンジニアのメモ帳

学んだことを書くところ

低レイヤチョットワカル(nand2tetris/コンピュータシステムの理論と実装1章)

低レイヤ

そろそろ勉強しようと思ったので、お勉強。

コンピュータシステムの理論と実装――モダンなコンピュータの作り方をやってみることに

www.oreilly.co.jp

だいぶ前に触ったアセンブリ言語よりもだいぶ低レイヤ。 章ごとにダラダラ学んだことを書いていく予定

1章 ブール論理

ブール関数

3つの表現方法がある

  1. 真理値表
  2. ブール式
  3. 正準表現

Xor = (x and Not y) or (Not x and y)

Nandのみで全ての演算が可能

xORy = (x Nand x) Nand (y Nand y)

論理ゲート=入力と出力のデバイス=ブール関数で表現できる

マルチプレクサ

複数の入力のいずれかを単一の出力にする。どちらかを選ぶかは選択制御Sによる。Sもブール値

「マルチプレクサ」の画像検索結果

Wikipediahttps://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%AB%E3%83%81%E3%83%97%E3%83%AC%E3%82%AF%E3%82%B5

デマルチプレクサ

単一の入力を複数の出力のいずれかにする。どちらかを選ぶかは選択制御Sによる。Sもブール値

多ビットのゲート

入力・出力共に配列であり、それぞれのindexに対して操作するだけ

多入力のゲート

入力が2つ以上のゲート

多入力Or=入力1…nまでのいずれかが1であれば1それ以外は0

多入力マルチプレクサ=複数の入力のいずれかを単一の出力にする。どちらかを選ぶかは選択制御Sによる。Sもブール値。入力の数がnに対して、選択制御は、log2n個必要

HDL

ハードウェアを構築するための言語。HDLで記載して各ゲートをシミュレートする宿題が出た。

HDLでloop使えない😇(単に1章だから使えないのかも?)

マルチプレクサゲート(Mux/4WayMux16)/デマルチプレクサゲート(Dmux)が一番しんどかった。

以下、僕の回答。

/** 
 * Multiplexor:
 * out = a if sel == 0
 *       b otherwise
 */
CHIP Mux {
    IN a, b, sel;
    OUT out;

    PARTS:
    Not(in=sel, out=notsel);
    And(a=a, b=notsel, out=outtemp1);
    And(a=b, b=sel, out=outtemp2);
    Or(a=outtemp1, b=outtemp2, out=out);
}

/**
 * Demultiplexor:
 * {a, b} = {in, 0} if sel == 0
 *          {0, in} if sel == 1
 */
CHIP DMux {
    IN in, sel;
    OUT a, b;

    PARTS:
    Not(in=sel, out=notsel);
    And(a=in, b=notsel, out=a);
    And(a=in, b=sel, out=b);
}

あと、配列の各要素へのアクセスにはまったが、自分と全く同じコードで全く同じ意図の質問があって助かった。

まさか右から読むとは思わなんだ。正しくはhoge = 01の場合、 hoge[0]=1hoge[1]=0らしい。

あと、記録はgitにのっけてます

MFクラウドの勤怠管理をコマンドラインから操作(puppeter)

こんばんは。スマブラ発売間近で胸がワクワクmorifujiです。

最近、MFクラウドの勤怠管理を利用することになりまして、出勤時退勤時には専用webサイトにログインして、ボタンを押さないといけなくなりました。。

めんどくさいので、ヘッドレスブラウザでサクッとつくったので知見を共有します。puppeter久しぶりすぐる

git

https://gitlab.com/morifuji/mf-auto-attendance

筆者環境

環境 バージョン
PC MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports)
仮想環境/ローカル ローカル環境
nodejs v10.7.0
yarn 0.27.5
puppeter 1.11.0

ゴール

  • 以下の手順をヘッドレスブラウザで実行すること
    • 1.ログインページからログイン
    • 2.出勤ボタンまたは退勤ボタンをクリック
    • 3.確認のダイアログに対して入力
  • 出勤・退勤の2つのスクリプトを作成
  • どちらも1コマンドで
  • ID/PASSは外部ファイルに

実装

準備

yarn init mf
yarn add puppeter

package.jsonを編集

{
  "name": "mf",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "puppeteer": "^1.11.0"
  },
  "scripts": {
    "mf-in": "node ./main.js in",
    "mf-out": "node ./main.js out"
  }
}

scriptsで出勤・退勤の2種類のスクリプトを叩きます

スクリプト

スクリプト本体作成

const puppeteer = require('puppeteer');
const config = require('./config.js')

console.log("action: " + process.argv[2])

let isInAttendance  = null
switch(process.argv[2]) {
  case "in": 
    isInAttendance = true
    break;
  case "out":
    isInAttendance = false
    break;
}

if (null === isInAttendance) {
  console.error("引数に `in` または `out` を設定してください")
  return;
}

if (!config.id || !config.pass) {
  console.error("config.jsでID/パスワードを設定してください")
  return;
}

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://payroll.moneyforward.com/session/new', {waitUntil: 'networkidle2'});

  await page.type('input[id=sign_in_session_service_email]', config.id);
  await page.type('input[id=sign_in_session_service_password]', config.pass);

  const inputElement = await page.$('input[name=commit]');
  await inputElement.click();

  await page.waitFor(2000);

  page.on("dialog", (dialog) => {
    dialog.accept();
  });

  let attendanceButton = null
  if (isInAttendance) {
    attendanceButton = await page.$('.btn-attendance');
  } else {
    attendanceButton = await page.$('.btn-leaving');
  }

  await attendanceButton.click();

  // 3秒待つ
  await page.waitFor(3000);
  await browser.close();
})();

ログイン情報を設定

module.exports = {
  id: "",
  pass: ""
}

idとpassに、自身のログイン情報を設定

実行

# 出勤
yarn mf-in

# 退勤
yarn mf-out

もうちょい楽に

毎日実行することを考えるともう少し短くしたいので、エイリアスを設定

echo -e "alias mf-in='cd ~/mf/ && yarn mf-in'\nalias mf-out='cd ~/mf/ && yarn mf-out'" >> ~/.bashrc

:warning: cd ~/mf/の部分は、自分のプロジェクトディレクトリに書き直してください

エイリアス実行

# 出勤
mf-in

# 退勤
mf-out

本体スクリプトざっくり説明

もろもろimport

const puppeteer = require('puppeteer');
const config = require('./config.js')

...

if (!config.id || !config.pass) {
  console.error("config.jsでID/パスワードを設定してください")
  return;
}

ライブラリ(puppeter)をrequireして 設定ファイルから設定値を取得。取得できなかった/false評価ならエラー

引数から、出勤/退勤を判定

console.log("action: " + process.argv[2])

let isInAttendance  = null
switch(process.argv[2]) {
  case "in": 
    isInAttendance = true
    break;
  case "out":
    isInAttendance = false
    break;
}

if (null === isInAttendance) {
  console.error("引数に `in` または `out` を設定してください")
  return;
}

process.argv[2]には、 inまたはoutが入っているので、それをもとに出勤か退勤か判定。

puppeterおまじない

(async () => {

  ...

})();

async/awaitを使いたいので、asyncつけて即時関数にしてる

ページ表示

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://payroll.moneyforward.com/session/new', {waitUntil: 'networkidle2'});

puppeterを起動して、ページを開いてます。 puppeteer.launch()の第3引数には、様々な設定ができます。例えば、{headless: false}とすると、ブラウザが表示された上で操作されます。デバッグに便利ですねー。

page.gotoの第三引数のwaitUntilは、puppeterがどの時点でページ描画完了とするかの設定値です。 ほかにも色々な設定ができます

form入力・submit

  await page.type('input[id=sign_in_session_service_email]', config.id);
  await page.type('input[id=sign_in_session_service_password]', config.pass);

  const inputElement = await page.$('input[name=commit]');
  await inputElement.click();

  await page.waitFor(2000);

クエリセレクタを書いて、そこに第二引数の文字を入力しています。 クリックは少しめんどくさい

waitForでページの描画を待機してます。

出勤ボタンクリック・ダイアログaccept

  page.on("dialog", (dialog) => {
    dialog.accept();
  });

  let attendanceButton = null
  if (isInAttendance) {
    attendanceButton = await page.$('.btn-attendance');
  } else {
    attendanceButton = await page.$('.btn-leaving');
  }

  await attendanceButton.click();

出勤ボタン/退勤ボタンのクリックは先ほどと同じ流れです。

この勤怠システムでは、出勤ボタン/退勤ボタンを押すと、確認のダイアログが表示されます。puppeterがそのダイアログを選択する必要があります。

今回は、ダイアログが表示されるとdialogイベントが発火するので、page.on('{イベント}', {発火する関数})でイベントリスなを設定してます。他にも、いろんなイベントをみることができるみたいです。

    page.on('close')v1.3.0
    page.on('console')v0.9.0
    page.on('dialog')v0.9.0
    page.on('domcontentloaded')v1.1.0
    page.on('error')v0.9.0
    page.on('frameattached')v0.9.0
    page.on('framedetached')v0.9.0
    page.on('framenavigated')v0.9.0
    page.on('load')v0.9.0
    page.on('metrics')v0.12.0
    page.on('pageerror')v0.9.0
    page.on('request')v0.9.0
    page.on('requestfailed')v0.9.0
    page.on('requestfinished')v0.9.0
    page.on('response')v0.9.0
    page.on('workercreated')v1.5.0
    page.on('workerdestroyed')v1.5.0

https://pptr.dev/#?product=Puppeteer&version=v1.11.0&show=api-class-page

終了

  // 3秒待つ
  await page.waitFor(3000);
  await browser.close();

出勤・退勤ボタンのクリックから三秒ほど待機。 そのあとヘッドレスブラウザを閉じます

Docker化

nodejsのイメージ使えばyarnがデフォで入っているのでamazonlinux2を使ってyarnのインストールしてるのは無駄でした。 というか、公式でDockerfile載せてるやん。。。 😭

以下、無駄ですがどうぞ

FROM amazonlinux:2

RUN yum update -y
RUN yum upgrade -y

RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo
# nodeのバージョンに注意
RUN curl --silent --location https://rpm.nodesource.com/setup_8.x | bash -
RUN yum install -y nodejs
RUN yum install -y yarn

RUN mkdir /root/mf

WORKDIR /root/mf

ADD . /root/mf

# 環境依存なのでdockerないでyarnさせる
RUN rm -rf node_modules
RUN yarn

# 起動設定
CMD /bin/bash -c "yarn mf-in"

実行

❯ docker build -t mf .
...

❯ docker run mf
yarn run v1.12.3
$ node ./main.js in
action: in
(node:27) UnhandledPromiseRejectionWarning: Error: Failed to launch chrome!
/root/mf/node_modules/puppeteer/.local-chromium/linux-609904/chrome-linux/chrome: error while loading shared libraries: libX11.so.6: cannot open shared object file: No such file or directory


TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md

    at onClose (/root/mf/node_modules/puppeteer/lib/Launcher.js:342:14)
    at Interface.helper.addEventListener (/root/mf/node_modules/puppeteer/lib/Launcher.js:331:50)
    at emitNone (events.js:111:20)
    at Interface.emit (events.js:208:7)
    at Interface.close (readline.js:368:8)
    at Socket.onend (readline.js:147:10)
    at emitNone (events.js:111:20)
    at Socket.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:139:11)
(node:27) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:27) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Done in 0.57s.

無理でした。

metabase3分クッキング

こんばんは。この季節サンダル通勤で足が冷えてくるmorifujiです

今回はmetabaseを使う機会があったのでサクッと構築したlogを置いておきます。

  • クライアントはBIツールが欲しいらしい
  • tableauは高い&運用費も高い(m4.2xlargeぐらい)
  • もっとサクッとかつ簡単なBIツールないんかなー

ということでmetabaseの登場です。

  • slack連携
  • メール連携

を使えばテーブルAのデータがある基準に達したときにslackやメールで通知ができるみたいです、すごいですね

環境

スクリプト

# 以下、ec2インスタンス内。

mkdir metabase
cd metabase
wget http://downloads.metabase.com/v0.30.4/metabase.jar

# javaバージョン確認
java -version

# 1.8>versionだったら
sudo yum update -y
sudo yum install -y java-1.8.0-openjdk.x86_64
# versionを1.8以上に切り替える
sudo alternatives --config java

# 確認
java -version
openjdk version "1.8.0_181"
OpenJDK Runtime Environment (build 1.8.0_181-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)


# `java -jar metabase.jar`でエラーが出たので修正
sudo echo "10.0.0.219 ip-10-0-0-219" | sudo tee -a /etc/hosts


# 実行
java -jar metabase.jar


# localhost:3000/setupにてGUIなセットアップができる!!

課題

  • metabaseのデータがインスタンスの停止などで消える可能性があるので何かしらでバックアップが必要
    • 手段1. ebsでマウントしておく
    • 手段2. metabaseのデータ保存先がデフォルトh2なので、rdsにする
    • 余裕あればまた別記事で書きます。 軽かったのでこの記事に描きました

metabaseのデータバックアップ

データをrdsに保存したいなら、以下の環境変数を設定するとdbに繋げてくれます。以下はmysqlの例です

export MB_DB_TYPE=mysql
export MB_DB_DBNAME=metabase
export MB_DB_PORT=3306
export MB_DB_USER=xxxxxxxxxxxx
export MB_DB_PASS=xxxxxxxxxxxx
export MB_DB_HOST=xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com

# この後にjavaコマンド

バックグラウンド化

java -jar metabase.jarしてもフォアグラウンドなのでssh切ったら止まります。(当たり前)

なのでバックグラウンドで起動できるようにしましょう

nohup java -jar metabase.jar > out.log &

この例だとout.logにログが測れますね

参考

https://www.yoheim.net/blog.php?q=20180101

jooby(kotlin)の環境構築,swagger,docker化まで

こんばんは。フォートナイトやってたら台風の爆音がうるさくて敵の足音が聞こえず殺されたmorifujiです

2018/09/19 追記

gitlabに使ったコードを載せました。開発環境もdockerで構築できるようにしたので割と便利です。

https://gitlab.com/morifuji/jooby-web

概要

Kotlinを触りたいなあと思っていたのでその練習がてらにjoobyなるものを触りました。

  • IntelliJでの環境構築
  • RESTfulAPI実装/Swgger出力
  • DBつなぎ込み(MySQL)
  • ビルド
  • docker化

までやったので知見を共有します。ちなみに僕はGradleとjavaSIer時代3ヶ月ほどだけやっていましたがほぼ忘れている状態です

jooby

公式サイト:https://jooby.org/

IntelliJでの環境構築

こちらを参考にしました。あっという間です。https://github.com/jooby-project/kotlin-gradle-starter

git clone https://github.com/jooby-project/kotlin-gradle-starter.git
cd kotlin-gradle-starter
./gradlew joobyRun

IntelliJでrunする場合は以下の手順です、

  1. IntelliJでプロジェクト開いて、右上のGradleを選択
  2. jooby>joobyRunを起動
...
[2018-09-04 23:15:13,805]-[Hotswap] INFO  starter.kotlin.App - [dev@netty]: Server started in 3873ms

  GET  /                        [*/*]     [*/*]    (/anonymous)

listening on:
  http://localhost:8080/

まだbuild.gradleにもなにも触っていない状態ですが、http://localhost:8080/を叩くとHelloWorldされているのが確認できます

ホットリロード

実はこのjoobyRunコマンドは裏でホットリロードも動いています。対象ファイルは.classと.confと.propertiesです。静的ファイルはさすがにリロードしてくれないみたいですね

こんな感じでbuild.gradleでカスタマイズも可能みたいです

joobyRun {
  mainClassName = 'com.mycompany.App'
  compiler = 'on'
  includes = ['**/*.class', '**/*.conf', '**/*.properties']
  excludes = []
  logLevel = 'info'
  srcExtensions = [".java", ".kt", ".conf", ".properties"]
}

LL系と比べると流石に遅いですが、自動でやってくれること自体に感動しますね、

https://jooby.org/doc/devtools/#gradle-hot-reload

RESTfulAPI実装

jacksonというライブラリが公式で紹介されてます。

https://jooby.org/doc/jackson/

build.gradleにライブラリを追加しましょう

dependencies {
    compile "org.jooby:jooby-lang-kotlin"
    compile "org.jooby:jooby-netty"
    compile "io.netty:netty-transport-native-epoll:${dependencyManagement.importedProperties['netty.version']}:${osdetector.classifier.contains('linux') ? 'linux-x86_64' : ''}"
    compile "io.netty:netty-tcnative-boringssl-static:${dependencyManagement.importedProperties['boringssl.version']}:${osdetector.classifier}"
    compile "org.jooby:jooby-jackson:$joobyVersion"

    testCompile "org.jetbrains.spek:spek-api:$spekVersion"
    testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spekVersion"
    testCompile "org.junit.platform:junit-platform-launcher:$junitPlatformVersion"
    testCompile "org.amshove.kluent:kluent:1.35"
    testCompile "io.rest-assured:rest-assured:3.1.0"
}

さらに、メインクラス(App.kt)を修正しましょう

data class People(val name: String, var age: Int)

/**
 * Gradle Kotlin stater project.
 */
class App : Kooby({
+    use(Jackson())

    get {
        val name = param("name").value("Kotlin")
        "Hello $name!"
    }

+    get("/array") { req ->
+        val arr = listOf(1,2,3,4,5)
+        arr
+    }
+
+    get("/map") { req ->
+        val map = mapOf("hoge" to "fuga", "りんご" to "ごりら")
+        map
+    }
+
+    get("/data_class") {req->
+        val people = People("金田哲夫", 19)
+        people
+    }

この時、IntelliJ上でパッケージの読み込みがうまくいかず、警告が表示されるはずです。後半にハマりポイントとして解消方法を書いたので参考にして下さい

この修正でホットリロードが動いた後にブラウザで叩くとわかりますがresponseのContent-Typeがjsonになっておりなおかつ、

/listのresponseはarrayのjsonとして表示され、/mapのresponseはobjectとして表示され、/data_classのresponseはobjectとして表示されます。

Swaggerを生成

今度はこのRESTfulAPIのAPI仕様書を自動生成しましょう。

swaggerとramlの自動生成が紹介されています。

https://jooby.org/doc/apitool/#API-tool

build.gradleを修正▼

dependencies {
    compile "org.jooby:jooby-lang-kotlin"
    compile "org.jooby:jooby-netty"
    compile "io.netty:netty-transport-native-epoll:${dependencyManagement.importedProperties['netty.version']}:${osdetector.classifier.contains('linux') ? 'linux-x86_64' : ''}"
    compile "io.netty:netty-tcnative-boringssl-static:${dependencyManagement.importedProperties['boringssl.version']}:${osdetector.classifier}"
    compile "org.jooby:jooby-jackson:$joobyVersion"
+    compile "org.jooby:jooby-apitool:$joobyVersion"

    testCompile "org.jetbrains.spek:spek-api:$spekVersion"
    testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spekVersion"
    testCompile "org.junit.platform:junit-platform-launcher:$junitPlatformVersion"
    testCompile "org.amshove.kluent:kluent:1.35"
    testCompile "io.rest-assured:rest-assured:3.1.0"
}

App.ktを修正▼

/**
 * Gradle Kotlin stater project.
 */
class App : Kooby({
    use(Jackson())
+    use(ApiTool().swagger().raml())

    get {
        val name = param("name").value("Kotlin")
        "Hello $name!"
    }
...

これでホットリロードを回した後、複数のエンドポイントが自動で追加されています。

  GET  /swagger/swagger.json    [*/*]     [*/*]    (/anonymous)
  GET  /swagger/swagger.yml     [*/*]     [*/*]    (/anonymous)
  GET  /swagger/static/**       [*/*]     [*/*]    (/anonymous)
  GET  /swagger/static/**       [*/*]     [*/*]    (/anonymous)
  GET  /swagger                 [*/*]     [*/*]    (/anonymous)
  GET  /raml/api.raml           [*/*]     [*/*]    (/anonymous)
  GET  /raml/static/**          [*/*]     [*/*]    (/anonymous)
  GET  /raml     

/swaggerを叩くとこんな感じで自動生成されてます、あとは煮るやり焼くなりできますね

スクリーンショット 2018-09-04 23.51.20.png

MySQLつなぎ込み

MySQLのつなぎ込みがしたかったのでやりました、SQLは書きたくないので、Hibernateを採用しました

https://jooby.org/doc/hbm/

conf/application.confを開き、追記。

# mysql
# add or override properties
# See https://github.com/typesafehub/config/blob/master/HOCON.md for more details

+ # mysql
+ db {
+   url: "jdbc:mysql://localhost:3111/test",
+   user: "root",
+   password: "password"
+ }

dependenciesに追加。僕はmysqlですが、ドライバを変えればポスグレでもなんでもいけると思います

dependencies {
    compile "org.jooby:jooby-lang-kotlin"
    compile "org.jooby:jooby-netty"
    compile "io.netty:netty-transport-native-epoll:${dependencyManagement.importedProperties['netty.version']}:${osdetector.classifier.contains('linux') ? 'linux-x86_64' : ''}"
    compile "io.netty:netty-tcnative-boringssl-static:${dependencyManagement.importedProperties['boringssl.version']}:${osdetector.classifier}"
    compile "org.jooby:jooby-jackson:$joobyVersion"
    compile "org.jooby:jooby-apitool:$joobyVersion"
+    compile "org.jooby:jooby-jdbc:$joobyVersion"
+    compile "org.jooby:jooby-hbm:$joobyVersion"
+    compile "mysql:mysql-connector-java:5.1.47"

    testCompile "org.jetbrains.spek:spek-api:$spekVersion"
    testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spekVersion"
    testCompile "org.junit.platform:junit-platform-launcher:$junitPlatformVersion"
    testCompile "org.amshove.kluent:kluent:1.35"
    testCompile "io.rest-assured:rest-assured:3.1.0"
}

まずは、entity作成(kotlin-gradle-starter/src/main/kotlin/starter/kotlin/entity/Contact.kt)

package starter.kotlin.entity

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id


@Entity(name = "contacts")
class Contact {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private var id: Int? = null

    private val name: String? = null

    var notes: String? = null

    protected var website: String? = null

    private var starred: Int = 0

    private var password: String? = null


    fun setPassword(rawPassword: String) {
        this.password = rawPassword
    }

    fun review(isUp: Boolean) {
        if (isUp) {
            this.starred++
        } else {
            this.starred--
        }
    }
}

controllerもとりあえずメインクラス(App.kt)作成。emというのが、EntityManagerで、処理のファサードになっている

/**
 * Gradle Kotlin stater project.
 */
class App : Kooby({
    use(Jackson())
    use(ApiTool().swagger().raml())
+   use(Jdbc())
+   use(Hbm().classes(Contact::class.java))

+   get("/api/contact/") { req ->
+        require(UnitOfWork::class.java).apply { em ->
+            // 新規作成
+            val c = Contact()
+            // publicなのでOK
+            c.notes = "メモだよ!!!そのままinsert!!"
+            // privateなので
+            c.review(true)
+            c.review(true)
+            // privateなので
+            c.setPassword("ほげほげ")
+
+            // 登録
+            em.save(c)
+
+            // さらに編集
+            c.review(false)
+            c.notes = "修正済み(´・ω・`)"
+            // さらに保存
+            em.save(c)
+
+            // 一覧取得
+            em.createQuery("from contacts").resultList
+        }
+    }

    get {
        val name = param("name").value("Kotlin")
        "Hello $name!"
    }
...

DBに繋がる状態でjoobyRunしてください。起動時にmysqlへの疎通確認が走ります。と同時にcontactsテーブルが自動で生成されています!!コンパイラ型言語っぽいですよね〜

この状態で叩くと、こんな感じのresponseになると思います。(5回叩きました)

スクリーンショット 2018-09-05 0.22.17.png

各entityのプロパティがnotesしかないのは、Contactクラスのpublicなプロパティだからです。

テーブルを自動作成したり、entityクラスのアクセス修飾子によってresponse変えたりするところを見ると、php等のORMよりもさらにDBとアプリケーションが密結合になっている感じがします。

ビルド

jarファイルを出力して、jar単体で動くかテストします。

Gradle(画面右上) > build > buildからビルド

成功したら、build/libsにjarファイルが出力されてます

~/jooby
❯ java -jar ./kotlin-gradle-starter/build/libs/kotlin-gradle-starter-1.0.jar
./kotlin-gradle-starter/build/libs/kotlin-gradle-starter-1.0.jarにメイン・マニフェスト属性がありません

manifestが入っていないらしいので、build.gradleを修正。

...

jar {
    manifest {
        attributes(
                'Main-Class': "starter.kotlin.AppKt"
        )
    }
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}

特にfrom { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }この部分を忘れないよう注意してください

再ビルドしてもういちどデプロイ

❯ java -jar ./kotlin-gradle-starter/build/libs/kotlin-gradle-starter-1.0.jar
[2018-09-04 22:19:30,882]-[main] INFO  com.zaxxer.hikari.HikariDataSource -
...
  GET  /swagger/swagger.json    [*/*]     [*/*]    (/anonymous)
  GET  /swagger/swagger.yml     [*/*]     [*/*]    (/anonymous)
  GET  /swagger/static/**       [*/*]     [*/*]    (/anonymous)
  GET  /swagger/static/**       [*/*]     [*/*]    (/anonymous)
  GET  /swagger                 [*/*]     [*/*]    (/anonymous)
  GET  /raml/api.raml           [*/*]     [*/*]    (/anonymous)
  GET  /raml/static/**          [*/*]     [*/*]    (/anonymous)
  GET  /raml                    [*/*]     [*/*]    (/anonymous)
  GET  /                        [*/*]     [*/*]    (/anonymous)
  GET  /array                   [*/*]     [*/*]    (/anonymous)
  GET  /map                     [*/*]     [*/*]    (/anonymous)
  GET  /data_class/:name        [*/*]     [*/*]    (/anonymous)

listening on:
  http://localhost:8080/

キタ━━━━━━━━m9( ゚∀゚)━━━━━━━━!!

joobyの特徴の一つに、サーブレットの概念がなく、jarファイルにサーバーも含まれているため、簡単にデプロイできると書かれています。サーバーはjettty/nettyほか多数から選択できるみたいです。 こういう丸ごと入ったjarファイルをfatJarって呼ぶらしいですね。fatって悪いイメージだけどいいのか笑

https://jooby.org/doc/deployment/#deployment-intro

docker化

これが一番しんどかった、、

公式には「こんなかから適当に選んでやってみー多分できるやろ(ハナホジ」みたいな感じでgradleのプラグイン検索ページのリンクが貼っていました、どうしたらええんや、、

https://jooby.org/doc/deployment/#docker

とりあえずgradle+dockerでメジャーそうな com.palantir.docker-runを使うことにしました

これを使用できるようbuild.gradle修正

buildscript {
...
    repositories {
        mavenLocal()
        jcenter()
        mavenCentral()
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
    }
    dependencies {
        classpath "com.google.gradle:osdetector-gradle-plugin:1.4.0"
        classpath "io.spring.gradle:dependency-management-plugin:1.0.4.RELEASE"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
        classpath "org.jooby:jooby-gradle-plugin:$joobyVersion"
        classpath "org.junit.platform:junit-platform-gradle-plugin:$junitPlatformVersion"
+        classpath "gradle.plugin.com.palantir.gradle.docker:gradle-docker:0.13.0"
    }
}

...

apply plugin: "jooby"
apply plugin: "org.junit.platform.gradle.plugin"
+apply plugin: 'com.palantir.docker'

...

さらにこちらも参考にして、dockerプラグインの設定を記載。

docker {
    name "${project.group}/morimorikochan"  // 任意の名前で
    files "{フルパス}/kotlin-gradle-starter/build/libs"
    buildArgs(['JAR_FILE': 'kotlin-gradle-starter-1.0.jar'])
}

また、docker内からアクセスできるDBをconf/application.confに記載した上で以下のコマンドを叩きましょう。(docker内からDBにアクセスできないとエラーで落ちるので)

./gradlew docker

途中でエラーになるかたは後半にハマりポイントに解消方法を書いてるので参考にしてください、ぼくはこれで1時間とかしました

うまくいくと、プラグインnameプロパティで指定したイメージ名でdockerのイメージが作成されています。

~/jooby/kotlin-gradle-starter master* 8s
❯ docker images
REPOSITORY                                                             TAG                 IMAGE ID            CREATED             SIZE
starter.kotlin/morimorikochan                                          latest              688cabd7a3dc        8 minutes ago       1.01GB
<none>                                                                 <none>              eee987ddfeb1        3 hours ago         1.01GB
<none>                                                                 <none>              cc016de61c54        3 hours ago         1.01GB
...

あとはこれをrunさせれば

docker run -it --rm starter.kotlin/morimorikochan

キタ━━━━━━━━m9( ゚∀゚)━━━━━━━━!!

ハマったこと

IntelliJ上でbuild.gradleに追加した新しいパッケージで警告が出る

スクリーンショット 2018-09-01 18.38.25.png

この原因は、IntelliJ上でパッケージが認識されていないのが原因みたいです、

File > Invalidate Caches/Restartでも治りませんでしたが、Gradle(画面右上) > build setup > wrapperを実行すると読み込まれました。もっと簡単な方法がありそう :thinking:

dockerが実行できない(Cannot run program "docker": error=2, No such file or directory)

./gradlew dockerをしても、途中でCannot run program "docker": error=2, No such file or directoryとなるときがあります。

その時はターミナル上で

./gradlew --stop

をしましょう。gradleのデーモンが停止します。その上でもう一度./gradlew dockerをするとビルドできるはずです

https://github.com/Transmode/gradle-docker/issues/80#issuecomment-348476060

所管

  • Kotlin書き方が面白い
    • クセがあるので慣れるまで時間かかりそう
  • joobyは思ったより今風な感じがした
  • プラグインとして機能が提供されているので、カスタマイズが容易にできそう
  • xmlで設定しなくていいことに感動した
  • コードとか設定周りがわりとDRY
  • アノテーションも最小限でコードを追えばすぐわかるフレームワークだと思った
  • nettyの起動早すぎ
    • docker-composeでmysqlと連携させたら、joobyの疎通確認早すぎてmysqlが起動中でjoobyが死ぬ
  • 最近のFWなのでドキュメントが貧弱かと思ったけどそんなこともなかった。モジュールを使えば大体のユースケースを満たせそう!!!
  • 実務で使ってみたい!!!
    • 誰かjoinさせてください :pray:
  • Hibernateもちょっとクセが強そう
    • その分細かいとこまでさわれそう

yii2のCRUDGenerator(scaffold)を改良した件

こんばんは。運動しなさすぎてフィットネスジムに入会しようか悩んでいるmorifujiです

概要

構築手順

めんどくさかったのでローカルでやっちゃいました :sweat_smile:

前提環境

  • Mac HighSierra
  • php 7.1
  • mysql5.7
composer create-project --prefer-dist yiisoft/yii2-app-basic basic
cd basic
php yii serve

終了。はや。

localhost:8080/でアクセスできる

スクリーンショット 2018-08-30 23.59.48.png

解説

こんな感じ1

こんな感じ2

basic/                  アプリケーションのベース・パス
    composer.json       Composer によって使用される。パッケージ情報を記述
    config/             アプリケーションその他の構成情報を格納
        console.php     コンソール・アプリケーションの構成情報
        web.php         ウェブ・アプリケーションの構成情報
    commands/           コンソール・コマンドのクラスを格納
    controllers/        コントローラのクラスを格納
    models/             モデルのクラスを格納
    runtime/            実行時に Yii によって生成されるファイル (ログやキャッシュなど) を格納
    vendor/             インストールされた Composer パッケージ (Yii フレームワークそのものを含む) を格納
    views/              ビュー・ファイルを格納
    web/                アプリケーションのウェブ・ルート。ウェブ・アクセス可能なファイルを格納
        assets/         Yii によって発行されるアセット・ファイル (javascript と CSS) を格納
        index.php       アプリケーションのエントリ・スクリプト (ブートストラップ・スクリプト)
    yii                 Yii コンソール・コマンド実行スクリプト

こんな感じ3

DB接続

config/db.phpに以下の通り設定。

dsnってポートはport=3111;こう書くんですね

return [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=127.0.0.1;port=3111;dbname=hogehoge',
    'username' => 'root',
    'password' => 'password',
    'charset' => 'utf8'
];

出典:https://www.yiiframework.com/doc/guide/2.0/ja/start-workflow

gii

yii2のscaffoldツールはgiiと呼ばれているらしい

https://www.yiiframework.com/doc/guide/2.0/ja/start-gii

実はすでにインストールされていて、/index.php?r=giiを叩くとメニュー出てきた、思ったより多機能 :astonished:

スクリーンショット 2018-08-31 0.01.05.png

基本的にはこの2つで事足りる - Model Generator - CRUD Generator

Model Generator

DBに定義しているテーブルからモデルクラスを自動出力してくれる

この2つだけ記載すればあとはよしなにやってくれる。

  • Table Name
  • Model Class Name

TableNameに入力すると予測変換が出てきて少し感動。続けてキャメルケースでModelClassNameを書く。

最後にPreviewを押す。するとどんなファイルが出力されるか表示される。Generateで出力する

スクリーンショット 2018-08-31 0.07.01.png

CRUD Generator

ModelGeneraterによって出力されたModelを元に自動でCRUDのページ・ルーティング、果てはフォームも自動生成する

この3つを設定

  • ModelClass
  • SearchClass
  • ControllerClass

ModelClass

すでにあげたディレクトリ構成に従うとapp\models\{キャメルケース}となる

SearchClass

ドキュメントでは、すでにあげたディレクトリ構成に従い、suffixをSearchとしてapp\models\{キャメルケース}Searchとしている

ControllerClass

すでにあげたディレクトリ構成に従うとapp\controllers\{キャメルケース}Controllerとなる

スクリーンショット 2018-08-31 0.13.23.png

同じくPreviewを押してGenerate

スクリーンショット 2018-08-31 0.13.31.png

できた!

実際の画面は?

/index.php?r={モデル名}で表示されます

検索画面

スクリーンショット 2018-08-31 0.15.10.png

ご丁寧に、画面に表示できないプロパティはコメントアウトしてくれている。こういうところ嬉しいですよね :cry:

    <?php $form = ActiveForm::begin([
        'action' => ['index'],
        'method' => 'get',
    ]); ?>

    <?= $form->field($model, 'id') ?>

    <?= $form->field($model, 'email') ?>

    <?= $form->field($model, 'password') ?>

    <?= $form->field($model, 'nearest_station_id') ?>

    <?= $form->field($model, 'frequently_drinking_station_id') ?>

    <?php // echo $form->field($model, 'sex') ?>

    <?php // echo $form->field($model, 'payment_customer_token') ?>

    <?php // echo $form->field($model, 'payment_subscription_token') ?>

    <?php // echo $form->field($model, 'date_of_birth') ?>

    <?php // echo $form->field($model, 'name') ?>

    <?php // echo $form->field($model, 'role') ?>

    <?php // echo $form->field($model, 'franchise_id') ?>

    <?php // echo $form->field($model, 'corporation_id') ?>

    <?php // echo $form->field($model, 'encrypted_id') ?>

    <?php // echo $form->field($model, 'created_at') ?>

    <?php // echo $form->field($model, 'updated_at') ?>

    <?php // echo $form->field($model, 'deleted_at') ?>

    <div class="form-group">

詳細画面

So simple

スクリーンショット 2018-08-31 0.19.58.png

登録画面・更新画面

スクリーンショット 2018-08-31 0.23.30.png スクリーンショット 2018-08-31 0.23.40.png

特徴としてはこんな感じ。

  • パスワードがシークレットな表示 (画像でいうPassword)
  • enumで定義したカラムはドロップダウン表示 (画像でいうRole)
  • date型・time型は文字列として表示 (画像でいうDate Of Birth)

本題

date型・time型は文字列として表示ここがおしい。ここもきちんとdatepicker/timepicker出してくれたらわりと活躍するかなあと思ってましたが残念すぎる、。。。

ということで、この部分だけサクッとカスタマイズしましょう

テンプレートファイル編集

https://www.yiiframework.com/extension/yiisoft/yii2-gii/doc/guide/2.1/en/topics-creating-your-own-templates

これにしたがって、giiのscaffold出力ロジックをいじいじした。

結果、ここでいじいじしてるみたい。


  /**
     * Generates code for active field
     * @param string $attribute
     * @return string
     */
    public function generateActiveField($attribute)
    {
        $tableSchema = $this->getTableSchema();
        if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) {
            if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) {
                return "\$form->field(\$model, '$attribute')->passwordInput()";
            }

            return "\$form->field(\$model, '$attribute')";
        }
        $column = $tableSchema->columns[$attribute];
        if ($column->phpType === 'boolean') {
            return "\$form->field(\$model, '$attribute')->checkbox()";
        }

        if ($column->type === 'text') {
            return "\$form->field(\$model, '$attribute')->textarea(['rows' => 6])";
        }

        if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) {
            $input = 'passwordInput';
        } else {
            $input = 'textInput';
        }

        if (is_array($column->enumValues) && count($column->enumValues) > 0) {
            $dropDownOptions = [];
            foreach ($column->enumValues as $enumValue) {
                $dropDownOptions[$enumValue] = Inflector::humanize($enumValue);
            }
            return "\$form->field(\$model, '$attribute')->dropDownList("
                . preg_replace("/\n\s*/", ' ', VarDumper::export($dropDownOptions)) . ", ['prompt' => ''])";
        }

        if ($column->phpType !== 'string' || $column->size === null) {
            return "\$form->field(\$model, '$attribute')->$input()";
        }

        return "\$form->field(\$model, '$attribute')->$input(['maxlength' => true])";
    }

なるほど。カラム名がpasswordとかpasswdとかならシークレットな表示にするのか笑

            return "\$form->field(\$model, '$attribute')->$input()";

日付型・時間型はここに引っかかっているらしい

となるとformを出力する必要があるので、Extensionから検索。だれか作ってるでしょ

datetimepickerプラグイン

あった。総ダウンロード数19000ぐらい、普通の感覚では少し不安かもしれないけど、他のextensionは一桁台ダウンロード数だったのでこのextensionの有用さがうかがえる

https://www.yiiframework.com/extension/zhuravljov/yii2-datetime-widgets

composerに追加

composer require zhuravljov/yii2-datetime-widgets

先ほどのGenerator.phpを編集

        if ($column->type === 'date') {
            return "\$form->field(\$model, '$attribute')->widget(\zhuravljov\yii\widgets\DatePicker::class, [
    'clientOptions' => [
        'format' => 'yyyy-mm-dd',
        'language' => 'ja',
        'autoclose' => true,
        'todayHighlight' => true,
    ],
    'clientEvents' => [],
])";
        }


        if ($column->type === 'time') {
            return "\$form->field(\$model, '$attribute')->widget(\zhuravljov\yii\widgets\DateTimePicker::class, [
    'clientOptions' => [
        'format' => 'hh:ii:00',
        'language' => 'ja',
        'autoclose' => true
    ]
])";
        }

        if (in_array($column->type, ["timestamp", "datetime"])) {
            return "\$form->field(\$model, '$attribute')->widget(\zhuravljov\yii\widgets\DateTimePicker::class, [
    'clientOptions' => [
        'format' => 'yyyy-mm-dd hh:ii:00',
        'language' => 'ja',
        'autoclose' => true
    ]
])";
        }

結果

もう一度CRUDGeneratorを使う

スクリーンショット 2018-08-31 0.38.00.png

このとき、overrideにチェックを入れてGenerate

そして登録画面・編集画面へ http://localhost:8080/index.php?r=users%2Fcreate

aaaaaa.gif

キタ━━━━━━━━m9( ゚∀゚)━━━━━━━━!!

所感

  • 普段使っていないfwは面白い
  • しかもscaffold使うとガシガシできて気持ちいい。
  • コミュニティは活発なようだがextensionの荒廃している感が否めない
  • 3コマンドで環境構築できるとか強すぎひん?
  • 地味にプロファイリングツールもついてたww

スクリーンショット 2018-08-31 0.50.16.png

RaspberryPiにAlexaをインストールしてAmazonMusicを流そうとした件

こんばんは。ボルダリングで腕がパンパンmorifujiです。

今日は久しぶりにRaspoberryPiを押入れから引っ張り出して、Alexaさんを憑依させてAmazonMusicを流させようとしたので知見を共有します。なんか憑依っていうと◯川隆法みたいですね

目次

  1. amazonで開発者アカウント作成・アプリケーション登録
  2. スピーカー・マイク準備
  3. バイス登録
  4. 初期設定

前提となる環境

  • RaspberryPiが起動している
  • RaspberryPiに接続できるスピーカーを持っている
  • RaspberryPiに接続できるマイクを持っている

ちなみにぼくはこういう環境です。

1. amazonで開発者アカウント作成・アプリケーション登録

まずこちらから。https://developer.amazon.com/ でアカウントを作成してください。

今回僕は日本のamazonのアカウントでAmazonPrimeに入会しているので、そのアカウントのemail/passで登録しました。

:warning: amazon.comamazon.co.jp複数アカウントを持つ人は、どのアカウントを使用するか注意してください。まじでハマります。

https://dev.classmethod.jp/voice-assistant/solution-of-a-problem-amazon-com-account-conflict/

まず、トップ右上のDeveloper Consoleに飛びます スクリーンショット 2018-07-08 20.42.31.png

次に、AlexaVoiceServiceに飛びます

スクリーンショット 2018-07-08 20.43.03.png

製品を作成するに飛びます、

項目 入力内容
製品名 任意の製品名です、後から参照しないのでなんでもok。ぼくはraspberry_alexaにしました
製品ID 任意の製品IDです。後から参照するのでメモを忘れず
製品はアプリやデバイスを使用しますか ? 必ずアプリケーションを選択。はまりますので :warning:
商品カテゴリ なんでもok,ぼくはワイヤレススピーカを選びました
製品概要 参照しないのでなんでもok。ぼくはalexa on raspberrypiって書きました
エンドユーザーは、商品とどのようにやり取りするのでしょうか ? ハンズフリーとファーフィールドを選択
画像 しなくてもよい
この製品を商品として配信する予定ですか ? プライベート用なので no
これは子供向け商品、それ以外は13歳以下の子供向けですか ? よくわからんのでnoを選択

これで次へを選択

セキュリティプロファイルを選択する

初めての人はセキュリティプロファイルを作成してから、それを商品に登録します。特に難しいことないです

プラットフォーム情報

プラットフォームを作成する必要があります。他のデバイスやプラットフォームを選択し Downloadボタンからバックアップを取っておきましょう。

スクリーンショット 2018-07-08 20.54.49.png

:thinking:ちなみにこの一般IDってgenerate idって意味なのだろうか :thinking:

スクリーンショット 2018-07-08 20.55.10.png

そのファイルには以下の様なフォーマットでデータが入っています、必ず保管しましょう

{
 "deviceInfo": {
  "clientId": "amzn1.application-oa2-client.XXXXXXXXXXXXX",
  "productId": "raspberry_alexa"
 }
}

規約にチェックをつけて進むと完了するはずです。

2. スピーカー・マイク準備

RaspberryPiのssh環境を用意します。

まずはスピーカー。つなぐ前にスピーカーの状況を確認

# スピーカーデバイス一覧表示
aplay -L

スピーカーをつなぎもう一度実行すると項目が増えているはずです。

そしたら出力確認です

# スピーカーテスト
speaker-test -t sine -f 1000

音が聞こえなかったら徐々に音量あげましょう。

amixer sset PCM 100% 

次にマイクです。同じ様にデバイスとして認識されてるかチェックします

# マイクデバイス一覧
arecord -l 

んで。テスト。今回は、マイクで入力された音声がスピーカーから出力されるか確認します。

arecord -f S16_LE -r 44100 -D hw:1 | aplay

おうむ返しみたいな感じになりましたか?okなら次に行きましょう!

3. デバイス登録

いよいよラズパイにAlexaをインストールしていきます。

今回使うライブラリは こちらのalexa/avs-device-sdkです

ライブラリ選定にはこちらの記事が非常にわかりやすく書かれていました。ありがとうございます ☺️

https://qiita.com/Dimeiza/items/182c4847d7c1ead7df54

# ディレクトリ作成
mkdir ~/raspberry_alexa
cd ~/raspberry_alexa

# 環境構築スクリプトインストール
sudo wget https://raw.githubusercontent.com/alexa/avs-device-sdk/master/tools/Install/setup.sh && sudo wget https://raw.githubusercontent.com/alexa/avs-device-sdk/master/tools/Install/config.txt && sudo wget https://raw.githubusercontent.com/alexa/avs-device-sdk/master/tools/Install/pi.sh

ここで、config.txtというファイルを開くとこんな感じ

#NOTE: The Device Serial Number can be any unique number
DEVICE_SERIAL_NUMBER="alexa_raspberry"
CLIENT_ID=""
PRODUCT_ID=""

先ほど、1でダウンロードしたファイルに書いてあるclient_idproduct_idをここに入力します、。DEVICE_SERIAL_NUMBERは適当でok

{
 "deviceInfo": {
  "clientId": "amzn1.application-oa2-client.XXXXXXXXXXXXX",
  "productId": "raspberry_alexa"
 }
}

この設定を読み込んでセットアップします

sudo bash setup.sh config.txt

ビルドするんでめっちゃ時間かかります。30~60分ほどの感じです。。。次に、サンプルアプリケーションを実行します。

sudo bash startsample.sh

これまた時間がかかりますが、すばらくすると以下の様にブラウザからの承認待ちの状態になります。

################################################################################################
#       To authorize, browse to: 'https://amazon.com/us/code' and enter the code: B8DEA5       #
################################################################################################

2018-07-08 11:17:10.405 [  2] 5 CBLAuthDelegate:handleRequestingToken
#################################################
#       Checking for authorization (1)...       #
#################################################

2018-07-08 11:17:10.407 [  2] 5 CBLAuthDelegate:requestToken
2018-07-08 11:17:10.923 [  2] 5 HttpPost:doPostSucceeded:code=400
2018-07-08 11:17:10.923 [  2] 5 CBLAuthDelegate:receiveTokenResponse:code=400
2018-07-08 11:17:10.924 [  2] 5 CBLAuthDelegate:mapHTTPStatusToError:code=400,error=INVALID_REQUEST
2018-07-08 11:17:10.925 [  2] 5 CBLAuthDelegate:errorInLwaResponseBody:error=authorization_pending,errorCode=AUTHORIZATION_PENDING
2018-07-08 11:17:10.925 [  2] 5 CBLAuthDelegate:setAuthError:authError=AUTHORIZATION_PENDING
2018-07-08 11:17:10.926 [  2] 0 CBLAuthDelegate:receiveTokenResponseFailed:result=AUTHORIZATION_PENDING
#################################################
#       Checking for authorization (2)...       #
#################################################

2018-07-08 11:17:15.927 [  2] 5 CBLAuthDelegate:requestToken
2018-07-08 11:17:16.349 [  2] 5 HttpPost:doPostSucceeded:code=400
2018-07-08 11:17:16.349 [  2] 5 CBLAuthDelegate:receiveTokenResponse:code=400
2018-07-08 11:17:16.349 [  2] 5 CBLAuthDelegate:mapHTTPStatusToError:code=400,error=INVALID_REQUEST
2018-07-08 11:17:16.350 [  2] 5 CBLAuthDelegate:errorInLwaResponseBody:error=authorization_pending,errorCode=AUTHORIZATION_PENDING
2018-07-08 11:17:16.350 [  2] 5 CBLAuthDelegate:setAuthError:authError=AUTHORIZATION_PENDING
2018-07-08 11:17:16.350 [  2] 0 CBLAuthDelegate:receiveTokenResponseFailed:result=AUTHORIZATION_PENDING
2018-07-08 11:17:21.351 [  2] 5 CBLAuthDelegate:requestToken
#################################################
#       Checking for authorization (3)...       #
#################################################

4,5,6と続く...

この状態で、https://amazon.com/us/codeにアクセスします。日本のamazon(amazon.co.jp)のアカウントでもこちらに飛んでください。

  1. コードを入力します。下の例ではB8DEA5です。
  2. 次に進んでアカウントを確認します。ここで、必ず最初にdeveloperに登録したアカウントになっているか必ず確認してください。
  3. 地域は日本に
  4. 規約等にokをしていくと無事に登録できます、

途中で画面が進んでWoopsみたいなこと言われた場合、考えられる原因は2つです。

  • 1つめは、アカウントを間違えている可能性です。上の手順2で必ず確認してください、違った場合は右上からサインアウトしてもう一度チャレンジしてください。(もしくは、この問題かも)
  • 2つめは、 config.txtを間違えている可能性です。config.txtを修正した場合は、sudo bash setup.sh config.txtからやり直してください。

ブラウザでsucceedと表示された場合は成功しました!。sshに戻ってください。

########################################
#       Alexa is currently idle!       #
########################################

と、どこかで出力されているはずです!これでRaspberryPiとalexaアプリケーションが紐づけられました!

この状態ではすでにAlexaはあなたの言葉を待っています。

アメリカンな口調で Alexa how about you?と聞いてみましょう。きっと答えてくれるはずです

4. 初期設定

この状態だと英語の勉強にはなりますが、日本語を喋ってもらいましょう。

言語設定

iを押してEnterでコマンド情報が見れます。

+----------------------------------------------------------------------------+
|                                  Options:                                  |
| Wake word:                                                                 |
|       Simply say Alexa and begin your query.                               |
| Tap to talk:                                                               |
|       Press 't' and Enter followed by your query (no need for the 'Alexa').|
| Hold to talk:                                                              |
|       Press 'h' followed by Enter to simulate holding a button.            |
|       Then say your query (no need for the 'Alexa').                       |
|       Press 'h' followed by Enter to simulate releasing a button.          |
| Stop an interaction:                                                       |
|       Press 's' and Enter to stop an ongoing interaction.                  |
| Privacy mode (microphone off):                                             |
|       Press 'm' and Enter to turn on and off the microphone.               |
| Echo Spatial Perception (ESP): This is for testing purpose only!           |
|       Press 'e' followed by Enter at any time to adjust ESP settings.      |
| Playback Controls:                                                         |
|       Press '1' for a 'PLAY' button press.                                 |
|       Press '2' for a 'PAUSE' button press.                                |
|       Press '3' for a 'NEXT' button press.                                 |
|       Press '4' for a 'PREVIOUS' button press.                             |
| Settings:                                                                  |
|       Press 'c' followed by Enter at any time to see the settings screen.  |
| Speaker Control:                                                           |
|       Press 'p' followed by Enter at any time to adjust speaker settings.  |
| Firmware Version:                                                          |
|       Press 'f' followed by Enter at any time to report a different        |
|       firmware version.                                                    |
| Info:                                                                      |
|       Press 'i' followed by Enter at any time to see the help screen.      |
| Reset device:                                                              |
|       Press 'k' followed by Enter at any time to reset your device. This   |
|       will erase any data stored in the device and you will have to        |
|       re-register your device.                                             |
|       This option will also exit the application.                          |
| Quit:                                                                      |
|       Press 'q' followed by Enter at any time to quit the application.     |
+----------------------------------------------------------------------------+
  1. c押してEnter
  2. 1押してEnter
  3. 6を押してEnter

すると以下の様に日本語になりました!

###################################
#       locale set to ja-JP       #
###################################

この状態で Alexa 明日の天気は!?というときちんと日本語で返してくれます!

ですが、いくらAlexa! Alexa! A・L・E・X・A・!といっても反応がありませんでした。。中華製マイクだしこんなもんですかね :cry:

こういうときは、tを押しましょう。呼びかけをする必要なく、スマートに明日の天気は?と聞くことができました(スマートとはいっていない)

その他環境設定

http://alexa.amazon.com/または https://alexa.amazon.co.jp(アカウントによる??)にアクセスし、アカウント情報を入力すると、現在つないでいるAlexaの設定や命令をブラウザ上で行えます。

スクリーンショット 2018-07-08 21.30.38.png

ぼくはAmazonPrime会員なので、ここにAmazpnMusicが自動でインポートされていました!!感動!!

スクリーンショット 2018-07-08 21.34.13.png

ということは、Alexaに喋りかけて、ぼくの珠玉のプレイリストをかけることもできるはず!と確信したので呼びかけました

ぼく:アレクサ 音楽を流して

Alexa:この端末ではAmazonMusicに対応しておりません

ぼく:😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢😢

ぼく:なんでや!管理画面からはAmazonPrimeの音楽流せたやん!!!

スクリーンショット 2018-07-08 21.50.50.png

ぼく:👊👊👊👊👊

所管

  • すごい便利
    • 一時期ゆっくりさんで、目覚ましアプリをつくってた時期を思い出した
    • それよりはるかに高機能すぎて嬉しい
  • スキル選び楽しい ヽ(゚∀゚ヽ 三 ノ゚∀゚)ノ
  • 自動起動の設定はまた今度かな
  • AmazonMusicの件に関しては金の亡者だと思った。
    • alexaの仕様よく読めよとか言わないでください
  • かわりにradikoを召喚した(代わりになってない)
  • いいマイク買ったほうがいい
    • 5000円くらいの買って試してみる予定
    • だれかうまくいった機種教えてください!!!
    • 試したら追記します

参考サイト

https://github.com/alexa/avs-device-sdk https://github.com/alexa/avs-device-sdk/wiki/Raspberry-Pi-Quick-Start-Guide-with-Script https://qiita.com/Dimeiza/items/182c4847d7c1ead7df54 http://blog.saboh.net/raspberrypiusbmaiku/ https://dev.classmethod.jp/voice-assistant/solution-of-a-problem-amazon-com-account-conflict/

RaspberryPiでのマイク・スピーカーの動作確認チートシート

# 音量変更
amixer sset PCM 100% 

# スピーカーテスト
speaker-test -t sine -f 1000

# マイクデバイス優先順位確認
arecord -l 

# マイクテスト
arecord -f S16_LE -r 44100 -D hw:1 | aplay