156 lines
4.1 KiB
C
156 lines
4.1 KiB
C
/*
|
|
* 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;
|
|
}
|