simconnect-H 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HuJie
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: simconnect-H
3
+ Version: 0.1.0
4
+ Summary: 原生 ctypes SimConnect 库 — 零外部依赖,直接加载 SimConnect.dll 与 MSFS 通讯
5
+ Author-email: HuJie <150501351+hjznb887@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/hjznb887/simconnect-H
8
+ Project-URL: Repository, https://github.com/hjznb887/simconnect-H
9
+ Keywords: simconnect,msfs,flight-simulator,ctypes,native
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Games/Entertainment :: Simulation
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # simconnect-H
21
+
22
+ **原生 ctypes SimConnect 库 — 零外部依赖,直接加载 SimConnect.dll 与 Microsoft Flight Simulator 通讯。**
23
+
24
+ ## 特性
25
+
26
+ - 🚫 **零 Python 依赖** — 仅使用 Python 标准库 `ctypes`,无任何第三方包
27
+ - 🔓 **无 AGPL 污染** — 不依赖 PySimConnect,MIT 许可证
28
+ - 🎯 **完全控制** — 手动定义所有 `argtypes`,无 Enum 类型漏洞
29
+ - 🪶 **轻量** — 单个文件,即插即用
30
+ - 🏗️ **可打包** — 支持 `pip install` 和 PyInstaller 打包
31
+ - 🔒 **线程安全** — dispatch 回调注册使用锁保护,适合多线程场景
32
+ - 📦 **上下文管理器** — 支持 `with sc:` 语法,自动断开连接
33
+ - 🔍 **智能 DLL 查找** — 自动扫描 MSFS SDK 安装目录、site-packages、系统 PATH
34
+
35
+ ## 安装
36
+
37
+ ```bash
38
+ # 从源码安装
39
+ pip install .
40
+
41
+ # 或直接复制 simconnect_native/ 到项目目录
42
+ ```
43
+
44
+ 需要 `SimConnect.dll`(微软模拟飞行 SDK 可再发行组件),程序会自动在以下位置查找:
45
+
46
+ 1. MSFS SDK 安装目录(`Program Files (x86)/Microsoft SDKs/FlightSimulator/`)
47
+ 2. 本文件同目录
48
+ 3. 当前工作目录
49
+ 4. `site-packages/SimConnect/`(PySimConnect 安装路径)
50
+ 5. 系统 PATH
51
+
52
+ 也可以手动指定路径:
53
+
54
+ ```python
55
+ sc = SimConnect()
56
+ sc.load_dll(r"C:\Path\To\SimConnect.dll")
57
+ ```
58
+
59
+ ## 快速入门
60
+
61
+ ```python
62
+ from simconnect_native import (
63
+ SimConnect,
64
+ SIMCONNECT_SIMOBJECT_TYPE_USER,
65
+ SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE,
66
+ SIMCONNECT_RECV_ID_OPEN,
67
+ SIMCONNECT_RECV_ID_EXCEPTION,
68
+ FULL_SIMOBJECT_DATA, EXCEPTION_MSG, EXCEPTION_NAMES,
69
+ )
70
+
71
+ # 支持 with 语句,自动 close
72
+ with SimConnect() as sc:
73
+ sc.load_dll()
74
+ sc.open(b"MyApp")
75
+
76
+ # 注册数据定义
77
+ sc.add_to_data_definition(1, b"PLANE ALTITUDE", b"Feet")
78
+
79
+ # 设置 dispatch 回调
80
+ def on_dispatch(pData, cbData, pContext):
81
+ try:
82
+ dwID = pData.contents.dwID
83
+ except Exception:
84
+ return
85
+
86
+ if dwID == SIMCONNECT_RECV_ID_OPEN:
87
+ print("✓ 已连接到 MSFS")
88
+
89
+ elif dwID == SIMCONNECT_RECV_ID_EXCEPTION:
90
+ exc = ctypes.cast(pData, ctypes.POINTER(EXCEPTION_MSG)).contents
91
+ name = EXCEPTION_NAMES.get(exc.dwException, f"UNKNOWN({exc.dwException})")
92
+ print(f"⚠ 异常: {name}")
93
+
94
+ elif dwID == SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE:
95
+ req_id, val = sc.read_double(pData)
96
+ print(f"📊 req={req_id} value={val}")
97
+
98
+ sc.set_dispatch_cb(on_dispatch)
99
+
100
+ # 请求数据
101
+ sc.request_data_on_simobject_type(1, 1, 0, SIMCONNECT_SIMOBJECT_TYPE_USER)
102
+
103
+ # 轮询
104
+ import time
105
+ for _ in range(100):
106
+ sc.dispatch()
107
+ time.sleep(0.01)
108
+
109
+ # 写入数据
110
+ from ctypes import c_double, cast, c_void_p
111
+ arr = (c_double * 1)(1000.0)
112
+ ptr = cast(arr, c_void_p)
113
+ sc.set_data_on_simobject(1, data_ptr=ptr)
114
+
115
+ # 发送事件
116
+ sc.map_client_event_to_sim_event(100, b"KEY_TOGGLE")
117
+ sc.transmit_client_event(0, 100, 0)
118
+
119
+ # with 块结束自动断开
120
+ ```
121
+
122
+ > **v0.2.0 迁移说明**:`FULL_SIMOBJECT_DATA` 已精简为数据头部结构体(`SIMOBJECT_DATA_HEADER`),不再包含预分配的 `dwData` 数组。
123
+ > 所有数据读取请改用 `SimConnect.read_data(pData, datatype)` 或 `sc.read_double(pData)`,内部使用指针偏移零拷贝读取。
124
+ > 向后兼容:`FULL_SIMOBJECT_DATA` 名称仍可作为 `SIMOBJECT_DATA_HEADER` 的别名导入。
125
+
126
+ ## API 一览
127
+
128
+ ### `SimConnect` 类
129
+
130
+ | 方法 | 说明 |
131
+ | ------ | ------ |
132
+ | `load_dll(path=None)` | 加载 SimConnect.dll |
133
+ | `open(app_name, ...)` | 连接 MSFS |
134
+ | `close()` | 断开连接 |
135
+ | `add_to_data_definition(id, name, unit, ...)` | 注册 SimVar |
136
+ | `clear_data_definition(id)` | 清除定义 |
137
+ | `request_data_on_simobject_type(req_id, def_id, ...)` | 请求数据 |
138
+ | `request_data_on_simobject(req_id, def_id, ...)` | 请求持续数据更新 |
139
+ | `add_and_request(req_id, def_id, name, unit, ...)` | 注册+请求一步完成 |
140
+ | `set_data_on_simobject(def_id, *, object_id, flags, ...)` | 写入数据 |
141
+ | `write_double(def_id, value)` | 快捷写入 double 值 |
142
+ | `map_client_event_to_sim_event(ev_id, name)` | 映射事件 |
143
+ | `transmit_client_event(obj_id, ev_id, data, ...)` | 发送事件 |
144
+ | `subscribe_to_system_event(id, name)` | 订阅系统事件 |
145
+ | `dispatch()` | 处理一次消息队列 |
146
+ | `set_dispatch_cb(callback)` | 设置 dispatch 回调 |
147
+ | `call_dispatch(callback)` | 设置并触发 dispatch |
148
+ | `read_double(pData)` | 从回调中解析 float64 值 |
149
+ | `read_data(pData, datatype=0)` | 从回调指针按类型读取数据(静态方法,零拷贝) |
150
+ | `start_background_dispatch(callback=None)` | 启动后台 dispatch 线程 |
151
+ | `stop_background_dispatch()` | 停止后台 dispatch 线程 |
152
+ | `get_last_sent_packet_id()` | 获取最后发送的数据包 ID |
153
+ | `event_data_float(value)` | float → DWORD 位转换(静态方法) |
154
+
155
+ ### 属性
156
+
157
+ | 属性 | 说明 |
158
+ | ------ | ------ |
159
+ | `handle` | SimConnect 句柄(HANDLE) |
160
+ | `dll` | 已加载的 WinDLL 对象 |
161
+ | `is_open` | 是否已连接 |
162
+
163
+ ### 模块级工具
164
+
165
+ | 函数 | 说明 |
166
+ | ------ | ------ |
167
+ | `find_simconnect_dll()` | 自动搜索 SimConnect.dll 路径 |
168
+ | `read_data_value(pData, datatype=0)` | 从 dispatch 回调中读取指定类型数据 |
169
+ | `__version__` | 当前库版本 `"0.1.0"` |
170
+
171
+ ## 许可证
172
+
173
+ MIT License
@@ -0,0 +1,154 @@
1
+ # simconnect-H
2
+
3
+ **原生 ctypes SimConnect 库 — 零外部依赖,直接加载 SimConnect.dll 与 Microsoft Flight Simulator 通讯。**
4
+
5
+ ## 特性
6
+
7
+ - 🚫 **零 Python 依赖** — 仅使用 Python 标准库 `ctypes`,无任何第三方包
8
+ - 🔓 **无 AGPL 污染** — 不依赖 PySimConnect,MIT 许可证
9
+ - 🎯 **完全控制** — 手动定义所有 `argtypes`,无 Enum 类型漏洞
10
+ - 🪶 **轻量** — 单个文件,即插即用
11
+ - 🏗️ **可打包** — 支持 `pip install` 和 PyInstaller 打包
12
+ - 🔒 **线程安全** — dispatch 回调注册使用锁保护,适合多线程场景
13
+ - 📦 **上下文管理器** — 支持 `with sc:` 语法,自动断开连接
14
+ - 🔍 **智能 DLL 查找** — 自动扫描 MSFS SDK 安装目录、site-packages、系统 PATH
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ # 从源码安装
20
+ pip install .
21
+
22
+ # 或直接复制 simconnect_native/ 到项目目录
23
+ ```
24
+
25
+ 需要 `SimConnect.dll`(微软模拟飞行 SDK 可再发行组件),程序会自动在以下位置查找:
26
+
27
+ 1. MSFS SDK 安装目录(`Program Files (x86)/Microsoft SDKs/FlightSimulator/`)
28
+ 2. 本文件同目录
29
+ 3. 当前工作目录
30
+ 4. `site-packages/SimConnect/`(PySimConnect 安装路径)
31
+ 5. 系统 PATH
32
+
33
+ 也可以手动指定路径:
34
+
35
+ ```python
36
+ sc = SimConnect()
37
+ sc.load_dll(r"C:\Path\To\SimConnect.dll")
38
+ ```
39
+
40
+ ## 快速入门
41
+
42
+ ```python
43
+ from simconnect_native import (
44
+ SimConnect,
45
+ SIMCONNECT_SIMOBJECT_TYPE_USER,
46
+ SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE,
47
+ SIMCONNECT_RECV_ID_OPEN,
48
+ SIMCONNECT_RECV_ID_EXCEPTION,
49
+ FULL_SIMOBJECT_DATA, EXCEPTION_MSG, EXCEPTION_NAMES,
50
+ )
51
+
52
+ # 支持 with 语句,自动 close
53
+ with SimConnect() as sc:
54
+ sc.load_dll()
55
+ sc.open(b"MyApp")
56
+
57
+ # 注册数据定义
58
+ sc.add_to_data_definition(1, b"PLANE ALTITUDE", b"Feet")
59
+
60
+ # 设置 dispatch 回调
61
+ def on_dispatch(pData, cbData, pContext):
62
+ try:
63
+ dwID = pData.contents.dwID
64
+ except Exception:
65
+ return
66
+
67
+ if dwID == SIMCONNECT_RECV_ID_OPEN:
68
+ print("✓ 已连接到 MSFS")
69
+
70
+ elif dwID == SIMCONNECT_RECV_ID_EXCEPTION:
71
+ exc = ctypes.cast(pData, ctypes.POINTER(EXCEPTION_MSG)).contents
72
+ name = EXCEPTION_NAMES.get(exc.dwException, f"UNKNOWN({exc.dwException})")
73
+ print(f"⚠ 异常: {name}")
74
+
75
+ elif dwID == SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE:
76
+ req_id, val = sc.read_double(pData)
77
+ print(f"📊 req={req_id} value={val}")
78
+
79
+ sc.set_dispatch_cb(on_dispatch)
80
+
81
+ # 请求数据
82
+ sc.request_data_on_simobject_type(1, 1, 0, SIMCONNECT_SIMOBJECT_TYPE_USER)
83
+
84
+ # 轮询
85
+ import time
86
+ for _ in range(100):
87
+ sc.dispatch()
88
+ time.sleep(0.01)
89
+
90
+ # 写入数据
91
+ from ctypes import c_double, cast, c_void_p
92
+ arr = (c_double * 1)(1000.0)
93
+ ptr = cast(arr, c_void_p)
94
+ sc.set_data_on_simobject(1, data_ptr=ptr)
95
+
96
+ # 发送事件
97
+ sc.map_client_event_to_sim_event(100, b"KEY_TOGGLE")
98
+ sc.transmit_client_event(0, 100, 0)
99
+
100
+ # with 块结束自动断开
101
+ ```
102
+
103
+ > **v0.2.0 迁移说明**:`FULL_SIMOBJECT_DATA` 已精简为数据头部结构体(`SIMOBJECT_DATA_HEADER`),不再包含预分配的 `dwData` 数组。
104
+ > 所有数据读取请改用 `SimConnect.read_data(pData, datatype)` 或 `sc.read_double(pData)`,内部使用指针偏移零拷贝读取。
105
+ > 向后兼容:`FULL_SIMOBJECT_DATA` 名称仍可作为 `SIMOBJECT_DATA_HEADER` 的别名导入。
106
+
107
+ ## API 一览
108
+
109
+ ### `SimConnect` 类
110
+
111
+ | 方法 | 说明 |
112
+ | ------ | ------ |
113
+ | `load_dll(path=None)` | 加载 SimConnect.dll |
114
+ | `open(app_name, ...)` | 连接 MSFS |
115
+ | `close()` | 断开连接 |
116
+ | `add_to_data_definition(id, name, unit, ...)` | 注册 SimVar |
117
+ | `clear_data_definition(id)` | 清除定义 |
118
+ | `request_data_on_simobject_type(req_id, def_id, ...)` | 请求数据 |
119
+ | `request_data_on_simobject(req_id, def_id, ...)` | 请求持续数据更新 |
120
+ | `add_and_request(req_id, def_id, name, unit, ...)` | 注册+请求一步完成 |
121
+ | `set_data_on_simobject(def_id, *, object_id, flags, ...)` | 写入数据 |
122
+ | `write_double(def_id, value)` | 快捷写入 double 值 |
123
+ | `map_client_event_to_sim_event(ev_id, name)` | 映射事件 |
124
+ | `transmit_client_event(obj_id, ev_id, data, ...)` | 发送事件 |
125
+ | `subscribe_to_system_event(id, name)` | 订阅系统事件 |
126
+ | `dispatch()` | 处理一次消息队列 |
127
+ | `set_dispatch_cb(callback)` | 设置 dispatch 回调 |
128
+ | `call_dispatch(callback)` | 设置并触发 dispatch |
129
+ | `read_double(pData)` | 从回调中解析 float64 值 |
130
+ | `read_data(pData, datatype=0)` | 从回调指针按类型读取数据(静态方法,零拷贝) |
131
+ | `start_background_dispatch(callback=None)` | 启动后台 dispatch 线程 |
132
+ | `stop_background_dispatch()` | 停止后台 dispatch 线程 |
133
+ | `get_last_sent_packet_id()` | 获取最后发送的数据包 ID |
134
+ | `event_data_float(value)` | float → DWORD 位转换(静态方法) |
135
+
136
+ ### 属性
137
+
138
+ | 属性 | 说明 |
139
+ | ------ | ------ |
140
+ | `handle` | SimConnect 句柄(HANDLE) |
141
+ | `dll` | 已加载的 WinDLL 对象 |
142
+ | `is_open` | 是否已连接 |
143
+
144
+ ### 模块级工具
145
+
146
+ | 函数 | 说明 |
147
+ | ------ | ------ |
148
+ | `find_simconnect_dll()` | 自动搜索 SimConnect.dll 路径 |
149
+ | `read_data_value(pData, datatype=0)` | 从 dispatch 回调中读取指定类型数据 |
150
+ | `__version__` | 当前库版本 `"0.1.0"` |
151
+
152
+ ## 许可证
153
+
154
+ MIT License
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "simconnect-H"
7
+ version = "0.1.0"
8
+ description = "原生 ctypes SimConnect 库 — 零外部依赖,直接加载 SimConnect.dll 与 MSFS 通讯"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ {name = "HuJie", email = "150501351+hjznb887@users.noreply.github.com"},
13
+ ]
14
+ keywords = ["simconnect", "msfs", "flight-simulator", "ctypes", "native"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: Microsoft :: Windows",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Games/Entertainment :: Simulation",
21
+ ]
22
+ requires-python = ">=3.8"
23
+ dependencies = []
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/hjznb887/simconnect-H"
27
+ Repository = "https://github.com/hjznb887/simconnect-H"
28
+
29
+ [tool.setuptools.packages.find]
30
+ include = ["simconnect_native*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: simconnect-H
3
+ Version: 0.1.0
4
+ Summary: 原生 ctypes SimConnect 库 — 零外部依赖,直接加载 SimConnect.dll 与 MSFS 通讯
5
+ Author-email: HuJie <150501351+hjznb887@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/hjznb887/simconnect-H
8
+ Project-URL: Repository, https://github.com/hjznb887/simconnect-H
9
+ Keywords: simconnect,msfs,flight-simulator,ctypes,native
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Games/Entertainment :: Simulation
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # simconnect-H
21
+
22
+ **原生 ctypes SimConnect 库 — 零外部依赖,直接加载 SimConnect.dll 与 Microsoft Flight Simulator 通讯。**
23
+
24
+ ## 特性
25
+
26
+ - 🚫 **零 Python 依赖** — 仅使用 Python 标准库 `ctypes`,无任何第三方包
27
+ - 🔓 **无 AGPL 污染** — 不依赖 PySimConnect,MIT 许可证
28
+ - 🎯 **完全控制** — 手动定义所有 `argtypes`,无 Enum 类型漏洞
29
+ - 🪶 **轻量** — 单个文件,即插即用
30
+ - 🏗️ **可打包** — 支持 `pip install` 和 PyInstaller 打包
31
+ - 🔒 **线程安全** — dispatch 回调注册使用锁保护,适合多线程场景
32
+ - 📦 **上下文管理器** — 支持 `with sc:` 语法,自动断开连接
33
+ - 🔍 **智能 DLL 查找** — 自动扫描 MSFS SDK 安装目录、site-packages、系统 PATH
34
+
35
+ ## 安装
36
+
37
+ ```bash
38
+ # 从源码安装
39
+ pip install .
40
+
41
+ # 或直接复制 simconnect_native/ 到项目目录
42
+ ```
43
+
44
+ 需要 `SimConnect.dll`(微软模拟飞行 SDK 可再发行组件),程序会自动在以下位置查找:
45
+
46
+ 1. MSFS SDK 安装目录(`Program Files (x86)/Microsoft SDKs/FlightSimulator/`)
47
+ 2. 本文件同目录
48
+ 3. 当前工作目录
49
+ 4. `site-packages/SimConnect/`(PySimConnect 安装路径)
50
+ 5. 系统 PATH
51
+
52
+ 也可以手动指定路径:
53
+
54
+ ```python
55
+ sc = SimConnect()
56
+ sc.load_dll(r"C:\Path\To\SimConnect.dll")
57
+ ```
58
+
59
+ ## 快速入门
60
+
61
+ ```python
62
+ from simconnect_native import (
63
+ SimConnect,
64
+ SIMCONNECT_SIMOBJECT_TYPE_USER,
65
+ SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE,
66
+ SIMCONNECT_RECV_ID_OPEN,
67
+ SIMCONNECT_RECV_ID_EXCEPTION,
68
+ FULL_SIMOBJECT_DATA, EXCEPTION_MSG, EXCEPTION_NAMES,
69
+ )
70
+
71
+ # 支持 with 语句,自动 close
72
+ with SimConnect() as sc:
73
+ sc.load_dll()
74
+ sc.open(b"MyApp")
75
+
76
+ # 注册数据定义
77
+ sc.add_to_data_definition(1, b"PLANE ALTITUDE", b"Feet")
78
+
79
+ # 设置 dispatch 回调
80
+ def on_dispatch(pData, cbData, pContext):
81
+ try:
82
+ dwID = pData.contents.dwID
83
+ except Exception:
84
+ return
85
+
86
+ if dwID == SIMCONNECT_RECV_ID_OPEN:
87
+ print("✓ 已连接到 MSFS")
88
+
89
+ elif dwID == SIMCONNECT_RECV_ID_EXCEPTION:
90
+ exc = ctypes.cast(pData, ctypes.POINTER(EXCEPTION_MSG)).contents
91
+ name = EXCEPTION_NAMES.get(exc.dwException, f"UNKNOWN({exc.dwException})")
92
+ print(f"⚠ 异常: {name}")
93
+
94
+ elif dwID == SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE:
95
+ req_id, val = sc.read_double(pData)
96
+ print(f"📊 req={req_id} value={val}")
97
+
98
+ sc.set_dispatch_cb(on_dispatch)
99
+
100
+ # 请求数据
101
+ sc.request_data_on_simobject_type(1, 1, 0, SIMCONNECT_SIMOBJECT_TYPE_USER)
102
+
103
+ # 轮询
104
+ import time
105
+ for _ in range(100):
106
+ sc.dispatch()
107
+ time.sleep(0.01)
108
+
109
+ # 写入数据
110
+ from ctypes import c_double, cast, c_void_p
111
+ arr = (c_double * 1)(1000.0)
112
+ ptr = cast(arr, c_void_p)
113
+ sc.set_data_on_simobject(1, data_ptr=ptr)
114
+
115
+ # 发送事件
116
+ sc.map_client_event_to_sim_event(100, b"KEY_TOGGLE")
117
+ sc.transmit_client_event(0, 100, 0)
118
+
119
+ # with 块结束自动断开
120
+ ```
121
+
122
+ > **v0.2.0 迁移说明**:`FULL_SIMOBJECT_DATA` 已精简为数据头部结构体(`SIMOBJECT_DATA_HEADER`),不再包含预分配的 `dwData` 数组。
123
+ > 所有数据读取请改用 `SimConnect.read_data(pData, datatype)` 或 `sc.read_double(pData)`,内部使用指针偏移零拷贝读取。
124
+ > 向后兼容:`FULL_SIMOBJECT_DATA` 名称仍可作为 `SIMOBJECT_DATA_HEADER` 的别名导入。
125
+
126
+ ## API 一览
127
+
128
+ ### `SimConnect` 类
129
+
130
+ | 方法 | 说明 |
131
+ | ------ | ------ |
132
+ | `load_dll(path=None)` | 加载 SimConnect.dll |
133
+ | `open(app_name, ...)` | 连接 MSFS |
134
+ | `close()` | 断开连接 |
135
+ | `add_to_data_definition(id, name, unit, ...)` | 注册 SimVar |
136
+ | `clear_data_definition(id)` | 清除定义 |
137
+ | `request_data_on_simobject_type(req_id, def_id, ...)` | 请求数据 |
138
+ | `request_data_on_simobject(req_id, def_id, ...)` | 请求持续数据更新 |
139
+ | `add_and_request(req_id, def_id, name, unit, ...)` | 注册+请求一步完成 |
140
+ | `set_data_on_simobject(def_id, *, object_id, flags, ...)` | 写入数据 |
141
+ | `write_double(def_id, value)` | 快捷写入 double 值 |
142
+ | `map_client_event_to_sim_event(ev_id, name)` | 映射事件 |
143
+ | `transmit_client_event(obj_id, ev_id, data, ...)` | 发送事件 |
144
+ | `subscribe_to_system_event(id, name)` | 订阅系统事件 |
145
+ | `dispatch()` | 处理一次消息队列 |
146
+ | `set_dispatch_cb(callback)` | 设置 dispatch 回调 |
147
+ | `call_dispatch(callback)` | 设置并触发 dispatch |
148
+ | `read_double(pData)` | 从回调中解析 float64 值 |
149
+ | `read_data(pData, datatype=0)` | 从回调指针按类型读取数据(静态方法,零拷贝) |
150
+ | `start_background_dispatch(callback=None)` | 启动后台 dispatch 线程 |
151
+ | `stop_background_dispatch()` | 停止后台 dispatch 线程 |
152
+ | `get_last_sent_packet_id()` | 获取最后发送的数据包 ID |
153
+ | `event_data_float(value)` | float → DWORD 位转换(静态方法) |
154
+
155
+ ### 属性
156
+
157
+ | 属性 | 说明 |
158
+ | ------ | ------ |
159
+ | `handle` | SimConnect 句柄(HANDLE) |
160
+ | `dll` | 已加载的 WinDLL 对象 |
161
+ | `is_open` | 是否已连接 |
162
+
163
+ ### 模块级工具
164
+
165
+ | 函数 | 说明 |
166
+ | ------ | ------ |
167
+ | `find_simconnect_dll()` | 自动搜索 SimConnect.dll 路径 |
168
+ | `read_data_value(pData, datatype=0)` | 从 dispatch 回调中读取指定类型数据 |
169
+ | `__version__` | 当前库版本 `"0.1.0"` |
170
+
171
+ ## 许可证
172
+
173
+ MIT License
@@ -0,0 +1,8 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ simconnect_H.egg-info/PKG-INFO
5
+ simconnect_H.egg-info/SOURCES.txt
6
+ simconnect_H.egg-info/dependency_links.txt
7
+ simconnect_H.egg-info/top_level.txt
8
+ simconnect_native/__init__.py
@@ -0,0 +1 @@
1
+ simconnect_native
@@ -0,0 +1,793 @@
1
+ # simconnect_native.py
2
+ """原生 ctypes SimConnect 库 — 零外部依赖,可直接加载 SimConnect.dll 与 MSFS 通讯。
3
+
4
+ 用法:
5
+ from simconnect_native import SimConnect, SIMCONNECT_SIMOBJECT_TYPE_USER
6
+
7
+ sc = SimConnect()
8
+ sc.open("MyApp")
9
+ # 注册数据定义
10
+ sc.add_to_data_definition(1, b"PLANE ALTITUDE", b"Feet", 0) # 0=FLOAT64
11
+ # 请求数据
12
+ sc.request_data_on_simobject_type(1, 1, 0, SIMCONNECT_SIMOBJECT_TYPE_USER)
13
+ # dispatch 回调
14
+ def on_dispatch(pData, cbData, pContext):
15
+ dwID = pData.contents.dwID
16
+ ...
17
+ # 启动后台线程持续接收(推荐),不再需要手动循环 dispatch
18
+ sc.start_background_dispatch(on_dispatch)
19
+ # 写入(object_id=0=SIMCONNECT_OBJECT_ID_USER)
20
+ sc.set_data_on_simobject(2, object_id=0, data_ptr=data_ptr)
21
+ # 事件
22
+ sc.map_client_event_to_sim_event(100, b"KEY_TOGGLE")
23
+ sc.transmit_client_event(0, 100, 0, 0x19000000, 16)
24
+ sc.close()
25
+
26
+ 不依赖 PySimConnect(AGPL)的任何代码。
27
+ """
28
+ import ctypes
29
+ import os
30
+ import time
31
+ import logging
32
+ import threading
33
+ from ctypes import (c_ulong, c_float, c_char_p, c_double, c_void_p, c_int32, c_int16, c_int8,
34
+ cast, POINTER, sizeof as c_sizeof, Structure, WinDLL, byref)
35
+ from ctypes.wintypes import HANDLE, DWORD, HRESULT
36
+
37
+ __all__ = [
38
+ # 常量
39
+ "SIMCONNECT_UNUSED", "SIMCONNECT_OBJECT_ID_USER",
40
+ "SIMCONNECT_DATATYPE_FLOAT64", "SIMCONNECT_DATATYPE_FLOAT32",
41
+ "SIMCONNECT_DATATYPE_INT32", "SIMCONNECT_DATATYPE_INT16",
42
+ "SIMCONNECT_DATATYPE_INT8", "SIMCONNECT_DATATYPE_STRINGV",
43
+ "SIMCONNECT_SIMOBJECT_TYPE_USER", "SIMCONNECT_SIMOBJECT_TYPE_ALL",
44
+ "SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT",
45
+ "SIMCONNECT_PERIOD_NEVER", "SIMCONNECT_PERIOD_ONCE",
46
+ "SIMCONNECT_PERIOD_VISUAL_FRAME", "SIMCONNECT_PERIOD_SIM_FRAME",
47
+ "SIMCONNECT_PERIOD_SECOND",
48
+ "SIMCONNECT_RECV_ID_NULL", "SIMCONNECT_RECV_ID_EXCEPTION",
49
+ "SIMCONNECT_RECV_ID_OPEN", "SIMCONNECT_RECV_ID_QUIT",
50
+ "SIMCONNECT_RECV_ID_EVENT", "SIMCONNECT_RECV_ID_SIMOBJECT_DATA",
51
+ "SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE",
52
+ "EXCEPTION_NAMES",
53
+ # 结构体
54
+ "SIMCONNECT_RECV", "SIMOBJECT_DATA_HEADER", "SIMOBJECT_DATA_HEADER_SIZE",
55
+ "FULL_SIMOBJECT_DATA", "EXCEPTION_MSG",
56
+ # 事件
57
+ "MSFS_EVENTS",
58
+ # 函数
59
+ "find_simconnect_dll",
60
+ # 类
61
+ "SimConnect",
62
+ # 新增工具
63
+ "read_data_value",
64
+ ]
65
+
66
+ logger = logging.getLogger(__name__)
67
+
68
+ # ═══════════════════════════════════════════════════
69
+ # 版本
70
+ # ═══════════════════════════════════════════════════
71
+
72
+ __version__ = "0.1.0"
73
+
74
+ # ═══════════════════════════════════════════════════
75
+ # 常量
76
+ # ═══════════════════════════════════════════════════
77
+
78
+ SIMCONNECT_UNUSED = DWORD(0xFFFFFFFF)
79
+ SIMCONNECT_OBJECT_ID_USER = DWORD(0)
80
+
81
+ # SIMCONNECT_DATATYPE
82
+ SIMCONNECT_DATATYPE_FLOAT64 = c_ulong(0)
83
+ SIMCONNECT_DATATYPE_FLOAT32 = c_ulong(1)
84
+ SIMCONNECT_DATATYPE_INT32 = c_ulong(2)
85
+ SIMCONNECT_DATATYPE_INT16 = c_ulong(3)
86
+ SIMCONNECT_DATATYPE_INT8 = c_ulong(4)
87
+ SIMCONNECT_DATATYPE_STRINGV = c_ulong(5)
88
+
89
+ # SIMCONNECT_SIMOBJECT_TYPE
90
+ SIMCONNECT_SIMOBJECT_TYPE_USER = c_ulong(0)
91
+ SIMCONNECT_SIMOBJECT_TYPE_ALL = c_ulong(1)
92
+ SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT = c_ulong(2)
93
+
94
+ # SIMCONNECT_PERIOD
95
+ SIMCONNECT_PERIOD_NEVER = c_ulong(0)
96
+ SIMCONNECT_PERIOD_ONCE = c_ulong(1)
97
+ SIMCONNECT_PERIOD_VISUAL_FRAME = c_ulong(2)
98
+ SIMCONNECT_PERIOD_SIM_FRAME = c_ulong(3)
99
+ SIMCONNECT_PERIOD_SECOND = c_ulong(4)
100
+
101
+ # SIMCONNECT_RECV_ID
102
+ SIMCONNECT_RECV_ID_NULL = 0
103
+ SIMCONNECT_RECV_ID_EXCEPTION = 1
104
+ SIMCONNECT_RECV_ID_OPEN = 2
105
+ SIMCONNECT_RECV_ID_QUIT = 3
106
+ SIMCONNECT_RECV_ID_EVENT = 4
107
+ SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE = 5
108
+ SIMCONNECT_RECV_ID_EVENT_FILENAME = 6
109
+ SIMCONNECT_RECV_ID_EVENT_FRAME = 7
110
+ SIMCONNECT_RECV_ID_SIMOBJECT_DATA = 14
111
+ SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE = 15
112
+ SIMCONNECT_RECV_ID_WEATHER_OBSERVATION = 16
113
+ SIMCONNECT_RECV_ID_CLIENT_DATA = 19
114
+ SIMCONNECT_RECV_ID_EVENT_WEATHER_MODE = 20
115
+ SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SERVER_STARTED = 27
116
+ SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_CLIENT_STARTED = 28
117
+ SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SESSION_ENDED = 29
118
+ SIMCONNECT_RECV_ID_EVENT_RACE_END = 30
119
+ SIMCONNECT_RECV_ID_EVENT_RACE_LAP = 31
120
+ SIMCONNECT_RECV_ID_SYSTEM_STATE = 33
121
+
122
+ # SIMCONNECT_EXCEPTION
123
+ EXCEPTION_NAMES = {
124
+ 0: "NONE", 1: "ERROR", 2: "SIZE_MISMATCH", 3: "UNRECOGNIZED_ID",
125
+ 4: "UNOPENED", 5: "VERSION_MISMATCH", 6: "TOO_MANY_GROUPS",
126
+ 7: "NAME_UNRECOGNIZED", 8: "TOO_MANY_EVENT_NAMES",
127
+ }
128
+
129
+ # ═══════════════════════════════════════════════════
130
+ # 消息结构体
131
+ # ═══════════════════════════════════════════════════
132
+
133
+ class SIMCONNECT_RECV(Structure):
134
+ """SimConnect 消息头部(所有消息的前 16 字节)"""
135
+ _fields_ = [
136
+ ("dwID", DWORD),
137
+ ("dwSize", DWORD),
138
+ ("dwVersion", DWORD),
139
+ ("dwSeqNumber", DWORD),
140
+ ]
141
+
142
+
143
+ class SIMOBJECT_DATA_HEADER(Structure):
144
+ """数据消息头部(元数据部分,不含数据负载),用于 SIMCONNECT_RECV_ID_SIMOBJECT_DATA / _BYTYPE"""
145
+ _fields_ = [
146
+ ("dwID", DWORD), ("dwSize", DWORD), ("dwVersion", DWORD), ("dwSeqNumber", DWORD),
147
+ ("dwRequestID", DWORD), ("dwObjectID", DWORD), ("dwDefineID", DWORD),
148
+ ("dwFlags", DWORD), ("dwentrynumber", DWORD), ("dwoutof", DWORD),
149
+ ("dwDefineCount", DWORD),
150
+ ]
151
+
152
+
153
+ SIMOBJECT_DATA_HEADER_SIZE = c_sizeof(SIMOBJECT_DATA_HEADER)
154
+
155
+ # 向后兼容别名(不含 dwData,所有数据读取改用指针偏移)
156
+ FULL_SIMOBJECT_DATA = SIMOBJECT_DATA_HEADER
157
+
158
+
159
+ class EXCEPTION_MSG(Structure):
160
+ """异常消息(含头部)"""
161
+ _fields_ = [
162
+ ("dwID", DWORD), ("dwSize", DWORD), ("dwVersion", DWORD), ("dwSeqNumber", DWORD),
163
+ ("dwException", DWORD), ("UNKNOWN_SENDID", DWORD),
164
+ ("UNKNOWN_INDEX", DWORD), ("dwSendID", DWORD), ("dwIndex", DWORD),
165
+ ]
166
+
167
+
168
+ # ═══════════════════════════════════════════════════
169
+ # 事件名称查询表
170
+ # ═══════════════════════════════════════════════════
171
+
172
+ MSFS_EVENTS = {
173
+ # 引擎
174
+ "THROTTLE_FULL", "THROTTLE_INCR", "THROTTLE_DECR",
175
+ "THROTTLE_CUT", "MIXTURE_INCR", "MIXTURE_DECR",
176
+ "PROP_PITCH_INCR", "PROP_PITCH_DECR",
177
+ # 飞行控制
178
+ "AP_MASTER", "AP_PANEL_ALTITUDE_HOLD", "AP_PANEL_HEADING_HOLD",
179
+ "AP_PANEL_SPEED_HOLD", "AP_PANEL_ATTITUDE_HOLD",
180
+ # 灯光
181
+ "LANDING_LIGHTS_TOGGLE", "STROBES_TOGGLE",
182
+ "BEACONS_TOGGLE", "NAV_LIGHTS_TOGGLE", "TAXI_LIGHTS_TOGGLE",
183
+ "PANEL_LIGHTS_TOGGLE",
184
+ # 系统
185
+ "GEAR_TOGGLE", "PARKING_BRAKES",
186
+ "SIM_RESET", "SITUATION_RESET", "REPAIR_AND_REFUEL",
187
+ "TOGGLE_ENGINE", "TOGGLE_MASTER_IGNITION",
188
+ "TOGGLE_ALTERNATOR", "TOGGLE_AVIONICS_MASTER",
189
+ # 视图
190
+ "VIEW_RESET", "EYEPOINT_RESET", "PAN_RESET",
191
+ # 襟翼
192
+ "FLAPS_INCR", "FLAPS_DECR", "FLAPS_UP", "FLAPS_DOWN",
193
+ # 配平
194
+ "ELEV_TRIM_UP", "ELEV_TRIM_DN",
195
+ "AILERON_TRIM_LEFT", "AILERON_TRIM_RIGHT",
196
+ "RUDDER_TRIM_LEFT", "RUDDER_TRIM_RIGHT",
197
+ }
198
+
199
+
200
+ # ═══════════════════════════════════════════════════
201
+ # DLL 查找
202
+ # ═══════════════════════════════════════════════════
203
+
204
+ def find_simconnect_dll():
205
+ """查找 SimConnect.dll 位置。
206
+
207
+ 搜索顺序:
208
+ 1. 本文件同目录
209
+ 2. 当前工作目录
210
+ 3. site-packages/SimConnect/(PySimConnect 安装路径)
211
+ 4. 系统 PATH(默认返回 "SimConnect.dll" 让 Windows 自动搜索)
212
+ """
213
+ search_dirs = [
214
+ os.path.dirname(__file__),
215
+ os.getcwd(),
216
+ ]
217
+ # Program Files (MSFS SDK 安装路径)
218
+ pf = os.environ.get("ProgramFiles(x86)", "C:\\Program Files (x86)")
219
+ sdk_root = os.path.join(pf, "Microsoft SDKs", "FlightSimulator")
220
+ if os.path.isdir(sdk_root):
221
+ for entry in os.listdir(sdk_root):
222
+ candidate = os.path.join(sdk_root, entry, "SimConnect.dll")
223
+ if os.path.exists(candidate):
224
+ search_dirs.insert(0, os.path.dirname(candidate))
225
+ for base in search_dirs:
226
+ p = os.path.join(base, "SimConnect.dll")
227
+ if os.path.exists(p):
228
+ return p
229
+ try:
230
+ import site
231
+ for d in site.getsitepackages():
232
+ p = os.path.join(d, "SimConnect", "SimConnect.dll")
233
+ if os.path.exists(p):
234
+ return p
235
+ except Exception:
236
+ pass
237
+ return "SimConnect.dll"
238
+
239
+
240
+ # ═══════════════════════════════════════════════════
241
+ # 模块级便利函数
242
+ # ═══════════════════════════════════════════════════
243
+
244
+ def read_data_value(pData, datatype=0):
245
+ """从 dispatch 回调的 pData 中读取指定类型的数据。
246
+
247
+ 用法:
248
+ from simconnect_native import read_data_value
249
+ val = read_data_value(pData, SIMCONNECT_DATATYPE_FLOAT64)
250
+ """
251
+ return SimConnect.read_data(pData, datatype)
252
+
253
+
254
+ # ═══════════════════════════════════════════════════
255
+ # 高层封装
256
+ # ═══════════════════════════════════════════════════
257
+
258
+ class SimConnect:
259
+ """SimConnect 原生封装 — 直接通过 ctypes WinDLL 调用 SimConnect.dll。
260
+
261
+ 特性:
262
+ - 零 Python 依赖(仅 ctypes 标准库)
263
+ - 完全控制 argtypes,无 Enum 类型污染
264
+ - 线程安全的 dispatch 回调注册
265
+ - 自动 DLL 查找
266
+
267
+ 用法:
268
+ sc = SimConnect()
269
+ sc.open("MyApp")
270
+ # ... 使用各种方法 ...
271
+ sc.close()
272
+ """
273
+
274
+ def __init__(self):
275
+ self._dll = None
276
+ self._hSimConnect = None
277
+ self._dispatch_cb = None
278
+ self._DispatchProc = None
279
+ # 后台 dispatch 线程
280
+ self._dispatch_thread = None
281
+ self._dispatch_running = False
282
+ self._dispatch_stop_event = threading.Event()
283
+ self._lock = threading.Lock()
284
+ # 断开回调(由 dispatch 在收到 SIMCONNECT_RECV_ID_QUIT 时调用)
285
+ self.on_disconnect = None
286
+
287
+ # ── 属性 ──────────────────────────────────────
288
+
289
+ @property
290
+ def handle(self):
291
+ """SimConnect 句柄(HANDLE),未连接时为 None"""
292
+ return self._hSimConnect
293
+
294
+ @property
295
+ def dll(self):
296
+ """已加载的 WinDLL 对象,未加载时为 None"""
297
+ return self._dll
298
+
299
+ @property
300
+ def is_open(self):
301
+ """是否已成功打开连接"""
302
+ return (self._hSimConnect is not None
303
+ and self._hSimConnect.value is not None
304
+ and self._hSimConnect.value != 0)
305
+
306
+ def __repr__(self):
307
+ status = "已连接" if self.is_open else "未连接"
308
+ dll_status = "已加载" if self._dll else "未加载"
309
+ return f"<SimConnect {status}, DLL {dll_status}>"
310
+
311
+ def __enter__(self):
312
+ """支持 with 语句 — 返回自身"""
313
+ return self
314
+
315
+ def __exit__(self, exc_type, exc_val, exc_tb):
316
+ """退出 with 块时自动关闭连接"""
317
+ self.close()
318
+
319
+ # ── 初始化 ────────────────────────────────────
320
+
321
+ def load_dll(self, dll_path=None):
322
+ """加载 SimConnect.dll。
323
+
324
+ Args:
325
+ dll_path: DLL 路径,为 None 时自动查找。
326
+
327
+ Raises:
328
+ FileNotFoundError: DLL 文件不存在。
329
+ OSError: DLL 加载失败(如架构不匹配、依赖缺失)。
330
+ """
331
+ path = dll_path or find_simconnect_dll()
332
+ logger.info("加载 SimConnect.dll: %s", path)
333
+
334
+ if not os.path.isfile(path):
335
+ raise FileNotFoundError(
336
+ f"SimConnect.dll 未找到: {path}\n"
337
+ "请确保已安装 MSFS 或从 SDK 获取 SimConnect.dll"
338
+ )
339
+
340
+ try:
341
+ self._dll = WinDLL(path)
342
+ except OSError as e:
343
+ raise OSError(
344
+ f"加载 SimConnect.dll 失败: {e}\n"
345
+ "请检查:\n"
346
+ " 1. DLL 是 64 位版本(Python 也是 64 位)\n"
347
+ " 2. 已安装 Microsoft Visual C++ Redistributable\n"
348
+ " 3. DLL 未被其他进程独占锁定"
349
+ ) from e
350
+
351
+ logger.debug("SimConnect.dll 加载成功")
352
+ self._setup_argtypes()
353
+
354
+ def _setup_argtypes(self):
355
+ """配置所有 SimConnect API 函数的 argtypes"""
356
+ d = self._dll
357
+
358
+ # SimConnect_Open
359
+ d.SimConnect_Open.restype = HRESULT
360
+ d.SimConnect_Open.argtypes = [POINTER(HANDLE), c_char_p, c_void_p, DWORD, HANDLE, DWORD]
361
+
362
+ # SimConnect_Close
363
+ d.SimConnect_Close.restype = HRESULT
364
+ d.SimConnect_Close.argtypes = [HANDLE]
365
+
366
+ # SimConnect_CallDispatch
367
+ self._DispatchProc = ctypes.WINFUNCTYPE(None, c_void_p, DWORD, c_void_p)
368
+ d.SimConnect_CallDispatch.restype = HRESULT
369
+ d.SimConnect_CallDispatch.argtypes = [HANDLE, self._DispatchProc, c_void_p]
370
+
371
+ # SimConnect_AddToDataDefinition
372
+ d.SimConnect_AddToDataDefinition.restype = HRESULT
373
+ d.SimConnect_AddToDataDefinition.argtypes = [
374
+ HANDLE, DWORD, c_char_p, c_char_p, c_ulong, c_float, DWORD,
375
+ ]
376
+
377
+ # SimConnect_RequestDataOnSimObjectType
378
+ d.SimConnect_RequestDataOnSimObjectType.restype = HRESULT
379
+ d.SimConnect_RequestDataOnSimObjectType.argtypes = [
380
+ HANDLE, DWORD, DWORD, DWORD, c_ulong,
381
+ ]
382
+
383
+ # SimConnect_RequestDataOnSimObject
384
+ d.SimConnect_RequestDataOnSimObject.restype = HRESULT
385
+ d.SimConnect_RequestDataOnSimObject.argtypes = [
386
+ HANDLE, DWORD, DWORD, DWORD, c_ulong, DWORD, DWORD, DWORD, DWORD,
387
+ ]
388
+
389
+ # SimConnect_SetDataOnSimObject
390
+ d.SimConnect_SetDataOnSimObject.restype = HRESULT
391
+ d.SimConnect_SetDataOnSimObject.argtypes = [
392
+ HANDLE, DWORD, c_ulong, DWORD, DWORD, DWORD, c_void_p,
393
+ ]
394
+
395
+ # SimConnect_MapClientEventToSimEvent
396
+ d.SimConnect_MapClientEventToSimEvent.restype = HRESULT
397
+ d.SimConnect_MapClientEventToSimEvent.argtypes = [HANDLE, DWORD, c_char_p]
398
+
399
+ # SimConnect_TransmitClientEvent
400
+ d.SimConnect_TransmitClientEvent.restype = HRESULT
401
+ d.SimConnect_TransmitClientEvent.argtypes = [HANDLE, c_ulong, DWORD, DWORD, DWORD, DWORD]
402
+
403
+ # SimConnect_SubscribeToSystemEvent
404
+ d.SimConnect_SubscribeToSystemEvent.restype = HRESULT
405
+ d.SimConnect_SubscribeToSystemEvent.argtypes = [HANDLE, DWORD, c_char_p]
406
+
407
+ # SimConnect_GetLastSentPacketID
408
+ d.SimConnect_GetLastSentPacketID.restype = HRESULT
409
+ d.SimConnect_GetLastSentPacketID.argtypes = [HANDLE, POINTER(DWORD)]
410
+
411
+ # SimConnect_ClearDataDefinition
412
+ d.SimConnect_ClearDataDefinition.restype = HRESULT
413
+ d.SimConnect_ClearDataDefinition.argtypes = [HANDLE, DWORD]
414
+
415
+ # ── 连接管理 ──────────────────────────────────
416
+
417
+ def open(self, app_name=b"SimConnectApp", window_handle=None, fifo_size=0,
418
+ window_event_handle=None, config_index=0):
419
+ """建立与 MSFS 的 SimConnect 连接。
420
+
421
+ Args:
422
+ app_name: 应用名称(bytes)。
423
+ window_handle: 窗口句柄,默认为 None。
424
+ fifo_size: FIFO 大小,默认为 0。
425
+ window_event_handle: 窗口事件句柄,默认为 None。
426
+ config_index: 配置索引,默认为 0。
427
+
428
+ Returns:
429
+ HANDLE: SimConnect 句柄。
430
+
431
+ Raises:
432
+ ConnectionError: 连接失败或返回空句柄。
433
+ RuntimeError: DLL 未加载,请先调用 load_dll()。
434
+ """
435
+ if not self._dll:
436
+ raise RuntimeError("DLL 未加载,请先调用 load_dll()")
437
+
438
+ hSim = HANDLE(0)
439
+ err = self._dll.SimConnect_Open(
440
+ ctypes.byref(hSim), app_name, window_handle, fifo_size,
441
+ window_event_handle, config_index,
442
+ )
443
+ if err != 0:
444
+ raise ConnectionError(
445
+ f"SimConnect_Open 失败: HRESULT=0x{err:08x}"
446
+ )
447
+ if not hSim or hSim.value is None or hSim.value == 0:
448
+ raise ConnectionError(
449
+ "SimConnect_Open 返回空句柄 — MSFS 可能未运行"
450
+ )
451
+ self._hSimConnect = hSim
452
+ logger.info("SimConnect 已连接 (app=%s)", app_name)
453
+ return hSim
454
+
455
+ def close(self):
456
+ """关闭 SimConnect 连接。"""
457
+ self.stop_background_dispatch()
458
+ if self._dll and self._hSimConnect:
459
+ try:
460
+ self._dll.SimConnect_Close(self._hSimConnect)
461
+ logger.info("SimConnect 已断开")
462
+ except Exception as e:
463
+ logger.debug("SimConnect_Close 异常: %s", e)
464
+ self._hSimConnect = None
465
+
466
+ # ── dispatch ──────────────────────────────────
467
+
468
+ def call_dispatch(self, callback):
469
+ """设置并调用 dispatch 回调处理 SimConnect 消息。
470
+
471
+ Args:
472
+ callback: 回调函数,签名 (pData, cbData, pContext) -> None。
473
+ 其中 pData 是 c_void_p,指向 SIMCONNECT_RECV 结构体。
474
+ """
475
+ if not self._dll or not self._hSimConnect:
476
+ return
477
+ with self._lock:
478
+ self._dispatch_cb = self._DispatchProc(callback)
479
+ self._dll.SimConnect_CallDispatch(
480
+ self._hSimConnect, self._dispatch_cb, None
481
+ )
482
+
483
+ def dispatch(self):
484
+ """处理一次 SimConnect 消息队列。需要先通过 set_dispatch_cb() 设置回调。"""
485
+ if not self._dll or not self._hSimConnect:
486
+ return
487
+ with self._lock:
488
+ cb = self._dispatch_cb
489
+ if not cb:
490
+ return
491
+ self._dll.SimConnect_CallDispatch(
492
+ self._hSimConnect, cb, None
493
+ )
494
+
495
+ def set_dispatch_cb(self, callback):
496
+ """设置 dispatch 回调函数(不触发调用)。
497
+
498
+ Args:
499
+ callback: 回调函数,签名 (pData, cbData, pContext) -> None。
500
+ """
501
+ with self._lock:
502
+ self._dispatch_cb = self._DispatchProc(callback)
503
+
504
+ # ── 后台 dispatch 线程 ────────────────────────
505
+
506
+ def start_background_dispatch(self, callback=None):
507
+ """启动后台线程持续 dispatch(适合高频率数据接收场景)。
508
+
509
+ Args:
510
+ callback: 可选,设置 dispatch 回调;已设置则传 None。
511
+ """
512
+ if callback:
513
+ self.set_dispatch_cb(callback)
514
+ if not self._dispatch_cb:
515
+ raise RuntimeError("请先通过 set_dispatch_cb 设置回调")
516
+ if self._dispatch_running:
517
+ logger.debug("后台 dispatch 已在运行")
518
+ return
519
+
520
+ self._dispatch_stop_event.clear()
521
+ self._dispatch_running = True
522
+ self._dispatch_thread = threading.Thread(
523
+ target=self._dispatch_loop, daemon=True,
524
+ name="SimConnectDispatch",
525
+ )
526
+ self._dispatch_thread.start()
527
+ logger.debug("后台 dispatch 线程已启动")
528
+
529
+ def stop_background_dispatch(self):
530
+ """停止后台 dispatch 线程。"""
531
+ self._dispatch_running = False
532
+ self._dispatch_stop_event.set() # 立即唤醒 dispatch 循环
533
+ if self._dispatch_thread and self._dispatch_thread.is_alive():
534
+ self._dispatch_thread.join(timeout=2)
535
+ if self._dispatch_thread.is_alive():
536
+ logger.warning("后台 dispatch 线程在 2 秒内未退出")
537
+ self._dispatch_thread = None
538
+ logger.debug("后台 dispatch 线程已停止")
539
+
540
+ def _dispatch_loop(self):
541
+ """后台 dispatch 循环。
542
+
543
+ 使用 Event.wait() 替代 time.sleep(),支持被 stop_background_dispatch()
544
+ 立即唤醒退出,避免线程卡在 sleep 中无法及时响应停止信号。
545
+ """
546
+ while self._dispatch_running and self.is_open:
547
+ try:
548
+ self.dispatch()
549
+ except Exception as e:
550
+ logger.warning("dispatch 异常: %s,1 秒后重试", e)
551
+ if self._dispatch_stop_event.wait(timeout=1.0):
552
+ break
553
+ continue
554
+ # 等待直到有数据或收到停止信号
555
+ self._dispatch_stop_event.wait(timeout=0.001)
556
+
557
+ if self.on_disconnect and not self.is_open:
558
+ try:
559
+ self.on_disconnect()
560
+ except Exception:
561
+ pass
562
+
563
+ # ── 数据定义 ──────────────────────────────────
564
+
565
+ def add_to_data_definition(self, define_id, simvar_name, unit,
566
+ datatype=0, epsilon=0.0, datasize=0xFFFFFFFF):
567
+ """注册 SimVar 数据定义。
568
+
569
+ Args:
570
+ define_id: 定义 ID(整数)。
571
+ simvar_name: SimVar 名称(bytes),如 b"PLANE ALTITUDE"。
572
+ unit: 单位(bytes),如 b"Feet"。
573
+ datatype: 数据类型,默认 0(FLOAT64)。
574
+ epsilon: 误差容限,默认 0.0。
575
+ datasize: 数据大小,默认 SIMCONNECT_UNUSED。
576
+
577
+ Returns:
578
+ HRESULT 错误码,0 表示成功。
579
+ """
580
+ return self._dll.SimConnect_AddToDataDefinition(
581
+ self._hSimConnect, DWORD(define_id), simvar_name, unit,
582
+ c_ulong(datatype), c_float(epsilon), DWORD(datasize),
583
+ )
584
+
585
+ def clear_data_definition(self, define_id):
586
+ """清除数据定义。"""
587
+ return self._dll.SimConnect_ClearDataDefinition(
588
+ self._hSimConnect, DWORD(define_id)
589
+ )
590
+
591
+ # ── 数据请求 ──────────────────────────────────
592
+
593
+ def request_data_on_simobject_type(self, request_id, define_id,
594
+ object_id=0, simobject_type=0):
595
+ """请求指定类型的 SimObject 数据。
596
+
597
+ Args:
598
+ request_id: 请求 ID(整数)。
599
+ define_id: 定义 ID(整数)。
600
+ object_id: 对象 ID,默认 0。
601
+ simobject_type: SimObject 类型,默认 SIMCONNECT_SIMOBJECT_TYPE_USER。
602
+ """
603
+ return self._dll.SimConnect_RequestDataOnSimObjectType(
604
+ self._hSimConnect, DWORD(request_id), DWORD(define_id),
605
+ DWORD(object_id), c_ulong(simobject_type),
606
+ )
607
+
608
+ def request_data_on_simobject(self, request_id, define_id, object_id=0,
609
+ period=4, flags=0, origin=0, interval=0, limit=0):
610
+ """请求指定 SimObject 的持续数据更新(更常用的 API)。
611
+
612
+ Args:
613
+ request_id: 请求 ID(整数)。
614
+ define_id: 定义 ID(整数)。
615
+ object_id: 目标对象 ID,默认 0(SIMCONNECT_OBJECT_ID_USER)。
616
+ period: 更新周期,默认 SIMCONNECT_PERIOD_SECOND。
617
+ flags: 标志,默认 0。
618
+ origin: 数据来源,默认 0。
619
+ interval: 间隔,默认 0。
620
+ limit: 限制,默认 0。
621
+ """
622
+ return self._dll.SimConnect_RequestDataOnSimObject(
623
+ self._hSimConnect, DWORD(request_id), DWORD(define_id),
624
+ DWORD(object_id), c_ulong(period), DWORD(flags),
625
+ DWORD(origin), DWORD(interval), DWORD(limit),
626
+ )
627
+
628
+ def add_and_request(self, request_id, define_id, simvar_name, unit,
629
+ datatype=0, period=4):
630
+ """注册定义并立即发起持续请求(一次性完成两个步骤)。
631
+
632
+ Args:
633
+ request_id: 定义 ID 和请求 ID 共用(简化管理)。
634
+ define_id: 同上,通常设为相同值。
635
+ simvar_name: SimVar 名称(bytes)。
636
+ unit: 单位(bytes)。
637
+ datatype: 数据类型,默认 0(FLOAT64)。
638
+ period: 更新周期,默认 SIMCONNECT_PERIOD_SECOND。
639
+ """
640
+ self.add_to_data_definition(define_id, simvar_name, unit, datatype)
641
+ return self.request_data_on_simobject(
642
+ request_id, define_id, object_id=0, period=period,
643
+ )
644
+
645
+ # ── 数据写入 ──────────────────────────────────
646
+
647
+ def set_data_on_simobject(self, define_id, object_id=0,
648
+ flags=0, array_count=1, unit_size=8, data_ptr=None):
649
+ """向 SimObject 写入数据。
650
+
651
+ Args:
652
+ define_id: 定义 ID(整数)。
653
+ object_id: 目标对象 ID,默认 0(SIMCONNECT_OBJECT_ID_USER)。
654
+ flags: 标志,默认 0(SIMCONNECT_DATA_SET_FLAG_DEFAULT)。
655
+ array_count: 数组元素个数,默认 1。
656
+ unit_size: 每个元素大小(字节),默认 8(double)。
657
+ data_ptr: 数据指针(c_void_p),为 None 时跳过。
658
+ """
659
+ if data_ptr is None:
660
+ return
661
+ return self._dll.SimConnect_SetDataOnSimObject(
662
+ self._hSimConnect, DWORD(define_id), c_ulong(object_id),
663
+ DWORD(flags), DWORD(array_count), DWORD(unit_size), data_ptr,
664
+ )
665
+
666
+ def write_double(self, define_id, value):
667
+ """快捷方法:写入一个 double 值。
668
+
669
+ Args:
670
+ define_id: 定义 ID(整数)。
671
+ value: 浮点数值。
672
+ """
673
+ data = c_double(float(value))
674
+ return self.set_data_on_simobject(
675
+ define_id, data_ptr=cast(byref(data), c_void_p),
676
+ )
677
+
678
+ @staticmethod
679
+ def event_data_float(value):
680
+ """将 float 转换为 DWORD 位表示(用于需要浮点参数的事件)。
681
+
682
+ 某些 SimConnect 事件(如襟翼角度)需要将 float 的二进制表示
683
+ 作为 DWORD 传入。
684
+
685
+ 用法:
686
+ sc.transmit_client_event(0, ev_id, SimConnect.event_data_float(15.5))
687
+ """
688
+ return ctypes.cast(byref(c_float(float(value))), POINTER(DWORD)).contents.value
689
+
690
+ # ── 事件 ──────────────────────────────────────
691
+
692
+ def map_client_event_to_sim_event(self, event_id, event_name):
693
+ """将客户端事件 ID 映射到 Sim 事件名称。
694
+
695
+ Args:
696
+ event_id: 事件 ID(整数)。
697
+ event_name: 事件名称(bytes),如 b"KEY_TOGGLE"。
698
+ """
699
+ return self._dll.SimConnect_MapClientEventToSimEvent(
700
+ self._hSimConnect, DWORD(event_id), event_name,
701
+ )
702
+
703
+ def transmit_client_event(self, object_id=0, event_id=0, data=0,
704
+ group_priority=0x19000000, flags=16):
705
+ """发送客户端事件到 SimObject。
706
+
707
+ Args:
708
+ object_id: 目标对象 ID,默认 SIMCONNECT_OBJECT_ID_USER。
709
+ event_id: 事件 ID(整数)。
710
+ data: 事件数据(整数)。
711
+ group_priority: 组优先级,默认 0x19000000(STANDARD)。
712
+ flags: 标志,默认 16(SIMCONNECT_EVENT_FLAG)。
713
+ """
714
+ return self._dll.SimConnect_TransmitClientEvent(
715
+ self._hSimConnect, c_ulong(object_id), DWORD(event_id),
716
+ DWORD(data), DWORD(group_priority), DWORD(flags),
717
+ )
718
+
719
+ # ── 系统事件 ──────────────────────────────────
720
+
721
+ def subscribe_to_system_event(self, event_id, event_name):
722
+ """订阅系统事件。
723
+
724
+ Args:
725
+ event_id: 事件 ID(整数)。
726
+ event_name: 事件名称(bytes),如 b"SimStart"。
727
+ """
728
+ return self._dll.SimConnect_SubscribeToSystemEvent(
729
+ self._hSimConnect, DWORD(event_id), event_name,
730
+ )
731
+
732
+ # ── 工具 ──────────────────────────────────────
733
+
734
+ def get_last_sent_packet_id(self):
735
+ """获取最后发送的数据包 ID。"""
736
+ pid = DWORD(0)
737
+ self._dll.SimConnect_GetLastSentPacketID(
738
+ self._hSimConnect, ctypes.byref(pid)
739
+ )
740
+ return pid.value
741
+
742
+ def read_double(self, pData):
743
+ """从 dispatch 回调的 pData 中读取 double 值。
744
+
745
+ 用于 SIMCONNECT_RECV_ID_SIMOBJECT_DATA / _BYTYPE 消息。
746
+
747
+ Args:
748
+ pData: c_void_p,指向完整消息的指针。
749
+
750
+ Returns:
751
+ (request_id, float_value) 元组,解析失败返回 (None, None)。
752
+ """
753
+ try:
754
+ header = cast(pData, POINTER(SIMOBJECT_DATA_HEADER)).contents
755
+ base = ctypes.cast(pData, c_void_p).value
756
+ val = ctypes.cast(base + SIMOBJECT_DATA_HEADER_SIZE, POINTER(c_double)).contents.value
757
+ return header.dwRequestID, float(val)
758
+ except Exception:
759
+ return None, None
760
+
761
+ @staticmethod
762
+ def read_data(pData, datatype=0):
763
+ """从 dispatch 回调指针中按类型读取数据值。
764
+
765
+ 用于 dispatch 回调中解析不同类型的数据。
766
+
767
+ Args:
768
+ pData: c_void_p,指向消息体的指针(dispatch 回调的第一个参数)。
769
+ datatype: SIMCONNECT_DATATYPE_*,默认 0(FLOAT64)。
770
+
771
+ Returns:
772
+ 数值(int/float/bytes),解析失败返回 None。
773
+ """
774
+ try:
775
+ base = ctypes.cast(pData, c_void_p).value
776
+ data_addr = base + SIMOBJECT_DATA_HEADER_SIZE
777
+ if datatype == 0: # FLOAT64
778
+ return ctypes.cast(data_addr, POINTER(c_double)).contents.value
779
+ elif datatype == 1: # FLOAT32
780
+ return ctypes.cast(data_addr, POINTER(c_float)).contents.value
781
+ elif datatype == 2: # INT32
782
+ return ctypes.cast(data_addr, POINTER(c_int32)).contents.value
783
+ elif datatype == 3: # INT16
784
+ return ctypes.cast(data_addr, POINTER(c_int16)).contents.value
785
+ elif datatype == 4: # INT8
786
+ return ctypes.cast(data_addr, POINTER(c_int8)).contents.value
787
+ elif datatype == 5: # STRINGV
788
+ # 读取 256 字节的字符串
789
+ buf = ctypes.cast(data_addr, POINTER(ctypes.c_char * 256)).contents
790
+ return buf.value.decode('utf-8', errors='replace').rstrip('\x00')
791
+ return None
792
+ except Exception:
793
+ return None