diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fb6a2af --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "python.autoComplete.extraPaths": [ + "${workspaceFolder}/sources/poky/bitbake/lib", + "${workspaceFolder}/sources/poky/meta/lib" + ], + "python.analysis.extraPaths": [ + "${workspaceFolder}/sources/poky/bitbake/lib", + "${workspaceFolder}/sources/poky/meta/lib" + ], + "files.associations": { + "*.dbclient-js": "javascript", + "*.conf": "bitbake", + "*.inc": "bitbake" + } +} \ No newline at end of file diff --git a/include/README b/include/README new file mode 100644 index 0000000..630164d --- /dev/null +++ b/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..8d3ee2a --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/media/0001.mp3 b/media/0001.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/0001.mp3 differ diff --git a/media/0002.mp3 b/media/0002.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/0002.mp3 differ diff --git a/media/0003.mp3 b/media/0003.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/0003.mp3 differ diff --git a/media/0004.mp3 b/media/0004.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/0004.mp3 differ diff --git a/media/0005.mp3 b/media/0005.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/0005.mp3 differ diff --git a/media/mp3/0001.mp3 b/media/mp3/0001.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/mp3/0001.mp3 differ diff --git a/media/mp3/0002.mp3 b/media/mp3/0002.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/mp3/0002.mp3 differ diff --git a/media/mp3/0003.mp3 b/media/mp3/0003.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/mp3/0003.mp3 differ diff --git a/media/mp3/0004.mp3 b/media/mp3/0004.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/mp3/0004.mp3 differ diff --git a/media/mp3/0005.mp3 b/media/mp3/0005.mp3 new file mode 100644 index 0000000..7005b95 Binary files /dev/null and b/media/mp3/0005.mp3 differ diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..03a47a5 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,17 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32doit-devkit-v1] +platform = espressif32 +board = esp32doit-devkit-v1 +framework = arduino +monitor_speed = 115200 +lib_deps = + makuna/DFPlayer Mini Mp3 by Makuna@^1.2.3 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..42d4759 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,231 @@ +#include +#include + +class Mp3Notify; + +typedef DFMiniMp3 DfMp3; +DfMp3 mp3(Serial2); + +constexpr int kDfPlayerRxPin = 16; +constexpr int kDfPlayerTxPin = 17; +constexpr unsigned long kDfPlayerBaud = 9600; +constexpr uint32_t kDfPlayerOnlineTimeoutMs = 2000; + +constexpr int kLedPins[] = {18, 19, 21, 22}; +constexpr size_t kLedCount = sizeof(kLedPins) / sizeof(kLedPins[0]); + +constexpr int kBuzzerPins[] = {33, 25, 26, 27}; +constexpr size_t kBuzzerCount = sizeof(kBuzzerPins) / sizeof(kBuzzerPins[0]); +constexpr size_t kPairCount = (kLedCount < kBuzzerCount) ? kLedCount : kBuzzerCount; + +constexpr int kResetPin = 23; +constexpr uint32_t kAdminHoldMillis = 3000; +constexpr uint32_t kResetReleaseDelayMs = 300; + +int gActiveBuzzer = -1; +bool gAdminMode = false; +uint32_t gResetPressStartMs = 0; + +void setLed(size_t index, bool on) +{ + if (index < kLedCount) + { + digitalWrite(kLedPins[index], on ? HIGH : LOW); + } +} + +void setAllLeds(bool on) +{ + for (int pin : kLedPins) + { + digitalWrite(pin, on ? HIGH : LOW); + } +} + +void syncLedsToButtons() +{ + for (size_t i = 0; i < kPairCount; ++i) + { + const bool pressed = (digitalRead(kBuzzerPins[i]) == LOW); + setLed(i, pressed); + } + for (size_t i = kPairCount; i < kLedCount; ++i) + { + setLed(i, false); + } +} + +class Mp3Notify +{ +public: + static void PrintlnSourceAction(DfMp3_PlaySources source, const char *action) + { + if (source & DfMp3_PlaySources_Sd) + { + Serial.print("SD Card, "); + } + if (source & DfMp3_PlaySources_Usb) + { + Serial.print("USB Disk, "); + } + if (source & DfMp3_PlaySources_Flash) + { + Serial.print("Flash, "); + } + Serial.println(action); + } + static void OnError([[maybe_unused]] DfMp3 &mp3, uint16_t errorCode) + { + // see DfMp3_Error for code meaning + Serial.println(); + Serial.print("Com Error "); + Serial.println(errorCode); + } + static void OnPlayFinished([[maybe_unused]] DfMp3 &mp3, [[maybe_unused]] DfMp3_PlaySources source, uint16_t track) + { + Serial.print("Play finished for #"); + Serial.println(track); + // No automatic restart; playback only occurs on the first buzzer press. + } + static void OnPlaySourceOnline([[maybe_unused]] DfMp3 &mp3, DfMp3_PlaySources source) + { + PrintlnSourceAction(source, "online"); + } + static void OnPlaySourceInserted([[maybe_unused]] DfMp3 &mp3, DfMp3_PlaySources source) + { + PrintlnSourceAction(source, "inserted"); + } + static void OnPlaySourceRemoved([[maybe_unused]] DfMp3 &mp3, DfMp3_PlaySources source) + { + PrintlnSourceAction(source, "removed"); + } +}; + +void setup() +{ + Serial.begin(9600); + Serial.println("Booting..."); + + for (int pin : kLedPins) + { + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + } + + for (int pin : kBuzzerPins) + { + pinMode(pin, INPUT_PULLUP); + } + + pinMode(kResetPin, INPUT_PULLUP); + + syncLedsToButtons(); + + mp3.begin(kDfPlayerRxPin, kDfPlayerTxPin, kDfPlayerBaud); + mp3.setComRetries(1); + + delay(200); + mp3.reset(false); + delay(1200); + + for (size_t i = 0; i < kPairCount; ++i) + { + if (kBuzzerPins[i] == kDfPlayerRxPin || kBuzzerPins[i] == kDfPlayerTxPin) + { + Serial.print("WARNING: buzzer pin conflicts with DF serial: index "); + Serial.println(i); + } + } + + const uint32_t waitStart = millis(); + while (!mp3.isOnline() && (millis() - waitStart) < kDfPlayerOnlineTimeoutMs) + { + mp3.loop(); + delay(1); + } + + const bool playerOnline = mp3.isOnline(); + if (playerOnline) + { + uint16_t version = mp3.getSoftwareVersion(); + Serial.print("version "); + Serial.println(version); + + uint16_t volume = mp3.getVolume(); + Serial.print("volume "); + Serial.println(volume); + mp3.setVolume(24); + + const uint16_t count = mp3.getTotalTrackCount(DfMp3_PlaySource_Sd); + Serial.print("files "); + Serial.println(count); + + Serial.println("ready."); + } + else + { + Serial.println("DFPlayer did not come online."); + } +} + +void loop() +{ + mp3.loop(); + + const bool resetPressed = (digitalRead(kResetPin) == LOW); + if (resetPressed && gResetPressStartMs == 0) + { + gResetPressStartMs = millis(); + mp3.stop(); + } + + if (resetPressed && !gAdminMode && (millis() - gResetPressStartMs) > kAdminHoldMillis) + { + gAdminMode = true; + setAllLeds(true); + } + + if (!resetPressed && gResetPressStartMs != 0) + { + if (gAdminMode) + { + gAdminMode = false; + setAllLeds(false); + gActiveBuzzer = -1; + } + else if (gActiveBuzzer != -1) + { + setLed(static_cast(gActiveBuzzer), false); + gActiveBuzzer = -1; + } + gResetPressStartMs = 0; + delay(kResetReleaseDelayMs); + } + + if (gActiveBuzzer == -1 && !gAdminMode) + { + for (size_t i = 0; i < kPairCount; ++i) + { + if (digitalRead(kBuzzerPins[i]) == LOW) + { + gActiveBuzzer = static_cast(i); + for (size_t j = 0; j < kLedCount; ++j) + { + setLed(j, (j == i)); + } + mp3.playMp3FolderTrack(static_cast(i + 1)); + break; + } + } + if (gActiveBuzzer == -1) + { + syncLedsToButtons(); + } + } + else if (!gAdminMode && gActiveBuzzer >= 0) + { + setLed(static_cast(gActiveBuzzer), true); + } + + delay(1); +} diff --git a/src/main.cpp.old b/src/main.cpp.old new file mode 100644 index 0000000..13c63e7 --- /dev/null +++ b/src/main.cpp.old @@ -0,0 +1,188 @@ +// Cleaned up and fixed to use SoftwareSerial on D4 (RX) and D1 (TX) + +#include +#include +#include + +class Mp3Notify; + +// Use a DFPlayer variant that doesn't require ACKs on all commands +// to avoid blocking on modules that don't ACK reset. +typedef DFMiniMp3 DfMp3; +// DFPlayer wiring: DF TX -> D4 (GPIO2), DF RX -> D1 (GPIO5) +// Important: Do NOT use GPIO1/GPIO3 for DFPlayer if you want USB Serial. +// SoftwareSerial args are (rx, tx) from ESP8266 perspective. +SoftwareSerial mp3Serial(/*rx =*/D4, /*tx =*/D1); +DfMp3 dfmp3(mp3Serial); + +// NOTE: These GPIO numbers currently map to GPIO0, GPIO1, GPIO2, GPIO3. +// Adjust to your wiring; avoid using GPIO1 (TX0) and GPIO2 (D4) if used by DFPlayer. +const int buzzerPins[4] = {0, 1, 2, 3}; // FIXME: update to match your wiring +const int ledPins[4] = {14, 12, 13, 15}; // D5–D8 +const int resetPin = 16; // D0 + +int activeBuzzer = -1; +bool adminMode = false; +unsigned long resetPressStart = 0; + +class Mp3Notify +{ +public: + static void PrintlnSourceAction(DfMp3_PlaySources source, const char *action) + { + if (source & DfMp3_PlaySources_Sd) + { + Serial.print("SD Card, "); + } + if (source & DfMp3_PlaySources_Usb) + { + Serial.print("USB Disk, "); + } + if (source & DfMp3_PlaySources_Flash) + { + Serial.print("Flash, "); + } + Serial.println(action); + } + static void OnError([[maybe_unused]] DfMp3 &mp3, uint16_t errorCode) + { + Serial.println(); + Serial.print("Com Error "); + Serial.println(errorCode); + } + static void OnPlayFinished([[maybe_unused]] DfMp3 &mp3, [[maybe_unused]] DfMp3_PlaySources source, uint16_t track) + { + Serial.print("Play finished for #"); + Serial.println(track); + + track += 1; + if (track > 3) + { + track = 1; + } + dfmp3.playMp3FolderTrack(track); + } + static void OnPlaySourceOnline([[maybe_unused]] DfMp3 &mp3, DfMp3_PlaySources source) + { + PrintlnSourceAction(source, "online"); + } + static void OnPlaySourceInserted([[maybe_unused]] DfMp3 &mp3, DfMp3_PlaySources source) + { + PrintlnSourceAction(source, "inserted"); + } + static void OnPlaySourceRemoved([[maybe_unused]] DfMp3 &mp3, DfMp3_PlaySources source) + { + PrintlnSourceAction(source, "removed"); + } +}; + +void setup() +{ + Serial.begin(9600); + Serial.println("Booting..."); + + mp3Serial.begin(9600); + Serial.println("After mp3Serial begin"); + + dfmp3.begin(); + dfmp3.setComRetries(1); // reduce blocking if no response + Serial.println("After dfmp3 begin"); + + delay(200); // let DFPlayer settle + + dfmp3.reset(); + // many DFPlayer clones need >1s after reset before responding reliably + delay(1200); + Serial.println("After dfmp3 reset"); + + for (int i = 0; i < 4; i++) + { + pinMode(buzzerPins[i], INPUT_PULLUP); + pinMode(ledPins[i], OUTPUT); + digitalWrite(ledPins[i], LOW); + } + pinMode(resetPin, INPUT_PULLUP); + + // Warn about potential pin conflicts with DFPlayer serial (D4/D1) + for (int i = 0; i < 4; i++) + { + if (buzzerPins[i] == D4 || buzzerPins[i] == D1) + { + Serial.print("WARNING: buzzer pin conflicts with DF serial: index "); + Serial.println(i); + } + } + + uint16_t version = dfmp3.getSoftwareVersion(); + Serial.print("version "); + Serial.println(version); + + uint16_t volume = dfmp3.getVolume(); + Serial.print("volume "); + Serial.println(volume); + dfmp3.setVolume(24); + + uint16_t count = dfmp3.getTotalTrackCount(DfMp3_PlaySource_Sd); + Serial.print("files "); + Serial.println(count); + + Serial.println("starting..."); + + dfmp3.playMp3FolderTrack(1); +} + +void loop() +{ + // Process DFPlayer callbacks + dfmp3.loop(); + + if (digitalRead(resetPin) == LOW && resetPressStart == 0) + { + resetPressStart = millis(); + } + + if (digitalRead(resetPin) == LOW && !adminMode && millis() - resetPressStart > 3000) + { + adminMode = true; + for (int i = 0; i < 4; i++) + { + digitalWrite(ledPins[i], HIGH); + } + } + + if (digitalRead(resetPin) == HIGH && resetPressStart != 0) + { + if (adminMode) + { + adminMode = false; + for (int i = 0; i < 4; i++) + { + digitalWrite(ledPins[i], LOW); + } + activeBuzzer = -1; + } + else if (activeBuzzer != -1) + { + digitalWrite(ledPins[activeBuzzer], LOW); + activeBuzzer = -1; + } + resetPressStart = 0; + delay(300); + } + + if (activeBuzzer == -1 && !adminMode) + { + for (int i = 0; i < 4; i++) + { + if (digitalRead(buzzerPins[i]) == LOW) + { + activeBuzzer = i; + for (int j = 0; j < 4; j++) + { + digitalWrite(ledPins[j], j == i ? HIGH : LOW); + } + break; + } + } + } +} diff --git a/test/README b/test/README new file mode 100644 index 0000000..b0416ad --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html