initial commit

This commit is contained in:
2026-04-20 13:09:27 -04:00
commit d938069aa6
12 changed files with 36844 additions and 0 deletions

BIN
aitrip-inmp441-amazon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
esp32 pinout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

BIN
esp32-pinout-devkitv1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

BIN
inmp441-datasheet.pdf Normal file

Binary file not shown.

183
mic/mic.ino Normal file
View File

@@ -0,0 +1,183 @@
#include <WiFi.h>
#include <WiFiUdp.h>
#include <math.h>
#include <driver/i2s.h>
#include <driver/touch_pad.h>
const char* ssid = "Fios-8EKF5";
const char* password = "hero816cars7050jon";
const char* DEST_IP = "192.168.1.190";
const int UDP_PORT = 5004;
const int OUTPUT_RATE = 8000; // 8kHz rtp payload rate
const int MIC_RATE = 8000; // capture rate inmp441
const int FRAME_SIZE = 160; // 20ms frames @ 8kHz
const float TONE_HZ = 440.0;
const float MIC_GAIN = 4.0f;
const touch_pad_t TOUCH_PIN = TOUCH_PAD_NUM4; // gpio13
const int TOUCH_THRESHOLD = 500;
const int I2S_WS = 25;
const int I2S_SCK = 22;
const int I2S_SD = 21;
WiFiUDP udp;
uint16_t seq = 0;
uint32_t ts = 0;
bool tone_mode = true; // start with tone
struct rtp_hdr {
uint8_t vpxcc;
uint8_t mpt;
uint16_t seq;
uint32_t ts;
uint32_t ssrc;
};
static const int16_t exp_lut[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};
uint8_t linear2ulaw(int16_t pcm_val) {
int sign = (pcm_val >> 8) & 0x80;
if (sign) pcm_val = (short)-pcm_val;
if (pcm_val > 32635) pcm_val = 32635;
pcm_val = pcm_val + 0x84;
int exponent = exp_lut[(pcm_val >> 7) & 0xFF];
int mantissa = (pcm_val >> (exponent + 3)) & 0x0F;
return ~(sign | (exponent << 4) | mantissa);
}
void setup_i2s_mic() {
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = MIC_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 256,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = I2S_SD
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config);
i2s_zero_dma_buffer(I2S_NUM_0);
}
void setup() {
Serial.begin(115200);
touch_pad_init();
touch_pad_set_voltage(TOUCH_HVOLT_2V7, TOUCH_LVOLT_0V5, TOUCH_HVOLT_ATTEN_1V);
touch_pad_config(TOUCH_PIN, 0);
setup_i2s_mic();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.printf("\nconnected, ip=%s\n", WiFi.localIP().toString().c_str());
udp.begin(UDP_PORT);
Serial.println("tap pin 13 to switch modes");
}
void loop() {
static unsigned long last_send = 0; // timer
static unsigned long last_touch_check = 0; // timer
static bool last_touch_state = false;
static const unsigned long FRAME_INTERVAL = 20; // 20ms
static const unsigned long TOUCH_CHECK_INTERVAL = 50; // check every 50ms
// check touch state periodically
unsigned long now = millis();
if (now - last_touch_check > TOUCH_CHECK_INTERVAL) {
uint16_t touch_val;
touch_pad_read(TOUCH_PIN, &touch_val);
bool touched = touch_val < TOUCH_THRESHOLD;
// detect touch press (not hold)
if (touched && !last_touch_state) {
tone_mode = !tone_mode;
Serial.printf("%s mode active\n", tone_mode ? "tone" : "mic");
delay(300); // debounce
}
last_touch_state = touched;
last_touch_check = now;
}
// frame buffer
if (now - last_send < FRAME_INTERVAL) {
yield();
return;
}
uint8_t packet[12 + FRAME_SIZE];
int16_t samples[FRAME_SIZE];
if (tone_mode) {
// fixed tone generation with integer phase accumulator
static uint32_t phase_acc = 0;
const uint32_t phase_inc_int = (uint32_t)(TONE_HZ * (1ULL << 32) / OUTPUT_RATE);
for (int i = 0; i < FRAME_SIZE; i++) {
float phase = (phase_acc >> 16) * (2.0 * M_PI / 65536.0);
samples[i] = (int16_t)(16000.0 * sinf(phase));
phase_acc += phase_inc_int;
}
} else {
int32_t mic_buf[FRAME_SIZE];
size_t bytes_read = 0;
i2s_read(I2S_NUM_0, mic_buf, sizeof(mic_buf), &bytes_read, portMAX_DELAY);
if (bytes_read != sizeof(mic_buf)) {
memset(samples, 0, sizeof(samples));
} else {
for (int i = 0; i < FRAME_SIZE; i++) {
float y = (mic_buf[i] >> 16) * MIC_GAIN; //gain
if (y > 32767.0f) y = 32767.0f;
if (y < -32768.0f) y = -32768.0f;
samples[i] = (int16_t)y;
}
}
}
for (int i = 0; i < FRAME_SIZE; i++) {
packet[12 + i] = linear2ulaw(samples[i]);
}
rtp_hdr* hdr = (rtp_hdr*)packet;
hdr->vpxcc = 0x80;
hdr->mpt = 0x00;
hdr->seq = htons(seq++);
hdr->ts = htonl(ts);
hdr->ssrc = htonl(0x12345678);
udp.beginPacket(DEST_IP, UDP_PORT);
udp.write(packet, sizeof(packet));
udp.endPacket();
ts += FRAME_SIZE;
last_send = now;
yield();
}

183
mic/mic.ino~ Normal file
View File

@@ -0,0 +1,183 @@
#include <WiFi.h>
#include <WiFiUdp.h>
#include <math.h>
#include <driver/i2s.h>
#include <driver/touch_pad.h>
const char* ssid = "Fios-8EKF5";
const char* password = "hero816cars7050jon";
const char* DEST_IP = "192.168.1.190";
const int UDP_PORT = 5004;
const int OUTPUT_RATE = 8000; // 8kHz rtp payload rate
const int MIC_RATE = 16000; // capture rate inmp441
const int FRAME_SIZE = 160; // 20ms frames @ 8kHz
const float TONE_HZ = 440.0;
const touch_pad_t TOUCH_PIN = TOUCH_PAD_NUM4; // gpio13
const int TOUCH_THRESHOLD = 800; // adjust as needed
// inmp441 pins - change as needed
const int I2S_WS = 22; // lrcl / ws
const int I2S_SCK = 26; // bclk
const int I2S_SD = 21; // data from mic to esp32
WiFiUDP udp;
uint16_t seq = 0;
uint32_t ts = 0;
bool tone_mode = true; // start with tone
struct rtp_hdr {
uint8_t vpxcc;
uint8_t mpt;
uint16_t seq;
uint32_t ts;
uint32_t ssrc;
};
static const int16_t exp_lut[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};
uint8_t linear2ulaw(int16_t pcm_val) {
int sign = (pcm_val >> 8) & 0x80;
if (sign) pcm_val = (short)-pcm_val;
if (pcm_val > 32635) pcm_val = 32635;
pcm_val = pcm_val + 0x84;
int exponent = exp_lut[(pcm_val >> 7) & 0xFF];
int mantissa = (pcm_val >> (exponent + 3)) & 0x0F;
return ~(sign | (exponent << 4) | mantissa);
}
void setup() {
Serial.begin(115200);
// init touch
touch_pad_init();
touch_pad_set_voltage(TOUCH_HVOLT_2V7, TOUCH_LVOLT_0V5, TOUCH_HVOLT_ATTEN_1V);
touch_pad_config(TOUCH_PIN, 0);
// init i2s for adc sampling
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
.sample_rate = OUTPUT_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 128,
.use_apll = false
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_0); // gpio36
i2s_adc_enable(I2S_NUM_0);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.printf("\nconnected, ip=%s\n", WiFi.localIP().toString().c_str());
udp.begin(UDP_PORT);
Serial.println("tap pin 13 to switch modes");
}
void loop() {
static unsigned long last_send = 0;
static unsigned long last_touch_check = 0;
static bool last_touch_state = false;
static const unsigned long FRAME_INTERVAL = 20; // 20ms
static const unsigned long TOUCH_CHECK_INTERVAL = 50; // check every 50ms
// check touch state periodically
unsigned long now = millis();
if (now - last_touch_check > TOUCH_CHECK_INTERVAL) {
uint16_t touch_val;
touch_pad_read(TOUCH_PIN, &touch_val);
bool touched = touch_val < TOUCH_THRESHOLD;
// detect touch press (not hold)
if (touched && !last_touch_state) {
tone_mode = !tone_mode;
Serial.printf("switched to %s mode\n", tone_mode ? "tone" : "mic");
delay(200); // debounce
}
last_touch_state = touched;
last_touch_check = now;
}
// only send if it's time
if (now - last_send < FRAME_INTERVAL) {
yield();
return;
}
uint8_t packet[12 + FRAME_SIZE];
int16_t samples[FRAME_SIZE];
if (tone_mode) {
// fixed tone generation with integer phase accumulator
static uint32_t phase_acc = 0;
const uint32_t phase_inc_int = (uint32_t)(TONE_HZ * (1ULL << 32) / OUTPUT_RATE);
for (int i = 0; i < FRAME_SIZE; i++) {
float phase = (phase_acc >> 16) * (2.0 * M_PI / 65536.0);
samples[i] = (int16_t)(16000.0 * sinf(phase));
phase_acc += phase_inc_int;
}
} else {
// read from i2s adc buffer - need multiple reads to fill frame
static float dc_filter = 2048.0; // dc offset removal filter
int samples_filled = 0;
while (samples_filled < FRAME_SIZE) {
size_t bytes_read;
uint16_t i2s_buffer[64]; // bigger buffer
i2s_read(I2S_NUM_0, i2s_buffer, 128, &bytes_read, 10);
int samples_read = bytes_read / 2; // 2 bytes per sample
for (int i = 0; i < samples_read && samples_filled < FRAME_SIZE; i++) {
// i2s adc gives 12-bit data in upper bits of 16-bit word
int adc_val = (i2s_buffer[i] >> 4) & 0x0FFF;
// dc offset removal with simple iir filter
dc_filter = dc_filter * 0.999 + adc_val * 0.001;
// scale and store
samples[samples_filled] = (int16_t)((adc_val - dc_filter) * 16);
samples_filled++;
}
}
}
// µ-law encode
for (int i = 0; i < FRAME_SIZE; i++) {
packet[12 + i] = linear2ulaw(samples[i]);
}
// build rtp header
rtp_hdr* hdr = (rtp_hdr*)packet;
hdr->vpxcc = 0x80;
hdr->mpt = 0x00;
hdr->seq = htons(seq++);
hdr->ts = htonl(ts);
hdr->ssrc = htonl(0x12345678);
// send packet
udp.beginPacket(DEST_IP, UDP_PORT);
udp.write(packet, sizeof(packet));
ts += FRAME_SIZE;
last_send = now;
yield();
}

BIN
plink.log Normal file

Binary file not shown.

BIN
vtech.lnk Normal file

Binary file not shown.

18626
vtech.org Normal file

File diff suppressed because it is too large Load Diff

17597
vtech.org~ Normal file

File diff suppressed because it is too large Load Diff

255
web/web.ino Normal file
View File

@@ -0,0 +1,255 @@
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <driver/touch_pad.h>
preferences prefs;
webserver server(80);
// touch pad used as boot override / setup trigger
const touch_pad_t touch_pin = TOUCH_PAD_NUM4; // gpio13
const int touch_threshold = 500;
// softap fallback
const char* ap_ssid = "baby-monitor-setup";
const char* ap_password = "setup1234";
// saved / editable config
String wifi_ssid = "";
String wifi_password = "";
String dest_ip_str = "192.168.1.190";
uint16_t udp_port = 5004;
bool config_mode = false;
const unsigned long wifi_connect_timeout_ms = 15000;
String html_escape(const String& s) {
String out = s;
out.replace("&", "&amp;");
out.replace("<", "&lt;");
out.replace(">", "&gt;");
out.replace("\"", "&quot;");
return out;
}
void load_config() {
prefs.begin("bmcfg", true);
wifi_ssid = prefs.getString("ssid", "");
wifi_password = prefs.getString("pwd", "");
dest_ip_str = prefs.getString("destip", "192.168.1.190");
udp_port = prefs.getUShort("port", 5004);
prefs.end();
}
void save_config(const String& ssid, const String& pwd, const String& dest_ip, uint16_t port) {
prefs.begin("bmcfg", false);
prefs.putString("ssid", ssid);
prefs.putString("pwd", pwd);
prefs.putString("destip", dest_ip);
prefs.putUShort("port", port);
prefs.end();
}
void clear_config() {
prefs.begin("bmcfg", false);
prefs.clear();
prefs.end();
}
bool force_setup_requested() {
// give touch pad a moment to settle
delay(100);
uint16_t touch_val = 0;
touch_pad_read(touch_pin, &touch_val);
Serial.printf("touch boot check: %u\n", touch_val);
return touch_val < touch_threshold;
}
void handle_root() {
String page =
"<!doctype html>"
"<html><head>"
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
"<title>baby monitor setup</title>"
"<style>"
"body{font-family:sans-serif;max-width:520px;margin:2rem auto;padding:0 1rem;}"
"input{width:100%;padding:.6rem;margin:.2rem 0 1rem 0;box-sizing:border-box;}"
"button{padding:.7rem 1rem;margin-right:.5rem;}"
"code{background:#eee;padding:.1rem .3rem;}"
"</style>"
"</head><body>"
"<h2>baby monitor setup</h2>"
"<p>connect to ap <code>" + String(ap_ssid) + "</code> then browse to <code>192.168.4.1</code>.</p>"
"<form method='post' action='/save'>"
"<label>wifi ssid</label>"
"<input name='ssid' value='" + html_escape(wifi_ssid) + "'>"
"<label>wifi password</label>"
"<input name='pwd' type='password' value='" + html_escape(wifi_password) + "'>"
"<label>destination ip</label>"
"<input name='destip' value='" + html_escape(dest_ip_str) + "'>"
"<label>udp port</label>"
"<input name='port' value='" + String(udp_port) + "'>"
"<button type='submit'>save and reboot</button>"
"</form>"
"<form method='post' action='/clear' onsubmit='return confirm(\"clear saved config?\");'>"
"<button type='submit'>clear saved config</button>"
"</form>"
"<p><a href='/status'>status json</a></p>"
"</body></html>";
server.send(200, "text/html", page);
}
void handle_status() {
String json = "{";
json += "\"config_mode\":" + String(config_mode ? "true" : "false") + ",";
json += "\"ap_ssid\":\"" + String(ap_ssid) + "\",";
json += "\"saved_ssid\":\"" + html_escape(wifi_ssid) + "\",";
json += "\"dest_ip\":\"" + html_escape(dest_ip_str) + "\",";
json += "\"udp_port\":" + String(udp_port);
json += "}";
server.send(200, "application/json", json);
}
bool valid_ipv4(const String& s) {
ipaddress ip;
return ip.fromString(s);
}
void handle_save() {
String new_ssid = server.arg("ssid");
String new_pwd = server.arg("pwd");
String new_dest = server.arg("destip");
uint16_t new_port = (uint16_t)server.arg("port").toInt();
if (new_ssid.length() == 0) {
server.send(400, "text/plain", "ssid required");
return;
}
if (!valid_ipv4(new_dest)) {
server.send(400, "text/plain", "invalid destination ip");
return;
}
if (new_port == 0) {
server.send(400, "text/plain", "invalid udp port");
return;
}
save_config(new_ssid, new_pwd, new_dest, new_port);
server.send(200, "text/html",
"<html><body><h3>saved. rebooting...</h3></body></html>");
delay(1000);
esp_restart();
}
void handle_clear() {
clear_config();
server.send(200, "text/html",
"<html><body><h3>config cleared. rebooting...</h3></body></html>");
delay(1000);
esp_restart();
}
void start_config_ap() {
config_mode = true;
WiFi.disconnect(true, true);
delay(250);
WiFi.mode(WIFI_AP);
WiFi.softAP(ap_ssid, ap_password);
IPAddress ip = WiFi.softAPIP();
Serial.printf("config ap active\n");
Serial.printf(" ssid: %s\n", ap_ssid);
Serial.printf(" pass: %s\n", ap_password);
Serial.printf(" ip: %s\n", ip.toString().c_str());
server.on("/", HTTP_GET, handle_root);
server.on("/save", HTTP_POST, handle_save);
server.on("/clear", HTTP_POST, handle_clear);
server.on("/status", HTTP_GET, handle_status);
server.begin();
}
bool try_connect_wifi() {
if (wifi_ssid.length() == 0) {
Serial.println("no saved wifi ssid");
return false;
}
WiFi.disconnect(true, true);
delay(250);
WiFi.mode(WIFI_STA);
WiFi.begin(wifi_ssid.c_str(), wifi_password.c_str());
Serial.printf("connecting to wifi: %s\n", wifi_ssid.c_str());
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED &&
millis() - start < wifi_connect_timeout_ms) {
delay(500);
Serial.print(".");
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("connected\n");
Serial.printf(" ip: %s\n", WiFi.localIP().toString().c_str());
Serial.printf(" dest ip: %s\n", dest_ip_str.c_str());
Serial.printf(" udp port: %u\n", udp_port);
return true;
}
Serial.println("wifi connect failed");
return false;
}
void setup() {
Serial.begin(115200);
delay(200);
touch_pad_init();
touch_pad_set_voltage(TOUCH_HVOLT_2V7, TOUCH_LVOLT_0V5, TOUCH_HVOLT_ATTEN_1V);
touch_pad_config(touch_pin, 0);
load_config();
if (force_setup_requested()) {
Serial.println("force setup mode requested");
start_config_ap();
return;
}
if (!try_connect_wifi()) {
start_config_ap();
}
}
void loop() {
if (config_mode) {
server.handleClient();
delay(2);
return;
}
// placeholder for later normal-mode behavior
static unsigned long last_print = 0;
if (millis() - last_print > 5000) {
Serial.printf("normal mode alive. wifi=%s ip=%s dest=%s:%u\n",
wifi_ssid.c_str(),
WiFi.localIP().toString().c_str(),
dest_ip_str.c_str(),
udp_port);
last_print = millis();
}
delay(10);
}