1282 lines
40 KiB
C
1282 lines
40 KiB
C
/*
|
||
* kline.c
|
||
*
|
||
* Created on: Aug 18, 2025
|
||
* Author: herli
|
||
*/
|
||
|
||
#include "kline.h"
|
||
#include "IKW1281Connection.h"
|
||
#include "ee_manager.h"
|
||
|
||
|
||
#include "stdint.h"
|
||
#include "main.h"
|
||
#include <string.h>
|
||
#include <math.h> // for roundf
|
||
|
||
static int ReceivePacket_Tmo(ParsedPacket *out, uint32_t overall_ms);
|
||
static void KLine_HandleWriteEeprom(const uint8_t *body, uint8_t body_len);
|
||
static void KLine_HandleReadRomEeprom(const uint8_t *body, uint8_t body_len);
|
||
static void KLine_HandleReadIdentAddress(const uint8_t *body, uint8_t body_len);
|
||
static void KLine_HandleReadEeprom(const uint8_t *body, uint8_t body_len);
|
||
static void KLine_HandlePassword(const uint8_t *body, uint8_t body_len);
|
||
static void KLine_HandleEnd(void);
|
||
static void KLine_HandleCustomPacket(const uint8_t *body, uint8_t body_len);
|
||
static void KLine_TerminateAndRearm(void);
|
||
|
||
extern float dFi;
|
||
|
||
int BitBang = 0;
|
||
uint8_t K_RxData[1];
|
||
|
||
volatile uint8_t kline_connection_status = 0; // 0 = idle, 1 = busy, 2 = hold
|
||
|
||
// --- config you can change ---
|
||
|
||
extern UART_HandleTypeDef huart1;
|
||
extern TIM_HandleTypeDef htim17;
|
||
|
||
typedef enum {
|
||
FIVE_IDLE = 0,
|
||
FIVE_WAIT_FIRST_SAMPLE,
|
||
FIVE_SAMPLING
|
||
} five_state_t;
|
||
|
||
volatile five_state_t five_state = FIVE_IDLE;
|
||
volatile uint8_t five_bit_index = 0; // 0..7
|
||
volatile uint8_t five_byte = 0; // assembled address byte
|
||
volatile uint32_t next_interval_ms = 0; // for dynamic ARR updates
|
||
volatile uint8_t five_active = 0;
|
||
|
||
#define K5_BIT_MS 200U
|
||
#define K5_FIRST_SAMPLE_MS 300U // 1.5 * 200ms
|
||
|
||
ControllerInfo info = {
|
||
"0002246 826E",
|
||
"1469947023",
|
||
"C062_2.V60",
|
||
"C062_0.P64",
|
||
"0281001827",
|
||
"971057"
|
||
};
|
||
|
||
static void KLine5_ResetToIdle(void);
|
||
//void KLine_Rearm5Baud(void);
|
||
static void KLine5_StartSampling(void);
|
||
static void KLine5_ScheduleTimer(uint32_t delay_ms);
|
||
static void KLine_EnterUartMode_9600(void);
|
||
static void KLine_SendSyncAndKeywords(void);
|
||
|
||
static void TIM17_ConfigTick100us(void)
|
||
{
|
||
// Stop & clean
|
||
__HAL_TIM_DISABLE(&htim17);
|
||
__HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
|
||
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
|
||
|
||
// Compute timer clock (APB2 x2 if prescaler != 1)
|
||
uint32_t timclk = HAL_RCC_GetPCLK2Freq();
|
||
RCC_ClkInitTypeDef clk; uint32_t fl;
|
||
HAL_RCC_GetClockConfig(&clk, &fl);
|
||
if (clk.APB2CLKDivider != RCC_HCLK_DIV1) timclk *= 2U;
|
||
|
||
// 10 kHz tick => 0.1 ms per tick
|
||
const uint32_t target_hz = 10000U;
|
||
uint32_t psc = (timclk + target_hz - 1U) / target_hz - 1U; // round up
|
||
if (psc > 0xFFFFU) psc = 0xFFFFU;
|
||
|
||
__HAL_TIM_SET_PRESCALER(&htim17, psc);
|
||
__HAL_TIM_SET_AUTORELOAD(&htim17, 9U); // dummy non-zero ARR
|
||
__HAL_TIM_SET_COUNTER(&htim17, 0U);
|
||
|
||
// One-pulse; URS so only overflow triggers update IRQ (UG won't)
|
||
htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS);
|
||
|
||
// Load PSC/ARR now without causing an IRQ
|
||
htim17.Instance->EGR = TIM_EGR_UG;
|
||
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
|
||
|
||
// NVIC
|
||
HAL_NVIC_SetPriority(TIM1_TRG_COM_TIM17_IRQn, 6, 0);
|
||
HAL_NVIC_EnableIRQ(TIM1_TRG_COM_TIM17_IRQn);
|
||
}
|
||
|
||
// Schedule an interrupt after delay_ms using TIM17 periodic update
|
||
static void KLine5_ScheduleTimer(uint32_t delay_ms)
|
||
{
|
||
// 0.1 ms per tick
|
||
uint32_t ticks = delay_ms * 10U;
|
||
if (ticks < 1U) ticks = 1U;
|
||
if (ticks > 0x10000U) ticks = 0x10000U; // clamp (ARR is 16-bit)
|
||
|
||
// Stop, clear, program ARR/CNT
|
||
__HAL_TIM_DISABLE(&htim17);
|
||
__HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
|
||
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
|
||
|
||
__HAL_TIM_SET_AUTORELOAD(&htim17, (uint16_t)(ticks - 1U)); // ARR = ticks-1
|
||
__HAL_TIM_SET_COUNTER(&htim17, 0U);
|
||
|
||
// Apply registers, then clear UIF so we don't fire instantly
|
||
htim17.Instance->EGR = TIM_EGR_UG;
|
||
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
|
||
|
||
// Ensure one-pulse remains set
|
||
htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS);
|
||
|
||
// Arm and start
|
||
__HAL_TIM_ENABLE_IT(&htim17, TIM_IT_UPDATE);
|
||
__HAL_TIM_ENABLE(&htim17); // CEN
|
||
}
|
||
|
||
// ===== Initialization entry (call once in main after MX_* inits) =====
|
||
void KLine_Slave_Init(void)
|
||
{
|
||
// We want to START in GPIO/EXTI mode on PA10 (falling edge).
|
||
// Ensure UART is not owning PA10:
|
||
HAL_UART_DeInit(&huart1);
|
||
|
||
// PA10 should already be configured by CubeMX as GPIO Input + EXTI (falling) with pull-up.
|
||
// If not, configure here manually.
|
||
|
||
TIM17_ConfigTick100us();
|
||
KLine_Rearm5Baud(); // <— call it here
|
||
|
||
//KLine5_ResetToIdle();
|
||
KLine_SetDFI_Value(dFi);
|
||
|
||
}
|
||
|
||
// ===== Return to IDLE (re-arm EXTI) =====
|
||
static void KLine5_ResetToIdle(void)
|
||
{
|
||
five_state = FIVE_IDLE;
|
||
five_bit_index = 0;
|
||
five_byte = 0;
|
||
five_active = 0;
|
||
|
||
kline_connection_status = 0; // <-- Disconnected
|
||
|
||
ResetPacketCounter();
|
||
|
||
// Stop timer and disable its IRQ
|
||
HAL_TIM_Base_Stop_IT(&htim17);
|
||
__HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
|
||
|
||
// Clear EXTI and re-arm falling edge on PA10
|
||
__HAL_GPIO_EXTI_CLEAR_IT(KLINE_RX_PIN);
|
||
|
||
HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
|
||
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
|
||
}
|
||
|
||
// ===== Start 5-baud sampling sequence =====
|
||
static void KLine5_StartSampling(void)
|
||
{
|
||
five_active = 1;
|
||
five_state = FIVE_WAIT_FIRST_SAMPLE;
|
||
five_bit_index = 0;
|
||
five_byte = 0;
|
||
|
||
// First sample at 1.5 bit times (300 ms)
|
||
KLine5_ScheduleTimer(K5_FIRST_SAMPLE_MS);
|
||
}
|
||
|
||
// ===== GPIO EXTI ISR hook =====
|
||
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
|
||
{
|
||
if (GPIO_Pin == KLINE_RX_PIN) // PA10 (K-Line RX)
|
||
{
|
||
// Falling edge = start bit candidate
|
||
if (five_state == FIVE_IDLE)
|
||
{
|
||
// Debounce / sanity: ensure line is still low ~a bit later if you want (optional)
|
||
// Disable EXTI so we don't retrigger during the 5-baud window
|
||
HAL_NVIC_DisableIRQ(EXTI15_10_IRQn);
|
||
KLine5_StartSampling();
|
||
}
|
||
}
|
||
}
|
||
void EXTI15_10_IRQHandler(void)
|
||
{
|
||
HAL_GPIO_EXTI_IRQHandler(KLINE_RX_PIN);
|
||
}
|
||
volatile uint8_t k5_done = 0;
|
||
volatile uint32_t k5_ready_at = 0; // tick when we can switch to UART
|
||
|
||
volatile uint8_t kline_cmd_pending = 0; // 0 = none, 1 = a command start is pending
|
||
volatile uint8_t kline_first_len = 0; // first byte (length) captured by ISR
|
||
|
||
#define KEEPALIVE_TIMEOUT_MS 1000U
|
||
static volatile uint32_t kl_session_deadline = 0;
|
||
|
||
// === W-time handshake state ===
|
||
static uint8_t hs_state = 0;
|
||
static uint32_t hs_next = 0;
|
||
|
||
static void KLine_SessionKick(void); // forward
|
||
|
||
static void KLine_SessionKick(void)
|
||
{
|
||
kl_session_deadline = HAL_GetTick() + KEEPALIVE_TIMEOUT_MS;
|
||
}
|
||
|
||
void KLine_Service(void)
|
||
{
|
||
// Always progress the TX byte pump
|
||
KLine_BytePump_Service();
|
||
|
||
// After 5-baud address done: switch to UART and schedule handshake
|
||
if (k5_done && (int32_t)(HAL_GetTick() - k5_ready_at) >= 0) {
|
||
k5_done = 0;
|
||
KLine_EnterUartMode_9600();
|
||
KLine_BytePump_Init();
|
||
|
||
// Arm RX
|
||
HAL_UART_Receive_IT(&huart1, K_RxData, 1);
|
||
|
||
// Schedule W1 -> 0x55 -> W2 -> KW LSB -> W3 -> KW MSB
|
||
hs_state = 1;
|
||
hs_next = HAL_GetTick() + KWP_W1_MS;
|
||
kline_connection_status = 1;
|
||
return;
|
||
}
|
||
|
||
// Handshake sequencer
|
||
if (hs_state != 0) {
|
||
if ((int32_t)(HAL_GetTick() - hs_next) >= 0) {
|
||
if (hs_state == 1) {
|
||
uint8_t b = SYNC_BYTE; // 0x55
|
||
KLine_TxStart(&b, 1, 0, 0); // no complement
|
||
hs_state = 2; hs_next = HAL_GetTick() + KWP_W2_MS;
|
||
} else if (hs_state == 2) {
|
||
uint8_t b = KEYWORD_LSB;
|
||
KLine_TxStart(&b, 1, 0, 0);
|
||
hs_state = 3; hs_next = HAL_GetTick() + KWP_W3_MS;
|
||
} else if (hs_state == 3) {
|
||
uint8_t b = KEYWORD_MSB;
|
||
KLine_TxStart(&b, 1, 0, 0);
|
||
hs_state = 4; // done
|
||
}
|
||
}
|
||
if (hs_state != 4) return;
|
||
|
||
// After keywords, send your initial ASCII ident stream
|
||
if (!KLine_TxBusy()) {
|
||
uint8_t combined[64] = {0};
|
||
size_t n = BuildCombinedFromInfo(&info, combined, sizeof(combined));
|
||
(void)Slave_SendAsciiStream_WithAcks(combined, n);
|
||
KLine_SessionKick();
|
||
kline_connection_status = 2;
|
||
hs_state = 0;
|
||
}
|
||
return;
|
||
}
|
||
|
||
if(kline_connection_status == 2){
|
||
ParsedPacket packet = {0};
|
||
if (ReceivePacket_Tmo(&packet, 800)) { //waiting for an ack packet of stay alive
|
||
// any packet seen → extend session
|
||
KLine_SessionKick();
|
||
// keep-alive: ACK → ACK
|
||
switch (packet.title) {
|
||
case PACKET_CMD_ReadIdent:
|
||
uint8_t combined[64] = {0};
|
||
size_t n = BuildCombinedFromInfo(&info, combined, sizeof(combined));
|
||
HAL_Delay(20);
|
||
Slave_SendAsciiStream_WithAcks(combined, n);
|
||
KLine_SessionKick();
|
||
break;
|
||
case PACKET_CMD_ReadIdentAdress: // readIdentAddressCMD
|
||
KLine_HandleReadIdentAddress(&packet.raw[3], (uint8_t)(packet.length - 4));
|
||
packet.type = PACKET_TYPE_UNKNOWN;
|
||
break;
|
||
case PACKET_CMD_FaultCodesRead:
|
||
KLine_DTCResponse(); // <-- respond
|
||
break;
|
||
case PACKET_CMD_ACK:
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
break;
|
||
case PACKET_CMD_LoginEeprom: // password/login for EEPROM
|
||
KLine_HandlePassword(&packet.raw[3], (uint8_t)(packet.length - 4));
|
||
packet.type = PACKET_TYPE_UNKNOWN;
|
||
break;
|
||
case PACKET_CMD_WriteEeprom: // 0x1A
|
||
KLine_HandleWriteEeprom(&packet.raw[3], (uint8_t)(packet.length - 4));
|
||
packet.type = PACKET_TYPE_UNKNOWN; // or PACKET_TYPE_WRITE_EEPROM_RESPONSE if you add it
|
||
break;
|
||
case PACKET_CMD_ReadEeprom: // 0x19
|
||
KLine_HandleReadEeprom(&packet.raw[3], (uint8_t)(packet.length - 4));
|
||
packet.type = PACKET_TYPE_READ_EEPROM_RESPONSE; // we just sent EF
|
||
break;
|
||
case PACKET_CMD_ReadRomEeprom: // 0x03
|
||
KLine_HandleReadRomEeprom(&packet.raw[3], (uint8_t)(packet.length - 4));
|
||
packet.type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE;
|
||
break;
|
||
case PACKET_CMD_End:
|
||
KLine_HandleEnd();
|
||
packet.type = PACKET_TYPE_UNKNOWN; // we’re done; session is torn down
|
||
break; // early return is fine
|
||
|
||
default:
|
||
KLine_HandleCustomPacket(&packet.raw[2], (uint8_t)(packet.length - 4));
|
||
break;
|
||
}
|
||
}
|
||
if (tick_diff(HAL_GetTick(), kl_session_deadline) >= 0) {
|
||
//KLine_HandleEnd();
|
||
KLine_TerminateAndRearm();
|
||
return;
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
// ===== TIM17 ISR hook: sampling scheduler =====
|
||
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
|
||
{
|
||
if (htim->Instance != TIM17) return;
|
||
|
||
if (!five_active) {
|
||
HAL_TIM_Base_Stop_IT(&htim17);
|
||
return;
|
||
}
|
||
|
||
if (five_state == FIVE_WAIT_FIRST_SAMPLE || five_state == FIVE_SAMPLING)
|
||
{
|
||
// Sample the data bit in the middle of its window
|
||
GPIO_PinState pin = HAL_GPIO_ReadPin(KLINE_GPIO_PORT, KLINE_RX_PIN);
|
||
uint8_t bit = (pin == GPIO_PIN_SET) ? 1U : 0U;
|
||
|
||
// LSB first
|
||
five_byte |= (bit << five_bit_index);
|
||
five_bit_index++;
|
||
|
||
if (five_bit_index >= 8)
|
||
{
|
||
// Done. We can ignore stop bit timing and switch to UART mode.
|
||
five_active = 0;
|
||
HAL_TIM_Base_Stop_IT(&htim17);
|
||
|
||
// Optional: check address value if you only respond to specific ones
|
||
if (five_byte != 0xF1) { KLine5_ResetToIdle(); return; }
|
||
|
||
kline_connection_status = 1; // <-- Connected
|
||
|
||
|
||
//HAL_Delay(25);
|
||
kline_connection_status = 1; // connected
|
||
k5_ready_at = HAL_GetTick() + 200; // W1 ~20..300ms; choose ~200ms margin
|
||
k5_done = 1; // signal main loop
|
||
// From now on you're in USART mode; if you ever want to
|
||
// go back to "listen for 5-baud", DeInit UART and call KLine5_ResetToIdle().
|
||
}
|
||
else
|
||
{
|
||
// Schedule next bit sample in 200 ms
|
||
five_state = FIVE_SAMPLING;
|
||
KLine5_ScheduleTimer(K5_BIT_MS);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void KLine_SetPinsToUartAF(void)
|
||
{
|
||
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
||
__HAL_RCC_GPIOA_CLK_ENABLE();
|
||
|
||
// PA9 = TX, PA10 = RX for USART1, AF7
|
||
GPIO_InitStruct.Pin = KLINE_TX_PIN | KLINE_RX_PIN;
|
||
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
||
GPIO_InitStruct.Pull = GPIO_PULLUP; // K-line transceiver RX usually idle-high
|
||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
||
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
|
||
HAL_GPIO_Init(KLINE_GPIO_PORT, &GPIO_InitStruct);
|
||
}
|
||
|
||
// ===== Switch PA9/PA10 into USART1 9600-8N1 mode =====
|
||
static void KLine_EnterUartMode_9600(void)
|
||
{
|
||
// Make sure peripheral is clean
|
||
HAL_UART_DeInit(&huart1);
|
||
|
||
// Restore pins to AF7
|
||
KLine_SetPinsToUartAF();
|
||
|
||
// Re-init only with HAL (no MX_ call needed)
|
||
huart1.Instance = USART1;
|
||
huart1.Init.BaudRate = 9600;
|
||
huart1.Init.WordLength = UART_WORDLENGTH_8B;
|
||
huart1.Init.StopBits = UART_STOPBITS_1;
|
||
huart1.Init.Parity = UART_PARITY_NONE;
|
||
huart1.Init.Mode = UART_MODE_TX_RX;
|
||
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
|
||
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
|
||
if (HAL_UART_Init(&huart1) != HAL_OK) {
|
||
Error_Handler();
|
||
}
|
||
|
||
static uint8_t rx;
|
||
HAL_UART_Receive_IT(&huart1, &rx, 1);
|
||
}
|
||
|
||
// ===== Send sync + keywords =====
|
||
static void KLine_SendSyncAndKeywords(void)
|
||
{
|
||
uint8_t buf[3] = {0x55, KEYWORD_LSB, KEYWORD_MSB};
|
||
//uint8_t buf[2] = {KEYWORD_LSB, KEYWORD_MSB};
|
||
//WriteByteRaw(SYNC_BYTE);
|
||
//WriteByteRaw(KEYWORD_LSB);
|
||
//WriteByteAndReadAck(KEYWORD_MSB);
|
||
HAL_UART_Transmit(&huart1, buf, sizeof(buf), 200);
|
||
}
|
||
void KLine_Rearm5Baud(void)
|
||
{
|
||
// 1. DeInit UART1 so PA9/PA10 return to GPIO state
|
||
HAL_UART_DeInit(&huart1);
|
||
|
||
// Make sure SYSCFG is clocked (EXTI routing)
|
||
__HAL_RCC_SYSCFG_CLK_ENABLE();
|
||
|
||
GPIO_InitTypeDef giTX = {0};
|
||
giTX.Pin = KLINE_TX_PIN; // PA9
|
||
//giTX.Mode = GPIO_MODE_OUTPUT_PP; // deterministic high
|
||
//giTX.Pull = GPIO_NOPULL;
|
||
giTX.Mode = GPIO_MODE_INPUT; // deterministic high
|
||
giTX.Pull = GPIO_PULLUP;
|
||
giTX.Speed = GPIO_SPEED_FREQ_LOW;
|
||
HAL_GPIO_Init(KLINE_GPIO_PORT, &giTX);
|
||
HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_TX_PIN, GPIO_PIN_SET); // idle high
|
||
|
||
// 2. Reconfigure PA10 back as GPIO input with EXTI (falling edge)
|
||
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
||
GPIO_InitStruct.Pin = KLINE_RX_PIN;
|
||
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
|
||
GPIO_InitStruct.Pull = GPIO_PULLUP;
|
||
HAL_GPIO_Init(KLINE_GPIO_PORT, &GPIO_InitStruct);
|
||
|
||
// 3. Clear pending EXTI interrupt for PA10
|
||
//__HAL_GPIO_EXTI_CLEAR_FLAG(KLINE_RX_PIN); //TODO PROBAR SI FUNCIONA PORQUE ANTES ESTABA CON FLAG Y IBA
|
||
__HAL_GPIO_EXTI_CLEAR_IT(KLINE_RX_PIN); // not _CLEAR_FLAG
|
||
|
||
//HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
|
||
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 0); // pick a prio that fits your app
|
||
|
||
HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
|
||
|
||
|
||
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
|
||
|
||
// 4. Reset state machine variables (your existing helper)
|
||
KLine5_ResetToIdle();
|
||
}
|
||
|
||
|
||
// ===== Optional: USART1 IRQ callback =====
|
||
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
|
||
{
|
||
if (huart->Instance != USART1) return;
|
||
|
||
uint8_t byte = K_RxData[0];
|
||
KLine_OnByteReceived(byte); // <-- NEW: central RX handler
|
||
|
||
HAL_UART_Receive_IT(&huart1, K_RxData, 1); // re-arm RX
|
||
}
|
||
|
||
void KLine_ServiceCommands(void)
|
||
{
|
||
if (!kline_cmd_pending) return; // nothing queued
|
||
|
||
// Clear the event first to avoid reentrancy/races
|
||
kline_cmd_pending = 0;
|
||
|
||
// Process exactly one full packet now (uses your timeout reads for remaining bytes)
|
||
ParsedPacket pkt;
|
||
if (KLine_ReceiveCMD(&pkt)) {
|
||
// Optionally: extend session on any well-formed packet
|
||
KLine_SessionKick();
|
||
|
||
// If you prefer handling keep-alive here:
|
||
if (pkt.title == PACKET_CMD_ACK) {
|
||
SendAckPacket(); // ACK → ACK
|
||
KLine_SessionKick();
|
||
}
|
||
// For FaultCodesRead you already call KLine_DTCResponse() inside KLine_ReceiveCMD()
|
||
// so nothing more to do here.
|
||
}
|
||
}
|
||
|
||
int KLine_ReceiveCMD(ParsedPacket *out){
|
||
ParsedPacket packet = (ParsedPacket){0};
|
||
|
||
uint8_t index = 0, v;
|
||
|
||
uint8_t packetLength = K_RxData[0];
|
||
packet.raw[index++] = packetLength;
|
||
|
||
if (!ReadPacketCounter_Tmo(100, &v)) return 0; // or call ReadPacketCounter() if you need lock-in
|
||
uint8_t packetCounter = v;
|
||
packet.raw[index++] = packetCounter;
|
||
|
||
if (!ReadAndAckByte_Tmo(20, &v)) return 0;
|
||
uint8_t packetCommand = v;
|
||
packet.raw[index++] = packetCommand;
|
||
|
||
for (int i = 0; i < packetLength - 3; i++) {
|
||
if (!ReadAndAckByte_Tmo(20, &v)) return 0;
|
||
packet.raw[index++] = v;
|
||
}
|
||
|
||
if (!ReadByte_Tmo(20, &v)) return 0;
|
||
if (v != PACKET_END_EXPECTED) return 0;
|
||
packet.raw[index++] = v;
|
||
packet.length = index;
|
||
|
||
packet.title = packetCommand;
|
||
switch (packetCommand) {
|
||
case PACKET_CMD_FaultCodesRead:
|
||
KLine_DTCResponse(); // <-- respond
|
||
packet.type = PACKET_TYPE_UNKNOWN; // (we don’t really use the parsed packet after responding)
|
||
break;
|
||
case PACKET_CMD_ACK: packet.type = PACKET_TYPE_ACK; packet.isAckNak = 1; break;
|
||
case PACKET_CMD_NAK: packet.type = PACKET_TYPE_NAK; packet.isAckNak = 1; break;
|
||
case PACKET_CMD_AsciiData:
|
||
packet.type = (packet.raw[3] == 0x00) ? PACKET_TYPE_CODING_WSC : PACKET_TYPE_ASCII_DATA;
|
||
break;
|
||
case PACKET_CMD_ReadEepromResponse: packet.type = PACKET_TYPE_READ_EEPROM_RESPONSE; break;
|
||
case PACKET_CMD_ReadRomEepromResponse: packet.type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE; break;
|
||
default: packet.type = PACKET_TYPE_UNKNOWN; break;
|
||
}
|
||
|
||
*out = packet;
|
||
return 1;
|
||
}
|
||
|
||
#define DTC_CHUNK_MAX 0x10U // payload bytes including 0xFC
|
||
#define DTC_CMD_SIZE 3U // 0xFC
|
||
#define DTC_BODY_MAX_PER_PKT (DTC_CHUNK_MAX - DTC_CMD_SIZE) // 0x0F = 15
|
||
#define DTC_RECORD_SIZE 8U
|
||
#define DTC_TOTAL_RECORDS 8U // 8*8 = 64 body bytes (your master's buffer)
|
||
|
||
// ---- storage set by app ----
|
||
static FaultCode s_dtc_list[DTC_TOTAL_RECORDS];
|
||
static size_t s_dtc_count = 0;
|
||
|
||
FaultCode dtcs[] = {
|
||
{ 0x5B, 0x20, 0x55 },
|
||
{ 0x50, 0x20, 0x03 },
|
||
{ 0x56, 0x20, 0x56 },
|
||
{ 0x5E, 0x30, 0x60 },
|
||
};
|
||
|
||
void KLine_SetDTCList(const FaultCode *list, size_t count)
|
||
{
|
||
if (!list) { s_dtc_count = 0; return; }
|
||
if (count > DTC_TOTAL_RECORDS) count = DTC_TOTAL_RECORDS;
|
||
for (size_t i = 0; i < count; ++i) s_dtc_list[i] = list[i];
|
||
s_dtc_count = count;
|
||
}
|
||
|
||
// one 8-byte record: first 3 meaningful, rest 0xFF
|
||
static void KLine_FillRecord(uint8_t *dst, const FaultCode *fc)
|
||
{
|
||
dst[0] = fc->dtc;
|
||
dst[1] = fc->status;
|
||
dst[2] = fc->extra;
|
||
dst[3] = 0xFF; dst[4] = 0xFF; dst[5] = 0xFF; dst[6] = 0xFF; dst[7] = 0xFF;
|
||
}
|
||
|
||
// Build the full 64-byte stream (8 records). Pads with "no-fault" records.
|
||
static size_t KLine_BuildDTCStream(uint8_t *out /*>=64*/)
|
||
{
|
||
KLine_SetDTCList(dtcs, 4); //custom test errors
|
||
|
||
KLine_SetDFI_Value(dFi); // whatever current DFI value is; this encodes s_dfi_code
|
||
|
||
size_t w = 0;
|
||
for (size_t i = 0; i < DTC_TOTAL_RECORDS; ++i) {
|
||
FaultCode fc;
|
||
if (i < s_dtc_count) {
|
||
fc = s_dtc_list[i];
|
||
} else {
|
||
fc.dtc = 0x00; fc.status = 0xFF; fc.extra = 0xFF; // no-fault filler
|
||
}
|
||
KLine_FillRecord(&out[w], &fc);
|
||
w += DTC_RECORD_SIZE;
|
||
}
|
||
return w; // 64
|
||
}
|
||
// Send one FC packet (0xFC + <=15 body bytes), wait for tester ACK
|
||
static int KLine_SendFCChunk_AndWaitAck(const uint8_t *body, uint8_t body_len)
|
||
{
|
||
uint8_t payload[DTC_CMD_SIZE + DTC_BODY_MAX_PER_PKT]; // 1 + 15 = 16
|
||
payload[0] = (uint8_t)PACKET_CMD_FaultCodesResponse; // 0xFC
|
||
memcpy(&payload[1], body, body_len);
|
||
|
||
SendPacket(payload, (uint8_t)(1 + body_len));
|
||
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (!ReceivePacket_Tmo(&ack, 200)) return 0;
|
||
if (ack.title != PACKET_CMD_ACK) return 0;
|
||
return 1;
|
||
}
|
||
|
||
// Respond to PACKET_CMD_FaultCodesRead using <=0x10 chunk rule (includes 0xFC)
|
||
void KLine_DTCResponse(void)
|
||
{
|
||
uint8_t stream[DTC_TOTAL_RECORDS * DTC_RECORD_SIZE]; // 64
|
||
size_t total = KLine_BuildDTCStream(stream);
|
||
size_t off = 0;
|
||
|
||
while (off < total) {
|
||
uint8_t body_len = (uint8_t)((total - off) > DTC_BODY_MAX_PER_PKT
|
||
? DTC_BODY_MAX_PER_PKT
|
||
: (total - off));
|
||
if (!KLine_SendFCChunk_AndWaitAck(&stream[off], body_len)) {
|
||
// Optional: KLine_EndSession();
|
||
return;
|
||
}
|
||
off += body_len;
|
||
}
|
||
|
||
// Tell the master the list is complete
|
||
SendAckPacket();
|
||
|
||
// Keep the session alive for immediate keep-alive exchange
|
||
KLine_SessionKick();
|
||
}
|
||
|
||
#define ASCII_CHUNK 13 // 13 info bytes + 1 f6 ascii title
|
||
static size_t append_n(const char *s, size_t n, uint8_t *out, size_t w, size_t maxlen)
|
||
{
|
||
size_t i = 0;
|
||
while (i < n && s[i] != '\0' && w < maxlen) {
|
||
out[w++] = (uint8_t)s[i++];
|
||
}
|
||
return w;
|
||
}
|
||
|
||
size_t BuildCombinedFromInfo(const ControllerInfo *ci, uint8_t *out, size_t maxlen)
|
||
{
|
||
size_t w = 0;
|
||
w = append_n(ci->client_ident, 12, out, w, maxlen);
|
||
w = append_n(ci->unk_ident1, 10, out, w, maxlen);
|
||
w = append_n(ci->soft_info, 10, out, w, maxlen);
|
||
w = append_n(ci->unk_ident2, 10, out, w, maxlen);
|
||
w = append_n(ci->unk_ident3, 10, out, w, maxlen);
|
||
w = append_n(ci->unk_ident4, 6, out, w, maxlen);
|
||
return w;
|
||
}
|
||
int Slave_SendAsciiStream_WithAcks(const uint8_t *data, size_t len)
|
||
{
|
||
uint8_t payload[1 + ASCII_CHUNK]; // [CMD][chunk...]
|
||
size_t off = 0;
|
||
|
||
while (off < len) {
|
||
uint8_t chunk = (uint8_t)((len - off) > ASCII_CHUNK ? ASCII_CHUNK : (len - off));
|
||
payload[0] = (uint8_t)PACKET_CMD_AsciiData;
|
||
for (uint8_t i = 0; i < chunk; ++i) payload[1 + i] = data[off + i];
|
||
|
||
// Send AsciiData packet using your SendPacket()
|
||
SendPacket(payload, (uint8_t)(chunk + 1));
|
||
|
||
// Expect a packet-level ACK from tester
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 400)) {
|
||
if (ack.title == PACKET_CMD_ACK) {
|
||
//SendAckPacket(); // mirror your style: ACK←→ACK
|
||
KLine_SessionKick();
|
||
} else {
|
||
return 0;
|
||
}
|
||
} else {
|
||
return 0;
|
||
}
|
||
|
||
// Optional strict check:
|
||
// if (ack.title != PACKET_CMD_ACK) { /* handle retry/error if you want */ }
|
||
|
||
off += chunk;
|
||
}
|
||
|
||
// Terminal ACK so master breaks out
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
|
||
return 1;
|
||
|
||
/*ParsedPacket rx = {0};
|
||
if (ReceivePacket_Tmo(&rx, 200)) { //waiting for an ack packet of stay alive
|
||
// any packet seen → extend session
|
||
// keep-alive: ACK → ACK
|
||
KLine_SessionKick();
|
||
|
||
if (rx.title == PACKET_CMD_ACK) {
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
return;
|
||
}
|
||
}*/
|
||
|
||
}
|
||
|
||
// Handle ReadIdent request by sending ControllerInfo as AsciiData stream
|
||
/*static void KLine_Slave_HandleReadIdent(const ControllerInfo *ci)
|
||
{
|
||
uint8_t combined[128] = {0};
|
||
size_t n = BuildCombinedFromInfo(ci, combined, sizeof(combined));
|
||
if (n > 0) {
|
||
Slave_SendAsciiStream_WithAcks(combined, n);
|
||
} else {
|
||
uint8_t nak_payload[1] = { (uint8_t)PACKET_CMD_NAK };
|
||
SendPacket(nak_payload, 1);
|
||
}
|
||
}*/
|
||
|
||
|
||
|
||
|
||
|
||
// Receive ONE packet with overall timeout (ms). Returns 1=ok, 0=timeout/error.
|
||
static int ReceivePacket_Tmo(ParsedPacket *out, uint32_t overall_ms)
|
||
{
|
||
uint32_t deadline = HAL_GetTick() + overall_ms;
|
||
|
||
// helper: remaining time
|
||
#define REMAIN_MS() ({ \
|
||
int32_t d__ = (int32_t)(deadline - HAL_GetTick()); \
|
||
(d__ > 0) ? (uint32_t)d__ : 0U; \
|
||
})
|
||
|
||
ParsedPacket packet = (ParsedPacket){0};
|
||
uint8_t index = 0, v;
|
||
uint32_t ms;
|
||
|
||
ms = REMAIN_MS(); if (!ms) return 0;
|
||
if (!ReadAndAckByte_Tmo(ms, &v)) return 0;
|
||
uint8_t packetLength = v;
|
||
packet.raw[index++] = packetLength;
|
||
|
||
ms = REMAIN_MS(); if (!ms) return 0;
|
||
if (!ReadPacketCounter_Tmo(ms, &v)) return 0; // or call ReadPacketCounter() if you need lock-in
|
||
uint8_t packetCounter = v;
|
||
packet.raw[index++] = packetCounter;
|
||
|
||
ms = REMAIN_MS(); if (!ms) return 0;
|
||
if (!ReadAndAckByte_Tmo(ms, &v)) return 0;
|
||
uint8_t packetCommand = v;
|
||
packet.raw[index++] = packetCommand;
|
||
|
||
for (int i = 0; i < packetLength - 3; i++) {
|
||
ms = REMAIN_MS(); if (!ms) return 0;
|
||
if (!ReadAndAckByte_Tmo(ms, &v)) return 0;
|
||
packet.raw[index++] = v;
|
||
}
|
||
|
||
ms = REMAIN_MS(); if (!ms) return 0;
|
||
if (!ReadByte_Tmo(ms, &v)) return 0;
|
||
if (v != PACKET_END_EXPECTED) return 0;
|
||
packet.raw[index++] = v;
|
||
packet.length = index;
|
||
|
||
packet.title = packetCommand;
|
||
switch (packetCommand) {
|
||
case PACKET_CMD_ACK: packet.type = PACKET_TYPE_ACK; packet.isAckNak = 1; break;
|
||
case PACKET_CMD_NAK: packet.type = PACKET_TYPE_NAK; packet.isAckNak = 1; break;
|
||
case PACKET_CMD_AsciiData:
|
||
packet.type = (packet.raw[3] == 0x00) ? PACKET_TYPE_CODING_WSC : PACKET_TYPE_ASCII_DATA;
|
||
break;
|
||
case PACKET_CMD_ReadEepromResponse: packet.type = PACKET_TYPE_READ_EEPROM_RESPONSE; break;
|
||
case PACKET_CMD_ReadRomEepromResponse: packet.type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE; break;
|
||
default: packet.type = PACKET_TYPE_UNKNOWN; break;
|
||
}
|
||
|
||
*out = packet;
|
||
return 1;
|
||
}
|
||
|
||
|
||
void KLine_KeepAlivePoll(void)
|
||
{
|
||
if (!kline_connection_status) return;
|
||
|
||
if (kl_session_deadline == 0) return;
|
||
|
||
// timeout?
|
||
if (tick_diff(HAL_GetTick(), kl_session_deadline) >= 0) {
|
||
KLine_EndSession();
|
||
return;
|
||
}
|
||
|
||
// try to receive one packet quickly
|
||
ParsedPacket rx = {0};
|
||
if (!ReceivePacket_Tmo(&rx, 20)) {
|
||
return; // nothing arrived this slice
|
||
}
|
||
|
||
// any packet seen → extend session
|
||
KLine_SessionKick();
|
||
|
||
// keep-alive: ACK → ACK
|
||
if (rx.title == PACKET_CMD_ACK) {
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
|
||
return;
|
||
}
|
||
|
||
// (optional) handle other tester commands here if needed
|
||
}
|
||
|
||
|
||
void KLine_Slave_Poll(void)
|
||
{
|
||
// Optional: if you later want to respond to ReadIdent on demand, you can:
|
||
// ParsedPacket req;
|
||
// if (ReceivePacket_Tmo(&req, 20)) { if (req.title == PACKET_CMD_ReadIdent) KLine_Slave_HandleReadIdent(&info); }
|
||
}
|
||
|
||
static volatile uint8_t s_eeprom_unlocked_dfi = 0;
|
||
int8_t s_dfi_code = 0; // encoded byte sent as first data of 0xEF
|
||
static volatile uint8_t s_eeprom_unlocked_dfi_write = 0;
|
||
|
||
|
||
// default value to return for address 0x9FFE
|
||
static volatile uint8_t s_rom_unlocked_cust; // from your earlier code
|
||
static uint16_t s_customerChangeAddress = 0x7E36; // example default -> FD body "36 7E"
|
||
static uint16_t s_identAddress = 0x7E56; // will be returned at 0x01 → 0xFE
|
||
static const char s_identAscii[10] = "0470504005"; // 10 bytes, no NUL
|
||
static const char s_serialNumberAscii[6] = "225832";// 6 ASCII bytes, no NUL
|
||
|
||
static const char s_modIndexAscii[6] = "000004";
|
||
|
||
|
||
// Passwords & login-result bodies
|
||
static const uint8_t kPwdBody_DFI[] = { 0x00, 0x03, 0xFF, 0xFF };
|
||
static const uint8_t kLoginBody_DFI[] = { 0x00, 0x00, 0x00, 0xBF, 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45 };
|
||
static const uint8_t kPwdBody_DFI_WRITE[] = { 0x00, 0x03, 0x2F, 0xFF, 'K','H','T','C','A','8','G','0','E'};
|
||
static const uint8_t kPwdBody_CUST[] = { 0x00, 0x00, 0x82, 0x33 };
|
||
static const uint8_t kLoginBody_CUST[] = { 0x00, 0x00, 0x9F, 0xFF, 0x41, 0x31, 0x32, 0x50, 0x35, 0x34, 0x11, 0x02, 0x00 };
|
||
|
||
static const uint8_t kReadIdentAddrBody[] = { 0x02, 0x00, 0xC6 };
|
||
|
||
static const uint8_t kActivateSyncOut[] = { 0x02, 0x88, 0x01, 0x04, 0x06, 0x01 };
|
||
|
||
void KLine_SetDFI_Value(float dfi)
|
||
{
|
||
// encode: master decodes as ((int8_t)raw[3]) * 3.0 / 256.0
|
||
float scaled = dfi * 256.0f / 3.0f;
|
||
int val = (int)lroundf(scaled);
|
||
if (val < -128) val = -128;
|
||
if (val > 127) val = 127;
|
||
s_dfi_code = (int8_t)val;
|
||
}
|
||
float KLine_GetDFI(void)
|
||
{
|
||
return ((float)s_dfi_code * 3.0f) / 256.0f;
|
||
}
|
||
|
||
void KLine_SetCustomerChangeAddress(uint16_t addr) { s_customerChangeAddress = addr; }
|
||
|
||
/*void KLine_SetModIndexAscii(const char *six_digits)
|
||
{
|
||
if (!six_digits) return;
|
||
for (int i = 0; i < 6; ++i) {
|
||
char c = six_digits[i];
|
||
s_modIndexAscii[i] = (c ? c : '0');
|
||
}
|
||
}*/
|
||
|
||
static int KLine_SendLoginResultBody_AndAck(const uint8_t *body, size_t len)
|
||
{
|
||
uint8_t payload[1 + 16]; // 0xF0 + up to 16 bytes (we send 13)
|
||
uint8_t idx = 0;
|
||
|
||
payload[idx++] = 0xF0; // login result command
|
||
for (size_t i = 0; i < len; ++i) payload[idx++] = body[i];
|
||
|
||
SendPacket(payload, idx);
|
||
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200)) {
|
||
if (ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket(); // mirror your style: ACK←→ACK
|
||
KLine_SessionKick();
|
||
|
||
} else {
|
||
return 0;
|
||
}
|
||
} else {
|
||
return 0;
|
||
}
|
||
KLine_SessionKick();
|
||
return 1;
|
||
}
|
||
static void KLine_HandlePassword(const uint8_t *body, uint8_t body_len)
|
||
{
|
||
if (body_len == sizeof(kPwdBody_DFI) &&
|
||
memcmp(body, kPwdBody_DFI, sizeof(kPwdBody_DFI)) == 0)
|
||
{
|
||
s_eeprom_unlocked_dfi = 1;
|
||
(void)KLine_SendLoginResultBody_AndAck(kLoginBody_DFI, sizeof(kLoginBody_DFI));
|
||
return;
|
||
}
|
||
|
||
if (body_len == sizeof(kPwdBody_CUST) &&
|
||
memcmp(body, kPwdBody_CUST, sizeof(kPwdBody_CUST)) == 0)
|
||
{
|
||
s_rom_unlocked_cust = 1;
|
||
(void)KLine_SendLoginResultBody_AndAck(kLoginBody_CUST, sizeof(kLoginBody_CUST));
|
||
return;
|
||
}
|
||
if (body_len == sizeof(kPwdBody_DFI_WRITE) &&
|
||
memcmp(body, kPwdBody_DFI_WRITE, sizeof(kPwdBody_DFI_WRITE)) == 0)
|
||
{
|
||
s_eeprom_unlocked_dfi_write = 1;
|
||
(void)KLine_SendLoginResultBody_AndAck(kLoginBody_DFI, sizeof(kLoginBody_DFI));
|
||
return;
|
||
}
|
||
// wrong password: clear both
|
||
s_eeprom_unlocked_dfi = 0;
|
||
s_eeprom_unlocked_dfi_write = 0;
|
||
s_rom_unlocked_cust = 0;
|
||
// (optional) Send NAK
|
||
// uint8_t nak = PACKET_CMD_NAK; SendPacket(&nak,1);
|
||
}
|
||
static void KLine_HandleWriteEeprom(const uint8_t *body, uint8_t body_len)
|
||
{
|
||
// Body: [len][addr_hi][addr_lo][value][csum]
|
||
if (body_len < 5U) return;
|
||
|
||
const uint8_t len_req = body[0];
|
||
const uint16_t addr = ((uint16_t)body[1] << 8) | body[2];
|
||
const uint8_t value = body[3];
|
||
const uint8_t csum = body[4];
|
||
|
||
// Only allow if write unlock is active
|
||
if (!s_eeprom_unlocked_dfi_write) return;
|
||
|
||
// Only handle DFI @ 0x0044, len 0x02
|
||
if (addr != 0x0044 || len_req != 0x02) return;
|
||
|
||
// Simple checksum rule: complement must match
|
||
if ((uint8_t)(value + csum) != 0) {
|
||
// Optional NAK on bad checksum:
|
||
// uint8_t nak = PACKET_CMD_NAK; SendPacket(&nak, 1);
|
||
return;
|
||
}
|
||
|
||
// Update DFI code in RAM (your GetDFI uses s_dfi_code -> float)
|
||
s_dfi_code = (int8_t)value;
|
||
memWrite = 1;
|
||
|
||
// Send WriteEepromResponse (0xF9) — often with empty body besides the title
|
||
uint8_t payload[1] = { (uint8_t)PACKET_CMD_WriteEepromResponse }; // 0xF9
|
||
SendPacket(payload, (uint8_t)sizeof(payload));
|
||
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
}
|
||
}
|
||
|
||
static void KLine_HandleReadEeprom(const uint8_t *body, uint8_t body_len)
|
||
{
|
||
// Body: [len_req][addr_hi][addr_lo]
|
||
if (body_len < 3U) return;
|
||
|
||
const uint8_t len_req = body[0];
|
||
const uint16_t addr = ((uint16_t)body[1] << 8) | body[2];
|
||
|
||
// ---- DFI @ 0x0044, len=2 (requires DFI unlock) ----
|
||
if (addr == 0x0044 && len_req == 0x02) {
|
||
if (!s_eeprom_unlocked_dfi) return;
|
||
|
||
uint8_t payload[3];
|
||
payload[0] = (uint8_t)PACKET_CMD_ReadEepromResponse; // 0xEF
|
||
payload[1] = (uint8_t)s_dfi_code; // dfi1
|
||
payload[2] = 0x00; // dfi2 (placeholder)
|
||
|
||
SendPacket(payload, (uint8_t)sizeof(payload));
|
||
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// ---- Serial number @ 0x0080, len=6 (no unlock) ----
|
||
if (addr == 0x0080 && len_req == 0x06) {
|
||
uint8_t payload[1 + 6];
|
||
payload[0] = (uint8_t)PACKET_CMD_ReadEepromResponse; // 0xEF
|
||
for (int i = 0; i < 6; ++i) payload[1 + i] = (uint8_t)s_serialNumberAscii[i];
|
||
|
||
SendPacket(payload, (uint8_t)sizeof(payload));
|
||
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Unknown/unsupported EEPROM address → ignore or NAK (uncomment to NAK)
|
||
// { uint8_t nak = PACKET_CMD_NAK; SendPacket(&nak, 1); }
|
||
}
|
||
static void KLine_HandleReadRomEeprom(const uint8_t *body, uint8_t body_len)
|
||
{
|
||
// Body: [len_req][addr_hi][addr_lo]
|
||
if (body_len < 3U) return;
|
||
|
||
const uint8_t len_req = body[0];
|
||
const uint16_t addr = ((uint16_t)body[1] << 8) | body[2];
|
||
|
||
// --- Case 1: 0x9FFE -> return customerChangeAddress (lo, hi)
|
||
// Requires ROM unlock (password 0x00 00 82 33).
|
||
if (addr == 0x9FFE) {
|
||
if (!s_rom_unlocked_cust) return;
|
||
if (len_req != 0x02) return; // enforce length
|
||
|
||
uint8_t payload[1 + 2];
|
||
payload[0] = (uint8_t)PACKET_CMD_ReadRomEepromResponse; // 0xFD
|
||
payload[1] = (uint8_t)(s_customerChangeAddress & 0xFF); // lo
|
||
payload[2] = (uint8_t)((s_customerChangeAddress >> 8) & 0xFF); // hi
|
||
|
||
SendPacket(payload, (uint8_t)sizeof(payload));
|
||
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket(); // mirror tester's ACK with our ACK
|
||
KLine_SessionKick();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// --- Case 2: Ident string at (s_identAddress - 10), len = 10
|
||
// DOES NOT require ROM unlock.
|
||
if (addr == (uint16_t)(s_identAddress - 10)) {
|
||
if (len_req != 0x0A) return;
|
||
|
||
uint8_t payload[1 + 10];
|
||
payload[0] = (uint8_t)PACKET_CMD_ReadRomEepromResponse; // 0xFD
|
||
for (int i = 0; i < 10; ++i) payload[1 + i] = (uint8_t)s_identAscii[i];
|
||
|
||
SendPacket(payload, (uint8_t)sizeof(payload));
|
||
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// --- Case 3: customerChangeAddress + 3 -> 6 ASCII bytes (mod index), len = 6
|
||
// Requires ROM unlock.
|
||
if (addr == (uint16_t)(s_customerChangeAddress + 3)) {
|
||
if (!s_rom_unlocked_cust) return;
|
||
if (len_req != 0x06) return;
|
||
|
||
uint8_t payload[1 + 6];
|
||
payload[0] = (uint8_t)PACKET_CMD_ReadRomEepromResponse; // 0xFD
|
||
for (int i = 0; i < 6; ++i) payload[1 + i] = (uint8_t)s_modIndexAscii[i];
|
||
|
||
SendPacket(payload, (uint8_t)sizeof(payload));
|
||
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Unknown ROM address: ignore (or NAK if you prefer)
|
||
// uint8_t nak = PACKET_CMD_NAK; SendPacket(&nak, 1);
|
||
}
|
||
|
||
void Kline_CCIResponse(uint8_t len_req){
|
||
// customerChangeIndex (2 bytes)
|
||
uint8_t payload[1 + 2]; // [FD][hi][lo]
|
||
payload[0] = (uint8_t)PACKET_CMD_ReadRomEepromResponse; // 0xFD
|
||
payload[1] = (uint8_t)((s_customerChangeAddress >> 8) & 0xFF);
|
||
payload[2] = (uint8_t)( s_customerChangeAddress & 0xFF);
|
||
|
||
// If tester asked for exactly 2 bytes; otherwise you could clamp/extend
|
||
(void)len_req; // if needed, enforce: if (len_req != 0x02) return;
|
||
|
||
SendPacket(payload, sizeof(payload));
|
||
|
||
// Expect tester ACK; respond ACK; keepalive
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
}
|
||
}
|
||
static void KLine_HandleReadIdentAddress(const uint8_t *body, uint8_t body_len)
|
||
{
|
||
if (body_len < sizeof(kReadIdentAddrBody)) return;
|
||
if (memcmp(body, kReadIdentAddrBody, sizeof(kReadIdentAddrBody)) != 0) return;
|
||
|
||
// Respond: 0xFE [lo][hi]
|
||
uint8_t payload[1 + 2];
|
||
payload[0] = 0xFE; // custom "ReadIdentAddressResponse"
|
||
payload[1] = (uint8_t)(s_identAddress & 0xFF); // lo
|
||
payload[2] = (uint8_t)((s_identAddress >> 8) & 0xFF); // hi
|
||
|
||
SendPacket(payload, sizeof(payload));
|
||
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket(); // mirror your ACK↔ACK style
|
||
KLine_SessionKick();
|
||
}
|
||
}
|
||
uint8_t SYNC_PULSE_OUT;
|
||
|
||
static void KLine_HandleCustomPacket(const uint8_t *body, uint8_t body_len)
|
||
{
|
||
if (body_len < sizeof(kActivateSyncOut)){
|
||
if (memcmp(body, kActivateSyncOut, sizeof(kActivateSyncOut)) == 0){
|
||
SYNC_PULSE_OUT = 1;
|
||
SendAckPacket();
|
||
KLine_SessionKick();
|
||
}
|
||
}
|
||
/*
|
||
ParsedPacket ack = (ParsedPacket){0};
|
||
if (ReceivePacket_Tmo(&ack, 200) && ack.title == PACKET_CMD_ACK) {
|
||
SendAckPacket(); // mirror your ACK↔ACK style
|
||
KLine_SessionKick();
|
||
}*/
|
||
}
|
||
/*static ControllerInfo info = {0}; // Zero init all strings
|
||
ControllerInfo ReadEcuInfo() {
|
||
int packet_count = 0;
|
||
ParsedPacket *packets = ReceivePackets(&packet_count);
|
||
|
||
char combined[128] = {0}; // Temporary buffer to build full ASCII text
|
||
|
||
if (packets != NULL) {
|
||
for (int i = 0; i < packet_count; i++) {
|
||
ParsedPacket *p = &packets[i];
|
||
if (p->type == PACKET_TYPE_ASCII_DATA && p->length > 4) {
|
||
size_t len = p->length - 4; // Exclude first 3 and last byte
|
||
strncat(combined, (char*)(p->raw + 3), len);
|
||
}
|
||
}
|
||
free(packets); // clean up when done
|
||
}
|
||
|
||
memset(&info, 0, sizeof(ControllerInfo));
|
||
|
||
// Fill ControllerInfo fields from combined text
|
||
strncpy(info.client_ident, combined, 12);
|
||
strncpy(info.unk_ident1, combined + 12, 10);
|
||
strncpy(info.soft_info, combined + 22, 10);
|
||
|
||
if (strlen(combined) > 40)
|
||
strncpy(info.unk_ident2, combined + 32, 10);
|
||
if (strlen(combined) > 50)
|
||
strncpy(info.unk_ident3, combined + 42, 10);
|
||
|
||
free(combined);
|
||
return info;
|
||
}*/
|
||
|
||
#define MAX_EEPROM_DATA 256 // adjust to your ECU’s max response
|
||
#define MAX_PACKETS 16
|
||
|
||
static ParsedPacket packet_buffer[MAX_PACKETS];
|
||
static uint8_t eeprom_result[MAX_EEPROM_DATA];
|
||
|
||
uint8_t* ReadRomEeprom(uint16_t address, uint8_t count, int* outLength)
|
||
{
|
||
uint8_t request[4] = {
|
||
PACKET_CMD_ReadRomEeprom, // 0x08 usually
|
||
count,
|
||
(uint8_t)(address >> 8),
|
||
(uint8_t)(address & 0xFF)
|
||
};
|
||
|
||
int packetCount = 0;
|
||
ParsedPacket* packets = SendCustom(request, sizeof(request), &packetCount, 0);
|
||
if (!packets || packetCount == 0) {
|
||
*outLength = 0;
|
||
return NULL;
|
||
}
|
||
|
||
// Check for NAK
|
||
if (packetCount == 1 && packets[0].type == PACKET_TYPE_NAK) {
|
||
*outLength = 0;
|
||
return NULL;
|
||
}
|
||
|
||
// Collect useful packets (non-Ack/Nak)
|
||
int usefulCount = 0;
|
||
for (int i = 0; i < packetCount && usefulCount < MAX_PACKETS; i++) {
|
||
if (!packets[i].isAckNak) {
|
||
packet_buffer[usefulCount++] = packets[i];
|
||
}
|
||
}
|
||
|
||
if (usefulCount != 1) {
|
||
*outLength = 0;
|
||
return NULL; // Expect exactly one useful packet
|
||
}
|
||
|
||
int dataLen = packet_buffer[0].length - 4; // skip 3-byte header + 1-byte end
|
||
if (dataLen > MAX_EEPROM_DATA) {
|
||
dataLen = MAX_EEPROM_DATA; // clamp to buffer size
|
||
}
|
||
|
||
memcpy(eeprom_result, packet_buffer[0].raw + 3, dataLen);
|
||
*outLength = dataLen;
|
||
|
||
return eeprom_result; // static buffer pointer
|
||
}
|
||
|
||
static void KLine_TerminateAndRearm(void)
|
||
{
|
||
// Drop session state
|
||
kline_connection_status = 0;
|
||
kl_session_deadline = 0;
|
||
|
||
// Clear feature unlocks (DFI/ROM)
|
||
s_eeprom_unlocked_dfi = 0;
|
||
s_rom_unlocked_cust = 0;
|
||
|
||
// Stop any pending command processing from ISR
|
||
kline_cmd_pending = 0;
|
||
|
||
// Reset packet counters (TX/RX sequencing)
|
||
ResetPacketCounter();
|
||
|
||
// Make sure RX IT isn’t mid-flight; then drop UART and rearm 5-baud
|
||
// (AbortReceive_IT only if you enabled it; otherwise DeInit is fine)
|
||
// HAL_UART_AbortReceive_IT(&huart1);
|
||
HAL_UART_DeInit(&huart1);
|
||
|
||
// Go back to EXTI/bit-bang listen
|
||
KLine_Rearm5Baud();
|
||
}
|
||
|
||
// Handle tester's End command: ACK then terminate
|
||
static void KLine_HandleEnd(void)
|
||
{
|
||
// ACK the End command as a packet-level ACK
|
||
SendAckPacket();
|
||
|
||
// Immediately end session and rearm
|
||
KLine_TerminateAndRearm();
|
||
}
|
||
|