Update
This commit is contained in:
178
pelican.py
178
pelican.py
@ -42,7 +42,7 @@ PASSWORD = "69390274"
|
||||
|
||||
|
||||
class PelicanSimulator:
|
||||
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
|
||||
@ -79,10 +79,7 @@ class PelicanSimulator:
|
||||
200, # 2.00
|
||||
]
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger = logger
|
||||
|
||||
def calc_crc(self, data: bytes) -> int:
|
||||
"""Calculate CRC-16 using the algorithm from the spec"""
|
||||
@ -184,8 +181,12 @@ class PelicanSimulator:
|
||||
random_counts = []
|
||||
for i in range(20):
|
||||
# Generate random counts, with higher probability for lower denominations
|
||||
if i < 8: # Standard denominations (0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2)
|
||||
max_count = max(1, 100 - i * 10) # Lower denominations have higher counts
|
||||
if (
|
||||
i < 8
|
||||
): # Standard denominations (0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2)
|
||||
max_count = max(
|
||||
1, 100 - i * 10
|
||||
) # Lower denominations have higher counts
|
||||
random_counts.append(random.randint(0, max_count))
|
||||
else: # Random denominations
|
||||
random_counts.append(random.randint(0, 20))
|
||||
@ -217,10 +218,21 @@ class PelicanSimulator:
|
||||
total_coins = sum(random_counts)
|
||||
|
||||
# Log detailed count information
|
||||
self.logger.info(f"Current count: {total_coins} total coins, {random_rejected} rejected")
|
||||
self.logger.info(
|
||||
f"Current count: {total_coins} total coins, {random_rejected} rejected"
|
||||
)
|
||||
|
||||
# Log counts for standard denominations
|
||||
standard_denoms = ["0.01", "0.02", "0.05", "0.10", "0.20", "0.50", "1.00", "2.00"]
|
||||
standard_denoms = [
|
||||
"0.01",
|
||||
"0.02",
|
||||
"0.05",
|
||||
"0.10",
|
||||
"0.20",
|
||||
"0.50",
|
||||
"1.00",
|
||||
"2.00",
|
||||
]
|
||||
count_details = []
|
||||
for i in range(8):
|
||||
if random_counts[i] > 0:
|
||||
@ -279,7 +291,9 @@ class PelicanSimulator:
|
||||
self.host_version & 0xFF,
|
||||
]
|
||||
)
|
||||
self.logger.info(f"SW Version: {self.sw_version:08X}, SW Code: {self.sw_code:08X}, Host Version: {self.host_version:08X}")
|
||||
self.logger.info(
|
||||
f"SW Version: {self.sw_version:08X}, SW Code: {self.sw_code:08X}, Host Version: {self.host_version:08X}"
|
||||
)
|
||||
return self.create_message(bytes(response))
|
||||
|
||||
# Variable 0x33 - Machine status
|
||||
@ -317,9 +331,11 @@ class PelicanSimulator:
|
||||
0x02: "Tubing mode",
|
||||
0x20: "Memory",
|
||||
0x40: "Programming",
|
||||
0x80: "Setup"
|
||||
0x80: "Setup",
|
||||
}
|
||||
state_desc = program_states.get(self.program_state, f"Unknown (0x{self.program_state:02X})")
|
||||
state_desc = program_states.get(
|
||||
self.program_state, f"Unknown (0x{self.program_state:02X})"
|
||||
)
|
||||
|
||||
status_desc = []
|
||||
if self.motor_running:
|
||||
@ -356,9 +372,139 @@ class PelicanSimulator:
|
||||
else:
|
||||
formatted_denoms.append(f"Coin{i+1}: 0.{denom:02d}")
|
||||
|
||||
self.logger.info(f"Denomination values (20 coins): {', '.join(formatted_denoms[:8])}")
|
||||
self.logger.info(
|
||||
f"Denomination values (20 coins): {', '.join(formatted_denoms[:8])}"
|
||||
)
|
||||
if len(formatted_denoms) > 8:
|
||||
self.logger.info(f"Additional denominations: {', '.join(formatted_denoms[8:])}")
|
||||
self.logger.info(
|
||||
f"Additional denominations: {', '.join(formatted_denoms[8:])}"
|
||||
)
|
||||
return self.create_message(bytes(response))
|
||||
|
||||
# Variable 0x22 - Coin exit counters (similar to 0x16 but for sorted coins)
|
||||
elif var_num == 0x22:
|
||||
self.logger.info("Responding with coin exit counters")
|
||||
|
||||
# Generate random exit counts for simulation
|
||||
exit_counts = []
|
||||
for i in range(20):
|
||||
if i < 8: # Standard denominations
|
||||
max_count = max(1, 100 - i * 10)
|
||||
exit_counts.append(random.randint(0, max_count))
|
||||
else:
|
||||
exit_counts.append(0) # No exits for unused channels
|
||||
|
||||
response = [CMD_VALUE_RETURNED, 0x00, 0x22]
|
||||
# Add 20 coin exit counters (4 bytes each)
|
||||
for count in exit_counts:
|
||||
response.extend(
|
||||
[
|
||||
(count >> 24) & 0xFF,
|
||||
(count >> 16) & 0xFF,
|
||||
(count >> 8) & 0xFF,
|
||||
count & 0xFF,
|
||||
]
|
||||
)
|
||||
|
||||
total_exits = sum(exit_counts)
|
||||
self.logger.info(f"Coin exit counters: {total_exits} total coins sorted")
|
||||
|
||||
# Log counts for standard denominations
|
||||
standard_denoms = [
|
||||
"0.01",
|
||||
"0.02",
|
||||
"0.05",
|
||||
"0.10",
|
||||
"0.20",
|
||||
"0.50",
|
||||
"1.00",
|
||||
"2.00",
|
||||
]
|
||||
exit_details = []
|
||||
for i in range(8):
|
||||
if exit_counts[i] > 0:
|
||||
exit_details.append(f"{standard_denoms[i]}: {exit_counts[i]}")
|
||||
|
||||
if exit_details:
|
||||
self.logger.info(f"Exit counts: {', '.join(exit_details)}")
|
||||
|
||||
return self.create_message(bytes(response))
|
||||
|
||||
# Variable 0x23 - Get transaction data
|
||||
elif var_num == 0x23:
|
||||
# Need transaction number (2 bytes) after variable number
|
||||
if len(data) < 4:
|
||||
self.logger.error(
|
||||
"Get transaction data request missing transaction number"
|
||||
)
|
||||
return self.create_message(bytes([CMD_VALUE_RETURNED, 0x01]))
|
||||
|
||||
transaction_num = (data[2] << 8) | data[3]
|
||||
self.logger.info(
|
||||
f"Responding with transaction data for transaction #{transaction_num}"
|
||||
)
|
||||
|
||||
# Generate simulated transaction data
|
||||
import time
|
||||
|
||||
current_time = time.localtime()
|
||||
|
||||
response = [CMD_VALUE_RETURNED, 0x00, 0x23]
|
||||
# Transaction number (2 bytes)
|
||||
response.extend([(transaction_num >> 8) & 0xFF, transaction_num & 0xFF])
|
||||
# Cashier number (2 bytes)
|
||||
cashier_num = random.randint(1, 99)
|
||||
response.extend([(cashier_num >> 8) & 0xFF, cashier_num & 0xFF])
|
||||
# Not used yet (1 byte)
|
||||
response.append(0x00)
|
||||
# Hour, Minute
|
||||
response.extend([current_time.tm_hour, current_time.tm_min])
|
||||
# Year (2-digit), Month, Day
|
||||
response.extend(
|
||||
[current_time.tm_year % 100, current_time.tm_mon, current_time.tm_mday]
|
||||
)
|
||||
|
||||
# Fee amount - currency 0 (4 bytes)
|
||||
fee_amount_0 = random.randint(0, 50000) # Up to 500.00
|
||||
response.extend(
|
||||
[
|
||||
(fee_amount_0 >> 24) & 0xFF,
|
||||
(fee_amount_0 >> 16) & 0xFF,
|
||||
(fee_amount_0 >> 8) & 0xFF,
|
||||
fee_amount_0 & 0xFF,
|
||||
]
|
||||
)
|
||||
|
||||
# Fee amount - currency 1 (4 bytes)
|
||||
response.extend([0x00, 0x00, 0x00, 0x00])
|
||||
|
||||
# Coin amount - currency 0 (4 bytes)
|
||||
coin_amount_0 = random.randint(10000, 500000) # 100.00 to 5000.00
|
||||
response.extend(
|
||||
[
|
||||
(coin_amount_0 >> 24) & 0xFF,
|
||||
(coin_amount_0 >> 16) & 0xFF,
|
||||
(coin_amount_0 >> 8) & 0xFF,
|
||||
coin_amount_0 & 0xFF,
|
||||
]
|
||||
)
|
||||
|
||||
# Coin amount - currency 1 (4 bytes)
|
||||
response.extend([0x00, 0x00, 0x00, 0x00])
|
||||
|
||||
# Note amount - currency 0 (4 bytes) / Account number high for CDS
|
||||
response.extend([0x00, 0x00, 0x00, 0x00])
|
||||
|
||||
# Note amount - currency 1 (4 bytes) / Account number low for CDS
|
||||
response.extend([0x00, 0x00, 0x00, 0x00])
|
||||
|
||||
self.logger.info(
|
||||
f"Transaction #{transaction_num}: Cashier {cashier_num}, "
|
||||
f"Date {current_time.tm_year}/{current_time.tm_mon}/{current_time.tm_mday} "
|
||||
f"{current_time.tm_hour:02d}:{current_time.tm_min:02d}, "
|
||||
f"Coin amount: ${coin_amount_0/100:.2f}, Fee: ${fee_amount_0/100:.2f}"
|
||||
)
|
||||
|
||||
return self.create_message(bytes(response))
|
||||
|
||||
else:
|
||||
@ -497,14 +643,14 @@ class PelicanSimulator:
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user