feat(plip): conversation state machine — dialtone/dialing/routing/ringback/busy (stage 1)
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* conversation.c — PLIP telephone conversation state machine.
|
||||
*
|
||||
* States: IDLE → DIALTONE → DIALING → RINGBACK | BUSY
|
||||
*
|
||||
* Transitions:
|
||||
* IDLE + off-hook → DIALTONE (tones_dialtone_start)
|
||||
* DIALTONE + first digit → DIALING (tones_stop)
|
||||
* DIALING + ms_since_last > 3000 → RINGBACK or BUSY (route decision)
|
||||
* any state + on-hook → IDLE (tones_stop, audio_stop, PA off)
|
||||
*
|
||||
* Routing table (known numbers → ringback; unknown → busy after 3 s silence):
|
||||
* "12", "3615", "15", "17", "18", "0142738200"
|
||||
*/
|
||||
|
||||
#include "conversation.h"
|
||||
#include "dialer.h"
|
||||
#include "tones.h"
|
||||
#include "audio.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "conversation"
|
||||
|
||||
typedef enum {
|
||||
STATE_IDLE,
|
||||
STATE_DIALTONE,
|
||||
STATE_DIALING,
|
||||
STATE_RINGBACK,
|
||||
STATE_BUSY,
|
||||
} conv_state_t;
|
||||
|
||||
static volatile conv_state_t s_state = STATE_IDLE;
|
||||
static volatile bool s_offhook = false;
|
||||
static volatile bool s_hook_changed = false;
|
||||
|
||||
/* Known numbers: ringback when dialed */
|
||||
static const char *KNOWN[] = {
|
||||
"12", "3615", "15", "17", "18", "0142738200", NULL
|
||||
};
|
||||
|
||||
static bool is_known(const char *num)
|
||||
{
|
||||
for (int i = 0; KNOWN[i] != NULL; i++) {
|
||||
if (strcmp(num, KNOWN[i]) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void go_idle(void)
|
||||
{
|
||||
tones_stop();
|
||||
audio_stop();
|
||||
audio_pa_set(false);
|
||||
dialer_reset();
|
||||
s_state = STATE_IDLE;
|
||||
ESP_LOGI(TAG, "-> IDLE");
|
||||
}
|
||||
|
||||
static void conv_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
ESP_LOGI(TAG, "conversation task ready");
|
||||
|
||||
for (;;) {
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
|
||||
/* Handle hook change events */
|
||||
if (s_hook_changed) {
|
||||
s_hook_changed = false;
|
||||
if (!s_offhook) {
|
||||
/* On-hook: always go idle regardless of current state */
|
||||
if (s_state != STATE_IDLE) {
|
||||
ESP_LOGI(TAG, "on-hook -> IDLE");
|
||||
go_idle();
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
/* Off-hook: start dial tone if we were idle */
|
||||
if (s_state == STATE_IDLE) {
|
||||
dialer_reset();
|
||||
tones_dialtone_start();
|
||||
s_state = STATE_DIALTONE;
|
||||
ESP_LOGI(TAG, "off-hook -> DIALTONE");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* State machine polling */
|
||||
switch (s_state) {
|
||||
case STATE_IDLE:
|
||||
break; /* nothing to do */
|
||||
|
||||
case STATE_DIALTONE:
|
||||
if (!s_offhook) {
|
||||
go_idle();
|
||||
break;
|
||||
}
|
||||
/* First digit received → stop dialtone, enter dialing */
|
||||
if (!dialer_idle()) {
|
||||
tones_stop();
|
||||
s_state = STATE_DIALING;
|
||||
ESP_LOGI(TAG, "first digit -> DIALING");
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_DIALING:
|
||||
if (!s_offhook) {
|
||||
go_idle();
|
||||
break;
|
||||
}
|
||||
/* Wait for 3 s of silence after last digit, then route */
|
||||
if (dialer_ms_since_last() > 3000) {
|
||||
const char *num = dialer_current();
|
||||
if (is_known(num)) {
|
||||
ESP_LOGI(TAG, "route %s -> known (ringback)", num);
|
||||
tones_ringback_start();
|
||||
s_state = STATE_RINGBACK;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "route %s -> unknown (busy)", num);
|
||||
tones_busy_start();
|
||||
s_state = STATE_BUSY;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_RINGBACK:
|
||||
case STATE_BUSY:
|
||||
if (!s_offhook) {
|
||||
go_idle();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void conversation_init(void)
|
||||
{
|
||||
s_state = STATE_IDLE;
|
||||
s_offhook = false;
|
||||
s_hook_changed = false;
|
||||
xTaskCreate(conv_task, "conv", 3072, NULL, 4, NULL);
|
||||
ESP_LOGI(TAG, "conversation init");
|
||||
}
|
||||
|
||||
void conversation_on_hook_change(bool offhook)
|
||||
{
|
||||
s_offhook = offhook;
|
||||
s_hook_changed = true;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
|
||||
void conversation_init(void);
|
||||
void conversation_on_hook_change(bool offhook);
|
||||
@@ -30,6 +30,8 @@
|
||||
#include "hook_client.h"
|
||||
#include "phone.h"
|
||||
#include "scenario_mesh.h"
|
||||
#include "dialer.h"
|
||||
#include "conversation.h"
|
||||
|
||||
static const char *TAG = "plip-main";
|
||||
|
||||
@@ -101,6 +103,11 @@ static void boot_task(void *arg)
|
||||
ESP_LOGW(TAG, "hook_client_init: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
/* ── Stage 1: dialer + conversation state machine ── */
|
||||
dialer_init();
|
||||
conversation_init();
|
||||
ESP_LOGI(TAG, "Stage 1: dialer + conversation state machine up");
|
||||
|
||||
/* ── 7. Phone task (Phase D) ── */
|
||||
ret = phone_init();
|
||||
if (ret != ESP_OK) {
|
||||
|
||||
Reference in New Issue
Block a user