From c0bf4286d1d84e67b06f9370be3c043a8ac451f7 Mon Sep 17 00:00:00 2001 From: DarkSlein Date: Mon, 25 Mar 2024 16:25:46 +0300 Subject: [PATCH] Implemented recording audio with threshold and sending it to Rat --- platformio.ini | 2 + src/Audio.cpp | 84 ------------- src/Audio.h | 26 ---- src/config/config.h | 29 +++++ src/domain/Recorder.cpp | 83 +++++++++++++ src/domain/Recorder.h | 25 ++++ src/infra/Audio.cpp | 123 +++++++++++++++++++ src/infra/Audio.h | 36 ++++++ src/infra/Eth.cpp | 92 ++++++++++++++ src/infra/Eth.h | 15 +++ src/infra/Http.cpp | 135 +++++++++++++++++++++ src/infra/Http.h | 32 +++++ src/{ => infra}/I2S.cpp | 2 +- src/{ => infra}/I2S.h | 0 src/main.cpp | 261 ++++++---------------------------------- src/utils/Time.cpp | 69 +++++++++++ src/utils/Time.h | 22 ++++ 17 files changed, 704 insertions(+), 332 deletions(-) delete mode 100644 src/Audio.cpp delete mode 100644 src/Audio.h create mode 100644 src/config/config.h create mode 100644 src/domain/Recorder.cpp create mode 100644 src/domain/Recorder.h create mode 100644 src/infra/Audio.cpp create mode 100644 src/infra/Audio.h create mode 100644 src/infra/Eth.cpp create mode 100644 src/infra/Eth.h create mode 100644 src/infra/Http.cpp create mode 100644 src/infra/Http.h rename src/{ => infra}/I2S.cpp (99%) rename src/{ => infra}/I2S.h (100%) create mode 100644 src/utils/Time.cpp create mode 100644 src/utils/Time.h diff --git a/platformio.ini b/platformio.ini index 3e467d5..757fa77 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,3 +19,5 @@ board_build.partitions = no_ota.csv lib_deps = ottowinter/AsyncMqttClient-esphome@^0.8.6 arduino-libraries/Ethernet@^2.0.2 + paulstoffregen/Time@^1.6.1 + bblanchon/ArduinoJson@^6.21.3 diff --git a/src/Audio.cpp b/src/Audio.cpp deleted file mode 100644 index 9e82a96..0000000 --- a/src/Audio.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "Audio.h" - -Audio::Audio(MicType micType) { - wavData = new char*[wavDataSize/dividedWavDataSize]; - for (int i = 0; i < wavDataSize/dividedWavDataSize; ++i) wavData[i] = new char[dividedWavDataSize]; - i2s = new I2S(micType); -} - -Audio::~Audio() { - for (int i = 0; i < wavDataSize/dividedWavDataSize; ++i) delete[] wavData[i]; - delete[] wavData; - delete i2s; -} - -void Audio::CreateWavHeader(byte* header, int waveDataSize){ - header[0] = 'R'; - header[1] = 'I'; - header[2] = 'F'; - header[3] = 'F'; - unsigned int fileSizeMinus8 = waveDataSize + 44 - 8; - header[4] = (byte)(fileSizeMinus8 & 0xFF); - header[5] = (byte)((fileSizeMinus8 >> 8) & 0xFF); - header[6] = (byte)((fileSizeMinus8 >> 16) & 0xFF); - header[7] = (byte)((fileSizeMinus8 >> 24) & 0xFF); - header[8] = 'W'; - header[9] = 'A'; - header[10] = 'V'; - header[11] = 'E'; - header[12] = 'f'; - header[13] = 'm'; - header[14] = 't'; - header[15] = ' '; - header[16] = 0x10; // linear PCM - header[17] = 0x00; - header[18] = 0x00; - header[19] = 0x00; - header[20] = 0x01; // linear PCM - header[21] = 0x00; - header[22] = 0x01; // monoral - header[23] = 0x00; - header[24] = 0x80; // sampling rate 16000 - header[25] = 0x3E; - header[26] = 0x00; - header[27] = 0x00; - header[28] = 0x00; // Byte/sec = 16000x2x1 = 32000 - header[29] = 0x7D; - header[30] = 0x00; - header[31] = 0x00; - header[32] = 0x02; // 16bit monoral - header[33] = 0x00; - header[34] = 0x10; // 16bit - header[35] = 0x00; - header[36] = 'd'; - header[37] = 'a'; - header[38] = 't'; - header[39] = 'a'; - header[40] = (byte)(waveDataSize & 0xFF); - header[41] = (byte)((waveDataSize >> 8) & 0xFF); - header[42] = (byte)((waveDataSize >> 16) & 0xFF); - header[43] = (byte)((waveDataSize >> 24) & 0xFF); -} - -void Audio::Record() { - CreateWavHeader(paddedHeader, wavDataSize); - int bitBitPerSample = i2s->GetBitPerSample(); - if (bitBitPerSample == 16) { - for (int j = 0; j < wavDataSize/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]; - } - } - } - else if (bitBitPerSample == 32) { - for (int j = 0; j < wavDataSize/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; - } - } - } -} diff --git a/src/Audio.h b/src/Audio.h deleted file mode 100644 index 3e1a9ee..0000000 --- a/src/Audio.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef _AUDIO_H -#define _AUDIO_H - -#include -#include "I2S.h" - -// 16bit, monoral, 16000Hz, linear PCM -class Audio { - I2S* i2s; - static const int headerSize = 44; - static const int i2sBufferSize = 6000; - char i2sBuffer[i2sBufferSize]; - void CreateWavHeader(byte* header, int waveDataSize); - -public: - 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. - 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. - - Audio(MicType micType); - ~Audio(); - void Record(); -}; - -#endif // _AUDIO_H diff --git a/src/config/config.h b/src/config/config.h new file mode 100644 index 0000000..2a906f2 --- /dev/null +++ b/src/config/config.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#define ETH_MISO 19 +#define ETH_MOSI 23 +#define ETH_SCK 18 +#define ETH_SS 5 + +#define SD_CS 5 +#define SPI_MOSI 23 +#define SPI_MISO 19 +#define SPI_SCK 18 +#define I2S_DOUT 25 +#define I2S_BCLK 27 +#define I2S_LRC 26 + +#define MYIPADDR 192,168,1,28 +#define MYIPMASK 255,255,255,0 +#define MYDNS 192,168,1,1 +#define MYGW 192,168,1,1 +#define MYMAC { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED } + +#define RAT_IP "192.168.1.173" +#define RAT_PORT 8081 + +// make it configurable with some panel +//#define NOISE_THRESHOLD 3000000 +#define NOISE_THRESHOLD 10000000 \ No newline at end of file diff --git a/src/domain/Recorder.cpp b/src/domain/Recorder.cpp new file mode 100644 index 0000000..381f120 --- /dev/null +++ b/src/domain/Recorder.cpp @@ -0,0 +1,83 @@ +#include "domain/Recorder.h" + +#include "config/config.h" +#include "utils/Time.h" + +Time& timeModule = Time::getInstance(); + +Recorder::Recorder(MicType micType, Http* http) { + _audio = new Audio(micType); + _http = http; +} + +Recorder::~Recorder() { + delete _audio; +} + +void Recorder::recordAudio() { + _ready(); + + if (_http->connect(RAT_IP, RAT_PORT)) { + Serial.println("========================================"); + Serial.println("| RECORDING..."); + _audio->record(); + Serial.println("| Recording finished"); + + _transcribe(); + + } else { + Serial.println("| Connection failed"); + } +} + +void Recorder::_ready() { + _http->send("POST", "/ready", RAT_IP, RAT_PORT, "", ""); +} + +void Recorder::_transcribe() { + String payloadStart = _formPayloadStart(); + String payloadEnd = _formPayloadEnd(); + + int audioLength = _audio->getSize(); + int jsonLength = payloadStart.length() + audioLength + payloadEnd.length(); + + _http->printHeader("POST", "/save_audio", RAT_IP, "application/json"); + _http->printContentLength(jsonLength); + + this->_printContent(payloadStart, payloadEnd, _http->getClient()); +} + +void Recorder::_printContent(String payloadStart, String payloadEnd, Stream* stream) { + stream->print(payloadStart); + _audio->print(stream); + stream->print(payloadEnd); + + _logContent(payloadStart, payloadEnd); +} + +void Recorder::_logContent(String payloadStart, String payloadEnd) { + Serial.print("| "); + Serial.print(payloadStart); + Serial.print("..."); + Serial.print(payloadEnd); +} + +String Recorder::_formPayloadStart() { + String payloadStart = ""; + + payloadStart += "{\"audio\":{\"micros\":"; + payloadStart += String(_audio->getStartMicros() - timeModule.getInitialMicros()); + payloadStart += ",\"initialTime\":"; + payloadStart += String(timeModule.getInitialTime()); + payloadStart += ",\"content\":\""; + + return payloadStart; +} + +String Recorder::_formPayloadEnd() { + return "\"}}\r\n\r\n"; +} + +bool Recorder::isNoiseDetected(int threshold) { + return _audio->isNoiseDetected(threshold); +} \ No newline at end of file diff --git a/src/domain/Recorder.h b/src/domain/Recorder.h new file mode 100644 index 0000000..c0da325 --- /dev/null +++ b/src/domain/Recorder.h @@ -0,0 +1,25 @@ +#pragma once + +#include "infra/Audio.h" +#include "infra/Http.h" + +class Recorder { +private: + Audio* _audio; + Http* _http; + + void _ready(); + void _transcribe(); + void _printContent(String payloadStart, String payloadEnd, Stream* stream); + void _logContent(String payloadStart, String payloadEnd); + void _printAudio(); + + String _formPayloadStart(); + String _formPayloadEnd(); + +public: + Recorder(MicType micType, Http* http); + ~Recorder(); + void recordAudio(); + bool isNoiseDetected(int threshold); +}; diff --git a/src/infra/Audio.cpp b/src/infra/Audio.cpp new file mode 100644 index 0000000..c692101 --- /dev/null +++ b/src/infra/Audio.cpp @@ -0,0 +1,123 @@ +#include "infra/Audio.h" + +#include + +Audio::Audio(MicType micType) { + _wavData = new char*[_wavDataSize/_dividedWavDataSize]; + for (int i = 0; i < _wavDataSize/_dividedWavDataSize; ++i) _wavData[i] = new char[_dividedWavDataSize]; + i2s = new I2S(micType); +} + +Audio::~Audio() { + for (int i = 0; i < _wavDataSize/_dividedWavDataSize; ++i) delete[] _wavData[i]; + delete[] _wavData; + delete i2s; +} + +void Audio::_createWavHeader(byte* header, int waveDataSize){ + header[0] = 'R'; + header[1] = 'I'; + header[2] = 'F'; + header[3] = 'F'; + unsigned int fileSizeMinus8 = waveDataSize + 44 - 8; + header[4] = (byte)(fileSizeMinus8 & 0xFF); + header[5] = (byte)((fileSizeMinus8 >> 8) & 0xFF); + header[6] = (byte)((fileSizeMinus8 >> 16) & 0xFF); + header[7] = (byte)((fileSizeMinus8 >> 24) & 0xFF); + header[8] = 'W'; + header[9] = 'A'; + header[10] = 'V'; + header[11] = 'E'; + header[12] = 'f'; + header[13] = 'm'; + header[14] = 't'; + header[15] = ' '; + header[16] = 0x10; // linear PCM + header[17] = 0x00; + header[18] = 0x00; + header[19] = 0x00; + header[20] = 0x01; // linear PCM + header[21] = 0x00; + header[22] = 0x01; // monoral + header[23] = 0x00; + header[24] = 0x80; // sampling rate 16000 + header[25] = 0x3E; + header[26] = 0x00; + header[27] = 0x00; + header[28] = 0x00; // Byte/sec = 16000x2x1 = 32000 + header[29] = 0x7D; + header[30] = 0x00; + header[31] = 0x00; + header[32] = 0x02; // 16bit monoral + header[33] = 0x00; + header[34] = 0x10; // 16bit + header[35] = 0x00; + header[36] = 'd'; + header[37] = 'a'; + header[38] = 't'; + header[39] = 'a'; + header[40] = (byte)(waveDataSize & 0xFF); + header[41] = (byte)((waveDataSize >> 8) & 0xFF); + header[42] = (byte)((waveDataSize >> 16) & 0xFF); + header[43] = (byte)((waveDataSize >> 24) & 0xFF); +} + +char** Audio::record() { + _createWavHeader(_paddedHeader, _wavDataSize); + int bitBitPerSample = i2s->GetBitPerSample(); + + _startMicros = micros(); + + if (bitBitPerSample == 16) { + for (int j = 0; j < _wavDataSize/_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]; + } + } + } + else if (bitBitPerSample == 32) { + for (int j = 0; j < _wavDataSize/_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; + } + } + } + return _wavData; +} + +int Audio::getSize() { + return (_wavDataSize + sizeof(_paddedHeader)) * 4 / 3; // 4/3 is from base64 encoding +} + +String Audio::print(Stream* stream) { + String enc = base64::encode(_paddedHeader, sizeof(_paddedHeader)); + enc.replace("\n", ""); + stream->print(enc); + + for (int j = 0; j < _wavDataSize / _dividedWavDataSize; ++j) { + enc = base64::encode((byte*)_wavData[j], _dividedWavDataSize); + enc.replace("\n", ""); + stream->print(enc); + } + + return enc; +} + +unsigned long Audio::getStartMicros() { + return _startMicros; +} + +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; + } +} \ No newline at end of file diff --git a/src/infra/Audio.h b/src/infra/Audio.h new file mode 100644 index 0000000..e30f11c --- /dev/null +++ b/src/infra/Audio.h @@ -0,0 +1,36 @@ +#ifndef _AUDIO_H +#define _AUDIO_H + +#include + +#include "infra/I2S.h" + +// 16bit, monoral, 16000Hz, linear PCM +class Audio { +private: + I2S* i2s; + static const int _headerSize = 44; + static const int _i2sBufferSize = 6000; + 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. + 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 _createWavHeader(byte* header, int waveDataSize); + +public: + Audio(MicType micType); + ~Audio(); + char** record(); + int getSize(); + String print(Stream* stream); + unsigned long getStartMicros(); + bool isNoiseDetected(int threshold); +}; + +#endif // _AUDIO_H diff --git a/src/infra/Eth.cpp b/src/infra/Eth.cpp new file mode 100644 index 0000000..8ba1bfc --- /dev/null +++ b/src/infra/Eth.cpp @@ -0,0 +1,92 @@ +#include + +#include "config/config.h" +#include "infra/Eth.h" + +EthernetClient* Eth::getEthClient() { + return &_client; +} + +void Eth::initEthernet() { + Serial.println("| Begin Ethernet"); + + Ethernet.init(5); // MKR ETH Shield + + SPI.begin(ETH_SCK, ETH_MISO, ETH_MOSI, ETH_SS); + + Serial.println("| SCK = " + String(ETH_SCK)); + Serial.println("| MISO = " + String(ETH_MISO)); + Serial.println("| MOSI = " + String(ETH_MOSI)); + Serial.println("| SS = " + String(ETH_SS)); + + byte mac[] = MYMAC; + + if (Ethernet.begin(mac)) { // Dynamic IP setup + Serial.println("| DHCP OK!"); + } else { + Serial.println("| Failed to configure Ethernet using DHCP"); + // Check for Ethernet hardware present + if (Ethernet.hardwareStatus() == EthernetNoHardware) { + Serial.println("| Ethernet shield was not found. Sorry, can't run without hardware. :("); + while (true) { + delay(1); // do nothing, no point running without Ethernet hardware + } + } + if (Ethernet.linkStatus() == LinkOFF) { + Serial.println("| Ethernet cable is not connected."); + } + + IPAddress ip(MYIPADDR); + IPAddress dns(MYDNS); + IPAddress gw(MYGW); + IPAddress sn(MYIPMASK); + Ethernet.begin(mac, ip, dns, gw, sn); + Serial.println("| STATIC OK!"); + } + delay(5000); + + Serial.print("| Local IP : "); + Serial.println(Ethernet.localIP()); + Serial.print("| Subnet Mask : "); + Serial.println(Ethernet.subnetMask()); + Serial.print("| Gateway IP : "); + Serial.println(Ethernet.gatewayIP()); + Serial.print("| DNS Server : "); + Serial.println(Ethernet.dnsServerIP()); + + Serial.println("| Ethernet Successfully Initialized"); +} + +void Eth::readAndPrintData(bool printWebData) { + int len = _client.available(); + if (len > 0) { + byte buffer[80]; + if (len > 80) len = 80; + _client.read(buffer, len); + if (printWebData) { + Serial.write(buffer, len); + } + _byteCount = _byteCount + len; + } +} + +void Eth::handleDisconnect(unsigned long beginMicros, unsigned long endMicros) { + if (!_client.connected() && !_disconnected) { + endMicros = micros(); + Serial.println(); + Serial.println("| disconnecting."); + _client.stop(); + Serial.print("| Received "); + Serial.print(_byteCount); + Serial.print(" bytes in "); + float seconds = (float)(endMicros - beginMicros) / 1000000.0; + Serial.print(seconds, 4); + float rate = (float)_byteCount / seconds / 1000.0; + Serial.print(", rate = "); + Serial.print(rate); + Serial.print(" kbytes/second"); + Serial.println(); + + _disconnected = true; + } +} \ No newline at end of file diff --git a/src/infra/Eth.h b/src/infra/Eth.h new file mode 100644 index 0000000..e03a282 --- /dev/null +++ b/src/infra/Eth.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class Eth { + EthernetClient _client; + unsigned long _byteCount = 0; + bool _disconnected = false; + +public: + EthernetClient* getEthClient(); + void initEthernet(); + void readAndPrintData(bool printWebData); + void handleDisconnect(unsigned long beginMicros, unsigned long endMicros); +}; \ No newline at end of file diff --git a/src/infra/Http.cpp b/src/infra/Http.cpp new file mode 100644 index 0000000..8d22088 --- /dev/null +++ b/src/infra/Http.cpp @@ -0,0 +1,135 @@ +#include "infra/Http.h" +#include "infra/Eth.h" +#include "Http.h" + +Http::Http(Eth* eth) { + _client = eth->getEthClient(); +} + +String Http::send(String method, String endpoint, String host, int port, String payload, String contentType) { + String body = ""; + + if (_client->connect(host.c_str(), port)) { + _logConnected(host, port); + + this->sendRequest(method, endpoint, host, contentType, payload); + String response = this->readResponse(); + + body = Http::extractBody(response); + + Serial.println("Response:"); + Serial.println(response); + } else { + Serial.println("| Connection failed"); + } + + return body; +} + +void Http::sendRequest(String method, String endpoint, String host, String contentType, String payload) { + this->printHeader(method, endpoint, host, contentType); + this->printPayload(payload); +} + +void Http::printHeader(String method, String endpoint, String host, String contentType) { + _client->print(method); + _client->print(" "); + _client->print(endpoint); + _client->println(" HTTP/1.1"); + _client->print("Host: "); + _client->println(host); + + if (contentType != "") { + _client->print("Content-Type: "); + _client->println(contentType); + } + + _logHeader(method, endpoint, host, contentType); +} + +void Http::printPayload(String payload) { + if (payload != "") { + this->printContentLength(payload.length()); + _client->println(payload); + } else { + _client->println(); + } + + _logPayload(payload); +} + +void Http::printContentLength(int contentLength) { + _client->print("Content-Length: "); + _client->println(contentLength); + _client->println(); +} + +String Http::readResponse() { + String response = ""; + + while (_client->connected()) { + if (_client->available()) { + char c = _client->read(); + response += c; + } + } + + return response; +} + +int Http::connect(const char *host, uint16_t port) { + return _client->connect(host, port); +} + +void Http::stop() { + _client->stop(); + Serial.println("| Disconnected from server"); +} + +EthernetClient* Http::getClient() { + return _client; +} + +String Http::extractBody(String httpResponse) { + String body = ""; + + const char* bodyStart = strstr(httpResponse.c_str(), "\r\n\r\n"); + + if (bodyStart != NULL) { + body = bodyStart + 4; + } else { + Serial.println("| Body not found in response."); + } + + return body; +} + +void Http::_logConnected(String host, int port) { + Serial.println(); + Serial.print("| Connected to server "); + Serial.print(host); + Serial.print(":"); + Serial.println(port); +} + +void Http::_logHeader(String method, String endpoint, String host, String contentType) { + Serial.println("========================================"); + + Serial.print("| "); + Serial.print(method); + Serial.print(" "); + Serial.print(host); + Serial.println(endpoint); + + if (contentType && contentType != "") { + Serial.print("| Content-Type: "); + Serial.println(contentType); + } +} + +void Http::_logPayload(String payload) { + if (payload && payload != "") { + Serial.println("| "); + Serial.println(payload); + } +} \ No newline at end of file diff --git a/src/infra/Http.h b/src/infra/Http.h new file mode 100644 index 0000000..a0d4994 --- /dev/null +++ b/src/infra/Http.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "infra/Eth.h" + +class Http { +private: + EthernetClient* _client; + + void _logHeader(String method, String endpoint, String host, String contentType); + void _logPayload(String payload); + void _logConnected(String host, int port); + +public: + Http(Eth* eth); + + String send(String method, String endpoint, String host, int port, String payload, String contentType); + void sendRequest(String method, String endpoint, String host, String contentType, String payload); + + void printHeader(String method, String endpoint, String host, String contentType); + void printContentLength(int contentLength); + void printPayload(String payload); + + String readResponse(); + + int connect(const char *host, uint16_t port); + void stop(); + EthernetClient* getClient(); + + static String extractBody(String httpResponse); +}; \ No newline at end of file diff --git a/src/I2S.cpp b/src/infra/I2S.cpp similarity index 99% rename from src/I2S.cpp rename to src/infra/I2S.cpp index 0f84f7d..1079e8f 100644 --- a/src/I2S.cpp +++ b/src/infra/I2S.cpp @@ -1,4 +1,4 @@ -#include "I2S.h" +#include "infra/I2S.h" #define SAMPLE_RATE (16000) #define PIN_I2S_BCLK (GPIO_NUM_26) #define PIN_I2S_LRC (GPIO_NUM_25) diff --git a/src/I2S.h b/src/infra/I2S.h similarity index 100% rename from src/I2S.h rename to src/infra/I2S.h diff --git a/src/main.cpp b/src/main.cpp index 8ea2353..47f30fe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,242 +1,61 @@ -/* - Web client - - This sketch connects to a test website (httpbin.org) - and try to do a GET request, the output is printed - on Serial - - by Renzo Mischianti - - https://www.mischianti.org - - */ - -#include + #include #include -#include "Audio.h" +#include -#define ETH_MISO 19 -#define ETH_MOSI 23 -#define ETH_SCK 18 -#define ETH_SS 5 +#include "config/config.h" +#include "utils/Time.h" +#include "infra/Audio.h" +#include "infra/Http.h" +#include "infra/Eth.h" -#define SD_CS 5 -#define SPI_MOSI 23 -#define SPI_MISO 19 -#define SPI_SCK 18 -#define I2S_DOUT 25 -#define I2S_BCLK 27 -#define I2S_LRC 26 - -// if you don't want to use DNS (and reduce your sketch size) -// use the numeric IP instead of the name for the server: -//IPAddress server(74,125,232,128); // numeric IP for Google (no DNS) -//char server[] = "www.google.com"; // name address for Google (using DNS) -char server[] = "httpbin.org"; // name address for Google (using DNS) - -// Enter a MAC address for your controller below. -// Newer Ethernet shields have a MAC address printed on a sticker on the shield -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; - -// Set the static IP address to use if the DHCP fails to assign -#define MYIPADDR 192,168,1,28 -#define MYIPMASK 255,255,255,0 -#define MYDNS 192,168,1,1 -#define MYGW 192,168,1,1 +#include "domain/Recorder.h" - -//#define ALTERNATE_PINS - - -// Initialize the Ethernet client library -// with the IP address and port of the server -// that you want to connect to (port 80 is default for HTTP): -EthernetClient client; - -// Variables to measure the speed unsigned long beginMicros, endMicros; -unsigned long byteCount = 0; -bool printWebData = true; // set to false for better speed measurement -void PrintHttpBody2(Audio* audio) -{ - String enc = base64::encode(audio->paddedHeader, sizeof(audio->paddedHeader)); - enc.replace("\n", ""); // delete last "\n" - client.print(enc); // HttpBody2 - char** wavData = audio->wavData; - for (int j = 0; j < audio->wavDataSize / audio->dividedWavDataSize; ++j) { - enc = base64::encode((byte*)wavData[j], audio->dividedWavDataSize); - enc.replace("\n", "");// delete last "\n" - client.print(enc); // HttpBody2 - } -} +Eth eth; +EthernetClient* client = eth.getEthClient(); +Http http(ð); -void Transcribe(Audio* audio) { - String HttpBody1 = "{\"audio\":{\"content\":\""; - String HttpBody3 = "\"}}\r\n\r\n"; - int httpBody2Length = (audio->wavDataSize + sizeof(audio->paddedHeader)) * 4 / 3; // 4/3 is from base64 encoding - String ContentLength = String(HttpBody1.length() + httpBody2Length + HttpBody3.length()); - String HttpHeader; - // if (authentication == USE_APIKEY) - HttpHeader = String("POST /save_audio HTTP/1.1\r\nHost: 192.168.1.173\r\nContent-Type: application/json\r\nContent-Length: ") + ContentLength + String("\r\n\r\n"); - // else if (authentication == USE_ACCESSTOKEN) - // HttpHeader = String("POST /v1beta1/speech:syncrecognize HTTP/1.1\r\nHost: speech.googleapis.com\r\nContent-Type: application/json\r\nAuthorization: Bearer ") - // + AccessToken + String("\r\nContent-Length: ") + ContentLength + String("\r\n\r\n"); - client.print(HttpHeader); - client.print(HttpBody1); - PrintHttpBody2(audio); - client.print(HttpBody3); - for (int i = 0; i < httpBody2Length; i++) { - client.print(" "); - } - String My_Answer=""; +Recorder* recorder; + +Time timeService = Time::getInstance(); + +void setInitialTime() { + String response = http.send("GET", "/time", RAT_IP, RAT_PORT, "", ""); + + DynamicJsonDocument jsonDoc(1024); + DeserializationError error = deserializeJson(jsonDoc, response); + JsonVariant root = jsonDoc.as(); + + unsigned long currentTime = root["current_timestamp"].as(); + + timeService.setInitialMicros(micros()); + timeService.setInitialTime(currentTime); } void setup() { + Serial.begin(115200); + delay(1000); + eth.initEthernet(); - Serial.begin(115200); - delay(1000); - Serial.println("Begin Ethernet"); - Serial.println("COM0 setup OK!"); - - - - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - SPI.begin(ETH_SCK, ETH_MISO, ETH_MOSI, ETH_SS); - - //SCLK = 18; - //MISO = 19; - // MOSI = 23; - // SS = 5; - //vspi->begin(); - //pinMode(LED_BUILTIN, OUTPUT); - //Serial.println("LED_BUILTIN = " + String(LED_BUILTIN)); - - Serial.println("SCK = " + String(ETH_SCK)); - Serial.println("MISO = " + String(ETH_MISO)); - Serial.println("MOSI = " + String(ETH_MOSI)); - Serial.println("SS = " + String(ETH_SS)); - - - - - if (Ethernet.begin(mac)) { // Dynamic IP setup - Serial.println("DHCP OK!"); - }else{ - Serial.println("Failed to configure Ethernet using DHCP"); - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - - IPAddress ip(MYIPADDR); - IPAddress dns(MYDNS); - IPAddress gw(MYGW); - IPAddress sn(MYIPMASK); - Ethernet.begin(mac, ip, dns, gw, sn); - Serial.println("STATIC OK!"); - } - delay(5000); - - - Serial.print("Local IP : "); - Serial.println(Ethernet.localIP()); - Serial.print("Subnet Mask : "); - Serial.println(Ethernet.subnetMask()); - Serial.print("Gateway IP : "); - Serial.println(Ethernet.gatewayIP()); - Serial.print("DNS Server : "); - Serial.println(Ethernet.dnsServerIP()); - - Serial.println("Ethernet Successfully Initialized"); - // if you get a connection, report back via serial: - /*if (client.connect(server, 80)) { - Serial.println("Connected!"); - // Make a HTTP request: - client.println("GET /get HTTP/1.1"); - client.println("Host: httpbin.org"); - client.println("Connection: close"); - client.println(); - } else { - // if you didn't get a connection to the server: - Serial.println("1 connection failed"); - }*/ - - if (client.connect("192.168.1.173", 5000)) { - Serial.println("Connected!"); - // Make a HTTP request: - // client.println("GET /get HTTP/1.1"); - // client.println("Host: 192.168.1.173"); - // client.println("Connection: close"); - // client.println(); - - Audio* audio = new Audio(ADMP441); - - Serial.println("RECORDING"); - audio->Record(); - Serial.println("Recoding Complited Processing"); - - Transcribe(audio); - - delete audio; - } else { - // if you didn't get a connection to the server: - Serial.println("connection failed"); - } + setInitialTime(); + recorder = new Recorder(ADMP441, &http); beginMicros = micros(); } void loop() { - // if there are incoming bytes available - // from the server, read them and print them: - int len = client.available(); - if (len > 0) { - byte buffer[80]; - if (len > 80) len = 80; - client.read(buffer, len); - if (printWebData) { - Serial.write(buffer, len); // show in the serial monitor (slows some boards) - } - byteCount = byteCount + len; - } - - // if the server's disconnected, stop the client: - if (!client.connected()) { - endMicros = micros(); - Serial.println(); - Serial.println("disconnecting."); - client.stop(); - Serial.print("Received "); - Serial.print(byteCount); - Serial.print(" bytes in "); - float seconds = (float)(endMicros - beginMicros) / 1000000.0; - Serial.print(seconds, 4); - float rate = (float)byteCount / seconds / 1000.0; - Serial.print(", rate = "); - Serial.print(rate); - Serial.print(" kbytes/second"); - Serial.println(); - - // do nothing forevermore: - while (true) { - delay(1); - } + eth.readAndPrintData(true); // set to false for better speed measurement + + if (recorder->isNoiseDetected(NOISE_THRESHOLD)) { + recorder->recordAudio(); } + + //recorder->recordAudio(); + //delay(15000); + + //eth.handleDisconnect(beginMicros, micros()); } diff --git a/src/utils/Time.cpp b/src/utils/Time.cpp new file mode 100644 index 0000000..481ee47 --- /dev/null +++ b/src/utils/Time.cpp @@ -0,0 +1,69 @@ +#include +#include "utils/time.h" + +unsigned long Time::_initialTime = 0; +unsigned long Time::_initialMicros = 0; + +Time::Time() {} + +Time& Time::getInstance() { + static Time instance; + return instance; +} + +void Time::setInitialTime(unsigned long time) { + _initialTime = time; +} + +unsigned long Time::getInitialTime() const { + return _initialTime; +} + +void Time::setInitialMicros(unsigned long time) { + _initialMicros = time; +} + +unsigned long Time::getInitialMicros() const { + return _initialMicros; +} + +unsigned long Time::getCurrentMillis() const { + return millis(); +} + +unsigned long Time::getCurrentTime() const { + return _initialTime + this->getCurrentMillis() / 1000; +} + +unsigned long Time::getMillisDifference(unsigned long otherTimeMillis) const { + return millis() - otherTimeMillis; +} + +String Time::getFormattedTime(unsigned long time) const { + time_t currentTime = _initialTime + time; + + struct tm *timeStruct = gmtime(¤tTime); + + char buffer[20]; + snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d", + timeStruct->tm_year + 1900, timeStruct->tm_mon + 1, timeStruct->tm_mday, + timeStruct->tm_hour, timeStruct->tm_min, timeStruct->tm_sec); + + return String(buffer); +} + +String Time::getFormattedTimeAgo(unsigned long time) const { + time_t currentTime = _initialTime + time; + + struct tm *timeStruct = gmtime(¤tTime); + + if (timeStruct->tm_yday > 0) { + return String(timeStruct->tm_yday) + " days ago"; + } else if (timeStruct->tm_hour > 0) { + return String(timeStruct->tm_hour) + " hours ago"; + } else if (timeStruct->tm_min > 0) { + return String(timeStruct->tm_min) + " minutes ago"; + } else { + return String(timeStruct->tm_sec) + " seconds ago"; + } +} \ No newline at end of file diff --git a/src/utils/Time.h b/src/utils/Time.h new file mode 100644 index 0000000..d06e4f4 --- /dev/null +++ b/src/utils/Time.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +class Time { +public: + static Time& getInstance(); + void setInitialTime(unsigned long time); + unsigned long getInitialTime() const; + void setInitialMicros(unsigned long time); + unsigned long getInitialMicros() const; + unsigned long getCurrentMillis() const; + unsigned long getCurrentTime() const; + unsigned long getMillisDifference(unsigned long otherTimeMillis) const; + String getFormattedTime(unsigned long timeMillis) const; + String getFormattedTimeAgo(unsigned long timeMillis) const; + +private: + Time(); + static unsigned long _initialTime; + static unsigned long _initialMicros; +}; \ No newline at end of file