Files
frigate_counter/frigate_counter.py
2026-02-14 17:00:37 +07:00

338 lines
9.5 KiB
Python

#!/usr/bin/env python3
import json
import threading
import paho.mqtt.client as mqtt
import os
import sqlite3
from datetime import datetime, timedelta
# =====================
# SITE CONFIG
# =====================
TOP_TOPIC = "cpsp"
SITE_NAME = "sukawarna"
SITE_TOPIC = f"{TOP_TOPIC}/counter/{SITE_NAME}"
# =====================
# KONFIGURASI MQTT
# =====================
BROKER = "localhost" # ganti sesuai broker
PORT = 1883
USERNAME = "" # ganti user
PASSWORD = "" # ganti password
TOPIC = "frigate/events"
# =====================
# MQTT PUBLISH
# =====================
PUBLISH_BROKER = "mqtt.backone.cloud"
PUBLISH_PORT = 1883
USERNAME = "" # ganti user
PASSWORD = "" # ganti password
# =====================
# FILE COUNTER
# =====================
DATA_FILE = "/etc/frigate/counter_data.json"
# =====================
# DATABASE INITIALIZATION
# =====================
DB_FILE = "/etc/frigate/counter_database.db"
def init_database():
"""Initialize the SQLite database with the required table"""
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
# Create table if it doesn't exist
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS counter_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
camera_name TEXT NOT NULL,
date TIMESTAMP NOT NULL,
counter_value INTEGER NOT NULL
)
"""
)
conn.commit()
conn.close()
# Initialize the database
init_database()
# =====================
# INISIALISASI COUNTER
# =====================
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r") as f:
counter = json.load(f)
else:
counter = {
"kandang_1_karung_pakan": {"karung": 0},
"kandang_2_karung_pakan": {"karung": 0},
"kandang_1_karung_masuk": {"karung": 0},
"kandang_atas_feeder_kiri": {"karung": 0},
"kandang_bawah_feeder_kanan": {"karung": 0},
}
camera_feeder_no_duplicate = [
"kandang_1_karung_pakan",
"kandang_2_karung_pakan"
]
camera_feeder = [
"kandang_1_karung_pakan",
"kandang_2_karung_pakan",
"kandang_atas_feeder_kiri",
"kandang_bawah_feeder_kanan"
]
camera_masuk = ["kandang_1_karung_masuk"]
# Untuk debounce object per track_id
seen_objects = {} # dict per kamera: {track_id: last_seen}
# =====================
# FUNGSI SAVE COUNTER
# =====================
def save_counter():
with open(DATA_FILE, "w") as f:
json.dump(counter, f, indent=2)
def save_counter_to_database(camera_list_to_save=[]):
"""Save current counter values to SQLite database"""
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
# Get current timestamp
current_time = datetime.now()
# Save each counter value to database
for camera_name, camera_data in counter.items():
for label, value in camera_data.items():
if label == "karung" and camera_name in camera_list_to_save: # Only save karung counters
cursor.execute(
"""
INSERT INTO counter_data (camera_name, date, counter_value)
VALUES (?, ?, ?)
""",
(camera_name, current_time, value),
)
conn.commit()
conn.close()
def republish_counter():
# Publish current counter
for camera, v in counter.items():
for label, value in v.items():
if label == "karung":
print(f"{SITE_TOPIC}/{camera}/{label}", value)
client_publish.publish(
f"{SITE_TOPIC}/{camera}/{label}", value, qos=1, retain=True
)
# =====================
# RESET HARIAN
# =====================
def reset_counter():
global counter, seen_objects
print(f"[{datetime.now()}] Reset counter otomatis")
# Save current counter values to database before reset
save_counter_to_database(camera_feeder)
# reset semua
# for cam in counter:
# for obj in counter[cam]:
# counter[cam][obj] = 0
for camera_name in camera_feeder:
counter[camera_name]["karung"] = 0
"""
counter["kandang_1_karung_pakan"]["karung"] = 0
counter["kandang_2_karung_pakan"]["karung"] = 0
counter["kandang_atas_feeder_kiri"]["karung"] = 0
counter["kandang_bawah_feeder_kanan"]["karung"] = 0
"""
seen_objects = {}
save_counter()
schedule_reset() # jadwalkan besok
republish_counter()
def reset_counter_masuk():
global counter, seen_objects
print(f"[{datetime.now()}] Reset counter otomatis")
# Save current counter values to database before reset
save_counter_to_database(camera_masuk)
# reset semua
# for cam in counter:
# for obj in counter[cam]:
# counter[cam][obj] = 0
for camera_name in camera_masuk:
counter[camera_name]["karung"] = 0
#counter["kandang_1_karung_masuk"]["karung"] = 0
seen_objects = {}
save_counter()
schedule_reset_masuk() # jadwalkan besok
republish_counter()
def schedule_reset():
now = datetime.now()
reset_time = now.replace(hour=17, minute=0, second=0, microsecond=0)
if now >= reset_time:
reset_time += timedelta(days=1)
delay = (reset_time - now).total_seconds()
threading.Timer(delay, reset_counter).start()
print(f"Reset counter dijadwalkan pada: {reset_time}")
def schedule_reset_masuk():
now = datetime.now()
reset_time = now.replace(hour=23, minute=59, second=55, microsecond=0)
if now >= reset_time:
reset_time += timedelta(days=1)
delay = (reset_time - now).total_seconds()
threading.Timer(delay, reset_counter_masuk).start()
print(f"Reset counter dijadwalkan pada: {reset_time}")
# =====================
# CALLBACK MQTT
# =====================
def on_connect(client, userdata, flags, rc):
print("Connected to MQTT with result code " + str(rc))
client.subscribe(TOPIC)
def on_connect_publish(client, userdata, flags, rc):
print("Connected to MQTT PUBLISH with result code " + str(rc))
# Publish current counter
republish_counter()
def on_message(client, userdata, msg):
try:
payload = json.loads(msg.payload.decode())
if "after" not in payload:
return
event_after = payload["after"]
event_before = payload.get("before", {})
camera = event_after.get("camera")
label = event_after.get("label")
track_id = event_after.get("id")
zones_after = event_after.get("entered_zones", [])
zones_before = event_before.get("entered_zones", [])
# Dont detect stationary
stationary = event_after.get("stationary")
if stationary:
return
# ambil zona baru yg sebelumnya belum ada
new_zones = [z for z in zones_after if z not in zones_before]
if not camera or not label:
return
if camera not in counter or label not in counter[camera]:
return
if not new_zones: # hanya hitung kalau masuk zona baru
return
# debounce per track_id
if camera not in seen_objects:
seen_objects[camera] = {}
if track_id in seen_objects[camera]:
return # sudah dihitung
# Check if timestamp is more than 30 seconds
# if camera == "kandang_1_karung_pakan" or camera == "kandang_2_karung_pakan":
"""
if (
camera == "kandang_1_karung_pakan"
or camera == "kandang_2_karung_pakan"
or camera == "kandang_atas_feeder_kiri"
or camera == "kandang_bawah_feeder_kanan"
):
"""
if camera in camera_feeder_no_duplicate:
DETIK_ANTAR_KARUNG = 30
current_time = datetime.now()
for ts in seen_objects[camera].values():
delta = current_time - ts
if delta.seconds < DETIK_ANTAR_KARUNG:
return
# hitung
counter[camera][label] += 1
seen_objects[camera][track_id] = datetime.now()
print(
f"[{datetime.now()}] Kamera {camera}: Object {label} masuk zona {new_zones}"
)
print(f"Total {label} di {camera}: {counter[camera][label]}")
print(f"{TOP_TOPIC}/counter/{SITE_NAME}/{camera}/{label}")
# simpan dan publish
save_counter()
# client_publish.publish(f"{TOP_TOPIC}/counter/{SITE_NAME}/{camera}/{label}", counter[camera][label], qos=1, retain=True)
client_publish.publish(
f"{SITE_TOPIC}/{camera}/{label}", counter[camera][label], qos=1, retain=True
)
except Exception as e:
print("Error parsing message:", e)
def view_database():
"""View all records in the database"""
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute("SELECT * FROM counter_data ORDER BY date DESC LIMIT 10")
rows = cursor.fetchall()
print("Last 10 records in database:")
for row in rows:
print(f"ID: {row[0]}, Camera: {row[1]}, Date: {row[2]}, Counter: {row[3]}")
conn.close()
# =====================
# MAIN
# =====================
client = mqtt.Client()
client.username_pw_set(USERNAME, PASSWORD)
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, PORT, 60)
client_publish = mqtt.Client()
client_publish.on_connect = on_connect_publish
client_publish.connect(PUBLISH_BROKER, PUBLISH_PORT, 60)
# jadwalkan reset pertama
schedule_reset()
schedule_reset_masuk()
client_publish.loop_start()
print(f"MQTT Publish Counter berjalan di {PUBLISH_BROKER}")
print(f"MQTT Counter berjalan di {BROKER}")
client.loop_forever()