pumaguard 21__py3-none-any.whl → 21.post4__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.
pumaguard/sound.py CHANGED
@@ -6,7 +6,9 @@ import logging
6
6
  import subprocess
7
7
  import sys
8
8
  import threading
9
- from typing import Optional
9
+ from typing import (
10
+ Optional,
11
+ )
10
12
 
11
13
  logger = logging.getLogger(__name__)
12
14
 
@@ -5,6 +5,10 @@ from __future__ import (
5
5
  )
6
6
 
7
7
  import logging
8
+ from datetime import (
9
+ datetime,
10
+ timezone,
11
+ )
8
12
  from typing import (
9
13
  TYPE_CHECKING,
10
14
  )
@@ -80,6 +84,32 @@ def register_dhcp_routes(app: "Flask", webui: "WebUI") -> None:
80
84
  "last_seen": timestamp,
81
85
  "status": "connected",
82
86
  }
87
+
88
+ # Update settings with camera list
89
+ # Convert cameras dict to list for settings persistence
90
+ camera_list = []
91
+ for _, cam_info in webui.cameras.items():
92
+ camera_list.append(
93
+ {
94
+ "hostname": cam_info["hostname"],
95
+ "ip_address": cam_info["ip_address"],
96
+ "mac_address": cam_info["mac_address"],
97
+ "last_seen": cam_info["last_seen"],
98
+ "status": cam_info["status"],
99
+ }
100
+ )
101
+
102
+ webui.presets.cameras = camera_list
103
+
104
+ # Persist to settings file
105
+ try:
106
+ webui.presets.save()
107
+ logger.info("Camera list saved to settings")
108
+ except Exception as e: # pylint: disable=broad-except
109
+ logger.error(
110
+ "Failed to save camera list to settings: %s", str(e)
111
+ )
112
+
83
113
  elif action == "del":
84
114
  # Camera disconnected
85
115
  logger.info("Camera '%s' disconnected", hostname)
@@ -88,6 +118,31 @@ def register_dhcp_routes(app: "Flask", webui: "WebUI") -> None:
88
118
  webui.cameras[mac_address]["status"] = "disconnected"
89
119
  webui.cameras[mac_address]["last_seen"] = timestamp
90
120
 
121
+ # Update settings with updated camera list
122
+ camera_list = []
123
+ for _, cam_info in webui.cameras.items():
124
+ camera_list.append(
125
+ {
126
+ "hostname": cam_info["hostname"],
127
+ "ip_address": cam_info["ip_address"],
128
+ "mac_address": cam_info["mac_address"],
129
+ "last_seen": cam_info["last_seen"],
130
+ "status": cam_info["status"],
131
+ }
132
+ )
133
+
134
+ webui.presets.cameras = camera_list
135
+
136
+ # Persist to settings file
137
+ try:
138
+ webui.presets.save()
139
+ logger.info("Camera list updated in settings")
140
+ except Exception as e: # pylint: disable=broad-except
141
+ logger.error(
142
+ "Failed to save camera list to settings: %s",
143
+ str(e),
144
+ )
145
+
91
146
  return (
92
147
  jsonify(
93
148
  {
@@ -156,6 +211,104 @@ def register_dhcp_routes(app: "Flask", webui: "WebUI") -> None:
156
211
  404,
157
212
  )
158
213
 
214
+ @app.route("/api/dhcp/cameras", methods=["POST"])
215
+ def add_camera():
216
+ """
217
+ Manually add a camera (for testing purposes).
218
+
219
+ Expected JSON payload:
220
+ {
221
+ "hostname": "camera-name",
222
+ "ip_address": "192.168.52.100",
223
+ "mac_address": "aa:bb:cc:dd:ee:ff",
224
+ "status": "connected" // optional, defaults to "connected"
225
+ }
226
+ """
227
+ try:
228
+ data = request.get_json()
229
+
230
+ if not data:
231
+ return jsonify({"error": "No JSON data provided"}), 400
232
+
233
+ hostname = data.get("hostname")
234
+ ip_address = data.get("ip_address")
235
+ mac_address = data.get("mac_address")
236
+ status = data.get("status", "connected")
237
+
238
+ # Validate required fields
239
+ if not hostname or not ip_address or not mac_address:
240
+ return (
241
+ jsonify(
242
+ {
243
+ "error": "Missing required fields: hostname, "
244
+ "ip_address, mac_address"
245
+ }
246
+ ),
247
+ 400,
248
+ )
249
+
250
+ # Generate timestamp
251
+ timestamp = datetime.now(timezone.utc).strftime(
252
+ "%Y-%m-%dT%H:%M:%SZ"
253
+ )
254
+
255
+ # Add camera to webui.cameras
256
+ webui.cameras[mac_address] = {
257
+ "hostname": hostname,
258
+ "ip_address": ip_address,
259
+ "mac_address": mac_address,
260
+ "last_seen": timestamp,
261
+ "status": status,
262
+ }
263
+
264
+ # Update settings with camera list
265
+ camera_list = []
266
+ for _, cam_info in webui.cameras.items():
267
+ camera_list.append(
268
+ {
269
+ "hostname": cam_info["hostname"],
270
+ "ip_address": cam_info["ip_address"],
271
+ "mac_address": cam_info["mac_address"],
272
+ "last_seen": cam_info["last_seen"],
273
+ "status": cam_info["status"],
274
+ }
275
+ )
276
+
277
+ webui.presets.cameras = camera_list
278
+
279
+ # Persist to settings file
280
+ try:
281
+ webui.presets.save()
282
+ logger.info(
283
+ "Manually added camera '%s' at %s", hostname, ip_address
284
+ )
285
+ except Exception as e: # pylint: disable=broad-except
286
+ logger.error(
287
+ "Failed to save camera list to settings: %s", str(e)
288
+ )
289
+
290
+ return (
291
+ jsonify(
292
+ {
293
+ "status": "success",
294
+ "message": "Camera added successfully",
295
+ "camera": webui.cameras[mac_address],
296
+ }
297
+ ),
298
+ 201,
299
+ )
300
+
301
+ except Exception as e: # pylint: disable=broad-except
302
+ logger.error("Error adding camera: %s", str(e))
303
+ return (
304
+ jsonify(
305
+ {
306
+ "error": "Failed to add camera",
307
+ }
308
+ ),
309
+ 500,
310
+ )
311
+
159
312
  @app.route("/api/dhcp/cameras", methods=["DELETE"])
160
313
  def clear_cameras():
161
314
  """
@@ -167,6 +320,14 @@ def register_dhcp_routes(app: "Flask", webui: "WebUI") -> None:
167
320
  webui.cameras.clear()
168
321
  logger.info("Cleared %d camera records", count)
169
322
 
323
+ # Update settings
324
+ webui.presets.cameras = []
325
+ try:
326
+ webui.presets.save()
327
+ logger.info("Camera list cleared from settings")
328
+ except Exception as e: # pylint: disable=broad-except
329
+ logger.error("Failed to save camera list to settings: %s", str(e))
330
+
170
331
  return (
171
332
  jsonify(
172
333
  {
@@ -48,7 +48,21 @@ def register_settings_routes(app: "Flask", webui: "WebUI") -> None:
48
48
 
49
49
  @app.route("/api/settings", methods=["GET"])
50
50
  def get_settings():
51
- return jsonify(dict(webui.presets))
51
+ settings_dict = dict(webui.presets)
52
+ # Add cameras from webui.cameras (runtime state)
53
+ camera_list = []
54
+ for _, cam_info in webui.cameras.items():
55
+ camera_list.append(
56
+ {
57
+ "hostname": cam_info["hostname"],
58
+ "ip_address": cam_info["ip_address"],
59
+ "mac_address": cam_info["mac_address"],
60
+ "last_seen": cam_info["last_seen"],
61
+ "status": cam_info["status"],
62
+ }
63
+ )
64
+ settings_dict["cameras"] = camera_list
65
+ return jsonify(settings_dict)
52
66
 
53
67
  @app.route("/api/settings", methods=["PUT"])
54
68
  def update_settings():
pumaguard/web_ui.py CHANGED
@@ -161,6 +161,18 @@ class WebUI:
161
161
  # Format: {mac_address: CameraInfo}
162
162
  self.cameras: dict[str, CameraInfo] = {}
163
163
 
164
+ # Load cameras from persisted settings
165
+ for camera in presets.cameras:
166
+ mac = camera.get("mac_address")
167
+ if mac:
168
+ self.cameras[mac] = CameraInfo(
169
+ hostname=camera.get("hostname", ""),
170
+ ip_address=camera.get("ip_address", ""),
171
+ mac_address=mac,
172
+ last_seen=camera.get("last_seen", ""),
173
+ status=camera.get("status", "disconnected"),
174
+ )
175
+
164
176
  # mDNS/Zeroconf support
165
177
  self.zeroconf: Zeroconf | None = None
166
178
  self.service_info: ServiceInfo | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pumaguard
3
- Version: 21
3
+ Version: 21.post4
4
4
  Author-email: Nicolas Bock <nicolasbock@gmail.com>
5
5
  Project-URL: Homepage, http://pumaguard.rtfd.io/
6
6
  Project-URL: Repository, https://github.com/PEEC-Nature-Youth-Group/pumaguard
@@ -5,13 +5,13 @@ pumaguard/main.py,sha256=1Wazv1wjwb46yNlqgWt88HQwKSxGmY24X5OsUv8gYyE,7029
5
5
  pumaguard/model-registry.yaml,sha256=V-pTaqJrk_jJo568okBDaVhs51qTHttSqKe6PqdX6Bc,10318
6
6
  pumaguard/model_cli.py,sha256=nzDv0lXSvRKpLxs579tiHInJPPV-AFO4jzeLk5t2GaA,1394
7
7
  pumaguard/model_downloader.py,sha256=zJQgCMOF2AfhB30VsfOMYtgRxcxVxkZBAdtG8KznPyY,12895
8
- pumaguard/presets.py,sha256=2owFBEqpeAjrFLSYtF9o3wQCc8aDjpwnU5j_YXSyIZ0,27757
8
+ pumaguard/presets.py,sha256=CUTOfrk2d1yprDox7h_S_HUiQUq8cVHJPz6Ua9LsFCM,27884
9
9
  pumaguard/server.py,sha256=zmzSXabt6K26u8kwBPdm1gI6aMAwJo3gCaSaX5Sh0vk,14705
10
- pumaguard/sound.py,sha256=fUp0FWmuj99ZMDacZAayYZOurFfvp68aFxqj0VvbqGw,4723
10
+ pumaguard/sound.py,sha256=-ceyO9xjfuVSal-CvaM_o2l45gYWUUV3bJN-Eni2XQQ,4732
11
11
  pumaguard/stats.py,sha256=ZwocfnFCQ-ky7me-YTTrEoJqsIHOWAgSzeoJHItsIU4,927
12
12
  pumaguard/utils.py,sha256=w1EgOLSZGyjq_b49hvVZhBESy-lVP0yRtNHe-sXBoIU,19735
13
13
  pumaguard/verify.py,sha256=vfw3PRzDt1uuH5FKV9F5vb1PH7KQ6AEgVNhJ6jck_hQ,5513
14
- pumaguard/web_ui.py,sha256=_DiEELlJwuL4DjlUoxGFnHecRmIc21J6zvQwEcXpFdA,16140
14
+ pumaguard/web_ui.py,sha256=2NtiW35rlLXhWXPAIShsK-pH8HUvog6rDQykeTD8Dy0,16636
15
15
  pumaguard/completions/pumaguard-classify-completions.sh,sha256=5QySg-2Jdinj15qpUYa5UzHbTgYzi2gmPVYYyyXny4c,1353
16
16
  pumaguard/completions/pumaguard-completions.sh,sha256=bRx3Q3_gM__3w0PyfQSCVdxylhhr3QlzaLCav24dfNc,1196
17
17
  pumaguard/completions/pumaguard-server-completions.sh,sha256=33c6GjbTImBOHn0SSNUOJoxqJ2mMHuDv3P3GQJGGHhA,1161
@@ -22,7 +22,7 @@ pumaguard/pumaguard-ui/flutter.js,sha256=7V1ZIKmGiouT15CpquQWWmKWJyjUq77FoU9gDXP
22
22
  pumaguard/pumaguard-ui/flutter_bootstrap.js,sha256=Nyt0e7m8U4hq4CmcHhK1ij4hNgUqANM1o4YieKUx4rA,9692
23
23
  pumaguard/pumaguard-ui/flutter_service_worker.js,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  pumaguard/pumaguard-ui/index.html,sha256=901-ZY0WysVAZWPwj2xGatoezwm9TX9IV_jpMrlsaXg,1205
25
- pumaguard/pumaguard-ui/main.dart.js,sha256=NMSeX3a-fd8kOCqxj6hsYyLWGiTwnLS66BQmEWXTC3E,2727541
25
+ pumaguard/pumaguard-ui/main.dart.js,sha256=Tz84jjODZPfwWO-wvAwzM1DUew3Kv1hjCzLnDZhzBeE,2733274
26
26
  pumaguard/pumaguard-ui/manifest.json,sha256=Hhnw_eLUivdrOlL7O9KGBsGXCKKt3lix17Fh3GB0g-s,920
27
27
  pumaguard/pumaguard-ui/version.json,sha256=uXZ6musTJUZaO0N2bEbr3cy9rpx2aesAS2YFMcu2WF8,94
28
28
  pumaguard/pumaguard-ui/assets/AssetManifest.bin,sha256=Qzp1G9iPlHSW-PnHyszTxZO31_NjmTlvSBWY_REPH_8,562
@@ -58,14 +58,14 @@ pumaguard/pumaguard-ui/icons/Icon-maskable-192.png,sha256=0shC4iqfTsnZlrIzc6kFyI
58
58
  pumaguard/pumaguard-ui/icons/Icon-maskable-512.png,sha256=au4Gzcq2sq73Sxc0xHePRCHS2hALD_nlKyG1UkAgKSk,20998
59
59
  pumaguard/web_routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  pumaguard/web_routes/artifacts.py,sha256=IpnMLdbgAYkwU3TuYJE-JHGnC_x5_XNCrc-1M_n2YKk,3879
61
- pumaguard/web_routes/dhcp.py,sha256=wgRwN-pMix5FxJ-UWG38Hn5FiZpMvUxW0rpff_hYjUA,5176
61
+ pumaguard/web_routes/dhcp.py,sha256=hQfYLiDmJeDvGzPmx74LovzbhNN2ywPZfP7avXTJ-FA,10971
62
62
  pumaguard/web_routes/diagnostics.py,sha256=EIIbjuixJyGXdnVQf8RQ6xQxJar0UHZO8dF-9zQLY9g,3294
63
63
  pumaguard/web_routes/directories.py,sha256=yy5TghCEyB4reRGAcVHIEfr2vlHnuiDChIXl9ZFquRM,2410
64
64
  pumaguard/web_routes/folders.py,sha256=Z63ap6dRi6NWye70HYurpCnsSXmFgzTbTsFKYdZ1Bjk,6305
65
65
  pumaguard/web_routes/photos.py,sha256=Tac_CbaZSeZzOfaJ73vlp3iyZbvfD7ei1YM3tsb0nTY,5106
66
- pumaguard/web_routes/settings.py,sha256=kjSqoX6E38UJ_J_YZRQr7QmACR7bDwFcUarqnowjHP4,11655
66
+ pumaguard/web_routes/settings.py,sha256=Rvt1wWqWi0ZsrmylacrgbPEXOjmaGIsLcMwVGVov5e0,12232
67
67
  pumaguard/web_routes/sync.py,sha256=Zvv6VARGE5xP29C5gWH3ul81PISRxoF8n472DITItE0,6378
68
- pumaguard-21.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
68
+ pumaguard-21.post4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
69
69
  pumaguard-sounds/cougar_call.mp3,sha256=jdPzi7Qneect3ez2G6XAeHWtetU5vSOSB6pceuB26Wc,129048
70
70
  pumaguard-sounds/cougarsounds.wav,sha256=hwVmmQ75dkOP3qd07YAvVOSm1neYtxLSzxw3Ulvs2cM,96346
71
71
  pumaguard-sounds/dark-engine-logo-141942.mp3,sha256=Vw-qyLTMPJZvsgQcZtH0DpGcP1dd7nJq-9BnHuNPGug,372819
@@ -83,8 +83,8 @@ pumaguard-sounds/mixkit-vintage-telephone-ringtone-1356.wav,sha256=zWWY2uFF0-l7P
83
83
  pumaguard-sounds/pumaguard-warning.mp3,sha256=wcCfHsulPo5P5s8MjpQAG2NYHQDsRpjqoMig1-o_MDI,232249
84
84
  pumaguard-sounds/short-round-110940.mp3,sha256=vdskGD94SeH1UJyJyR0Ek_7xGXPIZfnPdoBvxGnUt98,450816
85
85
  pumaguard-ui/ios/Flutter/ephemeral/flutter_lldb_helper.py,sha256=Bc_jl3_e5ZPvrSBJpPYtN05VxpztyKq-7lVms3rLg4Q,1276
86
- pumaguard-21.dist-info/METADATA,sha256=QrHSYlqLKczr0NSv-i9MKDtjjYzQKsJBeNIvvMPrOJM,8610
87
- pumaguard-21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
88
- pumaguard-21.dist-info/entry_points.txt,sha256=rmCdBTPWrbJQvPPwABSVobXE9D7hrKsITGZ6nvCrko8,127
89
- pumaguard-21.dist-info/top_level.txt,sha256=B-PzS4agkQNhOYbLLIrMVOyMD_pl5F-yujPBm5zYYjY,40
90
- pumaguard-21.dist-info/RECORD,,
86
+ pumaguard-21.post4.dist-info/METADATA,sha256=q1BEufOhEsWa4cm7W1ufv3CSDO69IMujgirdajdIdA8,8616
87
+ pumaguard-21.post4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
88
+ pumaguard-21.post4.dist-info/entry_points.txt,sha256=rmCdBTPWrbJQvPPwABSVobXE9D7hrKsITGZ6nvCrko8,127
89
+ pumaguard-21.post4.dist-info/top_level.txt,sha256=B-PzS4agkQNhOYbLLIrMVOyMD_pl5F-yujPBm5zYYjY,40
90
+ pumaguard-21.post4.dist-info/RECORD,,