pm2cd

en ja

PM2CD / PM2C-06A を Python から制御するためのパッケージです。

本パッケージは、既存の C版/MADOCA Device handler の実装思想と装置操作の意味を参照しながら、 Python では通信、装置固有処理、データ解析、利用者向け制御、multi-process 実行、 FastAPI 通信を分離して構成しています。

現在のバージョンは v0.1.0 です。

この README は、単なるインストールメモではなく、PM2CD Python 版の 総合マニュアル・設計仕様・チュートリアルとして記述しています。


1. 対象

対象装置は PM2CD / PM2C-06A です。

PM16C-16 と PM2CD / PM2C-06A は同一装置ではありません。 PM16C-16 の仕様をそのまま PM2CD の装置仕様として流用しません。

一方、既存の C版 dev_pm16c16_* Device handler 群には、 PM2CD Python 版を設計する上で重要な先行実装があります。

Python 版では、それら C ファイルを機械的に一対一移植するのではなく、

  • 一つの意味ある装置操作を一つの Device handler として扱う

  • 一つの操作に複数コマンドが必要なら Device 層で完結させる

  • 通信そのものは共通通信層へ分離する

  • raw response の複合解析は core parser へ分離する

  • Ctrl は raw bit / nibble / command string を利用者へ露出しない

という設計思想を引き継いでいます。


2. C版/MADOCA と Python 版の関係

2.1 C版の位置づけ

既存 C版は MADOCA 環境の Device handler として実装されてきたコードです。

今回 Python 版の基準として参照しているのは、MADOCA 全体のネットワーク API を Python へ移植することではなく、Device handler 内部で実際に行われている

  • command 生成

  • send / receive

  • 複数 command の処理順序

  • response の変換

  • 装置操作としての handler の粒度

です。

確認してきた C版 handler には、rotate/check_rotate、number/rate 変換、 emergency stop、backlash、display、hold、limit、pause、pulse、rate、 speed、speed mode、status/status_all、SETLS、TTL、version、home、 ALLREP、remote、stop、CRLF 送受信などがあります。

2.2 Python 版は C版の一対一翻訳ではない

Python 版では C版の責務を次のように再配置しています。

C版/MADOCA
    Device handler
        |
        | 装置操作の意味・処理順序を参照
        v
Python
    PM2CDDevice
        + core parser
        + Communication
        + PM2CDsingle
        + PM2CD

例えば C版の一つの handler が、

  1. 設定値を問い合わせる

  2. 必要な bit を変更する

  3. 設定 command を送る

  4. 本来の操作 command を送る

という処理を行う場合、Python 版でも「一つの意味ある装置操作」として PM2CDDevice 内で完結させます。

set_limit() はその代表例です。

SETLS?x
   |
   v
LimitSwitchSetting
   |
   +-- software limit が無効なら enable
   |
   v
SETLSx...
   |
   v
FLx... / BLx...

利用者に SETLS? と FL/BL を別々に操作させる設計にはしません。

2.3 MADOCA と Python server は別レイヤー

C版で使用されてきた MADOCA と、Python 版で使用する FastAPI / ese774_frame は同じ通信 framework ではありません。

Python 版 PM2CD は DeviceClass として単独でも利用でき、 必要な場合に FastAPI server/client を上に載せます。

したがって Python 版の設計では、

装置制御ロジック
        !=
ネットワーク framework

を明確に分離しています。


3. Python 版の全体構成

v0.1.0 の基本構造は次の通りです。

利用プログラム
    |
    +-- local
    |      |
    |      +--> PM2CDsingle
    |      |
    |      `--> PM2CD
    |
    `-- network
           |
           +--> SyncPM2CDClient / AsyncPM2CDClient
           |
           v
       FastAPI / ese774_frame
           |
           v
         PM2CD
           |
           v
     ProcessExecutor
           |
           +--> PM2CDsingle worker 1
           +--> PM2CDsingle worker 2
           +--> ...
                    |
                    v
                PM2CDDevice
                    |
                    v
          shared Communication proxy
                    |
                    v
             Communication x 1
                    |
                    v
             TCP + CRLF
                    |
                    v
             PM2CD / PM2C-06A

受信データについては、

raw response
    |
    v
core parser
    |
    v
frozen dataclass / Python value
    |
    v
PM2CDDevice
    |
    v
PM2CDsingle
    |
    v
PM2CD / client

という流れになります。


4. 各層の責務

4.1 Communication

TCP 接続と文字列送受信だけを担当します。

主な責務は、

  • TCP connect / disconnect

  • command への CRLF 付加

  • send

  • receive

  • query

  • timeout / socket error 処理

  • receive buffer 管理

です。

PM2CD 固有の STS?、SETLS?、FL? などの意味は知りません。

query() は send と receive を同一 lock 範囲で処理し、 要求と応答の対応を維持します。

4.2 core

装置の複合 response を意味のある Python データへ変換します。

現在の主要データ型は frozen dataclass として扱います。

  • MotorStatus

  • ControllerStatus

  • HomeStatus

  • LimitStatus

  • MotorSetting

  • LimitSwitchSetting

  • StopMode

データ保持と parsing は分離します。

raw response
    |
    v
parse_xxx()
    |
    v
@dataclass(frozen=True)

解析不能な response について、不正な状態 object を無理に生成せず、 取得 API の失敗として None を返す設計を基本とします。

4.3 PM2CDDevice

装置固有の実 handler 層です。

ここが C版 Device handler に最も近い責務を持ちます。

  • command 名を知る

  • command を組み立てる

  • 入力値を検証する

  • Communication を使用して send/query する

  • 必要な複数 command を一つの操作として実行する

  • core parser を呼ぶ

  • 装置固有 response を Python 値へ変換する

Device を単純な send() / query() wrapper にはしません。

4.4 PM2CDsingle

単一 process で動作する高水準の制御クラスです。

主な責務は、

  • 利用者向け API

  • default motor

  • 移動コンテキスト

  • wait_for_complete()

  • backlash を含む位置決め完了判定

  • Device API を意味ベースの公開 API へ整理

です。

PM2CDsingle は Communication の外部 injection に対応しています。

ctrl = PM2CDsingle(
    ip_address=IP_ADDRESS,
    communication=communication,
)

この dependency injection point は test 用だけではなく、 v0.1.0 の multi-process 構成で一つの物理 Communication を 複数 worker から使用するためにも利用します。

4.5 PM2CD

v0.1.0 では、利用者向け PM2CD は ProcessExecutor を使用する multi-process facade です。

公開 API の意味は PM2CDsingle と揃え、 実際の handler 実行を worker process へ dispatch します。

PM2CD.method(...)
    |
    v
ProcessExecutor.submit(...)
    |
    v
PM2CDsingle.method(...)
    |
    v
ProcessExecutor.wait(...)
    |
    v
return value

このため利用者から見る API は同期的ですが、 内部では handler が worker process で実行されます。


5. PM2CDsingle と PM2CD の使い分け

5.1 PM2CDsingle

一つの Python process 内で直接装置を制御する場合に使用します。

from pm2cd.pm2cd_single import PM2CDsingle

ctrl = PM2CDsingle(
    ip_address="10.170.10.62",
    port=7777,
    timeout_sec=3.0,
)

try:
    ctrl.remote()
    position = ctrl.get_position(0)
    print(position)
finally:
    ctrl.close()

構造は単純です。

application
    |
    v
PM2CDsingle
    |
    v
PM2CDDevice
    |
    v
Communication
    |
    v
PM2CD

5.2 PM2CD

worker process を使用する通常の multi-process 構成です。

from pm2cd.pm2cd_ctrl import PM2CD

ctrl = PM2CD(
    ip_address="10.170.10.62",
    port=7777,
    timeout_sec=3.0,
    worker_count=2,
)

try:
    ctrl.remote()
    position = ctrl.get_position(0)
    print(position)
finally:
    ctrl.close()

worker_count は ProcessExecutor の worker process 数です。


6. なぜ複数 worker でも Communication は一つなのか

6.1 誤った構成

worker ごとに PM2CDsingle を通常生成すると、 各 worker が自分の Communication を作ります。

worker 1 -> Communication 1 --+
worker 2 -> Communication 2 --+--> same PM2CD
worker 3 -> Communication 3 --+

各 Communication の lock は別 process / 別 instance なので、 装置全体の通信順序を保証しません。

6.2 v0.1.0 の構成

v0.1.0 では BaseManager が一つの Communication を所有し、 各 worker には proxy を渡します。

worker 1 --+
worker 2 --+--> Communication proxy --> Communication x 1 --> PM2CD
worker N --+

したがって、

worker process      N
PM2CDsingle         N
Communication       1
TCP connection      1

となります。

これは ProcessExecutor が PM2CD の通信を自動的に共有しているのではありません。

物理 resource の ownership は PM2CD 側の設計です。


7. ProcessExecutor との関係

ProcessExecutor は PM2CD 専用 framework ではありません。

PM2CD 側が利用している主な機能は、

  • worker process の生成

  • worker_factory(context)

  • handler dispatch

  • submit()

  • wait()

  • SharedContext

  • manager-backed shared state

  • process-safe lock

です。

概念上の責務は次のように分離します。

ProcessExecutor
    |
    +-- process を管理する
    +-- job を dispatch する
    +-- shared context を提供する
    |
    `-- PM2CD protocol は知らない

PM2CD
    |
    +-- PM2CDsingle を worker として構成する
    +-- Communication を一つだけ所有する
    +-- controller state を SharedContext へ置く
    |
    `-- PM2CD 固有の resource ownership を決める

7.1 worker_factory(context)

PM2CD は worker_factory(context) を使用して各 worker の PM2CDsingle を生成します。

その際、

  • 共通 Communication proxy

  • SharedContext.state

を worker に接続します。

7.2 shared state

worker が複数あると、通常の instance variable は worker ごとに別です。

PM2CD では少なくとも、

  • default motor

  • 移動コンテキスト

は worker をまたいで意味を維持する必要があります。

そのため SharedContext.state を使用します。


8. 移動コンテキストと wait_for_complete()

move() / move_relative() は、単に command を送るだけではありません。

位置決め完了判定のため、モーターごとに直前の移動条件を保持します。

主な内容は、

  • target position

  • backlash 指定

  • backlash enabled

  • backlash value

です。

8.1 backlash なし

基本的な完了判定は、

move
  |
  v
active
  |
  v
target position + inactive
  |
  v
complete

です。

ただし短い移動では polling 間で active 状態を観測できない場合があるため、 active を観測したこと自体を必須条件にはしません。

8.2 backlash あり

backlash 補正では概念的に、

target
  |
  v
backlash point
  |
  v
target

という追加動作が入ります。

そのため最初に target position で inactive になっただけでは、 補正完了と即断できません。

wait_for_complete() は直前の移動コンテキストを参照して、 最終 target への復帰までを判定します。

短い backlash 動作では active/inactive 遷移が polling 間で縮退する場合も考慮します。

8.3 multi-process での共有

move() を worker 1、wait_for_complete() を worker 2 が処理する可能性があります。

そのため v0.1.0 の PM2CD では移動コンテキストを SharedContext.state へ置きます。

worker 1: move()
       |
       v
SharedContext.state
       |
       v
worker 2: wait_for_complete()

これにより worker scheduling に依存せず同じ制御意味を維持します。


9. motor property

PM2CDsingle / PM2CD は default motor を motor property で保持します。

ctrl.motor = 0
position = ctrl.get_position()

各 API で motor を明示した場合は、その値を優先します。

position = ctrl.get_position(1)

multi-process PM2CD では default motor も shared state として扱い、 worker ごとに異なる default motor にならないようにします。


10. FastAPI / ese774_frame

PM2CD は ese774_frame の完全自動ディスパッチを使用します。

基本設定は、

FastApiServer
api_spec=None
object_name="pm2cd"

です。

個別の Pydantic model や ApiSpec を PM2CD 側で列挙せず、 公開メソッドとアノテーションを利用して API を構成します。

10.1 dataclass の転送

PM2CD は状態取得 API で frozen dataclass を返します。

例:

  • MotorStatus

  • ControllerStatus

  • HomeStatus

  • LimitStatus

  • MotorSetting

  • LimitSwitchSetting

  • StopMode

これらを完全自動ディスパッチで JSON 化し、 client 側で元の型へ復元するため、 ese774_frame v0.6.0 以上を使用します。

10.2 client

同期 client と非同期 client を使用できます。

SyncPM2CDClient
AsyncPM2CDClient

client は object_name="pm2cd" で server と対応します。


11. server の起動

11.1 module 側

v0.1.0 の server module は worker 数を受け取れます。

from pm2cd.server import run

run(
    worker_count=2,
)

内部では、

run(worker_count)
    |
    v
create_server(worker_count)
    |
    v
FastApiServer(device_kwargs)
    |
    v
PM2CD(worker_count=worker_count)

と渡されます。

server process の数と、PM2CD 内部の ProcessExecutor.worker_count は 別の概念です。

11.2 example server

実際の起動 example は worker 数をコード側で設定します。

from pm2cd.server import run

WORKER_COUNT = 2


def main() -> None:
    run(
        worker_count=WORKER_COUNT,
    )


if __name__ == "__main__":
    main()

worker 数のための CLI option は使用しません。


12. 基本チュートリアル

以下では PM2CD を使用します。

12.1 接続と終了

from pm2cd.pm2cd_ctrl import PM2CD

ctrl = PM2CD(
    ip_address="10.170.10.62",
    port=7777,
    timeout_sec=3.0,
    worker_count=2,
)

try:
    print(ctrl.is_connected())
finally:
    ctrl.close()

12.2 REMOTE へ切り替える

ctrl.remote()

LOCAL へ戻す場合は、

ctrl.local()

です。

12.3 状態を取得する

status = ctrl.get_status(0)

if status is not None:
    print(status.position)
    print(status.is_busy)

全軸:

statuses = ctrl.get_status_all()

12.4 現在位置を取得する

position = ctrl.get_position(0)
print(position)

default motor を使用する場合:

ctrl.motor = 0
position = ctrl.get_position()

12.5 絶対位置移動

ctrl.move(
    0,
    1000,
    backlash=False,
)

status = ctrl.wait_for_complete(
    0,
    timeout_sec=30.0,
)

12.6 backlash 付き位置決め

ctrl.move(
    0,
    1000,
    backlash=True,
)

status = ctrl.wait_for_complete(
    0,
    timeout_sec=30.0,
)

PM2CD では移動コンテキストが worker 間で共有されるため、 move() と wait_for_complete() が別 worker で実行されても backlash 条件を引き継ぎます。

12.7 相対移動

ctrl.move_relative(
    0,
    100,
    backlash=True,
)

ctrl.wait_for_complete(
    0,
    timeout_sec=30.0,
)

12.8 通常停止と非常停止

通常停止:

ctrl.stop(0)
ctrl.stop_all()

非常停止:

ctrl.emergency_stop(0)
ctrl.emergency_stop_all()

通常停止と非常停止は別 API として扱います。


13. 速度・rate・backlash

13.1 speed

speed = ctrl.get_speed(0)
ctrl.set_speed(0, 1000)

LOW / MIDDLE / HIGH をまとめて扱う API:

profile = ctrl.get_speed_profile(0)

個別 API:

get_high_speed / set_high_speed
get_middle_speed / set_middle_speed
get_low_speed / set_low_speed

13.2 rate

rate number:

number = ctrl.get_rate_number(0)
ctrl.set_rate_number(0, number)

rate 値:

rate = ctrl.get_rate(0)
ctrl.set_rate(0, rate)

変換 API:

rate = ctrl.number_to_rate(number)
number = ctrl.rate_to_number(rate)

13.3 backlash

backlash = ctrl.get_backlash(0)
ctrl.set_backlash(0, backlash)

14. limit

PM2CD Python API では、無修飾の limit は software limit を意味します。

hardware limit は hard_ を明示します。

14.1 software limit position

cw = ctrl.get_limit(0, "cw")
ccw = ctrl.get_limit(0, "ccw")

ctrl.set_limit(0, "cw", 10000)
ctrl.set_limit(0, "ccw", -10000)

14.2 software limit state

status = ctrl.get_limit_status(0)

意味順序:

[CW, CCW]

14.3 hardware limit state

status = ctrl.get_hard_limit_status(0)

意味順序:

[CW, CCW, HP]

14.4 enable

software/digital limit:

enabled = ctrl.get_limit_enabled(0)
ctrl.set_limit_enabled(0, True)

hardware limit:

enabled = ctrl.get_hard_limit_enabled(0)
ctrl.set_hard_limit_enabled(0, True)

14.5 normally closed / open

setting = ctrl.get_hard_limit_normally_closed(0)

ctrl.set_hard_limit_normally_closed(
    0,
    "cw",
    True,
)

公開 API では raw DYYY0yyy を直接扱いません。


15. HOME / HOLD / PAUSE

HOME:

home
find_home
scan_home
get_home_status
set_home_status
get_home_position
set_home_position
get_home_offset
set_home_offset

preset / JOG:

get_preset_position
set_preset_position
get_jog_pulse
set_jog_pulse

HOLD:

get_hold
set_hold
get_hold_on_time
set_hold_on_time
get_hold_off_time
set_hold_off_time

PAUSE:

pause
resume
get_pause_status

16. motor / stop setting

motor setting:

get_motor_setting
set_motor_setting

stop mode:

get_stop_mode
set_stop_mode

取得値は dataclass として扱います。


17. 主な公開 API

接続・mode

motor
is_connected
connect
disconnect
remote
local
close

version / controller

version
get_version
get_hardware_version
get_controller_status
controller_status

motion

move
move_relative
scan
constant_speed_scan
jog
scan_home
home
find_home
stop
stop_all
emergency_stop
emergency_stop_all
get_rotate
is_busy
wait_for_complete

status / position

get_status
get_status_all
status_all
get_position
set_position
zero_position

speed / rate / backlash

get_speed
set_speed
get_speed_profile
set_speed_profile
get_high_speed
set_high_speed
get_middle_speed
set_middle_speed
get_low_speed
set_low_speed
get_speed_mode
set_speed_mode
get_rate_number
set_rate_number
get_rate
set_rate
number_to_rate
rate_to_number
get_backlash
set_backlash

limit

get_limit
set_limit
get_limit_status
get_limit_status_all
get_hard_limit_status
get_hard_limit_status_all
get_limit_enabled
set_limit_enabled
get_hard_limit_enabled
set_hard_limit_enabled
get_hard_limit_normally_closed
set_hard_limit_normally_closed
get_limit_switch_setting

pause / hold

pause
resume
get_pause_status
get_hold
set_hold
get_hold_on_time
set_hold_on_time
get_hold_off_time
set_hold_off_time

HOME / preset / JOG

get_home_status
set_home_status
get_home_position
set_home_position
get_home_offset
set_home_offset
get_preset_position
set_preset_position
get_jog_pulse
set_jog_pulse

setting

get_motor_setting
set_motor_setting
get_stop_mode
set_stop_mode

18. 戻り値の基本方針

装置制御 API は概ね次の方針です。

操作成功/失敗    bool
値取得成功       値
値取得失敗       None

取得 API は Optional[...] のアノテーションを持つものがあります。

状態・設定 object は frozen dataclass として扱い、 取得後に状態を書き換える設計にはしません。


19. エラーと入力検証

入力値は可能な限り PM2CDDevice 側で検証し、 不正な値を装置へ送らない構成にします。

通信層の error は通信責務として扱い、 装置 response の解析失敗は parser / Device の責務として扱います。

Ctrl は装置 protocol の raw error 表現を利用者 API の中心には置きません。


20. server/client 利用時の構成

network 越しの利用では次の構成になります。

client application
      |
      v
SyncPM2CDClient
or AsyncPM2CDClient
      |
      v
HTTP / FastAPI
      |
      v
ese774_frame FastApiServer
      |
      v
PM2CD
      |
      v
ProcessExecutor
      |
      v
PM2CDsingle worker x N
      |
      v
shared Communication
      |
      v
PM2CD

PM2CD の内部 worker 数を増やしても、 装置への TCP connection を増やさないことが v0.1.0 の重要な設計条件です。


21. example の考え方

example は段階的に確認できるようにします。

21.1 single local

目的:

  • framework を介さず PM2CDsingle 自体を確認する

  • 装置 protocol / Device / Ctrl の基準動作を確認する

21.2 multi local

目的:

  • PM2CD + ProcessExecutor を確認する

  • worker process からの実制御を確認する

  • worker 数を2以上にしても Communication が一つである構成を確認する

21.3 FastAPI server/client

目的:

  • ese774_frame の完全自動ディスパッチを確認する

  • dataclass の server/client 間転送を確認する

  • network 越しでも同じ PM2CD 公開 API を使用する

21.4 server 起動 example

worker 数は example script の定数で指定します。

WORKER_COUNT = 2

CLI option で worker 数を与える方式にはしません。


22. 推奨する確認順序

装置を使った動作確認は、層を一度に増やさず次の順序で行います。

1. Communication
       |
2. PM2CDDevice
       |
3. PM2CDsingle local
       |
4. PM2CD multi local
       |
5. FastAPI server
       |
6. Sync/Async client

multi-process の問題と network framework の問題を同時に切り分けないためです。

特に新しい motion API の確認では、一つの method を小さく確認してから 次の層へ進めます。


23. close() と resource ownership

multi-process PM2CD では Communication は全 worker の共有 resource です。

したがって、一つの worker に PM2CDsingle.close() を実行させてはいけません。

PM2CD.close() は、

ProcessExecutor.close()
        |
        v
shared Communication.close()
        |
        v
CommunicationManager.shutdown()

の順で終了します。

物理 resource を作成した owner が最後に一度だけ閉じる、という構成です。


24. 設計上の重要事項

24.1 Ctrl と Device を混ぜない

Ctrl に command string を直接書き始めると、 C版で整理されていた Device handler の責務が崩れます。

装置固有 command は Device に置きます。

24.2 Device を薄くしすぎない

一つの意味ある操作に複数 command が必要なら、 その一連の処理は Device handler に残します。

24.3 core dataclass に処理を持たせすぎない

dataclass はデータを表します。

response parsing は parser 関数へ分離します。

24.4 ProcessExecutor に装置固有責務を入れない

ProcessExecutor は PM2CD の TCP socket や protocol を知りません。

Communication の共有方法は PM2CD が決めます。

24.5 shared は公開 API の概念にしない

worker 間で state を共有していても、 get_shared_xxx() のような専用 API を利用者へ追加しません。

shared state は multi-process 実装の内部事情です。


25. dependency

FastAPI 完全自動ディスパッチを使用する場合:

ese774_frame >= 0.6.0

が必要です。

multi-process PM2CD は ProcessExecutor の、

worker_factory
SharedContext
shared_context
submit
wait

を使用します。

PM2CD と ProcessExecutor の責務は分離されており、 ProcessExecutor 自体には PM2CD 固有コードを入れません。


26. v0.1.0 の位置づけ

v0.0.x では主に、

  • C版 Device handler の意味の整理

  • Communication / core / Device / Ctrl の責務分離

  • PM2CDsingle API の拡張

  • backlash を含む wait_for_complete()

  • frozen dataclass + parser

  • FastAPI 完全自動ディスパッチ

を整備しました。

v0.1.0 では、その single-process 制御ロジックを壊さず、 PM2CD を実用的な multi-process facade として成立させました。

中心となる設計は、

PM2CDsingle worker x N
        +
SharedContext
        +
Communication x 1
        +
TCP connection x 1

です。

これにより、単一 process の装置制御クラス、multi-process 制御、 FastAPI server/client を同じ Device/Ctrl 設計の上に積み上げる構成になっています。