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

1#!/usr/bin/env python3 

2import logging 

3import math 

4import os 

5import socket 

6 

7import fluent.sender 

8import my_lib.footprint 

9import my_lib.pretty 

10 

11import unit_cooler.actuator.sensor 

12import unit_cooler.actuator.valve 

13import unit_cooler.actuator.work_log 

14import unit_cooler.const 

15 

16 

17def init(pin_no): 

18 unit_cooler.actuator.sensor.init(pin_no) 

19 

20 

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 } 

30 

31 

32def send_mist_condition(handle, mist_condition, control_message, dummy_mode=False): 

33 send_data = {"hostname": handle["hostname"], "state": mist_condition["valve"]["state"].value} 

34 

35 if mist_condition["flow"] is not None: 

36 send_data["flow"] = mist_condition["flow"] 

37 

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"] 

40 

41 logging.debug("Send: %s", my_lib.pretty.format(send_data)) 

42 

43 if dummy_mode: 

44 return 

45 

46 if handle["sender"].emit("rasp", send_data): 

47 logging.debug("Send OK") 

48 else: 

49 logging.error(handle["sender"].last_error) 

50 

51 

52def get_mist_condition(): 

53 valve_status = unit_cooler.actuator.valve.get_status() 

54 

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 

64 

65 get_mist_condition.last_flow = flow 

66 

67 return {"valve": valve_status, "flow": flow} 

68 

69 

70get_mist_condition.last_flow = 0 

71 

72 

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) 

79 

80 unit_cooler.actuator.valve.set_state(unit_cooler.const.VALVE_STATE.CLOSE) 

81 

82 

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 

88 

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() 

96 

97 

98def check_mist_condition(handle, mist_condition): 

99 logging.debug("Check mist condition") 

100 

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 ) 

118 

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 ) 

152 

153 

154def check(handle, mist_condition, need_logging): 

155 handle["monitor_count"] += 1 

156 

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 ) 

163 

164 check_sensing(handle, mist_condition) 

165 

166 if mist_condition["flow"] is not None: 

167 check_mist_condition(handle, mist_condition) 

168 

169 return True