Coverage for src/unit_cooler/actuator/monitor.py: 98%
75 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-28 11:52 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-28 11:52 +0000
1#!/usr/bin/env python3
2import logging
3import math
4import os
5import socket
7import fluent.sender
8import my_lib.footprint
9import my_lib.pretty
11import unit_cooler.actuator.sensor
12import unit_cooler.actuator.valve
13import unit_cooler.actuator.work_log
14import unit_cooler.const
17def init(pin_no):
18 unit_cooler.actuator.sensor.init(pin_no)
21def gen_handle(config, interval_sec):
22 return {
23 "config": config,
24 "hostname": os.environ.get("NODE_HOSTNAME", socket.gethostname()),
25 "sender": fluent.sender.FluentSender("sensor", host=config["actuator"]["monitor"]["fluent"]["host"]),
26 "log_period": max(math.ceil(60 / interval_sec), 1), # この回数毎にログを出力する
27 "flow_unknown": 0, # 流量不明が続いた回数
28 "monitor_count": 0, # 観測した回数
29 }
32def send_mist_condition(handle, mist_condition, control_message, dummy_mode=False):
33 send_data = {"hostname": handle["hostname"], "state": mist_condition["valve"]["state"].value}
35 if mist_condition["flow"] is not None:
36 send_data["flow"] = mist_condition["flow"]
38 if control_message is not None: 38 ↛ 41line 38 didn't jump to line 41 because the condition on line 38 was always true
39 send_data["cooling_mode"] = control_message["mode_index"]
41 logging.debug("Send: %s", my_lib.pretty.format(send_data))
43 if dummy_mode:
44 return
46 if handle["sender"].emit("rasp", send_data):
47 logging.debug("Send OK")
48 else:
49 logging.error(handle["sender"].last_error)
52def get_mist_condition():
53 valve_status = unit_cooler.actuator.valve.get_status()
55 if valve_status["state"] == unit_cooler.const.VALVE_STATE.OPEN:
56 flow = unit_cooler.actuator.sensor.get_flow()
57 # NOTE: get_flow() の内部で流量センサーの電源を入れている場合は計測に時間がかかるので、
58 # その間に電磁弁の状態が変化している可能性があるので、再度状態を取得する。
59 valve_status = unit_cooler.actuator.valve.get_status()
60 else:
61 # NOTE: 電磁弁が閉じている場合、流量が 0 になるまでは計測を継続する。
62 # (電磁弁の電源を切るため、流量が 0 になった場合は、電磁弁が開かれるまで計測は再開しない)
63 flow = unit_cooler.actuator.sensor.get_flow() if get_mist_condition.last_flow != 0 else 0
65 get_mist_condition.last_flow = flow
67 return {"valve": valve_status, "flow": flow}
70get_mist_condition.last_flow = 0
73def hazard_notify(config, message):
74 hazard_file = config["actuator"]["control"]["hazard"]["file"]
75 logging.error(my_lib.footprint.exists(hazard_file))
76 if not my_lib.footprint.exists(hazard_file):
77 unit_cooler.actuator.work_log.add(message, unit_cooler.const.LOG_LEVEL.ERROR)
78 my_lib.footprint.update(hazard_file)
80 unit_cooler.actuator.valve.set_state(unit_cooler.const.VALVE_STATE.CLOSE)
83def check_sensing(handle, mist_condition):
84 if mist_condition["flow"] is None:
85 handle["flow_unknown"] += 1
86 else:
87 handle["flow_unknown"] = 0
89 if handle["flow_unknown"] > handle["config"]["actuator"]["monitor"]["sense"]["giveup"]:
90 unit_cooler.actuator.work_log.add("流量計が使えません。", unit_cooler.const.LOG_LEVEL.ERROR)
91 elif handle["flow_unknown"] > (handle["config"]["actuator"]["monitor"]["sense"]["giveup"] / 2):
92 unit_cooler.actuator.work_log.add(
93 "流量計が応答しないので一旦、リセットします。", unit_cooler.const.LOG_LEVEL.WARN
94 )
95 unit_cooler.actuator.sensor.stop()
98def check_mist_condition(handle, mist_condition):
99 logging.debug("Check mist condition")
101 if mist_condition["valve"]["state"] == unit_cooler.const.VALVE_STATE.OPEN:
102 for i in range(len(handle["config"]["actuator"]["monitor"]["flow"]["on"]["max"])):
103 if (
104 mist_condition["flow"] > handle["config"]["actuator"]["monitor"]["flow"]["on"]["max"][i]
105 ) and (mist_condition["valve"]["duration"] > 5 * (i + 1)):
106 hazard_notify(
107 handle["config"],
108 (
109 "水漏れしています。"
110 "(バルブを開いてから{duration:.1f}秒経過しても流量が "
111 "{flow:.1f} L/min [> {threshold:.1f} L/min])"
112 ).format(
113 duration=mist_condition["valve"]["duration"],
114 flow=mist_condition["flow"],
115 threshold=handle["config"]["actuator"]["monitor"]["flow"]["on"]["max"][i],
116 ),
117 )
119 if (mist_condition["flow"] < handle["config"]["actuator"]["monitor"]["flow"]["on"]["min"]) and (
120 mist_condition["valve"]["duration"] > 5
121 ):
122 # NOTE: ハザード扱いにはしない
123 unit_cooler.actuator.work_log.add(
124 (
125 "元栓が閉じています。"
126 "(バルブを開いてから{duration:.1f}秒経過しても流量が {flow:.1f} L/min)"
127 ).format(duration=mist_condition["valve"]["duration"], flow=mist_condition["flow"]),
128 unit_cooler.const.LOG_LEVEL.ERROR,
129 )
130 else:
131 logging.debug("Valve is close for %.1f sec", mist_condition["valve"]["duration"])
132 if (
133 mist_condition["valve"]["duration"]
134 >= handle["config"]["actuator"]["monitor"]["flow"]["power_off_sec"]
135 ) and (mist_condition["flow"] == 0):
136 # バルブが閉じてから長い時間が経っていて流量も 0 の場合、センサーを停止する
137 if unit_cooler.actuator.sensor.get_power_state(): 137 ↛ exitline 137 didn't return from function 'check_mist_condition' because the condition on line 137 was always true
138 unit_cooler.actuator.work_log.add(
139 "長い間バルブが閉じられていますので、流量計の電源を OFF します。"
140 )
141 unit_cooler.actuator.sensor.stop()
142 elif (mist_condition["valve"]["duration"] > 120) and (
143 mist_condition["flow"] > handle["config"]["actuator"]["monitor"]["flow"]["off"]["max"]
144 ):
145 hazard_notify(
146 handle["config"],
147 "電磁弁が壊れていますので制御を停止します。"
148 + "(バルブを閉じてから{duration:.1f}秒経過しても流量が {flow:.1f} L/min)".format(
149 duration=mist_condition["valve"]["duration"], flow=mist_condition["flow"]
150 ),
151 )
154def check(handle, mist_condition, need_logging):
155 handle["monitor_count"] += 1
157 if need_logging:
158 logging.info(
159 "Valve Condition: %s (flow = %s L/min)",
160 mist_condition["valve"]["state"].name,
161 "?" if mist_condition["flow"] is None else "{flow:.2f}".format(flow=mist_condition["flow"]),
162 )
164 check_sensing(handle, mist_condition)
166 if mist_condition["flow"] is not None:
167 check_mist_condition(handle, mist_condition)
169 return True