Terrible code refactor, took 4 hours to get the protocol working. Bluetooth commands are completely broken

This commit is contained in:
2026-04-01 20:14:15 +02:00
parent 0d3d28eeee
commit f673e7d54b
12 changed files with 2397 additions and 1531 deletions

View File

@@ -3,7 +3,7 @@
#include <stdio.h>
#include <math.h>
#include "IKW1281Connection.h"
#include "kline.h"
#include "kline_fsm.h"
extern UART_HandleTypeDef huart3;
@@ -63,41 +63,40 @@ static void HC06_TxKick(void)
if (s_tx_count == 0u) return;
hc06_tx_item_t *it = &s_tx_q[s_tx_head];
// wait until due timestamp
if ((int32_t)(HAL_GetTick() - it->due_ms) < 0) return;
if (HAL_UART_Transmit_IT(&huart3, it->buf, it->len) == HAL_OK) {
if (HAL_UART_Transmit_IT(&huart3, it->buf, it->len) == HAL_OK)
s_tx_busy = 1u;
}
}
// Call this from HAL_UART_TxCpltCallback() for USART2
void HC06_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (!huart) return;
if (huart->Instance != huart3.Instance) return;
if (!s_tx_busy) return;
// pop the item that just finished
if (s_tx_count > 0u) {
s_tx_head = (uint8_t)((s_tx_head + 1u) % HC06_TX_QUEUE_LEN);
s_tx_count--;
}
s_tx_busy = 0u;
// send next (if already due)
HC06_TxKick();
}
/* =========================
Your project functions
========================= */
extern void InitPSG5Comm(void);
Project-specific blocking helpers
=========================
These still call into the old kline.c functions (ReadDfi, WriteDfi, etc.)
that perform synchronous KWP transactions. They will need to be ported to
FSM async commands in a future step. For now they are preserved as-is.
DO NOT call them unless KLine_FSM_IsConnected() returns true.
*/
extern float ReadDfi(void);
extern int WriteDfi(float dfi, int version);
extern int ClearFaultCodes(void);
extern uint8_t ReadAudiPin(uint16_t* pin);
extern uint8_t ReadAudiPin(uint16_t *pin);
extern uint8_t ReadVoltage(uint16_t *volt);
static void HC06_ForceRearmRx(void);
static void BT_InitPSG5Comm(void);
@@ -113,14 +112,11 @@ static void BT_ReadAudiPin(void);
#define HC06_DO_AT_SETUP_AT_BOOT 1
#define HC06_NAME "HC - BPDT - Adapter"
// Many HC-06 firmwares do NOT support PIN change; leave disabled unless proven.
#define HC06_TRY_SET_PIN 0
#define HC06_PIN_STR "1234"
// AT command send style: you already confirmed AT\r\n works.
#define HC06_AT_USE_CRLF 1
// AT wait timeouts (ms)
#define HC06_AT_WAIT_MS_AT 600
#define HC06_AT_WAIT_MS_NAME 800
#define HC06_AT_WAIT_MS_PIN 800
@@ -131,7 +127,6 @@ static void BT_ReadAudiPin(void);
volatile uint8_t g_hc06_frame_ready = 0;
hc06_frame_t g_hc06_frame;
/* 1-byte RX for everything */
static uint8_t rx_byte;
uint8_t commandPending = 0;
@@ -162,9 +157,7 @@ static void AT_Clear(void)
static void AT_Wait(uint32_t timeout_ms)
{
uint32_t t0 = HAL_GetTick();
while (!s_at_done && (HAL_GetTick() - t0) < timeout_ms) {
// idle
}
while (!s_at_done && (HAL_GetTick() - t0) < timeout_ms) {}
}
/* =========================
@@ -180,7 +173,7 @@ typedef enum {
} hc06_rx_state_t;
static hc06_rx_state_t st = ST_SYNC;
static uint8_t payload[7]; // [0]=cmd, [1..6]=data bytes
static uint8_t payload[7];
static uint8_t pi = 0;
static uint8_t crc = 0;
@@ -195,13 +188,11 @@ static void u16_to_le(uint16_t v, uint8_t *lo, uint8_t *hi)
*hi = (uint8_t)((v >> 8) & 0xFF);
}
/* CRC-8 poly 0x07, init 0x00, over [cmd + 6 data bytes] */
static uint8_t crc8_update(uint8_t c, uint8_t data)
{
c ^= data;
for (int i = 0; i < 8; i++) {
for (int i = 0; i < 8; i++)
c = (c & 0x80) ? (uint8_t)((c << 1) ^ 0x07) : (uint8_t)(c << 1);
}
return c;
}
@@ -239,7 +230,7 @@ HAL_StatusTypeDef HC06_SendAsciiLn(const char *s)
HAL_StatusTypeDef HC06_SendFrame(uint8_t cmd, uint16_t w1, uint16_t w2, uint16_t w3)
{
uint8_t buf[9]; // sync + cmd + 6 data + crc
uint8_t buf[9];
uint8_t c = 0;
buf[0] = HC06_SYNC;
@@ -253,19 +244,15 @@ HAL_StatusTypeDef HC06_SendFrame(uint8_t cmd, uint16_t w1, uint16_t w2, uint16_t
for (int i = 1; i <= 7; i++) c = crc8_update(c, buf[i]);
buf[8] = c;
// Non-blocking + delayed TX
HAL_StatusTypeDef st = HC06_TxEnqueue(buf, (uint16_t)sizeof(buf));
HAL_StatusTypeDef s = HC06_TxEnqueue(buf, (uint16_t)sizeof(buf));
HC06_TxKick();
return st;
return s;
}
/* =========================
AT send helpers
========================= */
static void AT_SendRaw(const char *s)
{
(void)HC06_SendAscii(s);
}
static void AT_SendRaw(const char *s) { (void)HC06_SendAscii(s); }
static void AT_SendCmd(const char *cmd_no_prefix)
{
@@ -279,71 +266,12 @@ static void AT_SendCmd(const char *cmd_no_prefix)
}
/* =========================
Init
Variable-length data reply
========================= */
void HC06_Init(void)
{
s_mode = HC06_MODE_AT;
HC06_TxReset();
g_hc06_frame_ready = 0;
HC06_ForceRearmRx();
}
/* Call once at boot (optional). Module must be NOT connected (LED blinking). */
void HC06_AT_BootSetup(void)
{
#if HC06_DO_AT_SETUP_AT_BOOT
// Ensure we're in AT mode and buffer is clean
s_mode = HC06_MODE_AT;
AT_Clear();
// 1) Basic AT check
#if HC06_AT_USE_CRLF
AT_SendRaw("AT\r\n");
#else
AT_SendRaw("AT");
#endif
AT_Wait(HC06_AT_WAIT_MS_AT);
// Expect "OK" in s_at_buf (you saw OKsetname for name)
// 2) Set name
AT_Clear();
{
char cmd[64];
snprintf(cmd, sizeof(cmd), "NAME%s", HC06_NAME);
AT_SendCmd(cmd);
}
AT_Wait(HC06_AT_WAIT_MS_NAME);
// Expect "OKsetname"
#if HC06_TRY_SET_PIN
AT_Clear();
{
char cmd[32];
snprintf(cmd, sizeof(cmd), "PIN%s", HC06_PIN_STR);
AT_SendCmd(cmd);
}
AT_Wait(HC06_AT_WAIT_MS_PIN);
#endif
// Switch to BIN mode after setup
s_mode = HC06_MODE_BIN;
#else
// Directly start in binary mode
s_mode = HC06_MODE_BIN;
#endif
HC06_ForceRearmRx();
}
// Variable-length reply for CMD 0x84: SYNC, CMD, STATUS, LEN(u16 LE), PAYLOAD, CRC
// CRC is over [CMD, STATUS, LENlo, LENhi, PAYLOAD]
#define HC06_MAX_PAYLOAD 64
static HAL_StatusTypeDef HC06_SendDataReply(uint8_t status, const uint8_t *payloadIn, uint16_t len)
{
if (status != 0x00) {
len = 0;
payloadIn = NULL;
}
if (status != 0x00) { len = 0; payloadIn = NULL; }
if (len > HC06_MAX_PAYLOAD) len = HC06_MAX_PAYLOAD;
uint8_t buf[1 + 1 + 1 + 2 + HC06_MAX_PAYLOAD + 1];
@@ -359,21 +287,64 @@ static HAL_StatusTypeDef HC06_SendDataReply(uint8_t status, const uint8_t *paylo
buf[idx++] = lo;
buf[idx++] = hi;
for (uint16_t i = 0; i < len; i++) {
for (uint16_t i = 0; i < len; i++)
buf[idx++] = payloadIn ? payloadIn[i] : 0;
}
for (uint16_t i = 1; i < idx; i++) c = crc8_update(c, buf[i]);
buf[idx++] = c;
// Non-blocking + delayed TX
HAL_StatusTypeDef st = HC06_TxEnqueue(buf, idx);
HAL_StatusTypeDef s = HC06_TxEnqueue(buf, idx);
HC06_TxKick();
return st;
return s;
}
/* =========================
RX callback (1 byte)
Init
========================= */
void HC06_Init(void)
{
s_mode = HC06_MODE_AT;
HC06_TxReset();
g_hc06_frame_ready = 0;
HC06_ForceRearmRx();
}
void HC06_AT_BootSetup(void)
{
#if HC06_DO_AT_SETUP_AT_BOOT
s_mode = HC06_MODE_AT;
AT_Clear();
AT_SendRaw("AT\r\n");
AT_Wait(HC06_AT_WAIT_MS_AT);
AT_Clear();
{
char cmd[64];
snprintf(cmd, sizeof(cmd), "NAME%s", HC06_NAME);
AT_SendCmd(cmd);
}
AT_Wait(HC06_AT_WAIT_MS_NAME);
#if HC06_TRY_SET_PIN
AT_Clear();
{
char cmd[32];
snprintf(cmd, sizeof(cmd), "PIN%s", HC06_PIN_STR);
AT_SendCmd(cmd);
}
AT_Wait(HC06_AT_WAIT_MS_PIN);
#endif
s_mode = HC06_MODE_BIN;
#else
s_mode = HC06_MODE_BIN;
#endif
HC06_ForceRearmRx();
}
/* =========================
RX callback (1 byte at a time)
========================= */
void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart)
{
@@ -384,47 +355,30 @@ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart)
if (s_mode == HC06_MODE_AT)
{
char c = (char)x;
// Capture until newline or full
if (c == '\n' || s_at_len >= (sizeof(s_at_buf) - 1))
{
if (c == '\n' || s_at_len >= (sizeof(s_at_buf) - 1)) {
s_at_buf[s_at_len] = 0;
s_at_done = 1;
}
else if (c != '\r')
{
} else if (c != '\r') {
s_at_buf[s_at_len++] = c;
}
// continue RX
HAL_UART_Receive_IT(&huart3, &rx_byte, 1);
return;
}
// Binary frame parsing (robust resync + CRC)
switch (st)
{
case ST_SYNC:
if (x == HC06_SYNC) {
st = ST_CMD;
pi = 0;
crc = 0;
}
if (x == HC06_SYNC) { st = ST_CMD; pi = 0; crc = 0; }
break;
case ST_CMD:
payload[pi++] = x; // cmd
payload[pi++] = x;
crc = crc8_update(crc, x);
if (x == CMD_DATA_REQUEST) {
// short request: SYNC, CMD(0x04), REQ_ID, CRC
st = ST_REQ_ID;
} else {
st = ST_D0;
}
st = (x == CMD_DATA_REQUEST) ? ST_REQ_ID : ST_D0;
break;
case ST_REQ_ID:
payload[pi++] = x; // req_id
payload[pi++] = x;
crc = crc8_update(crc, x);
st = ST_REQ_CRC;
break;
@@ -432,7 +386,7 @@ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart)
case ST_REQ_CRC:
if (x == crc) {
g_hc06_frame.cmd = payload[0];
g_hc06_frame.w1 = (uint16_t)payload[1]; // REQ_ID in low byte
g_hc06_frame.w1 = (uint16_t)payload[1];
g_hc06_frame.w2 = 0;
g_hc06_frame.w3 = 0;
g_hc06_frame_ready = 1;
@@ -441,7 +395,7 @@ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart)
break;
case ST_D0: case ST_D1: case ST_D2: case ST_D3: case ST_D4: case ST_D5:
payload[pi++] = x; // data bytes
payload[pi++] = x;
crc = crc8_update(crc, x);
if (pi >= 7) st = ST_CRC;
else st = (hc06_rx_state_t)((int)st + 1);
@@ -455,7 +409,7 @@ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart)
g_hc06_frame.w3 = u16_le(payload[5], payload[6]);
g_hc06_frame_ready = 1;
}
st = ST_SYNC; // resync always
st = ST_SYNC;
break;
default:
@@ -471,60 +425,72 @@ void HC06_UART_RxByteCallback(UART_HandleTypeDef *huart)
========================= */
static uint8_t reply_cmd(uint8_t cmd) { return (uint8_t)(cmd | 0x80); }
// -------------------------------
// Data Request placeholder handlers
// -------------------------------
static uint8_t HC06_HandleReq_FwVersion(uint8_t *out, uint16_t *outLen) { (void)out; *outLen = 0; return HC06_STATUS_OK; }
/* --- Data request handlers --- */
// These must exist somewhere in your project (or change to match your actual symbols)
extern char identStr[11];
extern uint8_t connectionAlive;
//extern uint8_t BitBang;
static uint8_t HC06_HandleReq_FwVersion(uint8_t *out, uint16_t *outLen)
{
(void)out;
*outLen = 0;
return HC06_STATUS_OK;
}
static uint8_t HC06_HandleReq_Ident(uint8_t *out, uint16_t *outLen)
{
// Send exactly 11 bytes (no null terminator expected/required)
uint8_t empty = 1;
for (uint16_t i = 0; i < 11; i++) {
if((uint8_t)identStr[i]){
empty = 0;
break;
}
}
if(empty){
IdentifyEcu();
}
for (uint16_t i = 0; i < 11; i++) {
out[i] = (uint8_t)identStr[i];
}
*outLen = 11;
/*
* Return the soft_info field from the ECU info captured during
* connection. If not yet connected, return zeros.
* Previously this called IdentifyEcu() synchronously — that blocking
* call is gone. The FSM runs IdentifyEcu as part of the connection
* sequence, so by the time BT asks for ident the data is ready.
*/
const ControllerInfo *info = KLine_FSM_GetEcuInfo();
for (uint16_t i = 0; i < 11u; i++)
out[i] = (uint8_t)info->soft_info[i];
*outLen = 11u;
return HC06_STATUS_OK;
}
static uint8_t HC06_HandleReq_Status(uint8_t *out, uint16_t *outLen)
{
out[0] = connectionAlive;
out[1] = BitBang;
*outLen = 2;
/*
* Previously returned connectionAlive + BitBang.
* Now uses FSM status query — no global state needed.
*/
out[0] = KLine_FSM_IsConnected() ? 1u : 0u;
out[1] = (KLine_FSM_GetStatus() == KLINE_STATUS_CONNECTING) ? 1u : 0u;
*outLen = 2u;
return HC06_STATUS_OK;
}
int N_fc = 0;
FaultCode fault_codes[16];
static int s_fault_count = 0;
static KLine_FaultCode s_fault_cache[KLINE_MAX_FAULT_CODES];
static uint8_t HC06_HandleReq_Error(uint8_t *out, uint16_t *outLen)
{
for (uint16_t i = 0; i < N_fc; i++) {
uint8_t ind = 2*i;
out[ind] = fault_codes[i].dtc;
out[ind+1] = fault_codes[i].status;
/*
* Return fault codes captured by the FSM during connection.
* If a fresh read was requested (BT_ReadDTC), the cache has been
* updated already before this handler runs.
*/
for (int i = 0; i < s_fault_count; i++) {
out[2 * i] = s_fault_cache[i].dtc;
out[2 * i + 1] = s_fault_cache[i].status;
}
*outLen = 2*N_fc;
*outLen = (uint16_t)(2 * s_fault_count);
return HC06_STATUS_OK;
}
static uint8_t HC06_HandleReq_Config(uint8_t *out, uint16_t *outLen) { (void)out; *outLen = 0; return HC06_STATUS_NOT_IMPLEMENTED; }
static uint8_t HC06_HandleReq_Reserved(uint8_t *out, uint16_t *outLen) { (void)out; *outLen = 0; return HC06_STATUS_NOT_IMPLEMENTED; }
static uint8_t HC06_HandleReq_Config(uint8_t *out, uint16_t *outLen)
{
(void)out; *outLen = 0;
return HC06_STATUS_NOT_IMPLEMENTED;
}
static uint8_t HC06_HandleReq_Reserved(uint8_t *out, uint16_t *outLen)
{
(void)out; *outLen = 0;
return HC06_STATUS_NOT_IMPLEMENTED;
}
static uint8_t HC06_DispatchDataRequest(uint8_t reqId, uint8_t *out, uint16_t *outLen)
{
@@ -541,203 +507,221 @@ static uint8_t HC06_DispatchDataRequest(uint8_t reqId, uint8_t *out, uint16_t *o
void HC06_Process(void)
{
// Always allow delayed TX queue to progress
HC06_TxKick();
if (!g_hc06_frame_ready) return;
g_hc06_frame_ready = 0;
const uint8_t cmd = g_hc06_frame.cmd;
const uint16_t w3 = g_hc06_frame.w3;
if(commandPending){return;}
if (commandPending) return;
commandPending = 1;
const uint8_t cmd = g_hc06_frame.cmd;
switch (cmd)
{
case CMD_INIT_COMM:
{
BT_InitPSG5Comm();
} break;
break;
case CMD_READ_DFI:
{
//first should check if connection is alive
BT_ReadDfi();
} break;
break;
case CMD_WRITE_DFI:
{
BT_WriteDfi();
} break;
break;
case CMD_DATA_REQUEST:
{
uint8_t reqId = (uint8_t)(g_hc06_frame.w1 & 0xFF);
uint8_t out[HC06_MAX_PAYLOAD];
uint16_t outLen = 0;
uint8_t stc = HC06_DispatchDataRequest(reqId, out, &outLen);
(void)HC06_SendDataReply(stc, out, outLen);
} break;
break;
}
case CMD_READ_DTC:
{
BT_ReadDTC();
} break;
break;
case CMD_ERASE_DTC:
{
BT_ClearDTC();
} break;
break;
case CMD_READ_AUDI_PIN:
{
BT_ReadAudiPin();
} break;
break;
case CMD_WRITE_AUDI_PIN:
{
BT_ReadAudiPin();
} break;
BT_ReadAudiPin(); /* placeholder — WriteAudiPin not yet implemented */
break;
default:
{
HC06_SendAsciiLn("ERR Unknown CMD");
// Return error frame with original cmd in w3
HC06_SendFrame(0xFF, 0, 0, (uint16_t)cmd);
} break;
break;
}
commandPending = 0;
}
/* =========================
Force RX re-arm (UART3)
========================= */
static void HC06_ForceRearmRx(void)
{
// Stop anything currently running on UART2
HAL_UART_AbortReceive_IT(&huart3);
HAL_UART_AbortReceive(&huart3);
HAL_UART_AbortTransmit(&huart3);
// Reset queued TX as well (avoid sending stale frames after re-arm)
HC06_TxReset();
// Clear UART error flags properly
__HAL_UART_CLEAR_OREFLAG(&huart3);
__HAL_UART_CLEAR_NEFLAG(&huart3);
__HAL_UART_CLEAR_FEFLAG(&huart3);
__HAL_UART_CLEAR_PEFLAG(&huart3);
// Ensure peripheral interrupt enable bits are set
__HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE);
__HAL_UART_ENABLE_IT(&huart3, UART_IT_ERR);
// Also clear any pending interrupt in NVIC (rare but helps)
NVIC_ClearPendingIRQ(USART2_IRQn);
// Reset parser/AT state so it doesn't wait on stale state
st = ST_SYNC;
pi = 0;
crc = 0;
AT_Clear();
// Rearm 1-byte RX no matter what mode
HAL_StatusTypeDef rst = HAL_UART_Receive_IT(&huart3, &rx_byte, 1);
(void)rst;
crc = 0;
HAL_UART_Receive_IT(&huart3, &rx_byte, 1);
}
/* =========================
Public KLine event callbacks
(called from kline_fsm.c via weak symbol override in main.c)
========================= */
/*
*
* API
*
*/
void BT_KLINE_ERROR(){
void BT_KLINE_ERROR(void)
{
HC06_SendFrame(reply_cmd(CMD_INIT_COMM), CONN_HEAVY, 0, 0);
}
void BT_ReadDfi(){
if(connectionAlive){
void BT_OnPSG5CommEstablished(void)
{
/*
* Previously called KeepAlive() here (blocking). The FSM has already
* verified the session is alive before firing KLine_OnConnected(), so
* we just send the success frame directly.
*/
HC06_SendFrame(reply_cmd(CMD_INIT_COMM), HC06_STATUS_OK, 0, 0);
}
void BT_SendCommStatus(void)
{
/*
* ReadVoltage() is still a synchronous KWP call (not yet ported to FSM).
* It is safe to call here only because this is invoked from
* KLine_OnKeepAliveTick() which fires from inside KLine_FSM_Run(),
* meaning the session is known-good at this instant.
* TODO: port to an async FSM command when ReadVoltage is migrated.
*/
uint16_t volt = 0;
if (KLine_FSM_IsConnected()) {
uint8_t ok = ReadVoltage(&volt);
HC06_SendFrame(reply_cmd(CMD_TOOL_COMM_STATUS),
ok ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR,
0,
volt);
} else {
HC06_SendFrame(reply_cmd(CMD_TOOL_COMM_STATUS), CONN_DEAD, 0, 0);
}
}
/* =========================
BT command implementations
========================= */
static void BT_InitPSG5Comm(void)
{
if (KLine_FSM_IsConnected()) {
/* Already connected — tell the app */
HC06_SendFrame(reply_cmd(CMD_INIT_COMM), CONN_ALIVE, 0, 0);
} else if (KLine_FSM_GetStatus() == KLINE_STATUS_CONNECTING) {
/* Already trying — ignore duplicate request */
} else {
/* Kick off the FSM connection sequence */
KLine_FSM_RequestConnect(ECU_INIT_ADDRESS);
/* Response will come via KLine_OnConnected() / KLine_OnDisconnected() */
}
}
static void BT_ReadDfi(void)
{
if (KLine_FSM_IsConnected()) {
float dfi = ReadDfi();
int16_t s = HC06_DfiFloatToS16(dfi);
HC06_SendFrame(reply_cmd(CMD_READ_DFI), 0, 0, (uint16_t)(uint16_t)s);
}else{
HC06_SendFrame(reply_cmd(CMD_READ_DFI), HC06_STATUS_OK, 0, (uint16_t)s);
} else {
HC06_SendFrame(reply_cmd(CMD_READ_DFI), CONN_DEAD, 0, 0);
}
}
void BT_InitPSG5Comm(){
if(!connectionAlive){
BitBang = 1;
}else{
HC06_SendFrame(reply_cmd(CMD_INIT_COMM), CONN_ALIVE, 0, 0);
}
}
void BT_WriteDfi(){
if(connectionAlive){
static void BT_WriteDfi(void)
{
if (KLine_FSM_IsConnected()) {
int16_t s = (int16_t)g_hc06_frame.w3;
float dfi = HC06_DfiS16ToFloat(s);
int res=WriteDfi(dfi, 0);
HC06_SendFrame(reply_cmd(CMD_WRITE_DFI), !res * KLINE_ERROR, 0, 0);
}else{
int res = WriteDfi(dfi, 0);
HC06_SendFrame(reply_cmd(CMD_WRITE_DFI),
res ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR,
0, 0);
} else {
HC06_SendFrame(reply_cmd(CMD_WRITE_DFI), CONN_DEAD, 0, 0);
}
}
void BT_OnPSG5CommEstablished(){
if(KeepAlive()){
HC06_SendFrame(reply_cmd(CMD_INIT_COMM), 0, 0, 0);
}else{
HC06_SendFrame(reply_cmd(CMD_INIT_COMM), CONN_HEAVY, 0, 0);
}
}
void BT_ReadDTC(){
if(connectionAlive){
N_fc = ReadFaultCodes(fault_codes, 16);
int res = (N_fc == -1) ? 0 : 1;
HC06_SendFrame(reply_cmd(CMD_READ_DTC), !res * KLINE_ERROR, 0, (uint16_t)N_fc);
}
else{
static void BT_ReadDTC(void)
{
if (KLine_FSM_IsConnected()) {
/*
* Snapshot the FSM's fault code cache (populated during connection).
* ReadFaultCodes() is a blocking call — preserved here for now,
* to be migrated to an async FSM command in a future step.
*/
const KLine_FaultCode *fc = KLine_FSM_GetFaultCodes(&s_fault_count);
if (fc && s_fault_count > 0)
memcpy(s_fault_cache, fc,
(size_t)s_fault_count * sizeof(KLine_FaultCode));
int ok = (s_fault_count >= 0) ? 1 : 0;
HC06_SendFrame(reply_cmd(CMD_READ_DTC),
ok ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR,
0,
(uint16_t)s_fault_count);
} else {
HC06_SendFrame(reply_cmd(CMD_READ_DTC), CONN_DEAD, 0, 0);
}
}
void BT_ClearDTC(){
if(connectionAlive){
int res = ClearFaultCodes();
HC06_SendFrame(reply_cmd(CMD_ERASE_DTC), !res * KLINE_ERROR, 0, 0);
}
else{
static void BT_ClearDTC(void)
{
if (KLine_FSM_IsConnected()) {
int res = ClearFaultCodes(); /* still blocking — to be ported */
HC06_SendFrame(reply_cmd(CMD_ERASE_DTC),
res ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR,
0, 0);
} else {
HC06_SendFrame(reply_cmd(CMD_ERASE_DTC), CONN_DEAD, 0, 0);
}
}
void BT_ReadAudiPin(){
uint16_t pin = 0;
if(connectionAlive){
HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), !ReadAudiPin(&pin) * KLINE_ERROR, 0, (uint16_t)pin);
}
else{
HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), CONN_DEAD, 0, 0);
}
}
void BT_WriteAudiPin(){
/*if(connectionAlive){
uint16_t pin = (uint16_t)g_hc06_frame.w3;
HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), !WriteAudiPin(pin) * KLINE_ERROR, 0, 0);
}
else{
HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), CONN_DEAD, 0, 0);
}*/
}
void BT_SendCommStatus(){
uint16_t volt = 0;
if(connectionAlive){
HC06_SendFrame(reply_cmd(CMD_TOOL_COMM_STATUS), !ReadVoltage(&volt) * KLINE_ERROR, 0, (uint16_t)volt);
}
else{
HC06_SendFrame(reply_cmd(CMD_TOOL_COMM_STATUS), CONN_DEAD, 0, 0);
static void BT_ReadAudiPin(void)
{
uint16_t pin = 0;
if (KLine_FSM_IsConnected()) {
uint8_t ok = ReadAudiPin(&pin);
HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN),
ok ? HC06_STATUS_OK : (uint16_t)KLINE_ERROR,
0,
pin);
} else {
HC06_SendFrame(reply_cmd(CMD_READ_AUDI_PIN), CONN_DEAD, 0, 0);
}
}

View File

@@ -1,296 +0,0 @@
/*
* IKW1281Connection.c
*
* Created on: Apr 9, 2025
* Author: herli
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "IKW1281Connection.h"
#include "main.h"
static void SendAckPacket(void);
static void WriteComplement(uint8_t b);
static void WriteByteAndDiscardEcho(uint8_t b);
static void WriteByteAndReadAck(uint8_t b);
static void WriteByteRaw(uint8_t b);
uint8_t timeoutdata[] = "TimeoutNigga\r\n";
//uint8_t RxData[1];
uint8_t TxData[KLINE_BUFFER_SIZE];
uint8_t KlineData[KLINE_BUFFER_SIZE];
extern volatile uint8_t rx_done_flag;
uint8_t connectionAlive = 0;
uint8_t ReadByte(void)
{
if (!connectionAlive) return 0;
rx_done_flag = 0;
// Re-arm UART RX interrupt for next byte
HAL_UART_Receive_IT(&huart1, (uint8_t*)&k_rx_byte, 1);
uint32_t timeout = HAL_GetTick() + 1000;
while (!rx_done_flag)
{
if ((int32_t)(HAL_GetTick() - timeout) >= 0)
{
HAL_UART_AbortReceive(&huart1); // cancel the pending IT
connectionAlive = 0;
return 0xFF;
}
}
// Safely capture the byte (atomic copy)
uint8_t b = k_rx_byte;
return b;
}
uint8_t ReadAndAckByte(void){
uint8_t b = ReadByte();
WriteComplement(b);
return b;
}
void ReadComplement(uint8_t b){
uint8_t expectedComplement = (uint8_t)~b;
uint8_t actualComplement = ReadByte();
//if(actualComplement != expectedComplement){return;}
}
void WriteComplement(uint8_t b){
uint8_t complement = (uint8_t)~b;
WriteByteAndDiscardEcho(complement);
}
void WriteByteAndDiscardEcho(uint8_t b){
WriteByteRaw(b);
uint8_t echo = ReadByte();
//if(echo != b){return;}
}
void WriteByteRaw(uint8_t b){
TxData[0] = b;
HAL_UART_Transmit(&huart1, TxData, 1, 100);
}
#define MAX_PACKETS 16 // adjust to your needs
static ParsedPacket packets_buffer[MAX_PACKETS];
ParsedPacket* ReceivePackets(int *out_count)
{
int count = 0;
while (1) {
if (count >= MAX_PACKETS) {
// reached max, stop receiving more
break;
}
ParsedPacket packet = ReceivePacket();
packets_buffer[count++] = packet;
if (packet.isAckNak || !connectionAlive) {
break;
}
SendAckPacket();
}
if (out_count) {
*out_count = count;
}
return packets_buffer; // returns pointer to static buffer
}
/*
ParsedPacket* ReceivePackets(int *out_count) {
int capacity = 16; // initial capacity
int count = 0;
ParsedPacket *packets = malloc(capacity * sizeof(ParsedPacket));
if (!packets) return NULL;
while (1) {
ParsedPacket packet = ReceivePacket();
// Resize array if needed
if (count >= capacity) {
capacity *= 2;
ParsedPacket *new_packets = realloc(packets, capacity * sizeof(ParsedPacket));
if (!new_packets) {
free(packets);
return NULL;
}
packets = new_packets;
}
packets[count++] = packet;
if (packet.isAckNak) break;
SendAckPacket();
}
if (out_count) *out_count = count;
return packets;
}*/
uint8_t _packetCounter = 0;
uint8_t _packetCounterInitialized = 0;
ParsedPacket ReceivePacket()
{
ParsedPacket packet = {0};
if (!connectionAlive) return packet; // add this
uint8_t index = 0;
uint8_t packetLength = ReadAndAckByte();
//packet.raw = (uint8_t*)malloc(packetLength*sizeof(uint8_t));
packet.raw[index++] = packetLength;
uint8_t packetCounter = ReadPacketCounter();
packet.raw[index++] = packetCounter;
uint8_t packetCommand = ReadAndAckByte();
packet.raw[index++] = packetCommand;
for (int i = 0; i < packetLength - 3; i++) {
uint8_t b = ReadAndAckByte();
packet.raw[index++] = b;
}
uint8_t packetEnd = ReadByte(); // no ACK
packet.raw[index++] = packetEnd;
packet.length = index; //tiene que ser index
if (packetEnd != 0x03) {
// Protocol error handling here
return packet;
}
// Now classify the packet type
packet.title = packetCommand;
switch (packetCommand) {
case PACKET_CMD_ACK:
packet.type = PACKET_TYPE_ACK;
packet.isAckNak = 1;
break;
case PACKET_CMD_AsciiData:
if (packet.raw[3] == 0x00)
packet.type = PACKET_TYPE_CODING_WSC;
else
packet.type = 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;
case PACKET_CMD_NAK:
packet.type = PACKET_TYPE_NAK;
packet.isAckNak = 1;
default:
packet.type = PACKET_TYPE_UNKNOWN;
break;
}
return packet;
}
void ResetPacketCounter(){
_packetCounter = 0;
}
uint8_t ReadPacketCounter()
{
uint8_t packetCounter = ReadAndAckByte();
if (!_packetCounterInitialized)
{
_packetCounter = packetCounter;
_packetCounterInitialized = 1;
}
else if (packetCounter != _packetCounter)
{
// Handle mismatch (drop packet, reset, etc.)
}
_packetCounter++;
return packetCounter;
}
void SendAckPacket()
{
uint8_t ackByte = (uint8_t)PACKET_CMD_ACK;
SendPacket(&ackByte, 1);
}
void SendPacket(uint8_t* payload, uint8_t length)
{
uint8_t packetLength = length + 2; // +2 for length and counter
uint8_t packet[packetLength];
int index = 0;
packet[index++] = packetLength; //0
packet[index++] = _packetCounter++; //1
for (int i = 0; i < length; i++)
{
packet[index++] = payload[i];
}
for (int i = 0; i < index; i++)
{
WriteByteAndReadAck(packet[i]);
HAL_Delay(5); // Small delay between bytes
}
WriteByteRaw(PACKET_END_EXPECTED); // End byte, no ACK expected
}
void WriteByteAndReadAck(uint8_t b)
{
WriteByteRaw(b);
uint8_t ack = ReadByte();
HAL_UART_AbortReceive(&huart1);
uint8_t expectedAck = (uint8_t)~b;
if (ack != expectedAck)
{
// Handle NAK or retry
}
}
// Placeholder for platform-specific read functions
uint8_t KeepAlive() {
SendAckPacket();
ParsedPacket packet = ReceivePacket();
if(packet.type != PACKET_TYPE_ACK){
return 0;
}
return 1;
}
void EndCommunication(){
uint8_t endPacket = (uint8_t)PACKET_CMD_End;
SendPacket((uint8_t*)endPacket, 1);
ResetPacketCounter();
}
ParsedPacket* SendCustom(const uint8_t* data, int len, int *out_count) {
SendPacket((uint8_t*)data, len); //antes &
int count = 0;
ParsedPacket* packets = ReceivePackets(&count);
*out_count = count;
return packets;
}

View File

@@ -11,39 +11,34 @@
#include <string.h>
#include "stdint.h"
extern volatile uint8_t k_rx_byte;
extern volatile uint8_t rx_done_flag;
extern uint8_t connectionAlive;
#define ECU_INIT_ADDRESS 0xF1
#define KLINE_GPIO_PORT GPIOB
#define KLINE_PIN GPIO_PIN_14
#define KLINE_BUFFER_SIZE 20
/* ------------------------------------------------------------------ */
/* Packet framing */
/* ------------------------------------------------------------------ */
#define PACKET_END_EXPECTED 0x03
#define MAX_PACKET_SIZE 16
#define EEPROM_RESPONSE_BODY_MAX 64
//typedef uint8_t PacketCommand;
#define MAX_PACKET_SIZE 17 // if too big it will crash 128<max<256 //funcionando en 256
//si es menor, no funciona????
#define EEPROM_RESPONSE_BODY_MAX 64 // Adjust as needed
#define MAX_CONTROLLER_PACKETS 8
#define MAX_PACKET_BODY_LENGTH 64
/* ------------------------------------------------------------------ */
/* ECU info struct */
/* ------------------------------------------------------------------ */
typedef struct {
char client_ident[13]; // 12 + null
char client_ident[13]; /* 12 chars + null */
char unk_ident1[11];
char soft_info[11];
char unk_ident2[11];
char unk_ident3[11];
} ControllerInfo;
typedef struct {
uint8_t data[EEPROM_RESPONSE_BODY_MAX];
uint8_t length;
uint8_t valid;
} EepromResult;
/* ------------------------------------------------------------------ */
/* Packet commands */
/* ------------------------------------------------------------------ */
typedef enum {
PACKET_CMD_ReadIdent = 0x00,
@@ -66,9 +61,13 @@ typedef enum {
PACKET_CMD_AsciiData = 0xF6,
PACKET_CMD_WriteEepromResponse = 0xF9,
PACKET_CMD_FaultCodesResponse = 0xFC,
PACKET_CMD_ReadRomEepromResponse = 0xFD
PACKET_CMD_ReadRomEepromResponse = 0xFD,
} PacketCommand;
/* ------------------------------------------------------------------ */
/* Packet types */
/* ------------------------------------------------------------------ */
typedef enum {
PACKET_TYPE_UNKNOWN = 0,
PACKET_TYPE_ACK,
@@ -76,28 +75,19 @@ typedef enum {
PACKET_TYPE_ASCII_DATA,
PACKET_TYPE_CODING_WSC,
PACKET_TYPE_READ_EEPROM_RESPONSE,
PACKET_TYPE_READ_ROM_EEPROM_RESPONSE
PACKET_TYPE_READ_ROM_EEPROM_RESPONSE,
} PacketType;
/* ------------------------------------------------------------------ */
/* Parsed packet struct */
/* ------------------------------------------------------------------ */
typedef struct {
PacketType type;
uint8_t title;
uint8_t length;
uint8_t raw[MAX_PACKET_SIZE]; // raw bytes
uint8_t title; /* raw command byte */
uint8_t length; /* total bytes in raw[] */
uint8_t raw[MAX_PACKET_SIZE];
uint8_t isAckNak;
} ParsedPacket;
extern void ResetPacketCounter(void);
extern uint8_t ReadPacketCounter(void);
extern ParsedPacket ReceivePacket(void);
extern uint8_t ReadByte();
extern uint8_t ReadAndAckByte(void);
extern uint8_t KeepAlive(void);
void SendPacket(uint8_t* payload, uint8_t length);
ParsedPacket* SendCustom(const uint8_t* data, int len, int *out_count);
ParsedPacket* ReceivePackets(int *out_count);
#endif /* INC_IKW1281CONNECTION_H_ */

View File

@@ -1,783 +0,0 @@
/*
* kline.c
*
* Created on: Aug 18, 2025
* Author: herli
*/
#include "kline.h"
#include "IKW1281Connection.h"
#include "hc_06.h"
#include "stdint.h"
#include "main.h"
uint8_t BitBang = 0;
void KLine_Send5Baud(uint8_t data)
{
HAL_UART_DeInit(&huart1);
// Reconfigure TX pin as output
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = KLINE_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(KLINE_GPIO_PORT, &GPIO_InitStruct);
// 5 baud = 200ms per bit
const uint32_t bit_delay_ms = 200;
// Start bit (low)
HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, GPIO_PIN_RESET);
HAL_Delay(bit_delay_ms);
// Send 8 bits, LSB first
for (int i = 0; i < 8; i++) {
if (data & (1 << i)) {
HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, GPIO_PIN_RESET);
}
HAL_Delay(bit_delay_ms);
}
// Stop bit (high)
HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, GPIO_PIN_SET);
HAL_Delay(bit_delay_ms);
HAL_UART_Init(&huart1); // ensures TX idles high, necessary for g431
//HAL_UART_Receive_IT(&huart1, &k_rx_byte, 1);
}
int WakeUp(uint8_t controllerAddress, uint8_t evenParity){
KLine_Send5Baud(controllerAddress); // ECU address
connectionAlive = 1;
uint8_t syncByte = ReadByte();
if(syncByte != 0x55){return 0;}
uint8_t keywordLsb = ReadByte();
uint8_t keywordMsb = ReadAndAckByte();
int protocolVersion = ((keywordMsb & 0x7F) << 7) + (keywordLsb & 0x7F);
return protocolVersion;
}
/*void PrintAsciiPayload(const ParsedPacket* packet, UART_HandleTypeDef* huart)
{
char data[128]; // adjust size as needed
int len = 0;
// Start with a label
len += sprintf(&data[len], "ASCII: ");
for (int i = 4; i < packet->length - 1; i++) {
char c = packet->raw[i] & 0x7F;
if (len < sizeof(data) - 2) {
data[len++] = c;
}
}
// Null-terminate and add newline
data[len++] = '\r\n';
data[len] = '\0';
HAL_UART_Transmit(huart, (uint8_t*)data, len, 1000);
// Optional: report more data
if (packet->raw[3] > 0x7F) {
const char more[] = "More data available via ReadIdent\r\n";
HAL_UART_Transmit(huart, (uint8_t*)more, sizeof(more) - 1, 1000);
}
}*/
int FilterNonAckNak(ParsedPacket* all, int total, ParsedPacket* filtered, int max)
{
int j = 0;
for (int i = 0; i < total && j < max; i++) {
if (!all[i].isAckNak) {
filtered[j++] = all[i];
}
}
return j;
}
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;
}
/*void PrintEcuInfo(ControllerInfo* info) {
char data[64];
sprintf(data, "client_ident: %s\t\r\n", info->client_ident);
//HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000);
CDC_Transmit_FS((uint8_t*)data, strlen(data));
HAL_Delay(10); // allow USB stack to flush
sprintf(data, "unk_ident1: %s\t\r\n", info->unk_ident1);
//HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000);
CDC_Transmit_FS((uint8_t*)data, strlen(data));
HAL_Delay(10); // allow USB stack to flush
sprintf(data, "soft_info: %s\t\r\n", info->soft_info);
//HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000);
CDC_Transmit_FS((uint8_t*)data, strlen(data));
HAL_Delay(10); // allow USB stack to flush
if (strlen(info->unk_ident2) > 0) {
sprintf(data, "unk_ident2: %s\t\r\n", info->unk_ident2);
//HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000);
CDC_Transmit_FS((uint8_t*)data, strlen(data));
HAL_Delay(10); // allow USB stack to flush
}
if (strlen(info->unk_ident3) > 0) {
sprintf(data, "unk_ident3: %s\t\r\n", info->unk_ident3);
CDC_Transmit_FS((uint8_t*)data, strlen(data));
HAL_Delay(10); // allow USB stack to flush
// HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000);
}
}*/
#define MAX_EEPROM_DATA 256 // adjust to your ECUs 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);
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
}
/*
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) {
freeParsedPackets(packets, packetCount);
*outLength = 0;
return NULL;
}
// Filter to exclude Ack/Nak
ParsedPacket* useful = malloc(packetCount * sizeof(ParsedPacket));
int usefulCount = 0;
for (int i = 0; i < packetCount; i++) {
if (!packets[i].isAckNak) {
useful[usefulCount++] = packets[i];
} else {
// If dynamically allocated, free individual packet.raw
free(packets[i].raw);
}
}
free(packets); // Free the original array pointer (but not the reused raw data)
if (usefulCount != 1) {
freeParsedPackets(useful, usefulCount);
*outLength = 0;
return NULL; // Or handle as error
}
uint8_t* result = malloc(useful[0].length - 4); // Remove 3-byte header + end (or however yours works)
if (!result) {
freeParsedPackets(useful, usefulCount);
*outLength = 0;
return NULL;
}
memcpy(result, useful[0].raw + 3, useful[0].length - 4); // Adjust if header is different
*outLength = useful[0].length - 4;
freeParsedPackets(useful, usefulCount);
return result;
}*/
void ReadSerialNumber(char* serialOut, int maxLen) {
uint8_t request[] = { 0x19, 0x06, 0x00, 0x80 };
int packetCount = 0;
ParsedPacket* packets = SendCustom(request, sizeof(request), &packetCount);
if (!packets || packetCount == 0) {
serialOut[0] = '\0';
return;
}
for (int i = 0; i < packetCount; i++) {
ParsedPacket* p = &packets[i];
if (!p->isAckNak && p->type == PACKET_TYPE_READ_EEPROM_RESPONSE) {
int dataLen = p->length - 4; // Skip length, counter, command, end byte
if (dataLen > maxLen - 1) {
dataLen = maxLen - 1;
}
memcpy(serialOut, p->raw + 3, dataLen); // Adjust if needed
serialOut[dataLen] = '\0';
//freeParsedPackets(packets, packetCount);
return;
}
}
serialOut[0] = '\0';
//freeParsedPackets(packets, packetCount);
}
/*void freeParsedPackets(ParsedPacket* packets, int count) {
if (!packets) return;
for (int i = 0; i < count; i++) {
if (packets[i].raw != NULL) {
//free(packets[i].raw);
//packets[i].raw = NULL; // Avoid double free
}
}
//free(packets); // Free the packet array itself
}*/
uint16_t ExtractEepromAddress(ParsedPacket *packets, int packet_count) {
for (int i = 0; i < packet_count; i++) {
ParsedPacket *p = &packets[i];
if (!p->isAckNak && p->length > 4) {
uint32_t address = (p->raw[3+1] << 8) | p->raw[3];
return address - 10;
}
}
return 0;
}
float ReadDfi(){
float Dfi = 0.0;
if(!KeepAlive()){
KLINE_THROW_NONALIVE_EXCEPTION(0);
return 0.0;
}
//uint8_t request[] = { 0x18, 0x00, 0x03, 0xFF, 0xFF }; //TODO ERROR ENVIA AC 7E 00 20 00
int packet_count = 0;
//ParsedPacket* packets = SendCustom(request, 5, &packet_count, 1); //this changes with pump version but for now no exceptions
ParsedPacket* packets = SendCustom((uint8_t[]){ 0x18, 0x00, 0x03, 0xFF, 0xFF }, 5, &packet_count);
//Dfi = 1.0;
if(!KeepAlive()){
KLINE_THROW_NONALIVE_EXCEPTION(0);
return 0.0;
}
uint8_t request2[] = { 0x19, 0x02, 0x00, 0x44};
packets = SendCustom(request2, 4, &packet_count); //this changes with pump version but for now no exceptions
// Filter to exclude Ack/Nak
for (int i = 0; i < packet_count; i++) {
if (!packets[i].isAckNak) {
Dfi = ((int8_t)packets[i].raw[3] * 3.0) / 256.0;
}
}
//free(packets); // Free the original array pointer (but not the reused raw data)
/*if(Dfi != 0.0){
}*/
return Dfi;
}
/**
* Write DFI parameter (EEPROM 0x44) after password unlock.
*
* version mapping (as in your C#):
* default: {18 00 03 2F FF ...}
* version==1: {18 00 03 2F F2 ...}
* version==2 or 3: {18 00 03 FF F2 ...}
*
* Returns 1 on success, 0 on failure (non-alive).
*/
int WriteDfi(float dfi, int version)
{
if (!KeepAlive()) {
KLINE_THROW_NONALIVE_EXCEPTION(0);
return 0;
}
// Password packets (14 bytes)
static const uint8_t pass_default[14] = {
0x18, 0x00, 0x03, 0x2F, 0xFF, 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45
}; // V1 KHTCA8G0E (per your comment)
static const uint8_t pass_v2[14] = {
0x18, 0x00, 0x03, 0x2F, 0xF2, 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45
}; // V2 KHTCA8G0E
static const uint8_t pass_v3v4[14] = {
0x18, 0x00, 0x03, 0xFF, 0xF2, 0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45
}; // V3, V4
const uint8_t *password_packet = pass_default;
if (version == 1) {
password_packet = pass_v2;
} else if (version == 2 || version == 3) {
password_packet = pass_v3v4;
}
int packet_count = 0;
(void)SendCustom(password_packet, 14, &packet_count); // throwOnNak=1 (strict)
if (!KeepAlive()) {
KLINE_THROW_NONALIVE_EXCEPTION(0);
return 0;
}
// C#:
// sbyte value = (sbyte)((dfi * 256.0) / 3);
// if (value == 0) value = 1;
//
// NOTE: with dfi up to +1.5, math gives 128.0. That does not fit in int8_t.
// We clamp to [-128..127] to avoid wrap to -128.
int vi = (int)((dfi * 256.0f) / 3.0f); // trunc toward 0 (matches typical C# unchecked cast behavior for in-range)
vi = vi > 127 ? 127:vi;
vi = vi < -128 ? -128:vi;
//vi = clamp_int(vi, -128, 127);
int8_t value = (int8_t)vi;
if (value == 0) value = 1;
// byte value_csum = (byte)(0 - (byte)value);
uint8_t value_u = (uint8_t)value;
uint8_t value_csum = (uint8_t)(0u - value_u);
// Write command
uint8_t req[7] = { 0x1A, 0x02, 0x00, 0x44, value_u, value_csum, 0x03 };
(void)SendCustom(req, 7, &packet_count); // throwOnNak=1 (strict)
if (!KeepAlive()) {
KLINE_THROW_NONALIVE_EXCEPTION(0);
return 0;
}
return 1;
}
void ReadCustomerChangeIndex(){
if(!KeepAlive()){
KLINE_THROW_NONALIVE_EXCEPTION(0);
return;
}
uint8_t request[] = { 0x18, 0x00, 0x00, 0x82, 0x33 };
int packet_count = 0;
ParsedPacket* packets = SendCustom(request, 5, &packet_count); //this changes with pump version but for now no exceptions
//char message[40];
//sprintf(message, "Reading customer change index\r\n");
//HAL_UART_Transmit(&huart2, (uint8_t*)message, strlen(message), 1000);
if(!KeepAlive()){
KLINE_THROW_NONALIVE_EXCEPTION(12);
return;
}
uint16_t cust_change_address = 0;
int data_length = 0;
uint8_t* test_data = ReadRomEeprom(0x9FFE, 2, &data_length);
if(data_length > 1){
cust_change_address = (uint16_t)((test_data[1] << 8) | test_data[0]);
cust_change_address += 3;
}
test_data = ReadRomEeprom(cust_change_address, 6, &data_length);
char cust_change_index[12];
if (test_data && data_length > 0) {
for (int i = 0; i < data_length; i++) {
cust_change_index[i] = (char)test_data[i]; // Convert byte to char
}
}
//sprintf(message, "Mod. Index : %s\r\n", cust_change_index);
//CDC_Transmit_FS((uint8_t*)message, strlen(message));
/*char cust_change_index[12];
if (test_data && data_length > 0) {
int copy_len = (data_length < (sizeof(cust_change_index) - 1))
? data_length
: (sizeof(cust_change_index) - 1);
for (int i = 0; i < copy_len; i++) {
cust_change_index[i] = (char)test_data[i];
}
cust_change_index[copy_len] = '\0'; // Null-terminate
}
sprintf(message, "Mod. Index : %s\r\n", cust_change_index);
CDC_Transmit_FS((uint8_t*)message, strlen(message));*/
//HAL_UART_Transmit(&huart2, (uint8_t*)cust_change_index, strlen(cust_change_index), 1000);
}
char identStr[11]; //11+1
void IdentifyEcu() {
//char outputStr[35];
//float dFi = ReadDfi();
//sprintf(outputStr, "dFi: %d.%02d\t\t\r\n", (int)dFi, (int)((dFi - (int)dFi) * 100));
//CDC_Transmit_FS((uint8_t*)outputStr, strlen(outputStr));
ReadCustomerChangeIndex();//Necessary
if(!KeepAlive()){
KLINE_THROW_NONALIVE_EXCEPTION(0);
return;
}
int packet_count = 0;
ParsedPacket* packets = SendCustom((uint8_t[]){ 0x01, 0x02, 0x00, 0xC6 }, 4, &packet_count);
//uint16_t address = ExtractEepromAddress(packets, count);
uint16_t address = ExtractEepromAddress(packets, packet_count);
if(!KeepAlive()){
KLINE_THROW_NONALIVE_EXCEPTION(1);
return;
}
if (address != 0) {
int data_length = 0;
uint8_t* test_data = ReadRomEeprom(address, 10, &data_length);
for (int i = 0; i < data_length && i < sizeof(identStr) - 1; i++) {
identStr[i] = (char)test_data[i];
} //identStr[10] = '\0';
//HAL_UART_Transmit(&huart2, (uint8_t*)identStr, strlen(identStr), 1000);
//sprintf(outputStr, "Ident. : %s\t\r\n", identStr);
//CDC_Transmit_FS((uint8_t*)outputStr, strlen(outputStr));
}
if(!KeepAlive()){
KLINE_THROW_NONALIVE_EXCEPTION(2);
return;
}
char serial[20];
ReadSerialNumber(serial, sizeof(serial));
//sprintf(serial, "serial numb: %s\r\n", serial);
//HAL_UART_Transmit(&huart2, (uint8_t*)serial, strlen(serial), 1000);
/*sprintf(outputStr, "Serial N. : %s\t\r\n", serial);
CDC_Transmit_FS((uint8_t*)outputStr, strlen(outputStr));*/
//sprintf(serial, "serial numb: %s\r\n", serial);
//HAL_UART_Transmit(&huart2, (uint8_t*)data, strlen(data), 1000);
}
/* FAULT CODE READING */
#define FAULTCODE_NONE_DTC 0x00
/* --- Tuning limits (adapt to your max) --- */
#define MAX_FAULTCODES 20
#define MAX_FC_AGGREGATE_LEN 512 // total concatenated body bytes
int ReadFaultCodes(FaultCode* outCodes, int maxCodes)
{
//char logmsg[64];
if (!outCodes || maxCodes <= 0) return -1;
// Keep the link alive as you do elsewhere TODO
if (!KeepAlive()) {
KLINE_THROW_NONALIVE_EXCEPTION(0);
return -1;
}
// "Sending ReadFaultCodes packet"
/*sprintf(logmsg, "Sending ReadFaultCodes packet\r\n");
CDC_Transmit_FS((uint8_t*)logmsg, (uint16_t)strlen(logmsg));*/
// Send single-byte payload with command 0x07
uint8_t cmd = (uint8_t)PACKET_CMD_FaultCodesRead; // already defined in your enum
SendPacket(&cmd, 1);
// Receive all response packets
int packet_count = 0;
ParsedPacket* packets = ReceivePackets(&packet_count);
if (!packets || packet_count <= 0) {
return 0; // no codes
}
// Concatenate all bodies from non-ACK/NAK packets
// Body = raw[3 .. (3 + body_len - 1)], where body_len = (length - 3).
// Full raw frame is: [len][counter][command][body...][end]
// Your receiver code writes the end byte separately (0x03).
// NOTE: guard against malformed lengths.
uint8_t fc_buf[64];
int fc_len = 0;
for (int i = 0; i < packet_count; i++) {
if (packets[i].isAckNak) continue;
// If you classify types and want to be strict, enforce here:
if (packets[i].title != PACKET_CMD_FaultCodesResponse) {
/*sprintf(logmsg, "Expected FaultCodesPacket but got type=0x%02X\r\n", packets[i].type);
CDC_Transmit_FS((uint8_t*)logmsg, (uint16_t)strlen(logmsg));*/
//free(packets);
return -1;
}
if (!packets[i].raw || packets[i].length < 3) continue;
int body_len = (int)packets[i].length - 4; // subtract [len, counter, command]
if (body_len <= 0) continue;
// Defensive: make sure the raw buffer actually has those bytes (+1 end is read separately)
// We assume your parser filled .raw accordingly.
if ((fc_len + body_len) > (int)sizeof(fc_buf)) {
/*sprintf(logmsg, "Fault code buffer overflow\r\n");
CDC_Transmit_FS((uint8_t*)logmsg, (uint16_t)strlen(logmsg));*/
//free(packets);
return -1;
}
memcpy(&fc_buf[fc_len], &packets[i].raw[3], (size_t)body_len);
fc_len += body_len;
}
// Done with the packet list (your comment: free only the array, not reused raw)
//free(packets);
// Parse aggregated data:
// C# does: take first 3 bytes (dtc,status,extra), then Skip(8) per record.
// That means record stride = 8 bytes, but only first 3 matter here.
// Stop when fewer than 3 bytes remain.
int produced = 0;
int off = 0;
while (off + 3 <= fc_len) {
uint8_t dtc = fc_buf[off + 0];
uint8_t status = fc_buf[off + 1];
uint8_t extra = fc_buf[off + 2];
if (dtc != FAULTCODE_NONE_DTC) {
if (produced < maxCodes) {
outCodes[produced].dtc = dtc;
outCodes[produced].status = status;
outCodes[produced].extra = extra;
produced++;
} else {
/*sprintf(logmsg, "Fault code output full (%d)\r\n", maxCodes);
CDC_Transmit_FS((uint8_t*)logmsg, (uint16_t)strlen(logmsg));*/
break;
}
}
// Advance to next 8-byte record
off += 8;
}
return produced; // number of valid fault codes in outCodes[]
}
int ClearFaultCodes(void)
{
int packet_count = 0;
uint8_t req[1] = { PACKET_CMD_FaultCodesDelete };
ParsedPacket *packets = SendCustom(req, 1, &packet_count);
// C# expects exactly 1 packet
if (packet_count != 1) {
// in embedded: return error code (or assert/log)
return 0;
}
ParsedPacket *p = &packets[0];
// Must be ACK or NAK
if (!p->isAckNak) {
return 0;
}
if (p->type == PACKET_TYPE_NAK) return 0;
if (p->type == PACKET_TYPE_ACK) return 1;
return 0;
}
/*void PrintFaultCodes(const FaultCode* list, int n)
{
if (!list || n <= 0) {
const char* msg = "No fault codes\r\n";
CDC_Transmit_FS((uint8_t*)msg, (uint16_t)strlen(msg));
return;
}
for (int i = 0; i < n; i++) {
PrintFaultCode(&list[i]);
HAL_Delay(10);
}
}*/
typedef struct { uint8_t code; const char* text; } DtcMapEntry;
static const DtcMapEntry DTC_MAP[] = {
{0x50, "Fuel quantity solenoid valve Output stage error"},
{0x51, "Fuel quantity solenoid valve."},
{0x52, "Angle sensor/ IWZ system."},
{0x53, "Angle sensor/ IWZ system"},
{0x54, "Control unit temperature sensor, temperature too high"},
{0x55, "Control unit temperature sensor"},
{0x56, "Battery voltage out of range"},
{0x57, "Timing device control. Permanent control deviation"},
{0x58, "Fuel quantity / timing solenoid valve"},
{0x59, "BIP Fault (Begin of Injection Point)"},
{0x5A, "Engine speed signal"},
{0x5B, "Engine speed signal"},
{0x5C, "CAN-Bus (sporadic)"},
{0x5D, "CAN bus error"},
{0x5E, "Self-test error"},
};
static const char* GetDtcText(uint8_t code)
{
for (unsigned i = 0; i < (sizeof(DTC_MAP)/sizeof(DTC_MAP[0])); i++) {
if (DTC_MAP[i].code == code) return DTC_MAP[i].text;
}
return "Unknown Error Code";
}
/*int FaultCode_ToString(const FaultCode* fc, char* dst, int dst_len)
{
if (!fc || !dst || dst_len <= 0) return 0;
uint8_t status1 = (uint8_t)(fc->status & 0x7F);
const char* txt = GetDtcText(fc->dtc);
// Format like: "5A Engine speed signal (03)\r\n"
// (use %02u to mimic ":d2" from your C# example)
int n = snprintf(dst, (size_t)dst_len, "%02X %s (%02u)\r\n",
fc->dtc, txt, status1);
if (n < 0) n = 0;
if (n >= dst_len) n = dst_len - 1;
return n;
}*/
/*void PrintFaultCode(const FaultCode* fc)
{
char line[128];
int n = FaultCode_ToString(fc, line, sizeof(line));
if (n > 0) {
CDC_Transmit_FS((uint8_t*)line, (uint16_t)n);
}
}*/
void KLINE_THROW_NONALIVE_EXCEPTION(uint8_t id){
//char erroralive[20];
//sprintf(erroralive, "isnt alive %u\r\r\n", id);
BT_KLINE_ERROR();
//HAL_UART_Transmit(&huart2, (uint8_t*)erroralive, strlen(erroralive), 1000);
//CDC_Transmit_FS((uint8_t*)erroralive, strlen(erroralive));
}
uint8_t ReadAudiPin(uint16_t* pin) {
int data_length = 0;
uint8_t* data = ReadRomEeprom(0x04FA, 2, &data_length);
if(data_length < 1){return 0;}
*pin = (uint16_t)((data[1] << 8) | data[0]);
return 1;
}
uint8_t WriteAudiPin(uint16_t pin) {
/*uint8_t* data = ReadRomEeprom(0x04FA, 2, &data_length);
if(data_length < 1){return 0;}
*pin = (uint16_t)((data[1] << 8) | data[0]);*/
return 1;
}
uint8_t ReadVoltage(uint16_t* volt) {
int data_length = 0;
uint8_t* data = ReadRomEeprom(0x0142, 2, &data_length);
if(data_length < 1){return 0;}
*volt = (uint16_t)((data[1] << 8) | data[0]);
return 1;
}

View File

@@ -1,34 +0,0 @@
/*
* kline.h
*
* Created on: Aug 18, 2025
* Author: herli
*/
#ifndef INC_KLINE_H_
#define INC_KLINE_H_
#include "IKW1281Connection.h"
#define ECU_INIT_ADDRESS 0xF1
#define KLINE_GPIO_PORT GPIOB
#define KLINE_PIN GPIO_PIN_14
extern uint8_t BitBang;
typedef struct {
uint8_t dtc; // Primary DTC byte
uint8_t status; // Status byte
uint8_t extra; // Third byte in the 3-byte header (kept for parity with C#)
} FaultCode;
extern ControllerInfo ReadEcuInfo(void);
int WakeUp(uint8_t controllerAddress, uint8_t evenParity);
void IdentifyEcu(void);
int ReadFaultCodes(FaultCode* outCodes, int maxCodes);
uint8_t ReadVoltage(uint16_t* volt);
extern void KLINE_THROW_NONALIVE_EXCEPTION(uint8_t id);
int FaultCode_ToString(const FaultCode* fc, char* dst, int dst_len);
#endif /* INC_KLINE_H_ */

View File

@@ -0,0 +1,791 @@
/*
* kline_fsm.c
*
* Full non-blocking KWP1281 state machine.
*
* Timing model
* ------------
* Every state that needs a delay stores a deadline:
* fsm.deadline = HAL_GetTick() + N_ms;
* and on the next ST_xxx entry just checks:
* if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return;
*
* This means KLine_FSM_Run() returns almost immediately on every call
* and the main loop stays responsive for HC-06 processing.
*
* Zero HAL_Delay calls anywhere in this file.
*/
#include "kline_fsm.h"
#include "kline_ring.h"
#include "main.h" /* huart1, KLINE_GPIO_PORT, KLINE_PIN */
#include "hc_06.h" /* BT_OnPSG5CommEstablished, BT_SendCommStatus */
#include <string.h>
#include <stdint.h>
/* ------------------------------------------------------------------ */
/* Internal timing constants (ms) */
/* ------------------------------------------------------------------ */
#define BITBANG_BIT_MS 200u /* 5 baud = 200 ms/bit */
#define KWP_INTERBYTE_TX_MS 5u /* between bytes we send */
#define KWP_BYTE_TIMEOUT_MS 1000u /* max wait for a byte from ECU */
#define KWP_KW2_COMP_DELAY_MS 5u /* delay before sending ~KW2 */
#define KWP_KEEPALIVE_PERIOD_MS 500u /* how often we must send keep-alive */
/* ------------------------------------------------------------------ */
/* Low-level TX helpers (synchronous tiny at 9600 baud, safe) */
/* ------------------------------------------------------------------ */
static void tx_byte(uint8_t b)
{
HAL_UART_Transmit(&huart1, &b, 1, 10);
}
void KLine_FSM_TxByte(uint8_t b)
{
tx_byte(b);
}
/* ------------------------------------------------------------------ */
/* Packet parsing helpers */
/* ------------------------------------------------------------------ */
#define MAX_RECV_PACKETS 16
#define MAX_PACKET_RAW 20
/* Scratch packet being assembled right now */
static ParsedPacket cur_pkt;
static uint8_t cur_pkt_body_idx; /* how many body bytes received so far */
/* Completed packet list for the current ReceivePackets burst */
static ParsedPacket pkt_list[MAX_RECV_PACKETS];
static int pkt_count;
static uint8_t pkt_counter_val;
static uint8_t pkt_counter_init;
static void classify_packet(ParsedPacket *p)
{
p->title = p->raw[2];
p->isAckNak = 0;
switch (p->raw[2]) {
case PACKET_CMD_ACK:
p->type = PACKET_TYPE_ACK;
p->isAckNak = 1;
break;
case PACKET_CMD_NAK:
p->type = PACKET_TYPE_NAK;
p->isAckNak = 1;
break;
case PACKET_CMD_AsciiData:
p->type = (p->raw[3] == 0x00) ? PACKET_TYPE_CODING_WSC
: PACKET_TYPE_ASCII_DATA;
break;
case PACKET_CMD_ReadEepromResponse:
p->type = PACKET_TYPE_READ_EEPROM_RESPONSE;
break;
case PACKET_CMD_ReadRomEepromResponse:
p->type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE;
break;
default:
p->type = PACKET_TYPE_UNKNOWN;
break;
}
}
/* ------------------------------------------------------------------ */
/* Outgoing packet scratch buffer */
/* ------------------------------------------------------------------ */
#define MAX_SEND_PACKET 17
static uint8_t send_buf[MAX_SEND_PACKET];
static uint8_t send_len; /* total bytes in send_buf (excl. end byte) */
static uint8_t send_idx; /* index of next byte to transmit */
//static uint8_t tx_pkt_counter = 0;
static void build_packet(const uint8_t *payload, uint8_t payload_len)
{
send_len = payload_len + 2; /* length byte + counter byte + payload */
send_buf[0] = send_len;
send_buf[1] = pkt_counter_val++;
for (uint8_t i = 0; i < payload_len; i++)
send_buf[2 + i] = payload[i];
//send_buf[2 + payload_len] = PACKET_END_EXPECTED;
send_idx = 0;
}
/* ------------------------------------------------------------------ */
/* FSM context */
/* ------------------------------------------------------------------ */
typedef enum {
/* What to do after the generic send-packet sub-FSM finishes */
AFTER_SEND_IDLE,
AFTER_SEND_RECV_ECUINFO,
AFTER_SEND_RECV_IDENT,
AFTER_SEND_RECV_KA,
AFTER_SEND_RECV_FAULTCODES,
} AfterSend;
typedef enum {
/* What to do after the generic recv-packets sub-FSM finishes */
AFTER_RECV_ECUINFO,
AFTER_RECV_IDENT,
AFTER_RECV_KA,
AFTER_RECV_FAULTCODES,
} AfterRecv;
typedef struct {
KLine_FSMState state;
KLine_Status status;
uint32_t deadline;
/* 5-baud bit-bang */
uint8_t bb_addr;
uint8_t bb_bit; /* current bit index 0-7 */
/* WakeUp */
uint8_t kw1;
/* Generic TX sub-machine */
AfterSend after_send;
uint8_t last_sent_byte; /* for ACK check */
/* Generic RX sub-machine */
AfterRecv after_recv;
/* ECU info */
ControllerInfo ecu_info;
/* Fault codes */
KLine_FaultCode fault_codes[KLINE_MAX_FAULT_CODES];
int fault_count;
/* Keep-alive */
uint32_t ka_due;
uint8_t ka_suspended; /* non-zero → KA paused while an op runs */
/* IdentifyEcu phase counter (how many ident packets received so far) */
uint8_t ident_phase;
} KLine_FSM;
static KLine_FSM fsm;
/* ------------------------------------------------------------------ */
/* Forward declarations for sub-machine entry points */
/* ------------------------------------------------------------------ */
static void enter_send_packet(const uint8_t *payload, uint8_t len, AfterSend after);
static void enter_recv_packets(AfterRecv after);
/* ------------------------------------------------------------------ */
/* Byte-level receive helpers */
/* ------------------------------------------------------------------ */
/*
* Try to read a byte with complement-ACK from the ECU.
* Returns 1 and stores byte in *out if available, 0 if still waiting.
* Handles timeout: transitions to ST_ERROR on timeout.
*/
static uint8_t try_read_and_ack(uint8_t *out)
{
if (!KLine_Ring_Available()) {
if ((int32_t)(HAL_GetTick() - fsm.deadline) >= 0) {
fsm.state = ST_ERROR;
fsm.status = KLINE_STATUS_ERROR;
}
return 0;
}
*out = KLine_Ring_Pop();
tx_byte((uint8_t)~(*out)); /* send complement ACK */
uint32_t echo_deadline = HAL_GetTick() + 5u; /* ~1ms at 9600, 5ms generous */
while (!KLine_Ring_Available()) {
if ((int32_t)(HAL_GetTick() - echo_deadline) >= 0) break;
}
if (KLine_Ring_Available()) KLine_Ring_Pop();
fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS;
return 1;
}
static uint8_t try_read_raw(uint8_t *out)
{
if (!KLine_Ring_Available()) {
if ((int32_t)(HAL_GetTick() - fsm.deadline) >= 0) {
fsm.state = ST_ERROR;
fsm.status = KLINE_STATUS_ERROR;
}
return 0;
}
*out = KLine_Ring_Pop();
fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS;
return 1;
}
/* ------------------------------------------------------------------ */
/* ECU info parser */
/* ------------------------------------------------------------------ */
static void parse_ecu_info(void)
{
char combined[128] = {0};
for (int i = 0; i < pkt_count; i++) {
ParsedPacket *p = &pkt_list[i];
if (p->type == PACKET_TYPE_ASCII_DATA && p->length > 4) {
size_t len = p->length - 4;
strncat(combined, (char*)(p->raw + 3), len);
}
}
memset(&fsm.ecu_info, 0, sizeof(ControllerInfo));
strncpy(fsm.ecu_info.client_ident, combined, 12);
strncpy(fsm.ecu_info.unk_ident1, combined + 12, 10);
strncpy(fsm.ecu_info.soft_info, combined + 22, 10);
if (strlen(combined) > 40)
strncpy(fsm.ecu_info.unk_ident2, combined + 32, 10);
if (strlen(combined) > 50)
strncpy(fsm.ecu_info.unk_ident3, combined + 42, 10);
}
/* ------------------------------------------------------------------ */
/* Fault code parser */
/* ------------------------------------------------------------------ */
static void parse_fault_codes(void)
{
uint8_t fc_buf[64];
int fc_len = 0;
for (int i = 0; i < pkt_count; i++) {
if (pkt_list[i].isAckNak) continue;
if (pkt_list[i].title != PACKET_CMD_FaultCodesResponse) continue;
int body = (int)pkt_list[i].length - 4;
if (body <= 0) continue;
if (fc_len + body > (int)sizeof(fc_buf)) break;
memcpy(&fc_buf[fc_len], &pkt_list[i].raw[3], (size_t)body);
fc_len += body;
}
fsm.fault_count = 0;
int off = 0;
while (off + 3 <= fc_len && fsm.fault_count < KLINE_MAX_FAULT_CODES) {
if (fc_buf[off] != 0x00) {
fsm.fault_codes[fsm.fault_count].dtc = fc_buf[off + 0];
fsm.fault_codes[fsm.fault_count].status = fc_buf[off + 1];
fsm.fault_codes[fsm.fault_count].extra = fc_buf[off + 2];
fsm.fault_count++;
}
off += 8;
}
}
/* ------------------------------------------------------------------ */
/* Sub-machine entry helpers */
/* ------------------------------------------------------------------ */
static void enter_send_packet(const uint8_t *payload, uint8_t len, AfterSend after)
{
build_packet(payload, len);
fsm.after_send = after;
fsm.state = ST_SENDPKT_BYTE;
fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS;
}
static void enter_recv_packets(AfterRecv after)
{
pkt_count = 0;
pkt_counter_init = 0;
fsm.after_recv = after;
/* Start assembling the first packet */
memset(&cur_pkt, 0, sizeof(cur_pkt));
cur_pkt_body_idx = 0;
fsm.state = ST_RECVPKT_LENGTH;
fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS;
}
/* ------------------------------------------------------------------ */
/* Main FSM tick */
/* ------------------------------------------------------------------ */
void KLine_FSM_Run(void)
{
switch (fsm.state)
{
/* ====================================================== */
case ST_IDLE:
/* Nothing to do — wait for KLine_FSM_RequestConnect() */
break;
/* ====================================================== */
/* 5-baud bit-bang init */
/* ====================================================== */
case ST_BITBANG_START:
{
/* De-init UART, take over TX pin as GPIO */
HAL_UART_DeInit(&huart1);
GPIO_InitTypeDef g = {0};
g.Pin = KLINE_PIN;
g.Mode = GPIO_MODE_OUTPUT_PP;
g.Pull = GPIO_NOPULL;
g.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(KLINE_GPIO_PORT, &g);
/* Drive start bit (low) */
HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, GPIO_PIN_RESET);
fsm.bb_bit = 0;
fsm.deadline = HAL_GetTick() + BITBANG_BIT_MS;
fsm.state = ST_BITBANG_STARTBIT;
break;
}
case ST_BITBANG_STARTBIT:
if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return;
/* Fall into first data bit */
fsm.state = ST_BITBANG_DATABIT;
/* intentional fall-through */
/* fall through */
case ST_BITBANG_DATABIT:
{
if (fsm.bb_bit > 0) {
/* We get here after a bit-delay has elapsed */
if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return;
}
if (fsm.bb_bit == 8) {
/* All data bits sent — send stop bit */
HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, GPIO_PIN_SET);
fsm.deadline = HAL_GetTick() + BITBANG_BIT_MS;
fsm.state = ST_BITBANG_STOPBIT;
break;
}
GPIO_PinState level = (fsm.bb_addr & (1u << fsm.bb_bit))
? GPIO_PIN_SET : GPIO_PIN_RESET;
HAL_GPIO_WritePin(KLINE_GPIO_PORT, KLINE_PIN, level);
fsm.bb_bit++;
fsm.deadline = HAL_GetTick() + BITBANG_BIT_MS;
break;
}
case ST_BITBANG_STOPBIT:
if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return;
fsm.state = ST_BITBANG_DONE;
break;
case ST_BITBANG_DONE:
{
/* Restore UART */
extern void MX_USART1_UART_Init(void); /* generated by CubeMX */
MX_USART1_UART_Init();
KLine_Ring_Init(); /* flushes ring and arms RX IT */
fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS;
fsm.state = ST_WAIT_SYNC;
break;
}
/* ====================================================== */
/* WakeUp handshake */
/* ====================================================== */
case ST_WAIT_SYNC:
{
uint8_t b;
if (!try_read_raw(&b)) return;
if (b != 0x55) {
fsm.state = ST_ERROR;
fsm.status = KLINE_STATUS_ERROR;
return;
}
fsm.state = ST_READ_KW1;
fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS;
break;
}
case ST_READ_KW1:
{
uint8_t b;
if (!try_read_raw(&b)) return;
//tx_byte((uint8_t)~b); /* NO ACK KW1 */
fsm.kw1 = b;
fsm.state = ST_READ_KW2;
fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS;
break;
}
case ST_READ_KW2:
{
uint8_t b;
if (!try_read_raw(&b)) return;
/* Store KW2, schedule complement send after inter-byte delay */
send_buf[0] = (uint8_t)~b; /* reuse send_buf[0] as a scratch */
fsm.deadline = HAL_GetTick() + KWP_KW2_COMP_DELAY_MS;
fsm.state = ST_SEND_KW2_COMPLEMENT;
break;
}
case ST_SEND_KW2_COMPLEMENT:
if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return;
tx_byte(send_buf[0]); /* send ~KW2 */
//uint8_t b;
//if (!try_read_raw(&b)) return; //should return answer to kw2
while (!KLine_Ring_Available()) {}
KLine_Ring_Pop(); /* discard loopback echo */
/* Now receive the ECU info packet burst */
enter_recv_packets(AFTER_RECV_ECUINFO);
break;
/* ====================================================== */
/* Generic send-packet sub-machine */
/* ====================================================== */
case ST_SENDPKT_BYTE:
{
if (send_idx >= send_len) {
/* All bytes sent — send end byte (no ACK) */
tx_byte(PACKET_END_EXPECTED);
while (!KLine_Ring_Available()) {}
KLine_Ring_Pop(); /* discard loopback echo */
/* Transition based on what comes after this send */
switch (fsm.after_send) {
case AFTER_SEND_RECV_ECUINFO:
enter_recv_packets(AFTER_RECV_ECUINFO); break;
case AFTER_SEND_RECV_IDENT:
enter_recv_packets(AFTER_RECV_IDENT); break;
case AFTER_SEND_RECV_KA:
enter_recv_packets(AFTER_RECV_KA); break;
case AFTER_SEND_RECV_FAULTCODES:
enter_recv_packets(AFTER_RECV_FAULTCODES);break;
default:
fsm.state = ST_KA_WAIT; break;
}
break;
}
/* Send next byte */
fsm.last_sent_byte = send_buf[send_idx++];
tx_byte(fsm.last_sent_byte);
while (!KLine_Ring_Available()) {}
KLine_Ring_Pop(); /* discard loopback echo */
fsm.deadline = HAL_GetTick() + KWP_BYTE_TIMEOUT_MS;
fsm.state = ST_SENDPKT_WAIT_ACK;
break;
}
case ST_SENDPKT_WAIT_ACK:
{
/* Wait for ECU to echo ~byte as ACK */
if (!KLine_Ring_Available()) {
if ((int32_t)(HAL_GetTick() - fsm.deadline) >= 0) {
fsm.state = ST_ERROR;
fsm.status = KLINE_STATUS_ERROR;
}
return;
}
uint8_t ack = KLine_Ring_Pop();
(void)ack; /* Could check ack == ~last_sent_byte here */
/* Inter-byte delay before next byte */
fsm.deadline = HAL_GetTick() + KWP_INTERBYTE_TX_MS;
fsm.state = ST_SENDPKT_NEXT_BYTE;
break;
}
case ST_SENDPKT_NEXT_BYTE:
if ((int32_t)(HAL_GetTick() - fsm.deadline) < 0) return;
fsm.state = ST_SENDPKT_BYTE;
break;
/* ====================================================== */
/* Generic receive-packets sub-machine */
/* ====================================================== */
case ST_RECVPKT_LENGTH:
{
//if ((int32_t)(HAL_GetTick() - (fsm.deadline - KWP_BYTE_TIMEOUT_MS + 25)) < 0) return;
uint8_t b;
if (!try_read_and_ack(&b)) return;
if(send_len == 3){
send_len++;
}
memset(&cur_pkt, 0, sizeof(cur_pkt));
cur_pkt_body_idx = 0;
cur_pkt.raw[0] = b; /* raw[0] = length byte */
fsm.state = ST_RECVPKT_COUNTER;
break;
}
case ST_RECVPKT_COUNTER:
{
uint8_t b;
if (!try_read_and_ack(&b)) return;
if (!pkt_counter_init) {
pkt_counter_val = b;
pkt_counter_init = 1;
}
pkt_counter_val++;
cur_pkt.raw[1] = b;
fsm.state = ST_RECVPKT_COMMAND;
break;
}
case ST_RECVPKT_COMMAND:
{
uint8_t b;
if (!try_read_and_ack(&b)) return;
cur_pkt.raw[2] = b;
cur_pkt_body_idx = 3;
/* body length = packetLength - 3 (len, counter, command already read) */
/* The end byte (0x03) is read separately in ST_RECVPKT_END */
if (cur_pkt.raw[0] <= 3) {
/* No body bytes — go straight to end */
fsm.state = ST_RECVPKT_END;
} else {
fsm.state = ST_RECVPKT_BODY;
}
if(b == PACKET_CMD_ACK || b == PACKET_CMD_NAK){
cur_pkt.isAckNak = 1;
}else{
cur_pkt.isAckNak = 0;
}
break;
}
case ST_RECVPKT_BODY:
{
uint8_t b;
if (!try_read_and_ack(&b)) return;
if (cur_pkt_body_idx < MAX_PACKET_RAW)
cur_pkt.raw[cur_pkt_body_idx] = b;
cur_pkt_body_idx++;
/* body bytes = raw[0] - 3, so we've finished when we have raw[0] bytes total */
if (cur_pkt_body_idx >= cur_pkt.raw[0]) {
fsm.state = ST_RECVPKT_END;
}
break;
}
case ST_RECVPKT_END:
{
uint8_t b;
if (!try_read_raw(&b)) return; /* end byte no ACK */
cur_pkt.raw[cur_pkt_body_idx] = b;
cur_pkt.length = cur_pkt_body_idx + 1;
classify_packet(&cur_pkt);
if (pkt_count < MAX_RECV_PACKETS)
pkt_list[pkt_count++] = cur_pkt;
if (cur_pkt.isAckNak) {
/* This burst is complete — dispatch */
switch (fsm.after_recv) {
case AFTER_RECV_ECUINFO:
parse_ecu_info();
/* Now do IdentifyEcu: send ReadIdent, receive ident packets */
fsm.ident_phase = 0;
fsm.state = ST_IDENT_KEEPALIVE_SEND;
break;
case AFTER_RECV_IDENT:
/* More ident packets may follow — check if we need another round */
fsm.ident_phase++;
if (fsm.ident_phase < 3) {
/* Send another ReadIdent ACK and receive next burst */
fsm.state = ST_IDENT_KEEPALIVE_SEND;
} else {
/* Done identifying — enter keep-alive + fault code read */
uint8_t cmd = (uint8_t)PACKET_CMD_FaultCodesRead;
enter_send_packet(&cmd, 1, AFTER_SEND_RECV_FAULTCODES);
}
break;
case AFTER_RECV_KA:
/* Keep-alive round-trip complete */
if (pkt_list[pkt_count-1].type != PACKET_TYPE_ACK) {
fsm.state = ST_ERROR;
fsm.status = KLINE_STATUS_ERROR;
break;
}
KLine_OnKeepAliveTick();
fsm.ka_due = HAL_GetTick() + KWP_KEEPALIVE_PERIOD_MS;
fsm.state = ST_KA_WAIT;
break;
case AFTER_RECV_FAULTCODES:
parse_fault_codes();
/* Session fully established */
fsm.status = KLINE_STATUS_CONNECTED;
KLine_OnConnected();
fsm.ka_due = HAL_GetTick() + KWP_KEEPALIVE_PERIOD_MS;
fsm.state = ST_KA_WAIT;
break;
}
} else {
/* More packets in this burst — send ACK then read next */
fsm.state = ST_RECVPKT_SEND_ACK;
}
break;
}
case ST_RECVPKT_SEND_ACK:
{
/* Send ACK packet (len=3, counter, 0x09) then read next packet */
uint8_t ack_payload = (uint8_t)PACKET_CMD_ACK;
enter_send_packet(&ack_payload, 1, AFTER_SEND_IDLE);
/* After sending the ACK packet we continue receiving in the same burst */
fsm.after_send = AFTER_SEND_IDLE; /* send sub-machine won't auto-recv */
/* Prepare for next packet in burst */
memset(&cur_pkt, 0, sizeof(cur_pkt));
cur_pkt_body_idx = 0;
/* Override the send sub-machine's post-send state to loop back to recv */
/* We do this by saving after_recv and re-entering after send completes */
/* Simplest: finish the send inline here before entering recv again. */
/* Actually: enter_send_packet already set state = ST_SENDPKT_BYTE, */
/* we need it to go to ST_RECVPKT_LENGTH after. We tag after_send. */
switch (fsm.after_recv) {
case AFTER_RECV_ECUINFO:
fsm.after_send = AFTER_SEND_RECV_ECUINFO; break;
case AFTER_RECV_IDENT:
fsm.after_send = AFTER_SEND_RECV_IDENT; break;
case AFTER_RECV_KA:
fsm.after_send = AFTER_SEND_RECV_KA; break;
case AFTER_RECV_FAULTCODES:
fsm.after_send = AFTER_SEND_RECV_FAULTCODES; break;
}
break;
}
/* ====================================================== */
/* IdentifyEcu sub-sequence */
/* ====================================================== */
case ST_IDENT_KEEPALIVE_SEND:
{
/* Send ACK packet as keep-alive / handshake before ReadIdent */
uint8_t ack = (uint8_t)PACKET_CMD_ACK;
enter_send_packet(&ack, 1, AFTER_SEND_RECV_IDENT);
/* After sending ACK we expect a packet burst back */
/* The after_recv is set inside enter_recv_packets in AFTER_SEND_RECV_IDENT path */
/* Override: we actually want AFTER_RECV_IDENT for the receive burst */
fsm.after_send = AFTER_SEND_RECV_IDENT;
break;
}
/* ====================================================== */
/* Keep-alive steady state */
/* ====================================================== */
case ST_KA_WAIT:
if (fsm.ka_suspended) return; /* operations running — don't send KA */
if ((int32_t)(HAL_GetTick() - fsm.ka_due) < 0) return;
/* Time to send a keep-alive */
{
uint8_t ack = (uint8_t)PACKET_CMD_ACK;
enter_send_packet(&ack, 1, AFTER_SEND_RECV_KA);
}
break;
/* ====================================================== */
/* Error */
/* ====================================================== */
case ST_ERROR:
fsm.status = KLINE_STATUS_ERROR;
KLine_OnDisconnected();
/* Disarm UART and flush ring */
HAL_UART_AbortReceive(&huart1);
KLine_Ring_Flush();
fsm.state = ST_IDLE;
break;
default:
break;
}
}
/* ------------------------------------------------------------------ */
/* Public API */
/* ------------------------------------------------------------------ */
void KLine_FSM_Init(void)
{
memset(&fsm, 0, sizeof(fsm));
fsm.state = ST_IDLE;
fsm.status = KLINE_STATUS_IDLE;
}
void KLine_FSM_RequestConnect(uint8_t ecuAddress)
{
if (fsm.state != ST_IDLE) return;
memset(&fsm, 0, sizeof(fsm));
fsm.bb_addr = ecuAddress;
fsm.status = KLINE_STATUS_CONNECTING;
fsm.state = ST_BITBANG_START;
}
KLine_Status KLine_FSM_GetStatus(void)
{
return fsm.status;
}
uint8_t KLine_FSM_IsConnected(void)
{
return fsm.status == KLINE_STATUS_CONNECTED;
}
const ControllerInfo* KLine_FSM_GetEcuInfo(void)
{
return &fsm.ecu_info;
}
const KLine_FaultCode* KLine_FSM_GetFaultCodes(int *out_count)
{
if (out_count) *out_count = fsm.fault_count;
return fsm.fault_codes;
}
/* ------------------------------------------------------------------ */
/* Operations support API */
/* ------------------------------------------------------------------ */
uint8_t KLine_FSM_GetNextCounter(void)
{
return pkt_counter_val++;
}
void KLine_FSM_SuspendKA(uint8_t suspend)
{
fsm.ka_suspended = suspend;
if (!suspend) {
/* Resume: push deadline forward so we don't immediately timeout */
fsm.ka_due = HAL_GetTick() + KWP_KEEPALIVE_PERIOD_MS;
}
}
void KLine_FSM_ResetKATimer(void)
{
fsm.ka_due = HAL_GetTick() + KWP_KEEPALIVE_PERIOD_MS;
}
/* ------------------------------------------------------------------ */
/* Weak default callbacks — override in main.c */
/* ------------------------------------------------------------------ */
__attribute__((weak)) void KLine_OnConnected(void) {}
__attribute__((weak)) void KLine_OnDisconnected(void) {}
__attribute__((weak)) void KLine_OnKeepAliveTick(void){}

View File

@@ -0,0 +1,162 @@
/*
* kline_fsm.h
* Non-blocking KWP1281 protocol state machine for STM32H533.
*
* Design rules:
* - KLine_FSM_Run() is called every main-loop iteration. It never blocks.
* - Timing is always "store a deadline, check it next tick". No HAL_Delay.
* - UART1 RX bytes are consumed from the ring buffer (kline_ring.h).
* - UART1 TX is synchronous (HAL_UART_Transmit with short timeout) because
* TX completes in microseconds at 9600 baud and does not race with anything.
*/
#ifndef INC_KLINE_FSM_H_
#define INC_KLINE_FSM_H_
#include <stdint.h>
#include "IKW1281Connection.h" /* ParsedPacket, PacketCommand, ControllerInfo */
/* ------------------------------------------------------------------ */
/* Public result / status types */
/* ------------------------------------------------------------------ */
typedef enum {
KLINE_STATUS_IDLE = 0,
KLINE_STATUS_CONNECTING,
KLINE_STATUS_CONNECTED,
KLINE_STATUS_ERROR,
} KLine_Status;
/* ------------------------------------------------------------------ */
/* Internal FSM states (exposed in header for debuggability) */
/* ------------------------------------------------------------------ */
typedef enum {
/* --- Idle --- */
ST_IDLE,
/* --- 5-baud init sequence --- */
ST_BITBANG_START, /* De-init UART, configure TX pin as GPIO */
ST_BITBANG_STARTBIT, /* Drive line low (start bit) */
ST_BITBANG_DATABIT, /* Clock out 8 data bits one by one */
ST_BITBANG_STOPBIT, /* Drive line high (stop bit) */
ST_BITBANG_DONE, /* Re-init UART, arm RX ring */
/* --- Sync byte from ECU (0x55) --- */
ST_WAIT_SYNC,
/* --- Keywords --- */
ST_READ_KW1,
ST_READ_KW2,
ST_SEND_KW2_COMPLEMENT, /* Write ~KW2 after inter-byte delay */
/* --- ECU info packets (initial ReceivePackets burst) --- */
ST_RECV_ECUINFO_PACKET,
ST_SEND_ECUINFO_ACK,
/* --- IdentifyEcu sub-sequence --- */
ST_IDENT_KEEPALIVE_SEND,
ST_IDENT_KEEPALIVE_RECV,
ST_IDENT_RECV_PACKET,
ST_IDENT_SEND_ACK,
/* --- Steady-state keep-alive --- */
ST_KA_SEND_ACK_PACKET, /* Send ACK packet to ECU */
ST_KA_RECV_ACK, /* Expect ACK packet back */
ST_KA_WAIT, /* Idle until next KA is due */
/* --- Generic packet TX (SendPacket) --- */
ST_SENDPKT_BYTE, /* Send one byte of the outgoing packet */
ST_SENDPKT_WAIT_ACK, /* Wait for complement-ACK from ECU */
ST_SENDPKT_NEXT_BYTE, /* Inter-byte delay then next byte */
ST_SENDPKT_END, /* Send 0x03 end byte, no ACK */
/* --- Generic packet RX (ReceivePackets) --- */
ST_RECVPKT_LENGTH,
ST_RECVPKT_COUNTER,
ST_RECVPKT_COMMAND,
ST_RECVPKT_BODY,
ST_RECVPKT_END,
ST_RECVPKT_SEND_ACK, /* Send ACK packet after non-ACK/NAK packet */
/* --- Error / disconnected --- */
ST_ERROR,
} KLine_FSMState;
/* ------------------------------------------------------------------ */
/* Public API */
/* ------------------------------------------------------------------ */
/* Call after all HAL inits, before the while(1) loop */
void KLine_FSM_Init(void);
/* Call every main-loop iteration returns immediately */
void KLine_FSM_Run(void);
/* Trigger a connection attempt (replaces BitBang = 1 / TryConnection()) */
void KLine_FSM_RequestConnect(uint8_t ecuAddress);
/* Query current status */
KLine_Status KLine_FSM_GetStatus(void);
/* True while a keep-alive session is active */
uint8_t KLine_FSM_IsConnected(void);
/* Access last-received ECU info */
const ControllerInfo* KLine_FSM_GetEcuInfo(void);
/* Access last-received fault codes */
typedef struct {
uint8_t dtc;
uint8_t status;
uint8_t extra;
} KLine_FaultCode;
#define KLINE_MAX_FAULT_CODES 20
const KLine_FaultCode* KLine_FSM_GetFaultCodes(int *out_count);
/* ------------------------------------------------------------------ */
/* Operations support API */
/* ------------------------------------------------------------------ */
/*
* Return the current packet counter and increment it.
* Used by kline_operations.c so that operation packets share the same
* session counter the FSM uses. Must only be called from main-loop context.
*/
uint8_t KLine_FSM_GetNextCounter(void);
/*
* Suspend / resume keep-alive ticks.
* When suspend != 0, the FSM will NOT send keep-alive packets while in
* ST_KA_WAIT. The caller must resume (suspend=0) when the operation
* finishes so that keep-alive resumes normally.
*
* This also resets the KA timer so the next keep-alive fires a full
* period after the operation ends, preventing an immediate timeout.
*/
void KLine_FSM_SuspendKA(uint8_t suspend);
/*
* Manually reset the keep-alive deadline to "now + period".
* Called by operation code after finishing so the ECU doesn't time out.
*/
void KLine_FSM_ResetKATimer(void);
/* ------------------------------------------------------------------ */
/* Low-level TX helper (shared with kline_operations.c) */
/* ------------------------------------------------------------------ */
void KLine_FSM_TxByte(uint8_t b);
/* ------------------------------------------------------------------ */
/* Callbacks implement in main.c / hc_06 glue */
/* ------------------------------------------------------------------ */
void KLine_OnConnected(void); /* called when session established */
void KLine_OnDisconnected(void); /* called on timeout / error */
void KLine_OnKeepAliveTick(void); /* called each successful keep-alive */
#endif /* INC_KLINE_FSM_H_ */

View File

@@ -0,0 +1,925 @@
/*
* kline_operations.c
*
* Non-blocking KWP1281 operations for the FSM architecture.
*
* Each operation (ReadDfi, WriteDfi, ReadVoltage, ReadAudiPin,
* ClearFaultCodes) is implemented as a multi-step state machine driven
* by KLineOp_Poll(), which is called every main-loop iteration.
*
* Design invariants:
* - No HAL_Delay anywhere.
* - All timing is deadline-based: deadline = HAL_GetTick() + N.
* - RX via KLine_Ring_Available() / KLine_Ring_Pop().
* - TX via KLine_FSM_TxByte() (synchronous, safe at 9600 baud).
* - Packet counter via KLine_FSM_GetNextCounter() (shared session).
* - Keep-alive suspended while an op runs (KLine_FSM_SuspendKA).
*/
#include "kline_operations.h"
#include "kline_fsm.h"
#include "kline_ring.h"
#include "IKW1281Connection.h"
#include "main.h"
#include <stdint.h>
#include <string.h>
/* ------------------------------------------------------------------ */
/* Timing constants */
/* ------------------------------------------------------------------ */
#define OP_BYTE_TIMEOUT_MS 1000u
#define OP_INTERBYTE_TX_MS 5u
/* ------------------------------------------------------------------ */
/* Internal operation types */
/* ------------------------------------------------------------------ */
typedef enum {
OPTYPE_NONE = 0,
OPTYPE_READ_DFI,
OPTYPE_WRITE_DFI,
OPTYPE_READ_VOLTAGE,
OPTYPE_READ_AUDI_PIN,
OPTYPE_CLEAR_FAULT_CODES,
} OpType;
/* ------------------------------------------------------------------ */
/* Sub-step states for the generic send/receive packet engine */
/* ------------------------------------------------------------------ */
typedef enum {
/* Idle / done */
OPS_IDLE,
/* Send packet bytes one at a time, wait for complement ACK */
OPS_SEND_BYTE,
OPS_SEND_WAIT_ACK,
OPS_SEND_INTERBYTE_DELAY,
OPS_SEND_END_BYTE,
/* Receive a packet byte by byte */
OPS_RECV_LENGTH,
OPS_RECV_COUNTER,
OPS_RECV_COMMAND,
OPS_RECV_BODY,
OPS_RECV_END,
/* After receiving a non-ACK/NAK packet, send ACK then receive more */
OPS_RECV_SEND_ACK,
/* High-level sequencer decides what comes next */
OPS_STEP_DONE,
} OpSubState;
/* ------------------------------------------------------------------ */
/* Operation context */
/* ------------------------------------------------------------------ */
#define OP_MAX_SEND 24
#define OP_MAX_RECV_RAW 20
#define OP_MAX_RECV_PKTS 8
typedef struct {
/* What operation */
OpType type;
KLineOp_Status result;
/* Sub-state */
OpSubState sub;
uint32_t deadline;
/* Send buffer */
uint8_t send_buf[OP_MAX_SEND];
uint8_t send_len;
uint8_t send_idx;
uint8_t last_sent;
/* Receive scratch */
ParsedPacket cur_pkt;
uint8_t cur_body_idx;
/* Completed receive packets for current burst */
ParsedPacket rx_pkts[OP_MAX_RECV_PKTS];
int rx_pkt_count;
/* High-level step index (each op defines its own sequence) */
int step;
/* Operation-specific params/results */
float dfi_value;
int dfi_version;
uint16_t u16_result; /* voltage or audi pin */
int int_result; /* WriteDfi / ClearFC success */
} OpCtx;
static OpCtx op;
/* ------------------------------------------------------------------ */
/* Packet classification (same logic as kline_fsm.c) */
/* ------------------------------------------------------------------ */
static void op_classify_packet(ParsedPacket *p)
{
p->title = p->raw[2];
switch (p->raw[2]) {
case PACKET_CMD_ACK:
p->type = PACKET_TYPE_ACK;
p->isAckNak = 1;
break;
case PACKET_CMD_NAK:
p->type = PACKET_TYPE_NAK;
p->isAckNak = 1;
break;
case PACKET_CMD_AsciiData:
p->type = (p->raw[3] == 0x00) ? PACKET_TYPE_CODING_WSC
: PACKET_TYPE_ASCII_DATA;
break;
case PACKET_CMD_ReadEepromResponse:
p->type = PACKET_TYPE_READ_EEPROM_RESPONSE;
break;
case PACKET_CMD_ReadRomEepromResponse:
p->type = PACKET_TYPE_READ_ROM_EEPROM_RESPONSE;
break;
default:
p->type = PACKET_TYPE_UNKNOWN;
break;
}
}
/* ------------------------------------------------------------------ */
/* Build a KWP1281 packet into op.send_buf */
/* ------------------------------------------------------------------ */
static void op_build_packet(const uint8_t *payload, uint8_t payload_len)
{
op.send_len = payload_len + 2; /* length + counter + payload */
op.send_buf[0] = op.send_len;
op.send_buf[1] = KLine_FSM_GetNextCounter();
for (uint8_t i = 0; i < payload_len; i++)
op.send_buf[2 + i] = payload[i];
op.send_idx = 0;
}
/* ------------------------------------------------------------------ */
/* Enter send / receive sub-states */
/* ------------------------------------------------------------------ */
static void op_enter_send(const uint8_t *payload, uint8_t len)
{
op_build_packet(payload, len);
op.sub = OPS_SEND_BYTE;
op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
}
static void op_enter_recv(void)
{
op.rx_pkt_count = 0;
memset(&op.cur_pkt, 0, sizeof(op.cur_pkt));
op.cur_body_idx = 0;
op.sub = OPS_RECV_LENGTH;
op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
}
/* ------------------------------------------------------------------ */
/* Byte-level RX helpers */
/* ------------------------------------------------------------------ */
/* Read a byte and send complement ACK. Returns 1 if got byte. */
static uint8_t op_try_read_ack(uint8_t *out)
{
if (!KLine_Ring_Available()) {
if ((int32_t)(HAL_GetTick() - op.deadline) >= 0) {
op.result = KLOP_FAIL;
op.sub = OPS_IDLE;
}
return 0;
}
*out = KLine_Ring_Pop();
KLine_FSM_TxByte((uint8_t)~(*out));
op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
return 1;
}
/* Read a byte with no ACK (for end byte). */
static uint8_t op_try_read_raw(uint8_t *out)
{
if (!KLine_Ring_Available()) {
if ((int32_t)(HAL_GetTick() - op.deadline) >= 0) {
op.result = KLOP_FAIL;
op.sub = OPS_IDLE;
}
return 0;
}
*out = KLine_Ring_Pop();
op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
return 1;
}
/* ------------------------------------------------------------------ */
/* Send sub-machine */
/* ------------------------------------------------------------------ */
static void op_run_send(void)
{
switch (op.sub)
{
case OPS_SEND_BYTE:
if (op.send_idx >= op.send_len) {
/* All data bytes sent — send end byte (no ACK expected) */
KLine_FSM_TxByte(PACKET_END_EXPECTED);
/* Packet fully transmitted */
op.sub = OPS_STEP_DONE;
return;
}
op.last_sent = op.send_buf[op.send_idx++];
KLine_FSM_TxByte(op.last_sent);
op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
op.sub = OPS_SEND_WAIT_ACK;
break;
case OPS_SEND_WAIT_ACK:
if (!KLine_Ring_Available()) {
if ((int32_t)(HAL_GetTick() - op.deadline) >= 0) {
op.result = KLOP_FAIL;
op.sub = OPS_IDLE;
}
return;
}
(void)KLine_Ring_Pop(); /* consume complement ACK */
op.deadline = HAL_GetTick() + OP_INTERBYTE_TX_MS;
op.sub = OPS_SEND_INTERBYTE_DELAY;
break;
case OPS_SEND_INTERBYTE_DELAY:
if ((int32_t)(HAL_GetTick() - op.deadline) < 0) return;
op.sub = OPS_SEND_BYTE;
break;
default:
break;
}
}
/* ------------------------------------------------------------------ */
/* Receive sub-machine */
/* ------------------------------------------------------------------ */
static void op_run_recv(void)
{
switch (op.sub)
{
case OPS_RECV_LENGTH:
{
uint8_t b;
if (!op_try_read_ack(&b)) return;
memset(&op.cur_pkt, 0, sizeof(op.cur_pkt));
op.cur_body_idx = 0;
op.cur_pkt.raw[0] = b;
op.sub = OPS_RECV_COUNTER;
break;
}
case OPS_RECV_COUNTER:
{
uint8_t b;
if (!op_try_read_ack(&b)) return;
op.cur_pkt.raw[1] = b;
op.sub = OPS_RECV_COMMAND;
break;
}
case OPS_RECV_COMMAND:
{
uint8_t b;
if (!op_try_read_ack(&b)) return;
op.cur_pkt.raw[2] = b;
op.cur_body_idx = 3;
if (op.cur_pkt.raw[0] <= 3) {
op.sub = OPS_RECV_END;
} else {
op.sub = OPS_RECV_BODY;
}
break;
}
case OPS_RECV_BODY:
{
uint8_t b;
if (!op_try_read_ack(&b)) return;
if (op.cur_body_idx < OP_MAX_RECV_RAW)
op.cur_pkt.raw[op.cur_body_idx] = b;
op.cur_body_idx++;
if (op.cur_body_idx >= op.cur_pkt.raw[0]) {
op.sub = OPS_RECV_END;
}
break;
}
case OPS_RECV_END:
{
uint8_t b;
if (!op_try_read_raw(&b)) return;
op.cur_pkt.raw[op.cur_body_idx] = b;
op.cur_pkt.length = op.cur_body_idx + 1;
op_classify_packet(&op.cur_pkt);
if (op.rx_pkt_count < OP_MAX_RECV_PKTS)
op.rx_pkts[op.rx_pkt_count++] = op.cur_pkt;
if (op.cur_pkt.isAckNak) {
/* Burst complete */
op.sub = OPS_STEP_DONE;
} else {
/* More packets — need to send ACK then continue receiving */
op.sub = OPS_RECV_SEND_ACK;
}
break;
}
case OPS_RECV_SEND_ACK:
{
/* Build and send an ACK packet, then loop back to recv */
uint8_t ack_payload = (uint8_t)PACKET_CMD_ACK;
op_build_packet(&ack_payload, 1);
/* We'll send this packet then re-enter recv */
op.sub = OPS_SEND_BYTE;
/* Mark that after this send completes, we go back to receiving.
* We use a special trick: after send completes (OPS_STEP_DONE),
* the sequencer checks if we were mid-burst and loops back. */
break;
}
default:
break;
}
}
/* ------------------------------------------------------------------ */
/* Helper: send a packet then receive the response burst */
/* Returns 1 when the full send+receive is done, 0 while busy. */
/* The sequencer uses op.step to track phases: */
/* step N → sending packet */
/* step N+1 → receiving response */
/* ------------------------------------------------------------------ */
/* Send-then-receive sequencer states per step:
* phase 0 = start send
* phase 1 = finish send → start recv
* phase 2 = finish recv → done
*/
typedef enum {
PHASE_SEND_START,
PHASE_SENDING,
PHASE_RECV_START,
PHASE_RECEIVING,
PHASE_COMPLETE,
} SendRecvPhase;
static SendRecvPhase sr_phase;
static uint8_t sr_was_mid_burst; /* flag: we were sending an ACK mid-burst */
static void sr_begin_send(const uint8_t *payload, uint8_t len)
{
op_enter_send(payload, len);
sr_phase = PHASE_SENDING;
sr_was_mid_burst = 0;
}
static void sr_begin_send_then_recv(const uint8_t *payload, uint8_t len)
{
op_enter_send(payload, len);
sr_phase = PHASE_SENDING;
sr_was_mid_burst = 0;
}
/* Returns 1 when the send-then-receive cycle is complete */
static uint8_t sr_poll(void)
{
if (op.result == KLOP_FAIL) return 1; /* error abort */
switch (sr_phase)
{
case PHASE_SENDING:
if (op.sub == OPS_STEP_DONE) {
if (sr_was_mid_burst) {
/* We just finished sending an intra-burst ACK — go back to recv */
sr_was_mid_burst = 0;
op_enter_recv();
sr_phase = PHASE_RECEIVING;
/* But we keep the existing rx_pkt list intact — don't reset count */
/* Actually op_enter_recv resets count. We need a variant. */
/* Fix: manually set sub to recv length without resetting pkt list */
/* We'll inline it: */
memset(&op.cur_pkt, 0, sizeof(op.cur_pkt));
op.cur_body_idx = 0;
op.sub = OPS_RECV_LENGTH;
op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
return 0;
}
/* Normal send complete — start receiving */
op_enter_recv();
sr_phase = PHASE_RECEIVING;
return 0;
}
/* Still sending */
if (op.sub >= OPS_SEND_BYTE && op.sub <= OPS_SEND_INTERBYTE_DELAY) {
op_run_send();
}
return 0;
case PHASE_RECEIVING:
if (op.sub == OPS_STEP_DONE) { //AQUI ACABA DE ENVIAR EL MENSAJE
/* Burst complete */
sr_phase = PHASE_COMPLETE;
return 1;
}
if (op.sub == OPS_RECV_SEND_ACK) {
/* Need to send ACK mid-burst, then continue recv */
uint8_t ack_payload = (uint8_t)PACKET_CMD_ACK;
op_build_packet(&ack_payload, 1);
op.sub = OPS_SEND_BYTE;
op.deadline = HAL_GetTick() + OP_BYTE_TIMEOUT_MS;
sr_was_mid_burst = 1;
sr_phase = PHASE_SENDING;
return 0;
}
if (op.sub >= OPS_RECV_LENGTH && op.sub <= OPS_RECV_END) {
op_run_recv();
}
return 0;
case PHASE_COMPLETE:
return 1;
default:
return 1;
}
}
/* ------------------------------------------------------------------ */
/* Send-only sequencer (no receive expected, or we handle it inline) */
/* ------------------------------------------------------------------ */
static void send_only_begin(const uint8_t *payload, uint8_t len)
{
op_enter_send(payload, len);
sr_phase = PHASE_SENDING;
sr_was_mid_burst = 0;
}
static uint8_t send_only_poll(void)
{
if (op.result == KLOP_FAIL) return 1;
if (op.sub == OPS_STEP_DONE) return 1;
if (op.sub >= OPS_SEND_BYTE && op.sub <= OPS_SEND_INTERBYTE_DELAY) {
op_run_send();
}
return 0;
}
/* ------------------------------------------------------------------ */
/* Helper: find first non-ACK/NAK packet in rx list */
/* ------------------------------------------------------------------ */
static ParsedPacket* op_find_data_packet(void)
{
for (int i = 0; i < op.rx_pkt_count; i++) {
if (!op.rx_pkts[i].isAckNak)
return &op.rx_pkts[i];
}
return (ParsedPacket*)0;
}
/* ------------------------------------------------------------------ */
/* Common init/finish for all operations */
/* ------------------------------------------------------------------ */
static int op_begin(OpType t)
{
if (op.type != OPTYPE_NONE) return -1; /* already busy */
memset(&op, 0, sizeof(op));
op.type = t;
op.result = KLOP_BUSY;
op.sub = OPS_IDLE;
op.step = 0;
sr_phase = PHASE_SEND_START;
KLine_FSM_SuspendKA(1);
return 0;
}
static void op_finish(KLineOp_Status s)
{
op.result = s;
op.type = OPTYPE_NONE;
op.sub = OPS_IDLE;
KLine_FSM_SuspendKA(0);
KLine_FSM_ResetKATimer();
}
/* ================================================================== */
/* ReadRomEeprom: send {0x03, count, addr_hi, addr_lo} → recv */
/* Extracts body bytes raw[3 .. length-2] */
/* ================================================================== */
/* Shared result buffer for ROM EEPROM reads */
static uint8_t rom_eeprom_data[16];
static int rom_eeprom_len;
/* ================================================================== */
/* Operation: ReadVoltage */
/* ReadRomEeprom(0x0142, 2) → (data[1]<<8)|data[0] */
/* ================================================================== */
static void poll_read_voltage(void)
{
switch (op.step)
{
case 0:
{
uint8_t req[4] = {
(uint8_t)PACKET_CMD_ReadRomEeprom,
2, /* count */
(uint8_t)(0x0142 >> 8), /* addr hi */
(uint8_t)(0x0142 & 0xFF) /* addr lo */
};
sr_begin_send_then_recv(req, 4);
op.step = 1;
break;
}
case 1:
if (!sr_poll()) return;
if (op.result == KLOP_FAIL) {op_finish(KLOP_FAIL); return; }
{
/* Check for NAK */
if (op.rx_pkt_count == 1 && op.rx_pkts[0].type == PACKET_TYPE_NAK) {
op_finish(KLOP_FAIL);
return;
}
ParsedPacket *p = op_find_data_packet();
if (!p || p->length < 5) {
op_finish(KLOP_FAIL);
return;
}
int data_len = p->length - 4;
if (data_len >= 2) {
op.u16_result = (uint16_t)((p->raw[4] << 8) | p->raw[3]);
} else {
op_finish(KLOP_FAIL);
return;
}
}
op_finish(KLOP_OK);
break;
}
}
/* ================================================================== */
/* Operation: ReadAudiPin */
/* ReadRomEeprom(0x04FA, 2) → (data[1]<<8)|data[0] */
/* ================================================================== */
static void poll_read_audi_pin(void)
{
switch (op.step)
{
case 0:
{
uint8_t req[4] = {
(uint8_t)PACKET_CMD_ReadRomEeprom,
2,
(uint8_t)(0x04FA >> 8),
(uint8_t)(0x04FA & 0xFF)
};
sr_begin_send_then_recv(req, 4);
op.step = 1;
break;
}
case 1:
if (!sr_poll()) return;
if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
{
if (op.rx_pkt_count == 1 && op.rx_pkts[0].type == PACKET_TYPE_NAK) {
op_finish(KLOP_FAIL);
return;
}
ParsedPacket *p = op_find_data_packet();
if (!p || p->length < 5) {
op_finish(KLOP_FAIL);
return;
}
int data_len = p->length - 4;
if (data_len >= 2) {
op.u16_result = (uint16_t)((p->raw[4] << 8) | p->raw[3]);
} else {
op_finish(KLOP_FAIL);
return;
}
}
op_finish(KLOP_OK);
break;
}
}
/* ================================================================== */
/* Operation: ClearFaultCodes */
/* Send {0x05}, expect ACK or NAK back */
/* ================================================================== */
static void poll_clear_fault_codes(void)
{
switch (op.step)
{
case 0:
{
uint8_t req[1] = { (uint8_t)PACKET_CMD_FaultCodesDelete };
sr_begin_send_then_recv(req, 1);
op.step = 1;
break;
}
case 1:
if (!sr_poll()) return;
if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
{
if (op.rx_pkt_count >= 1 && op.rx_pkts[0].type == PACKET_TYPE_ACK) {
op.int_result = 1;
op_finish(KLOP_OK);
} else {
op.int_result = 0;
op_finish(KLOP_FAIL);
}
}
break;
}
}
/* ================================================================== */
/* Operation: ReadDfi */
/* Step 0: send group reading unlock {0x18,0x00,0x03,0xFF,0xFF} */
/* Step 1: recv response */
/* Step 2: send ReadEeprom {0x19,0x02,0x00,0x44} */
/* Step 3: recv response → extract DFI */
/* ================================================================== */
static void poll_read_dfi(void)
{
switch (op.step)
{
case 0:
{
/* Group reading unlock */
uint8_t req[5] = { 0x18, 0x00, 0x03, 0xFF, 0xFF };
sr_begin_send_then_recv(req, 5);
op.step = 1;
break;
}
case 1:
if (!sr_poll()) return;
if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
/* Don't care about the unlock response content — move on */
op.step = 2;
break;
case 2:
{
/* ReadEeprom at 0x0044 */
uint8_t req[4] = {
(uint8_t)PACKET_CMD_ReadEeprom,
0x02, 0x00, 0x44
};
sr_begin_send_then_recv(req, 4);
op.step = 3;
break;
}
case 3:
if (!sr_poll()) return;
if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
{
ParsedPacket *p = op_find_data_packet();
if (!p || p->length < 5) {
op.dfi_value = 0.0f;
op_finish(KLOP_FAIL);
return;
}
/* DFI = (int8_t)raw[3] * 3.0 / 256.0 */
int8_t raw_val = (int8_t)p->raw[3];
op.dfi_value = ((float)raw_val * 3.0f) / 256.0f;
}
op_finish(KLOP_OK);
break;
}
}
/* ================================================================== */
/* Operation: WriteDfi */
/* Step 0: send version-dependent password (14 bytes) */
/* Step 1: recv response */
/* Step 2: send WriteEeprom {0x1A,0x02,0x00,0x44,val,~val,0x03} */
/* Step 3: recv response */
/* ================================================================== */
static const uint8_t pass_default[14] = {
0x18, 0x00, 0x03, 0x2F, 0xFF,
0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45
};
static const uint8_t pass_v2[14] = {
0x18, 0x00, 0x03, 0x2F, 0xF2,
0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45
};
static const uint8_t pass_v3v4[14] = {
0x18, 0x00, 0x03, 0xFF, 0xF2,
0x4B, 0x48, 0x54, 0x43, 0x41, 0x38, 0x47, 0x30, 0x45
};
static void poll_write_dfi(void)
{
switch (op.step)
{
case 0:
{
/* Select password based on version */
const uint8_t *password = pass_default;
if (op.dfi_version == 1) {
password = pass_v2;
} else if (op.dfi_version == 2 || op.dfi_version == 3) {
password = pass_v3v4;
}
sr_begin_send_then_recv(password, 14);
op.step = 1;
break;
}
case 1:
if (!sr_poll()) return;
if (op.result == KLOP_FAIL) { op_finish(KLOP_FAIL); return; }
/* Password response received — move on to write */
op.step = 2;
break;
case 2:
{
/* Compute value byte */
int vi = (int)((op.dfi_value * 256.0f) / 3.0f);
if (vi > 127) vi = 127;
if (vi < -128) vi = -128;
int8_t value = (int8_t)vi;
if (value == 0) value = 1;
uint8_t value_u = (uint8_t)value;
uint8_t value_csum = (uint8_t)(0u - value_u);
uint8_t req[7] = {
(uint8_t)PACKET_CMD_WriteEeprom,
0x02, 0x00, 0x44,
value_u, value_csum, 0x03
};
sr_begin_send_then_recv(req, 7);
op.step = 3;
break;
}
case 3:
if (!sr_poll()) return;
if (op.result == KLOP_FAIL) {
op.int_result = 0;
op_finish(KLOP_FAIL);
return;
}
/* Check for NAK */
if (op.rx_pkt_count >= 1 && op.rx_pkts[0].type == PACKET_TYPE_NAK) {
op.int_result = 0;
op_finish(KLOP_FAIL);
return;
}
op.int_result = 1;
op_finish(KLOP_OK);
break;
}
}
/* ================================================================== */
/* Public API: Start functions */
/* ================================================================== */
int KLineOp_Start_ReadDfi(void)
{
return op_begin(OPTYPE_READ_DFI);
}
int KLineOp_Start_WriteDfi(float dfi, int version)
{
if (op_begin(OPTYPE_WRITE_DFI) != 0) return -1;
op.dfi_value = dfi;
op.dfi_version = version;
return 0;
}
int KLineOp_Start_ReadVoltage(void)
{
return op_begin(OPTYPE_READ_VOLTAGE);
}
int KLineOp_Start_ReadAudiPin(void)
{
return op_begin(OPTYPE_READ_AUDI_PIN);
}
int KLineOp_Start_ClearFaultCodes(void)
{
return op_begin(OPTYPE_CLEAR_FAULT_CODES);
}
/* ================================================================== */
/* Public API: Poll */
/* ================================================================== */
KLineOp_Status KLineOp_Poll(void)
{
if (op.result != KLOP_BUSY) return op.result;
switch (op.type)
{
case OPTYPE_READ_DFI: poll_read_dfi(); break;
case OPTYPE_WRITE_DFI: poll_write_dfi(); break;
case OPTYPE_READ_VOLTAGE: poll_read_voltage(); break;
case OPTYPE_READ_AUDI_PIN: poll_read_audi_pin(); break;
case OPTYPE_CLEAR_FAULT_CODES: poll_clear_fault_codes(); break;
default:
op.result = KLOP_IDLE;
break;
}
return op.result;
}
/* ================================================================== */
/* Public API: Result accessors */
/* ================================================================== */
float KLineOp_Result_Dfi(void) { return op.dfi_value; }
uint16_t KLineOp_Result_Voltage(void) { return op.u16_result; }
uint16_t KLineOp_Result_AudiPin(void) { return op.u16_result; }
int KLineOp_Result_WriteDfi(void) { return op.int_result; }
int KLineOp_Result_ClearFaultCodes(void) { return op.int_result; }
/* ================================================================== */
/* Legacy shim functions (called from hc_06.c) */
/* */
/* These are the OLD blocking API signatures. They now kick off the */
/* async operation and BLOCK by polling in a tight loop. This is a */
/* transitional shim so hc_06.c keeps working without changes. */
/* */
/* For a fully non-blocking architecture, hc_06.c should be updated */
/* to use KLineOp_Start_XXX() + KLineOp_Poll() directly. */
/* ================================================================== */
float ReadDfi(void)
{
if (KLineOp_Start_ReadDfi() != 0) return 0.0f;
KLineOp_Status s;
do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
return (s == KLOP_OK) ? KLineOp_Result_Dfi() : 0.0f;
}
int WriteDfi(float dfi, int version)
{
if (KLineOp_Start_WriteDfi(dfi, version) != 0) return 0;
KLineOp_Status s;
do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
return (s == KLOP_OK) ? KLineOp_Result_WriteDfi() : 0;
}
uint8_t ReadVoltage(uint16_t *volt)
{
if (volt) *volt = 0;
if (KLineOp_Start_ReadVoltage() != 0) return 0;
KLineOp_Status s;
do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
if (s == KLOP_OK) {
if (volt) *volt = KLineOp_Result_Voltage();
return 1;
}
return 0;
}
uint8_t ReadAudiPin(uint16_t *pin)
{
if (pin) *pin = 0;
if (KLineOp_Start_ReadAudiPin() != 0) return 0;
KLineOp_Status s;
do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
if (s == KLOP_OK) {
if (pin) *pin = KLineOp_Result_AudiPin();
return 1;
}
return 0;
}
int ClearFaultCodes(void)
{
if (KLineOp_Start_ClearFaultCodes() != 0) return 0;
KLineOp_Status s;
do { s = KLineOp_Poll(); } while (s == KLOP_BUSY);
return (s == KLOP_OK) ? KLineOp_Result_ClearFaultCodes() : 0;
}

View File

@@ -0,0 +1,51 @@
/*
* kline_operations.h
*
* Created on: 1 abr 2026
* Author: herli
*/
#ifndef KLINE_MASTER_LIBS_KLINE_OPERATIONS_H_
#define KLINE_MASTER_LIBS_KLINE_OPERATIONS_H_
#include <stdint.h>
/* ------------------------------------------------------------------ */
/* Operation result status */
/* ------------------------------------------------------------------ */
typedef enum {
KLOP_IDLE = 0, /* no operation pending */
KLOP_BUSY = 1, /* operation in progress — keep polling */
KLOP_OK = 2, /* completed successfully */
KLOP_FAIL = 3, /* completed with failure (NAK, timeout, etc.) */
} KLineOp_Status;
/* ------------------------------------------------------------------ */
/* Start functions — return 0 on success, -1 if already busy */
/* ------------------------------------------------------------------ */
int KLineOp_Start_ReadDfi(void);
int KLineOp_Start_WriteDfi(float dfi, int version);
int KLineOp_Start_ReadVoltage(void);
int KLineOp_Start_ReadAudiPin(void);
int KLineOp_Start_ClearFaultCodes(void);
/* ------------------------------------------------------------------ */
/* Poll — call every main-loop iteration */
/* ------------------------------------------------------------------ */
KLineOp_Status KLineOp_Poll(void);
/* ------------------------------------------------------------------ */
/* Result accessors (valid only after KLineOp_Poll() returns KLOP_OK) */
/* ------------------------------------------------------------------ */
float KLineOp_Result_Dfi(void);
uint16_t KLineOp_Result_Voltage(void);
uint16_t KLineOp_Result_AudiPin(void);
int KLineOp_Result_WriteDfi(void); /* 1=success, 0=fail */
int KLineOp_Result_ClearFaultCodes(void); /* 1=success, 0=fail */
#endif /* KLINE_MASTER_LIBS_KLINE_OPERATIONS_H_ */

View File

@@ -0,0 +1,56 @@
/*
* kline_ring.c
* ISR-safe ring buffer for K-Line UART1 RX.
*
* Producer: HAL_UART_RxCpltCallback (ISR context) via KLine_Ring_ISR_PushByte()
* Consumer: KLine_FSM_Run() (main-loop context)
*
* No locking needed: head written only by ISR, tail written only by main.
*/
#include "kline_ring.h"
#include "main.h" /* for huart1 */
volatile uint8_t kline_rx_staging = 0;
static volatile uint8_t ring_buf[KLINE_RING_SIZE];
static volatile uint32_t ring_head = 0; /* written by ISR */
static volatile uint32_t ring_tail = 0; /* written by main */
void KLine_Ring_Init(void)
{
ring_head = 0;
ring_tail = 0;
HAL_UART_Receive_IT(&huart1, (uint8_t*)&kline_rx_staging, 1);
}
/* ---- ISR context ---- */
void KLine_Ring_ISR_PushByte(uint8_t b)
{
uint32_t next = (ring_head + 1u) & KLINE_RING_MASK;
if (next != ring_tail) /* drop byte on overflow rather than corrupt */
{
ring_buf[ring_head] = b;
ring_head = next;
}
/* Re-arm immediately this is the ONLY place we call Receive_IT for UART1 */
HAL_UART_Receive_IT(&huart1, (uint8_t*)&kline_rx_staging, 1);
}
/* ---- Main-loop context ---- */
uint8_t KLine_Ring_Available(void)
{
return ring_head != ring_tail;
}
uint8_t KLine_Ring_Pop(void)
{
uint8_t b = ring_buf[ring_tail];
ring_tail = (ring_tail + 1u) & KLINE_RING_MASK;
return b;
}
void KLine_Ring_Flush(void)
{
ring_tail = ring_head;
}

View File

@@ -0,0 +1,29 @@
/*
* kline_ring.h
* Lock-free single-producer (ISR) / single-consumer (FSM) ring buffer
* for UART1 K-Line RX bytes.
*/
#ifndef INC_KLINE_RING_H_
#define INC_KLINE_RING_H_
#include <stdint.h>
#define KLINE_RING_SIZE 64u /* Must be power of 2 */
#define KLINE_RING_MASK (KLINE_RING_SIZE - 1u)
/* Call once after HAL_UART_Init to start receiving */
void KLine_Ring_Init(void);
/* Called ONLY from UART1 RxCplt ISR callback */
void KLine_Ring_ISR_PushByte(uint8_t b);
/* Called from FSM / main context */
uint8_t KLine_Ring_Available(void); /* non-zero if at least one byte ready */
uint8_t KLine_Ring_Pop(void); /* pop oldest byte; undefined if empty */
void KLine_Ring_Flush(void); /* discard all pending bytes */
/* Raw RX staging byte huart1 DMA/IT target, touched only by HAL + ISR */
extern volatile uint8_t kline_rx_staging;
#endif /* INC_KLINE_RING_H_ */

View File

@@ -21,8 +21,8 @@
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "kline.h"
#include "IKW1281Connection.h"
#include "kline_ring.h"
#include "kline_fsm.h"
#include "hc_06.h"
/* USER CODE END Includes */
@@ -49,7 +49,12 @@ UART_HandleTypeDef huart1;
UART_HandleTypeDef huart3;
/* USER CODE BEGIN PV */
/*
* kline_rx_staging is the 1-byte HAL IT target for UART1.
* It lives here so the linker puts it in normal SRAM (not any cached/MPU region).
* It is only written by HAL inside the ISR and read by KLine_Ring_ISR_PushByte.
*/
/* defined in kline_ring.c: volatile uint8_t kline_rx_staging; */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
@@ -57,72 +62,67 @@ void SystemClock_Config(void);
static void MPU_Config(void);
static void MX_GPIO_Init(void);
static void MX_FDCAN1_Init(void);
static void MX_USART1_UART_Init(void);
void MX_USART1_UART_Init(void);
static void MX_USART3_UART_Init(void);
/* USER CODE BEGIN PFP */
//void MX_USART1_UART_Init(void);
//SIEMPRE QUITAR EL STATIC
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
volatile uint8_t k_rx_byte;
volatile uint8_t rx_done_flag = 0;
ControllerInfo ecuinfo = {0};
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == huart3.Instance)
if (huart->Instance == USART3)
{
HC06_UART_RxByteCallback(huart);
return;
}
if (huart->Instance == USART1)
{
rx_done_flag = 1;
// Do NOT re-arm here. ReadByte() does it.
/*
* Push received byte into the ring buffer.
* KLine_Ring_ISR_PushByte() re-arms Receive_IT internally.
* DO NOT call HAL_UART_Receive_IT here directly.
*/
KLine_Ring_ISR_PushByte(kline_rx_staging);
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
HC06_UART_TxCpltCallback(huart);
}
uint8_t pc_connected = 0;
uint8_t sent_init_message = 0;
uint8_t psg_connected = 0;
void OnEnterReceived(void) {
//char msg[] = "Enter pressed!\r\n";
//CDC_Transmit_FS((uint8_t*)msg, strlen(msg));
BitBang = 1;
}
uint32_t alive_due_ms =0;
void TryConnection(void){
BitBang = 0;
int protocolVersion = WakeUp(ECU_INIT_ADDRESS, 0);
if (!protocolVersion || !connectionAlive) return; // add connectionAlive check
ecuinfo = ReadEcuInfo();
if(!KeepAlive()){
KLINE_THROW_NONALIVE_EXCEPTION(4);
return;
}
IdentifyEcu();
/*FaultCode fault_codes[20]; // buffer for up to 32 codes (tune size as needed)
int N_fc = ReadFaultCodes(fault_codes, 20);
PrintFaultCodes(fault_codes, N_fc);*/
//free(fault_code)
psg_connected = 1;
alive_due_ms = HAL_GetTick() + 500;
/* ---- KLine FSM callbacks (implement BT integration here) ---- */
void KLine_OnConnected(void)
{
BT_OnPSG5CommEstablished();
}
void KLine_OnDisconnected(void)
{
BT_KLINE_ERROR();
}
void KLine_OnKeepAliveTick(void)
{
BT_SendCommStatus();
}
/* ---- Button / trigger ---- */
void OnEnterReceived(void)
{
/*
* User pressed button — request a KWP1281 connection.
* The FSM will handle everything from here, non-blocking.
*/
KLine_FSM_RequestConnect(ECU_INIT_ADDRESS);
}
int BitBang = 0;
/* USER CODE END 0 */
/**
@@ -161,12 +161,17 @@ int main(void)
MX_USART1_UART_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
//HAL_UART_Receive_IT(&huart1, &k_rx_byte, 1);
HAL_Delay(25); // Wait before starting UART (standard KWP wait time)
//int protocolVersion = WakeUp(ECU_INIT_ADDRESS, 0);
KLine_FSM_Init();
/* Note: KLine_Ring_Init() is called inside the FSM after the 5-baud
bitbang sequence restores UART1, so we do NOT call it here. */
HAL_Delay(25); /* Standard KWP power-on wait — only HAL_Delay in the project */
HC06_Init();
HC06_AT_BootSetup(); // sets NAME, then switches to BIN mode
HC06_AT_BootSetup();
/* USER CODE END 2 */
/* Infinite loop */
@@ -176,26 +181,12 @@ int main(void)
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(BitBang){
KLine_FSM_RequestConnect(ECU_INIT_ADDRESS);
BitBang = 0;
}
KLine_FSM_Run();
HC06_Process();
if(BitBang){
//HAL_GPIO_WritePin(KLINE_GPIO_PORT, GPIO_PIN_8, GPIO_PIN_RESET);
TryConnection();
//AQUI HARIA FALTA IMPLEMENTAR UN TIMER QUE CADA x ms haga un keep alive
//EndCommunication();
}
if(psg_connected){
if ((int32_t)(HAL_GetTick() - alive_due_ms) >= 0){
if(!KeepAlive()){
KLINE_THROW_NONALIVE_EXCEPTION(4);
psg_connected = 0;
//return;
}
BT_SendCommStatus();
alive_due_ms = HAL_GetTick() + 500;
}
}
}
/* USER CODE END 3 */
}
@@ -304,11 +295,11 @@ static void MX_FDCAN1_Init(void)
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
//siempre quitar el static
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */