ikkToolKit 0.0.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.
Files changed (30) hide show
  1. ikktoolkit-0.0.0/LICENSE +21 -0
  2. ikktoolkit-0.0.0/PKG-INFO +98 -0
  3. ikktoolkit-0.0.0/README.md +67 -0
  4. ikktoolkit-0.0.0/ikkToolKit/ComPort/SerialBase.py +105 -0
  5. ikktoolkit-0.0.0/ikkToolKit/ComPort/SerialReceiver.py +278 -0
  6. ikktoolkit-0.0.0/ikkToolKit/ComPort/__init__.py +5 -0
  7. ikktoolkit-0.0.0/ikkToolKit/FileSelector/FileSelector.py +62 -0
  8. ikktoolkit-0.0.0/ikkToolKit/FileSelector/__init__.py +3 -0
  9. ikktoolkit-0.0.0/ikkToolKit/HID/HidCodeReader.py +130 -0
  10. ikktoolkit-0.0.0/ikkToolKit/HID/__init__.py +4 -0
  11. ikktoolkit-0.0.0/ikkToolKit/Lookup/CsvLookup.py +168 -0
  12. ikktoolkit-0.0.0/ikkToolKit/Lookup/__init__.py +4 -0
  13. ikktoolkit-0.0.0/ikkToolKit/QrCode/QrCodeBase.py +83 -0
  14. ikktoolkit-0.0.0/ikkToolKit/QrCode/__init__.py +4 -0
  15. ikktoolkit-0.0.0/ikkToolKit/Setting/SettingEditor.py +121 -0
  16. ikktoolkit-0.0.0/ikkToolKit/Setting/XmlSettingBase.py +123 -0
  17. ikktoolkit-0.0.0/ikkToolKit/Setting/__init__.py +5 -0
  18. ikktoolkit-0.0.0/ikkToolKit/Tcp/ModbusTcpSlave.py +188 -0
  19. ikktoolkit-0.0.0/ikkToolKit/Tcp/TcpServerBase.py +156 -0
  20. ikktoolkit-0.0.0/ikkToolKit/Tcp/__init__.py +5 -0
  21. ikktoolkit-0.0.0/ikkToolKit/Tcp/binary_echo_server.py +37 -0
  22. ikktoolkit-0.0.0/ikkToolKit/__init__.py +18 -0
  23. ikktoolkit-0.0.0/ikkToolKit.egg-info/PKG-INFO +98 -0
  24. ikktoolkit-0.0.0/ikkToolKit.egg-info/SOURCES.txt +28 -0
  25. ikktoolkit-0.0.0/ikkToolKit.egg-info/dependency_links.txt +1 -0
  26. ikktoolkit-0.0.0/ikkToolKit.egg-info/not-zip-safe +1 -0
  27. ikktoolkit-0.0.0/ikkToolKit.egg-info/requires.txt +5 -0
  28. ikktoolkit-0.0.0/ikkToolKit.egg-info/top_level.txt +1 -0
  29. ikktoolkit-0.0.0/pyproject.toml +48 -0
  30. ikktoolkit-0.0.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Insight.k.k
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,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: ikkToolKit
3
+ Version: 0.0.0
4
+ Summary: Insight.k.k. ToolKit for easy application development
5
+ Author-email: "Insight.k.k. Team" <maintainer@insightkk.net>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://insightkk.net/oss/ikkToolKit
8
+ Project-URL: Documentation, https://insightkk.net/oss/ikkToolKit/api/index.html
9
+ Project-URL: Issues, https://github.com/Insight-kk/ikkToolKit/issues
10
+ Project-URL: Repository, https://github.com/Insight-kk/ikkToolKit
11
+ Keywords: framework,toolkit,async,serial,network
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: System :: Hardware
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: cantok>=0.0.1
26
+ Requires-Dist: asyncio-cancel-token>=0.2.0
27
+ Requires-Dist: pyserial>=3.5
28
+ Requires-Dist: keyboard>=0.13.0
29
+ Requires-Dist: qrcode[pil]>=8.0
30
+ Dynamic: license-file
31
+
32
+ # ikkToolKit
33
+
34
+ Insight.k.k. による Insight Production開発向けPython ネットワーク・デバイス通信ツールキット
35
+
36
+ ## 概要
37
+
38
+ UT装置の周辺デバイスやハードウェア制御を伴うアプリケーション開発を効率化するための統合ツールキット。シリアル通信、TCP/IP、HID入力、QRコード生成、XML設定管理など、組み込みシステム開発に必要な機能を提供します。
39
+
40
+ **Python 3.10 以上対応**
41
+
42
+ ## 主なモジュール
43
+
44
+ ### ComPort - シリアル通信
45
+
46
+ - **SerialBase**: シリアルポート通信の基本クラス
47
+ - **SerialReceiver**: バーコードリーダー対応の受信特化クラス(接続監視・自動再接続機能)
48
+
49
+ ### HID - HID入力デバイス
50
+
51
+ - **HidCodeReader**: キーボード入力によるバーコード読み取り(プレフィックス・サフィックスにより対応)
52
+
53
+ ### Tcp - TCP/IPサーバー
54
+
55
+ - **TcpServerBase**: 非同期マルチクライアント対応のTCPサーバー基底クラス
56
+ - **ModbusTcpSlave**: Modbusプロトコル対応のTCPスレーブ実装
57
+
58
+ ### Lookup - CSV参照
59
+
60
+ - [CsvLookup](doc/Lookup.md): CSVファイルを使用したキー・バリュー検索
61
+
62
+ ### QrCode - QRコード生成
63
+
64
+ - **QrCodeBase**: 文字列からPIL画像形式のQRコード生成
65
+
66
+ ### Setting - 設定管理
67
+
68
+ - **XmlSettingBase**: XML設定ファイルの読み込み・管理
69
+ - **SettingEditor**: Tkinterベースの設定編集GUI
70
+
71
+ ### FileSelector - ファイル選択
72
+
73
+ - [CsvLookup](doc/FileSelector.md): ファイル選択ダイアログを
74
+
75
+ ## 主な機能
76
+
77
+ - 🔌 **非同期ネットワーク通信**: asyncio を活用したマルチクライアント対応
78
+ - 📡 **シリアル通信**: 自動接続・再接続機能付き
79
+ - ⚙️ **設定管理**: XML形式による柔軟な設定の読み込み・編集
80
+ - 🏷️ **バーコード処理**: QRコード生成、シリアル/HID入力対応
81
+ - 📊 **Modbus対応**: Modbus TCP レジスタ操作
82
+
83
+ ## インストール
84
+
85
+ ```bash
86
+ pip install ikkToolKit
87
+ ```
88
+
89
+ ## リンク
90
+
91
+ - [公式ページ](https://insightkk.net/oss/ikkToolKit)
92
+ - [APIドキュメント](https://insightkk.net/oss/ikkToolKit/api/index.html)
93
+ - [GitHub リポジトリ](https://github.com/Insight-kk/ikkToolKit)
94
+ - [Issues](https://github.com/Insight-kk/ikkToolKit/issues)
95
+
96
+ ## ライセンス
97
+
98
+ MIT License
@@ -0,0 +1,67 @@
1
+ # ikkToolKit
2
+
3
+ Insight.k.k. による Insight Production開発向けPython ネットワーク・デバイス通信ツールキット
4
+
5
+ ## 概要
6
+
7
+ UT装置の周辺デバイスやハードウェア制御を伴うアプリケーション開発を効率化するための統合ツールキット。シリアル通信、TCP/IP、HID入力、QRコード生成、XML設定管理など、組み込みシステム開発に必要な機能を提供します。
8
+
9
+ **Python 3.10 以上対応**
10
+
11
+ ## 主なモジュール
12
+
13
+ ### ComPort - シリアル通信
14
+
15
+ - **SerialBase**: シリアルポート通信の基本クラス
16
+ - **SerialReceiver**: バーコードリーダー対応の受信特化クラス(接続監視・自動再接続機能)
17
+
18
+ ### HID - HID入力デバイス
19
+
20
+ - **HidCodeReader**: キーボード入力によるバーコード読み取り(プレフィックス・サフィックスにより対応)
21
+
22
+ ### Tcp - TCP/IPサーバー
23
+
24
+ - **TcpServerBase**: 非同期マルチクライアント対応のTCPサーバー基底クラス
25
+ - **ModbusTcpSlave**: Modbusプロトコル対応のTCPスレーブ実装
26
+
27
+ ### Lookup - CSV参照
28
+
29
+ - [CsvLookup](doc/Lookup.md): CSVファイルを使用したキー・バリュー検索
30
+
31
+ ### QrCode - QRコード生成
32
+
33
+ - **QrCodeBase**: 文字列からPIL画像形式のQRコード生成
34
+
35
+ ### Setting - 設定管理
36
+
37
+ - **XmlSettingBase**: XML設定ファイルの読み込み・管理
38
+ - **SettingEditor**: Tkinterベースの設定編集GUI
39
+
40
+ ### FileSelector - ファイル選択
41
+
42
+ - [CsvLookup](doc/FileSelector.md): ファイル選択ダイアログを
43
+
44
+ ## 主な機能
45
+
46
+ - 🔌 **非同期ネットワーク通信**: asyncio を活用したマルチクライアント対応
47
+ - 📡 **シリアル通信**: 自動接続・再接続機能付き
48
+ - ⚙️ **設定管理**: XML形式による柔軟な設定の読み込み・編集
49
+ - 🏷️ **バーコード処理**: QRコード生成、シリアル/HID入力対応
50
+ - 📊 **Modbus対応**: Modbus TCP レジスタ操作
51
+
52
+ ## インストール
53
+
54
+ ```bash
55
+ pip install ikkToolKit
56
+ ```
57
+
58
+ ## リンク
59
+
60
+ - [公式ページ](https://insightkk.net/oss/ikkToolKit)
61
+ - [APIドキュメント](https://insightkk.net/oss/ikkToolKit/api/index.html)
62
+ - [GitHub リポジトリ](https://github.com/Insight-kk/ikkToolKit)
63
+ - [Issues](https://github.com/Insight-kk/ikkToolKit/issues)
64
+
65
+ ## ライセンス
66
+
67
+ MIT License
@@ -0,0 +1,105 @@
1
+ # シリアル通信の基本クラス
2
+ # 現時点ではバーコードリーダー向け用途に対応しているのでRead可能であるがWriteは未実装
3
+ # 自動接続などの機能は継承先で実装してね!
4
+ import logging
5
+ import serial
6
+ import serial.tools.list_ports
7
+ import time
8
+ from typing import Optional,Dict
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class SerialBase:
13
+ #--------------------------------------------------------------------------
14
+ def __init__(self):
15
+ '''コンストラクタ
16
+ '''
17
+ self._sp: Optional[serial.Serial] = None # シリアルオブジェクト
18
+ #--------------------------------------------------------------------------
19
+ @staticmethod
20
+ def PortLists() -> Dict[str, str]:
21
+ """有効なシリアルポート一覧を辞書にして返す
22
+ Returns:
23
+ {ポート名: デバイス説明文} の辞書
24
+ """
25
+ ports = serial.tools.list_ports.comports()
26
+ return {port.device: port.description for port in ports}
27
+ #--------------------------------------------------------------------------
28
+ @property
29
+ def BaudRate(self) -> int:
30
+ """現在のボーレート"""
31
+ return self._sp.baudrate if self._sp else 0
32
+ @property
33
+ def portName(self) -> Optional[str]:
34
+ """接続しているデバイスの名称"""
35
+ return self._sp.name if self._sp else None
36
+ @property
37
+ def deviceName(self) -> Optional[str]:
38
+ """接続しているデバイスの説明文(description)"""
39
+ if not self._sp:
40
+ return None
41
+ # 現在のポート名と一致する ListPortInfo を探して description を返す
42
+ for info in serial.tools.list_ports.comports():
43
+ if info.device == self._sp.port:
44
+ return info.description
45
+ return None
46
+ #==========================================================================
47
+ def close(self):
48
+ sp = self._sp
49
+ self._sp = None
50
+ if not sp:
51
+ return
52
+ try:
53
+ if sp.is_open:
54
+ sp.close()
55
+ except Exception as exc:
56
+ logger.warning("serial close failed during shutdown: %s", exc)
57
+ #==========================================================================
58
+ def open(self, port: str, baudrate: int = 9600) -> bool:
59
+ try:
60
+ self._sp = serial.Serial(
61
+ port=port,
62
+ baudrate=baudrate,
63
+ timeout=1,
64
+ write_timeout=1
65
+ )
66
+ # ⭐ 重要
67
+ self._sp.reset_input_buffer()
68
+ self._sp.reset_output_buffer()
69
+ # ⭐ CH340安定化
70
+ self._sp.dtr = False
71
+ time.sleep(0.05)
72
+ self._sp.dtr = True
73
+ return True
74
+ except serial.SerialException:
75
+ return False
76
+ #==========================================================================
77
+ def readLine(self, encoding: str = "utf-8", errors: str = "ignore") -> str:
78
+ '''シリアルから1行読み取る(ブロッキング)'''
79
+ sp = self._sp
80
+ if not sp or not sp.is_open:
81
+ raise serial.SerialException("Port not open")
82
+ buffer = bytearray()
83
+ while True:
84
+ ch = sp.read(1)
85
+ if ch == b'\n' or ch == b'\r': # 改行コードで終了
86
+ break
87
+ if ch == b'':
88
+ return ""
89
+ buffer += ch
90
+ return buffer.decode(encoding=encoding, errors=errors).strip()
91
+ ###############################################################################
92
+ # テスト
93
+ ###############################################################################
94
+ if __name__ == "__main__":
95
+ reader = SerialBase()
96
+ print("******** 有効なシリアルポート一覧 *********")
97
+ print(SerialBase.PortLists())
98
+ print("*****************************************")
99
+ #キーワードで開くテスト(環境に合わせてキーワードを変更してください)
100
+ for port in SerialBase.PortLists().keys():
101
+ if reader.open(port=port):
102
+ print(f"👌成功:{reader.portName} :{reader.deviceName} ")
103
+ reader.close()
104
+ else:
105
+ print("❌オープン失敗")
@@ -0,0 +1,278 @@
1
+ # シリアル通信を継承したバーコードリーダークラス
2
+ # 受信特化で、接続監視と自動再接続機能を備える
3
+ # コードの内容などのユーザーごとの都合は継承先で実装してね!
4
+ import threading
5
+ import logging
6
+ import time
7
+ import serial
8
+ from typing import Callable, Optional
9
+
10
+ try:
11
+ from .SerialBase import SerialBase
12
+ except ImportError:
13
+ from SerialBase import SerialBase
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class SerialReceiver(SerialBase):
18
+ #==========================================================================
19
+ def __init__(self, keyword: str, baudrate: int = 9600, debug: bool = False, encoding: str = "utf-8", decode_errors: str = "ignore", port_selector: Optional[Callable[[list[dict[str, str]]], Optional[str]]] = None):
20
+ '''コンストラクタ'''
21
+ super().__init__()
22
+ self._keyword = keyword
23
+ self._bordRrate = baudrate
24
+ self._debugMode = debug
25
+ self._encoding = encoding
26
+ self._decodeErrors = decode_errors
27
+ self._portSelector = port_selector
28
+ self._preferredPort = ""
29
+ self._preferredVid = ""
30
+ self._preferredPid = ""
31
+ self._preferredSerialNumber = ""
32
+ self._lastSelectionPromptAt = 0.0
33
+ self._lastSelectionSignature: tuple[tuple[str, str, str, str], ...] = tuple()
34
+ self.on_receive: Optional[Callable[[str], None]] = None
35
+ self.on_connected: Optional[Callable[[], None]] = None
36
+ self.on_disconnected: Optional[Callable[[], None]] = None
37
+ self._running: bool = False
38
+ self._thread: Optional[threading.Thread] = None
39
+ self._log:Optional[logging.Logger] = None
40
+ def setLogger(self, logger: logging.Logger):
41
+ self._log = logger
42
+ def setPortSelector(self, selector: Optional[Callable[[list[dict[str, str]]], Optional[str]]]) -> None:
43
+ self._portSelector = selector
44
+ def setPreferredDevice(self, port: Optional[str] = None, vid: Optional[str] = None, pid: Optional[str] = None, serial_number: Optional[str] = None) -> None:
45
+ self._preferredPort = (port or "").strip().upper()
46
+ self._preferredVid = (vid or "").strip().upper()
47
+ self._preferredPid = (pid or "").strip().upper()
48
+ self._preferredSerialNumber = (serial_number or "").strip()
49
+ #==========================================================================
50
+ @property
51
+ def BaudRate(self) -> int:
52
+ """現在のボーレート"""
53
+ return self._bordRrate
54
+ @property
55
+ def Encoding(self) -> str:
56
+ """受信デコードに使う文字コード"""
57
+ return self._encoding
58
+ #==========================================================================
59
+ def start(self):
60
+ '''接続監視'''
61
+ # すでに動いてるなら何もしない
62
+ if self._running:
63
+ return
64
+ self._running = True
65
+ # デバッグモードならキー入力ループ、通常モードなら接続監視ループをスレッドで開始
66
+ if self._debugMode:
67
+ self._thread = threading.Thread(
68
+ target=self._debugLoop,
69
+ daemon=True
70
+ )
71
+ else:
72
+ self._thread = threading.Thread(
73
+ target=self._loopRx,
74
+ daemon=True
75
+ )
76
+ self._thread.start()
77
+ #================================================================
78
+ def stop(self):
79
+ self._running = False
80
+ self.close()
81
+ if self._thread:
82
+ self._thread.join(timeout=1.2)
83
+ if self._thread.is_alive():
84
+ logger.warning("SerialReceiver thread did not stop within timeout")
85
+ self._thread = None
86
+ #----------------------------------------------------------------
87
+ def _debugLoop(self):
88
+ key_map = self._getDebugKeyMap()
89
+ print("=== DEBUG MODE ===")
90
+ if self.on_connected:
91
+ self.on_connected() #デバッグモードでも接続イベントは発火させる
92
+ for k, v in key_map.items():
93
+ print(f"{k} → {v}")
94
+ print("q → quit debug")
95
+ while self._running:
96
+ try:
97
+ key = input("> ").strip().lower()
98
+ if key == "q":
99
+ break
100
+ if key in key_map:
101
+ self._emitBarcode(key_map[key])
102
+ except (KeyboardInterrupt, EOFError):
103
+ break
104
+ self._running = False
105
+ #----------------------------------------------------------------
106
+ def _getDebugKeyMap(self):
107
+ """デバッグモードで模擬するメッセージと発動キーのマップ。継承先で上書きする(これはひな形)
108
+ """
109
+ return {
110
+ "a": "AAAA",
111
+ "b": "BBBB",
112
+ "c": "CCCC",
113
+ "1": "1111",
114
+ "2": "2222",
115
+ "3": "3333",
116
+ }
117
+ #----------------------------------------------------------------
118
+ def _emitBarcode(self, code: str):
119
+ if self.on_receive: #受信ベント発火🔥
120
+ self.on_receive(code)
121
+ #----------------------------------------------------------------
122
+ @staticmethod
123
+ def _portInfoToCandidate(port_info) -> dict[str, str]:
124
+ vid = getattr(port_info, "vid", None)
125
+ pid = getattr(port_info, "pid", None)
126
+ return {
127
+ "port": str(getattr(port_info, "device", "") or ""),
128
+ "description": str(getattr(port_info, "description", "") or ""),
129
+ "manufacturer": str(getattr(port_info, "manufacturer", "") or ""),
130
+ "product": str(getattr(port_info, "product", "") or ""),
131
+ "serial_number": str(getattr(port_info, "serial_number", "") or ""),
132
+ "vid": f"{vid:04X}" if vid is not None else "",
133
+ "pid": f"{pid:04X}" if pid is not None else "",
134
+ }
135
+ #----------------------------------------------------------------
136
+ def _keywordMatches(self, port_info) -> bool:
137
+ description = str(getattr(port_info, "description", "") or "")
138
+ return bool(self._keyword) and self._keyword.upper() in description.upper()
139
+ #----------------------------------------------------------------
140
+ def _isFallbackCandidate(self, port_info) -> bool:
141
+ return False
142
+ #----------------------------------------------------------------
143
+ def _matchesPreferredIdentity(self, port_info) -> bool:
144
+ candidate = self._portInfoToCandidate(port_info)
145
+ candidate_serial = candidate["serial_number"]
146
+ if self._preferredSerialNumber and candidate_serial == self._preferredSerialNumber:
147
+ return True
148
+ if self._preferredVid and self._preferredPid:
149
+ return candidate["vid"] == self._preferredVid and candidate["pid"] == self._preferredPid
150
+ return False
151
+ #----------------------------------------------------------------
152
+ def _notifyConnected(self) -> None:
153
+ if self.on_connected:
154
+ self.on_connected()
155
+ #----------------------------------------------------------------
156
+ def _tryOpenPort(self, port_info) -> bool:
157
+ if self.open(port_info.device, self._bordRrate):
158
+ self._preferredPort = str(port_info.device).upper()
159
+ self._notifyConnected()
160
+ return True
161
+ return False
162
+ #----------------------------------------------------------------
163
+ def _requestFallbackSelection(self, candidates: list[dict[str, str]]) -> Optional[str]:
164
+ if not self._portSelector or not candidates:
165
+ return None
166
+ signature = tuple(
167
+ (candidate["port"], candidate["vid"], candidate["pid"], candidate["serial_number"])
168
+ for candidate in candidates
169
+ )
170
+ now = time.monotonic()
171
+ if signature == self._lastSelectionSignature and (now - self._lastSelectionPromptAt) < 10.0:
172
+ return None
173
+ self._lastSelectionSignature = signature
174
+ self._lastSelectionPromptAt = now
175
+ try:
176
+ selected_port = self._portSelector(candidates)
177
+ return selected_port.strip().upper() if selected_port else None
178
+ except Exception:
179
+ logger.exception("COMポート選択コールバックで例外発生")
180
+ return None
181
+ #----------------------------------------------------------------
182
+ def _connectAvailablePort(self) -> bool:
183
+ port_infos = list(serial.tools.list_ports.comports())
184
+ if not port_infos:
185
+ return False
186
+
187
+ tried_ports: set[str] = set()
188
+
189
+ def try_open(predicate: Callable[[object], bool]) -> bool:
190
+ for port_info in port_infos:
191
+ port_name = str(getattr(port_info, "device", "") or "").upper()
192
+ if not port_name or port_name in tried_ports:
193
+ continue
194
+ if not predicate(port_info):
195
+ continue
196
+ tried_ports.add(port_name)
197
+ if self._tryOpenPort(port_info):
198
+ return True
199
+ return False
200
+
201
+ if try_open(self._matchesPreferredIdentity):
202
+ return True
203
+
204
+ if self._preferredPort:
205
+ preferred_port = self._preferredPort.upper()
206
+ for port_info in port_infos:
207
+ port_name = str(getattr(port_info, "device", "") or "").upper()
208
+ if port_name != preferred_port:
209
+ continue
210
+ tried_ports.add(port_name)
211
+ if self._tryOpenPort(port_info):
212
+ return True
213
+ break
214
+
215
+ if try_open(self._keywordMatches):
216
+ return True
217
+
218
+ fallback_candidates = [
219
+ self._portInfoToCandidate(port_info)
220
+ for port_info in port_infos
221
+ if str(getattr(port_info, "device", "") or "").upper() not in tried_ports and self._isFallbackCandidate(port_info)
222
+ ]
223
+ selected_port = self._requestFallbackSelection(fallback_candidates)
224
+ if not selected_port:
225
+ return False
226
+
227
+ for port_info in port_infos:
228
+ port_name = str(getattr(port_info, "device", "") or "").upper()
229
+ if port_name != selected_port:
230
+ continue
231
+ return self._tryOpenPort(port_info)
232
+ return False
233
+ #----------------------------------------------------------------
234
+ def _loopRx(self):
235
+ """仮想COMの受信ループ"""
236
+ while self._running:
237
+ # --- 未接続なら接続試行 ---
238
+ if not self._sp or not self._sp.is_open:
239
+ connected = self._connectAvailablePort()
240
+ if not connected:
241
+ time.sleep(1)
242
+ logger.debug("Waiting for device...")
243
+ continue
244
+ # --- 接続中は受信処理 ---
245
+ try:
246
+ line = self.readLine(encoding=self._encoding, errors=self._decodeErrors)
247
+ # logger.debug(f"Received line: {repr(line)}")
248
+ if line:
249
+ if self.on_receive:
250
+ self.on_receive(line) #受信ベント発火🔥
251
+ except serial.SerialException:
252
+ self.close()
253
+ if self.on_disconnected:
254
+ self.on_disconnected()
255
+ # ループ終了時安全クローズ
256
+ self.close()
257
+ ###############################################################################
258
+ # テスト
259
+ ###############################################################################
260
+ if __name__ == "__main__":
261
+ #コマンド引数 d があればデバッグモードで起動
262
+ import sys
263
+ debug_mode = "d" in sys.argv
264
+
265
+ #キーワードとデバッグモードを指定
266
+ sr = SerialReceiver(keyword="CH340", debug=debug_mode)
267
+ #イベントハンドラの設定(動作確認用のプリント)
268
+ sr.on_connected = lambda: print("🔗Device connected.")
269
+ sr.on_disconnected = lambda: print("⚡Device disconnected.")
270
+ sr.on_receive = lambda code: print(f"📥 Recive: {repr(code)}", flush=True)
271
+ sr.start()
272
+ print("Press Ctrl+C to stop...")
273
+ try:
274
+ while True:
275
+ time.sleep(1)
276
+ except KeyboardInterrupt:
277
+ print("Stopping...")
278
+ sr.stop()
@@ -0,0 +1,5 @@
1
+ """シリアル通信モジュール"""
2
+ from .SerialBase import SerialBase
3
+ from .SerialReceiver import SerialReceiver
4
+
5
+ __all__ = ["SerialBase", "SerialReceiver"]
@@ -0,0 +1,62 @@
1
+ import tkinter as tk
2
+ from tkinter import filedialog
3
+ from pathlib import Path
4
+
5
+ class FileSelector:
6
+ """ファイル選択クラス(UIツールキットに依存しない)"""
7
+ def __init__(self, base_folder: str, extensions: list[str]):
8
+ """
9
+ Args:
10
+ base_folder: 初期表示フォルダ
11
+ extensions: 拡張子リスト ['*.csv', '*.txt']
12
+ """
13
+ self._base_folder = base_folder
14
+ self._extensions = extensions
15
+ self._last_folder = base_folder
16
+
17
+ @property
18
+ def base_folder(self) -> str:
19
+ return self._base_folder
20
+ @base_folder.setter
21
+ def base_folder(self, folder: str):
22
+ self._base_folder = folder
23
+ def select(self, root=None) -> str | None:
24
+ """
25
+ ファイル選択ダイアログを表示
26
+ Args:
27
+ root: 外部から渡された tk.Tk インスタンス(オプション)
28
+ None の場合は自前で生成・破棄
29
+ Returns:
30
+ 選択ファイルのフルパス、キャンセルの場合は None
31
+ """
32
+ # 外部から root が渡されたかチェック
33
+ if root is None:
34
+ root = tk.Tk()
35
+ root.withdraw()
36
+ should_destroy = True
37
+ else:
38
+ should_destroy = False
39
+ try:
40
+ filetypes = [("対象ファイル", " ".join(self._extensions)),
41
+ ("すべてのファイル", "*.*")]
42
+ file_path = filedialog.askopenfilename(
43
+ initialdir=self._last_folder,
44
+ title="ファイルを選択",
45
+ filetypes=filetypes
46
+ )
47
+ if file_path:
48
+ self._last_folder = str(Path(file_path).parent)
49
+ return file_path
50
+ else:
51
+ return None
52
+ finally:
53
+ if should_destroy:
54
+ root.destroy()
55
+ ###############################################################################
56
+ #
57
+ ###############################################################################
58
+ if __name__ == "__main__":
59
+ # ikkToolKit 側(root不要)
60
+ selector = FileSelector("C:\\", ["*.pdf"])
61
+ file_path = selector.select() # 自前で root を生成・破棄
62
+ print("Selected file:", file_path)
@@ -0,0 +1,3 @@
1
+ from .FileSelector import FileSelector
2
+
3
+ __all__ = ["FileSelector"]