forked from cptskippy/speed-trap
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtask_license_plate_reader.py
More file actions
224 lines (173 loc) · 6.87 KB
/
Copy pathtask_license_plate_reader.py
File metadata and controls
224 lines (173 loc) · 6.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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
186
187
188
189
190
191
192
193
194
195
196
197
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
"""
task_license_plate_reader.py
Subscribes to an MQTT topic and performs LPR activities
against specified images.
"""
import asyncio
import json
from datetime import datetime
import logging
import signal
import sys
from shared import load_config, MqttClientWrapper, OpenAILicensePlateReader, Protect
# logging.basicConfig(
# level=logging.DEBUG, # or INFO, WARNING
# format='[%(levelname)s] %(name)s: %(message)s'
# )
logger = logging.getLogger(__name__)
# logger.debug("Debug message")
# logger.info("Info message")
# logger.warning("Warning message")
# Load configuration from yaml
config = load_config()
mqtt_config = config["servers"]["mqtt"]
protect_config = config["servers"]["unifi_protect"]
task_config = config["task"]["license_plate_reader"]
cameras_config = config["cameras"]
video_extension = config["media"]["video_extension"]
image_extension = config["media"]["image_extension"]
# Populate configuration variables
MQTT_URI = mqtt_config["uri"]
MQTT_USER = mqtt_config["username"]
MQTT_PASSWORD = mqtt_config["password"]
MQTT_QOS = mqtt_config["qos"]
MQTT_CLIENT_ID = task_config["mqtt"]["client_id"]
MQTT_SUBSCRIBE_TOPIC = task_config["mqtt"]["topics"]["subscribe"]
MQTT_PUBLISH_TOPIC = task_config["mqtt"]["topics"]["publish"]
MQTT_ERROR_TOPIC = task_config["mqtt"]["topics"]["error"]
AI_API_KEY = task_config["openai_api_key"]
AI_MODEL = task_config["openai_model"]
AI_PROMPT = task_config["openai_prompt"]
DELTA_OFFSET = task_config["delta_offset"]
LPR_METHOD = task_config["lpr_method"]
UI_URI = protect_config["uri"]
UI_USERNAME = protect_config["username"]
UI_PASSWORD = protect_config["password"]
LPR_CAMERAS = [cam for cam in cameras_config if cam["perform_lpr"] == True]
# Configure NVR client
NVR_CLIENT = Protect(UI_URI, UI_USERNAME, UI_PASSWORD)
# Configure LPR client
if LPR_METHOD == "OPENAI":
LPR_CLIENT = OpenAILicensePlateReader(AI_API_KEY, AI_MODEL, AI_PROMPT)
else:
LPR_CLIENT = NVR_CLIENT
def on_connect(client, userdata, flags, reason_code, properties):
"""Subscribe to topic on successful connection."""
if reason_code == 0:
client.subscribe(MQTT_SUBSCRIBE_TOPIC, MQTT_QOS)
print(f"Subscribed to topic: {MQTT_SUBSCRIBE_TOPIC}")
def on_message(client, userdata, message):
""""Processes the message from MQTT Broker"""
try:
payload = message.payload.decode('utf-8')
data = json.loads(payload)
print("\nLPR Event Received:")
print(f" Timestamp: {data.get('timestamp')}")
print(f" Sensor ID: {data.get('sensor_id')}")
print(f" Speed: {data.get('speed')} {data.get('uom')}")
print(f" Folder: {data.get('folder')}")
print(f" Data File: {data.get('data_file')}")
print(f" Payload: {payload}")
handle_event(data)
except json.JSONDecodeError:
print("Received invalid JSON: {message}")
except Exception as e:
print(f"Error processing message: {e}")
def update_summary(summary_path, license_plate, vehicle_color, vehicle_type):
"""Opens the summary file and updates the license_plate"""
logger.debug(f" Opening summary data: {summary_path}")
with open(summary_path, "r") as f:
summary_data = json.load(f)
logger.debug(f" Updating summary data...")
summary_data["license_plate"] = license_plate
summary_data["vehicle_color"] = vehicle_color
summary_data["vehicle_type"] = vehicle_type
logger.debug(f" Saving summary data...")
with open(summary_path, "w", encoding="utf-8") as f:
json.dump(summary_data, f, indent=4)
print(" File saved")
def get_lpr_reads(eventdata):
results = []
for camera in LPR_CAMERAS:
source = ""
if LPR_METHOD == "OPENAI":
# Generate Path
file_name = camera["file_name"]
folder = eventdata.get("folder")
source = folder + "/" + file_name + image_extension
if LPR_METHOD == "PROTECT":
camera_name = camera["camera_id"]
cameras = NVR_CLIENT.get_cameras([camera_name])
if len(cameras) > 0:
source = cameras[0]["id"]
print(f"Source: {source}")
timestamp = eventdata.get("timestamp")
occurred = datetime.fromisoformat(timestamp)
# Perform LPR
results = LPR_CLIENT.get_license_plate_reads(source=source,
dt=occurred,
offset=DELTA_OFFSET)
return results
def handle_event(data):
"""Performs a license plate read."""
status = "failure"
error = "No result"
plate = ""
# {
# "timestamp": "2025-08-15T19:54:57.406713+00:00",
# "speed": 25.041258573096,
# "uom": "mph",
# "sensor_id": "sensor.speedometer_speed",
# "folder": "./media/20250815125457",
# "data_file": "./media/20250815125457/data.json",
# "summary_file": "./media/20250815125457/summary.json",
# "videos": [
# "./media/20250815125457/street.mpg",
# "./media/20250815125457/driveway.mpg",
# "./media/20250815125457/globalshutter.mpg"
# ],
# "images": [
# "./media/20250815125457/street.png",
# "./media/20250815125457/driveway.png",
# "./media/20250815125457/globalshutter.png"
# ],
# "thumbnails": [
# "./media/20250815125457/street_thumb.png",
# "./media/20250815125457/driveway_thumb.png",
# "./media/20250815125457/globalshutter_thumb.png"
# ]
# }
results = get_lpr_reads(data)
if len(results) > 0:
result = results[0]
status = result.status
error = result.error_message
plate = result.license_plate
vehicle_color = result.vehicle_color
vehicle_type = result.vehicle_type
if status == "success":
# Update Summary file
summary_path = data.get("summary_file")
update_summary(summary_path, plate, vehicle_color, vehicle_type)
print(f" License plate read: {plate}")
else:
print(f" No plate data: {error}")
# Update Payload
payload = json.dumps(data)
print(f" New Payload: {payload}")
# Publish message for next task
client.publish(MQTT_PUBLISH_TOPIC, payload, MQTT_QOS)
print(f" Message Published: {MQTT_PUBLISH_TOPIC}")
def shutdown(*_args):
"""Gracefully closes the NVR client's connection before exiting."""
print("\nShutting down, closing NVR client...")
NVR_CLIENT.close()
sys.exit(0)
# Configure MQTT and wait...
client = MqttClientWrapper(MQTT_URI, MQTT_CLIENT_ID, MQTT_USER, MQTT_PASSWORD)
client.connect(on_connect, on_message)
# Ensure the long-lived NVR client connection is closed deliberately on
# Ctrl+C / SIGTERM, rather than relying on Protect.__del__ (which now only
# logs a warning instead of trying to close anything itself).
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)