Metadata-Version: 2.4
Name: csm-arguments
Version: 0.1.1
Summary: CSM 参数编解码：SAFESTR / ERRSTR / HEXSTR 与 CSM API String 的 Python 实现
Author: NEVSTOP-LAB
License: MIT
Project-URL: Homepage, https://github.com/NEVSTOP-LAB/CSM-Python-Arguments
Project-URL: Documentation, https://github.com/NEVSTOP-LAB/CSM-Python-Arguments/tree/main/docs
Project-URL: Issues, https://github.com/NEVSTOP-LAB/CSM-Python-Arguments/issues
Project-URL: Changelog, https://github.com/NEVSTOP-LAB/CSM-Python-Arguments/releases
Keywords: LabVIEW,CSM,variant,arguments,codec
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# CSM-Python-Arguments

CSM（Communicable State Machine）参数字符串的 Python 编解码实现。

CSM 中所有参数与返回值都以字符串承载，背后可以是任意 LabVIEW 数据类型。本项目提供与
LabVIEW 侧完全一致的编解码能力：既能把 Python 值编码成 CSM 参数字符串，也能把 LabVIEW
产生的参数字符串还原为 Python 值。

## 支持范围

| 编码方案 | 说明 |
| --- | --- |
| `SAFESTR` | 安全字符串：把 CSM 关键字转义为 `%Hex`，避免破坏消息解析 |
| `ERRSTR` | LabVIEW 错误簇：`<ErrStr>[Error: code] description` |
| `HEXSTR` | 任意数据：variant 内存映像的十六进制串 |
| CSM API String | 无标记的纯文本参数：全部基本类型、数组、簇与波形 |

数据类型覆盖：String、Path、Tag、Refnum、Boolean、Integer（I8…U64）、Float（SGL/DBL）、
Complex（CSG/CDB）、Timestamp、Enum、Array（多维）、Cluster（NamedTuple）、Waveform、Error，
以及经 `HEXSTR` 承载的其他类型（含嵌套 variant）。簇在 Python 侧一律以 `NamedTuple` 表示，
各类型与 Python 表示的对应关系见「数据类型对应」。

不在支持范围：扩展精度浮点 `EXT`（`0x0B`）、扩展精度复数 `CXT`（`0x0E`）与带物理单位的数值
（`0x19..0x1E`）。CSM API String 的 Float / Complex 只定义 SGL / DBL 与 CSG / CDB。这些类型码
可被类型表识别，编码与解码都拒绝，variant 层同样不实现。

两个类型可以编解码但无法与 LabVIEW 2026 经 `HEXSTR` 互操作：Refnum（`0x70`）与 `uint64`
枚举（`0x18`）。实测证据见 `labview/README.md`。

## 数据类型对应

LabVIEW 类型与 Python 表示的对应关系。同一份对应关系在 API String 与 HEXSTR 两层都成立，
类型码与二进制布局见 `docs/variant-binary-format.md`。

| LabVIEW 类型 | Python 表示 | Python 值示例 | API String 文本 |
| --- | --- | --- | --- |
| `Null`（空 variant） | `None` | `None` | 无（只经 `HEXSTR`） |
| `Boolean` | `bool` | `True` | `TRUE` |
| `I8` / `I16` / `I32` / `I64` | `int` | `-128` | `-128` |
| `U8` / `U16` / `U32` / `U64` | `int` | `255` | `255` |
| `SGL` / `DBL` | `float` | `1.5` | `1.5` |
| `CSG` / `CDB` | `complex` | `1.5-2.5j` | `1.5-2.5i` |
| `String` | `str` | `'a,b;c'` | `a%2Cb%3Bc` |
| `Path` | `str` | `'C:\temp\a.txt'` | `C:\temp\a.txt` |
| `Pict` | `str` | `'AB'` | `AB` |
| `Refnum` | `int`（variant 中的引用号） | `3` | 设备名文本，如 `VISA::INSTR` |
| `Enum` | `int`（项序） | `1` | 项名文本，如 `beta` |
| `Timestamp` | `datatypes.LVTimestamp` | `LVTimestamp(1, 0)` | `1904-01-01T00:00:01.000Z` |
| `Array` | `list` | `[[1.0, 2.0], [3.0, 4.0]]` | `[1,2;3,4]` |
| `Cluster` | `NamedTuple` | `{'x': 1, 'y': 2}` | `{x:1;y:2}` |
| `Error` | `datatypes.LVError`（簇 `{status, code, source}`） | `LVError(True, -1, 'boom')` | `{status:TRUE;code:-1;source:boom}` |
| `Waveform` | `datatypes.LVWaveform` | `LVWaveform(t0=LVTimestamp(1, 0), dt=0.5, Y=[1.0])` | `{t0:1904-01-01T00:00:01.000Z;dt:500m;Y:[1];…}` |
| `Variant` | `bytes`（内层完整 blob） | `EMPTY_VARIANT_BLOB` | 无（只经 `HEXSTR`） |

> [!NOTE]
>
> - LabVIEW 侧 `Tag` 与 `String` 同为安全字符串文本，Python 侧是 `str`。
> - `Timestamp` 还接受 `datetime` 与秒数，`Cluster`（含 `Error`）与 `Waveform` 还接受 `dict`。
> - 编码省略 `lvtype` 时按值推断：`bool` → `Boolean`、`int` → `I32`、`float` → `DBL`、`complex` → `CDB`、`str` → `String`、`bytes` → `Variant`，`NamedTuple` / `dict` / `list` 按元素递归。
> - 解码需要目标类型：`lvtype` 可以给类型单例、`list[int]` 一类类型注解，或用来推导类型的原型值。

```python
from csm.arguments import HexStr, api_string, lv_variant
from csm.arguments.datatypes import LVError, LVTimestamp

error = LVError(True, -1, "boom")

# 簇以 NamedTuple、dict 或同字段个数的序列表示。HEXSTR 与 API String 共用同一份类型
api_string.convert_data_to_api_string_arguments(error)
# '{status:TRUE;code:-1;source:boom}'
HexStr.convert_data_to_hexstr(error)
# '<HEXSTR>2600800000000004000C40210673746174757300000B40030004...'
HexStr.convert_hexstr_to_data(HexStr.convert_data_to_hexstr(error))
# Cluster(status=True, code=-1, source='boom')

# API String 不带类型信息，解码要给出目标类型
api_string.convert_api_string_arguments_to_data(
    "[1,2;3,4]", lv_variant.array_of(lv_variant.DBL, 2))
# [[1.0, 2.0], [3.0, 4.0]]
api_string.convert_api_string_arguments_to_data("beta", lv_variant.enum_of(["alpha", "beta"]))
# 1

# 时间戳：Python 侧是 LVTimestamp（秒 + 2^-64 秒小数），缺省文本为 RFC3339
api_string.convert_data_to_api_string_arguments(LVTimestamp(1, 0), lv_variant.TimestampType)
# '1904-01-01T00:00:01.000Z'
```

## 解析边界

`HEXSTR` 的载荷来自 LabVIEW，可能是截断的或被人为构造的。解析侧对每一次读取都校验范围、
长度与计数，越界、负长度、过大的计数、违反后序排列的记录引用、过深的嵌套一律抛
`ArgumentFormatError`（继承 `ValueError`），不会读到界外，也不会因为声明的计数过大而长时间
循环。详见 `docs/variant-binary-format.md` 的“解析边界”。

`ERRSTR` 解码与 `CSM - Convert Argument to Error.vi` 一致，不报错：头部 `[Kind: code]`
缺失或不合法时，整段文本作为 `source`、`code` 取 0、`status` 取 True。

## 编码边界

编码侧复用同一套上限与校验：嵌套超过 64 层、波形 `attributes` 或变体值不是完整合法的
variant blob、取值与声明类型不符（整数越界、簇字段个数不符等）都以 `ArgumentFormatError`
拒绝。因此“编码成功”隐含“同一类型可以解码”。详见 `docs/python-api.md` 的“输入校验”。

## 安装

```bash
pip install csm-arguments
```

源码安装：

```bash
git clone https://github.com/NEVSTOP-LAB/CSM-Python-Arguments.git
cd CSM-Python-Arguments
pip install -e .
```

最低支持 Python 3.8，运行时仅依赖标准库。

## 快速开始

```python
from csm.arguments import ErrorStr, HexStr, SafeStr
from csm.arguments.datatypes import LVError

# SAFESTR：字符串含 CSM 关键字时自动转义并加标记
SafeStr.make_string_arguments_safe("a,b;c")                  # '<SafeStr>a%2Cb%3Bc'
SafeStr.revert_arguments_safe_string("<SafeStr>a%2Cb%3Bc")   # 'a,b;c'

# ERRSTR：错误簇与可读参数互转
ErrorStr.convert_error_to_argument(LVError(True, -1, "文件未找到"))
# '<ErrStr>[Error: -1]文件未找到'
ErrorStr.convert_argument_to_error("<ErrStr>[Error: -1]文件未找到")
# LVError(status=True, code=-1, source='文件未找到')

# HEXSTR：任意 Python 值 <-> LabVIEW variant 十六进制串
HexStr.convert_data_to_hexstr([1, 2, 3])
# '<HEXSTR>2600800000000002000500030000...'
HexStr.convert_hexstr_to_data(HexStr.convert_data_to_hexstr([1, 2, 3]))
# [1, 2, 3]
```

按类型精确编解码：

```python
from csm.arguments import HexStr, lv_variant
from csm.arguments.datatypes import LVError

value = LVError(True, -1, "src")
blob_argument = HexStr.convert_data_to_hexstr(value, lv_variant.infer(value))
value, lvtype, attributes = HexStr.convert_hexstr_to_data(blob_argument, with_type=True)
```

CSM API String（无类型标记的纯文本参数）：

```python
from csm.arguments import api_string, lv_variant

api_string.convert_data_to_api_string_arguments([1, 2, 3])                 # '[1,2,3]'
api_string.convert_data_to_api_string_arguments(1.5, lv_variant.DBL)       # '1.5'
api_string.convert_data_to_api_string_arguments(1e20, lv_variant.DBL)      # '100E'
api_string.convert_api_string_arguments_to_data("0x1234", lv_variant.I32)  # 4660
api_string.convert_api_string_arguments_to_data("1.23k", lv_variant.DBL)   # 1230.0
api_string.convert_api_string_arguments_to_data(
    "beta", lv_variant.enum_of(["alpha", "beta", "gamma"], lv_variant.TYPE_ENUM_U8))
# 1
```

## 与 CSM 编解码 API 的对应

编解码函数一律按 LabVIEW VI 命名，一一对应，没有别名：

| Python | LabVIEW |
| --- | --- |
| `SafeStr.make_string_arguments_safe` | `CSM - Make String Arguments Safe.vi` |
| `SafeStr.revert_arguments_safe_string` | `CSM - Revert Arguments-Safe String.vi` |
| `ErrorStr.convert_error_to_argument` | `CSM - Convert Error to Argument.vi` |
| `ErrorStr.convert_argument_to_error` | `CSM - Convert Argument to Error.vi` |
| `HexStr.convert_data_to_hexstr` | `CSM - Convert Data to HexStr.vi` |
| `HexStr.convert_hexstr_to_data` | `CSM - Convert HexStr to Data.vi` |
| `protocol.argument_type` | `CSM - Argument Type.vi` |
| `api_string.convert_data_to_api_string_arguments` | `Data To API String Arguments.vi` |
| `api_string.convert_api_string_arguments_to_data` | `Convert API String Arguments to Data(variant).vi` |

各类型模块的 `data_to_api_string` / `api_string_to_data` 是两个多态 VI 在该类型下的实例。
形参名同样取自 VI 端子（`Argument String`、`Safe Argument String`、`Ignore Argument Type`、
`Force Convert`、`HEX String`、`Error`、`Argument`、`data`、`String`）。VI 的 `Type` 端子
因与内置名 `type` 冲突，在 Python 侧记为 `lvtype`。VI 的 `error in` / `error out` 由异常承载。

## 文件布局

| 路径 | 职责 |
| --- | --- |
| `src/csm/arguments/ErrorStr.py` | 错误簇参数（ERRSTR）编解码 |
| `src/csm/arguments/HexStr.py` | variant 二进制参数（HEXSTR）编解码 |
| `src/csm/arguments/SafeStr.py` | 安全字符串（SAFESTR）编解码 |
| `src/csm/arguments/api_string.py` | CSM API String 文本编解码 |
| `src/csm/arguments/datatypes.py` | LabVIEW 值对象的 Python 表示 |
| `src/csm/arguments/errors.py` | `ArgumentFormatError` |
| `src/csm/arguments/lv_variant.py` | LabVIEW variant 二进制格式编解码 |
| `src/csm/arguments/protocol.py` | 参数编码类型标记 |
| `tests/` | pytest 测试，含 LabVIEW 实测样本回归基线 |
| `docs/` | 格式说明、接口文档与验证记录 |
| `.github/workflows/` | 测试矩阵与 PyPI / TestPyPI 发布工作流 |
| `labview/` | LabVIEW 验证探针的生成脚本、输入规格与实测记录 |

## 文档索引

| 文档 | 内容 |
| --- | --- |
| `docs/argument-encoding.md` | 编码方案与 CSM API String 文本格式 |
| `docs/variant-binary-format.md` | LabVIEW variant 二进制结构 |
| `docs/python-api.md` | Python 接口说明 |
| `docs/labview-verification.md` | LabVIEW 侧验证方法与结果 |
| `CONTRIBUTING.md` | 环境、测试与扩展方式 |

## 许可

MIT，见 `LICENSE`。
