#!/usr/bin/env python3
"""
Configuration file for LIN automated test scripts

This file contains all hardware-specific settings and tool dependencies.
Modify these values to match your test setup.
"""

# ============================================================================
# Hardware Configuration
# ============================================================================

# MUM (Melexis Universal Master) Configuration
MUM_HOST = '192.168.7.2'  # Default MUM IP address on BeagleBone
MUM_LIN_DEVICE = 'lin0'   # LIN interface name on MUM
MUM_POWER_DEVICE = 'power_out0'  # Power control device name

# LIN Bus Configuration
LIN_BAUDRATE = 19200  # LIN bus baudrate in bps

# Valid NAD range for auto-addressing
VALID_NAD_RANGE = range(0x01, 0x11)  # NADs 0x01 through 0x10

# ============================================================================
# External Tool Dependencies
# ============================================================================

# Python packages required (install with: pip3 install <package>)
REQUIRED_PACKAGES = [
    'pylin',          # LIN bus communication library
    'pymumclient',    # Melexis Universal Master client library
]

# ============================================================================
# Test Parameters
# ============================================================================

# Auto-addressing test defaults
AUTOADDRESSING_DEFAULT_ITERATIONS = 1  # Number of BSM iterations
AUTOADDRESSING_POLL_DURATION = 2.0     # Status polling duration between iterations (seconds)
AUTOADDRESSING_STATUS_POLL_INTERVAL = 0.020  # Status frame poll interval (20ms)

# LED control test defaults
LED_DEFAULT_NAD = 0x01  # Default NAD for LED control test

# Power cycle defaults
POWER_CYCLE_WAIT_TIME = 2.0  # Wait time after power down/up (seconds)

# ============================================================================
# Frame IDs (from 4SEVEN_color_lib_test.ldf)
# ============================================================================

LIN_FRAME_ID_MASTERREQ    = 0x3C  # Diagnostic master request frame
LIN_FRAME_ID_ALM_STATUS   = 0x11  # ALM_Status (slave-to-master, 4 bytes)
LIN_FRAME_ID_ALM_REQ_A    = 0x0A  # ALM_Req_A (master-to-slave, 8 bytes, LED control)
LIN_FRAME_ID_CONFIG_FRAME = 0x06  # ConfigFrame (master-to-slave, 3 bytes)
LIN_FRAME_ID_VF_FRAME     = 0x13  # VF_Frame (slave-to-master, 8 bytes, LED forward voltages + VLED)
LIN_FRAME_ID_PWM_WO_COMP  = 0x15  # PWM_wo_Comp (slave-to-master, 8 bytes, PWM values + VS)

# ============================================================================
# Frame Definitions (from 4SEVEN_color_lib_test.ldf)
# ============================================================================
# Each entry mirrors the LDF Frames section.  The signal tuple is:
#   'SignalName': (start_bit, width_in_bits)
# where start_bit comes from the LDF Frames block and width comes from
# the LDF Signals section.  To update after an LDF change, copy the new
# Frames entry here and adjust widths from the Signals section.
#
# NAD selection for ALM_Req_A:
#   node responds if  AmbLightLIDFrom <= ALMNadNo <= AmbLightLIDTo
#   single node  ->  set both to the target NAD
#   broadcast    ->  AmbLightLIDFrom=0x01, AmbLightLIDTo=0xFF

# ALM_Req_A: 0x0A, Master_Node, 8
ALM_REQ_A_FRAME = {
    'frame_id': LIN_FRAME_ID_ALM_REQ_A,
    'length': 8,
    'signals': {
        'AmbLightColourRed':   (0,  8),   # AmbLightColourRed,   0;
        'AmbLightColourGreen': (8,  8),   # AmbLightColourGreen, 8;
        'AmbLightColourBlue':  (16, 8),   # AmbLightColourBlue,  16;
        'AmbLightIntensity':   (24, 8),   # AmbLightIntensity,   24;
        'AmbLightUpdate':      (32, 2),   # AmbLightUpdate,      32;
        'AmbLightMode':        (34, 6),   # AmbLightMode,        34;
        'AmbLightDuration':    (40, 8),   # AmbLightDuration,    40;
        'AmbLightLIDFrom':     (48, 8),   # AmbLightLIDFrom,     48;
        'AmbLightLIDTo':       (56, 8),   # AmbLightLIDTo,       56;
    },
}

# ALM_Status: 0x11, ALM_Node, 4
ALM_STATUS_FRAME = {
    'frame_id': LIN_FRAME_ID_ALM_STATUS,
    'length': 4,
    'signals': {
        'ALMNadNo':         (0,  8),  # ALMNadNo,         0;
        'ALMVoltageStatus': (8,  4),  # ALMVoltageStatus, 8;
        'ALMThermalStatus': (12, 4),  # ALMThermalStatus, 12;
        'ALMNVMStatus':     (16, 4),  # ALMNVMStatus,     16;
        'ALMLEDState':      (20, 2),  # ALMLEDState,      20;
        'SigCommErr':       (24, 1),  # SigCommErr,       24;
    },
}

# ConfigFrame: 6, Master_Node, 3
CONFIG_FRAME = {
    'frame_id': LIN_FRAME_ID_CONFIG_FRAME,
    'length': 3,
    'signals': {
        'ConfigFrame_Calibration':        (0,  1),  # ConfigFrame_Calibration,        0;
        'ConfigFrame_EnableDerating':     (1,  1),  # ConfigFrame_EnableDerating,     1;
        'ConfigFrame_EnableCompensation': (2,  1),  # ConfigFrame_EnableCompensation, 2;
        'ConfigFrame_MaxLM':              (3, 16),  # ConfigFrame_MaxLM,              3;
    },
}

# VF_Frame: 19 (0x13), ALM_Node, 8
VF_FRAME = {
    'frame_id': LIN_FRAME_ID_VF_FRAME,
    'length': 8,
    'signals': {
        'VF_Frame_Red_VF':   (0,  16),  # VF_Frame_Red_VF,   0;
        'VF_Frame_Green_VF': (16, 16),  # VF_Frame_Green_VF, 16;
        'VF_Frame_Blue1_VF': (32, 16),  # VF_Frame_Blue1_VF, 32;
        'VF_Frame_VLED':     (48, 16),  # VF_Frame_VLED,     48;
    },
}

# PWM_wo_Comp: 21 (0x15), ALM_Node, 8
PWM_WO_COMP_FRAME = {
    'frame_id': LIN_FRAME_ID_PWM_WO_COMP,
    'length': 8,
    'signals': {
        'PWM_wo_Comp_Red':   (0,  16),  # PWM_wo_Comp_Red,   0;
        'PWM_wo_Comp_Green': (16, 16),  # PWM_wo_Comp_Green, 16;
        'PWM_wo_Comp_Blue':  (32, 16),  # PWM_wo_Comp_Blue,  32;
        'VF_Frame_VS':       (48, 16),  # VF_Frame_VS,       48;
    },
}


def pack_frame(frame_def, **signals):
    """Pack signal values into a byte list using a frame definition.

    Unlisted signals default to 0.  Bit ordering follows the LDF/LIN
    convention: bit 0 of the signal sits at start_bit in the frame,
    packed little-endian within each byte.
    """
    data = bytearray(frame_def['length'])
    for name, value in signals.items():
        start_bit, width = frame_def['signals'][name]
        value = int(value) & ((1 << width) - 1)
        for i in range(width):
            bit_pos = start_bit + i
            if value & (1 << i):
                data[bit_pos // 8] |= 1 << (bit_pos % 8)
    return list(data)


def unpack_frame(frame_def, data):
    """Unpack a received byte sequence into a dict of signal values."""
    result = {}
    for name, (start_bit, width) in frame_def['signals'].items():
        value = 0
        for i in range(width):
            bit_pos = start_bit + i
            if data[bit_pos // 8] & (1 << (bit_pos % 8)):
                value |= 1 << i
        result[name] = value
    return result

# ============================================================================
# BSM-SNPD Protocol Constants
# ============================================================================

BSM_NAD_BROADCAST = 0x7F  # Broadcast NAD for BSM frames
BSM_PCI = 0x06            # Protocol Control Information (6 data bytes)
BSM_SID = 0xB5            # Service ID for BSM-SNPD
BSM_SUPPLIER_ID_LSB = 0xFF  # Supplier ID LSB (broadcast)
BSM_SUPPLIER_ID_MSB = 0x7F  # Supplier ID MSB (broadcast)

# BSM Subfunctions
BSM_SUBF_INIT = 0x01      # Initialize auto-addressing
BSM_SUBF_ASSIGN = 0x02    # Assign NAD
BSM_SUBF_STORE = 0x03     # Store to NVM
BSM_SUBF_FINALIZE = 0x04  # Finalize auto-addressing

# Timing parameters (matching babylin behavior)
BSM_INIT_DELAY = 0.050    # Delay after INIT subfunction (50ms)
BSM_FRAME_DELAY = 0.020   # Delay between frames (20ms)
