This commit is contained in:
Eden Kirin
2025-10-09 17:22:11 +02:00
parent 0a29898bf4
commit 1955256d06
4 changed files with 231 additions and 51 deletions

View File

@ -10,7 +10,6 @@ import logging
import random
import time
from typing import Optional
from datetime import datetime
import serial
@ -29,7 +28,7 @@ STATE_MESSAGE_PENDING = "PRN"
class GlorySimulator:
def __init__(self, port: str, baudrate: int = 9600):
def __init__(self, logger: logging.Logger, port: str, baudrate: int = 9600):
self.port = port
self.baudrate = baudrate
self.serial_conn: Optional[serial.Serial] = None
@ -77,10 +76,7 @@ class GlorySimulator:
# Message buffer
self.pending_message = None
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
self.logger = logging.getLogger(__name__)
self.logger = logger
def get_status(self) -> str:
"""Get current machine status"""
@ -116,7 +112,7 @@ class GlorySimulator:
def handle_clear_command(self, data: bytes) -> bytes:
"""Handle clear commands (CC, CB, CS, CG)"""
cmd = data.decode('ascii', errors='ignore').strip()
cmd = data.decode("ascii", errors="ignore").strip()
if cmd == "CB":
# Clear batch
@ -146,27 +142,32 @@ class GlorySimulator:
def handle_get_data_command(self, data: bytes) -> bytes:
"""Handle get data commands (GD, GT, GS, GG, GI, GV)"""
cmd = data.decode('ascii', errors='ignore').strip()
cmd = data.decode("ascii", errors="ignore").strip()
if cmd == "GT":
# Get batch total
# If no counts, generate random simulation data
# if not self.batch_counts and self.motor_has_run:
if True:
self._simulate_counting()
total = sum(self.batch_counts.values())
response = f"BT{total:08d}"
self.logger.info(f"Get batch total: {total}")
self.logger.info(f"Get batch total: {total} coins")
return self.create_message(response)
elif cmd == "GS":
# Get subtotal
total = sum(self.sub_counts.values())
response = f"BS{total:08d}"
self.logger.info(f"Get subtotal: {total}")
self.logger.info(f"Get subtotal: {total} coins")
return self.create_message(response)
elif cmd == "GG":
# Get grand total
total = sum(self.grand_counts.values())
response = f"BG{total:08d}"
self.logger.info(f"Get grand total: {total}")
self.logger.info(f"Get grand total: {total} coins")
return self.create_message(response)
elif cmd.startswith("GD"):
@ -174,7 +175,7 @@ class GlorySimulator:
denom_str = cmd[2:]
count = self.partial_counts.get(denom_str, 0)
response = f"BD{count:08d}"
self.logger.info(f"Get partial count for {denom_str}: {count}")
self.logger.info(f"Get partial count for {denom_str}: {count} coins")
return self.create_message(response)
elif cmd.startswith("GT") and len(cmd) > 2:
@ -182,7 +183,7 @@ class GlorySimulator:
denom_str = cmd[2:]
count = self.batch_counts.get(denom_str, 0)
response = f"BT{count:08d}"
self.logger.info(f"Get batch count for {denom_str}: {count}")
self.logger.info(f"Get batch count for {denom_str}: {count} coins")
return self.create_message(response)
elif cmd == "GI":
@ -205,7 +206,7 @@ class GlorySimulator:
def handle_bag_stop_command(self, data: bytes) -> bytes:
"""Handle SV - Set bag stop command"""
cmd = data.decode('ascii', errors='ignore').strip()
cmd = data.decode("ascii", errors="ignore").strip()
if len(cmd) >= 13 and cmd.startswith("SV"):
denom_str = cmd[2:5]
@ -224,7 +225,7 @@ class GlorySimulator:
def handle_motor_command(self, data: bytes) -> bytes:
"""Handle MG/MS - Motor control commands"""
cmd = data.decode('ascii', errors='ignore').strip()
cmd = data.decode("ascii", errors="ignore").strip()
if cmd == "MG":
# Start motor
@ -251,7 +252,7 @@ class GlorySimulator:
def handle_accept_command(self, data: bytes) -> bytes:
"""Handle AB/AS/AG - Accept commands"""
cmd = data.decode('ascii', errors='ignore').strip()
cmd = data.decode("ascii", errors="ignore").strip()
if cmd == "AB" or cmd == "Ab":
# Accept batch
@ -281,7 +282,7 @@ class GlorySimulator:
def handle_partial_count_command(self, data: bytes) -> bytes:
"""Handle PC - Set partial count command"""
cmd = data.decode('ascii', errors='ignore').strip()
cmd = data.decode("ascii", errors="ignore").strip()
if len(cmd) >= 13 and cmd.startswith("PC"):
denom_str = cmd[2:5]
@ -300,7 +301,7 @@ class GlorySimulator:
def handle_id_command(self, data: bytes) -> bytes:
"""Handle ID/IS/IG - Set ID number commands"""
cmd = data.decode('ascii', errors='ignore').strip()
cmd = data.decode("ascii", errors="ignore").strip()
if cmd.startswith("ID"):
self.batch_id = cmd[2:14] if len(cmd) >= 14 else cmd[2:]
@ -319,15 +320,40 @@ class GlorySimulator:
def _simulate_counting(self):
"""Simulate coin counting when motor runs"""
# Generate random counts for simulation
denominations = ["005", "010", "025", "050", "100"]
for denom in denominations:
count = random.randint(10, 100)
self.batch_counts[denom] = self.batch_counts.get(denom, 0) + count
self.partial_counts[denom] = self.partial_counts.get(denom, 0) + count
# Standard denominations with realistic count distributions
denominations = {
"001": (20, 150), # Pennies - high count
"005": (10, 100), # Nickels
"010": (10, 80), # Dimes
"025": (5, 60), # Quarters
"050": (0, 20), # Half dollars - rare
"100": (0, 10), # Dollar coins - rare
}
for denom, (min_count, max_count) in denominations.items():
count = random.randint(min_count, max_count)
if count > 0:
# Update batch counts
self.batch_counts[denom] = self.batch_counts.get(denom, 0) + count
# Update partial counts
self.partial_counts[denom] = self.partial_counts.get(denom, 0) + count
# Update subtotal
self.sub_counts[denom] = self.sub_counts.get(denom, 0) + count
# Update grand total
self.grand_counts[denom] = self.grand_counts.get(denom, 0) + count
self.logger.info(f"Counted {count} coins of denomination {denom}")
# Log total value counted
total_value = sum(
self.batch_counts.get(denom, 0) * int(denom)
for denom in denominations.keys()
)
self.logger.info(f"Total value counted in this batch: ${total_value/100:.2f}")
def create_message(self, data: str) -> bytes:
"""Create a message with STX and ETX"""
return bytes([STX]) + data.encode('ascii') + bytes([ETX])
return bytes([STX]) + data.encode("ascii") + bytes([ETX])
def parse_message(self, message: bytes) -> Optional[bytes]:
"""Parse and validate a received message"""
@ -352,7 +378,7 @@ class GlorySimulator:
return None
try:
cmd_str = data.decode('ascii', errors='ignore').strip()
cmd_str = data.decode("ascii", errors="ignore").strip()
cmd = cmd_str[:2]
self.logger.info(f"Received command: {cmd_str}")
@ -435,14 +461,14 @@ class GlorySimulator:
message = bytes(buffer[stx_idx : etx_idx + 1])
buffer = buffer[etx_idx + 1 :]
self.logger.debug(f"RX: {message.hex()}")
self.logger.debug(f"RX: {' '.join(f'{b:02X}' for b in message)}")
# Parse and handle message
parsed_data = self.parse_message(message)
if parsed_data:
response = self.handle_command(parsed_data)
if response:
self.logger.debug(f"TX: {response.hex()}")
self.logger.debug(f"TX: {' '.join(f'{b:02X}' for b in response)}")
self.serial_conn.write(response)
except ValueError:
pass # ETX not found yet