"""Person alerts with a built-in flash model (OpenMV + YOLO LC)

Publishes when someone enters or leaves the frame, and uploads a short video
clip on arrival. Two things are worth understanding here:

**The model's weights are free; its arena isn't.** OpenMV ships a YOLO LC person
detector in flash (``/rom/yolo_lc_192.tflite``). Its weights are read in place
rather than copied into the heap, so it runs on boards that could never fit a
custom model. Loading it still allocates a ~275 KB tensor arena on the heap
(``model.ram`` ~270 KB on an RT1062); inference after that adds nothing, ~310 ms
per frame. Budget the arena plus the camera framebuffer. ``person()`` wraps it.

**Detection is not the same as reporting.** A raw detector flips on every stray
frame — someone's arm, a shadow, one bad exposure. ``Watch`` debounces, so a
person has to be genuinely present for DEBOUNCE consecutive frames before the
device believes it. Then it publishes *once*:

    {"state": "person", "prev": "clear", "conf": 0.88}

...and ``on_change`` fires the clip upload. While the scene is stable, nothing
is sent at all.

Note the economics: every file upload is malware-scanned and costs one Surface
scan credit, charged to the sender. Video is expensive; a state change is not.
The model's real job is deciding which rare moments are worth the credit.

Person detection is adapted from OpenMV's TensorFlow Lite YOLO LC example:

  https://github.com/openmv/openmv/blob/master/scripts/examples/03-Machine-Learning/00-TensorFlow/yolo_lc_person_detector.py

Requirements:
- OpenMV cam with ``csi`` and ``ml``
- Tendrl SDK with the vision extension (``package-vision.json``)
- ``requests`` installed on the device (for the clip upload)
- File transfer enabled, and an entity token with ``entity:SendFiles``
"""

import time

try:
    from tendrl.vision import Camera, Watch, person
except ImportError:
    raise SystemExit("This example requires the tendrl vision extension (package-vision.json)")

from tendrl import Client

# --- Configure for your deployment ------------------------------------------

DETECT_THRESHOLD = 0.4        # YOLO LC confidence floor for a box to count
DEBOUNCE = 2                  # consecutive frames before a state change is believed
COOLDOWN_SECONDS = 30         # minimum gap between published changes (and clips)
CHECK_EVERY_MS = 200          # how often to run inference

RECORD_SECONDS = 3            # clip length per arrival
CLIP_FPS = 5                  # frames per second (lower = smaller zip)
CLIP_QUALITY = 85             # JPEG quality 1-100
SEND_CLIP = True              # set False to publish state only, no upload

TAGS = ["person-detected"]    # routes to a flow/workflow with the same tag

# -----------------------------------------------------------------------------


def on_person_change(cam, client):
    """Build the Watch callback: upload a clip when someone arrives."""

    def handler(state, prev, img):
        if state != "person" or not SEND_CLIP:
            return  # departures publish, but don't need video

        print("Person arrived — recording %ds clip..." % RECORD_SECONDS)
        cam.lock_exposure(True)  # stop auto-exposure pumping mid-clip
        try:
            resp = client.send_clip_burst(
                cam.snapshot,
                duration_s=RECORD_SECONDS,
                fps=CLIP_FPS,
                quality=CLIP_QUALITY,
                tags=TAGS,
                meta={"trigger": "yolo_lc_person", "record_s": RECORD_SECONDS},
            )
        finally:
            cam.lock_exposure(False)

        if resp:
            print("Clip uploaded:", resp.get("transfer_id"), resp.get("status"))
        else:
            print("Clip upload failed (see debug output above)")

    return handler


def main():
    print("=" * 60)
    print("Person alerts (YOLO LC, in-flash model)")
    print("=" * 60)

    # VGA windowed to a square keeps the model's aspect ratio sane.
    cam = Camera(framesize="VGA", window=(400, 400))

    client = Client(mode="sync", debug=True)
    client.start()
    print("Waiting for network...")
    time.sleep(5)

    watch = Watch(
        client,
        detect=person(min_conf=DETECT_THRESHOLD),
        debounce=DEBOUNCE,
        cooldown_s=COOLDOWN_SECONDS,
        tags=TAGS,
        on_change=on_person_change(cam, client),
        debug=True,
    )

    print(
        "Watching for people (debounce %d frames, cooldown %ds)..."
        % (DEBOUNCE, COOLDOWN_SECONDS)
    )

    while True:
        watch.update(cam.snapshot())
        time.sleep_ms(CHECK_EVERY_MS)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("Stopped.")
