Files

515 lines
16 KiB
C

/*
* kl_session.c
* Session layer: 5-baud init, handshake, ident stream, session lifecycle.
*
* All operations are non-blocking state machines.
* The 5-baud sampling is ISR-driven via TIM17.
*/
#include "kl_session.h"
#include "kl_phy.h"
#include "kl_transport.h"
#include "kl_app.h"
#include "psg_prop.h"
#include "main.h"
#include <string.h>
extern TIM_HandleTypeDef htim17;
static KL_Session sess;
/* Forward declarations for BuildCombined (ident data) */
static size_t sess_build_ident(uint8_t *out, size_t maxlen);
/* ================================================================
* TIM17 configuration (0.1ms tick for 5-baud sampling)
* ================================================================ */
static void tim17_config(void)
{
__HAL_TIM_DISABLE(&htim17);
__HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
uint32_t timclk = HAL_RCC_GetPCLK2Freq();
RCC_ClkInitTypeDef clk;
uint32_t fl;
HAL_RCC_GetClockConfig(&clk, &fl);
if (clk.APB2CLKDivider != RCC_HCLK_DIV1) timclk *= 2U;
const uint32_t target_hz = 10000U;
uint32_t psc = (timclk + target_hz - 1U) / target_hz - 1U;
if (psc > 0xFFFFU) psc = 0xFFFFU;
__HAL_TIM_SET_PRESCALER(&htim17, psc);
__HAL_TIM_SET_AUTORELOAD(&htim17, 9U);
__HAL_TIM_SET_COUNTER(&htim17, 0U);
htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS);
htim17.Instance->EGR = TIM_EGR_UG;
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
HAL_NVIC_SetPriority(TIM1_TRG_COM_TIM17_IRQn, 6, 0);
HAL_NVIC_EnableIRQ(TIM1_TRG_COM_TIM17_IRQn);
}
static void tim17_schedule(uint32_t delay_ms)
{
uint32_t ticks = delay_ms * 10U;
if (ticks < 1U) ticks = 1U;
if (ticks > 0x10000U) ticks = 0x10000U;
__HAL_TIM_DISABLE(&htim17);
__HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
__HAL_TIM_SET_AUTORELOAD(&htim17, (uint16_t)(ticks - 1U));
__HAL_TIM_SET_COUNTER(&htim17, 0U);
htim17.Instance->EGR = TIM_EGR_UG;
__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
htim17.Instance->CR1 |= (TIM_CR1_OPM | TIM_CR1_URS);
__HAL_TIM_ENABLE_IT(&htim17, TIM_IT_UPDATE);
__HAL_TIM_ENABLE(&htim17);
}
/* ================================================================
* EXTI / 5-baud arming
* ================================================================ */
static void sess_rearm_5baud(void)
{
KL_Phy_DisableUart();
__HAL_RCC_SYSCFG_CLK_ENABLE();
/* TX pin idle high (input with pull-up) */
GPIO_InitTypeDef giTx = {0};
giTx.Pin = KL_TX_PIN;
giTx.Mode = GPIO_MODE_INPUT;
giTx.Pull = GPIO_PULLUP;
giTx.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(KL_GPIO_PORT, &giTx);
HAL_GPIO_WritePin(KL_GPIO_PORT, KL_TX_PIN, GPIO_PIN_SET);
/* RX pin: EXTI falling edge */
GPIO_InitTypeDef giRx = {0};
giRx.Pin = KL_RX_PIN;
giRx.Mode = GPIO_MODE_IT_FALLING;
giRx.Pull = GPIO_PULLUP;
HAL_GPIO_Init(KL_GPIO_PORT, &giRx);
__HAL_GPIO_EXTI_CLEAR_IT(KL_RX_PIN);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 0);
HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
static void sess_reset_to_idle(void)
{
sess.state = KL_SESS_IDLE;
sess.five_bit_index = 0;
sess.five_byte = 0;
sess.five_active = 0;
sess.connection_status = 0;
sess.end_requested = 0;
KL_Transport_ResetCounter();
HAL_TIM_Base_Stop_IT(&htim17);
__HAL_TIM_DISABLE_IT(&htim17, TIM_IT_UPDATE);
__HAL_GPIO_EXTI_CLEAR_IT(KL_RX_PIN);
HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
/* ================================================================
* Init
* ================================================================ */
void KL_Session_Init(void)
{
memset(&sess, 0, sizeof(sess));
KL_Phy_Init();
tim17_config();
sess_rearm_5baud();
}
/* ================================================================
* ISR: GPIO EXTI falling edge on RX pin (start of 5-baud)
* ================================================================ */
void KL_Session_OnExtiRxFalling(void)
{
if (sess.state == KL_SESS_IDLE) {
HAL_NVIC_DisableIRQ(EXTI15_10_IRQn);
sess.five_active = 1;
sess.five_bit_index = 0;
sess.five_byte = 0;
sess.state = KL_SESS_5BAUD_SAMPLING;
tim17_schedule(KL_5BAUD_FIRST_MS);
}
}
/* ================================================================
* ISR: TIM17 period elapsed (5-baud bit sampling)
* ================================================================ */
void KL_Session_OnTim17Elapsed(void)
{
if (!sess.five_active) {
HAL_TIM_Base_Stop_IT(&htim17);
return;
}
if (sess.state == KL_SESS_5BAUD_SAMPLING) {
GPIO_PinState pin = HAL_GPIO_ReadPin(KL_GPIO_PORT, KL_RX_PIN);
uint8_t bit = (pin == GPIO_PIN_SET) ? 1U : 0U;
sess.five_byte |= (bit << sess.five_bit_index);
sess.five_bit_index++;
if (sess.five_bit_index >= 8) {
sess.five_active = 0;
HAL_TIM_Base_Stop_IT(&htim17);
if (sess.five_byte != KL_SLAVE_ADDR) {
sess_reset_to_idle();
sess_rearm_5baud();
return;
}
sess.connection_status = 1;
sess.hs_next_tick = HAL_GetTick() + KL_W1_MS;
sess.state = KL_SESS_5BAUD_DONE;
} else {
tim17_schedule(KL_5BAUD_BIT_MS);
}
}
}
/* ================================================================
* Session kick (extend deadline)
* ================================================================ */
void KL_Session_Kick(void)
{
sess.session_deadline = HAL_GetTick() + KL_SESSION_TMO_MS;
}
void KL_Session_RequestEnd(void)
{
sess.end_requested = 1;
}
uint8_t KL_Session_GetStatus(void)
{
return sess.connection_status;
}
/* ================================================================
* Build identification data
* ================================================================ */
static size_t append_str(const char *s, size_t n, uint8_t *out, size_t w, size_t maxlen)
{
for (size_t i = 0; i < n && s[i] != '\0' && w < maxlen; i++) {
out[w++] = (uint8_t)s[i];
}
return w;
}
static size_t sess_build_ident(uint8_t *out, size_t maxlen)
{
size_t w = 0;
w = append_str(PSG_KUNDENNUMMER_STR, 12, out, w, maxlen);
w = append_str(PSG_DATENSATZ_STR, 10, out, w, maxlen);
w = append_str(PSG_SOFTWARE_VER_STR, 10, out, w, maxlen);
w = append_str(PSG_SOFTWARE_VER2_STR, 10, out, w, maxlen);
w = append_str(PSG_STEUERGERAET_STR, 10, out, w, maxlen);
w = append_str(PSG_UNKNOWN_STR, 6, out, w, maxlen);
return w;
}
/* ================================================================
* Terminate session and rearm 5-baud
* ================================================================ */
static void sess_terminate(void)
{
sess.connection_status = 0;
sess.session_deadline = 0;
sess.end_requested = 0;
KL_App_Reset();
KL_Transport_ResetCounter();
KL_Transport_TxReset();
KL_Transport_RxReset();
KL_Phy_DisableUart();
sess_rearm_5baud();
sess.state = KL_SESS_IDLE;
}
/* ================================================================
* Main session service (called from KLine_Service)
* ================================================================ */
void KL_Session_Service(void)
{
uint32_t now = HAL_GetTick();
switch (sess.state) {
case KL_SESS_IDLE:
case KL_SESS_5BAUD_SAMPLING:
/* Driven by ISR */
break;
/* ---- 5-baud done: wait W1 then enter UART mode ---- */
case KL_SESS_5BAUD_DONE:
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
KL_Phy_EnableUart9600();
KL_Transport_Init();
KL_Phy_RxFlush();
sess.hs_retry_count = 0;
sess.hs_next_tick = now;
sess.connection_status = 1;
sess.state = KL_SESS_HS_SYNC;
}
break;
/* ---- Handshake: send sync byte ---- */
case KL_SESS_HS_SYNC:
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
if (KL_Transport_SendRawByte(KL_SYNC_BYTE)) {
sess.state = KL_SESS_HS_SYNC_WAIT;
}
}
break;
case KL_SESS_HS_SYNC_WAIT:
if (KL_Transport_RawTxDone()) {
sess.hs_next_tick = HAL_GetTick() + KL_W2_MS;
KL_Transport_RawTxReset();
sess.state = KL_SESS_HS_KW_LSB;
}
break;
/* ---- Handshake: send keyword LSB ---- */
case KL_SESS_HS_KW_LSB:
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
if (KL_Transport_SendRawByte(KL_KEYWORD_LSB)) {
sess.state = KL_SESS_HS_KW_LSB_WAIT;
}
}
break;
case KL_SESS_HS_KW_LSB_WAIT:
if (KL_Transport_RawTxDone()) {
sess.hs_next_tick = HAL_GetTick() + KL_W3_MS;
KL_Transport_RawTxReset();
sess.state = KL_SESS_HS_KW_MSB;
}
break;
/* ---- Handshake: send keyword MSB ---- */
case KL_SESS_HS_KW_MSB:
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
if (KL_Transport_SendRawByte(KL_KEYWORD_MSB)) {
sess.state = KL_SESS_HS_KW_MSB_WAIT;
}
}
break;
case KL_SESS_HS_KW_MSB_WAIT:
if (KL_Transport_RawTxDone()) {
KL_Transport_RawTxReset();
/* Set deadline for master ACK */
sess.hs_next_tick = HAL_GetTick() + KL_HS_RETRY_MS;
sess.state = KL_SESS_HS_WAIT_ACK;
}
break;
/* ---- Handshake: wait for master ~KW_MSB ---- */
case KL_SESS_HS_WAIT_ACK:
{
uint8_t rx_byte;
if (KL_Phy_RxPop(&rx_byte)) {
if (rx_byte == (uint8_t)~KL_KEYWORD_MSB) {
/* Handshake succeeded -- prepare identification */
sess.ident_len = (uint8_t)sess_build_ident(sess.ident_buf,
sizeof(sess.ident_buf));
sess.ident_offset = 0;
KL_Phy_RxFlush();
sess.session_deadline = HAL_GetTick() + KL_SESSION_TMO_MS;
sess.state = KL_SESS_IDENT_SEND;
break;
}
/* Unexpected byte, fall through to retry */
}
if (kl_tick_diff(now, sess.hs_next_tick) >= 0) {
sess.hs_retry_count++;
if (sess.hs_retry_count >= KL_HANDSHAKE_MAX_RETRIES) {
sess_terminate();
} else {
sess.hs_next_tick = now;
sess.state = KL_SESS_HS_SYNC;
}
}
break;
}
/* ---- Identification: send ASCII chunks ---- */
case KL_SESS_IDENT_SEND:
{
uint8_t remaining = sess.ident_len - sess.ident_offset;
sess.ident_chunk_len = (remaining > KL_ASCII_CHUNK_SIZE)
? KL_ASCII_CHUNK_SIZE : remaining;
uint8_t payload[1 + KL_ASCII_CHUNK_SIZE];
payload[0] = (uint8_t)KL_CMD_AsciiData;
memcpy(&payload[1], &sess.ident_buf[sess.ident_offset], sess.ident_chunk_len);
if (KL_Transport_SendPacket(payload, (uint8_t)(1 + sess.ident_chunk_len))) {
sess.state = KL_SESS_IDENT_WAIT_TX;
}
break;
}
case KL_SESS_IDENT_WAIT_TX:
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
sess_terminate();
break;
}
if (KL_Transport_TxDone()) {
KL_Transport_TxReset();
sess.ident_offset += sess.ident_chunk_len;
if (sess.ident_offset >= sess.ident_len) {
/* Last chunk sent -- go straight to ready.
* No ACK expected after the final chunk. */
KL_Session_Kick();
sess.connection_status = 2;
sess.state = KL_SESS_READY;
} else {
/* Intermediate chunk: wait for ACK before next */
KL_Transport_StartReceive(400);
sess.state = KL_SESS_IDENT_WAIT_ACK;
}
} else if (KL_Transport_TxError()) {
sess_terminate();
}
break;
case KL_SESS_IDENT_WAIT_ACK:
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
sess_terminate();
break;
}
if (KL_Transport_RxDone()) {
const KL_Packet *pkt = KL_Transport_GetRxPacket();
KL_Transport_RxReset();
if (pkt->command == (uint8_t)KL_CMD_ACK) {
sess.state = KL_SESS_IDENT_SEND;
} else {
sess_terminate();
}
} else if (KL_Transport_RxError()) {
sess_terminate();
}
break;
case KL_SESS_IDENT_FINAL_WAIT:
/* No longer used, kept for enum completeness */
if (KL_Transport_TxDone()) {
KL_Transport_TxReset();
KL_Session_Kick();
sess.connection_status = 2;
sess.state = KL_SESS_READY;
} else if (KL_Transport_TxError()) {
sess_terminate();
}
break;
/* ---- Active session: ready to receive commands ---- */
case KL_SESS_READY:
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
sess_terminate();
break;
}
/* Use the session deadline as the RX timeout so we don't
* artificially cut short a receive that started late.
* Compute remaining time until session deadline. */
{
int32_t remaining = (int32_t)(sess.session_deadline - now);
if (remaining <= 0) { sess_terminate(); break; }
if (KL_Transport_StartReceive((uint32_t)remaining)) {
sess.state = KL_SESS_CMD_RX;
}
/* If StartReceive fails (rx not idle), stay in READY
* and retry next cycle rather than advancing to CMD_RX
* with no active receive. */
}
break;
case KL_SESS_CMD_RX:
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
KL_Transport_RxReset();
sess_terminate();
break;
}
if (KL_Transport_RxDone()) {
KL_Session_Kick();
sess.state = KL_SESS_CMD_DISPATCH;
} else if (KL_Transport_RxError()) {
KL_Transport_RxReset();
/* Only send NAK if we actually received partial data
* (i.e., at least the length byte was consumed).
* A plain timeout with no data is just "no packet yet"
* and should silently retry. */
if (KL_Transport_RxHadData()) {
KL_Transport_SendNak();
sess.state = KL_SESS_CMD_NAK_WAIT;
} else {
sess.state = KL_SESS_READY;
}
}
break;
case KL_SESS_CMD_NAK_WAIT:
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
KL_Transport_TxReset();
sess_terminate();
break;
}
if (KL_Transport_TxDone() || KL_Transport_TxError()) {
KL_Transport_TxReset();
sess.state = KL_SESS_READY;
}
break;
case KL_SESS_CMD_DISPATCH:
{
const KL_Packet *pkt = KL_Transport_GetRxPacket();
KL_Transport_RxReset();
KL_App_Dispatch(pkt);
sess.state = KL_SESS_CMD_RESPONSE;
break;
}
case KL_SESS_CMD_RESPONSE:
if (kl_tick_diff(now, sess.session_deadline) >= 0) {
sess_terminate();
break;
}
if (KL_App_Done()) {
if (sess.end_requested) {
sess_terminate();
} else {
KL_Session_Kick();
sess.state = KL_SESS_READY;
}
}
break;
case KL_SESS_TERMINATE:
sess_terminate();
break;
}
}