Ralph Yamamoto
Created December 20, 2024

RPi Zero W Person Sensor Testbed

A previously unfinished project to test the Useful Sensors Person Sensor upgraded to use the latest available hardware components.

16
RPi Zero W Person Sensor Testbed

Things used in this project

Story

Read more

Custom parts and enclosures

RPi_Zero_2W_tripod_Mount

Mounts RPi Zero board vertically on tripod

Sketchfab still processing.

Schematics

Person_Sensor_uHat schematic

Person Sensor Hat for RPi Zero

Code

person_sensor_mqtt_code.py

Python
Python code to upload Person Sensor MQTT data to Node-Red
# Example of accessing the Person Sensor from Useful Sensors on a Pi Zero using
# Python. See https://usfl.ink/ps_dev for the full developer guide.

import io
import fcntl
import struct
import json
import random
import time

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):

    print("Connected with result code " + str(rc))

client = mqtt.Client()

client.on_connect = on_connect

client.connect("10.0.0.234", 1883)

# The person sensor has the I2C ID of hex 62, or decimal 98.
PERSON_SENSOR_I2C_ADDRESS = 0x62

# We will be reading raw bytes over I2C, and we'll need to decode them into
# data structures. These strings define the format used for the decoding, and
# are derived from the layouts defined in the developer guide.
PERSON_SENSOR_I2C_HEADER_FORMAT = "BBH"
PERSON_SENSOR_I2C_HEADER_BYTE_COUNT = struct.calcsize(
    PERSON_SENSOR_I2C_HEADER_FORMAT)

PERSON_SENSOR_FACE_FORMAT = "BBBBBBbB"
PERSON_SENSOR_FACE_BYTE_COUNT = struct.calcsize(PERSON_SENSOR_FACE_FORMAT)

PERSON_SENSOR_FACE_MAX = 4
PERSON_SENSOR_RESULT_FORMAT = PERSON_SENSOR_I2C_HEADER_FORMAT + \
    "B" + PERSON_SENSOR_FACE_FORMAT * PERSON_SENSOR_FACE_MAX + "H"
PERSON_SENSOR_RESULT_BYTE_COUNT = struct.calcsize(PERSON_SENSOR_RESULT_FORMAT)

# I2C channel 1 is connected to the GPIO pins
I2C_CHANNEL = 1
I2C_PERIPHERAL = 0x703

# How long to pause between sensor polls.
PERSON_SENSOR_DELAY = 0.2
#PERSON_SENSOR_DELAY = 1.0

i2c_handle = io.open("/dev/i2c-" + str(I2C_CHANNEL), "rb", buffering=0)
fcntl.ioctl(i2c_handle, I2C_PERIPHERAL, PERSON_SENSOR_I2C_ADDRESS)

while True:
    try:
        read_bytes = i2c_handle.read(PERSON_SENSOR_RESULT_BYTE_COUNT)
    except OSError as error:
        print("No person sensor data found")
        print(error)
        time.sleep(PERSON_SENSOR_DELAY)
        continue
    offset = 0
    (pad1, pad2, payload_bytes) = struct.unpack_from(
        PERSON_SENSOR_I2C_HEADER_FORMAT, read_bytes, offset)
    offset = offset + PERSON_SENSOR_I2C_HEADER_BYTE_COUNT

    (num_faces) = struct.unpack_from("B", read_bytes, offset)
    num_faces = int(num_faces[0])
    offset = offset + 1

    faces = []
    for i in range(num_faces):
        (box_confidence, box_left, box_top, box_right, box_bottom, id_confidence, id,
         is_facing) = struct.unpack_from(PERSON_SENSOR_FACE_FORMAT, read_bytes, offset)
        offset = offset + PERSON_SENSOR_FACE_BYTE_COUNT
        face = {
            "box_confidence": box_confidence,
            "box_left": box_left,
            "box_top": box_top,
            "box_right": box_right,
            "box_bottom": box_bottom,
            "id_confidence": id_confidence,
            "id": id,
            "is_facing": is_facing,
        }
        faces.append(face)
    checksum = struct.unpack_from("H", read_bytes, offset)
    print(num_faces, faces)
    
    # Convert to JSON string
    json_data = json.dumps(faces, indent=4)

    # Print the JSON data
    #print(json_data)
    
    if (len(faces) > 0):
        print(json_data)
        client.publish("person_sensor/face_detection", json_data)
    
    #client.loop_forever()
    
    time.sleep(PERSON_SENSOR_DELAY)

person_sensor_testbed_flow.json

JSON
JSON of Node-Red flow
[
    {
        "id": "0b9e029045fa445a",
        "type": "tab",
        "label": "Person Sensor Testbed",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "0864e53c4fee9221",
        "type": "mqtt in",
        "z": "0b9e029045fa445a",
        "name": "face_detection",
        "topic": "person_sensor/face_detection",
        "qos": "2",
        "datatype": "json",
        "broker": "98ad732.a6d0b9",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 260,
        "y": 640,
        "wires": [
            [
                "69361fc3fd8d49f8",
                "76c29d90e7d12f17",
                "099a2cd5faa83bb8",
                "373f30b1a8602e40"
            ]
        ]
    },
    {
        "id": "69361fc3fd8d49f8",
        "type": "debug",
        "z": "0b9e029045fa445a",
        "name": "face_detection",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 740,
        "y": 460,
        "wires": []
    },
    {
        "id": "76c29d90e7d12f17",
        "type": "ui_text",
        "z": "0b9e029045fa445a",
        "group": "7289634b3976b97b",
        "order": 1,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Box Confidence",
        "format": "{{msg.payload[0].box_confidence}}",
        "layout": "row-spread",
        "x": 740,
        "y": 560,
        "wires": []
    },
    {
        "id": "099a2cd5faa83bb8",
        "type": "ui_text",
        "z": "0b9e029045fa445a",
        "group": "7289634b3976b97b",
        "order": 2,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Is_Facing",
        "format": "{{msg.payload[0].is_facing}}",
        "layout": "row-spread",
        "x": 720,
        "y": 600,
        "wires": []
    },
    {
        "id": "43cc2fa56109df44",
        "type": "debug",
        "z": "0b9e029045fa445a",
        "name": "face location",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 950,
        "y": 800,
        "wires": []
    },
    {
        "id": "7f4fd912b756b6a1",
        "type": "ui_text",
        "z": "0b9e029045fa445a",
        "group": "7289634b3976b97b",
        "order": 3,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Box_X",
        "format": "{{msg.x}}",
        "layout": "row-spread",
        "x": 710,
        "y": 640,
        "wires": []
    },
    {
        "id": "117d9e832a96a7b6",
        "type": "ui_text",
        "z": "0b9e029045fa445a",
        "group": "7289634b3976b97b",
        "order": 4,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Box_Y",
        "format": "{{msg.y}}",
        "layout": "row-spread",
        "x": 710,
        "y": 680,
        "wires": []
    },
    {
        "id": "e66e288705d3ff91",
        "type": "ui_text",
        "z": "0b9e029045fa445a",
        "group": "7289634b3976b97b",
        "order": 5,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Box_W",
        "format": "{{msg.w}}",
        "layout": "row-spread",
        "x": 720,
        "y": 720,
        "wires": []
    },
    {
        "id": "bbf5aca6db7eeb24",
        "type": "ui_text",
        "z": "0b9e029045fa445a",
        "group": "7289634b3976b97b",
        "order": 6,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "Box_H",
        "format": "{{msg.h}}",
        "layout": "row-spread",
        "x": 710,
        "y": 760,
        "wires": []
    },
    {
        "id": "373f30b1a8602e40",
        "type": "function",
        "z": "0b9e029045fa445a",
        "name": "bbox",
        "func": "var x = msg.payload[0].box_left;\nmsg.x = x;\nvar y = msg.payload[0].box_top;\nmsg.y = y;\nvar w = msg.payload[0].box_right - msg.payload[0].box_left;\nmsg.w = w;\nvar h = msg.payload[0].box_bottom - msg.payload[0].box_top;\nmsg.h = h;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 490,
        "y": 640,
        "wires": [
            [
                "7f4fd912b756b6a1",
                "117d9e832a96a7b6",
                "e66e288705d3ff91",
                "bbf5aca6db7eeb24",
                "29541855f532d124",
                "4f15439d93735d11"
            ]
        ]
    },
    {
        "id": "29541855f532d124",
        "type": "debug",
        "z": "0b9e029045fa445a",
        "name": "bbox",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 710,
        "y": 900,
        "wires": []
    },
    {
        "id": "4f15439d93735d11",
        "type": "ui_template",
        "z": "0b9e029045fa445a",
        "group": "7289634b3976b97b",
        "name": "Face Location",
        "order": 7,
        "width": 13,
        "height": 13,
        "format": "<canvas id=\"myCanvas\" width=\"256\" height=\"256\" style=\"border:1px solid\"></canvas>\n\n<script>\n\n\n(function(scope) {\n    // $watch fires each time the node is triggered in the flow\n    scope.$watch('msg', function(msg) {\t\n\n    const canvas = document.getElementById(\"myCanvas\");\n    const ctx = canvas.getContext(\"2d\");\n    ctx.fillStyle = \"blue\"; // Set fill color\n\n  \tctx.clearRect(0, 0, canvas.width, canvas.height);\n\n    ctx.fillStyle = \"blue\";\n\n  \tctx.fillRect(msg.x, msg.y, msg.w, msg.h); // Draw rectangle\n\n    });\n\n})(scope);  \n</script>",
        "storeOutMessages": false,
        "fwdInMessages": true,
        "resendOnRefresh": false,
        "templateScope": "local",
        "x": 740,
        "y": 800,
        "wires": [
            [
                "43cc2fa56109df44"
            ]
        ]
    },
    {
        "id": "98ad732.a6d0b9",
        "type": "mqtt-broker",
        "name": "",
        "broker": "10.0.0.234",
        "port": "1883",
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "compatmode": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "autoUnsubscribe": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "userProps": "",
        "sessionExpiry": ""
    },
    {
        "id": "7289634b3976b97b",
        "type": "ui_group",
        "name": "Person Sensor",
        "tab": "2d394dc643d3b04a",
        "order": 1,
        "disp": true,
        "width": 13,
        "collapse": false
    },
    {
        "id": "2d394dc643d3b04a",
        "type": "ui_tab",
        "name": "Person Sensor",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]

Credits

Ralph Yamamoto
11 projects • 21 followers
Contact

Comments

Please log in or sign up to comment.