Safety, testing Exercise 21 README internal linking on Forgejo

This commit is contained in:
John Poole 2026-04-14 11:16:54 -07:00
commit 1d0a29f2a3
18 changed files with 2098 additions and 1 deletions

View file

@ -0,0 +1,3 @@
https://github.com/Xinyuan-LilyGO/LilyGo-LoRa-Series/issues/295#issuecomment-4241577603
tells us that the Specification as of April13, 2026, incorrectly includes a microphone. There is no microphone. The code here was ChatGPT's Codex trying to find the correct pins, a task that could never be accomplished. This exercise documents the attempts for posterity.

View file

@ -0,0 +1,453 @@
#include "MicrophoneRecorder.h"
#include <SPI.h>
#include <Wire.h>
#include <driver/i2s.h>
#include <limits.h>
#include <stdarg.h>
#include <time.h>
#include "driver/gpio.h"
#include "tbeam_supreme_adapter.h"
#ifndef MIC_SAMPLE_RATE
#define MIC_SAMPLE_RATE 16000
#endif
#ifndef MIC_BITS_PER_SAMPLE
#define MIC_BITS_PER_SAMPLE 16
#endif
#ifndef MIC_DATA_PIN
#define MIC_DATA_PIN 38
#endif
#ifndef MIC_CLK_PIN
#define MIC_CLK_PIN 39
#endif
#ifndef MIC_SELECT_PIN
#define MIC_SELECT_PIN 48
#endif
#ifndef MIC_SELECT_LEFT
#define MIC_SELECT_LEFT 1
#endif
#ifndef FW_BUILD_EPOCH
#define FW_BUILD_EPOCH 0
#endif
namespace {
constexpr i2s_port_t kI2sPort = I2S_NUM_0;
constexpr uint32_t kChunkBytes = 2048;
constexpr uint8_t kWavChannels = 1;
constexpr uint32_t kValidEpochFloor = 1700000000UL;
} // namespace
MicrophoneRecorder::MicrophoneRecorder(Print& out) : out_(out) {}
MicrophoneRecorder::~MicrophoneRecorder() {
close();
deactivateMicrophone();
}
bool MicrophoneRecorder::begin() {
micCfg_ = {
.dataPin = MIC_DATA_PIN,
.clkPin = MIC_CLK_PIN,
.selectPin = MIC_SELECT_PIN,
.selectLeft = (MIC_SELECT_LEFT != 0),
};
forceSpiDeselected();
pmuReady_ = initPmu();
if (!pmuReady_) {
return false;
}
cycleSdRail();
sdReady_ = mountSdCard();
if (!sdReady_) {
logf("SD mount failed");
return false;
}
if (!ensureDirectory("/recordings")) {
logf("failed to create /recordings");
return false;
}
logf("ready: sample_rate=%u bits=%u data=%d clk=%d sel=%d",
(unsigned)MIC_SAMPLE_RATE,
(unsigned)MIC_BITS_PER_SAMPLE,
micCfg_.dataPin,
micCfg_.clkPin,
micCfg_.selectPin);
return true;
}
void MicrophoneRecorder::setRuntimeConfig(const MicRuntimeConfig& cfg) {
deactivateMicrophone();
micCfg_ = cfg;
logf("config updated: data=%d clk=%d select=%d left=%d",
micCfg_.dataPin,
micCfg_.clkPin,
micCfg_.selectPin,
micCfg_.selectLeft ? 1 : 0);
}
bool MicrophoneRecorder::activateMicrophone() {
if (micActive_) {
return true;
}
pinMode(micCfg_.selectPin, OUTPUT);
digitalWrite(micCfg_.selectPin, micCfg_.selectLeft ? LOW : HIGH);
const i2s_config_t config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
.sample_rate = MIC_SAMPLE_RATE,
.bits_per_sample = (i2s_bits_per_sample_t)MIC_BITS_PER_SAMPLE,
.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,
#if ESP_IDF_VERSION_MAJOR >= 4
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
#endif
};
const i2s_pin_config_t pins = {
.bck_io_num = I2S_PIN_NO_CHANGE,
.ws_io_num = micCfg_.clkPin,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = micCfg_.dataPin,
};
i2s_driver_uninstall(kI2sPort);
esp_err_t err = i2s_driver_install(kI2sPort, &config, 0, nullptr);
if (err != ESP_OK) {
logf("i2s_driver_install failed err=%d", (int)err);
return false;
}
err = i2s_set_pin(kI2sPort, &pins);
if (err != ESP_OK) {
logf("i2s_set_pin failed err=%d", (int)err);
i2s_driver_uninstall(kI2sPort);
return false;
}
err = i2s_set_pdm_rx_down_sample(kI2sPort, I2S_PDM_DSR_8S);
if (err != ESP_OK) {
logf("i2s_set_pdm_rx_down_sample failed err=%d", (int)err);
i2s_driver_uninstall(kI2sPort);
return false;
}
i2s_zero_dma_buffer(kI2sPort);
micActive_ = true;
logf("microphone active clk(ws)=%d data=%d select=%d left=%d",
micCfg_.clkPin,
micCfg_.dataPin,
micCfg_.selectPin,
micCfg_.selectLeft ? 1 : 0);
return true;
}
void MicrophoneRecorder::deactivateMicrophone() {
if (!micActive_) {
return;
}
i2s_driver_uninstall(kI2sPort);
micActive_ = false;
logf("microphone inactive");
}
bool MicrophoneRecorder::open(const char* path) {
if (!sdReady_) {
logf("open refused: SD not ready");
return false;
}
close();
strncpy(currentPath_, path, sizeof(currentPath_) - 1);
currentPath_[sizeof(currentPath_) - 1] = '\0';
file_ = SD.open(currentPath_, FILE_WRITE);
if (!file_) {
logf("failed to open %s", currentPath_);
currentPath_[0] = '\0';
return false;
}
wavDataBytes_ = 0;
fileOpen_ = writeWavHeader();
if (!fileOpen_) {
file_.close();
currentPath_[0] = '\0';
return false;
}
logf("opened %s", currentPath_);
return true;
}
bool MicrophoneRecorder::openTimestamped() {
const uint32_t stamp = selectTimestamp();
char path[96];
snprintf(path, sizeof(path), "/recordings/mic_%lu.wav", (unsigned long)stamp);
return open(path);
}
bool MicrophoneRecorder::captureForMs(uint32_t durationMs) {
if (!fileOpen_) {
logf("capture refused: no open file");
return false;
}
if (!activateMicrophone()) {
return false;
}
int16_t samples[kChunkBytes / sizeof(int16_t)];
const uint32_t startMs = millis();
uint32_t lastProgressMs = startMs;
int16_t secondMin = INT16_MAX;
int16_t secondMax = INT16_MIN;
uint32_t secondSampleCount = 0;
uint64_t secondAbsSum = 0;
while ((uint32_t)(millis() - startMs) < durationMs) {
size_t bytesRead = 0;
const esp_err_t err =
i2s_read(kI2sPort, samples, sizeof(samples), &bytesRead, pdMS_TO_TICKS(250));
if (err != ESP_OK) {
logf("i2s_read failed err=%d", (int)err);
return false;
}
if (bytesRead > 0) {
const size_t sampleCount = bytesRead / sizeof(samples[0]);
for (size_t i = 0; i < sampleCount; ++i) {
const int16_t value = samples[i];
if (value < secondMin) secondMin = value;
if (value > secondMax) secondMax = value;
secondAbsSum += (uint64_t)(value < 0 ? -(int32_t)value : value);
}
secondSampleCount += sampleCount;
const size_t wrote = file_.write((const uint8_t*)samples, bytesRead);
if (wrote != bytesRead) {
logf("file write failed wanted=%u wrote=%u", (unsigned)bytesRead, (unsigned)wrote);
return false;
}
wavDataBytes_ += wrote;
}
const uint32_t now = millis();
if ((uint32_t)(now - lastProgressMs) >= 1000) {
lastProgressMs = now;
const unsigned long avgAbs =
secondSampleCount > 0 ? (unsigned long)(secondAbsSum / secondSampleCount) : 0UL;
const char* status =
(secondSampleCount == 0) ? "nosamples"
: (secondMin == secondMax) ? "flat"
: (avgAbs < 8UL) ? "nearzero"
: "active";
logf("recording %s elapsed=%lus bytes=%lu min=%d max=%d avgabs=%lu samples=%lu status=%s",
currentPath_,
(unsigned long)((now - startMs) / 1000),
(unsigned long)wavDataBytes_,
secondSampleCount > 0 ? secondMin : 0,
secondSampleCount > 0 ? secondMax : 0,
avgAbs,
(unsigned long)secondSampleCount,
status);
secondMin = INT16_MAX;
secondMax = INT16_MIN;
secondSampleCount = 0;
secondAbsSum = 0;
}
}
file_.flush();
return true;
}
void MicrophoneRecorder::close() {
if (!fileOpen_) {
return;
}
finalizeWavHeader();
file_.close();
fileOpen_ = false;
logf("closed %s bytes=%lu", currentPath_, (unsigned long)wavDataBytes_);
currentPath_[0] = '\0';
}
void MicrophoneRecorder::logf(const char* fmt, ...) {
char msg[200];
va_list args;
va_start(args, fmt);
vsnprintf(msg, sizeof(msg), fmt, args);
va_end(args);
out_.printf("[%10lu][%06lu] %s\r\n",
(unsigned long)millis(),
(unsigned long)logSeq_++,
msg);
}
bool MicrophoneRecorder::initPmu() {
if (!tbeam_supreme::initPmuForPeripherals(pmu_, &out_)) {
logf("PMU init failed");
return false;
}
return true;
}
void MicrophoneRecorder::forceSpiDeselected() {
pinMode(tbeam_supreme::sdCs(), OUTPUT);
digitalWrite(tbeam_supreme::sdCs(), HIGH);
pinMode(tbeam_supreme::imuCs(), OUTPUT);
digitalWrite(tbeam_supreme::imuCs(), HIGH);
}
void MicrophoneRecorder::cycleSdRail(uint32_t offMs, uint32_t onSettleMs) {
if (!pmu_) {
return;
}
forceSpiDeselected();
SD.end();
sdSpiH_.end();
sdSpiF_.end();
pmu_->disablePowerOutput(XPOWERS_BLDO1);
delay(offMs);
pmu_->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
pmu_->enablePowerOutput(XPOWERS_BLDO1);
delay(onSettleMs);
}
bool MicrophoneRecorder::mountSdCard() {
const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000};
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
if (tryMountWithBus(sdSpiH_, "HSPI", freqs[i])) {
return true;
}
}
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
if (tryMountWithBus(sdSpiF_, "FSPI", freqs[i])) {
return true;
}
}
return false;
}
bool MicrophoneRecorder::tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz) {
SD.end();
bus.end();
delay(5);
forceSpiDeselected();
bus.begin(tbeam_supreme::sdSck(),
tbeam_supreme::sdMiso(),
tbeam_supreme::sdMosi(),
tbeam_supreme::sdCs());
digitalWrite(tbeam_supreme::sdCs(), HIGH);
delay(1);
for (uint8_t i = 0; i < 10; ++i) {
bus.transfer(0xFF);
}
if (!SD.begin(tbeam_supreme::sdCs(), bus, hz)) {
return false;
}
if (SD.cardType() == CARD_NONE) {
SD.end();
return false;
}
activeSdSpi_ = &bus;
logf("SD mounted via %s @ %lu Hz", busName, (unsigned long)hz);
return true;
}
bool MicrophoneRecorder::ensureDirectory(const char* path) {
if (SD.exists(path)) {
return true;
}
return SD.mkdir(path);
}
bool MicrophoneRecorder::writeWavHeader() {
const WavHeader header = {
.riff = {'R', 'I', 'F', 'F'},
.chunkSize = 36,
.wave = {'W', 'A', 'V', 'E'},
.fmt = {'f', 'm', 't', ' '},
.subchunk1Size = 16,
.audioFormat = 1,
.numChannels = kWavChannels,
.sampleRate = MIC_SAMPLE_RATE,
.byteRate = MIC_SAMPLE_RATE * kWavChannels * (MIC_BITS_PER_SAMPLE / 8),
.blockAlign = (uint16_t)(kWavChannels * (MIC_BITS_PER_SAMPLE / 8)),
.bitsPerSample = MIC_BITS_PER_SAMPLE,
.data = {'d', 'a', 't', 'a'},
.dataSize = 0,
};
return file_.write((const uint8_t*)&header, sizeof(header)) == sizeof(header);
}
bool MicrophoneRecorder::finalizeWavHeader() {
if (!file_) {
return false;
}
WavHeader header = {
.riff = {'R', 'I', 'F', 'F'},
.chunkSize = 36 + wavDataBytes_,
.wave = {'W', 'A', 'V', 'E'},
.fmt = {'f', 'm', 't', ' '},
.subchunk1Size = 16,
.audioFormat = 1,
.numChannels = kWavChannels,
.sampleRate = MIC_SAMPLE_RATE,
.byteRate = MIC_SAMPLE_RATE * kWavChannels * (MIC_BITS_PER_SAMPLE / 8),
.blockAlign = (uint16_t)(kWavChannels * (MIC_BITS_PER_SAMPLE / 8)),
.bitsPerSample = MIC_BITS_PER_SAMPLE,
.data = {'d', 'a', 't', 'a'},
.dataSize = wavDataBytes_,
};
file_.seek(0);
const bool ok = file_.write((const uint8_t*)&header, sizeof(header)) == sizeof(header);
file_.seek(file_.size());
return ok;
}
uint32_t MicrophoneRecorder::selectTimestamp() const {
const time_t now = time(nullptr);
if (now >= (time_t)kValidEpochFloor) {
return (uint32_t)now;
}
if (FW_BUILD_EPOCH >= kValidEpochFloor) {
return (uint32_t)FW_BUILD_EPOCH + (millis() / 1000UL);
}
return millis() / 1000UL;
}

View file

@ -0,0 +1,78 @@
#pragma once
#include <Arduino.h>
#include <SD.h>
#include <SPI.h>
#include <XPowersLib.h>
class MicrophoneRecorder {
public:
struct MicRuntimeConfig {
int dataPin;
int clkPin;
int selectPin;
bool selectLeft;
};
explicit MicrophoneRecorder(Print& out = Serial);
~MicrophoneRecorder();
bool begin();
void setRuntimeConfig(const MicRuntimeConfig& cfg);
const MicRuntimeConfig& runtimeConfig() const { return micCfg_; }
bool activateMicrophone();
void deactivateMicrophone();
bool open(const char* path);
bool openTimestamped();
bool captureForMs(uint32_t durationMs);
void close();
bool isSdReady() const { return sdReady_; }
bool isMicActive() const { return micActive_; }
bool isOpen() const { return fileOpen_; }
const char* currentPath() const { return currentPath_; }
private:
struct WavHeader {
char riff[4];
uint32_t chunkSize;
char wave[4];
char fmt[4];
uint32_t subchunk1Size;
uint16_t audioFormat;
uint16_t numChannels;
uint32_t sampleRate;
uint32_t byteRate;
uint16_t blockAlign;
uint16_t bitsPerSample;
char data[4];
uint32_t dataSize;
};
void logf(const char* fmt, ...);
bool initPmu();
void forceSpiDeselected();
void cycleSdRail(uint32_t offMs = 250, uint32_t onSettleMs = 700);
bool mountSdCard();
bool tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz);
bool ensureDirectory(const char* path);
bool writeWavHeader();
bool finalizeWavHeader();
uint32_t selectTimestamp() const;
Print& out_;
SPIClass sdSpiH_{HSPI};
SPIClass sdSpiF_{FSPI};
SPIClass* activeSdSpi_ = nullptr;
XPowersLibInterface* pmu_ = nullptr;
File file_;
char currentPath_[96] = {0};
uint32_t logSeq_ = 0;
uint32_t wavDataBytes_ = 0;
bool pmuReady_ = false;
bool sdReady_ = false;
bool micActive_ = false;
bool fileOpen_ = false;
MicRuntimeConfig micCfg_{};
};

View file

@ -0,0 +1,7 @@
{
"name": "mic_recorder",
"version": "0.1.0",
"build": {
"includeDir": "."
}
}

View file

@ -0,0 +1,81 @@
; 20260413 ChatGPT
; Exercise 20_microphone
[platformio]
default_envs = guy
[env]
platform = espressif32
framework = arduino
board = esp32-s3-devkitc-1
board_build.partitions = default_8MB.csv
monitor_speed = 115200
extra_scripts = pre:scripts/set_build_epoch.py
lib_deps =
Wire
olikraus/U8g2@^2.36.4
lewisxhe/XPowersLib@0.3.3
build_flags =
-I ../../shared/boards
-I ../../external/microReticulum_Firmware
-D BOARD_MODEL=BOARD_TBEAM_S_V1
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
-D MIC_SAMPLE_RATE=16000
-D MIC_BITS_PER_SAMPLE=16
-D MIC_RECORD_SECONDS=30
-D MIC_IDLE_SECONDS=30
-D MIC_DATA_PIN=39
-D MIC_CLK_PIN=38
-D MIC_SELECT_PIN=48
-D MIC_SELECT_LEFT=1
[env:amy]
extends = env
build_flags =
${env.build_flags}
-D BOARD_ID=\"AMY\"
-D NODE_LABEL=\"Amy\"
[env:bob]
extends = env
build_flags =
${env.build_flags}
-D BOARD_ID=\"BOB\"
-D NODE_LABEL=\"Bob\"
[env:cy]
extends = env
build_flags =
${env.build_flags}
-D BOARD_ID=\"CY\"
-D NODE_LABEL=\"Cy\"
[env:dan]
extends = env
build_flags =
${env.build_flags}
-D BOARD_ID=\"DAN\"
-D NODE_LABEL=\"Dan\"
[env:ed]
extends = env
build_flags =
${env.build_flags}
-D BOARD_ID=\"ED\"
-D NODE_LABEL=\"Ed\"
[env:flo]
extends = env
build_flags =
${env.build_flags}
-D BOARD_ID=\"FLO\"
-D NODE_LABEL=\"Flo\"
[env:guy]
extends = env
build_flags =
${env.build_flags}
-D BOARD_ID=\"GUY\"
-D NODE_LABEL=\"Guy\"

View file

@ -0,0 +1,13 @@
import time
Import("env")
epoch = int(time.time())
utc_tag = time.strftime("%Y%m%d_%H%M%S_z", time.gmtime(epoch))
env.Append(
CPPDEFINES=[
("FW_BUILD_EPOCH", str(epoch)),
("FW_BUILD_UTC", '\"%s\"' % utc_tag),
]
)

View file

@ -0,0 +1,137 @@
// 20260413 ChatGPT
// Exercise 20: microphone recorder
#include <Arduino.h>
#include "MicrophoneRecorder.h"
#ifndef NODE_LABEL
#define NODE_LABEL "MIC"
#endif
#ifndef MIC_RECORD_SECONDS
#define MIC_RECORD_SECONDS 30
#endif
#ifndef MIC_IDLE_SECONDS
#define MIC_IDLE_SECONDS 30
#endif
#ifndef MIC_STARTUP_WAIT_SECONDS
#define MIC_STARTUP_WAIT_SECONDS 10
#endif
namespace {
constexpr uint32_t kSerialDelayMs = 1500;
constexpr uint32_t kBetweenRecordingsMs = MIC_IDLE_SECONDS * 1000UL;
MicrophoneRecorder g_recorder(Serial);
bool g_started = false;
bool g_finished = false;
size_t g_caseIndex = 0;
uint32_t g_nextActionMs = 0;
struct SweepCase {
const char* label;
MicrophoneRecorder::MicRuntimeConfig cfg;
};
SweepCase g_cases[] = {
{"base", {.dataPin = MIC_DATA_PIN, .clkPin = MIC_CLK_PIN, .selectPin = MIC_SELECT_PIN, .selectLeft = (MIC_SELECT_LEFT != 0)}},
{"flip_lr", {.dataPin = MIC_DATA_PIN, .clkPin = MIC_CLK_PIN, .selectPin = MIC_SELECT_PIN, .selectLeft = (MIC_SELECT_LEFT == 0)}},
{"swap_pins", {.dataPin = MIC_CLK_PIN, .clkPin = MIC_DATA_PIN, .selectPin = MIC_SELECT_PIN, .selectLeft = (MIC_SELECT_LEFT != 0)}},
{"swap_flip", {.dataPin = MIC_CLK_PIN, .clkPin = MIC_DATA_PIN, .selectPin = MIC_SELECT_PIN, .selectLeft = (MIC_SELECT_LEFT == 0)}},
};
void printWhatToSay(const SweepCase& sweepCase) {
Serial.println();
Serial.println("Speak this sentence clearly 3 times during the test:");
Serial.println("\"Testing T-Beam microphone case "
+ String(sweepCase.label)
+ ". One two three four five.\"");
Serial.println("Look for console lines where:");
Serial.println(" status=active");
Serial.println(" min and max change over time and are not equal");
Serial.println(" avgabs is not stuck at one repeated value");
Serial.println("If status=flat with min=max every second, that case is bad.");
}
} // namespace
void setup() {
Serial.begin(115200);
delay(kSerialDelayMs);
Serial.println();
Serial.println("============================================================");
Serial.printf("Exercise 20 Microphone Recorder [%s]\r\n", NODE_LABEL);
Serial.println("============================================================");
if (!g_recorder.begin()) {
Serial.println("Recorder init failed; loop will retry.");
}
Serial.printf("Waiting %u seconds before the first recording.\r\n",
(unsigned)MIC_STARTUP_WAIT_SECONDS);
g_nextActionMs = millis() + MIC_STARTUP_WAIT_SECONDS * 1000UL;
}
void loop() {
if (!g_recorder.isSdReady() || g_finished) {
delay(1000);
return;
}
const uint32_t now = millis();
if ((int32_t)(now - g_nextActionMs) < 0) {
delay(100);
return;
}
if (g_caseIndex >= (sizeof(g_cases) / sizeof(g_cases[0]))) {
Serial.println("Microphone sweep complete. No more recordings will be started.");
g_finished = true;
return;
}
SweepCase& sweepCase = g_cases[g_caseIndex];
g_recorder.setRuntimeConfig(sweepCase.cfg);
char path[96];
snprintf(path,
sizeof(path),
"/recordings/mic_%s_%lu.wav",
sweepCase.label,
(unsigned long)time(nullptr));
Serial.println();
Serial.printf("Starting case %u/%u: %s data=%d clk=%d select=%d left=%d\r\n",
(unsigned)(g_caseIndex + 1),
(unsigned)(sizeof(g_cases) / sizeof(g_cases[0])),
sweepCase.label,
sweepCase.cfg.dataPin,
sweepCase.cfg.clkPin,
sweepCase.cfg.selectPin,
sweepCase.cfg.selectLeft ? 1 : 0);
printWhatToSay(sweepCase);
if (!g_recorder.open(path)) {
Serial.println("Failed to open output file; delaying and retrying.");
g_nextActionMs = millis() + 1000;
delay(100);
return;
}
const bool ok = g_recorder.captureForMs(MIC_RECORD_SECONDS * 1000UL);
g_recorder.close();
Serial.printf("case %s status=%s next_case_in=%lus\r\n",
sweepCase.label,
ok ? "ok" : "failed",
(unsigned long)(kBetweenRecordingsMs / 1000UL));
g_caseIndex++;
g_nextActionMs = millis() + kBetweenRecordingsMs;
delay(100);
}

View file

@ -0,0 +1,172 @@
(rnsenv) jlpoole@jp ~/workstation $ date; pio device monitor -b 115200 --port /dev/ttytCY
Mon Apr 13 20:32:15 PDT 2026
/home/jlpoole/rnsenv/lib/python3.13/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.6.1) or chardet (6.0.0.post1)/charset_normalizer (3.4.4) doesn't match a supported version!
warnings.warn(
--- Terminal on /dev/ttytCY | 115200 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
[ 13073][000002] config updated: data=39 clk=38 select=48 left=1
Starting case 1/4: base data=39 clk=38 select=48 left=1
Speak this sentence clearly 3 times during the test:
"Testing T-Beam microphone case base. One two three four five."
Look for console lines where:
status=active
min and max change over time and are not equal
avgabs is not stuck at one repeated value
If status=flat with min=max every second, that case is bad.
[ 13152][000003] opened /recordings/mic_base_19.wav
E (13037) I2S: i2s_driver_uninstall(2048): I2S port 0 has not installed
[ 13153][000004] microphone active clk(ws)=38 data=39 select=48 left=1
[ 14158][000005] recording /recordings/mic_base_19.wav elapsed=1s bytes=30720 min=-31424 max=41 avgabs=30930 samples=15360 status=active
[ 15182][000006] recording /recordings/mic_base_19.wav elapsed=2s bytes=63488 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 16206][000007] recording /recordings/mic_base_19.wav elapsed=3s bytes=96256 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 17230][000008] recording /recordings/mic_base_19.wav elapsed=4s bytes=129024 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 18254][000009] recording /recordings/mic_base_19.wav elapsed=5s bytes=161792 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 19278][000010] recording /recordings/mic_base_19.wav elapsed=6s bytes=194560 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 20302][000011] recording /recordings/mic_base_19.wav elapsed=7s bytes=227328 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 21326][000012] recording /recordings/mic_base_19.wav elapsed=8s bytes=260096 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 22350][000013] recording /recordings/mic_base_19.wav elapsed=9s bytes=292864 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 23374][000014] recording /recordings/mic_base_19.wav elapsed=10s bytes=325632 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 24398][000015] recording /recordings/mic_base_19.wav elapsed=11s bytes=358400 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 25422][000016] recording /recordings/mic_base_19.wav elapsed=12s bytes=391168 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 26446][000017] recording /recordings/mic_base_19.wav elapsed=13s bytes=423936 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 27470][000018] recording /recordings/mic_base_19.wav elapsed=14s bytes=456704 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 28494][000019] recording /recordings/mic_base_19.wav elapsed=15s bytes=489472 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 29518][000020] recording /recordings/mic_base_19.wav elapsed=16s bytes=522240 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 30542][000021] recording /recordings/mic_base_19.wav elapsed=17s bytes=555008 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 31566][000022] recording /recordings/mic_base_19.wav elapsed=18s bytes=587776 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 32590][000023] recording /recordings/mic_base_19.wav elapsed=19s bytes=620544 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 33614][000024] recording /recordings/mic_base_19.wav elapsed=20s bytes=653312 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 34638][000025] recording /recordings/mic_base_19.wav elapsed=21s bytes=686080 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 35662][000026] recording /recordings/mic_base_19.wav elapsed=22s bytes=718848 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 36686][000027] recording /recordings/mic_base_19.wav elapsed=23s bytes=751616 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 37710][000028] recording /recordings/mic_base_19.wav elapsed=24s bytes=784384 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 38734][000029] recording /recordings/mic_base_19.wav elapsed=25s bytes=817152 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 39758][000030] recording /recordings/mic_base_19.wav elapsed=26s bytes=849920 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 40782][000031] recording /recordings/mic_base_19.wav elapsed=27s bytes=882688 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 41806][000032] recording /recordings/mic_base_19.wav elapsed=28s bytes=915456 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 42830][000033] recording /recordings/mic_base_19.wav elapsed=29s bytes=948224 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 43375][000034] closed /recordings/mic_base_19.wav bytes=960512
case base status=ok next_case_in=30s
[ 73375][000035] microphone inactive
[ 73376][000036] config updated: data=39 clk=38 select=48 left=0
Starting case 2/4: flip_lr data=39 clk=38 select=48 left=0
Speak this sentence clearly 3 times during the test:
"Testing T-Beam microphone case flip_lr. One two three four five."
Look for console lines where:
status=active
min and max change over time and are not equal
avgabs is not stuck at one repeated value
If status=flat with min=max every second, that case is bad.
[ 73465][000037] opened /recordings/mic_flip_lr_79.wav
E (73350) I2S: i2s_driver_uninstall(2048): I2S port 0 has not installed
[ 73466][000038] microphone active clk(ws)=38 data=39 select=48 left=0
[ 74471][000039] recording /recordings/mic_flip_lr_79.wav elapsed=1s bytes=30720 min=-32768 max=25661 avgabs=30858 samples=15360 status=active
[ 75495][000040] recording /recordings/mic_flip_lr_79.wav elapsed=2s bytes=63488 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 76519][000041] recording /recordings/mic_flip_lr_79.wav elapsed=3s bytes=96256 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 77543][000042] recording /recordings/mic_flip_lr_79.wav elapsed=4s bytes=129024 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 78567][000043] recording /recordings/mic_flip_lr_79.wav elapsed=5s bytes=161792 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 79591][000044] recording /recordings/mic_flip_lr_79.wav elapsed=6s bytes=194560 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 80615][000045] recording /recordings/mic_flip_lr_79.wav elapsed=7s bytes=227328 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 81639][000046] recording /recordings/mic_flip_lr_79.wav elapsed=8s bytes=260096 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 82663][000047] recording /recordings/mic_flip_lr_79.wav elapsed=9s bytes=292864 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 83687][000048] recording /recordings/mic_flip_lr_79.wav elapsed=10s bytes=325632 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 84711][000049] recording /recordings/mic_flip_lr_79.wav elapsed=11s bytes=358400 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 85735][000050] recording /recordings/mic_flip_lr_79.wav elapsed=12s bytes=391168 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 86759][000051] recording /recordings/mic_flip_lr_79.wav elapsed=13s bytes=423936 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 87783][000052] recording /recordings/mic_flip_lr_79.wav elapsed=14s bytes=456704 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 88807][000053] recording /recordings/mic_flip_lr_79.wav elapsed=15s bytes=489472 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 89831][000054] recording /recordings/mic_flip_lr_79.wav elapsed=16s bytes=522240 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 90855][000055] recording /recordings/mic_flip_lr_79.wav elapsed=17s bytes=555008 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 91879][000056] recording /recordings/mic_flip_lr_79.wav elapsed=18s bytes=587776 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 92903][000057] recording /recordings/mic_flip_lr_79.wav elapsed=19s bytes=620544 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 93927][000058] recording /recordings/mic_flip_lr_79.wav elapsed=20s bytes=653312 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 94951][000059] recording /recordings/mic_flip_lr_79.wav elapsed=21s bytes=686080 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 95975][000060] recording /recordings/mic_flip_lr_79.wav elapsed=22s bytes=718848 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 96999][000061] recording /recordings/mic_flip_lr_79.wav elapsed=23s bytes=751616 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 98023][000062] recording /recordings/mic_flip_lr_79.wav elapsed=24s bytes=784384 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 99047][000063] recording /recordings/mic_flip_lr_79.wav elapsed=25s bytes=817152 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 100071][000064] recording /recordings/mic_flip_lr_79.wav elapsed=26s bytes=849920 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 101095][000065] recording /recordings/mic_flip_lr_79.wav elapsed=27s bytes=882688 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 102119][000066] recording /recordings/mic_flip_lr_79.wav elapsed=28s bytes=915456 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 103143][000067] recording /recordings/mic_flip_lr_79.wav elapsed=29s bytes=948224 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 103689][000068] closed /recordings/mic_flip_lr_79.wav bytes=960512
case flip_lr status=ok next_case_in=30s
[ 133689][000069] microphone inactive
[ 133690][000070] config updated: data=38 clk=39 select=48 left=1
Starting case 3/4: swap_pins data=38 clk=39 select=48 left=1
Speak this sentence clearly 3 times during the test:
"Testing T-Beam microphone case swap_pins. One two three four five."
Look for console lines where:
status=active
min and max change over time and are not equal
avgabs is not stuck at one repeated value
If status=flat with min=max every second, that case is bad.
[ 133779][000071] opened /recordings/mic_swap_pins_139.wav
E (133664) I2S: i2s_driver_uninstall(2048): I2S port 0 has not installed
[ 133780][000072] microphone active clk(ws)=39 data=38 select=48 left=1
[ 134785][000073] recording /recordings/mic_swap_pins_139.wav elapsed=1s bytes=30720 min=-32768 max=0 avgabs=30931 samples=15360 status=active
[ 135809][000074] recording /recordings/mic_swap_pins_139.wav elapsed=2s bytes=63488 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 136833][000075] recording /recordings/mic_swap_pins_139.wav elapsed=3s bytes=96256 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 137857][000076] recording /recordings/mic_swap_pins_139.wav elapsed=4s bytes=129024 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 137922][000077] file write failed wanted=2048 wrote=468
[ 137993][000078] closed /recordings/mic_swap_pins_139.wav bytes=129024
case swap_pins status=failed next_case_in=30s
[ 167993][000079] microphone inactive
[ 167994][000080] config updated: data=38 clk=39 select=48 left=0
Starting case 4/4: swap_flip data=38 clk=39 select=48 left=0
Speak this sentence clearly 3 times during the test:
"Testing T-Beam microphone case swap_flip. One two three four five."
Look for console lines where:
status=active
min and max change over time and are not equal
avgabs is not stuck at one repeated value
If status=flat with min=max every second, that case is bad.
[ 168083][000081] opened /recordings/mic_swap_flip_174.wav
E (167968) I2S: i2s_driver_uninstall(2048): I2S port 0 has not installed
[ 168084][000082] microphone active clk(ws)=39 data=38 select=48 left=0
[ 169089][000083] recording /recordings/mic_swap_flip_174.wav elapsed=1s bytes=30720 min=-32768 max=0 avgabs=30931 samples=15360 status=active
[ 170113][000084] recording /recordings/mic_swap_flip_174.wav elapsed=2s bytes=63488 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 171137][000085] recording /recordings/mic_swap_flip_174.wav elapsed=3s bytes=96256 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 172161][000086] recording /recordings/mic_swap_flip_174.wav elapsed=4s bytes=129024 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 173185][000087] recording /recordings/mic_swap_flip_174.wav elapsed=5s bytes=161792 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 174209][000088] recording /recordings/mic_swap_flip_174.wav elapsed=6s bytes=194560 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 175233][000089] recording /recordings/mic_swap_flip_174.wav elapsed=7s bytes=227328 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 176257][000090] recording /recordings/mic_swap_flip_174.wav elapsed=8s bytes=260096 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 177281][000091] recording /recordings/mic_swap_flip_174.wav elapsed=9s bytes=292864 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 178305][000092] recording /recordings/mic_swap_flip_174.wav elapsed=10s bytes=325632 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 179329][000093] recording /recordings/mic_swap_flip_174.wav elapsed=11s bytes=358400 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 180353][000094] recording /recordings/mic_swap_flip_174.wav elapsed=12s bytes=391168 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 181377][000095] recording /recordings/mic_swap_flip_174.wav elapsed=13s bytes=423936 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 182401][000096] recording /recordings/mic_swap_flip_174.wav elapsed=14s bytes=456704 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 183425][000097] recording /recordings/mic_swap_flip_174.wav elapsed=15s bytes=489472 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 184449][000098] recording /recordings/mic_swap_flip_174.wav elapsed=16s bytes=522240 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 185473][000099] recording /recordings/mic_swap_flip_174.wav elapsed=17s bytes=555008 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 186497][000100] recording /recordings/mic_swap_flip_174.wav elapsed=18s bytes=587776 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 187521][000101] recording /recordings/mic_swap_flip_174.wav elapsed=19s bytes=620544 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 188545][000102] recording /recordings/mic_swap_flip_174.wav elapsed=20s bytes=653312 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 189569][000103] recording /recordings/mic_swap_flip_174.wav elapsed=21s bytes=686080 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 190593][000104] recording /recordings/mic_swap_flip_174.wav elapsed=22s bytes=718848 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 191617][000105] recording /recordings/mic_swap_flip_174.wav elapsed=23s bytes=751616 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 192641][000106] recording /recordings/mic_swap_flip_174.wav elapsed=24s bytes=784384 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 193665][000107] recording /recordings/mic_swap_flip_174.wav elapsed=25s bytes=817152 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 194689][000108] recording /recordings/mic_swap_flip_174.wav elapsed=26s bytes=849920 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 195713][000109] recording /recordings/mic_swap_flip_174.wav elapsed=27s bytes=882688 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 196737][000110] recording /recordings/mic_swap_flip_174.wav elapsed=28s bytes=915456 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 197761][000111] recording /recordings/mic_swap_flip_174.wav elapsed=29s bytes=948224 min=-30935 max=-30935 avgabs=30935 samples=16384 status=flat
[ 198306][000112] closed /recordings/mic_swap_flip_174.wav bytes=960512
case swap_flip status=ok next_case_in=30s
Disconnected ([Errno 5] Input/output error)
Reconnecting to /dev/ttytCY ..^C
(rnsenv) jlpoole@jp ~/workstation $ date
Mon Apr 13 20:37:09 PDT 2026
(rnsenv) jlpoole@jp ~/workstation $