plip: optional auto story-ring (off by default) #46
@@ -84,6 +84,16 @@ menu "PLIP Voice Configuration"
|
||||
Can be combined with PLIP_DIAL_PULSE: whichever source detects a
|
||||
digit first wins. Default off — enable for touch-tone handsets.
|
||||
|
||||
config PLIP_AUTO_RING
|
||||
bool "Auto story-ring (rings by itself every 15-30 min)"
|
||||
default n
|
||||
help
|
||||
When enabled, the phone rings on its own at a random 15-30 minute
|
||||
interval; picking up launches a random audio story straight away
|
||||
(no menu). If nobody answers within 1 minute the bell stops and the
|
||||
next ring is rescheduled. Default off — enable for the escape-room
|
||||
ambience where the phone calls the players.
|
||||
|
||||
config PLIP_GATEWAY_URL
|
||||
string "NPC Gateway Base URL"
|
||||
default "http://192.168.0.50:8401"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_random.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -98,6 +99,15 @@ static char s_incoming_number[16] = {0};
|
||||
static char s_incoming_scene[40] = {0};
|
||||
static volatile bool s_incoming_armed = false;
|
||||
|
||||
/* Auto story-ring: the phone rings on its own at a random interval; picking up
|
||||
* launches a random audio story straight away. If nobody answers, the bell
|
||||
* stops after STORY_RING_TIMEOUT_MS and the next ring is rescheduled. */
|
||||
#define STORY_RING_MIN_MS (15 * 60 * 1000) /* 15 min */
|
||||
#define STORY_RING_SPAN_MS (15 * 60 * 1000) /* + up to 15 min → 30 max */
|
||||
#define STORY_RING_TIMEOUT_MS (60 * 1000) /* give up after 1 min */
|
||||
static volatile bool s_story_ring_armed = false;
|
||||
static int64_t s_story_ring_start_us = 0;
|
||||
|
||||
/* Known numbers: ringback when dialed */
|
||||
static const char *KNOWN[] = {
|
||||
"12", "3615", "15", "17", "18", "0142738200", NULL
|
||||
@@ -127,6 +137,11 @@ static void scene_slug(const char *scene, char *out, size_t cap)
|
||||
|
||||
static void go_idle(void)
|
||||
{
|
||||
if (s_story_ring_armed) { /* hung up mid auto-ring → silence the bell */
|
||||
phone_ring_stop();
|
||||
slic_ring_stop();
|
||||
s_story_ring_armed = false;
|
||||
}
|
||||
tones_stop();
|
||||
audio_stop();
|
||||
audio_pa_set(false);
|
||||
@@ -164,6 +179,53 @@ static void enter_incoming_greet(void)
|
||||
ESP_LOGI(TAG, "INCOMING pickup -> GREET num=%s sid=%s", s_number, s_sid);
|
||||
}
|
||||
|
||||
/* Answered an auto story-ring: stop the bell and launch a random story right
|
||||
* into STATE_GAMEBOOK (no menu). Drives s_offhook directly because the
|
||||
* debounced GPIO hook detection is unreliable while the bell rings. */
|
||||
static void enter_story_from_ring(void)
|
||||
{
|
||||
s_story_ring_armed = false;
|
||||
phone_ring_stop();
|
||||
slic_ring_stop();
|
||||
tones_stop();
|
||||
dialer_reset();
|
||||
s_offhook = true;
|
||||
#if CONFIG_PLIP_DIAL_DTMF
|
||||
dtmf_start();
|
||||
#endif
|
||||
audio_pa_set(true);
|
||||
plip_gamebook_begin_random();
|
||||
s_state = STATE_GAMEBOOK;
|
||||
ESP_LOGI(TAG, "story-ring pickup -> GAMEBOOK (random story)");
|
||||
}
|
||||
|
||||
#if CONFIG_PLIP_AUTO_RING
|
||||
/* Rings the phone at a random 15–30 min interval. Only rings when the line is
|
||||
* truly idle (on-hook, no other call/ring armed) and a story pack is present.
|
||||
* The 1-minute no-answer timeout is handled in conv_task. */
|
||||
static void story_ring_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
uint32_t wait_ms = STORY_RING_MIN_MS + (esp_random() % STORY_RING_SPAN_MS);
|
||||
ESP_LOGI(TAG, "auto story-ring: next attempt in %u min",
|
||||
(unsigned)(wait_ms / 60000));
|
||||
vTaskDelay(pdMS_TO_TICKS(wait_ms));
|
||||
|
||||
if (s_state == STATE_IDLE && !s_offhook && !s_incoming_armed
|
||||
&& !s_story_ring_armed && plip_gamebook_available()) {
|
||||
s_story_ring_armed = true;
|
||||
s_story_ring_start_us = esp_timer_get_time();
|
||||
phone_ring_start();
|
||||
ESP_LOGI(TAG, "auto story-ring: ringing (up to %d s)",
|
||||
STORY_RING_TIMEOUT_MS / 1000);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "auto story-ring: line busy/unavailable — skipped");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PLIP_AUTO_RING */
|
||||
|
||||
static void conv_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
@@ -185,7 +247,10 @@ static void conv_task(void *arg)
|
||||
} else {
|
||||
/* Off-hook from idle */
|
||||
if (s_state == STATE_IDLE) {
|
||||
if (s_incoming_armed) {
|
||||
if (s_story_ring_armed) {
|
||||
/* Answered an auto story-ring → straight into a story. */
|
||||
enter_story_from_ring();
|
||||
} else if (s_incoming_armed) {
|
||||
/* INCOMING call answered (phone.c detected the edge). */
|
||||
enter_incoming_greet();
|
||||
} else if (plip_gamebook_available()) {
|
||||
@@ -222,6 +287,19 @@ static void conv_task(void *arg)
|
||||
* line directly — it reads the pickup cleanly mid-ring. */
|
||||
if (s_incoming_armed && slic_is_offhook()) {
|
||||
enter_incoming_greet();
|
||||
} else if (s_story_ring_armed) {
|
||||
/* Same raw-SLIC poll for the auto story-ring, plus the
|
||||
* no-answer timeout: stop the bell after 1 min unanswered. */
|
||||
if (slic_is_offhook()) {
|
||||
enter_story_from_ring();
|
||||
} else if ((esp_timer_get_time() - s_story_ring_start_us) / 1000
|
||||
>= STORY_RING_TIMEOUT_MS) {
|
||||
phone_ring_stop();
|
||||
slic_ring_stop();
|
||||
s_story_ring_armed = false;
|
||||
ESP_LOGI(TAG, "auto story-ring: no answer after %d s — stop",
|
||||
STORY_RING_TIMEOUT_MS / 1000);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -483,6 +561,10 @@ void conversation_init(void)
|
||||
xTaskCreate(conv_task, "conv", 8192, NULL, 4, NULL);
|
||||
#else
|
||||
xTaskCreate(conv_task, "conv", 6144, NULL, 4, NULL);
|
||||
#endif
|
||||
#if CONFIG_PLIP_AUTO_RING
|
||||
/* Auto story-ring scheduler: rings every 15–30 min when idle. */
|
||||
xTaskCreate(story_ring_task, "storyring", 3072, NULL, 3, NULL);
|
||||
#endif
|
||||
ESP_LOGI(TAG, "conversation init");
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
@@ -134,6 +135,24 @@ void plip_gamebook_begin(void)
|
||||
enter_menu();
|
||||
}
|
||||
|
||||
void plip_gamebook_begin_random(void)
|
||||
{
|
||||
plip_gamebook_end(); // clean any previous run
|
||||
|
||||
s_lib_root = load_json(GB_LIBRARY);
|
||||
if (!s_lib_root) return;
|
||||
s_lib = cJSON_GetObjectItem(s_lib_root, "library");
|
||||
if (!cJSON_IsArray(s_lib) || cJSON_GetArraySize(s_lib) == 0) {
|
||||
cJSON_Delete(s_lib_root); s_lib_root = NULL; s_lib = NULL;
|
||||
return;
|
||||
}
|
||||
s_lib_n = cJSON_GetArraySize(s_lib);
|
||||
audio_pa_set(true); // earpiece amplifier on
|
||||
int idx = (int)(esp_random() % (uint32_t)s_lib_n);
|
||||
ESP_LOGI(TAG, "auto-start random story #%d/%d", idx, s_lib_n);
|
||||
load_book(idx); // straight into the story, no menu
|
||||
}
|
||||
|
||||
void plip_gamebook_feed_digit(int d)
|
||||
{
|
||||
if (s_mode == GB_MENU) {
|
||||
|
||||
@@ -28,6 +28,10 @@ bool plip_gamebook_available(void);
|
||||
// Démarre le mode livre-jeu : charge la bibliothèque et joue le menu audio.
|
||||
void plip_gamebook_begin(void);
|
||||
|
||||
// Démarre directement UNE histoire tirée au hasard (sans passer par le menu) —
|
||||
// utilisé quand le téléphone sonne tout seul et qu'on décroche.
|
||||
void plip_gamebook_begin_random(void);
|
||||
|
||||
// Traite un chiffre composé (0..9). Menu → choisit l'histoire ; en histoire →
|
||||
// choisit la réponse ; sur une fin → 0 revient au menu. Interrompt la
|
||||
// narration en cours pour enchaîner.
|
||||
|
||||
Reference in New Issue
Block a user