GolangとNode.jsでmifareカード使ってチェックイン表示

Mifareカードリーダーを使ったチェックインシステムを
GolangとNode.js使って雛形もどきを作ってみた。Node.jsはwebアプリ雛形作成のexpress-generatorを使っています。

GolangでSQLite3のデータベーステーブル作成とアップデート、Node.jsはwebサーバー起動してviewを作成しています。

・index.js:データベースのテーブルから読み出したチェックイン情報を読み出して、ページ(view)の必要情報を用意します。データベース読み込みは非同期処理なので、async/awaitで同期化します。

・index.pug:index.html作成のテンプレートです。pugファイルにはJavaScriptの記述もできて繰り返し処理が簡単にできるから便利です。

・style.css:スタイルシート、本来のディレクトリから一個上の階層に移動してあります。GitHubへの転送がそうしないとうまくできなかったから。

・serial.go:M5StackC plusからのカード読み取り情報を受け取りjsonのuidと名前対応情報から手ブルを作成。テーブル名は年号と1月1日からの経過日を組み合わせたものにしています。

コードは分散していますが以下のリンクから、

https://github.com/chateight/golang/tree/master/serial

P.S. 2023/4/1

Unmarshalは簡単に使えてこのケースなら別に問題もないですが、巨大なファイルになるとメモリ大量に消費するだろうから効率考えたらストリームとして処理するのが良いようです。

 

*_tool.jsはデバッグ用のコードです。

カードリーダーのM5StackC plusのコードなどの情報は、

https://github.com/chateight/rfid

 

admin

SQLite3の非同期処理を同期化させる(Node.js)

便利なようで結構不便なJavaScriptのシングルタスクを有効に使おうとするイベント処理。非同期処理は簡単な使い方では不便でSQLite3のDBアクセスライブラリには同期型も存在するらしい。

ユーザーが多いだろうから非同期型のライブラリでなんとかしようとするとPromise処理が必要になります。最新の機能ではawait/asyncになりますが。以下はexpress-generatorで発生されたwebページのディスパッチ処理を行うindex.jsにデータベースアクセス処理を同期させて追加したコードです。

<index.js>

var express = require('express');
var router = express.Router();

let nameArray = new Array();

/* GET home page. */
router.get('/', function(req, res, next) {
  var response = Math.floor(Math.random() * (100 - 1)) + 1;

  let t1 = new Date();

async function dbReadExec(){
  try{
    nameArray = await dbRead();
    let t2 =new Date();
    console.log("dbReadEec",nameArray, t2 - t1);
    res.render('index', { title: 'Express', data: response, db: nameArray});
  } catch (err){
    console.log('error');
  } finally {
    console.log("done");
  }
}

dbReadExec();

});

//
// DB handler
//
function dbRead(){
  return new Promise((resolve, reject) => {

  const sqlite3 = require("sqlite3");
  const db = new sqlite3.Database("/Users/username/go/src/serial/myfare.db");
  
  // calc file name
  let d = new Date();
  let year = d.getFullYear();
  let month = d.getMonth();
  let day = d.getDate();
  
  let date1 = new Date(year, 0, 0);
  let date2 = new Date(year, month, day)
  let behind = Math.floor((date2 - date1) / (24*3600*1000));      // convert milisec to day
  
  nameArray = [];       // clear Array
  
  db.serialize(() => {
  db.each("select * from tbl" + year + behind + " where stat=0", (err, row) => {
    if (err) {
      reject(err);
    }
  //console.log(`${row.id} ${row.name} ${row.time} ${row.stat}`);    
  nameArray.push(row.name);
  resolve(nameArray);
  })
  
  db.close();

  });
  });
}


module.exports = router;

serialize()したつもりでも、resolve(nameArray);をdb.close()の後にするとうまく動きません。ここも非同期で動くから?ループ内でresolve()を複数回発行することになりますが、これはthenがchainされて最後のresolve()が有効になるようです。データ量が少ないなら、db.each()ではなくdb.all()で一回で返せば良さそうに思いますが。

let t2 =new Date();

console.log("dbReadEec",nameArray, t2 - t1);

はデータベース処理時間計測のためのタイムスタンプです。二回目以降はJavaScriptのキャッシュが有効だろうから高速です。

シングルタスク言語というのはちょっと複雑な構造のコードを書こうとすると不便なところが目立つように感じます。

 

P.S. 2023/3/28

db.each部分だけをdb.allに置き換え、

  db.all("select * from tbl" + year + behind + " where stat=0", (err, rows) =>{
    rows.forEach(row => nameArray.push(row.name));
    //console.log(nameArray);
    resolve(nameArray);
})

こちらの方がスマートでしょう。

 

admin

 

Node.jsフレームワーク(Express-Generator)でのページ遷移

ラズパイにはExpress-Generatorをインストールしていましたが、MacBookにもインストールして久しぶりに触っています。Express-Generatorはwebアプリ雛形作成のためのツールの一つです。webアプリ作成のためには理解すべき点がいくつかありますが、動的なページを作成するためのデータの与え方はその一つです。

以下は関連するディレクトリを表示していますが、index.jsでviews配下のindex.pugのコンテンツを作るようになっています。

index.jsソースの一部(dbName())は記載していませんが、データベースからの読み出しを行なっています。本タイトルと直接の関係はないので省略。

var express = require('express');
var router = express.Router();

var nameList = "";

/* GET home page. */
router.get('/', function(req, res, next) {
  var response = Math.floor(Math.random() * (100 - 1)) + 1;
  
  dbName();

  const wait = () => {
  res.render('index', { title: 'Express', data: response, db: nameList});
  }
  setTimeout(wait, 3);
});

res.render()の第二引数以下がindex.pugに渡されてhtmlのコンテンツを構成しています。雛形ではtitle一個しかありませんが、カンマ区切りで任意の個数を追加できます。ここでは1~100までの範囲の乱数をdataとして、データベースから読み出したデータを変数名dbでindex.pugに渡しています。

index.pug

extends layout

block content
  h1= title
  p Welcome to #{title}

  p #{data} 

  p #{db}

SQLite3のデータベースアクセスも非同期で実行されるので、ページ作成が先行して実行されてdbデータの供給が間に合わないので、仮に時間待ち3msを入れていますが、あくまでデバッグ専用でいずれ修正の予定。

 

admin

 

 

Node.js(JavaScript)で経過日(時間)を求める

GolangにはtimeパッケージにYearDay()という年初からの連続で今日が何日目かを返してくれる関数があってそれをファイル名(tbl + 西暦年 + 連続何日目)に使っています。1日に2個以上テーブル作る可能性がないから、これで一意にファイル名を決定できます。

https://pkg.go.dev/time

でもNode.jsで同じファイル名を指定しようとするとこのような関数はないので、計算が必要になります。なぜNode.jsで必要かというとGolangで作成したSQLiteのテーブルにNode.jsからアクセスしたいからですが。

Golangでのテーブル作成、年号と年初からの経過日でファイル名を作成しています。以下のコードの一番下の行でターブル作成。

date := strconv.Itoa(t.Year()) + strconv.Itoa(t.YearDay())

	if del {
		cmd := "drop table if exists tbl" + date

		_, err := db.Exec(cmd)
		if err != nil {
			fmt.Println("db table drop error")
		}
	}

	// table exist check
	cmd := "select*from tbl" + date
	_, table_check := db.Query(cmd) // checked by using return code

	if table_check != nil {

		cmd = "create table if not exists tbl" + date + "(id string primary key, name, time int, stat int)"

JavaScriptでのテーブル名の作成スクリプト

// calc file name
let d = new Date();
let year = d.getFullYear();
let month = d.getMonth();
let day = d.getDate();

let date1 = new Date(year, 0, 0);
let date2 = new Date(year, month, day)
let behind = Math.floor((date2 - date1) / (24*3600*1000));      // convert milisec to day


db.serialize(() => {
    db.each("select * from tbl" + year + behind + " where stat=0", (err, row) => {
    console.log(`${row.id} ${row.name} ${row.time} ${row.stat}`);
    })
});

Math.fllor():端数切り捨てはなくても大丈夫のようですが、入れておいて実害はないだろうから。

 

admin

Node.jsでSQLiteにアクセス

Golangで作成したSQLiteのテーブルをnode.jsからアクセスしてみようと思ったが、前提になるnpmが起動できないし、nodeも起動できない。

% node   

dyld[71646]: Library not loaded: /opt/homebrew/opt/icu4c/lib/libicui18n.71.dylib

  Referenced from: <66A3E1EC-93E2-36D1-889D-02EE5C192FCC> /opt/homebrew/Cellar/node/18.11.0/bin/node

Reason: tried: ‘/opt/homebrew/opt/icu4c~~~~以下省略

解決方法は、

https://stackoverflow.com/questions/53828891/dyld-library-not-loaded-usr-local-opt-icu4c-lib-libicui18n-62-dylib-error-run

から、

% brew reinstall icu4c

で解決。

・Node.jsからSQLiteへアクセスするドライバーをインストール

% npm install sqlite3

・すでに存在しているテーブルにアクセスしてみる、

//

// DB handler

//

'use strict';

const sqlite3 = require("sqlite3");

const db = new sqlite3.Database("./myfare.db");

db.serialize(() => {

    db.each("select * from tbl202382 where stat=0", (err, row) => {

    console.log(`${row.id} ${row.name}`);

    })

});

db.close();

do.run()が非同期実行なのでsierialize()が必要とのことですが、データ更新してなくて読んでるだけで並行処理されるものがないから実はserialize()はこのケースでは不要。

しかし実行すると、

Error: Cannot find module ‘sqlite3’

対策はパスを通すことらしいので、

% export NODE_PATH=`npm root -g`

で、

% echo $NODE_PATH

/opt/homebrew/lib/node_modules

にsqlite3は存在していますが、

% which sqlite3

とは違うディレクトリなので実はシンボリックリンクを使っているのかもしれない。

ともかくもSQLite3のテーブルアクセスはできた。

Nodeと関連技術

Nodeに関連して検索したサイトと自分用のメモです。

 

○ WebSocketのURL形式

通信時に指定するURLは、

ws://www.sample.com、あるいはセキュア接続ならwas://www.sample.com

明に使うことはあまりないだろうけども

 

○ Node.jsでソケット通信

<socket.ioのインストール>(アプリを作成するディレクトリで)

% npm install socket.io 

もはやdgramはインストール不要らしい

 

○ websocket動かすには

Node.jsとExpressの他にwsライブラリのインストールも必要、本当?

<HTTP serverとclientサンプル>

socket.ioを使った、

以下の二つはGitHubのサンプル動かしているのでセット

https://qiita.com/kouji0705/items/cf16044c7d825d09d707

https://github.com/neroneroffy/webSocketDemo

https://ai-soldier.work/websocket-node-ws/#

これ動かしてみたけど、ラズパイでも軽快に動く

 

○ Express

<documents>

https://expressjs.com

<解説>

https://qiita.com/mml/items/3cc90479df033c0998e4

https://qiita.com/ganyariya/items/85e51e718e56e7d128b8

<起動>

$ npm start

 

○ pug(HTMLテンプレート)について

<pugの概要>

https://and-ha.com/coding/what-is-pug/

こんなこともできる(markdownをincludeしてHTML出力)

https://ytyaru.hatenablog.com/entry/2018/05/01/000000

<pugの拡張>

https://qiita.com/zenno04/items/d16f881170170b567b16

Template Inheritance

https://pugjs.org/language/inheritance.html

 

○ wsサーバーの起動

Express serverとWebSocket severそれぞれ別に起動で良い

$ node ws_server.js

実際の運用は起動スクリプトに書くから個別起動で問題ない

 

○ Javascript(chart.js)でグラフ作成

json使いましょう

https://www.web-development-kb-ja.site/ja/javascript/chartjsでjsonデータを表示する/833307193/

 

○ JSからEJSへ

https://fukuno.jig.jp/2819

use strict : ESM(ES module)有効なら指定不要だったよね

 

admin

ラズパイでWebSocket通信(その2)

ラズパイでWebSocketの環境構築

の続編です。

Expressのプロジェクトのviews以下に以下のファイルを配置しました。pugファイルの継承機能の確認も兼ねています。block inheriの直下にblock contentがあるので、index.pugの内容が挿入されて結果として作成されるHTMLファイルは複数のpugファイルから構成されます。

layout.pug
--------------
doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    <script src="./javascripts/ws_client.js" defer>
  body
    block inheri<


inheri.pug
--------------
extends layout
block inheri
  block content
  p Test site for M5stack data transfer using UDP & WebSocket
  p#message.poor-writing ws test button

  <input type="button" id="send_server" value="send" />

  <div id="mes">web socket out put area</div>

index.pug 
-------------- 
extends inheri 

block content 
  h1= title 
  p #{title} is provided for timely data access 
  p ----------------------------------------------

クライアント側で動作するws_client.jsとサーバー側で動作するws_server.jsスクリプトです。いずれもpublic/javascriptsに配置してます。

ブラウザ上のbuttonを押すとサーバー側ではws_server.jsが検出して、サーバー側からレスポンスを返しています。

ws_client.js
--------------
        const ws = new WebSocket('ws://raspberrypi.local:3002')
 
        ws.onopen = e => {
            console.log('Hello M5 server!')
        }

        ws.onmessage = e => {
            const mes = document.querySelector("#mes");
	        mes.textContent = e.data;

            console.log('received data:%s',e.data)
            console.log(e)
        }


        ws.onerror = e => {
            console.log('fail to connect:${e.data}')
            console.log(e)
        }

        document.getElementById('send_server').addEventListener('click', () => {
            ws.send('call back from the button')
        })


ws_server.js
--------------
const WebSocketServer = require('ws').Server;

const wss = new WebSocketServer({ port: 3002 });

wss.on('connection', ws_client => {

  ws_client.send('good morning');

  ws_client.on('message', data => {
    console.log('send data: %s', data);
    ws_client.send('received the data:' + data);
  });
});

<起動>

Express:

$ npm start

WS:

$ node ws_server.js

別々に起動してもいいけど、おそらくExpress側で起動する様にできるはず。

ブラウザ画面の動画

以下の画面操作の動画です。

参考サイトは、

pugの継承

https://qiita.com/zenno04/items/d16f881170170b567b16

簡単ws使い方

https://ai-soldier.work/websocket-node-ws/

 

次にやりたいことはM5stackと以下のようにつなぐこと、Port 3000は共通ですが、UDPとTCP/IPの違いなので問題ありません。

 

admin

 

 

 

ラズパイでWebSocketの環境構築

基盤はNode.jsを使うことにしたので、必要なパッケージをインストールします。

Node.js他のインストールは以下のリンクを参考に、

https://it-evo.jp/blog/blog-813/

<Node.jsとnpmをインストール>

$ sudo apt install -y nodejs npm

途中でエラーになったので、

$ sudo apt-get update

この後再び、

$ sudo apt install -y nodejs npm

版数、

$ node -v
v12.22.12
$ npm -v
7.5.2

バージョン管理パッケージ(n)のインストール

$ sudo npm install n -g
added 1 package, and audited 2 packages in 19s
found 0 vulnerabilities

今の版数、

$ n -V
v9.0.1

長期安定版のインストール、

$ sudo n lts

安定版の方が版数古いけど、版数戻し、

$ node -v
v12.22.12

$ hash -r
$ node -v
v10.24.1

古い(安定版じゃない)パッケージを削除、

$ sudo apt purge -y nodejs npm

<Express/Express-Generatorのインストール>Express : Node.jsで利用できるWebアプリケーションフレームワーク

$ npm install express --save

Express-generatorはルート権限でないとインストールできなかった、

$ sudo npm install express-generator -g
$ express --version
4.16.1

P.S. 2022/11/25

npmでのインストールパラメータ “–save”は現在のnpmでは暗黙に指定されている様です。

<webサーバーの立ち上げ>

$ express t_web --view=pug
$ cd t_web
$ npm install

t_webのディレクトリ構成

見かけon Railsに雰囲気似ています。拡張子pugはHTMLを発生させるテンプレートです。views/index.pugがトップページだろうと思って中身を見ると、確かに作成されるHTMLを連想できるような内容になってます。

$ ls -l
total 68
-rw-r--r--   1 pi pi  1074 Nov 15 04:47 app.js
drwxr-xr-x   2 pi pi  4096 Nov 15 04:47 bin
drwxr-xr-x 123 pi pi  4096 Nov 15 04:52 node_modules
-rw-r--r--   1 pi pi   297 Nov 15 04:47 package.json
-rw-r--r--   1 pi pi 38995 Nov 15 04:52 package-lock.json
drwxr-xr-x   5 pi pi  4096 Nov 15 04:47 public
drwxr-xr-x   2 pi pi  4096 Nov 15 04:47 routes
drwxr-xr-x   2 pi pi  4096 Nov 15 04:47 views
~/t_web $ ls -l views
total 12
-rw-r--r-- 1 pi pi  84 Nov 15 04:47 error.pug
-rw-r--r-- 1 pi pi  66 Nov 15 04:47 index.pug
-rw-r--r-- 1 pi pi 125 Nov 15 04:47 layout.pug
~/t_web $ cat views/index.pug
extends layout
block content
  h1= title
  p Welcome to #{title}

<サーバー起動>

ディレクトリt-webでnpm startで起動してブラウザからアクセスすると、アクセスのログがコンソールに出力されます。

$ npm start
> t-web@0.0.0 start /home/pi/t_web
> node ./bin/www
GET / 200 9066.188 ms - 170
GET /stylesheets/style.css 200 89.833 ms - 111
GET /favicon.ico 404 432.286 ms - 962
GET / 200 371.813 ms - 170
GET /stylesheets/style.css 304 22.612 ms - -
GET / 200 293.274 ms - 170

初回のアクセスだけは展開処理が入るようで9秒程度かかっていますが、以降はキャッシュ使うので早い。

pugファイルから予想される内容のページが表示されます。

アクセス方法は、

http://url:3000

urlはローカルIPアドレスです。

ネットワークアクセスは有線LANなのでランプの点滅で確認可能。

 

admin

electron-packagerで作成したアプリを立ち上げるとエラーになる

タイトル通りですが、アプリ起動時に以下のようなポップアップが表示されます。アプリそのものは動くのですが、以下の例だとelectron-logファイル見つからないよと言ってます。

じゃその、electron-logファイルどこになるのかと言うとnode_modulesディレクトリに存在しています。全体のディレクトリ構造は以下の通り。

エラーはelectronディレクトリでmove_catディレクトリのアプリをパッケージにしようとした時に発生しています。

で、move_catディレクトリの必要ファイルをelectronディレクトリにコピーして、electronディレクトリの上の階層からelectron-packgerで作成すると問題なく作成できました。当然**-arm64は上の階層に作成されます。

つまり、アプリ関連ファイルとnode_modulesディレクトリは同じ階層にないとパッケージに組み込まれないと言うことですね。

 

admin

 

Electronのイントロ

初めてのelectron、ほぼ以下のリンクのままですが

https://www.materializer.co/lab/blog/66

electronって何なの? => “Build cross-platform desktop apps with JavaScript, HTML, and CSS”

とelectronのトップページに出ています。

Node.jsがインストールされている前提で、

npm(Node.jsのパッケージ管理)でelectronをインストール、ただし既にインストール済みでした。VScodeあるから?

% npm -v

8.18.0

・実行は作成したプロジェクトの一つ上のディレクトリで、例えば以下のディレクトリ構成でfirst_porjectを実行するならば、

/electron/first_project

/electronディレクトリでコマンド実行

% electron first_project

・ログの取り方はelectron-logをインストールしてファイルに書き出すようにする

% npm install electron-log

こんな感じで使います、

const log = require(‘electron-log’);

log.info(‘Hello, log’);

ログファイルの場所は、

# cat ~/Library/Logs/electronのプロジェクト名/main.log

・配布用の実行ファイル作成はelectron-packager使いますが、なぜか”Error: Cannot find module ‘electron-log’”と言うエラーメッセージウインドーが表示されます。

アプリの実行結果は、こんな感じで間伸びした画面、ほぼ中身ないから。

 

<Node.jsについての説明>

Node.jsはJavaScript用のサーバー機能を提供、electronではChromium(以下の通り)と一緒に使います。

“Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.”

https://nodejs.dev/en/learn/

 

P.S. 2022/9/10

electronのインストールはローカルインストールにすべきだと

-gオプションでインストールすると将来版数がアップされた時に互換がなくなると管理できなくなるからローカルでインストールすべきと。

・ローカルインストール

% npm install -save-dev electron

・実行

% npx electron <ターゲットディレクトリ>

npxはローカルインストールしたモジュールを実行するためのコマンドです。

 

admin