aandd-reader 0.2.15__py3-none-any.whl

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 (35) hide show
  1. aandd_reader/__init__.py +20 -0
  2. aandd_reader/aandd_reader.py +765 -0
  3. aandd_reader/config.py +105 -0
  4. aandd_reader/server.py +13 -0
  5. aandd_reader/server_fastapi/__init__.py +47 -0
  6. aandd_reader/server_fastapi/__init__.pyi +15 -0
  7. aandd_reader/server_fastapi/clients/__init__.py +6 -0
  8. aandd_reader/server_fastapi/clients/async_aandd_reader_client.py +104 -0
  9. aandd_reader/server_fastapi/clients/async_aandd_reader_client.pyi +86 -0
  10. aandd_reader/server_fastapi/clients/sync_aandd_reader_client.py +53 -0
  11. aandd_reader/server_fastapi/clients/sync_aandd_reader_client.pyi +80 -0
  12. aandd_reader/server_fastapi/make.py +345 -0
  13. aandd_reader/server_fastapi/make_device_proxy.py +35 -0
  14. aandd_reader/server_fastapi/make_old.py +164 -0
  15. aandd_reader/server_fastapi/models/__init__.py +0 -0
  16. aandd_reader/server_fastapi/models/comman.py +17 -0
  17. aandd_reader/server_fastapi/routers/__init__.py +3 -0
  18. aandd_reader/server_fastapi/routers/aandd_reader_router.py +36 -0
  19. aandd_reader/server_fastapi/routers/aandd_reader_router.pyi +34 -0
  20. aandd_reader/server_fastapi/spec_ctrl.py +82 -0
  21. aandd_reader/server_grpc/__init__.py +8 -0
  22. aandd_reader/server_grpc/aandd_reader_grpc_client.py +128 -0
  23. aandd_reader/server_grpc/aandd_reader_grpc_client.pyi +23 -0
  24. aandd_reader/server_grpc/aandd_reader_grpc_server.py +107 -0
  25. aandd_reader/server_grpc/async_aandd_reader_grpc_client.py +104 -0
  26. aandd_reader/server_grpc/async_aandd_reader_grpc_client.pyi +23 -0
  27. aandd_reader/server_grpc/make_pyi.py +56 -0
  28. aandd_reader/try/try01.py +28 -0
  29. aandd_reader/try/try02.py +108 -0
  30. aandd_reader/try/try03.py +113 -0
  31. aandd_reader-0.2.15.dist-info/METADATA +104 -0
  32. aandd_reader-0.2.15.dist-info/RECORD +35 -0
  33. aandd_reader-0.2.15.dist-info/WHEEL +5 -0
  34. aandd_reader-0.2.15.dist-info/licenses/LICENSE +201 -0
  35. aandd_reader-0.2.15.dist-info/top_level.txt +1 -0
@@ -0,0 +1,765 @@
1
+ """
2
+ K.NAKADA, kengo.nakada@gmail.com/kengo.nakada@mat.shimane-u.ac.jp
3
+
4
+ 返信メッセージの形式
5
+
6
+ - ACK なし(出荷時)
7
+ - ACK あり(現在)
8
+
9
+ PRT モードとデーター取得について
10
+
11
+ PRT は **「データ出力(Serial Print)の自動出力条件」**
12
+ 天秤が“自分の判断で”出力を送るタイミングの設定であり、
13
+ Q/S/SIR などの“コマンド要求”とは別軸となる
14
+
15
+ 1 Auto print A(基準=ゼロ) 安定+条件成立で自動出力
16
+ 2 Auto print B(基準=前回の安定値) 安定+条件成立で自動出力
17
+ 3 Stream mode 連続出力(表示更新周期に依存)
18
+ SIR と実施的には同じである
19
+ SIR はコマンドでスタートをキックできる PTR3 は開始したら動きっぱなし
20
+ どちらも readline で値を読み続けることができる
21
+ *) 測定可能なサイクルは 5pd で、 5/10/20 回/秒設定が可能
22
+ SAMPLE-key --> ba5fnc
23
+ 4 Key mode B PRINTキーを押すと出力(安定不要)
24
+ 5 Key mode C PRINTキーで出力(安定なら即、非安定なら待つ)
25
+ 6 Interval mode 一定間隔で周期出力( mode3 との違いに注意)
26
+
27
+ --- 上記は結果をシリアルに流すので、コマンドで値要求(Q/S/SIR)と
28
+ 混ざるとどの応答をシリアルで捉えているのか分かりにくい
29
+ STREAM MODE の測定周期は天秤側に設定する必要がある
30
+
31
+ コマンド出力(Q/S/SIR)
32
+ PCからのコマンドに対して天秤が返す出力
33
+ SIR は readline のストリームに流す。
34
+
35
+ """
36
+
37
+ from typing import Any, Dict, List, Optional, Union, Tuple, Callable, Sequence
38
+ import serial
39
+ import time
40
+ import threading
41
+ import sys
42
+
43
+
44
+ class AanddReader:
45
+ """
46
+ 通信速度
47
+ 天秤の 内部設定
48
+ bPS (ボーレート) 19200bps
49
+
50
+ 全てのコマンドに対しての何らかの応答(ACK)を返すモードであるのが前提
51
+ 天秤の 内部設定
52
+ SF(シリアルインターフェース)
53
+ ErCd 1 (出力する AK,コード)
54
+ """
55
+
56
+ def __init__(
57
+ self,
58
+ port: str,
59
+ baud_rate: Optional[int] = None,
60
+ timeout: Optional[float] = None,
61
+ logger: Optional[Any] = None,
62
+ log_level: Optional[str] = None,
63
+ ):
64
+ # XLogger が存在しない時に仕方がないのでデフォルトの logging を使う
65
+ if logger is None:
66
+ import logging
67
+
68
+ log_level = log_level or "INFO"
69
+ logging.basicConfig(level=log_level.upper())
70
+ logger = logging.getLogger(__name__)
71
+
72
+ self._logger = logger
73
+
74
+ self._port = port or "COM3"
75
+ self._baud_rate = baud_rate or 19200
76
+ self._timeout = timeout or 0.5
77
+ self._meas_mode = "Q"
78
+
79
+ self._status: Optional[str] = None
80
+ self._unit: Optional[str] = None
81
+ self._weight: Optional[float] = None
82
+
83
+ self._logger.info(f"Create AandD Device (Serial)")
84
+ self._logger.info(f"timeout = {self._timeout}")
85
+ self._logger.info(f"baud_rate = {self._baud_rate}")
86
+ self._logger.info(f"port = {self._port}")
87
+ try:
88
+ self._ser = serial.Serial(
89
+ port=self._port,
90
+ baudrate=self._baud_rate,
91
+ parity=serial.PARITY_EVEN,
92
+ bytesize=serial.SEVENBITS,
93
+ timeout=self._timeout,
94
+ )
95
+ except Exception as e:
96
+ self._logger.error(f"**Error**: {e}")
97
+ sys.exit(1)
98
+
99
+ # ----------------------------------
100
+ # stream 測定スレッド制御
101
+ self._stream_thread = None
102
+ self._stream_stop_event = threading.Event()
103
+ # ----------------------------------
104
+
105
+ # ----------------------------------
106
+ # gRPC 利用を意識
107
+ # _stream で得た値が式意図を超えたときの結果によってevent発生
108
+ # Event / stream related
109
+ self._lock = threading.Lock()
110
+
111
+ # ----------------------------------
112
+ # 外部通知先 (gRPC Event)
113
+ #
114
+ # 測定クライアント側で
115
+ # AanddReader生成
116
+ # → EventBus生成
117
+ # → reader._set_event_bus(event_bus)
118
+ # → stream_start()
119
+ # → event_arm()
120
+ self._event_bus: Optional[Any] = None
121
+ #
122
+ # eventを使わない場合
123
+ # (1) _event_sub は None
124
+ # (2) stream_start() は呼ばれない
125
+ # (3) event_arm() は呼ばれない
126
+
127
+ # ----------------------------------
128
+ # イベント条件・状態
129
+ # (gRPCに依存してないが実質的にはgRPC-Eventで用いる)
130
+ self._event_topic: Optional[str] = None
131
+ self._event_threshold_g: Optional[float] = None
132
+ self._event_once: bool = True
133
+ self._event_armed: bool = False
134
+
135
+ self._event_above: bool = True
136
+ # ----------------------------------
137
+
138
+ # ----------------------------------
139
+ # トリガ判定用(streamイベント用)
140
+ self._trigger_mode: str = "raw" # raw / mean / median / ema
141
+ self._trigger_window: int = 3
142
+ self._trigger_min_samples: int = 1
143
+ self._trigger_alpha: float = 0.3
144
+ self._trigger_buffer: List[float] = []
145
+ self._trigger_ema: Optional[float] = None
146
+ self._trigger_stable_only: bool = False
147
+ # ----------------------------------
148
+
149
+ def __del__(self):
150
+ try:
151
+ self._close()
152
+ except Exception as e:
153
+ pass
154
+
155
+ def _close(self) -> None:
156
+ """
157
+ ストリーム停止とシリアルクローズを確実に行う
158
+ gRPC からもし使うときに gRPC側の close() とかぶるので名前を変えておく
159
+ """
160
+ try:
161
+ self.stream_stop()
162
+ except Exception:
163
+ pass
164
+
165
+ try:
166
+ self._ser.close()
167
+ except Exception:
168
+ pass
169
+
170
+ # ---------------------------
171
+ # Device Info API
172
+ # ---------------------------
173
+
174
+ def status(self) -> Optional[str]:
175
+ return self._status
176
+
177
+ def get_status(self) -> Optional[str]:
178
+ return self._status
179
+
180
+ def unit(self) -> Optional[str]:
181
+ return self._unit
182
+
183
+ def get_unit(self) -> Optional[str]:
184
+ return self._unit
185
+
186
+ def get_weight(self) -> Optional[float]:
187
+ return self._weight
188
+
189
+ @staticmethod
190
+ def is_ack(data: bytes) -> bool:
191
+ return data == b"\x06"
192
+
193
+ def get_serial_number(self):
194
+ """
195
+ ?SN
196
+ マニュアルには \r でも OK とあるが、\r\n が必須
197
+
198
+ Returns:
199
+
200
+ """
201
+ self._logger.info(f"[Get Serial Number]")
202
+ self._ser.reset_input_buffer()
203
+
204
+ try:
205
+ self._ser.write(b"?SN\r\n")
206
+ line = self._ser.readline()
207
+ self._logger.info(f"[ID raw] {line!r}")
208
+
209
+ # return line.decode(errors="ignore").strip()
210
+ serial_number = ""
211
+ if len(line) == 13 and line != b"":
212
+ line = line.rstrip()
213
+ try:
214
+ serial_number = line[3:11].decode()
215
+ except Exception as e:
216
+ self._logger.error(f"**Error**: {e}")
217
+ return False
218
+ else:
219
+ self._logger.error(line)
220
+ return False
221
+ return serial_number
222
+
223
+ except Exception as e:
224
+ self._logger.error(f"**Error**: {e}")
225
+ return ""
226
+
227
+ def set_meas_mode(self, val: str = "Q"):
228
+ self._meas_mode: str = val
229
+
230
+ def get_meas_mode(self) -> str:
231
+ return self._meas_mode
232
+
233
+ # ---------------------------------
234
+ # Device Ctrl API
235
+ # ---------------------------------
236
+
237
+ def reset(self) -> Union[bool, str]:
238
+ """reset
239
+ reset 後、測定するには 0.5 s くらい待たないとダメ
240
+ """
241
+ return self._r()
242
+
243
+ def meas(
244
+ self,
245
+ mode: Optional[str] = None,
246
+ ) -> Union[bool, float, str, None]:
247
+ """
248
+ Q
249
+ 即時計測する
250
+ Returns:
251
+ """
252
+ # --- ストリームが動いているなら、必ず最新のストリーム値を返す ---
253
+ thread_obj = self._stream_thread
254
+ if thread_obj is not None and thread_obj.is_alive():
255
+ # 初回値がまだなら短時間だけ待つ
256
+ wait_sec = self._timeout if self._timeout is not None else 0.5
257
+ t0 = time.time()
258
+ while self._weight is None and (time.time() - t0) < wait_sec:
259
+ time.sleep(0.01)
260
+ return self._weight
261
+
262
+ # --- 通常の Q/S 測定 ---
263
+ mode_val = mode
264
+ if mode_val is None:
265
+ mode_val = self._meas_mode
266
+
267
+ if mode_val.upper() == "Q":
268
+ return self._q()
269
+
270
+ if mode_val.upper() == "S":
271
+ return self._s()
272
+
273
+ return False
274
+
275
+ # ---------------------------------
276
+ # Device Ctrl Driver
277
+ # ---------------------------------
278
+
279
+ def _r(
280
+ self,
281
+ ) -> Union[str, bool]:
282
+ """reset
283
+ ACK前提
284
+
285
+ reset 後、測定するには 0.5 s くらい待たないとダメ
286
+ """
287
+ self._logger.debug(f"[Serial] Reset(): _r")
288
+ if self._ser is None:
289
+ self._logger.error("Serial port is not opened.")
290
+ return False
291
+
292
+ self._ser.reset_input_buffer()
293
+ self._ser.write(b"R\r\n")
294
+
295
+ try:
296
+ line = self._ser.readline()
297
+ except Exception as e:
298
+ self._logger.error(f"**Error**: {e}")
299
+ return False
300
+
301
+ if len(line) == 0:
302
+ self._logger.error("**Error**: No data received.")
303
+ return False
304
+
305
+ if line[0] == 0x06:
306
+ return True
307
+ return False
308
+
309
+ def _s(
310
+ self,
311
+ ) -> Union[bool, float]:
312
+ """
313
+ ACKモード前提
314
+
315
+ Q, 安定後、データ取得
316
+
317
+ Returns:
318
+ """
319
+ if self._ser is None:
320
+ self._logger.error("Serial port is not opened.")
321
+ return False
322
+
323
+ self._ser.reset_input_buffer()
324
+
325
+ try:
326
+ self._ser.write(b"S\r\n")
327
+ line = self._ser.readline()
328
+ except Exception as e:
329
+ self._logger.error(f"**Error**: {e}")
330
+ return False
331
+
332
+ parsed = self._parse_measure_line(line)
333
+ if parsed is None:
334
+ self._logger.error(line)
335
+ return False
336
+
337
+ status, weight, unit = parsed
338
+ self._status = status
339
+ self._weight = weight
340
+ self._unit = unit
341
+ return float(weight)
342
+
343
+ def _q(
344
+ self,
345
+ ) -> Union[bool, float]:
346
+ """
347
+ ACKモード前提
348
+
349
+ Q, 即時
350
+
351
+ Returns:
352
+ """
353
+ if self._ser is None:
354
+ self._logger.error("Serial port is not opened.")
355
+ return False
356
+
357
+ self._ser.reset_input_buffer()
358
+
359
+ try:
360
+ self._ser.write(b"Q\r\n")
361
+ line = self._ser.readline()
362
+ except Exception as e:
363
+ self._logger.error(f"**Error(readline)**: {e}")
364
+ return False
365
+
366
+ if b"EC,E02" in line:
367
+ self._logger.error(f"**Error(EC, E02)**: {line}")
368
+ self._logger.error(
369
+ "計測時間が短くて前回の命令動作を本体足している可能性があります"
370
+ )
371
+ return False
372
+
373
+ parsed = self._parse_measure_line(line)
374
+ if parsed is None:
375
+ self._logger.error(f"**Error(len(line)!=17)**: {line}")
376
+ self._logger.error(
377
+ "返信メッセージが正しくない可能性があります。たとえば「空」の可能性です"
378
+ )
379
+ self._logger.error("計測モードなどのパラメーターを疑ってください")
380
+ self._logger.error(f"line: {line}")
381
+ return False
382
+
383
+ status, weight, unit = parsed
384
+ self._status = status
385
+ self._weight = weight
386
+ self._unit = unit
387
+ return float(weight)
388
+
389
+ def _stream(self) -> Union[bool, float]:
390
+ """
391
+ ストリームデータを1行読む
392
+ Q/S や SIR もしくは PRT の各モードも readline に値を書き出す
393
+ ので、どれがどれに対応しているのあわからなくなるので
394
+
395
+ Q/S で使うとき
396
+ SIR で readline を loop で撮り続ける
397
+
398
+ をきちんと区別して使い分けないとおかしいことになる。
399
+ また、PRT の各モードは上記のコマンドベースにキックされて readline に
400
+ 流れる以外にPRTの各機能でそれぞれの周期やボタンで readline にデーターを
401
+ 流すので Q/S/SIR を使う時は PRT を 0 にしておかないと訳がわからなくなる。
402
+ """
403
+ if self._ser is None:
404
+ self._logger.error("Serial port is not opened.")
405
+ return False
406
+
407
+ try:
408
+ line = self._ser.readline()
409
+ except Exception as e:
410
+ self._logger.error(f"**Error**: {e}")
411
+ return False
412
+
413
+ # SIR だとタイムアウトで空行は起こり得るので無視
414
+ if not line:
415
+ # self._logger.error("No data received.")
416
+ return False
417
+
418
+ # SIRの失敗は EC,Exx が返る
419
+ if line.startswith(b"EC,"):
420
+ self._logger.error(f"**Error(EC)**: {line!r}")
421
+ return False
422
+
423
+ # erCd=1 の場合、各コマンドに ACK(0x06) が返るため無視
424
+ if line == b"\x06":
425
+ return False
426
+
427
+ parsed = self._parse_measure_line(line)
428
+ if parsed is None:
429
+ return False
430
+
431
+ status, weight, unit = parsed
432
+ self._status = status
433
+ self._weight = weight
434
+ self._unit = unit
435
+ return float(weight)
436
+
437
+ @staticmethod
438
+ def _parse_measure_line(
439
+ line: bytes,
440
+ ) -> Optional[Tuple[str, float, str]]:
441
+ """"""
442
+ if line is None:
443
+ return None
444
+
445
+ raw = line.rstrip()
446
+
447
+ # 例: b'ST,+0000.000 g\r\n' を想定
448
+ # 固定長の厳密チェックはしないが、最低限の slicing が成立する長さは必要
449
+ if len(raw) < 15:
450
+ return None
451
+
452
+ try:
453
+ status = raw[0:2].decode()
454
+ weight = float(raw[3:12].decode())
455
+ unit = raw[12:15].decode().strip()
456
+ except Exception:
457
+ return None
458
+
459
+ return status, weight, unit
460
+
461
+ # ----------------------------
462
+ # for Event (gRPC and thread)
463
+ # ----------------------------
464
+
465
+ def _set_event_bus(self, event_bus: Any) -> None:
466
+ """
467
+ gRPC の event による制御が回るように gRPC のイベントを挿入
468
+ """
469
+ with self._lock:
470
+ self._event_bus = event_bus
471
+
472
+ def event_set_config(
473
+ self,
474
+ *,
475
+ topic: str,
476
+ threshold_g: float,
477
+ above: bool = True,
478
+ ) -> bool:
479
+ """
480
+ Event 条件の設定だけを行う。
481
+ 設定後、event_arm() で armed を有効化する。
482
+ """
483
+ with self._lock:
484
+ self._event_topic = topic
485
+ self._event_threshold_g = float(threshold_g)
486
+ self._event_above = bool(above)
487
+ return True
488
+
489
+ def event_arm(
490
+ self,
491
+ *,
492
+ once: bool = True,
493
+ ) -> bool:
494
+ """
495
+ Event の armed フラグを立てる。
496
+ topic/threshold は event_set_config() で先に設定する。
497
+
498
+ armed を立てていると gRPC のイベントモードになるのでチェックする
499
+ そのときに onece を入れていると、一回だけ閾値超え判定がある。
500
+
501
+ Args:
502
+ once (bool): イベントは一回のみ
503
+
504
+ Returns:
505
+ bool:
506
+
507
+ """
508
+ with self._lock:
509
+ if self._event_topic is None:
510
+ return False
511
+ if self._event_threshold_g is None:
512
+ return False
513
+ self._event_once = bool(once)
514
+ self._event_armed = True
515
+ return True
516
+
517
+ def event_disarm(self) -> bool:
518
+ with self._lock:
519
+ self._event_armed = False
520
+ return True
521
+
522
+ def set_trigger_mode(
523
+ self,
524
+ *,
525
+ mode: str = "raw",
526
+ window: int = 3,
527
+ min_samples: Optional[int] = None,
528
+ alpha: float = 0.3,
529
+ stable_only: bool = False,
530
+ ) -> None:
531
+ """
532
+ streamイベントのトリガ値算出方法を設定する。
533
+
534
+ mode=raw, stable_only=True で値が stable のものだけ閾値チェック
535
+
536
+ Args:
537
+ mode: raw / mean / median / ema
538
+ window: 平均・中央値で使う窓サイズ
539
+ min_samples: 判定に必要な最小サンプル数(Noneならwindow)
540
+ alpha: EMA係数(0-1)
541
+ stable_only: TrueならST以外は無視
542
+ """
543
+ with self._lock:
544
+ self._trigger_mode = str(mode).lower()
545
+ w = max(1, int(window))
546
+ self._trigger_window = w
547
+ if min_samples is None:
548
+ self._trigger_min_samples = w
549
+ else:
550
+ self._trigger_min_samples = max(1, int(min_samples))
551
+ self._trigger_alpha = float(alpha)
552
+ self._trigger_buffer = []
553
+ self._trigger_ema = None
554
+ self._trigger_stable_only = bool(stable_only)
555
+
556
+ def _calc_trigger_value(self, weight_val: float) -> Optional[float]:
557
+ """
558
+ トリガ判定に使う値を計算する。
559
+ None を返した場合は判定をスキップする。
560
+ """
561
+ # 安定値だけ使う場合
562
+ if self._trigger_stable_only:
563
+ if self._status != "ST":
564
+ return None
565
+
566
+ buf = self._trigger_buffer
567
+ buf.append(weight_val)
568
+ if len(buf) > self._trigger_window:
569
+ del buf[0]
570
+
571
+ if len(buf) < self._trigger_min_samples:
572
+ return None
573
+
574
+ mode = self._trigger_mode
575
+
576
+ if mode == "raw":
577
+ return weight_val
578
+
579
+ if mode == "mean":
580
+ return sum(buf) / float(len(buf))
581
+
582
+ if mode == "median":
583
+ b = sorted(buf)
584
+ return b[len(b) // 2]
585
+
586
+ if mode == "ema":
587
+ if self._trigger_ema is None:
588
+ self._trigger_ema = weight_val
589
+ else:
590
+ a = self._trigger_alpha
591
+ self._trigger_ema = (a * weight_val) + ((1.0 - a) * self._trigger_ema)
592
+ return self._trigger_ema
593
+
594
+ # 不明なモードは raw と同じ扱い
595
+ return weight_val
596
+
597
+ def stream_start(self) -> bool:
598
+ with self._lock:
599
+ thread_obj = self._stream_thread
600
+ if thread_obj is not None:
601
+ if thread_obj.is_alive():
602
+ return True
603
+
604
+ self._stream_stop_event.clear()
605
+
606
+ self._stream_thread = threading.Thread(
607
+ target=self._stream_loop,
608
+ name="AanddReaderStream",
609
+ daemon=True,
610
+ )
611
+ self._stream_thread.start()
612
+ return True
613
+
614
+ def stream_stop(self) -> bool:
615
+ """"""
616
+ # stream_loop での thread停止をするために thread-event set する
617
+ with self._lock:
618
+ self._stream_stop_event.set()
619
+
620
+ thread_obj: Optional[threading.Thread] = None
621
+ with self._lock:
622
+ thread_obj = self._stream_thread
623
+
624
+ # 念の為 SIRの停止コマンド
625
+ try:
626
+ if self._ser is not None:
627
+ self._ser.write(b"C\r\n")
628
+ except Exception:
629
+ pass
630
+
631
+ # loop で readline 取得を止める
632
+ if thread_obj is not None:
633
+ try:
634
+ thread_obj.join(timeout=2.0)
635
+ except Exception:
636
+ pass
637
+
638
+ with self._lock:
639
+ self._stream_thread = None
640
+
641
+ return True
642
+
643
+ def _stream_loop(self) -> None:
644
+ """
645
+ 1. stream_start() でスレッド起動
646
+ 2. _stream_loop の先頭で SIR を送る
647
+ 3. readline() で連続取得
648
+ 4. stream_stop() で停止フラグ
649
+ 5. _stream_loop 側で C を送って終了
650
+
651
+ Returns:
652
+
653
+ """
654
+ # --- SIR 開始 ---
655
+ if self._ser is None:
656
+ self._logger.error("Serial port is not opened.")
657
+ return
658
+
659
+ try:
660
+ self._ser.reset_input_buffer()
661
+ self._ser.write(b"SIR\r\n") # 連続データ要求
662
+ except Exception as e:
663
+ self._logger.error(f"**Error(SIR start)**: {e}")
664
+ return
665
+
666
+ # SIR開始直後の1行目はゴミになることがあるので捨てる
667
+ try:
668
+ _ = self._ser.readline()
669
+ except Exception:
670
+ pass
671
+
672
+ while True:
673
+ if self._stream_stop_event.is_set():
674
+ try:
675
+ self._ser.write(b"C\r\n") # SIR 停止
676
+ except Exception:
677
+ pass
678
+ return
679
+
680
+ stream_val = self._stream()
681
+ if isinstance(stream_val, bool):
682
+ time.sleep(0.01)
683
+ continue
684
+
685
+ weight_val = float(stream_val)
686
+
687
+ event_bus: Optional[Any] = None
688
+ topic: Optional[str] = None
689
+ threshold_g: Optional[float] = None
690
+ armed = False
691
+ once = True
692
+
693
+ ###################################################
694
+ # トリガーとして raw 値ではなくて計算する
695
+ trigger_val = self._calc_trigger_value(weight_val)
696
+ if trigger_val is None:
697
+ continue
698
+ weight_val = trigger_val
699
+ ###################################################
700
+
701
+ with self._lock:
702
+ above = bool(self._event_above)
703
+ event_bus = self._event_bus
704
+ topic = self._event_topic
705
+ threshold_g = self._event_threshold_g
706
+ armed = bool(self._event_armed)
707
+ once = bool(self._event_once)
708
+
709
+ if event_bus is None:
710
+ continue
711
+ if topic is None:
712
+ continue
713
+ if threshold_g is None:
714
+ continue
715
+
716
+ ###################################################
717
+ # 閾値チェックを入れるか入れないか?
718
+ # gRPC-event 入れるか?入れないか?
719
+ if armed is False:
720
+ continue
721
+
722
+ # 閾値より上と下を選べる
723
+ # if weight_val >= float(threshold_g):
724
+ if (above and weight_val >= float(threshold_g)) or (
725
+ (not above) and weight_val <= float(threshold_g)
726
+ ):
727
+ ###################################################
728
+ payload: Dict[str, Any] = {
729
+ "weight_g": weight_val,
730
+ "threshold_g": float(threshold_g),
731
+ }
732
+
733
+ try:
734
+ event_bus.publish(topic, payload)
735
+ except Exception:
736
+ pass
737
+
738
+ # 閾値判定は一回だけ発火(発火後のみどす)
739
+ if once:
740
+ with self._lock:
741
+ self._event_armed = False
742
+
743
+
744
+ if __name__ == "__main__":
745
+ from x_logger import XLogger
746
+
747
+ # log_level_ = "info"
748
+ log_level_ = "debug"
749
+ logger_ = XLogger(
750
+ log_level=log_level_,
751
+ logger_name="AandDReader",
752
+ )
753
+
754
+ aandd_reader = AanddReader(
755
+ port="COM3",
756
+ # port="COM4",
757
+ logger=logger_,
758
+ log_level=log_level_,
759
+ )
760
+ # aandd_reader.cal()
761
+ logger_.info(f"SN: {aandd_reader.get_serial_number()}")
762
+ time.sleep(0.5)
763
+ logger_.info(f"{aandd_reader.meas()}")
764
+ # print(aandd_reader.status())
765
+ # print(aandd_reader.unit())