Metadata-Version: 2.4
Name: pm2cd
Version: 0.1.1
Summary: tuji pm2cd
Author-email: Kengo NAKADA <kengo.nakada@email.com>
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: xlogger-py
Requires-Dist: dataclasses
Requires-Dist: coloredlogs
Requires-Dist: colorlog
Requires-Dist: pytest
Requires-Dist: pytest-asyncio
Requires-Dist: pyyaml
Requires-Dist: toml
Requires-Dist: nest_asyncio
Requires-Dist: sphinx
Requires-Dist: myst-parser
Requires-Dist: sphinx-autobuild
Requires-Dist: sphinx-rtd-theme
Requires-Dist: sphinxcontrib-napoleon
Requires-Dist: linkify-it-py

# pm2cd

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版の責務を次のように再配置しています。

``` text
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()` はその代表例です。

``` text
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 版の設計では、

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

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

------------------------------------------------------------------------

## 3. Python 版の全体構成

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

``` text
利用プログラム
    |
    +-- 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
```

受信データについては、

``` text
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 は分離します。

``` text
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 に対応しています。

``` python
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 します。

``` text
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 内で直接装置を制御する場合に使用します。

``` python
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()
```

構造は単純です。

``` text
application
    |
    v
PM2CDsingle
    |
    v
PM2CDDevice
    |
    v
Communication
    |
    v
PM2CD
```

### 5.2 `PM2CD`

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

``` python
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` を作ります。

``` text
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 を渡します。

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

したがって、

``` text
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

です。

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

``` text
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 なし

基本的な完了判定は、

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

です。

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

### 8.2 backlash あり

backlash 補正では概念的に、

``` text
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`
へ置きます。

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

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

------------------------------------------------------------------------

## 9. `motor` property

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

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

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

``` python
position = ctrl.get_position(1)
```

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

------------------------------------------------------------------------

## 10. FastAPI / `ese774_frame`

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

基本設定は、

``` text
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 を使用できます。

``` text
SyncPM2CDClient
AsyncPM2CDClient
```

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

------------------------------------------------------------------------

## 11. server の起動

### 11.1 module 側

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

``` python
from pm2cd.server import run

run(
    worker_count=2,
)
```

内部では、

``` text
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 数をコード側で設定します。

``` python
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 接続と終了

``` python
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 へ切り替える

``` python
ctrl.remote()
```

LOCAL へ戻す場合は、

``` python
ctrl.local()
```

です。

### 12.3 状態を取得する

``` python
status = ctrl.get_status(0)

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

全軸:

``` python
statuses = ctrl.get_status_all()
```

### 12.4 現在位置を取得する

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

default motor を使用する場合:

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

### 12.5 絶対位置移動

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

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

### 12.6 backlash 付き位置決め

``` python
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 相対移動

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

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

### 12.8 通常停止と非常停止

通常停止:

``` python
ctrl.stop(0)
ctrl.stop_all()
```

非常停止:

``` python
ctrl.emergency_stop(0)
ctrl.emergency_stop_all()
```

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

------------------------------------------------------------------------

## 13. 速度・rate・backlash

### 13.1 speed

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

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

``` python
profile = ctrl.get_speed_profile(0)
```

個別 API:

``` text
get_high_speed / set_high_speed
get_middle_speed / set_middle_speed
get_low_speed / set_low_speed
```

### 13.2 rate

rate number:

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

rate 値:

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

変換 API:

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

### 13.3 backlash

``` python
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

``` python
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

``` python
status = ctrl.get_limit_status(0)
```

意味順序:

``` text
[CW, CCW]
```

### 14.3 hardware limit state

``` python
status = ctrl.get_hard_limit_status(0)
```

意味順序:

``` text
[CW, CCW, HP]
```

### 14.4 enable

software/digital limit:

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

hardware limit:

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

### 14.5 normally closed / open

``` python
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:

``` text
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:

``` text
get_preset_position
set_preset_position
get_jog_pulse
set_jog_pulse
```

HOLD:

``` text
get_hold
set_hold
get_hold_on_time
set_hold_on_time
get_hold_off_time
set_hold_off_time
```

PAUSE:

``` text
pause
resume
get_pause_status
```

------------------------------------------------------------------------

## 16. motor / stop setting

motor setting:

``` text
get_motor_setting
set_motor_setting
```

stop mode:

``` text
get_stop_mode
set_stop_mode
```

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

------------------------------------------------------------------------

## 17. 主な公開 API

### 接続・mode

``` text
motor
is_connected
connect
disconnect
remote
local
close
```

### version / controller

``` text
version
get_version
get_hardware_version
get_controller_status
controller_status
```

### motion

``` text
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

``` text
get_status
get_status_all
status_all
get_position
set_position
zero_position
```

### speed / rate / backlash

``` text
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

``` text
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

``` text
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

``` text
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

``` text
get_motor_setting
set_motor_setting
get_stop_mode
set_stop_mode
```

------------------------------------------------------------------------

## 18. 戻り値の基本方針

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

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

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

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

------------------------------------------------------------------------

## 19. エラーと入力検証

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

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

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

------------------------------------------------------------------------

## 20. server/client 利用時の構成

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

``` text
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 の定数で指定します。

``` python
WORKER_COUNT = 2
```

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

------------------------------------------------------------------------

## 22. 推奨する確認順序

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

``` text
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()` は、

``` text
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 完全自動ディスパッチを使用する場合:

``` text
ese774_frame >= 0.6.0
```

が必要です。

multi-process `PM2CD` は `ProcessExecutor` の、

``` text
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 として成立させました。

中心となる設計は、

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

です。

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