公開日: 2024年9月4日

【Python】シリアル通信プログラム開発環境構築 FreeBSD 編

Python でシリアル通信プログラムが書ければ、通信相手にコマンドを 1000 回送りつけたり、応答が正しいかどうか自動で判定させたり、といったことが簡単に行えるようになると思います。また、「ヌルモデムエミュレーター」を導入すると、パソコンがシリアルポートを備えていなくても、セルフ送受信することができるので (1 人でボケて 1 人でつっこむ、みたいな)、作った通信プログラムの動作確認が容易です。ここでは FreeBSD 上にそのような環境を構築します。Windows 11 の場合は次の記事を参照してください。

Python と pip のインストール

pySerial を利用するには、Python 本体と pip (Python のパッケージマネージャー) が必要です。どちらも packages から簡単にインストールすることができます。

# pkg install -y python py311-pip
...
=====
Message from py311-pip-23.3.2_3:

--
pip MUST ONLY be used:

 * With the --user flag, OR
 * To install or manage Python packages in virtual environments

Failure to follow this warning can and will result in an inconsistent
system-wide Python environment (LOCALBASE/lib/pythonX.Y/site-packages) and
cause errors.

Avoid using pip as root unless you know what you're doing.

なんか注意書きが表示されますね。pip を使うときは--userフラグを使用してユーザーローカルな環境にインストールするか、仮想環境にせよ、と言われているようです。このあとの作業では、これに従うことにしましょう。

pySerial のインストール

言われたとおりに、pySerial をユーザーローカルな環境にインストールします。一般ユーザーで実行していることに注意してください。

% python3 -m pip install --user pyserial
Collecting pyserial
  Downloading pyserial-3.5-py2.py3-none-any.whl.metadata (1.6 kB)
Downloading pyserial-3.5-py2.py3-none-any.whl (90 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 90.6/90.6 kB 2.7 MB/s eta 0:00:00
Installing collected packages: pyserial
  WARNING: The scripts pyserial-miniterm and pyserial-ports are installed in '/home/mijinco/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed pyserial-3.5

また何か注意書きが出てます。pyserial-miniterm や pyserial-ports といったスクリプトにパスが通っていないので、使うならパスを通せ、ということのようです。必要になったら考えることにして、とりあえず放っておきます。ちなみにアンインストール方法は次のとおり。

% python3 -m pip uninstall pyserial

ヌルモデムエミュレーターの準備

FreeBSD でヌルモデムエミュレーターを使うには、nmdm ドライバをカーネルに組み込むか、OS 起動時にカーネルモジュールとしてロードします。やり方はどちらも nmdm(4) のマニュアルに書かれています。カーネルモジュールをロードするほうが簡単そうなので、そうしましょう。/boot/loader.conf に次のように書いて OS を再起動します。

/boot/loader.conf
nmdm_load="YES"

再起動後、ちゃんとロードされたか確認してみます。

% kldstat | grep nmdm
 2    1 0xffffffff82344000     3978 nmdm.ko

よさそうです。あとは nmdm ドライバが勝手に /dev の下に nmdm<N>A、nmdm<N>B (N = 0, 1, 2, ...) という仮想シリアルポートのペアを作ってくれるはずです。…でも /dev の下を見ても nmdm* は存在しません (??)。

% ls /dev/nmdm*
%

nmdm(4) のマニュアルをよく読むと、FILES の項目のところに「デバイスファイルへのアクセスが発生したら、そのインスタンスがオンデマンドで作成される」というようなことが書かれています。というわけで、何かしらのシリアル通信ソフトを使って、とにかくアクセスしてみましょう。ここでは標準で用意されている cu コマンドを使います。なお、/dev/nmdm* にアクセスするには通常 root 権限が必要です。一般ユーザーでアクセスする方法はあとで検討します。

# cu -s 9600 -l /dev/nmdm0A
Connected

次にもう 1 個端末エミュレーターを開いて、今度は /dev/nmdm0B にアクセスします。

# cu -s 9600 -l /dev/nmdm0B
Connected

なんかちゃんと「Connected」と表示されています。この場合、nmdm0A と nmdm0B がペアになって、仮想的なクロスケーブルで接続されている状態です。nmdm0B の側からキーボードで「tuu」でも「kaa」でも適当な文字列を打ち込んでみましょう。nmdm0A 側に表示されるはずです。逆方向も同様に送受信可能なはずです。切断するときは「~.」(チルダ、ピリオド) と入力しましょう。

pySerial の動作確認

これでセルフ送受信ができるようになったので、pySerial が使えるかどうか、ちょっと確認してみましょう。次のように、/dev/nmdm0B を開いて適当な文字列を相手側に送りつけるだけの簡単なプログラムです。ファイル名は serialtest.py とでもしておきます。

serialtest.py
import serial

p = serial.Serial(port = '/dev/nmdm0B', baudrate = 9600, parity = 'N')
p.write('tuu\r\n'.encode('utf-8'))

受信側は cu コマンドなどで /dev/nmdm0A を開いて、待機させておきます。で、プログラムを root 権限で実行します。

% sudo python3 serialtest.py
Traceback (most recent call last):
  File "/usr/home/mijinco/python/serialtest.py", line 1, in <module>
    import serial
ModuleNotFoundError: No module named 'serial'

失敗です。pySerial をこのユーザーだけにインストールしたので、root 権限で実行してしまうとそのモジュールが見つからない、ということのようです。かといってこのユーザーには /dev/nmdm* へのアクセス権限がない。どうすればいいんですかね?

一般ユーザーで /dev/nmdm* にアクセスする

というわけで、一般ユーザーでも /dev/nmdm* にアクセスできるようにしてみます。結論からいうとできるにはできたのですが、ネットで調べても似たような状況での対処法があまり見つからなかったので、妥当かどうかは不明です。一応次の資料を参考にしました:

まずは /dev/nmdm* の所有者・所有グループ・パーミッションを確認します。cu コマンドでポートを開き、

# cu -s 9600 -l /dev/nmdm0A

別の端末エミュレーターで ls コマンドを使って確認します。

% ls -l /dev/nmdm*
crw--w----  1 root  tty  0x71  9月  1 16:31 /dev/nmdm0A
crw--w----  1 root  tty  0x72  9月  1 16:31 /dev/nmdm0B

所有グループは tty で、書き込みのみ許可されていることがわかります。読み出しはできない?ようです。また、/dev/nmdm* を開いたときに /var/spool/lock にロックファイルが作成されるようなので、このディレクトリの所有グループも確認しておきます。こちらは dialer であることがわかります。

% ls -l /var/spool/ | grep lock/
drwxrwxr-x  2 uucp   dialer  512  9月  1 17:18 lock/

そこで、次のようにして tty グループと dialer グループに nmdm ドライバを利用したいユーザーを追加します。

# pw group mod tty -m mijinco
# pw group mod dialer -m mijinco

次に、tty グループに /dev/nmdm* へのリード・ライト権限を与えます。/etc/devfs.rules に (存在しなければ新規作成して) 次の 2 行を追加します。

/etc/devfs.rules
[localrules=10]
add path 'nmdm*' mode 0660 group tty

/etc/rc.conf に次の 1 行を追加します。

/etc/rc.conf
devfs_system_ruleset="localrules"

devfs を再起動します。

# service devfs restart

これで一般ユーザーでも /dev/nmdm* を開くことができるようになりました。

% cu -s 9600 -l /dev/nmdm0A
can't open log file /var/log/aculog.
Connected

/var/log/aculog に書き込み権限がないため、cu のログ?が残らないようですが、そこはまあいいでしょう。

pySerial の動作確認 (リベンジ)

ではリベンジです。やり方は先ほどと同じですが、今回は root 権限が不要です。

% python3 serialtest.py

nmdm0A 側に「tuu」と表示されたでしょうか?されていれば、今日の作業はこれで終了です。お疲れさまでした。