non vorrei lavorare

2020年度からの小学校プログラミング教育の必修化を親として迎えるブロガーの書く、子供との日常

2019年のAmazonのサイバーマンデーで買ったもの

こんばんは。週末はこのところ、弱ペダに影響された子供たちとサイクリングに出かけています。弱ペダ効果wで、これまでは、スピード出せ!と言うシーンが多かったのですが、ようやく、スピード出すな!と注意する事が多くなりました。@kjunichiです。

自転車関連

バイクパッキングでせいぜいデイキャンプが目標なのに、手のひらに乗っかってる写真に釣られて、買ったものの、 現物はかなり大き買った。夏場、狭い寝室から脱出して、廊下で寝るのにちょうど良さそうなマットになるかも?

ドッペルギャンガーのサドルバッグにはAPIDURAなどに付いている、バッグの上部のネット?が付いてないので、 その代用になりそうなので試しに購入。

後方の子供達の様子を確認できるかもと思いこちらも試しの購入。フロントフォークにつけてみたが、 ステムバッグの影になってしまい、緩い取り付けになってしまうものの、ハンドルバーに設置。

ハンドルバッグを弱ペダに影響され、競技用自転車にはハンドルバッグなんて付いてないじゃないか!と 次男がハンドルバッグをやめてしまし、夏場のドリンクホルダーをこれで設置できないかと購入。

サドルバッグの中に入れる一番外側のバッグとして購入。

実家の車に積んで旅行に行けないかととりあえず、1台分購入。

ツールは持ち歩いていたが、肝心のパンク修理キットをようやく購入。

立て続けにキャットアイのセイフティーランプを落としてしまったので、もう安物でいいやw。 電池の持ちが気になるくらい、非常に明るいw。

クロスバイク は工具はボトルケージに入れておく運用にすることに。

R250のステムバッグよりコスパの良いものを試して見るのと、インナーバッグを小物入れに流用する為に。

子供達の傷の手当て用の消毒液とバンドエイドの入れ物としても購入。

生活用品

関連記事

インドア派の俺がバイクパッキングを試行錯誤し始めた記録

背景

500万オーバーのスポーツセダンから100分の1の5万円台のCannondaleのクロスバイクQuick7 にダウングレードしてしまったw

同時期にフロントディレーラーが壊れてしまった20年以上前に買ったGTのMTBを購入したクロスバイク のカスタマイズ しようとシマノのパーツを物色していたら、適合しそうなパーツを見つけて、修理できてしまって、2台体制に。

そのおかげで、MTBクロスバイクの2台になり、家族でサイクリングする時は奥さんがクロスバイクで出かけるようになった。

夏場のソロ活動でのリュックフリーと家族でサイクリングに行く際に荷物運び役で活躍したい

ソロで早朝多摩川に行ったりする際は、クロスバイクモンベル のサイクールパック20を使っているが、夏場は背中の汗が気になりすぎるので、 大き目なサドルバッグを付けたいが、160cmと低身長でおまけに短足なので、700Cだとサドルがあまり上げられず、 スペースがきつい。

そんな厳しい制約でもドッペルギャンガーのサドルバッグはギリギリ使えた。

子供はよく転ぶので、バンドエイドと傷口の消毒液を入れるバッグ代わりに以下の救急セットを常備することにした。

山やキャンプに行く習慣がないので、バイクパッキングを機にスタッフバッグを購入

R250のツールボトルが良さげと聞き、ボトルケージに装備することに

飲み物はR250のステムバッグを導入することに

Cannondale quick7のバイクパッキング完成図

f:id:kjw_junichi:20191219213930j:plain

1994年モデルのGTのbackwoodsのバイクパッキング

GTの独特なフレームなのと、小柄なのでSサイズのフレームで、フレームバッグは絶望的。

今度もドッペルギャンガーが強い見方をしてくれた。

ちょっとペダリング時に荷物と干渉するけど、まぁ、家族でゆるポタなので、それほど気にしない。

ボトルケージの代わりにこちらもステムバッグを装着

R250のステムバッグに比べると安っぽいですが、ドリンクボトルサイズのスタッフバッグ付きでコスパは最高。

MTBのバイクパッキング

f:id:kjw_junichi:20191130152531j:plain

関連記事

node-ffi-napiでPythonを動かして、PyObjC経由でCocoa APIを叩いて結果を別スレッドでHTTPで受け取った

@kjunichiです。この記事はQiita Node.js Advent Calendar 2019 6日の記事です。

背景

たまーに、流行のGitHub見て転職案内してくれるサイトに登録してるので、 KeyCast.js見ました!なんてメールをいただくことがありますが。 AIからのメールなのはバレバレでもメンタル豆腐なので、この数年メンテしておらず心苦しくになることがあるので、 重い腰を上げて対応しようとしたら、NodObjCが不安定すぎて、別の方法を模索している。

ってのと、Keycast.jsが以前のアドカレがきっかけで作成したので、新ネタ 思いつかなかったというのもある。

Node.jsで直接Cocoaを扱えなくなった?!

node-ffiはここ数年更新がされていない状態が続いている。

NAPIに対応した node-ffi-napiが作られている。これはNode.jsにスレッドを入れた方で、Node学園祭でもスピーカで 来日された @addaleaxさんがメンテしている。

しかし、こちらのNAPI版のffi-napiではNodObjCをサポートしないっぽい。以下のISSUE参照。

ということで、今回は、Cocoa APIPythonのライブラリであるPyObjCから叩くことにした。 PyObjCは開発が活発なようで今年もアップデートされているのを観測済み。

HomebrewなPythonへの対応

dylibではなく、.framework形式で共有ライブラリがインストールされるので、node-ffi-napiで通常の処理ではアクセスできない。

node-ffi-napiのコードを眺めて以下のようにすれば解決した。

const handle = ffi.DynamicLibrary('/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/Python')
const libpython={}
libpython.Py_SetProgramName = ffi.ForeignFunction(
    handle.get('Py_SetProgramName'),
    'void',
    [voidPtr]
)

ffi.DynamicLibraryにてFrameworkの中身?を指定して、そのハンドルを取得して、 取得したハンドルに対してgetメソッドで個々のframework内のシンボル名を指定することでCで言うところの関数ポインタが 取得できて、これを使って、ffi.ForeiginFuncionでJSでの関数を取り出す事ができる。

Pythonからの結果を受け取るのがめんどくさい問題

Node.jsからnode-ffi-napiを経由してPythonを経由してPyObjCMacOS側からキーイベントを受け取ることはできるが、 これを受取には、Node.js側で、node-ffi-napiを経由してコールバック関数をPython側に渡す必要が出てくる。

これ、できなくはないが、めんどくさい。コールバック関数の型定義が複雑(個人の感想です)

ところで、PythonもNode.jsもhttp通信が追加モジュールなしで行える!

これだ! Python側で取得したキーイベントをJSONでNode.js側にPOSTすれば解決だ。

HTTPクライアントとHTTPサーバーを一つのプログラムで動かすには

シングルプロセスで、シングルスレッドではNode.jsからPythonを呼び出して、Python側でHTTPを呼び出しても、 Node.js側が受信待ち状態になってないので、アウト!という落とし穴が待ち受けていたw

しかし、この問題の対策として、Node.js側でスレッドを追加してマルチスレッドにすることで対応できる!

// HTTPサーバーを別スレッドで動かす
    worker = new Worker(path.join(__dirname, "../httpserver.js"), { workerData: 0 })
    worker.on('message', (msg) => {
      //console.log(`${msg}`)
      event.reply('nsevent', msg)
    })

    // Pythonを呼び出す
    const voidPtr = ref.refType(ref.types.void);
    const handle = ffi.DynamicLibrary('/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/Python')

    libpython.Py_SetProgramName = ffi.ForeignFunction(
      handle.get('Py_SetProgramName'),
      'void',
      [voidPtr]
    )
    libpython.Py_Initialize = ffi.ForeignFunction(
      handle.get('Py_Initialize'),
      'void',
      []
    )
    libpython.PyRun_SimpleString = ffi.ForeignFunction(
      handle.get('PyRun_SimpleString'),
      'int',
      ['string']
    )
    libpython.Py_Finalize = ffi.ForeignFunction(
      handle.get('Py_Finalize'),
      'void',
      []
    )

    libpython.Py_SetProgramName(null)
    libpython.Py_Initialize()
    const pyscript = fs.readFileSync(path.join(__dirname, 'api.py'))

    libpython.PyRun_SimpleString(pyscript)   

httpserver.js

// server.js
// worker thread
const { parentPort } = require('worker_threads')
const http = require('http')

const server = http.createServer()

server.on('request', (req, res) => {

    if (req.method === 'POST') {
        var data = ''

        //POSTデータを受けとる
        req.on('data', (chunk) => { data += chunk })
            .on('end', () => {
                res.writeHead(200, { 'Content-Type': 'text/plain' })
                res.write('hello world')
                res.end()
                
                parentPort.postMessage(data)
            })

    } 

    //console.dir(req)

})

server.listen(3000)

server.on('listening', () => {
    // ここで random port が取れる
    const port = server.address().port
    console.log(`port = ${port}`)
    parentPort.postMessage(`port = ${port}`)
})

parentPort.on('message', (msg) => {
    process.exit()
})

Cocoa APIの罠

前述の一連の方法を素のnode.jsで動かそうとするとCocoa APIの呼び出しで

(スクショ撮り忘れてるので、後日貼ります。。)

というようにmacOSからダメだしされる。

electronで動かすことで対処できる。おそらくmacOSのアプリとして必要なAPIの呼び出しをelectronが済ませてくれるからと 思われる。

また、コマンドラインから動かす場合、使っているターミナルソフト(標準ならTerminal.app)にコントロールパネルの セキュリティ設定でアクセス許可を与える必要がある。

electronをツールなりでパッケージ化して.appにして動かす場合は、.appを一度起動して、同様にコントロールパネルの セキュリティ設定でアクセス許可を与える必要がある。

f:id:kjw_junichi:20191206064704p:plain

electronをパッケージ化して発覚した問題

どうもworkerを作る際にasar形式の中のファイルにアクセスできないっぽい。

対応策としては、.appの中のResourceにworkerで読み込む.jsファイルを格納して物理的に独立したファイルとして 用意。

const builder = require('electron-builder')
const Platform = builder.Platform
const fs = require('fs')

builder.build({
    targets: Platform.MAC.createTarget(),
    config: {
        'appId': 'com.hatenablog.abrakatabura.keycastjs',
        'target': 'dir' 
    }
}).then(()=>{
    fs.copyFileSync('./httpserver.js','dist/mac/keycastjsdev.app/Contents/Resources/httpserver.js')
}).catch((e) =>{
    console.log(e)
})

実行結果

ごちゃごちゃ書いてもコードが動かないと説得力ないので、動画を上げました。

www.youtube.com

成果物

ごめんなさい、Keycast.jsにこれらの成果を反映させた版は担当の日までに間に合いませんでした。。 出来れば、年内に、Githubに上げたいです。

実験用リポジトリを晒しておきます。

github.com

git clone 

cd keycastjsdev
npm install
node build.js
open dist/mac/keycastjsdev.app

まとめ

  • Node.jsでマルチスレッドを使った。
  • プログラム内でHTTP通信することで、割と疎結合PythonとNode.jsをつなぐことが出来た。
  • Node.jsの12系でnpm installしたネイティブモジュールはElectron v7系でもそのまま動くことが分かった。
  • Electronをアプリ化してスレッドを使う際は.jsファイルをasarの外に置く必要がありそう。
  • PythonでのJSONをPOSTする方法が学べた。
  • Node.jsでのPOSTされたJSONを受取る方法が学べた。

関連記事

9年前の記事

5年前の記事

3年前の記事

2年前の記事

1年前の記事