non vorrei lavorare

昔はおもにプログラミングやガジェット系、今は?

NAPI版のnode-ffi-napiを使ってNode.jsからPythonを動かす

こんばんは。夜遅くまで子供達が起きてしまい、朝叩き起こしてもなかなか起きない悩みが一向に解決しない@kjunichiです。

背景

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

NodObjCの代わりにPyObjcを使ってみるための準備

NodObjCがN-API版のnode-ffi-napiではサポートされていない模様。 しかし、node-ffi-napiから直接libobjcを叩く際にはBlocksを引数にとるCocoa APIの扱いが不明なので、 PyObjCを使って回避を試みる。

ffi-napiを使ってNode.jsからPythonを動かした

const ffi = require('ffi-napi')
const ref = require('ref-napi')

const voidPtr = ref.refType(ref.types.void);

const libpython2 = ffi.Library('libpython2.7', {
'Py_SetProgramName': [ 'void',  [voidPtr] ],
'Py_Initialize': [ 'void',  [] ],
'PyRun_SimpleString': [ 'int',  ['string'] ],
'Py_Finalize': [ 'void',  [] ]
})

libpython2.Py_SetProgramName(null)
libpython2.Py_Initialize()
const pyscript = `print("Hello world!\\n")`
libpython2.PyRun_SimpleString(pyscript)
libpython2.Py_Finalize()

Node.jsでスレッドを作り、Pythonを動かす

Node.jsとPythonとのやり取りはHTTP越しで行うことを考えている。 Node.jsに限らず、FFIスクリプト系言語で行った方ならCのヘッダの関数や構造体の型定義をちまちま書く作業の 手間を想像することはたやすいかと思われる。

Node.js側でPythonスクリプトを起動するだけして、その後のやり取りはHTTP越しに行うことにすれば、Pythonの処理系にPIPで 追加モジュールのインストールの必要もなく、Node.js側も標準のモジュールで通信が行える。

ワーカ用のJavaScriptコード

const ffi = require('ffi-napi')
const ref = require('ref-napi')

const voidPtr = ref.refType(ref.types.void);

const libpython2 = ffi.Library('libpython2.7', {
'Py_SetProgramName': [ 'void',  [voidPtr] ],
'Py_Initialize': [ 'void',  [] ],
'PyRun_SimpleString': [ 'int',  ['string'] ],
'Py_Finalize': [ 'void',  [] ]
})

libpython2.Py_SetProgramName(null)
libpython2.Py_Initialize()
const pyscript = `str=raw_input()\nprint("Hello world!\\n")`
libpython2.PyRun_SimpleString(pyscript)
libpython2.Py_Finalize()

メインスレッド用のJavaScriptコード

const { Worker, isMainThread, workerData } = require('worker_threads')
const { resolve } = require('path')

let current = 0

const counter = (title, cnt) => {
    console.log(`| ${title} |: ${cnt}`)
}

if (isMainThread) {
    console.log('Main Thread')

        for (let i = 0; i < 1; i++) {
            // 相対パスはダメ
            // この場合(__filename)はworkerも同じこのファイルを参照する
            // 第二引数はグローバルパラメーター
            new Worker(resolve("python.js"), { workerData: i })
        }

    setInterval((title) => {
            counter(title, ++current)
            }, 1000, 'MainThread')
} else {
    console.log(`worker: ${workerData}`)

        setInterval((title) => {
                counter(title, ++current)
                }, 1000, `worker: ${workerData}`)
}

実行結果

youtu.be

おわりに

背景で触れたようにCocoa専用なので、今度のCatalinaではPythonが標準で入らなくなる模様。 しかしながら、Node.jsはいまでも確かPython2系が必要なはずで、今回のアプローチでも Catalina環境でもNode.jsを利用する場合、Pythonを入れる必要があり、無駄にはならないと思われる。

参考資料

blog.hiroppy.me

関連記事

8年前の記事

5年前の記事

go modulesでSocket.ioからWebSocketに移行せずに済んだ件

こんにちは。次男も来年からは夏休みに宿題がありますが、奥さんが、前年の長男の自由研究を行えば、良いじゃね?って言ってます。そんな訳で、長男はあとは大きな課題は交通安全ポスターです。あおり運転ネタとか思いつきますが、学校に提出するので自粛かなw@kjunichiです。

背景

goのデスクトップマスコットgopheronでちょっと前からSocket.io互換のモジュールを最新版を利用すると ビルドエラーとなってしまう状態だった。

しかも、最新版のGo版のsocket.ioの互換ライブラリがブロードキャストに対応しなくなってしまったので、 この機会に勉強もかねて、素のWebSocketでの実装に移行しようかと考えていた。

goの新しいモジュール管理方式

npmやmrubyでは依存するライブラリのブランチ等を指定することができ、この問題は回避できていた。 が、goはバージョンを指定することができず、昔から、あれこれ回避する方法が考えられていたものの、 決定打はなかった模様。。。

gopheronでの対応

GO111MODULE=on go mod init

go.moduleというファイルを作成して、これに以下の内容を記述

module github.com/kjunichi/gopheron

go 1.12

require github.com/googollee/go-socket.io v1.0.1

おわりに

go modulesを知って、当面は以前の版を指定することで、この問題を回避できた。

関連記事

7年前の記事

M5StickCがずいぶん前に届いていた件

こんばんは。夏休みもあと少しです。9月からは給食室が工事のため、引き続きお弁当が必要な長男がいる@kjunichiです。

背景

Apple Watchが欲しくてしかたない今日この頃だが、先立つものが無い。 たまたま、AKIBA PC Hotline!

akiba-pc.watch.impress.co.jp

の記事を見て、2千円程度でM5StickCなるデバイスが存在することを知った。 これなら腕にはめて時計スタイルで使用できると思った。

初期不良と思った

画面の明るさの制御ができない。 サンプルのファクトリーアプリを入れて、ボタンを押すと、 本来は液晶が徐々に暗くなるようなコードが見受けられたが、真っ暗に。。。

しかし、どうも、液晶の明るさ以外はWifiも使えるし、画面描画も問題なさそう。

時計を作ってみた

amazonで購入したのだが、アキバPCで紹介されていたものとは異なり、 腕時計用のアタッチメントやバンドは付属していなかったが、 電源入るとWifiに接続しNTPサーバーより時刻を取得して、これを表示するコードを書いてみた。

M5StickC Nixie tube&nbsp;Clockmacsbug.wordpress.com

lang-ship.com

を参考にして、LAN環境が無いと動かない時計が完成した!

// M5StickC Nixie tube Clock: 2019.06.06 
#include <M5StickC.h>
#include <WiFi.h>
#include <WiFiMulti.h>

#include "vfd_18x34.c"
#include "vfd_35x67.c"

const char* ssid       = "ssid";
const char* password   = "password";
const char* ntpServer =  "ntp.jst.mfeed.ad.jp";

RTC_TimeTypeDef RTC_TimeStruct;
RTC_DateTypeDef RTC_DateStruct;

WiFiMulti wifiMulti;

int mode_ = 3; // 3:2Lines 2: 2Lines(YYMM), 1:1Line
const uint8_t*n[] = { // vfd font 18x34
  vfd_18x34_0,vfd_18x34_1,vfd_18x34_2,vfd_18x34_3,vfd_18x34_4,
  vfd_18x34_5,vfd_18x34_6,vfd_18x34_7,vfd_18x34_8,vfd_18x34_9
  };
const uint8_t*m[] = { // vfd font 35x67
  vfd_35x67_0,vfd_35x67_1,vfd_35x67_2,vfd_35x67_3,vfd_35x67_4,
  vfd_35x67_5,vfd_35x67_6,vfd_35x67_7,vfd_35x67_8,vfd_35x67_9
  };
const char *monthName[12] = {
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};


void setup(void){ 
  M5.begin();
  pinMode(M5_BUTTON_HOME, INPUT);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setRotation(1);
  
  wifiMulti.addAP(ssid, password);
  if((wifiMulti.run() == WL_CONNECTED)) {
    // Set ntp time to local
    configTime(9 * 3600, 0, ntpServer);
 
    // Get local time
    struct tm timeInfo;
    if (getLocalTime(&timeInfo)) {
      M5.Lcd.print("NTP : ");
      M5.Lcd.println(ntpServer);
 
      // Set RTC time
      RTC_TimeTypeDef TimeStruct;
      TimeStruct.Hours   = timeInfo.tm_hour;
      TimeStruct.Minutes = timeInfo.tm_min;
      TimeStruct.Seconds = timeInfo.tm_sec;
      M5.Rtc.SetTime(&TimeStruct);
   
      RTC_DateTypeDef DateStruct;
      DateStruct.WeekDay = timeInfo.tm_wday;
      DateStruct.Month = timeInfo.tm_mon + 1;
      DateStruct.Date = timeInfo.tm_mday;
      DateStruct.Year = timeInfo.tm_year + 1900;
      M5.Rtc.SetData(&DateStruct);
      // rtc setup end -----------------------------------------------
    }
    //disconnect WiFi
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
  
  }
  
}

int showTime = 20;
bool fblank = false;
void loop(void){
  if(showTime<0) {
    if(!fblank) {
      M5.Lcd.fillScreen(BLACK);
      fblank=true;
    }
    if(digitalRead(M5_BUTTON_HOME) == LOW){
      while(digitalRead(M5_BUTTON_HOME) == LOW);
      Serial.println("display.");
      M5.Axp.DeepSleep(SLEEP_MSEC(1));
      showTime=10;
      fblank=false;
      vfd_3_line();
      //if (mode_ == 3){mode_ = 1;M5.Lcd.fillScreen(BLACK);return;}
      //if (mode_ == 2){mode_ = 3;M5.Lcd.fillScreen(BLACK);return;}
      //if (mode_ == 1){mode_ = 2;M5.Lcd.fillScreen(BLACK);return;}
    } else {        
        M5.Axp.LightSleep(SLEEP_MSEC(800));
        //delay(6);
        //M5.Axp.DeepSleep(SLEEP_MSEC(300));
        //M5.Lcd.setCursor(60, 30);
        //M5.Lcd.fillScreen(WHITE);
        //M5.Lcd.setCursor(60, 30);
        //M5.Lcd.print("unsleep");
        //Serial.println("test.");
    }
    delay(100); 
  } else {
    showTime--;
    if ( mode_ == 3 ){ vfd_3_line();}   // hh,mm,ss
    if ( mode_ == 2 ){ vfd_2_line();}   // yyyy,mm,dd,hh,mm,ss
    if ( mode_ == 1 ){ vfd_1_line();}   // mm,ss
    delay(1000);
  }
}
 
void vfd_3_line(){
  M5.Rtc.GetTime(&RTC_TimeStruct);
  M5.Rtc.GetData(&RTC_DateStruct);
  int h1 = int(RTC_TimeStruct.Hours / 10 );
  int h2 = int(RTC_TimeStruct.Hours - h1*10 );
  int i1 = int(RTC_TimeStruct.Minutes / 10 );
  int i2 = int(RTC_TimeStruct.Minutes - i1*10 );
  int s1 = int(RTC_TimeStruct.Seconds / 10 );
  int s2 = int(RTC_TimeStruct.Seconds - s1*10 );
  
  M5.Lcd.pushImage(  2,0,35,67, (uint16_t *)m[h1]);
  M5.Lcd.pushImage( 41,0,35,67, (uint16_t *)m[h2]);
  M5.Lcd.drawPixel( 79,22, ORANGE); M5.Lcd.drawPixel( 79,48,ORANGE); 
  M5.Lcd.drawPixel( 79,21, YELLOW); M5.Lcd.drawPixel( 79,47,YELLOW); 
  M5.Lcd.pushImage( 83,0,35,67, (uint16_t *)m[i1]);
  M5.Lcd.pushImage(121,0,35,67, (uint16_t *)m[i2]);
  M5.Lcd.pushImage(120,45,18,34, (uint16_t *)n[s1]);
  M5.Lcd.pushImage(140,45,18,34, (uint16_t *)n[s2]);
  
  if ( s1 == 0 && s2 == 0 ){ fade();}
}

void vfd_2_line(){
  M5.Rtc.GetTime(&RTC_TimeStruct);
  M5.Rtc.GetData(&RTC_DateStruct);
  //Serial.printf("Data: %04d-%02d-%02d\n",RTC_DateStruct.Year,RTC_DateStruct.Month,RTC_DateStruct.Date);
  //Serial.printf("Week: %d\n",RTC_DateStruct.WeekDay);
  //Serial.printf("Time: %02d : %02d : %02d\n",RTC_TimeStruct.Hours,RTC_TimeStruct.Minutes,RTC_TimeStruct.Seconds);
  // Data: 2019-06-06
  // Week: 0
  // Time: 09 : 55 : 26
  int y1 = int(RTC_DateStruct.Year    / 1000 );
  int y2 = int((RTC_DateStruct.Year   - y1*1000 ) / 100 );
  int y3 = int((RTC_DateStruct.Year   - y1*1000 - y2*100 ) / 10 );
  int y4 = int(RTC_DateStruct.Year    - y1*1000 - y2*100 - y3*10 );
  int j1 = int(RTC_DateStruct.Month   / 10);
  int j2 = int(RTC_DateStruct.Month   - j1*10 );
  int d1 = int(RTC_DateStruct.Date    / 10 );
  int d2 = int(RTC_DateStruct.Date    - d1*10 );
  int h1 = int(RTC_TimeStruct.Hours   / 10) ;
  int h2 = int(RTC_TimeStruct.Hours   - h1*10 );
  int i1 = int(RTC_TimeStruct.Minutes / 10 );
  int i2 = int(RTC_TimeStruct.Minutes - i1*10 );
  int s1 = int(RTC_TimeStruct.Seconds / 10 );
  int s2 = int(RTC_TimeStruct.Seconds - s1*10 );
   
  M5.Lcd.pushImage(  0, 0,18,34, (uint16_t *)n[y1]); 
  M5.Lcd.pushImage( 19, 0,18,34, (uint16_t *)n[y2]);
  M5.Lcd.pushImage( 38, 0,18,34, (uint16_t *)n[y3]);
  M5.Lcd.pushImage( 57, 0,18,34, (uint16_t *)n[y4]);
  M5.Lcd.drawPixel( 77,13, ORANGE); M5.Lcd.drawPixel( 77,23,ORANGE);
  M5.Lcd.pushImage( 80, 0,18,34, (uint16_t *)n[j1]);
  M5.Lcd.pushImage( 99, 0,18,34, (uint16_t *)n[j2]);
  M5.Lcd.drawPixel(118,13, ORANGE); M5.Lcd.drawPixel(119,23,ORANGE);
  M5.Lcd.pushImage(120, 0,18,34, (uint16_t *)n[d1]);
  M5.Lcd.pushImage(140, 0,18,34, (uint16_t *)n[d2]);
                                                    
  M5.Lcd.pushImage( 00,40,18,34, (uint16_t *)n[h1]);
  M5.Lcd.pushImage( 20,40,18,34, (uint16_t *)n[h2]);
  M5.Lcd.drawPixel( 48,54, ORANGE); M5.Lcd.drawPixel( 48,64,ORANGE); 
  M5.Lcd.pushImage( 60,40,18,34, (uint16_t *)n[i1]);
  M5.Lcd.pushImage( 80,40,18,34, (uint16_t *)n[i2]);
  M5.Lcd.drawPixel(108,54, ORANGE); M5.Lcd.drawPixel(108,64,ORANGE);
  M5.Lcd.pushImage(120,40,18,34, (uint16_t *)n[s1]);
  M5.Lcd.pushImage(140,40,18,34, (uint16_t *)n[s2]);
 
  if ( i1 == 0 && i2 == 0 ){ fade();}
}
 
void vfd_1_line(){
  M5.Rtc.GetTime(&RTC_TimeStruct);
  M5.Rtc.GetData(&RTC_DateStruct);
  int i1 = int(RTC_TimeStruct.Minutes / 10 );
  int i2 = int(RTC_TimeStruct.Minutes - i1*10 );
  int s1 = int(RTC_TimeStruct.Seconds / 10 );
  int s2 = int(RTC_TimeStruct.Seconds - s1*10 );
  
  M5.Lcd.pushImage(  2,6,35,67, (uint16_t *)m[i1]);
  M5.Lcd.pushImage( 41,6,35,67, (uint16_t *)m[i2]);
  M5.Lcd.drawPixel( 79,28, ORANGE); M5.Lcd.drawPixel( 79,54,ORANGE); 
  M5.Lcd.drawPixel( 79,27, YELLOW); M5.Lcd.drawPixel( 79,53,YELLOW); 
  M5.Lcd.pushImage( 83,6,35,67, (uint16_t *)m[s1]);
  M5.Lcd.pushImage(121,6,35,67, (uint16_t *)m[s2]);
 
  if ( s1 == 0 && s2 == 0 ){ fade();}
}

void fade(){
  //for (int i=7;i<16;i++){M5.Axp.ScreenBreath(i);delay(25);}
  //for (int i=15;i>7;i--){M5.Axp.ScreenBreath(i);delay(25);}
  //M5.Axp.ScreenBreath(12);
}

残念な点

バッテリーが持たなすぎ。 1時間ももたない。

実は、正常品だった

Arduino IDEでダウンロードしたフォルダをごっそりGithubからクローンしたフォルダに差し替える

macOS環境ではに~/Documents/Arduino/libraries/配下のM5StickCフォルダをクローンしたフォルダに入れ替えることで 対応できた。

関連記事

2年前の記事

1年前の記事