juham-watermeter 0.0.4__py3-none-any.whl → 0.0.6__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.
- juham_watermeter/__init__.py +2 -2
- juham_watermeter/leakdetector.py +170 -170
- juham_watermeter/watermeter_imgdiff.py +327 -328
- juham_watermeter/watermeter_ocr.py +273 -273
- juham_watermeter/watermeter_ts.py +100 -100
- juham_watermeter/webcamera.py +172 -174
- {juham_watermeter-0.0.4.dist-info → juham_watermeter-0.0.6.dist-info}/LICENSE.rst +22 -22
- juham_watermeter-0.0.6.dist-info/METADATA +131 -0
- juham_watermeter-0.0.6.dist-info/RECORD +13 -0
- {juham_watermeter-0.0.4.dist-info → juham_watermeter-0.0.6.dist-info}/WHEEL +1 -1
- juham_watermeter-0.0.4.dist-info/METADATA +0 -27
- juham_watermeter-0.0.4.dist-info/RECORD +0 -13
- {juham_watermeter-0.0.4.dist-info → juham_watermeter-0.0.6.dist-info}/entry_points.txt +0 -0
- {juham_watermeter-0.0.4.dist-info → juham_watermeter-0.0.6.dist-info}/top_level.txt +0 -0
@@ -1,328 +1,327 @@
|
|
1
|
-
"""Web camera based optical watermeter based on differences between subsequent frames.
|
2
|
-
|
3
|
-
"""
|
4
|
-
|
5
|
-
import json
|
6
|
-
import os
|
7
|
-
import subprocess
|
8
|
-
import tempfile
|
9
|
-
import time
|
10
|
-
import cv2
|
11
|
-
import numpy as np
|
12
|
-
from typing import Any, Optional, Union, cast
|
13
|
-
from typing_extensions import override
|
14
|
-
from masterpiece.mqtt import Mqtt
|
15
|
-
|
16
|
-
from juham_core.timeutils import timestamp
|
17
|
-
from .webcamera import WebCameraThread, WebCamera
|
18
|
-
|
19
|
-
|
20
|
-
class WaterMeterThreadImgDiff(WebCameraThread):
|
21
|
-
"""Asynchronous thread for capturing and processing images of web camera.
|
22
|
-
Uploads three images to sftp server for inspection: the original watermeter
|
23
|
-
image, the image holding differences between the last captured watermeter image,
|
24
|
-
and processed image with noise eliminated, and differences scaled up for maximum
|
25
|
-
image contrast: white pixels represent areas. The more white pixels, the bigger the
|
26
|
-
water consumption.
|
27
|
-
"""
|
28
|
-
|
29
|
-
# class attributes
|
30
|
-
_watermeter_topic: str = ""
|
31
|
-
_expected_image_size: int = 640 * 480
|
32
|
-
_save_images: bool = True
|
33
|
-
_calibration_factor: float = 1000.0 # img diff to liters
|
34
|
-
_max_change_area: float = 0.1 # difference can't be greater than this
|
35
|
-
ftp_site: str = ""
|
36
|
-
ftp_user: str = ""
|
37
|
-
ftp_pw: str = ""
|
38
|
-
|
39
|
-
def __init__(self, client: Optional[Mqtt] = None):
|
40
|
-
"""Construct with the given mqtt client.
|
41
|
-
|
42
|
-
Args:
|
43
|
-
client (object, optional): MQTT client. Defaults to None.
|
44
|
-
"""
|
45
|
-
super().__init__(client)
|
46
|
-
self.sensor_name = "watermeter_imgdiff"
|
47
|
-
self.mqtt_client: Optional[Mqtt] = client
|
48
|
-
self.total_liter: float = 0.0
|
49
|
-
self.active_liter_lpm: float = 0.0
|
50
|
-
self._prev_image: np.ndarray = np.zeros((1, 1, 3), dtype=np.uint8)
|
51
|
-
self._prev_image_initialized = False
|
52
|
-
self._prev_time: float = 0.0
|
53
|
-
|
54
|
-
# total cumulative diff between two consequtive images
|
55
|
-
self._wm_start_seconds: float = 0.0
|
56
|
-
|
57
|
-
# temp filenames for saving the original and processed images, for debugging purposes
|
58
|
-
self._temp_filename1: str = ""
|
59
|
-
self._temp_filename2: str = ""
|
60
|
-
|
61
|
-
def init_watermeter_imgdiff(
|
62
|
-
self,
|
63
|
-
interval: float,
|
64
|
-
location: str,
|
65
|
-
camera: int,
|
66
|
-
topic: str,
|
67
|
-
save_images: bool,
|
68
|
-
) -> None:
|
69
|
-
"""Initialize the data acquisition thread
|
70
|
-
|
71
|
-
Args:
|
72
|
-
topic (str): mqtt topic to publish the acquired system info
|
73
|
-
interval (float): update interval in seconds
|
74
|
-
location (str): geographic location
|
75
|
-
camera(int) : ordinal specifying the camera to be used (0, 1)
|
76
|
-
save_images (bool) : true to enable saving of captured images, for debugging
|
77
|
-
"""
|
78
|
-
super().init(interval, location, camera)
|
79
|
-
self._watermeter_topic = topic
|
80
|
-
self._save_images = save_images
|
81
|
-
# temp filenames for saving the original and processed images, for debugging purposes
|
82
|
-
tmpdir: str = tempfile.mkdtemp()
|
83
|
-
self._temp_filename1 = os.path.join(tmpdir, f"{self.sensor_name}_wm_1.png")
|
84
|
-
self._temp_filename2 = os.path.join(tmpdir, f"{self.sensor_name}_wm_2.png")
|
85
|
-
|
86
|
-
def upload_image(self, file: str, img: np.ndarray) -> None:
|
87
|
-
"""Save the image to the given file and upload the file to the FTP server.
|
88
|
-
|
89
|
-
Args:
|
90
|
-
file (str): The filename where the image will be saved.
|
91
|
-
img (np.ndarray): The image to be saved and uploaded.
|
92
|
-
"""
|
93
|
-
# Save the image to the specified file
|
94
|
-
cv2.imwrite(file, img)
|
95
|
-
|
96
|
-
try:
|
97
|
-
# Upload the file to the FTP server
|
98
|
-
self.upload_file(file)
|
99
|
-
|
100
|
-
# If the upload is successful, remove the file
|
101
|
-
os.remove(file)
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
"
|
162
|
-
|
163
|
-
"
|
164
|
-
"
|
165
|
-
"
|
166
|
-
"
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
self.upload_image(self.
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
captured_image
|
192
|
-
|
193
|
-
|
194
|
-
grayscale_image
|
195
|
-
|
196
|
-
|
197
|
-
processed_image
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
#
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
"
|
227
|
-
"
|
228
|
-
"
|
229
|
-
"
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
msg =
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
"
|
253
|
-
"
|
254
|
-
"
|
255
|
-
"
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
self.
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
"
|
285
|
-
|
286
|
-
|
287
|
-
)
|
288
|
-
|
289
|
-
|
290
|
-
worker.total_liter
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
self.
|
306
|
-
self.
|
307
|
-
self.
|
308
|
-
self.
|
309
|
-
|
310
|
-
)
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
data
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
setattr(self, attr, watermeter_data.get(attr, None))
|
1
|
+
"""Web camera based optical watermeter based on differences between subsequent frames.
|
2
|
+
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
import os
|
7
|
+
import subprocess
|
8
|
+
import tempfile
|
9
|
+
import time
|
10
|
+
import cv2
|
11
|
+
import numpy as np
|
12
|
+
from typing import Any, Optional, Union, cast
|
13
|
+
from typing_extensions import override
|
14
|
+
from masterpiece.mqtt import Mqtt
|
15
|
+
|
16
|
+
from juham_core.timeutils import timestamp
|
17
|
+
from .webcamera import WebCameraThread, WebCamera
|
18
|
+
|
19
|
+
|
20
|
+
class WaterMeterThreadImgDiff(WebCameraThread):
|
21
|
+
"""Asynchronous thread for capturing and processing images of web camera.
|
22
|
+
Uploads three images to sftp server for inspection: the original watermeter
|
23
|
+
image, the image holding differences between the last captured watermeter image,
|
24
|
+
and processed image with noise eliminated, and differences scaled up for maximum
|
25
|
+
image contrast: white pixels represent areas. The more white pixels, the bigger the
|
26
|
+
water consumption.
|
27
|
+
"""
|
28
|
+
|
29
|
+
# class attributes
|
30
|
+
_watermeter_topic: str = ""
|
31
|
+
_expected_image_size: int = 640 * 480
|
32
|
+
_save_images: bool = True
|
33
|
+
_calibration_factor: float = 1000.0 # img diff to liters
|
34
|
+
_max_change_area: float = 0.1 # difference can't be greater than this
|
35
|
+
ftp_site: str = ""
|
36
|
+
ftp_user: str = ""
|
37
|
+
ftp_pw: str = ""
|
38
|
+
|
39
|
+
def __init__(self, client: Optional[Mqtt] = None):
|
40
|
+
"""Construct with the given mqtt client.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
client (object, optional): MQTT client. Defaults to None.
|
44
|
+
"""
|
45
|
+
super().__init__(client)
|
46
|
+
self.sensor_name = "watermeter_imgdiff"
|
47
|
+
self.mqtt_client: Optional[Mqtt] = client
|
48
|
+
self.total_liter: float = 0.0
|
49
|
+
self.active_liter_lpm: float = 0.0
|
50
|
+
self._prev_image: np.ndarray = np.zeros((1, 1, 3), dtype=np.uint8)
|
51
|
+
self._prev_image_initialized = False
|
52
|
+
self._prev_time: float = 0.0
|
53
|
+
|
54
|
+
# total cumulative diff between two consequtive images
|
55
|
+
self._wm_start_seconds: float = 0.0
|
56
|
+
|
57
|
+
# temp filenames for saving the original and processed images, for debugging purposes
|
58
|
+
self._temp_filename1: str = ""
|
59
|
+
self._temp_filename2: str = ""
|
60
|
+
|
61
|
+
def init_watermeter_imgdiff(
|
62
|
+
self,
|
63
|
+
interval: float,
|
64
|
+
location: str,
|
65
|
+
camera: int,
|
66
|
+
topic: str,
|
67
|
+
save_images: bool,
|
68
|
+
) -> None:
|
69
|
+
"""Initialize the data acquisition thread
|
70
|
+
|
71
|
+
Args:
|
72
|
+
topic (str): mqtt topic to publish the acquired system info
|
73
|
+
interval (float): update interval in seconds
|
74
|
+
location (str): geographic location
|
75
|
+
camera(int) : ordinal specifying the camera to be used (0, 1)
|
76
|
+
save_images (bool) : true to enable saving of captured images, for debugging
|
77
|
+
"""
|
78
|
+
super().init(interval, location, camera)
|
79
|
+
self._watermeter_topic = topic
|
80
|
+
self._save_images = save_images
|
81
|
+
# temp filenames for saving the original and processed images, for debugging purposes
|
82
|
+
tmpdir: str = tempfile.mkdtemp()
|
83
|
+
self._temp_filename1 = os.path.join(tmpdir, f"{self.sensor_name}_wm_1.png")
|
84
|
+
self._temp_filename2 = os.path.join(tmpdir, f"{self.sensor_name}_wm_2.png")
|
85
|
+
|
86
|
+
def upload_image(self, file: str, img: np.ndarray) -> None:
|
87
|
+
"""Save the image to the given file and upload the file to the FTP server.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
file (str): The filename where the image will be saved.
|
91
|
+
img (np.ndarray): The image to be saved and uploaded.
|
92
|
+
"""
|
93
|
+
# Save the image to the specified file
|
94
|
+
cv2.imwrite(file, img)
|
95
|
+
|
96
|
+
try:
|
97
|
+
# Upload the file to the FTP server
|
98
|
+
self.upload_file(file)
|
99
|
+
|
100
|
+
# If the upload is successful, remove the file
|
101
|
+
os.remove(file)
|
102
|
+
|
103
|
+
except Exception as e:
|
104
|
+
# Handle any errors that occurred during upload
|
105
|
+
self.error(f"Error during file upload: {e}")
|
106
|
+
|
107
|
+
def compare_images(
|
108
|
+
self,
|
109
|
+
np_prev: np.ndarray,
|
110
|
+
np_current: np.ndarray,
|
111
|
+
threshold: int = 20,
|
112
|
+
) -> float:
|
113
|
+
"""
|
114
|
+
Compares two images and returns a float value representing the level of differences.
|
115
|
+
|
116
|
+
Parameters:
|
117
|
+
np_prev (np.ndarray): The previous image.
|
118
|
+
np_current (np.ndarray): The current image.
|
119
|
+
threshold (int): Threshold value to filter noise in the difference image.
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
float: A value between 0.0 and 1.0 indicating the level of difference.
|
123
|
+
0.0 means identical, 1.0 means maximally different.
|
124
|
+
"""
|
125
|
+
# Ensure both images are the same size and type
|
126
|
+
if np_prev.shape != np_current.shape:
|
127
|
+
self.error("Images have different shapes or dimensions.")
|
128
|
+
return -1.0 # Error value for different shapes
|
129
|
+
|
130
|
+
# Step 1: Calculate the absolute difference between the two images
|
131
|
+
diff_image = cv2.absdiff(np_prev, np_current)
|
132
|
+
|
133
|
+
# Step 2: Filter small differences (noise) using a binary threshold
|
134
|
+
_, binary_diff = cv2.threshold(diff_image, threshold, 255, cv2.THRESH_BINARY)
|
135
|
+
|
136
|
+
# Step 3: Calculate the proportion of changed pixels
|
137
|
+
change_area: float = np.count_nonzero(binary_diff) / binary_diff.size
|
138
|
+
|
139
|
+
if change_area > 0.0:
|
140
|
+
print(f"Waterflow detected {change_area}, uploading")
|
141
|
+
self.upload_images(np_current, binary_diff)
|
142
|
+
|
143
|
+
# Return a value between 0.0 (identical) and 1.0 (maximally different)
|
144
|
+
return min(max(change_area, 0.0), 1.0)
|
145
|
+
|
146
|
+
@override
|
147
|
+
def update_interval(self) -> float:
|
148
|
+
return self._interval
|
149
|
+
|
150
|
+
def upload_file(self, filename: str) -> None:
|
151
|
+
"""Upload the given filename to the ftp server, if the server is configured.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
filename (str): _description_
|
155
|
+
"""
|
156
|
+
if len(self.ftp_site) > 0 and len(self.ftp_user) > 0 and len(self.ftp_pw) > 0:
|
157
|
+
|
158
|
+
# Build the curl command for uploading the file
|
159
|
+
curl_command = [
|
160
|
+
"curl",
|
161
|
+
f"-u{self.ftp_user}:{self.ftp_pw}",
|
162
|
+
"--retry",
|
163
|
+
"3",
|
164
|
+
"--retry-delay",
|
165
|
+
"5",
|
166
|
+
"-T",
|
167
|
+
filename,
|
168
|
+
self.ftp_site,
|
169
|
+
]
|
170
|
+
|
171
|
+
# Execute the curl command
|
172
|
+
try:
|
173
|
+
subprocess.run(curl_command, check=True)
|
174
|
+
except subprocess.CalledProcessError as e:
|
175
|
+
self.error(f"Error during image upload: {e}")
|
176
|
+
|
177
|
+
def upload_images(self, np_watermeter: np.ndarray, np_diff: np.ndarray) -> None:
|
178
|
+
"""Upload captured grayscale watermeter image, and the diff image to ftp site
|
179
|
+
|
180
|
+
Parameters:
|
181
|
+
np_watermeter (np.ndarray): Watermeter image in grayscale
|
182
|
+
np_diff (np.ndarray): The diff image reflecting consumed water
|
183
|
+
"""
|
184
|
+
self.upload_image(self._temp_filename1, np_watermeter)
|
185
|
+
self.upload_image(self._temp_filename2, np_diff)
|
186
|
+
|
187
|
+
@override
|
188
|
+
def update(self) -> bool:
|
189
|
+
change_area: float = -1
|
190
|
+
captured_image = self.capture_image()
|
191
|
+
if captured_image.size < self._expected_image_size:
|
192
|
+
return False
|
193
|
+
grayscale_image = self.process_image(captured_image)
|
194
|
+
if grayscale_image.size < self._expected_image_size:
|
195
|
+
return False
|
196
|
+
processed_image = self.enhance_contrast(grayscale_image)
|
197
|
+
if processed_image.size < self._expected_image_size:
|
198
|
+
return False
|
199
|
+
|
200
|
+
if self._prev_image.size == self._expected_image_size:
|
201
|
+
change_area = self.compare_images(self._prev_image, processed_image)
|
202
|
+
else:
|
203
|
+
self._prev_image = processed_image
|
204
|
+
return True
|
205
|
+
|
206
|
+
lpm: float = 0.0
|
207
|
+
|
208
|
+
if change_area > 0.0:
|
209
|
+
# to capture even the smallest leaks, update the previous image
|
210
|
+
# only when difference is found
|
211
|
+
self._prev_image = processed_image
|
212
|
+
wm_elapsed_seconds: float = time.time() - self._wm_start_seconds
|
213
|
+
self._wm_start_seconds = time.time()
|
214
|
+
|
215
|
+
# image change_area factor to consumed water in liters
|
216
|
+
liters = change_area * self._calibration_factor
|
217
|
+
|
218
|
+
# update cumulative water consumption
|
219
|
+
self.total_liter += liters / 1000.0
|
220
|
+
|
221
|
+
# scale liters to flow (liters per minute)
|
222
|
+
lpm = liters / (wm_elapsed_seconds / 60.0)
|
223
|
+
|
224
|
+
watermeter: dict[str, Union[float, str]] = {
|
225
|
+
"location": self._location,
|
226
|
+
"sensor": self.sensor_name,
|
227
|
+
"total_liter": self.total_liter,
|
228
|
+
"active_lpm": lpm,
|
229
|
+
"ts": timestamp(),
|
230
|
+
}
|
231
|
+
|
232
|
+
msg = json.dumps(watermeter)
|
233
|
+
self.publish(self._watermeter_topic, msg, qos=0, retain=False)
|
234
|
+
return True
|
235
|
+
|
236
|
+
|
237
|
+
class WaterMeterImgDiff(WebCamera):
|
238
|
+
"""WebCamera based optical watermeter. Needs a low-cost web camera and a spot
|
239
|
+
light to illuminate the watermeter. Captures images with specified interval (the default
|
240
|
+
is 1 minute) and computes a factor that represents the level of difference, 0.0 being no
|
241
|
+
differences and 1.0 corresponding to the maximum difference (all pixels different with
|
242
|
+
maximum contrast e.g. 0 vs 255).
|
243
|
+
The more two consequtive images differ, the higher the water consumption. Cannot give any absolute water consumption
|
244
|
+
measurements as liters, but suits well for leak detection purposes - the greater
|
245
|
+
the difference the creater the water consumption.
|
246
|
+
|
247
|
+
"""
|
248
|
+
|
249
|
+
_WATERMETER: str = "watermeter_imgdiff"
|
250
|
+
_WATERMETER_ATTRS: list[str] = [
|
251
|
+
"topic",
|
252
|
+
"update_interval",
|
253
|
+
"location",
|
254
|
+
"camera",
|
255
|
+
"save_images",
|
256
|
+
]
|
257
|
+
|
258
|
+
_workerThreadId: str = WaterMeterThreadImgDiff.get_class_id()
|
259
|
+
update_interval: float = 30
|
260
|
+
topic = "watermeter"
|
261
|
+
location = "home"
|
262
|
+
camera: int = 0
|
263
|
+
save_images: bool = True
|
264
|
+
|
265
|
+
def __init__(self, name="watermeter_imgdiff") -> None:
|
266
|
+
"""Constructs system status automation object for acquiring and publishing
|
267
|
+
system info e.g. available memory and CPU loads.
|
268
|
+
|
269
|
+
Args:
|
270
|
+
name (str, optional): name of the object.
|
271
|
+
"""
|
272
|
+
super().__init__(name)
|
273
|
+
self.worker: Optional[WaterMeterThreadImgDiff] = None
|
274
|
+
self.watermeter_topic: str = self.make_topic_name(self.topic)
|
275
|
+
|
276
|
+
@override
|
277
|
+
def initialize(self) -> None:
|
278
|
+
# let the super class to initialize database first so that we can read it
|
279
|
+
super().initialize()
|
280
|
+
|
281
|
+
# read the latest known value from
|
282
|
+
last_value: dict[str, float] = self.read_last_value(
|
283
|
+
"watermeter",
|
284
|
+
{"sensor": self.name, "location": self.location},
|
285
|
+
["total_liter"],
|
286
|
+
)
|
287
|
+
worker: WaterMeterThreadImgDiff = cast(WaterMeterThreadImgDiff, self.worker)
|
288
|
+
if "total_liter" in last_value:
|
289
|
+
worker.total_liter = last_value["total_liter"]
|
290
|
+
self.info(f"Total liters {worker.total_liter} read from the database")
|
291
|
+
else:
|
292
|
+
self.warning("no previous database value for total_liter found")
|
293
|
+
|
294
|
+
@override
|
295
|
+
def run(self) -> None:
|
296
|
+
# create, initialize and start the asynchronous thread for acquiring forecast
|
297
|
+
|
298
|
+
self.worker = cast(
|
299
|
+
WaterMeterThreadImgDiff, self.instantiate(WaterMeterImgDiff._workerThreadId)
|
300
|
+
)
|
301
|
+
self.worker.sensor_name = self.name
|
302
|
+
|
303
|
+
self.worker.init_watermeter_imgdiff(
|
304
|
+
self.update_interval,
|
305
|
+
self.location,
|
306
|
+
self.camera,
|
307
|
+
self.watermeter_topic,
|
308
|
+
self.save_images,
|
309
|
+
)
|
310
|
+
super().run()
|
311
|
+
|
312
|
+
@override
|
313
|
+
def to_dict(self) -> dict[str, Any]:
|
314
|
+
data = super().to_dict() # Call parent class method
|
315
|
+
watermeter_data = {}
|
316
|
+
for attr in self._WATERMETER_ATTRS:
|
317
|
+
watermeter_data[attr] = getattr(self, attr)
|
318
|
+
data[self._WATERMETER] = watermeter_data
|
319
|
+
return data
|
320
|
+
|
321
|
+
@override
|
322
|
+
def from_dict(self, data: dict[str, Any]) -> None:
|
323
|
+
super().from_dict(data) # Call parent class method
|
324
|
+
if self._WATERMETER in data:
|
325
|
+
watermeter_data = data[self._WATERMETER]
|
326
|
+
for attr in self._WATERMETER_ATTRS:
|
327
|
+
setattr(self, attr, watermeter_data.get(attr, None))
|