プログラミングの最近のブログ記事

複数の Raspberry Pi などの端末につながったカードリーダーで FeliCa カードを読んで、http プロトコルでカード情報をサーバに送る仕組みを作ってる。

データを送ってリターンコードを受け取るだけの簡単な API の仕組みなので(データ種はカード新規登録、読み込み時間報告、カード登録キャンセルなど色々あるけど)、サーバ側も Apache とかインストールはせず Python で書こうかと思った。クライアント側のプログラムを Python で書いてるんでね。

ところが、ネット(Qiita「pythonでローカルwebサーバを立ち上げる」など)で拾った下のようなスクリプトを走らせて、そこに Python で作ったクライアントソフトでアクセスしてみると・・・

import http.server
import socketserver

PORT = 3000
Handler = http.server.SimplehttpRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()

これが、501 エラーになるのだ。

127.0.0.1 - - [06/Feb/2020 22:41:16] code 501, message Unsupported method ('POST')

ってエラーログ吐いてる。

POST メソッドに対応してないようだ。ええ???

確かに、ブラウザからアクセス(つまり GET メソッドのアクセス)してみたらエラーにならず、サーバスクリプトを置いているディレクトリのファイルリストが表示された。GET メソッドは OK のようだ。

20200206_pyserver.jpg

うーん・・・POST メソッドを使うには、http.server 以外にも import せんといかんのかいな?面倒くさっ。

というわけで、サーバ側は Node.js で組むことにした。(そして今、ほぼ完成してちゃんと動いている)

ただ、Python も JavaScript(しかも Node.js 拡張版)も日頃使ってる言語じゃないので、いちいちググりながら組んでいくのが面倒だけど、ま、これも勉強だからな。
仕事をしつつ勉強できるというのは良いことだ(笑)。そう思っておこう。
Python の requests ライブラリを使って Web API を叩くクライアントソフトを作ってるんだけど、接続時のタイムアウト処理をしようと思って、

    try:
        res = requests.post('https://127.0.0.1:3000/', data={'data': data}, stream=True, timeout=(3.0, 7.5))
    except requests.exceptions.ConnectTimeout:
        print('Requests.POST Timeout ' + data)
        return(901)
    else:
        print('Success' + data)
        return(res.status_code)
    finally:
        pass

こんな風に書いてたんだけど、サーバを立ち上げていない状態で一向にタイムアウト例外が発生しない。

んん???と思って、

    except requests.exceptions.RequestException as e:
        print(str(e))
        return(909)

と全部の requets 関係の例外を引っ掛けてみたら、

httpConnectionPool(host='127.0.0.1', port=3000): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.httpConnection object at 0x000000000523AE48>: Failed to establish a new connection: [WinError 10061] 対象のコンピューターによって拒否されたため、接続できませんでした。'))

だって。そうか、そうか。接続先のサーバにそもそも接続できない時は、レスポンス待ちにもなっていないわけだから、接続タイムアウトエラーではなく接続エラーなんだな。

というわけで、接続エラーを引っ掛ける

    except requests.exceptions.ConnectionError:
        print('Requests.POST ConnectionError ' + data)
        return(901)

という例外処理も追加しておかねばな。サーバが落ちてるんか、立ち上がっているけど反応が遅いのかわからんからな。
20200114_libre1.jpg

つまらんミスでハマってしまった(^^;

LibreOffice Calc のフォームボタン押下でマクロを実行するには、

  1. ボタン上で右ボタンクリック→「コントロールのプロパティ」を選択
  2. イベントタブを選択し、イベントの登録(「...」ボタンを押してマクロの選択)

という手順なわけだけど、ずっと「キーを押した時」ってイベントが俺の頭の中では「ボタンを押した時」に自動変換されてて、ここに割り当てたマクロが「ボタンを押しても実行されない」って悩んでたわ(^^;

「ボタンを押す」というイベントでマクロを実行したい時は「実行時」というイベントにマクロを登録する(関連付ける)。
「実行時」=「ボタンを押した時」なわけだ。

小一時間くらい悩んでたわ・・・(^^; 疲れてるなあ、俺。もう寝ます。
APSO は Alternative python script organizer という IDE である。

とりあえず LibreOffice Calc 上で Python の開発をするのに IDE 入れた方が便利かなと思って、「Python iDE LibreOffice Calc マクロ」とかで検索してみて、最初に当たった情報が APSO だったので入れてみただけである。

1.まず、「ツール」→「拡張機能マネージャー」で表示された画面の「ほかの拡張機能をオンラインで取得...」リンクをクリック。

20200113_LibroPython1.jpg

2.LibreOffice Extensions and Templates の画面が開くので「APSO」で検索。

3.「APSO」にヒットした一覧が表示されるので(と言っても 1個だけだと思うけど)、APSO - Alternative Script Organizer for Python のリンクをクリック

4.APSO のページから(現在使用中の)LibreOffice 6.3.0.4 に対応した版(APSO 1.2.6.2)のダウンロードページへ移動。apso.oxt(48KBほど)をダウンロードする。

5.「拡張機能マネージャー」で「追加」ボタン押下。今ダウンロードした apso.txt を選択。

6.「拡張機能マネージャー」に「APSO 1.2.6.2」が追加されたのを確認

20200113_LibroPython4.jpg

7.「閉じる」ボタン押下→再起動

8.Calc が再起動したら、「ツール」→「マクロ」→「Python スクリプトの管理」で APSO 画面を起動。

20200113_LibroPython6.jpg

9.試しに、APSO console を起動してみる。

20200113_LibroPython7.jpg

ちゃんと起動したので、とりあえず APSO のインストールは正常に終了してるってことでええのかな?
今の仕事が NetCOBOL(PowerCOBOL)だって話は以前書いたと思う。

まあ、イベント駆動型の言語なんで、おおむね PowerCOBOL はそれほど違和感を感じることなく楽しく使ってるんだけど、それでも時々 「COBOL だなぁ(^^;;」と思わせられることがある。

そのひとつが変数代入時の桁落ち。

昨日も、21,002回(中途半端な数字なのは気にしないで(笑))回す処理が 1002回しか回らなくて、なんでやねんと思ったら、案の定、21002という回数をセットする変数が、

01 WK-COUNT  PIC 9(4).

になってた(笑)
なので 5桁の数値が入らず、下 4桁だけがセットされたというわけ。

まあ、俺の単純ミスなんだけど、こういうのって普通はコンパイル時になんかエラーになったりワーニングが出たりせんもんかいな?

まあ、簡単に任意の桁の切り出しができるのが「便利」と言うことなのかもしれんが、例えば 5桁の数値から下 4桁の数値を切り出すのなら、ちゃんと明示的に、

01 WK-VAL.
  02 WK-H1   PIC 9(1).
  02 WK-T4   PIC 9(4).

という変数作って、

MOVE   12345   TO  WK-VAL.

して、やればいいやん。よっぽど後でソースも読みやすい。

いや、1〜10桁の間で桁数は変化して、その中の下 4桁が取りたいんや・・・ってことなら、WK-H1 を 6桁にすればええだけや。
どっちにしても COBOLの場合、「実際、何桁になるかわからん」場合もちゃんと固定長で変数宣言せんといかん。ちゅうても、何十桁もあるような数値、そもそも扱わんやろ、COBOLで(笑)

というわけで、桁あふれしてもなんも教えてくれん COBOL には若干苛立ちをおぼえるのお。
エラーが出てうまく動かなかった FeliCa カード読み込みマクロだが、結局、

com.sun.star.uno.RuntimeException: Couldn't convert SONY RC-S380/P on usb:003:008 to a UNO type; caught exception: <class 'AttributeError'>: 'ContactlessFrontend' object has no attribute 'getTypes', traceback follows
no traceback available...

というエラーのとおり、文字列しかセットできないセルに対して、ContactlessFrontend オブジェクトをそのまま突っ込んだのが悪かった。str(clf) のように、str 関数を使って文字列変換してからセルにセットすればよかったのだ。

というわけで、

# -*- coding: utf-8 -*-
# LibreOffice 用マクロ

import uno

def GetCardData( ):

    import nfc
    from nfc.clf import RemoteTarget

    doc = XSCRIPTCONTEXT.getDocument()
    sheet = doc.getSheets().getByName('sheet1')

    clf = nfc.ContactlessFrontend('usb')

    cell = sheet.getCellRangeByName('A1')
    cell.String = str(clf)

    tag = clf.connect(rdwr={
        'on-connect': lambda tag: False
    })

    cell = sheet.getCellRangeByName('A2')
    cell.String = str(tag)

というマクロを作成し、GetCardData を実行。

20190825_nfc1.jpg

すぐに、'A1'セルにカードリーダー情報がセットされる。

この状態で、カードリーダーで FeliCa カードを読むと、今度は 'A2'セルに FeliCa カード情報がセットされる。

20190825_nfc2.jpg

ばっちりやん、ばっちりやん。

これが一番操作的にはシンプルでええね。普通に Calc のファイルもダブルクリックで起動できるしね。(外部プログラムから FeliCa 情報を得ようとすると、Cals 自体を色々オプションをつけてコマンドラインから起動しないといけない)
外部プログラムで読んだ FeliCa カードの情報を LibreOffice の Calc に渡してみた。

もし、マクロで FeliCa カードが読めないのなら、この方法でも良いかな・・・と。
(ま、別のエントリーで書く予定だけど、マクロとして実行した Python プログラムで FeliCa カード読めたんだけどね)

1.LibreOffice Calc(表計算)を外部プログラムとの通信可能な状態で起動。

外部プログラムと通信する port 番号を指定して Calc を起動するので、メニュー等からではなく、コマンドで起動すること(Windows ならコマンドプロンプトからとか)

C:\Users\shinoda\Source\Python>"C:\Program Files\LibreOffice\program\soffice.exe" --calc --norestore --accept=socket,host=localhost,port=8100;urp

これで、外部プログラムとの通信用 port(8100番)を開けた Calc が起動する。

2.外部プログラムの実行。

今回起動する Python スクリプトはこんな感じ。
カードリーダー SONY RC-S380 で読みとった FeliCa カード情報を Calc の任意のセルに貼り付けるだけのプログラム。

import nfc
from nfc.clf import RemoteTarget

import uno
localContext = uno.getComponentContext()
resolver = localContext.ServiceManager.createInstanceWithContext(
    "com.sun.star.bridge.UnoUrlResolver", localContext)

# Calc に port=8100 で接続
ctx = resolver.resolve( "uno:socket,host=localhost,port=8100;urp;StarOffice.ComponentContext" )
smgr = ctx.ServiceManager

# ドキュメントオブジェクトの取得
desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",ctx)
doc = desktop.getCurrentComponent()
sheet = doc.getSheets().getByName('Sheet1')

# カード読み込み待ち
clf = nfc.ContactlessFrontend('usb')

# 'A1' セルにカードリーダー情報をセット
cell = sheet.getCellRangeByName('A1')
cell.String = str(clf)

# カードがタッチされた(情報読み込み)
tag = clf.connect(rdwr={
    'on-connect': lambda tag: False
})

# 'A2' セルにカード情報をセット
cell = sheet.getCellRangeByName('A2')
cell.String = str(tag)

これを、コマンドプロンプトから実行(LibreOffice に付いてきたマクロ実行用の Python で実行)する。

C:\Users\shinoda\Source\Python>"C:\Program Files\LibreOffice\program\python.exe" .\cellset_to_calc2.py

3.起動と同時に、Calc の 'A1'セルにカードリーダー情報がセットされる

4.カードを読むと、その情報が 'A2'セルにセットされる

20190825_libreOffice1.jpg

ばっちりやね。

デメリットは、コマンドプロンプトから Calc を起動しないといけないことか。

でも、まあ、最悪この方法で LibraOffice にカード情報を渡せることはわかった。人類にとっては大きな一歩だ(笑)
LibreOffice のマクロ用 Python 環境に nfcpy モジュールをインストールした話は別エントリーで書いたが、結局、マクロとしてはうまく動かすことができなかった。

そこで、nfcpy モジュールのインストール自体は上手く行っているのか確認してみることに。

確認と言っても、テストで使用したマクロ用 Python プログラムの参考にした、


というサイト内のサンプルスクリプトを、LibreOffice のマクロ用 Python で実行してみただけである。

C:\Users\shinoda\Source\Python ディレクトリ以下に

import nfc
from nfc.clf import RemoteTarget

clf = nfc.ContactlessFrontend('usb')
print(clf)

tag = clf.connect(rdwr={
    'on-connect': lambda tag: False
})

print(tag)

こういう簡単なスクリプトを用意して(nfc_test3.py)、LibreOffice のマクロ用 Python(C:\Program Files\LibreOffice\program\python-core-3.5.7\bin\python.exe)を使って実行。

C:\Users\shinoda\Source\Python>"C:\Program Files\LibreOffice\program\python.exe" .\nfc_test3.py
SONY RC-S380/P on usb:003:008
Type3Tag 'FeliCa Lite-S (RC-S966)' ID=012E4CD257C3AFA2 PMM=00F1000000014300 SYS=88B4

ちゃんと、FeliCa カード読めるじゃん!

nfcpy モジュール自体のインストールは上手くいってるってことだから、やっぱマクロ環境から USB 機器を制御したりってことが、そもそも無理ってことなんかな???
この間作った「FeliCa カード読み込み」プログラムを LibreOffice のマクロにしてみようと思ったんだけど(OpenOffice.org や、その流れをくむ LibreOffice は、マクロ言語として Python や JavaScript が使える)、とりあえず実行してみると、

Python のスクリプト vnd.sun.star.script:nfc_macro_test.py$GetCardData?language=Python&location=share の実行中に Scripting Framework エラーが発生しました。

メッセージ: <class 'ImportError'>: No module named 'nfc'
<以下略>

とエラーになる。カードリーダーを制御するためのモジュールが読み込めないようだ。

LibreOffice はマクロ実行に、Windows にインストールされている Python ではなく、自前で用意した C:\Program Files\LibreOffice\program\python-core-3.5.7\bin\python.exe を使用する。そのため、モジュール類もこの Python 用にインストールする必要がある。

Python環境としてのLibreOfficeをより便利に使うために (2018-03-23)」というサイトを参考にインストールを行った。

1.まず、パッケージ管理ソフト pip のインストーラ(get-pip.py)をダウンロード

https://pip.pypa.io/en/stable/installing/ では、UNIX 系 OS 用に curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py で手に入れろと書いてあるが、Windows に curl コマンドはないので、直接ブラウザに https://bootstrap.pypa.io/get-pip.py を打って入手。
あとでコマンドを実行するディレクトリに置く。

2.get-pip.py を実行し、pip をインストールする。

俺は、get-pip.py を C:\Users\shinoda\Source\Python の下に置き、そのディレクトリに cd してからコマンド実行。

C:\Users\shinoda\Source\Python>set PATH=C:\Program Files\LibreOffice\program;%PATH%
C:\Users\shinoda\Source\Python>python get-pip.py --user

これで pip がインストールされる。

C:\Users\shinoda\AppData\Roaming\Python\Python35\Scripts ディレクトリ以下を見れば、pip.exe、pip3.5.exe、pip3.exe がインストールされている(全部同じもの)。
これで、外部モジュールをインストールする準備はできた。

3.pip で nfcpy をインストールする

C:\Users\shinoda\AppData\Roaming\Python\Python35\Scripts ディレクトリ以下にインストールされた pip を使うために path を通してから実行。

C:\Users\shinoda\Source\Python>set PATH=C:\Users\shinoda\AppData\Roaming\Python\Python35\Scripts;%PATH%
C:\Users\shinoda\Source\Python>pip install --user nfcpy

これでインストールされる。

4.パッケージ(fcpy や libusb1)が正常に入ったか確認

C:\Users\shinoda\Source\Python>pip list
Package    Version
---------- -------
libusb1    1.7.1
ndeflib    0.3.3
nfcpy      1.0.3
pip        19.2.2
pyDes      2.0.1
pyserial   3.4
setuptools 41.2.0
wheel      0.33.6

バッチリですな。

一旦 LibreOffice を閉じて、もう一度開き直してマクロ実行。これでも、No module named 'nfc' なエラーは出なくなる。

ま、新たに、

com.sun.star.uno.RuntimeException: Couldn't convert SONY RC-S380/P on usb:003:008 to a UNO type; caught exception: <class 'AttributeError'>: 'ContactlessFrontend' object has no attribute 'getTypes', traceback follows
no traceback available
 (Error during invoking function GetCardData in module file:///C:/Program%20Files/LibreOffice/share/Scripts/python/nfc_macro_test.py (<class 'uno.com.sun.star.uno.RuntimeException'>: Couldn't convert SONY RC-S380/P on usb:003:008 to a UNO type; caught exception: <class 'AttributeError'>: 'ContactlessFrontend' object has no attribute 'getTypes', traceback follows
no traceback available

  File "C:\Program Files\LibreOffice\program\pythonscript.py", line 907, in invoke
    ret = self.func( *args )
  File "C:\Program Files\LibreOffice\share\Scripts\python\nfc_macro_test.py", line 17, in GetCardData
    cell.String = clf
))

なんてエラーが出ちゃうんだけど(^^;

マクロで外部のデバイス(SONY RC-S380 カードリーダ)を制御出来んのかな?
ちょっと別の方法を考えるか・・・

Python のプログラム側から LibreOffice API を叩く方が良さげ。

<追記>
結局、エラーメッセージのとおり、ContactlessFrontend オブジェクトをそのままセルにぶち込もうとしたためのエラーだった。別エントリーで書いているように、String型に変換してセルに入れればまったく問題なかった(^^;
FeliCa カード読み込みの Python スクリプトを LibreOffice Calc にマクロで組み込みたいんだけど、tagtool.py をもとにスクリプトを書くには、tagtool.py が大きすぎて骨が折れそう。
なにせ、Python を触ったことはあるけど、ちゃんとプログラミングしたことはないからな(^^; 真似するにしても、元ソースの構文調べながらになるから tagtool.py は大きすぎる。

とか考えてたら、@NoTASKさんの「nfcpy 0.13.4でNFCのIDを読む」というページにシンプルなソース例が。

とりあえず print 分だけ修正し(ちゃんと、カッコで出力する文字列(変数も)を囲まないと、Python3 ではエラーになるようだ)、実行してみた。

2枚のカードを読んでみたが、それぞれ

pi@raspberrypi:~/test/pasori $ sudo ./nfc_test2.py
Type3Tag 'FeliCa Lite-S (RC-S966)' ID=012E4CD257C6A091 PMM=00F1000000014300 SYS=88B4
Type3Tag
b'012E4CD257C6A091'
pi@raspberrypi:~/test/pasori $ sudo ./nfc_test2.py
Type3Tag 'FeliCa Lite-S (RC-S966)' ID=012E4CD257C35873 PMM=00F1000000014300 SYS=88B4
Type3Tag
b'012E4CD257C35873'

このようにちゃんと ID も取れている。ばっちりじゃない。

この機能をマクロに組み込みたいのよね。

一発だけ読むのではなく、ずーっと読み込み続けるように無限ループさせて。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import nfc
import binascii
import time

def on_connect(tag):
    print(tag)
    print(tag.type)
    print(binascii.hexlify(tag.identifier).upper())

def main():
    while True:
        with nfc.ContactlessFrontend('usb') as clf:
            clf.connect(rdwr={'on-connect': on_connect})

        time.sleep(3)

main()

こんな感じで。

一度の実行で連続で読み込める。(time.sleep しないと、カード当てたとたんにザーっと連続で読み込むんで注意ね(笑)

pi@raspberrypi:~/test/pasori $ sudo ./nfc_test2.py
Type3Tag 'FeliCa Lite-S (RC-S966)' ID=012E4CD257C6A091 PMM=00F1000000014300 SYS=88B4
Type3Tag
b'012E4CD257C6A091'
Type3Tag 'FeliCa Lite-S (RC-S966)' ID=012E4CD257C35873 PMM=00F1000000014300 SYS=88B4
Type3Tag
b'012E4CD257C35873'
Type3Tag 'FeliCa Lite-S (RC-S966)' ID=012E4CD257C3AFA2 PMM=00F1000000014300 SYS=88B4
Type3Tag
b'012E4CD257C3AFA2'

これを元に、読み込んだ ID 情報をシートに貼り付けていくマクロを作ってみよう。

ところで、Python の無限ループって、Ctrl+C とかも受け付けなくなるんやね。Perl だと、Ctrl+C とかで止められるんだけどな。Python の場合、一定時間ごとに break する処理を書かないと、kill して止めるしかなくなる(^^;

マクロに組み込んだときにどうなるんかな?

このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のカテゴリはパソコンです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。


月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら