Keep RTTTL tones and queued speech running across timer rollover
This commit is contained in:
1 parent
9b4d3350df
commit
d01b63f0c3
20 files changed
+1070
No files matched your search
@@ -780,6 +780,8 @@ jobs:
|
||||
|
||||
- name: Verify Companion Bluetooth settings and WebConfig
|
||||
run: |
|
||||
python3 -B test/test_rtttl_rollover.py -v
|
||||
python3 -B test/test_rtttl_rollover_recipes.py -v
|
||||
python3 -B test/test_gps_voice_buttons.py -v
|
||||
python3 -B test/test_gps_voice_generator.py -v
|
||||
python3 -B test/test_button_voice_actions.py -v
|
||||
|
||||
@@ -85,6 +85,7 @@ monitor_filters = esp32_exception_decoder
|
||||
; it needs a custom bootloader; RP2040/STM32 have no A/B path yet.)
|
||||
extra_scripts =
|
||||
pre:scripts/client_acl_role.py
|
||||
pre:scripts/rtttl_rollover_fix.py
|
||||
pre:scripts/generate_webconfig_html.py
|
||||
pre:scripts/meshcore_image_identity.py
|
||||
pre:scripts/ota_device_deflate_role.py
|
||||
@@ -162,6 +163,7 @@ platform_packages =
|
||||
platformio/toolchain-gccarmnoneeabi@^1.140201.0
|
||||
extra_scripts = create-uf2.py
|
||||
pre:scripts/client_acl_role.py
|
||||
pre:scripts/rtttl_rollover_fix.py
|
||||
pre:scripts/ota_device_deflate_role.py
|
||||
pre:scripts/nrf52_usb_power_fix.py
|
||||
pre:scripts/nrf52_internal_flash_fix.py
|
||||
@@ -339,6 +341,7 @@ lib_deps = ${nrf52_lora_ota.lib_deps}
|
||||
[rp2040_base]
|
||||
extends = arduino_base
|
||||
extra_scripts = pre:scripts/client_acl_role.py
|
||||
pre:scripts/rtttl_rollover_fix.py
|
||||
post:scripts/check_firmware_ram.py
|
||||
upload_protocol = picotool
|
||||
board_build.core = earlephilhower
|
||||
@@ -359,6 +362,7 @@ extends = arduino_base
|
||||
platform = ststm32@19.5.0
|
||||
extra_scripts = post:arch/stm32/build_hex.py
|
||||
pre:scripts/client_acl_role.py
|
||||
pre:scripts/rtttl_rollover_fix.py
|
||||
post:scripts/check_firmware_ram.py
|
||||
build_flags = ${arduino_base.build_flags}
|
||||
-D STM32_PLATFORM
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
"""Fix reviewed RTTTL timers in private build copies, never in libdeps.
|
||||
|
||||
An absolute millis() comparison can truncate a tone at rollover or keep it
|
||||
busy until the next wrap, blocking queued speech. Use a signed 32-bit delta
|
||||
and make the initial deadline due now, including in the upper counter half.
|
||||
As with other Arduino deadline comparisons, waits must remain below 2^31 ms;
|
||||
the firmware's note durations and repeat gaps are far below that limit.
|
||||
"""
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Each entry binds the actual source, its matching header, and the result.
|
||||
# The recipes allow both releases; do not silently upgrade cached 1.3.0.
|
||||
REVIEWED = {
|
||||
"b50c159c8806d177398972bf8f94ca762532a855b60cb1d3aa9c3ebf021423ca": (
|
||||
"abea0a4870e58222fdefd1e88d291136518912e83880af30d1c85d39d7bd5cc1",
|
||||
"29204341d99b8adeef501c0d4cb47a4137534ebe6a30f2e6b3032d558aeef802",
|
||||
),
|
||||
"d4fddfa663c8adeacea3b1daf56ec5fbe63ecaaf68e5477ac96dc4fb2e28d363": (
|
||||
"5a33eec77e2ba53ed9b83a8242633b8ae11873d3b4f8cfd257abbb2eb61b6220",
|
||||
"e7710c4e3309b25517f5bace697c0c2574a5ce219f3cb156d352f739c178d75f",
|
||||
),
|
||||
}
|
||||
ORIGINAL_BEGIN = " noteDelay = 0;\n"
|
||||
PATCHED_BEGIN = " noteDelay = millis();\n"
|
||||
ORIGINAL_COMPARE = " if (m < noteDelay)\n"
|
||||
PATCHED_COMPARE = " if (static_cast<int32_t>(static_cast<uint32_t>(m - noteDelay)) < 0)\n"
|
||||
|
||||
|
||||
def digest(source):
|
||||
return hashlib.sha256(source.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def reviewed_pair(source):
|
||||
source_hash = digest(source.replace("\r\n", "\n"))
|
||||
if source_hash in REVIEWED:
|
||||
return REVIEWED[source_hash]
|
||||
for header_hash, patched_hash in REVIEWED.values():
|
||||
if source_hash == patched_hash:
|
||||
return header_hash, patched_hash
|
||||
raise RuntimeError("RTTTL timing fix: changed reviewed source; review dependency update")
|
||||
|
||||
|
||||
def patched_source(source):
|
||||
source = source.replace("\r\n", "\n")
|
||||
_, patched_hash = reviewed_pair(source)
|
||||
if digest(source) == patched_hash:
|
||||
return source
|
||||
if source.count(ORIGINAL_BEGIN) != 1 or source.count(ORIGINAL_COMPARE) != 1:
|
||||
raise RuntimeError("RTTTL timing fix: changed reviewed timing statements")
|
||||
source = source.replace(ORIGINAL_BEGIN, PATCHED_BEGIN, 1)
|
||||
source = source.replace(ORIGINAL_COMPARE, PATCHED_COMPARE, 1)
|
||||
# Version 1.4.0 has a degree symbol only in a disabled debug string.
|
||||
# Keep generated project-private source ASCII without changing playback.
|
||||
source = source.replace("\u00b0", "")
|
||||
if digest(source) != patched_hash:
|
||||
raise RuntimeError("RTTTL timing fix: unexpected transform result")
|
||||
source.encode("ascii")
|
||||
return source
|
||||
|
||||
|
||||
def write_if_changed(path, content):
|
||||
if not path.exists() or path.read_text(encoding="ascii") != content:
|
||||
path.write_text(content, encoding="ascii")
|
||||
|
||||
|
||||
def replace_rtttl_source(build_env, node):
|
||||
source = Path(node.srcnode().get_abspath())
|
||||
if source.name != "NonBlockingRtttl.cpp" or source.parent.name != "src":
|
||||
return node
|
||||
original = source.read_text(encoding="utf-8")
|
||||
header_hash, _ = reviewed_pair(original)
|
||||
try:
|
||||
header = source.with_suffix(".h").read_text(encoding="ascii").replace("\r\n", "\n")
|
||||
except (OSError, UnicodeError) as error:
|
||||
raise RuntimeError("RTTTL timing fix: missing or changed reviewed header") from error
|
||||
if digest(header) != header_hash:
|
||||
raise RuntimeError("RTTTL timing fix: changed reviewed header; review dependency update")
|
||||
patched = patched_source(original)
|
||||
destination = Path(build_env.subst("$BUILD_DIR")) / "patched-rtttl" / source.name
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
# A quoted include must resolve to the reviewed matching header, not a
|
||||
# same-named library earlier in the build clone's include path.
|
||||
write_if_changed(destination.with_suffix(".h"), header)
|
||||
write_if_changed(destination, patched)
|
||||
return build_env.File(str(destination))
|
||||
|
||||
|
||||
def install(build_env):
|
||||
build_env.AddBuildMiddleware(replace_rtttl_source, "*NonBlockingRtttl.cpp")
|
||||
|
||||
|
||||
if "Import" in globals():
|
||||
Import("env")
|
||||
install(env)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# This file contains a list of people who've made contribution to
|
||||
# the project. People who commit code are encouraged to add
|
||||
# their names here. Please keep the list sorted by first names.
|
||||
|
||||
Antoine Beauchamp <end2endzone@gmail.com>
|
||||
Chris Dennis <https://github.com/StarsoftAnalysis>
|
||||
James Brown <https://github.com/jbrown123>
|
||||
per1234 <https://github.com/per1234>
|
||||
Erwan d'Orgeville <https://github.com/edorgeville>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Antoine Beauchamp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
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.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
NonBlockingRTTTL 1.3.0 regression fixture
|
||||
|
||||
Source: https://github.com/end2endzone/NonBlockingRTTTL/tree/1.3.0
|
||||
The official 1.3.0 tag's source is vendored as ASCII JSON for offline tests.
|
||||
Decode the "source" field in src/NonBlockingRtttl.cpp.json to recover the
|
||||
exact reviewed upstream CPP. The header is unchanged. LICENSE and AUTHORS
|
||||
retain the original MIT attribution.
|
||||
No firmware code is linked from this directory.
|
||||
|
||||
Canonical normalization is LF line endings and ASCII-only text. The upstream
|
||||
tag already has both, so no bytes or executable statements are changed.
|
||||
|
||||
Unpatched CPP SHA256:
|
||||
b50c159c8806d177398972bf8f94ca762532a855b60cb1d3aa9c3ebf021423ca
|
||||
Patched private CPP SHA256:
|
||||
29204341d99b8adeef501c0d4cb47a4137534ebe6a30f2e6b3032d558aeef802
|
||||
Unchanged header SHA256:
|
||||
abea0a4870e58222fdefd1e88d291136518912e83880af30d1c85d39d7bd5cc1
|
||||
LICENSE SHA256:
|
||||
44bbc2541c595d5d8790f9b0be9a56fc88c9a500ab6bd2a88b4e00f8e28f0585
|
||||
Original AUTHORS SHA256 (trailing whitespace trimmed in copy):
|
||||
35b555001d2fd35c64002cb517bd1abce2d502b88540aff61d6ae5387b697c45
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"source": "// ---------------------------------------------------------------------------\n// Created by Antoine Beauchamp based on the code from the ToneLibrary\n//\n// See \"NonBlockingRtttl.h\" for purpose, syntax, version history, links, and more.\n// ---------------------------------------------------------------------------\n\n#include \"Arduino.h\"\n#include \"NonBlockingRtttl.h\"\n\n/*********************************************************\n * RTTTL Library data\n *********************************************************/\n\nnamespace rtttl\n{\n\nconst int notes[] = { 0,\nNOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,\nNOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,\nNOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,\nNOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7\n};\n\n//#define isdigit(n) (n >= '0' && n <= '9')\n#define OCTAVE_OFFSET 0\n\nconst char * buffer = \"\";\nint bufferIndex = -32760;\nbyte default_dur = 4;\nbyte default_oct = 5;\nint bpm = 63;\nlong wholenote;\nbyte pin = -1;\nunsigned long noteDelay = 0; //m will always be after which means the last note is done playing\nbool playing = false;\n\n//pre-declaration\nvoid nextnote();\n\n// tone() and noTone() are not implemented for Arduino core for the ESP32\n// See https://github.com/espressif/arduino-esp32/issues/980\n// and https://github.com/espressif/arduino-esp32/issues/1720\n#if defined(ESP32)\nvoid noTone(){\n ledcWrite(0, 0); // channel, volume\n}\n\nvoid noTone(int pin){\n noTone();\n}\n\nvoid tone(int frq) {\n ledcWriteTone(0, frq); // channel, freq\n ledcWrite(0, 255); // channel, volume\n}\n\nvoid tone(int pin, int frq, int duration){\n tone(frq);\n}\n#endif\n\nvoid begin(byte iPin, const char * iSongBuffer)\n{\n #ifdef RTTTL_NONBLOCKING_DEBUG\n Serial.print(\"playing: \");\n Serial.println(iSongBuffer);\n #endif\n \n //init values\n pin = iPin;\n #if defined(ESP32)\n ledcSetup(0, 1000, 10); // resolution always seems to be 10bit, no matter what is given\n ledcAttachPin(pin, 0);\n #endif\n buffer = iSongBuffer;\n bufferIndex = 0;\n default_dur = 4;\n default_oct = 6;\n bpm=63;\n playing = true;\n noteDelay = 0;\n #ifdef RTTTL_NONBLOCKING_DEBUG\n Serial.print(\"noteDelay=\");\n Serial.println(noteDelay);\n #endif\n \n //stop current note\n noTone(pin);\n\n //read buffer until first note\n int num;\n\n // format: d=N,o=N,b=NNN:\n // find the start (skip name, etc)\n\n while(*buffer != ':') buffer++; // ignore name\n buffer++; // skip ':'\n\n // get default duration\n if(*buffer == 'd')\n {\n buffer++; buffer++; // skip \"d=\"\n num = 0;\n while(isdigit(*buffer))\n {\n num = (num * 10) + (*buffer++ - '0');\n }\n if(num > 0) default_dur = num;\n buffer++; // skip comma\n }\n\n #ifdef RTTTL_NONBLOCKING_INFO\n Serial.print(\"ddur: \"); Serial.println(default_dur, 10);\n #endif\n \n // get default octave\n if(*buffer == 'o')\n {\n buffer++; buffer++; // skip \"o=\"\n num = *buffer++ - '0';\n if(num >= 3 && num <=7) default_oct = num;\n buffer++; // skip comma\n }\n\n #ifdef RTTTL_NONBLOCKING_INFO\n Serial.print(\"doct: \"); Serial.println(default_oct, 10);\n #endif\n \n // get BPM\n if(*buffer == 'b')\n {\n buffer++; buffer++; // skip \"b=\"\n num = 0;\n while(isdigit(*buffer))\n {\n num = (num * 10) + (*buffer++ - '0');\n }\n bpm = num;\n buffer++; // skip colon\n }\n\n #ifdef RTTTL_NONBLOCKING_INFO\n Serial.print(\"bpm: \"); Serial.println(bpm, 10);\n #endif\n\n // BPM usually expresses the number of quarter notes per minute\n wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole note (in milliseconds)\n\n #ifdef RTTTL_NONBLOCKING_INFO\n Serial.print(\"wn: \"); Serial.println(wholenote, 10);\n #endif\n}\n\nvoid nextnote()\n{\n long duration;\n byte note;\n byte scale;\n\n //stop current note\n noTone(pin);\n\n // first, get note duration, if available\n int num = 0;\n while(isdigit(*buffer))\n {\n num = (num * 10) + (*buffer++ - '0');\n }\n \n if(num) duration = wholenote / num;\n else duration = wholenote / default_dur; // we will need to check if we are a dotted note after\n\n // now get the note\n note = 0;\n\n switch(*buffer)\n {\n case 'c':\n note = 1;\n break;\n case 'd':\n note = 3;\n break;\n case 'e':\n note = 5;\n break;\n case 'f':\n note = 6;\n break;\n case 'g':\n note = 8;\n break;\n case 'a':\n note = 10;\n break;\n case 'b':\n note = 12;\n break;\n case 'p':\n default:\n note = 0;\n Line truncated
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// AUTHOR/LICENSE:
|
||||
// The following code was written by Antoine Beauchamp. For other authors, see AUTHORS file.
|
||||
// The code & updates for the library can be found at http://github.com/end2endzone/NonBlockingRTTTL
|
||||
// Original source code for the RTTL player: https://code.google.com/archive/p/rogue-code/wikis/ToneLibraryDocumentation.wiki
|
||||
// MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#ifndef NonBlockingRtttl_h
|
||||
#define NonBlockingRtttl_h
|
||||
|
||||
#define NONBLOCKINGRTTTL_VERSION 1.3.0
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
//if notes are not already defined
|
||||
#ifndef NOTE_B0
|
||||
|
||||
#define NOTE_H 0
|
||||
#define NOTE_B0 31
|
||||
#define NOTE_C1 33
|
||||
#define NOTE_CS1 35
|
||||
#define NOTE_D1 37
|
||||
#define NOTE_DS1 39
|
||||
#define NOTE_E1 41
|
||||
#define NOTE_F1 44
|
||||
#define NOTE_FS1 46
|
||||
#define NOTE_G1 49
|
||||
#define NOTE_GS1 52
|
||||
#define NOTE_A1 55
|
||||
#define NOTE_AS1 58
|
||||
#define NOTE_B1 62
|
||||
#define NOTE_C2 65
|
||||
#define NOTE_CS2 69
|
||||
#define NOTE_D2 73
|
||||
#define NOTE_DS2 78
|
||||
#define NOTE_E2 82
|
||||
#define NOTE_F2 87
|
||||
#define NOTE_FS2 93
|
||||
#define NOTE_G2 98
|
||||
#define NOTE_GS2 104
|
||||
#define NOTE_A2 110
|
||||
#define NOTE_AS2 117
|
||||
#define NOTE_B2 123
|
||||
#define NOTE_C3 131
|
||||
#define NOTE_CS3 139
|
||||
#define NOTE_D3 147
|
||||
#define NOTE_DS3 156
|
||||
#define NOTE_E3 165
|
||||
#define NOTE_F3 175
|
||||
#define NOTE_FS3 185
|
||||
#define NOTE_G3 196
|
||||
#define NOTE_GS3 208
|
||||
#define NOTE_A3 220
|
||||
#define NOTE_AS3 233
|
||||
#define NOTE_B3 247
|
||||
#define NOTE_C4 262
|
||||
#define NOTE_CS4 277
|
||||
#define NOTE_D4 294
|
||||
#define NOTE_DS4 311
|
||||
#define NOTE_E4 330
|
||||
#define NOTE_F4 349
|
||||
#define NOTE_FS4 370
|
||||
#define NOTE_G4 392
|
||||
#define NOTE_GS4 415
|
||||
#define NOTE_A4 440
|
||||
#define NOTE_AS4 466
|
||||
#define NOTE_B4 494
|
||||
#define NOTE_C5 523
|
||||
#define NOTE_CS5 554
|
||||
#define NOTE_D5 587
|
||||
#define NOTE_DS5 622
|
||||
#define NOTE_E5 659
|
||||
#define NOTE_F5 698
|
||||
#define NOTE_FS5 740
|
||||
#define NOTE_G5 784
|
||||
#define NOTE_GS5 831
|
||||
#define NOTE_A5 880
|
||||
#define NOTE_AS5 932
|
||||
#define NOTE_B5 988
|
||||
#define NOTE_C6 1047
|
||||
#define NOTE_CS6 1109
|
||||
#define NOTE_D6 1175
|
||||
#define NOTE_DS6 1245
|
||||
#define NOTE_E6 1319
|
||||
#define NOTE_F6 1397
|
||||
#define NOTE_FS6 1480
|
||||
#define NOTE_G6 1568
|
||||
#define NOTE_GS6 1661
|
||||
#define NOTE_A6 1760
|
||||
#define NOTE_AS6 1865
|
||||
#define NOTE_B6 1976
|
||||
#define NOTE_C7 2093
|
||||
#define NOTE_CS7 2217
|
||||
#define NOTE_D7 2349
|
||||
#define NOTE_DS7 2489
|
||||
#define NOTE_E7 2637
|
||||
#define NOTE_F7 2794
|
||||
#define NOTE_FS7 2960
|
||||
#define NOTE_G7 3136
|
||||
#define NOTE_GS7 3322
|
||||
#define NOTE_A7 3520
|
||||
#define NOTE_AS7 3729
|
||||
#define NOTE_B7 3951
|
||||
#define NOTE_C8 4186
|
||||
#define NOTE_CS8 4435
|
||||
#define NOTE_D8 4699
|
||||
#define NOTE_DS8 4978
|
||||
|
||||
//if notes are not already defined
|
||||
#endif
|
||||
|
||||
//#define RTTTL_NONBLOCKING_DEBUG
|
||||
//#define RTTTL_NONBLOCKING_INFO
|
||||
|
||||
namespace rtttl
|
||||
{
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* begin() setups the NON-BLOCKING RTTTL library for
|
||||
* decoding a new RTTTL song.
|
||||
* Parameters:
|
||||
* iPin: The pin which is connected to the piezo buffer.
|
||||
* iSongBuffer: The string buffer of the RTTTL song.
|
||||
****************************************************************************/
|
||||
void begin(byte iPin, const char * iSongBuffer);
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* play() automatically plays a new note when required.
|
||||
* This function must constantly be called within the loop() function.
|
||||
* Warning: inserting too long delays within the loop function may
|
||||
* disrupt the NON-BLOCKING RTTTL library from playing properly.
|
||||
****************************************************************************/
|
||||
void play();
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* stop() stops playing the current song.
|
||||
****************************************************************************/
|
||||
void stop();
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* isPlaying() return true when the library is playing
|
||||
* the given RTTTL song.
|
||||
****************************************************************************/
|
||||
bool isPlaying();
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* done() return true when the library is done playing
|
||||
* the given RTTTL song.
|
||||
****************************************************************************/
|
||||
bool done();
|
||||
|
||||
}; //namespace
|
||||
|
||||
//NonBlockingRtttl_h
|
||||
#endif
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# This file contains a list of people who've made contribution to
|
||||
# the project. People who commit code are encouraged to add
|
||||
# their names here. Please keep the list sorted by first names.
|
||||
|
||||
Antoine Beauchamp <end2endzone@gmail.com>
|
||||
Chris Dennis <https://github.com/StarsoftAnalysis>
|
||||
James Brown <https://github.com/jbrown123>
|
||||
per1234 <https://github.com/per1234>
|
||||
Erwan d'Orgeville <https://github.com/edorgeville>
|
||||
Emanuele Palombo <https://github.com/elbowz>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Antoine Beauchamp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
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.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
NonBlockingRTTTL 1.4.0 regression fixture
|
||||
|
||||
Source: PlatformIO end2endzone/NonBlockingRTTTL 1.4.0
|
||||
Upstream: https://github.com/end2endzone/NonBlockingRTTTL
|
||||
The installed library's source is vendored as ASCII JSON for offline tests.
|
||||
Decode the "source" field in src/NonBlockingRtttl.cpp.json to recover the
|
||||
exact reviewed upstream CPP, including its degree symbol. The header is
|
||||
unchanged. LICENSE and AUTHORS retain the original MIT attribution.
|
||||
No firmware code is linked from this directory.
|
||||
|
||||
Canonical source normalization for hash comparison is LF line endings.
|
||||
The fixture file uses a JSON escape for the single U+00B0 degree symbol in
|
||||
the disabled diagnostic string "Loop n". The production private-build patch
|
||||
removes that diagnostic-only symbol to make generated copies ASCII as well.
|
||||
No executable statement in the unpatched fixture is changed.
|
||||
|
||||
Original installed CPP SHA256:
|
||||
d4fddfa663c8adeacea3b1daf56ec5fbe63ecaaf68e5477ac96dc4fb2e28d363
|
||||
Patched private CPP SHA256:
|
||||
e7710c4e3309b25517f5bace697c0c2574a5ce219f3cb156d352f739c178d75f
|
||||
Unchanged header SHA256:
|
||||
5a33eec77e2ba53ed9b83a8242633b8ae11873d3b4f8cfd257abbb2eb61b6220
|
||||
LICENSE SHA256:
|
||||
44bbc2541c595d5d8790f9b0be9a56fc88c9a500ab6bd2a88b4e00f8e28f0585
|
||||
Original AUTHORS SHA256 (trailing whitespace trimmed in copy):
|
||||
5ee7d6fd1d7730b5d9184accadc45ec5ee67e57bca50ed1729d0043acf8ff303
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"source": "// ---------------------------------------------------------------------------\n// Created by Antoine Beauchamp based on the code from the ToneLibrary\n//\n// See \"NonBlockingRtttl.h\" for purpose, syntax, version history, links, and more.\n// ---------------------------------------------------------------------------\n\n#include \"Arduino.h\"\n#include \"NonBlockingRtttl.h\"\n\n/*********************************************************\n * RTTTL Library data\n *********************************************************/\n\nnamespace rtttl\n{\n\nconst int notes[] = { 0,\nNOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,\nNOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,\nNOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,\nNOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7\n};\n\n//#define isdigit(n) (n >= '0' && n <= '9')\n#define OCTAVE_OFFSET 0\n\nconst char * buffer = \"\";\nconst char * firstNote = \"\";\nint bufferIndex = -32760;\nbyte default_dur = 4;\nbyte default_oct = 5;\nint bpm = 63;\nlong wholenote;\nbyte pin = -1;\nunsigned long noteDelay = 0; //m will always be after which means the last note is done playing\nunsigned long loopGap;\nbyte loopCount;\nbool playing = false;\n\n//pre-declaration\nvoid nextnote();\n\n#ifdef ESP32\n#ifdef ESP_ARDUINO_VERSION\n #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)\n // Code specific to ESP32 core 3.x\n // Core 3.x manages channels automatically:\n // In all esp32 functions, input parameter 'channel' has been changed to 'pin'.\n // See official documentation for migrating from core 2.x to 3.x:\n // https://docs.espressif.com/projects/arduino-esp32/en/latest/migration_guides/2.x_to_3.0.html\n\n #define LEDC_RESOLUTION 10\n\n // Function noTone() stop the PWM signal for the given pin.\n // It does not detach the pin from it's assigned channel.\n // You can have sequential calls to tone() and noTone()\n // without having to call toneSetup() between calls.\n void noTone(uint8_t pin) {\n ledcWriteTone(pin, 0);\n }\n\n // Function tone() set a pin to output a PWM signal that matches the given frequency.\n // The duration argument is ignored. The function signature \n // matches arduino's tone() function for compatibility reasons.\n // Arduino tone() function:\n // https://docs.arduino.cc/language-reference/en/functions/advanced-io/tone/\n // ESP32 ledcWriteTone() function:\n // https://github.com/espressif/arduino-esp32/blob/2.0.17/cores/esp32/esp32-hal-ledc.c#L118\n void tone(uint8_t pin, unsigned int frq, unsigned long duration) {\n // don't care about the given duration\n ledcWriteTone(pin, frq);\n }\n\n // Function toneSetup() setup the given pin to output a PWM signal for generating tones with a piezo buzzer.\n void toneSetup(uint8_t pin) {\n ledcAttach(pin, 1000, LEDC_RESOLUTION);\n }\n \n #elif ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0)\n // Code specific to ESP32 core 2.x\n // Core 2.x uses channels instead of pins:\n // In all esp32 functions, input parameter is a 'channel' instead of a given pin.\n // This library uses the function getEsp32ChannelForPin() to map a pin to a channel number.\n // This channel is then used with all esp32 apis functions.\n // See official documentation for migrating from core 2.x to 3.x:\n // https://docs.espressif.com/projects/arduino-esp32/en/latest/migration_guides/2.x_to_3.0.html\n\n #define ESP32_INVALID_CHANNEL 0xFF\n\n // Function getEsp32ChannelForPin() maps a given pin to a esp32 channel.\n // Returns a value between 0 and n where n is the maximum of channel for your board.\n // Returns ESP32_INVALID_CHANNEL if there is no assigned channel for the given pin number.\n // See your board documentation for details.\n // See https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/ledc.html#led-control-ledc\n uint8_t getEsp32ChannelForPin(uint8_t pin) {\n // This library only supports 1 pin at a time.\n // Always returns channel 0\n return 0;\n }\n\n // Function noTone() stop the PWM signal for the given pin.\n // It does not detach the pin from it's assigned channel.\n // You can have sequential calls to tone() and noTone()\n // without having to call toneSetup() between calls.\n void noTone(uint8_t pin) {\n uint8_t channel = getEsp32ChannelForPin(pin);\n ledcWriteTone(channel, 0); // Silence the buzzer without detaching\n }\n\n // Function tone() set a pin to output a PWM signal that matches the given frequency.\n // The duration argument is ignored. The function signature \n // matches arduino's tone() function foLine truncated
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// AUTHOR/LICENSE:
|
||||
// The following code was written by Antoine Beauchamp. For other authors, see AUTHORS file.
|
||||
// The code & updates for the library can be found at http://github.com/end2endzone/NonBlockingRTTTL
|
||||
// Original source code for the RTTL player: https://code.google.com/archive/p/rogue-code/wikis/ToneLibraryDocumentation.wiki
|
||||
// MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#ifndef NonBlockingRtttl_h
|
||||
#define NonBlockingRtttl_h
|
||||
|
||||
#define NONBLOCKINGRTTTL_VERSION 1.4.0
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
//if notes are not already defined
|
||||
#ifndef NOTE_B0
|
||||
|
||||
#define NOTE_H 0
|
||||
#define NOTE_B0 31
|
||||
#define NOTE_C1 33
|
||||
#define NOTE_CS1 35
|
||||
#define NOTE_D1 37
|
||||
#define NOTE_DS1 39
|
||||
#define NOTE_E1 41
|
||||
#define NOTE_F1 44
|
||||
#define NOTE_FS1 46
|
||||
#define NOTE_G1 49
|
||||
#define NOTE_GS1 52
|
||||
#define NOTE_A1 55
|
||||
#define NOTE_AS1 58
|
||||
#define NOTE_B1 62
|
||||
#define NOTE_C2 65
|
||||
#define NOTE_CS2 69
|
||||
#define NOTE_D2 73
|
||||
#define NOTE_DS2 78
|
||||
#define NOTE_E2 82
|
||||
#define NOTE_F2 87
|
||||
#define NOTE_FS2 93
|
||||
#define NOTE_G2 98
|
||||
#define NOTE_GS2 104
|
||||
#define NOTE_A2 110
|
||||
#define NOTE_AS2 117
|
||||
#define NOTE_B2 123
|
||||
#define NOTE_C3 131
|
||||
#define NOTE_CS3 139
|
||||
#define NOTE_D3 147
|
||||
#define NOTE_DS3 156
|
||||
#define NOTE_E3 165
|
||||
#define NOTE_F3 175
|
||||
#define NOTE_FS3 185
|
||||
#define NOTE_G3 196
|
||||
#define NOTE_GS3 208
|
||||
#define NOTE_A3 220
|
||||
#define NOTE_AS3 233
|
||||
#define NOTE_B3 247
|
||||
#define NOTE_C4 262
|
||||
#define NOTE_CS4 277
|
||||
#define NOTE_D4 294
|
||||
#define NOTE_DS4 311
|
||||
#define NOTE_E4 330
|
||||
#define NOTE_F4 349
|
||||
#define NOTE_FS4 370
|
||||
#define NOTE_G4 392
|
||||
#define NOTE_GS4 415
|
||||
#define NOTE_A4 440
|
||||
#define NOTE_AS4 466
|
||||
#define NOTE_B4 494
|
||||
#define NOTE_C5 523
|
||||
#define NOTE_CS5 554
|
||||
#define NOTE_D5 587
|
||||
#define NOTE_DS5 622
|
||||
#define NOTE_E5 659
|
||||
#define NOTE_F5 698
|
||||
#define NOTE_FS5 740
|
||||
#define NOTE_G5 784
|
||||
#define NOTE_GS5 831
|
||||
#define NOTE_A5 880
|
||||
#define NOTE_AS5 932
|
||||
#define NOTE_B5 988
|
||||
#define NOTE_C6 1047
|
||||
#define NOTE_CS6 1109
|
||||
#define NOTE_D6 1175
|
||||
#define NOTE_DS6 1245
|
||||
#define NOTE_E6 1319
|
||||
#define NOTE_F6 1397
|
||||
#define NOTE_FS6 1480
|
||||
#define NOTE_G6 1568
|
||||
#define NOTE_GS6 1661
|
||||
#define NOTE_A6 1760
|
||||
#define NOTE_AS6 1865
|
||||
#define NOTE_B6 1976
|
||||
#define NOTE_C7 2093
|
||||
#define NOTE_CS7 2217
|
||||
#define NOTE_D7 2349
|
||||
#define NOTE_DS7 2489
|
||||
#define NOTE_E7 2637
|
||||
#define NOTE_F7 2794
|
||||
#define NOTE_FS7 2960
|
||||
#define NOTE_G7 3136
|
||||
#define NOTE_GS7 3322
|
||||
#define NOTE_A7 3520
|
||||
#define NOTE_AS7 3729
|
||||
#define NOTE_B7 3951
|
||||
#define NOTE_C8 4186
|
||||
#define NOTE_CS8 4435
|
||||
#define NOTE_D8 4699
|
||||
#define NOTE_DS8 4978
|
||||
|
||||
//if notes are not already defined
|
||||
#endif
|
||||
|
||||
//#define RTTTL_NONBLOCKING_DEBUG
|
||||
//#define RTTTL_NONBLOCKING_INFO
|
||||
|
||||
namespace rtttl
|
||||
{
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* begin() setups the NON-BLOCKING RTTTL library for
|
||||
* decoding a new RTTTL song.
|
||||
* Parameters:
|
||||
* iPin: The pin which is connected to the piezo buffer.
|
||||
* iSongBuffer: The string buffer of the RTTTL song.
|
||||
* iLoopCount: Number of times the RTTTL song will be playback (default = 1).
|
||||
* iLoopGap: Pause of milliseconds between the song repetition (default = 1000ms).
|
||||
****************************************************************************/
|
||||
void begin(byte iPin, const char * iSongBuffer, byte iLoopCount = 1, unsigned long iLoopGap = 1000);
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* play() automatically plays a new note when required.
|
||||
* This function must constantly be called within the loop() function.
|
||||
* Warning: inserting too long delays within the loop function may
|
||||
* disrupt the NON-BLOCKING RTTTL library from playing properly.
|
||||
****************************************************************************/
|
||||
void play();
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* stop() stops playing the current song.
|
||||
****************************************************************************/
|
||||
void stop();
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* isPlaying() return true when the library is playing
|
||||
* the given RTTTL song.
|
||||
****************************************************************************/
|
||||
bool isPlaying();
|
||||
|
||||
/****************************************************************************
|
||||
* Description:
|
||||
* done() return true when the library is done playing
|
||||
* the given RTTTL song.
|
||||
****************************************************************************/
|
||||
bool done();
|
||||
|
||||
}; //namespace
|
||||
|
||||
//NonBlockingRtttl_h
|
||||
#endif
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
typedef unsigned char byte;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef int int32_t;
|
||||
typedef unsigned int size_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef short int16_t;
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned int uintptr_t;
|
||||
#define LOW 0
|
||||
#define HIGH 1
|
||||
#define OUTPUT 1
|
||||
extern uint32_t now_ms;
|
||||
extern unsigned tone_calls;
|
||||
extern unsigned silence_calls;
|
||||
inline uint32_t millis() { return now_ms; }
|
||||
inline int isdigit(int c) { return c >= '0' && c <= '9'; }
|
||||
inline void tone(byte, unsigned int, unsigned long) { ++tone_calls; }
|
||||
inline void noTone(byte) { ++silence_calls; }
|
||||
inline void pinMode(int, int) {}
|
||||
inline void digitalWrite(int, int) {}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
struct Sequence { uintptr_t PTR; uint32_t CNT, REFRESH, ENDDELAY; };
|
||||
struct Registers {
|
||||
uint32_t INTENCLR, SHORTS, ENABLE, MODE, COUNTERTOP, PRESCALER,
|
||||
DECODER, LOOP, EVENTS_STOPPED, EVENTS_LOOPSDONE, INTENSET;
|
||||
Sequence SEQ[2];
|
||||
uint32_t EVENTS_SEQEND[2], EVENTS_SEQSTARTED[2], TASKS_SEQSTART[2];
|
||||
};
|
||||
static Registers registers;
|
||||
#define NRF_PWM3 (®isters)
|
||||
#define PWM_MODE_UPDOWN_Up 0
|
||||
#define PWM_PRESCALER_PRESCALER_DIV_1 0
|
||||
#define PWM_DECODER_LOAD_Common 0
|
||||
#define PWM_SHORTS_LOOPSDONE_SEQSTART0_Msk 4
|
||||
#define PWM_INTENSET_SEQEND0_Msk 1
|
||||
#define PWM_INTENSET_SEQEND1_Msk 2
|
||||
#define PWM3_IRQn 45
|
||||
inline void NVIC_DisableIRQ(int) {}
|
||||
inline void NVIC_EnableIRQ(int) {}
|
||||
inline void NVIC_ClearPendingIRQ(int) {}
|
||||
inline void NVIC_SetPriority(int, int) {}
|
||||
struct HardwarePWM {
|
||||
bool owned;
|
||||
bool takeOwnership(uint32_t) { if (owned) return false; return owned = true; }
|
||||
bool addPin(int) { return true; }
|
||||
void removePin(int) {}
|
||||
bool releaseOwnership(uint32_t) { owned = false; return true; }
|
||||
};
|
||||
static HardwarePWM HwPWM3;
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
#define PIN_BUZZER 25
|
||||
#define MESH_GPS_VOICE 1
|
||||
#include <helpers/ui/buzzer.cpp>
|
||||
static_assert(sizeof(unsigned long) == 4, "Use device-width unsigned long");
|
||||
uint32_t now_ms = 0;
|
||||
unsigned tone_calls = 0;
|
||||
unsigned silence_calls = 0;
|
||||
extern "C" void* memset(void* dst, int value, unsigned length) {
|
||||
unsigned char* bytes = static_cast<unsigned char*>(dst);
|
||||
for (unsigned i = 0; i < length; ++i) bytes[i] = static_cast<unsigned char>(value);
|
||||
return dst;
|
||||
}
|
||||
static void quit(unsigned code) {
|
||||
__asm__ volatile("int $0x80" : : "a"(1), "b"(code));
|
||||
__builtin_unreachable();
|
||||
}
|
||||
static void check(bool okay, unsigned line) {
|
||||
if (!okay) quit(line);
|
||||
}
|
||||
#define CHECK(expression) check((expression), __LINE__)
|
||||
extern "C" void _start() {
|
||||
genericBuzzer buzzer;
|
||||
buzzer.begin();
|
||||
now_ms = 0xffffffffU - 100;
|
||||
CHECK(buzzer.speakGps(true));
|
||||
buzzer.loop();
|
||||
CHECK(tone_calls == 1 && buzzer.isPlaying() && !registers.ENABLE);
|
||||
now_ms = 0xffffffffU - 10;
|
||||
buzzer.loop();
|
||||
CHECK(tone_calls == 1 && buzzer.isPlaying());
|
||||
now_ms = 20;
|
||||
buzzer.loop();
|
||||
CHECK(tone_calls == 2 && buzzer.isPlaying() && !registers.ENABLE);
|
||||
now_ms += 94; buzzer.loop();
|
||||
CHECK(tone_calls == 3 && !registers.ENABLE);
|
||||
now_ms += 188; buzzer.loop();
|
||||
CHECK(rtttl::done() && !registers.ENABLE);
|
||||
buzzer.loop(); // Start the intentional speech gap.
|
||||
now_ms += 99; buzzer.loop();
|
||||
CHECK(!registers.ENABLE);
|
||||
now_ms += 1; buzzer.loop();
|
||||
CHECK(registers.ENABLE && buzzer.isPlaying() && HwPWM3.owned);
|
||||
buzzer.stop();
|
||||
CHECK(!registers.ENABLE && !buzzer.isPlaying() && !HwPWM3.owned);
|
||||
quit(0);
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
#include <NonBlockingRtttl.h>
|
||||
static_assert(sizeof(unsigned long) == 4, "Use device-width unsigned long");
|
||||
uint32_t now_ms = 0;
|
||||
unsigned tone_calls = 0;
|
||||
unsigned silence_calls = 0;
|
||||
extern unsigned long noteDelay;
|
||||
static const char rising[] = "Startup:d=4,o=5,b=160:16c6,16e6,8g6";
|
||||
static const char empty[] = "Empty:d=4,o=5,b=160:";
|
||||
static void quit(unsigned code) {
|
||||
__asm__ volatile("int $0x80" : : "a"(1), "b"(code));
|
||||
__builtin_unreachable();
|
||||
}
|
||||
static void check(bool okay, unsigned line) {
|
||||
if (!okay) quit(line);
|
||||
}
|
||||
#define CHECK(expression) check((expression), __LINE__)
|
||||
|
||||
static void advance(uint32_t amount) {
|
||||
now_ms += amount;
|
||||
rtttl::play();
|
||||
}
|
||||
static void checkStartup(uint32_t origin) {
|
||||
now_ms = origin;
|
||||
tone_calls = silence_calls = 0;
|
||||
rtttl::begin(25, rising);
|
||||
CHECK(rtttl::isPlaying() && tone_calls == 0);
|
||||
rtttl::play();
|
||||
CHECK(tone_calls == 1 && rtttl::isPlaying());
|
||||
for (unsigned i = 0; i < 4; ++i) rtttl::play();
|
||||
CHECK(tone_calls == 1 && rtttl::isPlaying());
|
||||
advance(93);
|
||||
CHECK(tone_calls == 1 && rtttl::isPlaying());
|
||||
advance(1);
|
||||
CHECK(tone_calls == 2 && rtttl::isPlaying());
|
||||
advance(93);
|
||||
CHECK(tone_calls == 2 && rtttl::isPlaying());
|
||||
advance(1);
|
||||
CHECK(tone_calls == 3 && rtttl::isPlaying());
|
||||
advance(187);
|
||||
CHECK(tone_calls == 3 && rtttl::isPlaying());
|
||||
advance(1);
|
||||
CHECK(tone_calls == 3 && rtttl::done());
|
||||
}
|
||||
extern "C" void _start() {
|
||||
// Identical note timing, including first note in the upper counter half.
|
||||
for (unsigned i = 0; i < 5; ++i) {
|
||||
const uint32_t origins[] = {0, 1000, 0x80000000U, 0xfffffff0U, 0xffffffffU - 100};
|
||||
checkStartup(origins[i]);
|
||||
}
|
||||
// A foreground loop which crosses an expired pre-wrap deadline must
|
||||
// continue the melody and allow the pending control clip to start.
|
||||
now_ms = 0xffffffffU - 100;
|
||||
tone_calls = 0;
|
||||
rtttl::begin(25, rising);
|
||||
rtttl::play();
|
||||
CHECK(tone_calls == 1);
|
||||
advance(90);
|
||||
CHECK(tone_calls == 1);
|
||||
now_ms = 20;
|
||||
rtttl::play();
|
||||
CHECK(tone_calls == 2 && rtttl::isPlaying());
|
||||
advance(94);
|
||||
CHECK(tone_calls == 3);
|
||||
advance(188);
|
||||
CHECK(rtttl::done());
|
||||
|
||||
#ifndef RTTTL_NO_LOOPS
|
||||
// Repeating gap also wraps safely; no new note during the gap.
|
||||
now_ms = 0xffffffffU - 500;
|
||||
tone_calls = 0;
|
||||
rtttl::begin(25, rising, 2, 1000);
|
||||
rtttl::play(); advance(94); advance(94); advance(188);
|
||||
CHECK(rtttl::isPlaying() && tone_calls == 3);
|
||||
advance(999);
|
||||
CHECK(tone_calls == 3);
|
||||
advance(1);
|
||||
CHECK(tone_calls == 4);
|
||||
advance(94); advance(94); advance(188);
|
||||
CHECK(rtttl::done() && tone_calls == 6);
|
||||
#endif
|
||||
|
||||
// A rest uses the duration directly, without the audible note's extra ms.
|
||||
now_ms = 0xfffffff0U;
|
||||
tone_calls = 0;
|
||||
rtttl::begin(25, "Rest:d=4,o=5,b=160:16p,16c6");
|
||||
rtttl::play();
|
||||
CHECK(tone_calls == 0 && rtttl::isPlaying());
|
||||
advance(92);
|
||||
CHECK(tone_calls == 0 && rtttl::isPlaying());
|
||||
advance(1);
|
||||
CHECK(tone_calls == 1 && rtttl::isPlaying());
|
||||
advance(93);
|
||||
CHECK(rtttl::isPlaying());
|
||||
advance(1);
|
||||
CHECK(rtttl::done());
|
||||
|
||||
// Empty melody, stop, and replacement do not remain artificially busy.
|
||||
now_ms = 0xfffffff0U;
|
||||
tone_calls = 0;
|
||||
rtttl::begin(25, empty);
|
||||
rtttl::play(); CHECK(rtttl::done() && tone_calls == 0);
|
||||
rtttl::begin(25, rising); rtttl::play();
|
||||
CHECK(tone_calls == 1);
|
||||
rtttl::stop(); CHECK(rtttl::done());
|
||||
advance(500); CHECK(tone_calls == 1);
|
||||
rtttl::begin(25, rising); rtttl::play();
|
||||
CHECK(tone_calls == 2 && rtttl::isPlaying());
|
||||
rtttl::stop();
|
||||
quit(0);
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
#include "Arduino.h"
|
||||
@@ -0,0 +1,232 @@
|
||||
"""Exercise reviewed real RTTTL dependencies with actual 32-bit clock widths.
|
||||
|
||||
The freestanding ELF32 binaries do not require 32-bit libc/libstdc++ packages.
|
||||
Only the clock, pins, IRQ controller and PWM registers are mocked. The queue
|
||||
test compiles the real buzzer, decoder, clip catalog and voice-player sources.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import importlib.util
|
||||
import json
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
FIXTURES = Path(__file__).resolve().parent / "fixtures/rtttl_rollover"
|
||||
SPEC = importlib.util.spec_from_file_location("rtttl_rollover_fix", ROOT / "scripts/rtttl_rollover_fix.py")
|
||||
FIX = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(FIX)
|
||||
CXX = shutil.which("g++")
|
||||
VERSIONS = ("1.3.0", "1.4.0")
|
||||
PINS = {
|
||||
"1.3.0": (
|
||||
"b50c159c8806d177398972bf8f94ca762532a855b60cb1d3aa9c3ebf021423ca",
|
||||
"abea0a4870e58222fdefd1e88d291136518912e83880af30d1c85d39d7bd5cc1",
|
||||
"29204341d99b8adeef501c0d4cb47a4137534ebe6a30f2e6b3032d558aeef802",
|
||||
),
|
||||
"1.4.0": (
|
||||
"d4fddfa663c8adeacea3b1daf56ec5fbe63ecaaf68e5477ac96dc4fb2e28d363",
|
||||
"5a33eec77e2ba53ed9b83a8242633b8ae11873d3b4f8cfd257abbb2eb61b6220",
|
||||
"e7710c4e3309b25517f5bace697c0c2574a5ce219f3cb156d352f739c178d75f",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def source_for(version):
|
||||
path = FIXTURES / version / "src/NonBlockingRtttl.cpp.json"
|
||||
return json.loads(path.read_text(encoding="ascii"))["source"]
|
||||
|
||||
|
||||
def header_for(version):
|
||||
return (FIXTURES / version / "src/NonBlockingRtttl.h").read_text(encoding="ascii")
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, path):
|
||||
self.path = Path(path)
|
||||
|
||||
def srcnode(self):
|
||||
return self
|
||||
|
||||
def get_abspath(self):
|
||||
return str(self.path)
|
||||
|
||||
|
||||
class BuildEnv:
|
||||
def __init__(self, build):
|
||||
self.build = Path(build)
|
||||
self.registrations = []
|
||||
|
||||
def subst(self, value):
|
||||
assert value == "$BUILD_DIR"
|
||||
return str(self.build)
|
||||
|
||||
def File(self, value):
|
||||
return Path(value)
|
||||
|
||||
def AddBuildMiddleware(self, callback, pattern):
|
||||
self.registrations.append((callback, pattern))
|
||||
|
||||
|
||||
class TransformTests(unittest.TestCase):
|
||||
def test_fixtures_are_ascii_licensed_and_exact_reviewed_bytes_after_json_decode(self):
|
||||
for path in FIXTURES.rglob("*"):
|
||||
if path.is_file():
|
||||
path.read_text(encoding="ascii")
|
||||
for version in VERSIONS:
|
||||
with self.subTest(version=version):
|
||||
source, header = source_for(version), header_for(version)
|
||||
expected_source, expected_header, _ = PINS[version]
|
||||
self.assertEqual(FIX.digest(source), expected_source)
|
||||
self.assertEqual(FIX.digest(header), expected_header)
|
||||
self.assertIn("#define NONBLOCKINGRTTTL_VERSION " + version, header)
|
||||
license_text = (FIXTURES / version / "LICENSE").read_text(encoding="ascii")
|
||||
self.assertIn("MIT License", license_text)
|
||||
self.assertIn("Copyright (c) 2018 Antoine Beauchamp", license_text)
|
||||
self.assertIn("Permission is hereby granted, free of charge", license_text)
|
||||
self.assertIn("THE SOFTWARE IS PROVIDED", license_text)
|
||||
self.assertIn("Antoine Beauchamp", (FIXTURES / version / "AUTHORS").read_text())
|
||||
|
||||
def test_both_reviewed_versions_raw_crlf_idempotence_and_expected_patch_digest(self):
|
||||
for version in VERSIONS:
|
||||
with self.subTest(version=version):
|
||||
original = source_for(version)
|
||||
patched = FIX.patched_source(original)
|
||||
self.assertEqual(FIX.digest(patched), PINS[version][2])
|
||||
patched.encode("ascii")
|
||||
self.assertEqual(FIX.patched_source(original.replace("\n", "\r\n")), patched)
|
||||
self.assertEqual(FIX.patched_source(patched), patched)
|
||||
self.assertEqual(FIX.patched_source(patched.replace("\n", "\r\n")), patched)
|
||||
self.assertEqual(patched.count(FIX.PATCHED_BEGIN), 1)
|
||||
self.assertEqual(patched.count(FIX.PATCHED_COMPARE), 1)
|
||||
|
||||
def test_unknown_changed_or_half_patched_sources_fail_closed(self):
|
||||
for version in VERSIONS:
|
||||
original = source_for(version)
|
||||
for changed in (
|
||||
original + "\n", original.replace("bpm=63", "bpm=64", 1),
|
||||
original.replace(FIX.ORIGINAL_BEGIN, FIX.PATCHED_BEGIN, 1),
|
||||
original.replace(FIX.ORIGINAL_COMPARE, FIX.PATCHED_COMPARE, 1),
|
||||
):
|
||||
with self.subTest(version=version), self.assertRaisesRegex(RuntimeError, "changed reviewed source"):
|
||||
FIX.patched_source(changed)
|
||||
|
||||
def test_middleware_registers_only_expected_source_filter(self):
|
||||
env = BuildEnv("unused")
|
||||
FIX.install(env)
|
||||
self.assertEqual(env.registrations, [(FIX.replace_rtttl_source, "*NonBlockingRtttl.cpp")])
|
||||
|
||||
def test_unrelated_source_is_unchanged_and_not_read(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
env = BuildEnv(Path(temporary) / "build")
|
||||
for path in ("src/not_rtttl.cpp", "other/NonBlockingRtttl.cpp"):
|
||||
node = Node(Path(temporary) / path)
|
||||
self.assertIs(FIX.replace_rtttl_source(env, node), node)
|
||||
self.assertFalse(env.build.exists())
|
||||
|
||||
def test_private_source_and_header_are_paired_idempotent_and_leave_shared_files_unchanged(self):
|
||||
for version in VERSIONS:
|
||||
for crlf in (False, True):
|
||||
with self.subTest(version=version, crlf=crlf), tempfile.TemporaryDirectory() as temporary:
|
||||
directory = Path(temporary)
|
||||
source = directory / "libdeps/NonBlockingRTTTL/src/NonBlockingRtttl.cpp"
|
||||
source.parent.mkdir(parents=True)
|
||||
original, header = source_for(version), header_for(version)
|
||||
if crlf:
|
||||
original, header = original.replace("\n", "\r\n"), header.replace("\n", "\r\n")
|
||||
source.write_bytes(original.encode("utf-8"))
|
||||
source.with_suffix(".h").write_bytes(header.encode("ascii"))
|
||||
before = (source.read_bytes(), source.with_suffix(".h").read_bytes())
|
||||
env = BuildEnv(directory / "build")
|
||||
destination = FIX.replace_rtttl_source(env, Node(source))
|
||||
self.assertEqual(destination, env.build / "patched-rtttl/NonBlockingRtttl.cpp")
|
||||
self.assertEqual(FIX.digest(destination.read_text(encoding="ascii")), PINS[version][2])
|
||||
self.assertEqual(destination.with_suffix(".h").read_text(encoding="ascii"), header_for(version))
|
||||
times = (destination.stat().st_mtime_ns, destination.with_suffix(".h").stat().st_mtime_ns)
|
||||
self.assertEqual(FIX.replace_rtttl_source(env, Node(source)), destination)
|
||||
self.assertEqual(times, (destination.stat().st_mtime_ns, destination.with_suffix(".h").stat().st_mtime_ns))
|
||||
self.assertEqual(before, (source.read_bytes(), source.with_suffix(".h").read_bytes()))
|
||||
|
||||
def test_missing_unknown_or_wrong_version_headers_fail_before_creating_private_output(self):
|
||||
for version in VERSIONS:
|
||||
other = "1.4.0" if version == "1.3.0" else "1.3.0"
|
||||
for header in (None, header_for(version) + "\n", header_for(other), "// unknown\n", "\u00b0"):
|
||||
with self.subTest(version=version, header=header is None), tempfile.TemporaryDirectory() as temporary:
|
||||
directory = Path(temporary)
|
||||
source = directory / "libdeps/NonBlockingRTTTL/src/NonBlockingRtttl.cpp"
|
||||
source.parent.mkdir(parents=True)
|
||||
source.write_text(source_for(version), encoding="utf-8")
|
||||
if header is not None:
|
||||
source.with_suffix(".h").write_text(header, encoding="utf-8")
|
||||
env = BuildEnv(directory / "build")
|
||||
with self.assertRaisesRegex(RuntimeError, "reviewed header"):
|
||||
FIX.replace_rtttl_source(env, Node(source))
|
||||
self.assertFalse(env.build.exists())
|
||||
|
||||
|
||||
@unittest.skipUnless(CXX and platform.system() == "Linux" and platform.machine() in ("x86_64", "i386", "i686"),
|
||||
"Freestanding ELF32 tests require Linux x86 and g++")
|
||||
class RuntimeTests(unittest.TestCase):
|
||||
def build_run(self, dependency, version, fixture):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
directory = Path(temporary)
|
||||
source = directory / "NonBlockingRtttl.cpp"
|
||||
source.write_text(dependency, encoding="utf-8")
|
||||
source.with_suffix(".h").write_text(header_for(version), encoding="ascii")
|
||||
binary = directory / "case"
|
||||
flags = ["-DRTTTL_NO_LOOPS=1"] if version == "1.3.0" else []
|
||||
subprocess.run([
|
||||
CXX, "-m32", "-std=c++17", "-Wall", "-Wextra", "-Werror", *flags,
|
||||
"-fno-exceptions", "-fno-rtti", "-fno-stack-protector", "-nostdlib", "-no-pie",
|
||||
"-I" + str(FIXTURES), "-I" + str(directory), "-I" + str(ROOT / "src"),
|
||||
str(FIXTURES / fixture), str(source), "-o", str(binary),
|
||||
], check=True, capture_output=True, timeout=30)
|
||||
self.assertEqual(binary.read_bytes()[:5], b"\x7fELF\x01")
|
||||
return subprocess.run([str(binary)], capture_output=True, timeout=10).returncode
|
||||
|
||||
def test_actual_unpatched_versions_reproduce_both_semantic_rollover_failures(self):
|
||||
for version in VERSIONS:
|
||||
with self.subTest(version=version):
|
||||
self.assertEqual(self.build_run(source_for(version), version, "regression.cpp"), 30)
|
||||
self.assertEqual(self.build_run(source_for(version), version, "queue.cpp"), 33)
|
||||
|
||||
def test_patched_versions_keep_tempo_empty_stop_replacement_and_real_voice_queue(self):
|
||||
for version in VERSIONS:
|
||||
with self.subTest(version=version):
|
||||
patched = FIX.patched_source(source_for(version))
|
||||
self.assertEqual(self.build_run(patched, version, "regression.cpp"), 0)
|
||||
self.assertEqual(self.build_run(patched, version, "queue.cpp"), 0)
|
||||
|
||||
def test_each_timing_fix_is_required_not_merely_accepted_by_source_checks(self):
|
||||
for version in VERSIONS:
|
||||
with self.subTest(version=version):
|
||||
original = source_for(version)
|
||||
initial_only = original.replace(FIX.ORIGINAL_BEGIN, FIX.PATCHED_BEGIN, 1)
|
||||
signed_only = original.replace(FIX.ORIGINAL_COMPARE, FIX.PATCHED_COMPARE, 1)
|
||||
self.assertEqual(self.build_run(initial_only, version, "regression.cpp"), 30)
|
||||
self.assertEqual(self.build_run(signed_only, version, "regression.cpp"), 28)
|
||||
|
||||
def test_private_cpp_quotes_its_paired_header_even_with_poison_include_path_first(self):
|
||||
for version in VERSIONS:
|
||||
with self.subTest(version=version), tempfile.TemporaryDirectory() as temporary:
|
||||
directory = Path(temporary)
|
||||
original = directory / "libdeps/NonBlockingRTTTL/src/NonBlockingRtttl.cpp"
|
||||
original.parent.mkdir(parents=True)
|
||||
original.write_text(source_for(version), encoding="utf-8")
|
||||
original.with_suffix(".h").write_text(header_for(version), encoding="ascii")
|
||||
destination = FIX.replace_rtttl_source(BuildEnv(directory / "build"), Node(original))
|
||||
poison = directory / "poison"
|
||||
poison.mkdir()
|
||||
(poison / "NonBlockingRtttl.h").write_text('#error "wrong header selected"\n', encoding="ascii")
|
||||
subprocess.run([
|
||||
CXX, "-m32", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
||||
"-fno-exceptions", "-fno-rtti", "-fno-stack-protector",
|
||||
"-I" + str(poison), "-I" + str(FIXTURES),
|
||||
"-c", str(destination), "-o", str(directory / "dependency.o"),
|
||||
], check=True, capture_output=True, timeout=30)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
@@ -0,0 +1,88 @@
|
||||
"""Resolve tracked RTTTL build hooks, including synthesized Full Companions."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
HOOK = "pre:scripts/rtttl_rollover_fix.py"
|
||||
|
||||
|
||||
class RtttlRolloverRecipeTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# Ignore developer-local overlays when checking the tracked matrix.
|
||||
cls.config = ProjectConfig(str(ROOT / "platformio.ini"), parse_extra=False)
|
||||
for path in sorted((ROOT / "variants").glob("*/platformio.ini")):
|
||||
cls.config.read(str(path), parse_extra=False)
|
||||
|
||||
def has_rtttl(self, section):
|
||||
return any("NonBlockingRTTTL" in dep
|
||||
for dep in self.config.get(section, "lib_deps", []))
|
||||
|
||||
def test_every_resolved_rtttl_recipe_loads_one_fix(self):
|
||||
tested = []
|
||||
for section in self.config.sections():
|
||||
if not section.startswith("env:") or not self.has_rtttl(section):
|
||||
continue
|
||||
with self.subTest(target=section):
|
||||
self.assertEqual(self.config.get(section, "extra_scripts", []).count(HOOK), 1)
|
||||
tested.append(section)
|
||||
self.assertGreater(len(tested), 70)
|
||||
self.assertIn("env:t1000e_companion_radio_usb", tested)
|
||||
self.assertIn("env:heltec_v4_r8_tft_companion_radio_ble", tested)
|
||||
|
||||
def test_all_arduino_platform_bases_propagate_fix(self):
|
||||
for base in ("esp32_base", "nrf52_base", "rp2040_base", "stm32_base"):
|
||||
with self.subTest(base=base):
|
||||
self.assertEqual(self.config.get(base, "extra_scripts", []).count(HOOK), 1)
|
||||
|
||||
def test_actual_builder_synthesized_full_targets_retain_their_base_hook(self):
|
||||
# Feed resolved tracked options into the real builder's target mapper.
|
||||
# No PlatformIO command, dependency download, compilation or flashing.
|
||||
options = ["build_flags", "lib_deps", "build_src_filter", "board", "extra_scripts"]
|
||||
records = [[section, [[key, self.config.get(section, key, [])] for key in options]]
|
||||
for section in self.config.sections() if section.startswith("env:")]
|
||||
shell = r'''
|
||||
source "$1/build_legacy.sh"
|
||||
pio() { printf 'FORBIDDEN_PLATFORMIO\n' >&2; return 99; }
|
||||
ALL_PIO_ENVS=(fixture)
|
||||
PIO_CONFIG_JSON=$(<"$2")
|
||||
init_project_context
|
||||
for target in "${SUPPORTED_PIO_ENVS[@]}"; do
|
||||
if is_companion_radio_full_target "$target"; then
|
||||
printf '%s\t%s\n' "$target" "${PIO_ENV_BUILD_BASE_BY_NAME[$target]:-$target}"
|
||||
fi
|
||||
done
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="rtttl-recipes-") as temporary:
|
||||
path = Path(temporary) / "config.json"
|
||||
path.write_text(json.dumps(records, ensure_ascii=True), encoding="ascii")
|
||||
result = subprocess.run(["bash", "-c", shell, "rtttl-recipes", str(ROOT), str(path)],
|
||||
cwd=ROOT, capture_output=True, text=True, timeout=30)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertNotIn("FORBIDDEN_PLATFORMIO", result.stdout + result.stderr)
|
||||
pairs = dict(line.split("\t") for line in result.stdout.splitlines())
|
||||
self.assertIn("t1000e_companion_radio_full", pairs)
|
||||
self.assertIn("heltec_v4_r8_tft_companion_radio_full", pairs)
|
||||
tested = []
|
||||
for target, base in pairs.items():
|
||||
section = "env:" + base
|
||||
if not self.has_rtttl(section):
|
||||
continue
|
||||
with self.subTest(target=target, base=base):
|
||||
self.assertEqual(self.config.get(section, "extra_scripts", []).count(HOOK), 1)
|
||||
tested.append(target)
|
||||
self.assertGreater(len(tested), 20)
|
||||
|
||||
def test_regression_suites_are_required_in_ci(self):
|
||||
workflow = (ROOT / ".github/workflows/run-unit-tests.yml").read_text(encoding="ascii")
|
||||
self.assertIn("python3 -B test/test_rtttl_rollover.py -v", workflow)
|
||||
self.assertIn("python3 -B test/test_rtttl_rollover_recipes.py -v", workflow)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user