diff --git a/looper.py b/looper.py new file mode 100644 index 0000000..d73eed8 --- /dev/null +++ b/looper.py @@ -0,0 +1,154 @@ +import serial +import struct +import random +import threading +import time +import sys +from pathlib import Path +from datetime import datetime + +# Initial state +toggle_state = 0 + +ser = serial.Serial(port='/dev/ttyACM0', baudrate=115200) + +class Tee(object): + def __init__(self, filename): + self.terminal = sys.stdout + self.log = open(filename, "w") # Use "a" to append + + def write(self, message): + self.terminal.write(message) + self.log.write(message) + + def flush(self): + # This flush method is needed for python 3 compatibility. + self.terminal.flush() + self.log.flush() + +def pack_integers_to_bytes(*integers: int) -> bytes: + # Setup Header (Sync bytes) + sync_bytes = [0x41, 0x52] + + # Process Data + # Mask each input to 8-bit to prevent packing errors + data_bytes = [i & 0xFF for i in integers] + + # Get length + data_length = [len(data_bytes) & 0xFF] + + # Calculate Checksum + # Summing all data bytes, then applying bitwise NOT + total_sum = sum(data_bytes) + rx_checksum = ~total_sum + + rx_checksum_h = (rx_checksum >> 8) & 0xFF + rx_checksum_l = rx_checksum & 0xFF + checksum_bytes = [rx_checksum_h, rx_checksum_l] + + # Combine all components + full_packet = sync_bytes + data_length + data_bytes + checksum_bytes + + # Dynamically create format string + # '!' = Network order, 'B' = unsigned char + # We need as many 'B's as there are elements in full_packet + format_string = f"!{len(full_packet)}B" + + return struct.pack(format_string, *full_packet) + +def trigger_serial_command(): + global toggle_state + global ser + global min_delay_input_f + global max_delay_input_f + + toggle_state = 1 - toggle_state + + if toggle_state == 0: + voltage = 0 + voltage_mv = 0 + state = 0 + + print("VOLTAGE SET TO: OFF") + + else: + # Generate voltage level + voltage = random.randint(9, 30) + voltage_mv = voltage * 1000 + state = 1 + + print("VOLTAGE SET TO: " + str(voltage) + "V") + + # Serial logic + print(f"[{time.strftime('%H:%M:%S')}] SENDING SERIAL COMMAND...") + + command = 83 # 0x53 'S' ASCII + + b1 = (voltage_mv >> 24) & 0xFF # Most Significant Byte + b2 = (voltage_mv >> 16) & 0xFF + b3 = (voltage_mv >> 8) & 0xFF + b4 = voltage_mv & 0xFF + + data = (command, state, b1, b2, b3, b4) + byte_data = pack_integers_to_bytes(*data) + ser.write(serial.to_bytes(byte_data)) + + # Schedule the NEXT random trigger + # random.uniform(0.1, 100.0) provides the random delay in seconds + delay = random.uniform(min_delay_input_f, max_delay_input_f) + print(f"NEXT COMMAND IN {delay:.2f} SECONDS...") + + # Create and start a new timer thread + timer = threading.Timer(delay, trigger_serial_command) + timer.start() + +# Run main +if __name__ == '__main__': + now = datetime.now() + + # Format as string: YYYY-MM-DD HH:MM:SS + timestamp_str = now.strftime("%Y_%m_%d_%H_%M_%S") + build_path = "~/Python/Automotive-Power-Simulator-App/LOGS/" + timestamp_str + ".txt" + log_file = Path(build_path).expanduser() + sys.stdout = Tee(log_file) + print("LOGGING TO: " + str(log_file)) + + # Get time constants + print ("PLEASE ENTER A MINIMUM DELAY VALUE...") + + min_delay_input = input() + min_delay_input_f = float(min_delay_input) + + print("MINIMUM TIME DELAY: " + str(min_delay_input_f)) + + # Get time constants + print ("PLEASE ENTER A MAXIMUM DELAY VALUE...") + + max_delay_input = input() + max_delay_input_f = float(max_delay_input) + + print("MAXIMUM TIME DELAY: " + str(max_delay_input_f)) + + # Start the very first trigger + trigger_serial_command() + # Setup serial port + + try: + while (1): + time.sleep(1) + + except KeyboardInterrupt: + state = 0 + command = 83 # 0x53 'S' ASCII + voltage_mv = 0 + + b1 = (voltage_mv >> 24) & 0xFF # Most Significant Byte + b2 = (voltage_mv >> 16) & 0xFF + b3 = (voltage_mv >> 8) & 0xFF + b4 = voltage_mv & 0xFF + + data = (command, state, b1, b2, b3, b4) + byte_data = pack_integers_to_bytes(*data) + ser.write(serial.to_bytes(byte_data)) + + ser.close() \ No newline at end of file