From a3b4e5c83de5e4ca8a0a6a40705aba6bb9957554 Mon Sep 17 00:00:00 2001 From: seanohair22 Date: Thu, 16 Jan 2025 10:26:20 -0700 Subject: [PATCH] feat: implement weight scale microservice (#26) * feat: implement weight scale microservice * fix: address pr feedback * address additional PR feedback * adjust copywrite header and add comment * Update reading class var init * Update comments to specify CAS PD-II scale --------- Signed-off-by: Sean O'Hair --- Makefile | 5 ++ performance-tools | 2 +- src/docker-compose.yml | 13 ++- src/weightScale/Dockerfile | 17 ++++ src/weightScale/main.py | 133 +++++++++++++++++++++++++++++++ src/weightScale/requirements.txt | 2 + 6 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 src/weightScale/Dockerfile create mode 100644 src/weightScale/main.py create mode 100644 src/weightScale/requirements.txt diff --git a/Makefile b/Makefile index 6d9fa7b..e997262 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,9 @@ build: docker build --build-arg HTTPS_PROXY=${HTTPS_PROXY} --build-arg HTTP_PROXY=${HTTP_PROXY} --target build-default -t dlstreamer:dev -f src/Dockerfile src/ docker build --build-arg HTTPS_PROXY=${HTTPS_PROXY} --build-arg HTTP_PROXY=${HTTP_PROXY} -t loss-prevention:dev -f src/app/Dockerfile src/app +build-scale: + docker build --build-arg HTTPS_PROXY=${HTTPS_PROXY} --build-arg HTTP_PROXY=${HTTP_PROXY} -t lp-scale:dev -f src/weightScale/Dockerfile src/weightScale + build-realsense: docker build --build-arg HTTPS_PROXY=${HTTPS_PROXY} --build-arg HTTP_PROXY=${HTTP_PROXY} --target build-realsense -t dlstreamer:realsense -f src/Dockerfile src/ @@ -73,12 +76,14 @@ down: run-demo: | download-models update-submodules download-sample-videos @echo "Building Loss Prevention app" $(MAKE) build + $(MAKE) build-scale @echo Running Loss Prevention pipeline $(MAKE) run-render-mode run-headless: | download-models update-submodules download-sample-videos @echo "Building Loss Prevention app" $(MAKE) build + $(MAKE) build-scale @echo Running Loss Prevention pipeline $(MAKE) run diff --git a/performance-tools b/performance-tools index d991b39..785cdfe 160000 --- a/performance-tools +++ b/performance-tools @@ -1 +1 @@ -Subproject commit d991b39426fc997c7f54ac45384b75ab3037b6e4 +Subproject commit 785cdfe3fabc1e7121186fbf525ac721ce8d507f diff --git a/src/docker-compose.yml b/src/docker-compose.yml index b7fef8c..dde5ee4 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -108,4 +108,15 @@ services: network_mode: "host" volumes: - ./go2rtc:/config - \ No newline at end of file + + lp-scale: + image: lp-scale:dev + network_mode: "host" + privileged: true + environment: + - MQTT_URL=127.0.0.1 + - MQTT_PORT=1883 + - MQTT_TOPIC=event/scale + restart: unless-stopped + depends_on: + - mosquitto diff --git a/src/weightScale/Dockerfile b/src/weightScale/Dockerfile new file mode 100644 index 0000000..724a4e2 --- /dev/null +++ b/src/weightScale/Dockerfile @@ -0,0 +1,17 @@ +# +# Copyright (C) 2024-25 Intel Corporation. +# +# SPDX-License-Identifier: Apache-2.0 +# + +FROM python:3.11-slim +ENV PYTHONUNBUFFERED=1 +WORKDIR /app +COPY requirements.txt . + +# Install dependencies from the requirements file +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["python", "main.py"] diff --git a/src/weightScale/main.py b/src/weightScale/main.py new file mode 100644 index 0000000..34a1e7a --- /dev/null +++ b/src/weightScale/main.py @@ -0,0 +1,133 @@ +# +# Copyright (C) 2024-25 Intel Corporation. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import serial +import time +import os +import paho.mqtt.client as mqtt + +# need to update code based on device used - designed with the CAS PD-II scale in mind +port_name = '/dev/ttyUSB0' +baud_rate = 9600 +timeout = 1 # 1 second timeout +parity = serial.PARITY_EVEN +byte_size = serial.SEVENBITS +stop_bits = serial.STOPBITS_ONE + +MQTT_BROKER_URL = os.getenv("MQTT_URL", "127.0.0.1") +MQTT_PORT = int(os.getenv("MQTT_PORT", "1883")) +MQTT_TOPIC = os.getenv("MQTT_TOPIC", "event/scale") + + +# Reading class object to hold data from the CAS PD-II scale +class Reading: + def __init__(self, status="", value="", unit=""): + self.status = status + self.value = value + self.unit = unit + + def print_weight(self): + return (f"Weight: {self.value} {self.unit}") + + def to_string(self): + if self.status == "OK" or self.status == "Motion": + return (f"Status: {self.status}, {self.print_weight()}") + else: + return (f"Status: {self.status}") + +def read_scale(dev:serial.Serial): + # build buffer + buffer_bytes = bytearray() + + # Verify communication - need to update code based on device used + # designed with the CAS PD-II scale in mind + weight_request = bytes([0x57, 0x0D]) + dev.write(weight_request) + + time.sleep(0.128) + + # print output request + weight_bytes = dev.read(16) # Read 16 bytes to check if the CAS PD-II scale is responsive + buffer_bytes.extend(weight_bytes) + buffer_str = buffer_bytes.hex().upper() + print(f"Received data (hex): {buffer_str}") + return buffer_str + +def process_scale_hex(buf:str): + # Need to update code based on device used - CAS PD-II + status_ending = b"0D03" # Status ending in response + weight_ending = b"0D0A" # Weight reading ending in response + weight_value_start = b"0A3" # Start of weight data + period_byte = b"2E" # Byte that signifies period + expected_period_index = 6 # Expected index for period byte in weight data + + # Map for known status codes + status_tbl = { + b"00": "OK", + b"10": "Motion", + b"20": "Scale at Zero", + b"01": "Under Capacity", + b"02": "Over Capacity", + } + + # Find the start index for the weight value + start_index = buf.find(weight_value_start.decode("utf-8")) + print(f"Start index: {start_index}") + + # Check for valid data structure + if start_index != 0 or len(buf) > 30: + print("Invalid start index or data too long") + + + # Look for status and weight data in the response + if status_ending.decode("utf-8") in buf: + reading = Reading() + + status_len = 4 + weight_len = 16 + status_index = buf.find(status_ending.decode("utf-8")) + weight_index = buf.find(weight_ending.decode("utf-8")) + period_index = buf.find(period_byte.decode("utf-8")) + + if status_index >= 0 and len(buf) > status_index: + status = buf[status_index - status_len:status_index] + status_def = status_tbl.get(bytes.fromhex(status), "N/A") + reading.status = status_def + # print(f"Status found: {reading.status}") + + if weight_index >= 0 and len(buf) > weight_index and period_index == expected_period_index: + weight_str = buf[weight_index - weight_len:weight_index] + weight = bytes.fromhex(weight_str) + + unit_len = 2 + unit_index = len(weight) - unit_len + reading.value = weight[:unit_index].decode("utf-8") + reading.unit = weight[unit_index:].decode("utf-8") + + return reading + +def main(): + while True: + try: + with serial.Serial(port_name, baud_rate, byte_size, parity, stop_bits, timeout ) as ser: + print(f"Connected to CAS PD-II scale at {port_name}") + + buffer_str = read_scale(ser) + + result = process_scale_hex(buffer_str) + + print(result.to_string()) + client.publish(topic=MQTT_TOPIC, payload=result.print_weight()) + + except serial.SerialException as e: + print(f"Error connecting to the port: {e}") + +if __name__=="__main__": + client = mqtt.Client(client_id="", clean_session=True, userdata=None, protocol=mqtt.MQTTv311, transport="tcp") + client.connect(MQTT_BROKER_URL, MQTT_PORT, 60) + print(f"Connected to MQTT") + client.loop_start() + main() diff --git a/src/weightScale/requirements.txt b/src/weightScale/requirements.txt new file mode 100644 index 0000000..3433815 --- /dev/null +++ b/src/weightScale/requirements.txt @@ -0,0 +1,2 @@ +paho-mqtt==1.6.1 +pyserial \ No newline at end of file