diff --git a/audio_test_20260616_164422.csv b/audio_test_20260616_164422.csv new file mode 100644 index 0000000..1cc92ea --- /dev/null +++ b/audio_test_20260616_164422.csv @@ -0,0 +1,6 @@ +timestamp,freq_hz,vol,duration_s,avg_db,min_db,max_db,samples +2026-06-16T16:44:31,250,0.2,3.0,56.6,56.3,56.8,7 +2026-06-16T16:44:34,500,0.2,3.0,56.0,56.0,56.1,6 +2026-06-16T16:44:38,1000,0.2,3.0,57.3,56.2,58.1,6 +2026-06-16T16:44:41,2000,0.2,3.0,61.8,58.6,64.4,5 +2026-06-16T16:44:45,4000,0.2,3.0,63.1,61.1,65.2,6 diff --git a/audio_test_20260616_165924.csv b/audio_test_20260616_165924.csv new file mode 100644 index 0000000..955b9fb --- /dev/null +++ b/audio_test_20260616_165924.csv @@ -0,0 +1 @@ +timestamp,freq_hz,vol,duration_s,avg_db,min_db,max_db,samples diff --git a/audio_test_20260616_170849.csv b/audio_test_20260616_170849.csv new file mode 100644 index 0000000..955b9fb --- /dev/null +++ b/audio_test_20260616_170849.csv @@ -0,0 +1 @@ +timestamp,freq_hz,vol,duration_s,avg_db,min_db,max_db,samples diff --git a/audio_test_20260616_171013.csv b/audio_test_20260616_171013.csv new file mode 100644 index 0000000..763817a --- /dev/null +++ b/audio_test_20260616_171013.csv @@ -0,0 +1,23 @@ +timestamp,freq_hz,vol,duration_s,avg_db,min_db,max_db,samples +2026-06-16T17:10:21,100,0.0,5.0,0.0,0.0,0.0,0 +2026-06-16T17:10:27,100,0.1,5.0,0.0,0.0,0.0,0 +2026-06-16T17:10:33,100,0.2,5.0,0.0,0.0,0.0,0 +2026-06-16T17:10:38,100,0.3,5.0,0.0,0.0,0.0,0 +2026-06-16T17:10:44,100,0.4,5.0,0.0,0.0,0.0,0 +2026-06-16T17:10:49,100,0.5,5.0,0.0,0.0,0.0,0 +2026-06-16T17:10:55,100,0.6,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:00,100,0.7,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:06,100,0.8,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:11,100,0.9,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:17,100,1.0,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:22,250,0.0,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:28,250,0.1,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:33,250,0.2,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:39,250,0.3,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:45,250,0.4,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:50,250,0.5,5.0,0.0,0.0,0.0,0 +2026-06-16T17:11:56,250,0.6,5.0,0.0,0.0,0.0,0 +2026-06-16T17:12:01,250,0.7,5.0,0.0,0.0,0.0,0 +2026-06-16T17:12:07,250,0.8,5.0,0.0,0.0,0.0,0 +2026-06-16T17:12:12,250,0.9,5.0,0.0,0.0,0.0,0 +2026-06-16T17:12:18,250,1.0,5.0,0.0,0.0,0.0,0 diff --git a/audio_test_20260616_171319.csv b/audio_test_20260616_171319.csv new file mode 100644 index 0000000..b9f59bd --- /dev/null +++ b/audio_test_20260616_171319.csv @@ -0,0 +1,4 @@ +timestamp,freq_hz,vol,duration_s,avg_db,min_db,max_db,samples +2026-06-16T17:13:29,100,0.0,5.0,0.0,0.0,0.0,0 +2026-06-16T17:13:34,100,0.1,5.0,0.0,0.0,0.0,0 +2026-06-16T17:13:40,100,0.2,5.0,0.0,0.0,0.0,0 diff --git a/audio_test_20260616_171529.csv b/audio_test_20260616_171529.csv new file mode 100644 index 0000000..c730f22 --- /dev/null +++ b/audio_test_20260616_171529.csv @@ -0,0 +1,199 @@ +timestamp,freq_hz,vol,duration_s,avg_db,min_db,max_db,samples +2026-06-16T17:15:39,100,0.0,5.0,56.3,56.2,56.4,8 +2026-06-16T17:15:44,100,0.1,5.0,56.2,56.1,56.2,9 +2026-06-16T17:15:50,100,0.2,5.0,56.1,56.1,56.2,10 +2026-06-16T17:15:56,100,0.3,5.0,56.1,56.0,56.1,9 +2026-06-16T17:16:01,100,0.4,5.0,56.1,56.1,56.1,9 +2026-06-16T17:16:07,100,0.5,5.0,56.1,56.1,56.1,9 +2026-06-16T17:16:12,100,0.6,5.0,56.1,56.1,56.2,9 +2026-06-16T17:16:18,100,0.7,5.0,56.1,56.1,56.1,9 +2026-06-16T17:16:23,100,0.8,5.0,56.1,56.1,56.2,8 +2026-06-16T17:16:29,100,0.9,5.0,56.2,56.1,56.2,9 +2026-06-16T17:16:34,100,1.0,5.0,56.2,56.1,56.2,9 +2026-06-16T17:16:40,250,0.0,5.0,56.2,56.1,56.2,9 +2026-06-16T17:16:45,250,0.1,5.0,56.1,56.1,56.1,9 +2026-06-16T17:16:51,250,0.2,5.0,56.1,56.0,56.1,9 +2026-06-16T17:16:57,250,0.3,5.0,56.1,56.1,56.1,9 +2026-06-16T17:17:02,250,0.4,5.0,56.1,56.1,56.1,9 +2026-06-16T17:17:08,250,0.5,5.0,56.1,56.1,56.2,9 +2026-06-16T17:17:13,250,0.6,5.0,56.1,56.1,56.2,9 +2026-06-16T17:17:19,250,0.7,5.0,56.2,56.1,56.2,9 +2026-06-16T17:17:24,250,0.8,5.0,56.2,56.1,56.2,8 +2026-06-16T17:17:30,250,0.9,5.0,56.2,56.2,56.3,8 +2026-06-16T17:17:35,250,1.0,5.0,56.4,56.3,56.4,9 +2026-06-16T17:17:41,500,0.0,5.0,56.4,56.3,56.5,9 +2026-06-16T17:17:46,500,0.1,5.0,56.3,56.2,56.4,9 +2026-06-16T17:17:52,500,0.2,5.0,56.6,56.4,56.9,9 +2026-06-16T17:17:57,500,0.3,5.0,58.5,57.1,59.3,9 +2026-06-16T17:18:03,500,0.4,5.0,60.5,59.5,61.3,9 +2026-06-16T17:18:09,500,0.5,5.0,62.4,61.4,63.2,9 +2026-06-16T17:18:14,500,0.6,5.0,64.0,62.6,64.9,10 +2026-06-16T17:18:20,500,0.7,5.0,65.5,64.2,66.3,9 +2026-06-16T17:18:25,500,0.8,5.0,66.8,65.5,67.5,9 +2026-06-16T17:18:31,500,0.9,5.0,67.9,66.6,68.6,9 +2026-06-16T17:18:36,500,1.0,5.0,68.9,67.8,69.5,9 +2026-06-16T17:18:42,1000,0.0,5.0,64.1,61.0,68.9,9 +2026-06-16T17:18:47,1000,0.1,5.0,61.8,60.1,63.2,9 +2026-06-16T17:18:53,1000,0.2,5.0,67.2,63.4,69.2,9 +2026-06-16T17:18:58,1000,0.3,5.0,71.8,69.5,72.8,7 +2026-06-16T17:19:04,1000,0.4,5.0,74.6,71.3,75.4,9 +2026-06-16T17:19:09,1000,0.5,5.0,76.6,73.7,77.3,9 +2026-06-16T17:19:15,1000,0.6,5.0,78.3,75.6,78.9,9 +2026-06-16T17:19:20,1000,0.7,5.0,79.6,77.7,80.1,9 +2026-06-16T17:19:26,1000,0.8,5.0,80.9,79.4,81.3,9 +2026-06-16T17:19:32,1000,0.9,5.0,81.9,80.5,82.3,9 +2026-06-16T17:19:37,1000,1.0,5.0,82.8,80.9,83.2,8 +2026-06-16T17:19:43,2000,0.0,5.0,67.7,63.5,73.1,8 +2026-06-16T17:19:48,2000,0.1,5.0,67.7,62.2,69.9,9 +2026-06-16T17:19:54,2000,0.2,5.0,74.6,69.6,76.0,9 +2026-06-16T17:19:59,2000,0.3,5.0,78.6,75.6,79.5,9 +2026-06-16T17:20:05,2000,0.4,5.0,81.7,79.6,82.1,9 +2026-06-16T17:20:10,2000,0.5,5.0,83.7,81.5,84.0,9 +2026-06-16T17:20:16,2000,0.6,5.0,85.2,82.3,85.6,9 +2026-06-16T17:20:21,2000,0.7,5.0,86.3,82.6,86.9,8 +2026-06-16T17:20:27,2000,0.8,5.0,87.9,87.6,88.0,8 +2026-06-16T17:20:32,2000,0.9,5.0,88.7,87.0,89.0,9 +2026-06-16T17:20:38,2000,1.0,5.0,89.8,89.0,89.9,9 +2026-06-16T17:20:44,3000,0.0,5.0,69.4,63.9,77.8,9 +2026-06-16T17:20:49,3000,0.1,5.0,70.2,64.0,72.4,9 +2026-06-16T17:20:55,3000,0.2,5.0,76.7,71.0,78.0,9 +2026-06-16T17:21:00,3000,0.3,5.0,80.6,74.3,81.8,9 +2026-06-16T17:21:06,3000,0.4,5.0,84.0,82.5,84.5,8 +2026-06-16T17:21:11,3000,0.5,5.0,85.3,83.6,85.8,9 +2026-06-16T17:21:17,3000,0.6,5.0,87.2,84.6,87.6,9 +2026-06-16T17:21:22,3000,0.7,5.0,88.4,84.3,89.1,9 +2026-06-16T17:21:28,3000,0.8,5.0,89.5,83.2,90.3,9 +2026-06-16T17:21:33,3000,0.9,5.0,90.1,80.6,91.4,9 +2026-06-16T17:21:39,3000,1.0,5.0,91.0,82.0,92.6,10 +2026-06-16T17:21:45,4000,0.0,5.0,70.8,64.0,83.3,10 +2026-06-16T17:21:50,4000,0.1,5.0,64.9,62.6,66.2,8 +2026-06-16T17:21:56,4000,0.2,5.0,70.5,65.7,72.2,9 +2026-06-16T17:22:01,4000,0.3,5.0,74.7,70.1,75.8,9 +2026-06-16T17:22:07,4000,0.4,5.0,77.5,73.1,78.5,9 +2026-06-16T17:22:12,4000,0.5,5.0,79.5,75.4,80.4,10 +2026-06-16T17:22:18,4000,0.6,5.0,80.8,77.4,81.8,10 +2026-06-16T17:22:23,4000,0.7,5.0,82.2,78.9,83.2,10 +2026-06-16T17:22:29,4000,0.8,5.0,83.4,79.3,84.4,10 +2026-06-16T17:22:34,4000,0.9,5.0,84.2,77.9,85.4,10 +2026-06-16T17:22:40,4000,1.0,5.0,85.3,79.2,86.4,9 +2026-06-16T17:22:45,5000,0.0,5.0,69.8,63.6,80.3,10 +2026-06-16T17:22:51,5000,0.1,5.0,64.4,62.2,65.8,10 +2026-06-16T17:22:57,5000,0.2,5.0,69.7,65.5,71.8,10 +2026-06-16T17:23:02,5000,0.3,5.0,74.1,69.9,75.5,10 +2026-06-16T17:23:08,5000,0.4,5.0,76.6,72.7,77.8,10 +2026-06-16T17:23:13,5000,0.5,5.0,78.8,74.8,79.8,9 +2026-06-16T17:23:19,5000,0.6,5.0,80.7,76.9,81.6,10 +2026-06-16T17:23:24,5000,0.7,5.0,82.0,78.8,82.8,10 +2026-06-16T17:23:30,5000,0.8,5.0,82.9,79.0,83.9,10 +2026-06-16T17:23:35,5000,0.9,5.0,83.7,77.7,84.7,10 +2026-06-16T17:23:41,5000,1.0,5.0,84.8,78.7,85.6,10 +2026-06-16T17:23:46,6000,0.0,5.0,69.6,63.6,79.9,10 +2026-06-16T17:23:52,6000,0.1,5.0,61.9,61.8,62.3,10 +2026-06-16T17:23:57,6000,0.2,5.0,64.6,61.6,66.5,10 +2026-06-16T17:24:03,6000,0.3,5.0,68.9,65.7,70.5,10 +2026-06-16T17:24:09,6000,0.4,5.0,71.9,68.8,73.1,10 +2026-06-16T17:24:14,6000,0.5,5.0,74.0,71.1,75.1,10 +2026-06-16T17:24:20,6000,0.6,5.0,75.7,73.0,76.7,9 +2026-06-16T17:24:25,6000,0.7,5.0,77.0,74.4,78.2,9 +2026-06-16T17:24:31,6000,0.8,5.0,78.4,75.5,79.3,10 +2026-06-16T17:24:36,6000,0.9,5.0,79.3,75.2,80.2,10 +2026-06-16T17:24:42,6000,1.0,5.0,80.2,75.8,81.2,10 +2026-06-16T17:24:47,7000,0.0,5.0,68.9,63.7,77.0,9 +2026-06-16T17:24:53,7000,0.1,5.0,66.7,61.9,69.2,9 +2026-06-16T17:24:58,7000,0.2,5.0,73.7,68.7,75.5,9 +2026-06-16T17:25:04,7000,0.3,5.0,77.9,73.4,79.6,9 +2026-06-16T17:25:09,7000,0.4,5.0,80.6,75.1,82.2,9 +2026-06-16T17:25:15,7000,0.5,5.0,82.3,77.1,83.3,9 +2026-06-16T17:25:21,7000,0.6,5.0,83.8,78.5,84.9,9 +2026-06-16T17:25:26,7000,0.7,5.0,85.2,80.3,86.3,9 +2026-06-16T17:25:32,7000,0.8,5.0,85.9,82.1,87.3,8 +2026-06-16T17:25:37,7000,0.9,5.0,87.0,81.0,88.5,9 +2026-06-16T17:25:43,7000,1.0,5.0,87.8,79.8,89.3,9 +2026-06-16T17:25:48,8000,0.0,5.0,73.2,65.3,89.1,9 +2026-06-16T17:25:54,8000,0.1,5.0,67.0,62.5,69.2,9 +2026-06-16T17:25:59,8000,0.2,5.0,73.7,68.6,75.5,9 +2026-06-16T17:26:05,8000,0.3,5.0,77.7,74.0,79.1,9 +2026-06-16T17:26:10,8000,0.4,5.0,80.3,75.8,81.5,9 +2026-06-16T17:26:16,8000,0.5,5.0,82.1,76.6,83.4,9 +2026-06-16T17:26:21,8000,0.6,5.0,84.0,78.4,85.2,9 +2026-06-16T17:26:27,8000,0.7,5.0,85.3,79.9,86.2,9 +2026-06-16T17:26:33,8000,0.8,5.0,86.5,81.6,87.8,9 +2026-06-16T17:26:38,8000,0.9,5.0,87.3,82.9,88.7,8 +2026-06-16T17:26:44,8000,1.0,5.0,87.9,79.7,89.6,8 +2026-06-16T17:26:49,9000,0.0,5.0,70.7,64.4,80.8,8 +2026-06-16T17:26:55,9000,0.1,5.0,61.7,61.3,62.5,9 +2026-06-16T17:27:00,9000,0.2,5.0,63.5,60.9,65.2,9 +2026-06-16T17:27:06,9000,0.3,5.0,67.4,64.6,69.0,9 +2026-06-16T17:27:11,9000,0.4,5.0,70.7,67.6,71.9,9 +2026-06-16T17:27:17,9000,0.5,5.0,73.0,70.2,73.8,9 +2026-06-16T17:27:22,9000,0.6,5.0,74.7,71.8,75.6,8 +2026-06-16T17:27:28,9000,0.7,5.0,76.1,73.4,76.9,9 +2026-06-16T17:27:34,9000,0.8,5.0,78.2,75.0,78.8,9 +2026-06-16T17:27:39,9000,0.9,5.0,79.2,76.9,79.7,8 +2026-06-16T17:27:45,9000,1.0,5.0,79.5,75.4,80.4,9 +2026-06-16T17:27:50,10000,0.0,5.0,69.6,63.5,80.1,9 +2026-06-16T17:27:56,10000,0.1,5.0,61.9,61.6,62.2,9 +2026-06-16T17:28:01,10000,0.2,5.0,65.6,62.0,67.3,9 +2026-06-16T17:28:07,10000,0.3,5.0,69.9,67.0,71.1,9 +2026-06-16T17:28:12,10000,0.4,5.0,72.8,70.6,73.7,9 +2026-06-16T17:28:18,10000,0.5,5.0,74.9,73.3,75.7,9 +2026-06-16T17:28:23,10000,0.6,5.0,76.5,74.9,77.2,9 +2026-06-16T17:28:29,10000,0.7,5.0,78.1,75.9,78.7,9 +2026-06-16T17:28:34,10000,0.8,5.0,79.4,78.4,79.8,7 +2026-06-16T17:28:40,10000,0.9,5.0,80.0,77.6,80.5,9 +2026-06-16T17:28:46,10000,1.0,5.0,81.1,79.2,81.7,9 +2026-06-16T17:28:51,11000,0.0,5.0,69.5,63.7,80.9,9 +2026-06-16T17:28:57,11000,0.1,5.0,65.5,62.6,67.0,9 +2026-06-16T17:29:02,11000,0.2,5.0,72.2,67.9,73.6,9 +2026-06-16T17:29:08,11000,0.3,5.0,76.1,72.5,77.0,9 +2026-06-16T17:29:13,11000,0.4,5.0,78.8,74.2,79.7,9 +2026-06-16T17:29:19,11000,0.5,5.0,80.6,75.2,81.6,9 +2026-06-16T17:29:24,11000,0.6,5.0,82.3,76.9,83.3,9 +2026-06-16T17:29:30,11000,0.7,5.0,83.9,78.8,84.8,10 +2026-06-16T17:29:35,11000,0.8,5.0,84.8,80.0,85.8,10 +2026-06-16T17:29:41,11000,0.9,5.0,86.0,82.2,86.7,9 +2026-06-16T17:29:47,11000,1.0,5.0,86.5,80.5,87.4,9 +2026-06-16T17:29:52,12000,0.0,5.0,70.2,64.3,79.4,9 +2026-06-16T17:29:58,12000,0.1,5.0,61.1,60.3,62.3,10 +2026-06-16T17:30:03,12000,0.2,5.0,61.9,60.0,63.3,10 +2026-06-16T17:30:09,12000,0.3,5.0,64.9,62.9,66.2,10 +2026-06-16T17:30:14,12000,0.4,5.0,67.8,65.2,69.1,10 +2026-06-16T17:30:20,12000,0.5,5.0,70.4,67.8,71.6,10 +2026-06-16T17:30:25,12000,0.6,5.0,72.2,69.8,73.2,10 +2026-06-16T17:30:31,12000,0.7,5.0,73.7,71.7,74.6,9 +2026-06-16T17:30:36,12000,0.8,5.0,75.0,72.7,76.1,9 +2026-06-16T17:30:42,12000,0.9,5.0,75.9,73.3,76.9,9 +2026-06-16T17:30:48,12000,1.0,5.0,76.8,73.4,77.9,9 +2026-06-16T17:30:53,13000,0.0,5.0,68.5,63.1,77.0,10 +2026-06-16T17:30:59,13000,0.1,5.0,61.1,61.0,61.4,9 +2026-06-16T17:31:04,13000,0.2,5.0,63.6,61.0,65.5,9 +2026-06-16T17:31:10,13000,0.3,5.0,67.9,65.1,69.6,9 +2026-06-16T17:31:15,13000,0.4,5.0,71.0,68.2,72.3,9 +2026-06-16T17:31:21,13000,0.5,5.0,73.2,70.1,74.3,9 +2026-06-16T17:31:26,13000,0.6,5.0,74.8,71.9,75.8,9 +2026-06-16T17:31:32,13000,0.7,5.0,76.2,73.5,77.3,8 +2026-06-16T17:31:37,13000,0.8,5.0,77.6,75.2,78.5,9 +2026-06-16T17:31:43,13000,0.9,5.0,78.5,75.9,79.6,8 +2026-06-16T17:31:48,13000,1.0,5.0,79.6,76.0,80.6,9 +2026-06-16T17:31:54,14000,0.0,5.0,70.1,64.2,79.7,9 +2026-06-16T17:32:00,14000,0.1,5.0,60.4,59.0,62.3,9 +2026-06-16T17:32:05,14000,0.2,5.0,57.8,57.6,58.2,9 +2026-06-16T17:32:11,14000,0.3,5.0,57.6,57.3,57.8,9 +2026-06-16T17:32:16,14000,0.4,5.0,58.2,57.6,58.7,9 +2026-06-16T17:32:22,14000,0.5,5.0,59.2,58.5,59.8,9 +2026-06-16T17:32:27,14000,0.6,5.0,60.3,59.7,60.9,9 +2026-06-16T17:32:33,14000,0.7,5.0,61.4,60.7,61.9,9 +2026-06-16T17:32:38,14000,0.8,5.0,62.3,61.4,62.8,9 +2026-06-16T17:32:44,14000,0.9,5.0,63.2,62.2,64.0,7 +2026-06-16T17:32:49,14000,1.0,5.0,64.4,63.3,65.0,9 +2026-06-16T17:32:55,15000,0.0,5.0,62.1,59.7,65.1,9 +2026-06-16T17:33:00,15000,0.1,5.0,58.3,57.6,59.1,9 +2026-06-16T17:33:06,15000,0.2,5.0,57.6,57.2,57.8,9 +2026-06-16T17:33:11,15000,0.3,5.0,58.4,57.7,59.0,9 +2026-06-16T17:33:17,15000,0.4,5.0,59.9,58.9,60.7,9 +2026-06-16T17:33:23,15000,0.5,5.0,61.5,60.3,62.4,9 +2026-06-16T17:33:28,15000,0.6,5.0,63.1,62.0,63.9,9 +2026-06-16T17:33:34,15000,0.7,5.0,64.5,63.6,65.3,9 +2026-06-16T17:33:39,15000,0.8,5.0,65.6,64.9,66.1,9 +2026-06-16T17:33:45,15000,0.9,5.0,66.6,65.7,67.3,7 +2026-06-16T17:33:50,15000,1.0,5.0,67.2,66.1,67.7,9 diff --git a/soundmeter_log.py b/soundmeter_log.py new file mode 100644 index 0000000..9a865c2 --- /dev/null +++ b/soundmeter_log.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +""" +soundmeter_log.py — BLE sound-meter logger + iMX8 tone-server client. +Target: Windows 11 / Python 3.10+. + + pip install bleak requests + +For each test step: + 1. POST /play to tone_server.py on the iMX8 + 2. Collect dB readings from the RS-95-EM BLE meter for the step duration + 3. Write avg / min / max to CSV + +BLE packet format +----------------- + Header : bytes[0]=0xD5 bytes[1]=0xF0 + dB : bytes[4:6] uint16 big-endian / 10.0 +""" + +import asyncio +import csv +import datetime +import struct +from pathlib import Path + +import requests +from bleak import BleakClient + +# ── Configuration ───────────────────────────────────────────────────────────── +IMX8_IP = "10.32.34.103" +IMX8_URL = f"http://{IMX8_IP}:5000" + +BLE_MAC = "B0:D2:78:5C:20:07" +BLE_CHAR = "0000fff2-0000-1000-8000-00805f9b34fb" + +# ── Test sequence — edit as required ───────────────────────────────────────── +_FREQS = [100, 250, 500, 1000, 2000, 3000, 4000, 5000, 6000, 7000, + 8000, 9000, 10000, 11000, 12000, 13000, 14000, 15000] + +TEST_STEPS = [ + {"freq": freq, "vol": round(vol / 10, 1), "duration": 5.0} + for freq in _FREQS + for vol in range(0, 11) # 0% → 100% in 10% steps +] + +# ── Output ──────────────────────────────────────────────────────────────────── +_ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') +CSV_FILE = Path(f"audio_test_{_ts}.csv") +CSV_HDR = ['timestamp', 'freq_hz', 'vol', 'duration_s', + 'avg_db', 'min_db', 'max_db', 'samples'] + + +# ── BLE packet parser ───────────────────────────────────────────────────────── +def _parse_db(data: bytearray) -> float | None: + if len(data) < 6 or data[0] != 0xD5 or data[1] != 0xF0: + return None + return struct.unpack_from('>H', data, 4)[0] / 10.0 + + +# ── HTTP helpers (blocking — run via asyncio.to_thread) ─────────────────────── +def _http_play(freq: float, vol: float, duration: float) -> dict: + r = requests.post( + f"{IMX8_URL}/play", + json={"freq": freq, "vol": vol, "duration": duration}, + timeout=5, + ) + r.raise_for_status() + return r.json() + + +def _http_stop() -> dict: + r = requests.post(f"{IMX8_URL}/stop", timeout=5) + r.raise_for_status() + return r.json() + + +# ── Main test runner ────────────────────────────────────────────────────────── +async def run_test(): + readings: list[float] = [] + + def on_notify(_sender, data: bytearray): + db = _parse_db(data) + if db is not None: + readings.append(db) + + print(f"Connecting to BLE {BLE_MAC} …") + async with BleakClient(BLE_MAC, timeout=15.0) as client: + print("Connected. Enumerating services…") + for svc in client.services: + print(f" SVC {svc.uuid}") + for ch in svc.characteristics: + props = ','.join(ch.properties) + print(f" CHAR {ch.uuid} [{props}]") + + await client.start_notify(BLE_CHAR, on_notify) + print(f"\nSubscribed to {BLE_CHAR}\n") + + with CSV_FILE.open('w', newline='') as f: + writer = csv.writer(f) + writer.writerow(CSV_HDR) + + for i, step in enumerate(TEST_STEPS, 1): + freq = step['freq'] + vol = step['vol'] + dur = step['duration'] + print(f"[{i}/{len(TEST_STEPS)}] {freq:>5} Hz vol={vol:.2f} dur={dur:.1f}s …", end='', flush=True) + + try: + await asyncio.to_thread(_http_play, freq, vol, dur) + except Exception as e: + print(f" ERROR /play: {e}") + continue + + readings.clear() + await asyncio.sleep(dur) + + snap = list(readings) + if snap: + avg_db = sum(snap) / len(snap) + min_db = min(snap) + max_db = max(snap) + print(f" avg={avg_db:5.1f} dB min={min_db:5.1f} max={max_db:5.1f} n={len(snap)}") + else: + avg_db = min_db = max_db = 0.0 + print(" WARNING: no BLE readings received") + + ts = datetime.datetime.now().isoformat(timespec='seconds') + writer.writerow([ + ts, freq, vol, dur, + f"{avg_db:.1f}", f"{min_db:.1f}", f"{max_db:.1f}", len(snap), + ]) + f.flush() + + await asyncio.sleep(0.5) # brief gap between steps + + # Ensure tone is off + try: + await asyncio.to_thread(_http_stop) + except Exception: + pass + + await client.stop_notify(BLE_CHAR) + + print(f"\nResults saved → {CSV_FILE.resolve()}") + + +if __name__ == '__main__': + asyncio.run(run_test()) diff --git a/tone_server.py b/tone_server.py new file mode 100644 index 0000000..341b119 --- /dev/null +++ b/tone_server.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +""" +tone_server.py — Sine-wave tone HTTP server for iMX8 audio test rig. +Target: iMX8 Debian Bookworm. + + pip3 install flask numpy pyaudio (pyaudio already installed) + +Endpoints +--------- +POST /play { "freq": 1000, "vol": 0.8, "duration": 1.0 } +POST /stop +GET /status +""" + +import math +import subprocess +import threading + +import numpy as np +import pyaudio +from flask import Flask, jsonify, request + +# ── ALSA routing (SGTL5000 on iMX8) ────────────────────────────────────────── +def _alsa_init(): + for cmd in [ + ['amixer', '-c', '0', 'sset', 'Headphone Mux', 'DAC'], + ['amixer', '-c', '0', 'sset', 'DAP Mux', 'I2S'], + ['amixer', '-c', '0', 'sset', 'Headphone', 'unmute'], + ['amixer', '-c', '0', 'sset', 'Lineout', 'unmute'], + ]: + try: + subprocess.run(cmd, capture_output=True, timeout=2) + except Exception: + pass + +# ── Audio ───────────────────────────────────────────────────────────────────── +SAMPLE_RATE = 44100 +BLOCK_FRAMES = 512 + +_pa = pyaudio.PyAudio() +_lock = threading.Lock() +_state = { + 'stream': None, + 'timer': None, + 'freq': 1000.0, + 'vol': 0.0, + 'phase': 0, +} + + +def _callback(in_data, frame_count, time_info, status): + freq = _state['freq'] + vol = _state['vol'] + phase = _state['phase'] + t = (np.arange(frame_count, dtype=np.float64) + phase) / SAMPLE_RATE + buf = (vol * np.sin(2.0 * math.pi * freq * t) * 32767).astype(np.int16) + _state['phase'] = (phase + frame_count) % (SAMPLE_RATE * 100) + return (buf.tobytes(), pyaudio.paContinue) + + +def _do_stop(): + """Stop active stream and cancel auto-stop timer. Call with _lock held.""" + t = _state['timer'] + s = _state['stream'] + _state['timer'] = None + _state['stream'] = None + if t: + t.cancel() + if s: + try: + s.stop_stream() + s.close() + except Exception: + pass + + +def _timer_stop(): + with _lock: + _do_stop() + + +# ── Flask app ───────────────────────────────────────────────────────────────── +app = Flask(__name__) + + +@app.route('/play', methods=['POST']) +def play(): + body = request.get_json(force=True) or {} + freq = float(body.get('freq', 1000.0)) + vol = max(0.0, min(1.0, float(body.get('vol', 0.8)))) + duration = max(0.05, float(body.get('duration', 1.0))) + + with _lock: + _do_stop() + _state['freq'] = freq + _state['vol'] = vol + _state['phase'] = 0 + + s = _pa.open( + rate=SAMPLE_RATE, + channels=1, + format=pyaudio.paInt16, + output=True, + frames_per_buffer=BLOCK_FRAMES, + stream_callback=_callback, + ) + s.start_stream() + _state['stream'] = s + + t = threading.Timer(duration, _timer_stop) + _state['timer'] = t + t.start() + + print(f"[play] {freq:.0f} Hz vol={vol:.2f} dur={duration:.2f}s") + return jsonify({'status': 'ok', 'freq': freq, 'vol': vol, 'duration': duration}) + + +@app.route('/stop', methods=['POST']) +def stop(): + with _lock: + _do_stop() + print("[stop]") + return jsonify({'status': 'ok'}) + + +@app.route('/status', methods=['GET']) +def status(): + with _lock: + playing = _state['stream'] is not None + return jsonify({'playing': playing, 'freq': _state['freq'], 'vol': _state['vol']}) + + +if __name__ == '__main__': + _alsa_init() + print("Tone server starting on 0.0.0.0:5000") + try: + app.run(host='0.0.0.0', port=5000, threaded=True) + finally: + with _lock: + _do_stop() + _pa.terminate()