import serial
import time
import binascii
import threading
import struct
import msvcrt

PORT = 'COM26'
BAUDRATE = 9600
REQUEST_HEX = "010313BA0006E0A9"

# --- Funkcja CRC16 Modbus ---
def modbus_crc(data: bytes) -> bytes:
    crc = 0xFFFF
    for pos in data:
        crc ^= pos
        for _ in range(8):
            if crc & 1:
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return crc.to_bytes(2, byteorder='little')


# --- Funkcja do konwersji float -> bytes ---
def convert_float_to_hex(value: float, endian: str = "BE_SWAP") -> list[int]:
    """Zwraca listę 4 bajtów float w podanym kodowaniu endian.

    endian:
        "BE"      – standardowy Big Endian (0x42 F6 E6 66)
        "LE"      – standardowy Little Endian (0x66 E6 F6 42)
        "BE_SWAP" – Big Endian + byte swap (0xE6 0x66 0x42 0xF6)  ← Twój przypadek
        "LE_SWAP" – Little Endian + byte swap
    """
    raw = struct.pack('>f', value)  # zawsze zaczynamy od Big Endian (IEEE754)
    b = list(raw)

    if endian == "BE":
        return b
    elif endian == "LE":
        return b[::-1]
    elif endian == "BE_SWAP":
        return [b[2], b[3], b[0], b[1]]  # słowo niskie najpierw
    elif endian == "LE_SWAP":
        return [b[1], b[0], b[3], b[2]]
    else:
        raise ValueError("Invalid endian type")


# --- Funkcja pomocnicza do tworzenia ramki Modbus ---
def build_response_frame(values, endian="BE_SWAP"):
    """Tworzy ramkę odpowiedzi Modbus z listy wartości float."""
    data = [0x01, 0x03, len(values) * 4 * 1.5]  # 6 rejestrów → 12 bajtów
    data[2] = int(len(values) * 4)  # liczba bajtów danych
    for val in values:
        data.extend(convert_float_to_hex(val, endian))
    return bytes(data)


paused = False


# --- Wątek klawiatury (działa poprawnie) ---
def keyboard_listener():
    global paused
    while True:
        if msvcrt.kbhit():
            key = msvcrt.getch().decode(errors="ignore").lower()
            if key == 'p':
                paused = not paused
                state = "PAUSED" if paused else "RUNNING"
                print(f"\n>>> Program {state}. Press 'P' again to toggle.")
        time.sleep(0.1)


# --- Główna pętla ---
def main():
    global paused

    ser = serial.Serial(PORT, BAUDRATE, bytesize=8, parity='N', stopbits=1, timeout=0.05)
    print(f"Listening on {PORT} at {BAUDRATE} baud...")
    print("Press 'P' to pause/resume reception.")

    expected_request = bytes.fromhex(REQUEST_HEX)
    threading.Thread(target=keyboard_listener, daemon=True).start()

    # --- Ustal dane dynamicznie ---
    RESPONSE_DATA = build_response_frame([543.21, 123.45, 9.87], endian="BE_SWAP")

    try:
        while True:
            if paused:
                time.sleep(0.2)
                continue

            if ser.in_waiting:
                data = ser.read(ser.in_waiting)
                hex_data = binascii.hexlify(data).upper().decode()
                print(f"Received: {hex_data}")

                if data == expected_request:
                    crc = modbus_crc(RESPONSE_DATA)
                    frame = RESPONSE_DATA + crc

                    ser.setRTS(True)
                    time.sleep(0.05)

                    ser.write(frame)
                    ser.flush()

                    time.sleep(0.05)
                    ser.setRTS(False)

                    print(f"Sent response: {binascii.hexlify(frame).upper().decode()}")
                else:
                    print("Unexpected frame received.")

            time.sleep(0.05)

    except KeyboardInterrupt:
        print("\nExiting...")
    finally:
        ser.close()


if __name__ == "__main__":
    main()
