diff --git a/Firmware/LowLevel/README-Sound, DFPIS5V.md b/Firmware/LowLevel/README-Sound, DFPIS5V.md new file mode 100644 index 00000000..b159fb60 --- /dev/null +++ b/Firmware/LowLevel/README-Sound, DFPIS5V.md @@ -0,0 +1,92 @@ +# Sound via "DFPlayer Mini" + +> [!IMPORTANT] +> Owner of mainboard version 0.13.x need to check if the required R7 and R13 +> resistors got assembled.
+> They're located on the top side of the PCB, underneath the DFPlayer.
+> If not assembled, you need to solder 1k resistors (otherwise all sounds get played continuously) + +> [!IMPORTANT] +> +> Users who use any of the DFPlayer-Clones,
+> in an open-mower-mainboard up to version 0.13.x,
+> should cut off Pin-11 from their DFPlayer-Clone.
+> If not, volume control will not work and always play at 100%!

+ +> [!WARNING] +> Consider about switching your DFPlayer's VCC from 3.3V to 5V +> (via solder jumper JP1 on your OpenMower MainBoard) + +**Explanation:**
+I'm with sound since June 2023.
+In the first month, I killed 2 Picos because his tiny Buck-Boost Converter "RT6150" cracked.
+After a short correspondence with Clemens, he pointed me to the DFPlayer +as a possible reason.
+Because: By OM default design, the DFPlayer's VCC is 3.3V (via JP1), +which is provided by the small Buck-Boost Converter on the Pico.
+After I switched my DFPlayer's VCC to 5V (via JP1), I didn't lost any Pico anymore.
+It's not confirmed yet if that really was the reason for my killed Pico's, +and you're invited to validate the assumption by leaving your DFPlayer on 3.3V. **But be warned**, even if the Pico is cheap, it's awful to replace it! + +### Update 10/13/2023 + +As I was still in doubt if it's really necessary to switch DFPlayer's VCC to 5V, +I did some measuring today: + +- Placed an 0.2Ω resistor (4W) within Pico's 3V3 output line +- Measured with an oscilloscope the occuring voltage over the resistor:
+ 0.028 VAVG => divided by 0.2Ω = 140mA => looks fine
+ 0.195 VPP => divided by 0.2Ω = 975mA => hugh :-/ but this is VPP! +- Within the Pico Datasheet it's written (somewhere), thats allowed to draw up to 300mA +- The specs of the Buck-Boost Converter "RT6150" (used on the Pico) say:
+ "*Up to 800mA Continuous Output Current*", as well as
+ "*... current limit.*" + +End of October 2023, I discussed my doubts with Clemens and he answered: +> The short peaks are also what worries me. The overcurrent protection will probably only take effect in the event of a longer overload. That's how you run it above the spec (even if for a short time) and that potentially breaks it at some point + +> [!IMPORTANT] +> You may run `OM_DFP_IS_5V=true` (to get full sound support), even if **not** switched to 5V, but we worry that you might kill your Pico's PMIC after some time + +## Sound Buttons + +| Custom CoverUI V1
Stock C500(A/B) | Custom CoverUI V2 | RM-ECOW-V1.0.0
(NX80i, ...) | RM-EC3-V1.1
(NX100i) | SA/SC-Pro
(240*160 Pixel) | Function | +| ------ | -------- | ---- | ---- | --- | --- | +| Mon | Sun | 4H | 1 | | Volume up | +| Tue | Mon | 6H | 2 | | Volume down + +## DFPlayer Module / Clones + +Beside the original [DFPlayer by DFRobot](https://www.dfrobot.com/product-1121.html) module, there are a couple of "DFPlayer-Mini" clones in the wild. + +If you order a "DFPlayer-Mini" by Amazon or the like, there's a >90% chance that you get one of these clones, instead of the original one. + +That's why I tried to adapt the code in that way, that some of these clones get also supported. + +Check the larger chip on the backside of the module, to verify if your DFPlayer is supported. The following DFPlayer chips are supported/tested at the moment: + +- `DFROBOT LISP3` is the one on my original [DFPlayer by DFRobot](https://www.dfrobot.com/product-1121.html) module +- `MH2024K-24SS` which is one of the older clones, whose support is okay +- `AB23A799755` is a newer clone and seem to be supported very well +- `GD3200B` is also one of the newer clones, but partly fail in support! + +The used DFPlayer library also indicate support for `YX5200-24SS` and `MH2024K-16SS`, but I couldn't test these by myself. If you've one of these, give it a try. + +## SD-Card + +Look like all DFPlayer support a SD-Card size up to 32GB. +As we don't have a lot soundfiles (<10MB), choose the smallest one you can find, format it with a FAT32 file system and copy all these [files and folder](./soundfiles/) to your SD-Card. + +Do not simply delete all files from an ancient SD-Card which you found in a dusty box!
+These tiny DFPlayer beasts are sensitive to SD-Card trash like orphaned files or unclean FAT tables and tend to loop or hang when initialized.
+Always prefer to cleanly format it with a FAT32 partition! + +### Folder/Track Structure + +Due to some incompatibilities with the libs and the clone chips, we unfortunately can't use DFPlayer's internal "advert" functionality for our usage. + +So we had to trick a little bit with the folder structure: + +- OM's old original sounds are now (partly) converted, translated, extended and reside now in a 2 digit, language specific subfolder. They're mainly 'advert' tracks as they're typically spoken adverts, which may interrupt background sounds like noises or music. +- Folder 'mp3' contain background sounds (noises or music), which are hardcoded in the sources. +- The single soundfile in the root folder, is used/required for DFPlayer "auto-play" detection. If you like to change it, you need to take care that the new file is as least 5 seconds long. diff --git a/Firmware/LowLevel/include/debug.h b/Firmware/LowLevel/include/debug.h new file mode 100644 index 00000000..aa063297 --- /dev/null +++ b/Firmware/LowLevel/include/debug.h @@ -0,0 +1,50 @@ +// Created by Apehaenger on 02/02/23. +// +// This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. +// +// Feel free to use the design in your private/educational projects, but don't try to sell the design or products based on it without getting my consent first. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +#ifndef _DEBUG_H_ +#define _DEBUG_H_ + +// Define to stream debugging messages via USB +//#define USB_DEBUG + +#ifdef USB_DEBUG + +#define DEBUG_SERIAL Serial +// #define DfMiniMp3Debug DEBUG_SERIAL // Also output DFPlayer IN/OUT cmd data + +// Some bloody simple debug macros which superfluous '#ifdef USB_DEBUG' ... +#define DEBUG_BEGIN(b) \ + DEBUG_SERIAL.begin(b); \ + while (!DEBUG_SERIAL); +#define DEBUG_PRINTLN(str) DEBUG_SERIAL.println(str) +#define DEBUG_PRINTF(fmt, ...) \ + do { \ + DEBUG_SERIAL.printf(fmt, ##__VA_ARGS__); \ + } while (0) +#define PRINTF_BINARY_PATTERN_INT8 "%c%c%c%c%c%c%c%c" +#define PRINTF_BYTE_TO_BINARY_INT8(i) \ + (((i) & 0x80ll) ? '1' : '0'), (((i) & 0x40ll) ? '1' : '0'), (((i) & 0x20ll) ? '1' : '0'), (((i) & 0x10ll) ? '1' : '0'), \ + (((i) & 0x08ll) ? '1' : '0'), (((i) & 0x04ll) ? '1' : '0'), (((i) & 0x02ll) ? '1' : '0'), (((i) & 0x01ll) ? '1' : '0') + +#else + +#define DEBUG_BEGIN(b) +#define DEBUG_PRINTLN(str) +#define DEBUG_PRINTF(fmt, ...) +#define PRINTF_BINARY_PATTERN_INT8 +#define PRINTF_BYTE_TO_BINARY_INT8(i) +#endif + +#endif // _DEBUG_H_ diff --git a/Firmware/LowLevel/include/soundsystem.h b/Firmware/LowLevel/include/soundsystem.h index f39ecf01..0c5101e0 100644 --- a/Firmware/LowLevel/include/soundsystem.h +++ b/Firmware/LowLevel/include/soundsystem.h @@ -17,53 +17,84 @@ #ifndef _SOUND_SYSTEM_H_ #define _SOUND_SYSTEM_H_ -//#include +#include #include -#include - -#include -#include -#include - - -#define BUFFERSIZE 100 - - -class MP3Sound -{ - - - - - public: - - int16_t anzSoundfiles; // number of files stored on the SD-card - bool playing; - - MP3Sound(); - bool begin(); // init serial stream and soundmodule, anzsoundOnSD : maximum number of available soundfiles on the SD-card - - void playSound(int soundNr); // play soundfile number. This method writes soundfile nr in a list, the method processSounds() (has to run in loop) will play - // the sounds according to the list - - void playSoundAdHoc(int soundNr); // play soundfile number immediately whithout waiting until the end of sound - - void setvolume(int vol); // scales loudness from 0 to 100 % - - int sounds2play(); // returns the number if sounds to play in the list - - int processSounds(); // play all sounds from the list. This method has to be calles cyclic, e.g. every second. +#include +#include +#include - - private: - std::list active_sounds; - bool sound_available; - +#include "datatypes.h" - +namespace soundSystem { +enum class TrackType { + BACKGROUND = 1, // Background tracks are stored in folder mp3 and get interrupted/aborted by higher priority sound like advert + ADVERT, // Advert tracks are stored in language specific folder, i.e. "01" US or "49" German, and interrupt/stop background sounds +}; +struct CardSources { + bool origin : 1; // This sound track is available on origin SD-Card + bool improve_sound : 1; // Available since ImproveSound PR +}; +struct TrackFlags { + bool repeat : 1; // Repeat this track. This flag is limited to background sounds! + bool stop_background : 1; // Stop replaying of a current running background track after this sound got played +}; +struct TrackDef { + uint16_t num; // SD-Card's track number + TrackType type; + CardSources card_sources; + TrackFlags track_flags; + unsigned long pauseAfter = 0; // Cosmetic pause in ms, after advert track got played, before the next sound get processed from queue. + int32_t repeatDuration = 180000; // How long (ms) to repeat a background sound. Default to 180 sec. noise pollution (i.e. VdS 2300) }; - -#endif // _SOUND_SYSTEM_H_ HEADER_FILE +// For better reading, let's use track macro names with the detailed track definition +// clang-format off +#define SOUND_TRACK_BGD_OM_BOOT TrackDef{ 2, TrackType::BACKGROUND, CardSources{.improve_sound=true}, TrackFlags{.repeat=true}} +#define SOUND_TRACK_ADV_HI_IM_STEVE TrackDef{ 1, TrackType::ADVERT, CardSources{.origin=true, .improve_sound=true}, .pauseAfter=1500} +#define SOUND_TRACK_ADV_IMU_INIT_FAILED TrackDef{19, TrackType::ADVERT, CardSources{.improve_sound=true}, .pauseAfter=500} +#define SOUND_TRACK_BGD_OM_ALARM TrackDef{15, TrackType::BACKGROUND, CardSources{.improve_sound=true}, TrackFlags{.repeat=true}, .repeatDuration=20000} +#define SOUND_TRACK_ADV_EMERGENCY_STOP TrackDef{ 8, TrackType::ADVERT, CardSources{.origin=true, .improve_sound=true}, TrackFlags{.stop_background=true}, .pauseAfter = 500} +#define SOUND_TRACK_ADV_EMERGENCY_LIFT TrackDef{ 9, TrackType::ADVERT, CardSources{.origin=true, .improve_sound=true}, TrackFlags{.stop_background=true}, .pauseAfter = 500} +#define SOUND_TRACK_ADV_EMERGENCY_ROS TrackDef{24, TrackType::ADVERT, CardSources{.improve_sound=true}, TrackFlags{.stop_background=true}, .pauseAfter = 500} +#define SOUND_TRACK_ADV_EMERGENCY_CLEARED TrackDef{23, TrackType::ADVERT, CardSources{.improve_sound=true}, TrackFlags{.stop_background=true}, .pauseAfter = 500} +#define SOUND_TRACK_BGD_EMERGENCY_ALARM TrackDef{ 9, TrackType::BACKGROUND, CardSources{.improve_sound=true}} +#define SOUND_TRACK_ADV_OM_STARTUP_SUCCESS TrackDef{ 2, TrackType::ADVERT, CardSources{.origin=true, .improve_sound=true}, .pauseAfter = 1500} +#define SOUND_TRACK_ADV_ROS_INIT TrackDef{ 3, TrackType::ADVERT, CardSources{.origin=true, .improve_sound=true}, TrackFlags{.stop_background=true}, .pauseAfter = 500} +#define SOUND_TRACK_BGD_ROS_BOOT TrackDef{ 5, TrackType::BACKGROUND, CardSources{.improve_sound=true}, TrackFlags{.repeat=true}} +#define SOUND_TRACK_ADV_ROS_STARTUP_SUCCESS TrackDef{16, TrackType::ADVERT, CardSources{.improve_sound=true}, TrackFlags{.stop_background=true}} +#define SOUND_TRACK_ADV_ROS_STOPPED TrackDef{17, TrackType::ADVERT, CardSources{.improve_sound=true}, TrackFlags{.stop_background=true}} +#define SOUND_TRACK_ADV_MAP_RECORD_START TrackDef{ 4, TrackType::ADVERT, CardSources{.origin=true, .improve_sound=true}, TrackFlags{.stop_background=true}} +#define SOUND_TRACK_ADV_AUTONOMOUS_START TrackDef{12, TrackType::ADVERT, CardSources{.origin=true, .improve_sound=true}, TrackFlags{.stop_background=true}, .pauseAfter = 1500} +#define SOUND_TRACK_ADV_RAIN TrackDef{10, TrackType::ADVERT, CardSources{.origin=true, .improve_sound=true}, TrackFlags{.stop_background=true}, .pauseAfter = 3000} +#define SOUND_TRACK_ADV_RTKGPS_WAIT TrackDef{ 5, TrackType::ADVERT, CardSources{.origin=true, .improve_sound=true}, .pauseAfter = 1500} +#define SOUND_TRACK_ADV_RTKGPS_POOR TrackDef{20, TrackType::BACKGROUND, CardSources{.improve_sound=true}} +#define SOUND_TRACK_ADV_RTKGPS_MODERATE TrackDef{21, TrackType::BACKGROUND, CardSources{.improve_sound=true}} +#define SOUND_TRACK_ADV_RTKGPS_GOOD TrackDef{22, TrackType::BACKGROUND, CardSources{.improve_sound=true}} +#define SOUND_TRACK_BGD_MUSIC_PINK_PANTHER TrackDef{12, TrackType::BACKGROUND, CardSources{.improve_sound=true}} +#define SOUND_TRACK_ADV_UP TrackDef{21, TrackType::ADVERT, CardSources{.improve_sound=true}, .pauseAfter = 100} +#define SOUND_TRACK_ADV_DOWN TrackDef{20, TrackType::ADVERT, CardSources{.improve_sound=true}, .pauseAfter = 100} +#define SOUND_TRACK_ADV_LANGUAGE TrackDef{22, TrackType::ADVERT, CardSources{.improve_sound=true}, .pauseAfter = 100} +#define SOUND_TRACK_ADV_MOW_DONE_DOCK TrackDef{11, TrackType::ADVERT, CardSources{.improve_sound=true}, TrackFlags{.stop_background=true}, .pauseAfter = 3000} +// clang-format on + +bool begin(); // Init serial stream, soundmodule and sound_available_ + +void playSound(const TrackDef&); // Play sound trackDef. This method writes sound trackDef in a list, the method processSounds() (has to run in loop) will play the sounds according to the list +void playSoundAdHoc(const TrackDef&); // Play sound track number immediately without waiting until the end of sound + +void setDFPis5V(const bool t_dfpis5v); // Set if DFP is set to 5V Vcc +void setEnableBackground(const bool); // Set if background sounds shall get played (true) or not (false) + +void setLanguage(const iso639_1 language_p, const bool quiet = false); // Set language to the pointing ISO639-1 (2 char) language code and announce if changed and not quiet + +void setVolume(const uint8_t t_vol); // Set volume (0-100%) +uint8_t setVolumeUp(); // Scale volume up by VOLUME_STEPS and return new volume (%) +uint8_t setVolumeDown(); // Scale volume down by VOLUME_STEPS and return new volume (%) + +void applyConfig(const ll_high_level_config t_config, const bool quiet); // Apply the volume specific config options + +void processSounds(const ll_status t_ll_state, const bool t_ros_running, const ll_high_level_state t_hl_state); // This method has to be called cyclic, e.g. every second. +} // namespace soundSystem +#endif // _SOUND_SYSTEM_H_ HEADER_FILE diff --git a/Firmware/LowLevel/platformio.ini b/Firmware/LowLevel/platformio.ini index 8cecc9b1..5d691517 100644 --- a/Firmware/LowLevel/platformio.ini +++ b/Firmware/LowLevel/platformio.ini @@ -42,39 +42,42 @@ board_build.f_cpu = 133000000L lib_ldf_mode = off debug_build_flags = -O0 -g -ggdb -build_src_filter = +<*> -<.git/> -<.svn/> - - +build_src_filter = +<*> -<.git/> -<.svn/> - + +[sound] +lib_deps = makuna/DFPlayer Mini Mp3 by Makuna@^1.2.3 [env:0_13_X] lib_ignore = JY901_SERIAL,JY901_I2C lib_deps = ${env.lib_deps} + ${sound.lib_deps} stm32duino/STM32duino LSM6DSO@^2.0.3 - powerbroker2/DFPlayerMini_Fast@^1.2.4 build_src_filter = ${env.build_src_filter} + -build_flags = ${env.build_flags} -DHW_0_13_X +build_flags = ${env.build_flags} -DHW_0_13_X -DENABLE_SOUND_MODULE [env:0_12_X] lib_ignore = JY901_SERIAL,JY901_I2C lib_deps = ${env.lib_deps} + ${sound.lib_deps} stm32duino/STM32duino LSM6DSO@^2.0.3 jpiat/PioSPI@^0.0.1 - powerbroker2/DFPlayerMini_Fast@^1.2.4 -build_src_filter = ${env.build_src_filter} + + +build_src_filter = ${env.build_src_filter} + build_flags = ${env.build_flags} -DHW_0_12_X -DENABLE_SOUND_MODULE [env:0_11_X_MPU9250] lib_ignore = JY901_SERIAL,JY901_I2C lib_deps = ${env.lib_deps} + ${sound.lib_deps} bolderflight/Bolder Flight Systems MPU9250@^1.0.2 - powerbroker2/DFPlayerMini_Fast@^1.2.4 -build_src_filter = ${env.build_src_filter} + + +build_src_filter = ${env.build_src_filter} + build_flags = ${env.build_flags} -DHW_0_11_X -DENABLE_SOUND_MODULE [env:0_11_X_WT901] -build_src_filter = ${env.build_src_filter} + + +build_src_filter = ${env.build_src_filter} + lib_ignore = JY901_SERIAL lib_deps = ${env.lib_deps} - powerbroker2/DFPlayerMini_Fast@^1.2.4 + ${sound.lib_deps} JY901_I2C build_flags = ${env.build_flags} -DWT901_I2C -DHW_0_11_X -DENABLE_SOUND_MODULE @@ -82,16 +85,16 @@ build_flags = ${env.build_flags} -DWT901_I2C -DHW_0_11_X -DENABLE_SOUND_MODULE [env:0_10_X_MPU9250] lib_ignore = JY901_SERIAL,JY901_I2C lib_deps = ${env.lib_deps} + ${sound.lib_deps} bolderflight/Bolder Flight Systems MPU9250@^1.0.2 - powerbroker2/DFPlayerMini_Fast@^1.2.4 -build_src_filter = ${env.build_src_filter} + + +build_src_filter = ${env.build_src_filter} + build_flags = ${env.build_flags} -DHW_0_10_X -DENABLE_SOUND_MODULE [env:0_10_X_WT901] -build_src_filter = ${env.build_src_filter} + + +build_src_filter = ${env.build_src_filter} + lib_ignore = JY901_SERIAL lib_deps = ${env.lib_deps} - powerbroker2/DFPlayerMini_Fast@^1.2.4 + ${sound.lib_deps} JY901_I2C build_flags = ${env.build_flags} -DWT901_I2C -DHW_0_10_X -DENABLE_SOUND_MODULE @@ -99,22 +102,22 @@ build_flags = ${env.build_flags} -DWT901_I2C -DHW_0_10_X -DENABLE_SOUND_MODULE [env:0_9_X_MPU9250] lib_ignore = JY901_SERIAL,JY901_I2C lib_deps = ${env.lib_deps} + ${sound.lib_deps} bolderflight/Bolder Flight Systems MPU9250@^1.0.2 - powerbroker2/DFPlayerMini_Fast@^1.2.4 -build_src_filter = ${env.build_src_filter} + + +build_src_filter = ${env.build_src_filter} + build_flags = ${env.build_flags} -DHW_0_9_X -DENABLE_SOUND_MODULE [env:0_9_X_WT901_INSTEAD_OF_SOUND] lib_ignore = JY901_I2C -build_src_filter = ${env.build_src_filter} + +build_src_filter = ${env.build_src_filter} + - lib_deps = ${env.lib_deps} JY901_SERIAL build_flags = ${env.build_flags} -DWT901_INSTEAD_OF_SOUND -DHW_0_9_X [env:0_9_X_WT901] lib_ignore = JY901_I2C -build_src_filter = ${env.build_src_filter} + + +build_src_filter = ${env.build_src_filter} + lib_deps = ${env.lib_deps} + ${sound.lib_deps} JY901_SERIAL - powerbroker2/DFPlayerMini_Fast@^1.2.4 build_flags = ${env.build_flags} -DWT901 -DHW_0_9_X -DENABLE_SOUND_MODULE diff --git a/Firmware/LowLevel/soundfiles/0001Hi, I am Steve, your.mp3 b/Firmware/LowLevel/soundfiles/0001Hi, I am Steve, your.mp3 deleted file mode 100644 index 2be965de..00000000 Binary files a/Firmware/LowLevel/soundfiles/0001Hi, I am Steve, your.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0001_success_02-68338.mp3 b/Firmware/LowLevel/soundfiles/0001_success_02-68338.mp3 new file mode 100644 index 00000000..0c520622 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/0001_success_02-68338.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/0002open mower start up .mp3 b/Firmware/LowLevel/soundfiles/0002open mower start up .mp3 deleted file mode 100644 index 175557b2..00000000 Binary files a/Firmware/LowLevel/soundfiles/0002open mower start up .mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0003Initializing robot o.mp3 b/Firmware/LowLevel/soundfiles/0003Initializing robot o.mp3 deleted file mode 100644 index 6d923168..00000000 Binary files a/Firmware/LowLevel/soundfiles/0003Initializing robot o.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0004Starting map area re.mp3 b/Firmware/LowLevel/soundfiles/0004Starting map area re.mp3 deleted file mode 100644 index 4bb59e8e..00000000 Binary files a/Firmware/LowLevel/soundfiles/0004Starting map area re.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0005waiting for rtk gps .mp3 b/Firmware/LowLevel/soundfiles/0005waiting for rtk gps .mp3 deleted file mode 100644 index c8660270..00000000 Binary files a/Firmware/LowLevel/soundfiles/0005waiting for rtk gps .mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0006calculating map dime.mp3 b/Firmware/LowLevel/soundfiles/0006calculating map dime.mp3 deleted file mode 100644 index 4740b5dd..00000000 Binary files a/Firmware/LowLevel/soundfiles/0006calculating map dime.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0007initiate mowing sequ.mp3 b/Firmware/LowLevel/soundfiles/0007initiate mowing sequ.mp3 deleted file mode 100644 index f1c050db..00000000 Binary files a/Firmware/LowLevel/soundfiles/0007initiate mowing sequ.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0008emergency stop butto.mp3 b/Firmware/LowLevel/soundfiles/0008emergency stop butto.mp3 deleted file mode 100644 index 13912b22..00000000 Binary files a/Firmware/LowLevel/soundfiles/0008emergency stop butto.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0009emergency wheel lift.mp3 b/Firmware/LowLevel/soundfiles/0009emergency wheel lift.mp3 deleted file mode 100644 index e5cb9f1f..00000000 Binary files a/Firmware/LowLevel/soundfiles/0009emergency wheel lift.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0010rain detected, headi.mp3 b/Firmware/LowLevel/soundfiles/0010rain detected, headi.mp3 deleted file mode 100644 index 463dcf6d..00000000 Binary files a/Firmware/LowLevel/soundfiles/0010rain detected, headi.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0011open mower has compl.mp3 b/Firmware/LowLevel/soundfiles/0011open mower has compl.mp3 deleted file mode 100644 index 0d9be84b..00000000 Binary files a/Firmware/LowLevel/soundfiles/0011open mower has compl.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0012stay back, autononou.mp3 b/Firmware/LowLevel/soundfiles/0012stay back, autononou.mp3 deleted file mode 100644 index c6554940..00000000 Binary files a/Firmware/LowLevel/soundfiles/0012stay back, autononou.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/0013ups, I did it again.mp3 b/Firmware/LowLevel/soundfiles/0013ups, I did it again.mp3 deleted file mode 100644 index 5d670faf..00000000 Binary files a/Firmware/LowLevel/soundfiles/0013ups, I did it again.mp3 and /dev/null differ diff --git a/Firmware/LowLevel/soundfiles/01/001_Hi, I am Steve, your.mp3 b/Firmware/LowLevel/soundfiles/01/001_Hi, I am Steve, your.mp3 new file mode 100644 index 00000000..f152ae4d Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/001_Hi, I am Steve, your.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/002_open mower start up .mp3 b/Firmware/LowLevel/soundfiles/01/002_open mower start up .mp3 new file mode 100644 index 00000000..9a8434e9 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/002_open mower start up .mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/003_Initializing robot o.mp3 b/Firmware/LowLevel/soundfiles/01/003_Initializing robot o.mp3 new file mode 100644 index 00000000..697e28a3 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/003_Initializing robot o.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/004_Starting map area re.mp3 b/Firmware/LowLevel/soundfiles/01/004_Starting map area re.mp3 new file mode 100644 index 00000000..856ea516 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/004_Starting map area re.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/005_waiting for rtk gps .mp3 b/Firmware/LowLevel/soundfiles/01/005_waiting for rtk gps .mp3 new file mode 100644 index 00000000..5113dd1f Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/005_waiting for rtk gps .mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/006_calculating map dime.mp3 b/Firmware/LowLevel/soundfiles/01/006_calculating map dime.mp3 new file mode 100644 index 00000000..4a3493f2 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/006_calculating map dime.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/007_initiate mowing sequ.mp3 b/Firmware/LowLevel/soundfiles/01/007_initiate mowing sequ.mp3 new file mode 100644 index 00000000..bcd46487 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/007_initiate mowing sequ.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/008_stop button.mp3 b/Firmware/LowLevel/soundfiles/01/008_stop button.mp3 new file mode 100644 index 00000000..0d0fcf31 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/008_stop button.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/009_emergency wheel lift.mp3 b/Firmware/LowLevel/soundfiles/01/009_emergency wheel lift.mp3 new file mode 100644 index 00000000..edf72b67 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/009_emergency wheel lift.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/010_rain detected, headi.mp3 b/Firmware/LowLevel/soundfiles/01/010_rain detected, headi.mp3 new file mode 100644 index 00000000..5682e73e Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/010_rain detected, headi.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/011_open mower has compl.mp3 b/Firmware/LowLevel/soundfiles/01/011_open mower has compl.mp3 new file mode 100644 index 00000000..c07fafd8 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/011_open mower has compl.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/012_stay back, autononou.mp3 b/Firmware/LowLevel/soundfiles/01/012_stay back, autononou.mp3 new file mode 100644 index 00000000..b75255cd Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/012_stay back, autononou.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/013_ups, I did it again.mp3 b/Firmware/LowLevel/soundfiles/01/013_ups, I did it again.mp3 new file mode 100644 index 00000000..f606f7cf Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/013_ups, I did it again.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/0014Hallo, ich bin Tobi,.mp3 b/Firmware/LowLevel/soundfiles/01/014_Hallo, ich bin Tobi,.mp3 similarity index 100% rename from Firmware/LowLevel/soundfiles/0014Hallo, ich bin Tobi,.mp3 rename to Firmware/LowLevel/soundfiles/01/014_Hallo, ich bin Tobi,.mp3 diff --git a/Firmware/LowLevel/soundfiles/0015Hi, I am Tobi, your .mp3 b/Firmware/LowLevel/soundfiles/01/015_Hi, I am Tobi, your .mp3 similarity index 100% rename from Firmware/LowLevel/soundfiles/0015Hi, I am Tobi, your .mp3 rename to Firmware/LowLevel/soundfiles/01/015_Hi, I am Tobi, your .mp3 diff --git a/Firmware/LowLevel/soundfiles/01/016_ROS startup successful.mp3 b/Firmware/LowLevel/soundfiles/01/016_ROS startup successful.mp3 new file mode 100644 index 00000000..77c5e6cc Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/016_ROS startup successful.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/017_ROS stopped.mp3 b/Firmware/LowLevel/soundfiles/01/017_ROS stopped.mp3 new file mode 100644 index 00000000..86c57f73 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/017_ROS stopped.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/018_RPI shut down and powered off.mp3 b/Firmware/LowLevel/soundfiles/01/018_RPI shut down and powered off.mp3 new file mode 100644 index 00000000..1f05b331 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/018_RPI shut down and powered off.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/019_IMU initialization failed.mp3 b/Firmware/LowLevel/soundfiles/01/019_IMU initialization failed.mp3 new file mode 100644 index 00000000..79344492 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/019_IMU initialization failed.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/020_down.mp3 b/Firmware/LowLevel/soundfiles/01/020_down.mp3 new file mode 100644 index 00000000..59c39515 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/020_down.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/021_up.mp3 b/Firmware/LowLevel/soundfiles/01/021_up.mp3 new file mode 100644 index 00000000..b0b9fa9e Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/021_up.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/022_Switched to english language.mp3 b/Firmware/LowLevel/soundfiles/01/022_Switched to english language.mp3 new file mode 100644 index 00000000..6580ab79 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/022_Switched to english language.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/023_Emergency cleared.mp3 b/Firmware/LowLevel/soundfiles/01/023_Emergency cleared.mp3 new file mode 100644 index 00000000..e1dea551 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/023_Emergency cleared.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/024_Emergency triggered by ROS.mp3 b/Firmware/LowLevel/soundfiles/01/024_Emergency triggered by ROS.mp3 new file mode 100644 index 00000000..f5b7d8d1 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/024_Emergency triggered by ROS.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/025_Emergency triggered by collision sensor.mp3 b/Firmware/LowLevel/soundfiles/01/025_Emergency triggered by collision sensor.mp3 new file mode 100644 index 00000000..4e3325e1 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/01/025_Emergency triggered by collision sensor.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/01/GoogleTTS_Strings.txt b/Firmware/LowLevel/soundfiles/01/GoogleTTS_Strings.txt new file mode 100644 index 00000000..a894826f --- /dev/null +++ b/Firmware/LowLevel/soundfiles/01/GoogleTTS_Strings.txt @@ -0,0 +1,36 @@ +-- file leading nr -- ------ string to convert ------------ + +001 Hi, I am Steve, your open mower green care is my profession +002 open mower start up successful +003 Initializing robot operating system +004 Starting map area recording +005 waiting for rtk gps signal +006 calculating map dimensions +007 initiate mowing sequence +008 stop button triggered +009 emergency wheel lift sensor triggered +010 rain detected, heading back to base +011 open mower has completed mowing the lawn, heading back to charging station +012 stay back, autononous robot mower in use +013 ups, I did it again +014 Hallo, ich bin Tobi, dein intelligenter Rasemäher, Rasenpflege ist mein Beruf +015 Hello, I am Tobi, your smart mower, green care ist my profession +016 Robot operating system start up successful + +017 Robot operating system stopped +018 Raspberry Pi shut down and powered off. It's save to power off your open mower now +019 IMU initialization failed +020 down +021 up +022 Switched to English Language +023 Emergency cleared +024 Emergency triggered by Robot operating system +025 Emergency triggered by collision sensor + + +New adverts >= 0017 sampled via Google TTS with settings: +Language: English (US), Neural2, en-US-Neural2-D +Small home speaker, Speed = 1.00, Pitch 0 + +Amplified to a max of -5db +Saved as mono mp3 with medium quality diff --git a/Firmware/LowLevel/soundfiles/49/001_Hallo, ich bin Steve.mp3 b/Firmware/LowLevel/soundfiles/49/001_Hallo, ich bin Steve.mp3 new file mode 100644 index 00000000..76300190 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/001_Hallo, ich bin Steve.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/002_open mower erfolgreich gestartet.mp3 b/Firmware/LowLevel/soundfiles/49/002_open mower erfolgreich gestartet.mp3 new file mode 100644 index 00000000..a717a94e Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/002_open mower erfolgreich gestartet.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/003_Initialisiere Roboter Betriebssystem.mp3 b/Firmware/LowLevel/soundfiles/49/003_Initialisiere Roboter Betriebssystem.mp3 new file mode 100644 index 00000000..1a885aa3 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/003_Initialisiere Roboter Betriebssystem.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/004_Starte Aufzeichnung des Lageplans.mp3 b/Firmware/LowLevel/soundfiles/49/004_Starte Aufzeichnung des Lageplans.mp3 new file mode 100644 index 00000000..3323841a Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/004_Starte Aufzeichnung des Lageplans.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/005_Warte auf RTK-GPS Signal.mp3 b/Firmware/LowLevel/soundfiles/49/005_Warte auf RTK-GPS Signal.mp3 new file mode 100644 index 00000000..677f263d Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/005_Warte auf RTK-GPS Signal.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/006_Berechne Abmessungen des Lageplans.mp3 b/Firmware/LowLevel/soundfiles/49/006_Berechne Abmessungen des Lageplans.mp3 new file mode 100644 index 00000000..46576267 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/006_Berechne Abmessungen des Lageplans.mp3 differ diff --git "a/Firmware/LowLevel/soundfiles/49/007_Starte den M\303\244hvorgang.mp3" "b/Firmware/LowLevel/soundfiles/49/007_Starte den M\303\244hvorgang.mp3" new file mode 100644 index 00000000..174ee834 Binary files /dev/null and "b/Firmware/LowLevel/soundfiles/49/007_Starte den M\303\244hvorgang.mp3" differ diff --git a/Firmware/LowLevel/soundfiles/49/008_Stop-Taste gedrueckt.mp3 b/Firmware/LowLevel/soundfiles/49/008_Stop-Taste gedrueckt.mp3 new file mode 100644 index 00000000..2f62961f Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/008_Stop-Taste gedrueckt.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/009_Not-Aus durch Rad Hebesensor.mp3 b/Firmware/LowLevel/soundfiles/49/009_Not-Aus durch Rad Hebesensor.mp3 new file mode 100644 index 00000000..94a220d2 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/009_Not-Aus durch Rad Hebesensor.mp3 differ diff --git "a/Firmware/LowLevel/soundfiles/49/010_Regen festgestellt, fahre zur\303\274ck zur Basis.mp3" "b/Firmware/LowLevel/soundfiles/49/010_Regen festgestellt, fahre zur\303\274ck zur Basis.mp3" new file mode 100644 index 00000000..3cf8ce70 Binary files /dev/null and "b/Firmware/LowLevel/soundfiles/49/010_Regen festgestellt, fahre zur\303\274ck zur Basis.mp3" differ diff --git "a/Firmware/LowLevel/soundfiles/49/011_open-mower hat das Rasenm\303\244hen beendet.mp3" "b/Firmware/LowLevel/soundfiles/49/011_open-mower hat das Rasenm\303\244hen beendet.mp3" new file mode 100644 index 00000000..599b77c7 Binary files /dev/null and "b/Firmware/LowLevel/soundfiles/49/011_open-mower hat das Rasenm\303\244hen beendet.mp3" differ diff --git a/Firmware/LowLevel/soundfiles/49/012_Vorsicht, autonomer Maehroboter im Einsatz.mp3 b/Firmware/LowLevel/soundfiles/49/012_Vorsicht, autonomer Maehroboter im Einsatz.mp3 new file mode 100644 index 00000000..cdd5662a Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/012_Vorsicht, autonomer Maehroboter im Einsatz.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/013_Au-weia, schon wieder.mp3 b/Firmware/LowLevel/soundfiles/49/013_Au-weia, schon wieder.mp3 new file mode 100644 index 00000000..3c1c5375 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/013_Au-weia, schon wieder.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/016_Start des Roboter Betriebssystems erfolgreich.mp3 b/Firmware/LowLevel/soundfiles/49/016_Start des Roboter Betriebssystems erfolgreich.mp3 new file mode 100644 index 00000000..7c675a02 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/016_Start des Roboter Betriebssystems erfolgreich.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/017_Das Roboter Betriebssystem wurde gestoppt.mp3 b/Firmware/LowLevel/soundfiles/49/017_Das Roboter Betriebssystem wurde gestoppt.mp3 new file mode 100644 index 00000000..752f3c87 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/017_Das Roboter Betriebssystem wurde gestoppt.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/018_Der Raspberry Pai wurde herunter gefahren.mp3 b/Firmware/LowLevel/soundfiles/49/018_Der Raspberry Pai wurde herunter gefahren.mp3 new file mode 100644 index 00000000..554f5030 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/018_Der Raspberry Pai wurde herunter gefahren.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/019_Die IMU Initialisierung ist fehlgeschlagen.mp3 b/Firmware/LowLevel/soundfiles/49/019_Die IMU Initialisierung ist fehlgeschlagen.mp3 new file mode 100644 index 00000000..38b0cf19 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/019_Die IMU Initialisierung ist fehlgeschlagen.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/020_leiser.mp3 b/Firmware/LowLevel/soundfiles/49/020_leiser.mp3 new file mode 100644 index 00000000..43938a95 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/020_leiser.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/021_lauter.mp3 b/Firmware/LowLevel/soundfiles/49/021_lauter.mp3 new file mode 100644 index 00000000..9c8ad631 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/021_lauter.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/022_auf deutsche sprache umgestellt.mp3 b/Firmware/LowLevel/soundfiles/49/022_auf deutsche sprache umgestellt.mp3 new file mode 100644 index 00000000..574103c8 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/022_auf deutsche sprache umgestellt.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/023_Notaus aufgehoben.mp3 b/Firmware/LowLevel/soundfiles/49/023_Notaus aufgehoben.mp3 new file mode 100644 index 00000000..a38eda9b Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/023_Notaus aufgehoben.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/024_Not-Aus durch ROS.mp3 b/Firmware/LowLevel/soundfiles/49/024_Not-Aus durch ROS.mp3 new file mode 100644 index 00000000..8ed89aa9 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/024_Not-Aus durch ROS.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/025_Not-Aus durch Kollisionssensor.mp3 b/Firmware/LowLevel/soundfiles/49/025_Not-Aus durch Kollisionssensor.mp3 new file mode 100644 index 00000000..93255af1 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/49/025_Not-Aus durch Kollisionssensor.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/49/GoogleTTS_Strings.txt b/Firmware/LowLevel/soundfiles/49/GoogleTTS_Strings.txt new file mode 100644 index 00000000..86d51acd --- /dev/null +++ b/Firmware/LowLevel/soundfiles/49/GoogleTTS_Strings.txt @@ -0,0 +1,33 @@ +-- file leading nr -- ------ string to convert ------------ + +001 Hallo, ich bin Steve, dein intelligenter Rasemäher, Rasenpflege ist mein Beruf +002 "open mower" erfolgreich gestartet +003 Initialisiere Roboter Betriebssystem +004 Starte Aufzeichnung des Lageplans +005 Warte auf RTK-GPS Signal +006 Berechne Abmessungen des Lageplans +007 Starte den Mähvorgang +008 Stop-Taste gedrückt +009 Not-Aus durch Rad Hebesensor +010 Regen festgestellt, fahre zurück zur Basis +011 "open mower" hat das Rasenmähen beendet und fährt zurück zu Ladestation +012 Vorsicht! Autonomer Mähroboter im Einsatz +013 Au-weia, schon wieder +016 Start des Roboter Betriebssystems erfolgreich +017 Das Roboter Betriebssystem wurde gestoppt +018 Der Raspberry Pai wurde heruntergefahren, Du kannst Deinen open mower nun ausschalten +019 Die ei-em-ju-Initialisierung ist fehlgeschlagen +020 leiser +021 lauter +022 auf deutsche sprache umgestellt +023 Notaus aufgehoben +024 Not-Aus durch Roboter Betriebssystem +025 Not-Aus durch Kollisionssensor + + +New german adverts sampled via Google TTS with settings: +Language: Deutsch (Deutschland), Neural2, de-DE-Neural2-B +Small home speaker, Speed = 1.00, Pitch 0 + +Amplified to a max of -5db +Saved as mono mp3 with medium quality diff --git a/Firmware/LowLevel/soundfiles/GoogleTTS_Strings.txt b/Firmware/LowLevel/soundfiles/GoogleTTS_Strings.txt deleted file mode 100755 index f1e1405d..00000000 --- a/Firmware/LowLevel/soundfiles/GoogleTTS_Strings.txt +++ /dev/null @@ -1,18 +0,0 @@ --- file leading nr -- ------ string to convert ------------ - -0001 Hi, I am Steve, your open mower green care is my profession -0002 open mower start up successful -0003 Initializing robot operating system -0004 Starting map area recording -0005 waiting for rtk gps signal -0006 calculating map dimensions -0007 initiate mowing sequence -0008 emergency stop button triggered -0009 emergency wheel lift sensor triggered -0010 rain detected, heading back to base -0011 open mower has completed mowing the lawn, heading back to charging station -0012 stay back, autononous robot mower in use -0013 ups, I did it again -0014 Hallo, ich bin Tobi, dein intelligenter Rasemäher, Rasenpflege ist mein Beruf -0015 Hello, I am Tobi, your smart mower, green care ist my profession - diff --git a/Firmware/LowLevel/soundfiles/mp3/0001_intergalactic-comm-scifi-8238.mp3 b/Firmware/LowLevel/soundfiles/mp3/0001_intergalactic-comm-scifi-8238.mp3 new file mode 100644 index 00000000..1e01b4b5 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0001_intergalactic-comm-scifi-8238.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0002_success_02-68338.mp3 b/Firmware/LowLevel/soundfiles/mp3/0002_success_02-68338.mp3 new file mode 100644 index 00000000..0c520622 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0002_success_02-68338.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0003_scifi_ruined_environment.mp3 b/Firmware/LowLevel/soundfiles/mp3/0003_scifi_ruined_environment.mp3 new file mode 100644 index 00000000..29c5fcab Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0003_scifi_ruined_environment.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0004_computer-heart.mp3 b/Firmware/LowLevel/soundfiles/mp3/0004_computer-heart.mp3 new file mode 100644 index 00000000..a24e728d Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0004_computer-heart.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0005_evil-robot-or-spaceship.mp3 b/Firmware/LowLevel/soundfiles/mp3/0005_evil-robot-or-spaceship.mp3 new file mode 100644 index 00000000..44bbc4f9 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0005_evil-robot-or-spaceship.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0007_montage-machine.mp3 b/Firmware/LowLevel/soundfiles/mp3/0007_montage-machine.mp3 new file mode 100644 index 00000000..8ccfc4c5 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0007_montage-machine.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0008_r2d2-sfx.mp3 b/Firmware/LowLevel/soundfiles/mp3/0008_r2d2-sfx.mp3 new file mode 100644 index 00000000..9a78f9e6 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0008_r2d2-sfx.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0009_Minion-Fire-Alarm.mp3 b/Firmware/LowLevel/soundfiles/mp3/0009_Minion-Fire-Alarm.mp3 new file mode 100644 index 00000000..c349a7a5 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0009_Minion-Fire-Alarm.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0012_Pink-Panther.mp3 b/Firmware/LowLevel/soundfiles/mp3/0012_Pink-Panther.mp3 new file mode 100644 index 00000000..7787cf18 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0012_Pink-Panther.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0013_Highway-to-Hell.mp3 b/Firmware/LowLevel/soundfiles/mp3/0013_Highway-to-Hell.mp3 new file mode 100644 index 00000000..da3be70c Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0013_Highway-to-Hell.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0014_Alarm01.mp3 b/Firmware/LowLevel/soundfiles/mp3/0014_Alarm01.mp3 new file mode 100644 index 00000000..0fbfd93d Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0014_Alarm01.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0015_Alarm02.mp3 b/Firmware/LowLevel/soundfiles/mp3/0015_Alarm02.mp3 new file mode 100644 index 00000000..952099b2 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0015_Alarm02.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0020_gps-poor.mp3 b/Firmware/LowLevel/soundfiles/mp3/0020_gps-poor.mp3 new file mode 100644 index 00000000..2f79c891 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0020_gps-poor.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0021_gps-acceptable.mp3 b/Firmware/LowLevel/soundfiles/mp3/0021_gps-acceptable.mp3 new file mode 100644 index 00000000..689a9be7 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0021_gps-acceptable.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0022_gps-good.mp3 b/Firmware/LowLevel/soundfiles/mp3/0022_gps-good.mp3 new file mode 100644 index 00000000..4f2d6b37 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0022_gps-good.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0100_SingingInTheRain_Remix.mp3 b/Firmware/LowLevel/soundfiles/mp3/0100_SingingInTheRain_Remix.mp3 new file mode 100644 index 00000000..cec39cc5 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0100_SingingInTheRain_Remix.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0101_ItsRainingAgain_Supertramp.mp3 b/Firmware/LowLevel/soundfiles/mp3/0101_ItsRainingAgain_Supertramp.mp3 new file mode 100644 index 00000000..35d13968 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0101_ItsRainingAgain_Supertramp.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0102_OnlyHappyWhenItRains_Cover.mp3 b/Firmware/LowLevel/soundfiles/mp3/0102_OnlyHappyWhenItRains_Cover.mp3 new file mode 100644 index 00000000..7930cdc6 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0102_OnlyHappyWhenItRains_Cover.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0200_Minions Fart.mp3 b/Firmware/LowLevel/soundfiles/mp3/0200_Minions Fart.mp3 new file mode 100644 index 00000000..34aeb728 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0200_Minions Fart.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0201_Sheep-Lamb-Bah-B.mp3 b/Firmware/LowLevel/soundfiles/mp3/0201_Sheep-Lamb-Bah-B.mp3 new file mode 100644 index 00000000..0866686d Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0201_Sheep-Lamb-Bah-B.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0202_Cow-Mooing-C.mp3 b/Firmware/LowLevel/soundfiles/mp3/0202_Cow-Mooing-C.mp3 new file mode 100644 index 00000000..772e20f4 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0202_Cow-Mooing-C.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0203_wet-fart.mp3 b/Firmware/LowLevel/soundfiles/mp3/0203_wet-fart.mp3 new file mode 100644 index 00000000..71579a9b Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0203_wet-fart.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0204_vegan-fart-voice.mp3 b/Firmware/LowLevel/soundfiles/mp3/0204_vegan-fart-voice.mp3 new file mode 100644 index 00000000..7a17467b Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0204_vegan-fart-voice.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0205_Burp-A2.mp3 b/Firmware/LowLevel/soundfiles/mp3/0205_Burp-A2.mp3 new file mode 100644 index 00000000..11c5ceb7 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0205_Burp-A2.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0300_MamaImComingHome_Demo.mp3 b/Firmware/LowLevel/soundfiles/mp3/0300_MamaImComingHome_Demo.mp3 new file mode 100644 index 00000000..1347f8c0 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0300_MamaImComingHome_Demo.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0301_ComingHome_Remix.mp3 b/Firmware/LowLevel/soundfiles/mp3/0301_ComingHome_Remix.mp3 new file mode 100644 index 00000000..2a50eefa Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0301_ComingHome_Remix.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0302_SweetHomeAlabama.mp3 b/Firmware/LowLevel/soundfiles/mp3/0302_SweetHomeAlabama.mp3 new file mode 100644 index 00000000..621b3659 Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0302_SweetHomeAlabama.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/0303_Darth-Vader.mp3 b/Firmware/LowLevel/soundfiles/mp3/0303_Darth-Vader.mp3 new file mode 100644 index 00000000..0b75c45b Binary files /dev/null and b/Firmware/LowLevel/soundfiles/mp3/0303_Darth-Vader.mp3 differ diff --git a/Firmware/LowLevel/soundfiles/mp3/Legal.md b/Firmware/LowLevel/soundfiles/mp3/Legal.md new file mode 100644 index 00000000..eba00f61 --- /dev/null +++ b/Firmware/LowLevel/soundfiles/mp3/Legal.md @@ -0,0 +1,13 @@ +Questionable sound origins + +| Title | License | Link | +| ----- | ------- | ----------- | +| 0012_Pink-Panther.mp3 | Free Ringtone | https://klingeltonekostenlos.de/pink-panther-1/ | +| 0013_Highway-to-Hell.mp3 | Free / Covered by Philbert Audio | https://soundcloud.com/simonpvo/acdc-highway-to-hell | +| 0100_Singin' In The Rain - Gene Kelly.mp3 | Free / The Bangerz Remix | https://soundcloud.com/exit33/singing-in-the-rain-the-bangerz-remix | +| 0101_ItsRainingAgain_Supertramp.mp3 | Freely available @ Internet Archive.org | https://archive.org/details/supertramp-music/Supertramp+-+It's+Raining+Again.mp3 | +| 0102_OnlyHappyWhenItRains_Cover.mp3 | Covered by Giulio Preuch | https://soundcloud.com/giulio-peruch/garbage-only-happy-when-it-rains-cover | +| 0206_Darth-Vader.mp3 | Free Ringtone | https://klingeltonekostenlos.de/darth-vader/ | +| 0300_MamaImComingHome_Demo.mp3 | Public Available Demo | Shortened from https://open.spotify.com/intl-de/track/1yQiYwG2N8WiWHlhj3CX3T Demo | +| 0301_ComingHome_Remix.mp3 | Free / Remix | https://www.reverbnation.com/n3xg3nmusik/song/9300198-diddy-dirty-money-coming-home-zouk | +| 0302_SweetHomeAlabama.mp3 | Freely available @ Internet Archive.org | https://archive.org/details/lynyrd.-skynyrd.2/Lynyrd+Skynyrd+2/Lynyrd+Skynyrd+2/01+Sweet+Home+Alabama.wav | diff --git a/Firmware/LowLevel/src/datatypes.h b/Firmware/LowLevel/src/datatypes.h index bba80b49..9779daf6 100644 --- a/Firmware/LowLevel/src/datatypes.h +++ b/Firmware/LowLevel/src/datatypes.h @@ -19,6 +19,7 @@ #define _DATATYPES_H #include +#include #define PACKET_ID_LL_STATUS 1 #define PACKET_ID_LL_IMU 2 @@ -28,10 +29,44 @@ #define PACKET_ID_LL_HEARTBEAT 0x42 #define PACKET_ID_LL_HIGH_LEVEL_STATE 0x43 -enum HighLevelMode { - MODE_IDLE = 1, // ROS connected, idle mode - MODE_AUTONOMOUS = 2, // ROS connected, Autonomous mode, either mowing or docking or undocking - MODE_RECORDING = 3 // ROS connected, Manual mode during recording etc +/** + * @brief Simple class containing only the static methods, + * for more comfortable as well as more speaking HighLevel-Mode and SubMode handling + */ +#define HL_MODE_MASK 0b11111 +#define HL_SUBMODE_SHIFT 6 +#define HL_SUBMODE_MASK 0b11 +class HighLevelState { + public: + enum class Mode : uint8_t { + IDLE = 1, // ROS connected, idle mode + AUTONOMOUS = 2, // ROS connected, Autonomous mode, either mowing or docking or undocking + RECORDING = 3, // ROS connected, Manual mode during recording etc + }; + enum SubModeIdle : uint8_t { + AUTONOMOUS_MOWING = 0, // ?! + }; + enum SubModeAutonomous : uint8_t { + MOWING = 0, + DOCKING = 1, + UNDOCKING = 2, + }; + enum SubModeRecording : uint8_t { + OUTLINE = 1, + OBSTACLE = 2, + }; + + static Mode getMode(uint8_t t_mode) { + return Mode(t_mode & HL_MODE_MASK); + } + + static uint8_t getSubMode(uint8_t t_mode) { + return (t_mode >> HL_SUBMODE_SHIFT) & HL_SUBMODE_MASK; + } + + static SubModeAutonomous getAutonomousSubMode(uint8_t t_mode) { + return SubModeAutonomous(getSubMode(t_mode)); + } }; // clang-format off @@ -48,7 +83,14 @@ enum HighLevelMode { #define LL_EMERGENCY_BIT_CU_LIFTX (1 << 5) // CoverUI-LIFTX (as of CoverUI FW 2.1x) #define LL_EMERGENCY_BIT_CU_RBUMP (1 << 6) // CoverUI-RBUMP (as of CoverUI FW 2.1x) -#define LL_STATUS_BIT_UI_AVAIL (1 << 7) +#define LL_STATUS_BIT_INITIALIZED (1 << 0) +#define LL_STATUS_BIT_RASPI_POWER (1 << 1) +#define LL_STATUS_BIT_CHARGING (1 << 2) +#define LL_STATUS_BIT_FREE (1 << 3) +#define LL_STATUS_BIT_RAIN (1 << 4) +#define LL_STATUS_BIT_SOUND_AVAIL (1 << 5) +#define LL_STATUS_BIT_SOUND_BUSY (1 << 6) +#define LL_STATUS_BIT_UI_AVAIL (1 << 7) // clang-format on #pragma pack(push, 1) diff --git a/Firmware/LowLevel/src/main.cpp b/Firmware/LowLevel/src/main.cpp index 4b8d61ac..6f256ab0 100644 --- a/Firmware/LowLevel/src/main.cpp +++ b/Firmware/LowLevel/src/main.cpp @@ -19,16 +19,18 @@ #include #include #include -#include + #include #include "datatypes.h" #include "imu.h" #include "pins.h" #include "ui_board.h" +#include "debug.h" #ifdef ENABLE_SOUND_MODULE #include +using namespace soundSystem; #endif #define IMU_CYCLETIME 20 // cycletime for refresh IMU data @@ -38,21 +40,11 @@ #define BUTTON_EMERGENCY_MILLIS 20 // Time for button emergency to activate. This is to debounce the button. -// Define to stream debugging messages via USB -// #define USB_DEBUG - -// Only define DEBUG_SERIAL if USB_DEBUG is actually enabled. -// This enforces compile errors if it's used incorrectly. -#ifdef USB_DEBUG -#define DEBUG_SERIAL Serial -#endif #define PACKET_SERIAL Serial1 SerialPIO uiSerial(PIN_UI_TX, PIN_UI_RX, 250); #define UI1_SERIAL uiSerial -#define ANZ_SOUND_SD_FILES 3 - // Millis after charging is retried #define CHARGING_RETRY_MILLIS 10000 @@ -74,10 +66,6 @@ PacketSerial packetSerial; // COBS communication PICO <> Raspi PacketSerial UISerial; // COBS communication PICO UI-Board FastCRC16 CRC16; -#ifdef ENABLE_SOUND_MODULE -MP3Sound my_sound; // Soundsystem -#endif - unsigned long last_imu_millis = 0; unsigned long last_status_update_millis = 0; unsigned long last_heartbeat_millis = 0; @@ -96,7 +84,7 @@ bool stock_ui_rain = false; // Get set by received Get_Rain packet // Predefined message buffers, so that we don't need to allocate new ones later. struct ll_imu imu_message = {0}; -struct ll_status status_message = {0}; +struct ll_status status_message = {}; // current high level state struct ll_high_level_state last_high_level_state = {0}; @@ -122,6 +110,7 @@ uint16_t ui_interval = 1000; // UI send msg (LED/State) interval ( // LL/HL config #define CONFIG_FILENAME "/openmower.cfg" // Where our llhl_config get saved in LittleFS (flash) uint16_t config_crc_in_flash = 0; +uint16_t config_size_in_flash = 0; // Require for wear level protection struct ll_high_level_config llhl_config; // LL/HL configuration (is initialized with YF-C500 defaults) // Available hall input sources, same order as in ll_high_level_config.hall_configs @@ -146,6 +135,8 @@ void onPacketReceived(const uint8_t *buffer, size_t size); void onUIPacketReceived(const uint8_t *buffer, size_t size); void manageUISubscriptions(); void readConfigFromFlash(); +void saveConfigToFlash(const uint8_t *t_buffer, const size_t t_size, const uint16_t t_crc); +void updateConfigInFlash(); void setRaspiPower(bool power) { // Update status bits in the status message @@ -272,25 +263,25 @@ void manageUILEDS() { if (!ROS_running) { setLed(leds_message, LED_S1, LED_off); } else { - switch (last_high_level_state.current_mode & 0b111111) { - case HighLevelMode::MODE_IDLE: + switch (HighLevelState::getMode(last_high_level_state.current_mode)) { + case HighLevelState::Mode::IDLE: setLed(leds_message, LED_S1, LED_on); break; - case HighLevelMode::MODE_AUTONOMOUS: + case HighLevelState::Mode::AUTONOMOUS: setLed(leds_message, LED_S1, LED_blink_slow); break; default: setLed(leds_message, LED_S1, LED_blink_fast); break; } - switch ((last_high_level_state.current_mode >> 6) & 0b11) { - case 1: + switch (HighLevelState::getSubMode(last_high_level_state.current_mode)) { + case 1: // Docking or Record outline setLed(leds_message, LED_S2, LED_blink_slow); break; - case 2: + case 2: // Undocking or Record obstacle setLed(leds_message, LED_S2, LED_blink_fast); break; - case 3: + case 3: // Not defined yet setLed(leds_message, LED_S2, LED_on); break; default: @@ -333,8 +324,10 @@ void manageUISubscriptions() } void setup1() { - // Core - digitalWrite(LED_BUILTIN, HIGH); + pinMode(PIN_MUX_OUT, OUTPUT); + pinMode(PIN_MUX_ADDRESS_0, OUTPUT); + pinMode(PIN_MUX_ADDRESS_1, OUTPUT); + pinMode(PIN_MUX_ADDRESS_2, OUTPUT); } void loop1() { @@ -349,9 +342,9 @@ void loop1() { mutex_enter_blocking(&mtx_status_message); if (state || stock_ui_rain) { - status_message.status_bitmask |= 0b00010000; + status_message.status_bitmask |= LL_STATUS_BIT_RAIN; } else { - status_message.status_bitmask &= 0b11101111; + status_message.status_bitmask &= ~LL_STATUS_BIT_RAIN; } mutex_exit(&mtx_status_message); @@ -359,9 +352,9 @@ void loop1() { case 6: mutex_enter_blocking(&mtx_status_message); if (state) { - status_message.status_bitmask |= 0b01000000; + status_message.status_bitmask &= ~LL_STATUS_BIT_SOUND_BUSY; } else { - status_message.status_bitmask &= 0b10111111; + status_message.status_bitmask |= LL_STATUS_BIT_SOUND_BUSY; } mutex_exit(&mtx_status_message); break; @@ -370,17 +363,19 @@ void loop1() { } } +#ifdef ENABLE_SOUND_MODULE + soundSystem::processSounds(status_message, ROS_running, last_high_level_state); +#endif + delay(100); } void setup() { // We do hardware init in this core, so that we don't get invalid states. - // Therefore, we pause the other core until setup() was a success + // Therefore, we pause the other core until vars used in OtherCore got initialized. rp2040.idleOtherCore(); -#ifdef USB_DEBUG - DEBUG_SERIAL.begin(9600); -#endif + DEBUG_BEGIN(9600); emergency_latch = true; ROS_running = false; @@ -393,6 +388,9 @@ void setup() { imu_message.type = PACKET_ID_LL_IMU; status_message.type = PACKET_ID_LL_STATUS; + // Save to start other core now, as well as required i.e. for LittleFS + rp2040.resumeOtherCore(); + // Setup pins pinMode(LED_BUILTIN, OUTPUT); pinMode(PIN_ENABLE_CHARGE, OUTPUT); @@ -409,11 +407,6 @@ void setup() { setRaspiPower(true); p.neoPixelSetValue(0, 255, 0, 0, true); - pinMode(PIN_MUX_OUT, OUTPUT); - pinMode(PIN_MUX_ADDRESS_0, OUTPUT); - pinMode(PIN_MUX_ADDRESS_1, OUTPUT); - pinMode(PIN_MUX_ADDRESS_2, OUTPUT); - pinMode(PIN_EMERGENCY_1, INPUT); pinMode(PIN_EMERGENCY_2, INPUT); pinMode(PIN_EMERGENCY_3, INPUT); @@ -421,31 +414,54 @@ void setup() { analogReadResolution(12); - // init serial com to RasPi + // Init serial com to RasPi PACKET_SERIAL.begin(115200); packetSerial.setStream(&PACKET_SERIAL); packetSerial.setPacketHandler(&onPacketReceived); + // Init serial com to CoverUI UI1_SERIAL.begin(115200); UISerial.setStream(&UI1_SERIAL); UISerial.setPacketHandler(&onUIPacketReceived); + // Initialize flash and try to read config. + // ATTENTION: LittleFS needs other core (at least for initial format)! + LittleFS.begin(); + readConfigFromFlash(); // Set llhl_config with defaults or saved state + +#ifdef ENABLE_SOUND_MODULE + p.neoPixelSetValue(0, 0, 255, 255, true); + sound_available = soundSystem::begin(); + if (sound_available) { + p.neoPixelSetValue(0, 0, 0, 255, true); // Blue + soundSystem::applyConfig(llhl_config, true); + // Do NOT play any initial sound now, because we've to handle the special case of + // old DFPlayer SD-Card format @ DFROBOT LISP3 with wrong IO2 level. See soundSystem::processSounds() + p.neoPixelSetValue(0, 255, 255, 0, true); + } else { + for (uint8_t b = 0; b < 3; b++) { + p.neoPixelSetValue(0, 0, 0, 0, true); + delay(200); + p.neoPixelSetValue(0, 0, 0, 255, true); + delay(200); + } + } +#endif + /* * IMU INITIALIZATION */ - bool init_imu_success = false; - int init_imu_tries = 1000; + int init_imu_tries = 10; while(init_imu_tries --> 0) { if(init_imu()) { init_imu_success = true; break; } -#ifdef USB_DEBUG - DEBUG_SERIAL.println("IMU initialization unsuccessful, retrying in 1 sec"); -#endif + DEBUG_PRINTF("IMU initialization unsuccessful, retrying in 1 sec (%d tries left)\n", init_imu_tries + 1); + p.neoPixelSetValue(0, 0, 0, 0, true); - delay(1000); + delay(800); p.neoPixelSetValue(255, 255, 0, 0, true); delay(100); p.neoPixelSetValue(0, 0, 0, 0, true); @@ -453,59 +469,39 @@ void setup() { } if (!init_imu_success) { -#ifdef USB_DEBUG - DEBUG_SERIAL.println("IMU initialization unsuccessful"); - DEBUG_SERIAL.println("Check IMU wiring or try cycling power"); -#endif + unsigned long next_ann = millis(); + DEBUG_PRINTLN("IMU initialization failed"); + DEBUG_PRINTLN("Check IMU wiring or try cycling power"); + status_message.status_bitmask = 0; - while (1) { // Blink RED for IMU failure + while (1) { // Infinite blink RED for IMU failure p.neoPixelSetValue(0, 255, 0, 0, true); delay(500); p.neoPixelSetValue(0, 0, 0, 0, true); delay(500); - } - } - p.neoPixelSetValue(0, 255, 255, 255, true); // White for IMU Success - -#ifdef USB_DEBUG - DEBUG_SERIAL.println("Imu initialized"); -#endif - - status_message.status_bitmask |= 1; - - rp2040.resumeOtherCore(); - - // Initialize flash and try to read config. - // ATTENTION: LittleFS needs other core (at least for initial format)! - LittleFS.begin(); - readConfigFromFlash(); - #ifdef ENABLE_SOUND_MODULE - p.neoPixelSetValue(0, 0, 255, 255, true); - - sound_available = my_sound.begin(); - if (sound_available) { - p.neoPixelSetValue(0, 0, 0, 255, true); - my_sound.setvolume(llhl_config.volume); - my_sound.playSoundAdHoc(1); - p.neoPixelSetValue(0, 255, 255, 0, true); - } else { - for (uint8_t b = 0; b < 3; b++) { - p.neoPixelSetValue(0, 0, 0, 0, true); - delay(200); - p.neoPixelSetValue(0, 0, 0, 255, true); - delay(200); + if (millis() >= next_ann) { + soundSystem::playSound(SOUND_TRACK_ADV_IMU_INIT_FAILED); + soundSystem::playSound(SOUND_TRACK_BGD_OM_ALARM); + next_ann = millis() + 8000; + } +#endif } } -#endif + p.neoPixelSetValue(0, 255, 255, 255, true); // White for IMU Success + DEBUG_PRINTLN("Imu initialized"); + + status_message.status_bitmask |= LL_STATUS_BIT_INITIALIZED; // Cover UI board clear all LEDs leds_message.type = Set_LEDs; leds_message.leds = 0; sendUIMessage(&leds_message, sizeof(leds_message)); - p.neoPixelSetValue(0, 255, 255, 255, true); // White 1s final success + p.neoPixelSetValue(0, 255, 255, 255, true); // White 1s final success delay(1000); + + digitalWrite(LED_BUILTIN, HIGH); // Signal that both cores got setup and looping start } void onUIPacketReceived(const uint8_t *buffer, size_t size) { @@ -539,19 +535,30 @@ void onUIPacketReceived(const uint8_t *buffer, size_t size) { ui_event.button_id = msg->button_id; ui_event.press_duration = msg->press_duration; sendMessage(&ui_event, sizeof(ui_event)); - } - else if (buffer[0] == Get_Emergency && size == sizeof(struct msg_event_emergency)) - { + +#ifdef ENABLE_SOUND_MODULE + // Handle Sound Buttons. FIXME: Should/might go to mower_logic? But as sound isn't hip ... :-/ + switch (msg->button_id) { + case 8: // Mon = Volume up + llhl_config.volume = soundSystem::setVolumeUp(); + updateConfigInFlash(); + break; + case 9: // Tue = Volume down + llhl_config.volume = soundSystem::setVolumeDown(); + updateConfigInFlash(); + break; + + default: + break; + } +#endif + } else if (buffer[0] == Get_Emergency && size == sizeof(struct msg_event_emergency)) { struct msg_event_emergency *msg = (struct msg_event_emergency *)buffer; stock_ui_emergency_state = msg->state; - } - else if (buffer[0] == Get_Rain && size == sizeof(struct msg_event_rain)) - { + } else if (buffer[0] == Get_Rain && size == sizeof(struct msg_event_rain)) { struct msg_event_rain *msg = (struct msg_event_rain *)buffer; stock_ui_rain = (msg->value < llhl_config.rain_threshold); - } - else if (buffer[0] == Get_Subscribe && size == sizeof(struct msg_event_subscribe)) - { + } else if (buffer[0] == Get_Subscribe && size == sizeof(struct msg_event_subscribe)) { struct msg_event_subscribe *msg = (struct msg_event_subscribe *)buffer; ui_topic_bitmask = msg->topic_bitmask; ui_interval = msg->interval; @@ -625,7 +632,7 @@ void applyConfig(const uint8_t *buffer, const size_t size) { llhl_config = new_config; // Make new config live } -void onPacketReceived(const uint8_t *buffer, size_t size) { +void onPacketReceived(const uint8_t *buffer, const size_t size) { // sanity check for CRC to work (1 type, 1 data, 2 CRC) if (size < 4) return; @@ -659,18 +666,17 @@ void onPacketReceived(const uint8_t *buffer, size_t size) { } else if (buffer[0] == PACKET_ID_LL_HIGH_LEVEL_CONFIG_REQ || buffer[0] == PACKET_ID_LL_HIGH_LEVEL_CONFIG_RSP) { applyConfig(buffer + 1, size - 3); // Skip packet- type and CRC +#ifdef ENABLE_SOUND_MODULE + // We can't move the sound stuff to applyConfig because applyConfig get also called by readConfigFromFlash() + // which is before (and also needs to be before) soundSystem::begin() + soundSystem::applyConfig(llhl_config, true); +#endif + // Response if requested (before save, to ensure REQ/RSP timing) if (buffer[0] == PACKET_ID_LL_HIGH_LEVEL_CONFIG_REQ) sendConfigMessage(PACKET_ID_LL_HIGH_LEVEL_CONFIG_RSP); // Other side requested a config response - // Store the whole received packet in flash and not the live (applied) config. This has the following benefits: - // - We're free to change ll_high_level_config defaults in future without fiddling with already stored ones - // - Reuse the already calculated and validated CRC - if (crc == config_crc_in_flash) return; // Protect wear leveling - File f = LittleFS.open(CONFIG_FILENAME, "w"); - if (!f) return; - if (f.write(buffer, size) == size) config_crc_in_flash = crc; - f.close(); + saveConfigToFlash(buffer, size, crc); } } @@ -795,11 +801,6 @@ void loop() { { next_ui_msg_millis = now + ui_interval; manageUISubscriptions(); -#ifdef ENABLE_SOUND_MODULE - if (sound_available) { - my_sound.processSounds(); - } -#endif } // Check UI version/available @@ -883,3 +884,53 @@ void readConfigFromFlash() { applyConfig(buffer + 1, size - 3); // Skip Type & CRC free(buffer); } + +/** + * @brief saveConfigToFlash() save a (received) config packet to flash, if the crc differ to the one already stored in flash (wear level protection) + * + * We do store the whole received packet in flash and not the live (applied) config because this has the following advantages: + * - We're free to change ll_high_level_config defaults in future without fiddling with already stored ones + * - Reuse the already calculated and validated CRC + * + * @param t_buffer + * @param t_size + * @param t_crc + */ +void saveConfigToFlash(const uint8_t *t_buffer, const size_t t_size, const uint16_t t_crc) { + if (t_crc == config_crc_in_flash) return; // Protect wear leveling + File f = LittleFS.open(CONFIG_FILENAME, "w"); + if (!f) return; + if (f.write(t_buffer, t_size) == t_size) { + config_crc_in_flash = t_crc; + config_size_in_flash = t_size; + } + f.close(); +} + +/** + * @brief updateConfigInFlash() will update the config in flash with the live one, if CRC differ (wear level protection) + * + */ +void updateConfigInFlash() { + // We need to simulate a packet + uint16_t size = sizeof(struct ll_high_level_config) + 3; // + 1 type + 2 crc + if (config_size_in_flash > 3) + size = min(size, config_size_in_flash); // Wear level protection + + uint8_t *buffer = (uint8_t *)malloc(size); + if (buffer == NULL) return; + + buffer[0] = PACKET_ID_LL_HIGH_LEVEL_CONFIG_RSP; // Not needed in real, but let's pretend to be + memcpy(buffer + 1, &llhl_config, size); // Copy our live llhl_config into the buffer + + uint16_t crc = CRC16.ccitt(buffer, size - 2); + if (crc == config_crc_in_flash) return; // No need to write, protect wear leveling + + buffer[size - 1] = (crc >> 8) & 0xFF; + buffer[size - 2] = crc & 0xFF; + + File f = LittleFS.open(CONFIG_FILENAME, "w"); + if (!f) return; + if (f.write(buffer, size) == size) config_crc_in_flash = crc; + f.close(); +} diff --git a/Firmware/LowLevel/src/soundsystem.cpp b/Firmware/LowLevel/src/soundsystem.cpp index 1a079ed7..0cd0fe4a 100644 --- a/Firmware/LowLevel/src/soundsystem.cpp +++ b/Firmware/LowLevel/src/soundsystem.cpp @@ -1,5 +1,7 @@ // Created by Elmar Elflein on 18/07/22. // Copyright (c) 2022 Elmar Elflein. All rights reserved. +// Restructured by Jörg Ebeling on 10/16/23. +// Copyright (c) 2023, 2024 Jörg Ebeling. All rights reserved. // // This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. // @@ -14,93 +16,664 @@ // SOFTWARE. // -#include - - -SerialPIO soundSerial(PIN_SOUND_TX, PIN_SOUND_RX, 250); - -DFPlayerMini_Fast myMP3; - - -MP3Sound::MP3Sound() +/* | Original | Old clones | Newer clones | + * DFPlayer Chip | DFROBOT LISP3 | YX5200-24SS | MH2024K-24SS | MH2024K-16SS | GD3200B | JL AB23A799755 | + * ----------------------------------------------------------------------------------------------------------------- + * Tested/working with OM | ok | | ok | | partly | ok | + * Autoplay files *1 | yes | | no | | no | no | + * Require reset() | | | | | YES | | + * Reset->isOnline *2 | 800-850ms | | 750ms | | 600ms | | + * Without SD Card | error 0x1 | | | | | | + * getTotalTrackCount() | 0 | | 24 | | 24 | | + * getTotalFolderCount() | 5 | | 0 | | 3 | | + * getStatus().source | 2 | | 0 | | 0 | | + * playAdvertisement() | yes*4 | | yes*4 | | yes | | + * setRepeatPlayAllInRoot() | Plays ALL | | yes | | | | + * getCurrentTrack() | *3 | | *4 | | *3 | | + * OnPlayFinished() | Play&Advert*5 | | *5,*6 | | Play&Adv| | + * Auto increase volume *7 | no | | yes | | yes | yes | + * DfMp3_Error_FileMismatch | yes | | yes | | no | yes | + * Plausible busy signal | yes | | no | | no | yes | + * ----------------------------------------------------------------------------------------------------------------- + * *1 = Autoplay (unasked) all (at least) root files (after reset()) when Pin-11 (IO_2) at GND (0.9.x-0.13.x) + * *2 = Highly depends on SD-Card and content + * *3 = Advert track get returned with an internal track number + * *4 = Advert get only played with a current running play sound + * *5 = Sometimes called a second time within 10-150ms + * *6 = Two callbacks (advert & play) at the end of play sound + * *7 = Auto increase volume if Pin-11 (IO_2) at GND (0.9.x-0.13.x) + * + * Conclusion by this evaluation: + * DFPlayer's "advert" functionality is not usable for our requirements, as MH2024K-24S don't return a usable advert state. + * + * Detection Matrix (old/new SD-Card): + * | getTotalTrackCount() | getFolderTrackCount(1) | getTotalFolderCount() | + * ---------------|----------------------|------------------------|-----------------------| + * DFROBOT LISP3 | 15/78 | 0/25 | 0/6 | + * YX5200-24SS | / | / | / | + * MH2024K-24SS | 15/78 | 15/25 | 0/3 | + * MH2024K-16SS | / | / | / | + * GD3200B | 15/78 | 15/25(1) | 1/4 | + * JL AB23A799755 | 15/78 | 0/25 | 0/0 | + * ---------------|----------------------|------------------------|-----------------------| + * Conclusion | usable | not usable due to (1) | not usable see 0/0 | + * ---------------------------------------------------------------------------------------| + * (num) = after hard reset + */ + +#include "soundsystem.h" + +#include +#include +#include + +#include "debug.h" +#include "pins.h" + +// #define DEBUG_NEW_DFP_CLONE // Enable for compatibility evaluation for possible new DFPlayer clones + +// ----- Sound specific ----- +#define GPS_SOUND_CYCLETIME 3000 +#define MOW_SOUND_INITIAL_FIX_DELAY 120000 // Play random mow sounds earliest 3 minutes after first fix (max. expected time to navigate to mowing area) +#define MOW_SOUND_MIN_PAUSE_AFTER 60000 // Minimum pause before a new randomized mow sounds get played +#define MOW_SOUND_CHANCE 50 // % change to play a new sound within the next minute after MOW_SOUND_MIN_PAUSE_AFTER +#define ROS_RUNNING_BEFORE_EMERGENCY 10000 // Min. millis of running ROS before emergencies get handled +#define VOLUME_DEFAULT 80 +#define VOLUME_STEPS 5 // Amount of volume setps for volumeUp() and volumeDown() + +// ----- Detection specific ----- +#define DFP_NEW_SDCARD_MIN_SND_FILES 70 // If getTotalTrackCount() return < 70 snd files it's detected as DFP_DETECTION_BIT_ORIG_CARD_STRUCTURE +#define DFP_AUTOPLAY_TIMEOUT 4000 // Auto-play track detection timeout. "Hi I'm Steve ..." is about 4.x seconds. After that time, auto-play- detection is useless + +// clang-format off +#define DFP_DETECTION_BIT_END (1 << 0) // All detection phases ended +#define DFP_DETECTION_BIT_HAS_AUTOPLAY (1 << 1) // DFPlayer has played a track after power-on/or reset +#define DFP_DETECTION_BIT_ORIG_CARD_STRUCTURE (1 << 2) // Detected (old) origin SD-Card structure +// clang-format on + +// ----- Internal ----- +#define DFP_REDUNDANT_ONPLAYFINISH_CB_MAX 300 // Max. ms to detect a recurring OnPlayFinish() CB as redundant +#define SOUND_QUEUE_SIZE 10 +#define PROCESS_CYCLETIME 500 + +namespace soundSystem { +namespace // anonymous (private) namespace { +class Mp3Notify_; // forward declaration +typedef DFMiniMp3 DfMp3; // ... for a more readable/shorter DfMp3 typedef + // typedef DFMiniMp3 DfMp3; // Need to be tested if really required +SerialPIO soundSerial(PIN_SOUND_TX, PIN_SOUND_RX, 250); +DfMp3 myMP3(soundSerial); + +const std::map language_to_playFolder_map_{{"en", 1}, {"de", 49}}; // ISO639-1 (string) to playFolder (uint) map for localized advert sounds +etl::queue active_sounds_; +bool sound_available_ = false; // Sound module available as well as SD-Card with some kind of files +bool dfp_is_5v_ = false; // Enable full sound if DFP is set to 5V Vcc +bool background_enabled_ = false; // Enable background sounds +uint8_t volume_ = VOLUME_DEFAULT; // Last set volume (%) +uint8_t play_folder_ = 1; // Default play folder (1 = es/en) + +uint16_t last_error_code_ = 0; // Last DFPlayer error code. See DfMp3_Error for code meaning + +ll_status last_ll_state_ = {}; // Last processed low-level state +ll_high_level_state last_hl_state_ = {}; // Last processed high-level state +unsigned long hl_mode_has_fix_ms_; // Millis when the current high-level mode got his initial GPS fix. 0 if idle. +uint8_t hl_mode_flags_; // High level mode flags (assumptions), like initial GPS "fix", rain, docking... + +TrackDef background_track_def_ = {}; // Current/last background track +TrackDef advert_track_def_ = {}; // Current/last playing advert track +bool current_playing_is_background_; // Current/last playing sound is a background sound +unsigned long current_playing_started_; // Millis when current/last playing sound got started + +bool last_ros_running_ = false; // Last ROS running state +unsigned long ros_running_since_ = 0; // ros_running since millis state +unsigned long last_advert_end_; // Millis when the last played advert sound ended. Used for pauseAfter calculation +unsigned long init_ms_ = millis(); // Millis when sound got initialized. Required to check if autoplay is already talking before reset + +// Describe specific (assumed) mode flags +enum ModeFlags : uint8_t { + started = 0b1 << 0, // Mode started + primalGpsFix = 0b1 << 1, // Got the primal GPS "fix", by which we can assume that i.e. the mower drive to his mowing start point + rainDetected = 0b1 << 2, // LL rain sensor signal received + docking = 0b1 << 3, // Heading back to base, i.e. due to rain detected +}; + +uint8_t dfp_detection_status_ = 0; // Sound-card detection status bits i.e. for old- sound-card-format detection +unsigned long online_ms_ = 0; // millis() when module got detected as "online". Required for detection if module is configured (IO2) with autoplay + +// Forward declarations +void playMowSound_(); +bool handleAutoplay_(ll_status t_ll_state); +} // namespace + +bool begin() { + myMP3.begin(); + + // It's advised to reset DFPlayer, but due to auto-play quirk by (IO2=GND) a reset would result in a aborted "Hi I'm Steve..." advert. + // myMP3.reset(false); // Non-blocking reset + + // Ensure that a possible wrong IO2=GND doesn't play all sounds. We do this independent on SD-Card result to ensure not polluting our environment + // myMP3.setRepeatPlayAllInRoot(false); // This would abort the current auto-playing title @ DFROBOT LISP3 + delay(100); // Let's be save that this command will be processed + myMP3.setRepeatPlayCurrentTrack(false); // This will stop IO2=GND auto-play of all root files @ DFROBOT LISP3 + + delay(500); // SD-Card phase is fragile. Give it enough delay to scan also an old SD-Card + uint16_t total_tracks = myMP3.getTotalTrackCount(DfMp3_PlaySource_Sd); // Total tracks on SD-Card, used for old-/new-SD-Card decision + DEBUG_PRINTF("total_tracks_ = %d\n", total_tracks); + +#ifdef DEBUG_NEW_DFP_CLONE + // Used for compatibility evaluation for possible new DFPlayer clones + delay(200); // SD-Card phase is fragile. Give it enough delay + uint16_t us_folder = myMP3.getFolderTrackCount(1); + DEBUG_PRINTF("getFolderTrackCount(1) = %d\n", us_folder); + + delay(200); // SD-Card phase is fragile. Give it enough delay + uint16_t total_folder = myMP3.getTotalFolderCount(); + DEBUG_PRINTF("getTotalFolderCount() = %d\n", total_folder); + + DfMp3_Status status = myMP3.getStatus(); + DEBUG_PRINTF("getStatus().source = %d\n", status.source); +#endif + + if (!total_tracks) { + DEBUG_PRINTF("Found no SD-Card tracks = No Sound!\n"); + return false; + } + + if (total_tracks < DFP_NEW_SDCARD_MIN_SND_FILES) + dfp_detection_status_ |= DFP_DETECTION_BIT_ORIG_CARD_STRUCTURE; + + sound_available_ = true; + + return sound_available_; +} - this->anzSoundfiles = 0; // number of files stored on the SD-card - this->playing = false; - this->sound_available = false; +void setDFPis5V(const bool t_dfpis5v) { + dfp_is_5v_ = t_dfpis5v; +} +void setEnableBackground(const bool t_bool) { + background_enabled_ = t_bool; } +void setLanguage(const iso639_1 language_p, const bool quiet) { // Set language to the pointing ISO639-1 (2 char) language code and announce if changed (but not quiet) + uint8_t last_play_folder = play_folder_; + std::string language_str; + for (size_t i = 0; i < sizeof(iso639_1); i++) + language_str += language_p[i]; -bool MP3Sound::begin() - -{ + if (auto it = language_to_playFolder_map_.find(language_str.c_str()); it != language_to_playFolder_map_.end()) + play_folder_ = it->second; - // serial stream init for soundmodule - soundSerial.begin(9600); - soundSerial.flush(); - while (soundSerial.available()) - soundSerial.read(); - // init soundmodule - sound_available = myMP3.begin(soundSerial,true); - this->anzSoundfiles = myMP3.numSdTracks(); - return this->anzSoundfiles > 0; -} + if (!sound_available_ || play_folder_ == last_play_folder || quiet || !dfp_is_5v_) + return; + playSoundAdHoc(SOUND_TRACK_ADV_LANGUAGE); +} -void MP3Sound::setvolume(int vol) // scales from 0 to 100 % +void setVolume(const uint8_t t_vol) // Set volume (0-100%) { - - // value of 30 is max equivalent to 100 % - int val = (int) (30.0 / 100.0 * (double)vol); - myMP3.volume(val); - delay(300); - + if (!sound_available_) + return; + + // value of 30 is max equivalent to 100 % + uint8_t val = (uint8_t)(30.0 / 100.0 * (double)t_vol); + DEBUG_PRINTF("Set volume %d\n", val); + myMP3.setVolume(val); + volume_ = t_vol; + delay(50); // (sometimes) required for "DFR LISP3" } +uint8_t setVolumeUp() { + if (!sound_available_ || volume_ >= 100) + return volume_; + setVolume(min(volume_ + VOLUME_STEPS, 100)); + if (dfp_is_5v_) + playSoundAdHoc(SOUND_TRACK_ADV_UP); -void MP3Sound::playSoundAdHoc(int soundNr) -{ - if(soundNr > anzSoundfiles) return; - - myMP3.play(soundNr); + return volume_; } +uint8_t setVolumeDown() { + if (!sound_available_ || volume_ < VOLUME_STEPS) + return volume_; + setVolume(max(volume_ - VOLUME_STEPS, 0)); + if (dfp_is_5v_) + playSoundAdHoc(SOUND_TRACK_ADV_DOWN); + return volume_; +} -void MP3Sound::playSound(int soundNr) -{ - if((soundNr > anzSoundfiles) || (active_sounds.size() == BUFFERSIZE) ) return; +void applyConfig(const ll_high_level_config t_config, const bool quiet) { + setDFPis5V(t_config.options.dfp_is_5v == OptionState::ON); + setVolume(t_config.volume); + setLanguage(t_config.language, quiet); + setEnableBackground(t_config.options.background_sounds == OptionState::ON); +} - active_sounds.push_front(soundNr); - +void playSoundAdHoc(const TrackDef &t_track_def) { + // DEBUG_PRINTF("playSoundAdHoc(num %d, type %d, flags " PRINTF_BINARY_PATTERN_INT8 ")\n", t_track_def.num, t_track_def.type, PRINTF_BYTE_TO_BINARY_INT8(t_track_def.track_flags)); + + if (!sound_available_ || // Sound not available (yet) + !(dfp_detection_status_ & DFP_DETECTION_BIT_END) || // Still in auto-play detection phase. Do not disturb! + ((dfp_detection_status_ & DFP_DETECTION_BIT_ORIG_CARD_STRUCTURE) && !(t_track_def.card_sources.origin))) // Track not available on origin SD-Card + return; + + switch (t_track_def.type) { + case TrackType::BACKGROUND: + if (!background_enabled_) + return; + myMP3.stop(); + delay(50); // (sometimes) required for "MH2024K-24SS" + myMP3.playMp3FolderTrack(t_track_def.num); + delay(50); // (sometimes) required for "MH2024K-24SS" + background_track_def_ = t_track_def; + current_playing_is_background_ = true; + break; + case TrackType::ADVERT: + myMP3.stop(); + delay(50); // (sometimes) required for "MH2024K-24SS" + if (dfp_detection_status_ & DFP_DETECTION_BIT_ORIG_CARD_STRUCTURE) { // Old SD-Card detected + // Simple support for old SD-Card format + myMP3.playGlobalTrack(t_track_def.num); + } else { + // ATTENTION: Don't use playFolderTrack16() as it does NOT work reliable across DFPlayer clones (as of writing). But playFolderTrack() does. + myMP3.playFolderTrack(play_folder_, t_track_def.num); + } + delay(50); // (sometimes) required for "MH2024K-24SS" + advert_track_def_ = t_track_def; + current_playing_is_background_ = false; + break; + } + current_playing_started_ = millis(); + + if (t_track_def.track_flags.repeat) { + myMP3.setRepeatPlayCurrentTrack(true); // FIXME: Does NOT work reliable, see manual handling in Mp3Notify::OnPlayFinished() + } + + if (t_track_def.track_flags.stop_background) { + background_track_def_ = {}; + } } +void playSound(const TrackDef &t_track_def) { + // DEBUG_PRINTF("playSound(num %d, type %d, flags " PRINTF_BINARY_PATTERN_INT8 ")\n", t_track_def.num, t_track_def.type, PRINTF_BYTE_TO_BINARY_INT8(t_track_def.flags)); -int MP3Sound::sounds2play() -{ + if (!sound_available_ || active_sounds_.full()) + return; + active_sounds_.push(t_track_def); +} - return active_sounds.size(); +/** + * @brief Handle all kind of emergency sounds + * + * @param t_ll_state + * @param t_ros_running + */ +void handleEmergencies(const ll_status t_ll_state, const bool t_ros_running) { + if ((last_ll_state_.emergency_bitmask & LL_EMERGENCY_BIT_LATCH) == (t_ll_state.emergency_bitmask & LL_EMERGENCY_BIT_LATCH)) + return; // Ignore stop button or wheel lift changes if latch didn't changed + + if (t_ll_state.v_charge > 20.0f || // No emergencies while docked + !t_ros_running || // No emergencies as long as ROS does not run + !ros_running_since_ || // Pico just started up, while ROS might already running (which happen i.e. after a FW update) + millis() < (ros_running_since_ + ROS_RUNNING_BEFORE_EMERGENCY)) // No emergency handling before ROS is running that long + { + last_ll_state_.emergency_bitmask = t_ll_state.emergency_bitmask; + return; + } + + if (!(last_ll_state_.emergency_bitmask & LL_EMERGENCY_BIT_LATCH) && (t_ll_state.emergency_bitmask & LL_EMERGENCY_BIT_LATCH)) { // Latch changed from 0 -> 1 + if (t_ll_state.emergency_bitmask & LL_EMERGENCY_BIT_STOP) { + playSoundAdHoc(SOUND_TRACK_ADV_EMERGENCY_STOP); + // Do not take attention via "Bida bida" as user pressed the button itself + } else if (t_ll_state.emergency_bitmask & LL_EMERGENCY_BIT_LIFT) { + playSoundAdHoc(SOUND_TRACK_ADV_EMERGENCY_LIFT); + playSound(SOUND_TRACK_BGD_EMERGENCY_ALARM); + } else { + playSoundAdHoc(SOUND_TRACK_ADV_EMERGENCY_ROS); + playSound(SOUND_TRACK_BGD_EMERGENCY_ALARM); + } + } else { + // Latch changed from 1 -> 0 + playSoundAdHoc(SOUND_TRACK_ADV_EMERGENCY_CLEARED); + } + last_ll_state_.emergency_bitmask = t_ll_state.emergency_bitmask; +} +/** + * @brief Handle sound output related to Low-Level changes + * + * @param t_ll_state + * @param t_ros_running + * @param t_hl_state + */ +void handleLowLevelChanges(const ll_status t_ll_state, const bool t_ros_running, const ll_high_level_state t_hl_state) { + const uint8_t changed_status = t_ll_state.status_bitmask ^ last_ll_state_.status_bitmask; // Get changed bits + + if (!changed_status) // Nothing changed = nothing to do + return; + + DEBUG_PRINTF("Changed status_bitmask " PRINTF_BINARY_PATTERN_INT8 " (new status " PRINTF_BINARY_PATTERN_INT8 " XOR last status " PRINTF_BINARY_PATTERN_INT8 ")\n", + PRINTF_BYTE_TO_BINARY_INT8(changed_status), + PRINTF_BYTE_TO_BINARY_INT8(t_ll_state.status_bitmask), + PRINTF_BYTE_TO_BINARY_INT8(last_ll_state_.status_bitmask)); + + // if (!(last_ll_state_.status_bitmask & LL_STATUS_BIT_INITIALIZED) && (t_ll_state.status_bitmask & LL_STATUS_BIT_INITIALIZED)) { + if ((changed_status & LL_STATUS_BIT_INITIALIZED) && (t_ll_state.status_bitmask & LL_STATUS_BIT_INITIALIZED)) { + playSound(SOUND_TRACK_ADV_OM_STARTUP_SUCCESS); // OM startup successful + } + if (!t_ros_running && (changed_status & LL_STATUS_BIT_RASPI_POWER) && (t_ll_state.status_bitmask & LL_STATUS_BIT_RASPI_POWER)) { + playSound(SOUND_TRACK_ADV_ROS_INIT); // Initializing ROS + // We're in a new "Raspi/ROS" bootup phase, which might take longer. Change background sound for better identification + playSound(SOUND_TRACK_BGD_ROS_BOOT); + } + if ((changed_status & LL_STATUS_BIT_RAIN) && (t_ll_state.status_bitmask & LL_STATUS_BIT_RAIN)) { + if (HighLevelState::getMode(t_hl_state.current_mode) == HighLevelState::Mode::AUTONOMOUS && + !((hl_mode_flags_ & ModeFlags::rainDetected) || (hl_mode_flags_ & ModeFlags::docking))) { + playSoundAdHoc(SOUND_TRACK_ADV_RAIN); // Rain detected, heading back to base + playSound(TrackDef{.num = (uint16_t)(100 + (rand() % 3)), .type = TrackType::BACKGROUND}); // Play background track 100-102 by random + hl_mode_flags_ |= ModeFlags::docking; + } + hl_mode_flags_ |= ModeFlags::rainDetected; + } + + last_ll_state_.status_bitmask = t_ll_state.status_bitmask; +} +/** + * @brief Handle sound output related to High-Level changes + * + * @param t_hl_state + */ +void handleHighLevelChanges(const ll_high_level_state t_hl_state) { + if (t_hl_state.current_mode == last_hl_state_.current_mode) + return; + + auto mode = HighLevelState::getMode(t_hl_state.current_mode); + auto last_mode = HighLevelState::getMode(last_hl_state_.current_mode); + auto sub_mode = HighLevelState::getSubMode(t_hl_state.current_mode); + auto last_sub_mode = HighLevelState::getSubMode(last_hl_state_.current_mode); + + switch (HighLevelState::getMode(t_hl_state.current_mode)) { + case HighLevelState::Mode::RECORDING: + hl_mode_flags_ |= ModeFlags::started; + playSound(SOUND_TRACK_ADV_MAP_RECORD_START); // Starting map area recording + if (t_hl_state.gps_quality < 75) + playSound(SOUND_TRACK_ADV_RTKGPS_WAIT); // Waiting for RTK GPS signal + break; + case HighLevelState::Mode::AUTONOMOUS: + hl_mode_flags_ |= ModeFlags::started; + if (last_mode == HighLevelState::Mode::IDLE && sub_mode == HighLevelState::SubModeAutonomous::UNDOCKING) { // IDLE => Autonomous-Undocking + playSound(SOUND_TRACK_ADV_AUTONOMOUS_START); // Stay back, autonomous robot mower in use + if (t_hl_state.gps_quality < 75) + playSound(SOUND_TRACK_ADV_RTKGPS_WAIT); // Waiting for RTK GPS signal + } else if (last_sub_mode != HighLevelState::SubModeAutonomous::DOCKING && sub_mode == HighLevelState::SubModeAutonomous::DOCKING) { // !Docking => Docking + playSound(SOUND_TRACK_ADV_MOW_DONE_DOCK); // OM has completed mowing the lawn, heading back to docking station + playSound(TrackDef{.num = (uint16_t)(300 + (rand() % 4)), .type = TrackType::BACKGROUND}); // Play background track 300 to 203 by random + } + break; + default: + hl_mode_has_fix_ms_ = 0; + hl_mode_flags_ = 0; + break; + } + + last_hl_state_.current_mode = t_hl_state.current_mode; } +/** + * @brief Handle GPS quality related sounds + * + * @param t_ll_state + */ +void handleGpsQuality(const ll_status t_ll_state, const bool t_ros_running, const ll_high_level_state t_hl_state) { + static unsigned long next_gps_sound_cycle = millis(); // Next cycle when a GPS ping sound might get played + + if (!t_ros_running || t_hl_state.gps_quality == last_hl_state_.gps_quality || millis() < next_gps_sound_cycle) + return; + + next_gps_sound_cycle = millis() + GPS_SOUND_CYCLETIME; + + switch (HighLevelState::getMode(t_hl_state.current_mode)) { + case HighLevelState::Mode::RECORDING: + // Ping only rated GPS quality changes + if (t_hl_state.gps_quality < 50) { + if (last_hl_state_.gps_quality >= 50) + playSound(SOUND_TRACK_ADV_RTKGPS_POOR); // GPS poor ping + } else if (t_hl_state.gps_quality < 75) { + if (last_hl_state_.gps_quality < 50 || last_hl_state_.gps_quality >= 75) + playSound(SOUND_TRACK_ADV_RTKGPS_MODERATE); // GPS moderate/acceptable ping + } else { // Current GPS quality >= 75 + if (last_hl_state_.gps_quality < 75) + playSound(SOUND_TRACK_ADV_RTKGPS_GOOD); // GPS good ping + + hl_mode_flags_ |= ModeFlags::primalGpsFix; + if (!hl_mode_has_fix_ms_) + hl_mode_has_fix_ms_ = millis(); + } + break; + + case HighLevelState::Mode::AUTONOMOUS: + // Stalking "Pink Panther" sound only once when starting to mow + if (HighLevelState::getAutonomousSubMode(t_hl_state.current_mode) != HighLevelState::SubModeAutonomous::MOWING || + (hl_mode_flags_ & ModeFlags::primalGpsFix) || + (t_hl_state.gps_quality < 75)) + break; + playSound(SOUND_TRACK_BGD_MUSIC_PINK_PANTHER); // Stalking "Pink Panther" + hl_mode_flags_ |= ModeFlags::primalGpsFix; + if (!hl_mode_has_fix_ms_) + hl_mode_has_fix_ms_ = millis(); + break; + + default: + break; + } + last_hl_state_.gps_quality = t_hl_state.gps_quality; +} +void processSounds(const ll_status t_ll_state, const bool t_ros_running, const ll_high_level_state t_hl_state) { + // DEBUG_PRINTF("processSounds(ll_status.status_bitmask 0b" PRINTF_BINARY_PATTERN_INT8 "), sound_available_ %d, init_ms_ %dms, now %dms\n", PRINTF_BYTE_TO_BINARY_INT8(t_ll_state.status_bitmask), sound_available_, init_ms_, millis()); + + if (!handleAutoplay_(t_ll_state) || // Don't go-on as long as auto-play detection hasn't finished + !sound_available_) + return; // Still in auto-play detection + + myMP3.loop(); + + // Next cycle for sound processing reached? + static unsigned long next_cycle; + if (!(millis() > next_cycle)) { + return; + } + next_cycle = millis() + PROCESS_CYCLETIME; + + // If get docked, stop all current playing and clear queue + if (t_ll_state.v_charge > 20.0f && last_ll_state_.v_charge < 10.0f) { + myMP3.stop(); + background_track_def_ = {}; + active_sounds_.clear(); + } + last_ll_state_.v_charge = t_ll_state.v_charge; + + // Full sound support if DFP is set to 5V Vcc, but not before Pico is initialized. + if (dfp_is_5v_) { + handleEmergencies(t_ll_state, t_ros_running); + handleLowLevelChanges(t_ll_state, t_ros_running, t_hl_state); + + // ROS running changed + if (!last_ros_running_ && t_ros_running && !ros_running_since_) { + ros_running_since_ = millis(); + playSound(SOUND_TRACK_ADV_ROS_STARTUP_SUCCESS); // ROS startup successful + } else if (last_ros_running_ && !t_ros_running) { + ros_running_since_ = 0; + playSound(SOUND_TRACK_ADV_ROS_STOPPED); // ROS stopped + } + last_ros_running_ = t_ros_running; + + handleHighLevelChanges(t_hl_state); + handleGpsQuality(t_ll_state, t_ros_running, t_hl_state); + + // Generic, state-change-independent sounds + if (last_ros_running_) + playMowSound_(); + } // dfp_is_5v_ + + // Process sound queue + if (active_sounds_.empty()) + return; + + DfMp3_Status status = myMP3.getStatus(); + uint16_t current_track = myMP3.getCurrentTrack(); + DEBUG_PRINTF("DFP-Status %#04x, playing track %d\n", status.state, current_track); + + // Do not interrupt advert sound if it's still playing (don't use busy signal from status_bitmask as it's not reliable set by DFPlayer clones) + if (!current_playing_is_background_ && (status.state == DfMp3_StatusState_Playing || status.state == DfMp3_StatusState_Shuffling)) + return; + + // Cosmetic pause after advert sound + if (advert_track_def_.pauseAfter && last_advert_end_ + advert_track_def_.pauseAfter > millis()) + return; + + // Play next in queue + auto track_def = active_sounds_.front(); + // DEBUG_PRINTF("Next (num %d, type %d, flags " PRINTF_BINARY_PATTERN_INT8 ")\n", track_def.num, track_def.type, PRINTF_BYTE_TO_BINARY_INT8(track_def.flags)); + playSoundAdHoc(track_def); + active_sounds_.pop(); +} -int MP3Sound::processSounds() +namespace // anonymous (private) namespace { - int n = active_sounds.size(); - if (n == 0) return(n); - if (myMP3.isPlaying()) return(n); - - int file2play = active_sounds.back(); - myMP3.play(file2play); - active_sounds.pop_back(); +/** + * @brief Handles all "auto-play" related stuff like if the module has autoplay, + * and the possibly of an already played track (Hi I'm Steve) + * + * @return true if it already got handled + * @return false if not yet handled (because still in detection phase) + */ +bool handleAutoplay_(const ll_status t_ll_state) { + // DEBUG_PRINTF("handleAutoplay_(ll_status.status_bitmask 0b" PRINTF_BINARY_PATTERN_INT8 "), init_ms_ %dms, now %dms\n", PRINTF_BYTE_TO_BINARY_INT8(t_ll_state.status_bitmask), init_ms_, millis()); + + if (dfp_detection_status_ & DFP_DETECTION_BIT_END) + return true; + + // Has "auto-play" detection for the time of "Hi I'm Steve" + if (!(dfp_detection_status_ & DFP_DETECTION_BIT_HAS_AUTOPLAY) && millis() < DFP_AUTOPLAY_TIMEOUT) { + DEBUG_PRINTF("Auto-play check if busy at %dms (after init)\n", millis()); + if (t_ll_state.status_bitmask & LL_STATUS_BIT_SOUND_BUSY) { + dfp_detection_status_ |= DFP_DETECTION_BIT_HAS_AUTOPLAY; + DEBUG_PRINTLN("DFP is auto-playing"); + } else { + return false; + } + } + + if (!sound_available_) + return false; // Don't go on as long as sound isn't available + + dfp_detection_status_ |= DFP_DETECTION_BIT_END; + DEBUG_PRINTF("dfp_detection_status_ 0b" PRINTF_BINARY_PATTERN_INT8 " %dms (after init)\n", PRINTF_BYTE_TO_BINARY_INT8(dfp_detection_status_), millis()); + + // Play "Hi I'm Steve ..." if not autoplayed + if (!(dfp_detection_status_ & DFP_DETECTION_BIT_HAS_AUTOPLAY) || !(dfp_detection_status_ & DFP_DETECTION_BIT_ORIG_CARD_STRUCTURE)) { + myMP3.stop(); + delay(50); + playSound(SOUND_TRACK_ADV_HI_IM_STEVE); // Queue it instead of Adhoc, which save us another hacky delay + } + + // Success "wait" ping + if (dfp_is_5v_ && background_enabled_ && !(dfp_detection_status_ & DFP_DETECTION_BIT_ORIG_CARD_STRUCTURE)) + playSound(SOUND_TRACK_BGD_OM_BOOT); + + return true; +} - return active_sounds.size(); +/** + * @brief Play a randomized mow- background sound at randomized times + */ +void playMowSound_() { + static unsigned long last_mow_sound_started_ms = 0; + + if (HighLevelState::getMode(last_hl_state_.current_mode) != HighLevelState::Mode::AUTONOMOUS || last_hl_state_.gps_quality < 50) + return; + + unsigned long now = millis(); + if (now < (hl_mode_has_fix_ms_ + MOW_SOUND_INITIAL_FIX_DELAY) || + now < (last_mow_sound_started_ms + MOW_SOUND_MIN_PAUSE_AFTER)) + return; + + // Rand play on MOW_SOUND_CHANCE within next minute + uint16_t tries_per_minute = 60000 / PROCESS_CYCLETIME; + int dice = rand() % (tries_per_minute * 60000 / PROCESS_CYCLETIME); + // DEBUG_PRINTF("tries_per_minute %u, chance %u, rand %i\n", tries_per_minute, MOW_SOUND_CHANCE, dice); + if (dice > MOW_SOUND_CHANCE) + return; // No luck + + // Play sound + playSound(TrackDef{.num = (uint16_t)(200 + (rand() % 6)), .type = TrackType::BACKGROUND}); // Play background track 200 to 205 by random + last_mow_sound_started_ms = now; +} -} \ No newline at end of file +/** + * @brief Notification class required by DFMiniMP3's lib (see https://github.com/Makuna/DFMiniMp3/wiki/Notification-Method) + * Also handles background sound repeat. + */ +class Mp3Notify_ { + public: + static void OnError([[maybe_unused]] DfMp3 &mp3, uint16_t errorCode) { + DEBUG_PRINTF("Error: %#06x\n", errorCode); + last_error_code_ = errorCode; + } + + static void OnPlayFinished([[maybe_unused]] DfMp3 &mp3, [[maybe_unused]] DfMp3_PlaySources source, uint16_t track) { + static unsigned long last_finished_cb_call = 0; // Last DFPlayer OnPlayFinished() callback call (ms). Required for redundant call detection + static uint16_t last_finished_cb_track = 0; // Last DFPlayer OnPlayFinished() callback track. Required for redundant call detection + unsigned long now = millis(); + DEBUG_PRINTF("Finished track %d (now %lu ms)\n", track, now); + + // Redundant CB call protection + if (track == last_finished_cb_track && now < last_finished_cb_call + DFP_REDUNDANT_ONPLAYFINISH_CB_MAX) { + DEBUG_PRINTF("Redundant OnPLayFinish() call\n"); + return; + } + last_finished_cb_track = track; + last_finished_cb_call = now; + + // Required for pauseAfter calculation + if (!current_playing_is_background_) { + last_advert_end_ = now; + } + + // Repeat background sound handling + if (background_track_def_.num && background_track_def_.track_flags.repeat) { + DEBUG_PRINTF("Remaining repeat duration %i\n", background_track_def_.repeatDuration); + if (background_track_def_.repeatDuration > 0) { + unsigned long play_duration = now - current_playing_started_; + background_track_def_.repeatDuration -= play_duration; + playSoundAdHoc(background_track_def_); + } else { + delay(50); // (sometimes) required for "DFR LISP3" + myMP3.stop(); + background_track_def_ = {}; + } + } + } + + static void OnPlaySourceOnline([[maybe_unused]] DfMp3 &mp3, DfMp3_PlaySources source) { + DEBUG_PRINTF("Play-source online %#04x\n", source); + } + + static void OnPlaySourceInserted([[maybe_unused]] DfMp3 &mp3, DfMp3_PlaySources source) { + DEBUG_PRINTF("Play-source inserted %#04x\n", source); + } + + static void OnPlaySourceRemoved([[maybe_unused]] DfMp3 &mp3, DfMp3_PlaySources source) { + DEBUG_PRINTF("Play-source removed %#04x\n", source); + } +}; +} // namespace +} // namespace soundSystem \ No newline at end of file diff --git a/img/dfplayer-clone_cut-pin.png b/img/dfplayer-clone_cut-pin.png new file mode 100644 index 00000000..0903d974 Binary files /dev/null and b/img/dfplayer-clone_cut-pin.png differ