From d6716df80768ae0ba48c33748505fd4c942c28d3 Mon Sep 17 00:00:00 2001
From: DarkSlein <slava982007@gmail.com>
Date: Wed, 8 May 2024 18:39:19 +0300
Subject: [PATCH] Started implementation of saving pre-recording data

Created micTask for noise detection

Installed GoogleTest

Refactored audio module
---
 platformio.ini                   |  1 +
 src/config/config.h              |  5 ++-
 src/domain/Recorder.cpp          |  2 +-
 src/infra/Audio.cpp              | 23 +++++-----
 src/infra/Audio.h                |  7 ++-
 src/infra/I2S.cpp                | 14 ++++--
 src/libs/CircularBuffer.h        | 65 +++++++++++++++++++++++++++
 src/main.cpp                     | 28 +++++++++++-
 test/CMakeLists.txt              | 27 ++++++++++++
 test/TestAll.cpp                 | 10 +++++
 test/arduino_mock/CMakeLists.txt | 16 +++++++
 test/build.sh                    |  9 ++++
 test/libs/CircularBufferTest.h   | 76 ++++++++++++++++++++++++++++++++
 13 files changed, 264 insertions(+), 19 deletions(-)
 create mode 100644 src/libs/CircularBuffer.h
 create mode 100644 test/CMakeLists.txt
 create mode 100644 test/TestAll.cpp
 create mode 100644 test/arduino_mock/CMakeLists.txt
 create mode 100644 test/build.sh
 create mode 100644 test/libs/CircularBufferTest.h

diff --git a/platformio.ini b/platformio.ini
index 757fa77..ab791c7 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -21,3 +21,4 @@ lib_deps =
 	arduino-libraries/Ethernet@^2.0.2
 	paulstoffregen/Time@^1.6.1
 	bblanchon/ArduinoJson@^6.21.3
+	google/googletest@^1.12.1
diff --git a/src/config/config.h b/src/config/config.h
index 2a906f2..a159ed9 100644
--- a/src/config/config.h
+++ b/src/config/config.h
@@ -21,9 +21,10 @@
 #define MYGW 192,168,1,1
 #define MYMAC { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }
 
-#define RAT_IP "192.168.1.173"
+#define RAT_IP "192.168.0.148"
 #define RAT_PORT 8081
 
 // make it configurable with some panel
 //#define NOISE_THRESHOLD 3000000
-#define NOISE_THRESHOLD 10000000
\ No newline at end of file
+//#define NOISE_THRESHOLD 10000000
+#define NOISE_THRESHOLD 20000000
\ No newline at end of file
diff --git a/src/domain/Recorder.cpp b/src/domain/Recorder.cpp
index 381f120..d7877d1 100644
--- a/src/domain/Recorder.cpp
+++ b/src/domain/Recorder.cpp
@@ -41,7 +41,7 @@ void Recorder::_transcribe() {
   int audioLength = _audio->getSize();
   int jsonLength = payloadStart.length() + audioLength + payloadEnd.length();
 
-  _http->printHeader("POST", "/save_audio", RAT_IP, "application/json");
+  _http->printHeader("POST", "/save_to_file", RAT_IP, "application/json");
   _http->printContentLength(jsonLength);
 
   this->_printContent(payloadStart, payloadEnd, _http->getClient());
diff --git a/src/infra/Audio.cpp b/src/infra/Audio.cpp
index c692101..dc2fa3c 100644
--- a/src/infra/Audio.cpp
+++ b/src/infra/Audio.cpp
@@ -64,29 +64,34 @@ void Audio::_createWavHeader(byte* header, int waveDataSize){
 
 char** Audio::record() {
   _createWavHeader(_paddedHeader, _wavDataSize);
-  int bitBitPerSample = i2s->GetBitPerSample();
 
   _startMicros = micros();
+  _writeToBuffer(_wavData, _wavDataSize - _prerecordedWavDataSize);
+
+  return _wavData;
+}
+
+void Audio::_writeToBuffer(char** wavBuffer, int waveDataSize) {
+  int bitBitPerSample = i2s->GetBitPerSample();
 
   if (bitBitPerSample == 16) {
-    for (int j = 0; j < _wavDataSize/_dividedWavDataSize; ++j) {
+    for (int j = 0; j < waveDataSize / _dividedWavDataSize; ++j) {
       i2s->Read(_i2sBuffer, _i2sBufferSize/2);
       for (int i = 0; i < _i2sBufferSize/8; ++i) {
-        _wavData[j][2*i] = _i2sBuffer[4*i + 2];
-        _wavData[j][2*i + 1] = _i2sBuffer[4*i + 3];
+        wavBuffer[j][2*i] = _i2sBuffer[4*i + 2];
+        wavBuffer[j][2*i + 1] = _i2sBuffer[4*i + 3];
       }
     }
   }
   else if (bitBitPerSample == 32) {
-    for (int j = 0; j < _wavDataSize/_dividedWavDataSize; ++j) {
+    for (int j = 0; j < waveDataSize / _dividedWavDataSize; ++j) {
       i2s->Read(_i2sBuffer, _i2sBufferSize);
       for (int i = 0; i < _i2sBufferSize/8; ++i) {
-        _wavData[j][2*i] = _i2sBuffer[8*i + 2] << 4;
-        _wavData[j][2*i + 1] = _i2sBuffer[8*i + 3] << 4;
+        wavBuffer[j][2*i] = _i2sBuffer[8*i + 2] << 4;
+        wavBuffer[j][2*i + 1] = _i2sBuffer[8*i + 3] << 4;
       }
     }
   }
-  return _wavData;
 }
 
 int Audio::getSize() {
@@ -114,8 +119,6 @@ unsigned long Audio::getStartMicros() {
 bool Audio::isNoiseDetected(int threshold) {
   i2s->Read(_i2sBuffer2, _i2sBufferSize/2);
   if (*(int*)_i2sBuffer2 > threshold || *(int*)_i2sBuffer2 < -threshold) {
-    Serial.print(*(int*)_i2sBuffer2);
-    Serial.print(" ");
     return true;
   } else {
     return false;
diff --git a/src/infra/Audio.h b/src/infra/Audio.h
index e30f11c..67ee080 100644
--- a/src/infra/Audio.h
+++ b/src/infra/Audio.h
@@ -10,17 +10,22 @@ class Audio {
 private:
   I2S* i2s;
   static const int _headerSize = 44;
-  static const int _i2sBufferSize = 6000;
+  static const int _i2sBufferSize = 6000; // 22500
   char _i2sBuffer[_i2sBufferSize];
   char _i2sBuffer2[_i2sBufferSize];
 
   static const int _wavDataSize = 90000;                   // It must be multiple of _dividedWavDataSize. Recording time is about 1.9 second.
   static const int _dividedWavDataSize = _i2sBufferSize/4;
   char** _wavData;                                         // It's divided. Because large continuous memory area can't be allocated in esp32.
+
+  static const int _prerecordedWavDataSize = _wavDataSize/4;
+  char** _prerecordedWavData;
+
   byte _paddedHeader[_headerSize + 4] = {0};               // The size must be multiple of 3 for Base64 encoding. Additional byte size must be even because wave data is 16bit.
 
   unsigned long _startMicros = 0;
 
+  void _writeToBuffer(char** wavBuffer, int waveDataSize);
   void _createWavHeader(byte* header, int waveDataSize);
 
 public:
diff --git a/src/infra/I2S.cpp b/src/infra/I2S.cpp
index 1079e8f..dbc2f35 100644
--- a/src/infra/I2S.cpp
+++ b/src/infra/I2S.cpp
@@ -1,4 +1,6 @@
 #include "infra/I2S.h"
+#include "config/config.h"
+
 #define SAMPLE_RATE (16000)
 #define PIN_I2S_BCLK (GPIO_NUM_26)
 #define PIN_I2S_LRC (GPIO_NUM_25)
@@ -6,6 +8,9 @@
 #define PIN_I2S_DOUT (GPIO_NUM_22)
 #define PIN_I2S_MCK (GPIO_NUM_0)
 
+SemaphoreHandle_t dmaInterruptSemaphore;
+static intr_handle_t dmaInterruptRetHandle;
+
 // This I2S specification : 
 //  -   LRC high is channel 2 (right).
 //  -   LRC signal transitions once each word.
@@ -22,7 +27,7 @@ I2S::I2S(MicType micType) {
       .bits_per_sample = BITS_PER_SAMPLE,
       .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
       .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB),
-      .intr_alloc_flags = 0,
+      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
       .dma_buf_count = 2,
       .dma_buf_len = 1024
     };
@@ -39,9 +44,9 @@ I2S::I2S(MicType micType) {
       .bits_per_sample = BITS_PER_SAMPLE,
       .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
       .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
-      .intr_alloc_flags = 0,
+      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
       .dma_buf_count = 16,
-      .dma_buf_len = 60
+      .dma_buf_len = 60,
     };
 
     i2s_pin_config_t pin_config;
@@ -55,7 +60,10 @@ I2S::I2S(MicType micType) {
     i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
     i2s_set_pin(I2S_NUM_0, &pin_config);
     i2s_set_clk(I2S_NUM_0, SAMPLE_RATE, BITS_PER_SAMPLE, I2S_CHANNEL_STEREO);
+    i2s_zero_dma_buffer(I2S_NUM_0);
   }
+
+  dmaInterruptSemaphore = xSemaphoreCreateBinary();
 }
 
 int I2S::Read(char* data, int numData) {
diff --git a/src/libs/CircularBuffer.h b/src/libs/CircularBuffer.h
new file mode 100644
index 0000000..e1a9d25
--- /dev/null
+++ b/src/libs/CircularBuffer.h
@@ -0,0 +1,65 @@
+#include <Arduino.h>
+
+enum class ErrorCode {
+  SUCCESS,
+  BUFFER_FULL,
+  BUFFER_EMPTY,
+  MEMORY_ERROR
+};
+
+template<typename T>
+class CircularBuffer {
+public:
+  CircularBuffer(size_t _capacity) : _capacity(_capacity) {
+    _buffer = static_cast<T*>(malloc(_capacity * sizeof(T)));
+    if (_buffer == nullptr) {
+      // Handle memory allocation error
+      _errorCode = ErrorCode::MEMORY_ERROR;
+    } else {
+      _bufferEnd = _buffer + _capacity;
+      _count = 0;
+      _head = _buffer;
+      _tail = _buffer;
+      _errorCode = ErrorCode::SUCCESS;
+    }
+  }
+
+  ~CircularBuffer() {
+    free(_buffer);
+  }
+
+  ErrorCode pushBack(const T& item) {
+    if (_count == _capacity) {
+      return ErrorCode::BUFFER_FULL;
+    }
+    *_head = item;
+    _head++;
+    if (_head == _bufferEnd) {
+      _head = _buffer;
+    }
+    _count++;
+    return ErrorCode::SUCCESS;
+  }
+
+  ErrorCode popFront(T& item) {
+    if (_count == 0) {
+      return ErrorCode::BUFFER_EMPTY;
+    }
+    item = *_tail;
+    _tail++;
+    if (_tail == _bufferEnd) {
+      _tail = _buffer;
+    }
+    _count--;
+    return ErrorCode::SUCCESS;
+  }
+
+private:
+  T *_buffer;      // Data _buffer
+  T *_bufferEnd;    // End of data buffer
+  size_t _capacity;   // Maximum number of items in the buffer
+  size_t _count;    // Number of items in the buffer
+  T *_head;       // Pointer to head
+  T *_tail;       // Pointer to tail
+  ErrorCode _errorCode; // Error code
+};
diff --git a/src/main.cpp b/src/main.cpp
index 47f30fe..2e0947b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -12,6 +12,8 @@
 
 #include "domain/Recorder.h"
 
+volatile bool loudnessFlag = false;
+
 unsigned long beginMicros, endMicros;
 
 Eth eth;
@@ -35,6 +37,25 @@ void setInitialTime() {
   timeService.setInitialTime(currentTime);
 }
 
+void micTask(void* parameter) {
+  while (true) {
+    if (recorder->isNoiseDetected(NOISE_THRESHOLD)) {
+      recorder->recordAudio();
+    }
+    vTaskDelay(1);
+  }
+}
+
+void createMicTask() {
+  TaskHandle_t xHandle = NULL;
+
+  xTaskCreatePinnedToCore(micTask, "micTask", 4096, NULL, 1, &xHandle, 1);
+
+  if (xHandle == NULL) {
+    ESP_LOGE("TASK1", "Failed to task create");
+  };
+}
+
 void setup() {
   Serial.begin(115200);
   delay(1000);
@@ -44,15 +65,18 @@ void setup() {
   setInitialTime();
   recorder = new Recorder(ADMP441, &http);
 
+  createMicTask();
+
   beginMicros = micros();
 }
  
 void loop() {
   eth.readAndPrintData(true); // set to false for better speed measurement
 
-  if (recorder->isNoiseDetected(NOISE_THRESHOLD)) {
+  /*if (loudnessFlag) {
+    loudnessFlag = false;
     recorder->recordAudio();
-  }
+  }*/
 
   //recorder->recordAudio();
   //delay(15000);
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..7bcb8c8
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 2.8.8)
+project(arduino-mock-test-all)
+
+find_package(Threads REQUIRED)
+add_subdirectory(arduino_mock)
+
+include_directories(
+    ${ARDUINO_MOCK_INCLUDE_DIRS}
+    ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest/googletest/include
+    ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest/googlemock/include
+)
+
+file(GLOB LIB_SRCS "../*.ino")
+file(GLOB SRCS "*.cpp")
+add_executable(test-all ${SRCS} ${LIB_SRCS})
+
+target_link_libraries(test-all
+    ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest-build/googlemock/gtest/libgtest.a
+    ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest-build/googlemock/libgmock.a
+    ${ARDUINO_MOCK_LIBS_DIR}/dist/lib/libarduino_mock.a
+    ${CMAKE_THREAD_LIBS_INIT}
+)
+
+add_dependencies(test-all arduino_mock)
+
+enable_testing()
+add_test(TestAll test-all)
diff --git a/test/TestAll.cpp b/test/TestAll.cpp
new file mode 100644
index 0000000..fef5429
--- /dev/null
+++ b/test/TestAll.cpp
@@ -0,0 +1,10 @@
+/*
+ * TestAll.cpp
+ */
+
+#include "gtest/gtest.h"
+
+int main(int argc, char* argv[]) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/arduino_mock/CMakeLists.txt b/test/arduino_mock/CMakeLists.txt
new file mode 100644
index 0000000..d70698b
--- /dev/null
+++ b/test/arduino_mock/CMakeLists.txt
@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 2.8.8)
+project(arduino_mock_builder C CXX)
+include(ExternalProject)
+
+ExternalProject_Add(arduino_mock
+    GIT_REPOSITORY https://github.com/wjszlachta/arduino-mock.git
+    GIT_TAG master
+    PREFIX ${CMAKE_CURRENT_BINARY_DIR}/arduino_mock
+    INSTALL_COMMAND ""
+)
+
+ExternalProject_Get_Property(arduino_mock source_dir)
+set(ARDUINO_MOCK_INCLUDE_DIRS ${source_dir}/include PARENT_SCOPE)
+
+ExternalProject_Get_Property(arduino_mock binary_dir)
+set(ARDUINO_MOCK_LIBS_DIR ${binary_dir} PARENT_SCOPE)
diff --git a/test/build.sh b/test/build.sh
new file mode 100644
index 0000000..fb1c9d1
--- /dev/null
+++ b/test/build.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -eu
+
+cd -- "$(dirname -- "$0")"
+mkdir -p build
+cd build
+cmake ..
+make
+ctest -V
diff --git a/test/libs/CircularBufferTest.h b/test/libs/CircularBufferTest.h
new file mode 100644
index 0000000..a0b791b
--- /dev/null
+++ b/test/libs/CircularBufferTest.h
@@ -0,0 +1,76 @@
+#include <gtest/gtest.h>
+#include "../../src/libs/CircularBuffer.h"
+
+// Test fixture for CircularBuffer
+class CircularBufferTest : public ::testing::Test {
+protected:
+    void SetUp() override {
+        // Initialization code that runs before each test
+        buffer = new CircularBuffer<int>(5); // Create a circular buffer of integers with capacity 5
+    }
+
+    void TearDown() override {
+        // Clean-up code that runs after each test
+        delete buffer;
+    }
+
+    // Member variables accessible in test cases
+    CircularBuffer<int>* buffer;
+};
+
+// Test CircularBuffer push and pop operations
+TEST_F(CircularBufferTest, PushAndPop) {
+    // Push some integers into the circular buffer
+    EXPECT_EQ(buffer->pushBack(10), ErrorCode::SUCCESS);
+    EXPECT_EQ(buffer->pushBack(20), ErrorCode::SUCCESS);
+    EXPECT_EQ(buffer->pushBack(30), ErrorCode::SUCCESS);
+
+    // Pop integers from the circular buffer and check values
+    int item;
+    EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS);
+    EXPECT_EQ(item, 10);
+    EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS);
+    EXPECT_EQ(item, 20);
+    EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS);
+    EXPECT_EQ(item, 30);
+}
+
+// Test CircularBuffer with different types
+TEST_F(CircularBufferTest, DifferentTypes) {
+    // Create a circular buffer of characters with capacity 3
+    CircularBuffer<char> charBuffer(3);
+
+    // Push some characters into the circular buffer
+    EXPECT_EQ(charBuffer.pushBack('a'), ErrorCode::SUCCESS);
+    EXPECT_EQ(charBuffer.pushBack('b'), ErrorCode::SUCCESS);
+    EXPECT_EQ(charBuffer.pushBack('c'), ErrorCode::SUCCESS);
+
+    // Pop characters from the circular buffer and check values
+    char c;
+    EXPECT_EQ(charBuffer.popFront(c), ErrorCode::SUCCESS);
+    EXPECT_EQ(c, 'a');
+    EXPECT_EQ(charBuffer.popFront(c), ErrorCode::SUCCESS);
+    EXPECT_EQ(c, 'b');
+    EXPECT_EQ(charBuffer.popFront(c), ErrorCode::SUCCESS);
+    EXPECT_EQ(c, 'c');
+}
+
+// Test CircularBuffer with full and empty conditions
+TEST_F(CircularBufferTest, FullAndEmpty) {
+    // Fill up the buffer
+    buffer->pushBack(10);
+    buffer->pushBack(20);
+    buffer->pushBack(30);
+
+    // Try pushing into a full buffer
+    EXPECT_EQ(buffer->pushBack(40), ErrorCode::BUFFER_FULL);
+
+    // Pop all elements from the buffer
+    int item;
+    EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS);
+    EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS);
+    EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS);
+
+    // Try popping from an empty buffer
+    EXPECT_EQ(buffer->popFront(item), ErrorCode::BUFFER_EMPTY);
+}
\ No newline at end of file