From b347f1c63fe86ae65740f7e32ab0a807538dbffd Mon Sep 17 00:00:00 2001 From: MangaValk Date: Sun, 25 Oct 2026 20:48:04 +0100 Subject: [PATCH] Import ESP_UniClock (upstream 1189810) Vendored from https://github.com/gpeter62/ESP_UniClock: the sketch source, the web UI and the libraries the ESP8266 build needs, unpacked from the bundled zips. Adds a PlatformIO project for the ESP-12. The source is unchanged; local changes follow as separate commits. Co-Authored-By: Claude Opus 5.5 --- firmware/uniclock/ESP32_UniClock2/1tube.ino | 306 + .../uniclock/ESP32_UniClock2/DHT_temp.ino | 132 + .../ESP32_UniClock2/ESP32_UniClock2.ino | 3259 +++++ firmware/uniclock/ESP32_UniClock2/HV5122.ino | 303 + .../uniclock/ESP32_UniClock2/HV5122new.ino | 422 + .../uniclock/ESP32_UniClock2/I2Csensors.ino | 450 + firmware/uniclock/ESP32_UniClock2/MAX6921.ino | 305 + .../ESP32_UniClock2/MAX6921_ESP32.ino | 309 + .../uniclock/ESP32_UniClock2/MAX7219CNG.ino | 158 + firmware/uniclock/ESP32_UniClock2/MM5450.ino | 278 + .../ESP32_UniClock2/Numitron_4511N.ino | 354 + firmware/uniclock/ESP32_UniClock2/PT6311.ino | 361 + firmware/uniclock/ESP32_UniClock2/PT6355.ino | 354 + firmware/uniclock/ESP32_UniClock2/SN75512.ino | 251 + firmware/uniclock/ESP32_UniClock2/SN75518.ino | 485 + firmware/uniclock/ESP32_UniClock2/VQC10.ino | 625 + firmware/uniclock/ESP32_UniClock2/ZM1500.ino | 242 + firmware/uniclock/ESP32_UniClock2/clocks.h | 2599 ++++ .../uniclock/ESP32_UniClock2/dallas_temp.ino | 222 + .../uniclock/ESP32_UniClock2/data/favicon.ico | Bin 0 -> 318 bytes .../uniclock/ESP32_UniClock2/data/index.html | 427 + .../ESP32_UniClock2/data/jquery_351.js | 2 + .../ESP32_UniClock2/data/jquery_360.js | 2 + .../uniclock/ESP32_UniClock2/data/page.js | 616 + .../uniclock/ESP32_UniClock2/data/site.css | 479 + firmware/uniclock/ESP32_UniClock2/dummy.ino | 20 + firmware/uniclock/ESP32_UniClock2/font5x7.h | 303 + firmware/uniclock/ESP32_UniClock2/gps.ino | 151 + firmware/uniclock/ESP32_UniClock2/mqtt.ino | 306 + firmware/uniclock/ESP32_UniClock2/mulev2.ino | 260 + .../ESP32_UniClock2/multiplex74141.ino | 194 + .../ESP32_UniClock2/multiplex74141_ESP32.ino | 215 + .../ESP32_UniClock2/no_multiplex74141.ino | 203 + .../ESP32_UniClock2/no_multiplex_ESP32.ino | 242 + .../ESP32_UniClock2/pcf_multiplex74141.ino | 318 + firmware/uniclock/ESP32_UniClock2/pwmleds.ino | 186 + firmware/uniclock/ESP32_UniClock2/rtc.ino | 534 + .../uniclock/ESP32_UniClock2/wordclock.ino | 823 ++ .../uniclock/ESP32_UniClock2/z_neopixel.ino | 621 + firmware/uniclock/README.md | 24 + .../uniclock/lib/ArduinoJson/ArduinoJson.h | 5 + .../uniclock/lib/ArduinoJson/CHANGELOG.md | 783 ++ .../uniclock/lib/ArduinoJson/CMakeLists.txt | 25 + .../uniclock/lib/ArduinoJson/CONTRIBUTING.md | 10 + firmware/uniclock/lib/ArduinoJson/LICENSE.md | 10 + firmware/uniclock/lib/ArduinoJson/README.md | 162 + firmware/uniclock/lib/ArduinoJson/SUPPORT.md | 27 + .../uniclock/lib/ArduinoJson/appveyor.yml | 37 + firmware/uniclock/lib/ArduinoJson/banner.svg | 367 + .../uniclock/lib/ArduinoJson/component.mk | 1 + .../JsonConfigFile/JsonConfigFile.ino | 160 + .../JsonFilterExample/JsonFilterExample.ino | 63 + .../JsonGeneratorExample.ino | 77 + .../JsonHttpClient/JsonHttpClient.ino | 126 + .../JsonParserExample/JsonParserExample.ino | 80 + .../examples/JsonServer/JsonServer.ino | 117 + .../examples/JsonUdpBeacon/JsonUdpBeacon.ino | 106 + .../examples/MsgPackParser/MsgPackParser.ino | 75 + .../ProgmemExample/ProgmemExample.ino | 64 + .../examples/StringExample/StringExample.ino | 77 + .../extras/ArduinoJsonConfig.cmake.in | 4 + .../ArduinoJson/extras/CompileOptions.cmake | 101 + .../extras/ci/espidf/CMakeLists.txt | 8 + .../extras/ci/espidf/main/CMakeLists.txt | 6 + .../extras/ci/espidf/main/component.mk | 4 + .../extras/ci/espidf/main/main.cpp | 16 + .../lib/ArduinoJson/extras/ci/particle.sh | 10 + .../lib/ArduinoJson/extras/conf_test/avr.cpp | 16 + .../ArduinoJson/extras/conf_test/esp8266.cpp | 16 + .../lib/ArduinoJson/extras/conf_test/x64.cpp | 15 + .../lib/ArduinoJson/extras/conf_test/x86.cpp | 15 + .../ArduinoJson/extras/fuzzing/CMakeLists.txt | 59 + .../lib/ArduinoJson/extras/fuzzing/Makefile | 22 + .../extras/fuzzing/json_fuzzer.cpp | 11 + .../fuzzing/json_seed_corpus/Comments.json | 10 + .../fuzzing/json_seed_corpus/EmptyArray.json | 1 + .../fuzzing/json_seed_corpus/EmptyObject.json | 1 + .../json_seed_corpus/ExcessiveNesting.json | 1 + .../json_seed_corpus/IntegerOverflow.json | 1 + .../fuzzing/json_seed_corpus/Numbers.json | 24 + .../json_seed_corpus/OpenWeatherMap.json | 53 + .../fuzzing/json_seed_corpus/Strings.json | 8 + .../json_seed_corpus/WeatherUnderground.json | 90 + .../extras/fuzzing/msgpack_fuzzer.cpp | 11 + .../fuzzing/msgpack_seed_corpus/array16 | Bin 0 -> 15 bytes .../fuzzing/msgpack_seed_corpus/array32 | Bin 0 -> 15 bytes .../extras/fuzzing/msgpack_seed_corpus/false | 1 + .../fuzzing/msgpack_seed_corpus/fixarray | 1 + .../msgpack_seed_corpus/fixint_negative | 1 + .../msgpack_seed_corpus/fixint_positive | 1 + .../extras/fuzzing/msgpack_seed_corpus/fixmap | 1 + .../extras/fuzzing/msgpack_seed_corpus/fixstr | 1 + .../fuzzing/msgpack_seed_corpus/float32 | 1 + .../fuzzing/msgpack_seed_corpus/float64 | 1 + .../extras/fuzzing/msgpack_seed_corpus/int16 | 1 + .../extras/fuzzing/msgpack_seed_corpus/int32 | 1 + .../extras/fuzzing/msgpack_seed_corpus/int64 | 1 + .../extras/fuzzing/msgpack_seed_corpus/int8 | 1 + .../extras/fuzzing/msgpack_seed_corpus/map16 | Bin 0 -> 19 bytes .../extras/fuzzing/msgpack_seed_corpus/map32 | Bin 0 -> 23 bytes .../extras/fuzzing/msgpack_seed_corpus/nil | 1 + .../extras/fuzzing/msgpack_seed_corpus/str16 | Bin 0 -> 8 bytes .../extras/fuzzing/msgpack_seed_corpus/str32 | Bin 0 -> 10 bytes .../extras/fuzzing/msgpack_seed_corpus/str8 | 1 + .../extras/fuzzing/msgpack_seed_corpus/true | 1 + .../extras/fuzzing/msgpack_seed_corpus/uint16 | 1 + .../extras/fuzzing/msgpack_seed_corpus/uint32 | 1 + .../extras/fuzzing/msgpack_seed_corpus/uint64 | 1 + .../extras/fuzzing/msgpack_seed_corpus/uint8 | 1 + .../ArduinoJson/extras/fuzzing/reproducer.cpp | 50 + .../extras/particle/project.properties | 1 + .../extras/particle/src/smocktest.ino | 5 + .../extras/scripts/build-arduino-package.sh | 23 + .../extras/scripts/build-single-header.sh | 64 + .../extras/scripts/get-release-body.sh | 14 + .../extras/scripts/get-release-page.sh | 18 + .../scripts/publish-particle-library.sh | 18 + .../lib/ArduinoJson/extras/scripts/publish.sh | 75 + .../scripts/wandbox/JsonGeneratorExample.cpp | 60 + .../scripts/wandbox/JsonParserExample.cpp | 59 + .../scripts/wandbox/MsgPackParserExample.cpp | 68 + .../extras/scripts/wandbox/publish.sh | 29 + .../ArduinoJson/extras/tests/CMakeLists.txt | 30 + .../extras/tests/Cpp11/CMakeLists.txt | 32 + .../extras/tests/Cpp11/issue1120.cpp | 58 + .../extras/tests/Cpp11/nullptr.cpp | 47 + .../extras/tests/Cpp11/use_long_long_0.cpp | 16 + .../extras/tests/Cpp11/use_long_long_1.cpp | 17 + .../extras/tests/Cpp17/CMakeLists.txt | 29 + .../extras/tests/Cpp17/string_view.cpp | 100 + .../extras/tests/Cpp20/CMakeLists.txt | 29 + .../extras/tests/Cpp20/smoke_test.cpp | 15 + .../extras/tests/FailingBuilds/CMakeLists.txt | 48 + .../extras/tests/FailingBuilds/Issue1189.cpp | 13 + .../extras/tests/FailingBuilds/Issue978.cpp | 13 + .../tests/FailingBuilds/assign_char.cpp | 12 + .../FailingBuilds/delete_jsondocument.cpp | 12 + .../tests/FailingBuilds/read_long_long.cpp | 20 + .../tests/FailingBuilds/variant_as_char.cpp | 12 + .../tests/FailingBuilds/write_long_long.cpp | 19 + .../extras/tests/Helpers/Arduino.h | 12 + .../extras/tests/Helpers/CustomReader.hpp | 26 + .../extras/tests/Helpers/api/Print.h | 33 + .../extras/tests/Helpers/api/Stream.h | 14 + .../extras/tests/Helpers/api/String.h | 69 + .../tests/Helpers/progmem_emulation.hpp | 29 + .../tests/IntegrationTests/CMakeLists.txt | 24 + .../tests/IntegrationTests/gbathree.cpp | 210 + .../tests/IntegrationTests/issue772.cpp | 28 + .../tests/IntegrationTests/openweathermap.cpp | 68 + .../tests/IntegrationTests/round_trip.cpp | 82 + .../extras/tests/JsonArray/CMakeLists.txt | 28 + .../extras/tests/JsonArray/add.cpp | 138 + .../extras/tests/JsonArray/clear.cpp | 25 + .../extras/tests/JsonArray/copyArray.cpp | 346 + .../extras/tests/JsonArray/createNested.cpp | 21 + .../extras/tests/JsonArray/equals.cpp | 69 + .../extras/tests/JsonArray/get.cpp | 16 + .../extras/tests/JsonArray/isNull.cpp | 58 + .../extras/tests/JsonArray/iterator.cpp | 52 + .../extras/tests/JsonArray/memoryUsage.cpp | 42 + .../extras/tests/JsonArray/nesting.cpp | 35 + .../extras/tests/JsonArray/remove.cpp | 89 + .../extras/tests/JsonArray/size.cpp | 31 + .../extras/tests/JsonArray/std_string.cpp | 31 + .../extras/tests/JsonArray/subscript.cpp | 174 + .../extras/tests/JsonArray/unbound.cpp | 35 + .../tests/JsonDeserializer/CMakeLists.txt | 28 + .../JsonDeserializer/DeserializationError.cpp | 120 + .../extras/tests/JsonDeserializer/array.cpp | 253 + .../tests/JsonDeserializer/array_static.cpp | 89 + .../extras/tests/JsonDeserializer/filter.cpp | 773 + .../JsonDeserializer/incomplete_input.cpp | 29 + .../tests/JsonDeserializer/input_types.cpp | 198 + .../tests/JsonDeserializer/invalid_input.cpp | 42 + .../extras/tests/JsonDeserializer/misc.cpp | 117 + .../tests/JsonDeserializer/nestingLimit.cpp | 101 + .../extras/tests/JsonDeserializer/number.cpp | 133 + .../extras/tests/JsonDeserializer/object.cpp | 315 + .../tests/JsonDeserializer/object_static.cpp | 64 + .../extras/tests/JsonDeserializer/string.cpp | 132 + .../tests/JsonDocument/BasicJsonDocument.cpp | 187 + .../extras/tests/JsonDocument/CMakeLists.txt | 31 + .../JsonDocument/DynamicJsonDocument.cpp | 230 + .../tests/JsonDocument/ElementProxy.cpp | 247 + .../extras/tests/JsonDocument/MemberProxy.cpp | 287 + .../tests/JsonDocument/StaticJsonDocument.cpp | 224 + .../extras/tests/JsonDocument/add.cpp | 22 + .../extras/tests/JsonDocument/cast.cpp | 18 + .../extras/tests/JsonDocument/compare.cpp | 103 + .../extras/tests/JsonDocument/containsKey.cpp | 44 + .../tests/JsonDocument/createNested.cpp | 66 + .../extras/tests/JsonDocument/isNull.cpp | 39 + .../extras/tests/JsonDocument/nesting.cpp | 30 + .../extras/tests/JsonDocument/overflowed.cpp | 85 + .../extras/tests/JsonDocument/remove.cpp | 52 + .../extras/tests/JsonDocument/shrinkToFit.cpp | 154 + .../extras/tests/JsonDocument/size.cpp | 28 + .../extras/tests/JsonDocument/subscript.cpp | 53 + .../extras/tests/JsonDocument/swap.cpp | 27 + .../extras/tests/JsonObject/CMakeLists.txt | 28 + .../extras/tests/JsonObject/clear.cpp | 25 + .../extras/tests/JsonObject/containsKey.cpp | 39 + .../extras/tests/JsonObject/copy.cpp | 115 + .../tests/JsonObject/createNestedArray.cpp | 27 + .../tests/JsonObject/createNestedObject.cpp | 25 + .../extras/tests/JsonObject/equals.cpp | 67 + .../extras/tests/JsonObject/invalid.cpp | 35 + .../extras/tests/JsonObject/isNull.cpp | 58 + .../extras/tests/JsonObject/iterator.cpp | 73 + .../extras/tests/JsonObject/memoryUsage.cpp | 43 + .../extras/tests/JsonObject/nesting.cpp | 35 + .../extras/tests/JsonObject/remove.cpp | 82 + .../extras/tests/JsonObject/size.cpp | 39 + .../extras/tests/JsonObject/std_string.cpp | 109 + .../extras/tests/JsonObject/subscript.cpp | 233 + .../tests/JsonSerializer/CMakeLists.txt | 22 + .../tests/JsonSerializer/CustomWriter.cpp | 52 + .../extras/tests/JsonSerializer/JsonArray.cpp | 129 + .../tests/JsonSerializer/JsonArrayPretty.cpp | 75 + .../tests/JsonSerializer/JsonObject.cpp | 119 + .../tests/JsonSerializer/JsonObjectPretty.cpp | 77 + .../tests/JsonSerializer/JsonVariant.cpp | 121 + .../extras/tests/JsonSerializer/misc.cpp | 36 + .../tests/JsonSerializer/std_stream.cpp | 66 + .../tests/JsonSerializer/std_string.cpp | 57 + .../extras/tests/JsonVariant/CMakeLists.txt | 33 + .../extras/tests/JsonVariant/add.cpp | 46 + .../extras/tests/JsonVariant/as.cpp | 270 + .../extras/tests/JsonVariant/clear.cpp | 26 + .../extras/tests/JsonVariant/compare.cpp | 316 + .../extras/tests/JsonVariant/containsKey.cpp | 44 + .../extras/tests/JsonVariant/converters.cpp | 142 + .../extras/tests/JsonVariant/copy.cpp | 95 + .../extras/tests/JsonVariant/createNested.cpp | 86 + .../extras/tests/JsonVariant/is.cpp | 319 + .../extras/tests/JsonVariant/isnull.cpp | 80 + .../extras/tests/JsonVariant/memoryUsage.cpp | 41 + .../extras/tests/JsonVariant/misc.cpp | 50 + .../extras/tests/JsonVariant/nesting.cpp | 31 + .../extras/tests/JsonVariant/or.cpp | 159 + .../extras/tests/JsonVariant/overflow.cpp | 72 + .../extras/tests/JsonVariant/remove.cpp | 42 + .../extras/tests/JsonVariant/set.cpp | 172 + .../extras/tests/JsonVariant/subscript.cpp | 204 + .../extras/tests/JsonVariant/types.cpp | 163 + .../extras/tests/JsonVariant/unbound.cpp | 66 + .../extras/tests/MemoryPool/CMakeLists.txt | 18 + .../extras/tests/MemoryPool/StringCopier.cpp | 93 + .../extras/tests/MemoryPool/allocVariant.cpp | 50 + .../extras/tests/MemoryPool/clear.cpp | 32 + .../extras/tests/MemoryPool/saveString.cpp | 106 + .../extras/tests/MemoryPool/size.cpp | 35 + .../extras/tests/Misc/CMakeLists.txt | 30 + .../extras/tests/Misc/FloatParts.cpp | 44 + .../extras/tests/Misc/JsonString.cpp | 103 + .../extras/tests/Misc/NoArduinoHeader.cpp | 20 + .../ArduinoJson/extras/tests/Misc/Readers.cpp | 225 + .../extras/tests/Misc/StringAdapters.cpp | 202 + .../extras/tests/Misc/StringWriter.cpp | 153 + .../extras/tests/Misc/TypeTraits.cpp | 212 + .../ArduinoJson/extras/tests/Misc/Utf16.cpp | 68 + .../ArduinoJson/extras/tests/Misc/Utf8.cpp | 60 + .../extras/tests/Misc/arithmeticCompare.cpp | 103 + .../extras/tests/Misc/conflicts.cpp | 56 + .../extras/tests/Misc/custom_string.hpp | 12 + .../extras/tests/Misc/deprecated.cpp | 115 + .../extras/tests/Misc/printable.cpp | 144 + .../extras/tests/Misc/unsigned_char.cpp | 271 + .../ArduinoJson/extras/tests/Misc/version.cpp | 18 + .../extras/tests/Misc/weird_strcmp.hpp | 29 + .../tests/MixedConfiguration/CMakeLists.txt | 31 + .../MixedConfiguration/decode_unicode_0.cpp | 12 + .../MixedConfiguration/decode_unicode_1.cpp | 11 + .../MixedConfiguration/enable_alignment_0.cpp | 41 + .../MixedConfiguration/enable_alignment_1.cpp | 40 + .../MixedConfiguration/enable_comments_0.cpp | 54 + .../MixedConfiguration/enable_comments_1.cpp | 411 + .../MixedConfiguration/enable_infinity_0.cpp | 35 + .../MixedConfiguration/enable_infinity_1.cpp | 39 + .../tests/MixedConfiguration/enable_nan_0.cpp | 25 + .../tests/MixedConfiguration/enable_nan_1.cpp | 31 + .../MixedConfiguration/enable_progmem_1.cpp | 191 + .../enable_string_deduplication_0.cpp | 123 + .../enable_string_deduplication_1.cpp | 122 + .../tests/MixedConfiguration/issue1707.cpp | 17 + .../tests/MixedConfiguration/use_double_0.cpp | 17 + .../tests/MixedConfiguration/use_double_1.cpp | 17 + .../tests/MsgPackDeserializer/CMakeLists.txt | 24 + .../MsgPackDeserializer/deserializeArray.cpp | 83 + .../MsgPackDeserializer/deserializeObject.cpp | 130 + .../deserializeStaticVariant.cpp | 152 + .../deserializeVariant.cpp | 147 + .../MsgPackDeserializer/doubleToFloat.cpp | 25 + .../tests/MsgPackDeserializer/filter.cpp | 1133 ++ .../MsgPackDeserializer/incompleteInput.cpp | 158 + .../tests/MsgPackDeserializer/input_types.cpp | 95 + .../extras/tests/MsgPackDeserializer/misc.cpp | 24 + .../MsgPackDeserializer/nestingLimit.cpp | 85 + .../MsgPackDeserializer/notSupported.cpp | 82 + .../tests/MsgPackSerializer/CMakeLists.txt | 19 + .../MsgPackSerializer/destination_types.cpp | 59 + .../tests/MsgPackSerializer/measure.cpp | 14 + .../extras/tests/MsgPackSerializer/misc.cpp | 46 + .../MsgPackSerializer/serializeArray.cpp | 58 + .../MsgPackSerializer/serializeObject.cpp | 83 + .../MsgPackSerializer/serializeVariant.cpp | 161 + .../extras/tests/Numbers/CMakeLists.txt | 19 + .../extras/tests/Numbers/convertNumber.cpp | 78 + .../extras/tests/Numbers/parseDouble.cpp | 96 + .../extras/tests/Numbers/parseFloat.cpp | 101 + .../extras/tests/Numbers/parseInteger.cpp | 68 + .../extras/tests/Numbers/parseNumber.cpp | 57 + .../extras/tests/TextFormatter/CMakeLists.txt | 18 + .../extras/tests/TextFormatter/writeFloat.cpp | 119 + .../tests/TextFormatter/writeInteger.cpp | 55 + .../tests/TextFormatter/writeString.cpp | 57 + .../extras/tests/catch/CMakeLists.txt | 21 + .../ArduinoJson/extras/tests/catch/catch.cpp | 6 + .../ArduinoJson/extras/tests/catch/catch.hpp | 11689 ++++++++++++++++ .../uniclock/lib/ArduinoJson/keywords.txt | 40 + .../uniclock/lib/ArduinoJson/library.json | 24 + .../lib/ArduinoJson/library.properties | 11 + .../lib/ArduinoJson/src/ArduinoJson.h | 17 + .../lib/ArduinoJson/src/ArduinoJson.hpp | 80 + .../src/ArduinoJson/Array/ArrayFunctions.hpp | 31 + .../src/ArduinoJson/Array/ArrayImpl.hpp | 28 + .../src/ArduinoJson/Array/ArrayIterator.hpp | 121 + .../src/ArduinoJson/Array/ArrayRef.hpp | 213 + .../src/ArduinoJson/Array/ArrayShortcuts.hpp | 49 + .../src/ArduinoJson/Array/ElementProxy.hpp | 197 + .../src/ArduinoJson/Array/Utilities.hpp | 103 + .../ArduinoJson/Collection/CollectionData.hpp | 89 + .../ArduinoJson/Collection/CollectionImpl.hpp | 237 + .../src/ArduinoJson/Configuration.hpp | 236 + .../Deserialization/DeserializationError.hpp | 107 + .../ArduinoJson/Deserialization/Filter.hpp | 66 + .../Deserialization/NestingLimit.hpp | 29 + .../ArduinoJson/Deserialization/Reader.hpp | 56 + .../Readers/ArduinoStreamReader.hpp | 31 + .../Readers/ArduinoStringReader.hpp | 19 + .../Deserialization/Readers/FlashReader.hpp | 55 + .../Readers/IteratorReader.hpp | 43 + .../Deserialization/Readers/RamReader.hpp | 50 + .../Readers/StdStreamReader.hpp | 29 + .../Deserialization/Readers/VariantReader.hpp | 34 + .../Deserialization/deserialize.hpp | 71 + .../Document/BasicJsonDocument.hpp | 166 + .../Document/DynamicJsonDocument.hpp | 29 + .../src/ArduinoJson/Document/JsonDocument.hpp | 346 + .../Document/StaticJsonDocument.hpp | 56 + .../src/ArduinoJson/Json/EscapeSequence.hpp | 39 + .../src/ArduinoJson/Json/JsonDeserializer.hpp | 735 + .../src/ArduinoJson/Json/JsonSerializer.hpp | 143 + .../src/ArduinoJson/Json/Latch.hpp | 56 + .../ArduinoJson/Json/PrettyJsonSerializer.hpp | 89 + .../src/ArduinoJson/Json/TextFormatter.hpp | 171 + .../src/ArduinoJson/Json/Utf16.hpp | 67 + .../ArduinoJson/src/ArduinoJson/Json/Utf8.hpp | 46 + .../src/ArduinoJson/Memory/Alignment.hpp | 60 + .../src/ArduinoJson/Memory/MemoryPool.hpp | 211 + .../src/ArduinoJson/Misc/SafeBoolIdiom.hpp | 26 + .../src/ArduinoJson/Misc/SerializedValue.hpp | 68 + .../src/ArduinoJson/Misc/Visitable.hpp | 21 + .../MsgPack/MsgPackDeserializer.hpp | 601 + .../ArduinoJson/MsgPack/MsgPackSerializer.hpp | 216 + .../src/ArduinoJson/MsgPack/endianess.hpp | 46 + .../src/ArduinoJson/MsgPack/ieee754.hpp | 18 + .../ArduinoJson/src/ArduinoJson/Namespace.hpp | 26 + .../src/ArduinoJson/Numbers/Float.hpp | 17 + .../src/ArduinoJson/Numbers/FloatParts.hpp | 87 + .../src/ArduinoJson/Numbers/FloatTraits.hpp | 219 + .../src/ArduinoJson/Numbers/Integer.hpp | 32 + .../ArduinoJson/Numbers/arithmeticCompare.hpp | 121 + .../src/ArduinoJson/Numbers/convertNumber.hpp | 121 + .../src/ArduinoJson/Numbers/parseNumber.hpp | 153 + .../src/ArduinoJson/Object/MemberProxy.hpp | 206 + .../ArduinoJson/Object/ObjectFunctions.hpp | 52 + .../src/ArduinoJson/Object/ObjectImpl.hpp | 69 + .../src/ArduinoJson/Object/ObjectIterator.hpp | 123 + .../src/ArduinoJson/Object/ObjectRef.hpp | 282 + .../ArduinoJson/Object/ObjectShortcuts.hpp | 73 + .../src/ArduinoJson/Object/Pair.hpp | 57 + .../src/ArduinoJson/Polyfills/alias_cast.hpp | 29 + .../src/ArduinoJson/Polyfills/assert.hpp | 14 + .../src/ArduinoJson/Polyfills/attributes.hpp | 54 + .../src/ArduinoJson/Polyfills/ctype.hpp | 20 + .../src/ArduinoJson/Polyfills/integer.hpp | 30 + .../src/ArduinoJson/Polyfills/limits.hpp | 45 + .../src/ArduinoJson/Polyfills/math.hpp | 27 + .../src/ArduinoJson/Polyfills/mpl/max.hpp | 26 + .../src/ArduinoJson/Polyfills/pgmspace.hpp | 113 + .../Polyfills/pgmspace_generic.hpp | 24 + .../ArduinoJson/Polyfills/preprocessor.hpp | 35 + .../ArduinoJson/Polyfills/static_array.hpp | 30 + .../src/ArduinoJson/Polyfills/type_traits.hpp | 25 + .../Polyfills/type_traits/conditional.hpp | 20 + .../Polyfills/type_traits/declval.hpp | 14 + .../Polyfills/type_traits/enable_if.hpp | 19 + .../type_traits/integral_constant.hpp | 19 + .../Polyfills/type_traits/is_array.hpp | 21 + .../Polyfills/type_traits/is_base_of.hpp | 26 + .../Polyfills/type_traits/is_class.hpp | 26 + .../Polyfills/type_traits/is_const.hpp | 17 + .../Polyfills/type_traits/is_convertible.hpp | 47 + .../Polyfills/type_traits/is_enum.hpp | 22 + .../type_traits/is_floating_point.hpp | 20 + .../Polyfills/type_traits/is_integral.hpp | 37 + .../Polyfills/type_traits/is_pointer.hpp | 16 + .../Polyfills/type_traits/is_same.hpp | 17 + .../Polyfills/type_traits/is_signed.hpp | 30 + .../Polyfills/type_traits/is_unsigned.hpp | 28 + .../Polyfills/type_traits/make_unsigned.hpp | 49 + .../Polyfills/type_traits/make_void.hpp | 14 + .../Polyfills/type_traits/remove_const.hpp | 20 + .../Polyfills/type_traits/remove_cv.hpp | 27 + .../type_traits/remove_reference.hpp | 20 + .../Polyfills/type_traits/type_identity.hpp | 15 + .../Serialization/CountingDecorator.hpp | 33 + .../src/ArduinoJson/Serialization/Writer.hpp | 47 + .../Writers/ArduinoStringWriter.hpp | 53 + .../Serialization/Writers/DummyWriter.hpp | 21 + .../Serialization/Writers/PrintWriter.hpp | 30 + .../Writers/StaticStringWriter.hpp | 35 + .../Serialization/Writers/StdStreamWriter.hpp | 32 + .../Serialization/Writers/StdStringWriter.hpp | 40 + .../src/ArduinoJson/Serialization/measure.hpp | 18 + .../ArduinoJson/Serialization/serialize.hpp | 54 + .../StringStorage/StringCopier.hpp | 68 + .../ArduinoJson/StringStorage/StringMover.hpp | 47 + .../StringStorage/StringStorage.hpp | 23 + .../Strings/Adapters/ArduinoString.hpp | 24 + .../Strings/Adapters/FlashString.hpp | 80 + .../Strings/Adapters/JsonString.hpp | 20 + .../Strings/Adapters/RamString.hpp | 119 + .../Strings/Adapters/StdString.hpp | 23 + .../Strings/Adapters/StringView.hpp | 20 + .../src/ArduinoJson/Strings/IsString.hpp | 17 + .../src/ArduinoJson/Strings/StoragePolicy.hpp | 56 + .../src/ArduinoJson/Strings/String.hpp | 77 + .../ArduinoJson/Strings/StringAdapters.hpp | 88 + .../src/ArduinoJson/Variant/Converter.hpp | 17 + .../src/ArduinoJson/Variant/ConverterImpl.hpp | 306 + .../src/ArduinoJson/Variant/SlotFunctions.hpp | 46 + .../ArduinoJson/Variant/VariantCompare.hpp | 208 + .../ArduinoJson/Variant/VariantContent.hpp | 57 + .../src/ArduinoJson/Variant/VariantData.hpp | 340 + .../ArduinoJson/Variant/VariantFunctions.hpp | 109 + .../src/ArduinoJson/Variant/VariantImpl.hpp | 184 + .../ArduinoJson/Variant/VariantOperators.hpp | 180 + .../src/ArduinoJson/Variant/VariantRef.hpp | 381 + .../ArduinoJson/Variant/VariantShortcuts.hpp | 23 + .../src/ArduinoJson/Variant/VariantSlot.hpp | 112 + .../src/ArduinoJson/Variant/VariantTag.hpp | 16 + .../src/ArduinoJson/Variant/VariantTo.hpp | 32 + .../src/ArduinoJson/Variant/Visitor.hpp | 54 + .../src/ArduinoJson/compatibility.hpp | 23 + .../ArduinoJson/src/ArduinoJson/version.hpp | 10 + .../lib/ArduinoJson/src/CMakeLists.txt | 90 + .../scripts/install-arduino-core-esp8266.sh | 29 + .../.github/scripts/install-arduino-ide.sh | 220 + .../.github/scripts/install-platformio.sh | 133 + .../ESPAsyncTCP/.github/scripts/on-push.sh | 64 + .../lib/ESPAsyncTCP/.github/stale.yml | 31 + .../ESPAsyncTCP/.github/workflows/push.yml | 32 + firmware/uniclock/lib/ESPAsyncTCP/.gitignore | 2 + firmware/uniclock/lib/ESPAsyncTCP/LICENSE.txt | 165 + firmware/uniclock/lib/ESPAsyncTCP/README.md | 50 + .../ESPAsyncTCP/espasynctcp.code-workspace | 7 + .../lib/ESPAsyncTCP/library.properties | 9 + .../lib/ESPAsyncTCP/src/AsyncPrinter.cpp | 214 + .../lib/ESPAsyncTCP/src/AsyncPrinter.h | 73 + .../lib/ESPAsyncTCP/src/DebugPrintMacros.h | 96 + .../lib/ESPAsyncTCP/src/ESPAsyncTCP.cpp | 1393 ++ .../lib/ESPAsyncTCP/src/ESPAsyncTCP.h | 322 + .../lib/ESPAsyncTCP/src/ESPAsyncTCPbuffer.cpp | 555 + .../lib/ESPAsyncTCP/src/ESPAsyncTCPbuffer.h | 118 + .../lib/ESPAsyncTCP/src/SyncClient.cpp | 414 + .../uniclock/lib/ESPAsyncTCP/src/SyncClient.h | 109 + .../lib/ESPAsyncTCP/src/async_config.h | 35 + .../uniclock/lib/ESPAsyncTCP/src/tcp_axtls.c | 592 + .../uniclock/lib/ESPAsyncTCP/src/tcp_axtls.h | 98 + .../lib/ESPAsyncTCP/ssl/gen_server_cert.sh | 36 + .../uniclock/lib/ESPAsyncTCP/ssl/server.cer | Bin 0 -> 587 bytes .../uniclock/lib/ESPAsyncTCP/ssl/server.key | Bin 0 -> 611 bytes .../scripts/install-arduino-core-esp32.sh | 36 + .../scripts/install-arduino-core-esp8266.sh | 29 + .../.github/scripts/install-arduino-ide.sh | 228 + .../.github/scripts/install-platformio.sh | 140 + .../.github/scripts/on-push.sh | 71 + .../lib/ESPAsyncWebServer/.github/stale.yml | 31 + .../.github/workflows/push.yml | 34 + .../uniclock/lib/ESPAsyncWebServer/.gitignore | 2 + .../lib/ESPAsyncWebServer/.travis.yml | 46 + .../lib/ESPAsyncWebServer/CMakeLists.txt | 17 + .../uniclock/lib/ESPAsyncWebServer/README.md | 1521 ++ .../lib/ESPAsyncWebServer/_config.yml | 1 + .../lib/ESPAsyncWebServer/component.mk | 3 + .../examples/CaptivePortal/CaptivePortal.ino | 47 + .../ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino | 221 + .../ESP_AsyncFSBrowser/data/.exclude.files | 2 + .../ESP_AsyncFSBrowser/data/ace.js.gz | Bin 0 -> 98943 bytes .../data/ext-searchbox.js.gz | Bin 0 -> 3131 bytes .../ESP_AsyncFSBrowser/data/favicon.ico | Bin 0 -> 1150 bytes .../ESP_AsyncFSBrowser/data/index.htm | 131 + .../ESP_AsyncFSBrowser/data/mode-css.js.gz | Bin 0 -> 6102 bytes .../ESP_AsyncFSBrowser/data/mode-html.js.gz | Bin 0 -> 17037 bytes .../data/mode-javascript.js.gz | Bin 0 -> 6315 bytes .../ESP_AsyncFSBrowser/data/worker-html.js.gz | Bin 0 -> 50024 bytes .../examples/regex_patterns/.test.build_flags | 1 + .../regex_patterns/regex_patterns.ino | 77 + .../examples/simple_server/simple_server.ino | 74 + .../lib/ESPAsyncWebServer/keywords.txt | 3 + .../lib/ESPAsyncWebServer/library.json | 37 + .../lib/ESPAsyncWebServer/library.properties | 9 + .../src/AsyncEventSource.cpp | 368 + .../ESPAsyncWebServer/src/AsyncEventSource.h | 133 + .../lib/ESPAsyncWebServer/src/AsyncJson.h | 254 + .../ESPAsyncWebServer/src/AsyncWebSocket.cpp | 1294 ++ .../ESPAsyncWebServer/src/AsyncWebSocket.h | 350 + .../src/AsyncWebSynchronization.h | 87 + .../ESPAsyncWebServer/src/ESPAsyncWebServer.h | 471 + .../ESPAsyncWebServer/src/SPIFFSEditor.cpp | 544 + .../lib/ESPAsyncWebServer/src/SPIFFSEditor.h | 24 + .../lib/ESPAsyncWebServer/src/StringArray.h | 193 + .../src/WebAuthentication.cpp | 235 + .../ESPAsyncWebServer/src/WebAuthentication.h | 34 + .../ESPAsyncWebServer/src/WebHandlerImpl.h | 151 + .../lib/ESPAsyncWebServer/src/WebHandlers.cpp | 220 + .../lib/ESPAsyncWebServer/src/WebRequest.cpp | 1008 ++ .../ESPAsyncWebServer/src/WebResponseImpl.h | 136 + .../ESPAsyncWebServer/src/WebResponses.cpp | 699 + .../lib/ESPAsyncWebServer/src/WebServer.cpp | 193 + .../lib/ESPAsyncWebServer/src/edit.htm | 627 + .../ESPAsyncWiFiManager.cpp | 1177 ++ .../ESPAsyncWiFiManager/ESPAsyncWiFiManager.h | 279 + .../uniclock/lib/ESPAsyncWiFiManager/LICENSE | 22 + .../lib/ESPAsyncWiFiManager/README.md | 270 + .../examples/AutoConnect/AutoConnect.ino | 38 + .../AutoConnectWithFSParameters.ino | 162 + ...AutoConnectWithFSParametersAndCustomIP.ino | 194 + .../AutoConnectWithFeedback.ino | 53 + .../AutoConnectWithFeedbackLED.ino | 74 + .../AutoConnectWithReset.ino | 55 + .../AutoConnectWithStaticIP.ino | 81 + .../AutoConnectWithTimeout.ino | 51 + .../ModelessConnect/ModelessConnect.ino | 46 + .../ModelessWithInterrupts.ino | 85 + .../OnDemandConfigPortal.ino | 67 + .../lib/ESPAsyncWiFiManager/keywords.txt | 39 + .../lib/ESPAsyncWiFiManager/library.json | 13 + .../ESPAsyncWiFiManager/library.properties | 9 + firmware/uniclock/lib/NTPClient/CHANGELOG | 15 + firmware/uniclock/lib/NTPClient/NTPClient.cpp | 212 + firmware/uniclock/lib/NTPClient/NTPClient.h | 114 + firmware/uniclock/lib/NTPClient/README.md | 52 + .../NTPClient/examples/Advanced/Advanced.ino | 37 + .../lib/NTPClient/examples/Basic/Basic.ino | 33 + .../examples/IsTimeSet/IsTimeSet.ino | 53 + firmware/uniclock/lib/NTPClient/keywords.txt | 24 + .../uniclock/lib/NTPClient/library.properties | 9 + .../lib/NeoPixelBus_by_Makuna/COPYING | 165 + .../lib/NeoPixelBus_by_Makuna/ReadMe.md | 38 + .../examples/DotStarTest/DotStarTest.ino | 88 + .../DotStarTest_Esp32Advanced.ino | 159 + .../DotStarTest_Esp32DmaSpi.ino | 148 + .../NeoPixelBrightness/NeoPixelBrightness.ino | 83 + .../examples/NeoPixelGamma/NeoPixelGamma.ino | 95 + .../examples/NeoPixelTest/NeoPixelTest.ino | 139 + .../NeoPixelAnimation/NeoPixelAnimation.ino | 222 + .../NeoPixelCylon/NeoPixelCylon.ino | 123 + .../NeoPixelFunFadeInOut.ino | 130 + .../NeoPixelFunLoop/NeoPixelFunLoop.ino | 139 + .../NeoPixelFunRandomChange.ino | 114 + .../NeoPixelRotateLoop/NeoPixelRotateLoop.ino | 97 + .../bitmaps/NeoPixelBitmap/NeoPixelBitmap.ino | 98 + .../bitmaps/NeoPixelBitmap/Strings.bmp | Bin 0 -> 62262 bytes .../bitmaps/NeoPixelBitmap/StringsW.bmp | Bin 0 -> 82998 bytes .../bitmaps/NeoPixelBufferCylon/Cylon.pdn | Bin 0 -> 3953 bytes .../bitmaps/NeoPixelBufferCylon/CylonGrb.h | 30 + .../bitmaps/NeoPixelBufferCylon/CylonGrbw.h | 30 + .../NeoPixelBufferCylon.ino | 74 + .../NeoPixelBufferShader.ino | 167 + .../NeoPixelDibTest/NeoPixelDibTest.ino | 158 + .../NeoSegmentBus/NeoSegmentBus.ino | 36 + .../NeoSegmentFade/NeoSegmentFade.ino | 144 + .../NeoPixelMosaicDump/NeoPixelMosaicDump.ino | 98 + .../NeoPixelMosaicTest/NeoPixelMosaicTest.ino | 97 + .../NeoPixelRingDynamicTopologyTest.ino | 122 + .../NeoPixelRingTopologyTest.ino | 104 + .../NeoPixelTilesDump/NeoPixelTilesDump.ino | 102 + .../NeoPixelTilesTest/NeoPixelTilesTest.ino | 103 + .../NeoPixelTopologyDump.ino | 78 + .../NeoPixelTopologyTest.ino | 92 + .../extras/curves/circular.png | Bin 0 -> 44762 bytes .../extras/curves/cubic.png | Bin 0 -> 42669 bytes .../extras/curves/different.png | Bin 0 -> 37163 bytes .../extras/curves/exponential.png | Bin 0 -> 43532 bytes .../extras/curves/gamma.png | Bin 0 -> 22467 bytes .../extras/curves/pronounced.png | Bin 0 -> 42590 bytes .../extras/curves/quadratic.png | Bin 0 -> 42622 bytes .../extras/curves/quintic.png | Bin 0 -> 42362 bytes .../extras/curves/sinusoidal.png | Bin 0 -> 40710 bytes .../lib/NeoPixelBus_by_Makuna/keywords.txt | 755 + .../lib/NeoPixelBus_by_Makuna/library.json | 19 + .../NeoPixelBus_by_Makuna/library.properties | 9 + .../src/NeoPixelAnimator.h | 180 + .../src/NeoPixelBrightnessBus.h | 154 + .../NeoPixelBus_by_Makuna/src/NeoPixelBus.h | 510 + .../src/NeoPixelSegmentBus.h | 67 + .../src/internal/DotStarColorFeatures.h | 698 + .../src/internal/DotStarEsp32DmaSpiMethod.h | 356 + .../src/internal/DotStarGenericMethod.h | 170 + .../src/internal/Esp32_i2s.c | 525 + .../src/internal/Esp32_i2s.h | 44 + .../src/internal/HsbColor.cpp | 83 + .../src/internal/HsbColor.h | 121 + .../src/internal/HslColor.cpp | 87 + .../src/internal/HslColor.h | 122 + .../src/internal/HtmlColor.cpp | 58 + .../src/internal/HtmlColor.h | 336 + .../src/internal/HtmlColorNameStrings.cpp | 178 + .../src/internal/HtmlColorNameStrings.h | 180 + .../src/internal/HtmlColorNames.cpp | 191 + .../src/internal/HtmlColorShortNames.cpp | 62 + .../src/internal/Layouts.h | 426 + .../src/internal/Lpd6803ColorFeatures.h | 301 + .../src/internal/Lpd6803GenericMethod.h | 143 + .../src/internal/Lpd8806ColorFeatures.h | 189 + .../src/internal/Lpd8806GenericMethod.h | 143 + .../src/internal/NeoArmMethod.h | 866 ++ .../src/internal/NeoAvrMethod.h | 240 + .../src/internal/NeoBitmapFile.h | 388 + .../src/internal/NeoBuffer.h | 172 + .../src/internal/NeoBufferContext.h | 48 + .../src/internal/NeoBufferMethods.h | 263 + .../src/internal/NeoBusChannel.h | 43 + .../src/internal/NeoColorFeatures.h | 713 + .../src/internal/NeoDib.h | 208 + .../src/internal/NeoEase.h | 313 + .../src/internal/NeoEsp32I2sMethod.h | 379 + .../src/internal/NeoEsp32RmtMethod.cpp | 331 + .../src/internal/NeoEsp32RmtMethod.h | 915 ++ .../src/internal/NeoEsp8266DmaMethod.h | 355 + .../src/internal/NeoEsp8266I2sDmx512Method.h | 335 + .../src/internal/NeoEsp8266I2sMethodCore.cpp | 34 + .../src/internal/NeoEsp8266I2sMethodCore.h | 325 + .../src/internal/NeoEsp8266UartMethod.cpp | 176 + .../src/internal/NeoEsp8266UartMethod.h | 542 + .../src/internal/NeoEspBitBangMethod.cpp | 267 + .../src/internal/NeoEspBitBangMethod.h | 399 + .../src/internal/NeoGamma.cpp | 47 + .../src/internal/NeoGamma.h | 100 + .../src/internal/NeoHueBlend.h | 118 + .../src/internal/NeoMosaic.h | 191 + .../src/internal/NeoNrf52xMethod.h | 665 + .../src/internal/NeoPixelAnimator.cpp | 197 + .../src/internal/NeoPixelAvr.c | 648 + .../src/internal/NeoRingTopology.h | 112 + .../src/internal/NeoSegmentFeatures.h | 216 + .../src/internal/NeoSettings.h | 62 + .../src/internal/NeoSm168xxColorFeatures.h | 326 + .../src/internal/NeoSpriteSheet.h | 163 + .../src/internal/NeoTiles.h | 158 + .../src/internal/NeoTm1814ColorFeatures.h | 137 + .../src/internal/NeoTm1914ColorFeatures.h | 177 + .../src/internal/NeoTopology.h | 91 + .../src/internal/NeoUtil.h | 69 + .../src/internal/P9813ColorFeatures.h | 162 + .../src/internal/P9813GenericMethod.h | 139 + .../src/internal/Rgb16Color.h | 281 + .../src/internal/Rgb48Color.cpp | 159 + .../src/internal/Rgb48Color.h | 213 + .../src/internal/RgbColor.cpp | 186 + .../src/internal/RgbColor.h | 211 + .../src/internal/RgbColorBase.cpp | 134 + .../src/internal/RgbColorBase.h | 42 + .../src/internal/Rgbw64Color.cpp | 188 + .../src/internal/Rgbw64Color.h | 248 + .../src/internal/RgbwColor.cpp | 191 + .../src/internal/RgbwColor.h | 231 + .../src/internal/SegmentDigit.cpp | 165 + .../src/internal/SegmentDigit.h | 267 + .../src/internal/Tlc5947GenericMethod.h | 214 + .../src/internal/TwoWireBitBangImple.h | 98 + .../src/internal/TwoWireBitBangImpleAvr.h | 118 + .../src/internal/TwoWireHspiImple.h | 92 + .../src/internal/TwoWireSpiImple.h | 225 + .../src/internal/Ws2801GenericMethod.h | 145 + firmware/uniclock/lib/PubSubClient/.gitignore | 5 + .../uniclock/lib/PubSubClient/.travis.yml | 7 + .../uniclock/lib/PubSubClient/CHANGES.txt | 85 + .../uniclock/lib/PubSubClient/LICENSE.txt | 20 + firmware/uniclock/lib/PubSubClient/README.md | 50 + .../examples/mqtt_auth/mqtt_auth.ino | 43 + .../examples/mqtt_basic/mqtt_basic.ino | 77 + .../examples/mqtt_esp8266/mqtt_esp8266.ino | 129 + .../mqtt_large_message/mqtt_large_message.ino | 179 + .../mqtt_publish_in_callback.ino | 60 + .../mqtt_reconnect_nonblocking.ino | 67 + .../examples/mqtt_stream/mqtt_stream.ino | 57 + .../uniclock/lib/PubSubClient/keywords.txt | 36 + .../uniclock/lib/PubSubClient/library.json | 18 + .../lib/PubSubClient/library.properties | 9 + .../lib/PubSubClient/src/PubSubClient.cpp | 769 + .../lib/PubSubClient/src/PubSubClient.h | 184 + .../lib/PubSubClient/tests/.gitignore | 4 + .../uniclock/lib/PubSubClient/tests/Makefile | 25 + .../uniclock/lib/PubSubClient/tests/README.md | 93 + .../PubSubClient/tests/src/connect_spec.cpp | 336 + .../PubSubClient/tests/src/keepalive_spec.cpp | 185 + .../lib/PubSubClient/tests/src/lib/Arduino.h | 26 + .../PubSubClient/tests/src/lib/BDDTest.cpp | 50 + .../lib/PubSubClient/tests/src/lib/BDDTest.h | 23 + .../lib/PubSubClient/tests/src/lib/Buffer.cpp | 34 + .../lib/PubSubClient/tests/src/lib/Buffer.h | 23 + .../lib/PubSubClient/tests/src/lib/Client.h | 21 + .../PubSubClient/tests/src/lib/IPAddress.cpp | 44 + .../PubSubClient/tests/src/lib/IPAddress.h | 72 + .../lib/PubSubClient/tests/src/lib/Print.h | 28 + .../PubSubClient/tests/src/lib/ShimClient.cpp | 153 + .../PubSubClient/tests/src/lib/ShimClient.h | 51 + .../lib/PubSubClient/tests/src/lib/Stream.cpp | 39 + .../lib/PubSubClient/tests/src/lib/Stream.h | 22 + .../lib/PubSubClient/tests/src/lib/trace.h | 10 + .../PubSubClient/tests/src/publish_spec.cpp | 191 + .../PubSubClient/tests/src/receive_spec.cpp | 340 + .../PubSubClient/tests/src/subscribe_spec.cpp | 178 + .../PubSubClient/tests/testcases/__init__.py | 0 .../tests/testcases/mqtt_basic.py | 39 + .../testcases/mqtt_publish_in_callback.py | 59 + .../PubSubClient/tests/testcases/settings.py | 2 + .../lib/PubSubClient/tests/testsuite.py | 181 + firmware/uniclock/lib/Rtc_by_Makuna/COPYING | 165 + firmware/uniclock/lib/Rtc_by_Makuna/README.md | 22 + .../uniclock/lib/Rtc_by_Makuna/SUPPORT.md | 8 + .../examples/DS1302_Memory/DS1302_Memory.ino | 135 + .../examples/DS1302_Simple/DS1302_Simple.ino | 101 + .../examples/DS1307_Memory/DS1307_Memory.ino | 172 + .../examples/DS1307_Simple/DS1307_Simple.ino | 137 + .../examples/DS3231_Alarms/DS3231_Alarms.ino | 207 + .../examples/DS3231_Memory/DS3231_Memory.ino | 197 + .../examples/DS3231_Simple/DS3231_Simple.ino | 145 + .../DS3231_StoreIt/DS3231_StoreIt.ino | 100 + .../examples/DS3234_Alarms/DS3234_Alarms.ino | 179 + .../examples/DS3234_Memory/DS3234_Memory.ino | 139 + .../examples/DS3234_Simple/DS3234_Simple.ino | 115 + .../RtcTemperatureTests.ino | 204 + .../uniclock/lib/Rtc_by_Makuna/keywords.txt | 148 + .../uniclock/lib/Rtc_by_Makuna/library.json | 13 + .../lib/Rtc_by_Makuna/library.properties | 9 + .../lib/Rtc_by_Makuna/src/EepromAT24C32.h | 112 + .../lib/Rtc_by_Makuna/src/RtcDS1302.h | 272 + .../lib/Rtc_by_Makuna/src/RtcDS1307.h | 259 + .../lib/Rtc_by_Makuna/src/RtcDS3231.h | 631 + .../lib/Rtc_by_Makuna/src/RtcDS3234.h | 699 + .../lib/Rtc_by_Makuna/src/RtcDateTime.cpp | 211 + .../lib/Rtc_by_Makuna/src/RtcDateTime.h | 185 + .../lib/Rtc_by_Makuna/src/RtcTemperature.h | 153 + .../lib/Rtc_by_Makuna/src/RtcUtility.cpp | 34 + .../lib/Rtc_by_Makuna/src/RtcUtility.h | 35 + .../lib/Rtc_by_Makuna/src/ThreeWire.h | 99 + firmware/uniclock/lib/Time/DateStrings.cpp | 97 + firmware/uniclock/lib/Time/Readme.md | 165 + firmware/uniclock/lib/Time/Time.cpp | 321 + firmware/uniclock/lib/Time/TimeLib.h | 144 + .../uniclock/lib/Time/docs/issue_template.md | 64 + .../SyncArduinoClock/SyncArduinoClock.pde | 87 + .../Processing/SyncArduinoClock/readme.txt | 9 + .../TimeArduinoDue/TimeArduinoDue.ino | 71 + .../lib/Time/examples/TimeGPS/TimeGPS.ino | 87 + .../lib/Time/examples/TimeNTP/TimeNTP.ino | 135 + .../TimeNTP_ENC28J60/TimeNTP_ENC28J60.ino | 168 + .../TimeNTP_ESP8266WiFi.ino | 156 + .../lib/Time/examples/TimeRTC/TimeRTC.ino | 55 + .../Time/examples/TimeRTCLog/TimeRTCLog.ino | 109 + .../Time/examples/TimeRTCSet/TimeRTCSet.ino | 80 + .../Time/examples/TimeSerial/TimeSerial.ino | 81 + .../TimeSerialDateStrings.ino | 108 + .../Time/examples/TimeTeensy3/TimeTeensy3.ino | 78 + firmware/uniclock/lib/Time/keywords.txt | 34 + firmware/uniclock/lib/Time/library.json | 26 + firmware/uniclock/lib/Time/library.properties | 11 + .../uniclock/lib/Timezone/CONTRIBUTING.md | 18 + firmware/uniclock/lib/Timezone/LICENSE.md | 675 + firmware/uniclock/lib/Timezone/README.md | 196 + .../examples/Change_TZ_1/Change_TZ_1.ino | 119 + .../examples/Change_TZ_2/Change_TZ_2.ino | 105 + .../lib/Timezone/examples/Clock/Clock.ino | 71 + .../examples/HardwareRTC/HardwareRTC.ino | 57 + .../examples/WorldClock/WorldClock.ino | 123 + .../examples/WriteRules/WriteRules.ino | 29 + .../lib/Timezone/examples/tzTest/tzTest.ino | 85 + firmware/uniclock/lib/Timezone/keywords.txt | 9 + .../uniclock/lib/Timezone/library.properties | 10 + .../uniclock/lib/Timezone/src/Timezone.cpp | 243 + firmware/uniclock/lib/Timezone/src/Timezone.h | 61 + .../arduino-home-assistant-main/CHANGELOG.md | 37 + .../lib/arduino-home-assistant-main/README.md | 85 + .../advanced-availability.ino | 66 + .../examples/availability/README.md | 33 + .../examples/availability/availability.ino | 59 + .../examples/binary-sensor/binary-sensor.ino | 46 + .../examples/cover/cover.ino | 49 + .../examples/fan/fan.ino | 53 + .../examples/hvac/hvac.ino | 102 + .../examples/led-switch/led-switch.ino | 50 + .../examples/mqtt-advanced/mqtt-advanced.ino | 56 + .../mqtt-with-credentials.ino | 42 + .../multi-state-button/multi-state-button.ino | 52 + .../examples/nano33iot/nano33iot.ino | 53 + .../examples/nodemcu/nodemcu.ino | 60 + .../examples/sensor/sensor.ino | 54 + .../library.properties | 10 + .../src/ArduinoHA.h | 16 + .../src/ArduinoHADefines.h | 23 + .../src/HADevice.cpp | 198 + .../src/HADevice.h | 53 + .../src/HAMqtt.cpp | 331 + .../arduino-home-assistant-main/src/HAMqtt.h | 243 + .../src/HAUtils.cpp | 67 + .../arduino-home-assistant-main/src/HAUtils.h | 33 + .../src/device-types/BaseDeviceType.cpp | 163 + .../src/device-types/BaseDeviceType.h | 72 + .../src/device-types/DeviceTypeSerializer.cpp | 421 + .../src/device-types/DeviceTypeSerializer.h | 117 + .../src/device-types/HABinarySensor.cpp | 194 + .../src/device-types/HABinarySensor.h | 81 + .../src/device-types/HACover.cpp | 286 + .../src/device-types/HACover.h | 113 + .../src/device-types/HAFan.cpp | 343 + .../src/device-types/HAFan.h | 156 + .../src/device-types/HAHVAC.cpp | 1078 ++ .../src/device-types/HAHVAC.h | 336 + .../src/device-types/HASensor.cpp | 218 + .../src/device-types/HASensor.h | 90 + .../src/device-types/HASwitch.cpp | 211 + .../src/device-types/HASwitch.h | 123 + .../src/device-types/HATagScanner.cpp | 111 + .../src/device-types/HATagScanner.h | 43 + .../src/device-types/HATriggers.cpp | 321 + .../src/device-types/HATriggers.h | 73 + firmware/uniclock/platformio.ini | 30 + 843 files changed, 122061 insertions(+) create mode 100644 firmware/uniclock/ESP32_UniClock2/1tube.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/DHT_temp.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/ESP32_UniClock2.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/HV5122.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/HV5122new.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/I2Csensors.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/MAX6921.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/MAX6921_ESP32.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/MAX7219CNG.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/MM5450.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/Numitron_4511N.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/PT6311.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/PT6355.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/SN75512.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/SN75518.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/VQC10.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/ZM1500.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/clocks.h create mode 100644 firmware/uniclock/ESP32_UniClock2/dallas_temp.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/data/favicon.ico create mode 100644 firmware/uniclock/ESP32_UniClock2/data/index.html create mode 100644 firmware/uniclock/ESP32_UniClock2/data/jquery_351.js create mode 100644 firmware/uniclock/ESP32_UniClock2/data/jquery_360.js create mode 100644 firmware/uniclock/ESP32_UniClock2/data/page.js create mode 100644 firmware/uniclock/ESP32_UniClock2/data/site.css create mode 100644 firmware/uniclock/ESP32_UniClock2/dummy.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/font5x7.h create mode 100644 firmware/uniclock/ESP32_UniClock2/gps.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/mqtt.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/mulev2.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/multiplex74141.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/multiplex74141_ESP32.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/no_multiplex74141.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/no_multiplex_ESP32.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/pcf_multiplex74141.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/pwmleds.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/rtc.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/wordclock.ino create mode 100644 firmware/uniclock/ESP32_UniClock2/z_neopixel.ino create mode 100644 firmware/uniclock/README.md create mode 100644 firmware/uniclock/lib/ArduinoJson/ArduinoJson.h create mode 100644 firmware/uniclock/lib/ArduinoJson/CHANGELOG.md create mode 100644 firmware/uniclock/lib/ArduinoJson/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/CONTRIBUTING.md create mode 100644 firmware/uniclock/lib/ArduinoJson/LICENSE.md create mode 100644 firmware/uniclock/lib/ArduinoJson/README.md create mode 100644 firmware/uniclock/lib/ArduinoJson/SUPPORT.md create mode 100644 firmware/uniclock/lib/ArduinoJson/appveyor.yml create mode 100644 firmware/uniclock/lib/ArduinoJson/banner.svg create mode 100644 firmware/uniclock/lib/ArduinoJson/component.mk create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/JsonConfigFile/JsonConfigFile.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/JsonFilterExample/JsonFilterExample.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/JsonGeneratorExample/JsonGeneratorExample.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/JsonHttpClient/JsonHttpClient.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/JsonParserExample/JsonParserExample.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/JsonServer/JsonServer.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/JsonUdpBeacon/JsonUdpBeacon.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/MsgPackParser/MsgPackParser.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/ProgmemExample/ProgmemExample.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/examples/StringExample/StringExample.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/ArduinoJsonConfig.cmake.in create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/CompileOptions.cmake create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/component.mk create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/main.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/ci/particle.sh create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/conf_test/avr.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/conf_test/esp8266.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/conf_test/x64.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/conf_test/x86.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/Makefile create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_fuzzer.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Comments.json create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/EmptyArray.json create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/EmptyObject.json create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/ExcessiveNesting.json create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/IntegerOverflow.json create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Numbers.json create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/OpenWeatherMap.json create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Strings.json create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/WeatherUnderground.json create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_fuzzer.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/array16 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/array32 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/false create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/fixarray create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/fixint_negative create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/fixint_positive create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/fixmap create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/fixstr create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/float32 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/float64 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/int16 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/int32 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/int64 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/int8 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/map16 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/map32 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/nil create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/str16 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/str32 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/str8 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/true create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/uint16 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/uint32 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/uint64 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/uint8 create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/fuzzing/reproducer.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/particle/project.properties create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/particle/src/smocktest.ino create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/build-arduino-package.sh create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/build-single-header.sh create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/get-release-body.sh create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/get-release-page.sh create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/publish-particle-library.sh create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/publish.sh create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/JsonGeneratorExample.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/JsonParserExample.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/MsgPackParserExample.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/publish.sh create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/issue1120.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/nullptr.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/use_long_long_0.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/use_long_long_1.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp17/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp17/string_view.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp20/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp20/smoke_test.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/Issue1189.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/Issue978.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/assign_char.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/delete_jsondocument.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/read_long_long.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/variant_as_char.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/write_long_long.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/Arduino.h create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/CustomReader.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/Print.h create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/Stream.h create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/String.h create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/progmem_emulation.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/gbathree.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/issue772.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/openweathermap.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/round_trip.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/add.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/clear.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/copyArray.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/createNested.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/equals.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/get.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/isNull.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/iterator.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/memoryUsage.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/nesting.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/remove.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/size.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/std_string.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/subscript.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/unbound.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/DeserializationError.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/array.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/array_static.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/filter.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/incomplete_input.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/input_types.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/invalid_input.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/misc.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/nestingLimit.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/number.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/object.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/object_static.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/string.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/BasicJsonDocument.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/DynamicJsonDocument.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/ElementProxy.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/MemberProxy.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/StaticJsonDocument.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/add.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/cast.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/compare.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/containsKey.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/createNested.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/isNull.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/nesting.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/overflowed.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/remove.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/shrinkToFit.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/size.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/subscript.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/swap.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/clear.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/containsKey.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/copy.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/createNestedArray.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/createNestedObject.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/equals.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/invalid.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/isNull.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/iterator.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/memoryUsage.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/nesting.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/remove.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/size.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/std_string.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/subscript.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/CustomWriter.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonArray.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonArrayPretty.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonObject.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonObjectPretty.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonVariant.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/misc.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/std_stream.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/std_string.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/add.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/as.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/clear.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/compare.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/containsKey.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/converters.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/copy.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/createNested.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/is.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/isnull.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/memoryUsage.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/misc.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/nesting.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/or.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/overflow.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/remove.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/set.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/subscript.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/types.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/unbound.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/StringCopier.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/allocVariant.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/clear.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/saveString.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/size.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/FloatParts.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/JsonString.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/NoArduinoHeader.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Readers.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/StringAdapters.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/StringWriter.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/TypeTraits.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Utf16.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Utf8.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/arithmeticCompare.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/conflicts.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/custom_string.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/deprecated.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/printable.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/unsigned_char.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/version.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/weird_strcmp.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_0.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_1.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_alignment_0.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_alignment_1.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_0.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_1.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_0.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_1.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_0.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_1.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_progmem_1.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_0.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_1.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/issue1707.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/use_double_0.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/use_double_1.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeArray.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeObject.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeStaticVariant.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeVariant.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/doubleToFloat.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/filter.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/incompleteInput.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/input_types.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/misc.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/nestingLimit.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/notSupported.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/destination_types.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/measure.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/misc.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeArray.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeVariant.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/convertNumber.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseDouble.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseFloat.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseInteger.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseNumber.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeFloat.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeInteger.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeString.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/catch/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/catch/catch.cpp create mode 100644 firmware/uniclock/lib/ArduinoJson/extras/tests/catch/catch.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/keywords.txt create mode 100644 firmware/uniclock/lib/ArduinoJson/library.json create mode 100644 firmware/uniclock/lib/ArduinoJson/library.properties create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson.h create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayFunctions.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayIterator.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayShortcuts.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Document/BasicJsonDocument.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Document/DynamicJsonDocument.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Document/StaticJsonDocument.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Json/EscapeSequence.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Json/JsonDeserializer.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Json/JsonSerializer.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Json/Latch.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Json/PrettyJsonSerializer.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Json/Utf8.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Memory/Alignment.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Misc/SafeBoolIdiom.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Misc/SerializedValue.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Misc/Visitable.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/MsgPack/endianess.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/MsgPack/ieee754.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Namespace.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Numbers/Float.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatParts.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatTraits.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Numbers/Integer.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Numbers/arithmeticCompare.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Numbers/parseNumber.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Object/ObjectFunctions.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Object/ObjectIterator.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Object/ObjectRef.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Object/ObjectShortcuts.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Object/Pair.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/alias_cast.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/ctype.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/integer.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/math.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/mpl/max.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace_generic.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/preprocessor.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/static_array.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/conditional.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/declval.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/enable_if.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/integral_constant.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_array.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_base_of.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_class.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_const.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_enum.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_pointer.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_same.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_unsigned.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_void.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_const.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_cv.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_reference.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/type_identity.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/CountingDecorator.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/DummyWriter.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/PrintWriter.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StaticStringWriter.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStreamWriter.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStringWriter.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/measure.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Serialization/serialize.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/StringStorage/StringCopier.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/StringStorage/StringMover.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/StringStorage/StringStorage.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/ArduinoString.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/FlashString.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/JsonString.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/RamString.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/StdString.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/Adapters/StringView.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/IsString.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/StoragePolicy.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/String.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Strings/StringAdapters.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/Converter.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/ConverterImpl.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/SlotFunctions.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantContent.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantFunctions.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantImpl.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantOperators.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantRef.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantShortcuts.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantSlot.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantTag.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/VariantTo.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Variant/Visitor.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/compatibility.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/version.hpp create mode 100644 firmware/uniclock/lib/ArduinoJson/src/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/.github/scripts/install-arduino-core-esp8266.sh create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/.github/scripts/install-arduino-ide.sh create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/.github/scripts/install-platformio.sh create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/.github/scripts/on-push.sh create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/.github/stale.yml create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/.github/workflows/push.yml create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/.gitignore create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/LICENSE.txt create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/README.md create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/espasynctcp.code-workspace create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/library.properties create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/AsyncPrinter.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/AsyncPrinter.h create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/DebugPrintMacros.h create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/ESPAsyncTCP.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/ESPAsyncTCP.h create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/ESPAsyncTCPbuffer.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/ESPAsyncTCPbuffer.h create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/SyncClient.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/SyncClient.h create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/async_config.h create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/tcp_axtls.c create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/src/tcp_axtls.h create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/ssl/gen_server_cert.sh create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/ssl/server.cer create mode 100644 firmware/uniclock/lib/ESPAsyncTCP/ssl/server.key create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/.github/scripts/install-arduino-core-esp32.sh create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/.github/scripts/install-arduino-core-esp8266.sh create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/.github/scripts/install-arduino-ide.sh create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/.github/scripts/install-platformio.sh create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/.github/scripts/on-push.sh create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/.github/stale.yml create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/.github/workflows/push.yml create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/.gitignore create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/.travis.yml create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/CMakeLists.txt create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/README.md create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/_config.yml create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/component.mk create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/.exclude.files create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/ace.js.gz create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/favicon.ico create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/index.htm create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/regex_patterns/.test.build_flags create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/regex_patterns/regex_patterns.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/examples/simple_server/simple_server.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/keywords.txt create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/library.json create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/library.properties create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/AsyncEventSource.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/AsyncEventSource.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/AsyncJson.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/AsyncWebSocket.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/AsyncWebSocket.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/AsyncWebSynchronization.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/SPIFFSEditor.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/SPIFFSEditor.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/StringArray.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/WebAuthentication.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/WebAuthentication.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/WebHandlerImpl.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/WebHandlers.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/WebRequest.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/WebResponseImpl.h create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/WebResponses.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/WebServer.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncWebServer/src/edit.htm create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/ESPAsyncWiFiManager.cpp create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/ESPAsyncWiFiManager.h create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/LICENSE create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/README.md create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/AutoConnect/AutoConnect.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/AutoConnectWithFSParameters/AutoConnectWithFSParameters.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/AutoConnectWithFSParametersAndCustomIP/AutoConnectWithFSParametersAndCustomIP.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/AutoConnectWithFeedback/AutoConnectWithFeedback.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/AutoConnectWithFeedbackLED/AutoConnectWithFeedbackLED.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/AutoConnectWithReset/AutoConnectWithReset.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/AutoConnectWithStaticIP/AutoConnectWithStaticIP.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/AutoConnectWithTimeout/AutoConnectWithTimeout.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/ModelessConnect/ModelessConnect.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/ModelessWithInterrupts/ModelessWithInterrupts.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/examples/OnDemandConfigPortal/OnDemandConfigPortal.ino create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/keywords.txt create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/library.json create mode 100644 firmware/uniclock/lib/ESPAsyncWiFiManager/library.properties create mode 100644 firmware/uniclock/lib/NTPClient/CHANGELOG create mode 100644 firmware/uniclock/lib/NTPClient/NTPClient.cpp create mode 100644 firmware/uniclock/lib/NTPClient/NTPClient.h create mode 100644 firmware/uniclock/lib/NTPClient/README.md create mode 100644 firmware/uniclock/lib/NTPClient/examples/Advanced/Advanced.ino create mode 100644 firmware/uniclock/lib/NTPClient/examples/Basic/Basic.ino create mode 100644 firmware/uniclock/lib/NTPClient/examples/IsTimeSet/IsTimeSet.ino create mode 100644 firmware/uniclock/lib/NTPClient/keywords.txt create mode 100644 firmware/uniclock/lib/NTPClient/library.properties create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/COPYING create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/ReadMe.md create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/DotStarTest/DotStarTest.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/ESP32/DotStarTest_Esp32Advanced/DotStarTest_Esp32Advanced.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/ESP32/DotStarTest_Esp32DmaSpi/DotStarTest_Esp32DmaSpi.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/NeoPixelBrightness/NeoPixelBrightness.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/NeoPixelGamma/NeoPixelGamma.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/NeoPixelTest/NeoPixelTest.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/animations/NeoPixelAnimation/NeoPixelAnimation.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/animations/NeoPixelCylon/NeoPixelCylon.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/animations/NeoPixelFunFadeInOut/NeoPixelFunFadeInOut.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/animations/NeoPixelFunLoop/NeoPixelFunLoop.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/animations/NeoPixelFunRandomChange/NeoPixelFunRandomChange.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/animations/NeoPixelRotateLoop/NeoPixelRotateLoop.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/bitmaps/NeoPixelBitmap/NeoPixelBitmap.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/bitmaps/NeoPixelBitmap/Strings.bmp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/bitmaps/NeoPixelBitmap/StringsW.bmp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/bitmaps/NeoPixelBufferCylon/Cylon.pdn create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/bitmaps/NeoPixelBufferCylon/CylonGrb.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/bitmaps/NeoPixelBufferCylon/CylonGrbw.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/bitmaps/NeoPixelBufferCylon/NeoPixelBufferCylon.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/bitmaps/NeoPixelBufferShader/NeoPixelBufferShader.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/bitmaps/NeoPixelDibTest/NeoPixelDibTest.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/sevensegment/NeoSegmentBus/NeoSegmentBus.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/sevensegment/NeoSegmentFade/NeoSegmentFade.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/topologies/NeoPixelMosaicDump/NeoPixelMosaicDump.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/topologies/NeoPixelMosaicTest/NeoPixelMosaicTest.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/topologies/NeoPixelRingDynamicTopologyTest/NeoPixelRingDynamicTopologyTest.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/topologies/NeoPixelRingTopologyTest/NeoPixelRingTopologyTest.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/topologies/NeoPixelTilesDump/NeoPixelTilesDump.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/topologies/NeoPixelTilesTest/NeoPixelTilesTest.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/topologies/NeoPixelTopologyDump/NeoPixelTopologyDump.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/examples/topologies/NeoPixelTopologyTest/NeoPixelTopologyTest.ino create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/extras/curves/circular.png create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/extras/curves/cubic.png create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/extras/curves/different.png create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/extras/curves/exponential.png create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/extras/curves/gamma.png create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/extras/curves/pronounced.png create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/extras/curves/quadratic.png create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/extras/curves/quintic.png create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/extras/curves/sinusoidal.png create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/keywords.txt create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/library.json create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/library.properties create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/NeoPixelAnimator.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/NeoPixelBrightnessBus.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/NeoPixelBus.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/NeoPixelSegmentBus.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/DotStarColorFeatures.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/DotStarEsp32DmaSpiMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/DotStarGenericMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Esp32_i2s.c create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Esp32_i2s.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HsbColor.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HsbColor.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HslColor.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HslColor.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HtmlColor.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HtmlColor.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HtmlColorNameStrings.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HtmlColorNameStrings.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HtmlColorNames.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/HtmlColorShortNames.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Layouts.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Lpd6803ColorFeatures.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Lpd6803GenericMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Lpd8806ColorFeatures.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Lpd8806GenericMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoArmMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoAvrMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoBitmapFile.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoBuffer.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoBufferContext.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoBufferMethods.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoBusChannel.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoColorFeatures.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoDib.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEase.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEsp32I2sMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEsp32RmtMethod.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEsp32RmtMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEsp8266DmaMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEsp8266I2sDmx512Method.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEsp8266I2sMethodCore.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEsp8266I2sMethodCore.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEsp8266UartMethod.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEsp8266UartMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEspBitBangMethod.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoEspBitBangMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoGamma.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoGamma.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoHueBlend.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoMosaic.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoNrf52xMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoPixelAnimator.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoPixelAvr.c create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoRingTopology.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoSegmentFeatures.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoSettings.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoSm168xxColorFeatures.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoSpriteSheet.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoTiles.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoTm1814ColorFeatures.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoTm1914ColorFeatures.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoTopology.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/NeoUtil.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/P9813ColorFeatures.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/P9813GenericMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Rgb16Color.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Rgb48Color.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Rgb48Color.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/RgbColor.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/RgbColor.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/RgbColorBase.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/RgbColorBase.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Rgbw64Color.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Rgbw64Color.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/RgbwColor.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/RgbwColor.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/SegmentDigit.cpp create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/SegmentDigit.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Tlc5947GenericMethod.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/TwoWireBitBangImple.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/TwoWireBitBangImpleAvr.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/TwoWireHspiImple.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/TwoWireSpiImple.h create mode 100644 firmware/uniclock/lib/NeoPixelBus_by_Makuna/src/internal/Ws2801GenericMethod.h create mode 100644 firmware/uniclock/lib/PubSubClient/.gitignore create mode 100644 firmware/uniclock/lib/PubSubClient/.travis.yml create mode 100644 firmware/uniclock/lib/PubSubClient/CHANGES.txt create mode 100644 firmware/uniclock/lib/PubSubClient/LICENSE.txt create mode 100644 firmware/uniclock/lib/PubSubClient/README.md create mode 100644 firmware/uniclock/lib/PubSubClient/examples/mqtt_auth/mqtt_auth.ino create mode 100644 firmware/uniclock/lib/PubSubClient/examples/mqtt_basic/mqtt_basic.ino create mode 100644 firmware/uniclock/lib/PubSubClient/examples/mqtt_esp8266/mqtt_esp8266.ino create mode 100644 firmware/uniclock/lib/PubSubClient/examples/mqtt_large_message/mqtt_large_message.ino create mode 100644 firmware/uniclock/lib/PubSubClient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino create mode 100644 firmware/uniclock/lib/PubSubClient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino create mode 100644 firmware/uniclock/lib/PubSubClient/examples/mqtt_stream/mqtt_stream.ino create mode 100644 firmware/uniclock/lib/PubSubClient/keywords.txt create mode 100644 firmware/uniclock/lib/PubSubClient/library.json create mode 100644 firmware/uniclock/lib/PubSubClient/library.properties create mode 100644 firmware/uniclock/lib/PubSubClient/src/PubSubClient.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/src/PubSubClient.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/.gitignore create mode 100644 firmware/uniclock/lib/PubSubClient/tests/Makefile create mode 100644 firmware/uniclock/lib/PubSubClient/tests/README.md create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/connect_spec.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/keepalive_spec.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/Arduino.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/BDDTest.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/BDDTest.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/Buffer.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/Buffer.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/Client.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/IPAddress.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/IPAddress.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/Print.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/ShimClient.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/ShimClient.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/Stream.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/Stream.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/lib/trace.h create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/publish_spec.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/receive_spec.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/src/subscribe_spec.cpp create mode 100644 firmware/uniclock/lib/PubSubClient/tests/testcases/__init__.py create mode 100644 firmware/uniclock/lib/PubSubClient/tests/testcases/mqtt_basic.py create mode 100644 firmware/uniclock/lib/PubSubClient/tests/testcases/mqtt_publish_in_callback.py create mode 100644 firmware/uniclock/lib/PubSubClient/tests/testcases/settings.py create mode 100644 firmware/uniclock/lib/PubSubClient/tests/testsuite.py create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/COPYING create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/README.md create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/SUPPORT.md create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS1302_Memory/DS1302_Memory.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS1302_Simple/DS1302_Simple.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS1307_Memory/DS1307_Memory.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS1307_Simple/DS1307_Simple.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS3231_Alarms/DS3231_Alarms.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS3231_Memory/DS3231_Memory.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS3231_Simple/DS3231_Simple.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS3231_StoreIt/DS3231_StoreIt.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS3234_Alarms/DS3234_Alarms.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS3234_Memory/DS3234_Memory.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/examples/DS3234_Simple/DS3234_Simple.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/extras/RtcTemperatureTests/RtcTemperatureTests.ino create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/keywords.txt create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/library.json create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/library.properties create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/EepromAT24C32.h create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/RtcDS1302.h create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/RtcDS1307.h create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/RtcDS3231.h create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/RtcDS3234.h create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/RtcDateTime.cpp create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/RtcDateTime.h create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/RtcTemperature.h create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/RtcUtility.cpp create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/RtcUtility.h create mode 100644 firmware/uniclock/lib/Rtc_by_Makuna/src/ThreeWire.h create mode 100644 firmware/uniclock/lib/Time/DateStrings.cpp create mode 100644 firmware/uniclock/lib/Time/Readme.md create mode 100644 firmware/uniclock/lib/Time/Time.cpp create mode 100644 firmware/uniclock/lib/Time/TimeLib.h create mode 100644 firmware/uniclock/lib/Time/docs/issue_template.md create mode 100644 firmware/uniclock/lib/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde create mode 100644 firmware/uniclock/lib/Time/examples/Processing/SyncArduinoClock/readme.txt create mode 100644 firmware/uniclock/lib/Time/examples/TimeArduinoDue/TimeArduinoDue.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeGPS/TimeGPS.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeNTP/TimeNTP.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeNTP_ENC28J60/TimeNTP_ENC28J60.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeRTC/TimeRTC.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeRTCLog/TimeRTCLog.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeRTCSet/TimeRTCSet.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeSerial/TimeSerial.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino create mode 100644 firmware/uniclock/lib/Time/examples/TimeTeensy3/TimeTeensy3.ino create mode 100644 firmware/uniclock/lib/Time/keywords.txt create mode 100644 firmware/uniclock/lib/Time/library.json create mode 100644 firmware/uniclock/lib/Time/library.properties create mode 100644 firmware/uniclock/lib/Timezone/CONTRIBUTING.md create mode 100644 firmware/uniclock/lib/Timezone/LICENSE.md create mode 100644 firmware/uniclock/lib/Timezone/README.md create mode 100644 firmware/uniclock/lib/Timezone/examples/Change_TZ_1/Change_TZ_1.ino create mode 100644 firmware/uniclock/lib/Timezone/examples/Change_TZ_2/Change_TZ_2.ino create mode 100644 firmware/uniclock/lib/Timezone/examples/Clock/Clock.ino create mode 100644 firmware/uniclock/lib/Timezone/examples/HardwareRTC/HardwareRTC.ino create mode 100644 firmware/uniclock/lib/Timezone/examples/WorldClock/WorldClock.ino create mode 100644 firmware/uniclock/lib/Timezone/examples/WriteRules/WriteRules.ino create mode 100644 firmware/uniclock/lib/Timezone/examples/tzTest/tzTest.ino create mode 100644 firmware/uniclock/lib/Timezone/keywords.txt create mode 100644 firmware/uniclock/lib/Timezone/library.properties create mode 100644 firmware/uniclock/lib/Timezone/src/Timezone.cpp create mode 100644 firmware/uniclock/lib/Timezone/src/Timezone.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/CHANGELOG.md create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/README.md create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/advanced-availability/advanced-availability.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/availability/README.md create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/availability/availability.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/binary-sensor/binary-sensor.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/cover/cover.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/fan/fan.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/hvac/hvac.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/led-switch/led-switch.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/mqtt-advanced/mqtt-advanced.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/mqtt-with-credentials/mqtt-with-credentials.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/multi-state-button/multi-state-button.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/nano33iot/nano33iot.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/nodemcu/nodemcu.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/examples/sensor/sensor.ino create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/library.properties create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/ArduinoHA.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/ArduinoHADefines.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/HADevice.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/HADevice.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/HAMqtt.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/HAMqtt.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/HAUtils.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/HAUtils.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/BaseDeviceType.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/BaseDeviceType.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/DeviceTypeSerializer.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/DeviceTypeSerializer.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HABinarySensor.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HABinarySensor.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HACover.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HACover.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HAFan.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HAFan.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HAHVAC.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HAHVAC.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HASensor.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HASensor.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HASwitch.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HASwitch.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HATagScanner.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HATagScanner.h create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HATriggers.cpp create mode 100644 firmware/uniclock/lib/arduino-home-assistant-main/src/device-types/HATriggers.h create mode 100644 firmware/uniclock/platformio.ini diff --git a/firmware/uniclock/ESP32_UniClock2/1tube.ino b/firmware/uniclock/ESP32_UniClock2/1tube.ino new file mode 100644 index 0000000..50e4d5a --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/1tube.ino @@ -0,0 +1,306 @@ +/* + * ====================================================================== + * 1TUBE.INO - driver for the UNFI "1TUBE" single-Nixie-tube clock + * ====================================================================== + * Hardware: ONE physical Nixie tube (e.g. IN-16/Z-series) fed by a single + * BCD-to-decimal decoder/driver IC (e.g. 74141/K155ID1-class chip) wired + * to the 4 "ABCDPins" GPIOs. Because there is only one tube, the classic + * simultaneous-multiplex approach used by multi-tube drivers cannot show + * several digits at once; instead this driver walks through the 4 logical + * digit positions (H,H,M,M held in digit[0..3]) ONE AT A TIME, holding + * each on the single tube for a while and cross-fading (PWM wipe) between + * the old and new character of that position before advancing. + * Board target: ESP8266 (uses the ESP8266 timer1 API: timer1_write(), + * ICACHE_RAM_ATTR); this driver is NOT built for ESP32. + * Protocol: 4 parallel GPIO lines (ABCDPins[0..3]) carry the BCD nibble + * that the decoder IC translates into "light cathode N" on the tube. + * BCD codes 10 (0xA) and 15 (0xF) are both outside the valid 0-9 range, + * so the decoder lights no cathode for them - used here as "blank" codes. + * Used by CLOCK_NN configs (see clocks.h): CLOCK_70 ("1 tube esp8266 + * Nixie Clock by UNFI") is the only config that #defines TUBE1CLOCK. + * ====================================================================== + */ +#ifdef TUBE1CLOCK +char tubeDriver[] = "1TUBE"; +//used by UNFI 1TUBE Clock + +//byte ABCDPins[4] = {2,5,4,15}; //defined in clocks.h + +int maxDigits = 4; //4 LOGICAL digit positions (H,H,M,M) even though there is only 1 PHYSICAL tube +int pos = 0; //pointer of show phase - which of the 4 logical digit positions is currently on the single tube + +int PWMrefresh = 15000; //msec, multiplex time period. Greater value => slower multiplex frequency +int PWM_min = 2000; +int PWM_max = 15000; +int maxDig=maxDigits; + +void setup_pins() { + char tmp[30]; + + DPRINTLN("1TUBE Clock - setup pins"); + + for (int i=0;i<4;i++) { + pinMode(ABCDPins[i], OUTPUT); + sprintf(tmp,"Pin[%c]",char('A'+i)); + regPin(ABCDPins[i],tmp); + } + startTimer(); +} + +void ICACHE_RAM_ATTR writeBits(byte num) { + //Send the 4-bit BCD code to the decoder IC, LSB first on ABCDPins[0]. + //Note: digitalWrite() treats ANY non-zero value as HIGH, so passing the + //raw masked bit (0,1,2,4,8) instead of a strict 0/1 is fine here. + for (int j=0;j<4;j++) { + digitalWrite(ABCDPins[j],num & (1< 0) animM = animMask[pos]; + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + if ((brightness ==0) || !radarON){ + writeBits(0xA); //black display - BCD code 10 is outside 0-9, decoder lights no cathode + + #if DECIMALPOINT_PIN>=0 + digitalWrite(DECIMALPOINT_PIN,LOW); + #endif + #if COLON_PIN >= 0 + digitalWrite(COLON_PIN,LOW); // colon pin OFF + #endif + timer1_write(100*PWMrefresh); //re-check only occasionally while display is fully off (saves ISR overhead) + return; + } + else { + digit[4] = 0xA; //extra "5th" slot (blank) used as the makeAnim() end-of-cycle marker/target position - see writeDisplay2() + if (state==0) { + if (animMask[pos]==0) num = digit[pos]; + else num = oldDigit[pos]; //mid-animation: still fading OUT the previous character + } + else if (state==1) num = newDigit[pos]; //mid-animation: now fading IN the new character + else num = 0xF; //BCD code 15 - also outside 0-9, another "blank" pattern (belt-and-braces vs 0xA) + + writeBits(num); + switch (state) { //state machine: 0=show current/old char, 1=show new char (animation only), 2=blank (brightness/PWM off-time) + case 0: + #if DECIMALPOINT_PIN>=0 + if (decimalpointON) digitalWrite(DECIMALPOINT_PIN,HIGH); + #endif + #if COLON_PIN>=0 + digitalWrite(COLON_PIN,colonBlinkState); // Blink colon pin + #endif + if (autoBrightness && displayON) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); //ambient-light-driven ON-time within the PWM period + } + else { + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); //fixed day/night brightness setting -> ON-time + } + if (animM > 0) { //Animation running for this position? animM (1..19) is the wipe progress from animMask[pos] + timer = (PWMtimeBrightness * (20-animM))/20; //old-character share of the ON-time shrinks as animM grows + state = 1; //next state is: show newDigits + } + else { + timer = PWMtimeBrightness; + if (brightness>=MAXBRIGHTNESS) state = 0; //at max brightness skip the blank phase entirely (always-on, no flicker) + else state = 2; //default next state is: BLANK display + } + break; + case 1: //show new character, if animation + timer = (PWMtimeBrightness * animM)/20; //new-character share of the ON-time grows as animM grows + if (brightness>=MAXBRIGHTNESS) state = 0; + else state = 2; //default next state is: BLANK display + break; + case 2: //blank display + #if DECIMALPOINT_PIN>=0 + digitalWrite(DECIMALPOINT_PIN,LOW); + #endif + #if COLON_PIN>=0 + digitalWrite(COLON_PIN,LOW); // colon pin OFF + #endif + state = 0; + timer = PWMrefresh-PWMtimeBrightness; //remainder of the fixed-length PWM period -> keeps overall refresh rate constant + break; + } //end switch + } //end else + + if ((brightness == 0) || (!radarON)) {timer = PWM_max; state = 0;} //no time sharing is needed + if (timer<500) timer = 500; //safety only... (timer1 min practical reload value; too-short reload could starve other ISRs/WiFi) + timer1_write(timer); +} + +//Cooperative/non-blocking-ish delay: since the whole time-showing sequence +//in writeDisplay2()/makeAnim() is written as a straight-line blocking loop +//(one call per animation step), we can't just call delay(d) - that would +//starve WiFi, the captive-portal DNS, the alarm buzzer and GPS parsing for +//the whole animation. So Adelay() busy-waits but keeps servicing them. +void Adelay(unsigned long d) { + unsigned long dStart = millis(); + + doAnimationMakuna(); + while ((millis() - dStart) < d) { + if (WiFi.getMode() == 2) dnsServer.processNextRequest(); //WiFi.getMode()==2 is WIFI_AP: service the captive-portal DNS while in AP config mode + alarmSound(); + #ifdef USE_GPS + smartDelay(1); //feed GPS + #endif + yield(); //let the ESP8266 core service WiFi/SDK background tasks (required to avoid WDT resets) + } +} + +//Drives the crossfade of the SINGLE current position `pos` from its old +//character to its new one. This is only ever meaningful for one tube at a +//time (unlike multi-tube drivers' changeDigit(), which is bypassed +//entirely for TUBE1CLOCK - see core .ino changeDigit()). Called once per +//position from writeDisplay2()'s position-walk loop. +void makeAnim() { + static byte oldDig = 10; //remembers the digit that was actually shown last time THIS function ran (persists across positions/cycles) + + if (cathodeProtRunning) { + //Anti-cathode-poisoning routine: just snap to the target digit, no fade, + //no PWM animation bookkeeping - doCathodeProtect() drives digit[] itself. + memset(animMask,0,sizeof(animMask)); + digit[pos] = newDigit[pos]; + return; + + } + else if ((prm.animMode==0) || ipShowRunning ){ + //Animation disabled by user setting, or showing the boot-time IP address: snap instantly. + digit[pos] = newDigit[pos]; + memset(animMask,0,sizeof(animMask)); + Adelay(ANIMSPEED*20); + return; + } + + //Time showing + if (oldDig == newDigit[pos]) oldDig = 10; //if the old digit == new digit, use space instead (avoids a pointless fade from a digit onto an identical digit) + oldDigit[pos] = oldDig; //last displayed digit + //DPRINT(oldDigit[pos]); DPRINT("/"); DPRINTLN(newDigit[pos]); + for (int i = 1; i < 20; i++) { + animMask[pos] = i; //drives the writeDisplay() ISR's PWM wipe ratio (old-share shrinks, new-share grows) for this position, step by step + Adelay(ANIMSPEED); + } + oldDig = newDigit[pos]; //save last displayed digit + digit[pos] = newDigit[pos]; + animMask[pos] = 0; + Adelay(5*ANIMSPEED); + if (pos==2) { + //pos==2 is the "hours ones" digit - digit[3]=hour tens, digit[2]=hour ones, + //digit[1]=minute tens, digit[0]=minute ones (set in core .ino's time-digit + //code). The walk in writeDisplay2() goes 3,2,1,0, so after showing the HH + //part (positions 3 and 2) we insert a blank gap here so the HH/MM halves + //of the time are visually separated on the single tube instead of running together. + digit[pos] = 10; + oldDigit[pos] = 10; + oldDig = 10; + Adelay(20*ANIMSPEED); + } +} + +//Required common-interface function ("blocking, non-interrupt, one-shot +//write of the display buffer") - but for this single-tube driver the +//actual pixel/segment output is always produced asynchronously by the +//writeDisplay() timer ISR, driven by `pos`. So this implementation does +//NOT push bits to hardware itself; it only redirects `pos` to the single +//physical tube's slot (index 3, reused as the cathode-protect "digit") +//so that the next ISR tick renders whatever doCathodeProtect() just wrote +//into digit[3]/newDigit[3]. Outside cathode-protect it is a no-op, so +//normal time updates still rely entirely on writeDisplay2() below +//(called from the main loop - see changeDigit() in the core .ino, which +//for TUBE1CLOCK short-circuits to just calling this function and returning). +void writeDisplaySingle() { + if (cathodeProtRunning) { + pos = 3; + } +} + +//NOT part of the required driver interface (setup_pins/writeDisplay/ +//writeDisplaySingle/clearTubes/maxDigits/tubeDriver) - this is an EXTRA +//function that the core .ino calls directly (only for TUBE1CLOCK/VQC10/ +//WORDCLOCK, see the `writeDisplay2` stub at the bottom of the core file) +//from the main loop to drive the single tube's sequential "flying digit" +//display, since a single tube can't be updated purely by changeDigit()'s +//normal per-position-array animation. +void writeDisplay2(void){ + static unsigned long lastShow = 0; + + if (cathodeProtRunning) { //show cathode protect, only one digit + pos = 3; + return; + } + + if (ipShowRunning) { //boot-time diagnostic: show the device's IP address, 3 digits at a time, on the single tube + memset(oldDigit,10,sizeof(oldDigit)); + newDigit[3] = 10; + digit[3] = 10; + for (int i=2;i>=0;i--) { + pos = i; + makeAnim(); + digit[pos] = 10; + Adelay(200); + } + pos = 3; + makeAnim(); + pos = 0; + ipShowRunning = false; + memset(digit,10,sizeof(digit)); + memset(newDigit,10,sizeof(newDigit)); + memset(oldDigit,10,sizeof(oldDigit)); + memset(animMask,0,sizeof(animMask)); + Adelay(500); + return; + } + + if ((millis()-lastShow)<3000) return; //show time - only start a new HH/MM sweep once every 3s; between sweeps the last-shown digit just stays lit via the ISR + for (int i=3;i>=0;i--) { + pos = i; + makeAnim(); + if (prm.animMode==0) { //make some pause + digit[pos] = 10; + Adelay((i==2)? 400 : 200); //extra-long pause after the HH half (i==2) to emphasize the HH/MM boundary + } + } + newDigit[4] = 10; + pos = 4; //parks the state machine on the unused 5th slot (always 0xA/blank, see digit[4]=0xA in writeDisplay()) so the tube goes dark between sweeps + makeAnim(); + memcpy(digit,newDigit,sizeof(digit)); + memcpy(oldDigit,newDigit,sizeof(oldDigit)); + memset(animMask,0,sizeof(animMask)); + lastShow = millis(); +} + +//Required common-interface function, but intentionally a no-op here: the core .ino no longer has +//any caller for clearTubes() at all (its only former call site, disableDisplay(), has been removed +//entirely as dead code), so there is nothing to implement. Actual blanking of the single tube is +//handled inside writeDisplay()'s own brightness==0 / !radarON branch instead. +void clearTubes() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/DHT_temp.ino b/firmware/uniclock/ESP32_UniClock2/DHT_temp.ino new file mode 100644 index 0000000..f471abd --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/DHT_temp.ino @@ -0,0 +1,132 @@ +// ================================================================================================= +// DHT_temp.ino - DHT11 / DHT22 (AM2302/AM2321) / DHT21 combined temperature+humidity sensor driver +// ------------------------------------------------------------------------------------------------- +// Single-wire (not 1-Wire protocol, just one signal wire) sensor read via the Adafruit DHT sensor +// library (libraries/DHT_sensor_library.zip), on pin TEMP_DHT_PIN with sensor type DHTTYPE. +// Enabled per-clock via USE_DHT_TEMP in clocks.h (pin: TEMP_DHT_PIN, type: DHTTYPE, default DHT22). +// Used (as of this writing) by CLOCK_18, CLOCK_21, CLOCK_22, CLOCK_23A, CLOCK_23B, CLOCK_32, CLOCK_50 +// and CLOCK_99 - see clocks.h for the exact list. Unlike dallas_temp.ino, this driver only ever +// supports a single physical sensor (one temperature + one humidity reading). +// +// DHT sensors are slow and must not be polled too often (Adafruit recommends >=2s between reads, +// otherwise you get stale or garbage data back) - getDHTemp() below enforces its own minimum gap +// between reads independent of the DHT library (which does not enforce this itself). +// ================================================================================================= +#ifdef USE_DHT_TEMP + +// REQUIRES the following Arduino libraries: +// - DHT Sensor Library: https://github.com/adafruit/DHT-sensor-library +// - Adafruit Unified Sensor Lib: https://github.com/adafruit/Adafruit_Sensor + +#include "DHT.h" +// TEMP_DHT_PIN is used to connect the sensor +// Feather HUZZAH ESP8266 note: use pins 3, 4, 5, 12, 13 or 14 -- +// Pin 15 can work but DHT must be disconnected during program upload. + +// Uncomment whatever type you're using! +//#define DHTTYPE DHT11 // DHT 11 +#ifndef DHTTYPE + #define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321 //default DHT type +#endif +//#define DHTTYPE DHT21 // DHT 21 (AM2301) + +// Connect pin 1 (on the left) of the sensor to +5V +// NOTE: If using a board with 3.3V logic like ESP8266 or ESP32 connect pin 1 +// to 3.3V instead of 5V! +// Connect pin 2 of the sensor to whatever your DHTPIN is +// Connect pin 4 (on the right) of the sensor to GROUND +// Connect a 10K resistor from pin 2 (data) to pin 1 (power) of the sensor + + +DHT dht(TEMP_DHT_PIN, DHTTYPE); +int DHTtempPtr = -1; // index into the shared temperature[] array reserved for this sensor; -1 = "no sensor found / not set up" +int DHThumidPtr = -1; // index into the shared humid[] array reserved for this sensor; -1 = "no sensor found / not set up" + +// Probes for a DHT sensor at boot. Up to 5 attempts, 1 second apart (DHT sensors need time between +// reads to settle - see getDHTemp() below for the steady-state rate limit), stopping as soon as one +// attempt returns a valid (non-NaN) temperature. Only DHTtempPtr/DHThumidPtr are left at -1 (their +// initialized value) if no sensor ever responds, which is how getDHTemp() knows to do nothing later. +void setupDHTemp() { + float tempTMP, humidTMP; + regPin(TEMP_DHT_PIN,"TEMP_DHT_PIN"); + dht.begin(); + int i= 0; + while (i<5) { + humidTMP = dht.readHumidity(); + tempTMP = dht.readTemperature(); + DPRINT("DHT Test:"); DPRINT(tempTMP); DPRINT("/"); DPRINTLN(humidTMP); + i++; + if (!isnan(tempTMP)) // note: only the temperature reading is checked here, humidity's NaN state is not verified + break; + delay(1000); + } + if (isnan(tempTMP)) { + DPRINT("No DHTxx sensor found on GPIO"); DPRINTLN(TEMP_DHT_PIN); + return; // DHTtempPtr/DHThumidPtr stay -1 and useTemp/useHumid are not incremented - sensor is effectively disabled + } + DHTtempPtr = useTemp; //remember my ID-s + DHThumidPtr = useHumid; + useTemp++; //increase the number of sensors + useHumid++; +} + +// Called every ~200ms from timeProgram(), AND once directly from setup() right after pin init (see +// ESP32_UniClock2.ino) to get an initial reading before the first display refresh. +// Unlike the DS18B20 driver, the DHT library's readTemperature()/readHumidity() calls themselves +// block for ~250ms (bit-banged single-wire protocol, no async/non-blocking mode available) - there is +// no separate request/poll split here, it's a straightforward "gate how often we call it" scheme instead. +void getDHTemp() { +static unsigned long lastRun = 0; +float tempTMP, humidTMP; + + if (EEPROMsaving) return; + if (DHTtempPtr<0) return; //no sensor + // Rate limiting: DHT11/DHT22 sensors must not be read faster than roughly once every 2 seconds, or + // they return stale/garbage data - readings are gated to once per minute, at the exact second + // prm.tempStart (the same "start of temperature display window" second used to schedule the Dallas + // driver's request), which is always >=2.5s apart in practice for any sane tempStart. The 2500ms + // millis()-based check below is really just a safety net (e.g. against `second()` briefly matching + // more than once, or the caller re-entering the same second) - the schedule itself already respects + // the sensor's minimum read interval. + //if (((millis()-lastRun)<2500) || (second()!=prm.tempStart)) return; + // `&& (lastRun !=0)` makes the very first call (from setup(), before lastRun has ever been set) + // bypass both the 2.5s and the second()==tempStart gating, so a reading is taken immediately at boot + // instead of waiting for the clock to reach prm.tempStart. + if ((((millis()-lastRun)<2500) || (second()!=prm.tempStart)) && (lastRun !=0)) return; + lastRun = millis(); + + // Reading temperature or humidity takes about 250 milliseconds! + // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor) + humidTMP = round1(dht.readHumidity()); + // Range check doubles as the NaN/error guard: a NaN humidity fails both comparisons (NaN vs any value + // is always false in C/C++), so a failed read simply leaves humid[DHThumidPtr] holding its previous value. + if ((humidTMP>0) && (humidTMP<=100)) humid[DHThumidPtr] = humidTMP; + tempTMP = round1(dht.readTemperature()); // Read temperature as Celsius (the default) + // Same implicit-NaN-guard trick as above: NaN<99 is false, so a failed read is silently skipped and + // temperature[DHTtempPtr] keeps its last good value rather than being overwritten with garbage. + if (tempTMP<99) temperature[DHTtempPtr] = tempTMP; + + // Read temperature as Fahrenheit (isFahrenheit = true) + //float f = dht.readTemperature(true); Compute heat index in Fahrenheit (the default) + //float hif = dht.computeHeatIndex(f, h); + + //float hic = dht.computeHeatIndex(t, h, false); // Compute heat index in Celsius (isFahreheit = false) + + // NOTE: this isnan() check runs AFTER the assignments above have already decided (via the range + // checks) whether to update temperature[]/humid[] - by this point the display has already been + // re-enabled too. So this block is purely diagnostic logging on the raw (pre-round1) values, it does + // not itself prevent a bad reading from being used (that already happened, or didn't, above). + if (isnan(humidTMP) || isnan(tempTMP)) { // Check if any reads failed and exit early (to try again). + DPRINTLN("DHT sensor reading error!"); + //temperature[DHTtempPtr] = 99.9f; + //humid[DHThumidPtr] = 0; + } + else { + DPRINT("DHTxx Temperature:"); DPRINT(temperature[DHTtempPtr]); DPRINT("C Humidity:"); DPRINT(humid[DHThumidPtr]); DPRINTLN(" %"); + } +} + +#else +void setupDHTemp() {} +void getDHTemp() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/ESP32_UniClock2.ino b/firmware/uniclock/ESP32_UniClock2/ESP32_UniClock2.ino new file mode 100644 index 0000000..e0a33a8 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/ESP32_UniClock2.ino @@ -0,0 +1,3259 @@ +/* + Universal Clock v3.xx (Nixie, VFD, LED, Numitron) for ESP8266 or ESP32 + with optional Dallas Thermometer and DS3231 RTC, Neopixel strip, GPS and more... + 03/04/2022 + Copyright (C) 2020 Peter Gautier + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + Always check the usable pins of 8266: https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/ + and usable pins for ESP32: https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ + + //https://lastminuteengineers.com/esp32-arduino-ide-tutorial/ + + Web page files, which are found in the "dat" subfolder, must be uploaded to SPIFFS ! + ESP8266: https://randomnerdtutorials.com/install-esp8266-filesystem-uploader-arduino-ide/ + ESP32: https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/ +*/ +//==================================================================================================== +// ESP32_UniClock2.ino - CORE file of the project. This is the ONE file shared by all ~65 physical +// clock builds (see clocks.h) - it contains everything that is NOT specific to how a digit gets +// written to the hardware: setup()/loop(), WiFi connection management + web admin server (REST-ish +// /configChanged API + JSON status endpoints), EEPROM-persisted settings (`prm` struct), time-source +// selection (NTP/GPS/RTC priority chain in calcTime()), the display/animation pipeline +// (displayTimeN()->changeDigit()->writeDisplaySingle()), alarm buzzer, day/night brightness, +// radar-based auto shutoff, light-sensor auto-dim, and cathode/element "protect" (anti burn-in) test +// patterns. The Arduino build concatenates every .ino file in this sketch folder into one compilation +// unit, and exactly one of the ~25 driver .ino files (e.g. MAX6921.ino, ZM1500.ino, wordclock.ino) +// actually compiles in per build, selected by whichever driver macro the active CLOCK_NN block in +// clocks.h defines - this core file calls that driver only through a small set of `extern` functions +// (setup_pins/writeDisplay/writeDisplaySingle/clearTubes) and shared global buffers (digit[]/newDigit[] +// /oldDigit[]/digitDP[]/animMask[]), so it never needs to know which physical driver is active. +// See docs/DEVELOPER_GUIDE.md for the full architecture writeup (this file is section 4) and +// docs/CODE_REVIEW.md for the list of known bugs/dead-code spots (also marked in-place below with +// "// POSSIBLE BUG:" / "// NOTE:"). +//==================================================================================================== +#include +//#define byte uint8_t +#include "clocks.h" //DEFINE YOUR CLOCKS SETUP IN THIS FILE!!! - clocks.h picks the single active CLOCK_NN block, which #defines the driver macro, pins and feature flags read throughout this file + +// Detect which generation of the ESP8266 Arduino core is in use: older cores (2.6.x/2.7.x) name the +// RAM-resident-ISR attribute ICACHE_RAM_ATTR, while newer cores renamed it to IRAM_ATTR (shared with +// ESP32) - the #define below normalizes ICACHE_RAM_ATTR to mean IRAM_ATTR on newer cores so driver +// .ino files can use either name interchangeably regardless of installed core version. +#if defined(ESP8266) + #if defined(ARDUINO_ESP8266_RELEASE_2_6_0) || \ + defined(ARDUINO_ESP8266_RELEASE_2_6_1) || \ + defined(ARDUINO_ESP8266_RELEASE_2_6_2) || \ + defined(ARDUINO_ESP8266_RELEASE_2_6_3) || \ + defined(ARDUINO_ESP8266_RELEASE_2_7_0) || \ + defined(ARDUINO_ESP8266_RELEASE_2_7_1) || \ + defined(ARDUINO_ESP8266_RELEASE_2_7_2) || \ + defined(ARDUINO_ESP8266_RELEASE_2_7_3) || \ + defined(ARDUINO_ESP8266_RELEASE_2_7_4) + #define ESP8266_CORE_2xx + #pragma message "8266_Core 2.xx" + #else + #pragma message "8266_Core_3.xx" + #define ICACHE_RAM_ATTR IRAM_ATTR + #endif +#endif +/*_______________________________ USABLE PARAMETERS ____________________________________________ + //#define DEBUG //Enable Serial Monitor, 115200baud (only, if TX pin is not used anywhere!!!) + //---------------------------- CLOCK EXTRA OPTION PARAMETERS --------------------------------- + //#define USE_DALLAS_TEMP //TEMP_DALLAS_PIN is used to connect DS18B20 temperature sensors + //#define USE_DHT_TEMP //TEMP_DHT_PIN is the DHT sensor's data pin + //#define USE_BME280 //I2C Temperature + humidity + pressure, SDA+SCL I2C pins are used! + //#define USE_BMP280 //I2C Temperature + barometric pressure, SDA+SCL I2C pins are used! + //#define USE_AHTX0 //I2C Temperature + humidity, SDA+SCL I2C pins are used! + //#define USE_SHT21 //I2C Temperature + humidity, SDA+SCL I2C pins are used! + //#define USE_BH1750 //I2C Luxmeter sensor, SDA+SCL I2C pins are used! + //#define USE_RTC //DS3231 realtime clock, SDA+SCL I2C pins are used! + //#define USE_GPS //use for standalone clock, without wifi internet access + //#define USE_NEOPIXEL //WS2812B led stripe, for tubes backlight. Don't forget to define tubePixels[] ! + //#define USE_PWMLEDS //WWM led driver on 3 pins for RG + //#define USE_MQTT //Home Assistant integration: https://www.home-assistant.io/ + //#define USE_WIFIMANAGER //if no WiFi defined, use Wifimanager + //----- DRIVER SELECTION ------ Use only 1 driver from the following options in the clocks.h file! + //#define MULTIPLEX74141_ESP32 //4..8 Nixie tubes generic driver for ESP32 + //#define MAX6921_ESP32 //4..8 VFD tubes (IV18) driver for ESP8232 + //#define HV5122 //4..8 Nixie driver - development in progress + //-------------- 8266 clock drivers -------------------------------------------------- + //#define MULTIPLEX74141 //4..8 Nixie tubes generic driver for ESP8266 + //#define MAX6921 //4..8 VFD tubes (IV18) driver for ESP8266 + //#define NO_MULTIPLEX74141 //4..6 Nixie tubes, serial latch driver, 74141 for each tube + //#define MM5450 //6..8 LEDS + //#define MAX7219CNG //4..8 LED + //#define Numitron_4511N //Numitron 4x tube clock + //#define SN75512 //4..8 VFD tubes + //#define samsung //samsung serial display + //#define PCF_74141 //PCF pin expander for tube selection + //#define PT6355 //VFD clock - development in progress + + //--------------------- PINOUT & PIN PARAMETERS --------------------------------------- + //#define PIN_SDA xx // you can set the used SDA and SCL pins + //#define PIN_SCL xx // if it is not default value + //#define COLON_PIN -1 //Blinking Colon pin. If not used, SET TO -1 + //#define TEMP_DALLAS_PIN -1 //Dallas DS18B20 temp sensor pin. If not used, SET TO -1 + //#define TEMP_DHT_PIN -1 //DHT temp sensor pin. If not used, SET TO -1 + //#define DHTTYPE DHT22 //DHT sensor type, if used... + //#define LED_SWITCH_PIN -1 //external led backlight ON/OFF. If not used, SET TO -1 + //#define DECIMALPOINT_PIN -1 //Nixie decimal point between digits. If not used, SET TO -1 + //#define ALARMSPEAKER_PIN -1 //Alarm buzzer pin + //#define ALARMBUTTON_PIN -1 //Alarm switch off button pin + //#define ALARM_ON HIGH //HIGH or LOW level is needed to switch ON the buzzer? + //#define NEOPIXEL_PIN 3 //8266 Neopixel LEDstripe pin is always the RX pin!!! + //#define RGB_MIN_BRIGHTNESS 8 //Neopixel leds minimum brightness + //#define RGB_MAX_BRIGHTNESS 255 //Neopixel leds maximum brightness + //#define RADAR_PIN -1 //Radar sensor pin + //#define RADAR_TIMEOUT 5 //Automatic switch off tubes (without radar detecting somebody) after xxx min + //#define TUBE_POWER_PIN -1 //Filament or HV switch ON/OFF pin + //#define TUBE_POWER_ON HIGH //HIGH or LOW level is needed to switch ON the TUBE POWER? + //#define LIGHT_SENSOR_PIN -1 //Environment light sensor, only ADC pins are usable! ESP32 for example: 34,35,36,39... 8266: only A0 + //#define REF_RESISTANCE 10000.0 // Resistor value is 10k, between LDR sensor and GND + //#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness + //#define LUX_CALC_SCALAR 12518931 * 1.2 //Calibrate here the LDR sensor + //#define LUX_CALC_EXPONENT -1.405 //LDR sensor characteristic + //#define PWM1_PIN -1 //red channel if USE_PWMLEDS is defined + //#define PWM2_PIN -1 //green channel if USE_PWMLEDS is defined + //#define PWM3_PIN -1 //blue channel if USE_PWMLEDS is defined + //#define MAXBRIGHTNESS 10 //Do not change this value! + + //Display temperature and date in every minute between START..END seconds + //#define ENABLE_CLOCK_DISPLAY true //false, if no clock display is needed (for example: thermometer + humidity only) + //#define SHIFT_TUBES_LEFT_BY_1 //shift the display left by 1 tube, if a thermometer is used with a special tube + //#define LEFTDECIMAL false //set true (Z574M), if decimal point is on the left side on the tube. Else set false (Z573M)! + //#define DATE_REPEAT_MIN 1 //show date only every xxx minute. If zero, the date is never displayed! + //#define DOUBLE_BLINK //both separator points are blinking (6 or 8 tubes VFD clock) + //#define TEMP_START 35 //Temperature display start..end + //#define TEMP_END 40 + //#define HUMID_START 40 //Humidity% display start..end + //#define HUMID_END 45 + //#define DATE_START 45 //Date is displayed start..end + //#define DATE_END 50 + //#define ANIMSPEED 50 //Animation speed in millisec + //#define TEMP_CHARCODE 15 //Thermometer "C", set to -1 to disable + //#define GRAD_CHARCODE 16 //Thermometer grad, set to -1 to disable + //#define PERCENT_CHARCODE 17 //Humidity % + //#define AP_NAME "UNICLOCK" //Access Point name + //#define AP_PASSWORD "" //AP password + //#define WEBNAME "LED UniClock" //Clock's name on the web page + //#define DEFAULT_SSID "" //factoryReset default value for WiFi + //#define DEFAULT_PSW "" //factoryReset default value for WiFi password + //#define FACTORYRESET_PIN -1 //if defined, do a factory reset if the button is pushed +*/ +//#define USE_MDNS +#define TIMESERVER_REFRESH 7200000 //2h, Refresh time in millisec 86400000 = 24h +unsigned long timeserverErrors = 0; //timeserver refresh errors + +int timerON=0; //for debugging +int timerOFF=0; +unsigned long intCounter = 0; //for testing only, interrupt counter + +byte c_MinBrightness = RGB_MIN_BRIGHTNESS; //minimum LED brightness +byte c_MaxBrightness = RGB_MAX_BRIGHTNESS; //maximum LED brightness +//-------------------------------------------------------------------------------------------------- +#define FIRMWARE_SERVER "http://c.landventure.hu/store" //default OTA update server (see doFirmwareUpdate()); overridable per-clock via prm.firmwareServer / the web admin "firmware" field +String usedPinsStr; //human-readable list of registered GPIOs, built by listPins(), shown on the web admin "clock details" page +String driverSetupStr; //driver-specific extra setup info string (set by some drivers), also shown on the web admin page + +// Platform-specific setup: WiFi/HTTP/filesystem headers, board defaults (web page title / AP name), +// DRAM_ATTR (only meaningful on ESP32, defined empty here so ESP8266 code using it still compiles), +// and the ASCII-art `ESPpinout[]` string used by regPin() below to sanity-check GPIO usage per pin +// (each character is one GPIO: 'O'=usable output/IO pin, 'I'=input-only pin, ' '=reserved/unusable). +#if defined(ESP8266) + #define DRAM_ATTR //https://docs.espressif.com/projects/esp-idf/en/v4.3-beta2/esp32/api-reference/storage/spi_flash_concurrency.html + #ifndef WEBNAME + #define WEBNAME "ESP32_UniClock 3.1" + #endif + #ifndef AP_NAME + #define AP_NAME "UNICLOCK" + #endif + #ifndef AP_PASSWORD + #define AP_PASSWORD "" + #endif + #include + #include + #include + #include "ESPAsyncTCP.h" + #include "FS.h" + #include + #ifdef USE_MDNS + #include + #endif + + + const char ESPpinout[] = {"OOOOOO OOOOOI"}; //GPIO 0..5, 12..16, A0) usable pins + +#elif defined(ESP32) + #ifndef WEBNAME + #define WEBNAME "ESP32UniClock 3.0" + #endif + #ifndef AP_NAME + #define AP_NAME "UNICLOCK32" + #endif + #ifndef AP_PASSWORD + #define AP_PASSWORD "" + #endif + #include + #include + #include + #include + #include + + #include "AsyncTCP.h" + #include "SPIFFS.h" + #include "FS.h" + #include "soc/soc.h" + #include "soc/rtc_cntl_reg.h" + #ifdef USE_MDNS + #include + #endif + hw_timer_t * ESP32timer = NULL; //hardware timer handle, started by startTimer() below; drives the ISR-based multiplex drivers' writeDisplay() + portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; //spinlock used by drivers to guard shared state accessed from both the ISR and the main loop + #define PRESCALER 15 //multiplex timer prescaler, about 80Hz for 6 tubes + + const char ESPpinout[] = {"OOOOOO OOOOOOOO OOO OOO OOIII I"}; //GPIO 0..5, 12..19, 21..23, 25..27, 32..33, Input:34..36, 39) usable pins +#else + #error "Board is not supported!" +#endif + +#include +DNSServer dnsServer; +#include "ESPAsyncWebServer.h" +#include +#include + +#include +#include +#include +#include +#include "ArduinoJson.h" + +#if defined(USE_NEOPIXEL) || defined(WORDCLOCK) + #include +#endif + +// Driver-interface contract: every driver .ino file must implement these regardless of which chip it +// targets (see docs/DEVELOPER_GUIDE.md section 3) - this is the entire surface the core code needs to +// drive an arbitrary physical display. writeDisplay() is the ISR-based multiplex refresh (a no-op for +// "smart" chip drivers that scan internally, e.g. PT6311/PT6355/VQC10); writeDisplaySingle() is a +// blocking, non-ISR, one-shot render of the current digit[]/digitDP[] buffers. +extern void ICACHE_RAM_ATTR writeDisplay(); +extern void writeDisplaySingle(); +extern void setup_pins(); +extern void clearTubes(); +extern int maxDigits; //number of physical digit positions (tubes) on this clock build +extern char tubeDriver[]; //human-readable driver name string, shown on the web admin page + +char webName[] = WEBNAME; +AsyncWebServer server(80); +#define CACHE_MAX_AGE "max-age=31536000" //maximum is: 31536000 + +#ifdef USE_WIFIMANAGER + #include + AsyncWiFiManager myWiFiManager(&server,&dnsServer); +#endif + +//______________ WiFi finding variables ____________________________________________ +byte bestChn; //the recent found best wifi AP's channel No. +uint8_t bestBssid[6]; //the recent found best wifi AP's BSSID (mac address) +uint8_t oldBssid[6]; //the current wifi AP's BSSID (mac address) +int bestRSSI = -200; //the recent found best wifi AP's RSSI value +unsigned long lastWifiScan = 0; //last wifi scan timestamp in CPU msec millis() +boolean wifiOK = false; +char myIp[20]=""; + +//Display buffers - shared with every driver .ino file, indexed 0..maxDigits-1 (0 = rightmost/seconds digit). +//Value encoding (see docs/DEVELOPER_GUIDE.md section 3): 0-9 = digit to show, 10 = BLANK, 11 = dash/minus, +//12 = standalone decimal-point glyph (a few drivers only), 13+ = driver-specific extra characters +//(letters, degree sign, %, circles, etc. - each driver's own charDefinition[]/charTable[] maps these). +#define BUFSIZE 12 +byte DRAM_ATTR digit[BUFSIZE]; //what is CURRENTLY on the physical display (what writeDisplay()/writeDisplaySingle() actually render) +byte DRAM_ATTR newDigit[BUFSIZE]; //the target value for the next display update, filled in by displayTime4/6/8()/displayDate()/displayTemp()/displayHumid() +byte DRAM_ATTR oldDigit[BUFSIZE]; //the value that was displayed before the current changeDigit() animation started - used to detect per-tube changes and drive the various animMode animations +boolean DRAM_ATTR digitDP[BUFSIZE]; //actual value to put to display +boolean digitsOnly = true; //only 0..9 numbers are possible to display? +byte DRAM_ATTR animMask[BUFSIZE]; //0 = no animation mask is used; used by the "wipe" animation (animMode==5, see changeDigit()) - a driver-specific progress value per tube, drivers read this to fade/wipe segments in or out + +volatile boolean EEPROMsaving = false; //saving in progress - stop display refresh + +// EEPROM/prm struct format version. Bump this whenever the `prm` struct below changes shape (fields +// added/removed/reordered/resized) - loadEEPROM() compares the stored value against this constant in +// setup(), and a mismatch triggers an automatic factoryReset() (old EEPROM bytes would otherwise be +// misinterpreted as the new struct layout, since EEPROM.get()/put() do a raw memcpy with no versioning +// of their own). +#define MAGIC_VALUE 304 //EEPROM version + + +//#define THERMOMETER_CLOCK //it means, first digit is +- sign, last digit is C/F, 4 digit is for numbers +#ifdef THERMOMETER_CLOCK //it means 6 tubes: first digit is +- sign, last digit is C/F, and only 4 digit are for numbers + boolean thermometerClock = true; +#else + boolean thermometerClock = false; +#endif + +// 8266 internal pin registers +// https://github.com/esp8266/esp8266-wiki/wiki/gpio-registers +// example: https://github.com/mgo-tec/OLED_1351/blob/master/src/OLED_SSD1351.cpp + + +// Debug print macros: compile to real Serial.print calls when DEBUG is defined in clocks.h for the +// active CLOCK_NN, otherwise compile to nothing (zero code/RAM cost) - used everywhere in this file +// and in every driver .ino instead of calling Serial.* directly, so a release build can drop all +// debug logging just by commenting out one #define. +#ifdef DEBUG //Macros are usually in all capital letters. +#define DPRINT(...) Serial.print(__VA_ARGS__) //DPRINT is a macro, debug print +#define DPRINTLN(...) Serial.println(__VA_ARGS__) //DPRINT is a macro, debug print +#define DPRINTF(...) Serial.printf(__VA_ARGS__) //DPRINT is a macro, debug print +#define DPRINTBEGIN(...) Serial.begin(__VA_ARGS__) //DPRINTLN is a macro, debug print with new line +#else +#define DPRINT(...) //now defines a blank line +#define DPRINTLN(...) //now defines a blank line +#define DPRINTF(...) //now defines a blank line +#define DPRINTBEGIN(...) //now defines a blank line +#endif + +#define PIN_OUT_SET PERIPHS_GPIO_BASEADDR + 4 +#define PIN_OUT_CLEAR PERIPHS_GPIO_BASEADDR + 8 + +bool colonBlinkState = false; +boolean radarON = true; +boolean mqttRadarON = true; +unsigned long radarLastOn = 0; +boolean makeFirmwareUpdate = false; +boolean makeCathodeProtect = false; +int cathProtMin = 5; +unsigned long lastTimeUpdate = 0; //last time refresh from GPS or internet timeserver +boolean RTCexist = false; +boolean GPSexist = false; +boolean BME280exist = false; +boolean BMP280exist = false; +boolean AHTX0exist = false; +boolean SHT21exist = false; +boolean BH1750exist = false; +boolean LDRexist = false; + +byte useDallasTemp = 0; //number of Dallas temperature sensors: 0,1,2 +byte useTemp = 0; //Total number of any temperature sensors: 0..6 +byte usePress = 0; //Total number of pressure sensors +byte useHumid = 0; //Total number of humidity sensors +byte useLux = 0; +float temperature[6] = {0,0,0,0,0,0}; +float humid[6] = {0,0,0,0,0,0}; +float pressur[6] = {0,0,0,0,0,0}; +int lx = 0; //Environment LUX value, set by Light Sensor +boolean autoBrightness = false; //Enable automatic brightness levels +//----------------- EEPROM addresses ------------------------------------------------------------------- +const int EEPROM_addr = 0; + +// `prm` - the ONE struct holding every user-configurable, persistent setting (alarm, RGB effect, WiFi +// credentials, MQTT broker, brightness/day-night schedule, date/temp/humidity display windows, +// calibration offsets, etc). Saved/loaded as a single raw block via EEPROM.put()/EEPROM.get() +// (saveEEPROM()/loadEEPROM() below) - NOT field-by-field, so struct layout changes require bumping +// MAGIC_VALUE (see above) or old EEPROM contents get reinterpreted as garbage. The web admin's +// POST /configChanged (handleConfigChanged()) is the single entry point that mutates these fields at +// runtime; handleSendConfig() serializes (most of) this struct back out to the client as JSON. +// In-struct default values (e.g. "= 7") are only the compiled-in fallback used until the first +// factoryReset()/loadEEPROM() actually runs - factoryReset() re-assigns most fields explicitly. +struct { +//Main screen settings _____________________________________________________________________________________ + boolean alarmEnable = false; //Yes or No + byte alarmHour = 7; //Alarm time + byte alarmMin = 0; + byte alarmPeriod = 15; //Alarm length, sec +//RGB settings ____________________________________________________________________________________________ + byte rgbEffect = 1; //0=OFF, 1=FixColor + byte rgbBrightness = 100; // 0..255 + unsigned int rgbFixColor = 150; //0..255 + byte rgbSpeed = 50; //0..255msec / step + boolean rgbDir = false; //false = right, true = left +//WiFi / IP settings _______________________________________________________________________________________ + boolean wifiMode = true; + char wifiSsid[20]; + char wifiPsw[20]; + char ApSsid[20]; + char ApPsw[20]; + char NtpServer[30]; + char mqttBrokerAddr[30]; + char mqttBrokerUser[20] = "mqtt"; + char mqttBrokerPsw[20] = "mqtt"; + int mqttBrokerRefresh = 10; //sec + boolean mqttEnable = false; + char firmwareServer[78]; +//Tube settings ______________________________________________________________________________________ + int tempRepeatMin = 1; //temperature and humidity display repeat (min) + int utc_offset = 1; //time zone offset + bool enableDST = true; // Flag to enable DST (summer time...) + bool set12_24 = true; // Flag indicating 12 vs 24 hour time (false = 12, true = 24); + bool showZero = true; // Flag to indicate whether to show zero in the hour ten's place + bool enableBlink = true; // Flag to indicate whether center colon should blink + int interval = 15; // prm.interval in minutes, with 0 = off + bool enableAutoShutoff = true; // Flag to enable/disable nighttime shut off + byte dayHour = 8; // start of daytime + byte dayMin = 0; + byte nightHour = 22; // start of night time + byte nightMin = 0; + byte dayBright = MAXBRIGHTNESS; // display daytime brightness + byte nightBright = 5; // display night brightness + byte animMode = 2; //0=no anim, if 1 or 2 is used, animation, when a digit changes + byte dateMode = 2; // 0:dd/mm/yyyy 1:mm/dd/yyyy 2:yyyy/mm/dd + boolean tempCF = false; //Temperature Celsius=false / Fahrenheit=true + boolean enableTimeDisplay; //ENABLE_CLOCK_DISPLAY + byte dateStart; //Date is displayed start..end + byte dateEnd; + byte tempStart; //Temperature display start..end + byte tempEnd; + byte humidStart; //Humidity% display start..end + byte humidEnd; + byte dateRepeatMin; //show date only every xxx minute. If zero, the date is never displayed! + boolean enableDoubleBlink; //both separator points are blinking (6 or 8 tubes VFD clock) + boolean enableAutoDim = false; //Automatic dimming by luxmeter + boolean enableRadar = false; //Radar sensor + int radarTimeout = 5; //min + float corrT0 = 0; + float corrT1 = 0; + float corrH0 = 0; + float corrH1 = 0; +//____________________________________________________________________________________________________ + int magic = MAGIC_VALUE; //magic value, to check EEPROM version when starting the clock - see loadEEPROM()/factoryReset() and the MAGIC_VALUE comment above +} prm; + +#define EEPROM_SIZE sizeof(prm) //EEPROM.begin() is sized exactly to the struct - no extra reserved space for future growth, so any struct change is effectively a breaking EEPROM-layout change (mitigated only by the magic-number factory-reset above) + +//------------------------------------------------------------------------------------------------------- + +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP, prm.NtpServer, 0, TIMESERVER_REFRESH); // Refresh time in millisec +IPAddress ip; + +// Set timezone rules. Offsets set to zero, since they will be loaded from EEPROM +// DST rule is HARD-CODED to the EU convention (last Sunday of March -> first Sunday of November) - +// prm.utc_offset/prm.enableDST only scale the offset (see updateTimefromTimeserver()), the actual +// switchover DATES are not configurable from the web UI. myTZ is rebuilt with the current offsets +// every time updateTimefromTimeserver() runs (WiFi/NTP path only - the GPS-only fallback in gps.ino +// uses a simpler, always-on +1h DST rule, see CODE_REVIEW.md). +TimeChangeRule myDST = {"DST", Last, Sun, Mar, 2, 0}; +TimeChangeRule mySTD = {"STD", First, Sun, Nov, 2, 0}; +Timezone myTZ(myDST, mySTD); +time_t prevTime = 0; //last second() value that was actually rendered to the display - timeProgram() only redraws when now()!=prevTime, i.e. at most once per real second +time_t protectTimer = 0; //timestamp of the last cathode-protect animation, used by timeProgram()'s prm.interval-minute scheduler +bool displayON = true; //current day/night brightness state (true=day brightness) - set by evalShutoffTime(), or forced by manualOverride via the web UI +bool manualOverride = false; //user has manually forced displayON via the web "manualOverride" key, overriding the automatic day/night schedule until evalShutoffTime() sees the schedule naturally cross into the opposite state again +bool initProtectionTimer = false; // Set true at the top of the hour to synchronize protection timer with clock +bool decimalpointON = false; +bool alarmON = false; //Alarm in progress +unsigned long alarmStarted = 0; //Start timestamp millis() + +// Which "screen" timeProgram() decided to render this second - set once per second, then read by +// displayTime4/6/8() to pick between clock/date/temperature/humidity output. Mutually exclusive +// (the display*() dispatch chain checks them in priority order: temp1 > temp0 > humid1 > humid0 > date > clock). +boolean showClock = false; +boolean showDate = false; +boolean showTemp0 = false; +boolean showTemp1 = false; +boolean showHumid0 = false; +boolean showHumid1 = false; +int lastCathodeProt = -1; //minute() value when the cathode-protect animation last ran, informational only (not read elsewhere in this file) +boolean cathodeProtRunning = false; //true while any cathode-protect animation is active - other code (e.g. wordclock.ino) can check this to skip normal rendering +boolean ipShowRunning = false; //true while showMyIp() is running at boot (IP address shown digit-by-digit on the tubes) +boolean wifiConnectRunning = false; //true while setup() is attempting the initial WiFi connection +boolean editorRunning = false; //true while the RTC manual date/time editor (rtc.ino) is active - read by wordclock.ino to show an "editing" indicator + +#define MAX_PIN sizeof(ESPpinout)-1 +#define PIN_TXT_LEN 30 +char pinTxt[MAX_PIN][PIN_TXT_LEN+1]; + +// Every driver/peripheral setup call is expected to call this once per GPIO it uses (label is a +// human-readable name, e.g. "PIN_SDA"). Purpose is twofold: (1) sanity-check the pin against the +// board's ESPpinout[] map (flags reserved/input-only/nonexistent pins) and (2) detect two features +// accidentally configured onto the same physical pin (a common clocks.h copy-paste mistake) - both +// classes of problem are only reported via DPRINT, not by refusing to continue, so a build with DEBUG +// off will silently run with a bad pin assignment. +void regPin(byte p,const char * txt) { //register used pins + + DPRINT("- "); DPRINT(txt); DPRINT(": GPIO"); DPRINT(p); + if (p>MAX_PIN-1) { + DPRINT(" ERROR: PIN# DOESN'T EXIST."); + } + else if (ESPpinout[p]==' ') { + DPRINT(" ERROR: RESERVED PIN."); + } + else if (ESPpinout[p]=='I') { + DPRINT(" Warning: input only pin"); + } + DPRINTLN(" "); + if (strlen(pinTxt[p])>0) { + DPRINT("*** ERROR *** "); DPRINT(txt); DPRINT(" on PIN#"); DPRINT(p); + DPRINT(" ALREADY DEFINED AS "); DPRINTLN(pinTxt[p]); + strncpy(pinTxt[p],"Error:MULTI DEF",PIN_TXT_LEN); + } + else + strncpy(pinTxt[p],txt,sizeof(pinTxt[p])); +} + +// Builds usedPinsStr (the HTML fragment shown on the web admin "clock details" page, see +// handleSendClockDetails()) from everything regPin() recorded, and also dumps the same list to the +// debug console. +void listPins() { + DPRINTLN("_______________________"); + DPRINTLN("___ USED CLOCK PINS ___"); + #if defined(ESP32) + usedPinsStr = String("ESP32 used pins:
"); + #else + usedPinsStr = String("ESP8266 used pins:
"); + #endif + for (byte i=0;i<=MAX_PIN;i++) { + if (strlen(pinTxt[i])>0) { + usedPinsStr += String(i) + ": " + String(pinTxt[i]) + "
"; + DPRINT(i); DPRINT(": "); DPRINTLN(pinTxt[i]); + } + } + DPRINTLN("_______________________"); + //DPRINT("MAX_PIN"); DPRINTLN(MAX_PIN); +} + +// Starts the hardware-timer interrupt that periodically calls the active driver's writeDisplay() (the +// ISR-based multiplex refresh). Called from most drivers' setup_pins() - drivers that scan their +// display internally (PT6311/PT6355/VQC10/MAX7219/etc, see docs/DEVELOPER_GUIDE.md section 3) never +// call this, since their writeDisplay() is an empty no-op and refresh instead happens cooperatively +// in writeDisplay2()/writeDisplaySingle(). +void startTimer() { //ESP_INTR_FLAG_IRAM +#if defined(ESP8266) + timer1_attachInterrupt(writeDisplay); + timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE); + timer1_write(15000); + DPRINTLN("Starting 8266 timer..."); + +#elif defined(ESP32) + // https://techtutorialsx.com/2017/10/07/esp32-arduino-timer-interrupts/ + ESP32timer = timerBegin(0, PRESCALER, true); //set prescaler to 80 -> 1MHz signal, true = edge generated signal + timerAttachInterrupt(ESP32timer, &writeDisplay, true); + timerAlarmWrite(ESP32timer, 1000, true); //100millisec, no repeat + timerAlarmEnable(ESP32timer); + //DPRINTLN("Starting ESP32 timer..."); +#else +#error "Unknown controller board!" +#endif +} + +void stopTimer() { +#if defined(ESP8266) + timer1_detachInterrupt(); +#endif +} + +#ifdef USE_WIFIMANAGER +void configModeCallback (AsyncWiFiManager *myWiFiManager) { + DPRINTLN("Switch to AP config mode..."); + DPRINTLN("To configure Wifi, "); + DPRINT("connect to Wifi network "); DPRINTLN(prm.wifiSsid); + DPRINTLN("and open 192.168.4.1 in web browser"); +} +#endif + +// Resets all three digit buffers to BLANK (10) and clears every decimal-point flag - a full display +// clear, used at startup and around mode transitions (WiFi connect, IP display, etc). +void clearDigits() { + memset(oldDigit, 10, sizeof(oldDigit)); + memset(digit, 10, sizeof(digit)); + memset(newDigit, 10, sizeof(newDigit)); + memset(digitDP, 0, sizeof(digitDP)); +} + +// A cooperative-multitasking substitute for delay(d): the firmware has no delay()-based blocking +// waits in loop() (see docs/DEVELOPER_GUIDE.md section 4.2), so any function that needs to "wait" +// (animations, WiFi connect retries, cathode-protect, IP display, etc.) calls Fdelay() instead - it +// busy-waits for `d` milliseconds but keeps servicing the DNS captive-portal server (AP mode only), +// RGB/PWM background animations, the alarm buzzer, the light sensor and (if USE_GPS) the GPS serial +// feed, so none of those subsystems stalls during a long animation/connect sequence. +void Fdelay(unsigned long d) { + unsigned long dStart = millis(); + + doAnimationMakuna(); + while ((millis() - dStart) < d) { + if (WiFi.getMode() == 2) dnsServer.processNextRequest(); + writeDisplay2(); + getLightSensor(); + doAnimationMakuna(); + doAnimationPWM(); + alarmSound(); + #ifdef USE_GPS + smartDelay(1); //feed GPS + #endif + yield(); + } +} + +float round1(float in) { + return round(10.0 * in)/10.0; +} + +//-------------------------------------------------------------------------------------------------------------------- + +// Scans for WiFi networks and, among all APs broadcasting the configured SSID (prm.wifiSsid), +// remembers the one with the best RSSI (bestChn/bestBssid/bestRSSI) - useful when multiple APs/mesh +// nodes share the same SSID, so the clock can explicitly target the strongest one via WiFi.begin(..., +// bestChn, bestBssid, true) in startNewWifiMode() below. +// NOTE: dead code in the current build - its only two call sites (in setup() and resetWiFi() further +// down) are both commented out (`//findBestWifi();`), so this function, its result variables +// (bestChn/bestBssid/bestRSSI/lastWifiScan) and its consumer startNewWifiMode()/checkWifiMode() are +// effectively unused; the live WiFi-connect path is startWifiMode() (plain WiFi.begin(ssid,psw), no +// BSSID targeting). +boolean findBestWifi() { + int n; + int newRssi; + boolean found = false; + + if (WiFi.status() == WL_CONNECTED) { //save wifi settings to prm + WiFi.disconnect(); + } + WiFi.scanNetworks(false); + n = WiFi.scanComplete(); + DPRINT(F("Scanning WiFi, found networks:")); DPRINTLN(n); + if (n>=0){ + bestRSSI = -200; + memset(bestBssid,0,sizeof(bestBssid)); + for (int i = 0; i < n; ++i){ + if (WiFi.SSID(i) == String(prm.wifiSsid)) { + newRssi = WiFi.RSSI(i); + DPRINT(F("scan: RSSI")); DPRINT(newRssi); + if (bestRSSI -95) && (WiFi.status() != WL_CONNECTED)) { + DPRINTLN(F("_____________ Connecting WiFi ___________________")); + if (strlen(prm.wifiSsid)==0) return; + WiFi.disconnect(); + delay(200); + WiFi.mode(WIFI_OFF); + delay(200); + WiFi.mode(WIFI_STA); + WiFi.setAutoReconnect(true); + #if defined(ESP32) + esp_wifi_set_ps(WIFI_PS_NONE); + WiFi.setHostname(webName); + WiFi.setSleep(false); + #else + WiFi.hostname(webName); + #endif + + char tmp[20]; + sprintf(tmp,"%02X:%02X:%02X:%02X:%02X:%02X",bestBssid[0],bestBssid[1],bestBssid[2],bestBssid[3],bestBssid[4],bestBssid[5]); + DPRINT(F("Connecting to best WiFi AP:")); DPRINT(prm.wifiSsid); DPRINT(F(" PSW:")); DPRINT(prm.wifiPsw); + DPRINT(F(" BSSID:")); DPRINT(tmp); DPRINT(" Chn:"); DPRINTLN(bestChn); + WiFi.begin(prm.wifiSsid, prm.wifiPsw, bestChn, bestBssid, true); + + Fdelay(500); + int counter = 0; + while (WiFi.status() != WL_CONNECTED) { + DPRINT('.'); + Fdelay(3000); + if (counter++>3) { + DPRINTLN("Connecting to WiFi failed."); + return; + } + } + ip = WiFi.localIP(); + sprintf(myIp,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]); + DPRINT("\nConnected to "); DPRINT(WiFi.SSID()); + DPRINT(". LocalIP:"); DPRINT(myIp); + DPRINT(" BSSID:"); DPRINT(WiFi.BSSIDstr()); + DPRINT(" RSSI:"); DPRINT(WiFi.RSSI()); + DPRINT(" CHN:"); DPRINTLN(WiFi.channel()); + DPRINTLN("\n\nNetwork Configuration:"); + DPRINTLN("----------------------"); + DPRINT(" SSID: "); DPRINTLN(WiFi.SSID()); + DPRINT(" Wifi Status: "); DPRINTLN(WiFi.status()); + DPRINT("Wifi Strength: "); DPRINT(WiFi.RSSI()); DPRINTLN(" dBm"); + DPRINT(" MAC: "); DPRINTLN(WiFi.macAddress()); + DPRINT(" IP: "); DPRINTLN(myIp); + DPRINT(" Subnet: "); DPRINTLN(WiFi.subnetMask()); + DPRINT(" Gateway: "); DPRINTLN(WiFi.gatewayIP()); + DPRINT(" DNS 1: "); DPRINTLN(WiFi.dnsIP(0)); + DPRINT(" DNS 2: "); DPRINTLN(WiFi.dnsIP(1)); + DPRINT(" DNS 3: "); DPRINTLN(WiFi.dnsIP(2)); + DPRINTLN("_____________________________________________________________________________"); + memcpy(oldBssid,WiFi.BSSID(),sizeof(oldBssid)); +} + DPRINTLN("\r\n"); +} + + +// The LIVE Infrastructure(STA)-mode connect path, called from setup() when prm.wifiMode==true. Plain +// WiFi.begin(ssid,psw) (no BSSID targeting, unlike startNewWifiMode() above), retries once a second +// for up to ~10s, then falls back to wifiManager() (config portal) if still not connected - and +// wifiManager() itself falls straight through here again if no SSID was stored at all. +void startWifiMode() { + DPRINTLN("Starting Clock in WiFi Mode!"); + if (strlen(prm.wifiSsid)==0) { + DPRINTLN("WiFi SSID not defined!"); + wifiManager(); + return; + } + WiFi.disconnect(true); + //WiFi.mode(WIFI_OFF); + delay(100); + WiFi.mode(WIFI_STA); + delay(100); + //Fdelay(1000); + #if defined(ESP32) + WiFi.setHostname(webName); + WiFi.setSleep(false); + #else + WiFi.hostname(webName); + #endif + #if defined(ESP32) + esp_wifi_set_ps (WIFI_PS_NONE); //power saving disable! + #endif + //if (WiFi.status() == WL_CONNECTED) return; + + DPRINT("\nConnecting to WiFi SSID:("); DPRINT(prm.wifiSsid); DPRINT(") PSW:("); DPRINT(prm.wifiPsw); DPRINTLN(")"); + int counter = 0; + if (strlen(prm.wifiPsw)!=0) + WiFi.begin(prm.wifiSsid, prm.wifiPsw); + else + WiFi.begin(prm.wifiSsid); + while (WiFi.status() != WL_CONNECTED) { + DPRINT('.'); + //playTubes(); + //Fdelay(3000); + delay(1000); + if (counter++>10) { + wifiManager(); + return; + } + } + DPRINTLN(" "); + ip = WiFi.localIP(); + WiFi.setAutoReconnect(true); +} + + +// Primary time source (see calcTime()/docs/DEVELOPER_GUIDE.md 4.4): refreshes the system clock from +// NTP over WiFi, at most once every TIMESERVER_REFRESH (2h) - or every 5 minutes if the previous +// attempt failed (lastTimeFailure backoff), retrying the NTP request itself up to 5 times per call. +// On success, rebuilds myTZ from the current prm.utc_offset/prm.enableDST (so a live timezone/DST +// change from the web UI takes effect on the very next scheduled refresh) and calls setTime() with +// the timezone-adjusted local time. Returns true only if this call actually refreshed the clock - +// callers (calcTime()) use that to decide whether the RTC also needs updating this cycle. +boolean updateTimefromTimeserver() { //true, if successful + static unsigned long lastTimeFailure = 0; + boolean res = false; + int count = 1; + + if (((millis()-lastTimeUpdate)0) && ((millis()-lastTimeFailure)<300000)) return(res); //wait for 5min to retry, if no success + + if (WiFi.status() == WL_CONNECTED) { + while (true) { + DPRINT("Connecting to timeserver: "); DPRINTLN(count); + res = timeClient.forceUpdate(); + if (res) { + mySTD.offset = prm.utc_offset * 60; + myDST.offset = mySTD.offset; + if (prm.enableDST) { + myDST.offset += 60; + } + myTZ = Timezone(myDST, mySTD); + setTime(myTZ.toLocal(timeClient.getEpochTime())); + lastTimeUpdate = millis(); + DPRINT("Timeserver date:"); DPRINT(year()); DPRINT("/"); DPRINT(month()); DPRINT("/"); DPRINT(day()); + DPRINT(" time:"); DPRINT(hour()); DPRINT(":"); DPRINT(minute()); DPRINT(":"); DPRINTLN(second()); + DPRINTLN("Clock refreshed from timeserver."); + lastTimeFailure = 0; + } + count ++; + if (res) break; //success!!! + Fdelay(1000); + if (count > 5) { //failure, but stop trying + lastTimeFailure = millis(); + break; + } + } //end while + } + return (res); +} + +#ifndef MDNSNAME + #define MDNSNAME "uniclock" +#endif + +// Optional mDNS responder (only compiled/started if USE_MDNS is defined - commented out by default +// near the top of this file) so the clock can be reached as e.g. "uniclock.local" instead of typing +// its IP address. Guards against re-starting an already-running responder via the static flag. +void startMDNS() { + static boolean mdnsStarted = false; + + if (mdnsStarted) return; //running and not changed + #ifdef USE_MDNS + if (!MDNS.begin(MDNSNAME)) { //mdns_free() switch off + DPRINTLN(F("Error setting up MDNS responder!")); + mdnsStarted = false; + } + else { + DPRINT(F("Started mDns service:")); DPRINTLN(MDNSNAME); + MDNS.addService("http", "tcp", 80); + mdnsStarted = true; + } + #endif +} + +// The Standalone(AP)-mode path (prm.wifiMode==false, or a fallback from a fully-failed STA connect): +// the clock hosts its own WiFi hotspot at the fixed address 192.168.4.1 (prm.ApSsid/prm.ApPsw) and +// runs a DNS server that answers every query with its own IP (dnsServer.start(53,"*",...)) so most +// phones/laptops auto-launch a captive-portal browser pointed at the admin page. In this mode there +// is no internet, so calcTime() never gets a WiFi/NTP refresh - GPS (if fitted) or the RTC are the +// only time sources. +void startStandaloneMode() { + DPRINTLN("Starting Clock in Standalone Mode!"); + DPRINT("Clock's AP SSID:"); DPRINT(prm.ApSsid); + DPRINT(" PSW:"); DPRINTLN(prm.ApPsw); + DPRINTLN("IP: 192.168.4.1"); + IPAddress local_ip(192, 168, 4, 1); + IPAddress gateway(192, 168, 4, 1); + IPAddress subnet(255, 255, 255, 0); + WiFi.mode(WIFI_AP); + WiFi.softAPConfig(local_ip, gateway, subnet); + boolean nwState; + if (strlen(prm.ApPsw)) { + nwState = WiFi.softAP(prm.ApSsid, prm.ApPsw); + } + else { + nwState = WiFi.softAP(prm.ApSsid); + } + Fdelay(2000); //info: https://github.com/espressif/arduino-esp32/issues/2025 + + DPRINT("AP status:"); DPRINTLN(nwState ? "Ready" : "Failed!"); //channel + DPRINT("Mac address:"); DPRINTLN(WiFi.softAPmacAddress()); + ip = WiFi.softAPIP(); + DPRINT("SOFT_AP IP:"); DPRINTLN(WiFi.softAPIP()); + + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer.start(53, "*", WiFi.softAPIP()); + +} + +// OTA firmware+filesystem update over plain HTTP. Triggered by the web admin's POST /firmwareupdate +// (sets makeFirmwareUpdate, actually run from loop() so it doesn't block the request handler) or +// automatically whenever the "firmware" server URL is changed via /configChanged ("for testing only", +// per the comment there). Downloads and flashes two files in sequence from prm.firmwareServer: +// ".bin" (the sketch binary) then ".spiffs.bin" (the SPIFFS web-page image) - FW is the +// firmware ID string (see clocks.h). Stops the web server first (frees RAM/sockets for the update), +// and only calls doReset() to actually reboot into the new firmware if BOTH updates reported success; +// otherwise the (old) server is simply restarted so the clock keeps running on the current firmware. +void doFirmwareUpdate(){ + if (WiFi.status() != WL_CONNECTED) { + DPRINTLN("Wifi disconnected. FirmwareUpdate failed."); + return; + } + + DPRINTLN("Webserver stopped..."); + server.reset(); //Stop server + delay(2000); + yield(); + String fname = String(prm.firmwareServer)+"/"+String(FW)+".bin"; + DPRINT("Update firmware: "); DPRINTLN(fname); + t_httpUpdate_return ret,ret2; + boolean succ = false; + WiFiClient client; + ESPhttpUpdate.rebootOnUpdate(false); + #if defined(ESP32) || defined(ESP8266_CORE_2xx) + ret = ESPhttpUpdate.update(fname); //ESP32 and old 8266 Core + #else + ret = ESPhttpUpdate.update(client,fname); //new 8266 core + #endif + switch(ret) { + case HTTP_UPDATE_FAILED: + DPRINTF("HTTP_UPDATE_FAILED Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + DPRINTLN(" "); + break; + + case HTTP_UPDATE_NO_UPDATES: + DPRINTLN("HTTP_UPDATE_NO_UPDATES"); + break; + + case HTTP_UPDATE_OK: + DPRINTLN("HTTP_UPDATE_OK"); + succ = true; + break; + } + + fname = String(prm.firmwareServer)+"/"+String(FW)+".spiffs.bin"; + DPRINT("Update SPIFFS: "); DPRINTLN(fname); + #if defined(ESP32)|| defined(ESP8266_CORE_2xx) + ret2 = ESPhttpUpdate.updateSpiffs(fname); //ESP32 or old 8266 Core + #else + ret2 = ESPhttpUpdate.updateFS(client,fname); //new 8266 core + #endif + switch(ret2) { + case HTTP_UPDATE_FAILED: + DPRINTF("HTTP_UPDATE_FAILED Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + DPRINTLN(" "); + break; + + case HTTP_UPDATE_NO_UPDATES: + DPRINTLN("HTTP_UPDATE_NO_UPDATES"); + break; + + case HTTP_UPDATE_OK: + DPRINTLN("HTTP_UPDATE_OK"); + succ = true; + break; + } + + if (succ) { + DPRINTLN(" "); + delay(1000); + doReset(); + } + DPRINTLN(" "); + startServer(); //restart webserver +} + +// Simple "run all digits through 0-9" anti-cathode-poisoning/burn-in pattern, triggered by the web +// admin's POST /cathodeProtect button (via makeCathodeProtect, executed from loop()) for +// cathProtMin minutes at full brightness (temporarily forces MAXBRIGHTNESS and disables auto-dim, +// restored at the end). Distinct from the automatic, scheduled newCathodeProtect() below (which runs +// every prm.interval minutes via timeProgram() and has per-tube stop timing/direction) - this one is +// the manual, user-triggered, uniform version. +void doCathodeProtect() { + unsigned long started = millis(); + byte num =0; + int db = prm.dayBright; //save brightness values + int nb = prm.nightBright; + boolean ab = autoBrightness; + byte onOff = 0; + + prm.dayBright=MAXBRIGHTNESS; + prm.nightBright = MAXBRIGHTNESS; + autoBrightness = false; + cathodeProtRunning = true; + + DPRINT("Cathode Protect is running for "); DPRINT(cathProtMin); DPRINTLN(" minutes."); + memset(digitDP, 0, sizeof(digitDP)); + memset(animMask,0,sizeof(animMask)); + while (true) { + for (int i=0;i9) num = 0; + onOff++; + if ((millis()-started)>uint32_t(cathProtMin)*60000l) break; + } //end while + + prm.dayBright = db; //restore brightness values + prm.nightBright = nb; + autoBrightness = ab; + cathodeProtRunning = false; +} + +// Sets up the ESPAsyncWebServer instance (port 80) that backs the web admin UI: static file routes +// serve the SPA's HTML/CSS/JS straight out of SPIFFS (uploaded separately via the IDE's filesystem +// uploader from the repo's data/ folder - see file header), plus a handful of action routes +// (/reset, /factoryreset, /firmwareupdate, /cathodeProtect) and the JSON/form REST-ish API +// (handleConfigChanged/handleSendConfig/handleSendCurrentInfos/handleSendClockDetails, defined below). +void startServer() { + DPRINTLN("Starting Async Webserver..."); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { + DPRINTLN("Webserver: / -> index.html"); + if(SPIFFS.exists("/index.html")) { + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.html", "text/html"); + request->send(response); + } + else { + request->send( 204, "text/html", "File Not Found" ); + DPRINTLN("/index.html not found"); + } + }); + + server.on("/jquery_351.js", HTTP_GET, [](AsyncWebServerRequest * request) { + DPRINTLN("Webserver: /jquery_351.js"); + if (SPIFFS.exists("/jquery_351.js")) { + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/jquery_351.js", "text/js"); + response->addHeader("Cache-Control", CACHE_MAX_AGE); + request->send(response); + } + else { + request->send( 204, "text/html", "File Not Found" ); + DPRINTLN("/jquery_351.js not found"); + } + }); + + server.on("/jquery_360.js", HTTP_GET, [](AsyncWebServerRequest * request) { + DPRINTLN("Webserver: /jquery_360.js"); + if (SPIFFS.exists("/jquery_360.js")) { + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/jquery_360.js", "text/js"); + response->addHeader("Cache-Control", CACHE_MAX_AGE); + request->send(response); + } + else { + request->send( 204, "text/html", "File Not Found" ); + DPRINTLN("/jquery_360.js not found"); + } + }); + + server.on("/page.js", HTTP_GET, [](AsyncWebServerRequest * request) { + + DPRINTLN("Webserver: /page.js"); + if (SPIFFS.exists("/page.js")) { + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/page.js", "text/js"); + response->addHeader("Cache-Control", CACHE_MAX_AGE); + request->send(response); + } + else { + request->send( 204, "text/html", "File Not Found" ); + DPRINTLN("/page.js not found"); + } + }); + + server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest * request) { + DPRINTLN("Webserver: /favicon.ico"); + if (SPIFFS.exists("/favicon.ico")){ + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/favicon.ico", "image/png"); + response->addHeader("Cache-Control", CACHE_MAX_AGE); + request->send(response); + } + else { + request->send( 204, "text/html", "File Not Found" ); + DPRINTLN("/favicon.ico not found"); + } + }); + + server.on("/generate_204", HTTP_GET, [](AsyncWebServerRequest * request) { + DPRINTLN("Webserver: /generate_204"); + AsyncWebServerResponse *response = request->beginResponse(302); // + response->addHeader("Location", String("http://") + WiFi.softAPIP().toString().c_str()); + request->send(response); + /* + //request->send(SPIFFS, "/index.html", "text/html"); + AsyncResponseStream *response = request->beginResponseStream("text/html"); + response->print("UniClock"); + response->print("

"); response->print(webName); + response->print("

Please, login to network and

"); + response->printf("

try opening %s

", WiFi.softAPIP().toString().c_str(),WiFi.softAPIP().toString().c_str()); + //response->printf("192.168.4.1"); + response->print("

"); + request->send(response); + */ + }); + +//____________________________________________________________________________ + server.on("/site.css", HTTP_GET, [](AsyncWebServerRequest * request) { + DPRINTLN("Webserver: /site.css"); + if (SPIFFS.exists("/site.css")) { + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/site.css", "text/css"); + response->addHeader("Cache-Control", CACHE_MAX_AGE); + request->send(response); + } + else { + request->send( 204, "text/html", "File Not Found" ); + DPRINTLN("/site.css not found"); + } + }); +//____________________________________________________________________________ + server.on("/reset", HTTP_POST, [] (AsyncWebServerRequest *request) { + DPRINTLN("/reset:"); + + request->send(200, "text/plain", "Reset: Restarting the Box!"); + delay(200); + doReset(); + }); +//____________________________________________________________________________ + server.on("/factoryreset", HTTP_POST, [] (AsyncWebServerRequest *request) { + DPRINTLN("/factoryreset:"); + + request->send(200, "text/plain", "Factory Reset!"); + delay(200); + factoryReset(); + doReset(); + }); + +//____________________________________________________________________________ + server.on("/firmwareupdate", HTTP_POST, [](AsyncWebServerRequest * request) { + makeFirmwareUpdate = true; + }); +//____________________________________________________________________________ + server.on("/cathodeProtect", HTTP_POST, [](AsyncWebServerRequest * request) { + makeCathodeProtect = true; + }); + + + server.on("/saveSetting", HTTP_POST, handleConfigChanged); + server.on("/getConfiguration", HTTP_GET, handleSendConfig); + server.on("/getCurrentInfos", HTTP_GET, handleSendCurrentInfos); + server.on("/getClockDetails", HTTP_GET, handleSendClockDetails); + server.onNotFound(handleNotFound); + + server.begin(); + +} //end of procedure + +// Catch-all 404 handler (server.onNotFound) - also doubles as a debug request-dumper (prints every +// request param, GET/POST, to the serial console when DEBUG is on) which is handy for reverse- +// engineering what the SPA's JS is actually sending during development. +void handleNotFound(AsyncWebServerRequest *request) { + int params = request->params(); + for (int i = 0; i < params; i++) { + #ifdef DEBUG + AsyncWebParameter* p = request->getParam(i); + if (p->isFile()) { //p->isPost() is also true + Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size()); + } else if (p->isPost()) { + Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } else { + Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } + #endif + } + + String message = "File Not Found\n\n"; + message += "URL: "; + message += request->url(); + message += "\nMethod: "; + message += ( request->method() == HTTP_GET ) ? "GET" : "POST"; + message += "\nArguments: "; + message += request->args(); + message += "\n"; + + for ( uint8_t i = 0; i < request->args(); i++ ) { + message += " " + request->argName ( i ) + ": " + request->arg ( i ) + "\n"; + } + + DPRINTLN(message); + request->send( 204, "text/html", "URL Not Found" ); +} + +// POST /saveSetting - the SINGLE entry point for every settings change made from the web admin UI. +// Request carries two form fields, `key` (a string identifying which setting) and `value` (its new +// value as a string) - handled as one long if/else-if chain matching `key`, converting `value` to the +// right type and writing it into the matching prm.* field (or, for a few keys, a plain global like +// c_MinBrightness/cathProtMin/autoBrightness). Any recognized key ends with saveEEPROM() so the +// change survives a reboot; an unrecognized key returns HTTP 404 without touching EEPROM. Most +// branches are simple 1:1 field assignments and aren't commented individually below - only branches +// with side effects beyond "store this value" are called out. +void handleConfigChanged(AsyncWebServerRequest *request) { + if (request->hasParam("key", true) && request->hasParam("value", true)) { + + //int args = request->args(); + + //for(int i=0;iargName(i).c_str(), request->arg(i).c_str()); + //} + boolean oldDST = prm.enableDST; + int old_utc_offset = prm.utc_offset; + + String key = request->getParam("key", true)->value(); + String value = request->getParam("value", true)->value(); + DPRINT(key); DPRINT(" = "); DPRINTLN(value); + + boolean paramFound = true; + + if (key == "utc_offset") { + // Changing the UTC offset immediately shifts the LIVE running clock (not just the stored + // preference) by the delta in hours, and pushes that change into the RTC right away via + // updateRTC() - so the displayed time updates instantly rather than waiting for the next NTP + // sync (which would otherwise take up to TIMESERVER_REFRESH to reflect the new zone). + prm.utc_offset = value.toInt(); + if (old_utc_offset != prm.utc_offset) { //Change time zone + setTime(now()+(prm.utc_offset-old_utc_offset)*3600); + updateRTC(); + } + } + else if (key == "set12_24") { + prm.set12_24 = (value == "true"); + DPRINT("set12_24:"); + DPRINTLN(prm.set12_24); + } + else if (key == "showZero") { + prm.showZero = (value == "true"); + DPRINT("showZero:"); + DPRINTLN(prm.showZero); + } + else if (key == "enableBlink") { + prm.enableBlink = (value == "true"); + } + else if (key == "enableDST") { + // Same immediate-effect pattern as utc_offset above: toggling DST support live-shifts the + // running clock by +-1h right away and re-syncs the RTC, instead of only taking effect on the + // next scheduled NTP refresh (myTZ itself is rebuilt from prm.enableDST inside + // updateTimefromTimeserver() on that next refresh). + prm.enableDST = (value == "true"); + if (oldDST && !prm.enableDST) { //Switching off DST + setTime(now()-3600); + updateRTC(); + } + if (!oldDST && prm.enableDST) { //Switching on DST + setTime(now()+3600); + updateRTC(); + } + } + else if (key == "interval") { + prm.interval = value.toInt(); + } + else if (key == "enableAutoShutoff") { + prm.enableAutoShutoff = (value == "true"); + } + + else if (key == "dayTimeHours") { + prm.dayHour = value.toInt(); + } + else if (key == "dayTimeMinutes") { + prm.dayMin = value.toInt(); + } + else if (key == "nightTimeHours") { + prm.nightHour = value.toInt(); + } + else if (key == "nightTimeMinutes") { + prm.nightMin = value.toInt(); + } + else if (key == "dayBright") { + prm.dayBright = value.toInt(); + } + else if (key == "nightBright") { + prm.nightBright = value.toInt(); + } + else if (key == "animMode") { + prm.animMode = value.toInt(); + } + else if (key == "manualOverride") { + // Not a prm.* field - "manualOverride" is the web UI's day/night ON/OFF toggle: it flips the + // live displayON state directly and sets the manualOverride flag so evalShutoffTime() (called + // every second from timeProgram()) stops auto-driving displayON from the day/night schedule + // until the schedule naturally catches up to the state the user forced (see evalShutoffTime()). + boolean v = value == "false"; + if (v != displayON) { + manualOverride = true; + displayON = v; + } + } + else if (key == "wifiMode") { + prm.wifiMode = (value == "true"); + } + else if (key == "alarmEnable") { + prm.alarmEnable = (value == "true"); + } + else if (key == "alarmTimeHours") { + prm.alarmHour = value.toInt(); + } + else if (key == "alarmTimeMinutes") { + prm.alarmMin = value.toInt(); + } + else if (key == "alarmPeriod") { + prm.alarmPeriod = value.toInt(); + } + + //RGB LED values + else if (key == "rgbEffect") { + prm.rgbEffect = value.toInt(); + } + else if (key == "rgbBrightness") { + prm.rgbBrightness = value.toInt(); + } + else if (key == "rgbFixColor") { + prm.rgbFixColor = value.toInt(); + } + else if (key == "rgbSpeed") { + prm.rgbSpeed = value.toInt(); + } + else if (key == "rgbDir") { + prm.rgbDir = (value == "true"); + } + else if (key == "rgbMinBrightness") { + c_MinBrightness = value.toInt(); + } + else if (key == "wifiSsid") { + value.toCharArray(prm.wifiSsid,sizeof(prm.wifiSsid)); + } + else if (key == "wifiPsw") { + value.toCharArray(prm.wifiPsw,sizeof(prm.wifiPsw)); + } + else if (key == "ApSsid") { + // Sanitizes the OLD prm.ApSsid in place (replacing any non-printable/non-ASCII byte with '_') + // BEFORE overwriting it with the new value from the web form - i.e. this cleans up a + // possibly-corrupted stored SSID from a prior session, not the incoming `value` itself (same + // repair loop also runs unconditionally in factoryReset(), see below). + for (int i=0;i<(int)strlen(prm.ApSsid);i++) { //repair bad chars in AP SSID + if ((prm.ApSsid[i]<32) || (prm.ApSsid[i]>126)) prm.ApSsid[i]='_'; + } + value.toCharArray(prm.ApSsid,sizeof(prm.ApSsid)); + } + else if (key == "ApPsw") { + value.toCharArray(prm.ApPsw,sizeof(prm.ApPsw)); + } + else if (key == "NtpServer") { + value.toCharArray(prm.NtpServer,sizeof(prm.NtpServer)); + } + else if (key == "firmware") { + // Side effect: merely changing the firmware-server URL setting also immediately kicks off an + // OTA update attempt (makeFirmwareUpdate, picked up by loop()->doFirmwareUpdate()) - per the + // "for testing only" comment this looks like a leftover dev convenience rather than intended + // production behavior (a user just correcting a typo in the URL would trigger a real update). + value.toCharArray(prm.firmwareServer,sizeof(prm.firmwareServer)); + makeFirmwareUpdate = true; //for testing only + } + else if (key == "mqttBrokerAddr") { + value.toCharArray(prm.mqttBrokerAddr,sizeof(prm.mqttBrokerAddr)); + } + else if (key == "mqttBrokerUser") { + value.toCharArray(prm.mqttBrokerUser,sizeof(prm.mqttBrokerUser)); + } + else if (key == "mqttBrokerPsw") { + value.toCharArray(prm.mqttBrokerPsw,sizeof(prm.mqttBrokerPsw)); + } + else if (key == "mqttBrokerRefresh") { + prm.mqttBrokerRefresh = value.toInt(); + } + else if (key == "mqttEnable") { + prm.mqttEnable = (value == "true"); + } + else if (key == "dateMode") { + prm.dateMode = value.toInt(); + } + else if (key == "dateStart") { + prm.dateStart = value.toInt(); + } + else if (key == "dateEnd") { + prm.dateEnd = value.toInt(); + } + else if (key == "tempStart") { + prm.tempStart = value.toInt(); + } + else if (key == "tempEnd") { + prm.tempEnd = value.toInt(); + } + else if (key == "humidStart") { + prm.humidStart = value.toInt(); + } + else if (key == "humidEnd") { + prm.humidEnd = value.toInt(); + } + else if (key == "dateRepeatMin") { + prm.dateRepeatMin = value.toInt(); + if (prm.dateRepeatMin>10) prm.dateRepeatMin = 10; + } + else if (key == "tempRepeatMin") { + prm.tempRepeatMin = value.toInt(); + if (prm.tempRepeatMin>10) prm.tempRepeatMin = 10; + } + else if (key == "enableDoubleBlink") { + prm.enableDoubleBlink = (value == "true"); + } + else if (key == "enableTimeDisplay") { + prm.enableTimeDisplay = (value == "true"); + } + else if (key == "enableAutoDim") { + prm.enableAutoDim = (value == "true"); + autoBrightness = prm.enableAutoDim; //mirrors the flag into the live `autoBrightness` global read by getLightSensor()/the display brightness logic, so the change takes effect immediately rather than only after the next getLightSensor() poll + } + else if (key == "enableRadar") { + prm.enableRadar = (value == "true"); + radarLastOn = millis(); //resets the radar "last motion seen" timestamp so toggling the feature on doesn't immediately look timed-out and shut the tubes off + radarON = true; + } + else if (key == "radarTimeout"){ + prm.radarTimeout = value.toInt(); + if (prm.radarTimeout>60) prm.radarTimeout = 60; + if (prm.radarTimeout<1) prm.radarTimeout = 1; + radarLastOn = millis(); //same reasoning as enableRadar above - avoid an immediate false shutoff after changing the timeout value + radarON = true; + } + + else if (key == "tempCF") { + prm.tempCF = (value == "true"); + } + else if (key == "corrT0") { + prm.corrT0 = value.toFloat(); + } + else if (key == "corrT1") { + prm.corrT1 = value.toFloat(); + } + else if (key == "corrH0") { + prm.corrH0 = value.toFloat(); + } + else if (key == "corrH1") { + prm.corrH1 = value.toFloat(); + } + else if (key == "cathProtMin") { + cathProtMin = value.toInt(); + } + else { + paramFound = false; + } + if (paramFound) { + saveEEPROM(); + request->send(200, "text/plain", "Ok"); + } + else { + request->send(404, "text/plain", "404: Parameter not found"); + } + } + else { + request->send(400, "text/plain", "400: Invalid Request. Parameters: key and value"); + } +} + +// GET /getConfiguration - dumps (almost) the entire `prm` struct plus a handful of live/derived values +// (current date/time, sensor readings, MAC, driver name...) as one JSON object. This is what the web +// SPA fetches once on page load to populate every settings form field; a "255" sentinel value marks a +// sensor reading/feature as "not installed" (the JS treats 255 specially rather than 0, since 0 is a +// plausible real reading). Compare with handleSendCurrentInfos() below, which is the lightweight, +// frequently-polled subset used to keep the live clock/sensor display refreshed without re-sending +// the whole settings form each time. +void handleSendConfig(AsyncWebServerRequest *request) { + StaticJsonDocument<2048> doc; + char buf[20]; //conversion buffer + + DPRINTLN("Sending configuration to web client."); + + //Global data + doc["version"] = webName; + doc["FW"] = FW; + doc["tubeDriver"] = tubeDriver; + doc["maxDigits"] = maxDigits; //number of digits (tubes) + doc["maxBrightness"] = MAXBRIGHTNESS; //Maximum tube brightness usually 10, sometimes 12 + + //Actual time and environment data + sprintf(buf, "%4d.%02d.%02d", year(), month(), day()); + doc["currentDate"] = buf; + sprintf(buf, "%02d:%02d", hour(), minute()); + doc["currentTime"] = buf; + //DPRINT("useTemp:"); DPRINT(useTemp); DPRINT("useHumid:"); DPRINT(useHumid); + if (useTemp > 0) { + doc["temperature"] = round1(temperature[0] + prm.corrT0); + } + else + doc["temperature"] = 255; + +if (useTemp > 1) + doc["temperature2"] = round1(temperature[1] + prm.corrT1); + else + doc["temperature2"] = 255; + + if (useHumid>0) { + doc["humidity"] = round1(humid[0] + prm.corrH0); + } + else + doc["humidity"] = 255; + + if (useHumid>1) + doc["humidity2"] = round1(humid[1] + prm.corrH1); + else + doc["humidity2"] = 255; + + if (usePress>0) { + doc["pressure"] = pressur[0]; + } + else + doc["pressure"] = 255; + + if (useLux>0) { + doc["lux"] = lx; + } + else + doc["lux"] = 255; + + //Clock calculation and display parameters + doc["utc_offset"] = prm.utc_offset; + doc["enableDST"] = prm.enableDST; // Flag to enable DST (summer time...) + doc["set12_24"] = prm.set12_24; // Flag indicating 12 vs 24 hour time (false = 12, true = 24); + doc["showZero"] = prm.showZero; // Flag to indicate whether to show zero in the hour ten's place + doc["enableBlink"] = prm.enableBlink; // Flag to indicate whether center colon should blink + doc["interval"] = prm.interval; // interval in minutes, with 0 = off + + //Day/Night dimmer parameters + doc["enableAutoShutoff"] = prm.enableAutoShutoff; // Flag to enable/disable nighttime shut off + sprintf(buf, "%02d:%02d", prm.dayHour, prm.dayMin); + doc["dayTime"] = buf; + sprintf(buf, "%02d:%02d", prm.nightHour, prm.nightMin); + doc["nightTime"] = buf; + doc["dayBright"] = prm.dayBright; + doc["nightBright"] = prm.nightBright; + doc["animMode"] = prm.animMode; //Tube animation + doc["manualOverride"] = !displayON; + + //Alarm values + doc["alarmEnable"] = prm.alarmEnable; //1 = ON, 0 = OFF + sprintf(buf, "%02d:%02d", prm.alarmHour, prm.alarmMin); + doc["alarmTime"] = buf; + doc["alarmPeriod"] = prm.alarmPeriod; + + //RGB LED values + #if defined(USE_NEOPIXEL) || defined(USE_PWMLEDS) + doc["rgbEffect"] = prm.rgbEffect; // if 255, no RGB exist! + #else + doc["rgbEffect"] = 255; //Not installed Neopixels!!! + #endif + + doc["rgbBrightness"] = prm.rgbBrightness; // c_MinBrightness..255 + doc["rgbFixColor"] = prm.rgbFixColor; // 0..256 + doc["rgbSpeed"] = prm.rgbSpeed; // 1..255 + doc["rgbDir"] = prm.rgbDir; // 0 = right direction, 1 = left direction + doc["rgbMinBrightness"] = c_MinBrightness; //minimum brightness for range check!! + doc["wifiMode"] = prm.wifiMode; + doc["wifiSsid"] = prm.wifiSsid; + doc["wifiPsw"] = prm.wifiPsw; + doc["ApSsid"] = prm.ApSsid; + doc["ApPsw"] = prm.ApPsw; + doc["NtpServer"] = prm.NtpServer; + doc["firmware"] = prm.firmwareServer; + + doc["mqttBrokerAddr"] = prm.mqttBrokerAddr; + doc["mqttBrokerUser"] = prm.mqttBrokerUser; + doc["mqttBrokerPsw"] = prm.mqttBrokerPsw; + doc["mqttEnable"] = prm.mqttEnable; + #if defined(USE_MQTT) + doc["mqttBrokerRefresh"] = prm.mqttBrokerRefresh; + #else + doc["mqttBrokerRefresh"] = 0; + #endif + doc["dateMode"] = prm.dateMode; + doc["dateRepeatMin"] = prm.dateRepeatMin; + doc["tempRepeatMin"] = prm.tempRepeatMin; + doc["tempCF"] = prm.tempCF; + doc["enableTimeDisplay"] = prm.enableTimeDisplay; + doc["enableDoubleBlink"] = prm.enableDoubleBlink; + doc["dateStart"] = prm.dateStart; + doc["dateEnd"] = prm.dateEnd; + doc["tempStart"] = prm.tempStart; + doc["tempEnd"] = prm.tempEnd; + doc["humidStart"] = prm.humidStart; + doc["humidEnd"] = prm.humidEnd; + doc["enableAutoDim"] = prm.enableAutoDim; + doc["enableRadar"] = prm.enableRadar; + #if (RADAR_PIN >= 0) || defined(USE_MQTT) + if (prm.radarTimeout<1) prm.radarTimeout = 1; + doc["radarTimeout"] = prm.radarTimeout; + #else + doc["radarTimeout"] = 0; + #endif + doc["corrT0"] = prm.corrT0; + doc["corrT1"] = prm.corrT1; + doc["corrH0"] = prm.corrH0; + doc["corrH1"] = prm.corrH1; + doc["cathProtMin"] = 3; //default value for cathProtMin slider + String json; + serializeJson(doc, json); + request->send(200, "application/json", json); +} + +// GET /getClockDetails - returns a pre-formatted HTML fragment (not JSON, unlike the other two +// handlers) with static/diagnostic info: firmware ID, driver name, MAC address, sensor counts, and +// the full used-GPIO listing built by listPins()/regPin() (usedPinsStr) plus any driver-supplied +// extra setup info (driverSetupStr). Shown on an "about/diagnostics" panel of the web admin UI. +void handleSendClockDetails(AsyncWebServerRequest *request) { + StaticJsonDocument<200> doc; + //StaticJsonDocument<512> doc; + String dat; + + DPRINTLN("SendClockDetails"); + dat = String("

FirmwareID:") + FW + "
"; + dat += String(" Tube driver:") + tubeDriver + "
"; + dat += String(" Mac:") + String(WiFi.macAddress()) + "

"; + dat += String("MAXBRIGHTNESS:") + String(MAXBRIGHTNESS) + "
"; + if (RTCexist) dat += String("RTC exist: YES
"); + if (useTemp>0) dat += String("Temperature sensors:") + useTemp + "
"; + if (useHumid>0) dat += String("Humidity sensors:") + useHumid + "
"; + if (usePress>0) dat += String("Pressure sensors:") + usePress + "
"; + dat += usedPinsStr + "
"; + dat += driverSetupStr + "
"; + DPRINTLN(dat); + request->send(200, "text/html", dat); +} + +// GET /getCurrentInfos - the lightweight, frequently AJAX-polled JSON endpoint (date/time, +// temperature/humidity/pressure/lux, WiFi RSSI) used to keep the web admin page's live readouts +// current without re-fetching the whole settings form (see handleSendConfig() above). Keeps both new +// ("temperature1"/"humidity1") and legacy ("temperature"/"humidity") key names for backwards +// compatibility with older versions of the web front-end. +void handleSendCurrentInfos(AsyncWebServerRequest *request) { + StaticJsonDocument<400> doc; + //StaticJsonDocument<512> doc; + char buf[20]; //conversion buffer + + DPRINTLN("WebRefresh"); + //Actual time and environment data + sprintf(buf, "%4d.%02d.%02d", year(), month(), day()); + doc["currentDate"] = buf; + sprintf(buf, "%02d:%02d", hour(), minute()); + doc["currentTime"] = buf; + //DPRINT("useTemp:"); DPRINT(useTemp); DPRINT("useHumid:"); DPRINT(useHumid); + if (useTemp > 0) { + doc["temperature1"] = temperature[0] + prm.corrT0; + doc["temperature"] = temperature[0] + prm.corrT0; //for compatibility with the old web page + } + else + doc["temperature1"] = 255; + +if (useTemp > 1) + doc["temperature2"] = temperature[1] + prm.corrT1; + else + doc["temperature2"] = 255; + + if (useHumid>0) { + doc["humidity1"] = humid[0] + prm.corrH0; + doc["humidity"] = humid[0] + prm.corrH0; //for compatibility with the old web page + } + else + doc["humidity1"] = 255; + + if (useHumid>1) + doc["humidity2"] = humid[1] + prm.corrH1; + else + doc["humidity2"] = 255; + + if (usePress>0) { + doc["pressure"] = pressur[0]; + } + else + doc["pressure"] = 255; + + if (useLux>0) { + doc["lux"] = lx; + } + else + doc["lx"] = 255; + doc["rssi"] = WiFi.RSSI(); + String json; + serializeJson(doc, json); + request->send(200, "application/json", json); +} + +// Startup sequence (see docs/DEVELOPER_GUIDE.md section 4.1 for the full ordered summary): +// brownout detector (optional) -> EEPROM init -> setup_pins() (driver-specific GPIO setup) -> +// optional peripherals (alarm/colon/radar/tube-power/light-sensor/PWM LEDs) -> temperature/I2C/RTC/GPS +// setup -> loadEEPROM() (factoryReset() if the magic number doesn't match) -> NeoPixel setup -> tube +// test animation -> SPIFFS mount (web page files) -> WiFi connect (STA or AP mode) -> web server start +// -> MQTT setup -> show IP address on the tubes -> initial time sync. Order matters here: pins must be +// configured before any peripheral touches them, and EEPROM must be loaded before WiFi connects +// (credentials live in prm). +void setup() { + #if defined(DISABLE_BROWNOUT) && defined(ESP32) + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector + #endif + delay(1000); + WiFi.mode(WIFI_OFF); + EEPROM.begin(EEPROM_SIZE); + memset(pinTxt,0,sizeof(pinTxt)); + DPRINTBEGIN(115200); DPRINTLN(" "); + DPRINTLN(F("=================================================")); + DPRINT("Starting "); DPRINTLN(webName); + DPRINT("Firmware code:"); DPRINT(FW); DPRINT(" Tube driver:"); DPRINTLN(tubeDriver); + DPRINT("MAXBRIGHTNESS:"); DPRINTLN(MAXBRIGHTNESS); + DPRINTLN(F("=================================================")); + clearDigits(); + setup_pins(); + #if ALARMSPEAKER_PIN >= 0 + pinMode(ALARMSPEAKER_PIN, OUTPUT); regPin(ALARMSPEAKER_PIN,"ALARMSPEAKER_PIN"); + digitalWrite(ALARMSPEAKER_PIN,!ALARM_ON); + DPRINT(" - ON state:"); + if (ALARM_ON == HIGH) {DPRINTLN("HIGH"); } + else {DPRINTLN("LOW"); } + #endif + #if ALARMBUTTON_PIN >= 0 + pinMode(ALARMBUTTON_PIN, INPUT_PULLUP); regPin(ALARMBUTTON_PIN,"ALARMBUTTON_PIN"); + #endif + #if COLON_PIN >= 0 + pinMode(COLON_PIN, OUTPUT); regPin(COLON_PIN,"COLON_PIN"); + #endif + #if LED_SWITCH_PIN >= 0 + pinMode(LED_SWITCH_PIN, OUTPUT); regPin(LED_SWITCH_PIN,"LED_SWITCH_PIN"); + #endif + #if DECIMALPOINT_PIN >= 0 + pinMode(DECIMALPOINT_PIN, OUTPUT); regPin(DECIMALPOINT_PIN,"DECIMALPOINT_PIN"); + #endif + #if RADAR_PIN >= 0 + pinMode(RADAR_PIN, INPUT); regPin(RADAR_PIN,"RADAR_PIN"); + DPRINT(" - RADAR Timeout:"); DPRINTLN(prm.radarTimeout); + #endif + #if TUBE_POWER_PIN >= 0 + pinMode(TUBE_POWER_PIN, OUTPUT); regPin(TUBE_POWER_PIN,"TUBE_POWER_PIN"); + digitalWrite(TUBE_POWER_PIN,TUBE_POWER_ON); + DPRINT(" - ON state:"); + if (TUBE_POWER_ON == HIGH) {DPRINTLN("HIGH"); } + else {DPRINTLN("LOW"); } + #endif + #if LIGHT_SENSOR_PIN >= 0 + pinMode(LIGHT_SENSOR_PIN, INPUT); regPin(LIGHT_SENSOR_PIN,"LIGHT_SENSOR_PIN"); + DPRINT(" - MAXIMUM_LUX for MAXBRIGHTNESS:"); DPRINTLN(MAXIMUM_LUX); + useLux++; + LDRexist = true; + #endif + + #ifdef USE_PWMLEDS + #if PWM1_PIN >= 0 + pinMode(PWM1_PIN, OUTPUT); regPin(PWM1_PIN,"PWM1_PIN"); + ledcAttachPin(PWM1_PIN, 0); + ledcSetup(0, 200, 8); // 12 + #endif + #if PWM2_PIN >= 0 + pinMode(PWM2_PIN, OUTPUT); regPin(PWM2_PIN,"PWM2_PIN"); + ledcAttachPin(PWM2_PIN, 1); + ledcSetup(1, 200, 8); // 12 + #endif + #if PWM3_PIN >= 0 + pinMode(PWM3_PIN, OUTPUT); regPin(PWM3_PIN,"PWM3_PIN"); + ledcAttachPin(PWM3_PIN, 2); + ledcSetup(2, 200, 8); // 12 + #endif + #endif + + #if defined(PIN_EXTINP1) + if (PIN_EXTINP1>=0) + pinMode(PIN_EXTINP1, INPUT); regPin(PIN_EXTINP1,"NOT_USED_INPUT"); + #endif + #if defined(PIN_EXTINP2) + if (PIN_EXTINP2>=0) + pinMode(PIN_EXTINP2, INPUT); regPin(PIN_EXTINP2,"NOT_USED_INPUT"); + #endif + + #if TEMP_DALLAS_PIN >= 0 + setupDallasTemp(); + #endif + #if TEMP_DHT_PIN >= 0 + setupDHTemp(); + #endif + #ifdef USE_I2CSENSORS + setupI2Csensors(); + #endif + + #ifdef USE_RTC + setupRTC(); + #endif + + #ifdef USE_GPS + setupGPS(); + #endif + + decimalpointON = false; + DPRINT("Number of digits:"); DPRINTLN(maxDigits); + + #ifdef FACTORYRESET_PIN + if (FACTORYRESET_PIN>=0) { + DPRINTLN("FactoryReset button is pushed!"); + pinMode(FACTORYRESET_PIN,INPUT_PULLUP); regPin(FACTORYRESET_PIN,"FACTORYRESET_PIN"); + if (digitalRead(FACTORYRESET_PIN)==LOW) factoryReset(); + } + #endif + loadEEPROM(); + if (prm.magic != MAGIC_VALUE) factoryReset(); //EEPROM struct version mismatch (see MAGIC_VALUE comment) - wipe to sane defaults + + setupNeopixel(); + listPins(); + writeAlarmPin(ALARM_ON); writeAlarmPin(!ALARM_ON); //quick blip of the alarm pin at boot (on then immediately off) - a self-test/audible "I'm alive" click, not a real alarm + getDHTemp(); //get the first DHT temp+humid measure + + // Temporarily force animMode to 2 (all changed tubes spin together) for the power-on tube test + // below, regardless of the user's saved animation preference, then restore it afterwards - ensures + // testTubes() always animates visibly even if the stored prm.animMode is 0 (no animation). + byte saveMode = prm.animMode; + prm.animMode = 2; + doAnimationPWM(); + testTubes(300); + + clearDigits(); + Fdelay(200); + if (!SPIFFS.begin()) { + DPRINTLN("An Error has occurred while mounting SPIFFS"); + } + else { + #if defined(ESP32) + DPRINT("SPIFFS Total bytes: "); DPRINTLN(SPIFFS.totalBytes()); + DPRINT("SPIFFS Used bytes: "); DPRINTLN(SPIFFS.usedBytes()); + #endif + DPRINTLN("SPIFFS started."); + if(!SPIFFS.exists("/index.html")) {DPRINTLN("/index.html not found!");} + if(!SPIFFS.exists("/site.css")) {DPRINTLN("/site.css not found!");} + if(!SPIFFS.exists("/page.js")) {DPRINTLN("/page.js not found!");} + if(!SPIFFS.exists("/jquery_360.js")) {DPRINTLN("/jquery_360.js not found!");} + } + memset(digit,10,sizeof(digit)); //clear display + memset(newDigit,10,sizeof(newDigit)); + memset(oldDigit,10,sizeof(oldDigit)); + + wifiConnectRunning = true; + writeDisplay2(); + if (prm.wifiMode) { + //findBestWifi(); + startWifiMode(); //note: startWifiMode() already falls back to wifiManager() internally on failure/empty SSID - the retry loop below is an extra layer only exercised when USE_WIFIMANAGER is enabled + + #ifdef USE_WIFIMANAGER + int counter = 0; + while (WiFi.status() != WL_CONNECTED) { //try to use wifiManeger, if enabled + DPRINT('.'); + Fdelay(3000); + if (counter++>2) { + wifiManager(); + break; + } + } + DPRINTLN(" "); + #endif + + if (WiFi.status() != WL_CONNECTED) { //failed to connect to wifi - give up on infrastructure mode entirely and fall back to hosting our own AP so the clock is at least reachable + startStandaloneMode(); + } + else { + ip = WiFi.localIP(); + WiFi.setAutoReconnect(true); + } + } + else { + startStandaloneMode(); + } + wifiConnectRunning = false; + + startMDNS(); + startServer(); + #ifdef USE_MQTT + setupMqtt(); + #endif + + showMyIp(); + Fdelay(500); + clearDigits(); + Fdelay(200); + prm.animMode = saveMode; + timeClient.begin(); + calcTime(); + timeProgram(); + + //newCathodeProtect(5000,0); +} + +// Time-source priority chain (docs/DEVELOPER_GUIDE.md section 4.4): NTP-over-WiFi is always tried +// first when connected (updateTimefromTimeserver() self-throttles to once per TIMESERVER_REFRESH, so +// this is cheap to call every cycle); GPS is tried next if fitted; the DS3231 RTC (if fitted) is the +// LAST-RESORT fallback: if either of the above actually refreshed the system clock this cycle, the RTC +// is instead WRITTEN (updateRTC(), to keep it in sync as a fallback for the next power-loss/no-network +// boot) - otherwise the RTC is READ (getRTC()) to keep the clock ticking with no network/GPS at all. +// A build with none of WiFi/GPS/RTC available simply free-runs on TimeLib's own millis()-derived clock +// (set once at some point in the past) with no further correction. +void calcTime() { + boolean refreshed = false; + boolean refreshed2 = false; + + if (WiFi.status() == WL_CONNECTED) { //check wifi connection + refreshed = updateTimefromTimeserver(); //update time from wifi + } + if (GPSexist) { //update time from GPS, if exist + refreshed2 = getGPS(); + } + + if (RTCexist) { //update time from RTC, if exist + if (refreshed || refreshed2) + updateRTC(); //update RTC, if needed + else + getRTC(); + } +} + + +// Main per-tick clock/sensor/display driver, called unconditionally every loop() iteration but +// self-throttled to run at most every 200ms (calcTime()/sensor polling cadence) - the actual visible +// digit refresh only happens on real second boundaries (`if (now() != prevTime)` below), so most 200ms +// ticks only refresh sensor readings and return without touching the display buffers. +void timeProgram() { + static unsigned long lastRun = 0; + + if ((millis() - lastRun) < 200) return; + lastRun = millis(); + calcTime(); + if (useDallasTemp > 0) { + requestDallasTemp(false); //start measure + getTemp(); //get result, if ready + } + getDHTemp(); + getI2Csensors(); //check all existing I2C sensors + + #ifdef SIMULATE_TEMPERATURE_SENSOR //for test only + useTemp = 2; temperature[0] = 20.4; temperature[1] = 22.8; //test only + useHumid = 2; humid[0] = 41.2; humid[1] = 42.7; + #endif + + if (now() != prevTime) { // Update time every second + prevTime = now(); + evalShutoffTime(); // Check whether display should be turned off (Auto shutoff mode) + if (!displayON || !prm.enableBlink) colonBlinkState = false; + else colonBlinkState = (bool)(second() % 2); + + // Decide which "screen" to show this second, in priority order: temperature > humidity > date > + // plain clock (see the showClock/showDate/showTemp*/showHumid* globals declared near the top of + // this file). Each window is an independent [xStart,xEnd) range of seconds-within-minute, so e.g. + // temperature and date windows can be configured to overlap or not from the web UI; whichever + // display*() function actually runs is decided later in displayTime4/6/8() by checking these + // flags in the same priority order. + showClock = false; + showDate = (prm.enableTimeDisplay && (prm.dateRepeatMin>0) && ((second() >= prm.dateStart) && (second() < prm.dateEnd))); + if (showDate && (prm.dateRepeatMin>1)) { + if ((minute() % prm.dateRepeatMin) != 0) { + showDate = false; + } + } +//useTemp = 1; temperature[0] = -12.34; useHumid = 1; humid[0] = 26.7; //for test only + showTemp0 = (useTemp > 0) && (second() >= prm.tempStart) && (second() < prm.tempEnd); + showTemp1 = (useTemp > 1) && (second() >= prm.tempStart + (prm.tempEnd - prm.tempStart) / 2) && (second() < prm.tempEnd); + showHumid0 = (useHumid > 0) && (second() >= prm.humidStart) && (second() < prm.humidEnd); + showHumid1 = (useHumid >1) && (second() >= prm.humidStart + (prm.humidEnd-prm.humidStart)/2) && (second() < prm.humidEnd); + if ((prm.tempRepeatMin==0) || ((minute() % prm.tempRepeatMin) != 0)) { + showTemp0 = false; + showTemp1 = false; + showHumid0 = false; + showHumid1 = false; + } + if (maxDigits >= 8) displayTime8(); + else if (maxDigits == 6) displayTime6(); + else displayTime4(); + // if (COLON_PIN>=0) digitalWrite(COLON_PIN,colonBlinkState); // Blink colon pin + #if LED_SWITCH_PIN >= 0 + if (displayON) //Switch on backlight LED only daytime + digitalWrite(LED_SWITCH_PIN, HIGH); + else + digitalWrite(LED_SWITCH_PIN, LOW); + #endif + changeDigit(); + printDigits(0); + + if (prm.interval > 0) { // protection is enabled + // At the first top of the hour, initialize protection logic timer + if (!initProtectionTimer && (minute() == 0)) { + protectTimer = 0; // Ensures protection logic will be immediately triggered + initProtectionTimer = true; + } + if ((now() - protectTimer) >= 60 * prm.interval) { + protectTimer = now(); + // The current time can drift slightly relative to the protectTimer when NIST time is updated + // Need to make a small adjustment to the timer to ensure it is triggered at the minute change + protectTimer -= ((second() + 30) % 60 - 30); + // random(3)-1 picks the animation direction for this run: -1, 0 (random-order) or +1 - see + // newCathodeProtect()'s dir parameter and its POSSIBLE BUG note further down. + if (displayON && (millis() > 50000)) newCathodeProtect(maxDigits*1500,random(3)-1); //don't play in the first 50 sec + } + } + } +} + +// Raw single-block read of the whole `prm` struct from EEPROM address 0 - does NOT itself check +// prm.magic; the caller (setup()) is responsible for calling factoryReset() if the loaded magic value +// doesn't match MAGIC_VALUE. +void loadEEPROM() { + DPRINT("Loading setting from EEPROM. Size:"); DPRINT(EEPROM_SIZE); + + EEPROM.get(EEPROM_addr, prm); + DPRINT(" version:"); DPRINTLN(prm.magic); +} + +// Raw single-block write of the whole `prm` struct back to EEPROM (address 0) - called after every +// successful key/value change in handleConfigChanged(), so every settings tweak from the web UI does +// a full-struct EEPROM commit (flash wear consideration: frequent single-field changes each cost a +// full-struct flash write, not just the changed bytes). +void saveEEPROM() { + EEPROM.put(EEPROM_addr, prm); + EEPROM.commit(); + DPRINTLN("Settings saved to EEPROM!"); +} + +// Resets every prm.* field to its hard-coded default (falling back to clocks.h-provided compile-time +// defaults like DEFAULT_SSID/AP_NAME/DATE_START/etc where available), then saves and re-syncs the +// clock. Triggered by: EEPROM magic-number mismatch at boot (see setup()), the web admin's +// POST /factoryreset, or (if wired) a held FACTORYRESET_PIN button at boot. +void factoryReset() { + DPRINTLN("Factory Reset!!!"); + prm.alarmEnable = false; + prm.alarmHour = 7; + prm.alarmMin = 0; + prm.alarmPeriod = 15; + prm.rgbEffect = 2; + prm.rgbBrightness = 80; + prm.rgbFixColor = 150; + prm.rgbSpeed = 50; + prm.rgbDir = 0; + prm.wifiMode = true; + #ifdef DEFAULT_SSID + strncpy(prm.wifiSsid,DEFAULT_SSID,sizeof(prm.wifiSsid)); + #else + strcpy(prm.wifiSsid,""); + #endif + #ifdef DEFAULT_PSW + strncpy(prm.wifiPsw,DEFAULT_PSW,sizeof(prm.wifiPsw)); + #else + strcpy(prm.wifiPsw,""); + #endif + for (int i=0;i<(int)strlen(prm.ApSsid);i++) { //repair bad chars in AP SSID + if ((prm.ApSsid[i]<32) || (prm.ApSsid[i]>126)) prm.ApSsid[i]='_'; + } + strncpy(prm.ApSsid,AP_NAME,sizeof(prm.ApSsid)); + strncpy(prm.ApPsw,AP_PASSWORD,sizeof(prm.ApPsw)); + strncpy(prm.NtpServer,"pool.ntp.org",sizeof(prm.NtpServer)); + strcpy(prm.mqttBrokerAddr,"10.10.0.202"); + strcpy(prm.mqttBrokerUser,"mqtt"); + strcpy(prm.mqttBrokerPsw,"mqtt"); + strncpy(prm.firmwareServer,FIRMWARE_SERVER,sizeof(prm.firmwareServer)); + prm.mqttEnable = false; + prm.mqttBrokerRefresh = 10; //sec + prm.utc_offset = 1; + prm.enableDST = false; // Flag to enable DST (summer time...) + prm.set12_24 = true; // Flag indicating 12 vs 24 hour time (false = 12, true = 24); + prm.showZero = true; // Flag to indicate whether to show zero in the hour ten's place + prm.enableBlink = true; // Flag to indicate whether center colon should blink + prm.interval = 15; // prm.interval in minutes, with 0 = off + prm.enableAutoShutoff = true; // Flag to enable/disable nighttime shut off + prm.dayHour = 7; + prm.dayMin = 0; + prm.nightHour = 20; + prm.nightMin = 0; + prm.dayBright = MAXBRIGHTNESS; + prm.nightBright = 3; + prm.animMode = 6; + prm.dateMode = 2; // 0:dd/mm/yyyy 1:mm/dd/yyyy 2:yyyy/mm/dd + prm.tempCF = false; //Temperature Celsius / Fahrenheit + prm.enableTimeDisplay = ENABLE_CLOCK_DISPLAY; + prm.dateStart = DATE_START; //Date is displayed start..end + prm.dateEnd = DATE_END; + prm.tempStart = TEMP_START; //Temperature display start..end + prm.tempEnd = TEMP_END; + prm.humidStart = HUMID_START; //Humidity% display start..end + prm.humidEnd = HUMID_END; + prm.dateRepeatMin = DATE_REPEAT_MIN; //show date only every xxx minute. If zero, the date is never displayed! + prm.tempRepeatMin = 1; + #ifdef DOUBLE_BLINK + prm.enableDoubleBlink = true; //both separator points are blinking (6 or 8 tubes VFD clock) + #else + prm.enableDoubleBlink = false; + #endif + prm.enableAutoDim = false; //Automatic dimming by luxmeter + prm.enableRadar = false; //Radar sensor + prm.radarTimeout = 5; //min + prm.corrT0 = 0; + prm.corrT1 = 0; + prm.corrH0 = 0; + prm.corrH1 = 0; + prm.magic = MAGIC_VALUE; //magic value to check the EEPROM version + saveEEPROM(); + calcTime(); +} + + +// Scheduled/automatic anti-cathode-poisoning animation: every tube counts rapidly through 0-9, then +// tubes stop one-by-one (in the order controlled by `dir`) once they reach their real target digit +// value (`tmp[]`, the display content saved at entry). dir<0 stops digits left-to-right (index 0 +// first), dir>0 stops them right-to-left (highest index first), dir==0 is meant to stop them in random +// order - see the POSSIBLE BUG note below. Called automatically every prm.interval minutes from +// timeProgram(), and also reachable via the commented-out `newCathodeProtect(5000,0);` line at the end +// of setup(). Distinct from the simpler, manually-triggered doCathodeProtect() above. +void newCathodeProtect(unsigned long t,int dir) { //t = time in msec, dir = direction -1,0,1 (0=random) + byte tmp[10]; + boolean tmpDP[10]; + unsigned long started = millis(); + boolean finish, stopThis; + byte fin[10]; + byte nextStoppedDigit; + int sum = 0; + + cathodeProtRunning = true; + DPRINT("Cathode Protect running! dir:"); DPRINTLN(dir); + memcpy(tmp,digit,sizeof(tmp)); + memcpy(tmpDP,digitDP,sizeof(tmpDP)); + memset(fin,0,sizeof(fin)); + switch (dir) { + case -1: + nextStoppedDigit = 0; + break; + case 0: + nextStoppedDigit = random(maxDigits); + break; + case 1: + nextStoppedDigit = maxDigits-1; + break; + } + // POSSIBLE BUG: this line unconditionally overwrites whatever the switch above just computed, + // including the `case 0` (dir==0, "random") result - so the FIRST digit to stop is never actually + // random, it's always index 0 (dir<=0) or maxDigits-1 (dir>0), identical to the dir<0/dir>0 cases. + // The random-order behavior only actually shows up LATER, in the `else if (sum<=maxDigits-2)` branch + // below that picks each SUBSEQUENT stop digit randomly - so dir==0 does look random from the second + // stopped digit onward, just not on the very first one, which is likely not what was intended. + nextStoppedDigit = dir>0 ? 0 : maxDigits-1; + while(true) { + for (int i=0;i= t); + stopThis = (i == nextStoppedDigit); + + if (finish && stopThis) { //this digit stops + fin[i] = 1; + digitDP[i] = tmpDP[i]; //restore original DP value + digit[i] = tmp[i]; + t +=1000; + if (dir>0) { //find next stop digit + nextStoppedDigit++; + if (nextStoppedDigit>=maxDigits) nextStoppedDigit =0; + } + else if (dir<0) { + nextStoppedDigit--; + if (nextStoppedDigit==255) nextStoppedDigit = maxDigits-1; + } + else if (sum<=maxDigits-2) { + while(true) { + nextStoppedDigit = random(maxDigits); + if (fin[nextStoppedDigit] ==0) break; //found a running digit + } + } + } + else if (fin[i]==0) { //get next number + if (dir>=0) + digit[i] = (digit[i]+1) % 10; + else { + digit[i]--; if (digit[i] == 255) digit[i] = 9; + } + digitDP[i] = ((digit[i]%2) == 0); + } + } //end for + //DPRINT("lastStoppedDigit:"); DPRINT(lastStoppedDigit); + //DPRINT(" nextStoppedDigit:"); DPRINTLN(nextStoppedDigit); + + writeDisplaySingle(); + Fdelay(100); + sum = 0; + for (int i=0;i= maxDigits) //all digits ready + break; + if ((millis()-started) >= (t+5000)) { + DPRINTLN("Safety exit"); + break; + } + } //end while + + memcpy(oldDigit, digit, sizeof(oldDigit)); + lastCathodeProt = minute(); + cathodeProtRunning = false; +} + +// NOTE: earlier/simpler cathode-protect implementation, superseded by newCathodeProtect() above +// (kept for reference/comparison - "original version"). Not called from anywhere else in this file; +// appears to be dead code left in place. +void cathodeProtect() { //original version + int hour12_24 = prm.set12_24 ? (byte)hour() : (byte)hourFormat12(); + byte hourShow = (byte)hour12_24; + byte minShow = (byte)minute(); + byte secShow = (byte)second(); + byte dh1 = (hourShow / 10), dh2 = (hourShow % 10); + byte dm1 = (minShow / 10), dm2 = (minShow % 10); + byte ds1 = (secShow / 10), ds2 = (secShow % 10); + + DPRINTLN("Cathode Protect running!"); + // All four digits will increment up at 10 Hz. + // At T=2 sec, individual digits will stop at + // the correct time every second starting from + // the right and ending on the left + for (int i = 0; i <= 70; i++) { + if (i >= 20) ds2 = (secShow % 10); + if (i >= 30) ds1 = (secShow / 10); + if (i >= 40) dm2 = (minShow % 10); + if (i >= 50) dm1 = (minShow / 10); + if (i >= 60) dh2 = (hourShow % 10); + if (i == 70) dh1 = (hourShow / 10); + if (maxDigits >= 8) { + digit[7] = dh1; + digit[6] = dh2; + digit[5] = 11; + digit[4] = dm1; + digit[3] = dm2; + digit[2] = 11; + digit[1] = ds1; + digit[0] = ds2; + } + else if (maxDigits == 6) { + digit[5] = dh1; + digit[4] = dh2; + digit[3] = dm1; + digit[2] = dm2; + digit[1] = ds1; + digit[0] = ds2; + } + else { + digit[3] = dh1; + digit[2] = dh2; + digit[1] = dm1; + digit[0] = dm2; + } + incMod10(dh1); incMod10(dh2); + incMod10(dm1); incMod10(dm2); + incMod10(ds1); incMod10(ds2); + for (int d=0;d=0) newDigit[digPtr] = PLUS_CHARCODE; + if (t<0) newDigit[digPtr] = MINUS_CHARCODE; + digPtr--; + #endif + t = abs(t); + newDigit[digPtr] = t / 10; + if (newDigit[digPtr] == 0) newDigit[digPtr] = 10; //BLANK!!! + newDigit[--digPtr] = int(t) % 10; + digitDP[digPtr] = true; + newDigit[--digPtr] = int(t * 10) % 10; + if ((maxDigits > 5) && (GRAD_CHARCODE >= 0)) { + digPtr -=1; + newDigit[digPtr] = GRAD_CHARCODE; //grad + } + if (TEMP_CHARCODE >= 0) { + if (prm.tempCF) newDigit[--digPtr] = TEMP_CHARCODE+5; // 'F' + else newDigit[--digPtr] = TEMP_CHARCODE; // 'C' + } + + if (prm.animMode == 0) memcpy(oldDigit, newDigit, sizeof(oldDigit)); //don't do animation + colonBlinkState = true; + decimalpointON = true; +} +#endif + +#ifdef DISPLAYHUMID_ONLY_2DIGITS +void displayHumid(byte ptr) { //special version, 2 digits only + float h = humid[ptr]; + if (ptr==0) h = round(h + prm.corrH0); + if (ptr==1) h = round(h + prm.corrH1); + + int digPtr = 3; //tube number: first digit of minutes (hours:5,4 minutes:3,2 seconds: 1,0 ) + #ifdef HUMID_START_POSITION + digPtr = HUMID_START_POSITION; + #endif + for (int i = 0; i < maxDigits; i++) { //clear display + digitDP[i] = false; + newDigit[i] = 10; + } + + newDigit[digPtr] = h / 10; + if (newDigit[digPtr] == 0) newDigit[digPtr] = 10; //BLANK, if zero + newDigit[--digPtr] = int(h) % 10; + + if (prm.animMode == 0) memcpy(oldDigit, newDigit, sizeof(oldDigit)); //don't do animation + colonBlinkState = false; + decimalpointON = false; +} + +#else +void displayHumid(byte ptr) { + int digPtr = maxDigits-1; + #ifdef HUMID_START_POSITION + digPtr = HUMID_START_POSITION; + #endif + for (int i = 0; i < maxDigits; i++) { + digitDP[i] = false; + newDigit[i] = 10; + } + float h = humid[ptr]; + if (ptr==0) h += prm.corrH0; + if (ptr==1) h += prm.corrH1; + #if defined(PLUS_CHARCODE) || defined(MINUS_CHARCODE) //leading sign + digPtr--; + #endif + newDigit[digPtr] = int(h) / 10; + if (newDigit[digPtr] == 0) newDigit[digPtr] = 10; //BLANK if zero!!! + newDigit[--digPtr] = int(h) % 10; + digitDP[digPtr] = true; + newDigit[--digPtr] = int(h * 10) % 10; + if (maxDigits > 5) { + if (digitsOnly) { + newDigit[--digPtr] = 10; //empty character + newDigit[--digPtr] = PERCENT_CHARCODE; // "%" + } + else { + newDigit[--digPtr] = 10; //empty character + newDigit[--digPtr] = UPPER_CIRCLE_CHARCODE; //upper circle = 16 + newDigit[--digPtr] = LOWER_CIRCLE_CHARCODE; // lower circle = 18 + } + } + else { //4 tubes only + if (digitsOnly) { + newDigit[--digPtr] = PERCENT_CHARCODE; // "%" + } + else { + for (int i = 0; i < maxDigits; i++) {digitDP[i] = false;} //clear any decimal point + newDigit[digPtr] = UPPER_CIRCLE_CHARCODE; //upper circle = 16 + newDigit[--digPtr] = LOWER_CIRCLE_CHARCODE; // lower circle = 18 + } + } + + if (prm.animMode == 0) memcpy(oldDigit, newDigit, sizeof(oldDigit)); //don't do animation + colonBlinkState = true; + decimalpointON = true; +} +#endif + +// Renders the current calendar date into newDigit[] according to prm.dateMode (0=dd/mm/yyyy, +// 1=mm/dd/yyyy, 2=yyyy/mm/dd), truncating the year portion on clocks with fewer than 8 digit +// positions (>6 digits shows a 2-digit year, <=6 shows none) - the `p[m][i]` lookup table encodes +// which date component (0=day,1=month,2=year) goes in which position for each mode. +void displayDate() { + byte m = prm.dateMode; + if (m>2) m=2; + byte p[3][8] = + { //tube# 76543210 543210 3210 + {2,1,0}, //ddmmyyyy ddmmyy ddmm + {2,0,1}, //mmddyyyy mmddyy mmdd + {0,1,2} //yyyymmdd yymmdd mmdd + }; + + int t = 0; + if (thermometerClock) t = 1; + for (int i=0;i<3;i++) { + if (p[m][i] == 2) { + if (!thermometerClock) { + if (maxDigits>4) { + newDigit[t++] = year() % 10; + newDigit[t++] = (year() % 100) / 10; + } + if (maxDigits>6) { + newDigit[t++] = (year() % 1000) / 100; + newDigit[t++] = year() / 1000; + } + } //!thermometerClock + } + if (p[m][i] == 1) { + newDigit[t++] = month() % 10; + newDigit[t++] = month() / 10; + } + if (p[m][i] == 0) { + newDigit[t++] = day() % 10; + newDigit[t++] = day() / 10; + } + } //end for + if ((maxDigits>6) && (m<2)) { + digitDP[4] = true; + digitDP[6] = true; + } + else { + if (thermometerClock) { + digitDP[3] = true; + newDigit[0] = 10; + newDigit[5] = 10; + } + else { + digitDP[2] = true; + digitDP[4] = true; + } + } + colonBlinkState = true; + if (prm.animMode == 1) memcpy(oldDigit, newDigit, sizeof(oldDigit)); //don't do animation +} + +// displayTime4/6/8() are the top-level "what goes on the display this second" dispatchers, selected by +// timeProgram() based on maxDigits. Each one first blanks digitDP[], sets the colon-separator DP +// positions, then dispatches by priority to temperature/humidity/date, falling through to the normal +// H:M(:S) clock rendering only if none of those "special screens" is currently active AND +// prm.enableTimeDisplay is set (a thermometer-only build can disable the clock face entirely). +// showZero controls whether a leading-zero hour (e.g. "07") shows the 0 or blanks it (digit value 10). +void displayTime4() { + for (int i = 0; i < maxDigits; i++) digitDP[i] = false; + digitDP[4] = true; digitDP[2] = true; + int hour12_24 = prm.set12_24 ? (byte)hour() : (byte)hourFormat12(); + if (showTemp1) displayTemp(1); + else if (showTemp0) displayTemp(0); + else if (showHumid1) displayHumid(1); + else if (showHumid0) displayHumid(0); + else if (showDate && prm.enableTimeDisplay) displayDate(); + else if (prm.enableTimeDisplay) { + showClock = true; + newDigit[3] = hour12_24 / 10; + if ((!prm.showZero) && (newDigit[3] == 0)) newDigit[3] = 10; + newDigit[2] = hour12_24 % 10; + newDigit[1] = minute() / 10; + newDigit[0] = minute() % 10; + if (prm.enableBlink && (second() % 2 == 0)) digitDP[2] = false; + } +} + + +// 6-digit variant of displayTime4() above - has an extra branch for `thermometerClock` builds (see +// THERMOMETER_CLOCK in clocks.h: first digit reserved for +/- sign, last for C/F, middle 4 for HH:MM) +// vs the normal 6-tube H:M:S layout. +void displayTime6() { + for (int i = 0; i < maxDigits; i++) digitDP[i] = false; + digitDP[4] = true; digitDP[2] = true; + int hour12_24 = prm.set12_24 ? (byte)hour() : (byte)hourFormat12(); + if (showTemp1) displayTemp(1); + else if (showTemp0) displayTemp(0); + else if (showHumid1) displayHumid(1); + else if (showHumid0) displayHumid(0); + else if (showDate && prm.enableTimeDisplay) displayDate(); + else if (prm.enableTimeDisplay) { + showClock = true; + if (thermometerClock) { + newDigit[4] = hour12_24 / 10; + if ((!prm.showZero) && (newDigit[4] == 0)) newDigit[4] = 10; + newDigit[3] = hour12_24 % 10; + newDigit[2] = minute() / 10; + newDigit[1] = minute() % 10; + newDigit[0] = 10; + newDigit[5] = 10; + if (prm.enableBlink && (second() % 2 == 0)) digitDP[3] = false; + } + else { //normal clock + newDigit[5] = hour12_24 / 10; + if ((!prm.showZero) && (newDigit[5] == 0)) newDigit[5] = 10; + newDigit[4] = hour12_24 % 10; + newDigit[3] = minute() / 10; + newDigit[2] = minute() % 10; + if (prm.enableBlink && (second() % 2 == 0)){ + digitDP[2] = false; + if (prm.enableDoubleBlink) { + digitDP[4] = false; + } + } + newDigit[1] = second() / 10; + newDigit[0] = second() % 10; + } + } +} + +// 8-digit variant of displayTime4() above - full H:M:S with dash separators (digit value 11, see the +// BUFSIZE encoding comment near the digit[] declaration) instead of decimal points between the groups. +void displayTime8() { + for (int i = 0; i < maxDigits; i++) { + digitDP[i] = false; + newDigit[i] = 10; + } + int hour12_24 = prm.set12_24 ? (byte)hour() : (byte)hourFormat12(); + if (showTemp1) displayTemp(1); + else if (showTemp0) displayTemp(0); + else if (showHumid1) displayHumid(1); + else if (showHumid0) displayHumid(0); + else if (showDate && prm.enableTimeDisplay) displayDate(); + else if (prm.enableTimeDisplay) { + showClock = true; + newDigit[8] = 10; //sign digit = BLANK + newDigit[7] = hour12_24 / 10; + if ((!prm.showZero) && (newDigit[7] == 0)) newDigit[7] = 10; + newDigit[6] = hour12_24 % 10; + newDigit[5] = 11; //- sign + newDigit[4] = minute() / 10; + newDigit[3] = minute() % 10; + newDigit[2] = 11; //- sign + if (prm.enableBlink && (second() % 2 == 0)) { + newDigit[2] = 10; //BLANK + if (prm.enableDoubleBlink) { + newDigit[5] = 10; //BLANK + } + } + newDigit[1] = second() / 10; + newDigit[0] = second() % 10; + } +} + + +// The driver-independent animation engine: compares newDigit[] (the target the current displayTimeN()/ +// displayDate()/displayTemp()/displayHumid() call just computed) against oldDigit[] (what was on +// screen before) and, if anything changed, plays the animation selected by prm.animMode (0-7, see +// docs/DEVELOPER_GUIDE.md section 4.5): +// 0 = no animation, instant switch +// 1 = each changed tube "rolls" through the digits from its old value up to its new one, one tube at a time +// 2 = all changed tubes spin through 0-9 together, in lockstep +// 3/4 = whole digit-row slides in from the right/left (with a "----" gap between old and new content) +// 5 = "wipe" - drives animMask[] with a ramp value per changed tube; interpretation is driver-specific +// (some multiplex-family drivers get a finer 1..19 ramp, others a coarser 1..5-then-back-down ramp) +// 6 = picks a random animation from 1-5 on every call +// 7 = picks a random animation from 1-4 on every call (excludes the wipe animation) +// Special characters (value >9, e.g. blank/dash/custom glyphs) are always applied immediately outside +// the animation (never rolled through like a numeric digit change) since there's no sensible +// "in-between" glyph to animate through. On clocks with >4 digits, the seconds tube (index 0) is +// excluded from most animations (`j=1`) so a normal ticking seconds digit doesn't animate every second. +void changeDigit() { + int j = 0; + byte tmp[50]; + byte space = 4; + byte anim; + + #if defined(TUBE1CLOCK) //no tube animation possible + writeDisplaySingle(); + return; + #endif + anim = prm.animMode; + #if defined(DISABLE_NIGHT_ANIMATION) + if (!displayON) anim = 0; //At NIGHT switch off animation + #endif + #if defined(WORDCLOCK) + anim = 0; + #endif + + if (anim == 6) anim = 1 + rand() % 5; + else if (anim == 7) anim = 1 + rand() % 4; + + if (anim != 5) { + for (int i = 0; i < maxDigits; i++) + if ((newDigit[i] > 9) || ((oldDigit[i] > 9) && (newDigit[i] <= 9) )) { + digit[i] = newDigit[i]; //show special characters ASAP or if special char changes to numbers + oldDigit[i] = newDigit[i]; + } + if ((maxDigits > 4) && (newDigit[0] != 0)) j = 1; //if 6 or 8 tube clock, don't play (animate) the seconds digit + } + + if (memcmp(newDigit + j, oldDigit + j, maxDigits - j) != 0) { + switch (anim) { + case 1: + for (int tube = j; tube < maxDigits; tube++) { + if ((newDigit[tube] != oldDigit[tube]) && (newDigit[tube] <= 9)) { + for (int i = oldDigit[tube]; i <= int(newDigit[tube] + 10); i++) { + digit[tube] = i % 10; + writeDisplaySingle(); + Fdelay(ANIMSPEED); + } //end for i + writeDisplaySingle(); + } //endif + } //end for tube + break; + case 2: + for (int i = 0; i <= 9; i++) { + for (int tube = j; tube < maxDigits; tube++) { + if ((newDigit[tube] != oldDigit[tube]) && (newDigit[tube] <= 9)) + digit[tube] = (oldDigit[tube] + i) % 10; + } //end for tube + writeDisplaySingle(); + Fdelay(ANIMSPEED); + } //end for i + break; + case 3: + memcpy(tmp, oldDigit, maxDigits); + memcpy(tmp + maxDigits + space, newDigit, maxDigits); + memset(tmp + maxDigits, 11, space); //---- + for (int i = 0; i < maxDigits + space; i++) { + for (int tube = 0; tube < maxDigits; tube++) { + digit[tube % maxDigits] = tmp[i + tube]; + } //end for tube + writeDisplaySingle(); + Fdelay(ANIMSPEED); + } //end for i + break; + case 4: + memcpy(tmp, newDigit, maxDigits); + memcpy(tmp + maxDigits + space, oldDigit, maxDigits); + memset(tmp + maxDigits, 11, space); //---- + for (int i = maxDigits - 1 + space; i >= 0; i--) { + for (int tube = 0; tube < maxDigits; tube++) { + digit[tube % maxDigits] = tmp[i + tube]; + } //end for tube + writeDisplaySingle(); + Fdelay(ANIMSPEED); + } //end for i + break; + case 5: + // "Wipe" animation - two different ramp shapes depending on which driver is active, since + // different segment/multiplex hardware interprets the animMask[] progress value differently: + // the SN74141-family/VQC10/HV5122 drivers get a fine 1..19 monotonic ramp (their own + // writeDisplay*() decodes finer wipe steps), everything else gets a coarser 1..5-then-5..1 + // "ramp up then back down" pattern, with digit[] snapped to newDigit[] partway through. + memset(animMask, 0, sizeof(animMask)); +#if defined(MULTIPLEX74141) || defined(NO_MULTIPLEX74141) || defined(MULTIPLEX74141_ESP32) || defined(MULE_V2) || defined(PCF_74141) || defined(NO_MULTIPLEX_ESP32) || defined(VQC10) || defined(NEWHV5122) + for (int i = 1; i < 20; i++) { + for (int tube = j; tube < maxDigits; tube++) { + if (oldDigit[tube] != newDigit[tube]) animMask[tube] = i; //digit is changed + } //end for tube + writeDisplaySingle(); + Fdelay(ANIMSPEED); + } //end for i +#else + for (int i = 1; i <= 5; i++) { + for (int tube = j; tube < maxDigits; tube++) { + if (oldDigit[tube] != newDigit[tube]) animMask[tube] = i; //digit is changed + } //end for tube + writeDisplaySingle(); + Fdelay(ANIMSPEED); + } //end for i + memcpy(digit, newDigit, sizeof(digit)); + for (int i = 1; i <= 5; i++) { + for (int tube = j; tube < maxDigits; tube++) { + if (oldDigit[tube] != newDigit[tube]) animMask[tube] = 6 - i; //digit is changed + } //end for tube + writeDisplaySingle(); + Fdelay(ANIMSPEED); + } //end for i +#endif + memset(animMask, 0, sizeof(animMask)); + break; + } + } //endif memcmp + + memcpy(digit, newDigit, sizeof(digit)); + memcpy(oldDigit, newDigit, sizeof(oldDigit)); + writeDisplaySingle(); +} + +// Drives ALARMSPEAKER_PIN, but only actually calls digitalWrite() when the requested state differs +// from what's already set (oldState) - avoids redundant GPIO writes on every alarmSound() tick. +void writeAlarmPin(boolean newState) { + #if ALARMSPEAKER_PIN>=0 + static boolean oldState = !ALARM_ON; + if (oldState != newState) { + oldState = newState; + //if (newState == ALARM_ON) DPRINTLN("Alarm ON"); else DPRINTLN("Alarm OFF"); + digitalWrite(ALARMSPEAKER_PIN, newState); + } + #endif +} + +// Called every loop() iteration. Non-blocking alarm-tone state machine: when the wall-clock time +// matches prm.alarmHour:prm.alarmMin (checked once, in the first 3 seconds of that minute, to avoid +// re-triggering all through the minute), it starts a beep pattern defined by the `t[]` timestamp table +// below (an escalating series of short beeps, in milliseconds relative to alarmStarted) that repeats +// beyond the table's end as plain 500ms on/off beeping, for prm.alarmPeriod seconds total. Can be +// silenced early via ALARMBUTTON_PIN if wired. +void alarmSound(void) { + static const unsigned int t[] = {0, 3000, 6000, 6200, 9000, 9200, 9400, 12000, 12200, 12400, 15000, 15200, 15400}; + static int count = 0; + static unsigned long nextEvent; + const int cMax = sizeof(t) / sizeof(t[0]); //number of time steps + + if (prm.alarmEnable) { + if ( (!alarmON && prm.alarmHour == hour()) && (prm.alarmMin == minute()) && (second() <= 3)) { //switch ON alarm sound + DPRINTLN("Alarm started!"); + alarmON = true; + alarmStarted = millis(); + count = 0; + nextEvent = alarmStarted - 1; + } + } + else { + alarmON = false; + } + + if (!alarmON) { + writeAlarmPin(!ALARM_ON); + return; //nothing to do + } + + if ((millis() - alarmStarted) > 1000 * (long)prm.alarmPeriod) { + alarmON = false; //alarm period is over + darkenNeopixels(); + DPRINTLN("Alarm ended."); + } + + if (ALARMBUTTON_PIN >= 0) { //is button installed? + if (digitalRead(ALARMBUTTON_PIN) == LOW) { //stop alarm + DPRINTLN("Alarm stopped by button press."); + alarmON = false; + } + } + + if (!prm.alarmEnable || !alarmON) { //no alarm, switch off + writeAlarmPin(!ALARM_ON); + return; + } + + //-------- Generate sound -------------------------------- + + if (millis() > nextEvent) { //go to the next event + if (count % 2 == 0) { + nextEvent += 500; + writeAlarmPin(ALARM_ON); + //DPRINT(" Sound ON"); DPRINT(" Next:"); DPRINTLN(nextEvent); + } + else { + writeAlarmPin(!ALARM_ON); + nextEvent = (count / 2 < cMax) ? alarmStarted + t[count / 2] : nextEvent + 500; + //DPRINT(" OFF"); DPRINT(" Next:"); DPRINTLN(nextEvent); + } + count++; + } +} + + +// Called once per second from timeProgram(). Decides displayON (day/night brightness state) from the +// prm.dayHour:dayMin / prm.nightHour:nightMin schedule, handling the case where the night window wraps +// past midnight (mn_off > mn_on, e.g. night starts 22:00 and ends 07:00) vs. the (unusual) case where +// it doesn't. manualOverride (set by the web UI's "manualOverride" key in handleConfigChanged()) lets +// the user force displayON one way regardless of the schedule; that override is automatically cleared +// again the moment the schedule's own computed state (prevShutoffState) naturally transitions to match +// what the override had forced - i.e. a manual override lasts until the next scheduled day/night +// boundary, not forever. +void evalShutoffTime(void) { // Determine whether tubes should be turned to NIGHT mode + + if (!prm.enableAutoShutoff) return; + + int mn = 60 * hour() + minute(); + int mn_on = prm.dayHour * 60 + prm.dayMin; + int mn_off = prm.nightHour * 60 + prm.nightMin; + + static bool prevShutoffState = true; + if ( (((mn_off < mn_on) && (mn >= mn_off) && (mn < mn_on))) || // Nighttime + ((mn_off > mn_on) && ((mn >= mn_off) || (mn < mn_on)))) { + if (!manualOverride) displayON = false; + if (prevShutoffState == true) manualOverride = false; + prevShutoffState = false; + } + else { // Tubes should be on + if (!manualOverride) displayON = true; + if (prevShutoffState == false) manualOverride = false; + prevShutoffState = true; + } + return; + // POSSIBLE BUG / NOTE: unconditional `return` above makes the debug DPRINT block below permanently + // unreachable dead code - harmless (it only ever fed DPRINT, a no-op without DEBUG), but the + // early-return means these diagnostics can never be re-enabled just by turning DEBUG on; the `return` + // itself would also need removing. + DPRINT("mn="); DPRINT(mn); + DPRINT(" mn_on="); DPRINT(mn_on); + DPRINT(" mn_off="); DPRINT(mn_off); + DPRINT(" manOverride:"); DPRINT(manualOverride); + DPRINT(" displayON:"); DPRINTLN(displayON); + DPRINT(" enableBlink:"); DPRINTLN(prm.enableBlink); + DPRINT(" blinkState:"); DPRINTLN(colonBlinkState); +} + + +// Renders a single IP-address octet (0-255) as a 3-digit number, optionally preceded by "IP" glyphs +// on displays with an alphabetic character set and >=6 digit positions, and shows it once via +// changeDigit(). Called 4 times in a row by showMyIp() below, once per octet. +void writeIpTag(byte iptag) { + +#ifdef SHIFT_TUBES_LEFT_BY_1 + byte offset = 1; +#else + byte offset = 0; +#endif + + memset(newDigit, 10, sizeof(newDigit)); + if (!digitsOnly && (maxDigits >= 6)) { + newDigit[5] = 19; //'I' + newDigit[4] = 14; //'P' + } + //if (iptag >= 100) + newDigit[2 + offset] = iptag / 100; + newDigit[1 + offset] = (iptag % 100) / 10; + newDigit[0 + offset] = iptag % 10; + memcpy(digit,newDigit,sizeof(digit)); + printDigits(0); + changeDigit(); +} + +// Called once at the end of setup(): displays the clock's IP address on the tubes, one octet at a +// time (each shown for SPEED=1500ms via writeIpTag()), so the user knows what address to browse to +// for the web admin UI without needing a router's client list or a phone app. +void showMyIp(void) { //at startup, show the web page internet address + clearDigits(); + #define SPEED 1500 + for (int i=0;i<4;i++) { + ipShowRunning = true; + writeIpTag(ip[i]); + Fdelay(SPEED); + } + ipShowRunning = false; +} + + +// NOTE: converts a "number of 15-minute steps since midnight" value into hours+minutes - not called +// anywhere in this file or elsewhere in the project; dead/unused helper. +void fifteenMinToHM(int &hours, int &minutes, int fifteenMin) +{ + hours = fifteenMin / 4; + minutes = (fifteenMin % 4) * 15; +} + +// Called from loop() whenever prm.wifiMode is true but WiFi.status()!=WL_CONNECTED. Self-throttled to +// try at most once every 10 minutes: first attempts a lightweight WiFi.disconnect()+WiFi.reconnect() +// (reuses previously stored credentials), and only falls back to the heavier startWifiMode() (which +// can itself open the wifiManager() config portal) if that quick reconnect didn't succeed. +void resetWiFi(void) { + static unsigned long lastTest = millis(); + static unsigned int counter = 0; + + if ((millis() - lastTest) < 600000) return; //check in every 10 min + lastTest = millis(); + DPRINTLN("Lost WiFi. Reconnect."); + //#if defined(ESP8266) + WiFi.disconnect(); + WiFi.reconnect(); + //#endif + if (WiFi.status() == WL_CONNECTED) return; + + //findBestWifi(); + startWifiMode(); + return; + // POSSIBLE BUG / NOTE: unconditional `return` above makes everything below permanently unreachable - + // including a 3-hour "give up and force a full reboot" counter/message. As written, resetWiFi() will + // retry forever (every 10 minutes) without ever calling doReset(), no matter how long WiFi stays + // down. Likely intentional (a full reboot loop might have been judged riskier than an endless retry), + // but flagged as dead code worth reviewing - see docs/CODE_REVIEW.md. + counter++; + DPRINT("Wifi lost! Minutes to restart:"); DPRINTLN(180 - counter); + if (counter < 180) return; //3 hours passed + + DPRINTLN("Restart Clock..."); + delay(1000); + doReset(); +} + +// NOTE: proper (always-non-negative) modulo helper - not called anywhere in this file or elsewhere in +// the project; dead/unused helper. +inline int mod(int a, int b) { + int r = a % b; + return r < 0 ? r + b : r; +} + + +// Power-on self-test animation: sweeps every tube through 0-9 in unison (called once from setup()). +void testTubes(int dely) { + Fdelay(dely); + cathodeProtRunning = true; + DPRINT("Testing tubes: "); + for (int i = 0; i < 10; i++) { + DPRINT(i); DPRINT(" "); + for (int j = 0; j < maxDigits; j++) { + newDigit[j] = i; + // POSSIBLE BUG: likely meant to be `digit[j] = i;` (j is the tube-index loop variable here; i is + // the 0..9 character being swept through). As written this indexes digit[] by the CHARACTER + // value i (0..9) instead of the tube position j, so it writes into at most the first 10 entries + // of digit[] regardless of which tube is being iterated - harmless in practice only because + // changeDigit() (called right below, every outer-loop iteration) immediately overwrites the + // whole digit[] buffer from newDigit[] anyway, so this line's result is never actually rendered. + digit[i] = i; + digitDP[j] = i % 2; + } + changeDigit(); + Fdelay(dely); + } + DPRINTLN(" "); + Fdelay(1000); + memset(digitDP, 0, sizeof(digitDP)); + cathodeProtRunning = false; +} + +// NOTE: cycles a rotating "walking digit" pattern across the tubes - not called from setup()/loop() or +// anywhere else in this file; dead/unused (a leftover manual test helper, presumably invoked ad hoc +// during development). +void playTubes() { + static byte tube = 0; + memset(digitDP, 0, sizeof(digitDP)); + for (int j = 0; j < maxDigits; j++) { + newDigit[j] = tube++ % maxDigits; + } + changeDigit(); +} + +// Debug helper: dumps all temperature/humidity sensor readings to the serial console, throttled to +// once every 30s. Called from printDigits() below (itself a no-op unless DEBUG is defined). +void printSensors(void) { + static unsigned long lastRun = millis(); + + if ((millis() - lastRun) < 30000) return; + lastRun = millis(); + if (useTemp>0) { + DPRINT("Temperature ("); DPRINT(useTemp); DPRINT("): "); + for (int i=0;i0) { + DPRINT("Humidity ("); DPRINT(useHumid); DPRINT("): "); + for (int i=0;i= 0; i--) {printChar(digit[i]);} DPRINT("]"); + DPRINT(colonBlinkState ? " * " : " "); + //DPRINT("\noldDigit: "); for (int i = maxDigits - 1; i >= 0; i--) {printChar(oldDigit[i]);} + //DPRINT("\nnewDigit: "); for (int i = maxDigits - 1; i >= 0; i--) {printChar(newDigit[i]);} + + /* + if ((millis()/1000%10) == 1) { //show free memory for debugging memory leak + DPRINT("Heap:"); DPRINT(ESP.getFreeHeap()); DPRINT(" byte"); + #if defined(ESP8266) + DPRINT(" Fragmentation:"); DPRINT(100 - ESP.getMaxFreeBlockSize() * 100.0 / ESP.getFreeHeap()); DPRINT("%"); + #endif + } + */ + //DPRINT("INT:"); DPRINT(intCounter); intCounter = 0; //show multiplex interrupt counter + //DPRINT(" ESaving:"); DPRINT(EEPROMsaving); + if (useLux>0) { + DPRINT(" Lux:"); DPRINT(lx); + } + if (RADAR_PIN>=0) { + //DPRINT(" RadarPin:"); DPRINT(digitalRead(RADAR_PIN)); DPRINT(" lastOn:"); DPRINT(radarLastOn); DPRINT(" sec:"); DPRINTLN((millis()-radarLastOn)/1000); + } + //DPRINT(" tON:"); DPRINT(timerON); DPRINT(" tOFF:"); DPRINT(timerOFF); //Multiplex timing values for testing + if (WiFi.status() != WL_CONNECTED) DPRINT(" no WIFI"); + DPRINTLN(" "); + printSensors(); + #endif +} + + +// Called every loop() iteration (self-throttled to 500ms). Drives TUBE_POWER_PIN (filament/HV supply +// on/off) based on radar-detected presence: motion resets a timeout (radarLastOn), and if no motion is +// seen for prm.radarTimeout minutes the tubes' power supply is switched off entirely (a harder cutoff +// than just dimming brightness). The radar signal can come from a LOCAL RADAR_PIN sensor (if wired - +// takes priority) or from mqttRadarON (a remote/MQTT-published radar state, see mqtt.ino's master/ +// slave radar sharing). If prm.enableRadar is off, radarON is forced true (always powered) regardless +// of any radar input. +void checkTubePowerOnOff(void) { + static unsigned long lastRun = 0; + + if ((millis()-lastRun)<500) return; + lastRun = millis(); + + boolean newP = mqttRadarON; + if (RADAR_PIN >=0) { //if local RADAR is defined, it has priority + newP = digitalRead(RADAR_PIN); + } + if (!radarON && newP) { //check, is switching ON? + DPRINTLN("RADAR: Switching ON tubes."); + } + if (newP) { //restart timeout + radarLastOn = millis(); + radarON = true; + } + if (radarON && (millis()-radarLastOn)>60000ul*uint32_t(prm.radarTimeout)) { //check if should switch off? + radarON = false; + DPRINTLN("RADAR: Switching OFF tubes."); + } + //DPRINT("RadarON:"); DPRINT(radarON); DPRINT(" timeout:"); DPRINTLN((millis()-radarLastOn)); + + if (!prm.enableRadar) radarON = true; //If Radar is disabled, always ON ! + #if TUBE_POWER_PIN >=0 + if (((displayON ? prm.dayBright : prm.nightBright) == 0) || !radarON) { + digitalWrite(TUBE_POWER_PIN,!TUBE_POWER_ON); //Switch OFF + //DPRINTLN("POWER OFF"); + } + else { + digitalWrite(TUBE_POWER_PIN,TUBE_POWER_ON); //Switch ON + //DPRINTLN("POWER ON"); + } + #endif +} + + +//Calculation parameters are defined in clocks.h +//https://www.pcboard.ca/ldr-light-dependent-resistor + +// Converts a raw ADC reading from an LDR (light-dependent resistor) voltage divider into an +// approximate lux value, using the LUX_CALC_SCALAR/LUX_CALC_EXPONENT calibration constants from +// clocks.h (LDR characteristic curve) and a simple exponential smoothing filter (oldLux) to avoid +// jumpy brightness changes from ADC noise. Returns 0 (a build with no LIGHT_SENSOR_PIN configured) +// when there's no LDR wired at all - see getLightSensor() below for the BH1750 (digital lux sensor) +// alternative, which takes priority over this analog LDR path when both are present. +int luxMeter(void) { +#if LIGHT_SENSOR_PIN >=0 + static float oldLux = MAXIMUM_LUX; + float ADCdata = analogRead(LIGHT_SENSOR_PIN); //DPRINT("ADC:"); DPRINTLN(ADCdata); + float ldrResistance = (MAX_ADC_READING - ADCdata) / ADCdata * REF_RESISTANCE; + float ldrLux = LUX_CALC_SCALAR * pow(ldrResistance, LUX_CALC_EXPONENT); + if ((ldrLux>=MAXIMUM_LUX)||(ldrLux <0)) ldrLux = MAXIMUM_LUX; //Limit lux value to maximum + oldLux = oldLux + (ldrLux-oldLux)/10; //slow down Lux change + return (int)oldLux; +#else + return (0); +#endif +} + +// Called from loop()/Fdelay() (self-throttled to 500ms) to refresh the global `lx` (ambient lux) and +// `autoBrightness` (whether auto-dim is actually active) values. Prefers a BH1750 digital sensor if +// present, falls back to an analog LDR (luxMeter()) if not, and if neither exists forces a fixed +// MAXIMUM_LUX reading with autoBrightness permanently off (i.e. "auto-dim" silently has no hardware to +// act on). autoBrightness itself is only ever actually true when prm.enableAutoDim is also set by the +// user - this function just gates it on hardware presence. +void getLightSensor(void) { + static unsigned long lastRun = 0; + static int oldLx = 0; + int tmp; + if ((millis()-lastRun)<500) return; + lastRun = millis(); + + if (BH1750exist) { + tmp = getBH1750(); + if ((abs(tmp-oldLx)>1) || (tmp >= MAXIMUM_LUX)) { + lx=tmp; oldLx = lx; + } + if (lx>=MAXIMUM_LUX-2) lx = MAXIMUM_LUX; + autoBrightness = prm.enableAutoDim; + } + else if (LDRexist) { + tmp = luxMeter(); + if ((abs(tmp-oldLx)>1) || (tmp >= MAXIMUM_LUX)) { + lx=tmp; oldLx = lx; + } + if (lx>=MAXIMUM_LUX-2) lx = MAXIMUM_LUX; + autoBrightness = prm.enableAutoDim; + } + else { + lx = MAXIMUM_LUX; + autoBrightness = false; + } +} + +// NOTE: intended to let the web UI's "wifiMode" (STA vs AP) toggle switch the radio mode live, without +// a reboot - would re-run startNewWifiMode()/startStandaloneMode() whenever prm.wifiMode changes. Its +// only call site in loop() is commented out (`//checkWifiMode();`), so this is currently dead code; +// changing prm.wifiMode from the web UI today only takes effect after the next reboot/doReset(). +void checkWifiMode() { +static boolean oldMode = prm.wifiMode; + + if (oldMode != prm.wifiMode) { + if (prm.wifiMode) { + startNewWifiMode(); + } + else { + startStandaloneMode(); + } + oldMode = prm.wifiMode; + } +} + +// Debug helper: logs to serial whenever any byte of `prm` differs from the last time this was called - +// a crude way to spot unexpected/unintended settings changes during development. Its only call site in +// loop() is commented out (`//checkPrm();`), so it is currently dead code / not part of the live build. +void checkPrm() { + static byte oldPrm[sizeof(prm)]; + int tmp = memcmp(oldPrm,&prm,sizeof(prm)); + if (tmp!=0){ + DPRINT("Prm mismatch!"); DPRINTLN(tmp); + memcpy(oldPrm,&prm,sizeof(prm)); + } +} + + +// Main loop. Deliberately delay()-free (see docs/DEVELOPER_GUIDE.md section 4.2 and the Fdelay() +// comment above) - every subsystem gets serviced once per iteration: cooperative display refresh +// (writeDisplay2(), only does anything for non-ISR/"smart chip" drivers), DNS captive-portal service +// (AP mode only), the clock/sensor/display tick (timeProgram(), itself throttled to ~200ms), RGB/PWM +// backlight animations, the alarm buzzer, radar-driven tube power, the light sensor, MQTT publish (STA +// mode + WiFi connected) or WiFi reconnect attempts (STA mode + WiFi down), the RTC manual-editor +// button scan, and finally any pending OTA-update/cathode-protect request queued by the web server +// (makeFirmwareUpdate/makeCathodeProtect flags, set from server route handlers which must return +// quickly and can't run these long operations inline). +void loop(void) { + //checkPrm(); + writeDisplay2(); + if (WiFi.status() != WL_CONNECTED) + dnsServer.processNextRequest(); + timeProgram(); + //writeDisplaySingle(); + writeDisplay2(); + doAnimationMakuna(); + doAnimationPWM(); + alarmSound(); + checkTubePowerOnOff(); + getLightSensor(); + //checkWifiMode(); + if (prm.wifiMode) { //WiFi Clock Mode + if (WiFi.status() == WL_CONNECTED) { + if (prm.mqttEnable) mqttSend(); + } + else { + resetWiFi(); + } + } + editor(); + if (makeFirmwareUpdate) { + makeFirmwareUpdate = false; + doFirmwareUpdate(); + } + if (makeCathodeProtect) { + makeCathodeProtect = false; + doCathodeProtect(); + } + yield(); +} + +// Clean reboot: drops the WiFi radio (and, on ESP8266, its TX power) before calling ESP.restart(), +// which avoids leaving the radio associated to the AP mid-reset. Called after a successful OTA update, +// from the web admin's POST /reset and /factoryreset routes, and (dead-code path only, see the +// resetWiFi() NOTE above) from the unreachable end of resetWiFi(). +void doReset(void) { + DPRINTLN("Restart Clock..."); + #if defined(ESP8266) + WiFi.setOutputPower(0); + #endif + + WiFi.disconnect(); + delay(1000); + ESP.restart(); +} + +// Default empty stand-in for writeDisplay2() - the cooperative (non-ISR) "give the display a chance to +// refresh" hook called from loop()/Fdelay(). Only VQC10/TUBE1CLOCK/WORDCLOCK actually define a real +// writeDisplay2() (in their own driver .ino files) because their hardware needs cooperative, +// non-interrupt-safe timing (e.g. delayMicroseconds() during row scanning) that a hardware-timer ISR +// can't safely provide; every other driver relies purely on the ISR-based writeDisplay(), so this +// no-op keeps the call sites unconditional without needing per-driver #ifdef guards everywhere. +#if defined(VQC10) || defined(TUBE1CLOCK) || defined(WORDCLOCK) +#else + void writeDisplay2(void){} //not interrupt driven display handler +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/HV5122.ino b/firmware/uniclock/ESP32_UniClock2/HV5122.ino new file mode 100644 index 0000000..9daa4b3 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/HV5122.ino @@ -0,0 +1,303 @@ +//============================================================================ +// HV5122 Nixie tube driver (LEGACY / ORIGINAL VERSION) +//---------------------------------------------------------------------------- +// Chip: Microchip/Supertex HV5122 - 32-channel high-voltage (up to ~170V) +// serial-in shift register / latch, used here to drive Nixie tube +// cathodes directly (no external cathode-driver transistors needed). +// Wiring: Two HV5122 chips are cascaded (bit-banged, SPI-like, 3-wire bus): +// PIN_DIN -> chip0 DIN, chip0 DOUT -> chip1 DIN (daisy-chained), +// PIN_CLK -> both chips' CLK (shared), +// PIN_OE -> both chips' output-enable (shared, active HIGH here +// i.e. energizes/blanks the tubes - see writeDisplay()). +// Together the two chips give 64 open-drain HV outputs, addressed +// via the digitPins[][] table (pin numbers 1-32 = chip0, 101-132 = +// chip1; see clocks.h for the actual per-clock wiring tables). +// Status: THIS IS THE OLDER/LEGACY DRIVER. As of the current clocks.h, no +// active CLOCK_NN config selects plain "HV5122" anymore - every +// HV5122-based config (CLOCK_50, 51, 52, 53, 54, 55, 56, 57, 57A) +// now defines NEWHV5122 and is compiled from HV5122new.ino instead. +// This file is effectively unused/orphaned code, kept for +// reference. It lacks the crossfade/wipe digit-change animation +// (animMask/newDigit/oldDigit) support that HV5122new.ino adds - +// see HV5122new.ino's header comment for details. +//============================================================================ +#ifdef HV5122 //Nixie driver +//P.S. 6x tubes PCB version + +char tubeDriver[] = "HV5122"; +//HV5122 control pins +//#define PIN_DIN 17 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +//#define PIN_CLK 22 // Clock +//#define PIN_OE 21 // OutputEnable + +//int maxDigits = 6; + +//Data pin numbers 100+ means: chip1 pins are used. Chip0-s DOUT is connected to chip1's DIN +/* Example: + byte digitPins[maxDigits+1][10] = { + {2,10,9,8,7,6,5,4,3,1}, //sec 1 , chip0 (tube#0) + {11,32,20,19,18,17,16,15,14,13}, //sec 10 , chip0 (tube#1) + {22,31,29,30,27,28,25,26,23,24}, //min 1 , chip0 (tube#2) + {101,131,110,109,108,107,106,105,104,103}, //min 10 , chip1 (tube#3) + {111,132,120,119,118,117,116,115,114,113}, //hour 1 , chip1 (tube#4) + {122,129,130,127,128,125,126,123,124,121}, //hour 10 , chip1 (tube#5) + {0,12,21,102,112,0, 0,0,0,0} //extra decimal point (tube0...tube6) + }; +*/ + +#define SHIFT_LSB_FIRST false //true= LSB first, false= MSB first +int PWMrefresh = 10000; //Brightness PWM period (microseconds-ish timer ticks). Greater value => slower brightness PWM frequency +//int PWMtiming[11] = {0,500,800,1200,2000,2500,3000,4500,6000,8000,10000}; +int PWM_min = 500; //shortest allowed ON time (floor, avoids a 0-length timer alarm) +int PWM_max = 10000; //longest ON time = full brightness (== PWMrefresh, i.e. tube stays on the whole period) + +void setup_pins() { + DPRINTLN("Setup pins - HV5122 Nixie driver..."); + DPRINT("- CLK : GPIO"); DPRINTLN(PIN_CLK); + DPRINT("- DATAIN: GPIO"); DPRINTLN(PIN_DIN); + DPRINT("- OUTPUT_ENABLE: GPIO"); DPRINTLN(PIN_OE); + pinMode(PIN_CLK, OUTPUT); regPin(PIN_CLK, "PIN_CLK"); + pinMode(PIN_DIN, OUTPUT); regPin(PIN_DIN, "PIN_DIN"); + pinMode(PIN_OE, OUTPUT); regPin(PIN_OE, "PIN_OE"); + digitalWrite(PIN_CLK, HIGH); + digitalWrite(PIN_OE, LOW); //keep outputs blanked until clearTubes() below has pushed a known (all-zero) pattern into the shift registers + driverSetupStr = "
HV5122 pin settings:
"; + for (int i=0;i"); + } + clearTubes(); + startTimer(); +} + + +// writeDisplay(): TIMER-ISR. Unlike most other drivers in this project, this +// driver does NOT multiplex individual tube positions from here - the full +// digit pattern for ALL tubes was already shifted out into the HV5122 latches +// by writeDisplaySingle() (called from the main loop). This ISR only toggles +// the shared PIN_OE line on/off to implement brightness as a simple +// square-wave PWM (whole display blinks ON/OFF at a variable duty cycle - +// there is no per-tube time-slicing here since HV5122 has a real static +// latch per output, not a multiplexed matrix). +#if defined(ESP32) +void IRAM_ATTR writeDisplay() { //void IRAM_ATTR writeDisplay(){ +#else +void ICACHE_RAM_ATTR writeDisplay() { //https://circuits4you.com/2018/01/02/esp8266-timer-ticker-example/ +#endif + static volatile int timer = PWMrefresh; + static volatile boolean state=true; //true = about to enter/currently in the ON phase of the PWM cycle + static volatile int brightness; + static int PWMtimeBrightness=PWM_min; + + intCounter++; + if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! + #if defined(ESP8266) + timer1_write(PWMrefresh); + #endif + return; + } + + #if defined(ESP32) + portENTER_CRITICAL_ISR(&timerMux); + noInterrupts(); + #endif + + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + if (autoBrightness && displayON) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); //day mode + ALS: scale ON-time by measured lux instead of the stored brightness setting + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + + //At full brightness, force a steady ON state every call instead of letting the + //ON/OFF alternation run its course - avoids a needless (and here pointless, + //since ON-time==PWMrefresh anyway) OFF sliver caused by scheduling/rounding jitter. + if ((!autoBrightness) && (brightness==MAXBRIGHTNESS)) + state = true; + + if (state) { //ON state + timer = PWMtimeBrightness; + timerON = timer; + } + else { //OFF state + timer = PWMrefresh-PWMtimeBrightness; + timerOFF = timer; + } + if (timer<500) timer = 500; //safety only... (timerAlarmWrite/timer1_write with 0 or a very small value can hang/misfire) + + if ( (brightness == 0) || (!state) || (!radarON)) { //OFF state, blank digit + digitalWrite(PIN_OE, LOW); //OFF -- NOTE: on this board OE is active-HIGH (HIGH=tubes lit), opposite of the active-low /OE found on many shift-register ICs + } + else { //ON state + digitalWrite(PIN_OE, HIGH); //ON + } + + state = !state; //flip for next ISR call (alternate ON phase / OFF phase); irrelevant while the full-brightness override above forces true every time + +#if defined(ESP8266) + timer1_write(timer); +#elif defined(ESP32) + portEXIT_CRITICAL_ISR(&timerMux); + //ESP32timer = timerBegin(0, PRESCALER, true); //set prescaler, true = edge generated signal + //timerAttachInterrupt(ESP32timer, &writeDisplay, true); + timerAlarmWrite(ESP32timer, timer, true); + timerAlarmEnable(ESP32timer); + interrupts(); +#endif +} + + + +// shift(): bit-bangs one 32-bit word into the HV5122 shift-register chain. +// The ~20-NOP delays around each CLK/DIN edge are required because the +// HV5122 is a high-voltage part (internal level-shifters to the ~170V rail) +// and is much slower than a typical 5V logic shift register - driving it at +// full CPU speed without these delays can corrupt shifted-in data. +// Data is latched into the HV5122 on the falling edge of CLK. +#if defined(ESP32) +void IRAM_ATTR shift(uint32_t Data) { +#else +void ICACHE_RAM_ATTR shift(uint32_t Data) { +#endif + static boolean b; + + for (uint32_t i = 0; i < 32; i++) { + digitalWrite(PIN_CLK, HIGH); + for (int t=0;t<20;t++) asm volatile ("nop"); + if (SHIFT_LSB_FIRST) + b = ((Data & (uint32_t(1) << i))) > 0; //LSB first + else + b = (Data & (uint32_t(1) << (31 - i))) > 0; //MSB first + digitalWrite(PIN_DIN, b); + for (int t=0;t<20;t++) asm volatile ("nop"); + digitalWrite(PIN_CLK, LOW); //falling CLK to store DIN + for (int t=0;t<20;t++) asm volatile ("nop"); + } + digitalWrite(PIN_CLK, HIGH); + for (int t=0;t<20;t++) asm volatile ("nop"); +} + +void clearTubes() { + shift(0); + shift(0); +} + +// writeDisplaySingle(): builds the two 32-bit bit-buffers (one per cascaded +// HV5122 chip) from the current digit[]/digitDP[] state and shifts them out. +// Called repeatedly from the main loop (not from the ISR) and self-throttled +// to run at most every 50ms, since the bit-bang shift() above is relatively +// slow (32 bits x ~60 NOPs, twice) and re-shifting on every idle loop +// iteration would be wasteful. NOTE: unlike HV5122new.ino, this version does +// NOT check whether anything actually changed since the last call - it always +// re-shifts full data every 50ms regardless, and it ignores newDigit[]/ +// oldDigit[]/animMask[] entirely, so it has no digit-change wipe/crossfade +// animation support. +void writeDisplaySingle() { + static unsigned long lastRun = 0; + uint32_t bitBuffer0 = 0; + uint32_t bitBuffer1 = 0; + byte num; + + if ((millis() - lastRun) < 50) return; //slow down!!! + lastRun = millis(); + + bitBuffer0 = 0; bitBuffer1 = 0; + for (int i = 0; i < maxDigits; i++) { //Set the clock digits + num = digit[i]; + if (num < 10) { //codes 10 (BLANK), 11 (dash), 12 (dp glyph) and 13+ (extra glyphs) have no cathode mapping on a Nixie tube -> leave bits cleared = tube blank + if (digitPins[i][num] < 100) { + bitBuffer0 |= (uint32_t)(1 << (digitPins[i][num]-1)); + //DPRINT(digitPins[i][num]); DPRINT(":"); + } //chip0 + else { + bitBuffer1 |= (uint32_t)(1 << (digitPins[i][num] - 101)); + //DPRINT(digitPins[i][num]);DPRINT(":");} //chip1 + } + } + } //end for i + + #ifdef MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! + if (showClock) { + digitDP[1] = digitDP[2]; //left two dots + digitDP[3] = digitDP[4]; //right two dots + } + #endif + + for (int i = 0; i < maxDigits-1; i++) { //Set the extra decimal point dots + if (digitDP[i] && digitPins[maxDigits][i]>0) { + if (digitPins[maxDigits][i] < 100) + bitBuffer0 |= (uint32_t)(1 << (digitPins[maxDigits][i]-1)); //chip0 + else + bitBuffer1 |= (uint32_t)(1 << (digitPins[maxDigits][i] - 101)); //chip1 + } + } //end for i + + //bitBuffer0 = 1<32) cnt = 0; DPRINTLN(cnt); + //Shift chip1 (the far end of the DIN->DOUT daisy-chain) FIRST, then chip0 LAST: + //bits shifted in earliest travel furthest down the chain, so chip1's data must + //go in before chip0's, otherwise chip0's own data would get pushed on into chip1. + shift(bitBuffer1); + //delayMicroseconds(1); //for testing on oscilloscope + shift(bitBuffer0); + //showBits(bitBuffer1); showBits(bitBuffer0); DPRINTLN(" "); +} + +//For testing ------------------- +/* +void test() { + digitalWrite(PIN_OE, HIGH); + clearTubes(); + for (int j=0;j<2;j++) + for (uint32_t i=0;i<32;i++) { + DPRINT("D"); DPRINT(i+1); DPRINT(": "); + shift((uint32_t)(1 << (i))); + DPRINTLN((uint32_t)(1 << (i)),HEX); + delay(3000); + } +} + +void testTubes2() { +while (true) { + + DPRINTLN("Testing tubes: "); + for (int i = 0; i < 10; i++) { + DPRINT(i); DPRINT(" "); + for (int j = 0; j < maxDigits; j++) { + digit[j] = i; + digitDP[j] = i % 2; + } + //changeDigit(); + + writeDisplaySingle(); + delay(3000); + yield(); + } + } //end while +} + +void showBits(uint32_t bits) { + boolean b; + + for (uint32_t i = 0; i < 32; i++) { + if (SHIFT_LSB_FIRST) + b = ((bits & (uint32_t(1) << i))) > 0; //LSB first + else + b = (bits & (uint32_t(1) << (31 - i))) > 0; //MSB first + if (b) { + DPRINT("1"); + } + else { + DPRINT("0"); + } + } //end for + DPRINT("-"); +} + +*/ +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/HV5122new.ino b/firmware/uniclock/ESP32_UniClock2/HV5122new.ino new file mode 100644 index 0000000..7acfa08 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/HV5122new.ino @@ -0,0 +1,422 @@ +//============================================================================ +// HV5122 Nixie tube driver (CURRENT / PREFERRED VERSION - "NEW") +//---------------------------------------------------------------------------- +// Chip: Microchip/Supertex HV5122 - 32-channel high-voltage (up to ~170V) +// serial-in shift register / latch, used here to drive Nixie tube +// cathodes directly (no external cathode-driver transistors needed). +// Wiring: Two HV5122 chips are cascaded (bit-banged, SPI-like, 3-wire bus): +// PIN_DIN -> chip0 DIN, chip0 DOUT -> chip1 DIN (daisy-chained), +// PIN_CLK -> both chips' CLK (shared), +// PIN_OE -> both chips' output-enable (shared, active HIGH here +// i.e. energizes/blanks the tubes - see writeDisplay()). +// Together the two chips give 64 open-drain HV outputs, addressed +// via the digitPins[][] table (pin numbers 1-32 = chip0, 101-132 = +// chip1; see clocks.h for the actual per-clock wiring tables). +// Status: This is the CURRENT driver: every HV5122-based config in +// clocks.h (CLOCK_50, 51, 52, 53, 54, 55, 56, 57, 57A) defines +// NEWHV5122, so this file is the one that actually gets compiled. +// The plain "HV5122" macro (see HV5122.ino) is no longer selected +// by any active config - that file is legacy/orphaned. +// What's new vs. HV5122.ino: writeDisplay() (the timer ISR) now runs a +// 3-phase state machine (old digit -> new digit -> blank) that +// implements the digit-change "wipe" animation (animMask[] / +// newDigit[] / oldDigit[], see changeDigit() mode 5 in the core +// .ino) by time-slicing how long the OLD vs. NEW cathode pattern +// is shown within one PWM refresh period. writeDisplaySingle() also +// now only recomputes the shift-out bit buffers when digit/newDigit/ +// animMask actually changed (memcmp-cached), instead of +// unconditionally re-shifting every 50ms. +//============================================================================ +#ifdef NEWHV5122 //Nixie driver +//P.S. 6x tubes PCB version + +char tubeDriver[] = "HV5122NEW"; +//HV5122 control pins +//#define PIN_DIN 17 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +//#define PIN_CLK 22 // Clock +//#define PIN_OE 21 // OutputEnable + +//int maxDigits = 6; + +//Data pin numbers 100+ means: chip1 pins are used. Chip0-s DOUT is connected to chip1's DIN +/* Example: + byte digitPins[maxDigits+1][10] = { + {2,10,9,8,7,6,5,4,3,1}, //sec 1 , chip0 (tube#0) + {11,32,20,19,18,17,16,15,14,13}, //sec 10 , chip0 (tube#1) + {22,31,29,30,27,28,25,26,23,24}, //min 1 , chip0 (tube#2) + {101,131,110,109,108,107,106,105,104,103}, //min 10 , chip1 (tube#3) + {111,132,120,119,118,117,116,115,114,113}, //hour 1 , chip1 (tube#4) + {122,129,130,127,128,125,126,123,124,121}, //hour 10 , chip1 (tube#5) + {0,12,21,102,112,0, 0,0,0,0} //extra decimal point (tube0...tube6) + }; +*/ + +// DRAM_ATTR: forces these globals to live in internal DRAM rather than being +// placed somewhere the ISR might reach via the flash cache. On ESP32, if a +// flash-cache miss/disable happens to coincide with a SPIFFS/OTA/flash write +// while this IRAM_ATTR ISR (writeDisplay(), below) touches these variables, +// an un-cached access could stall or crash; DRAM_ATTR keeps them reliably +// accessible from the ISR at all times. +uint32_t DRAM_ATTR bitBuffer0 = 0; //new digits - bit pattern for chip0, prepared by writeDisplaySingle(), consumed by the writeDisplay() ISR +uint32_t DRAM_ATTR bitBuffer1 = 0; //new digits - chip1 +uint32_t DRAM_ATTR oldBitBuffer0 = 0; //old (pre-change) digits - chip0, shown briefly during the wipe/crossfade animation +uint32_t DRAM_ATTR oldBitBuffer1 = 0; //old (pre-change) digits - chip1 +int DRAM_ATTR animPtr = 0; //0 = no animation in progress; otherwise the animMask[] value driving how the old/new time split is weighted (see writeDisplaySingle()) + +#define SHIFT_LSB_FIRST false //true= LSB first, false= MSB first + +//Brightness PWM timing. Greater value => slower brightness PWM frequency +// Suggested values: 100000 / 20000, for faster PWM: 50000 / 10000 +uint32_t DRAM_ATTR PWMrefresh = 100000; //full PWM period length, in timer ticks; also the time-budget shared between old-digit/new-digit/blank phases (see writeDisplay()) +uint32_t DRAM_ATTR PWM_min = 6000; //floor for the ON portion of the cycle, so very low brightness settings still produce a visible/stable flash instead of a near-zero-width pulse + +uint32_t DRAM_ATTR time1 = 2000; //how long (ticks) to show the OLD digit pattern this refresh cycle (state 0 below) +uint32_t DRAM_ATTR time2 = 2000; //how long (ticks) to show the NEW digit pattern this refresh cycle (state 1 below) +uint32_t DRAM_ATTR offTime = 2000; //how long (ticks) the display stays blanked (OE off) this refresh cycle (state 2 below) - this is what actually implements the brightness dimming +uint32_t DRAM_ATTR brightness = 0; +uint32_t DRAM_ATTR PWMtimeBrightness; //time1+time2 combined, i.e. total "lit" time per refresh cycle, derived from brightness/lux + +// shift(): bit-bangs one 32-bit word into the HV5122 shift-register chain. +// The ~20-NOP delays around each CLK/DIN edge are required because the +// HV5122 is a high-voltage part (internal level-shifters to the ~170V rail) +// and is much slower than a typical 5V logic shift register - driving it at +// full CPU speed without these delays can corrupt shifted-in data. +// Data is latched into the HV5122 on the falling edge of CLK. +// Runs from the ISR context here (called by writeDisplay() below), unlike in +// HV5122.ino where it's only called from the main-loop writeDisplaySingle(). +#if defined(ESP32) +void IRAM_ATTR shift(uint32_t Data) { +#else +void ICACHE_RAM_ATTR shift(uint32_t Data) { +#endif + static boolean b; + + for (uint32_t i = 0; i < 32; i++) { + digitalWrite(PIN_CLK, HIGH); + for (int t=0;t<20;t++) asm volatile ("nop"); + if (SHIFT_LSB_FIRST) + b = ((Data & (uint32_t(1) << i))) > 0; //LSB first + else + b = (Data & (uint32_t(1) << (31 - i))) > 0; //MSB first + digitalWrite(PIN_DIN, b); + for (int t=0;t<20;t++) asm volatile ("nop"); + digitalWrite(PIN_CLK, LOW); //falling CLK to store DIN + for (int t=0;t<20;t++) asm volatile ("nop"); + } + digitalWrite(PIN_CLK, HIGH); + for (int t=0;t<20;t++) asm volatile ("nop"); +} + +void clearTubes() { + shift(0); + shift(0); +} + +void setup_pins() { + DPRINTLN("Setup pins - HV5122 Nixie driver..."); + DPRINT("- CLK : GPIO"); DPRINTLN(PIN_CLK); + DPRINT("- DATAIN: GPIO"); DPRINTLN(PIN_DIN); + DPRINT("- OUTPUT_ENABLE: GPIO"); DPRINTLN(PIN_OE); + pinMode(PIN_CLK, OUTPUT); regPin(PIN_CLK, "PIN_CLK"); + pinMode(PIN_DIN, OUTPUT); regPin(PIN_DIN, "PIN_DIN"); + pinMode(PIN_OE, OUTPUT); regPin(PIN_OE, "PIN_OE"); + digitalWrite(PIN_CLK, HIGH); + digitalWrite(PIN_OE, LOW); //keep outputs blanked until clearTubes() below has pushed a known (all-zero) pattern into the shift registers + driverSetupStr = "
HV5122 pin settings:
"; + for (int i=0;i"); + } + clearTubes(); + startTimer(); +} + + +// writeDisplay(): TIMER-ISR implementing a 3-phase state machine per refresh +// cycle, re-armed with a different timer delay for each phase: +// state 0: show the OLD (pre-change) digit pattern for time1 ticks +// state 1: show the NEW digit pattern for time2 ticks +// state 2: blank (OE off) for offTime ticks - this is the actual dimming mechanism +// (state 3 is not a real switch case: it's set inline within state 2's +// handling and consumed immediately, in the same ISR call, by the +// "state==3" check below - it never survives to the next ISR invocation) +// time1/time2/offTime/bitBuffer*/oldBitBuffer* are all precomputed once by +// writeDisplaySingle() (main-loop side) whenever the digit/animation state +// changes; this ISR just plays them back every refresh cycle. Showing the OLD +// pattern briefly before the NEW one is what produces the digit-change wipe/ +// crossfade animation (animMask[]-driven) - when no animation is active, +// writeDisplaySingle() sets oldBitBuffer==bitBuffer so phases 0 and 1 render +// the identical (new) pattern and no visible transition occurs. +// Each state's "if" falls through to the next case when its time is too short +// (<1000 ticks) to bother with - e.g. if time1<1000 the old-digit phase is +// skipped entirely and state 0 falls straight into state 1's code within the +// same ISR call. +#if defined(ESP32) +void IRAM_ATTR writeDisplay() { //void IRAM_ATTR writeDisplay(){ +#else +void ICACHE_RAM_ATTR writeDisplay() { //https://circuits4you.com/2018/01/02/esp8266-timer-ticker-example/ +#endif + + static DRAM_ATTR byte state=0; + int timer = PWMrefresh; //fallback delay if neither state's time threshold is met below (e.g. both time1 and time2 < 1000 at very low brightness) + + intCounter++; + if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! + #if defined(ESP8266) + timer1_write(PWMrefresh); + #endif + return; + } + + #if defined(ESP32) + portENTER_CRITICAL_ISR(&timerMux); + noInterrupts(); + #endif + digitalWrite(PIN_OE, LOW); //OFF - blank while shifting new data in, to avoid a brief flash of garbage/transitional segment patterns on the tubes + switch (state) { //state machine... + case 0: //show old character + if (time1>=1000) { + timer = time1; + shift(oldBitBuffer1); + shift(oldBitBuffer0); + state = 1; + break; + } + //time1 too short: fall through to state 1 without a break, skipping the old-digit phase for this cycle + case 1: //show new character + if (time2>=1000) { + timer = time2; + shift(bitBuffer1); + shift(bitBuffer0); + if (offTime<500) state = 0; //offTime negligible: skip the blank phase too, go straight back to old-digit next cycle + else state = 2; + break; + } + //time2 too short: fall through to state 2 without a break + case 2: //blank display + timer = offTime; + state = 3; //state 3 exists only to be caught by the "state==3" check just below, within this same ISR call - see comment above + break; + } //end switch + + if (timer<500) timer = 500; //safety only... (timerAlarmWrite/timer1_write with 0 or a very small value can hang/misfire) + + if ( (state==3) || (!radarON) || (brightness == 0) ) { //OFF state, blank digit + digitalWrite(PIN_OE, LOW); //OFF + state = 0; //resets the state machine to restart the old->new->blank cycle from state 0 on the next ISR call + } + else { //ON state + digitalWrite(PIN_OE, HIGH); //ON -- NOTE: active-HIGH OE on this board, opposite of the active-low /OE found on many shift-register ICs + } + +#if defined(ESP8266) + timer1_write(timer); +#elif defined(ESP32) + portEXIT_CRITICAL_ISR(&timerMux); + timerAlarmWrite(ESP32timer, timer, true); + timerAlarmEnable(ESP32timer); + interrupts(); +#endif +} + +// writeDisplaySingle(): recomputes the shift-out bit buffers (bitBuffer0/1, +// oldBitBuffer0/1) and the per-phase timing (time1/time2/offTime) from the +// current digit[]/newDigit[]/animMask[]/digitDP[] state, then hands them off +// (via the shared globals above) to the writeDisplay() ISR to actually shift +// out and time-multiplex. Called from the main loop; guarded by the memcmp +// check below so the (relatively expensive) recompute only runs when +// something actually changed, instead of unconditionally every call like +// HV5122.ino's writeDisplaySingle() does. +void writeDisplaySingle() { + static byte lastAnimMask[BUFSIZE]; + static byte lastDigit[BUFSIZE]; + static unsigned long lastRun = 0; + uint32_t b0,b1,ob0,ob1; //temp buffers + byte num; + + // POSSIBLE BUG: this early-return only compares digit[]/newDigit[]/animMask[] + // against their cached values - it does NOT compare digitDP[] (the decimal + // point / separator-dot buffer). If only digitDP[] changes between calls + // (e.g. a blinking colon toggled by prm.enableBlink, or MAKE_BLINKING_DOTS + // mode - both used by this driver's configs, see clocks.h CLOCK_52/54) + // while digit/newDigit/animMask stay the same, this function returns early + // and bitBuffer0/1 are never recomputed with the new DP state, so the + // blink would not actually appear on the tubes. In most normal clock + // screens this is masked because the seconds digit (or minute digit) also + // changes in the same tick as the blink, but it could manifest as a + // frozen/stuck blinking dot on screens where only the dot toggles + // (e.g. while a static date/temperature reading is shown with blink enabled). + if ( (memcmp(digit,lastDigit,maxDigits) == 0) && (memcmp(lastDigit,newDigit,maxDigits) == 0) && (memcmp(animMask,lastAnimMask,maxDigits)==0) ) return; + //if ((millis()-lastRun)<5) return; + lastRun = millis(); + + memcpy(lastAnimMask,animMask,maxDigits); + memcpy(lastDigit,digit,maxDigits); + + animPtr = 0; + b0 = 0; b1 = 0; + for (int i = 0; i < maxDigits; i++) { //Set the clock digits new values + num = digit[i]; + if (animMask[i]>0) { //this tube position is mid-animation: use the incoming NEW value here (digit[i] is presumably still the transitional/old value at this point) and remember the mask so we know to build an "old" buffer below too + animPtr = animMask[i]; + num = newDigit[i]; + } + if (num < 10) { //codes 10 (BLANK), 11 (dash), 12 (dp glyph) and 13+ (extra glyphs) have no cathode mapping on a Nixie tube -> leave bits cleared = tube blank + if (digitPins[i][num] < 100) { + b0 |= (uint32_t)(1 << (digitPins[i][num]-1)); + //DPRINT(digitPins[i][num]); DPRINT(":"); + } //chip0 + else { + b1 |= (uint32_t)(1 << (digitPins[i][num] - 101)); + //DPRINT(digitPins[i][num]);DPRINT(":");} //chip1 + } + } + } //end for i + +if (animPtr>0) { //at least one position is animating -> also build the OLD-pattern buffer, shown during the "state 0" phase in writeDisplay() for the crossfade/wipe effect + ob0 = 0; ob1 = 0; + for (int i = 0; i < maxDigits; i++) { //Set the clock digits old values + num = oldDigit[i]; + if (num < 10) { + if (digitPins[i][num] < 100) { + ob0 |= (uint32_t)(1 << (digitPins[i][num]-1)); + //DPRINT(digitPins[i][num]); DPRINT(":"); + } //chip0 + else { + ob1 |= (uint32_t)(1 << (digitPins[i][num] - 101)); + //DPRINT(digitPins[i][num]);DPRINT(":");} //chip1 + } + } + } //end for i +} + + #ifdef MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! + if (showClock) { + digitDP[1] = digitDP[2]; //left two dots + digitDP[3] = digitDP[4]; //right two dots + } + #endif + + for (int i = 0; i < maxDigits-1; i++) { //Set the extra decimal point dots + if (digitDP[i] && digitPins[maxDigits][i]>0) { + //DP bits are ORed into BOTH the new (b0/b1) and old (ob0/ob1) buffers, so + //decimal points/separator dots stay lit continuously through the old->new + //wipe animation instead of blinking off during the old-digit phase. + if (digitPins[maxDigits][i] < 100) { + b0 |= (uint32_t)(1 << (digitPins[maxDigits][i]-1)); //chip0 + ob0 |= (uint32_t)(1 << (digitPins[maxDigits][i]-1)); //chip0 + } + else { + b1 |= (uint32_t)(1 << (digitPins[maxDigits][i] - 101)); //chip1 + ob1 |= (uint32_t)(1 << (digitPins[maxDigits][i] - 101)); //chip1 + } + } + } //end for i + + + + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + if (autoBrightness && displayON) + PWMtimeBrightness = max(PWM_min, (PWMrefresh*lx)/MAXIMUM_LUX); //day mode + ALS: scale total lit-time by measured lux instead of the stored brightness setting + else + PWMtimeBrightness = max(PWM_min, (PWMrefresh*brightness)/MAXBRIGHTNESS); + + //Below a certain lit-time (PWMtimeBrightness<15000, i.e. brightness/lux is + //low), skip the wipe animation altogether: the old/new split would be too + //fast to see anyway, and at very short lit-times splitting it further into + //old+new sub-phases risks pulses too short for the HV5122/tubes to respond + //to cleanly. animPtr=10 forces an even old:new time split further below, + //but since ob0/ob1==b0/b1 here, phase 0 and phase 1 render identically + //(no visible transition) regardless of the split. + if ((animPtr==0) || (PWMtimeBrightness<15000)) { //no animation #5 + ob0 = b0; + ob1 = b1; + animPtr = 10; //to make equal timing + } + + //Split the total lit time (PWMtimeBrightness) between the OLD-digit phase + //(time1) and NEW-digit phase (time2) according to animPtr, which walks from + //a low value (just-started animation, mostly OLD digit still shown) up to + //~20 (animation complete, mostly/only NEW digit) as the wipe animation + //progresses across successive changeDigit() calls - see animMask[] in the + //core .ino. animPtr==10 (forced above when not animating) gives an even + //50/50 split, which is harmless there since ob0/ob1==b0/b1 already. + time1 = PWMtimeBrightness * (20-animPtr)/20; + if (time1<1000) time1=0; //too short to bother with a separate old-digit phase -> writeDisplay() will fall through and skip it + if (time1 > (PWMtimeBrightness-1000)) time1 = PWMtimeBrightness; //leave less than 1000 ticks for time2 -> just give the old phase the whole lit budget instead + time2 = PWMtimeBrightness - time1; + offTime = PWMrefresh - time1 - time2; //remaining budget of the refresh period is spent blanked - this (not time1/time2) is what actually produces the brightness dimming + + bitBuffer0 = b0; + bitBuffer1 = b1; + oldBitBuffer0 = ob0; + oldBitBuffer1 = ob1; + +// DPRINT("Br:"); DPRINT(brightness); DPRINT(" A:"); DPRINT(animPtr); +// DPRINT(" PTB"); DPRINT(PWMtimeBrightness); +// DPRINT(" t1:"); DPRINT(time1); DPRINT(" t2:"); DPRINT(time2); DPRINT(" off:"); DPRINT(offTime); +// DPRINT(" "); DPRINT(b0,HEX); DPRINT(" "); DPRINT(b1,HEX); DPRINT(" "); +// DPRINT(ob0,HEX); DPRINT(" "); DPRINTLN(ob1,HEX); + +} + + +//For testing ------------------- +/* +void test() { + digitalWrite(PIN_OE, HIGH); + clearTubes(); + for (int j=0;j<2;j++) + for (uint32_t i=0;i<32;i++) { + DPRINT("D"); DPRINT(i+1); DPRINT(": "); + shift((uint32_t)(1 << (i))); + DPRINTLN((uint32_t)(1 << (i)),HEX); + delay(3000); + } +} + +void testTubes2() { +while (true) { + + DPRINTLN("Testing tubes: "); + for (int i = 0; i < 10; i++) { + DPRINT(i); DPRINT(" "); + for (int j = 0; j < maxDigits; j++) { + digit[j] = i; + digitDP[j] = i % 2; + } + //changeDigit(); + + writeDisplaySingle(); + delay(3000); + yield(); + } + } //end while +} + +void showBits(uint32_t bits) { + boolean b; + + for (uint32_t i = 0; i < 32; i++) { + if (SHIFT_LSB_FIRST) + b = ((bits & (uint32_t(1) << i))) > 0; //LSB first + else + b = (bits & (uint32_t(1) << (31 - i))) > 0; //MSB first + if (b) { + DPRINT("1"); + } + else { + DPRINT("0"); + } + } //end for + DPRINT("-"); +} + +*/ +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/I2Csensors.ino b/firmware/uniclock/ESP32_UniClock2/I2Csensors.ino new file mode 100644 index 0000000..012af34 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/I2Csensors.ino @@ -0,0 +1,450 @@ +// ============================================================================ +// I2Csensors.ino +// ---------------------------------------------------------------------------- +// Drivers for the I2C environmental sensors supported by this codebase. +// All sensors below share ONE physical I2C bus (Wire, PIN_SDA/PIN_SCL, set +// per-CLOCK_NN in clocks.h) and can be enabled in ANY combination for a given +// clock build - several CLOCK_NN configs enable 3-5 of them at once: +// +// USE_BME280 - Bosch BME280, temperature + humidity + pressure (Adafruit_BME280_Library) +// I2C address 0x76 (hardcoded here; chip also supports 0x77) +// USE_BMP280 - Bosch BMP280, temperature + pressure only (Adafruit_BMP280_Library) +// I2C address 0x77 (hardcoded here; chip also supports 0x76) +// -> BME280 and BMP280 are deliberately pinned to different +// default addresses so both can coexist on the same bus. +// USE_AHTX0 - AHT10/AHT20, temperature + humidity (Adafruit_AHTX0) +// I2C address 0x38 (fixed, not configurable on the chip) +// USE_SHT21 - Sensirion SHT21, temperature + humidity (custom SHT21 lib) +// I2C address 0x40 (7-bit); see getSHT21()/detection code below +// USE_BH1750 - Ambient light / lux meter (BH1750 lib) +// I2C address 0x23 (hardcoded; chip also supports 0x5C) +// +// Which CLOCK_NN configs use which sensor (see clocks.h): +// USE_SHT21 alone : CLOCK_23, CLOCK_23A, CLOCK_23B +// BME280+BMP280+AHTX0+SHT21 : CLOCK_26 +// All five (incl. BH1750) : CLOCK_46, CLOCK_48, CLOCK_50 (and, commented +// out / inactive by default, CLOCK_51, 52, 54, +// 55, 56, 57, 57A), CLOCK_99 +// BME280+AHTX0+SHT21 : CLOCK_80, CLOCK_81, CLOCK_82 +// +// USE_I2CSENSORS (the #ifdef guarding this whole file) is NOT set directly by +// any CLOCK_NN block - it is auto-derived in clocks.h: +// #if defined(USE_BME280) || defined(USE_BMP280) || defined(USE_AHTX0) || +// defined(USE_SHT21) || defined(USE_BH1750) || defined(USE_RTC) +// #define USE_I2CSENSORS +// i.e. it turns on automatically as soon as any one sensor (or the RTC) is +// requested for the selected clock. +// +// MULTI-SENSOR INDEXING: temperature[6]/humid[6]/pressur[6] (declared in the +// core .ino) are shared result arrays. Each sensor type that measures a given +// quantity claims the NEXT FREE slot in that quantity's array at boot, using +// three independent counters (useTemp/useHumid/usePress) - see the detailed +// comment in setupI2Csensors() below for exactly how collisions are avoided +// when several sensors are active simultaneously (e.g. CLOCK_26). +// +// Calibration offsets (prm.corrT0/corrT1/corrH0/corrH1) are NOT applied in +// this file - they are added later, at display time, in displayTemp()/ +// displayHumid() in the core .ino. This file only stores raw (rounded) +// readings, so there is no risk of double-applying the correction here. +// ============================================================================ +#ifdef USE_I2CSENSORS + +// NOTE: these are commented-out and inert by design - the real per-build +// enabling of each sensor happens in clocks.h inside the relevant CLOCK_NN +// block. Since this .ino is compiled into every build regardless of which +// clock is selected, uncommenting one of these lines here would force that +// sensor ON for EVERY clock (as long as USE_I2CSENSORS ends up defined by +// some other means), not just the one you're working on. Leave commented; +// they exist only as a quick reference of the five supported macros. +//#define USE_BME280 +//#define USE_BMP280 +//#define USE_AHTX0 +//#define USE_SHT21 +//#define USE_BH1750 + +#include +#include + +#ifdef USE_BME280 + #include + Adafruit_BME280 bme; // use I2C interface address = 0x76 or 0x77 + Adafruit_Sensor *bme_temp = bme.getTemperatureSensor(); + Adafruit_Sensor *bme_pressure = bme.getPressureSensor(); + Adafruit_Sensor *bme_humidity = bme.getHumiditySensor(); + // *Ptr variables below hold this sensor's assigned slot (index) in the + // shared temperature[]/humid[]/pressur[] arrays. They are filled in once, + // in setupI2Csensors(), and stay 0 (default global init) if the sensor + // isn't found - harmless, since the getBME280() reader is only ever + // called when BME280exist is true. + byte BME280tempPtr; + byte BME280humidPtr; + byte BME280pressPtr; +#endif + +#ifdef USE_BMP280 + #include + // Hardcoded to 0x77 (BME280 above is hardcoded to 0x76) so that both chips + // can be wired to the same bus at once without an address clash - several + // CLOCK_NN configs (e.g. CLOCK_26, CLOCK_46/48/50, CLOCK_99) enable both + // USE_BME280 and USE_BMP280 simultaneously. + Adafruit_BMP280 bmp; // use I2C interface address = 0x76 or 0x77 + Adafruit_Sensor *bmp_temp = bmp.getTemperatureSensor(); + Adafruit_Sensor *bmp_pressure = bmp.getPressureSensor(); + byte BMP280tempPtr; + byte BMP280humidPtr; // NOTE: BMP280 has no humidity sensor - this pointer is declared for symmetry but never used/assigned (BMP280 never touches humid[]). + byte BMP280pressPtr; +#endif + +#ifdef USE_AHTX0 + #include //I2C address: 0x38 + Adafruit_AHTX0 aht; + Adafruit_Sensor *aht_humidity, *aht_temp; + byte AHTX0tempPtr; + byte AHTX0humidPtr; +#endif + +#ifdef USE_SHT21 + // POSSIBLE BUG (see detection code in setupI2Csensors() below): this header + // comment says the 8-bit write/read addresses are 0x80/0x81, which + // correspond to 7-bit address 0x40 (the SHT21's actual, standard I2C + // address). The Arduino Wire library used here takes a 7-bit address + // (0-127), so 0x80 is out of range for Wire.beginTransmission() - see the + // detection code further down which probes both 0x40 (correct) and 0x80 + // (likely meaningless/dead check for this Wire API). + #include //I2C address: 0x80 and 0x81 + SHT21 sht; + float SHT21temp,SHT21humid =0; + byte SHT21tempPtr; + byte SHT21humidPtr; +#endif + +#ifdef USE_BH1750 + #include + BH1750 lightMeter(0x23); // fixed address 0x23 (ADDR pin low/floating); chip's alternate address is 0x5C +#endif + +void setupI2Csensors() { + DPRINTLN("Starting I2Csensors sensors..."); + + // pinMode(OUTPUT) here is just for regPin() bookkeeping (pin-conflict + // registration/debug); Wire.begin() below reconfigures both pins for I2C + // (open-drain + pull-ups) regardless of this pinMode call. + pinMode(PIN_SDA,OUTPUT); regPin(PIN_SDA,"PIN_SDA"); + pinMode(PIN_SCL,OUTPUT); regPin(PIN_SCL,"PIN_SCL"); + Wire.begin(PIN_SDA,PIN_SCL); + delay(100); + I2Cscanner(); // debug-only bus scan; logs every ACKing address, does not affect detection below + +#ifdef USE_RTC + DPRINTLN("Starting RTC Clock..."); + while(millis()<2000) {yield();} //waiting for 2 sec from startup + //I2C_ClearBus(); + delay(100); + #if defined(PIN_SDA) && defined(PIN_SCL) + Wire.beginTransmission(0x68); + byte error = Wire.endTransmission(); + if (error == 0) { //DS3231_get_addr(0x68) + RTCexist = true; + DPRINTLN("RTC found on 0x68."); + } + else { + DPRINTLN("!!!No RTC found on 0x68!!!"); + } + #else + RTCexist = false; + DPRINTLN("PIN_SDA or PIN_SCL not defined!"); + #endif +#endif + + + // --------------------------------------------------------------------- + // MULTI-SENSOR INDEXING SCHEME (applies to every #ifdef block below): + // temperature[]/humid[]/pressur[] are shared 6-slot arrays that can hold + // readings from several sensors at once. There are THREE INDEPENDENT + // counters - useTemp, useHumid, usePress - one per array/quantity, each + // incremented only by sensors that actually measure that quantity. + // Each sensor, when found, "reserves" the array slot equal to the CURRENT + // value of the relevant counter (its own xxxPtr), then increments + // that counter so the NEXT detected sensor of that quantity gets the next + // free slot. Because each array has its own counter, two sensors can + // never collide/overwrite each other's slot in the SAME array, even when + // several are active together (e.g. CLOCK_26: BME280+BMP280+AHTX0+SHT21): + // temperature[]: BME280->0, BMP280->1, AHTX0->2, SHT21->3 (useTemp=4) + // humid[] : BME280->0, AHTX0->1, SHT21->2 (useHumid=3, BMP280 has none) + // pressur[] : BME280->0, BMP280->1 (usePress=2, AHTX0/SHT21 have none) + // Detection order below (BME280, BMP280, AHTX0, SHT21, BH1750) therefore + // determines slot order. Downstream, only slots 0 and 1 of temperature[]/ + // humid[] receive a manual calibration offset (prm.corrT0/corrT1/corrH0/ + // corrH1, applied in displayTemp()/displayHumid() in the core .ino) - a + // 3rd+ simultaneous temperature/humidity sensor (e.g. SHT21 at slot 2/3 + // above) is displayed uncorrected. That is a limitation of the core + // display code, not of the indexing here, but worth knowing when wiring + // up configs with 3+ temp/humid sensors. + // --------------------------------------------------------------------- +#ifdef USE_BME280 + if (!bme.begin(0x76)) { + DPRINTLN("Could not find a valid BME280 sensor on address 0x76, check wiring!"); + } + else { + BME280exist = true; + DPRINTLN("BME280 Found!"); + bme_temp->printSensorDetails(); + bme_pressure->printSensorDetails(); + bme_humidity->printSensorDetails(); + BME280tempPtr = useTemp; //remember my ID-s + BME280humidPtr = useHumid; + BME280pressPtr = usePress; + useTemp++; //increase the number of sensors + useHumid++; + usePress++; + } + #endif + +#ifdef USE_BMP280 + if (!bmp.begin(0x77)) { + DPRINTLN("Could not find a valid BMP280 sensor on address 0x77, check wiring!"); + } + else { + BMP280exist = true; + /* BMP280 default settings from datasheet. */ + DPRINTLN("BMP280 Found!"); + bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */ + Adafruit_BMP280::SAMPLING_X2, /* Temp. oversampling */ + Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */ + Adafruit_BMP280::FILTER_X16, /* Filtering. */ + Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */ + bmp_temp->printSensorDetails(); + BMP280tempPtr = useTemp; //remember my ID-s + BMP280pressPtr = usePress; //remember my ID-s + useTemp++; //increase the number of sensors + usePress++; //increase the number of sensors + } +#endif + +#ifdef USE_AHTX0 + if (!aht.begin()) { + DPRINTLN("Could not find a valid AHT10/AHT20 sensor, check wiring!"); + } + else { + AHTX0exist = true; + DPRINTLN("AHT10/AHT20 Found!"); + aht_temp = aht.getTemperatureSensor(); + aht_temp->printSensorDetails(); + aht_humidity = aht.getHumiditySensor(); + aht_humidity->printSensorDetails(); + AHTX0tempPtr = useTemp; //remember my ID-s + AHTX0humidPtr = useHumid; + useTemp++; //increase the number of sensors + useHumid++; + } +#endif + DPRINTLN(" "); + +#ifdef USE_SHT21 + int error1,error2; + Wire.beginTransmission(0x40); // correct 7-bit SHT21 address + error1 = Wire.endTransmission(); + // POSSIBLE BUG: 0x80 is not a valid 7-bit I2C address for Wire.beginTransmission() + // (Wire expects 0-127; 0x80/128 is the 8-bit write address, i.e. 0x40<<1). + // This second probe is likely a no-op/dead check left over from confusing + // 7-bit vs 8-bit addressing (see comment above the #include line). + // Harmless as written because of the "&&" below (SHT21exist still gets set + // correctly whenever the real 0x40 probe succeeds), but error2 does not + // reliably indicate anything about a real device. + Wire.beginTransmission(0x80); + error2 = Wire.endTransmission(); + if ((error1 !=0) && (error2!=0)) { + DPRINTLN("Could not find a valid SHT21 sensor on 0x40 / 0x80, check wiring!"); + } + else { + SHT21exist = true; + DPRINTLN("SHT21 Found!"); + SHT21tempPtr = useTemp; //remember my ID-s + SHT21humidPtr = useHumid; + useTemp++; //increase the number of sensors + useHumid++; + } +#endif + +#ifdef USE_BH1750 + // BH1750 doesn't share temperature[]/humid[]/pressur[] - it has its own + // dedicated useLux counter/flow (see getBH1750() at the bottom of this + // file, called from getLightSensor() in the core .ino), so no slot/Ptr + // bookkeeping is needed here. + if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) { + DPRINTLN("BH1750 luxmeter sensor found"); + BH1750exist = true; + useLux++; + } + else { + DPRINTLN("Error initialising BH1750 on address 0x23"); + } +#endif + + DPRINTLN(" "); +} //end of SetupI2Csensors() + + +// getBME280()/getBMP280()/getAHTX0()/getSHT21() below each write ONLY into +// their own reserved slot (the *Ptr index assigned in setupI2Csensors(), see +// the indexing scheme comment there) and store the RAW rounded reading - no +// calibration offset (prm.corrT0/corrT1/corrH0/corrH1) is applied here; that +// happens later, at display time, in displayTemp()/displayHumid() (core +// .ino), so there's no double-correction between this file and that one. +void getBME280() { +#ifdef USE_BME280 + temperature[BME280tempPtr] = round1(bme.readTemperature()); + humid[BME280humidPtr] = round1(bme.readHumidity()); + pressur[BME280pressPtr] = round1(bme.readPressure()/100); + DPRINT("BME280: "); + DPRINT("Temperature = "); + DPRINT(temperature[BME280tempPtr]); + DPRINT(" *C"); + + DPRINT(" Humidity = "); + DPRINT(humid[BME280humidPtr]); + DPRINT(" %"); + + DPRINT(" Pressure = "); + DPRINT(pressur[BME280pressPtr]); + DPRINTLN(" hPa"); +#endif +} + + +void getBMP280() { +#ifdef USE_BMP280 + temperature[BMP280tempPtr] = round1(bmp.readTemperature()); + pressur[BMP280pressPtr] = round1(bmp.readPressure()/100); + DPRINT("BMP280: "); + DPRINT(F("Temperature = ")); + DPRINT(temperature[BMP280tempPtr]); + DPRINT(" *C"); + + DPRINT(F(" Pressure = ")); + DPRINT(pressur[BMP280pressPtr]); + DPRINTLN(" hPa"); +#endif +} + +void getAHTX0() { +#ifdef USE_AHTX0 + // /* Get a new normalized sensor event */ + sensors_event_t humidity; + sensors_event_t temp; + aht_humidity->getEvent(&humidity); + aht_temp->getEvent(&temp); + temperature[AHTX0tempPtr] = round1(temp.temperature); + DPRINT("AHT sensor: "); + DPRINT(" Temperature "); + DPRINT(temperature[AHTX0tempPtr]); + DPRINT(" deg C"); + + DPRINT(" Humidity: "); /* Display the results (humidity is measured in % relative humidity (% rH) */ + humid[AHTX0humidPtr] = round1(humidity.relative_humidity); + DPRINT(humid[AHTX0humidPtr]); + DPRINTLN(" % rH"); +#endif +} + +void getSHT21() { +#ifdef USE_SHT21 + temperature[SHT21tempPtr] = round1(sht.getTemperature()); + DPRINT("SHT21 sensor: "); + DPRINT(" Temperature "); + DPRINT(temperature[SHT21tempPtr]); + DPRINT(" deg C"); + + DPRINT(" Humidity: "); /* Display the results (humidity is measured in % relative humidity (% rH) */ + humid[SHT21humidPtr] = round1(sht.getHumidity()); + DPRINT(humid[SHT21humidPtr]); + DPRINTLN(" % rH"); +#endif +} + + + +void getI2Csensors() { + static unsigned long lastRun = 0; + + // Called every ~200ms from timeProgram() (core .ino), but the actual I2C + // reads only happen once every 30s per sensor - this throttle avoids + // hammering the bus/sensors and is independent of the caller's cadence. + // lastRun==0 lets the very first call (at millis()==0..~30000) through + // immediately so readings are available right after boot. + if (((millis()-lastRun)<30000) && (lastRun !=0)) return; // || (second()!=TEMP_START) + lastRun = millis(); + if (BME280exist) getBME280(); + if (BMP280exist) getBMP280(); + if (AHTX0exist) getAHTX0(); + if (SHT21exist) getSHT21(); + if (BH1750exist) getBH1750(); +} + + +void I2Cscanner() { + byte error, address; + int nDevices=0; + DPRINTLN(F("________________________________")); + DPRINTLN("Scanning I2C devices..."); + + for(address = 1; address < 127; address++ ) { + // The i2c_scanner uses the return value of + // Wire.endTransmission() to see if + // a device acknowledged the address. + Wire.beginTransmission(address); + error = Wire.endTransmission(); + + if (error == 0) { + DPRINT("I2C device found at address 0x"); + if (address<16) + DPRINT("0"); + DPRINT(address,HEX); + DPRINTLN(" !"); + + nDevices++; + } + else if (error==4) { + DPRINT("Unknown error at address 0x"); + if (address<16) + DPRINT("0"); + DPRINTLN(address,HEX); + } + } //end for + + if (nDevices == 0) + DPRINTLN("No I2C devices found\n"); + DPRINTLN(F("_________________________________")); +} + + +#else +void setupI2Csensors() {} +void getI2Csensors() {} +#endif + +#ifdef USE_BH1750 +// Called directly from getLightSensor() (core .ino) whenever BH1750exist is +// true - unlike the other get*() functions this is NOT gated through +// getI2Csensors()'s 30s throttle, so it can be polled as often as needed; +// the BH1750 library itself paces new measurements (measurementReady()). +int getBH1750() { + static float oldLux = MAXIMUM_LUX; + + if (lightMeter.measurementReady()) { + float lux = lightMeter.readLightLevel(); + //DPRINT("BH1750 Light: "); DPRINT(lux); DPRINTLN(" lx"); + + if (lux>MAXIMUM_LUX) lux = MAXIMUM_LUX; //Limited //Limit lux value to maximum + if (lux<0) lux = oldLux; // negative reading = bus glitch/error from the library; ignore and keep last good value + oldLux = oldLux + (lux-oldLux)/10; //slow down Lux change (simple exponential smoothing, ~10-sample time constant) + } + return (int)oldLux; +} +#else +// Stub used when USE_BH1750 is not defined for the selected clock, so +// getLightSensor() (core .ino) can call getBH1750() unconditionally. +int getBH1750() { + return(0); +} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/MAX6921.ino b/firmware/uniclock/ESP32_UniClock2/MAX6921.ino new file mode 100644 index 0000000..67768cf --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/MAX6921.ino @@ -0,0 +1,305 @@ +#ifdef MAX6921 +//=========================================================================================== +// Driver for the MAXIM MAX6921 20-output VFD/vacuum-tube driver chip -- ESP8266 variant. +// +// Hardware / protocol: +// The MAX6921 is NOT a segment/digit-specific chip -- it is a generic 20-bit serial-in, +// parallel-out latch driving 20 high-voltage open-drain outputs (OUT19..OUT0). Which +// physical output pin ends up wired to a VFD segment (a..g, DP) and which to a tube's +// grid/anode (digit select) is decided purely by the PCB wiring, described per-clock in +// clocks.h via the segmentEnablePins[8] and digitEnablePins[] arrays. Every refresh, this +// driver ORs together "which digit-select output is active" with "which segment outputs +// should light up for the current character" into a single 20-bit word and shifts the +// whole word out. +// Serial protocol (3 wires, similar to SPI but no MISO/CS in the usual sense): +// DATA (PIN_DATA) - serial bit, sampled on CLK rising edge, MSB (OUT19) first +// CLK (PIN_CLK) - one pulse per bit, 20 pulses per full frame +// LE (PIN_LE) - Latch Enable: after the 20 bits are clocked in, a LOW->HIGH->LOW +// pulse transfers the shift register into the (separate) output +// latch, so outputs change atomically instead of glitching mid-shift +// BL (PIN_BL) - BLANK: MAX6921's active-HIGH output disable. HIGH = all 20 outputs +// forced off (tube dark) regardless of latch content, LOW = outputs +// follow the latch. Used here both for hard blanking and, via +// ON/OFF time-slicing in writeDisplay(), for brightness control. +// +// Used by (see clocks.h, search for "#define MAX6921"): +// CLOCK_3 - PCB-less clock, single IV-18 VFD tube +// CLOCK_4 - custom clock, 4x IV-11 VFD tubes +// CLOCK_5 - UNFI PCB clock, IVL-2-5/7 tube with RTC +// CLOCK_14 - 6x LD8035 VFD-clock +// CLOCK_15 / CLOCK_15a - 6x IV-6 VFD-clock RGB +// CLOCK_16 - 4x IV-6, 1x LD8035 +// CLOCK_18 - IV-18 VFD-clock RGB +// (all of the above are ESP8266 boards -- the ESP32 counterpart configs use +// MAX6921_ESP32.ino instead, see that file's header for their CLOCK_NN list.) +//=========================================================================================== +//VFD driver for ESP8266 +char tubeDriver[] = "MAX6921"; +//------------------abcdefgDP---------------- definition of different characters +// Each byte below is one glyph's segment pattern, bit7..bit0 = a,b,c,d,e,f,g,DP (a=MSB). +// A '1' bit means "this segment should be lit" for that glyph; generateBitTable() later +// translates these abstract a..g/DP bit positions into the actual MAX6921 OUTx bits using +// segmentEnablePins[] (the PCB-specific wiring), producing charTable[] used in writeDisplay(). +// Indices 0-9 are the digits; 10-20 are special glyphs referenced via the digit[] buffer +// (see ESP32_UniClock2.ino digit[] value convention: 10=BLANK,11=minus,12=DP,13+=extra glyphs). +byte charDefinition[] = { + B11111100, //0: abcdef + B01100000, //1: bc + B11011010, //2: abdeg + B11110010, //3: abcdg + B01100110, //4: bcfg + B10110110, //5: acdfg + B10111110, //6: acdefg + B11100000, //7: abc + B11111110, //8: abcdefg + B11110110, //9: abcdfg + B00000000, // : BLANK (10) + B00000010, //-: minus (11) + B00000001, // decimal point (12) + B11101110, // A abcefg (13) + B11001110, // P abefg (14) + B10011100, // C adef (15) + B11000110, //grad (upper circle) abfg (16) + B10110100, //% acdf (17) + B00111010, //lower circle cdeg (18) + B01100000, //I bc (19) + B10001110 //F aefg (20) + +}; + +uint32_t animationMaskBits[5]; //precomputed AND-masks for the "wipe" transition animation (animMask[]), see generateBitTable() + +// NOTE: MAXCHARS/MAXSEGMENTS/maxDigits rely on sizeof() over `byte` arrays (1 byte/element), +// so sizeof() == element count here. segmentEnablePins[] must always have exactly 8 entries +// (a,b,c,d,e,f,g,DP) per the clocks.h convention; digitEnablePins[] length = number of tubes. +#define MAXCHARS sizeof(charDefinition) +#define MAXSEGMENTS sizeof(segmentEnablePins) +int maxDigits = sizeof(digitEnablePins); + +uint32_t charTable[MAXCHARS]; //generated pin table from segmentDefinitions +uint32_t segmentEnableBits[MAXSEGMENTS]; //bitmaps, generated from EnablePins tables +// FIXED (was fixed-size [10] regardless of the actual maxDigits): sized to BUFSIZE (12, see the +// digit[]/newDigit[]/animMask[] buffers in ESP32_UniClock2.ino), the same ceiling the rest of the +// codebase uses for tube-position buffers, so this can no longer silently overflow in +// generateBitTable() for any config the system otherwise supports. +uint32_t digitEnableBits[12]; + +// Multiplex/brightness timing. Despite the variable names, these are NOT milliseconds -- they +// are raw ESP8266 hardware timer1 tick counts (see startTimer() in ESP32_UniClock2.ino: timer1 +// runs with TIM_DIV16, i.e. 80MHz/16 = 5MHz, so 1 tick = 0.2us). So PWMrefresh=5500 ticks is +// actually ~1.1ms per full ON/OFF half-cycle, and PWM_min/PWM_max (500..5000 ticks) is ~100us..1ms. +// Greater PWMrefresh => slower multiplex/refresh frequency (more visible flicker risk). +int PWMrefresh=5500; //Multiplex time period, in timer1 ticks (not milliseconds, despite the variable name -- see note above). Greater value => slower multiplex frequency +int PWM_min = 500; +int PWM_max = 5000; + +//int PWMtiming[11] = {0,500,800,1200,2000,2500,3000,3500,4000,4500,5000}; +//----------------------------------------------------------------------------------------- + +//https://sub.nanona.fi/esp8266/timing-and-ticks.html +//One tick is 1us / 80 = 0.0125us = 12.5ns on 80MHz +//One tick is 1us / 80 = 0.0125us = 6.25ns on 160MHz +// 1 NOP is 1 tick + + +// Dead/no-op helper: `d` is ignored, so this always executes exactly one NOP regardless of the +// requested delay -- effectively a leftover no-op. All call sites below are commented out +// (superseded by relying on digitalWrite()'s own latency to satisfy MAX6921 timing), so this +// function currently has no effect on the build. +void inline delayMS(int d) { + //for (int i=0;i"); + + generateBitTable(); //precompute charTable/digitEnableBits/animationMaskBits once, so the ISR (writeDisplay) never has to do this math + digitsOnly = false; //this driver can show letters/symbols too (A,P,C,deg,%,I,F...), not just 0-9 + startTimer(); //arms ESP8266 timer1 and attaches writeDisplay() as its ISR -- multiplexing runs entirely in the background from here on +} + +// Timer1 ISR (ESP8266, TIM_SINGLE mode -- fires once, must be rescheduled by calling +// timer1_write() again at the very end of every invocation, which is what drives the whole +// multiplex loop). This implements brightness via a simple two-phase (ON/OFF) time-slicing +// scheme rather than true PWM: each call toggles `state` between an "ON phase" (tube lit, +// duration proportional to brightness) and an "OFF phase" (tube dark, remaining time of the +// PWMrefresh period), i.e. classic duty-cycle dimming spread across one multiplex step. +// https://circuits4you.com/2018/01/02/esp8266-timer-ticker-example/ +void ICACHE_RAM_ATTR writeDisplay(){ //https://circuits4you.com/2018/01/02/esp8266-timer-ticker-example/ + static volatile int timer = PWMrefresh; + static volatile uint32_t val; + static volatile byte pos = 0; + static volatile boolean state=true; + static volatile int brightness; + static int PWMtimeBrightness=PWM_min; + + if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! (EEPROM writes are blocking/slow, and touching them concurrently with flash-adjacent SPI activity risks corruption) + digitalWrite(PIN_BL,HIGH); //OFF -- blank the display so it doesn't hang mid-brightness while the ISR is skipped + timer1_write(PWMrefresh); //timer1 (TIM_SINGLE) does NOT auto-repeat, so it must be explicitly rearmed even while "paused" here, or the ISR would never fire again + return; + } + + intCounter++; + brightness = displayON ? prm.dayBright : prm.nightBright; //day/night dimming level, selected by the core's displayON flag + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + if ((!autoBrightness) && (brightness==MAXBRIGHTNESS)) state = true; //at max fixed brightness, always stay in the "ON" phase (skip the OFF/dimming phase entirely) + + if (state) { //ON state: advance to the next tube position and compute what to shift out + pos++; if (pos>maxDigits-1) { //go to the tube#0 + pos = 0; //go to the first tube + // Brightness (PWM ON time) is only recalculated once per full multiplex sweep (at tube#0), + // not per-tube, so all tubes share the same duty cycle within one refresh cycle. + if (autoBrightness && displayON) { //change brightness only on the tube#0 + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); //ambient-light-driven brightness (auto-dimming), scaled by measured lux vs MAXIMUM_LUX + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); //manual brightness setting, scaled 0..MAXBRIGHTNESS into PWM_min..PWM_max ticks + } + // MAX6921 has no separate "digit select" vs "segment" concept -- both are just output + // bits. Combine the bit for this tube's grid/anode-enable output with the bits for the + // segments that should light up for the current character into one 20-bit frame. + val = (digitEnableBits[pos] | charTable[digit[pos]]); //the full bitmap to send to MAX chip + if (digitDP[pos]) val = val | charTable[12]; //Decimal Point (charTable[12] = the DP-only glyph, OR'd in independently of the digit glyph) + + timer = PWMtimeBrightness; + #ifdef CLOCK_4 + if (pos==2) timer = 3*timer; //Weak IV11 tube#2 brightness compensation, some hacking -- this particular physical tube is dimmer than its neighbours, so its ON time is tripled + #endif + timerON = timer; //exposed for debug/diagnostics only (see ESP32_UniClock2.ino) + timerOFF = PWMrefresh-PWMtimeBrightness; + } + else { //OFF state: nothing to shift out this call, just wait out the rest of the duty cycle + timer = PWMrefresh-PWMtimeBrightness; + } + + if (timer<500) timer = 500; //safety only... avoid an excessively short/zero timer1 reload that could starve the CPU with ISR re-entry + + if ( (brightness == 0) || (!state) || (!radarON)) { //OFF state, blank digit (radarON gates the whole display off when a presence/radar sensor says nobody is around) + digitalWrite(PIN_BL,HIGH); //OFF + } + else { //ON state + + if (animMask[pos]>0) val &= animationMaskBits[animMask[pos]-1]; //animationMode 6, mask characters from up to down and back -- animMask[] holds a 1-based progressive-reveal stage per tube; the precomputed masks blank segments in a..g order to fake a vertical wipe + // Bit-bang 20 bits out MSB-first (OUT19 first). No explicit delay is inserted between + // digitalWrite() calls: the GPIO register write + digitalWrite() call overhead on the + // ESP8266 already exceeds the MAX6921's minimum DIN setup/CLK pulse-width requirement, so + // the commented-out nop/delayMS() calls below are intentionally left disabled. + for (int i=0; i<20; i++) { + if (val & uint32_t(1 << (19 - i))) { + digitalWrite(PIN_DATA, HIGH); + //asm volatile ("nop"); + } + else { + digitalWrite(PIN_DATA, LOW); + //asm volatile ("nop"); + } + + digitalWrite(PIN_CLK,HIGH); //rising edge shifts the current DATA bit into the MAX6921 internal shift register + //asm volatile ("nop"); //delayMS(1); + digitalWrite(PIN_CLK,LOW); + //asm volatile ("nop"); //delayMS(1); + } //end for + + // Latch pulse: transfers the 20-bit shift register into the MAX6921's output latch in one + // shot, so the outputs (and thus the tube) change atomically rather than rippling bit by + // bit while still shifting (which would show visible garbage/flicker on the tube). + digitalWrite(PIN_LE,HIGH ); asm volatile ("nop"); + digitalWrite(PIN_LE,LOW); + digitalWrite(PIN_BL,LOW ); //ON -- unblank now that the new frame is safely latched + } //end else + + state = !state; + timer1_write(timer); //reschedule this one-shot ISR for the next phase (ON or OFF) -- required every call since timer1 here runs in TIM_SINGLE mode +} + +// One-time setup helper (called from setup_pins()): translates the PCB-specific pin-index +// tables from clocks.h (segmentEnablePins[], digitEnablePins[]) and the abstract a..g/DP +// glyph patterns (charDefinition[]) into ready-to-OR 32-bit MAX6921 output bitmaps. Doing +// this heavy lifting once at boot keeps the writeDisplay() ISR itself lightweight (just a +// couple of array lookups + OR + bit-shift loop). +void generateBitTable() { +uint32_t out; + + DPRINTLN("--- Generating segment pins bitmap ---"); + for (int i=0;idown->up" reveal + // used when a digit changes). Each stage progressively adds more segments in a fixed visual + // order (a -> b+f -> g -> e+c -> d), i.e. top segment, then the two middle-height segments, + // then the center bar, then the two lower-middle segments, then the bottom segment -- + // roughly mimicking a wipe sweeping down the 7-segment glyph. + animationMaskBits[0] = uint32_t(1<> "); + + charTable[i] = out; //final per-glyph MAX6921 output bitmap, ready to OR with digitEnableBits[pos] in writeDisplay() + //DPRINTLN(charTable[i],BIN); + } //end for +} + + +void clearTubes() { + // Only asserts BLANK (forces all MAX6921 outputs off) -- it does NOT clear the shift + // register/latch contents or the digit[]/newDigit[] buffers. The stale frame stays latched + // in the chip and will reappear instantly the moment BLANK is deasserted again (e.g. next + // writeDisplay() ISR tick), so this is a cheap/fast "go dark" rather than a true reset. + digitalWrite(PIN_BL,HIGH); +} + +// Intentionally empty: this driver's entire refresh/multiplex logic runs inside the +// timer1 ISR (writeDisplay()), so there is nothing left for a synchronous single-shot write +// to do. The function still needs to exist because the core file (ESP32_UniClock2.ino) calls +// writeDisplaySingle() unconditionally from several places (e.g. after changing a setting). +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/MAX6921_ESP32.ino b/firmware/uniclock/ESP32_UniClock2/MAX6921_ESP32.ino new file mode 100644 index 0000000..9c32b8b --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/MAX6921_ESP32.ino @@ -0,0 +1,309 @@ +#ifdef MAX6921_ESP32 +//=========================================================================================== +// Driver for the MAXIM MAX6921 20-output VFD/vacuum-tube driver chip -- ESP32 variant. +// This is the ESP32 port of MAX6921.ino; the protocol/chip behaviour is identical, only the +// platform-specific timer/interrupt plumbing differs (ESP32 hardware timer + spinlock instead +// of ESP8266 timer1; IRAM_ATTR/DRAM_ATTR instead of ICACHE_RAM_ATTR). See MAX6921.ino's header +// for the full protocol writeup (SPI-like DATA/CLK/LE shift register + BLANK). +// +// Hardware / protocol summary: +// 20-bit serial shift register + latch, MSB (OUT19) first on DATA, one CLK pulse per bit, +// LE (Latch Enable) pulses HIGH->LOW after the 20 bits to atomically transfer the shift +// register into the output latch, BL (BLANK, active HIGH) forces all outputs off. Which +// output pin drives a segment vs. a tube's digit-select grid is purely PCB wiring, described +// by segmentEnablePins[8]/digitEnablePins[] in clocks.h. +// +// Used by (see clocks.h, search for "#define MAX6921_ESP32"): +// CLOCK_26 - ESP32 D1 mini, UNFI 6x IV-11 VFD tubes clock, SHT21 sensor +// CLOCK_30 - ESP32, UNFI PCB clock, 6x IV-11 VFD tubes +// CLOCK_32 - ESP32, UNFI 6x IV-11 VFD tubes clock, DHT22 sensor +// CLOCK_40 - V1 ESP32, UNFI 6x IV-11 VFD tubes clock +// CLOCK_41 - V2 ESP32, UNFI 6x IV-11 VFD tubes clock (jumper wire) +// CLOCK_42 - V3 ESP32, UNFI 6x IV-11 VFD tubes clock +// CLOCK_47 - V3 ESP32, UNFI 10-LT-50G VFD tubes clock +// (all ESP32 boards -- the ESP8266 counterpart configs use MAX6921.ino instead, see that +// file's header for their CLOCK_NN list.) +//=========================================================================================== +//VFD driver for ESP32 + char tubeDriver[] = "MAX6921_ESP32"; + #if defined(ESP32) + #else + #error "Only ESP32 board is supported by this driver!" + #endif + +// POSSIBLE BUG (minor / dead code): declared but never assigned or referenced anywhere else in +// this file (or the project, unlike the same-named `maxDig` pattern used by e.g. +// multiplex74141_ESP32.ino / Numitron_4511N.ino, where it actually caches maxDigits and is used +// in the ISR). Here it is vestigial and can be removed; `maxDigits` is used directly instead. +int DRAM_ATTR maxDig; //memory variable version of maxDigits +//------------------abcdefgDP---------------- definition of different characters +// Each byte below is one glyph's segment pattern, bit7..bit0 = a,b,c,d,e,f,g,DP (a=MSB). +// See generateBitTable(): these abstract a..g/DP bits get remapped through segmentEnablePins[] +// (the PCB-specific wiring) into the actual MAX6921 OUTx bits, producing charTable[]. +// Indices 0-9 are digits; 10-20 are special glyphs referenced via the digit[] buffer. +byte DRAM_ATTR charDefinition[] = { + B11111100, //0: abcdef + B01100000, //1: bc + B11011010, //2: abdeg + B11110010, //3: abcdg + B01100110, //4: bcfg + B10110110, //5: acdfg + B10111110, //6: acdefg + B11100000, //7: abc + B11111110, //8: abcdefg + B11110110, //9: abcdfg + B00000000, // : BLANK (10) + B00000010, //-: minus (11) + B00000001, // decimal point (12) + B11101110, // A abcefg (13) + B11001110, // P abefg (14) + B10011100, // C adef (15) + B11000110, //grad (upper circle) abfg (16) + B10110100, //% acdf (17) + B00111010, //lower circle cdeg (18) + B01100000, //I bc (19) + B10001110 //F aefg (20) +}; + +uint32_t DRAM_ATTR animationMaskBits[5]; //precomputed AND-masks for the "wipe" transition animation (animMask[]), see generateBitTable(). DRAM_ATTR pins these in internal RAM so the IRAM-resident ISR can access them without touching flash-cached memory. + +// NOTE: MAXCHARS/MAXSEGMENTS/maxDigits rely on sizeof() over `byte` arrays (1 byte/element), +// so sizeof() == element count here. segmentEnablePins[] must always have exactly 8 entries +// (a,b,c,d,e,f,g,DP) per the clocks.h convention; digitEnablePins[] length = number of tubes. +#define MAXCHARS sizeof(charDefinition) +#define MAXSEGMENTS sizeof(segmentEnablePins) +int maxDigits = sizeof(digitEnablePins); + +uint32_t DRAM_ATTR charTable[MAXCHARS]; //generated pin table from segmentDefinitions +uint32_t DRAM_ATTR segmentEnableBits[MAXSEGMENTS]; //bitmaps, generated from EnablePins tables +// FIXED (was fixed-size [10] regardless of the actual maxDigits): sized to BUFSIZE (12, see the +// digit[]/newDigit[]/animMask[] buffers in ESP32_UniClock2.ino), the same ceiling the rest of the +// codebase uses for tube-position buffers, so this can no longer silently overflow in +// generateBitTable() for any config the system otherwise supports. +uint32_t DRAM_ATTR digitEnableBits[12]; + +// Multiplex/brightness timing. Despite the variable names, these are NOT milliseconds -- they +// are raw ESP32 hardware-timer tick counts (see startTimer() in ESP32_UniClock2.ino: PRESCALER=15 +// divides the 80MHz APB clock down to roughly a few MHz, so each tick is on the order of 0.2us). +// So PWMrefresh=5500 ticks is roughly ~1ms per ON/OFF half-cycle, PWM_min/PWM_max similarly small. +// Greater PWMrefresh => slower multiplex/refresh frequency (more visible flicker risk). +// All DRAM_ATTR here for the same reason as above: read from inside an IRAM-resident ISR. +int DRAM_ATTR PWMrefresh=5500; //Multiplex time period, in timer ticks (not milliseconds, despite the variable name -- see note above). Greater value => slower multiplex frequency +int DRAM_ATTR PWM_min = 500; +int DRAM_ATTR PWM_max = 5000; +//int DRAM_ATTR PWMtiming[11] = {0,500,800,1200,2000,2500,3000,3500,4000,4500,5000}; + +//----------------------------------------------------------------------------------------- + +//https://sub.nanona.fi/esp8266/timing-and-ticks.html +//One tick is 1us / 80 = 0.0125us = 12.5ns on 80MHz +//One tick is 1us / 80 = 0.0125us = 6.25ns on 160MHz +// 1 NOP is 1 tick + +// Dead/no-op helper: `d` is ignored, so this always executes exactly three NOPs regardless of +// the requested delay. Not called anywhere in this file (see the commented-out delayMS(1) call +// sites in writeDisplay() below) -- relies instead on digitalWrite()'s own call overhead to +// satisfy MAX6921 minimum pulse-width timing. +void inline delayMS(int d) { + asm volatile ("nop"); + asm volatile ("nop"); + asm volatile ("nop"); +} + + +void setup_pins() { + DPRINTLN("VFD Clock - setup MAX6921 pins"); + pinMode(PIN_LE, OUTPUT); regPin(PIN_LE,"PIN_LE"); + pinMode(PIN_BL, OUTPUT); regPin(PIN_BL,"PIN_BL"); + digitalWrite(PIN_BL,LOW); //brightness + pinMode(PIN_DATA,OUTPUT); regPin(PIN_DATA,"PIN_DATA"); + pinMode(PIN_CLK, OUTPUT); regPin(PIN_CLK,"PIN_CLK"); + + // Build a human-readable dump of the pin mapping for the web UI's diagnostics page (not + // used by the driver logic itself). + driverSetupStr = "MAX6921 segmentEnablePins:"; + for (int j=0;j"); + + generateBitTable(); //precompute charTable/digitEnableBits/animationMaskBits once, so the ISR (writeDisplay) never has to do this math + digitsOnly = false; //this driver can show letters/symbols too (A,P,C,deg,%,I,F...), not just 0-9 + startTimer(); //arms the ESP32 hardware timer and attaches writeDisplay() as its ISR -- multiplexing runs entirely in the background from here on +} + +// Hardware-timer ISR (ESP32). Unlike the ESP8266 timer1 (one-shot, must be manually +// rearmed every call), timerAlarmWrite(..., true) below enables auto-reload, so the timer +// keeps firing periodically on its own; the repeated timerAlarmWrite()/timerAlarmEnable() +// calls at the end of every invocation just update the *next* period's length (for the +// following ON or OFF phase) rather than being strictly required to keep it running. +// Implements brightness via a simple two-phase (ON/OFF) time-slicing scheme rather than true +// PWM: each call toggles `state` between an "ON phase" (tube lit, duration proportional to +// brightness) and an "OFF phase" (tube dark, remaining time of the PWMrefresh period). +void IRAM_ATTR writeDisplay(){ //void IRAM_ATTR writeDisplay(){ + static DRAM_ATTR int timer = PWMrefresh; + static DRAM_ATTR uint32_t val; + static DRAM_ATTR byte pos = 0; + static DRAM_ATTR boolean state=true; + static DRAM_ATTR int brightness; + static DRAM_ATTR int PWMtimeBrightness=PWM_min; + + if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! (EEPROM writes are blocking/slow, and touching them concurrently with flash-adjacent SPI activity risks corruption) + //digitalWrite(PIN_BL,HIGH); //OFF + // NOTE: unlike MAX6921.ino (ESP8266), BL is NOT forced HIGH here, so the display is left + // showing whatever frame was last latched (frozen, not blanked) for the duration of the + // EEPROM write. Also, no timerAlarmWrite()/timerAlarmEnable() call is needed here (unlike + // the ESP8266 timer1_write() call in the sibling file) because this hardware timer's + // auto-reload keeps it firing on its own even while this ISR body returns early. + return; + } + + // portENTER_CRITICAL_ISR + noInterrupts()/interrupts() together ensure this whole bit-bang + // sequence (and the shared `val`/`pos`/`state` statics) cannot be interrupted or preempted + // by the other CPU core or a higher-priority ISR mid-frame, which would otherwise risk + // shifting out a half-updated/torn frame to the MAX6921. + portENTER_CRITICAL_ISR(&timerMux); + noInterrupts(); + intCounter++; + + brightness = displayON ? prm.dayBright : prm.nightBright; //day/night dimming level, selected by the core's displayON flag + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + if ((!autoBrightness) && (brightness==MAXBRIGHTNESS)) state = true; //at max fixed brightness, always stay in the "ON" phase (skip the OFF/dimming phase entirely) + + if (state) { //ON state: advance to the next tube position and compute what to shift out + pos++; if (pos>maxDigits-1) { //go to the tube#0 + pos = 0; + // Brightness (PWM ON time) is only recalculated once per full multiplex sweep (at tube#0), + // not per-tube, so all tubes share the same duty cycle within one refresh cycle. + if (autoBrightness && displayON) { //change brightness only on the tube#0 + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); //ambient-light-driven brightness (auto-dimming), scaled by measured lux vs MAXIMUM_LUX + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); //manual brightness setting, scaled 0..MAXBRIGHTNESS into PWM_min..PWM_max ticks + } + + // MAX6921 has no separate "digit select" vs "segment" concept -- both are just output + // bits. Combine the bit for this tube's grid/anode-enable output with the bits for the + // segments that should light up for the current character into one 20-bit frame. + val = (digitEnableBits[pos] | charTable[digit[pos]]); //the full bitmap to send to MAX chip + if (digitDP[pos]) val = val | charTable[12]; //Decimal Point (charTable[12] = the DP-only glyph, OR'd in independently of the digit glyph) + + timer = PWMtimeBrightness; + //if (pos==2) timer = 3*timer; //Weak IV11 tube#2 brightness compensation -- see the CLOCK_4-guarded equivalent in MAX6921.ino; no ESP32 clocks.h config currently needs this hack, left disabled here + timerON = timer; //exposed for debug/diagnostics only (see ESP32_UniClock2.ino) + timerOFF = PWMrefresh-PWMtimeBrightness; + } + else { //OFF state: nothing to shift out this call, just wait out the rest of the duty cycle + timer = PWMrefresh-PWMtimeBrightness; + } + + if (timer<500) timer = 500; //safety only... avoid an excessively short/zero timer reload that could starve the CPU with ISR re-entry + + if ( (brightness == 0) || (!state) || (!radarON)) { //OFF state, blank digit (radarON gates the whole display off when a presence/radar sensor says nobody is around) + digitalWrite(PIN_BL,HIGH); //OFF + } + else { //ON state + if (animMask[pos]>0) val &= animationMaskBits[animMask[pos]-1]; //animationMode 6, mask characters from up to down and back -- animMask[] holds a 1-based progressive-reveal stage per tube; the precomputed masks blank segments in a..g order to fake a vertical wipe + // Bit-bang 20 bits out MSB-first (OUT19 first). No explicit delay is inserted between + // digitalWrite() calls: the GPIO register write + digitalWrite() call overhead already + // exceeds the MAX6921's minimum DIN setup/CLK pulse-width requirement, so the + // commented-out nop/delayMS() calls below are intentionally left disabled. + for (int i=0; i<20; i++) { + if (val & uint32_t(1 << (19 - i))) + {digitalWrite(PIN_DATA, HIGH); //asm volatile ("nop"); + } + else + {digitalWrite(PIN_DATA, LOW); //asm volatile ("nop"); + } + + digitalWrite(PIN_CLK,HIGH); //asm volatile ("nop"); //delayMS(1); //rising edge shifts the current DATA bit into the MAX6921 internal shift register + digitalWrite(PIN_CLK,LOW); //asm volatile ("nop"); //delayMS(1); + } //end for + + // Latch pulse: transfers the 20-bit shift register into the MAX6921's output latch in one + // shot, so the outputs (and thus the tube) change atomically rather than rippling bit by + // bit while still shifting (which would show visible garbage/flicker on the tube). + digitalWrite(PIN_LE,HIGH ); asm volatile ("nop"); + digitalWrite(PIN_LE,LOW); + digitalWrite(PIN_BL,LOW ); //ON -- unblank now that the new frame is safely latched + } //end else + + portEXIT_CRITICAL_ISR(&timerMux); + state = !state; + //ESP32timer = timerBegin(0, PRESCALER, true); //set prescaler, true = edge generated signal + //timerAttachInterrupt(ESP32timer, &writeDisplay, true); + timerAlarmWrite(ESP32timer, timer, true); //schedule the next phase's (ON or OFF) duration; `true` keeps auto-reload enabled + timerAlarmEnable(ESP32timer); + interrupts(); +} + +// One-time setup helper (called from setup_pins()): translates the PCB-specific pin-index +// tables from clocks.h (segmentEnablePins[], digitEnablePins[]) and the abstract a..g/DP +// glyph patterns (charDefinition[]) into ready-to-OR 32-bit MAX6921 output bitmaps. Doing +// this heavy lifting once at boot keeps the writeDisplay() ISR itself lightweight (just a +// couple of array lookups + OR + bit-shift loop). +void generateBitTable() { +uint32_t out; + + DPRINTLN("--- Generating segment pins bitmap ---"); + for (int i=0;idown->up" reveal + // used when a digit changes). Each stage progressively adds more segments in a fixed visual + // order (a -> b+f -> g -> e+c -> d), i.e. top segment, then the two middle-height segments, + // then the center bar, then the two lower-middle segments, then the bottom segment -- + // roughly mimicking a wipe sweeping down the 7-segment glyph. + animationMaskBits[0] = uint32_t(1<> "); + + charTable[i] = out; //final per-glyph MAX6921 output bitmap, ready to OR with digitEnableBits[pos] in writeDisplay() + //DPRINTLN(charTable[i],BIN); + } //end for +} + + +void clearTubes() { + // Only asserts BLANK (forces all MAX6921 outputs off) -- it does NOT clear the shift + // register/latch contents or the digit[]/newDigit[] buffers. The stale frame stays latched + // in the chip and will reappear instantly the moment BLANK is deasserted again (e.g. next + // writeDisplay() ISR tick), so this is a cheap/fast "go dark" rather than a true reset. + digitalWrite(PIN_BL,HIGH); +} + +// Intentionally empty: this driver's entire refresh/multiplex logic runs inside the +// hardware-timer ISR (writeDisplay()), so there is nothing left for a synchronous single-shot +// write to do. The function still needs to exist because the core file (ESP32_UniClock2.ino) +// calls writeDisplaySingle() unconditionally from several places (e.g. after changing a setting). +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/MAX7219CNG.ino b/firmware/uniclock/ESP32_UniClock2/MAX7219CNG.ino new file mode 100644 index 0000000..d9b647b --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/MAX7219CNG.ino @@ -0,0 +1,158 @@ +/*============================================================================== + MAX7219CNG.ino - driver for the MAX7219 (or compatible, e.g. MAX7221/CNG clone) + LED display driver IC. + + Hardware: unlike the MM5450 driver in this project, the MAX7219 has its OWN + internal digit scan/multiplexing (up to 8 digits) and its own PWM intensity + control (REG_INTENSITY, 16 steps), so this driver does NOT use the periodic + hardware-timer ISR / startTimer() at all - there is no writeDisplay() ISR + here, only a blocking writeDisplaySingle() that pushes one full frame (all + digits) whenever the displayed content changes. The chip keeps refreshing + the LEDs by itself between calls. + + Wiring/protocol: standard MAX72xx 3-wire serial bus (DIN/CLK/LOAD-CS), bit- + banged with shiftOut() (MSBFIRST) rather than hardware SPI. Each 16-bit + transfer is {8-bit register/digit address, 8-bit data}, latched into the + chip's internal register on the LOAD/CS_ rising edge. This driver runs the + chip in "No-Decode" mode (REG_DECODE=0) so it can drive arbitrary custom + 7-segment patterns (including the extra glyphs A,P,C,deg,%,I,F) instead of + the chip's built-in BCD/font decoder, which only knows digits 0-9 and a few + symbols. + + Used by (see clocks.h): CLOCK_10 - "8266 LED Clock with MAX7219 chip" + (clocks.h defines MAX7219CNG only inside the #ifdef CLOCK_10 block). +==============================================================================*/ +#ifdef MAX7219CNG +char tubeDriver[] = "MAX7219CNG"; +int maxDigits = 6; +byte tubes[] = {0,1,2,3,4,5}; //change it, if needed for the correct tube sequence +// tubes[i] maps logical digit position i (0..maxDigits-1, left to right as wired to the +// MAX7219's DIG0..DIG7 addresses) to the index into the shared digit[]/digitDP[] buffers - +// lets the PCB wire digits in any physical order without changing the core clock logic. +byte brightConvert[] = {0,1,2,3,4,6,8,10,12,14,15}; //convert brightness from 0..10 to 0..15 +// Non-linear lookup table: the firmware-wide brightness scale is 0..10 (MAXBRIGHTNESS=10 +// for CLOCK_10), but REG_INTENSITY wants 0..15 - this table also biases the mapping +// (skipping value 5/7/etc.) so perceived brightness steps feel more even than a plain x1.5 scale. + +//MAX7219CNG control pins +#define PIN_LOAD 15 // D8 LOAD/CS_ +#define PIN_CLK 14 // D5 Clock +#define PIN_DIN 13 // D7 DataIN + +//MAX7219 Registers (see MAX7219/MAX7221 datasheet "Table 2. Register Address Map") +#define REG_SHUTDOWN 0x0C // 0 = shutdown (blank, low power), 1 = normal operation +#define REG_SCANLIMIT 0x0B // how many digits (0..7, i.e. count-1) the chip actively multiplexes +#define REG_DECODE 0x09 // per-digit BCD-decode enable bitmask; 0 = No-Decode (raw segment bits, used here) +#define REG_INTENSITY 0x0A // global PWM duty cycle for all digits, 0x00 (dimmest) .. 0x0F (brightest) +#define REG_TEST 0x0F // 1 = display-test mode (forces all segments on at full brightness), 0 = normal + +//------------------abcdefgDP---------------- definition of different characters +byte charDefinition[] = { + B11111100, //0: abcdef + B01100000, //1: bc + B11011010, //2: abdeg + B11110010, //3: abcdg + B01100110, //4: bcfg + B10110110, //5: acdfg + B10111110, //6: acdefg + B11100000, //7: abc + B11111110, //8: abcdefg + B11110110, //9: abcdfg + B00000000, // : BLANK (10) + B00000010, //-: minus (11) + B00000001, // decimal point (12) + B11101110, // A abcefg (13) + B11001110, // P abefg (14) + B10011100, // C adef (15) + B11000110, //grad (upper circle) abfg (16) + B10110100, //% acdf (17) + B00111010, //lower circle cdeg (18) + B01100000, //I bc (19) + B10001110 //F aefg (20) +}; + +//Fill this table with the output-bit numbers of the MAX7219 chip! Only No-Decode mode is used, for flexible hardware +// segmentEnablePins[j] = which bit position (0..7) within the MAX7219's per-digit data +// byte drives logical segment j (a,b,c,d,e,f,g,DP) - lets the segment-to-pin wiring on the +// PCB differ from the charDefinition[] bit order without touching the font table itself. +byte segmentEnablePins[] = {6,5,4,3,2,1,0,7}; //segment enable bits of MAX7219 (a,b,c,d,e,f,g,DP) (You MUST always define 8 bits!!!) + +#define MAXCHARS sizeof(charDefinition) // (currently unused in this file - kept for reference/consistency with other drivers) +#define MAXSEGMENTS 8 // (currently unused in this file) + +// Sends one 16-bit MAX7219 command: {address (register or digit 1..8), data byte}. +// LOAD/CS_ must be held LOW for the whole 16-bit transfer and pulsed HIGH afterward to +// latch it - the chip ignores clock edges while LOAD is high, so partial/torn writes +// are not possible even though this is a blocking (non-ISR) call. +void ICACHE_RAM_ATTR sendBits(byte address,byte val){ + digitalWrite(PIN_LOAD, LOW); + shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, address); + shiftOut(PIN_DIN, PIN_CLK, MSBFIRST, val); + digitalWrite(PIN_LOAD, HIGH); + delay(3); // conservative settle/latch delay; MAX7219 datasheet only requires ~tCSW ~ 50ns, + // so this is far more generous than strictly needed (see optimization note in report) + //DPRINT(address,HEX); DPRINT(","); DPRINTLN(val,HEX); +} + +void setup_pins() { +#if defined(ESP8266) +#else + #error "Only 8266 Board is supported!" +#endif + + DPRINTLN("MAX7219 Clock - setup pins..."); + pinMode(PIN_LOAD,OUTPUT); regPin(PIN_LOAD,"PIN_LOAD"); + pinMode(PIN_DIN, OUTPUT); regPin(PIN_DIN,"PIN_DIN"); + pinMode(PIN_CLK, OUTPUT); regPin(PIN_CLK,"PIN_CLK"); + digitsOnly = false; // this driver's charDefinition[] also supports letters (A,P,C,deg,%,I,F) besides 0-9 + // Note: no startTimer() call here - this driver relies on the MAX7219's own internal + // digit multiplexing, so no software multiplex/PWM ISR is needed for this chip. + + sendBits(REG_SHUTDOWN,1); //Set to Normal (not Shutdown) mode + sendBits(REG_TEST,0); //Set to Normal, (Non-Test) mode + sendBits(REG_SCANLIMIT,maxDigits-1); //Set scanlimit (chip multiplexes digits 0..maxDigits-1 only) + sendBits(REG_DECODE,0 ); //Set Non-Decode Mode - segment data is sent as raw bit patterns, not BCD + sendBits(REG_INTENSITY,0x0F); //Set maximum brightness (actual runtime brightness is set per-frame in writeDisplaySingle()) +} + +// Blocking, one-shot push of the ENTIRE current frame (all digits + brightness) to the +// MAX7219. Called directly from the main sketch (not from an ISR - despite the +// ICACHE_RAM_ATTR tag, likely inherited from other drivers' ISR-based writeDisplay() +// pattern; harmless here, just places the function in IRAM instead of flash). +// Because the MAX7219 keeps displaying the last-latched frame by itself (on-chip +// multiplexing), this only needs to run when digit[]/digitDP[]/brightness actually change - +// there is no periodic refresh ISR for this driver (see setup_pins() note). +void ICACHE_RAM_ATTR writeDisplaySingle() { +static byte oldBright = 0; +static byte newBright = 0; +int bitBuffer; + +//https://sub.nanona.fi/esp8266/timing-and-ticks.html +//One tick is 1us / 80 = 0.0125us = 12.5ns on 80MHz +//One tick is 1us / 80 = 0.0125us = 6.25ns on 160MHz +// 1 NOP is 1 tick +// (NOP-timing reference is a leftover from the bit-banged drivers - this file uses +// shiftOut()/delay() instead and doesn't rely on cycle-counted NOPs) + + + newBright = brightConvert[displayON ? prm.dayBright : prm.nightBright]; //convert 0..10 brightness levels to 0..15 + if (!radarON) newBright = 0; // no presence detected -> force display off (still fully addressed/latched, just at 0 intensity) + if (newBright != oldBright) { // only touch the INTENSITY register when the value actually changed, to save a 16-bit transfer + 3ms delay + sendBits(REG_INTENSITY, newBright); //Set Brightness + oldBright = newBright; + } + for (int i=0;i MM5450 output pin number (1..34). +// Digit groups 0,1 belong to display "side" 0, groups 2,3 belong to "side" 1 +// (see sideEnablePins[] and the side-switching logic in writeDisplay()). +byte segmentEnablePins[4][8] = { + {16, 15, 14, 13, 12, 11, 10, 9}, //segment enable pins of MM5450 (a,b,c,d,e,f,g,DP) + {8, 7, 6, 5, 4, 3, 2, 1}, + {17, 18, 19, 20, 21, 22, 23, 24}, + {25, 26, 27, 28, 29, 30, 31, 32} +}; + + +/* Old version - kept for reference only, NOT compiled (different pin-to-output mapping, + presumably from an earlier PCB revision with different MM5450 output wiring). + byte segmentEnablePins[4][8] = { + {25,26,27,28,29,30,31,32}, //segment enable pins of MM5450 (a,b,c,d,e,f,g,DP) + {17,18,19,20,21,22,23,24}, + {9,10,11,12,13,14,15,16}, + {1,2,3,4,5,6,7,8} + }; +*/ + + +byte sideEnablePins[] = {34, 33}; //enable pins for the two groups of 4 digits (2x4 LEDs) +// sideEnablePins[0] = MM5450 output 34 = enable line for "side 0" (digit groups 0,1) +// sideEnablePins[1] = MM5450 output 33 = enable line for "side 1" (digit groups 2,3) +// Only one side's enable line is driven HIGH at a time -> the two 4-digit +// groups share the same 32 segment lines and are multiplexed in time. + +//MM5450 control pins (GPIO numbers - see clocks.h D5pin/D6pin/etc for the NodeMCU Dx aliases: +// GPIO14=D5, GPIO12=D6, GPIO13=D7, GPIO15=D8) +#define PIN_LE 14 // D5 Shift Register Latch Enable +#define PIN_CLK 12 // D6 Shift Register Clock +#define PIN_DATA 13 // D7 Shift Register Data +#define PIN_BR 15 // D8 Shift Register Brightness (chip-enable/blank output, active HIGH) + +// Bitmasks for the same pins, used with WRITE_PERI_REG(PIN_OUT_SET/CLEAR, ...) for fast +// direct GPIO register access inside the ISR (digitalWrite() is too slow/has too much +// call overhead to meet the tight bit-banging timing below). +#define PIN_LE_BIT 1<<14 // D5 Shift Register Latch Enable +#define PIN_CLK_BIT 1<<12 // D6 Shift Register Clock +#define PIN_DATA_BIT 1<<13 // D7 Shift Register Data +#define PIN_BR_BIT 1<<15 // D8 Shift Register Brightness + +//------------------abcdefgDP---------------- definition of different characters +byte charDefinition[] = { + B11111100, //0: abcdef + B01100000, //1: bc + B11011010, //2: abdeg + B11110010, //3: abcdg + B01100110, //4: bcfg + B10110110, //5: acdfg + B10111110, //6: acdefg + B11100000, //7: abc + B11111110, //8: abcdefg + B11110110, //9: abcdfg + B00000000, // : BLANK (10) + B00000010, //-: minus (11) + B00000001, // decimal point (12) + B11101110, // A abcefg (13) + B11001110, // P abefg (14) + B10011100, // C adef (15) + B11000110, //grad (upper circle) abfg (16) + B10110100, //% acdf (17) + B00111010, //lower circle cdeg (18) + B01100000, //I bc (19) + B10001110 //F aefg (20) +}; + +#define MAXCHARS sizeof(charDefinition) // (currently unused in this file - kept for reference/consistency with other drivers) + +int PWMrefresh = 10000; //usec, Multiplex time period (one full ON+OFF brightness cycle). Greater value => slower multiplex/PWM frequency +// PWM_min/PWM_max bound the ON-time portion of PWMrefresh: since the MM5450 has no +// hardware intensity register, brightness is entirely software PWM - writeDisplay() +// re-arms the hw timer with a shorter or longer "ON" interval depending on the +// requested brightness (see PWMtimeBrightness below). +int PWM_min = 500; +int PWM_max = 10000; +//int PWMtiming[11] = {0,500,800,1200,2000,2500,3000,4500,6000,8000,10000}; + +// bitBuffer holds the full 34-bit MM5450 serial frame plus its leading start bit +// (bitBuffer[0] = start bit, bitBuffer[1..34] = one byte per output pin, 0/1 = LED off/on). +// One array element is used per bit (not packed) to make indexing by MM5450 pin number +// trivial for writeBits(); size 36 leaves a little headroom beyond the 35 bits actually sent. +byte bitBuffer[36]; + +void setup_pins() { +#if defined(ESP8266) +#else +#error "Only 8266 Board is supported!" +#endif + + DPRINTLN("MM5450 - Setup pins..."); + pinMode(PIN_LE, OUTPUT); regPin(PIN_LE, "PIN_LE"); + pinMode(PIN_BR, OUTPUT); regPin(PIN_BR, "PIN_BR"); + pinMode(PIN_DATA, OUTPUT); regPin(PIN_DATA, "PIN_DATA"); + pinMode(PIN_CLK, OUTPUT); regPin(PIN_CLK, "PIN_CLK"); + digitalWrite(PIN_BR, HIGH); //brightness + + digitsOnly = false; // this driver's charDefinition[] also supports letters (A,P,C,deg,%,I,F) besides 0-9 + startTimer(); // arms the periodic hw timer -> writeDisplay() becomes the multiplex/PWM ISR + +} + +// Bit-bangs one full 35-bit frame (start bit + 34 output bits) into the MM5450 shift +// register and latches it. Runs inside the timer ISR (called from writeDisplay()), so it +// uses direct GPIO SET/CLEAR peripheral-register writes instead of digitalWrite() - the +// latter's call/pin-lookup overhead would blow the tight clock pulse widths needed here. +void ICACHE_RAM_ATTR writeBits() { + //https://sub.nanona.fi/esp8266/timing-and-ticks.html + //One tick is 1us / 80 = 0.0125us = 12.5ns on 80MHz + //One tick is 1us / 80 = 0.0125us = 6.25ns on 160MHz + // 1 NOP is 1 tick + + //noInterrupts(); // not needed: this code already runs inside the timer ISR itself + WRITE_PERI_REG( PIN_OUT_CLEAR, PIN_LE_BIT ); //DATA_ENABLE - LE must be LOW while shifting data in + for (int t = 0; t < 5; t++) asm volatile ("nop"); //100nsec + + for (int i = 0; i < 36; i++) { // shift bitBuffer out LSB-index-first (index 0 = start bit, sent first) + if (bitBuffer[i]) { + WRITE_PERI_REG( PIN_OUT_SET, PIN_DATA_BIT ); //digitalWrite(PIN_DATA,HIGH); + } + else { + WRITE_PERI_REG( PIN_OUT_CLEAR, PIN_DATA_BIT ); //digitalWrite(PIN_DATA,LOW); + } + + for (int t = 0; t < 12; t++) asm volatile ("nop"); //300nsec - data setup time before clock edge + + WRITE_PERI_REG( PIN_OUT_SET, PIN_CLK_BIT ); // MM5450 samples DATA on this rising CLK edge + for (int t = 0; t < 12; t++) asm volatile ("nop"); //300nsec + WRITE_PERI_REG( PIN_OUT_CLEAR, PIN_CLK_BIT ); + for (int t = 0; t < 12; t++) asm volatile ("nop"); //300nsec + } //end for + + WRITE_PERI_REG( PIN_OUT_SET, PIN_LE_BIT ); // rising edge on LE latches the shifted-in frame to the outputs + //interrupts(); +} + +// Main multiplex/PWM ISR, attached to the ESP8266 hw timer1 by startTimer(). +// timer1 is configured TIM_SINGLE (one-shot), so this handler must re-arm itself via +// timer1_write() at the very end of every call - the display refresh rate/PWM duty +// cycle is entirely driven by how long each re-armed interval is (see 'timer' below). +// Each call does ONE of: (a) blank the display for the computed OFF duration (brightness +// PWM low phase), or (b) blank for the ON duration but first shift out a fresh frame for +// whichever "side" (group of 4 digits) is up next, alternating sides on every ON call. +void ICACHE_RAM_ATTR writeDisplay() { // Writes to the MM5450 driver for LEDS + static volatile int timer = PWMrefresh; + static volatile boolean state = true; // true = ON phase of the PWM cycle, false = OFF phase + static volatile int brightness; + static int PWMtimeBrightness; + static volatile uint32_t val; + static volatile byte pos = 0; + static byte side = 0; + byte num = 0; + + if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! + // Flash writes on ESP8266 briefly disable interrupts/stall the CPU; touching the + // display bus during that window could corrupt the SPI-like bit-bang timing, so we + // just keep re-arming the timer with a fixed period and skip the actual refresh. + timer1_write(PWMrefresh); + return; + } + + intCounter++; + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness > MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + if (autoBrightness && displayON) { + // Ambient-light (lx) driven brightness: brighter room -> longer ON time within PWMrefresh + PWMtimeBrightness = max(PWM_min, PWM_max * lx / MAXIMUM_LUX); + } + else + // Manual brightness setting (0..MAXBRIGHTNESS) mapped linearly onto the ON-time window + PWMtimeBrightness = max(PWM_min, PWM_max * brightness / MAXBRIGHTNESS); + + if ((!autoBrightness) && (brightness == MAXBRIGHTNESS)) + // At max manual brightness PWMtimeBrightness == PWMrefresh, so the OFF phase would be + // ~0us; forcing state=true here keeps the ISR permanently in the ON branch instead of + // wastefully toggling into a near-zero-length OFF phase every other call. + state = true; + + if (state) { //ON state + timer = PWMtimeBrightness; + timerON = timer; + timerOFF = PWMrefresh - PWMtimeBrightness; + } + else { //OFF state + timer = PWMrefresh - PWMtimeBrightness; + } + if (timer < 500) timer = 500; //safety only... (avoid re-arming the hw timer with too short/zero an interval) + + if ( (brightness == 0) || (!state) || (!radarON)) { //OFF state, blank digit + WRITE_PERI_REG( PIN_OUT_CLEAR, PIN_BR_BIT ); //OFF - BR pin blanks all MM5450 outputs regardless of shift-register content + // radarON reflects presence-detection (radar/PIR): when nobody is around the display + // is blanked here (power saving / tube-life) without touching the multiplex state. + } + else { //ON state + WRITE_PERI_REG( PIN_OUT_SET, PIN_BR_BIT ); //ON + + memset(bitBuffer, 0, sizeof(bitBuffer)); //clear array + bitBuffer[0] = 1; //starting bit required to frame the 34 data bits (see MM5450 datasheet) + + bitBuffer[sideEnablePins[side]] = 0; + writeBits(); // 1st transfer: push an all-blank frame (except start bit) so BOTH sides are + // momentarily off before the new side's segments are shifted in - avoids + // ghosting/cross-talk between the outgoing and incoming digit group. + for (int t = 0; t < 100; t++) asm volatile ("nop"); //clear display and wait between switching the two sides + bitBuffer[sideEnablePins[side]] = 0; //disable old side.. + // NOTE: bitBuffer was just memset to 0 a few lines above and this index was already + // cleared before the previous writeBits() call, so this re-assignment is a redundant + // no-op (POSSIBLE dead/duplicated code - likely meant to clear something else, e.g. + // the *other* side, but 'side' has not been toggled yet at this point). + + if (side == 0) side = 1; else side = 0; //change side... + + for (int i = 0; i < 4; i++) { + pos = 7 - (4 * side + i); + // POSSIBLE BUG: pos always ranges 0..7 (8 digit positions) regardless of maxDigits + // (=6 above). digit[6] and digit[7] are never written by the core display-update + // logic when maxDigits==6, and being plain (non-initialized-to-BLANK) globals they + // default to 0 -> segment pattern for '0'. If the physical PCB for CLOCK_11 really + // only has 6 digits wired, positions 6/7 are presumably just unused MM5450 outputs + // (harmless); if it has 8 digits, the top 2 would never be updated by the clock logic. + num = digit[pos]; + //if (animMask[pos]>0) val &= animationMaskBits[animMask[pos]-1]; //animationMode 6, mask characters from up to down and back + // (wipe/mask animation support - referenced 'val' and 'animationMaskBits' are unused/ + // undefined in this file, so this feature is not implemented for the MM5450 driver) + for (int j = 0; j <= 7; j++) //from a to g + if ((charDefinition[num] & 1 << (7 - j)) != 0) { + bitBuffer[segmentEnablePins[i][j]] = 1; + } + if (digitDP[pos]) bitBuffer[segmentEnablePins[i][7]] = 1; //Decimal Point + } //end for + + bitBuffer[sideEnablePins[side]] = 1; //enable new side + writeBits(); // 2nd transfer: actual segment data + new side's enable bit + } //end else ON + + state = !state; + timer1_write(timer); // re-arm the one-shot hw timer for the next ON or OFF phase +} + + +// Both required by the common driver interface but intentionally no-ops here: this driver +// does all display updates from the writeDisplay() ISR (brightness PWM + side multiplex +// depend on continuous timer-driven refresh), so there is nothing useful for a one-shot +// blank (clearTubes) or a one-shot blocking full-frame write (writeDisplaySingle) to do - +// the next ISR tick will simply pick up the current digit[]/digitDP[] contents. +void clearTubes() {} +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/Numitron_4511N.ino b/firmware/uniclock/ESP32_UniClock2/Numitron_4511N.ino new file mode 100644 index 0000000..86691e3 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/Numitron_4511N.ino @@ -0,0 +1,354 @@ +// ============================================================================ +// Numitron_4511N driver +// ---------------------------------------------------------------------------- +// Drives IV-16 (and similar) Numitron filament (resistive, incandescent-style +// 7-segment) tubes through one CD4511/74HC4511 BCD-to-7-segment latch/decoder +// chip PER TUBE. Numitron tubes are NOT gas-discharge (like Nixies) or +// vacuum-fluorescent (like VFDs): each segment is a tiny resistive filament, +// so "brightness" here is achieved purely by PWM-blanking the whole panel's +// power rail (via LTBIpin), not by varying per-segment current the way a +// current-driven display might. +// +// Wiring / protocol: +// - ABCDPins[4] : 4 GPIOs = the shared BCD data bus (bits A..D), +// wired in parallel to the BCD inputs of every +// 4511 chip on the panel (one chip per digit). +// - digitEnablePins[] : one GPIO per digit/tube position, each wired to +// the LE (Latch Enable) pin of that digit's own +// 4511. Because the BCD bus is shared, only ONE +// digit's latch may be allowed to "see" the bus +// at a time while the bus is being changed - see +// the per-chip strobe sequence in +// writeDisplaySingle() below. +// - LTBIpin : "Lamp Test / Blanking Input" - shared across the +// whole panel. Driving it LOW blanks all digits +// instantly regardless of latched BCD data; this +// is exploited as a cheap panel-wide PWM dimming +// switch (see writeDisplay()) instead of touching +// each digit's latch content. +// - PCB_VERSION : identifies which physical GP Numitron carrier PCB +// revision (1, 3, or 32) the code is running on; +// see the panelVersion note near the ESP8266 +// writeDisplay() below - currently this only +// affects code that is commented out, so on the +// live code path PCB_VERSION has no effect. +// +// Used by (see clocks.h, search "Numitron_4511N"): +// - CLOCK_8 : ESP8266, GP PCB v3, 4x IV-16 Numitron tubes (plexi box) +// - CLOCK_9 : ESP8266, GP PCB v1, 4x IV-16 Numitron tubes (brown box) +// - CLOCK_13 : ESP8266, GP PCB v1/v3, 4x IV-16 Numitron tubes + GPS timesync +// - CLOCK_46 : ESP32, UNFI board, 6x IV-9 Numitron tubes +// ============================================================================ + +#ifdef Numitron_4511N +char tubeDriver[] = "Numitron_4511N"; +//used by GP Numitron v3 panel + +// digitEnablePins[], ABCDPins[] and LTBIpin are actually #defined/declared +// per-clock in clocks.h (see CLOCK_8/9/13/46 blocks) - the lines below are +// only a commented-out reference/example of what those definitions look like. +//#define LTBIpin 5 +//byte digitEnablePins[] = {13,12,14,16}; //define here the digit enable pins from 4 to 8 +//byte ABCDPins[4] = {4,0,2,15}; + +// digitEnablePins[] is a byte array, so sizeof() here correctly yields the +// element count (number of tube positions), not a byte size in a larger type. +int maxDigits = sizeof(digitEnablePins); + +// NOTE: unlike position-multiplexed displays, this driver does not cycle +// through digit positions in the timer ISR (see writeDisplay() below) - all +// digits are latched simultaneously and stay lit continuously. PWMrefresh/ +// PWM_min/PWM_max instead define a slow (sub-kHz) panel-wide ON/OFF blanking +// cycle (via LTBIpin) used purely for brightness dimming, not for scanning +// digits. DRAM_ATTR pins these vars in internal RAM on ESP32 so the ISR +// (running from IRAM) can access them without touching flash-backed memory. +#if defined(ESP32) + int DRAM_ATTR PWMrefresh=5500; //msec, Multiplex time period. Greater value => slower multiplex frequency + //int DRAM_ATTR PWMtiming[11] = {0,500,800,1200,2000,2500,3000,3500,4000,4500,5000}; + int DRAM_ATTR PWM_min = 500; + int DRAM_ATTR PWM_max = 5000; + int DRAM_ATTR maxDig=maxDigits; +#else + int PWMrefresh=5500; //msec, Multiplex time period. Greater value => slower multiplex frequency + int PWM_min = 500; + int PWM_max = 5000; + int maxDig=maxDigits; + //int PWMtiming[11] = {0,500,800,1200,2000,2500,3000,3500,4000,4500,5000}; +#endif + +void setup_pins() { + char tmp[30]; + + DPRINTLN("Numitron Clock - setup pins"); + delay(1000); + pinMode(LTBIpin, OUTPUT); regPin(LTBIpin,"LTBIpin"); + digitalWrite(LTBIpin,HIGH); //panel enabled (not blanked) by default until the first PWM tick decides otherwise + for (int i=0;iMAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + if (autoBrightness && (displayON )) { + // Ambient-light (lux) driven brightness: scale the ON-time fraction of the + // PWMrefresh period by measured lux against MAXIMUM_LUX, clamped to PWM_min + // so the panel is never driven at a 0% duty cycle (would look "dead"). + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + + if ((!autoBrightness) && (brightness==MAXBRIGHTNESS)) + state = true; // at full fixed brightness, force the next phase to start as ON so the panel doesn't sit in a leftover OFF phase + + if (state) { //ON state + timer = PWMtimeBrightness; + timerON = timer; + timerOFF = PWMrefresh-PWMtimeBrightness; + } + else { //OFF state + timer = PWMrefresh-PWMtimeBrightness; + } + if (timer<500) timer = 500; //safety only... avoids re-arming the hardware timer with too short/zero a period, which could stall the ISR cadence + if ( (brightness == 0) || (!state) || (!radarON)) { //OFF state, blank digit + digitalWrite(LTBIpin,LOW); //disable display -- global blank: LTBIpin overrides all latched BCD data on every 4511 at once, regardless of what's latched + #if COLON_PIN >=0 + digitalWrite(COLON_PIN,LOW); // Blink colon pin + #endif + } + else { //ON state + digitalWrite(LTBIpin,HIGH); //enable display + #if COLON_PIN >=0 + digitalWrite(COLON_PIN,colonBlinkState); + #endif + } + state = !state; //flip ON/OFF phase for the next ISR invocation + portEXIT_CRITICAL_ISR(&timerMux); + timerAlarmWrite(ESP32timer, timer, true); //re-arm with the just-computed (variable) period - this is what makes the PWM period self-scheduling instead of a fixed tick + timerAlarmEnable(ESP32timer); + interrupts(); +} + +// Blocking, one-shot write of the whole digit[] buffer into the tube latches. +// Because ABCDPins is a single bus shared by every digit's 4511, digits must +// be written one at a time: pulse that digit's own LE line while the bus +// carries its value, so only that digit's latch captures it. +void IRAM_ATTR writeDisplaySingle() { + int num; + + // FIXED (was "pos<=maxDig", an off-by-one that read digitEnablePins[maxDig] - one element past + // the end of that array, from clocks.h - and called digitalWrite() with whatever garbage byte + // happened to sit there in memory). Valid indices are 0..maxDig-1 (maxDig == maxDigits == the + // actual array length, e.g. 4 or 6). + for (int pos=0; pos drive ABCD with the new value -> LE LOW (latch + // it in, freezing it against subsequent bus changes for the next digit). + // This code instead does LE LOW -> drive ABCD -> LE HIGH, which leaves + // every already-processed digit's latch in the *transparent* state, so it + // may keep following the shared bus as later positions are written - + // potentially causing all/most tubes to end up showing the last digit's + // value, or visible cross-talk between positions. If this matches the + // actual physical panel behavior it may be intentional/compensated in + // hardware, but it contradicts the datasheet's normal usage - flagging + // for hardware verification rather than "fixing" blind. + digitalWrite(digitEnablePins[pos],LOW); //latch enable + for (int j=0;j<4;j++) {digitalWrite(ABCDPins[j],num & (1<=3 (only v3 panels apparently supported blanking this way). +// It also multiplexed the decimal-point/dot output (dpCounter cycling +// through digitDP[]) and inverted the colon blink polarity relative to the +// active implementation below. All of this was superseded by the simpler +// LTBIpin-based panel-wide PWM blanking used today, which is why dpCounter, +// brightCounter, DP_PIN and digitDP[] are effectively unused by this driver +// now - decimal points/dots are NOT currently displayed by Numitron_4511N. +/* + if (((panelVersion>=3) && ((displayON ? prm.dayBright : prm.nightBright)MAXBRIGHTNESS) brightCounter = 1; + if (brightCounter == 1) { + dpCounter++; if (dpCounter>maxDig) dpCounter = 0; + #if defined(DP_PIN) && (DP_PIN>=0) + digitalWrite(DP_PIN,digitDP[dpCounter]); + #endif + } + #if COLON_PIN >=0 + digitalWrite(COLON_PIN,!colonBlinkState); // Blink colon pin + #endif +*/ + + // Same panel-wide PWM-blanking scheme as the ESP32 writeDisplay() above: + // compute the ON-time (PWMtimeBrightness) out of the PWMrefresh period from + // either lux (auto) or the configured day/night brightness, then toggle + // LTBIpin and reschedule the ESP8266 hardware timer (timer1) for the next + // phase's duration. See the ESP32 branch for the fuller rationale. + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + if (autoBrightness && (displayON )) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + + if ((!autoBrightness) && (brightness==MAXBRIGHTNESS)) + state = true; + + if (state) { //ON state + timer = PWMtimeBrightness; + timerON = timer; + timerOFF = PWMrefresh-PWMtimeBrightness; + } + else { //OFF state + timer = PWMrefresh-PWMtimeBrightness; + } + if (timer<500) timer = 500; //safety only... avoid a near-zero/zero timer1 reload value + if ( (brightness == 0) || (!state) || (!radarON)) { //OFF state, blank digit + digitalWrite(LTBIpin,LOW); //disable display + #if COLON_PIN >=0 + digitalWrite(COLON_PIN,LOW); // Blink colon pin + #endif + } + else { //ON state + digitalWrite(LTBIpin,HIGH); //enable display + #if COLON_PIN >=0 + digitalWrite(COLON_PIN,colonBlinkState); + #endif + } + state = !state; + + timer1_write(timer); //ESP8266 hardware timer1 reload, analogous to timerAlarmWrite() on the ESP32 branch +} + +// Blocking, one-shot write of the whole digit[] buffer into the tube latches +// (ESP8266 twin of the ESP32 writeDisplaySingle() above - see that copy for +// the detailed strobe-order/latch-semantics discussion, which applies here +// identically). +void writeDisplaySingle() { + int num; + // FIXED: same off-by-one as the ESP32 writeDisplaySingle() above was ("pos<=maxDig", one element + // past the end of digitEnablePins[]) - see that copy for the detailed explanation. + for (int pos=0; posset bus->HIGH leaves the latch transparent instead of latching + // it) - see the detailed comment in the ESP32 writeDisplaySingle(). + digitalWrite(digitEnablePins[pos],LOW); //latch enable + for (int j=0;j<4;j++) {digitalWrite(ABCDPins[j],num & (1< */ + 0b10010100000000111, /* ? */ + 0b00000101011110111, /* @ */ + 0b01000100011001111, /* A */ + 0b00010101000111111, /* B */ + 0b00000000011110011, /* C */ + 0b00010001000111111, /* D */ + 0b01000000011110011, /* E */ + 0b01000000011000011, /* F */ + 0b00000100011111011, /* G */ + 0b01000100011001100, /* H */ + 0b00010001000110011, /* I */ + 0b00000000001111100, /* J */ + 0b01001010011000000, /* K */ + 0b00000000011110000, /* L */ + 0b00000010111001100, /* M */ + 0b00001000111001100, /* N */ + 0b00000000011111111, /* O */ + 0b01000100011000111, /* P */ + 0b00001000011111111, /* Q */ + 0b01001100011000111, /* R */ + 0b01000100010111011, /* S */ + 0b00010001000000011, /* T */ + 0b00000000011111100, /* U */ + 0b00100010011000000, /* V */ + 0b00101000011001100, /* W */ + 0b00101010100000000, /* X */ + 0b01000100010111100, /* Y */ + 0b00100010000110011, /* Z */ + 0b00010001000010010, /* [ */ + 0b00001000100000000, /* \ */ + 0b00010001000100001, /* ] */ + 0b00101000000000000, /* ^ */ + 0b00000000000110000, /* _ */ + 0b00000000100000000, /* ` */ + 0b01010000001110000, /* a */ + 0b01010000011100000, /* b */ + 0b01000000001100000, /* c */ + 0b00010100000011100, /* d */ + 0b01100000001100000, /* e */ + 0b01010101000000010, /* f */ + 0b01010001010100001, /* g */ + 0b01010000011000000, /* h */ + 0b00010000000000000, /* i */ + 0b00010001001100000, /* j */ + 0b00011011000000000, /* k */ + 0b00000000011000000, /* l */ + 0b01010100001001000, /* m */ + 0b01010000001000000, /* n */ + 0b01010000001100000, /* o */ + 0b01000001011000001, /* p */ + 0b01010001010000001, /* q */ + 0b01000000001000000, /* r */ + 0b01010000010100001, /* s */ + 0b01000000011100000, /* t */ + 0b00010000001100000, /* u */ + 0b00100000001000000, /* v */ + 0b00101000001001000, /* w */ + 0b00101010100000000, /* x */ + 0b00000101000011100, /* y */ + 0b01100000000100000, /* z */ + 0b01010001000010010, /* { */ + 0b00010001000000000, /* | */ + 0b00010101000100001, /* } */ + 0b01100110000000000, /* ~ */ + 0b00000000000000000, /* (del) */ +}; + + //byte segmentEnablePins[] = {16,7,0,1,2,3,4,5,6,8,9,11,10,12,13,14,15}; //segment enable bits +#endif + + +// Maps the special glyph codes (BLANK, minus, DP, A,P,C,deg,%,circle,I,F -- digit[] values +// 10..20) onto printable ASCII characters, used only when ASCIImode is true (16-segment +// branch) so the digit[i] index can be turned into an ASCII char before it is used to look up +// charDefinition[]. Note this string is 1:1 positional with the 10..20 glyph codes, i.e. +// asciiConvert[0] corresponds to digit value 0 ('0'), asciiConvert[10] (' ') to BLANK, etc. +char asciiConvert[] = "0123456789 -.APC~%oIF"; + +#define MAXCHARS sizeof(charDefinition)/sizeof(charDefinition[0]) + +// Bit-bangs one byte out on PIN_DIN/PIN_CLK. NOTE: LSB-first (i starts at 0, tests bit 1<10) newBright = 10; //clamp: DISPLAY_MODE[] only has 11 entries (indices 0..10) + + digitalWrite(PIN_LOAD, LOW); //safety only + delayMicroseconds(2); + + // C2_DATA_SETTING switches the chip into "write display data / auto-increment address" + // mode; DISPLAY_MODE[newBright] is then a second command byte (not display data) setting + // brightness/on-off -- both are sent back-to-back inside the same PIN_LOAD-low window + // opened just above. + sendCommand(C2_DATA_SETTING); + sendCommand(DISPLAY_MODE[newBright]); + +for (int i=0;i= sizeof(asciiConvert), or dispChar==21 + // landing on the string's NUL terminator) - that produced a negative index into + // charDefinition[] a few lines down (out-of-bounds read). Now the subtraction only happens + // together with a successful lookup; anything unmapped falls back to charDefinition[0], the + // blank/space glyph, instead of indexing out of bounds. + if (dispChar blank glyph (charDefinition[0]) + } + } + bitBuffer = 0; + digitalWrite(PIN_LOAD,LOW); + delayMicroseconds(2); + // Address increments by 3 per digit (not 1) -- the PT6311 reserves 3 RAM bytes per digit + // position internally (enough for up to 24 segment bits), even though this build only + // uses 1-2 of them (MAXSEGMENTS is 8 or 17, see charDefinition table above). + shift(C3_ADDRESS_SETTING + i*3); //set display address + for (int j=0;j>8); + if (MAXSEGMENTS>16) shift(bitBuffer >>16); + delayMicroseconds(2); + digitalWrite(PIN_LOAD,HIGH); + delayMicroseconds(2); + } //end for i + +/* //TEST mode + digitalWrite(PIN_LOAD, LOW); //upload data + delayMicroseconds(2); + shift(C3_ADDRESS_SETTING); + for (int i=0;i<48;i++) { + shift(0xff); // Data to fill table 5*16 = 80 bits + //shift(0x00); // Data to fill table 5*16 = 80 bits + //shift(0xff); // Data to fill table 5*16 = 80 bits + } + digitalWrite(PIN_LOAD, HIGH); + delay(2); +*/ +} + +/*********************** Send command without strobe/chip select******************************************/ + +// No-op: this driver has no timer-ISR multiplex loop (see file header) -- the PT6311 refreshes +// its own tubes internally, so there is nothing for the periodic writeDisplay() ISR hook to do. +void writeDisplay() {} +// clearTubes() is a required part of the common driver interface, but nothing in the core .ino +// calls it any more (its only former caller, disableDisplay(), has been removed entirely as dead +// code) - kept as a no-op stub purely for interface compliance. If a synchronous immediate-blank is +// ever needed again, this would have to actively send an all-blank frame instead. +void clearTubes() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/PT6355.ino b/firmware/uniclock/ESP32_UniClock2/PT6355.ino new file mode 100644 index 0000000..ac63d14 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/PT6355.ino @@ -0,0 +1,354 @@ +#ifdef PT6355 +//=========================================================================================== +// Driver for the Princeton Technology PT6355 VFD controller/driver IC. +// +// Hardware / protocol: +// Like the PT6311 (see PT6311.ino), the PT6355 is a "smart" VFD grid/segment controller +// with its own display RAM and internal multiplex/scan generator -- the MCU only pushes a +// full frame after something changes, the chip keeps scanning the tubes on its own. That is +// why writeDisplay() (the timer-ISR multiplex hook used by "dumb" shift-register drivers) +// is an empty stub here and setup_pins() never calls startTimer(): all display updates go +// through the blocking writeDisplaySingle() instead of an ISR-driven refresh loop. +// 3-wire serial bus (similar to SPI, chip-specific framing): +// PIN_CS - LOW for the duration of one whole command or one whole per-frame data burst +// (see startBits()/stopBits()), HIGH otherwise. Unlike the PT6311 driver, this +// chip's frame is NOT re-strobed byte-by-byte: PIN_CS stays low across the +// entire multi-digit data burst in writeDisplaySingle(). +// PIN_CLK - one pulse per bit. +// PIN_SIN - serial data, LSB-first per byte (see shift(): loop starts at bit 0). +// Note this file structurally mirrors PT6311.ino very closely (same DISPLAY_MODE-less +// brightness idea, same 8-segment/16-segment charDefinition tables, same ASCIImode / +// asciiConvert[] translation) even though the two chips are different parts with different +// command sets and bus framing -- treat any future edits to the shared-looking tables in +// one file as NOT automatically applicable to the other without checking its own datasheet. +// +// Used by (see clocks.h, search for "#define PT6355"): +// CLOCK_12 - ESP8266 VFD clock with PT6355 chip +//=========================================================================================== +char tubeDriver[] = "PT6355"; +//int maxDigits = 6; +int wt = 5; //Serial timing + +//#define PIN_CS 22 //12 or 15 // D6 CS_ +//#define PIN_CLK 16 // D7 Clock +//#define PIN_SIN 21 // D5 DataIN + +//#define SEGMENT8 +//#define TESTMODE //for testing the segments of a new type VFD module + +//PT6355 Commands +#define C1_DISPLAY_STATE_SETTING B11000000 // Display duty setting=15 + Display ON +#define C3_PORT_DATA_SETTINGS B10000000 //Port selection + output data + +#ifdef SEGMENT8 +//------------------abcdefgDP---------------- definition of different characters +// C0 selects "10 grids, <=16 segments/grid" data-write mode; C2 sets which physical output +// pin the grid (digit-select) scan starts from (D17..D7 range) -- both are chip-mode setup +// commands, sent once per full frame in writeDisplaySingle(), not per digit. +#define C0_DISPLAY_DATA_SETTING B11101100 //grid=10, segments<=16 +#define C2_GRID_SELECTION B10101000 //Grid start pin setting D17..D7 +// 8-bit segment mask per glyph, MSB-first as a,b,c,d,e,f,g,DP (bit7=a .. bit0=DP). Indices +// 0-9 = digits, 10 = blank, 11 = minus, 12 = decimal point, 13+ = extra glyphs (A,P,C,deg,%, +// lower-circle,I,F) -- matches the digit[]/newDigit[] encoding used by the core firmware, so +// digit[] values index straight into this table (see also the identical table in PT6311.ino). +#define MAXSEGMENTS 8 +boolean ASCIImode = false; +byte charDefinition[] = { + B11111100, //0: abcdef + B01100000, //1: bc + B11011010, //2: abdeg + B11110010, //3: abcdg + B01100110, //4: bcfg + B10110110, //5: acdfg + B10111110, //6: acdefg + B11100000, //7: abc + B11111110, //8: abcdefg + B11110110, //9: abcdfg + B00000000, // : BLANK (10) + B00000010, //-: minus (11) + B00000001, // decimal point (12) + B11101110, // A abcefg (13) + B11001110, // P abefg (14) + B10011100, // C adef (15) + B11000110, //grad (upper circle) abfg (16) + B10110100, //% acdf (17) + B00111010, //lower circle cdeg (18) + B01100000, //I bc (19) + B10001110 //F aefg (20) +}; + +//byte segmentEnablePins[] = {9,8,5,3,4,7,6,2}; //segment enable bits (a,b,c,d,e,f,g,DP) (You MUST define always 8 bits!!!) + +#else //16 segment + DP --------------------------------------------------------------------------------------------- +#define C0_DISPLAY_DATA_SETTING B11101111 //grid=10, segments>17 +#define C2_GRID_SELECTION B10101000 //Grid start pin setting D17..D7 +#define MAXSEGMENTS 17 +boolean ASCIImode = true; +// Full 16-segment-star + DP font, indexed by ASCII code - 32 (index 0 = space/0x20). digit[] +// values are translated through asciiConvert[] first (see below) since they are not ASCII. +uint32_t charDefinition[96] = { + 0b00000000000000000, /* (space) */ + 0b10000000000001100, /* ! */ + 0b00000001000000100, /* " */ // 100000001 + 0b01010101000111100, /* # */ + 0b01010101010111011, /* $ */ + 0b01110111010011001, /* % */ + 0b01001001101110001, /* & */ + 0b00000001000000000, /* ' */ + 0b00001010000000000, /* ( */ + 0b00100000100000000, /* ) */ + 0b01111111100000000, /* * */ + 0b01010101000000000, /* + */ + 0b00100000000000000, /* , */ + 0b01000100000000000, /* - */ + 0b10000000000000000, /* . */ + 0b00100010000000000, /* / */ + 0b00100010011111111, /* 0 */ + 0b00000010000001100, /* 1 */ + 0b01000100001110111, /* 2 */ + 0b00000100000111111, /* 3 */ + 0b01000100010001100, /* 4 */ + 0b01001000010110011, /* 5 */ + 0b01000100011111011, /* 6 */ + 0b00000000000001111, /* 7 */ + 0b01000100011111111, /* 8 */ + 0b01000100010111111, /* 9 */ + 0b00010001000000000, /* : */ + 0b00100001000000000, /* ; */ + 0b01001010000000000, /* < */ + 0b01000100000110000, /* = */ + 0b00100100100000000, /* > */ + 0b10010100000000111, /* ? */ + 0b00000101011110111, /* @ */ + 0b01000100011001111, /* A */ + 0b00010101000111111, /* B */ + 0b00000000011110011, /* C */ + 0b00010001000111111, /* D */ + 0b01000000011110011, /* E */ + 0b01000000011000011, /* F */ + 0b00000100011111011, /* G */ + 0b01000100011001100, /* H */ + 0b00010001000110011, /* I */ + 0b00000000001111100, /* J */ + 0b01001010011000000, /* K */ + 0b00000000011110000, /* L */ + 0b00000010111001100, /* M */ + 0b00001000111001100, /* N */ + 0b00000000011111111, /* O */ + 0b01000100011000111, /* P */ + 0b00001000011111111, /* Q */ + 0b01001100011000111, /* R */ + 0b01000100010111011, /* S */ + 0b00010001000000011, /* T */ + 0b00000000011111100, /* U */ + 0b00100010011000000, /* V */ + 0b00101000011001100, /* W */ + 0b00101010100000000, /* X */ + 0b01000100010111100, /* Y */ + 0b00100010000110011, /* Z */ + 0b00010001000010010, /* [ */ + 0b00001000100000000, /* \ */ + 0b00010001000100001, /* ] */ + 0b00101000000000000, /* ^ */ + 0b00000000000110000, /* _ */ + 0b00000000100000000, /* ` */ + 0b01010000001110000, /* a */ + 0b01010000011100000, /* b */ + 0b01000000001100000, /* c */ + 0b00010100000011100, /* d */ + 0b01100000001100000, /* e */ + 0b01010101000000010, /* f */ + 0b01010001010100001, /* g */ + 0b01010000011000000, /* h */ + 0b00010000000000000, /* i */ + 0b00010001001100000, /* j */ + 0b00011011000000000, /* k */ + 0b00000000011000000, /* l */ + 0b01010100001001000, /* m */ + 0b01010000001000000, /* n */ + 0b01010000001100000, /* o */ + 0b01000001011000001, /* p */ + 0b01010001010000001, /* q */ + 0b01000000001000000, /* r */ + 0b01010000010100001, /* s */ + 0b01000000011100000, /* t */ + 0b00010000001100000, /* u */ + 0b00100000001000000, /* v */ + 0b00101000001001000, /* w */ + 0b00101010100000000, /* x */ + 0b00000101000011100, /* y */ + 0b01100000000100000, /* z */ + 0b01010001000010010, /* { */ + 0b00010001000000000, /* | */ + 0b00010101000100001, /* } */ + 0b01100110000000000, /* ~ */ + 0b00000000000000000, /* (del) */ +}; + + //byte segmentEnablePins[] = {16,7,0,1,2,3,4,5,6,8,9,11,10,12,13,14,15}; //segment enable bits +#endif + + +// Maps the special glyph codes (BLANK, minus, DP, A,P,C,deg,%,circle,I,F -- digit[] values +// 10..20) onto printable ASCII characters, used only when ASCIImode is true (16-segment +// branch), positionally 1:1 with those glyph codes (asciiConvert[10]==' '==BLANK, etc.) -- +// see the fuller explanation of this same table in PT6311.ino. +char asciiConvert[] = "0123456789 -.APC~%oIF"; + +#define MAXCHARS sizeof(charDefinition)/sizeof(charDefinition[0]) + +// PIN_CS framing helpers: unlike PT6311 (which re-strobes its STB pin every single byte), +// this driver holds PIN_CS low for an entire command or an entire frame -- callers bracket +// one or more shift() calls between startBits()/stopBits() themselves (see sendCommand() and +// writeDisplaySingle() below, where the whole per-frame digit loop sits inside one CS-low +// window). +void inline startBits(byte CS) { + digitalWrite(CS, LOW); + delayMicroseconds(wt); +} + +void inline stopBits(byte CS) { + delayMicroseconds(wt); + digitalWrite(CS, HIGH); + delayMicroseconds(wt); +} + +// Bit-bangs one byte out on SIN/CLK. NOTE: LSB-first (i starts at 0, tests bit 1<5. So the effective brightness only has + // three distinct hardware states (off / low duty / high duty) even though prm.dayBright / + // prm.nightBright can range 0..10. This may be a deliberate simplification for this + // chip's duty-setting bits, but it has not been verified against the PT6355 datasheet. + dispState = C1_DISPLAY_STATE_SETTING; + if (newBright>0) dispState |= 1; + if (newBright>5) dispState |= 6; + sendCommand(dispState); //Set Brightness + oldBright = newBright; + } + startBits(PIN_CS); + // Unlike PT6311 (which addresses each digit individually via C3_ADDRESS_SETTING + i*3 and + // re-strobes CS per digit), this driver sends ONE C0_DISPLAY_DATA_SETTING command and then + // streams all maxDigits worth of segment data continuously while CS stays low the whole + // time (stopBits() only happens once, after the loop) -- the chip auto-advances through its + // internal grid RAM as bytes arrive. + shift(PIN_SIN, PIN_CLK, C0_DISPLAY_DATA_SETTING); + for (int i=0;i= + // sizeof(asciiConvert), or dispChar==21 landing on the string's NUL terminator), producing + // a negative index into charDefinition[] below. Now the subtraction only happens together + // with a successful lookup; anything unmapped falls back to charDefinition[0] (blank glyph). + if (dispChar blank glyph (charDefinition[0]) + } + } + bitBuffer = 0; + for (int j=0;j>8); + if (MAXSEGMENTS>16) shift(PIN_SIN, PIN_CLK, bitBuffer >>16); + } //end for i + stopBits(PIN_CS); + count++; +} + +// No-op: this driver has no timer-ISR multiplex loop (see file header) -- the PT6355 refreshes +// its own tubes internally, so there is nothing for the periodic writeDisplay() ISR hook to do. +void writeDisplay() { +} + +// clearTubes() is a required part of the common driver interface, but nothing in the core .ino +// calls it any more (its only former caller, disableDisplay(), has been removed entirely as dead +// code) - kept as a no-op stub purely for interface compliance. +void clearTubes() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/SN75512.ino b/firmware/uniclock/ESP32_UniClock2/SN75512.ino new file mode 100644 index 0000000..f96f228 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/SN75512.ino @@ -0,0 +1,251 @@ +#ifdef SN75512 +//============================================================================================= +// Driver for VFD (Vacuum Fluorescent Display) tubes driven by 2x cascaded Texas Instruments +// SN75512 serial-in / high-voltage parallel-out shift-register + segment/grid driver ICs. +// +// Wiring / protocol: +// - DATA/CLK: plain serial shift-register bit-banging, MSB first (see writeDisplay()). +// - The two SN75512 chips are daisy-chained (12 outputs each) into one 24-bit (DATABITS) +// shift register: shift all 24 bits in, then pulse LE (Latch Enable) once to transfer the +// shifted-in bits to the chips' parallel high-voltage outputs (glitch-free update - the +// outputs only change on the LE pulse, not while shifting). +// - STROBE is the chip's display blank/enable input (per the pin comment below: 1=display +// OFF, 0=display ON) and is toggled once per digit refresh to implement brightness dimming +// (see the brightCounter[] dithering trick in writeDisplay()). +// - Platform: ESP8266 only - uses direct WRITE_PERI_REG() GPIO register access instead of +// digitalWrite() because bit-banging 24 bits per digit, at the multiplex refresh rate, +// needs to be much faster than the Arduino digitalWrite() call overhead allows. +// +// NOTE: no CLOCK_NN block currently active in clocks.h defines SN75512 (grep of clocks.h for +// "SN75512" / "#define SN75512" comes up empty) - it only appears as a commented-out option in +// the driver-selection list in ESP32_UniClock2.ino. This driver therefore currently looks like +// a legacy/orphan module not wired to any active clock configuration. +//============================================================================================= +// 2 x SN75512 +char tubeDriver[] = "SN75512"; +#define VFDrefresh 1200 //msec, Multiplex time period. Greater value => slower multiplex frequency + //NOTE: comment says "msec" but this value is passed straight to timer1_write() (ESP8266 + //hw timer1 in TIM_DIV16/TIM_EDGE/TIM_SINGLE mode, i.e. ~5MHz tick rate => ~0.2us/tick, + //see startTimer() in ESP32_UniClock2.ino) - so the real unit is timer ticks (~0.2us + //each), not milliseconds. The label is misleading but functionally harmless. + +#define DATABITS 24 //total length of the shift registers (2 x 12-bit SN75512, daisy-chained) + +//Fill this table with the bit positions of the shift registers! +//Bit position = index (0-based) of that output within the 24-bit combined (2-chip) shift +//register, in the order the bits are shifted out (see writeDisplay(): bit DATABITS-1 goes +//out first). segmentEnablePins order must match the a,b,c,d,e,f,g,DP order of charDefinition[]. +byte segmentEnablePins[] = {16,17,18,19,20,21,22,23}; //segment enable bits (a,b,c,d,e,f,g,DP) (You MUST define always 8 bits!!!) +byte digitEnablePins[] = {0,1,2,3,4,5,6,7,8}; //digit enable bits (You may define any number of digits between 1 and 10 ) + +//SN75512 control pins +#define PIN_LE 13 // D7 Shift Register Latch Enable +#define PIN_CLK 12 // D6 Shift Register Clock +#define PIN_DATA 14 // D5 Shift Register Data +#define PIN_STROBE 5 // D1 Shift Register Strobe (1=display off 0=display on) + +//Precomputed GPIO bitmasks (1< shift-register-bit-position map. +//Indices 0-9 = digits, 10=BLANK, 11=minus, 12=decimal point, 13-20 = extra glyphs +//(A,P,C,deg,%,lower-circle,I,F) - these indices must line up with the digit[]/newDigit[] +//buffer value convention documented in the core .ino file. +byte charDefinition[] = { + B11111100, //0: abcdef + B01100000, //1: bc + B11011010, //2: abdeg + B11110010, //3: abcdg + B01100110, //4: bcfg + B10110110, //5: acdfg + B10111110, //6: acdefg + B11100000, //7: abc + B11111110, //8: abcdefg + B11110110, //9: abcdfg + B00000000, // : BLANK (10) + B00000010, //-: minus (11) + B00000001, // decimal point (12) + B11101110, // A abcefg (13) + B11001110, // P abefg (14) + B10011100, // C adef (15) + B11000110, //grad (upper circle) abfg (16) + B10110100, //% acdf (17) + B00111010, //lower circle cdeg (18) + B01100000, //I bc (19) + B10001110 //F aefg (20) +}; + +#define MAXCHARS sizeof(charDefinition) +#define MAXSEGMENTS sizeof(segmentEnablePins) +int maxDigits = sizeof(digitEnablePins); + +uint32_t charTable[MAXCHARS]; //generated pin table from segmentDefinitions +uint32_t segmentEnableBits[MAXSEGMENTS]; //bitmaps, generated from EnablePins tables +uint32_t digitEnableBits[10]; +//----------------------------------------------------------------------------------------- + +void ICACHE_RAM_ATTR writeDisplay(){ +static volatile uint32_t val = 0; +static volatile byte pos = 0; +//Per-digit dithering/duty-cycle phase counters, one per tube position (up to 10 supported here). +//Each entry free-runs 1..MAXBRIGHTNESS and is compared against the target brightness to decide +//ON/OFF for that digit's current refresh slot (see below) - a cheap "N-cycle duty modulation" +//dimmer rather than true PWM within one frame. The staggered starting phases {0,9,2,8,4,7,6,5,3,1} +//(instead of all starting at 0) spread each digit's ON pulses across different points in the +//brightness cycle, which avoids all digits blinking on/off in visible lock-step and reduces +//perceived flicker/beating between adjacent tubes. +static volatile int brightCounter[] = {0,9,2,8,4,7,6,5,3,1}; + +//https://sub.nanona.fi/esp8266/timing-and-ticks.html +//One tick is 1us / 80 = 0.0125us = 12.5ns on 80MHz +//One tick is 1us / 80 = 0.0125us = 6.25ns on 160MHz +// 1 NOP is 1 tick + + //noInterrupts(); + + + if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! + timer1_write(VFDrefresh); //TIM_SINGLE (one-shot) mode requires re-arming the timer every call, + return; //even when skipping the refresh, or the ISR would never fire again. + } + + //Brightness gating for THIS digit position (pos), applied BEFORE this position's segment data + //is shifted out below. STROBE is the chip's global blank input (LOW=display on, HIGH=off, per + //the pin comment above), so this briefly leaves the PREVIOUS digit's already-latched pattern + //on screen at the new brightness-derived on/off state until the new data is latched via LE + //further down. With a ~1200-tick (see VFDrefresh) refresh period this overlap is a small + //fraction of the slot and isn't visually significant. + if (brightCounter[pos] % MAXBRIGHTNESS < (displayON ? prm.dayBright : prm.nightBright)) + WRITE_PERI_REG( PIN_OUT_CLEAR, PIN_STROBE_BIT ); //ON + else + WRITE_PERI_REG( PIN_OUT_SET, PIN_STROBE_BIT ); //OFF + + for (int t=0; t<4;t++) asm volatile ("nop"); + + brightCounter[pos]++; + if (brightCounter[pos]>MAXBRIGHTNESS) brightCounter[pos] = 1; + + //NOTE: digit[pos] indexes charTable[] (built from charDefinition[], MAXCHARS=21 entries) + //directly with no bounds check. If digit[pos] were ever >=21 this would read out-of-bounds + //memory. In practice the rest of the firmware is expected to only ever write valid glyph + //codes (0-20) into digit[], so this is a latent risk rather than an observed failure. + val = (digitEnableBits[pos] | charTable[digit[pos]]); //the full bitmap to send to MAX chip + if (digitDP[pos]) val = val | charTable[12]; //Decimal Point + //Shift out all 24 bits, MSB first (bit DATABITS-1 down to bit 0), matching the bit-position + //convention used by segmentEnablePins[]/digitEnablePins[] (index = bit position, MSB shifted + //first so it ends up in the "first" chip in the daisy-chain after the final LE latch). + //Note the asymmetric NOP padding: a '1' bit only pads 4 NOPs before raising CLK, while a '0' + //bit pads 8 - this compensates for the extra WRITE_PERI_REG() call needed to explicitly clear + //the DATA line (vs. just letting it stay set) so that DATA has equal setup time before CLK + //rises in both cases; hand-tuned bit-bang timing, not an accidental asymmetry. + for (int i=0; i= maxDigits) pos = 0; + + timer1_write(VFDrefresh); //re-arm the one-shot timer1 for the next digit's refresh + //interrupts(); +} + +//One-time (setup) precomputation: turns the human-editable pin-index tables +//(segmentEnablePins[]/digitEnablePins[]) and the per-glyph segment masks (charDefinition[]) +//into ready-to-OR 24-bit shift-register bitmaps, so writeDisplay() never has to do this bit +//translation work inside the timing-critical ISR - it just OR's digitEnableBits[pos] | +//charTable[digit[pos]] together. +void generateBitTable() { +uint32_t out; + + DPRINTLN("--- Generating segment pins bitmap ---"); + for (int i=0;i> "); + + charTable[i] = out; + //DPRINTLN(charTable[i],BIN); + } //end for +} + + +void setup_pins() { +#if defined(ESP8266) +#else + //Enforced at compile time because writeDisplay() bit-bangs via ESP8266-only + //WRITE_PERI_REG()/PIN_OUT_SET/PIN_OUT_CLEAR register macros and relies on the ESP8266 + //timer1 API (timer1_write, TIM_SINGLE) armed in startTimer() - none of that exists on ESP32. + #error "Only 8266 Board is supported!" +#endif + + DPRINTLN("Clock with SN75512 - Setup pins..."); + pinMode(PIN_LE, OUTPUT); regPin(PIN_LE,"PIN_LE"); + pinMode(PIN_STROBE,OUTPUT); regPin(PIN_STROBE,"PIN_STROBE"); + pinMode(PIN_DATA,OUTPUT); regPin(PIN_DATA,"PIN_DATA"); + pinMode(PIN_CLK, OUTPUT); regPin(PIN_CLK,"PIN_CLK"); + digitalWrite(PIN_STROBE,LOW); //brightness + + generateBitTable(); //must run before the timer/ISR starts, since writeDisplay() reads charTable[]/digitEnableBits[]/segmentEnableBits[] + digitsOnly = false; + + startTimer(); +} + +//POSSIBLE ISSUE: both clearTubes() and writeDisplaySingle() are empty stubs - they satisfy the +//common driver interface (see other driver files / ESP32_UniClock2.ino) syntactically, but do +//not actually blank the display or perform a blocking one-shot write. Note that the core .ino no +//longer calls clearTubes() from anywhere at all (its former caller, disableDisplay(), has been +//removed entirely as dead code), so in practice this is moot for now regardless. Given this driver +//is not wired to any active CLOCK_NN (see file header), this looks like an incomplete/legacy +//stub rather than something exercised by current configurations - flagged for confirmation. +void clearTubes() {} +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/SN75518.ino b/firmware/uniclock/ESP32_UniClock2/SN75518.ino new file mode 100644 index 0000000..ad20807 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/SN75518.ino @@ -0,0 +1,485 @@ +#ifdef SN75518_ESP32 +//============================================================================================= +// Driver for VFD (Vacuum Fluorescent Display) tubes driven by the Texas Instruments SN75518 +// serial-in / high-voltage parallel-out VFD grid/segment driver IC (32 outputs). +// +// Wiring / protocol: +// - DATA/CLK: plain serial shift-register bit-banging, MSB first (bit 31 down to bit 0), +// see writeDisplay(). LE (Latch Enable) is pulsed high-then-low once per digit after all +// 32 bits are shifted, to atomically transfer the shifted-in bits to the SN75518's +// high-voltage outputs (glitch-free - outputs don't change mid-shift). +// - STROBE is the chip's output-enable/blank pin (LOW=display on, HIGH=off, per the pin +// comment below) and is toggled per-digit to blank the display outside its multiplex slot. +// - SN75518 outputs are numbered 1..32 (not 0-based) in its datasheet, hence the "-1" applied +// to segmentEnablePins[]/digitEnablePins[] everywhere they're turned into bitmasks (see +// generateBitTable()) - unlike the SN75512 driver, which uses 0-based bit positions. +// - Two glyph-table variants, selected in clocks.h per clock config: +// * #define SEGMENT8: classic 7-segment+DP tubes (e.g. IV-11/IV-12), reuses the same +// 8-bit a..g+DP charDefinition[] layout as the SN75512/MAX6921 drivers. +// * default (no SEGMENT8): 16-segment+DP alphanumeric tubes (e.g. IV-17), with a full +// ASCII (space..DEL) 17-bit charDefinition[] table and ASCIImode glyph lookup via +// asciiConvert[] (maps the digit[]-buffer glyph codes 0-20 to a printable ASCII char). +// - Brightness/dimming is done differently from SN75512: instead of a fixed multiplex period +// with a duty-cycle counter, writeDisplay() runs the ESP32 hardware timer in a genuine +// variable-period ON/OFF PWM scheme - see the state/timerON/timerOFF logic below. +// - Platform: ESP32 only (uses the ESP32 hw_timer_t API - timerAlarmWrite/Enable - and +// portENTER_CRITICAL_ISR/DRAM_ATTR/IRAM_ATTR). +// +// Used by CLOCK_48 (WROOM ESP32, UNFI, 6x IV-17, 16-segment mode) and CLOCK_49 (WROOM ESP32, +// UNFI, 6x IV-11, SEGMENT8/7-segment mode) in clocks.h. +//============================================================================================= +char tubeDriver[] = "SN75518_ESP32"; +//int maxDigits = 6; +int wt = 5; //Serial timing + +#define PIN_LE 13 // D7 Shift Register Latch Enable +#define PIN_CLK 12 // D6 Shift Register Clock +#define PIN_DATA 27 // D5 Shift Register Data +#define PIN_STROBE 14 // D1 Shift Register Strobe (1=display off 0=display on) + +//#define SEGMENT8 //please set in clocks.h +//#define TESTMODE //for testing the segments of a new type VFD module - cycles through output + //Q1..Q32 one at a time (see "count" below) so each physical segment/grid + //wire can be identified by eye when commissioning a new/unknown VFD module. +int count = 15; //output# to test +unsigned long lastTest = 0; + +#ifdef SEGMENT8 + //7-segment+DP tubes (e.g. IV-11): reuse the digit[]-buffer glyph-code convention directly + //(no ASCII remapping), so ASCIImode stays false and dispChar==digit[pos] in writeDisplay(). + boolean ASCIImode = false; + #define DP_CHAR 12 + byte charDefinition[] = { +//------------------abcdefgDP---------------- definition of different characters +//Each byte is a 7-segment+DP mask, MSB=a ... LSB=DP. generateBitTable() expands these into +//32-bit shift-register bitmaps (charTable[]) via segmentEnablePins[] (defined in clocks.h). + B11111100, //0: abcdef + B01100000, //1: bc + B11011010, //2: abdeg + B11110010, //3: abcdg + B01100110, //4: bcfg + B10110110, //5: acdfg + B10111110, //6: acdefg + B11100000, //7: abc + B11111110, //8: abcdefg + B11110110, //9: abcdfg + B00000000, // : BLANK (10) + B00000010, //-: minus (11) + B00000001, // decimal point (12) + B11101110, // A abcefg (13) + B11001110, // P abefg (14) + B10011100, // C adef (15) + B11000110, //grad (upper circle) abfg (16) + B10110100, //% acdf (17) + B00111010, //lower circle cdeg (18) + B01100000, //I bc (19) + B10001110 //F aefg (20) +}; + +//byte segmentEnablePins[] = {9,8,5,3,4,7,6,2}; //segment enable bits (a,b,c,d,e,f,g,DP) (You MUST define always 8 bits!!!) + +#else + //16 segment + DP --------------------------------------------------------------------------------------------- + //16-segment alphanumeric tubes (e.g. IV-17): full ASCII glyph set is needed (letters, symbols), + //so instead of the 21-entry digit[]-buffer glyph codes we index this table by ASCII character + //(space=0x20 .. DEL=0x7F, 96 entries), via the asciiConvert[]+dispChar-=32 lookup below. + boolean ASCIImode = true; + #define DP_CHAR 14 + //Each entry is a 17-bit mask, one bit per segment, in the order given by the "Dutsrpnmkhgfedcba" + //comment below (bit0=a ... bit15=u, bit16=D/decimal point) - note this is the OPPOSITE bit + //order from the SEGMENT8/charDefinition[] table above (there MSB=a); generateBitTable() below + //reverses it (segmentEnableBits[MAXSEGMENTS-j-1]) to compensate when building charTable[]. + uint32_t charDefinition[96] = { +// Dutsrpnmkhgfedcba + 0b00000000000000000, /* (space) */ + 0b10000000000001100, /* ! */ + 0b00000001000000100, /* " */ // 100000001 + 0b01010101000111100, /* # */ + 0b01010101010111011, /* $ */ + 0b01110111010011001, /* % */ + 0b01001001101110001, /* & */ + 0b00000001000000000, /* ' */ + 0b00001010000000000, /* ( */ + 0b00100000100000000, /* ) */ + 0b01111111100000000, /* * */ + 0b01010101000000000, /* + */ + 0b00100000000000000, /* , */ + 0b01000100000000000, /* - */ + 0b10000000000000000, /* . */ + 0b00100010000000000, /* / */ + 0b00100010011111111, /* 0 */ + 0b00000010000001100, /* 1 */ + 0b01000100001110111, /* 2 */ + 0b00000100000111111, /* 3 */ + 0b01000100010001100, /* 4 */ + 0b01001000010110011, /* 5 */ + 0b01000100011111011, /* 6 */ + 0b00000000000001111, /* 7 */ + 0b01000100011111111, /* 8 */ + 0b01000100010111111, /* 9 */ + 0b00010001000000000, /* : */ + 0b00100001000000000, /* ; */ + 0b01001010000000000, /* < */ + 0b01000100000110000, /* = */ + 0b00100100100000000, /* > */ + 0b10010100000000111, /* ? */ + 0b00000101011110111, /* @ */ + 0b01000100011001111, /* A */ + 0b00010101000111111, /* B */ + 0b00000000011110011, /* C */ + 0b00010001000111111, /* D */ + 0b01000000011110011, /* E */ + 0b01000000011000011, /* F */ + 0b00000100011111011, /* G */ + 0b01000100011001100, /* H */ + 0b00010001000110011, /* I */ + 0b00000000001111100, /* J */ + 0b01001010011000000, /* K */ + 0b00000000011110000, /* L */ + 0b00000010111001100, /* M */ + 0b00001000111001100, /* N */ + 0b00000000011111111, /* O */ + 0b01000100011000111, /* P */ + 0b00001000011111111, /* Q */ + 0b01001100011000111, /* R */ + 0b01000100010111011, /* S */ + 0b00010001000000011, /* T */ + 0b00000000011111100, /* U */ + 0b00100010011000000, /* V */ + 0b00101000011001100, /* W */ + 0b00101010100000000, /* X */ + 0b01000100010111100, /* Y */ + 0b00100010000110011, /* Z */ + 0b00010001000010010, /* [ */ + 0b00001000100000000, /* \ */ + 0b00010001000100001, /* ] */ + 0b00101000000000000, /* ^ */ + 0b00000000000110000, /* _ */ + 0b00000000100000000, /* ` */ + 0b01010000001110000, /* a */ + 0b01010000011100000, /* b */ + 0b01000000001100000, /* c */ + 0b00010100000011100, /* d */ + 0b01100000001100000, /* e */ + 0b01010101000000010, /* f */ + 0b01010001010100001, /* g */ + 0b01010000011000000, /* h */ + 0b00010000000000000, /* i */ + 0b00010001001100000, /* j */ + 0b00011011000000000, /* k */ + 0b00000000011000000, /* l */ + 0b01010100001001000, /* m */ + 0b01010000001000000, /* n */ + 0b01010000001100000, /* o */ + 0b01000001011000001, /* p */ + 0b01010001010000001, /* q */ + 0b01000000001000000, /* r */ + 0b01010000010100001, /* s */ + 0b01000000011100000, /* t */ + 0b00010000001100000, /* u */ + 0b00100000001000000, /* v */ + 0b00101000001001000, /* w */ + 0b00101010100000000, /* x */ + 0b00000101000011100, /* y */ + 0b01100000000100000, /* z */ + 0b01010001000010010, /* { */ + 0b00010001000000000, /* | */ + 0b00010101000100001, /* } */ + 0b01000001010000001, /* ~ */ + 0b00000000000000000, /* (del) */ + //Dutsrpnmkhgfedcba +}; + + //byte segmentEnablePins[] = {16,7,0,1,2,3,4,5,6,8,9,11,10,12,13,14,15}; //segment enable bits defined in clocks.h +#endif + +#define MAXCHARS sizeof(charDefinition)/sizeof(charDefinition[0]) +#define MAXSEGMENTS sizeof(segmentEnablePins) +//Maps the digit[]-buffer glyph codes (0-9=digits,10=blank,11=minus,12=dp,13-20=A,P,C,deg,%, +//lower-circle,I,F - see index comments on the SEGMENT8 charDefinition[] table above) to a +//printable ASCII character, so that in 16-segment/ASCIImode the same digit[]-buffer convention +//used by the rest of the firmware still works: writeDisplay() does +//asciiConvert[digit[pos]] then "-32" to turn that into a 0-based index into the 96-entry +//charDefinition[]/charTable[] (which starts at ASCII space=0x20). +char asciiConvert[] = "0123456789 -.APC~% IF"; +int maxDigits = sizeof(digitEnablePins); +int dispChar; + +uint32_t DRAM_ATTR animationMaskBits[5]; +uint32_t DRAM_ATTR charTable[MAXCHARS]; //generated pin table from segmentDefinitions +uint32_t DRAM_ATTR segmentEnableBits[MAXSEGMENTS]; //bitmaps, generated from EnablePins tables +uint32_t DRAM_ATTR digitEnableBits[10]; + +int DRAM_ATTR PWMrefresh=5500; //Multiplex time period, in ESP32 hardware-timer ticks (not milliseconds, despite the variable name). Greater value => slower multiplex frequency +int DRAM_ATTR PWM_min = 500; +int DRAM_ATTR PWM_max = 5000; +//int DRAM_ATTR PWMtiming[11] = {0,500,800,1200,2000,2500,3000,3500,4000,4500,5000}; + +//----------------------------------------------------------------------------------------- + +//https://sub.nanona.fi/esp8266/timing-and-ticks.html +//One tick is 1us / 80 = 0.0125us = 12.5ns on 80MHz +//One tick is 1us / 80 = 0.0125us = 6.25ns on 160MHz +// 1 NOP is 1 tick + +//POSSIBLE BUG / dead code: this function is never actually called (only referenced from +//commented-out lines in writeDisplay(), e.g. "//delayMS(1);"). Also, despite its name and the +//"int d" parameter (presumably intended as a millisecond count), the parameter is unused and +//the body always executes exactly 3 NOPs (a few nanoseconds), not d milliseconds - so even if +//re-enabled it would not delay by the requested amount. Left as-is since it is not on any live +//code path. +void inline delayMS(int d) { + asm volatile ("nop"); + asm volatile ("nop"); + asm volatile ("nop"); +} + +void setup_pins() { + DPRINTLN("VFD Clock - setup SN75518 pins"); + pinMode(PIN_LE, OUTPUT); regPin(PIN_LE,"PIN_LE"); + pinMode(PIN_STROBE, OUTPUT); regPin(PIN_STROBE,"PIN_STROBE"); + digitalWrite(PIN_STROBE,LOW); //brightness + pinMode(PIN_DATA,OUTPUT); regPin(PIN_DATA,"PIN_DATA"); + pinMode(PIN_CLK, OUTPUT); regPin(PIN_CLK,"PIN_CLK"); + #ifdef TESTMODE + DPRINT("Testing output Q#:"); DPRINTLN(count); + #endif + //Build a human-readable summary of the active pin mapping for display on the config web page + //(see driverSetupStr usage in ESP32_UniClock2.ino) - purely diagnostic, no functional effect. + driverSetupStr = "SN75518 segmentEnablePins:"; + for (int j=0;j"); + + generateBitTable(); //must run before the timer/ISR starts - writeDisplay() reads charTable[]/digitEnableBits[]/segmentEnableBits[]/animationMaskBits[] + digitsOnly = false; + startTimer(); +} + +//Timer-interrupt multiplex refresh handler. Unlike the fixed-period SN75512 driver (which uses +//a constant multiplex tick and a duty-cycle counter for brightness), this driver re-arms the +//ESP32 hardware timer with a DIFFERENT period every call, alternating between an "ON" phase +//(digit lit, duration = PWMtimeBrightness, proportional to brightness) and an "OFF" phase +//(digit blanked, duration = PWMrefresh - PWMtimeBrightness) via the `state` toggle - i.e. true +//variable-mark-space PWM dimming rather than a fixed-frequency multiplex with sub-cycle gating. +//The digit position (pos) only advances at the start of each "ON" phase. +void IRAM_ATTR writeDisplay(){ //void IRAM_ATTR writeDisplay(){ + static DRAM_ATTR int timer = PWMrefresh; + static DRAM_ATTR uint32_t val; + static DRAM_ATTR byte pos = 0; + static DRAM_ATTR boolean state=true; + static DRAM_ATTR int brightness; + static DRAM_ATTR int PWMtimeBrightness=PWM_min; + + if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! + //digitalWrite(PIN_STROBE,HIGH); //OFF + return; //NOTE: unlike SN75512's ISR, no timerAlarmWrite()/timerAlarmEnable() re-arm is + //needed here - this driver's ESP32 hw timer is configured with autoreload=true + //(see the timerAlarmWrite(...,true) calls below), so it keeps firing at its last + //configured period on its own; simply returning is enough to skip a refresh cycle + //while an EEPROM write is in progress. (Whatever was last shifted out stays lit, + //since STROBE is left as-is - the blanking line above is commented out.) + } + //Example of how to force specific glyphs onto specific tube positions for bench testing: + //digit[5] = 15; digit[4] = 16; digit[3] = 17; digit[2] = 18; digit[1] = 19; digit[0] = 20; + portENTER_CRITICAL_ISR(&timerMux); + noInterrupts(); //belt-and-braces alongside portENTER_CRITICAL_ISR - protects the whole + //shift+latch sequence below from being interleaved with other interrupts/ + //the other core while the SN75518's serial frame is being clocked out. + intCounter++; + + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + //At full manual brightness (auto-brightness off), force state permanently ON so the OFF/blank + //phase below is skipped entirely - i.e. 100% duty cycle / no blanking gap, for maximum + //perceived brightness and refresh rate when the user has explicitly asked for max brightness. + if ((!autoBrightness) && (brightness==MAXBRIGHTNESS)) state = true; + + if (state) { //ON state: light up tube #pos for PWMtimeBrightness ticks, then go to OFF state + pos++; if (pos>maxDigits-1) { //go to the tube#0 + pos = 0; + //Brightness (the ON-phase duration) is only recalculated once per full multiplex cycle, + //at tube#0, so all tubes share the same brightness within one cycle (avoids visible + //brightness differences between tubes if the ambient light level changes mid-cycle). + if (autoBrightness && displayON) { //change brightness only on the tube#0 + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + } + dispChar = digit[pos]; + if (ASCIImode) { + //16-segment tables are indexed by ASCII code (space=0x20 based), not by the digit[]-buffer + //glyph code directly, so translate glyph-code -> ASCII char via asciiConvert[] first. + //FIXED: previously "dispChar -= 32" ran unconditionally, even when the bounds check just + //below skipped the asciiConvert[] lookup (dispChar >= sizeof(asciiConvert), or dispChar + //landing on the string's NUL terminator) - that produced a negative index into charTable[] + //below (out-of-bounds read). Now the subtraction only happens together with a successful + //lookup; anything unmapped falls back to charTable[0], the blank/space glyph. + if (dispChar blank glyph (charTable[0]) + } + } + //NOTE: as with the SN75512 driver, dispChar indexes charTable[] (MAXCHARS entries) with no + //explicit bounds check against MAXCHARS itself - relies on upstream code only ever writing + //valid glyph codes into digit[]. + val = (digitEnableBits[pos] | charTable[dispChar]); //the full bitmap to send to MAX chip + if (digitDP[pos]) val = val | charTable[DP_CHAR]; //Decimal Point + #ifdef TESTMODE + //Override the normal glyph lookup: light exactly one output (Q) at a time so each + //physical pin can be identified on the tube; "count-1" because SN75518 outputs are + //numbered 1-based (Q1..Q32), same convention as segmentEnablePins/digitEnablePins. + val = digitEnableBits[pos] | ((uint32_t)1<<(count-1)); + animMask[pos] = 0; + if ((millis()-lastTest)>5000) { + lastTest=millis(); + count++; if (count>32) count = 0; + } + #endif + timer = PWMtimeBrightness; + #ifdef CLOCK_xx //example only + //Template/example only - "CLOCK_xx" is never actually #defined by any real CLOCK_NN in + //clocks.h, so this block is permanently dead code as shipped. It documents the intended + //pattern for compensating individual tubes that appear dimmer than others (e.g. due to + //VFD wear or grid-wiring differences): give a specific tube position a longer ON time + //(here 2x/3x) than PWMtimeBrightness would otherwise give it. To use it for a real clock, + //replace CLOCK_xx with that clock's actual CLOCK_NN macro. + if (pos==2) timer = 2*timer; //Weak tube#2 brightness compensation, some hacking + if (pos==4) timer = 3*timer; //Weak tube#4 brightness compensation, some hacking + #endif + timerON = timer; //debug/telemetry only (see intCounter/timerON/timerOFF in ESP32_UniClock2.ino) + timerOFF = PWMrefresh-PWMtimeBrightness; + } + else { //OFF state: blank for the remainder of the PWMrefresh period + timer = PWMrefresh-PWMtimeBrightness; + } + + if (timer<500) timer = 500; //safety only... avoids an excessively short/zero hw-timer alarm period (very high ISR rate) from a pathological brightness/PWM combination + + if ( (brightness == 0) || (!state) || (!radarON)) { //OFF state, blank digit + digitalWrite(PIN_STROBE,HIGH); //OFF + } + else { //ON state + if (animMask[pos]>0) val &= animationMaskBits[animMask[pos]-1]; //animationMode 6, mask characters from up to down and back + //Shift out all 32 bits, MSB first (Q32 down to Q1). Timing note: unlike the SN75512 driver + //(which uses raw WRITE_PERI_REG()+NOP padding for a tightly-controlled bit-bang), this uses + //plain digitalWrite() with no explicit delay between DATA and CLK edges - the delayMS(1) + //calls that would add a small pad are commented out. The function-call overhead of + //digitalWrite() itself appears to provide enough setup/hold margin for the SN75518 at the + //ESP32's clock speed, but that margin is implicit rather than guaranteed by an explicit delay. + for (int i=0; i<32; i++) { + if (val & uint32_t(1 << (31 - i))) + {digitalWrite(PIN_DATA, HIGH); //asm volatile ("nop"); + } + else + {digitalWrite(PIN_DATA, LOW); //asm volatile ("nop"); + } + + digitalWrite(PIN_CLK,HIGH); //asm volatile ("nop"); //delayMS(1); + digitalWrite(PIN_CLK,LOW); //asm volatile ("nop"); //delayMS(1); + } //end for + + //Latch: pulse LE high then low to transfer the just-shifted 32 bits into the SN75518's + //output latch (glitch-free update), then re-enable STROBE (LOW=on) to actually light the tube. + digitalWrite(PIN_LE,HIGH ); asm volatile ("nop"); + digitalWrite(PIN_LE,LOW); + digitalWrite(PIN_STROBE,LOW ); //ON + } //end else + + portEXIT_CRITICAL_ISR(&timerMux); + state = !state; //flip ON/OFF phase for the next timer interrupt + //ESP32timer = timerBegin(0, PRESCALER, true); //set prescaler, true = edge generated signal + //timerAttachInterrupt(ESP32timer, &writeDisplay, true); + //Re-arm the hw timer with the period (ON or OFF duration) just computed above - this is what + //gives this driver its variable-frequency PWM dimming (as opposed to SN75512's fixed period). + timerAlarmWrite(ESP32timer, timer, true); + timerAlarmEnable(ESP32timer); + interrupts(); +} + +//One-time (setup) precomputation, mirroring SN75512's generateBitTable() but for the SN75518's +//1-based (Q1..Q32) output numbering (note the "-1" on every segmentEnablePins[i]/ +//digitEnablePins[i] use below) and its extra animationMaskBits[] table used by the digit-change +//"wipe" animation (mode 5/6 in changeDigit(), core .ino file). +void generateBitTable() { +uint32_t out; + + DPRINT("--- Generating segment pins bitmap:"); DPRINTLN(MAXSEGMENTS); + for (int i=0;i +b/f -> +g -> +c/e -> +d), later inverted below so that + //ANDing a character's bitmap with animationMaskBits[N] blanks out the segments not yet + //reached by the wipe. + animationMaskBits[0] = uint32_t(1<> "); + + charTable[i] = out; + //DPRINTLN(charTable[i],BIN); + } //end for +} + + +void clearTubes() { + digitalWrite(PIN_STROBE,HIGH); +} + +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/VQC10.ino b/firmware/uniclock/ESP32_UniClock2/VQC10.ino new file mode 100644 index 0000000..61f2b7b --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/VQC10.ino @@ -0,0 +1,625 @@ +// ============================================================================ +// VQC10 driver +// ---------------------------------------------------------------------------- +// Drives 8x VQC10 tubes/modules, each a 5x7 dot-matrix (LED or VFD-style) +// character display (see Datasheets/VQC10_Funkamateur.pdf and +// "VQC10 timing.jpeg" for the original hardware reference - VQC10 is an +// Eastern-European 5x7 dot-matrix indicator tube, not a classic 7-segment +// Nixie/VFD tube). Because each character cell is a dot matrix rather than +// a fixed set of segments, this driver renders full ASCII text (via the +// bitmap _font[] table below) instead of just digits. +// +// Wiring / protocol: the 8 character cells are driven through a chain of +// 74HC595 serial-in/parallel-out shift registers (latchPin=RCK/STCP, +// clockPin=SCK/SHCP, dataPin=DIN/SER - classic 3-wire SPI-like shift-out, +// no MISO). Unlike most other drivers in this project (which multiplex +// digit-by-digit), the VQC10 hardware is scanned ROW-by-ROW: for every one +// of the NUMROWS=7 dot-rows, the column data for all NUMDIGITS=8 characters +// is shifted out and latched digit-by-digit, then a single row-select line +// is pulsed on for a PWM-timed on-period before moving to the next row. +// Refresh timing is fully cooperative/blocking (see writeDisplay2() below) +// rather than driven from the hardware timer ISR - see the note above +// writeDisplay() for why. +// +// Used by clocks.h configuration(s): CLOCK_71 ("2x VQC10 clock by UNFI"). +// ============================================================================ +#ifdef VQC10 +char tubeDriver[] = "VQC10"; +#define NUMDIGITS 8 +int maxDigits = NUMDIGITS; + +//Please, set the pin numbers in clock.h! +//#define latchPin 5 //D2 RCK +//#define clockPin 4 //D1 SCK +//#define dataPin 2 //D4 DIN +//#define ledOffpin 0 //D5 +//#define D5pin 14 + + +#define NUMCOLS 5 // font glyph width in dots (columns per character cell) +#define NUMROWS 7 // font glyph height in dots (rows per character cell) - also the number of physical row-select lines scanned in writeDisplay2() + +boolean _upsidedown = false; // flips the dot-matrix rendering 180 degrees (for tubes mounted upside down); never set to true anywhere in this codebase - effectively dead/unused, kept for parity with wordclock.ino which shares this same font/transpose code +byte row[NUMROWS]; // scratch buffer filled by show()/transposePattern(): one row-bitmap per dot-row for the character currently being rendered + +// Maps the shared digit[]/newDigit[] buffer codes (see core file: 0-9=digit, 10=BLANK, +// 11=minus, 12=decimal point, 13+=driver-specific extra glyphs) to an index into the +// ASCII-ordered _font[] table below, i.e. this converts the generic "digit code" alphabet +// used by the core firmware into actual character codes this driver can render. +char asciiConvert[] = "0123456789 -.APC~%oIF"; +char DPchar = 0xFF; //decimal point - index of the dedicated 1-dot "decimal point" glyph appended at the very end of _font[] (see 0xFF entry below) + +char cDigit[NUMDIGITS+1] = "76543210"; // NOTE: declared/initialized but never referenced anywhere else in this file or the project - dead code (candidate for removal) +byte dat[NUMDIGITS][NUMROWS]; // final row-bitmaps actually shifted out to hardware (post-animation-blend result) +byte oldDat[NUMDIGITS][NUMROWS]; // row-bitmaps for the outgoing (old) character, built fresh each writeDisplaySingle() call +byte newDat[NUMDIGITS][NUMROWS]; // row-bitmaps for the incoming (new) character, built fresh each writeDisplaySingle() call + +// standard ascii 5x7 font incl. the IBM-PC extension: +// already defined in DotMatrx5x7.h +// Format: NUMCOLS(5) bytes per glyph, indexed as glyph_code*NUMCOLS in show() above; each +// byte is one column of the cell, with dot rows packed in its low NUMROWS(7) bits (bit0=row0 +// top .. bit6=row6 bottom in normal orientation). Glyph 0xFF at the very end of the table is +// repurposed as a dedicated "decimal point" glyph (see DPchar above) rather than a real +// character, so it only lights a single dot instead of a printable symbol. +//#define aUML "\x84" // ä = 0x84 +//#define oUML "\x94" // ö = 0x94 +//#define uUML "\x81" // ü = 0x81 +//#define sZET "\xE1" // ß = 0xE1 +//#define AUML "\x8E" // Ä = 0x8E +//#define OUML "\x99" // Ö = 0x99 +//#define UUML "\x9A" // Ü = 0x9A + +byte _font[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00 empty + 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, // 0x01 sad + 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, // 0x02 happy + 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, // 0x03 heart + 0x18, 0x3C, 0x7E, 0x3C, 0x18, // 0x04 diamond + 0x1C, 0x57, 0x7D, 0x57, 0x1C, // 0x05 clover + 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, // 0x06 spade + 0x00, 0x18, 0x3C, 0x18, 0x00, // 0x07 small full circle + 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, // 0x08 inverted small circle + 0x06, 0x09, 0x06, 0x00, 0x00, // 0x09 small empty circle //old: 0x00, 0x18, 0x24, 0x18, 0x00, // 0x09 small empty circle + 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, // 0x0A block (linefeed) + 0x30, 0x48, 0x3A, 0x06, 0x0E, // 0x0B male + 0x26, 0x29, 0x79, 0x29, 0x26, // 0x0C female + 0x40, 0x7F, 0x05, 0x05, 0x07, // 0x0D note (carriage return) + 0x40, 0x7F, 0x05, 0x25, 0x3F, // 0x0E two notes + 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, // 0x0F sun + 0x7F, 0x3E, 0x1C, 0x1C, 0x08, // 0x10 right triangle + 0x08, 0x1C, 0x1C, 0x3E, 0x7F, // 0x11 left triangle + 0x14, 0x22, 0x7F, 0x22, 0x14, // 0x12 up/down arrow + 0x5F, 0x5F, 0x00, 0x5F, 0x5F, // 0x13 double exclamation mark + 0x06, 0x09, 0x7F, 0x01, 0x7F, // 0x14 paragraph end sign (pilcrow) + // 0x00, 0x66, 0x89, 0x95, 0x6A, // 0x15 paragraph sign original + 0x00, 0x26, 0x49, 0x55, 0x28, // 0x15 paragraph sign + 0x60, 0x60, 0x60, 0x60, 0x60, // 0x16 double underline + 0x54, 0x62, 0x7F, 0x62, 0x54, // 0x17 up/down arrow with underline + 0x08, 0x04, 0x7E, 0x04, 0x08, // 0x18 up arrow + 0x10, 0x20, 0x7E, 0x20, 0x10, // 0x19 down arrow + 0x08, 0x08, 0x2A, 0x1C, 0x08, // 0x1A right arrow + 0x08, 0x1C, 0x2A, 0x08, 0x08, // 0x1B left arrow + 0x14, 0x3E, 0x55, 0x41, 0x22, // 0x1C euro + 0x63, 0x75, 0x69, 0x75, 0x63, // 0x1D timer + 0x30, 0x38, 0x3E, 0x38, 0x30, // 0x1E up triangle + 0x06, 0x0E, 0x3E, 0x0E, 0x06, // 0x1F down triangle + 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 space + 0x00, 0x00, 0x5F, 0x00, 0x00, // 0x21 ! + 0x00, 0x07, 0x00, 0x07, 0x00, // 0x22 " + 0x14, 0x7F, 0x14, 0x7F, 0x14, // 0x23 # + 0x24, 0x2A, 0x7F, 0x2A, 0x12, // 0x24 $ + 0x23, 0x13, 0x08, 0x64, 0x62, // 0x25 % + 0x36, 0x49, 0x56, 0x20, 0x50, // 0x26 & + 0x00, 0x08, 0x07, 0x03, 0x00, // 0x27 ´ + 0x00, 0x1C, 0x22, 0x41, 0x00, // 0x28 ( + 0x00, 0x41, 0x22, 0x1C, 0x00, // 0x29 ) + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, // 0x2A * + 0x08, 0x08, 0x3E, 0x08, 0x08, // 0x2B + + 0x00, 0x80, 0x70, 0x30, 0x00, // 0x2C , + 0x08, 0x08, 0x08, 0x08, 0x08, // 0x2D - + 0x00, 0x00, 0x60, 0x60, 0x00, // 0x2E . + 0x20, 0x10, 0x08, 0x04, 0x02, // 0x2F / + 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0x30 0 + 0x00, 0x42, 0x7F, 0x40, 0x00, // 0x31 1 + 0x72, 0x49, 0x49, 0x49, 0x46, // 0x32 2 + 0x21, 0x41, 0x49, 0x4D, 0x33, // 0x33 3 + 0x18, 0x14, 0x12, 0x7F, 0x10, // 0x34 4 + 0x27, 0x45, 0x45, 0x45, 0x39, // 0x35 5 + 0x3C, 0x4A, 0x49, 0x49, 0x31, // 0x36 6 + 0x41, 0x21, 0x11, 0x09, 0x07, // 0x37 7 + 0x36, 0x49, 0x49, 0x49, 0x36, // 0x38 8 + 0x46, 0x49, 0x49, 0x29, 0x1E, // 0x39 9 + 0x00, 0x00, 0x14, 0x00, 0x00, // 0x3A : + 0x00, 0x40, 0x34, 0x00, 0x00, // 0x3B ; + 0x00, 0x08, 0x14, 0x22, 0x41, // 0x3C < + 0x14, 0x14, 0x14, 0x14, 0x14, // 0x3D = + 0x00, 0x41, 0x22, 0x14, 0x08, // 0x3E > + 0x02, 0x01, 0x59, 0x09, 0x06, // 0x3F ? + 0x3E, 0x41, 0x5D, 0x59, 0x4E, // 0x40 @ + 0x7C, 0x12, 0x11, 0x12, 0x7C, // 0x41 A + 0x7F, 0x49, 0x49, 0x49, 0x36, // 0x42 B + 0x3E, 0x41, 0x41, 0x41, 0x22, // 0x43 C + 0x7F, 0x41, 0x41, 0x41, 0x3E, // 0x44 D + 0x7F, 0x49, 0x49, 0x49, 0x41, // 0x45 E + 0x7F, 0x09, 0x09, 0x09, 0x01, // 0x46 F + 0x3E, 0x41, 0x41, 0x51, 0x73, // 0x47 G + 0x7F, 0x08, 0x08, 0x08, 0x7F, // 0x48 H + 0x00, 0x41, 0x7F, 0x41, 0x00, // 0x49 I + 0x20, 0x40, 0x41, 0x3F, 0x01, // 0x4A J + 0x7F, 0x08, 0x14, 0x22, 0x41, // 0x4B K + 0x7F, 0x40, 0x40, 0x40, 0x40, // 0x4C L + 0x7F, 0x02, 0x1C, 0x02, 0x7F, // 0x4D M + 0x7F, 0x04, 0x08, 0x10, 0x7F, // 0x4E N + 0x3E, 0x41, 0x41, 0x41, 0x3E, // 0x4F O + 0x7F, 0x09, 0x09, 0x09, 0x06, // 0x50 P + 0x3E, 0x41, 0x51, 0x21, 0x5E, // 0x51 Q + 0x7F, 0x09, 0x19, 0x29, 0x46, // 0x52 R + 0x26, 0x49, 0x49, 0x49, 0x32, // 0x53 S + 0x03, 0x01, 0x7F, 0x01, 0x03, // 0x54 T + 0x3F, 0x40, 0x40, 0x40, 0x3F, // 0x55 U + 0x1F, 0x20, 0x40, 0x20, 0x1F, // 0x56 V + 0x3F, 0x40, 0x38, 0x40, 0x3F, // 0x57 W + 0x63, 0x14, 0x08, 0x14, 0x63, // 0x58 X + 0x03, 0x04, 0x78, 0x04, 0x03, // 0x59 Y + 0x61, 0x59, 0x49, 0x4D, 0x43, // 0x5A Z + 0x00, 0x7F, 0x41, 0x41, 0x41, // 0x5B [ + 0x02, 0x04, 0x08, 0x10, 0x20, // 0x5C '\' + 0x00, 0x41, 0x41, 0x41, 0x7F, // 0x5D ] + 0x04, 0x02, 0x01, 0x02, 0x04, // 0x5E ^ + 0x40, 0x40, 0x40, 0x40, 0x40, // 0x5F _ + 0x00, 0x03, 0x07, 0x08, 0x00, // 0x60 ` + 0x20, 0x54, 0x54, 0x78, 0x40, // 0x61 a + 0x7F, 0x28, 0x44, 0x44, 0x38, // 0x62 b + 0x38, 0x44, 0x44, 0x44, 0x00, // 0x63 c + 0x38, 0x44, 0x44, 0x28, 0x7F, // 0x64 d + 0x38, 0x54, 0x54, 0x54, 0x18, // 0x65 e + 0x00, 0x08, 0x7E, 0x09, 0x02, // 0x66 f + // 0x18, 0xA4, 0xA4, 0x9C, 0x78, // 0x67 g (old) + 0x0C, 0x52, 0x52, 0x4E, 0x3C, // 0x67 g + 0x7F, 0x08, 0x04, 0x04, 0x78, // 0x68 h + 0x00, 0x44, 0x7D, 0x40, 0x00, // 0x69 i + 0x20, 0x40, 0x40, 0x3D, 0x00, // 0x6A j + 0x7F, 0x10, 0x28, 0x44, 0x00, // 0x6B k + 0x00, 0x41, 0x7F, 0x40, 0x00, // 0x6C l + 0x7C, 0x04, 0x78, 0x04, 0x78, // 0x6D m + 0x7C, 0x08, 0x04, 0x04, 0x78, // 0x6E n + 0x38, 0x44, 0x44, 0x44, 0x38, // 0x6F o + 0xFC, 0x18, 0x24, 0x24, 0x18, // 0x70 p + // 0x18, 0x24, 0x24, 0x18, 0xFC, // 0x71 q (old) + 0x0C, 0x12, 0x12, 0x0C, 0x7E, // 0x71 q + 0x7C, 0x08, 0x04, 0x04, 0x08, // 0x72 r + 0x48, 0x54, 0x54, 0x54, 0x24, // 0x73 s + 0x04, 0x04, 0x3F, 0x44, 0x24, // 0x74 t + 0x3C, 0x40, 0x40, 0x20, 0x7C, // 0x75 u + 0x1C, 0x20, 0x40, 0x20, 0x1C, // 0x76 v + 0x3C, 0x40, 0x30, 0x40, 0x3C, // 0x77 w + 0x44, 0x28, 0x10, 0x28, 0x44, // 0x78 x + 0x0C, 0x10, 0x10, 0x50, 0x3C, // 0x79 y + 0x44, 0x64, 0x54, 0x4C, 0x44, // 0x7A z + 0x00, 0x08, 0x36, 0x41, 0x00, // 0x7B { + 0x00, 0x00, 0x7F, 0x00, 0x00, // 0x7C | + 0x00, 0x41, 0x36, 0x08, 0x00, // 0x7D } + 0x02, 0x01, 0x02, 0x04, 0x02, // 0x7E ~ + 0x3C, 0x26, 0x23, 0x26, 0x3C, // 0x7F house + 0x1E, 0xA1, 0xA1, 0x61, 0x12, // 0x81 + 0x3A, 0x40, 0x40, 0x20, 0x7A, // 0x81 ü + 0x38, 0x54, 0x54, 0x55, 0x59, // 0x82 + 0x21, 0x55, 0x55, 0x79, 0x41, // 0x83 + 0x21, 0x54, 0x54, 0x78, 0x41, // 0x84 ä + 0x21, 0x55, 0x54, 0x78, 0x40, // 0x85 + 0x20, 0x54, 0x55, 0x79, 0x40, // 0x86 + 0x0C, 0x1E, 0x52, 0x72, 0x12, // 0x87 + 0x39, 0x55, 0x55, 0x55, 0x59, // 0x88 + 0x39, 0x54, 0x54, 0x54, 0x59, // 0x89 + 0x39, 0x55, 0x54, 0x54, 0x58, // 0x8A + 0x00, 0x00, 0x45, 0x7C, 0x41, // 0x8B + 0x00, 0x02, 0x45, 0x7D, 0x42, // 0x8C + 0x00, 0x01, 0x45, 0x7C, 0x40, // 0x8D + 0xF0, 0x29, 0x24, 0x29, 0xF0, // 0x8E Ä + 0xF0, 0x28, 0x25, 0x28, 0xF0, // 0x8F + 0x7C, 0x54, 0x55, 0x45, 0x00, // 0x90 + 0x20, 0x54, 0x54, 0x7C, 0x54, // 0x91 + 0x7C, 0x0A, 0x09, 0x7F, 0x49, // 0x92 + 0x32, 0x49, 0x49, 0x49, 0x32, // 0x93 + 0x32, 0x48, 0x48, 0x48, 0x32, // 0x94 ö + 0x32, 0x4A, 0x48, 0x48, 0x30, // 0x95 + 0x3A, 0x41, 0x41, 0x21, 0x7A, // 0x96 + 0x3A, 0x42, 0x40, 0x20, 0x78, // 0x97 + 0x00, 0x9D, 0xA0, 0xA0, 0x7D, // 0x98 + //0x39, 0x44, 0x44, 0x44, 0x39, // 0x99 Ö (old) + 0x3D, 0x42, 0x42, 0x42, 0x3D, // 0x99 Ö + 0x3D, 0x40, 0x40, 0x40, 0x3D, // 0x9A Ü + 0x3C, 0x24, 0xFF, 0x24, 0x24, // 0x9B cent + 0x48, 0x7E, 0x49, 0x43, 0x66, // 0x9C pound + 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, // 0x9D + 0xFF, 0x09, 0x29, 0xF6, 0x20, // 0x9E + 0xC0, 0x88, 0x7E, 0x09, 0x03, // 0x9F + 0x20, 0x54, 0x54, 0x79, 0x41, // 0xA0 + 0x00, 0x00, 0x44, 0x7D, 0x41, // 0xA1 + 0x30, 0x48, 0x48, 0x4A, 0x32, // 0xA2 + 0x38, 0x40, 0x40, 0x22, 0x7A, // 0xA3 + 0x00, 0x7A, 0x0A, 0x0A, 0x72, // 0xA4 + 0x7D, 0x0D, 0x19, 0x31, 0x7D, // 0xA5 + 0x26, 0x29, 0x29, 0x2F, 0x28, // 0xA6 + 0x26, 0x29, 0x29, 0x29, 0x26, // 0xA7 + 0x30, 0x48, 0x4D, 0x40, 0x20, // 0xA8 + 0x38, 0x08, 0x08, 0x08, 0x08, // 0xA9 + 0x08, 0x08, 0x08, 0x08, 0x38, // 0xAA + 0x2F, 0x10, 0xC8, 0xAC, 0xBA, // 0xAB + 0x2F, 0x10, 0x28, 0x34, 0xFA, // 0xAC + 0x00, 0x00, 0x7B, 0x00, 0x00, // 0xAD exclamation mark upside down + 0x08, 0x14, 0x2A, 0x14, 0x22, // 0xAE less less + 0x22, 0x14, 0x2A, 0x14, 0x08, // 0xAF greater greater + 0xAA, 0x00, 0x55, 0x00, 0xAA, // 0xB0 + 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 0xB1 + 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 0xB2 + 0x00, 0x00, 0x00, 0xFF, 0x00, // 0xB3 + 0x10, 0x10, 0x10, 0xFF, 0x00, // 0xB4 + 0x14, 0x14, 0x14, 0xFF, 0x00, // 0xB5 + 0x10, 0x10, 0xFF, 0x00, 0xFF, // 0xB6 + 0x10, 0x10, 0xF0, 0x10, 0xF0, // 0xB7 + 0x14, 0x14, 0x14, 0xFC, 0x00, // 0xB8 + 0x14, 0x14, 0xF7, 0x00, 0xFF, // 0xB9 + 0x00, 0x00, 0xFF, 0x00, 0xFF, // 0xBA + 0x14, 0x14, 0xF4, 0x04, 0xFC, // 0xBB + 0x14, 0x14, 0x17, 0x10, 0x1F, // 0xBC + 0x10, 0x10, 0x1F, 0x10, 0x1F, // 0xBD + 0x14, 0x14, 0x14, 0x1F, 0x00, // 0xBE + 0x10, 0x10, 0x10, 0xF0, 0x00, // 0xBF + 0x00, 0x00, 0x00, 0x1F, 0x10, // 0xC0 + 0x10, 0x10, 0x10, 0x1F, 0x10, // 0xC1 + 0x10, 0x10, 0x10, 0xF0, 0x10, // 0xC2 + 0x00, 0x00, 0x00, 0xFF, 0x10, // 0xC3 + 0x10, 0x10, 0x10, 0x10, 0x10, // 0xC4 + 0x10, 0x10, 0x10, 0xFF, 0x10, // 0xC5 + 0x00, 0x00, 0x00, 0xFF, 0x14, // 0xC6 + 0x00, 0x00, 0xFF, 0x00, 0xFF, // 0xC7 + 0x00, 0x00, 0x1F, 0x10, 0x17, // 0xC8 + 0x00, 0x00, 0xFC, 0x04, 0xF4, // 0xC9 + 0x14, 0x14, 0x17, 0x10, 0x17, // 0xCA + 0x14, 0x14, 0xF4, 0x04, 0xF4, // 0xCB + 0x00, 0x00, 0xFF, 0x00, 0xF7, // 0xCC + 0x14, 0x14, 0x14, 0x14, 0x14, // 0xCD + 0x14, 0x14, 0xF7, 0x00, 0xF7, // 0xCE + 0x14, 0x14, 0x14, 0x17, 0x14, // 0xCF + 0x10, 0x10, 0x1F, 0x10, 0x1F, // 0xD0 + 0x14, 0x14, 0x14, 0xF4, 0x14, // 0xD1 + 0x10, 0x10, 0xF0, 0x10, 0xF0, // 0xD2 + 0x00, 0x00, 0x1F, 0x10, 0x1F, // 0xD3 + 0x00, 0x00, 0x00, 0x1F, 0x14, // 0xD4 + 0x00, 0x00, 0x00, 0xFC, 0x14, // 0xD5 + 0x00, 0x00, 0xF0, 0x10, 0xF0, // 0xD6 + 0x10, 0x10, 0xFF, 0x10, 0xFF, // 0xD7 + 0x14, 0x14, 0x14, 0xFF, 0x14, // 0xD8 + 0x10, 0x10, 0x10, 0x1F, 0x00, // 0xD9 + 0x00, 0x00, 0x00, 0xF0, 0x10, // 0xDA + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xDB + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, // 0xDC + 0xFF, 0xFF, 0xFF, 0x00, 0x00, // 0xDD + 0x00, 0x00, 0x00, 0xFF, 0xFF, // 0xDE + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, // 0xDF + 0x38, 0x44, 0x44, 0x38, 0x44, // 0xE0 alpha + 0x7C, 0x2A, 0x2A, 0x3E, 0x14, // 0xE1 beta / ß + 0x7E, 0x02, 0x02, 0x06, 0x06, // 0xE2 Gamma + 0x02, 0x7E, 0x02, 0x7E, 0x02, // 0xE3 Pi + 0x63, 0x55, 0x49, 0x41, 0x63, // 0xE4 Sigma + 0x38, 0x44, 0x44, 0x3C, 0x04, // 0xE5 sigma + 0x40, 0x7E, 0x20, 0x1E, 0x20, // 0xE6 mu + 0x06, 0x02, 0x7E, 0x02, 0x02, // 0xE7 tau + 0x99, 0xA5, 0xE7, 0xA5, 0x99, // 0xE8 Phi + 0x1C, 0x2A, 0x49, 0x2A, 0x1C, // 0xE9 theta + 0x4C, 0x72, 0x01, 0x72, 0x4C, // 0xEA Omega + 0x30, 0x4A, 0x4D, 0x4D, 0x30, // 0xEB delta + 0x30, 0x48, 0x78, 0x48, 0x30, // 0xEC phi? + 0xBC, 0x62, 0x5A, 0x46, 0x3D, // 0xED empty set + 0x3E, 0x49, 0x49, 0x49, 0x00, // 0xEE epsilon + 0x7E, 0x01, 0x01, 0x01, 0x7E, // 0xEF + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, // 0xF0 equivalence + 0x44, 0x44, 0x5F, 0x44, 0x44, // 0xF1 plus minus + 0x40, 0x51, 0x4A, 0x44, 0x40, // 0xF2 greater or equal + 0x40, 0x44, 0x4A, 0x51, 0x40, // 0xF3 less or equal + 0x00, 0x00, 0xFF, 0x01, 0x03, // 0xF4 + 0xE0, 0x80, 0xFF, 0x00, 0x00, // 0xF5 + 0x08, 0x08, 0x6B, 0x6B, 0x08, // 0xF6 divided by + 0x36, 0x12, 0x36, 0x24, 0x36, // 0xF7 approx + 0x06, 0x0F, 0x09, 0x0F, 0x06, // 0xF8 degree + 0x00, 0x00, 0x18, 0x18, 0x00, // 0xF9 big dot + 0x00, 0x00, 0x10, 0x10, 0x00, // 0xFA small dot + 0x30, 0x40, 0xFF, 0x01, 0x01, // 0xFB square root + 0x00, 0x1F, 0x01, 0x01, 0x1E, // 0xFC + 0x00, 0x19, 0x1D, 0x17, 0x12, // 0xFD power of two + 0x00, 0x3C, 0x3C, 0x3C, 0x3C, // 0xFE blob + //0x00, 0x00, 0x00, 0x60, 0x60, // 0xFF: Decimal point: 4 points + 0x00, 0x00, 0x00, 0x00, 0x40, // 0xFF: Decimal point, only 1 point +}; + +void setup_pins() { + DPRINTLN("VQC10 Clock - setup 74HC595 pins"); + pinMode(latchPin, OUTPUT); regPin(latchPin,"latchPin"); + pinMode(dataPin,OUTPUT); regPin(dataPin,"dataPin"); + pinMode(clockPin,OUTPUT); regPin(clockPin,"clockPin"); + pinMode(D5pin,INPUT); regPin(D5pin,"D5pin"); // NOTE: configured and registered but never read anywhere in this driver or the core file - purpose (spare sensor/button input?) is undocumented, see open questions + digitsOnly = false; // this driver can render full ASCII text (dot-matrix font), not just 0-9, so let the core firmware know + asciiConvert[16] = 9; // runtime override: digit-buffer code 16 (originally mapped to '~' above) is remapped to raw font index 9 ("small empty circle" glyph, see _font table) for a driver-specific extra symbol + // Deliberately does NOT call startTimer() here (unlike most other drivers): the hardware + // timer ISR (writeDisplay(), see below) is intentionally a no-op for VQC10, and indeed + // startTimer() is never invoked anywhere for this driver in the whole project. All actual + // refresh work happens cooperatively via writeDisplay2(), called repeatedly from loop()/ + // Fdelay() in the core file - see the comment above writeDisplay() for why. +} + +void inline shiftOutForward(byte myDataOut) { + // This shifts 8 bits out MSB first, on the rising edge of the clock, clock idles low + // Used for the "digit select" (Z) and "row select" shift-register bytes, whose bit + // order must match how those select lines are physically wired to the 74HC595 outputs. + for (int i=7; i>=0; i--) { + digitalWrite(clockPin, 0); + digitalWrite(dataPin, myDataOut & (1<MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + if (autoBrightness && displayON) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); // ambient-light-driven dimming: scale row on-time by measured lux vs. MAXIMUM_LUX + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); // manual brightness: scale row on-time by configured day/night brightness level + + iRow = 0; + // FIXED (was "loopMax = maxDigits;", i.e. NUMDIGITS=8): this loop indexes dot-matrix ROWS, which + // only range 0..NUMROWS-1 (NUMROWS=7, i.e. dat[i] is sized [NUMROWS]=[7]), not digit columns. The + // old bound made the loop run iRow = 0..7 (8 iterations) instead of 0..6 (7 iterations), so on the + // last iteration (iRow==7) "dat[i][iRow]" below read one element past the end of each digit's + // 7-byte row array - for i> 1; + else cmask = cmask << 1; + rows[r] = rows[r] << (7-NUMCOLS); + } +} + +// Debug helper: prints one byte as 8 ASCII '0'/'1' characters (MSB first). +void bPrint(byte in) { + for (int i=7;i>=0;i--) { + boolean b = in & (1<7) a=7; + for (int k=0;k slower multiplex frequency +int DRAM_ATTR PWM_min = 2000; +int DRAM_ATTR PWM_max = 10000; + +//#define dataPin 14 //D5 +//#define latchPin 27 //12 //D6 +//#define clkPin 26 //13 //D7 + +// Segment bit patterns per displayable glyph code (index = the value stored in digit[]/newDigit[]). +// Each byte is a bitmask over 8 abstract "segment slots" (bit7=a .. bit0=DP by convention elsewhere in +// this codebase); generateBitTable() below translates these abstract bits into the actual physical +// shift-register output bits via segmentEnablePins[] (defined per-CLOCK_NN in clocks.h). Codes 0-9 are +// digits, 10=blank, 11=minus, 12=a standalone decimal-point glyph, 13+ are extra characters (A, P, C, +// degree symbol, %, etc.) used for date/temperature/humidity/IP-address display - see clocks.h's +// TEMP_CHARCODE/GRAD_CHARCODE/PERCENT_CHARCODE and the core file's displayTemp()/displayHumid()/writeIpTag(). +byte DRAM_ATTR charDefinition[] = { + B11111100, //0: abcdef + B01100000, //1: bc + B11011010, //2: abdeg + B11110010, //3: abcdg + B01100110, //4: bcfg + B10110110, //5: acdfg + B10111110, //6: acdefg + B11100000, //7: abc + B11111110, //8: abcdefg + B11110110, //9: abcdfg + B00000000, // : BLANK (10) + B00000010, //-: minus (11) + B00000001, // decimal point (12) + B11101110, // A abcefg (13) + B11001110, // P abefg (14) + B10011100, // C adef (15) + B11000110, //grad (upper circle) abfg (16) + B10110100, //% acdf (17) + B00111010, //lower circle cdeg (18) + B01100000, //I bc (19) + B10001110 //F aefg (20) +}; + +#define MAXCHARS sizeof(charDefinition) +#define MAXSEGMENTS sizeof(segmentEnablePins) +int maxDigits = sizeof(digitEnablePins); + +uint32_t DRAM_ATTR charTable[MAXCHARS]; //generated pin table from segmentDefinitions +uint32_t DRAM_ATTR segmentEnableBits[MAXSEGMENTS]; //bitmaps, generated from EnablePins tables +uint32_t DRAM_ATTR animationMaskBits[5]; +uint32_t DRAM_ATTR digitEnableBits[12]; + +// This driver refreshes the whole display purely from the writeDisplay() ISR below (hardware-timer +// driven multiplex), so there is nothing left to do for a "blocking, one-shot" write - required by +// the common driver interface, but intentionally a no-op here. +void IRAM_ATTR writeDisplaySingle() { } + +void setup_pins(){ + DPRINT("ZM1500 clock "); DPRINT(maxDigits); DPRINTLN(" digits - setup pins"); + pinMode(dataPin, OUTPUT); regPin(dataPin,"dataPin"); + pinMode(latchPin,OUTPUT); regPin(latchPin,"latchPin"); + pinMode(clkPin,OUTPUT); regPin(clkPin,"clkPin"); + digitalWrite(latchPin,LOW); + digitalWrite(clkPin,LOW); + generateBitTable(); //pre-compute charTable[]/digitEnableBits[]/animationMaskBits[] from clocks.h's pin arrays + startTimer(); //arms the ESP32 hardware timer that drives writeDisplay() below +} + +// Hardware-timer ISR: multiplexes one digit position per "ON" call, using a two-phase (ON/OFF) +// duty-cycle so the *ratio* of ON time to OFF time controls perceived brightness (a software PWM), +// then re-arms itself (timerAlarmWrite/timerAlarmEnable) with however long the next phase should +// last. Runs continuously in the background once startTimer() has been called from setup_pins(). +void IRAM_ATTR writeDisplay(){ //void IRAM_ATTR writeDisplay(){ + + static DRAM_ATTR int pos = maxDigits; + static DRAM_ATTR boolean state=0; + static DRAM_ATTR int brightness; + static DRAM_ATTR uint32_t PWMtimeBrightness=PWM_min; + uint32_t timer = PWMrefresh; + uint32_t val = 0; + + portENTER_CRITICAL_ISR(&timerMux); + noInterrupts(); + intCounter++; + + if (state) { //ON state: advance to the next digit position and compute its bit pattern + pos++; + if (pos>maxDigits-1) { //go to the tube#0 + pos = 0; + // Brightness (and the resulting PWMtimeBrightness ON-duration) is only recomputed once per + // full multiplex sweep (i.e. when we wrap back to position 0), not on every single digit - + // this keeps all digits at the same, consistently-updated brightness within one sweep. + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + if (autoBrightness && displayON) { //change brightness only on the tube#0 + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + } + // Look up this position's live glyph, apply the wipe-animation mask (changeDigit()'s animMode 5, + // in the core .ino) and OR in a standalone decimal-point glyph (index 12) if this tube's dot + // should be lit - digit[]/animMask[]/digitDP[] are all written by the core file's display + // pipeline (displayTimeX()/changeDigit()) before this ISR ever reads them. + val = uint32_t(charTable[digit[pos]]); //the full bitmap to send to the shift-register chain + if (animMask[pos]>0) val &= uint32_t(animationMaskBits[animMask[pos]-1]); //animationMode 6, mask characters from up to down and back + if (digitDP[pos]) val = val | uint32_t(charTable[12]); //Decimal Point + + timer = PWMtimeBrightness; + timerON = timer; //debug only + timerOFF = PWMrefresh-PWMtimeBrightness; //debug only + } + else { //OFF state: blank the shift-register output for the remainder of this digit's time-slice + timer = PWMrefresh-PWMtimeBrightness; + val = 0; + } + + if (timer<500) timer = 500; //safety only... + if ( (brightness == 0) || (!radarON)) val = 0; //blank digit + val = val | digitEnableBits[pos]; //combine segment bits (val) with this tube's select bit + + // Bit-bang val out LSB-first over 20 clock pulses - only the low 20 bits of the 32-bit val + // actually reach the hardware (8 segment bits + up to 12 digit-select bits, see the file header). + for (int i=0; i<20; i++) { + if (val & uint32_t(1 << i)) //reverse: (1 << (19 - i))) + digitalWrite(dataPin, HIGH); + else + digitalWrite(dataPin, LOW); + digitalWrite(clkPin,HIGH); //asm volatile ("nop"); //Clock impulse + digitalWrite(clkPin,LOW); //asm volatile ("nop"); + } //end for + + // Pulse latchPin to atomically move the just-shifted-in 20 bits from the 74HC595's internal shift + // register to its output latches - this is the instant the physical display actually changes. + digitalWrite(latchPin,HIGH); //asm volatile ("nop"); + digitalWrite(latchPin,LOW); + state = !state; + + portEXIT_CRITICAL_ISR(&timerMux); + timerAlarmWrite(ESP32timer, timer, true); //re-arm the hardware timer for the just-computed phase duration + timerAlarmEnable(ESP32timer); + interrupts(); +} + + +// One-time setup helper (called from setup_pins()): pre-computes the lookup tables writeDisplay()'s +// ISR needs every cycle, so the ISR itself never has to do bit-shifting arithmetic based on the raw +// clocks.h pin arrays - just plain table lookups, keeping the interrupt handler fast. +void generateBitTable() { +uint32_t out; + + DPRINTLN("--- Generating segment pins bitmap ---"); + for (int i=0;i> "); + + charTable[i] = out; + DPRINTLN(charTable[i],BIN); + } //end for + + // "+8" shifts the digit-select bit up past the 8 segment-enable bit positions (0-7, from + // segmentEnablePins[] above), so a combined value (segment bits | digitEnableBits[pos]) never + // collides: segment data occupies bits 0-7, tube selection occupies bits 8-19 (up to 12 tubes). + DPRINTLN("--- Generating digit pins bitmap ---"); + for (int i=0;i ... #endif". This file is + the switchboard that decides which one of those driver bodies actually survives the preprocessor: + + 1. Exactly ONE "#define CLOCK_NN" below (out of the ~65 listed) should be left un-commented - + that selects which physical clock build this firmware image is for. + 2. That CLOCK_NN's "#ifdef CLOCK_NN ... #endif" block (further down in this file) then defines: + - a DRIVER-SELECTION macro (exactly one), e.g. MAX6921, MAX6921_ESP32, HV5122, NEWHV5122, + PT6311, PT6355, ZM1500_ESP32, MULTIPLEX74141_ESP32, VQC10, WORDCLOCK, MULE_V2, TUBE1CLOCK, + Numitron_4511N, SN75512, SN75518_ESP32, MM5450, MAX7219CNG, NO_MULTIPLEX74141, + NO_MULTIPLEX_ESP32, PCF_74141, DUMMY - this is what makes ONE specific driver .ino's + "#ifdef " body compile, and leaves every other driver file's body empty. + - the physical GPIO pin assignments for that driver (names vary per driver family - e.g. + digitEnablePins[]/segmentEnablePins[]/ABCDPins[] for BCD/segment-style drivers, or + PIN_LE/PIN_CLK/PIN_DATA/PIN_BL for MAX6921-style shift-register drivers). + - optional feature toggles for that specific build (USE_NEOPIXEL, USE_RTC, USE_GPS, + USE_DALLAS_TEMP, USE_MQTT, ALARMSPEAKER_PIN, etc.) - only define the ones this build needs. + 3. Right at the end of this file (search for "if not defined, here is defined as NOT USED") a big + block of "#ifndef X / #define X / #endif" pairs fills in a sane default for every + OPTIONAL parameter a CLOCK_NN block didn't bother to set explicitly (e.g. pins default to -1 + meaning "not connected", brightness/timing constants get conservative defaults). This is what + lets each CLOCK_NN block above stay short - it only needs to override what's actually different + for that particular physical build. + + To add a brand-new clock: copy the block of an existing, similar CLOCK_NN, give it the next free + number, add its "//#define CLOCK_NN ..." line to the list below (commented out until you're ready to + build it), and write a new driver .ino file if the hardware needs a genuinely new driver (or reuse an + existing driver macro if the same driver chip is used with different pins/tube count). + + Driver-macro -> implementing .ino file quick reference (see docs/DEVELOPER_GUIDE.md section 7 for the + full table with which CLOCK_NN uses which): + MULTIPLEX74141 / MULTIPLEX74141_ESP32 -> multiplex74141.ino / multiplex74141_ESP32.ino + NO_MULTIPLEX74141 / NO_MULTIPLEX_ESP32 -> no_multiplex74141.ino / no_multiplex_ESP32.ino + PCF_74141 -> pcf_multiplex74141.ino + MAX6921 / MAX6921_ESP32 -> MAX6921.ino / MAX6921_ESP32.ino + HV5122 (orphaned, unused) / NEWHV5122 -> HV5122.ino / HV5122new.ino + PT6311, PT6355 -> PT6311.ino, PT6355.ino + SN75512 (orphaned, unused) -> SN75512.ino + SN75518_ESP32 -> SN75518.ino + MM5450, MAX7219CNG -> MM5450.ino, MAX7219CNG.ino + Numitron_4511N -> Numitron_4511N.ino + VQC10 -> VQC10.ino + TUBE1CLOCK -> 1tube.ino + MULE_V2 -> mulev2.ino + ZM1500_ESP32 (CURRENTLY ACTIVE, CLOCK_72 below) -> ZM1500.ino + WORDCLOCK -> wordclock.ino (+ font5x7.h for its scrolling/overlay font) + DUMMY -> dummy.ino (sensor-only "clock", no physical display) +*/ + +//#define CLOCK_1 //8266, UNFI PCB clock, 4x IN-16 tubes +//#define CLOCK_2 //8266, UNFI PCB clock, 4x Z-570M tubes +//#define CLOCK_3 //8266, PCB less clock, IV-18 VFD tube https://www.thingiverse.com/thing:3417955 +//#define CLOCK_4 //8266, custom clock, 4x IV-11 VFD tubes +//#define CLOCK_5 //8266, UNFI PCB clock IVL-2-5/7 tube with RTC +//#define CLOCK_6 //8266, GP PCB clock, 4x IN-1 tubes and 4x74141 driver (NON-MULTIPLEX) +//#define CLOCK_6A //8266, UNFI PCB clock, 4x Z568 tubes and 4x74141 driver (NON-MULTIPLEX) +//#define CLOCK_7 //8266 UNFI PCB clock 6x Z574M/Z573M tubes, tube selection by PCF8574 chip +//#define CLOCK_8 //8266 GP PCB v3 clock with 4x IV-16 Numitron tubes (plexi box) +//#define CLOCK_9 //8266 GP PCB v1 clock with 4x IV-16 Numitron tubes (brown box) +//#define CLOCK_10 //8266 LED Clock with MAX7219 chip +//#define CLOCK_11 //8266 LED Clock with MM5450 chip +//#define CLOCK_12 //8266 VFD Clock with PT6355 chip +//#define CLOCK_13 //8266 GP PCB v1 clock with 4x IV-16 Numitron tubes + GPS timesync (white box) +//#define CLOCK_14 //8266 6X LD8035 VFD-clock +//#define CLOCK_15 //8266 6X IV-6 VFD-clock RGB +//#define CLOCK_15a //8266 6X IV-6 VFD-clock RGB +//#define CLOCK_16 //8266 4X IV-6, 1X LD8035 +//#define CLOCK_17 //8266 + PT6311 4X IV-22, russian clock +//#define CLOCK_18 //8266 IV-18 VFD-clock RGB +//#define CLOCK_20 //8266 D1-mini, P.S. PCB 4xIN14 clock-thermometer +//#define CLOCK_21 //8266 D1-mini, P.S. PCB 4xIN14 thermometer / humidity +//#define CLOCK_22 //8266 NODEMCU, P.S. PCB 4xIN14 thermometer / humidity +//#define CLOCK_23 ////ESP32 D1 mini, P.S. PCB 3xIN14 1xIN-19A thermometer / humidity +//#define CLOCK_23A //ESP32 WROOM, U.Z. PCB 4xZ566M 1xIN-19A thermometer / humidity +//#define CLOCK_23B //ESP32 WROOM U.Z. PCB Z571M+4xZ566M+IN-19A clock/thermometer/humidity +//#define CLOCK_26 //ESP32 D1 mini, UNFI 6 x IV-11 VFD tubes clock, SHT21 +//#define CLOCK_30 //ESP32 prototype, UNFI PCB clock, 6 x IV-11 VFD tubes +//#define CLOCK_31 //ESP32 prototype, UNFI PCB board, 6 x Z573M Nixie tubes +//#define CLOCK_32 //ESP32 prototype, UNFI 6 x IV-11 VFD tubes clock, DHT22 sensor +//#define CLOCK_33 //TOM025 ESP32, Pálfi S. board, 6 x Z573M Nixie tubes +//#define CLOCK_34 //Mule V2 ESP32, board, 6 x SA40 LED Display +//#define CLOCK_35 //ESP32, 6x Z566M and 3x74595 6x74141 (NON-MULTIPLEX) +//#define CLOCK_36 //ESP32, 4x IN-1 tubes and 4x74141 driver (NON-MULTIPLEX) +//#define CLOCK_40 //V1 ESP32, UNFI 6 x IV-11 VFD tubes clock +//#define CLOCK_41 //V2 ESP32, UNFI 6 x IV-11 VFD tubes clock (rewired revision, átkötés) +//#define CLOCK_42 //V3 ESP32, UNFI 6 x IV-11 VFD tubes clock +//#define CLOCK_43 //V1 ESP32, UNFI 6 x Z573M Nixie tubes +//#define CLOCK_44 //V2 ESP32, UNFI 6 x Z573M Nixie tubes (rewired revision, átkötés) +//#define CLOCK_45 //V3 ESP32, UNFI 6 x Z573M Nixie tubes +//#define CLOCK_46 //V1 ESP32, UNFI 6 x IV-9 Numitron clock +//#define CLOCK_47 //V3 ESP32, UNFI 10-LT-50G VFD tubes clock +//#define CLOCK_48 //WROOM ESP32, UNFI SN75518 6xIV-17 clock +//#define CLOCK_49 //WROOM ESP32, UNFI SN75518 6xIV-11 clock +//-------------------------------------------------------------------------------------------------------------- +//#define CLOCK_50 //V1 ESP32, UNFI 2xHV5122 PCB version, 6xZ573 clock +//#define CLOCK_51 //V2 ESP32, UNFI 2xHV5122 PCB version, 6xZ573 clock +//#define CLOCK_52 //ESP32, P.S. 2xHV5122 PCB version, 6xIN18 clock PROTOTYPE TESTING!!! +//#define CLOCK_53 //ESP8266, UNFI 2xHV5122 PCB version, 6xZ573 clock PROTOTYPE TESTING!!! +//#define CLOCK_54 // D1 R32 ESP32 (Uno compatible), + NCS312 Nixie clock KIT!!! +//#define CLOCK_55 //ESP8266, UNFI 2xHV5122 PCB version, 6xZ573 clock +//#define CLOCK_56 //WROOM ESP32, UNFI 2xHV5122 6xZ573 clock /flat panel/ +//#define CLOCK_57 //V2-WROOM ESP32, UNFI 2xHV5122 6xZM1000 clock /flat panel +//#define CLOCK_57A //V2-WROOM ESP32, UNFI 2xHV5222 6xZ57xM clock /flat panel +//#define CLOCK_70 //1 tube esp8266 Nixie Clock by UNFI +//#define CLOCK_71 //2x VQC10 clock by UNFI +#define CLOCK_72 //ESP32, UNFI board, ZM1500 +//#define CLOCK_80 //word clock, english version +//#define CLOCK_81 //word clock, german version +//#define CLOCK_82 //word clock, hungarian version +//#define CLOCK_99 //Dummy clock, sensor box with MQTT connector + +//______________________ESP8266 CLOCKS by UNFI and GP ______________________________________________________ +#ifdef CLOCK_1 //8266, UNFI PCB clock, 4x IN-16 tubes +#define DEBUG +#define FW "fw1" //firmware name +#define MAXBRIGHTNESS 255 +//#define USE_NEOPIXEL +//byte tubePixels[] = {3,2,1,0}; //4 tubes, single leds, reverse direction +//#define USE_DALLAS_TEMP +//#define TEMP_DALLAS_PIN -1 //Dallas temp sensor pin. If not used, SET TO -1 +#define MULTIPLEX74141 +byte digitEnablePins[] = {14, 12, 13, 15}; //IN16 4x tube clock +byte ABCDPins[4] = {2, 4, 5, 0}; +#define DP_PIN -1 // decimalPoint inside Nixie tube, set -1, if not used! +#define COLON_PIN 16 //Blinking Colon pin. If not used, SET TO -1 +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IN-16 Nixie Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER + +#define USE_GPS +#define SPEEDOMETER //use clock as speedometer +#define SPEEDOMETER_SWITCH -1 //optional pin for mode switch: clock / speedometer +#endif + +#ifdef CLOCK_2 //8266, UNFI PCB clock, 4x Z-570M tubes +#define DEBUG +#define FW "fw2" //firmware name +#define MAXBRIGHTNESS 100 +//#define USE_NEOPIXEL +//#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN -1 //Dallas temp sensor pin. +#define MULTIPLEX74141 +byte digitEnablePins[] = {13, 12, 14, 15}; +byte ABCDPins[4] = {16, 5, 4, 0}; +#define DP_PIN -1 // decimalPoint inside Nixie tube, set -1, if not used! +#define COLON_PIN 2 //Blinking Colon pin. If not used, SET TO -1 +#define ALARMSPEAKER_PIN -1 //Alarm buzzer pin +#define ALARMBUTTON_PIN -1 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "Z-570M Nixie Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_3 //8266, PCB less clock, IV-18 VFD tube https://www.thingiverse.com/thing:3417955 +#define DEBUG +#define FW "fw3" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 4 //Dallas temp sensor pin. If not used, SET TO -1 +#define MAX6921 +byte segmentEnablePins[] = {0, 2, 5, 6, 4, 1, 3, 7}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {18, 11, 17, 12, 16, 13, 14, 15}; //19}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6,7,8) (You may define any number) +//MAX6921 pins +#define PIN_LE 12 // D6 Shift Register Latch Enable +#define PIN_CLK 13 // D7 Shift Register Clock +#define PIN_DATA 14 // D5 Shift Register Data +#define PIN_BL 15 // D8 Shift Register Blank (1=display off 0=display on) +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IV-18 VFD Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + + +#ifdef CLOCK_4 //8266, custom clock, 4x IV-11 VFD tubes +#define DEBUG +#define FW "fw4" //firmware name +#define MAXBRIGHTNESS 100 +#define MAX6921 +byte segmentEnablePins[] = {7, 5, 1, 0, 2, 4, 6, 3}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {19, 18, 17, 16}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6,7,8) (You may define any number) +//MAX6921 pins +#define PIN_LE 12 // D6 Shift Register Latch Enable +#define PIN_CLK 13 // D7 Shift Register Clock +#define PIN_DATA 14 // D5 Shift Register Data +#define PIN_BL 15 // D8 Shift Register Blank (1=display off 0=display on) +#define USE_NEOPIXEL +byte tubePixels[] = {0, 9, 1, 9, 2, 9, 3}; //4 tubes, single leds, 3 leds not used +//#define RADAR_PIN 21 +#define RADAR_TIMEOUT 5 //(min) +//#define TUBE_POWER_PIN 2 +//#define TUBE_POWER_ON LOW +#define LIGHT_SENSOR_PIN A0 //Only ADC pins are usable! for example: 34,35,36,39... +#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +#define LUX_CALC_SCALAR 12518931 * 1.2 +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IV-11 VFD Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_5 //8266, UNFI PCB clock IVL-2-5/7 tube with RTC +//#define DEBUG +#define FW "fw5" //firmware name +#define USE_DALLAS_TEMP +#define MAXBRIGHTNESS 100 +#define TEMP_DALLAS_PIN 3 +#define USE_RTC +#define PIN_MODE_SWITCH 17 //Analog A0 port!!! +#define PIN_FLD_BUTTON 16 +#define PIN_SET_BUTTON 0 +#define PIN_SDA 5 //D1 SDA/SCL swapped +#define PIN_SCL 4 //D2 +#define MAX6921 +//Fill this table with the OUT positions of the MAX6921 chip! +byte segmentEnablePins[] = {11, 13, 5, 8, 3, 12, 2, 1}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {10, 9, 4, 0}; //segment enable OUTbits of MAX6921 (1,2,3,4,5,6,7,8) +//MAX6921 pins +#define PIN_LE 13 // D6 Shift Register Latch Enable +#define PIN_CLK 12 // D7 Shift Register Clock +#define PIN_DATA 14 // D5 Shift Register Data +#define PIN_BL 15 // D8 Shift Register Blank (1=display off 0=display on) +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IVL-2 VFD Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_6 //8266, GP PCB clock, 4x IN-1 tubes and 4x74141 driver (NON-MULTIPLEX) +#define DEBUG +#define FW "fw6" //firmware name +#define MAXBRIGHTNESS 100 +//#define USE_DALLAS_TEMP +//#define TEMP_DALLAS_PIN -1 +#define USE_NEOPIXEL +byte tubePixels[] = {0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0}; //4 tubes, double row, 17 leds (GP) +#define NO_MULTIPLEX74141 +byte tubes[] = {3, 2, 1, 0}; //4 tubes, old clock... in clocks.h +#define dataPin 14 //D5 +#define latchPin 12 //D6 +#define clkPin 13 //D7 +#define TEMP_DALLAS_PIN -1 +#define COLON_PIN 2 //Blinking Colon pin. If not used, SET TO -1 ( old IN-1-Clock: white:2 brown: SDA) +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IN-1 Nixie Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_6A //8266, UNFI PCB clock, 4x Z568 tubes and 4x74141 driver (NON-MULTIPLEX) +#define DEBUG +#define FW "fw6a" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 4 +#define GRAD_CHARCODE -1 //no display grad char +#define TEMP_CHARCODE -1 //no display temp char +//#define USE_NEOPIXEL +//byte tubes[] = {2,3,0,1}; //4 tubes, old clock... +byte tubes[] = {5, 4, 2, 3, 0, 1}; //6 tubes, Kovecses clock... +#define NO_MULTIPLEX74141 +#define dataPin 14 //D5 +#define latchPin 12 //D6 +#define clkPin 13 //D7 +#define COLON_PIN 15 //Blinking Colon pin. If not used, SET TO -1 ( old IN-1-Clock: white:2 brown: SDA) +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "Z568M Nixie Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +#endif + + +#ifdef CLOCK_7 //8266 UNFI PCB clock 6x Z574M/Z573M tubes, tube selection by PCF8574 chip +#define DEBUG +#define FW "fw7" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 0 +//#define USE_NEOPIXEL +//byte tubePixels[] = {0,1,2,3,4,5}; //6 tubes, single leds +//byte tubePixels[] = {5,4,3,2,1,0}; //6 tubes, single leds, reverse direction +#define LEFTDECIMAL true //set true (Z574M), false to Z573M +#define TEMP_CHARCODE -1 //disable char => shift display right with 1 digit +//#define GRAD_CHARCODE -1 //disable char => shift display right with 1 digit +//#define LIGHT_SENSOR_PIN A0 //Only ADC pins are usable! for example: 34,35,36,39... +#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +#define LUX_CALC_SCALAR 12518931 * 1.2 +#define PCF_74141 +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "Z574M Nixie Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_8 //8266 GP PCB v3 clock with 4x IV-16 Numitron tubes (plexi box) //Flash size: 1MB (FS:160k, OTA:422k) +//#define DEBUG +#define FW "fw8" //firmware name +#define MAXBRIGHTNESS 100 +#define PCB_VERSION 3 //1,2 or 3 +#define LTBIpin 5 +byte digitEnablePins[] = {13, 12, 14, 16}; //define here the digit enable pins from 4 to 8 +byte ABCDPins[4] = {4, 0, 2, 15}; +#define USE_NEOPIXEL +byte tubePixels[] = {3, 2, 6, 1, 0}; //Numitron 4 tubes, 4 x single leds + 1. The extra led in the middle is not used, is always dark! +#define Numitron_4511N +#define ALARMSPEAKER_PIN -1 //Alarm buzzer pin +#define ALARMBUTTON_PIN -1 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "Numitron Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_9 //8266 GP PCB v1 clock with 4x IV-16 Numitron tubes (brown box) //Flash size: 1MB (FS:160k, OTA:422k) +#define DEBUG +#define FW "fw9" //firmware name +#define MAXBRIGHTNESS 100 +#define PCB_VERSION 1 //1,2 or 3 +#define Numitron_4511N +#define LTBIpin 5 +byte digitEnablePins[] = {13, 12, 14, 16}; //define here the digit enable pins from 4 to 8 +byte ABCDPins[4] = {4, 0, 2, 15}; +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "Numitron Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_10 //8266 LED Clock with MAX7219 chip +#define DEBUG +#define MAXBRIGHTNESS 10 +#define FW "fw10" //firmware name +#define MAX7219CNG +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "LED UniClock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_11 //8266 LED Clock with MM5450 chip +#define DEBUG +#define FW "fw11" //firmware name +#define MAXBRIGHTNESS 100 +//#define USE_DALLAS_TEMP +//#define TEMP_DALLAS_PIN 2 +//#define RADAR_PIN 4 +#define RADAR_TIMEOUT 5 //min +#define LIGHT_SENSOR_PIN A0 //Only ADC pins are usable! for example: 34,35,36,39... +#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +#define MM5450 +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "LED UniClock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_12 //8266 VFD Clock with PT6355 chip +#define DEBUG +#define FW "fw12" //firmware name +#define PT6355 +int maxDigits = 6; +byte segmentEnablePins[] = {16, 7, 0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 10, 12, 13, 14, 15}; //segment enable bits +#define PIN_CS 22 //12 or 15 // D6 CS_ +#define PIN_CLK 16 // D7 Clock +#define PIN_SIN 21 // D5 DataIN +#define PIN_SDA 19 +#define PIN_SCL 23 +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 5 +#define ALARMSPEAKER_PIN 26 //Alarm buzzer pin +#define ALARMBUTTON_PIN 4 //32 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define RADAR_PIN -1 +#define RADAR_TIMEOUT 5 //5min +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "VFD UniClock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_13 //8266 GP PCB v1 clock with 4x IV-16 Numitron tubes + GPS timesync (white box) +#define DEBUG +#define FW "fw13" //firmware name +#define MAXBRIGHTNESS 10 +#define USE_GPS +#define PCB_VERSION 3 //1,2 or 3 +#define Numitron_4511N +#define LTBIpin 5 +byte digitEnablePins[] = {13, 12, 14, 16}; //define here the digit enable pins from 4 to 8 +byte ABCDPins[4] = {4, 0, 2, 15}; +#define AP_NAME "UNICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "Numitron UniClock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_14 //8266 6X LD8035 VFD-clock +#define DEBUG +#define FW "fw14" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 4 //Dallas temp sensor pin. If not used, SET TO -1 +#define MAX6921 +byte segmentEnablePins[] = {7, 9, 2, 4, 8, 5, 6, 3}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {14, 15, 19, 18, 17, 16}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6,7,8) (You may define any number) +//MAX6921 pins +#define PIN_LE 0 // D3 Shift Register Latch Enable +#define PIN_CLK 2 // D4 Shift Register Clock +#define PIN_DATA 5 // D1 Shift Register Data +#define PIN_BL 15 // D8 Shift Register Blank (1=display off 0=display on) +//#define LIGHT_SENSOR_PIN A0 //Only ADC pins are usable! +//#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +//#define LUX_CALC_SCALAR 12518931 * 1.2 +#define AP_NAME "UNFICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "LD 8035 VFD Clock" +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_15 //8266 6X IV-6 VFD-clock RGB +#define DEBUG +#define FW "fw15" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 3 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 4 //Dallas temp sensor pin. If not used, SET TO -1 +#define MAX6921 +byte segmentEnablePins[] = {7, 9, 2, 4, 8, 5, 6, 3}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {14, 15, 19, 18, 17, 16}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6,7,8) (You may define any number) +//MAX6921 pins +#define PIN_LE 0 // D3 Shift Register Latch Enable +#define PIN_CLK 2 // D4 Shift Register Clock +#define PIN_DATA 5 // D1 Shift Register Data +#define PIN_BL 15 // D8 Shift Register Blank (1=display off 0=display on) +#define ALARMSPEAKER_PIN 12 //Alarm buzzer pin +#define ALARMBUTTON_PIN 13 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define LIGHT_SENSOR_PIN A0 //Only ADC pins are usable! +//#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +//#define LUX_CALC_SCALAR 12518931 * 1.2 +#define AP_NAME "UNFICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IV-6 VFD Clock" +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_15a //8266 6X IV-6 VFD-clock RGB +#define DEBUG +#define FW "fw15A" //firmware name +#define MAXBRIGHTNESS 100 +//#define USE_NEOPIXEL +//#define NEOPIXEL_PIN 3 +#define PIN_FLD_BUTTON 16 +#define PIN_SET_BUTTON 13 +#define USE_RTC +#define PIN_SDA 2 +#define PIN_SCL 0 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 5 //Dallas temp sensor pin. If not used, SET TO -1 +#define MAX6921 +byte segmentEnablePins[] = {10, 11, 16, 15, 14, 13, 12, 17}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {6, 7, 8, 9, 19, 18}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6,7,8) (You may define any number) +//MAX6921 pins +#define PIN_LE 12 // D3 Shift Register Latch Enable +#define PIN_CLK 14 // D4 Shift Register Clock +#define PIN_DATA 15 // D1 Shift Register Data +#define PIN_BL 4 // D8 Shift Register Blank (1=display off 0=display on) +//#define LIGHT_SENSOR_PIN A0 //Only ADC pins are usable! +//#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +//#define LUX_CALC_SCALAR 12518931 * 1.2 +#define AP_NAME "UNFICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IV-6 VFD Clock" +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_16 //8266 4X IV-6, 1X LD8035 +#define DEBUG +#define FW "fw16" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 4 //Dallas temp sensor pin. If not used, SET TO -1 +#define MAX6921 +byte segmentEnablePins[] = {7, 9, 2, 4, 8, 5, 6, 3}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {19, 18, 17, 16}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6,7,8) (You may define any number) +//MAX6921 pins +#define PIN_LE 0 // D3 Shift Register Latch Enable +#define PIN_CLK 2 // D4 Shift Register Clock +#define PIN_DATA 5 // D1 Shift Register Data +#define PIN_BL 15 // D8 Shift Register Blank (1=display off 0=display on) +//#define LIGHT_SENSOR_PIN A0 //Only ADC pins are usable! +//#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +//#define LUX_CALC_SCALAR 12518931 * 1.2 +#define AP_NAME "UNFICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IV-6 VFD Clock" +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_17 //8266 + PT6311 4X IV-22, russian clock +#define DEBUG +#define USE_RTC +#define FW "fw17" //firmware name +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 3 +byte tubePixels[] = {0, 1, 2, 3}; //4 tubes, single leds +#define MAXBRIGHTNESS 10 +//#define USE_DALLAS_TEMP +//#define TEMP_DALLAS_PIN 4 //Dallas temp sensor pin. If not used, SET TO -1 +#define USE_RTC +#define PT6311 +byte segmentEnablePins[] = {4, 3, 1, 7, 6, 5, 2, 0}; +byte digitEnablePins[] = {0, 1, 2, 3}; +//PT6311 pins +#define PIN_CLK 14 // Clock +#define PIN_DIN 13 // DataIN +#define PIN_LOAD 15 // STB pin +//#define LIGHT_SENSOR_PIN A0 //Only ADC pins are usable! +//#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +//#define LUX_CALC_SCALAR 12518931 * 1.2 +#define PIN_SDA 4 +#define PIN_SCL 5 +#define AP_NAME "UNFICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IV-22 VFD Clock" +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_18 //8266 IV-18 VFD-clock RGB +#define DEBUG +#define FW "fw18" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 3 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +//#define USE_DALLAS_TEMP +//#define TEMP_DALLAS_PIN -1 //Dallas temp sensor pin. If not used, SET TO -1 +#define USE_DHT_TEMP +#define DHTTYPE DHT11 +#define TEMP_DHT_PIN 12 +#define MAX6921 +byte segmentEnablePins[] = {13, 15, 18, 19, 17, 14, 16, 12}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {6, 10, 5, 4, 0, 3, 2, 1}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6,7,8) (You may define any number) +//MAX6921 pins +#define PIN_LE 4 // D4 Shift Register Latch Enable +#define PIN_CLK 5 // D1 Shift Register Clock +#define PIN_DATA 2 // D4 Shift Register Data +#define PIN_BL 14 // D5 Shift Register Blank (1=display off 0=display on) +#define ALARMSPEAKER_PIN 16 //Alarm buzzer pin +#define ALARMBUTTON_PIN 13 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define LIGHT_SENSOR_PIN A0 //Only ADC pins are usable! +//#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +//#define LUX_CALC_SCALAR 12518931 * 1.2 +#define AP_NAME "UNFICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "IV-18 VFD Clock" +//#define DISABLE_NIGHT_ANIMATION +#endif + +//____________ P.S. clocks / thermometers ____________________________________________ +#ifdef CLOCK_20 //8266 D1-mini, P.S. PCB 4xIN14 clock-thermometer +//#define DEBUG +#define FW "fw20" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 3 //RX +#define MULTIPLEX74141 +byte digitEnablePins[] = {15, 13, 12, 14}; +byte ABCDPins[4] = {2, 4, 5, 0}; +#define DP_PIN -1 // decimalPoint inside Nixie tube, set -1, if not used! +#define ENABLE_CLOCK_DISPLAY true //don't display date/time!!! +#define DATE_REPEAT_MIN 3 //show date only every xxx minute. If zero, datum is never displayed +#define SHIFT_TUBES_LEFT_BY_1 //shift left IP address by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 15 +#define GRAD_CHARCODE 16 +#define PERCENT_CHARCODE 17 +#define COLON_PIN 1 //TX //Blinking Colon pin. If not used, SET TO -1 +#define LED_SWITCH_PIN 16 //external led backlight ON/OFF. If not used, SET TO -1 +#define AP_NAME "Nixie ORA" +#define AP_PASSWORD "" +#define WEBNAME "Nixie IN-14 Óra" +#define DEFAULT_SSID "" +#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_21 //8266 D1-mini, P.S. PCB 3xIN14 1xIN-19A thermometer / humidity +//#define DEBUG +#define FW "fw21" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_DHT_TEMP +#define DHTTYPE DHT22 +#define TEMP_DHT_PIN 3 //RX +#define MULTIPLEX74141 +byte digitEnablePins[] = {15, 13, 12, 14}; +byte ABCDPins[4] = {2, 4, 5, 0}; +#define DP_PIN -1 // decimalPoint inside Nixie tube, set -1, if not used! +#define ENABLE_CLOCK_DISPLAY false //don't display date/time!!! +#define SHIFT_TUBES_LEFT_BY_1 //shift left IP address by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 4 +#define GRAD_CHARCODE 16 +#define PERCENT_CHARCODE 7 +#define LED_SWITCH_PIN 16 //external led backlight ON/OFF. If not used, SET TO -1 +#define DECIMALPOINT_PIN 1 //TX //Nixie decimal point between digits. If not used, SET TO -1 +#define DATE_REPEAT_MIN 0 //show date only every xxx minute. If zero, datum is never displayed +#define TEMP_START 01 +#define TEMP_END 45 +#define HUMID_START 45 +#define HUMID_END 59 +#define AP_NAME "Nixie Homero" +#define AP_PASSWORD "" +#define WEBNAME "Nixie Hőmérő" +#define DEFAULT_SSID "" +#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_22 //8266 NODEMCU, P.S. PCB 3xIN14 1xIN-19A thermometer / humidity +//#define DEBUG +#define FW "fw22" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +byte tubePixels[] = {0, 1, 2, 3}; //4 tubes, single leds +#define USE_DHT_TEMP +#define DHTTYPE DHT22 +#define TEMP_DHT_PIN 1 +#define MULTIPLEX74141 +byte digitEnablePins[] = {15, 13, 12, 14}; +byte ABCDPins[4] = {2, 4, 5, 0}; +#define DP_PIN -1 // decimalPoint inside Nixie tube, set -1, if not used! +#define ENABLE_CLOCK_DISPLAY false //don't display date/time!!! +#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 5 +#define GRAD_CHARCODE 16 +#define PERCENT_CHARCODE 6 +#define DECIMALPOINT_PIN 16 //Nixie decimal point between digits. If not used, SET TO -1 +#define DATE_REPEAT_MIN 0 //show date only every xxx minute. If zero, datum is never displayed +#define TEMP_START 01 +#define TEMP_END 45 +#define HUMID_START 45 +#define HUMID_END 59 +#define AP_NAME "Nixie Homero" +#define AP_PASSWORD "" +#define WEBNAME "Nixie Hőmérő" +#define DEFAULT_SSID "" +#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_23 //ESP32 D1 mini, P.S. PCB 3xIN14 1xIN-19A thermometer / humidity +#define DEBUG +#define SIMULATE_TEMPERATURE_SENSOR +#define FW "fw23" //firmware name +#define MAXBRIGHTNESS 100 +//#define USE_NEOPIXEL +//#define NEOPIXEL_PIN 22 +//byte tubePixels[] = {0,1,2,3}; //4 tubes, single leds +//#define USE_DHT_TEMP +#define DHTTYPE DHT22 +#define TEMP_DHT_PIN 1 +#define MULTIPLEX74141_ESP32 +//byte digitEnablePins[] = {26,18,33,19}; //Old sw,old ESP32 +byte digitEnablePins[] = {19, 33, 18, 26}; +byte ABCDPins[4] = {32, 16, 4, 17}; +#define DP_PIN -1 // decimalPoint inside Nixie tube, set -1, if not used! +#define ENABLE_CLOCK_DISPLAY false //don't display date/time!!! +#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define USE_SHT21 //I2C Temperature + humidity +#define PIN_SDA 25 // you can set the used SDA and SCL pins +#define PIN_SCL 21 // if it is not default value +#define TEMP_CHARCODE 5 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 6 +#define DECIMALPOINT_PIN 23 //Nixie decimal point between digits. If not used, SET TO -1 +#define DATE_REPEAT_MIN 0 //show date only every xxx minute. If zero, datum is never displayed +#define TEMP_START 01 +#define TEMP_END 45 +#define HUMID_START 45 +#define HUMID_END 59 +#define AP_NAME "Nixie-Homero" +#define AP_PASSWORD "" +#define WEBNAME "Nixie Hőmérő" +#define DEFAULT_SSID "***NET***" +#define DEFAULT_PSW "Szentatya5" +//#define USE_WIFIMANAGER +#define DISABLE_NIGHT_ANIMATION +//#define DISABLE_BROWNOUT +//#define USE_MQTT +#define MQTT_PREFIX "Nixie_Homero" +#define USE_MASTER_CLOCK //enable it, if you want to get any data from MASTER CLOCK. This will be the sensor#0 +//#define USE_MASTER_TEMP //enable it, if you want to get temperature from MASTER CLOCK +//#define USE_MASTER_HUMID //enable it, if you want to get humidity from MASTER CLOCK +#define USE_MASTER_RADAR //enable it, if you want to get radar from MASTER CLOCK +//#define MASTER_TEMPERATURE_TOPIC "homeassistant/sensor/10521c5e14c4/temperature/state" +//#define MASTER_HUMIDITY_TOPIC "homeassistant/sensor/10521c5e14c4/humidity/state" +#define MASTER_RADAR_TOPIC "Nixie_IN-18_Clock/sensor/10521c5e14c4/radar/state" +#endif + +#ifdef CLOCK_23A //ESP32 WROOM, U.Z. PCB 4xZ566M 1xIN-19A thermometer / humidity +#define DEBUG +#define FW "fw23A" //firmware name +#define MAXBRIGHTNESS 100 +byte tubePixels[] = {0, 1, 2, 3}; //4 tubes, single leds +#define USE_DHT_TEMP +#define DHTTYPE DHT11 +#define TEMP_DHT_PIN 19 +#define MULTIPLEX74141_ESP32 +byte digitEnablePins[] = {18, 13, 12, 27, 5}; //C/percent - tenths-of-degree (tizedfok) - degree ones (fok1) - tens-of-degree (fok10) - sign (előjel), pin 18 +byte ABCDPins[4] = {26, 33, 32, 25}; +#define COLON_PIN 14 // decimalPoint inside Nixie tube, set -1, if not used! +#define ENABLE_CLOCK_DISPLAY false //don't display date/time!!! +#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define USE_SHT21 //I2C Temperature + humidity +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +#define TEMP_CHARCODE 4 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 7 +#define PLUS_CHARCODE 0 +#define MINUS_CHARCODE 1 +#define DATE_REPEAT_MIN 0 //show date only every xxx minute. If zero, datum is never displayed +#define TEMP_START 01 +#define TEMP_END 45 +#define HUMID_START 45 +#define HUMID_END 59 +#define AP_NAME "Nixie-Homero" +#define AP_PASSWORD "" +#define WEBNAME "Nixie Hőmérő" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define USE_MQTT +//#define MQTT_PREFIX "Nixie_Homero" +//#define USE_MASTER_CLOCK //enable it, if you want to get any data from MASTER CLOCK. This will be the sensor#0 +//#define USE_MASTER_TEMP //enable it, if you want to get temperature from MASTER CLOCK +//#define USE_MASTER_HUMID //enable it, if you want to get humidity from MASTER CLOCK +//#define USE_MASTER_RADAR //enable it, if you want to get radar from MASTER CLOCK +//#define MASTER_TEMPERATURE_TOPIC "homeassistant/sensor/10521c5e14c4/temperature/state" +//#define MASTER_HUMIDITY_TOPIC "homeassistant/sensor/10521c5e14c4/humidity/state" +//#define MASTER_RADAR_TOPIC "Nixie_IN-18_Clock/sensor/3c6105172188/radar/state" +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_23B //ESP32 WROOM U.Z. PCB Z571M+4xZ566M+IN-19A clock/thermometer/humidity +#define DEBUG +#define FW "fw23B" //firmware name +#define THERMOMETER_CLOCK +#define MAXBRIGHTNESS 100 +#define USE_DHT_TEMP +#define DHTTYPE DHT11 +#define TEMP_DHT_PIN 19 +#define MULTIPLEX74141_ESP32 +byte digitEnablePins[] = {13, 17, 12, 27, 5, 18}; //sign (előjel), pin 18 - tens-of-degree (fok10) - degree ones (fok1) - tenths-of-degree (tizedfok) - hundredths-of-degree (századfok) - C/percent, pin 13 +byte ABCDPins[4] = {26, 33, 32, 25}; +#define COLON_PIN 14 // decimalPoint inside Nixie tube, set -1, if not used! +#define ENABLE_CLOCK_DISPLAY false //don't display date/time!!! +#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define USE_SHT21 //I2C Temperature + humidity +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +#define TEMP_CHARCODE 4 +#define PERCENT_CHARCODE 7 +#define PLUS_CHARCODE 0 +#define MINUS_CHARCODE 1 +#define DATE_REPEAT_MIN_1 //show date only every xxx minute. If zero, datum is never displayed +#define TEMP_START 40 +#define TEMP_END 45 +#define HUMID_START 45 +#define HUMID_END 50 +#define AP_NAME "Nixie-Ora-Homero" +#define AP_PASSWORD "" +#define WEBNAME "Nixie Óra-Hőmérő-Páramérő" +#endif + +#ifdef CLOCK_26 //ESP32 D1 mini, UNFI 6 x IV-11 VFD tubes clock, SHT21 +#define DEBUG +#define FW "fw26" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 22 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 25 //Dallas temp sensor pin. If not used, SET TO -1 +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 25 //DHT temp sensor pin. If not used, SET TO -1 +#define USE_BME280 //I2C Temperature + humidity + pressure +#define USE_BMP280 //I2C Temperature + barometric pressure +#define USE_AHTX0 //I2C Temperature + humidity +#define USE_SHT21 //I2C Temperature + humidity +#define PIN_SDA 26 // you can set the used SDA and SCL pins +#define PIN_SCL 27 // if it is not default value +//#define USE_MQTT +#define MAX6921_ESP32 +byte segmentEnablePins[] = {19, 17, 15, 12, 13, 16, 18, 14}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {9, 8, 7, 2, 1, 0}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6) (You may define any number) +#define DOUBLE_BLINK //both separator points are blinking +#define DATE_REPEAT_MIN 60 //show date only every xxx minute. If zero, datum is never displayed +#define DATE_START 07 +#define DATE_END 12 +#define TEMP_START 25 +#define TEMP_END 30 +#define HUMID_START 30 +#define HUMID_END 35 +//MAX6921 pins +#define PIN_LE 4 // Shift Register Latch Enable +#define PIN_CLK 17 // Shift Register Clock +#define PIN_DATA 16 // Shift Register Data +#define PIN_BL 32 // Shift Register Blank (1=display off 0=display on) +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 0 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "VFD-Ora" +#define AP_PASSWORD "" +#define WEBNAME "VFD-Óra" +#define DEFAULT_SSID "***NET***" +#define DEFAULT_PSW "Szentatya5" +//#define USE_WIFIMANAGER +#define DISABLE_NIGHT_ANIMATION +//#define DISABLE_BROWNOUT +#define USE_MQTT +//#define MQTT_PREFIX "VFD-Clock" +#define USE_MASTER_CLOCK //enable it, if you want to get any data from MASTER CLOCK. This will be the sensor#0 +//#define USE_MASTER_TEMP //enable it, if you want to get temperature from MASTER CLOCK +//#define USE_MASTER_HUMID //enable it, if you want to get humidity from MASTER CLOCK +#define USE_MASTER_RADAR //enable it, if you want to get radar from MASTER CLOCK +//#define MASTER_TEMPERATURE_TOPIC "homeassistant/sensor/10521c5e14c4/temperature/state" +//#define MASTER_HUMIDITY_TOPIC "homeassistant/sensor/10521c5e14c4/humidity/state" +#define MASTER_RADAR_TOPIC "Nixie_IN-18_Clock/sensor/10521c5e14c4/radar/state" + +#endif + +//______________________ESP-32 CLOCKS (2x18pin ESP32 module) ______________________________________________________ + +#ifdef CLOCK_30 //ESP32, UNFI PCB clock, 6 x IV-11 VFD tubes +#define DEBUG +#define FW "fw30" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 2 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define DATE_REPEAT_MIN 3 //show date only every 3 minutes. If zero, datum is never displayed +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 23 //15 //Dallas temp sensor pin. If not used, SET TO -1 +#define MAX6921_ESP32 +byte segmentEnablePins[] = {19, 17, 15, 12, 13, 16, 18, 14}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {9, 8, 7, 2, 1, 0}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6) (You may define any number) +#define DOUBLE_BLINK //both separator points are blinking +//MAX6921 pins +#define PIN_LE 14 // Shift Register Latch Enable +#define PIN_CLK 13 // Shift Register Clock +#define PIN_DATA 27 // Shift Register Data +#define PIN_BL 12 // Shift Register Blank (1=display off 0=display on) +//#define PIN_SDA 21 // you can set the used SDA and SCL pins +//#define PIN_SCL 22 // if it is not default value +#define ALARMSPEAKER_PIN 33 //Alarm buzzer pin +#define ALARMBUTTON_PIN -1 //32 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define RADAR_PIN -1 +#define RADAR_TIMEOUT 5 //5min +//#define TUBE_POWER_PIN 25 +//#define TUBE_POWER_ON LOW +#define LIGHT_SENSOR_PIN 35 //Only ADC pins are usable! for example: 34,35,36,39... +#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness +#define LUX_CALC_SCALAR 12518931 * 1.2 +#define AP_NAME "UNICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "GP IV-11" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_31 //ESP32, UNFI board, 6 x Z573M Nixie tubes +#define DEBUG +#define FW "fw31" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 2 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 23 //Dallas temp sensor pin. If not used, SET TO -1 +#define MULTIPLEX74141_ESP32 +byte digitEnablePins[] = {4, 16, 17, 5, 18, 19}; //ESP32 6x tube Clock +byte ABCDPins[4] = {12, 27, 14, 13}; +#define DP_PIN 15 // decimalPoint inside Nixie tube, set -1, if not used! +#define LEFTDECIMAL false //set true (Z574M), if decimal point is on the left side on the tube. Else set false (Z573M)! +//#define TEMP_CHARCODE -1 //disable char => shift display right with 1 digit +//#define GRAD_CHARCODE -1 //disable char => shift display right with 1 digit +#define ALARMSPEAKER_PIN 33 //Alarm buzzer pin +#define ALARMBUTTON_PIN 32 //Alarm switch off button pin +#define ALARM_ON HIGH +#define AP_NAME "UNICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32UniClock 3.0" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + + +#ifdef CLOCK_32 //ESP32, UNFI 6 x IV-11 VFD tubes clock, DHT22 sensor +#define DEBUG +#define FW "fw32" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 2 +#define LIGHT_SENSOR_PIN 23 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DHT_TEMP +#define DHTTYPE DHT22 +#define TEMP_DHT_PIN 23 //DHT temp sensor pin. If not used, SET TO -1 +#define MAX6921_ESP32 +byte segmentEnablePins[] = {19, 17, 15, 12, 13, 16, 18, 14}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {9, 8, 7, 2, 1, 0}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6) (You may define any number) +//MAX6921 pins +#define PIN_LE 14 // Shift Register Latch Enable +#define PIN_CLK 13 // Shift Register Clock +#define PIN_DATA 27 // Shift Register Data +#define PIN_BL 12 // Shift Register Blank (1=display off 0=display on) +//#define ALARMSPEAKER_PIN 33 //Alarm buzzer pin +#define ALARMBUTTON_PIN 32 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "UNICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32UniClock 3.0" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_33 //TOM025 ESP32, Palfi S. board, 6 x Z573M Nixie tubes +#define DEBUG +#define FW "fw33" +#define MAXBRIGHTNESS 100 +//#define USE_NEOPIXEL +//#define NEOPIXEL_PIN 2 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +//#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 23 //Dallas temp sensor pin. If not used, SET TO -1 +#define MULTIPLEX74141_ESP32 +byte digitEnablePins[] = {22, 23, 21, 19, 12, 14}; //ESP32 6x tube Clock +byte ABCDPins[4] = {18, 5, 16, 17}; +#define DP_PIN 15 // decimalPoint inside Nixie tube, set -1, if not used! +#define LEFTDECIMAL false //set true (Z574M), if decimal point is on the left side on the tube. Else set false (Z573M)! +//#define TEMP_CHARCODE -1 //disable char => shift display right with 1 digit +//#define GRAD_CHARCODE -1 //disable char => shift display right with 1 digit +//#define ALARMSPEAKER_PIN 33 //Alarm buzzer pin +//#define ALARMBUTTON_PIN 32 //Alarm switch off button pin +//#define ALARM_ON HIGH +#define AP_NAME "UNICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32UniClock 3.0" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_34 //Mule V2 ESP32, board, 6 x SA40 LED Display +#define DEBUG +#define FW "fw34" +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 26 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +//#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 25 //Dallas temp sensor pin. If not used, SET TO -1 +#define MULE_V2 +byte digitEnablePins[] = {5, 16, 23}; //CD4028 A,B,C +byte ABCDPins[4] = {33, 32, 14, 13}; //CD4543 A,B,C,D +//#define DP_PIN 15 // decimalPoint inside Nixie tube, set -1, if not used! +//#define LEFTDECIMAL false //set true (Z574M), if decimal point is on the left side on the tube. Else set false (Z573M)! +//#define TEMP_CHARCODE -1 //disable char => shift display right with 1 digit +//#define GRAD_CHARCODE -1 //disable char => shift display right with 1 digit +//#define ALARMSPEAKER_PIN 22 //Alarm buzzer pin +//#define ALARMBUTTON_PIN 32 //Alarm switch off button pin +//#define ALARM_ON HIGH +#define AP_NAME "UNICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32UniClock 3.0" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_35 //ESP32, 6X Z566M and 3x 74595 6x74141 driver (NON-MULTIPLEX) +#define DEBUG +#define SIMULATE_TEMPERATURE_SENSOR +#define FW "fw35" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 32 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 25 +//#define USE_DHT_TEMP +#define TEMP_DHT_PIN 32 +byte tubes[] = {5, 4, 3, 2, 1, 0}; //6 tubes, reverse order +byte tubePixels[] = {5, 4, 3, 2, 1, 0}; +#define dataPin 14 //D5 +#define latchPin 27 //D6 +#define clkPin 26 //D7 +#define NO_MULTIPLEX_ESP32 +#define COLON_PIN 13 //Blinking Colon pin. If not used, SET TO -1 +#define ALARMSPEAKER_PIN 12 //Alarm buzzer pin +//#define ALARMBUTTON_PIN 36 //Alarm switch off button pin +#define ALARM_ON HIGH +#define LIGHT_SENSOR_PIN 35 +#define MAXIMUM_LUX 100 +#define LUX_CALC_SCALAR 12518931 * 1.2 //Calibrate here +//#define RADAR_PIN 15 +//#define RADAR_TIMEOUT 5 //min +#define USE_RTC +#define PIN_SDA 21 +#define PIN_SCL 22 +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "Z566M Nixie Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#define DISPLAYTEMP_ONLY_2DIGITS +#define DISPLAYHUMID_ONLY_2DIGITS +#define GRAD_CHARCODE 10 //no display grad char +#define TEMP_CHARCODE 10 //no display temp char +#define PERCENT_CHARCODE 10 //no % tube +#define TEMP_START_POSITION 3 +#define HUMID_START_POSITION 3 +#endif + +#ifdef CLOCK_36 //ESP32, 4x IN-1 tubes and 4x74141 driver (NON-MULTIPLEX) +#define DEBUG +#define FW "fw35" //firmware name +#define MAXBRIGHTNESS 100 +//#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN -1 +#define USE_NEOPIXEL +#define dataPin 14 //D5 +#define latchPin 27 //D6 +#define clkPin 26 //D7 +byte tubes[] = {3, 2, 1, 0}; //4 tubes, old clock... +byte tubePixels[] = {0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0}; //4 tubes, double row, 17 leds (GP) +#define NO_MULTIPLEX_ESP32 +#define COLON_PIN 2 //Blinking Colon pin. If not used, SET TO -1 ( old IN-1-Clock: white:2 brown: SDA) +#define AP_NAME "UNICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "IN-1 Nixie Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +//______________________ESP-32 CLOCKS (2x20pin ESP32 D1 mini module) ______________________________________________________ +#ifdef CLOCK_40 //V1 ESP32, UNFI 6 x IV-11 VFD tubes clock +#define DEBUG +#define FW "fw40" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 22 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 25 //Dallas temp sensor pin. If not used, SET TO -1 +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 25 //DHT temp sensor pin. If not used, SET TO -1 +#define MAX6921_ESP32 +byte segmentEnablePins[] = {19, 17, 15, 12, 13, 16, 18, 14}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {9, 8, 7, 2, 1, 0}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6) (You may define any number) +#define DOUBLE_BLINK //both separator points are blinking +//MAX6921 pins +#define PIN_LE 4 // Shift Register Latch Enable +#define PIN_CLK 17 // Shift Register Clock +#define PIN_DATA 16 // Shift Register Data +#define PIN_BL 32 // Shift Register Blank (1=display off 0=display on) +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 0 //Alarm switch off button pin +#define TUBE_POWER_PIN 23 +#define TUBE_POWER_ON HIGH +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32 IV-11 VFD-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_41 //V2 ESP32, UNFI 6 x IV-11 VFD tubes clock (jumper wire) +#define DEBUG +#define FW "fw41" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 32 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 4 //Dallas temp sensor pin. If not used, SET TO -1 +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 4 //DHT temp sensor pin. If not used, SET TO -1 +#define MAX6921_ESP32 +byte segmentEnablePins[] = {19, 17, 15, 12, 13, 16, 18, 14}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {9, 8, 7, 2, 1, 0}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6) (You may define any number) +#define DOUBLE_BLINK //both separator points are blinking +//MAX6921 pins +#define PIN_LE 22 // Shift Register Latch Enable +#define PIN_CLK 17 // Shift Register Clock +#define PIN_DATA 16 // Shift Register Data +#define PIN_BL 21 // Shift Register Blank (1=display off 0=display on) +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 0 //Alarm switch off button pin +#define TUBE_POWER_PIN 23 +#define TUBE_POWER_ON HIGH +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32 IV-11 VFD-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_42 //V3 ESP32, UNFI 6 x IV-11 VFD tubes clock +#define DEBUG +#define FW "fw42" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 25 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 22 //Dallas temp sensor pin. If not used, SET TO -1 +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 22 //DHT temp sensor pin. If not used, SET TO -1 +#define MAX6921_ESP32 +byte segmentEnablePins[] = {19, 17, 15, 12, 13, 16, 18, 14}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {9, 8, 7, 2, 1, 0}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6) (You may define any number) +#define DOUBLE_BLINK //both separator points are blinking +//MAX6921 pins +#define PIN_LE 4 // Shift Register Latch Enable +#define PIN_CLK 17 // Shift Register Clock +#define PIN_DATA 16 // Shift Register Data +#define PIN_BL 32 // Shift Register Blank (1=display off 0=display on) +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 0 //Alarm switch off button pin +#define TUBE_POWER_PIN 27 +#define TUBE_POWER_ON HIGH +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32 IV-11 VFD-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + + +#ifdef CLOCK_43 //V1 ESP32, UNFI board, 6 x Z573M Nixie tubes +#define DEBUG +//#define SIMULATE_TEMPERATURE_SENSOR +#define FW "fw43" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 22 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 25 +#define TEMP_DALLAS_PIN 25 //Dallas temp sensor pin. If not used, SET TO -1 +#define MULTIPLEX74141_ESP32 +byte DRAM_ATTR digitEnablePins[] = {26, 18, 33, 19, 23, 5}; //ESP32 6x tube Clock +byte DRAM_ATTR ABCDPins[4] = {32, 16, 4, 17}; +#define DP_PIN 27 // decimalPoint inside Nixie tube, set -1, if not used! +#define LEFTDECIMAL false //set true (Z574M), if decimal point is on the left side on the tube. Else set false (Z573M)! +//#define TEMP_CHARCODE -1 //disable char => shift display right with 1 digit +//#define GRAD_CHARCODE -1 //disable char => shift display right with 1 digit +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 0 //Alarm switch off button pin +#define ALARM_ON HIGH +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "Z573M Nixie-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_44 //V2 ESP32, UNFI board, 6 x Z573M Nixie tubes (rewired revision, átkötéses) +#define DEBUG +#define FW "fw44" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 32 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 4 +#define TEMP_DALLAS_PIN 4 //Dallas temp sensor pin. If not used, SET TO -1 +#define MULTIPLEX74141_ESP32 +byte digitEnablePins[] = {26, 18, 33, 19, 5, 25}; //ESP32 6x tube Clock +byte ABCDPins[4] = {21, 16, 22, 17}; +#define DP_PIN 27 // decimalPoint inside Nixie tube, set -1, if not used! +#define LEFTDECIMAL false //set true (Z574M), if decimal point is on the left side on the tube. Else set false (Z573M)! +//#define TEMP_CHARCODE -1 //disable char => shift display right with 1 digit +//#define GRAD_CHARCODE -1 //disable char => shift display right with 1 digit +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 0 //Alarm switch off button pin +#define ALARM_ON HIGH +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32 Z573M Nixie-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_45 //V3 ESP32, UNFI board, 6 x Z573M Nixie tubes +#define DEBUG +#define FW "fw45" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 25 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 22 +#define TEMP_DALLAS_PIN 22 //Dallas temp sensor pin. If not used, SET TO -1 +#define MULTIPLEX74141_ESP32 +byte digitEnablePins[] = {26, 18, 33, 19, 23, 5}; //ESP32 6x tube Clock +byte ABCDPins[4] = {32, 16, 4, 17}; +#define DP_PIN 27 // decimalPoint inside Nixie tube, set -1, if not used! +#define LEFTDECIMAL false //set true (Z574M), if decimal point is on the left side on the tube. Else set false (Z573M)! +//#define TEMP_CHARCODE -1 //disable char => shift display right with 1 digit +//#define GRAD_CHARCODE -1 //disable char => shift display right with 1 digit +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 0 //Alarm switch off button pin +#define ALARM_ON HIGH +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32 Z573M Nixie-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_46 //ESP32, UNFI board, 6 x IV-9 Numitron tubes +#define DEBUG +#define ANIMSPEED 70 +#define FW "fw46" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 22 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 27 +#define TEMP_DALLAS_PIN 27 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 35 +#define PIN_SDA 5 // you can set the used SDA and SCL pins +#define PIN_SCL 0 // if it is not default value +#define USE_BH1750 //I2C luxmeter sensor +#define USE_BME280 //I2C Temperature + humidity + pressure +#define USE_BMP280 //I2C Temperature + barometric pressure +#define USE_AHTX0 //I2C Temperature + humidity +#define USE_SHT21 //I2C Temperature + humidity +#define Numitron_4511N +#define PCB_VERSION 32 +#define LTBIpin 23 +byte digitEnablePins[] = {4, 16, 17, 32, 21, 25}; //ESP32 6x tube Clock +byte ABCDPins[4] = {19, 18, 26, 33}; +#define DP_PIN -1 // decimalPoint inside Nixie tube, set -1, if not used! +#define LEFTDECIMAL false //set true (Z574M), if decimal point is on the left side on the tube. Else set false (Z573M)! +//#define TEMP_CHARCODE -1 //disable char => shift display right with 1 digit +//#define GRAD_CHARCODE -1 //disable char => shift display right with 1 digit +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +//#define ALARMBUTTON_PIN 34 //Alarm switch off button pin +#define ALARM_ON HIGH +#define AP_NAME "NUMITRON6_CLOCK" +#define AP_PASSWORD "" +#define WEBNAME "ESP32 Numitron-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_47 //V3 ESP32, UNFI 10-LT-50G VFD tubes clock +#define DEBUG +#define FW "fw47" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 22 //Dallas temp sensor pin. If not used, SET TO -1 +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 22 //DHT temp sensor pin. If not used, SET TO -1 +#define MAX6921_ESP32 +byte segmentEnablePins[] = {12, 13, 16, 18, 17, 14, 15, 19}; //segment enable OUTbits of MAX6921 (a,b,c,d,e,f,g,DP) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {10, 9, 8, 7, 2, 1, 0, 11}; //digit enable OUTbits of MAX6921 (1,2,3,4,5,6,7,8) (You may define any number) +#define DOUBLE_BLINK //both separator points are blinking +//MAX6921 pins +#define PIN_LE 4 // Shift Register Latch Enable +#define PIN_CLK 17 // Shift Register Clock +#define PIN_DATA 16 // Shift Register Data +#define PIN_BL 32 // Shift Register Blank (1=display off 0=display on) +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 0 //Alarm switch off button pin +#define TUBE_POWER_PIN 27 +#define TUBE_POWER_ON HIGH +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32 10-LT-50G VFD-Clock" +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_48 //WROOM ESP32, UNFI SN75518 6xIV-17 clock +#define DEBUG +#define FW "fw48" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 26 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 23 //Dallas temp sensor pin. If not used, SET TO -1 +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 23 //DHT temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 32 +#define LOWER_CIRCLE_CHARCODE 10 +#define UPPER_CIRCLE_CHARCODE 17 +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +#define USE_BH1750 //I2C luxmeter sensor +#define USE_BME280 //I2C Temperature + humidity + pressure +#define USE_BMP280 //I2C Temperature + barometric pressure +#define USE_AHTX0 //I2C Temperature + humidity +#define USE_SHT21 //I2C Temperature + humidity +#define SN75518_ESP32 +//Segments: Dutsrpnmkhgfedcba +byte segmentEnablePins[] = {23, 30, 18, 19, 20, 26, 27, 28, 29, 32, 15, 17, 21, 22, 24, 25, 31}; //segment enable OUTbits of SN75518 (2,3,4,5,6,7,8,9,10,13,14,15,16,17,18,19,20,21) (You MUST define always 8 Pins!!!) +byte digitEnablePins[] = {6, 5, 4, 3, 2, 1}; //digit enable OUTbits of SN75518 (1,2,3,4,5,6) (You may define any number) +#define DOUBLE_BLINK //both separator points are blinking +//SN75518 pins +#define PIN_LE 13 // Shift Register Latch Enable +#define PIN_CLK 12 // Shift Register Clock +#define PIN_DATA 27 // Shift Register Data +#define PIN_STB 14 // Shift Register Strobe +#define ALARMSPEAKER_PIN 19 //Alarm buzzer pin +#define ALARMBUTTON_PIN 33 //Alarm switch off button pin +//#define TUBE_POWER_PIN +#define TUBE_POWER_ON HIGH +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32 IV-17 VFD-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_BROWNOUT +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_49 //WROOM ESP32, UNFI SN75518 6xIV-11 clock +#define DEBUG +#define FW "fw49" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 26 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 23 //Dallas temp sensor pin. If not used, SET TO -1 +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 23 //DHT temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 32 +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +#define SN75518_ESP32 +//Segments: abcdefgD +#define SEGMENT8 +byte segmentEnablePins[] = {17, 18, 19, 20, 21, 22, 23, 24}; //IV-11 segment enable OUTbits of SN75518 (You MUST define 8 Pins!!!) +byte digitEnablePins[] = {27, 28, 29, 30, 31, 32}; //IV-11 digit enable OUTbits of SN75518 (1,2,3,4,5,6) (You may define any number) +//byte segmentEnablePins[] = {17,18,19,20,21,22,23,24}; //IV-12 segment enable OUTbits of SN75518 (You MUST define 8 Pins!!!) +//byte digitEnablePins[] = {27,28,29,30,31,32}; //IV-12 digit enable OUTbits of SN75518 (1,2,3,4,5,6) (You may define any number) +#define DOUBLE_BLINK //both separator points are blinking +//SN75518 pins +#define PIN_LE 13 // Shift Register Latch Enable +#define PIN_CLK 12 // Shift Register Clock +#define PIN_DATA 27 // Shift Register Data +#define PIN_STB 14 // Shift Register Strobe +#define ALARMSPEAKER_PIN 19 //Alarm buzzer pin +#define ALARMBUTTON_PIN 33 //Alarm switch off button pin +//#define TUBE_POWER_PIN +#define TUBE_POWER_ON HIGH +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "ESP32 IV-11 VFD-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_BROWNOUT +//#define DISABLE_NIGHT_ANIMATION +#endif + +//---------------------ESP32 2X HV5122-------------------------------------------------- + +#ifdef CLOCK_50 //V1 ESP32,UNFI 2xHV5122 PCB version, 6xZ573M clock Example clock!!! +#define DEBUG +#define FW "fw50" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 27 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +//#define USE_DALLAS_TEMP +//#define TEMP_DALLAS_PIN 26 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 23 +#define PIN_SDA 4 // you can set the used SDA and SCL pins +#define PIN_SCL 32 // if it is not default value +#define USE_DHT_TEMP +#define DHTTYPE DHT22 +#define TEMP_DHT_PIN 26 +//#define USE_RTC +//#define USE_GPS +#define USE_BME280 //I2C Temperature + humidity + pressure +#define USE_BMP280 //I2C Temperature + barometric pressure +#define USE_AHTX0 //I2C Temperature + humidity +#define USE_SHT21 //I2C Temperature + humidity +#define USE_BH1750 //I2C luxmeter sensor +//_______________________________ HV5122 setup ____________________________________________________ +#define NEWHV5122 +#define PIN_DIN 22 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +#define PIN_CLK 17 // Clock +#define PIN_OE 21 // OutputEnable +#define MAXDIGITS 6 +int maxDigits = MAXDIGITS; +byte digitPins[MAXDIGITS + 1][10] = { ////Data pin numbers 100+ means: chip1 pins are used. Chip0's DOUT is connected to chip1's DIN + {2, 10, 9, 8, 7, 6, 5, 4, 3, 1}, //sec 1 , chip0 (tube#0) + {11, 32, 20, 19, 18, 17, 16, 15, 14, 13}, //sec 10 , chip0 (tube#1) + {22, 31, 29, 30, 27, 28, 25, 26, 23, 24}, //min 1 , chip0 (tube#2) + {101, 131, 110, 109, 108, 107, 106, 105, 104, 103}, //min 10 , chip1 (tube#3) + {111, 132, 120, 119, 118, 117, 116, 115, 114, 113}, //hour 1 , chip1 (tube#4) + {122, 129, 130, 127, 128, 125, 126, 123, 124, 121}, //hour 10 , chip1 (tube#5) + {0, 12, 21, 102, 112, 0, 0, 0, 0, 0} //extra decimalPoint/blinking dots (tube0...tube6) +}; +//#define MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! #1..#4 positions are used +//___________________________________________________________________________________ +#define ALARMSPEAKER_PIN 16 //Alarm buzzer pin +//#define ALARMBUTTON_PIN 23 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define RADAR_PIN 4 +#define RADAR_TIMEOUT 5 //min +//#define TUBE_POWER_PIN 4 +#define TUBE_POWER_ON HIGH +#define ENABLE_CLOCK_DISPLAY true +#define DATE_REPEAT_MIN 3 //show date only every xxx minute. If zero, datum is never displayed +//#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 10 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 10 +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "UNFI Nixie-Clock HV5122" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_51 //V2 ESP32, UNFI 2xHV5122 PCB version, 6xZ573M clock Example clock!!! +#define DEBUG +#define FW "fw51" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 27 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 19 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 23 +#define PIN_SDA 4 // you can set the used SDA and SCL pins +#define PIN_SCL 32 // if it is not default value +//#define USE_DHT_TEMP +//#define DHTTYPE DHT11 +//#define TEMP_DHT_PIN 23 +//#define USE_BME280 //I2C Temperature + humidity + pressure +//#define USE_BMP280 //I2C Temperature + barometric pressure +//#define USE_AHTX0 //I2C Temperature + humidity +//#define USE_SHT21 //I2C Temperature + humidity +//#define USE_BH1750 //I2C luxmeter sensor +//USE_MQTT +//#define MQTT_PREFIX "UniClock32" +//#define USE_MASTER_CLOCK //enable it, if you want to get any data from MASTER CLOCK. This will be the sensor#0 +//#define USE_MASTER_TEMP //enable it, if you want to get temperature from MASTER CLOCK +//#define USE_MASTER_HUMID //enable it, if you want to get humidity from MASTER CLOCK +//#define USE_MASTER_RADAR //enable it, if you want to get radar from MASTER CLOCK +//#define MASTER_TEMPERATURE_TOPIC "homeassistant/sensor/10521c5e14c4/temperature/state" +//#define MASTER_HUMIDITY_TOPIC "homeassistant/sensor/10521c5e14c4/humidity/state" +//#define MASTER_RADAR_TOPIC "homeassistant/sensor/10521c5e14c4/radar/state" +//_______________________________ HV5122 setup ____________________________________________________ +#define NEWHV5122 +#define PIN_DIN 22 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +#define PIN_CLK 17 // Clock +#define PIN_OE 21 // OutputEnable +#define MAXDIGITS 6 +int maxDigits = MAXDIGITS; +byte digitPins[MAXDIGITS + 1][10] = { + {2, 10, 9, 8, 7, 6, 5, 4, 3, 1}, //sec 1 , chip0 (tube#0) + {11, 32, 20, 19, 18, 17, 16, 15, 14, 13}, //sec 10 , chip0 (tube#1) + {22, 31, 29, 30, 27, 28, 25, 26, 23, 24}, //min 1 , chip0 (tube#2) + {101, 131, 110, 109, 108, 107, 106, 105, 104, 103}, //min 10 , chip1 (tube#3) + {111, 132, 120, 119, 118, 117, 116, 115, 114, 113}, //hour 1 , chip1 (tube#4) + {122, 129, 130, 127, 128, 125, 126, 123, 124, 121}, //hour 10 , chip1 (tube#5) + {0, 21, 12, 112, 102, 0, 0, 0, 0, 0} //extra decimalPoint/blinking dots (tube0...tube6) +}; +//#define MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! #1..#4 positions are used +//___________________________________________________________________________________ +#define ALARMSPEAKER_PIN 23 //Alarm buzzer pin +#define ALARMBUTTON_PIN 16 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define RADAR_PIN 4 +#define RADAR_TIMEOUT 5 //min +//#define TUBE_POWER_PIN 4 +#define TUBE_POWER_ON HIGH +#define ENABLE_CLOCK_DISPLAY true +#define DATE_REPEAT_MIN 1 //show date only every xxx minute. If zero, datum is never displayed +//#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 10 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 10 +#define AP_NAME "UNICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "Nixie-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_52 //ESP32, P.S. 2xHV5122 PCB version, 6xIN18 clock PROTOTYPE TESTING!!! +#define DEBUG +#define FW "fw52" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 22 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +//#define USE_DALLAS_TEMP +//#define TEMP_DALLAS_PIN 26 //Dallas temp sensor pin. If not used, SET TO -1 +//#define USE_DHT_TEMP +//#define DHTTYPE DHT11 +//#define TEMP_DHT_PIN 23 +//#define USE_BME280 //I2C Temperature + humidity + pressure +//#define USE_BMP280 //I2C Temperature + barometric pressure +//#define USE_AHTX0 //I2C Temperature + humidity +//#define USE_SHT21 //I2C Temperature + humidity +//#define PIN_SDA 4 // you can set the used SDA and SCL pins +//#define PIN_SCL 32 // if it is not default value +#define PIN_EXTINP1 4 //extra input pin (not used) +#define PIN_EXTINP2 32 //extra input pin (not used) +#define USE_RTC +#define PIN_SDA 27 //D1 SDA/SCL swapped +#define PIN_SCL 26 //D2 +//_______________________________ HV5122 setup ____________________________________________________ +#define NEWHV5122 +#define PIN_DIN 16 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +#define PIN_CLK 17 // Clock +#define PIN_OE 21 // OutputEnable +#define MAXDIGITS 6 +int maxDigits = MAXDIGITS; +byte digitPins[MAXDIGITS + 1][10] = { ////Data pin numbers 100+ means: chip1 pins are used. Chip0's DOUT is connected to chip1's DIN + {126, 127, 128, 130, 124, 123, 132, 131, 129, 125}, //hour 10 , CHIP0 + {116, 117, 118, 120, 114, 113, 122, 121, 119, 115}, //hour 1 , CHIP0 + {104, 105, 106, 108, 102, 101, 110, 109, 107, 103}, //min 10 , CHIP0 + {26, 27, 28, 30, 24, 23, 32, 31, 29, 25}, //min 1 , CHIP1 + {16, 17, 18, 20, 14, 13, 22, 21, 19, 15}, //second 10 , CHIP1 + {4, 5, 6, 8, 2, 1, 10, 9, 7, 3}, //second 1 , CHIP1 + {0, 111, 112, 11, 12, 0, 0, 0, 0, 0} //extra GL dots //GL1,GL2,GL3,GL4 +}; +#define MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! #1..#4 positions are used, #0. and #5..#9. positions are zero! +//_______________________________________________________________________________________ +#define ALARMSPEAKER_PIN 23 //Alarm buzzer pin +//#define ALARMBUTTON_PIN 23 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define LIGHT_SENSOR_PIN 34 +#define RADAR_PIN 19 +#define RADAR_TIMEOUT 5 //min +//#define TUBE_POWER_PIN 4 +//#define TUBE_POWER_ON LOW +#define ENABLE_CLOCK_DISPLAY true +#define DATE_REPEAT_MIN 60 //show date only every xxx minute. If zero, datum is never displayed +#define DATE_START 07 +#define DATE_END 12 +#define TEMP_START 25 +#define TEMP_END 30 +#define HUMID_START 30 +#define HUMID_END 35 +#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 10 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 10 +#define AP_NAME "IN-18-Clock" +#define AP_PASSWORD "" +#define WEBNAME "Nixie IN-18 Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_53 //ESP8266 Wemos D1 mini,UNFI 2xHV5122 PCB version, 6xIN18 clock +#define DEBUG +#define FW "fw53" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 3 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 12 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 23 +//#define USE_DHT_TEMP +//#define DHTTYPE DHT11 +//#define TEMP_DHT_PIN 23 + +//_______________________________ HV5122 setup ____________________________________________________ +#define NEWHV5122 +#define PIN_DIN 5 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +#define PIN_CLK 0 // Clock +#define PIN_OE 4 // OutputEnable +#define MAXDIGITS 6 +int maxDigits = MAXDIGITS; +byte digitPins[MAXDIGITS + 1][10] = { ////Data pin numbers 100+ means: chip1 pins are used. Chip0's DOUT is connected to chip1's DIN + {2, 10, 9, 8, 7, 6, 5, 4, 3, 1}, //sec 1 , chip0 + {11, 32, 20, 19, 18, 17, 16, 15, 14, 13}, //sec 10 , chip0 + {22, 31, 29, 30, 27, 28, 25, 26, 23, 24}, //min 1 , chip0 + {101, 131, 110, 109, 108, 107, 106, 105, 104, 103}, //min 10 , chip1 + {111, 132, 120, 119, 118, 117, 116, 115, 114, 113}, //hour 1 , chip1 + {122, 129, 130, 127, 128, 125, 126, 123, 124, 121}, //hour 10 , chip1 + {0, 12, 21, 102, 112, 0, 0, 0, 0, 0} //extra decimal point (tube0...tube6) +}; +//#define MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! #1..#4 positions are used, #0. and #5..#9. positions are zero! +//__________________________________________________________________________________________________ +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 13 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define RADAR_PIN 4 +#define RADAR_TIMEOUT 5 //min +//#define TUBE_POWER_PIN 4 +#define TUBE_POWER_ON LOW +#define ENABLE_CLOCK_DISPLAY true +#define DATE_REPEAT_MIN 1 //show date only every xxx minute. If zero, datum is never displayed +//#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 10 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 10 +#define AP_NAME "UNFICLOCK" +#define AP_PASSWORD "" +#define WEBNAME "UNFI Nixie Clock HV5122" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_54 // D1 R32 ESP32 (Uno compatible), + NCS312 Nixie clock KIT!!! +#define DEBUG +#define FW "fw54" //firmware name +#define MAXBRIGHTNESS 100 +#define DOUBLE_BLINK +//#define USE_NEOPIXEL +#define NEOPIXEL_PIN 22 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 14 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 23 +//#define PIN_SDA 4 // you can set the used SDA and SCL pins +//#define PIN_SCL 32 // if it is not default value +//#define USE_DHT_TEMP +//#define DHTTYPE DHT11 +//#define TEMP_DHT_PIN 23 +//#define USE_BME280 //I2C Temperature + humidity + pressure +//#define USE_BMP280 //I2C Temperature + barometric pressure +//#define USE_AHTX0 //I2C Temperature + humidity +//#define USE_SHT21 //I2C Temperature + humidity +#define NEWHV5122 +#define PIN_DIN 23 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +#define PIN_CLK 18 // Clock +#define PIN_OE 5 // OutputEnable +#define MAXDIGITS 6 +int maxDigits = MAXDIGITS; +byte digitPins[MAXDIGITS + 1][10] = { ////Data pin numbers 100+ means: chip1 pins are used. Chip0's DOUT is connected to chip1's DIN + {121, 122, 123, 124, 125, 126, 127, 128, 129, 130}, //sec 1 , chip1 + {111, 112, 113, 114, 115, 116, 117, 118, 119, 120}, //sec 10 , chip1 + {101, 102, 103, 104, 105, 106, 107, 108, 109, 110}, //min 1 , chip1 + {21, 22, 23, 24, 25, 26, 27, 28, 29, 30}, //min 10 , chip0 + {11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, //hour 1 , chip0 + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, //hour 10 , chip0 + {0, 131, 132, 31, 32, 0, 0, 0, 0, 0} //extra GL dots +}; +#define MAKE_BLINKING_DOTS +#define USE_PWMLEDS +#define PWM1_PIN 27 //Uno: D6 +#define PWM2_PIN 13 //Uno: D9 +#define PWM3_PIN 25 //Uno: D3 +//#define ALARMSPEAKER_PIN 26 //Alarm buzzer pin +//#define ALARMBUTTON_PIN 2 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define RADAR_PIN 4 +//#define RADAR_TIMEOUT 5 //min +//#define TUBE_POWER_PIN 16 +#define TUBE_POWER_ON LOW +#define ENABLE_CLOCK_DISPLAY true +#define TEMP_CHARCODE 10 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 10 +#define AP_NAME "UNICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "NCS312 Nixie Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_55 //ESP8266 D1 Mini,UNFI 2xHV5122 PCB version, 6xZ573M clock +#define DEBUG +#define FW "fw55" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 3 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 12 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 23 +//#define PIN_SDA 4 // you can set the used SDA and SCL pins +//#define PIN_SCL 32 // if it is not default value +//#define USE_DHT_TEMP +//#define DHTTYPE DHT11 +//#define TEMP_DHT_PIN 23 +//#define USE_BME280 //I2C Temperature + humidity + pressure +//#define USE_BMP280 //I2C Temperature + barometric pressure +//#define USE_AHTX0 //I2C Temperature + humidity +//#define USE_SHT21 //I2C Temperature + humidity +//#define USE_BH1750 //I2C luxmeter sensor +//_______________________________ HV5122 setup ____________________________________________________ +#define NEWHV5122 +#define PIN_DIN 5 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +#define PIN_CLK 0 // Clock +#define PIN_OE 4 // OutputEnable +#define MAXDIGITS 6 +int maxDigits = MAXDIGITS; +byte digitPins[MAXDIGITS + 1][10] = { ////Data pin numbers 100+ means: chip1 pins are used. Chip0's DOUT is connected to chip1's DIN + {2, 10, 9, 8, 7, 6, 5, 4, 3, 1}, //sec 1 , chip0 (tube#0) + {11, 32, 20, 19, 18, 17, 16, 15, 14, 13}, //sec 10 , chip0 (tube#1) + {22, 31, 29, 30, 27, 28, 25, 26, 23, 24}, //min 1 , chip0 (tube#2) + {101, 131, 110, 109, 108, 107, 106, 105, 104, 103}, //min 10 , chip1 (tube#3) + {111, 132, 120, 119, 118, 117, 116, 115, 114, 113}, //hour 1 , chip1 (tube#4) + {122, 129, 130, 127, 128, 125, 126, 123, 124, 121}, //hour 10 , chip1 (tube#5) + {0, 12, 21, 102, 112, 0, 0, 0, 0, 0} //extra decimalPoint/blinking dots (tube0...tube6) +}; +//#define MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! #1..#4 positions are used +//___________________________________________________________________________________ +#define ALARMSPEAKER_PIN 2 //Alarm buzzer pin +#define ALARMBUTTON_PIN 13 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define RADAR_PIN 4 +#define RADAR_TIMEOUT 5 //5min +//#define TUBE_POWER_PIN 4 +#define TUBE_POWER_ON LOW +#define ENABLE_CLOCK_DISPLAY true +//#define DATE_REPEAT_MIN 1 //show date only every xxx minute. If zero, datum is never displayed +//#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 10 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 10 +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "" +#define WEBNAME "UNFI Nixie Clock HV5122" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_56 //WROOM ESP32, UNFI 2xHV5122 PCB version, 6xZ573M clock Example clock!!! +#define DEBUG +#define FW "fw56" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 26 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 23 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 18 +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +//#define USE_DHT_TEMP +//#define DHTTYPE DHT11 +//#define TEMP_DHT_PIN 23 +//#define USE_BME280 //I2C Temperature + humidity + pressure +//#define USE_BMP280 //I2C Temperature + barometric pressure +//#define USE_AHTX0 //I2C Temperature + humidity +//#define USE_SHT21 //I2C Temperature + humidity +//#define USE_BH1750 //I2C luxmeter sensor +//_______________________________ HV5122 setup ____________________________________________________ +#define NEWHV5122 +#define PIN_DIN 14 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +#define PIN_CLK 12 // Clock +#define PIN_OE 27 // OutputEnable +#define MAXDIGITS 6 +int maxDigits = MAXDIGITS; +byte digitPins[MAXDIGITS + 1][10] = { + {129, 128, 127, 126, 125, 124, 123, 132, 131, 130}, //sec 1 , chip1 (tube#0) + {118, 117, 116, 115, 114, 113, 112, 122, 121, 119}, //sec 10 , chip1 (tube#1) + {107, 106, 105, 104, 103, 102, 101, 111, 110, 108}, //min 1 , chip1 (tube#2) + {28, 27, 26, 25, 24, 23, 22, 32, 31, 29}, //min 10 , chip0 (tube#3) + {17, 16, 15, 14, 13, 12, 11, 21, 20, 18}, //hour 10 , chip0 (tube#4) + {7, 6, 5, 4, 3, 2, 1, 10, 9, 8}, //hour 1 , chip0 (tube#5) + {0, 0, 109, 30, 19, 0, 0, 0, 0, 0} //extra decimalPoint/blinking dots (tube0...tube6) +}; +//#define MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! #1..#4 positions are used +//___________________________________________________________________________________ +#define ALARMSPEAKER_PIN 32 //Alarm buzzer pin +#define ALARMBUTTON_PIN 33 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define RADAR_PIN 4 +#define RADAR_TIMEOUT 5 //min +//#define TUBE_POWER_PIN 4 +#define TUBE_POWER_ON HIGH +#define ENABLE_CLOCK_DISPLAY true +#define DATE_REPEAT_MIN 1 //show date only every xxx minute. If zero, datum is never displayed +//#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 10 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 10 +#define AP_NAME "UNICLOCK32" +#define AP_PASSWORD "uniclock" +#define WEBNAME "UNFI Nixie-Clock HV5122" +#define DEFAULT_SSID "Unferdorben_2" +#define DEFAULT_PSW "4073404540734" +#define USE_WIFIMANAGER +#define DISABLE_NIGHT_ANIMATION //in night mode, no animation allowed +#endif + +#ifdef CLOCK_57 //WROOM ESP32, UNFI 2xHV5122 PCB version, 6xZM1000 clock PLCC44 socket! +#define DEBUG +#define FW "fw57" //firmware name +#define MAXBRIGHTNESS 100 +#define FACTORYRESET_PIN 25 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 26 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 23 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 18 +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +//#define USE_DHT_TEMP +//#define DHTTYPE DHT11 +//#define TEMP_DHT_PIN 23 +//#define USE_BME280 //I2C Temperature + humidity + pressure +//#define USE_BMP280 //I2C Temperature + barometric pressure +//#define USE_AHTX0 //I2C Temperature + humidity +//#define USE_SHT21 //I2C Temperature + humidity +//#define USE_BH1750 //I2C luxmeter sensor +//#define USE_MQTT +//_______________________________ HV5122 setup ____________________________________________________ +#define NEWHV5122 +#define PIN_DIN 14 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +#define PIN_CLK 13 // Clock +#define PIN_OE 27 // OutputEnable +#define MAXDIGITS 6 +int maxDigits = MAXDIGITS; +byte digitPins[MAXDIGITS + 1][10] = { + {130, 128, 126, 124, 123, 125, 127, 129, 131, 132}, //sec 1 , chip1 (tube#0) 0-1-2-3-4-5-6-7-8-9 + {120, 118, 116, 114, 113, 115, 117, 119, 121, 122}, //sec 10 , chip1 (tube#1) + {109, 107, 105, 103, 102, 104, 106, 108, 110, 111}, //min 1 , chip1 (tube#2) + {30, 28, 26, 24, 23, 25, 27, 29, 31, 32}, //min 10 , chip0 (tube#3) + {19, 17, 15, 13, 12, 14, 16, 18, 20, 21}, //hour 10 , chip0 (tube#4) + {8, 6, 4, 2, 1, 3, 5, 7, 9, 10}, //hour 1 , chip0 (tube#5) + {0, 112, 101, 22, 11, 0} //Z574M {0,101,112,11,22,0}blinking dots (tube0...tube6)Z573M {0,112,101,22,11,0} +}; +//#define MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! #1..#4 positions are used +//___________________________________________________________________________________ +#define ALARMSPEAKER_PIN 19 //Alarm buzzer pin +#define ALARMBUTTON_PIN 33 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define RADAR_PIN 4 +#define RADAR_TIMEOUT 5 //min +//#define TUBE_POWER_PIN 4 +#define TUBE_POWER_ON HIGH +//#define ENABLE_CLOCK_DISPLAY true +//#define DATE_REPEAT_MIN 1 //show date only every xxx minute. If zero, datum is never displayed +//#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +//#define TEMP_CHARCODE 10 +//#define GRAD_CHARCODE 10 +//#define PERCENT_CHARCODE 10 +#define AP_NAME "UNFICLOCK32" +#define AP_PASSWORD "uniclock" +#define WEBNAME " Nixie-Clock" +#define DEFAULT_SSID "" +#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +#endif + +#ifdef CLOCK_57A //WROOM ESP32, UNFI 2xHV5222 PCB version, 6xZ57xM clock Example clock!!! +#define DEBUG +#define FW "fw57A" //firmware name +#define MAXBRIGHTNESS 100 +#define USE_NEOPIXEL +#define NEOPIXEL_PIN 26 +byte tubePixels[] = {0, 1, 2, 3, 4, 5}; //6 tubes, single leds +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 23 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 18 +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +//#define USE_DHT_TEMP +//#define DHTTYPE DHT11 +//#define TEMP_DHT_PIN 23 +//#define USE_BME280 //I2C Temperature + humidity + pressure +//#define USE_BMP280 //I2C Temperature + barometric pressure +//#define USE_AHTX0 //I2C Temperature + humidity +//#define USE_SHT21 //I2C Temperature + humidity +//#define USE_BH1750 //I2C luxmeter sensor +//#define USE_MQTT +//_______________________________ HV5122 setup ____________________________________________________ +#define NEWHV5122 +#define PIN_DIN 14 // DataIn - chip0 DOUT pin is connected to chip1 DIN pin! +#define PIN_CLK 13 // Clock +#define PIN_OE 27 // OutputEnable +#define MAXDIGITS 6 +int maxDigits = MAXDIGITS; +byte digitPins[MAXDIGITS + 1][10] = { + {103, 105, 107, 109, 110, 108, 106, 104, 102, 101}, //sec 1 , chip1 (tube#0) 0-1-2-3-4-5-6-7-8-9 + {113, 115, 117, 119, 120, 118, 116, 114, 112, 111}, //sec 10 , chip1 (tube#1) + {124, 126, 128, 130, 131, 129, 127, 125, 123, 122}, //min 1 , chip1 (tube#2) + {3, 5, 7, 9, 10, 8, 6, 4, 2, 1}, //min 10 , chip0 (tube#3) + {14, 16, 18, 20, 21, 19, 17, 15, 13, 12}, //hour 10 , chip0 (tube#4) + {25, 27, 29, 31, 32, 30, 28, 26, 24, 23}, //hour 1 , chip0 (tube#5) + {0, 121, 132, 11, 22, 0, 0, 0, 0} //extra decimalPoint/blinking dots (tube0...tube6) +}; +//#define MAKE_BLINKING_DOTS //it means, the extra datapins are used as 4 blinking dot instead of decimal points! #1..#4 positions are used +//___________________________________________________________________________________ +#define ALARMSPEAKER_PIN 19 //Alarm buzzer pin +#define ALARMBUTTON_PIN 33 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +//#define RADAR_PIN 4 +#define RADAR_TIMEOUT 5 //min +//#define TUBE_POWER_PIN 4 +#define TUBE_POWER_ON HIGH +//#define ENABLE_CLOCK_DISPLAY true +//#define DATE_REPEAT_MIN 1 //show date only every xxx minute. If zero, datum is never displayed +//#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +//#define TEMP_CHARCODE 10 +//#define GRAD_CHARCODE 10 +//#define PERCENT_CHARCODE 10 +#define AP_NAME "UNFICLOCK" +#define AP_PASSWORD "uniclock" +#define WEBNAME "ZM1336 Nixie-Clock" +#define DEFAULT_SSID "" +#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +#endif + +#ifdef CLOCK_70 //1 tube esp8266 Nixie Clock by UNFI +#define DEBUG +#define FW "fw70" //firmware name +#define MAXBRIGHTNESS 100 +#define TUBE1CLOCK +byte ABCDPins[4] = {2, 4, 5, 15}; +#define AP_NAME "1TUBE_CLOCK" +#define AP_PASSWORD "" +#define WEBNAME "1TUBE Nixie Clock" +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_71 //2x VQC10 clock by UNFI +#define DEBUG +#define PIN_FLD_BUTTON 12 +#define PIN_SET_BUTTON 13 +//#define USE_GPS +#define USE_RTC //no RTC exist, but the push buttons are defined here, too +#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 15 //PIN16 not working!!! +#define FW "fw71" //firmware name +#define MAXBRIGHTNESS 100 +#define VQC10 +#define latchPin 5 //D2 RCK +#define clockPin 4 //D1 SCK +#define dataPin 2 //D4 DIN +#define ledOffpin 0 //D5 +#define D5pin 14 +#define AP_NAME "VQC10_LED_CLOCK" +#define AP_PASSWORD "" +#define WEBNAME "VQC10 LED Clock" +//#define DEFAULT_SSID "xxx" //factory reset default wifi ssid/psw +//#define DEFAULT_PSW "yyy" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +// *** This is the CURRENTLY ACTIVE clock (CLOCK_72 is the one un-commented in the list at the top of +// this file). See ZM1500.ino for the driver. *** +#ifdef CLOCK_72 //ESP32, UNFI board, ZM1500 +#define DEBUG +#define ANIMSPEED 50 +#define FW "fw72" //firmware name +#define MAXBRIGHTNESS 255 +//#define SIMULATE_TEMPERATURE_SENSOR +//#define USE_DALLAS_TEMP +//#define USE_DHT_TEMP +//#define DHTTYPE DHT22 +//#define TEMP_DHT_PIN 27 +#define TEMP_DALLAS_PIN 27 //Dallas temp sensor pin. If not used, SET TO -1 +#define ZM1500_ESP32 +byte digitEnablePins[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; //{11,10,9,8,7,6,5,4,3,2,1,0}; //12 tubes selection values +byte segmentEnablePins[] = {7, 6, 5, 4, 3, 2, 1, 0}; +#define dataPin 23 //D5 +#define latchPin 18 //D6 +#define clkPin 19 //D7 +//#define TEMP_CHARCODE -1 //disable char => shift display right with 1 digit +//#define GRAD_CHARCODE -1 //disable char => shift display right with 1 digit +#define AP_NAME "ZM1500_CLOCK" +#define AP_PASSWORD "" +#define WEBNAME "ZM1500-Clock" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +#endif + +#ifdef CLOCK_80 //word clock, english version +#define DEBUG +//#define SIMULATE_TEMPERATURE_SENSOR //for test only!!! +#define NEOPIXEL_PIN 4 +//#define PIN_FLD_BUTTON 27 +//#define PIN_SET_BUTTON 14 +#define LED1_PIN 25 +#define LED2_PIN 26 +#define LIGHT_SENSOR_PIN 35 +#define RADAR_PIN 16 +#define RADAR_TIMEOUT 5 //min +//#define USE_GPS +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +#define USE_RTC //RTC + push buttons are defined here +#define USE_BME280 //I2C Temperature + humidity + pressure +#define USE_AHTX0 //I2C Temperature + humidity +#define USE_SHT21 //I2C Temperature + humidity +#define FW "fw80" //firmware name +#define START_CORNER LEFTUP //RIGHTUP, LEFTUP, RIGHTDOWN, LEFTDOWN : starting corner of led stripe +#define MAXBRIGHTNESS 255 //maximum led brightness, 100..255 +#define MAXIMUM_LUX 40 +#define WORDCLOCK +#define AP_NAME "WORD_CLOCK" +#define AP_PASSWORD "" +#define WEBNAME "WordClock" +#define MDNSNAME "wordclock" +//#define DEFAULT_SSID "xxx" //factory reset default wifi ssid/psw +//#define DEFAULT_PSW "yyy" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +// +//----- WORD CLOCK TEXT DEFINITIONS ----------------- +#define WORD_LENGTH 15 //maximum length of any word +struct wordDef { + char txt[WORD_LENGTH + 1]; //readable word + int startX; //led X positions + int startY; //led Y position + int len; //number of characters +}; +#define XMAX 11 //Clock dimensions +#define YMAX 11 +int extraDots[4] = {XMAX*YMAX + 3, XMAX*YMAX + 2, XMAX*YMAX + 1, XMAX * YMAX}; //extra dots led numbers +#define WEEKDAY_POSX 1 //start of weekdays +#define WEEKDAY_POSY 11 //start of weekdays +#define SENSOR_POSX 8 //start position of extra dots +#define SENSOR_POSY 11 + +const char matrix[YMAX][XMAX + 1] = { + {"itLisASwifi"}, + {"quarterXset"}, + {"twentyfiveX"}, + {"halfBtenFto"}, + {"pastERUNINE"}, + {"ONESIXTHREE"}, + {"FOURFIVETWO"}, + {"EIGHTELEVEN"}, + {"SEVENTWELVE"}, + {"TENSEoclock"}, + {"SMTWTFSCF%W"} +}; + +const char sequence[12][40] = { //#1 = actual hour, #2 = actual hour + 1 + "it is #1 oclock", + "it is five past #1", + "it is ten past #1", + "it is quarter past #1", + "it is twenty past #1", + "it is twenty five past #1", + "it is half past #1", + "it is twenty five to #2", + "it is twenty to #2", + "it is quarter to #2", + "it is ten to #2", + "it is five to #2" +}; + +struct wordDef hours[] { + {"O"}, {"ONE"}, {"TWO"}, {"THREE"}, {"FOUR"}, {"FIVE"}, {"SIX"}, {"SEVEN"}, {"EIGHT"}, {"NINE"}, {"TEN"}, {"ELEVEN"}, {"TWELVE"}, +}; + +struct wordDef words[] { + {"it"}, {"is"}, {"wifi"}, {"ip"}, {"twenty"}, {"five"}, {"ten"}, {"quarter"}, {"half"}, {"to"}, {"past"}, {"oclock"}, {"SMTWTFS"}, {"CF%W"} +}; +#endif + +#ifdef CLOCK_81 //word clock, german version +#define DEBUG +#define NEOPIXEL_PIN 4 +//#define PIN_FLD_BUTTON 27 +//#define PIN_SET_BUTTON 14 +#define LED1_PIN 25 +#define LED2_PIN 26 +#define LIGHT_SENSOR_PIN 35 +#define RADAR_PIN 16 +#define RADAR_TIMEOUT 5 //min +//#define USE_GPS +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +#define USE_RTC //RTC + push buttons are defined here +#define USE_BME280 //I2C Temperature + humidity + pressure +#define USE_AHTX0 //I2C Temperature + humidity +#define USE_SHT21 //I2C Temperature + humidity +#define FW "fw81" //firmware name +#define START_CORNER RIGHTUP //RIGHTUP, LEFTUP, RIGHTDOWN, LEFTDOWN : starting corner of led stripe +#define MAXBRIGHTNESS 255 //led maximum brightness 100...255 +#define MAXIMUM_LUX 40 +#define WORDCLOCK +#define AP_NAME "WORD_CLOCK" +#define AP_PASSWORD "" +#define WEBNAME "wordclock" +//#define DEFAULT_SSID "xxx" //factory reset default wifi ssid/psw +//#define DEFAULT_PSW "yyy" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +// +//----- WORD CLOCK TEXT DEFINITIONS ----------------- +#define WORD_LENGTH 15 //maximum length of any word +struct wordDef { + char txt[WORD_LENGTH + 1]; //readable word + int startX; //led X positions + int startY; //led Y position + int len; //number of characters +}; + +#define XMAX 11 //Clock dimensions +#define YMAX 11 +int extraDots[4] = {XMAX*YMAX + 3, XMAX*YMAX + 2, XMAX*YMAX + 1, XMAX * YMAX}; //extra dots led numbers +#define WEEKDAY_POSX 1 //start of weekdays +#define WEEKDAY_POSY 11 //start of weekdays +#define SENSOR_POSX 8 //start position of extra dots +#define SENSOR_POSY 11 + +const char matrix[YMAX][XMAX + 1] = { + {"esListAwifi"}, + {"zehnzwanzig"}, + {"dreiviertel"}, + {"funfvornach"}, + {"halbOELFUNF"}, + {"EINSXAMZWEI"}, + {"DREIAUJVIER"}, + {"SECHSNLACHT"}, + {"SIEBENZWOLF"}, + {"ZEHNEUNKuhr"}, + {"SMTWTFSCF%W"} +}; + +const char sequence[12][40] = { //#1 = actual hour, #2 = actual hour + 1 + "es ist #1 uhr", + "es ist funf nach #1", + "es ist zehn nach #1", + "es ist viertel #2", + "es ist zehn vor halb #2", + "es ist funf vor halb #2", + "es ist halb #2", + "es ist funf nach halb #2", + "es ist zehn nach halb #2", + "es ist drei viertel #2", + "es ist zehn vor #2", + "es ist funf vor #2" +}; + +struct wordDef hours[] { + {"O"}, {"EIN"}, {"ZWEI"}, {"DREI"}, {"FIER"}, {"FUNF"}, {"SECHS"}, {"SIEBEN"}, {"ACHT"}, {"NEUN"}, {"ZEHN"}, {"ELF"}, {"ZWOLF"}, +}; + +struct wordDef words[] { + {"es"}, {"ist"}, {"zwanzig"}, {"funf"}, {"zehn"}, {"drei"}, {"viertel"}, {"halb"}, {"vor"}, {"nach"}, {"uhr"}, {"SMDMDFS"}, {"CF%W"}, {"wifi"} +}; +#endif + +#ifdef CLOCK_82 //word clock, hungarian version +#define DEBUG +#define NEOPIXEL_PIN 4 +//#define PIN_FLD_BUTTON 27 +//#define PIN_SET_BUTTON 14 +#define LED1_PIN 25 +#define LED2_PIN 26 +#define LIGHT_SENSOR_PIN 35 +#define RADAR_PIN 16 +#define RADAR_TIMEOUT 5 //min +//#define USE_GPS +#define PIN_SDA 21 // you can set the used SDA and SCL pins +#define PIN_SCL 22 // if it is not default value +#define USE_RTC //RTC + push buttons are defined here +#define USE_BME280 //I2C Temperature + humidity + pressure +#define USE_AHTX0 //I2C Temperature + humidity +#define USE_SHT21 //I2C Temperature + humidity +#define FW "fw82" //firmware name +#define START_CORNER RIGHTUP //RIGHTUP, LEFTUP, RIGHTDOWN, LEFTDOWN : starting corner of led stripe +#define MAXBRIGHTNESS 255 //led max brightness 100...255 +#define MAXIMUM_LUX 40 +#define WORDCLOCK +#define AP_NAME "WORD_CLOCK" +#define AP_PASSWORD "" +#define WEBNAME "wordclock" +//#define DEFAULT_SSID "xxx" //factory reset default wifi ssid/psw +//#define DEFAULT_PSW "yyy" +//#define USE_WIFIMANAGER +//#define DISABLE_NIGHT_ANIMATION +// +//----- WORD CLOCK TEXT DEFINITIONS ----------------- +#define WORD_LENGTH 15 //maximum length of any word +struct wordDef { + char txt[WORD_LENGTH + 1]; //readable word + int startX; //led X positions + int startY; //led Y position + int len; //number of characters +}; + +#define XMAX 11 //Clock dimensions +#define YMAX 11 +int extraDots[4] = {XMAX*YMAX + 3, XMAX*YMAX + 2, XMAX*YMAX + 1, XMAX * YMAX}; //extra dots led numbers +#define WEEKDAY_POSX 1 //start of weekdays +#define WEEKDAY_POSY 11 //start of weekdays +#define SENSOR_POSX 8 //start position of extra dots +#define SENSOR_POSY 11 + +const char matrix[YMAX][XMAX + 1] = { + {"otBtizJwifi"}, + {"perccelEWip"}, + {"multmulvaQZ"}, + {"haromUXfelJ"}, + {"negyedXHHAT"}, + {"NEGYAHAROMS"}, + {"NYOLCOTZHET"}, + {"TIZEN__1EBC"}, + {"TIZENKETTOA"}, + {"KILENCForaP"}, + {"SMTWTFSCF%W"} +}; + +const char sequence[12][40] = { //#1 = actual hour, #2 = actual hour + 1 + "#1 ora", + "ot perccel mult #1 ora", + "tiz perccel mult #1 ora", + "negyed #2", + "tiz perc mulva fel #2", + "ot perc mulva fel #2", + "fel #2", + "ot perccel mult fel #2", + "tiz perccel mult fel #2", + "harom negyed #2", + "tiz perc mulva #2 ora", + "ot perc mulva #2 ora" +}; + +struct wordDef hours[] { + {"O"}, {"__1"}, {"KETTO"}, {"HAROM"}, {"NEGY"}, {"OT"}, {"HAT"}, {"HET"}, {"NYOLC"}, {"KILENC"}, {"TIZ"}, {"TIZEN__1"}, {"TIZENKETTO"}, +}; + +struct wordDef words[] { + {"ora"}, {"ot"}, {"tiz"}, {"harom"}, {"negyed"}, {"fel"}, {"perc"}, {"perccel"}, {"mulva"}, {"mult"}, {"wifi"}, {"ip"}, {"VHKSCPS"} +}; +#endif + +#ifdef CLOCK_99 //Dummy clock, sensor box with MQTT connector +#define DEBUG +#define FW "fw99" //firmware name +#define MAXBRIGHTNESS 100 +//#define USE_DALLAS_TEMP +#define TEMP_DALLAS_PIN 26 //Dallas temp sensor pin. If not used, SET TO -1 +//#define LIGHT_SENSOR_PIN 23 +#define PIN_SDA 4 // you can set the used SDA and SCL pins +#define PIN_SCL 32 // if it is not default value +#define USE_DHT_TEMP +#define DHTTYPE DHT11 +#define TEMP_DHT_PIN 26 +//#define USE_RTC +//#define USE_GPS +#define USE_BME280 //I2C Temperature + humidity + pressure +#define USE_BMP280 //I2C Temperature + barometric pressure +#define USE_AHTX0 //I2C Temperature + humidity +#define USE_SHT21 //I2C Temperature + humidity +#define USE_BH1750 //I2C luxmeter sensor +#define USE_MQTT +//#define USE_MASTER_CLOCK //if used, this will be the sensor#0 +//#define USE_MASTER_TEMP +//#define USE_MASTER_HUMID +//#define MASTER_TEMPERATURE_TOPIC "homeassistant/sensor/10521c5e14c4/temperature/state" +//#define MASTER_HUMIDITY_TOPIC "homeassistant/sensor/10521c5e14c4/humidity/state" +//#define MASTER_RADAR_TOPIC "homeassistant/sensor/10521c5e14c4/radar/state" +//_______________________________ Driver setup ____________________________________________________ +#define DUMMY +//___________________________________________________________________________________ +#define ALARMSPEAKER_PIN 16 //Alarm buzzer pin +//#define ALARMBUTTON_PIN 23 //Alarm switch off button pin +#define ALARM_ON HIGH //How to switch ON alarm buzzer +#define RADAR_PIN 22 +#define RADAR_TIMEOUT 5 //min +#define ENABLE_CLOCK_DISPLAY true +#define DATE_REPEAT_MIN 3 //show date only every xxx minute. If zero, datum is never displayed +//#define SHIFT_TUBES_LEFT_BY_1 //shift left by 1 tube the display, if a thermometer is used with spec tube +#define TEMP_CHARCODE 10 +#define GRAD_CHARCODE 10 +#define PERCENT_CHARCODE 10 +#define AP_NAME "SENSORBOX" +#define AP_PASSWORD "" +#define WEBNAME "SENSORBOX" +//#define DEFAULT_SSID "" +//#define DEFAULT_PSW "" +//#define USE_WIFIMANAGER +#endif + + + +//__________________________________________________________________________________________________________________________ +//if not defined, here is defined as NOT USED +// +// FALLBACK DEFAULTS SECTION: every "#ifndef X / #define X " pair below fires only if the +// active CLOCK_NN block above did NOT already define that macro. This is what keeps each CLOCK_NN +// block short (it only needs to override what's actually different from these defaults). Pin +// defaults are -1, which the core .ino and driver files everywhere treat as "not connected / feature +// disabled" (e.g. "#if RADAR_PIN >= 0" guards). If you add a new optional parameter to clocks.h, +// add its fallback here too so old CLOCK_NN blocks that predate it keep compiling correctly. +#ifndef COLON_PIN +#define COLON_PIN -1 //Blinking Colon pin. +#endif + +#ifndef TEMP_DALLAS_PIN +#define TEMP_DALLAS_PIN -1 //Dallas temp sensor pin. +#endif + +#ifndef TEMP_DHT_PIN +#define TEMP_DHT_PIN -1 //DHT temp sensor pin. +#endif + +#if defined(USE_BME280) || defined(USE_BMP280) || defined(USE_AHTX0) || defined(USE_SHT21) || defined(USE_BH1750) || defined(USE_RTC) +#if defined(PIN_SDA) && defined(PIN_SCL) +#define USE_I2CSENSORS +#else +#warning "PIN_SDA or PIN_SCL is not defined!" +#endif +#endif + +#ifndef LED_SWITCH_PIN +#define LED_SWITCH_PIN -1 //external led backlight ON/OFF. +#endif + +#ifndef DECIMALPOINT_PIN +#define DECIMALPOINT_PIN -1 //Nixie decimal point between digits. +#endif + +#ifndef ALARMSPEAKER_PIN +#define ALARMSPEAKER_PIN -1 //Alarm buzzer pin +#endif +#ifndef ALARMBUTTON_PIN +#define ALARMBUTTON_PIN -1 //Alarm switch off button pin +#endif + +#ifdef USE_NEOPIXEL +#ifndef NEOPIXEL_PIN +#define NEOPIXEL_PIN 3 +#endif +#endif +#ifndef RGB_MIN_BRIGHTNESS +#define RGB_MIN_BRIGHTNESS 8 //Neopixel leds minimum brightness +#endif +#ifndef RGB_MAX_BRIGHTNESS +#define RGB_MAX_BRIGHTNESS 255 //Neopixel leds maximum brightness +#endif + +#ifndef TUBE_POWER_PIN +#define TUBE_POWER_PIN -1 //Tube filament or HV ON/OFF +#endif +// POSSIBLE BUG: this guard checks "#ifndef TUBE_POWER_PIN" again (always false here, since the block +// right above just defined it unconditionally) instead of "#ifndef TUBE_POWER_ON" - so this fallback +// for TUBE_POWER_ON never actually fires. In practice this is harmless today: every CLOCK_NN block +// that leaves TUBE_POWER_PIN undefined (i.e. feature disabled, defaults to -1 above) either also +// defines TUBE_POWER_ON itself anyway, or never references it (all TUBE_POWER_ON usage in the core +// .ino is gated behind "#if TUBE_POWER_PIN >= 0"). But if a future CLOCK_NN block used TUBE_POWER_PIN +// without also setting TUBE_POWER_ON, this would leave TUBE_POWER_ON completely undefined and fail +// to compile at the digitalWrite(TUBE_POWER_PIN, TUBE_POWER_ON) call site in ESP32_UniClock2.ino. +#ifndef TUBE_POWER_PIN +#define TUBE_POWER_ON LOW //Tube Power On is HIGH or LOW? +#endif + +#ifndef RADAR_PIN +#define RADAR_PIN -1 //Radar sensor for automatic ON/OFF the TUBE_POWER +#endif + +#ifndef LIGHT_SENSOR_PIN +#define LIGHT_SENSOR_PIN -1 ///Environment light sensor, analog input pin +#endif + +#ifndef RADAR_TIMEOUT +#define RADAR_TIMEOUT 5 //Automatic switch off tubes (without radar detecting somebody) after xxx min +#endif + +#ifndef ALARM_ON +#define ALARM_ON HIGH +#endif + +#ifndef MAXBRIGHTNESS +#define MAXBRIGHTNESS 10 //do not change this value +#endif + +//Display temperature and date in every minute between START..END seconds +#ifndef ENABLE_CLOCK_DISPLAY +#define ENABLE_CLOCK_DISPLAY true //false, if no clock display is needed (for example: thermometer + humidity only) +#endif + +#ifndef TEMP_START +#define TEMP_START 35 +#endif + +#ifndef TEMP_END +#define TEMP_END 40 +#endif + +#ifndef HUMID_START +#define HUMID_START 40 +#endif + +#ifndef HUMID_END +#define HUMID_END 45 +#endif + +#ifndef DATE_START +#define DATE_START 45 +#endif + +#ifndef DATE_END +#define DATE_END 50 +#endif + +#ifndef ANIMSPEED +#define ANIMSPEED 50 //Animation speed in millisec +#endif + +#ifndef TEMP_CHARCODE +#define TEMP_CHARCODE 15 +#endif + +#ifndef GRAD_CHARCODE +#define GRAD_CHARCODE 16 +#endif + +#ifndef PERCENT_CHARCODE +#define PERCENT_CHARCODE 17 +#endif + +#ifndef UPPER_CIRCLE_CHARCODE +#define UPPER_CIRCLE_CHARCODE 16 +#endif + +#ifndef LOWER_CIRCLE_CHARCODE +#define LOWER_CIRCLE_CHARCODE 18 +#endif + + +#ifndef LEFTDECIMAL +#define LEFTDECIMAL false //set true (Z574M), if decimal point is on the left side on the tube. Else set false (Z573M)! +#endif + +//______________________________________________________________________________________ +//--------------- LDR LIGHT_SENSOR LUXMETER parameters ------------ +#ifndef LIGHT_SENSOR_PIN +#define LIGHT_SENSOR_PIN -1 +#endif +#if defined(ESP32) +#define MAX_ADC_READING 4095 //ESP32 ADC_REF_VOLTAGE = 3.3V !! +#else +#define MAX_ADC_READING 1023 //8266 ADC_REF_VOLTAGE = 1.0V !! +#endif +#ifndef REF_RESISTANCE +#define REF_RESISTANCE 10000.0 // Resistor value is 10k, between LDR sensor and GND +#endif +#ifndef LUX_CALC_SCALAR +#define LUX_CALC_SCALAR 12518931 * 1.2 //Calibrate here +#endif +#ifndef LUX_CALC_EXPONENT +#define LUX_CALC_EXPONENT -1.405 +#endif +#ifndef MAXIMUM_LUX +#define MAXIMUM_LUX 100 +#endif +#ifndef PWM1_PIN +#define PWM1_PIN -1 +#endif +#ifndef PWM2_PIN +#define PWM2_PIN -1 +#endif +#ifndef PWM3_PIN +#define PWM3_PIN -1 +#endif +#ifndef DATE_REPEAT_MIN +#define DATE_REPEAT_MIN 1 +#endif +#ifndef FW +#define FW "UNKNOWN" +#endif +#ifndef DP_PIN +#define DP_PIN -1 //Decimal Point pin. +#endif +//______________________________________________________________________________________ diff --git a/firmware/uniclock/ESP32_UniClock2/dallas_temp.ino b/firmware/uniclock/ESP32_UniClock2/dallas_temp.ino new file mode 100644 index 0000000..1dd2358 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/dallas_temp.ino @@ -0,0 +1,222 @@ +// ================================================================================================= +// dallas_temp.ino - DS18B20 (1-Wire) digital temperature sensor driver +// ------------------------------------------------------------------------------------------------- +// Uses the OneWire + DallasTemperature libraries (libraries/OneWire.zip, libraries/DallasTemperature.zip) +// to talk to one or two DS18B20 sensors sharing a SINGLE 1-Wire data bus on pin TEMP_DALLAS_PIN +// (a 4.7k pull-up resistor between DATA and VCC is required on the bus, as usual for 1-Wire). +// Enabled per-clock via USE_DALLAS_TEMP in clocks.h; the pin itself is set via TEMP_DALLAS_PIN. +// Used (as of this writing) by CLOCK_3, CLOCK_5, CLOCK_6A, CLOCK_7, CLOCK_12, CLOCK_14, CLOCK_15, +// CLOCK_15a, CLOCK_16, CLOCK_20, CLOCK_26, CLOCK_30, CLOCK_31, CLOCK_35, CLOCK_40..CLOCK_49 (Nixie/VFD +// UNFI board revisions), CLOCK_51, CLOCK_53..CLOCK_57A and CLOCK_71 - see clocks.h for the exact list. +// +// Because a DS18B20 conversion takes up to ~750ms, readings are NOT taken with a blocking call. +// Instead a request/poll pattern is used: requestDallasTemp() kicks off a conversion, and getTemp() +// is called repeatedly (every ~200ms from timeProgram() in the core .ino) until DS_delay has elapsed, +// at which point the converted value is actually fetched from the sensor. See the two functions below. +// ================================================================================================= +#ifdef USE_DALLAS_TEMP //DS18B20 sensor + +// Change HERE 0<>1, to swap sensors!!! +// NOTE: these two macros are not referenced anywhere else in this file (or the rest of the project) - +// looks like dead/leftover code; swapping sensor order today has to be done differently (see below). +#define SENSOR1 0 +#define SENSOR2 1 +#define TEMPERATURE_PRECISION 12 // DS18B20 resolution in bits (9-12); 12 bits = 0.0625C steps, ~750ms conversion time + +#include +#include +OneWire oneWire(TEMP_DALLAS_PIN); // Set up a OneWire instance to communicate with OneWire devices +DallasTemperature tempSensors(&oneWire); // Create an instance of the temperature sensor class +DeviceAddress thermometer1, thermometer2; // 64-bit ROM addresses of sensor #1 / #2, read once in setupDallasTemp() +boolean requested = false; // true while a conversion is in progress (request sent, result not yet fetched) +unsigned long lastRequest = millis(); // millis() timestamp of the last requestTemperatures() call +boolean DallasOK = true; //is any measure from Dallas thermometer? +float lastTemperature = 0; // NOTE: written nowhere, read nowhere else in the project - appears to be dead/unused +char lastTemperatureStr[5] = "----"; // NOTE: only ever written (see getTemp() below), never read back anywhere - looks unused/vestigial +unsigned long intervalTemp = 30000; // Do a temperature measurement every 30sec +unsigned long DS_delay = 800; // Reading the temperature from the DS18x20 can take up to 750ms + +// Indexes into the shared temperature[]/humid[] arrays (declared in ESP32_UniClock2.ino) assigned to +// this driver's sensor(s) at setup time. Multiple sensor drivers (Dallas/DHT/I2C) share those arrays, +// each claiming the next free slot via the global useTemp counter - see setupDallasTemp() below. +byte d1Ptr = 0; +byte d2Ptr = 0; + +void setupDallasTemp() { + regPin(TEMP_DALLAS_PIN,"TEMP_DALLAS_PIN"); + pinMode(TEMP_DALLAS_PIN,OUTPUT); + #if defined(ESP8266) + oneWire.reset(); // ESP8266 needs an explicit bus reset before the first transaction; ESP32 does not + delay(200); //200ms + #endif + tempSensors.begin(); // Start the temperature sensor + tempSensors.setResolution(TEMPERATURE_PRECISION); + tempSensors.setWaitForConversion(false); // Don't block the program while the temperature sensor is reading + delay(100); //100ms + // Blocking probe done ONCE at boot: request a conversion and wait out the full 800ms here (setup() + // isn't time-critical), just so we can count/enumerate the sensors below. During normal operation + // (requestDallasTemp()/getTemp()) the same conversion is done non-blocking instead. + tempSensors.requestTemperatures(); + delay(800); + int counter = 0; + while ((tempSensors.getDeviceCount() == 0) && (counter<5)) { + if (counter>3) { + DPRINT("No DS18x20 temperature sensor found on GPIO"); DPRINTLN(TEMP_DALLAS_PIN); + resetSensors(); + } + counter++; + delay(100); + } //end while + + // useDallasTemp: 0 = no sensor, 1 = single sensor, 2 = two sensors on the same bus. + // The two DS18B20s are NOT told apart by their ROM address/serial number - they are simply + // enumerated by their scan order on the bus (DallasTemperature index 0 and index 1, see + // getAddress()/getTempCByIndex() below), which is whatever order the library's internal 1-Wire + // search finds them in. There is no user-facing way here to pin a physical sensor to a fixed + // index; the SENSOR1/SENSOR2 macros above look like an abandoned attempt at that. + useDallasTemp = tempSensors.getDeviceCount(); + if (useDallasTemp>0) { + d1Ptr = useTemp; //remember the pointers of sensors + useTemp++; + if (useDallasTemp>1) { + d2Ptr = useTemp; + useTemp++; + } + DPRINT("DS18B20 sensors found:"); DPRINTLN(useDallasTemp); + if (!tempSensors.getAddress(thermometer1, 0)) DPRINTLN("Unable to find address for Device 0"); + if ((useDallasTemp>1) && !tempSensors.getAddress(thermometer2, 1)) DPRINTLN("Unable to find address for Device 1"); + // Kick off one more conversion right away (rather than waiting for the normal once-a-minute + // trigger in requestDallasTemp()) so a real reading is available soon after boot. + // POSSIBLE BUG: this only delays 200ms, but getTemp() below requires DS_delay (800ms) to have + // elapsed since the request before it will actually fetch anything - with only 200ms elapsed, + // getTemp() just returns immediately here without reading temperature[]. `requested` stays true, + // so the real fetch only happens later, whenever timeProgram()'s periodic getTemp() call is + // reached ~800ms after this request - i.e. this call does not actually pre-populate temperature[] + // before setup() returns, despite appearances. + requestDallasTemp(true); + delay(200); + getTemp(); + } +} + +// Called every ~200ms from timeProgram() (with force=false). Does NOT block: requestTemperatures() +// with setWaitForConversion(false) (set in setupDallasTemp()) just tells the sensor(s) to start +// converting and returns immediately; the actual value is picked up later by getTemp() once enough +// time has passed. EEPROMsaving is checked because 1-Wire bus timing is sensitive to interrupts/delays, +// and EEPROMsaving also doubles as a "display refresh suspended" flag elsewhere in the project. +void requestDallasTemp(boolean force) { + if (EEPROMsaving) return; + + // Old, no-longer-used scheme: re-request every intervalTemp (30s) regardless of clock time. + //if (force || (!requested && ((millis()-lastRequest) > intervalTemp))) { //request a new reading + // Current scheme: instead of a fixed interval, a new conversion is requested once per minute, + // exactly 1 second before prm.tempStart (the second-of-minute when the display starts showing the + // temperature) - so by the time the display wants to show it, the ~750-800ms conversion has had + // time to finish and getTemp() already has a fresh value ready. + if (force || (!requested && (second()==prm.tempStart-1))) { //request a new reading + tempSensors.requestTemperatures(); // Request the temperature from the sensor (it takes some time to read it) + requested = true; + lastRequest = millis(); + DPRINTLN("Temperature requested"); + } +} + +// Called every ~200ms from timeProgram() (and once directly from setupDallasTemp()). This is the +// other half of the async request/read pattern: it does nothing until DS_delay has elapsed since the +// matching requestDallasTemp() call, at which point the conversion is guaranteed finished and the +// (already-converted) value can be read from the sensor over 1-Wire without blocking/waiting. +void getTemp() { //get the earlier requested temperature data +static int errors = 0; +float tmp_temp; + + if (!requested) return; // no conversion pending - nothing to fetch yet + if ((millis()- lastRequest) < DS_delay) return; // 1000 ms after requesting the temperature + // ^ comment says 1000ms but DS_delay is actually 800ms (see top of file) - conversion itself needs + // up to ~750ms at 12-bit resolution, so 800ms leaves a small safety margin. + if (EEPROMsaving) return; // bus busy (flash write in progress) - try again on the next ~200ms tick + + requested = false; // mark conversion consumed; a new request is needed before we read again + if (tempSensors.getDeviceCount() == 0) { + // Sensor(s) no longer answering on the bus (unplugged, wiring fault, bus glitch, etc.) + // POSSIBLE BUG: temperature[d2Ptr] is zeroed unconditionally here, even when useDallasTemp<=1 + // (i.e. only one sensor was ever configured and d2Ptr was never assigned an index, so it is + // still 0 - its C++ default). If some other sensor driver's reading happens to live at slot 0 + // and this Dallas sensor's own slot (d1Ptr) is not 0, this line would stomp on that other + // sensor's temperature[] entry instead of (or in addition to) this one. Contrast with the + // success path below, which correctly guards the second-sensor update with `if (useDallasTemp>1)`. + temperature[d1Ptr] = 0; temperature[d2Ptr] = 0; + DallasOK = false; + resetSensors(); + DPRINTLN("Missing TEMP sensor!"); + // NOTE: decrementing lastRequest here has no actual effect on when the retry happens, because + // the trigger condition in requestDallasTemp() is `second()==prm.tempStart-1`, a fixed point in + // the minute, not a millis()-based interval check anymore (that scheme is the commented-out + // line above). So despite the "retry ASAP!" comment, the next request still only fires at the + // next occurrence of second()==prm.tempStart-1 (i.e. up to ~60s later), same as `errors` below. + lastRequest -= intervalTemp; //retry ASAP! + } + else { + delay(10); + tmp_temp = tempSensors.getTempCByIndex(0); // Get the temperature from the sensor + // DallasTemperature reports -127C (DEVICE_DISCONNECTED_C) when a read fails - checked here + // via "> -127" rather than comparing to the library's named constant. + if (tmp_temp > -127) { + tmp_temp = round(tmp_temp * 10.0) / 10.0; // round temperature to 1 digits + DallasOK = true; + // POSSIBLE BUG: writes to temperature[0] literally instead of temperature[d1Ptr] (used + // everywhere else, e.g. the DPRINT right below and in the "sensor missing" branch above). + // This happens to be equivalent today because setupDallasTemp() is always called first in + // setup() (see ESP32_UniClock2.ino), so d1Ptr is always 0 - but it's fragile: if any other + // sensor driver (DHT/I2C) were ever initialized before this one, or a config combined Dallas + // with another sensor type such that useTemp>0 at the point setupDallasTemp() runs, this + // sensor's reading would land in the wrong slot in temperature[], while d1Ptr would still + // point at its correctly-reserved (but now unwritten) slot. + temperature[0] = tmp_temp; + DPRINT("Dallas#1 temp:"); DPRINTLN(temperature[d1Ptr]); + dtostrf(temperature[0], 4, 1, lastTemperatureStr); // formats temperature[0] into lastTemperatureStr, which (see top of file) is never read anywhere + errors = 0; // any successful read clears the consecutive-error counter + if (useDallasTemp>1) { + temperature[d2Ptr] = round(tempSensors.getTempCByIndex(1) * 10.0) / 10.0; // round temperature to 1 digits + DPRINT("Dallas#2 temp:"); DPRINTLN(temperature[d2Ptr]); + } + } + else { + // Sensor answered on the bus but returned a bad/disconnected reading (-127C) this cycle. + DPRINT("Temp sensor error!!!"); + DPRINTLN(tmp_temp); + errors++; + lastRequest -= intervalTemp; //retry ASAP! (see NOTE above - has no effect on the actual retry timing) + resetSensors(); + } + } + // After 20 consecutive failed reads (~20 minutes, since retries are gated to once/minute), give up + // on the sensor entirely so the UI/display stops trying to show a Dallas temperature. + if (errors>20) + useDallasTemp = 0; //forget BAD sensor!!! +} + +// Attempts to recover a 1-Wire bus that has stopped responding (called from getTemp() whenever a +// sensor is missing or returns a bad reading). Blocking (delay(500) x2 = ~1s total), but it's only +// invoked on error paths, not on every normal read cycle, so it doesn't affect steady-state timing. +void resetSensors() { + if (EEPROMsaving) return; + DPRINTLN("Reset sensors..."); + //#if defined(ESP8266) + oneWire.reset(); + //#endif + delay(500); //200ms + + tempSensors.begin(); //try to restart sensor + tempSensors.setWaitForConversion(false); + tempSensors.getDeviceCount(); + delay(500); //200ms +} +//----------------------------------------------------------------------------------------------------- +#else +// USE_DALLAS_TEMP not defined for this CLOCK_NN config: compile out to no-op stubs so the rest of the +// codebase (setup()/timeProgram() in ESP32_UniClock2.ino) can call these unconditionally. +void setupDallasTemp() {} +void requestDallasTemp(boolean force) {} +void getTemp() {} +void resetSensors() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/data/favicon.ico b/firmware/uniclock/ESP32_UniClock2/data/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0cbd449ee6d09edce941c6d1b94cd7102e80ccfa GIT binary patch literal 318 zcma)1K?;B{42wna=py5s_F}7G5U;?lZiEu*q zU#sg5 + + + Uniclock 3.0 + + + + + + + + + +
+ +
+ +
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + off + + on +
+ +
+ +
+ + : + +
+
+ +
+ + +
+
+ + + off + + on +
+
+ + +
+
+ + + off + + on +
+
+ +
+
+
+ + + DAY + + NIGHT +
+ +
+ + + 12h + + 24h +
+ +
+ + + Hide + + Show +
+ +
+ + + off + + on +
+
+ + + off + + on +
+
+ + +
+ +
+ + + off + + on +
+ +
+ + + +
+ +
+ + +
+ + +
+ + + off + + on +
+ +
+ + +
+
+ + +
+ +
+ +
+ + : + +
+
+
+ +
+ + : + +
+
+
+ + + off + + on +
+
+ + +
+
+ + +
+
+ + +
+
+ + + C + + F +
+
+ +
+ + - + +
+
+
+ +
+ + - + +
+
+
+ +
+ + - + +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+
+
+
+
+ +
+ + + left + + right +
+ +
+ + +
+
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + AP + + Client +
+
+ + +
+
+ + + off + + on +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/firmware/uniclock/ESP32_UniClock2/data/jquery_351.js b/firmware/uniclock/ESP32_UniClock2/data/jquery_351.js new file mode 100644 index 0000000..d467083 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/data/jquery_351.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0https://github.com/gpeter62/ESP_UniClock', + "maxDigits": "Number of tubes", + "maxBrightness": "MAX Brightness", + "currentDate": "Date", + "currentTime": "Time", + "temperature": "Temperature sensor #1", + "temperature2": "Temperature sensor #2", + "humidity": "Humidity sensor #1", + "humidity2": "Humidity sensor #2", + "pressure": "Pressure sensor #1", + "lux": "Lux sensor current value", + "rssi": "WiFi RSSI level dB (connection quality)", + "wifiMode": "WiFi Client or Access Point", + "alarmEnable": "Switch ON/OFF alarm", + "alarmTime": "Alarm time (hour/minute)", + "alarmPeriod": "Alarm maximum length (sec)", + + //Tube display settings + "utc_offset": 'Timezone setting. https://en.wikipedia.org/wiki/List_of_time_zones_by_country', + "enableDST": "Daylight Saving Time ON/OFF", + "set12_24": "Clock display 12 / 24 hours", + "showZero": "Show starting zero on hours", + "enableBlink": "ON / OFF", + "interval": "Cathode protect interval (minutes)", + "enableAutoShutoff": "Enable automatic day/night switch", + "dayTime": "Day starts (manual brightness control)", + "nightTime": "Night starts (manual brightness control)", + "dayBright": "Day brightness", + "nightBright": "Night brightness", + "manualOverride": "Manual Day/Night change", + "animMode": "Tube animation mode: 0=OFF, 1-5: Animations 6-7: Random", + "dateMode": "0:dd/mm/yyyy 1:mm/dd/yyyy 2:yyyy/mm/dd", + "tempCF": "Celsius / Fahrenheit display", + "enableTimeDisplay": "Enable time&date display (Use OFF for thermometers)" , + "dateStart": "Date display start: Date is shown between Start/End seconds", + "dateEnd": "Date display end", + "tempStart": "Temperature display time slice start", + "tempEnd": "Temperature display end", + "humidStart": "Humidity display time slice start", + "humidEnd": "Humidity display end", + "dateRepeatMin": "Date is shown in every xx minutes. 0 = Disable date display", + "tempRepeatMin": "Temperature/humidity is shown in every xx minutes. 0 = Disable temp/humid display", + "enableDoubleBlink": "Enable both blinking dots", + "enableAutoDim": "Enable auto brightness control (if sensor is installed)", + "enableRadar": "Enable Radar/Pir tube switch (if sensor is installed)", + "radarTimeout": "If Radar is used, automatic switch off after this timeout (minutes)", + + //RGB settings + "rgbDir" : "RGB animation direction (left/right)", + "rgbEffect": "RGB animation#", + "rgbBrightness": "RGB leds brightness 0..255", + "rgbFixColor": "RGB color, if FixColor Effect is used", + "rgbSpeed": "RGB animation speed", + + //Wifi settings + "wifiSsid": "WiFi network SSID to connect", + "wifiPsw": "WiFI password", + "ApSsid": "AccessPoint name, if standalone mode is used", + "ApPsw": "AP password", + "NtpServer": "Timeserver name, using WiFi. (Default is: pool.ntp.org)", + "mqttBrokerAddr": "MQTT server address, if MQTT used", + "mqttBrokerUser": "MQTT user", + "mqttBrokerPsw": "MQTT password", + "mqttBrokerRefresh": "MQTT send data in every xx sec", + "mqttEnable": "Enable/Disable MQTT refresh", + "firmware": "http server + firmware name to download new firmware", + "corrT0": "Temperature sensor#1 correction", + "corrT1": "Temperature sensor#2 correction", + "corrH0": "Humidity sensor#1 correction", + "corrH1": "Humidity sensor#2 correction" , + "cathProtMin": "Cathode Protect procedure (minutes)", + "FW": "firmware code", + "tubeDriver": "Tube driver modul" +}; + +//Example config that UI recieves +var configuration = { + //Main screen + "version": "UniClock 3.0", + "maxDigits": 6, + "maxBrightness": 10, + "currentDate": "2020.10.24", + "currentTime": "15:01", + "temperature": 22.1, + "temperature2": 255, + "humidity": 45, + "humidity2": 255, + "pressure": 255, + "lux": 100, + "rssi": 0, + "wifiMode": true, + "alarmEnable": false, + "alarmTime": "6:30", + "alarmPeriod": 15, + "enableRadar": false, + "radarTimeout": 5, + + //Tube display settings + "utc_offset": 1, + "enableDST": true, + "set12_24": true, + "showZero": true, + "enableBlink": true, + "interval": 15, + "enableAutoShutoff": true, + "dayTime": "7:00", + "nightTime": "22:00", + "dayBright": 10, + "nightBright": 3, + "manualOverride": true, + "animMode": 6, + "dateMode": 2, // 0:dd/mm/yyyy 1:mm/dd/yyyy 2:yyyy/mm/dd + "tempCF": false, //Temperature Celsius / Fahrenheit + "enableTimeDisplay": true, + "dateStart": 45, + "dateEnd": 50, + "tempStart": 35, + "tempEnd": 40, + "humidStart": 40, + "humidEnd": 45, + "dateRepeatMin": 3, + "tempRepeatMin": 1, + "enableDoubleBlink": true, + "enableAutoDim": false, + + //RGB settings + "rgbDir" : true, + "rgbEffect": 1, + "rgbBrightness": 100, + "rgbFixColor": 150, + "rgbSpeed": 50, + + //Wifi settings + "wifiSsid": "mywifi", + "wifiPsw": "mypsw", + "ApSsid": "UniClock", + "ApPsw": "uniclock", + "NtpServer": "pool.ntp.org", + "mqttBrokerAddr": "10.0.99.12", + "mqttBrokerUser": "mqtt", + "mqttBrokerPsw": "mqttPW", + "mqttBrokerRefresh": 30, + "mqttEnable": false, + "firmware": "http://server/fw.bin", + "corrT0": 0.0, + "corrT1": 0.0, + "corrH0": 0.0, + "corrH1": 0.0, + "cathProtMin": 5, + "FW": "fw", + "tubeDriver": "xxx" +}; + +var isMouseDown = 0; +//Runs, when HTML document is fully loaded +$(document).ready(function(){ + $(document).on('mousedown touchstart',function(event) { + ++isMouseDown; + }); + $(document).on('mouseup touchend',function(event) { + --isMouseDown; + }); + + if(isTest){ + Init(); + setCurrentInfos(); + } + else{ + getConfiguration(); + //Set current infos, like time, humidity and temperature + getCurrentInfos(); + } +}); + +function setLocalPagination(){ + $('[page]').not('[hidden]').each(function(){ + var page = $(this).attr('page'); + $('#menu tr').append(''); + }); +} + +function getConfiguration(){ + $.get('/getConfiguration/').done(function(data){ + configuration = data; + }).always(function(){ + Init(); + }); +} + +function sendMsgToArduino(key,value) { + if(isTest){ + console.log(key + " " + value); + } + else{ + $.post('/saveSetting/', {"key" : key ,"value" : value }).done(function(data){ + console.log(data); + }); + } +} + +var COLORSATURATION = 255; +var WHITE_INDEX = 192; +function getColorFrom(WheelPos){ + if (WheelPos == WHITE_INDEX) + return RgbColor(COLORSATURATION/2, COLORSATURATION/2, COLORSATURATION/2); + + WheelPos = 255 - WheelPos; + if(WheelPos < 85) { + return RgbColor(255 - WheelPos * 3, 0, WheelPos * 3); + } else if(WheelPos < 170) { + WheelPos -= 85; + return RgbColor(0, WheelPos * 3, 255 - WheelPos * 3); + } else { + WheelPos -= 170; + return RgbColor(WheelPos * 3, 255 - WheelPos * 3, 0); + } +} + +function RgbColor(r, g, b){ + return 'rgb('+r+','+g+','+b+')'; +} + +function setPreviewColor(){ + var color = getColorFrom($('#rgbFixColor').val()); + $('#selectedColor').css('background-color',color); +} + +function setCurrentInfos(){ + $('#currentTime').html(getCurrentDate(configuration["currentDate"]) + " " + configuration["currentTime"]); + $('#lux').html(configuration["lux"]); + $('#rssi').html(configuration["rssi"]); + $('#pressure').html(round(configuration["pressure"],1)); + $('#humidity').html(round(configuration["humidity"],1)); + $('#humidity2').html(round(configuration["humidity2"],1)); + $('#temperature').html(getTemperature(configuration["temperature"])); + $('#temperature2').html(getTemperature(configuration["temperature2"])); + $('#temperature, #temperature2').toggleClass('fahrenheit',configuration["tempCF"]); +} + +function round(value, decimals) { + return parseFloat(value).toFixed(decimals); + //return Number(Math.round(value+'e'+decimals)+'e-'+decimals); +} + +function getTemperature(temperatureInC){ + if(configuration["tempCF"]){ //Fahrenheit + return round(temperatureInC * 1.8 + 32,1); + } + else{ + return round(temperatureInC,1); + } +} + +function getControlInfo(index){ + return controlInfos[index]; +} + +//Contains the most important initializes +function Init(){ + + $('#dayBright, #nightBright').attr('max',configuration['maxBrightness']); + document.title = configuration['version']; + $('#versionHeader').html(configuration['version']); + + setLocalPagination(); + $('[pagemenu]').on('click',function(){ + $('.menu-item.active').removeClass('active'); + $('[page].active').removeClass('active'); + $(this).addClass('active'); + $('[page="'+$(this).attr('pagemenu')+'"]').addClass('active'); + if($(this).attr('pagemenu') == 'settings'){ + getClockDetails(); + } + }); + $('.menu-item').eq(0).trigger('click'); + + $('#rgbFixColor').on('input',function(){ + setPreviewColor(); + }); + + var sliderFadeOutTimer; + function setFadeOutTimer(element){ + setTimeout(function(){ + if(isMouseDown > 0){ + clearTimeout(sliderFadeOutTimer); + sliderFadeOutTimer = setFadeOutTimer(element); + } + else{ + $(element).addClass('fading').fadeOut('slow',function(){ + $(this).remove(); + }); + } + },600); + } + + $('input[type="range"]').on('mousedown touchstart input',function(){ + clearTimeout(sliderFadeOutTimer); + var id = $(this).attr('id'); + var parent = $(this).closest('.control-holder'); + $('[sliderfor]:not(.fading)').each(function(){ + if($(this).attr('sliderfor') != id && !$(this).hasClass('fading')){ + $(this).addClass('fading').fadeOut('slow',function(){ + $(this).remove(); + }); + } + }); + if($(parent).children('.slider-info').length == 0){ + $(parent).append('
'); + $(parent).children('.slider-info').fadeIn(300); + } + var _this = $(parent).children('.slider-info').text($(this).val()); + sliderFadeOutTimer = setFadeOutTimer(_this); + }); + + $('.form label').on('click',function(){ + var id = ''; + if(!!$(this).attr('for')){ + id = $(this).attr('for'); + } + else{ + id = $(this).closest('.control-holder').find('input, select, .info').eq(0).attr('id'); + } + + var info = getControlInfo(id); + if(!!info){ + showPopUp($(this).text(), info, 450, true); + } + }); + $('#popup-close-btn, #info-popup').on('click',function(){ + if($('#info-popup').hasClass('cantClose')){ + return; + } + else{ + $('#info-popup').fadeOut(450); + } + }); + $("#popup-box").click(function(event){ + event.stopPropagation(); + }); + + //binds custom switch functionality + $('.switcher').on('click',function(){ + $('#'+$(this).attr('for')).prop('checked',!$('#'+$(this).attr('for')).is(":checked")).trigger('change'); + }); + + //binds custom switch text functionality + $('.switcher-text').on('click',function(){ + $('#'+$(this).attr('for')).prop('checked',$(this).hasClass('right')).trigger('change'); + }); + + //fills autogenerated select drop downs + /* + Possible css class: + two-digit - fills with two digit values if value lower than 10 + Possible attributes: + min (mandatory) - auto generation's minimum value + max (mandatory) - auto generation's maximum value + step (optional) - auto generation's step, if not set, default is 1 + prefix (optional) - concatanate this string before value + suffix (optional) - concatanate this string after value + */ + $('.number-select').each(function(){ + let from = $(this).attr('min')*1; + let to = $(this).attr('max')*1; + let step = !!$(this).attr('step') ? $(this).attr('step') : 1; + + for(let i = from; i < to+1; i){ + let value = $(this).hasClass('two-digit') ? formatToTwoDigit(i) : i; + let text_value = value; + if($(this).attr('prefix') != null){ + text_value = $(this).attr('prefix') + text_value + } + if($(this).attr('suffix') != null){ + text_value += $(this).attr('suffix') + } + $(this).append(''); + i += step*1; + } + }); + + //fills the inputs with configuration values + for(var index in configuration){ + let value = configuration[index]; + if(index == 'dayTime' || index == 'nightTime' || index == 'alarmTime'){ + value = value.split(':'); + $('#'+index+'Hours').val(formatToTwoDigit(value[0])); + $('#'+index+'Minutes').val(formatToTwoDigit(value[1])); + } + else if(index == 'version' + ){ + $('#'+index).html(value); + } + else if(index == 'utc_offset' || index == 'maxBrightness' || + index == 'dayBright' || index == 'nightBright' || + index == 'animMode' || index == 'rgbBrightness' || + index == 'rgbFixColor' || index == 'rgbSpeed' || + index == 'rgbEffect' || index == 'interval' || + index == 'alarmPeriod' || index == 'radarTimeout' || + index == 'dateRepeatMin' || index == 'dateMode' || + index == 'dateStart' || index == 'dateEnd' || + index == 'timeStart' || index == 'timeEnd' || + index == 'tempStart' || index == 'tempEnd' || + index == 'humidStart' || index == 'humidEnd' || + index == 'mqttBrokerRefresh' || index == 'tempRepeatMin' || + index == 'wifiSsid' || index == 'wifiPsw' || + index == 'ApSsid' || index == 'ApPsw' || + index == 'mqttBrokerAddr' || index == 'NtpServer' || + index == 'mqttBrokerUser' || index =='mqttBrokerPsw' || + index == 'firmware' || index == 'corrT0' || index == 'corrT1' || + index == 'corrH0' || index == 'corrH1' || index == 'cathProtMin' + ){ + $('#'+index).val(value); + } + else if((index == 'enableDST' || index == 'set12_24' || + index == 'showZero' || index == 'enableBlink' || + index == 'enableAutoShutoff' || index == 'alarmEnable' || + index == 'rgbDir' || index == 'manualOverride' || + index == 'enableAutoDim' || index == 'enableRadar' || + index == 'enableDoubleBlink' || index == 'enableTimeDisplay' || + index == 'mqttEnable' || index == 'tempCF' || index == "wifiMode" + ) && !!value + ){ + $('#'+index).prop('checked',true); + } + else if(index == "maxDigits"){ + //TODO + } + } + $('.radar-holder').toggleClass('hidden',configuration['radarTimeout'] == 0); + $('.mqtt-holder').toggleClass('hidden',configuration['mqttBrokerRefresh'] == 0); + $('.lux-holder').toggleClass('hidden',configuration['lux'] == 255); + $('.pressure-holder').toggleClass('hidden',configuration['pressure'] == 255); + $('.humidity-holder').toggleClass('hidden',configuration['humidity'] == 255); + $('.humidity-holder2').toggleClass('hidden',configuration['humidity2'] == 255); + $('.temperature-holder').toggleClass('hidden',configuration['temperature'] == 255); + $('.temperature-holder2').toggleClass('hidden',configuration['temperature2'] == 255); + $('.rgb-holder').toggleClass('hidden',configuration['rgbEffect'] == 255); + $('.tube-holder').toggleClass('hidden',configuration['tubeDriver'] == "DUMMY"); + $('.wordclock-holder').toggleClass('hidden',configuration['tubeDriver'] == "WORDCLOCK"); + $('.sensors-holder').toggleClass('hidden',configuration['temperature'] == 255); + setPreviewColor(); + + //sets a possible good timezone, if not already set + //if(!configuration['utc_offset']){ + // //Intl.DateTimeFormat().resolvedOptions().timeZone <- I am in this Zone + // var tryToFindCurrentZone = -(new Date().getTimezoneOffset() / 60); + // $('#utc_offset').val(tryToFindCurrentZone); + //} + + setTimeout(function(){ + $('input, select').on('change',function(){ + var value = ''; + if($(this).attr('id').indexOf("Hours") == -1 || $(this).attr('id').indexOf("Minutes") == -1){ + if($(this).attr('type') == 'checkbox'){ + value = $(this).is(':checked'); + } + else{ + value = $(this).val(); + } + sendMsgToArduino($(this).attr('id'), value); + } + else{ + var key = $(this).attr('id').replace('Hours','').replace('Minutes',''); + value = $('#'+key+'Hours').val() + ":" + $('#'+key+'Minutes').val(); + sendMsgToArduino(key, value); + } + }); + $('#dateMode').on('change',function(){ + configuration["dateMode"] = $(this).val(); + setCurrentInfos(); + }); + $('#tempCF').on('change',function(){ + configuration["tempCF"] = !configuration["tempCF"]; + setCurrentInfos(); + }); + },200); +} + +function showPopUp(header, content, timeout, canClose){ + $('#popup-box h2').text(header); + $('#popup-box .content').html(content); + $('#info-popup').toggleClass('cantClose',!canClose); + $('#info-popup').fadeIn(timeout); +} + +function areYouSure(fn){ + if (confirm("Are you sure to run " + fn + "?")) { + if(fn == "reset"){ + callReset(); + } + else if(fn == "factoryreset"){ + callFactoryReset(); + } + else if(fn == "firmwareupdate"){ + callFirmwareUpdate(); + } + else if(fn == "cathodeProtect"){ + callCathodeProtect(); + } + } +} + +function callReset(){ + if(isTest){return;} + $.post('/reset/', {}).done(function(data){ + location.reload(); + }).always(function(){ + + }); +} + +function callFactoryReset(){ + if(isTest){return;} + $.post('/factoryreset/', {}).done(function(data){ + location.reload(); + }).always(function(){ + + }); +} + +function callFirmwareUpdate(){ + if(isTest){return;} + showPopUp("Firmware is updating", "Please do not turn off the clock", 300, false); + $.post('/firmwareupdate/', {}).done(function(data){ + if(!!data.header){ + showPopUp(data.header, data.content, 300, data.canClose); + } + }).always(function(){ + + }); +} + +function callCathodeProtect(){ + if(isTest){return;} + $.post('/cathodeProtect/', {}).done(function(data){ + location.reload(); + }).always(function(){ + + }); +} + +function getClockDetails(){ + if(isTest){return;} + $.get('/getClockDetails/').done(function(data){ + $('#clock-details').html(data); + }).always(function(){ + + }); +} + +function getCurrentInfos(){ + if(isTest){return;} + $.get('/getCurrentInfos/').done(function(data){ + for(var i in data){ + configuration[i] = data[i]; + } + if(!!data["popupMsg"] && data["popupMsg"].length > 0){ + showPopUp("Info", data["popupMsg"], 300, true); + } + setCurrentInfos(); + setTimeout(getCurrentInfos,20000); //refreshes time every 20 second by calling itself + }).always(function(){ + + }); +} + +function getCurrentDate(date){ + var splittedDate = date.split('.'); + var yyyy = splittedDate[0]; + var mm = splittedDate[1]; + var dd = splittedDate[2]; + + if(configuration["dateMode"] == 0){ + return dd+"/"+mm+"/"+yyyy; + } + else if(configuration["dateMode"] == 1){ + return mm+"/"+dd+"/"+yyyy; + } + else if(configuration["dateMode"] == 2){ + return yyyy+"/"+mm+"/"+dd; + } + else{ + return yyyy+"/"+mm+"/"+dd; //if dateMode?? // 0:dd/mm/yyyy 1:mm/dd/yyyy 2:yyyy/mm/dd + } +} + +//Gets the current time from browser and gives back as readable format +function getCurrentTime(){ + var today = new Date(); + var dd = formatToTwoDigit(today.getDate()); + var mm = formatToTwoDigit(today.getMonth()+1); + var yyyy = today.getFullYear(); + var hour = formatToTwoDigit(today.getHours()); + var minute = formatToTwoDigit(today.getMinutes()); + + if(configuration["dateMode"] == 0){ + return dd+"-"+mm+"-"+yyyy+" "+hour+":"+minute; + } + else if(configuration["dateMode"] == 1){ + return mm+"-"+dd+"-"+yyyy+" "+hour+":"+minute; + } + else if(configuration["dateMode"] == 2){ + return yyyy+"-"+mm+"-"+dd+" "+hour+":"+minute; + } + else{ + return yyyy+"-"+mm+"-"+dd+" "+hour+":"+minute; //if dateMode?? // 0:dd/mm/yyyy 1:mm/dd/yyyy 2:yyyy/mm/dd + } +} + +//if number is lower than 10, adds a zero +function formatToTwoDigit(number){ + return ("0" + number).slice(-2); +} \ No newline at end of file diff --git a/firmware/uniclock/ESP32_UniClock2/data/site.css b/firmware/uniclock/ESP32_UniClock2/data/site.css new file mode 100644 index 0000000..80652dc --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/data/site.css @@ -0,0 +1,479 @@ +*, *:before, *:after{ + box-sizing: border-box; +} +.non-selectable{ + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Old versions of Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome, Edge, Opera and Firefox */ +} +html { + -webkit-font-smoothing: antialiased; + background-image: radial-gradient(#222, #111); + min-height: 100%; + background-attachment: fixed; + color: #c35; +} +.hidden{ + display: none !important; +} +a, a:visited{ + color: #c35; + text-decoration: none; +} +a:hover{ + text-decoration: underline; + color: #777; +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, "Liberation Sans", FreeSans, sans-serif; + margin: 0; +} +.form{ + overflow: auto; +} +.max-width{ + max-width: 800px; + margin: auto; +} +.row{ + clear: both; + overflow: auto; +} +.row + .row{ + margin-top: 15px; +} +.control-holder{ + width: 33.333333%; + display: block; + float: left; + padding: 5px; + min-height: 80px; + position: relative; +} +.header{ + overflow: auto; +} +.form label { + cursor: help; + width: 100%; + display: inline-block; + font-weight: bold; + padding-top: 0; + padding-left: 0; + letter-spacing: 0.025em; + font-size: 20px; + vertical-align: top; + line-height: 30px; + position: relative; + text-shadow: 1px 1px 0 black; + margin-bottom: 5px; +} +input, select, button{ + margin: 0; + font: inherit; + line-height: normal; + width: 100%; + max-width: 250px; + background: #222222; + color: #eee; + position: relative; + padding: 5px; + min-height: 30px; + outline: none; + line-height: normal; + border-radius: 3px; + border: 1px solid #666; + transition: 0.15s background; +} +input:not([type="text"]):not(:disabled),select:not(:disabled),button:not(:disabled){ + cursor: pointer; +} +input:hover, select:hover, button:hover{ + background: #333; +} +input:focus, select:focus, button:focus{ + background: #4E4E4E; + border-color: #888; +} +input:active, select:active, button:active{ + background: #5E5E5E; + border-color: #aaa; +} +button{ + border: 2px solid #c35; + color: #c35; + text-transform: uppercase; + font-weight: bold; +} +button:hover{ + color: #ddd; +} +.multiple-input span{ + color: white; + font-weight: bold; +} +.two-digit, .small{ + max-width: 60px; +} +[pagemenu="settings"]:hover{ + cursor: pointer; + text-decoration: underline; +} +[type="checkbox"].switch{ + position: fixed; + left: -300px; + top: -300px; +} +[type="checkbox"].switch ~ .switcher{ + width: 60px; + display: inline-block; + vertical-align: top; + height: 30px; + padding: 2px; + background: #222222; + border: 1px solid #666; + transition: 0.15s background; + border-radius: 30px; + position: relative; + cursor: pointer; +} +[type="checkbox"].switch:focus ~ .switcher{ + background: #5E5E5E; + border-color: #aaa; +} +[type="checkbox"].switch ~ .switcher:after{ + display: inline-block; + content: ''; + vertical-align: top; + width: 20px; + height: 20px; + position: absolute; + top: 4px; + left: 4px; + border-radius: 50%; + background: rgba(255,255,255,0.4); + transition: 0.3s all; +} +[type="checkbox"].switch:checked ~ .switcher:after{ + left: 34px; +} +[type="checkbox"].switch:not(.two-side):checked ~ .switcher:after{ + background: rgba(255,255,255,0.7); +} +[type="checkbox"].switch:not(.two-side):checked ~ .switcher{ + background: #37bfb9; + background-color: #c35; +} +[page]{ + display: none; +} +[page].active{ + display: block; +} +#menu-holder{ + padding-bottom: 40px; +} +#menu-holder > .floating-menu{ + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 110; + background: #161616; +} +#menu{ + width: 100%; +} +#menu td{ + padding: 0 18px; +} +#menu td .menu-item{ + cursor: pointer; + border-bottom: 2px solid gray; + text-align: center; + text-transform: uppercase; + font-weight: bold; + line-height: 30px; + background-color: transparent; + transition: 0.3s border, 0.3s background, 0.3s color; +} +#menu td .menu-item:hover{ + background-color: #4E4E4E; + border-color: #ddd; +} +#menu td .menu-item.active{ + color: #ddd; + border-color: #c35; +} +.switcher-text{ + text-transform: uppercase; + font-weight: bold; + line-height: 30px; + cursor: pointer; + transition: 0.3s color; + text-shadow: 1px 1px 0 black; +} +.switcher-text:hover{ + color: #c35 !important; +} +.switcher-text.left{ + margin-right: 10px; +} +.switcher-text.right{ + margin-left: 10px; + color: #555; +} +[type="checkbox"].switch:checked ~ .switcher-text.left{ + color: #555; +} +[type="checkbox"].switch:checked ~ .switcher-text.right{ + color: inherit; +} +.footer{ + margin: auto; + display: block; + text-align: center; + text-shadow: 1px 1px 0 black; +} +.footer span{ + margin: 0 10px; + font-size: 26px; + color: #555; +} +.separator{ + border-top: 1px dotted #555; + margin-top: 20px; + margin-bottom: 20px; +} +.info{ + color: #555; + padding: 10px 0; + font-style: italic; + display: inline-block; + text-shadow: 1px 1px 0 black; +} +.info.medium{ + color: #888; + font-weight: bold; + font-size: 25px; + line-height: 32px; +} +.info.big{ + color: #888; + font-weight: bold; + font-size: 32px; + line-height: 32px; +} +.info:after{ + color: #666; + display: inline-block; + padding-left: 5px; + font-size: 28px; +} +#temperature:after{ + content: ' °C'; +} +#temperature.fahrenheit:after{ + content: ' °F'; +} +#temperature2:after{ + content: ' °C'; +} +#temperature2.fahrenheit:after{ + content: ' °F'; +} +#humidity:after{ + content: '%'; +} +#pressure:after{ + content: ' hPa'; +} +#lux:after{ + content: ' lux'; +} +#color-select-holder > div{ + float: left; +} +#color-select-holder > div:first-child{ + width: calc(100% - 40px); +} +#color-select-holder > div:last-child{ + width: 40px; +} + +#info-popup{ + display: none; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 9999; + background: rgba(0,0,0,0.6); +} + +#popup-box{ + padding: 12px; + border: 2px solid; + margin: auto; + width: 100%; + background: #161616; + margin-top: 50px; + border-radius: 12px; + position: relative; +} +#popup-box h2{ + margin-top: 0; + color: #ddd; +} +#popup-close-btn{ + position: absolute; + left: 100%; + top: 0; + background: #161616; + width: 30px; + height: 30px; + border-radius: 50%; + border: 2px solid; + line-height: 30px; + text-align: center; + font-weight: bold; + cursor: pointer; + margin-left: -15px; + margin-top: -15px; + transition: 0.3s background, 0.3s color; +} +#popup-close-btn:hover{ + color: #ddd; +} +#info-popup.cantClose #popup-close-btn{ + display: none; +} + +#selectedColor{ + width: 30px; + height: 30px; + background-color: white; + border-radius: 50%; + margin: auto; +} + +.slider-info{ + display: none; + position: absolute; + padding: 3px; + font-weight: bold; + font-size: 24px; + background: #161616; + border: 2px solid #c35; + z-index: 3; + border-radius: 50%; + text-align: center; + color: #ddd; + min-width: 50px; + top: 0; + right: 10px; +} + +/* slider control */ + +.slidecontainer { + width: 100%; /* Width of the outside container */ +} + +/* The slider itself */ +.slider { + margin-top: 9px; + height: 10px; + min-height: auto; + -webkit-appearance: none; /* Override default CSS styles */ + appearance: none; + outline: none; /* Remove outline */ + opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */ + -webkit-transition: .2s; /* 0.2 seconds transition on hover */ + transition: opacity .2s; +} + +/* Mouse-over effects */ +.slider:hover { + opacity: 1; /* Fully shown on mouse-over */ +} + +/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */ +.slider::-webkit-slider-thumb { + -webkit-appearance: none; /* Override default look */ + appearance: none; + width: 20px; /* Set a specific slider handle width */ + height: 20px; /* Slider handle height */ + background: #c35; + cursor: pointer; /* Cursor on hover */ + border-radius: 50%; + border: 2px solid #ddd; +} + +.slider::-moz-range-thumb { + width: 20px; /* Set a specific slider handle width */ + height: 20px; /* Slider handle height */ + background: #c35; + cursor: pointer; /* Cursor on hover */ + border-radius: 50%; + border: 2px solid #ddd; +} + +/* slider control end */ + + +/* width */ +::-webkit-scrollbar { + width: 10px; +} + +/* Track */ +::-webkit-scrollbar-track { + background: #333; +} + +/* Handle */ +::-webkit-scrollbar-thumb { + background: #c35; +} + +/* Handle on hover */ +::-webkit-scrollbar-thumb:hover { + background: #d43d5f; +} +@media screen and (max-width: 600px){ + .control-holder{ + width: 50%; + } + .form label{ + font-size: 15px; + } + .switcher-text{ + font-size: 14px; + } + .info{ + padding: 5px 0; + } + .info:after{ + font-size: 16px; + } + .info.big{ + font-size: 20px; + } + .info.medium{ + font-size: 15px; + } +} +@media screen and (max-width: 360px){ + body{ + text-align: center; + } + .control-holder{ + width: 100%; + } +} \ No newline at end of file diff --git a/firmware/uniclock/ESP32_UniClock2/dummy.ino b/firmware/uniclock/ESP32_UniClock2/dummy.ino new file mode 100644 index 0000000..ed1d4b5 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/dummy.ino @@ -0,0 +1,20 @@ +#ifdef DUMMY + +//Dummy clock driver for sensor box / mqtt sensors + +char tubeDriver[] = "DUMMY"; +int maxDigits = 6; + +void setup_pins() { + DPRINTLN("Dummy display: no pins are used"); +} + +#if defined(ESP32) +void IRAM_ATTR writeDisplay() {} +#else +void ICACHE_RAM_ATTR writeDisplay(){} +#endif + +void clearTubes() {} +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/font5x7.h b/firmware/uniclock/ESP32_UniClock2/font5x7.h new file mode 100644 index 0000000..65e6efe --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/font5x7.h @@ -0,0 +1,303 @@ +// ============================================================================= +// font5x7.h - 5x7 dot-matrix bitmap font table +// ============================================================================= +// +// WHAT THIS IS +// A 256-glyph bitmap font (derived from the classic Adafruit GLCD 5x7 font, +// "glcdfont.c") used to render single characters on 5-column x 7-row LED +// matrix / dot-matrix displays. Several glyphs have been hand-modified from +// the original Adafruit set (see "old:" comments below for the replaced +// values), and the upper half of the table (0x80-0xFF) has been customized +// with Hungarian/German accented letters (a, ä, ö, u, Ä, Ö, U), Greek +// letters, and math/misc symbols in place of the original CP437 glyphs. +// +// DATA LAYOUT +// _font[] is a flat byte array with exactly 5 bytes per character: +// _font[c*5 + col] for col = 0..4 (NUMCOLS in wordclock.ino) +// Each byte is one COLUMN of the glyph (column-major, not row-major). +// Within a byte, bit 0 (LSB) is the TOP pixel of that column and bit 6 is +// the BOTTOM pixel (7 rows tall); bit 7 is unused/always 0. Columns are +// ordered left-to-right within each 5-byte block. +// The table holds 256 entries indexed 0x00-0xFF, i.e. it is indexed +// directly by (unsigned) character code - there is no separate character +// map or lookup table. +// +// WHO USES IT +// wordclock.ino is the only consumer: it does #include "font5x7.h" and +// reads the array in show(byte c) as +// loccol[i] = _font[c*NUMCOLS + i]; +// then calls transposePattern() to convert the column-major glyph into the +// row-major bit pattern actually clocked out to the display hardware +// (honoring the _upsidedown orientation flag). No other .ino in this +// project references _font[] or includes this header. +// +// CHARACTER RANGE +// 0x00-0x1F : custom symbols (smileys, suits, arrows, euro sign, etc.), +// not standard control characters. +// 0x20-0x7E : standard printable ASCII (space through ~). +// 0x7F-0xFF : custom/extended glyphs - accented Hungarian/German letters, +// Greek letters, and math symbols (see inline comments). +// ============================================================================= +byte _font[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00 empty + 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, // 0x01 sad + 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, // 0x02 happy + 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, // 0x03 heart + 0x18, 0x3C, 0x7E, 0x3C, 0x18, // 0x04 diamond + 0x1C, 0x57, 0x7D, 0x57, 0x1C, // 0x05 clover + 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, // 0x06 spade + 0x00, 0x18, 0x3C, 0x18, 0x00, // 0x07 small full circle + 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, // 0x08 inverted small circle + 0x06, 0x09, 0x06, 0x00, 0x00, // 0x09 small empty circle //old: 0x00, 0x18, 0x24, 0x18, 0x00, // 0x09 small empty circle + 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, // 0x0A block (linefeed) + 0x30, 0x48, 0x3A, 0x06, 0x0E, // 0x0B male + 0x26, 0x29, 0x79, 0x29, 0x26, // 0x0C female + 0x40, 0x7F, 0x05, 0x05, 0x07, // 0x0D note (carriage return) + 0x40, 0x7F, 0x05, 0x25, 0x3F, // 0x0E two notes + 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, // 0x0F sun + 0x7F, 0x3E, 0x1C, 0x1C, 0x08, // 0x10 right triangle + 0x08, 0x1C, 0x1C, 0x3E, 0x7F, // 0x11 left triangle + 0x14, 0x22, 0x7F, 0x22, 0x14, // 0x12 up/down arrow + 0x5F, 0x5F, 0x00, 0x5F, 0x5F, // 0x13 double exclamation mark + 0x06, 0x09, 0x7F, 0x01, 0x7F, // 0x14 paragraph end sign (pilcrow) + // 0x00, 0x66, 0x89, 0x95, 0x6A, // 0x15 paragraph sign original + 0x00, 0x26, 0x49, 0x55, 0x28, // 0x15 paragraph sign + 0x60, 0x60, 0x60, 0x60, 0x60, // 0x16 double underline + 0x54, 0x62, 0x7F, 0x62, 0x54, // 0x17 up/down arrow with underline + 0x08, 0x04, 0x7E, 0x04, 0x08, // 0x18 up arrow + 0x10, 0x20, 0x7E, 0x20, 0x10, // 0x19 down arrow + 0x08, 0x08, 0x2A, 0x1C, 0x08, // 0x1A right arrow + 0x08, 0x1C, 0x2A, 0x08, 0x08, // 0x1B left arrow + 0x14, 0x3E, 0x55, 0x41, 0x22, // 0x1C euro + 0x63, 0x75, 0x69, 0x75, 0x63, // 0x1D timer + 0x30, 0x38, 0x3E, 0x38, 0x30, // 0x1E up triangle + 0x06, 0x0E, 0x3E, 0x0E, 0x06, // 0x1F down triangle + 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 space + 0x00, 0x00, 0x5F, 0x00, 0x00, // 0x21 ! + 0x00, 0x07, 0x00, 0x07, 0x00, // 0x22 " + 0x14, 0x7F, 0x14, 0x7F, 0x14, // 0x23 # + 0x24, 0x2A, 0x7F, 0x2A, 0x12, // 0x24 $ + 0x23, 0x13, 0x08, 0x64, 0x62, // 0x25 % + 0x36, 0x49, 0x56, 0x20, 0x50, // 0x26 & + 0x00, 0x08, 0x07, 0x03, 0x00, // 0x27 ´ + 0x00, 0x1C, 0x22, 0x41, 0x00, // 0x28 ( + 0x00, 0x41, 0x22, 0x1C, 0x00, // 0x29 ) + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, // 0x2A * + 0x08, 0x08, 0x3E, 0x08, 0x08, // 0x2B + + 0x00, 0x80, 0x70, 0x30, 0x00, // 0x2C , + 0x08, 0x08, 0x08, 0x08, 0x08, // 0x2D - + 0x00, 0x00, 0x60, 0x60, 0x00, // 0x2E . + 0x20, 0x10, 0x08, 0x04, 0x02, // 0x2F / + 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0x30 0 + 0x00, 0x42, 0x7F, 0x40, 0x00, // 0x31 1 + 0x72, 0x49, 0x49, 0x49, 0x46, // 0x32 2 + 0x21, 0x41, 0x49, 0x4D, 0x33, // 0x33 3 + 0x18, 0x14, 0x12, 0x7F, 0x10, // 0x34 4 + 0x27, 0x45, 0x45, 0x45, 0x39, // 0x35 5 + 0x3C, 0x4A, 0x49, 0x49, 0x31, // 0x36 6 + 0x41, 0x21, 0x11, 0x09, 0x07, // 0x37 7 + 0x36, 0x49, 0x49, 0x49, 0x36, // 0x38 8 + 0x46, 0x49, 0x49, 0x29, 0x1E, // 0x39 9 + 0x00, 0x00, 0x14, 0x00, 0x00, // 0x3A : + 0x00, 0x40, 0x34, 0x00, 0x00, // 0x3B ; + 0x00, 0x08, 0x14, 0x22, 0x41, // 0x3C < + 0x14, 0x14, 0x14, 0x14, 0x14, // 0x3D = + 0x00, 0x41, 0x22, 0x14, 0x08, // 0x3E > + 0x02, 0x01, 0x59, 0x09, 0x06, // 0x3F ? + 0x3E, 0x41, 0x5D, 0x59, 0x4E, // 0x40 @ + 0x7C, 0x12, 0x11, 0x12, 0x7C, // 0x41 A + 0x7F, 0x49, 0x49, 0x49, 0x36, // 0x42 B + 0x3E, 0x41, 0x41, 0x41, 0x22, // 0x43 C + 0x7F, 0x41, 0x41, 0x41, 0x3E, // 0x44 D + 0x7F, 0x49, 0x49, 0x49, 0x41, // 0x45 E + 0x7F, 0x09, 0x09, 0x09, 0x01, // 0x46 F + 0x3E, 0x41, 0x41, 0x51, 0x73, // 0x47 G + 0x7F, 0x08, 0x08, 0x08, 0x7F, // 0x48 H + 0x00, 0x41, 0x7F, 0x41, 0x00, // 0x49 I + 0x20, 0x40, 0x41, 0x3F, 0x01, // 0x4A J + 0x7F, 0x08, 0x14, 0x22, 0x41, // 0x4B K + 0x7F, 0x40, 0x40, 0x40, 0x40, // 0x4C L + 0x7F, 0x02, 0x1C, 0x02, 0x7F, // 0x4D M + 0x7F, 0x04, 0x08, 0x10, 0x7F, // 0x4E N + 0x3E, 0x41, 0x41, 0x41, 0x3E, // 0x4F O + 0x7F, 0x09, 0x09, 0x09, 0x06, // 0x50 P + 0x3E, 0x41, 0x51, 0x21, 0x5E, // 0x51 Q + 0x7F, 0x09, 0x19, 0x29, 0x46, // 0x52 R + 0x26, 0x49, 0x49, 0x49, 0x32, // 0x53 S + 0x03, 0x01, 0x7F, 0x01, 0x03, // 0x54 T + 0x3F, 0x40, 0x40, 0x40, 0x3F, // 0x55 U + 0x1F, 0x20, 0x40, 0x20, 0x1F, // 0x56 V + 0x3F, 0x40, 0x38, 0x40, 0x3F, // 0x57 W + 0x63, 0x14, 0x08, 0x14, 0x63, // 0x58 X + 0x03, 0x04, 0x78, 0x04, 0x03, // 0x59 Y + 0x61, 0x59, 0x49, 0x4D, 0x43, // 0x5A Z + 0x00, 0x7F, 0x41, 0x41, 0x41, // 0x5B [ + 0x02, 0x04, 0x08, 0x10, 0x20, // 0x5C '\' + 0x00, 0x41, 0x41, 0x41, 0x7F, // 0x5D ] + 0x04, 0x02, 0x01, 0x02, 0x04, // 0x5E ^ + 0x40, 0x40, 0x40, 0x40, 0x40, // 0x5F _ + 0x00, 0x03, 0x07, 0x08, 0x00, // 0x60 ` + 0x20, 0x54, 0x54, 0x78, 0x40, // 0x61 a + 0x7F, 0x28, 0x44, 0x44, 0x38, // 0x62 b + 0x38, 0x44, 0x44, 0x44, 0x00, // 0x63 c + 0x38, 0x44, 0x44, 0x28, 0x7F, // 0x64 d + 0x38, 0x54, 0x54, 0x54, 0x18, // 0x65 e + 0x00, 0x08, 0x7E, 0x09, 0x02, // 0x66 f + // 0x18, 0xA4, 0xA4, 0x9C, 0x78, // 0x67 g (old) + 0x0C, 0x52, 0x52, 0x4E, 0x3C, // 0x67 g + 0x7F, 0x08, 0x04, 0x04, 0x78, // 0x68 h + 0x00, 0x44, 0x7D, 0x40, 0x00, // 0x69 i + 0x20, 0x40, 0x40, 0x3D, 0x00, // 0x6A j + 0x7F, 0x10, 0x28, 0x44, 0x00, // 0x6B k + 0x00, 0x41, 0x7F, 0x40, 0x00, // 0x6C l + 0x7C, 0x04, 0x78, 0x04, 0x78, // 0x6D m + 0x7C, 0x08, 0x04, 0x04, 0x78, // 0x6E n + 0x38, 0x44, 0x44, 0x44, 0x38, // 0x6F o + 0xFC, 0x18, 0x24, 0x24, 0x18, // 0x70 p + // 0x18, 0x24, 0x24, 0x18, 0xFC, // 0x71 q (old) + 0x0C, 0x12, 0x12, 0x0C, 0x7E, // 0x71 q + 0x7C, 0x08, 0x04, 0x04, 0x08, // 0x72 r + 0x48, 0x54, 0x54, 0x54, 0x24, // 0x73 s + 0x04, 0x04, 0x3F, 0x44, 0x24, // 0x74 t + 0x3C, 0x40, 0x40, 0x20, 0x7C, // 0x75 u + 0x1C, 0x20, 0x40, 0x20, 0x1C, // 0x76 v + 0x3C, 0x40, 0x30, 0x40, 0x3C, // 0x77 w + 0x44, 0x28, 0x10, 0x28, 0x44, // 0x78 x + 0x0C, 0x10, 0x10, 0x50, 0x3C, // 0x79 y + 0x44, 0x64, 0x54, 0x4C, 0x44, // 0x7A z + 0x00, 0x08, 0x36, 0x41, 0x00, // 0x7B { + 0x00, 0x00, 0x7F, 0x00, 0x00, // 0x7C | + 0x00, 0x41, 0x36, 0x08, 0x00, // 0x7D } + 0x02, 0x01, 0x02, 0x04, 0x02, // 0x7E ~ + 0x3C, 0x26, 0x23, 0x26, 0x3C, // 0x7F house + 0x1E, 0xA1, 0xA1, 0x61, 0x12, // 0x81 + 0x3A, 0x40, 0x40, 0x20, 0x7A, // 0x81 ü + 0x38, 0x54, 0x54, 0x55, 0x59, // 0x82 + 0x21, 0x55, 0x55, 0x79, 0x41, // 0x83 + 0x21, 0x54, 0x54, 0x78, 0x41, // 0x84 ä + 0x21, 0x55, 0x54, 0x78, 0x40, // 0x85 + 0x20, 0x54, 0x55, 0x79, 0x40, // 0x86 + 0x0C, 0x1E, 0x52, 0x72, 0x12, // 0x87 + 0x39, 0x55, 0x55, 0x55, 0x59, // 0x88 + 0x39, 0x54, 0x54, 0x54, 0x59, // 0x89 + 0x39, 0x55, 0x54, 0x54, 0x58, // 0x8A + 0x00, 0x00, 0x45, 0x7C, 0x41, // 0x8B + 0x00, 0x02, 0x45, 0x7D, 0x42, // 0x8C + 0x00, 0x01, 0x45, 0x7C, 0x40, // 0x8D + 0xF0, 0x29, 0x24, 0x29, 0xF0, // 0x8E Ä + 0xF0, 0x28, 0x25, 0x28, 0xF0, // 0x8F + 0x7C, 0x54, 0x55, 0x45, 0x00, // 0x90 + 0x20, 0x54, 0x54, 0x7C, 0x54, // 0x91 + 0x7C, 0x0A, 0x09, 0x7F, 0x49, // 0x92 + 0x32, 0x49, 0x49, 0x49, 0x32, // 0x93 + 0x32, 0x48, 0x48, 0x48, 0x32, // 0x94 ö + 0x32, 0x4A, 0x48, 0x48, 0x30, // 0x95 + 0x3A, 0x41, 0x41, 0x21, 0x7A, // 0x96 + 0x3A, 0x42, 0x40, 0x20, 0x78, // 0x97 + 0x00, 0x9D, 0xA0, 0xA0, 0x7D, // 0x98 + //0x39, 0x44, 0x44, 0x44, 0x39, // 0x99 Ö (old) + 0x3D, 0x42, 0x42, 0x42, 0x3D, // 0x99 Ö + 0x3D, 0x40, 0x40, 0x40, 0x3D, // 0x9A Ü + 0x3C, 0x24, 0xFF, 0x24, 0x24, // 0x9B cent + 0x48, 0x7E, 0x49, 0x43, 0x66, // 0x9C pound + 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, // 0x9D + 0xFF, 0x09, 0x29, 0xF6, 0x20, // 0x9E + 0xC0, 0x88, 0x7E, 0x09, 0x03, // 0x9F + 0x20, 0x54, 0x54, 0x79, 0x41, // 0xA0 + 0x00, 0x00, 0x44, 0x7D, 0x41, // 0xA1 + 0x30, 0x48, 0x48, 0x4A, 0x32, // 0xA2 + 0x38, 0x40, 0x40, 0x22, 0x7A, // 0xA3 + 0x00, 0x7A, 0x0A, 0x0A, 0x72, // 0xA4 + 0x7D, 0x0D, 0x19, 0x31, 0x7D, // 0xA5 + 0x26, 0x29, 0x29, 0x2F, 0x28, // 0xA6 + 0x26, 0x29, 0x29, 0x29, 0x26, // 0xA7 + 0x30, 0x48, 0x4D, 0x40, 0x20, // 0xA8 + 0x38, 0x08, 0x08, 0x08, 0x08, // 0xA9 + 0x08, 0x08, 0x08, 0x08, 0x38, // 0xAA + 0x2F, 0x10, 0xC8, 0xAC, 0xBA, // 0xAB + 0x2F, 0x10, 0x28, 0x34, 0xFA, // 0xAC + 0x00, 0x00, 0x7B, 0x00, 0x00, // 0xAD exclamation mark upside down + 0x08, 0x14, 0x2A, 0x14, 0x22, // 0xAE less less + 0x22, 0x14, 0x2A, 0x14, 0x08, // 0xAF greater greater + 0xAA, 0x00, 0x55, 0x00, 0xAA, // 0xB0 + 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 0xB1 + 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 0xB2 + 0x00, 0x00, 0x00, 0xFF, 0x00, // 0xB3 + 0x10, 0x10, 0x10, 0xFF, 0x00, // 0xB4 + 0x14, 0x14, 0x14, 0xFF, 0x00, // 0xB5 + 0x10, 0x10, 0xFF, 0x00, 0xFF, // 0xB6 + 0x10, 0x10, 0xF0, 0x10, 0xF0, // 0xB7 + 0x14, 0x14, 0x14, 0xFC, 0x00, // 0xB8 + 0x14, 0x14, 0xF7, 0x00, 0xFF, // 0xB9 + 0x00, 0x00, 0xFF, 0x00, 0xFF, // 0xBA + 0x14, 0x14, 0xF4, 0x04, 0xFC, // 0xBB + 0x14, 0x14, 0x17, 0x10, 0x1F, // 0xBC + 0x10, 0x10, 0x1F, 0x10, 0x1F, // 0xBD + 0x14, 0x14, 0x14, 0x1F, 0x00, // 0xBE + 0x10, 0x10, 0x10, 0xF0, 0x00, // 0xBF + 0x00, 0x00, 0x00, 0x1F, 0x10, // 0xC0 + 0x10, 0x10, 0x10, 0x1F, 0x10, // 0xC1 + 0x10, 0x10, 0x10, 0xF0, 0x10, // 0xC2 + 0x00, 0x00, 0x00, 0xFF, 0x10, // 0xC3 + 0x10, 0x10, 0x10, 0x10, 0x10, // 0xC4 + 0x10, 0x10, 0x10, 0xFF, 0x10, // 0xC5 + 0x00, 0x00, 0x00, 0xFF, 0x14, // 0xC6 + 0x00, 0x00, 0xFF, 0x00, 0xFF, // 0xC7 + 0x00, 0x00, 0x1F, 0x10, 0x17, // 0xC8 + 0x00, 0x00, 0xFC, 0x04, 0xF4, // 0xC9 + 0x14, 0x14, 0x17, 0x10, 0x17, // 0xCA + 0x14, 0x14, 0xF4, 0x04, 0xF4, // 0xCB + 0x00, 0x00, 0xFF, 0x00, 0xF7, // 0xCC + 0x14, 0x14, 0x14, 0x14, 0x14, // 0xCD + 0x14, 0x14, 0xF7, 0x00, 0xF7, // 0xCE + 0x14, 0x14, 0x14, 0x17, 0x14, // 0xCF + 0x10, 0x10, 0x1F, 0x10, 0x1F, // 0xD0 + 0x14, 0x14, 0x14, 0xF4, 0x14, // 0xD1 + 0x10, 0x10, 0xF0, 0x10, 0xF0, // 0xD2 + 0x00, 0x00, 0x1F, 0x10, 0x1F, // 0xD3 + 0x00, 0x00, 0x00, 0x1F, 0x14, // 0xD4 + 0x00, 0x00, 0x00, 0xFC, 0x14, // 0xD5 + 0x00, 0x00, 0xF0, 0x10, 0xF0, // 0xD6 + 0x10, 0x10, 0xFF, 0x10, 0xFF, // 0xD7 + 0x14, 0x14, 0x14, 0xFF, 0x14, // 0xD8 + 0x10, 0x10, 0x10, 0x1F, 0x00, // 0xD9 + 0x00, 0x00, 0x00, 0xF0, 0x10, // 0xDA + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xDB + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, // 0xDC + 0xFF, 0xFF, 0xFF, 0x00, 0x00, // 0xDD + 0x00, 0x00, 0x00, 0xFF, 0xFF, // 0xDE + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, // 0xDF + 0x38, 0x44, 0x44, 0x38, 0x44, // 0xE0 alpha + 0x7C, 0x2A, 0x2A, 0x3E, 0x14, // 0xE1 beta / ß + 0x7E, 0x02, 0x02, 0x06, 0x06, // 0xE2 Gamma + 0x02, 0x7E, 0x02, 0x7E, 0x02, // 0xE3 Pi + 0x63, 0x55, 0x49, 0x41, 0x63, // 0xE4 Sigma + 0x38, 0x44, 0x44, 0x3C, 0x04, // 0xE5 sigma + 0x40, 0x7E, 0x20, 0x1E, 0x20, // 0xE6 mu + 0x06, 0x02, 0x7E, 0x02, 0x02, // 0xE7 tau + 0x99, 0xA5, 0xE7, 0xA5, 0x99, // 0xE8 Phi + 0x1C, 0x2A, 0x49, 0x2A, 0x1C, // 0xE9 theta + 0x4C, 0x72, 0x01, 0x72, 0x4C, // 0xEA Omega + 0x30, 0x4A, 0x4D, 0x4D, 0x30, // 0xEB delta + 0x30, 0x48, 0x78, 0x48, 0x30, // 0xEC phi? + 0xBC, 0x62, 0x5A, 0x46, 0x3D, // 0xED empty set + 0x3E, 0x49, 0x49, 0x49, 0x00, // 0xEE epsilon + 0x7E, 0x01, 0x01, 0x01, 0x7E, // 0xEF + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, // 0xF0 equivalence + 0x44, 0x44, 0x5F, 0x44, 0x44, // 0xF1 plus minus + 0x40, 0x51, 0x4A, 0x44, 0x40, // 0xF2 greater or equal + 0x40, 0x44, 0x4A, 0x51, 0x40, // 0xF3 less or equal + 0x00, 0x00, 0xFF, 0x01, 0x03, // 0xF4 + 0xE0, 0x80, 0xFF, 0x00, 0x00, // 0xF5 + 0x08, 0x08, 0x6B, 0x6B, 0x08, // 0xF6 divided by + 0x36, 0x12, 0x36, 0x24, 0x36, // 0xF7 approx + 0x06, 0x0F, 0x09, 0x0F, 0x06, // 0xF8 degree + 0x00, 0x00, 0x18, 0x18, 0x00, // 0xF9 big dot + 0x00, 0x00, 0x10, 0x10, 0x00, // 0xFA small dot + 0x30, 0x40, 0xFF, 0x01, 0x01, // 0xFB square root + 0x00, 0x1F, 0x01, 0x01, 0x1E, // 0xFC + 0x00, 0x19, 0x1D, 0x17, 0x12, // 0xFD power of two + 0x00, 0x3C, 0x3C, 0x3C, 0x3C, // 0xFE blob + //0x00, 0x00, 0x00, 0x60, 0x60, // 0xFF: Decimal point: 4 points + 0x00, 0x00, 0x00, 0x00, 0x40, // 0xFF: Decimal point, only 1 point +}; diff --git a/firmware/uniclock/ESP32_UniClock2/gps.ino b/firmware/uniclock/ESP32_UniClock2/gps.ino new file mode 100644 index 0000000..fdf2855 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/gps.ino @@ -0,0 +1,151 @@ +// ============================================================================ +// gps.ino - GPS-based time synchronization +// ---------------------------------------------------------------------------- +// Purpose : for STANDALONE clocks that have no WiFi/internet connectivity +// (see "//#define USE_GPS //use for standalone clock, without wifi +// internet access" in the parameter reference block at the top of +// ESP32_UniClock2.ino). Where WiFi IS available, the core program +// normally gets time from an NTP timeserver instead (see +// updateTimefromTimeserver() in ESP32_UniClock2.ino); GPS and WiFi +// time sync are not mutually exclusive, but GPS exists specifically +// to cover the no-internet case. +// Hardware: any generic NMEA-0183 serial GPS receiver module (e.g. Ublox +// NEO-6M/NEO-8M or similar). Only the module's TX output needs to +// be wired to the MCU's RX pin - we only ever READ NMEA sentences +// from the module, we never send configuration commands to it, so +// TX/TXPin on the MCU side is intentionally unused (-1). +// Library : TinyGPS++ (TinyGPSPlus) - see libraries/TinyGPSPlus-1.0.2.zip. +// It incrementally parses NMEA sentences (GPRMC/GPGGA/... checksum +// validated) character by character via gps.encode(), and exposes +// decoded fields (time, date, location, etc.) with isValid()/ +// isUpdated() freshness flags. +// Wiring : ESP8266 -> SoftwareSerial on GPIO3 (RX only, bit-banged, no HW +// UART needed). GPIO3 is also the chip's native +// RXD0/USB-serial pin, so the GPS wire must be +// disconnected while flashing firmware. +// ESP32 -> HardwareSerial(1) on GPIO25 (RX only). +// Used by : CLOCK_1 (ESP8266, IN-16 Nixie tubes) and CLOCK_13 (ESP8266, +// IV-16 Numitron tubes) in clocks.h have USE_GPS active by +// default. CLOCK_50, CLOCK_71, CLOCK_80/81/82 and CLOCK_99 carry +// a commented-out "//#define USE_GPS" as an optional switch. +// Time : GPS always provides time in UTC. This file converts to local +// time itself using a flat prm.utc_offset/enableDST arithmetic +// offset (see getGPS() below) - unlike the WiFi/NTP path in +// ESP32_UniClock2.ino, which uses the Timezone library (myTZ/ +// myDST/mySTD) to resolve the exact calendar-based DST transition +// date. See the note near the setTime() calls below. +// ============================================================================ +#ifdef USE_GPS + +#include // Tiny GPS Plus Library + +#if defined(ESP8266) + #include "SoftwareSerial.h" // Software Serial Library so we can use other Pins for communication with the GPS module + static int RXPin = 3; //RX pin - remove wire, when uploading program to 8266 + static int TXPin = -1; //not used + SoftwareSerial ss(RXPin, TXPin); +#else + #include //on ESP32 no SoftwareSerial is needed + static int RXPin = 25; //RX pin + static int TXPin = -1; //not used (we only read the GPS, never write to it, so TX is not wired/used) + HardwareSerial ss(1); +#endif + +// The serial connection to the GPS device +static uint32_t GPSBaud = 9600; // Ublox GPS default Baud Rate is 9600 (NMEA default for most modules; if a module was reconfigured to a different baud rate, this must be changed to match, otherwise all sentences will fail checksum validation) +TinyGPSPlus gps; // Create an Instance of the TinyGPS++ object called gps + +void setupGPS() { + delay(1500); //give the GPS module time to power up / boot before we start talking to it + DPRINTLN("Starting GPS..."); + if (RXPin>=0) regPin(RXPin,"RXPin"); //bookkeeping only (for the pin-usage diagnostics/webpage), doesn't affect GPS operation + if (TXPin>=0) regPin(TXPin,"TXPin"); //TXPin is -1 (unused) on both platforms, so this branch never actually runs + #if defined(ESP8266) + ss.begin(GPSBaud); + //ss.begin(GPSBaud, SWSERIAL_8N1, RXPin, TXPin, false); + #else + ss.begin(GPSBaud, SERIAL_8N1, RXPin); // ESP32 HardwareSerial::begin(baud, config, rxPin, txPin=-1) - txPin left at its default (-1/unused) since we don't transmit to the GPS + #endif + delay(200); + GPSexist = true; // POSSIBLE BUG / design note: this is set unconditionally right after opening the serial port, regardless of whether a GPS module is actually attached, powered, or has ever produced a valid NMEA sentence. calcTime() uses GPSexist to decide whether to call getGPS() every cycle; if no module is present this flag stays true forever and getGPS() will just keep returning false harmlessly, but GPSexist does NOT mean "GPS is present/working", only "USE_GPS was compiled in and setupGPS() ran". Compare with how RTCexist is determined elsewhere (presumably via a presence check) for a stricter pattern. + smartDelay(500); //feed the parser for 500ms so gps.encode() has a chance to see the first sentences before we try to read anything from it + getGPS(); +} + +boolean getGPS(){ +static unsigned long lastRun = 0; +boolean res = false; + + // Throttle: only attempt a refresh once every 10s. lastRun==0 lets the very first call + // (from setupGPS()) through immediately without waiting for the first 10s window. + if ((lastRun!=0) && (millis() - lastRun) < 10000) //refresh rate + return(res); + + lastRun = millis(); + smartDelay(500); //block for 500ms while feeding gps.encode(), giving the parser a chance to complete a fresh RMC/GGA/ZDA sentence since the last call + // NOTE: this checks gps.time.isValid()/isUpdated() (sentence was parsed correctly and is new since last read), + // but deliberately does NOT require gps.location.isValid() or any fix-quality/satellite-count check. + // Many GPS chipsets can decode time/date from the almanac/ephemeris before achieving a full position fix, + // so this allows time sync slightly earlier than a location fix would. It does mean the reported time could + // in theory be used even with a very weak/no fix, as long as the receiver has parsed a valid time field. + // gps.date.isUpdated() is intentionally/incidentally not checked here - time and date normally arrive + // together in the same NMEA sentence (e.g. RMC), so checking only time.isUpdated() is normally sufficient. + if (gps.time.isValid() && gps.date.isValid() && gps.time.isUpdated()) { + if (gps.date.year()>2020) { //sanity check: many GPS modules report a bogus default date (e.g. from firmware build date or epoch start) until they have decoded a real almanac; reject anything before this fixed threshold. NOTE: this threshold is a fixed constant and will need to be bumped periodically as time passes. + DPRINTLN("Time refreshed from GPS"); + setTime(gps.time.hour(),gps.time.minute(),gps.time.second(),gps.date.day(),gps.date.month(),gps.date.year()); //set the time (hr,min,sec,day,mnth,yr) -- GPS time is always UTC, so system time is UTC at this point + // Convert UTC -> local time. NOTE: unlike updateTimefromTimeserver() in ESP32_UniClock2.ino (which builds a + // Timezone object from myDST/mySTD TimeChangeRule's and lets the Timezone library pick the correct offset + // for the current calendar date, i.e. it auto-detects whether "today" falls inside the DST period), this GPS + // path just adds a flat prm.utc_offset hour, plus one more hour if prm.enableDST is currently true - it does + // NOT look at gps.date to determine whether the DST period has actually started/ended this year. Since + // enableDST is only ever changed manually (web UI / MQTT, see key=="enableDST" handling in + // ESP32_UniClock2.ino) and is never auto-toggled by date, a standalone GPS-only clock (no WiFi to run the + // calendar-aware Timezone conversion) will NOT automatically switch to/from summer time on the correct + // calendar day unless the user (or some other mechanism) flips enableDST at the right moment. + setTime(now()+(prm.utc_offset + (prm.enableDST ? 1 : 0)) * 3600); + lastTimeUpdate = millis(); + } + #ifdef DEBUG + printGPS(); + #endif + + res = true; + } //endif valid date&time + return (res); +} + +void printGPS() { // debug-only helper (only called when DEBUG is defined, see getGPS() above); values printed here are NOT freshness-checked, so location/satellites may be stale or invalid if no fix has ever been acquired + DPRINT("Latitude : "); DPRINT(gps.location.lat(), 5); DPRINT(" Longitude : "); DPRINTLN(gps.location.lng(), 4); + DPRINT("Satellites: "); DPRINT(gps.satellites.value()); DPRINT(" Elevation : "); DPRINT(gps.altitude.feet()); DPRINTLN("ft"); + DPRINT("GPS Time UTC : "); DPRINT(gps.time.hour()); // GPS time UTC + DPRINT(":"); DPRINT(gps.time.minute()); // Minutes + DPRINT(":"); DPRINTLN(gps.time.second()); // Seconds +} + + +// This custom version of delay() ensures the TinyGPS++ object keeps being "fed" bytes while we're blocked. +// TinyGPSPlus has no internal buffering of its own: gps.encode() must be called for every single byte read from +// the serial port, or that byte's contribution to the current NMEA sentence is lost. The GPS module streams NMEA +// sentences continuously (typically once per second) regardless of whether anyone is listening, and the serial +// RX FIFO (HW UART on ESP32, or the SoftwareSerial ring buffer on ESP8266) is small, so if this isn't called +// often enough for long enough, incoming bytes get silently dropped/overwritten and whole sentences are lost - +// which is exactly why Fdelay() in ESP32_UniClock2.ino calls smartDelay(1) on every iteration of its busy-loop +// (see "feed GPS" there): it's not really about producing a 1ms delay, it's about draining ss.available() often +// enough during any other blocking/animation work so the GPS UART buffer never has a chance to overflow. +void smartDelay(unsigned long ms) { + unsigned long start = millis(); + do { + while (ss.available()) + gps.encode(ss.read()); + } while (millis() - start < ms); +} + +//------------------------------------------------------------------------------------------ + +#else +// USE_GPS not defined for this CLOCK_NN config: provide no-op stand-ins so the rest of the core program +// (setup()/calcTime() in ESP32_UniClock2.ino) can call setupGPS()/getGPS() unconditionally without more #ifdef's. +void setupGPS() {GPSexist = false;} +boolean getGPS() {return(false);} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/mqtt.ino b/firmware/uniclock/ESP32_UniClock2/mqtt.ino new file mode 100644 index 0000000..0ec4c64 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/mqtt.ino @@ -0,0 +1,306 @@ +#ifdef USE_MQTT +//========================================================================================================================= +// mqtt.ino - Home Assistant / MQTT integration (compiled only if USE_MQTT is defined for the active CLOCK_NN in clocks.h) +// +// Library: ArduinoHA (https://github.com/dawidchyrzynski/arduino-home-assistant), which wraps PubSubClient +// (https://pubsubclient.knolleary.net/) and adds Home Assistant MQTT Discovery on top of it. Because ArduinoHA +// is used, this file never hand-builds HA discovery JSON payloads itself - HADevice/HAMqtt/HASensor below +// publish the discovery config topics automatically the first time mqtt.begin()/mqtt.loop() connects. +// +// MASTER / SLAVE sensor-sharing architecture (the distinctive part of this subsystem): +// Every clock that defines USE_MQTT connects to the same broker (prm.mqttBrokerAddr/User/Psw, set on the web UI) and +// PUBLISHES its own local sensors (temperature/humidity/pressure/lux/radar, whichever are physically present - see +// HASensor objects below) under a "/sensor//..." style topic, discovered via HA MQTT Discovery. +// A clock that does NOT have its own sensor (or wants to reuse another clock's reading) can instead be built with +// USE_MASTER_CLOCK, and SUBSCRIBE to another clock's already-published topics: +// USE_MASTER_TEMP + MASTER_TEMPERATURE_TOPIC -> mirrors the master's temperature into local temperature[] array +// USE_MASTER_HUMID + MASTER_HUMIDITY_TOPIC -> mirrors the master's humidity into local humid[] array +// USE_MASTER_RADAR + MASTER_RADAR_TOPIC -> mirrors the master's PIR/radar presence state into mqttRadarON +// This lets one "master" clock's physical sensor (e.g. a dedicated sensor box, or a clock with a real radar/PIR +// sensor wired to RADAR_PIN) feed several "slave" clocks that have no sensor of their own, purely over MQTT. +// IMPORTANT: if a "slave" clock DOES also have its own local RADAR_PIN wired, the local pin always wins - the radar +// topic subscription is skipped entirely in onMqttConnected() (see RADAR_PIN>=0 check there), and the core loop's +// checkTubePowerOnOff() also prefers digitalRead(RADAR_PIN) over the MQTT-derived mqttRadarON when both exist. +// As of the current clocks.h: CLOCK_99 ("Dummy clock, sensor box with MQTT connector") is a pure MASTER/publisher +// (USE_MQTT on, USE_MASTER_CLOCK off, has its own RADAR_PIN). CLOCK_26 has USE_MQTT + USE_MASTER_CLOCK + +// USE_MASTER_RADAR all on, so it both publishes its own temperature/humidity sensors AND subscribes to another +// clock's radar topic. CLOCK_23/CLOCK_23A define the USE_MASTER_* macros too but currently have USE_MQTT commented +// out in clocks.h, so for those configs this whole file is compiled out and the master settings are presently inert. +// +// Call pattern from the core .ino: setupMqtt() is called once from setup() when USE_MQTT is defined; mqttSend() is +// called every loop() iteration (but only while WiFi is connected and prm.mqttEnable is set) - it internally rate-limits +// actual publishes to once every prm.mqttBrokerRefresh seconds, while still pumping mqtt.loop() (which also drives +// PubSubClient's/ArduinoHA's own reconnect handling) on every call. +//_______________________________________________________________________________________________________________________ +//To use MQTT services the following settings should be defined in clocks.h +//#define USE_MQTT +//#define MQTT_PREFIX "UniClock32" +//#define USE_MASTER_CLOCK //enable it, if you want to get any data from MASTER CLOCK. This will be the sensor#0 +//#define USE_MASTER_TEMP //enable it, if you want to get temperature from MASTER CLOCK +//#define USE_MASTER_HUMID //enable it, if you want to get humidity from MASTER CLOCK +//#define USE_MASTER_RADAR //enable it, if you want to get radar from MASTER CLOCK +//#define MASTER_TEMPERATURE_TOPIC "homeassistant/sensor/10521c5e14c4/temperature/state" +//#define MASTER_HUMIDITY_TOPIC "homeassistant/sensor/10521c5e14c4/humidity/state" +//#define MASTER_RADAR_TOPIC "homeassistant/sensor/10521c5e14c4/radar/state" +//_______________________________________________________________________________________________________________________ +// (the block above is documentation only, shown here as a template of what to copy into a CLOCK_NN section of clocks.h - +// none of these #define lines are active in this file; the real values come from clocks.h at compile time.) + +//To subscribe to my sensors: You can check it with Chrome browser + MQTTlens addon) MAC_ADDRESS: for example: 3e090b3e090b +// homeassistant/sensor/3e090b3e090b/temperature/value +// homeassistant/sensor/3e090b3e090b/humidity/value +// homeassistant/sensor/3e090b3e090b/pressure/value +// homeassistant/sensor/3e090b3e090b/lux/value + +// old solution, now moved to prm.mqttBrokerAddr, available on clock's web page +//#define BROKER_ADDR IPAddress(192,168,1,241) +//#define BROKER_USERNAME "" //moved to prm.mqttBrokerUser +//#define BROKER_PASSWORD "" //moved to mqttBrokerPsw +//_______________________________________________________________________________________________________________________ +// Credential handling: broker IP/user/password are NOT compile-time constants (see the commented-out BROKER_* macros +// above, kept only as historical documentation) - they live in prm.mqttBrokerAddr/mqttBrokerUser/mqttBrokerPsw, which +// are persisted settings editable from the clock's own web UI (and read here in setupMqtt()/mqttSend()). This lets a +// user point any already-flashed clock at a different broker without recompiling. +// SECURITY: factoryReset() in the core .ino seeds these with a hardcoded default +// (strcpy(prm.mqttBrokerAddr,"10.10.0.202"); strcpy(prm.mqttBrokerUser,"mqtt"); strcpy(prm.mqttBrokerPsw,"mqtt")) - +// i.e. a real broker IP and a trivial user/password baked into the firmware as the out-of-the-box default. Not this +// file's code, but directly relevant here since it is exactly the credential pair mqttSend()/setupMqtt() use below. + +#include //https://github.com/dawidchyrzynski/arduino-home-assistant +//https://pubsubclient.knolleary.net/ https://github.com/knolleary/pubsubclient + +#if !defined(MQTT_PREFIX) + #define MQTT_PREFIX "UniClock32" //used both as the HA discovery prefix and as the first path segment of this device's own published topics +#endif + +byte mac[6]; //this device's WiFi MAC, used as a per-device unique ID/topic segment (see setupMqtt()) +char masterTemperatureTopic[80] = ""; //resolved from MASTER_TEMPERATURE_TOPIC (clocks.h) if USE_MASTER_CLOCK/USE_MASTER_TEMP is used - topic this clock SUBSCRIBES to +char masterHumidityTopic[80] = ""; //resolved from MASTER_HUMIDITY_TOPIC (clocks.h) if USE_MASTER_CLOCK/USE_MASTER_HUMID is used - topic this clock SUBSCRIBES to +char masterRadarTopic[80] = ""; //resolved from MASTER_RADAR_TOPIC (clocks.h) if USE_MASTER_CLOCK/USE_MASTER_RADAR is used - topic this clock SUBSCRIBES to + +// Index into the shared temperature[]/humid[] arrays (declared in the core .ino, up to 6 slots) reserved for the +// MASTER clock's mirrored reading, so the rest of the firmware (display rotation, web UI, etc.) can treat the +// MQTT-sourced master reading exactly like one more local sensor. Assigned in setupMqtt() as "next free slot" +// (useTemp/useHumid BEFORE incrementing) - despite the doc comment above saying "this will be the sensor#0", it is +// only actually sensor#0 if this clock has no other local temperature/humidity sensor already counted. +byte MASTERtempPtr=0; +byte MASTERhumidPtr=0; + +WiFiClient client; +HADevice device; +HAMqtt mqtt(client, device); //ArduinoHA wrapper around PubSubClient; mqtt.loop() also drives PubSubClient's own reconnect/keepalive handling +//Sensors of local clock, to send data (each one auto-publishes its HA MQTT Discovery config the first time it connects): +HASensor mqttTemp("temperature"); +HASensor mqttHumid("humidity"); +HASensor mqttPress("pressure"); +HASensor mqttLux("lux"); +HASensor mqttRadar("radar"); + +// This is the SLAVE side of the master/slave architecture: called by ArduinoHA/PubSubClient whenever a message +// arrives on any topic this clock is subscribed to (see onMqttConnected() below for what gets subscribed - only +// the MASTER_* topics selected via USE_MASTER_TEMP/USE_MASTER_HUMID/USE_MASTER_RADAR in clocks.h). The payload is +// the raw value published by the MASTER clock's own mqttSend()/HASensor.setValue() (a plain ASCII number for +// temperature/humidity, "0"/"1" for radar - matching HASensor's default text encoding). +void onMqttMessage(const char* topic, const uint8_t* payload, uint16_t length) { + float tempTMP,humidTMP; + char tmp[100]; + // This callback is called when message from MQTT broker is received. + // Please note that you should always verify if the message's topic is the one you expect. + // For example: if (memcmp(topic, "myCustomTopic") == 0) { ... } + + DPRINT("New message on topic: "); DPRINT(topic); + + // POSSIBLE BUG: `length` (the broker-reported payload size) is copied into the fixed 100-byte `tmp` buffer with + // no bounds check - a payload of 100 bytes or more (e.g. a misconfigured/malicious publisher on the same topic) + // overflows `tmp` on the stack. Also, memcpy() does NOT null-terminate `tmp`: it is used afterwards as a C string + // (DPRINTLN(tmp), atof(tmp), tmp[0]) with whatever byte happens to follow the copied data in memory, which is + // undefined behavior if that byte isn't already '\0'. A `if (length >= sizeof(tmp)) length = sizeof(tmp)-1;` + // guard plus `tmp[length] = '\0';` after the memcpy would fix both issues. + memcpy(tmp,payload,length); + DPRINT(" Data: "); DPRINTLN(tmp); + // Topic match: compares up to sizeof(masterTemperatureTopic) (80, the fixed buffer size) bytes rather than the + // actual topic length. This still behaves like strcmp() in practice because strncmp() stops as soon as it hits + // the '\0' terminator on either side, but it is fragile - if MASTER_TEMPERATURE_TOPIC in clocks.h were ever 80 + // characters or longer, strncpy() in setupMqtt() would not null-terminate masterTemperatureTopic, and this + // comparison could silently misbehave. Using strcmp(topic, masterTemperatureTopic) would be more robust. + if ((strlen(masterTemperatureTopic)>0) && (strncmp(topic,masterTemperatureTopic,sizeof(masterTemperatureTopic)) ==0)) { + tempTMP = atof(tmp); + temperature[MASTERtempPtr] = tempTMP; //overwrite the reserved "master" slot in the shared temperature[] array (see MASTERtempPtr above) + DPRINT("MASTER clock's temperature:"); DPRINTLN(tempTMP); + } + if ((strlen(masterHumidityTopic)>0) && (strncmp(topic,masterHumidityTopic,sizeof(masterHumidityTopic)) ==0)) { + humidTMP = atof(tmp); + DPRINT("MASTER clock's humidity:"); DPRINTLN(humidTMP); + humid[MASTERhumidPtr] = humidTMP; //overwrite the reserved "master" slot in the shared humid[] array (see MASTERhumidPtr above) + } + if ((strlen(masterRadarTopic)>0) && (strncmp(topic,masterRadarTopic,sizeof(masterRadarTopic)) ==0)) { + // Master clock's radar/PIR presence state, mirrored into mqttRadarON (and radarON) so the rest of the + // firmware (night animation, tube power on/off, etc.) reacts to it exactly like a local sensor reading. + // We only ever get here if this clock did NOT subscribe to the radar topic due to having its own local + // RADAR_PIN (see the RADAR_PIN>=0 guard in onMqttConnected() below) - local hardware always wins. + DPRINT("MASTER radar:"); DPRINTLN(tmp[0]); + if (tmp[0] == '0') { + mqttRadarON = false; + radarON = mqttRadarON; + } + else { + mqttRadarON = true; + radarON = mqttRadarON; + radarLastOn = millis(); + } + DPRINT("mqttRadarON:"); DPRINTLN(mqttRadarON); + + } + //mqtt.publish("myPublishTopic", "hello"); +} + +// Called by ArduinoHA once the broker connection (re)establishes - this is where the SLAVE side actually subscribes +// to the MASTER clock's topics. Runs again on every reconnect (not just the first connect), so subscriptions survive +// broker restarts/WiFi drops without any extra logic needed in mqttSend(). +void onMqttConnected() { + DPRINTLN("Connected to the broker!"); + // You can subscribe to custom topic if you need + #ifdef USE_MASTER_CLOCK + DPRINTLN(F("MASTER_CLOCK enabled! Subscribe to following topics:")); + #ifdef USE_MASTER_TEMP + if (strlen(masterTemperatureTopic)==0) { + DPRINTLN("Please define MASTER_TEMPERATURE_TOPIC in clocks.h'"); + } + else { + DPRINT(F("Master temperature topic:")); DPRINTLN(masterTemperatureTopic); + mqtt.subscribe(masterTemperatureTopic); + } + #endif + #ifdef USE_MASTER_HUMID + if (strlen(masterHumidityTopic)==0) { + DPRINTLN("Please define MASTER_HUMIDITY_TOPIC in clocks.h'"); + } + else { + DPRINT(F("Master humidity topic:")); DPRINTLN(masterHumidityTopic); + mqtt.subscribe(masterHumidityTopic); + } + #endif + #ifdef USE_MASTER_RADAR + // WHY: if this clock has its own physical radar/PIR sensor wired to RADAR_PIN, that local reading always + // takes priority (see checkTubePowerOnOff() in the core .ino, which prefers digitalRead(RADAR_PIN) over + // mqttRadarON when both exist). So there is no point subscribing to the master's radar topic at all in that + // case - onMqttMessage()'s radar branch would just be mirrored into a value nothing ever reads. + if (RADAR_PIN>=0) { + DPRINTLN(F("Local radar enabled! MQTT MASTER radar disabled!")); + } + else { + if (strlen(masterRadarTopic)==0) { + DPRINTLN("Please define MASTER_RADAR_TOPIC in clocks.h'"); + } + else { + DPRINT(F("Master radar topic:")); DPRINTLN(masterRadarTopic); + mqtt.subscribe(masterRadarTopic); + } + } + #endif + #endif +} + +void setupMqtt() { + char mactmp[20]; + + WiFi.macAddress(mac); + device.setUniqueId(mac, sizeof(mac)); //set device MAC, as unique ID + sprintf(mactmp,"%02x%02x%02x%02x%02x%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); + #if defined(MASTER_TEMPERATURE_TOPIC) + strncpy(masterTemperatureTopic,MASTER_TEMPERATURE_TOPIC,sizeof(masterTemperatureTopic)); + #endif + #if defined(MASTER_HUMIDITY_TOPIC) + strncpy(masterHumidityTopic,MASTER_HUMIDITY_TOPIC,sizeof(masterHumidityTopic)); + #endif + #if defined(MASTER_RADAR_TOPIC) + strncpy(masterRadarTopic,MASTER_RADAR_TOPIC,sizeof(masterRadarTopic)); + #endif + DPRINT("Starting MQTT CLient: "); DPRINT(MQTT_PREFIX); DPRINT("/sensor/"); DPRINTLN(mactmp); + + mqtt.setDiscoveryPrefix(MQTT_PREFIX); + device.setName(FW); + device.setSoftwareVersion("3.0"); + if (useTemp>0) { + mqttTemp.setUnitOfMeasurement("°C"); + mqttTemp.setDeviceClass("sensor"); + mqttTemp.setName("Inside temperature"); + } + if (useHumid>0) { + mqttHumid.setUnitOfMeasurement("%"); + mqttHumid.setDeviceClass("sensor"); + mqttHumid.setName("Inside humidity"); + } + if (usePress>0) { + mqttPress.setUnitOfMeasurement("hPa"); + mqttPress.setDeviceClass("sensor"); + mqttPress.setName("Air pressure"); + } + if (useLux>0) { + mqttLux.setUnitOfMeasurement("lux"); + mqttLux.setDeviceClass("sensor"); + mqttLux.setName("Inside lux"); + } + #if RADAR_PIN>=0 + mqttRadar.setUnitOfMeasurement("on/off"); + mqttRadar.setDeviceClass("sensor"); + mqttRadar.setName("Radar sensor"); + #endif + + mqtt.onMessage(onMqttMessage); + mqtt.onConnected(onMqttConnected); + + IPAddress tmp; + tmp.fromString(String(prm.mqttBrokerAddr)); + if (strlen(prm.mqttBrokerUser)==0) + mqtt.begin(tmp); + else + mqtt.begin(tmp, prm.mqttBrokerUser, prm.mqttBrokerPsw); + + //mqtt.begin(BROKER_ADDR, BROKER_USERNAME, BROKER_PASSWORD); + + device.enableLastWill(); + #ifdef USE_MASTER_CLOCK + DPRINTLN("MQTT MASTER_CLOCK enabled!"); + DPRINT("MASTER_TEMPERATURE_TOPIC:"); DPRINTLN(masterTemperatureTopic); + DPRINT("MASTER_HUMIDITY_TOPIC:"); DPRINTLN(masterHumidityTopic); + DPRINT("MASTER_RADAR_TOPIC:"); DPRINTLN(masterRadarTopic); + #ifdef USE_MASTER_TEMP + DPRINTLN(" - MQTT MASTER_TEMP sensor added!"); + MASTERtempPtr = useTemp; //remember my ID-s + useTemp++; //increase the number of sensors + #endif + #ifdef USE_MASTER_HUMID + DPRINTLN(" - MQTT MASTER_HUMID sensor added!"); + MASTERhumidPtr = useHumid; + useHumid++; + #endif + #endif +} + +void mqttSend() { + static unsigned long lastRun = millis(); + //if (!mqtt.isConnected()) return; + mqtt.loop(); + + if ((millis() - lastRun) < prm.mqttBrokerRefresh*1000) return; + lastRun = millis(); + if (mqtt.isConnected()) DPRINT("MQTT Connected."); + else { + DPRINTLN("MQTT Failed to connect."); + return; + } + DPRINT("MQTT send:"); + if (useTemp>0) {mqttTemp.setValue(temperature[0]+ prm.corrT0); DPRINT(temperature[0]+ prm.corrT0); DPRINT(", ");} + if (useHumid>0) {mqttHumid.setValue(humid[0]+ prm.corrH0); DPRINT(humid[0]+ prm.corrH0); DPRINT(", ");} + if (usePress>0) {mqttPress.setValue(pressur[0]); DPRINT(pressur[0]); DPRINT(", ");} + if (useLux>0) {mqttLux.setValue(lx); DPRINT(lx); DPRINT(", ");} + #if RADAR_PIN>=0 + mqttRadar.setValue(radarON); DPRINT(radarON); + #endif + DPRINTLN(" "); +} + +#else + void mqttSend() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/mulev2.ino b/firmware/uniclock/ESP32_UniClock2/mulev2.ino new file mode 100644 index 0000000..868ac1a --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/mulev2.ino @@ -0,0 +1,260 @@ +// ============================================================================ +// MULE_V2 driver +// +// Drives the "Mule V2" ESP32 board: 6 x SA40 7-segment LED digit modules, +// used by CLOCK_34 ("Mule V2 ESP32, board, 6 x SA40 LED Display", see +// clocks.h around line 972). This is a BCD-multiplexed 7-segment LED clock, +// NOT a Nixie/VFD tube driver, even though several comments/variable names +// below (DP_PIN, "Nixie clock", tube terminology) are inherited boilerplate +// copied from the tube-driver files this codebase is built from. +// +// Hardware / wiring protocol (see clocks.h CLOCK_34 block for actual pins): +// - ABCDPins[4] -> CD4543 BCD-to-7-segment latch/decoder/driver IC. +// 4 bits carry the digit VALUE (0-9) for whichever +// digit position is currently selected; the 4543 +// decodes it to segments and drives the LED digit +// directly (latching, so no extra data hold time is +// required). +// - digitEnablePins[3] -> CD4028 BCD-to-Decimal decoder ("A,B,C" inputs). +// Only 3 bits are used (not per-digit enable lines!) +// to binary-encode which ONE of the 6 SA40 digits +// is active right now; the 4028's decoded output +// lines 1-6 are wired to the 6 digit commons, output +// 0 and 7 are intentionally left unconnected and are +// used as a "blank all digits" state (see writeDisplay()). +// - This is a classic BCD multiplex scheme: only one digit is illuminated +// at any instant, cycling fast enough (via hardware timer ISR) that +// persistence of vision makes all 6 digits appear lit simultaneously. +// - Digit brightness is done by PWM-ing the "on" time of each digit within +// its multiplex slot (see PWMrefresh/PWM_min/PWM_max and PWMtimeBrightness +// below), not by varying LED current. +// +// Board also has optional peripherals declared in clocks.h but NOT touched +// by this file directly: NeoPixel backlight (USE_NEOPIXEL/NEOPIXEL_PIN) and +// a Dallas temperature sensor pin - both handled by the core .ino. +// ============================================================================ +#ifdef MULE_V2 +char tubeDriver[] = "MULE_V2"; + +//define here the digit enable pins from 4 to 8 + +//byte digitEnablePins[] = {15,13,12,14}; //for example... But SET in clocks.h !!! +//byte ABCDPins[4] = {2,4,5,0}; +//int DP_PIN = -1; // decimalPoint inside Nixie tube, set -1, if not used! + +int maxDigits = 6; //sizeof(digitEnablePins); +// NOTE: maxDigits (6) is the number of physical SA40 digits, NOT the size of +// digitEnablePins (which is only 3 - see CLOCK_34 in clocks.h: {5,16,23}). +// digitEnablePins holds the 3 CD4028 select lines, so its correct element +// count is always 3 regardless of maxDigits. Do not assume they are equal +// (setup_pins() below used to get this wrong - now fixed, see the note there). +int DRAM_ATTR maxDig = maxDigits; //memory variable version +// Local DRAM_ATTR copy of maxDigits, used inside the IRAM_ATTR ISR +// (writeDisplay). Keeping a dedicated DRAM-resident copy guarantees the +// value stays reachable from the ISR even in situations where flash-cache +// backed data access would be unsafe (e.g. during flash/EEPROM writes). + +//byte convert[] = {1,0,9,8,7,6,5,4,3,2}; //tube pin conversion, if needed (e.g. for a non-standard tube pin layout) +int DRAM_ATTR PWMrefresh=11000; //msec, Multiplex time period. Greater value => slower multiplex frequency +int DRAM_ATTR PWM_min = 1000; +int DRAM_ATTR PWM_max = 10000; +//int DRAM_ATTR PWMtiming[11] = {0,1000,2000,3000,4000,5000,6000,7000,8000,9000,10000}; +// PWMrefresh is the total time budget (in hardware-timer ticks) for one full +// digit's multiplex slot: part of it is "digit ON" (length set by +// PWMtimeBrightness, scaled between PWM_min/PWM_max according to +// brightness/ambient light) and the remainder is "digit OFF" (blanking), see +// case 2 in writeDisplay(). This is how per-digit brightness/dimming is +// achieved without a dedicated PWM peripheral per digit. + + #if defined(ESP32) + #else + #error "Only ESP32 board is supported by this driver!" + #endif + +void setup_pins() { + char tmp[30]; + + DPRINTLN("Nixie clock - setup pins - Multiplex BCD mode (MULE_V2)..."); + // "Nixie clock" text is inherited boilerplate from the tube-driver + // templates this file was cloned from; this board drives LED digits. + DPRINTLN("BCD digitEnablePins: "); + // FIXED (was "i=0 + pinMode(DP_PIN,OUTPUT); + DPRINT("DP_PIN:"); DPRINTLN(DP_PIN); + #endif + startTimer(); // arms the hardware timer that drives writeDisplay() as an ISR +} + +void IRAM_ATTR writeDisplay(){ //void IRAM_ATTR writeDisplay(){ +// Timer-interrupt multiplex refresh handler. Runs once per "phase" of a +// single digit's multiplex slot (see state machine below) and re-arms the +// hardware timer itself with the next phase's duration - i.e. this is a +// self-rescheduling one-shot timer, not a fixed-period tick. Because it is +// IRAM_ATTR it (and everything it touches, hence the DRAM_ATTR statics) must +// not depend on code/data that lives in flash, since it can fire while the +// SPI flash cache is disabled. + + static DRAM_ATTR byte pos = 0; + static DRAM_ATTR byte state=0; + static DRAM_ATTR int timer = PWMrefresh; + static DRAM_ATTR int num,brightness; + static DRAM_ATTR byte DPpos; + static DRAM_ATTR int PWMtimeBrightness=PWM_min; + + if (EEPROMsaving) { //stop refresh while an EEPROM write is in progress! + return; + } + + portENTER_CRITICAL_ISR(&timerMux); + noInterrupts(); + + intCounter++; + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + timer = PWMrefresh; + + switch (state) { //state machine... + // 4-phase cycle per digit position, advancing `pos` only in case 0: + // 0: decide which digit to show next (advance pos, recompute + // brightness once per full 6-digit sweep) and pick ON-phase timing + // 1: (only reached when animMask[pos]>0, i.e. a digit-change animation + // is in progress) show the NEW character for the remaining + // fraction of the ON time, crossfading old->new digit by splitting + // the ON slot into two sub-phases weighted by animMask (0..20) + // 2: blank phase - digit output stays on the last-set value but the + // remaining PWMrefresh budget elapses with the digit dark, giving + // the dimming effect + // 3: sentinel reached via case 2's fallthrough-by-timer only, forces + // the blank branch below and resets state to 0 next call + case 0: + pos++; if (pos>maxDig-1) { + pos = 0; //go to the first tube + if (autoBrightness && displayON) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); //ambient-light-driven auto brightness + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); //manual day/night brightness setting + } + if (animMask[pos] > 0) { //Animation? + num = oldDigit[pos]; //show old character + timer = (PWMtimeBrightness * (20-animMask[pos]))/20; + state = 1; //next state is: show newDigit + } + else { + num = digit[pos]; //show active character + timer = PWMtimeBrightness; + state = 2; //next state is: BLANK display + } + break; + case 1: //show new character, if animation + num = newDigit[pos]; + timer = (PWMtimeBrightness * animMask[pos])/20; + state = 2; //default next state is: BLANK display + break; + case 2: //blank display + timer = PWMrefresh-PWMtimeBrightness; + state = 3; + break; + } //end switch + if (timer<500) timer = 500; //safety only... (avoid re-arming the HW timer with an unreasonably short/zero alarm) + + // if ((pos>0) && (num<=9)) num = convert[num]; //tube character conversion, if needed... (maybe bad pin numbering) + + // Blank the currently-selected digit when: display globally off/zero + // brightness, we're in the "blank" phase (state==3), the character code + // isn't a real digit (10=BLANK,11=dash,12=DP,13+=custom glyph - none of + // these have a 7-segment BCD encoding this driver understands), or the + // radar/PIR presence sensor says nobody is around. + if ((brightness == 0) || (state == 3) || (num >9) || (!radarON)) { //blank digit + state = 0; + for (int i=0;i=0 + digitalWrite(DP_PIN,LOW); + #endif + } + else { + // Push the digit VALUE (0-9) as 4 bits onto the CD4543 (ABCDPins), + // which latches and decodes it to 7-segment outputs for whichever + // digit is currently selected below. + for (int i=0;i<4;i++) {digitalWrite(ABCDPins[i],num & 1<=0 + if (LEFTDECIMAL) DPpos = min(maxDig-1,pos+1); else DPpos = pos; + if (digitDP[DPpos]) digitalWrite(DP_PIN,HIGH); //switch ON decimal point, if needed + #endif + } + + // COLON_PIN / DECIMALPOINT_PIN are both -1 (unused) for the Mule V2 board + // (CLOCK_34 in clocks.h does not define them), so these blocks compile out + // entirely on this board; kept for source compatibility with other boards + // that reuse this driver file. + #if COLON_PIN >= 0 + if (num==10) digitalWrite(COLON_PIN,LOW); // Colon pin OFF + else digitalWrite(COLON_PIN,colonBlinkState); // Blink colon pin + #endif + + #if DECIMALPOINT_PIN >=0 + if (num==10) {digitalWrite(DECIMALPOINT_PIN,LOW);} + else {digitalWrite(DECIMALPOINT_PIN,decimalpointON); } + #endif + + portEXIT_CRITICAL_ISR(&timerMux); + //ESP32timer = timerBegin(0, PRESCALER, true); //set prescaler, true = edge generated signal + //timerAttachInterrupt(ESP32timer, &writeDisplay, true); + timerAlarmWrite(ESP32timer, timer, true); // re-arm the one-shot HW timer for the next phase's duration (see state machine above) + timerAlarmEnable(ESP32timer); + interrupts(); +} + +void clearTubes() { + // Blank all digits immediately (outside the ISR/multiplex cycle), using + // the same "select CD4028 output 7" trick as the blanking branch in + // writeDisplay() above. + for (int i=0;i=0) + digitalWrite(DP_PIN,LOW); + #endif +} + +// Intentionally empty: unlike non-multiplexed drivers, this board's display +// is refreshed continuously by the writeDisplay() timer ISR, which reads the +// live digit[]/newDigit[]/animMask[] state on every phase. Callers elsewhere +// in the core .ino invoke writeDisplaySingle() to force an immediate +// blocking redraw after changing digit values (e.g. during animations); for +// MULE_V2 no explicit action is needed since the next ISR tick (at most +// PWM_min..PWMrefresh ticks away) will already pick up the new state. +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/multiplex74141.ino b/firmware/uniclock/ESP32_UniClock2/multiplex74141.ino new file mode 100644 index 0000000..512ab21 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/multiplex74141.ino @@ -0,0 +1,194 @@ +// ============================================================================ +// Nixie driver: shared/multiplexed BCD-to-decimal decoder (SN74141 / 7441 / K155ID1) +// ---------------------------------------------------------------------------- +// Hardware: ONE (or a few) 74141-family BCD driver chip(s) whose 4 BCD inputs +// (ABCDPins[4]) are wired in parallel to ALL Nixie tubes, while each tube's +// high-voltage anode is switched individually through a transistor connected +// to digitEnablePins[]. Only one digitEnablePin is HIGH at any instant, so +// only that tube's cathodes actually light up with the BCD value currently +// present on ABCDPins - this is classic time-division multiplexing: the +// digits are shown one tube at a time, fast enough (via the hardware timer +// ISR below) that persistence of vision makes them look simultaneously lit. +// This trades tube count for 74141-chip count (cheap: 1 decoder for N tubes) +// at the cost of average brightness per tube (each tube is only ON 1/N of +// the time) and of needing careful ISR timing to avoid visible flicker. +// +// Platform: ESP8266 ONLY (uses the 8266 hardware timer1 API: timer1_write / +// ICACHE_RAM_ATTR). For the ESP32 port of the same multiplex strategy see +// multiplex74141_ESP32.ino (uses timerAlarmWrite/IRAM_ATTR instead). +// +// Used by these clocks.h configs (search for "#define MULTIPLEX74141"): +// CLOCK_1, CLOCK_2, CLOCK_20, CLOCK_21, CLOCK_22 +// digitEnablePins[], ABCDPins[4] and DP_PIN are actually defined per-CLOCK_NN +// in clocks.h (the declarations commented out below are just examples of the +// shape clocks.h must provide). +// ============================================================================ +#ifdef MULTIPLEX74141 //Nixie driver for 8266 only! +char tubeDriver[] = "MULTIPLEX74141"; +//define here the digit enable pins from 4 to 8 +//byte digitEnablePins[] = {15,13,12,14}; //for example... But SET in clocks.h !!! +//byte ABCDPins[4] = {2,4,5,0}; +//#define DP_PIN -1 // decimalPoint inside Nixie tube, set -1, if not used! + +#if defined(ESP8266) +#else + #error "Board is not supported! For ESP32 use MULTIPLEX74141_ESP32 !" +#endif + +int maxDigits = sizeof(digitEnablePins); //digitEnablePins is byte[], so sizeof == element count +int maxDig = maxDigits; //memory variable version (kept as a separate copy so it can be read cheaply from the ISR) + +//byte convert[] = {1,0,9,8,7,6,5,4,3,2}; //tube pin conversion, is needed (for example: bad tube pin layout) +// NOTE: these "PWM..." values are NOT milliseconds despite the comment - they are raw +// timer1 tick counts (timer1 on ESP8266 runs at TIM_DIV16, i.e. 5MHz/16 ~= 312.5kHz, +// so 1 tick ~= 3.2us). They set how long (in ticks) each tube stays lit / blanked +// within one full multiplex refresh cycle, which is what actually controls brightness. +int PWMrefresh=11000; //msec, Multiplex time period. Greater value => slower multiplex frequency +int PWM_min = 1000; +int PWM_max = 10000; +//int PWMtiming[11] = {0,1000,2000,3000,4000,5000,6000,7000,8000,9000,10000}; + +void setup_pins() { + char tmp[30]; + + DPRINTLN("Nixie clock - setup pins - Multiplex 74141 mode..."); + DPRINTLN("digitEnablePins: "); + for (int i=0;i=0 + pinMode(DP_PIN,OUTPUT); regPin(DP_PIN,"DP_PIN"); + DPRINT("DP_PIN:"); DPRINTLN(DP_PIN); + #endif + startTimer(); +} + +void ICACHE_RAM_ATTR writeDisplay(){ //https://circuits4you.com/2018/01/02/esp8266-timer-ticker-example/ + // ESP8266 timer1 is a SINGLE-SHOT (TIM_SINGLE) timer, so unlike the ESP32 + // version, it must be explicitly re-armed with timer1_write(timer) at the + // END of every single call below, otherwise the ISR would never fire again. + static byte pos = 0; + static volatile byte state=0; + static int timer = PWMrefresh; + static int num,brightness; + static byte DPpos; + static int PWMtimeBrightness=PWM_min; + + if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! + digitalWrite(digitEnablePins[pos],LOW); //blank current tube so it doesn't stay lit "stuck" during the (possibly long) flash write + timer1_write(PWMrefresh); //re-arm with a "slow idle" period; state machine resumes normally once EEPROMsaving clears + return; + } + + intCounter++; + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + timer = PWMrefresh; + + // 4-state per-tube state machine, re-entered once per ISR call: + // state 0: advance to next tube position; if animating, show the OLD digit for a + // fraction of the slot proportional to animMask[pos] (0..20 => wipe progress) + // state 1: show the NEW digit for the remaining fraction of the slot (only reached + // when animating) + // state 2: blank the tube for the rest of the PWMrefresh period - this "off time" + // is what implements brightness dimming (shorter ON / longer OFF = dimmer) + // state 3: transient marker consumed immediately below to force digitalWrite LOW + switch (state) { //state machine... + case 0: + pos++; if (pos>maxDig-1) { + pos = 0; //go to the first tube + // Brightness/lux is only re-sampled once per FULL refresh cycle (when wrapping + // back to tube 0), not on every tube - this keeps brightness visually uniform + // across all tubes within one cycle even if lx/brightness changes mid-cycle. + if (autoBrightness && displayON) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + } + + if (animMask[pos] > 0) { //Animation? + num = oldDigit[pos]; //show old character + timer = (PWMtimeBrightness * (20-animMask[pos]))/20; //animMask range is 0..20; older digit gets the (20-mask) share of the ON time + state = 1; //next state is: show newDigit + } + else { + num = digit[pos]; //show active character + timer = PWMtimeBrightness; + state = 2; //next state is: BLANK display + } + break; + case 1: //show new character, if animation + num = newDigit[pos]; + timer = (PWMtimeBrightness * animMask[pos])/20; //new digit gets the mask/20 share - together with case 0 this "wipes" old->new within one tube's ON slot + state = 2; //default next state is: BLANK display + break; + case 2: //blank display + timer = PWMrefresh-PWMtimeBrightness; //remaining OFF portion of the cycle = dimming + state = 3; + break; + } //end switch + if (timer<500) timer = 500; //safety only... (a too-short timer1 reload can miss the interrupt entirely on 8266) + + // if ((pos>0) && (num<=9)) num = convert[num]; //tube character conversion, if needed... (maybe bad pin numbering) + + // num>9 (BLANK=10, MINUS=11, DP glyph=12, ...) intentionally falls through to "blank digit": + // the 74141 itself also blanks all outputs for BCD inputs 10-15, so this check is really a + // software-side shortcut that avoids driving ABCDPins at all for non-numeric glyphs. + if ((brightness == 0) || (state == 3) || (num >9) || (!radarON)) { //blank digit + state = 0; + digitalWrite(digitEnablePins[pos],LOW); //switch off anode + #if DP_PIN >=0 + digitalWrite(DP_PIN,LOW); + #endif + } + else { + for (int i=0;i<4;i++) {digitalWrite(ABCDPins[i],num & 1< ABCDPins[i]) + digitalWrite(digitEnablePins[pos],HIGH); //switch on the new digit + #if DP_PIN >=0 + // Some tube types (Z574M) have their decimal-point cathode wired so it visually + // sits to the LEFT of the printed digit; LEFTDECIMAL compensates by lighting the + // DP together with the NEXT tube position instead of the current one. + if (LEFTDECIMAL) DPpos = min(maxDig-1,pos+1); else DPpos = pos; + if (digitDP[DPpos]) digitalWrite(DP_PIN,HIGH); //switch ON decimal point, if needed + #endif + } + + #if COLON_PIN >= 0 + if (num==10) digitalWrite(COLON_PIN,LOW); // Colon pin OFF + else digitalWrite(COLON_PIN,colonBlinkState); // Blink colon pin + #endif + + #if DECIMALPOINT_PIN >=0 + if (num==10) {digitalWrite(DECIMALPOINT_PIN,LOW);} + else {digitalWrite(DECIMALPOINT_PIN,decimalpointON); } + #endif + + timer1_write(timer); //re-arm the single-shot timer1 for the next phase (see note at top of function) +} + +void clearTubes() { + for (int i=0;i= 0 + digitalWrite(DP_PIN,LOW); + #endif +} + +// Not implemented for this driver: the multiplex ISR (writeDisplay) already refreshes +// continuously on its own, so there's no separate "blocking single write" path here +// (unlike the no_multiplex*.ino drivers, which are not otherwise refreshed by an ISR loop +// in the same way and rely more on this being a real no-op too, kept only for interface parity). +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/multiplex74141_ESP32.ino b/firmware/uniclock/ESP32_UniClock2/multiplex74141_ESP32.ino new file mode 100644 index 0000000..43e3360 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/multiplex74141_ESP32.ino @@ -0,0 +1,215 @@ +// ============================================================================ +// Nixie driver: shared/multiplexed BCD-to-decimal decoder (SN74141 / 7441 / K155ID1) +// ---------------------------------------------------------------------------- +// ESP32 port of multiplex74141.ino - identical hardware/multiplex strategy +// (one shared BCD decoder driving all tubes, one digitEnablePins[] transistor +// per tube selects which tube's cathodes are actually connected at any given +// instant; see the header comment in multiplex74141.ino for the full write-up +// of the wiring/timing rationale). The two files are kept separate rather +// than merged behind #ifdefs because the ESP8266 and ESP32 hardware-timer +// APIs are different enough (timer1_write single-shot vs. timerAlarmWrite +// auto-reload, ICACHE_RAM_ATTR vs IRAM_ATTR/DRAM_ATTR placement, and the +// dual-core critical-section requirements on ESP32) that sharing one file +// would need almost as many #ifdefs as duplicating it. +// +// Platform: ESP32 ONLY. +// Used by these clocks.h configs (search for "#define MULTIPLEX74141_ESP32"): +// CLOCK_23, CLOCK_23A, CLOCK_23B, CLOCK_31, CLOCK_33, CLOCK_43, CLOCK_44, CLOCK_45 +// digitEnablePins[], ABCDPins[4] and DP_PIN are defined per-CLOCK_NN in clocks.h. +// ============================================================================ +#ifdef MULTIPLEX74141_ESP32 +char tubeDriver[] = "MULTIPLEX74141_ESP32"; + +//define here the digit enable pins from 4 to 8 + +//byte digitEnablePins[] = {15,13,12,14}; //for example... But SET in clocks.h !!! +//byte ABCDPins[4] = {2,4,5,0}; +//int DP_PIN = -1; // decimalPoint inside Nixie tube, set -1, if not used! + +int maxDigits = sizeof(digitEnablePins); //digitEnablePins is byte[], so sizeof == element count +int DRAM_ATTR maxDig = maxDigits; //memory variable version; DRAM_ATTR forces it into internal RAM (never flash-cached) so the IRAM ISR can safely read it + +//byte convert[] = {1,0,9,8,7,6,5,4,3,2}; //tube pin conversion, is needed (for example: bad tube pin layout) +// NOTE: despite the "msec" comment these are raw hardware-timer tick counts (see +// PRESCALER in the core .ino / startTimer()), not real milliseconds - they only +// control the ON/OFF ratio of each tube's multiplex slot (= brightness). +int DRAM_ATTR PWMrefresh=11000; //msec, Multiplex time period. Greater value => slower multiplex frequency +int DRAM_ATTR PWM_min = 1000; +int DRAM_ATTR PWM_max = 10000; +//int DRAM_ATTR PWMtiming[11] = {0,1000,2000,3000,4000,5000,6000,7000,8000,9000,10000}; + + #if defined(ESP32) + #else + #error "Only ESP32 board is supported by this driver!" + #endif + +void setup_pins() { + char tmp[30]; + + DPRINTLN("Nixie clock - setup pins - Multiplex 74141 mode..."); + DPRINTLN("digitEnablePins: "); + for (int i=0;i=0 + pinMode(DP_PIN,OUTPUT); + // POSSIBLE BUG: no regPin(DP_PIN,"DP_PIN") call here, unlike the ESP8266 sibling + // (multiplex74141.ino) and the other drivers in this family. DP_PIN will therefore + // be silently missing from the "USED CLOCK PINS" diagnostic list even though it is + // actively driven below in writeDisplay(). + DPRINT("DP_PIN:"); DPRINTLN(DP_PIN); + #endif + startTimer(); +} + +void IRAM_ATTR writeDisplay(){ //void IRAM_ATTR writeDisplay(){ + // All static locals are DRAM_ATTR: code and read-only data placed by the linker in + // flash can become briefly inaccessible while the ESP32 is reading/writing SPI flash + // (e.g. Wi-Fi, EEPROM/OTA). An IRAM_ATTR ISR is safe to run during that time, but only + // if every byte of data it touches - not just the function code - is forced into + // internal DRAM instead of flash-backed memory. Hence DRAM_ATTR on every static here + // and on the module-level PWM constants above. + + static DRAM_ATTR byte pos = 0; + static DRAM_ATTR byte state=0; + static DRAM_ATTR int timer = PWMrefresh; + static DRAM_ATTR int num,brightness; + static DRAM_ATTR byte DPpos; + static DRAM_ATTR int PWMtimeBrightness=PWM_min; + + if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! + // Note: unlike the ESP8266 sibling, this does NOT re-arm the hardware timer before + // returning. That's fine here because startTimer() configured ESP32timer with + // autoreload=true (see timerAlarmWrite(...,true) in startTimer()), so the timer + // keeps firing on its own at whatever period was last programmed; only the display + // content update is skipped while EEPROM is being written. + return; + } + + // ESP32 is dual-core: portENTER/EXIT_CRITICAL_ISR takes a spinlock so the other core + // (e.g. running Wi-Fi/loop() code) cannot preempt this ISR mid-update of shared state, + // while noInterrupts()/interrupts() additionally blocks other interrupt sources on + // THIS core. Both are used together defensively so the digitalWrite() sequence below + // (which must stay atomic - partial BCD + digit-enable writes would flash a wrong + // digit) can't be interleaved with anything else. + portENTER_CRITICAL_ISR(&timerMux); + noInterrupts(); + + intCounter++; + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + timer = PWMrefresh; + + // Same 4-state per-tube multiplex/animation state machine as the ESP8266 version + // (multiplex74141.ino) - see the detailed comments there. In short: state 0 advances + // to the next tube and (if a wipe animation is active for it) shows the OLD digit for + // part of the slot; state 1 shows the NEW digit for the rest; state 2 blanks the tube + // for the remainder of the refresh period (this ON/OFF ratio is what implements + // brightness); state 3 is a one-tick marker consumed immediately below. + switch (state) { //state machine... + case 0: + pos++; if (pos>maxDig-1) { + pos = 0; //go to the first tube + // Brightness/lux is only resampled once per full refresh cycle (all tubes), not per-tube. + if (autoBrightness && displayON) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + } + if (animMask[pos] > 0) { //Animation? + num = oldDigit[pos]; //show old character + timer = (PWMtimeBrightness * (20-animMask[pos]))/20; //older digit gets the (20-mask)/20 share of ON time + state = 1; //next state is: show newDigit + } + else { + num = digit[pos]; //show active character + timer = PWMtimeBrightness; + state = 2; //next state is: BLANK display + } + break; + case 1: //show new character, if animation + num = newDigit[pos]; + timer = (PWMtimeBrightness * animMask[pos])/20; //new digit gets the mask/20 share -> "wipe" transition within one tube's slot + state = 2; //default next state is: BLANK display + break; + case 2: //blank display + timer = PWMrefresh-PWMtimeBrightness; + state = 3; + break; + } //end switch + if (timer<500) timer = 500; //safety only... (too-short alarm periods risk missed/late reloads) + + // if ((pos>0) && (num<=9)) num = convert[num]; //tube character conversion, if needed... (maybe bad pin numbering) + + // num>9 (BLANK/MINUS/DP-glyph/... codes >=10) is treated as "blank" here purely as a + // software shortcut; the 74141 hardware itself also outputs nothing for BCD 10-15. + if ((brightness == 0) || (state == 3) || (num >9) || (!radarON)) { //blank digit + state = 0; + digitalWrite(digitEnablePins[pos],LOW); //switch off anode + #if DP_PIN >=0 + digitalWrite(DP_PIN,LOW); + #endif + #if COLON_PIN >= 0 + digitalWrite(COLON_PIN,LOW); // Colon pin OFF + #endif + #if DECIMALPOINT_PIN >=0 + digitalWrite(DECIMALPOINT_PIN,LOW); + #endif + } + else { + for (int i=0;i<4;i++) {digitalWrite(ABCDPins[i],num & 1< ABCDPins[i]) to the shared decoder + digitalWrite(digitEnablePins[pos],HIGH); //switch on the new digit + #if DP_PIN >=0 + // LEFTDECIMAL: some tube types have the DP cathode physically positioned to the + // left of the digit glyph, so the DP for logical position `pos` must be lit + // together with the NEXT tube's slot instead of this one's. + if (LEFTDECIMAL) DPpos = min(maxDig-1,pos+1); else DPpos = pos; + if (digitDP[DPpos]) digitalWrite(DP_PIN,HIGH); //switch ON decimal point, if needed + else digitalWrite(DP_PIN,LOW); + #endif + #if COLON_PIN >= 0 + digitalWrite(COLON_PIN,colonBlinkState); // Blink colon pin + #endif + #if DECIMALPOINT_PIN >=0 + digitalWrite(DECIMALPOINT_PIN,decimalpointON); + #endif + } + + + portEXIT_CRITICAL_ISR(&timerMux); + //ESP32timer = timerBegin(0, PRESCALER, true); //set prescaler, true = edge generated signal + //timerAttachInterrupt(ESP32timer, &writeDisplay, true); + // Even though startTimer() configured the alarm as auto-reload, we still rewrite the + // alarm value + re-enable every call: `timer` changes call-to-call (it encodes the + // current state's ON/OFF duration), so this is how the variable-period software PWM + // is actually implemented on top of a fixed-period hardware auto-reload timer. + timerAlarmWrite(ESP32timer, timer, true); + timerAlarmEnable(ESP32timer); + interrupts(); +} + +void clearTubes() { + + for (int i=0;i=0) + digitalWrite(DP_PIN,LOW); + #endif +} + +// Not implemented for this driver: writeDisplay() (the timer ISR) already refreshes the +// display continuously on its own; kept only for interface parity with drivers that need it. +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/no_multiplex74141.ino b/firmware/uniclock/ESP32_UniClock2/no_multiplex74141.ino new file mode 100644 index 0000000..4d53402 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/no_multiplex74141.ino @@ -0,0 +1,203 @@ +// ============================================================================ +// Nixie driver: NON-multiplexed, one 74141 BCD decoder LATCHED per tube +// ---------------------------------------------------------------------------- +// Hardware: 4 digit Nixie Clock, 2x 74HC595N shift register + 4x 74141 +// (or 6 tubes with more 74595/74141 chips, depending on clocks.h). Unlike the +// multiplex74141*.ino drivers, there is NO time-division sharing of a single +// decoder across tubes: EVERY tube has its own 74141, and all of them are fed +// from a shared serial shift-register chain (dataPin/clkPin/latchPin, classic +// 74HC595 bit-bang protocol - MSB-of-nibble first, see writeBits()). All tube +// digits are pushed out serially and then latched (latchPin pulse) so that +// ALL tubes display their digit SIMULTANEOUSLY and permanently (no flicker, +// no per-tube ON/OFF multiplexing) - at the cost of needing one 74141 chip +// per tube instead of one shared chip. +// Because there is no per-tube ON slot to shorten/lengthen for dimming, the +// SAME hardware-timer ISR mechanism as the multiplex drivers is reused here +// only to fake brightness control: the whole shift-register chain is +// periodically re-latched with either real digits or an all-blank pattern +// (0xF nibbles) for a portion of each refresh cycle, so perceived brightness +// is still a global ON-time/OFF-time duty cycle, just applied to all tubes at +// once rather than tube-by-tube. +// +// Platform: ESP8266 ONLY. For the ESP32 port see no_multiplex_ESP32.ino. +// Used by these clocks.h configs (search for "#define NO_MULTIPLEX74141"): +// CLOCK_6, CLOCK_6A +// ============================================================================ + +#ifdef NO_MULTIPLEX74141 + +char tubeDriver[] = "NO_MULTIPLEX74141"; +#if defined(ESP8266) +#else + #error "Only 8266 Board is supported!" +#endif + + //change it, if needed for the correct tube sequence +//byte tubes[] = {3,2,1,0}; //4 tubes, old clock... in clocks.h +//byte tubes[] = {5,4,3,2,1,0}; //6 tubes, reverse order in clocks.h +// tubes[] maps PHYSICAL shift-register output position -> LOGICAL digit[] index, +// so a clock's tube wiring order (which may not match the natural digit order) +// can be corrected purely in clocks.h without touching this driver's logic. + +int maxDigits = sizeof(tubes); //tubes is byte[], so sizeof == tube count +// NOTE: despite the "msec" comment, these are raw timer1 tick counts (see startTimer(): +// TIM_DIV16 => ~312.5kHz => ~3.2us/tick), not real milliseconds. +int PWMrefresh=12000; //msec, Multiplex time period. Greater value => slower multiplex frequency +int PWM_min = 1000; +int PWM_max = 12000; +//int PWMtiming[11] = {1000,1000,2000,3000,4000,5000,6000,7000,8000,10000,12000}; + +//#define dataPin 14 //D5 +//#define latchPin 12 //D6 +//#define clkPin 13 //D7 + +// Not implemented for this driver: brightness/blanking is handled entirely inside the +// timer ISR (writeDisplay) by periodically re-shifting an all-blank pattern, so there is +// no separate blocking "write once" path. Kept only for interface parity. +void writeDisplaySingle() { } + +void setup_pins(){ + DPRINTLN("Nixie clock - setup pins NON-MULTIPLEX 4(or 6) x 74141"); + pinMode(dataPin, OUTPUT); regPin(dataPin,"dataPin"); + pinMode(latchPin,OUTPUT); regPin(latchPin,"latchPin"); + pinMode(clkPin,OUTPUT); regPin(clkPin,"clkPin"); + startTimer(); +} + +// Bit-bangs one 4-bit BCD nibble (one tube's worth of data) into the 74HC595 shift +// register chain, MSB-first. Runs inside the timer ISR (ICACHE_RAM_ATTR keeps it in IRAM +// so it stays callable even while flash is busy), so it cannot use delayMicroseconds() +// (which is not ISR-safe / not guaranteed available) - the "for t<10 nop" loops are a +// crude but deterministic ~100ns busy-wait to satisfy the 74HC595's minimum clock +// pulse-width/setup-time requirements. +void ICACHE_RAM_ATTR writeBits(byte num) { //shift out 4 bits + for (int i=3;i>=0;i--) { + digitalWrite(dataPin,num & (1< 0) animM = animMask[i]; //find, if any animation is wanted? + } + + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + digitalWrite(latchPin, LOW); //keep 74141 outputs frozen on their OLD latched values while we shift in new data + + if ((brightness ==0) || !radarON){ + for (int i=0;i 74141 outputs nothing, i.e. blank) + + #if DECIMALPOINT_PIN>=0 + digitalWrite(DECIMALPOINT_PIN,LOW); + #endif + #if COLON_PIN >= 0 + digitalWrite(COLON_PIN,LOW); // colon pin OFF + #endif + digitalWrite(latchPin, HIGH); //commit the all-blank pattern to the 74141 outputs + timer1_write(100*PWMrefresh); //display fully off (radar/night-off) - just idle-poll slowly instead of running the PWM state machine + return; + } + else { + // Shift out one BCD nibble per tube, all in a single ISR pass (this is what makes + // the driver "non-multiplexed": every tube gets its data on every call, rather than + // only the currently-selected tube as in the multiplex74141*.ino drivers). + for (int i=0;i0); state 1 = show the "new" half of a wipe + // for the ON time; state 2 = blank all tubes for the remaining OFF time (dimming). + // When brightness is already MAXBRIGHTNESS, state 2 is skipped entirely (goes straight + // back to state 0) since there's no need to ever blank at 100% duty cycle. + switch (state) { //state machine... + case 0: + #if DECIMALPOINT_PIN>=0 + if (decimalpointON) digitalWrite(DECIMALPOINT_PIN,HIGH); + #endif + #if COLON_PIN>=0 + digitalWrite(COLON_PIN,colonBlinkState); // Blink colon pin + #endif + if (autoBrightness && displayON) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); + } + else { + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + } + if (animM > 0) { //Animation? + timer = (PWMtimeBrightness * (20-animM))/20; + state = 1; //next state is: show newDigits + } + else { + timer = PWMtimeBrightness; + if (brightness>=MAXBRIGHTNESS) state = 0; + else state = 2; //default next state is: BLANK display + } + break; + case 1: //show new character, if animation + timer = (PWMtimeBrightness * animM)/20; + if (brightness>=MAXBRIGHTNESS) state = 0; + else state = 2; //default next state is: BLANK display + break; + case 2: //blank display + #if DECIMALPOINT_PIN>=0 + digitalWrite(DECIMALPOINT_PIN,LOW); + #endif + #if COLON_PIN>=0 + digitalWrite(COLON_PIN,LOW); // colon pin OFF + #endif + state = 0; + timer = PWMrefresh-PWMtimeBrightness; + break; + } //end switch + } //end else + + digitalWrite(latchPin, HIGH); //commit the freshly shifted-in data to all 74141 outputs at once (this is the actual visible update instant) + + if ((brightness == 0) || (!radarON)) {timer = PWM_max; state = 0;} //no time sharing is needed + if (timer<500) timer = 500; //safety only... + timer1_write(timer); +} + +// Not implemented for this driver: there's no persistent "enable" line to blank, since +// blanking is achieved by re-shifting an all-off (0xA-0xF) pattern from within +// writeDisplay() itself; a real clearTubes() would need to bit-bang a blank frame here too. +void clearTubes() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/no_multiplex_ESP32.ino b/firmware/uniclock/ESP32_UniClock2/no_multiplex_ESP32.ino new file mode 100644 index 0000000..e4bcbbc --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/no_multiplex_ESP32.ino @@ -0,0 +1,242 @@ +// ============================================================================ +// Nixie driver: NON-multiplexed, one 74141 BCD decoder LATCHED per tube (ESP32) +// ---------------------------------------------------------------------------- +// Hardware: 4 (or 6) digit Nixie Clock, 2x 74HC595N shift register + 4x (or 6x) +// 74141. Identical hardware/wiring strategy to no_multiplex74141.ino (every +// tube has its own dedicated 74141, all fed from one shared 74HC595 shift- +// register chain via dataPin/clkPin/latchPin; ALL tubes are shifted+latched +// together every ISR call so they update SIMULTANEOUSLY - there is no +// per-tube time-division multiplexing here, unlike the multiplex74141*.ino / +// pcf_multiplex74141.ino drivers). Brightness is still faked with the same +// trick: the whole chain is periodically re-latched with an all-blank pattern +// (0xF nibbles) for part of each refresh cycle, turning a single shared +// ON/OFF duty cycle into perceived dimming across all tubes at once. +// +// This file is the ESP32 port of no_multiplex74141.ino - kept as a separate +// file (rather than merged behind #ifdefs) for the same reason as +// multiplex74141_ESP32.ino vs multiplex74141.ino: the hardware-timer APIs +// differ (timer1_write single-shot vs. timerAlarmWrite auto-reload, +// ICACHE_RAM_ATTR vs IRAM_ATTR/DRAM_ATTR placement) and ESP32 is dual-core, +// so the ISR here additionally needs portENTER/EXIT_CRITICAL_ISR(&timerMux) +// around the shared-state update, which the single-core ESP8266 doesn't need. +// +// Platform: ESP32 ONLY. For the ESP8266 original see no_multiplex74141.ino. +// Used by these clocks.h configs (search for "#define NO_MULTIPLEX_ESP32"): +// CLOCK_35 (6x Z566M tubes, 3x74595/6x74141), CLOCK_36 (4x IN-1 tubes, +// 4x74595/4x74141) - both currently commented out (inactive) in clocks.h. +// dataPin/latchPin/clkPin and tubes[] are defined per-CLOCK_NN in clocks.h. +// ============================================================================ + +#ifdef NO_MULTIPLEX_ESP32 + +char tubeDriver[] = "NO_MULTIPLEX_ESP32"; +#if defined(ESP32) +#else + #error "Only ESP32 Board is supported!" +#endif + + //change it, if needed for the correct tube sequence +//byte tubes[] = {3,2,1,0}; //4 tubes, old clock... use in clocks.h +//byte tubes[] = {5,4,3,2,1,0}; //6 tubes, reverse order use in clocks.h +// tubes[] maps PHYSICAL shift-register output position -> LOGICAL digit[] index, +// exactly as in no_multiplex74141.ino, so tube wiring order is corrected in +// clocks.h alone without touching this driver's logic. + +// NOTE: unlike multiplex74141_ESP32.ino (which keeps a separate DRAM_ATTR maxDig +// copy of maxDigits specifically for the ISR to read), writeDisplay() below reads +// this plain maxDigits directly. In practice this is harmless: ordinary non-const +// globals like this one already live in .bss (real internal RAM), not in a +// flash-cached region, so no DRAM_ATTR is strictly required here - but it is an +// inconsistency with the pattern established elsewhere in this driver family, so +// flagging it for consistency. +int maxDigits = sizeof(tubes); +// NOTE: PWMrefresh/PWM_min/PWM_max here are noticeably larger than the ESP8266 +// sibling's (12000/1000/12000) because the ESP32 hardware timer used by +// startTimer() runs at a different effective tick rate (PRESCALER=15 against the +// APB clock, see startTimer() in the core .ino) than the ESP8266's TIM_DIV16 +// timer1 - these were re-tuned for ESP32 rather than being real milliseconds +// on either platform. +int DRAM_ATTR PWMrefresh=25000; //msec, Multiplex time period. Greater value => slower multiplex frequency +int DRAM_ATTR PWM_min = 5000; +int DRAM_ATTR PWM_max = 25000; + +//#define dataPin 14 //D5 +//#define latchPin 27 //12 //D6 +//#define clkPin 26 //13 //D7 + +// Not implemented for this driver: brightness/blanking is handled entirely inside +// the timer ISR (writeDisplay) by periodically re-shifting an all-blank pattern, so +// there is no separate blocking "write once" path. Kept only for interface parity. +void IRAM_ATTR writeDisplaySingle() { } + +void setup_pins(){ + DPRINTLN("Nixie clock - setup pins NON-MULTIPLEX 4(or 6) x 74141"); + pinMode(dataPin, OUTPUT); regPin(dataPin,"dataPin"); + pinMode(latchPin,OUTPUT); regPin(latchPin,"latchPin"); + pinMode(clkPin,OUTPUT); regPin(clkPin,"clkPin"); + startTimer(); +} + +// Bit-bangs one 4-bit BCD nibble (one tube's worth of data) into the 74HC595 shift +// register chain, MSB-first - same protocol/timing as no_multiplex74141.ino's +// writeBits(). Uses ICACHE_RAM_ATTR (aliased to IRAM_ATTR for ESP32 builds, see +// "#define ICACHE_RAM_ATTR IRAM_ATTR" in the core .ino) purely so this file stays +// close to its ESP8266 sibling; functionally identical to marking it IRAM_ATTR +// directly. Runs from inside the timer ISR, so it cannot use delayMicroseconds() +// - the "for t<10 nop" loops are a crude but deterministic busy-wait instead, to +// satisfy the 74HC595's minimum clock pulse-width/setup-time requirements. +void ICACHE_RAM_ATTR writeBits(byte num) { //shift out 4 bits + for (int i=3;i>=0;i--) { + digitalWrite(dataPin,num & (1< 0) animM = animMask[i]; //find, if any animation is wanted? + } + + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + digitalWrite(latchPin, LOW); //keep the 74141 outputs frozen on their OLD latched values while new data is shifted in + + if ((brightness ==0) || !radarON) { + for (int i=0;i every 74141 output stays off) + + #if DECIMALPOINT_PIN>=0 + digitalWrite(DECIMALPOINT_PIN,LOW); + #endif + #if COLON_PIN >= 0 + digitalWrite(COLON_PIN,LOW); // colon pin OFF + #endif + digitalWrite(latchPin, HIGH); //commit the all-blank pattern to the 74141 outputs + portEXIT_CRITICAL_ISR(&timerMux); + timerAlarmWrite(ESP32timer, 100*PWMrefresh, true); //display fully off (radar/night-off) - just idle-poll slowly instead of running the PWM state machine + timerAlarmEnable(ESP32timer); + interrupts(); + return; + } + else { + // Shift out one BCD nibble per tube, all in a single ISR pass - this is what makes + // the driver "non-multiplexed": every tube gets fresh data on every call, rather + // than only the currently-selected tube as in the multiplex74141*.ino drivers. + for (int i=0;i0); state 1 = show the "new" half of a wipe for + // the ON time; state 2 = blank all tubes for the remaining OFF time (dimming). At + // MAXBRIGHTNESS, state 2 is skipped entirely (stays at/returns to state 0) since + // there's no need to ever blank at 100% duty cycle. + switch (state) { //state machine... + case 0: + #if DECIMALPOINT_PIN>=0 + if (decimalpointON) digitalWrite(DECIMALPOINT_PIN,HIGH); + #endif + #if COLON_PIN>=0 + digitalWrite(COLON_PIN,colonBlinkState); // Blink colon pin + #endif + if (autoBrightness && displayON) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); + } + else { + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + } + if (animM > 0) { //Animation? + timer = (PWMtimeBrightness * (20-animM))/20; + state = 1; //next state is: show newDigits + } + else { + timer = PWMtimeBrightness; + if (brightness>=MAXBRIGHTNESS) state = 0; + else state = 2; //default next state is: BLANK display + } + break; + case 1: //show new character, if animation + timer = (PWMtimeBrightness * animM)/20; + if (brightness>=MAXBRIGHTNESS) state = 0; + else state = 2; //default next state is: BLANK display + break; + case 2: //blank display + #if DECIMALPOINT_PIN>=0 + digitalWrite(DECIMALPOINT_PIN,LOW); + #endif + #if COLON_PIN>=0 + digitalWrite(COLON_PIN,LOW); // colon pin OFF + #endif + state = 0; + timer = PWMrefresh-PWMtimeBrightness; + break; + } //end switch + } //end else + + digitalWrite(latchPin, HIGH); //commit the freshly shifted-in data to all 74141 outputs at once (this is the actual visible update instant) + + if ((brightness == 0) || (!radarON)) {timer = PWM_max; state = 0;} //no time sharing is needed + if (timer<500) timer = 500; //safety only... + portEXIT_CRITICAL_ISR(&timerMux); + // Even though startTimer() set the alarm to auto-reload, the alarm value is still + // rewritten on every call: `timer` changes call-to-call (it encodes the current + // state's ON/OFF duration), so this is how the variable-period software PWM is + // actually implemented on top of a fixed-period hardware auto-reload timer. + timerAlarmWrite(ESP32timer, timer, true); + timerAlarmEnable(ESP32timer); + interrupts(); +} + +// Not implemented for this driver: there's no persistent "enable" line to blank, since +// blanking is achieved by re-shifting an all-off (0xA/0xF) pattern from within +// writeDisplay() itself; a real clearTubes() would need to bit-bang a blank frame here too. +void IRAM_ATTR clearTubes() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/pcf_multiplex74141.ino b/firmware/uniclock/ESP32_UniClock2/pcf_multiplex74141.ino new file mode 100644 index 0000000..279f8ba --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/pcf_multiplex74141.ino @@ -0,0 +1,318 @@ +// ============================================================================ +// Nixie driver: shared/multiplexed BCD-to-decimal decoder (SN74141), tube +// selection via a PCF8574 I2C GPIO expander +// ---------------------------------------------------------------------------- +// Hardware: same multiplex strategy as multiplex74141.ino (ONE shared 74141 +// decoder, BCD inputs wired in parallel to all tubes, time-division +// multiplexed one tube at a time via a per-tube anode-enable line - see that +// file's header for the full rationale). The difference here is where the +// per-tube ENABLE lines come from: instead of driving each digitEnablePins[i] +// straight off an ESP GPIO, up to 8 of them can instead live on the port pins +// (P0..P7) of a PCF8574 I2C I/O expander chip, addressed with a hand-rolled +// (bit-banged, NOT Wire-library) I2C implementation. This lets a clock with +// more tubes than spare direct GPIOs (this file supports up to 6-8 tubes) +// still be built with a single shared 74141 and only 2 extra ESP pins +// (PCF_SDA_PIN/PCF_SCL_PIN) instead of one extra GPIO per tube. +// digitEnablePins[i] values 0-99 address a direct ESP GPIO as usual; values +// 100-107 address PCF8574 port P(value-100) instead (see sendBits() below) - +// by convention "add 100 to the port number" when a tube's enable line is on +// the PCF chip. The ABCDPins[4] (shared BCD lines) and DP_PIN always stay on +// direct ESP GPIOs since there are only 4 of them regardless of tube count. +// +// The I2C bit-banging (not Wire.h) is deliberate: writeDisplay()/sendBits()/ +// shiftout() all run inside the hardware-timer ISR (ICACHE_RAM_ATTR), and the +// Arduino Wire library is not safe/available to call from an ISR context, so +// the SDA/SCL toggling is done by hand with busy-wait "delayMS()" nop loops +// instead (see the protocol walkthrough further down). +// +// Platform: ESP8266 ONLY (this file has never been ported to ESP32; compare +// with how multiplex74141.ino/multiplex74141_ESP32.ino form a matching pair - +// no *_ESP32 sibling exists for this PCF-expander strategy). +// Used by these clocks.h configs (search for "#define PCF_74141"): +// CLOCK_7 (8266 UNFI PCB clock, 6x Z574M/Z573M tubes) - currently commented +// out (inactive) in clocks.h, i.e. this driver is presently orphaned/unused +// by the active build (CLOCK_72 is the config currently enabled). +// Unlike the other files in this family, digitEnablePins[]/ABCDPins[]/DP_PIN/ +// I2C_ADDR/PCF_SDA_PIN/PCF_SCL_PIN are hardcoded directly in THIS file rather +// than left commented-out for clocks.h to define per-config - clocks.h's +// CLOCK_7 block only sets "#define PCF_74141" and does not redefine any of +// these, so every clock built with this driver currently gets this exact +// fixed 6-tube/PCF pinout with no per-config override, unlike every sibling +// driver in this family. +// ============================================================================ +#ifdef PCF_74141 +char tubeDriver[] = "PCF_74141"; +//PCF8574 I/O expander version, 4..8 tube Nixie Clock, driven by 74141 +//define here the digit enable pins from 4 to 8 tubes + +//if a digitEnablePort is on PCF chip, add 100 to the port number! (for example 2-->102) + +#define I2C_ADDR 0x20 //PCF8574 base 7-bit I2C address (A0/A1/A2 strapped to GND on this board) +#define PCF_SDA_PIN 4 +#define PCF_SCL_PIN 5 + +//If a digitEnablePin is on pcf8574, add 100 to the pin number. (P0 = 100,... P7 = 107) +byte digitEnablePins[] = {100,101,102,103,104,105}; //6 tube nixie driven by PCF - all 6 enable lines live on the PCF8574 here, none direct on ESP GPIOs +byte ABCDPins[4] = {14,12,13,2}; //D5,D6,D7,D4 on 8266 - shared BCD lines, always direct ESP GPIOs regardless of tube count +#define DP_PIN 16 // decimalPoint on 8266's D0 + +int maxDigits = sizeof(digitEnablePins); + +//byte convert[] = {1,0,9,8,7,6,5,4,3,2}; //tube pin conversion, is needed (for example: bad tube pin layout) +int PWMrefresh=15000; //msec, Multiplex time period. Greater value => slower multiplex frequency +int PWM_min = 2000; +int PWM_max = 14000; +//int PWMtiming[11] = {0,2000,3000,4000,5000,6000,7000,8000,10000,12000,14000}; + +#if defined(ESP8266) +#else + #error "Only 8266 Board is supported!" +#endif + +// NOTE: named "delayMS" but the comment says "Delay microsec" - actually neither is +// literally true: it's a plain busy-wait nop loop, roughly proportional to `d` at +// whatever clock speed the 8266 runs at (not a calibrated microsecond/millisecond +// delay). Used instead of delayMicroseconds()/delay() because this runs inside the +// timer ISR (ICACHE_RAM_ATTR) where those aren't safe/appropriate to call. +void ICACHE_RAM_ATTR delayMS(int d) { //Delay microsec + for (int i=0;i LOW, while PCF_SCL_PIN is HIGH + //digitalWrite(PCF_SCL_PIN,HIGH); + //delayMS(4); + digitalWrite(PCF_SDA_PIN,LOW); + delayMS(3); + digitalWrite(PCF_SCL_PIN,LOW); + delayMS(4); + + shiftout(address<<1); //shift address one bit left, because 0bit is READ/WRITE mode. 0=WRITE + shiftout(val); + + // STOP condition: SDA transitions LOW->HIGH while SCL is HIGH. + //void ICACHE_RAM_ATTR stopCondition() { + delayMS(4); + digitalWrite(PCF_SDA_PIN,LOW); //PCF_SDA_PIN LOW -> HIGH, while PCF_SCL_PIN is HIGH + delayMS(2); + digitalWrite(PCF_SCL_PIN,HIGH); + delayMS(4); + digitalWrite(PCF_SDA_PIN,HIGH); + delayMS(4); +} + + +void setup_pins() { + char tmp[30]; + + DPRINTLN("PCF8574 MULTIPLEX driver"); + for (int i=0;i=100 (PCF-hosted) are intentionally NOT pinMode'd/regPin'd here - + // they aren't real ESP GPIOs at all, just an index into the PCF8574's output byte + // sent over I2C via sendBits() below, so there's nothing local to configure. + } + for (int i=0;i<4;i++) { + pinMode(ABCDPins[i], OUTPUT); + sprintf(tmp,"Pin[%c]",char('A'+i)); + regPin(ABCDPins[i],tmp); + } + pinMode(PCF_SDA_PIN,OUTPUT); regPin(PCF_SDA_PIN,"PCF_SDA_PIN"); + pinMode(PCF_SCL_PIN,OUTPUT); regPin(PCF_SCL_PIN,"PCF_SCL_PIN"); + pinMode(DP_PIN,OUTPUT); regPin(DP_PIN,"DP_PIN"); + digitalWrite(PCF_SDA_PIN,HIGH); + digitalWrite(PCF_SCL_PIN,HIGH); //leave the bus idle (both lines HIGH) before the first transaction + delay(100); + sendBits(I2C_ADDR,0); //clear all 8 PCF8574 outputs (all tubes disabled) at startup + + startTimer(); + // Software watchdog re-armed with a generous 8s timeout: the bit-banged I2C + // transaction in sendBits() runs with interrupts effectively serialized inside the + // ISR and can take a comparatively long time (many delayMS() busy-waits per call), + // so the default (shorter) 8266 watchdog period could otherwise trip during startup. + ESP.wdtDisable(); + ESP.wdtEnable(WDTO_8S); + } + +void ICACHE_RAM_ATTR writeDisplay(){ //https://circuits4you.com/2018/01/02/esp8266-timer-ticker-example/ + static byte pos = 0; + static byte state=0; + static int timer = PWMrefresh; + static byte num,brightness; + static byte p,DPpos; + static int PWMtimeBrightness = PWM_min; + +if (EEPROMsaving) { //stop refresh, while EEPROM write is in progress! + //digitalWrite(digitEnablePins[pos],LOW); + timer1_write(PWMrefresh); + return; + } + + // POSSIBLE BUG: PWMtimeBrightness is computed here from `brightness` BEFORE + // `brightness` is (re)assigned a few lines below - so this uses the value left + // over from the PREVIOUS ISR call, not the current one (a one-call lag whenever + // brightness/lx changes). Also, unlike multiplex74141.ino/multiplex74141_ESP32.ino + // (which only recompute PWMtimeBrightness once per FULL multiplex cycle, when pos + // wraps back to 0, specifically to keep brightness visually uniform across all + // tubes within one cycle), this recomputes it on EVERY ISR call regardless of + // state/pos - so if autoBrightness/lx changes mid-cycle, different tubes within + // the same refresh pass could end up with different PWMtimeBrightness values. + if (autoBrightness && displayON) { + PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); + } + else + PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); + + intCounter++; + timer = PWMrefresh; + brightness = displayON ? prm.dayBright : prm.nightBright; + if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety + + // Same 4-state per-tube multiplex/animation state machine as multiplex74141.ino - + // see that file's header for the full write-up. state 0: advance to next tube + // (old digit during a wipe); state 1: new digit for the rest of a wipe; state 2: + // blank for the remaining OFF portion of the cycle (brightness dimming); state 3: + // one-tick marker consumed immediately below. + switch (state) { //state machine... + case 0: + pos++; if (pos>maxDigits-1) pos = 0; //go to the first tube + + if (animMask[pos] > 0) { //Animation? + num = oldDigit[pos]; //show old character + timer = (PWMtimeBrightness * (20-animMask[pos]))/20; + state = 1; //next state is: show newDigit + } + else { + num = digit[pos]; //show active character + timer = PWMtimeBrightness; + state = 2; //next state is: BLANK display + } + break; + case 1: //show new character, if animation + num = newDigit[pos]; + timer = (PWMtimeBrightness * animMask[pos])/20; + state = 2; //default next state is: BLANK display + break; + case 2: //blank display + timer = PWMrefresh-PWMtimeBrightness; + state = 3; + break; + } //end switch + + // if ((pos>0) && (num<=9)) num = convert[num]; //tube character conversion, if needed... (maybe bad pin numbering) + + p = digitEnablePins[pos]; //resolve this cycle's tube-enable target: <100 = direct ESP GPIO, >=100 = PCF8574 port (p-100) + + if ((brightness == 0) || (state == 3) || (num >9) || (!radarON)) { //blank digit + state = 0; + // Blanking is done purely by pushing an invalid BCD code (1111=15) to the shared + // 74141 - unlike multiplex74141.ino/multiplex74141_ESP32.ino, which additionally + // switch the CURRENT tube's digitEnablePins[pos] line LOW here. That matters: + // POSSIBLE BUG - `p` (the just-selected tube's enable line) is never explicitly + // turned back off, neither here nor anywhere else in this file. For a PCF-hosted + // pin (p>=100) this is harmless in practice, because sendBits() below always + // rewrites the WHOLE PCF8574 output byte with only the newly-selected tube's bit + // set, which implicitly clears every previously-enabled PCF pin. But for a + // DIRECT ESP GPIO enable pin (p<100), digitalWrite(p,HIGH) is a single-pin write + // with no such "whole port" side effect - once such a pin is first driven HIGH it + // is NEVER driven LOW again anywhere in this file. If a clock config mixes direct + // and PCF-hosted digitEnablePins (the setup_pins() loop above clearly anticipates + // this), every direct-GPIO tube's anode would stay permanently powered after its + // first turn, relying solely on the BCD=1111 blanking to keep it dark the rest of + // the time - but since ALL tubes share the same 74141/ABCDPins, that direct-GPIO + // tube would then visibly ghost (light up showing) whatever digit is currently + // being driven for a DIFFERENT tube's turn. The shipped default config + // (digitEnablePins[] = {100,101,102,103,104,105}, all PCF-hosted) never exercises + // this path, which is presumably why it has gone unnoticed. + for (int i=0;i<4;i++) {digitalWrite(ABCDPins[i],HIGH); } + digitalWrite(DP_PIN,LOW); + } + else { + if (p<100) digitalWrite(p,HIGH); //switch ON new digit on 8266 + else sendBits(I2C_ADDR,1<<(p-100)) ; // or on PCF port - single-bit-set byte, see note above + // LEFTDECIMAL: some tube types (Z574M) have their DP cathode positioned to the + // LEFT of the printed digit, so its DP is lit together with the NEXT tube's slot + // instead of the current one (see CLOCK_7's "#define LEFTDECIMAL true" in clocks.h). + if (LEFTDECIMAL) DPpos = min(maxDigits-1,pos+1); else DPpos = pos; + if (digitDP[DPpos] && (brightness>0)) digitalWrite(DP_PIN,HIGH); //switch ON decimal point, if needed + for (int i=0;i<4;i++) {digitalWrite(ABCDPins[i],num & 1< ABCDPins[i]) to the shared 74141 + } + + #if COLON_PIN >= 0 + if (num==10) digitalWrite(COLON_PIN,LOW); // Colon pin OFF + else digitalWrite(COLON_PIN,colonBlinkState); // Blink colon pin + #endif + + #if DECIMALPOINT_PIN >=0 + // POSSIBLE BUG: stray extra "}" at the end of this line - the `else` branch is a + // single bare statement (no opening brace), so this closing brace has no match. + // Currently harmless only because DECIMALPOINT_PIN is normally left at -1 for + // this driver's clock configs, so this whole #if block never actually compiles; + // it would very likely fail to compile as soon as a PCF_74141-based config ever + // defined DECIMALPOINT_PIN >=0. Compare with the correctly-braced + // "else {digitalWrite(DECIMALPOINT_PIN,decimalpointON); }" in multiplex74141.ino. + if (num==10) {digitalWrite(DECIMALPOINT_PIN,LOW);} + else digitalWrite(DECIMALPOINT_PIN,decimalpointON); } + #endif + + if (timer<500) timer = 500; //safety only... + timer1_write(timer); +} + +void ICACHE_RAM_ATTR clearTubes() { + digitalWrite(DP_PIN,LOW); + sendBits(I2C_ADDR,0); //zero out all PCF8574 outputs - blanks every PCF-hosted tube; note direct-GPIO (p<100) tubes are NOT addressed here (see the ABCDPins-only comment below) + //for (int i=0;i<4;i++) digitalWrite(ABCDPins[i],HIGH); +} + +// Not implemented for this driver: the multiplex ISR (writeDisplay) already refreshes +// continuously on its own, so there's no separate blocking "write once" path here; +// kept only for interface parity with drivers that need it. +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/pwmleds.ino b/firmware/uniclock/ESP32_UniClock2/pwmleds.ino new file mode 100644 index 0000000..b0cb822 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/pwmleds.ino @@ -0,0 +1,186 @@ +// ============================================================================ +// PWM Tube Backlight subsystem (plain R/G/B PWM, no addressable LEDs) +// ---------------------------------------------------------------------------- +// Drives a single RGB backlight (or 3 separate R/G/B LED strips wired in +// parallel) behind/around the tubes using the ESP32 LEDC hardware PWM +// peripheral directly on 3 GPIOs (PWM1_PIN/PWM2_PIN/PWM3_PIN = R/G/B). This +// is the simpler alternative to the addressable-WS2812 backlight implemented +// in z_neopixel.ino (USE_NEOPIXEL): here there is only ONE logical color for +// the whole backlight (no per-tube pixel control), computed in software and +// pushed out as 3 duty-cycle values instead of a serial LED protocol. +// +// Enabled per-config via #define USE_PWMLEDS in clocks.h. Currently the only +// config using it is CLOCK_54 (D1 R32 ESP32 + NCS312 Nixie clock KIT) - grep +// clocks.h for "#define USE_PWMLEDS" for the authoritative, current list. +// USE_NEOPIXEL and USE_PWMLEDS are meant to be mutually exclusive per clock +// config (CLOCK_54 keeps USE_NEOPIXEL commented out) - see the ESP8266/ESP32 +// and "both defined" caveats in the POSSIBLE BUG notes below. +// +// LEDC is an ESP32-only peripheral (esp32-hal-ledc.h: ledcAttachPin/ +// ledcSetup/ledcWrite have no ESP8266 equivalent). Pin setup (pinMode + +// ledcAttachPin + ledcSetup, 200Hz/8-bit per channel) happens once in +// ESP32_UniClock2.ino's setup(), NOT in this file - this file only ever +// calls ledcWrite() to update duty cycle. See setup()'s "#ifdef USE_PWMLEDS" +// block (channels 0/1/2 = R/G/B) for the actual pin/peripheral configuration. +// +// Called from the core program (ESP32_UniClock2.ino): +// - doAnimationPWM() : every loop() iteration, AND from inside Fdelay()'s +// busy-wait loop, so the backlight keeps animating +// even while tube digits are mid-transition (Fdelay +// blocks for the tube-switch duration). It rate-limits +// itself internally (see FPS_MSEC / prm.rgbSpeed), +// mirroring doAnimationMakuna() in z_neopixel.ino. +// +// Only a subset of prm's RGB settings is used here compared to the fuller +// z_neopixel.ino: prm.rgbEffect (0=off, 1=fixed color, anything else= +// rainbow flow), prm.rgbBrightness, prm.rgbFixColor, prm.rgbSpeed and +// prm.rgbDir. There is no equivalent here of z_neopixel's effect1/effect2/ +// random-color modes - this file only ever shows "off", a single fixed +// color, or one continuous rainbow sweep. +// ============================================================================ + +#ifdef USE_PWMLEDS + +#define FPS 60 //frames per second, animation frame-rate cap (matches z_neopixel.ino) +#define FPS_MSEC 1000/FPS //ms per animation frame = minimum interval between doAnimationPWM() updates +#define COLORSATURATION 255 //POSSIBLE BUG (minor/dead code): defined but never referenced in this file - leftover from z_neopixel.ino where it sizes RgbColor constants +#define WHITE_INDEX 192 //reserved "color wheel" position (out of 0..255) that setPWMcolor() special-cases to pure white instead of a hue + +long pwmBrightness; +int pwmColorStep = 1; //POSSIBLE BUG (dead code, same pattern already noted in z_neopixel.ino's colorStep): computed from prm.rgbSpeed in doAnimationPWM() but never actually used to step the rainbow index - pwmRainbow() always advances j by exactly 1 per call, so animation speed is controlled only via the frame-timing gate below, not via this variable +int pwmRed,pwmGreen,pwmBlue = 0; //POSSIBLE BUG (dead code): these globals are always shadowed by the same-named local variables declared in setPWMcolor() below, so they are never actually read or written - harmless (ints default-init to 0) but misleading, and the "= 0" here only initializes pwmBlue, not pwmRed/pwmGreen (equivalent for globals, which zero-init anyway, but worth noting the syntax doesn't do what it looks like) + +// Converts a "color wheel" position (0..255 hue, or the special WHITE_INDEX, +// or <0 for "off") plus a brightness (0..255) into R/G/B PWM duty cycles and +// writes them out via LEDC. Same hue math as z_neopixel.ino's Wheel(), just +// inlined here and applied directly to 3 PWM channels instead of an RgbColor. +void setPWMcolor(int WheelPos, byte brightness) { +#ifdef USE_PWMLEDS + + int pwmRed,pwmGreen,pwmBlue; //local color components (deliberately shadow the unused globals above) + + if (WheelPos == WHITE_INDEX) { + pwmRed = 255; pwmGreen = 255; pwmBlue = 255; + } + else if (WheelPos<0) { + pwmRed =0; pwmGreen = 0; pwmBlue = 0; //WheelPos<0 is the "backlight off" sentinel used by doAnimationPWM()/pwmAlarmLight() + } + else { + // Standard 3-band color wheel: 0..255 hue split into 3 rising/falling + // ramps (R->B, B->G, G->R) of 85 steps each, *3 to scale 0..85 up to 0..255. + WheelPos = 255 - WheelPos; + if(WheelPos < 85) { + pwmRed =255 - WheelPos * 3; pwmGreen = 0; pwmBlue = WheelPos * 3; + } else if(WheelPos < 170) { + WheelPos -= 85; + pwmRed = 0, pwmGreen = WheelPos * 3; pwmBlue = 255 - WheelPos * 3; + } else { + WheelPos -= 170; + pwmRed = WheelPos * 3; pwmGreen = 255 - WheelPos * 3; pwmBlue = 0; + } + } + + // Scale each color channel by brightness (0..255) and push out as PWM duty + // cycle. Channel numbers (0/1/2) must match the ledcAttachPin() channel + // assignment done once in ESP32_UniClock2.ino's setup() - this file does + // not own pin/channel setup, only per-frame duty-cycle updates. + #if PWM1_PIN>=0 + ledcWrite(0, pwmRed * brightness / 255); + #endif + #if PWM2_PIN>=0 + ledcWrite(1, pwmGreen * brightness / 255); + #endif + #if PWM3_PIN>=0 + ledcWrite(2, pwmBlue * brightness / 255); + #endif +#endif +} + + +// Continuous rainbow sweep ("flow" effect, prm.rgbEffect >= 2): walks the +// color wheel index j by exactly 1 step per call (see pwmColorStep note +// above - prm.rgbSpeed only affects how often doAnimationPWM() calls this, +// not the step size), wrapping via byte overflow at 0/255. Direction is set +// by prm.rgbDir. +void pwmRainbow() { + static byte j=0; + + setPWMcolor(j,pwmBrightness); + if (prm.rgbDir) { j++; } + else { j--; } +} + +// Alarm indicator: while alarmON, ignore the normal effect/color settings and +// blink the backlight white/off at full brightness every 500ms (2 calls per +// on/off cycle = 1Hz blink), regardless of prm.rgbBrightness, so the alarm is +// clearly visible even if the user configured a dim or off backlight. +void pwmAlarmLight() { + static unsigned long lastRun = 0; + static byte counter; + + if ((millis()-lastRun)<500) return; + lastRun = millis(); + counter++; + if (counter%2) + setPWMcolor(WHITE_INDEX,255); + else + setPWMcolor(-1,0); +} + + +// Main entry point, called every loop()/Fdelay() iteration (see file header). +// Self-throttles internally so it's safe to call at arbitrarily high +// frequency - the actual color update rate is capped well below the caller's +// call frequency. +void doAnimationPWM() { +static unsigned long lastRun = 0; + + if (EEPROMsaving) return; //don't touch PWM/SPI-ish timing while an EEPROM write is in progress + if (alarmON) { + pwmAlarmLight(); + return; + } + + // Fixed color (or OFF) doesn't need 60fps updates - only re-check once a + // second (e.g. in case displayON/radarON toggled) instead of every frame. + if ((prm.rgbEffect <=1) && ((millis()-lastRun)<1000)) return; //fix color + // Animated (rainbow) effect: gate to a frame rate derived from prm.rgbSpeed + // (0..255), from ~60fps (FPS_MSEC, fast/high speed) down to ~3ms floor at + // max speed and up to ~258ms at speed=0 (slowest). max() with FPS_MSEC caps + // the update rate at 60fps even for high rgbSpeed values; 258-rgbSpeed can't + // go negative since rgbSpeed is a byte (0..255). + if ((millis()-lastRun)), see +// libraries/Rtc_by_Makuna.zip. Uses the Arduino `Wire` (I2C) object, so it shares the +// I2C bus with any other I2C sensors defined for the clock (see I2Csensors.ino). +// Wiring: I2C bus only - PIN_SDA / PIN_SCL, set per-CLOCK_NN in clocks.h (any GPIO pair works, +// it's not necessarily the chip's "default" I2C pins). Presence is auto-detected at +// startup by I2Csensors.ino (probes address 0x68) and reported in the RTCexist flag. +// Role in the system: the DS3231 is a battery-backed FALLBACK time source, not the primary one. +// NTP (WiFi) and/or GPS are preferred whenever available; the RTC is only read to set +// the running clock when neither NTP nor GPS refreshed the time on that update cycle, +// and it is written to (kept in sync) whenever NTP/GPS DID refresh the time - so after +// a power loss/reset with no network, the clock still starts from a decent time. +// See calcTime() in ESP32_UniClock2.ino: +// if (RTCexist) { if (refreshed || refreshed2) updateRTC(); else getRTC(); } +// Manual set: on clocks that define PIN_FLD_BUTTON/PIN_SET_BUTTON (and optionally +// PIN_MODE_SWITCH), a long-press on the FLD button enters/leaves a manual date/time +// editor (editor()/showValue()); the SET button changes the value of the currently +// selected field. See scanButFLD()/scanButSET() below for the button state machine. +// Used by: CLOCK_5 (IVL-2 VFD clock, MAX6921, has RTC+buttons) +// CLOCK_15a (IV-6 VFD clock RGB) +// CLOCK_17 (PT6311 IV-22 russian clock) +// CLOCK_35 (Z566M/IN-1 tubes, non-multiplex driver) +// CLOCK_52 (2xHV5122 IN18 clock, PROTOTYPE) +// CLOCK_71 (2x VQC10 clock - USE_RTC defined here only to also enable the pushbutton +// code below; this build has no physical DS3231, RTCexist stays false) +// CLOCK_80/81/82 (word clocks EN/DE/HU - RTC only, PIN_FLD_BUTTON/PIN_SET_BUTTON are +// commented out on these builds, so the manual editor is never entered) +// (CLOCK_50 and CLOCK_99 have USE_RTC available but commented out by default) +//==================================================================================================== +#include //Makuna/RTC +RtcDS3231 Rtc(Wire); +RtcDateTime rtcNow; +RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__); //firmware build timestamp, used as a sane fallback date if the RTC ever loses confidence (e.g. dead backup battery) +#define EPOCH_OFFSET 946684800l //offset from 1970->2000 epoch start; the Makuna library's RtcDateTime counts seconds since 2000-01-01, while TimeLib's now() is a standard Unix epoch (1970) - subtract this constant to convert Unix-epoch -> DS3231 epoch +//------ Mode Switch and Push Buttons --------------------- +#ifndef PIN_MODE_SWITCH + #define PIN_MODE_SWITCH -1 //-1 = "not wired on this clock", all buttoncode below is compiled out via the #if PIN_xxx>=0 guards +#endif + +#ifndef PIN_FLD_BUTTON + #define PIN_FLD_BUTTON -1 +#endif +#ifndef PIN_SET_BUTTON + #define PIN_SET_BUTTON -1 +#endif + +boolean pushedFldButtonValue = LOW; //which logic level means "button pushed" +boolean pushedSetButtonValue = LOW; //which logic level means "button pushed" + +//------- I2C bus definition Any pins are usable Please, SET in clocks.h -------- +//#define PIN_SDA 4 //D2 original general setup for 8266 +//#define PIN_SCL 5 //D1 + +#define MENU_UNSELECT_TIMEOUT 30000 //NOTE: defined but not actually read/used anywhere in this file (LastModify is set on every keypress but never compared against this timeout) - the editor only exits via a long FLD press, not by an idle timeout. Left in place for a possible future auto-exit-on-idle feature. + +int monthdays[13] = {0,31,29,31,30,31,30,31,31,30,31,30,31}; //days-per-month lookup (index 1-12, Feb hardcoded to 29/leap); NOTE: not currently referenced anywhere in this file - no leap-year/day-overflow validation is actually performed when editing the Day field (see scanButSET()) + +struct Menu { + short lowlimit; // lower limit for this field's value + short uplimit; // upper limit for this field's value + char name[5]; // short label shown on the display while editing this field +}; + +// Editable fields for manual time setting, indexed by `fld` (1..MAXFLD). Index 0 is a dummy/unused +// "no field selected" placeholder so `fld==0` can mean "editor not active" without needing -1 checks. +const struct Menu m1[]= { + {0,0," "}, + {2022,2099,"Year"}, + {1,12,"Mon "}, + {1,31,"Day "}, //NOTE: fixed 1..31 range regardless of month/leap-year - editing Feb 30 or Apr 31 is not prevented, see scanButSET() + {0,23,"Hour"}, + {0,59,"Min "}, +}; + +#define MAXFLD 5 + +int mvar[MAXFLD+1]; //working copy of the field being edited: mvar[1]=year, [2]=month, [3]=day, [4]=hour, [5]=minute (seconds are always reset to 0 on save, see saveRTC()) + +// Pushbutton debounce/press-length state machine (used identically by both FLD and SET buttons): +// IS_OPEN idle, button released +// IS_PUSHING/PUSHED button just went down (one extra state to timestamp the press) +// IS_LONGPRESSING/-ED button has been held past the long-press threshold (~2s) - action fires once, on entry +// IS_OPENING button just released - if this happened quickly (<500ms since push) it's a "short press" +// IS_LONGOPENING button released after a long press - swallow the release so it isn't also seen as a short press +enum switchStates {IS_OPEN, IS_OPENING, IS_PUSHED, IS_LONGPRESSING, IS_LONGPRESSED, IS_PUSHING, IS_LONGOPENING}; + +int fld = 0; //0 = manual editor not active; 1..MAXFLD = index into m1[]/mvar[] of the field currently being edited +unsigned long LastModify = 0; //timestamp of the last field change while editing; set by scanButFLD/scanButSET, only used as a "was anything changed?" flag by editor() to decide whether saveRTC() is needed on exit (NOT used for the MENU_UNSELECT_TIMEOUT idle-exit that its name might suggest) + +void setupRTC() { + #if PIN_MODE_SWITCH>=0 + pinMode(PIN_MODE_SWITCH,INPUT_PULLUP); + regPin(PIN_MODE_SWITCH,"PIN_MODE_SWITCH"); + #endif + #if PIN_FLD_BUTTON>=0 + pinMode(PIN_FLD_BUTTON,INPUT_PULLUP); + regPin(PIN_FLD_BUTTON,"PIN_FLD_BUTTON"); + // Auto-detect button polarity at boot: with INPUT_PULLUP the idle (unpressed) line should read + // HIGH. If it reads LOW here instead, the wiring/hardware inverts the logic (e.g. external + // pull-down, or the button is wired to the pin differently than the internal-pullup default), + // so remember that for this board "pressed" actually reads HIGH, not LOW. + // NOTE: this assumes the button is NOT physically held down at power-on - if it were, polarity + // detection here would be wrong for the rest of the session. + if (digitalRead(PIN_FLD_BUTTON) == LOW) //inverted button? + pushedFldButtonValue = HIGH; + #endif + #if PIN_SET_BUTTON>=0 + pinMode(PIN_SET_BUTTON,INPUT_PULLUP); + regPin(PIN_SET_BUTTON,"PIN_SET_BUTTON"); + if (digitalRead(PIN_SET_BUTTON) == LOW) //inverted button? + pushedSetButtonValue = HIGH; + #endif + + if (RTCexist) { + DPRINTLN("Connecting to RTC clock..."); + Rtc.Begin(); + if (!Rtc.IsDateTimeValid()) { + // IsDateTimeValid() also surfaces DS3231-specific fault conditions (oscillator stop flag, + // invalid register values) via LastError(), distinct from a plain I2C communication failure. + if (Rtc.LastError() != 0) { + // A nonzero LastError() here means the I2C transaction itself failed (bus error / chip + // not responding) - do NOT touch the RTC's stored time in that case, since we can't + // trust what we'd be reading or writing over a broken bus. + DPRINT("RTC communications error = "); DPRINTLN(Rtc.LastError()); + } + else { + // I2C is fine, but the chip reports its own datetime as invalid - typically means the + // DS3231's Oscillator Stop Flag (OSF) is set, i.e. the coin-cell backup battery died (or + // is missing/depleted) at some point and the chip lost power, so its date/time can no + // longer be trusted. Reseed it with the firmware's own compile timestamp as a "better + // than nothing" starting point until NTP/GPS can correct it. + DPRINTLN("RTC lost confidence in the DateTime!"); + Rtc.SetDateTime(compiled); //Set RTC time to firmware compile time + } + } + + if (!Rtc.GetIsRunning()) { + // The DS3231 has an Enable Oscillator (EOSC) bit that can leave the clock halted even + // though the chip itself answers on I2C - make sure the oscillator/counting is actually on. + DPRINTLN("RTC was not actively running, starting now"); + Rtc.SetIsRunning(true); + } + + // Disable the 32kHz output and the SQW/INT pin's square-wave function - this firmware never + // uses the DS3231 as an interrupt/alarm source, so both are turned off to save a little power + // and avoid an unused, possibly-floating/toggling pin on the board. + Rtc.Enable32kHzPin(false); + Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone); + } +/* +while (true) { + DPRINT(digitalRead(PIN_MODE_SWITCH)); DPRINT(" / "); + DPRINT(digitalRead(PIN_FLD_BUTTON)); DPRINT(" / "); + DPRINTLN(digitalRead(PIN_SET_BUTTON)); + delay(1000); + } +*/ +} + + +// Called once per main loop() iteration (unconditionally, whenever USE_RTC is defined). Most of the +// time this is a cheap no-op: it just polls the FLD button once (scanButFLD) and returns immediately +// because fld==0 (editor not active). Only when a long FLD-press flips fld to nonzero does it drop +// into the inner while(true) loop below - and while that loop runs, it BLOCKS the rest of loop() (no +// WiFi/animation/sensor/etc. processing happens) until the user long-presses FLD again to save & exit. +void editor() { + static unsigned long lastDisplay = 0; + if (RTCexist) {rtcNow = Rtc.GetDateTime();} //note: read but not otherwise used here - editor() re-reads current time into mvar[] via getRTC() only at the moment edit mode is entered (see scanButFLD's IS_LONGPRESSING case) + LastModify = 0; + #if PIN_FLD_BUTTON >= 0 && PIN_SET_BUTTON >= 0 //Are buttons installed? + editorRunning = true; //flag read elsewhere (e.g. wordclock.ino) to show an "editing" indicator on the display + while (true) { + scanButFLD(100); //100ms poll interval for both buttons - also doubles as the blocking loop's own pacing/refresh rate + if (fld == 0) { + editorRunning = false; + break; + } + scanButSET(100); + showValue(); + #if defined(WORDCLOCK) + if ((millis()-lastDisplay)>100) { + lastDisplay = millis(); + writeDisplay2(); + } + #else + writeDisplaySingle(); + #endif + printDigits(1000); + Fdelay(100); + } + #endif + if (LastModify != 0) saveRTC(); //only persist if at least one field was actually changed during this edit session +} + +// Renders the value of the field currently being edited (mvar[fld]) into the shared `digit[]` +// display buffer (digit 10 = blank, used by memset to clear unused positions first). +void showValue() { + memset(digit,10,sizeof(digit)); //clear array + memset(digitDP,0,sizeof(digitDP)); + if(fld==1) { + //Year is the only 4-digit field (e.g. 2026) - split into thousands/hundreds/tens/units across digit[0..3] + digit[3] = mvar[1] / 1000; + digit[2] = (mvar[1] % 1000) / 100; + digit[1] = (mvar[1] % 100) / 10; + digit[0] = mvar[1] % 10; + } + else { + //Month/Day/Hour/Minute are all 1-2 digit values - tens/units into digit[0..1] + digit[1] = mvar[fld] / 10; + digit[0] = mvar[fld] % 10; + } + #if defined(MAX6921) || defined(MAX6921_ESP32) + //character set not available yet (MAX6921-driven displays can't render the alphabetic field-name label below, only digits) + #else + // On displays with more than 4 digit positions, show the field's short text label (e.g. "Year", + // "Hour") in the upper digit positions alongside the numeric value, so the user knows which field + // they're editing. Needs >=6 digits to fit 3 label characters + a ':' separator, or >=8 to fit the + // full 4-character label without the ':' filler. + if (!digitsOnly && (maxDigits>4)) { + if (maxDigits>=8) digit[7] = m1[fld].name[0]; + if (maxDigits>=8) digit[6] = m1[fld].name[1]; + if (maxDigits>=6) digit[5] = m1[fld].name[2]; + else digit[5] = ':'; //not enough digit positions for the 3rd label character - show a separator instead + digit[4] = m1[fld].name[3]; + + } + #endif +} + +//------------------------------------- RTC Clock functions -------------------------------------------------------------------- +// Called from calcTime() (ESP32_UniClock2.ino) ONLY on cycles where the system time (TimeLib's +// now()) was just refreshed from NTP and/or GPS this same cycle - i.e. "we just got a trustworthy +// time from the network/satellites, so push it into the battery-backed RTC to keep it in sync as +// the fallback source for the next time we DON'T have network/GPS (e.g. after a reboot)." +void updateRTC() { + DPRINTLN("Update RTC?"); + if (!RTCexist) return; + if (year()<2020) return; //invalid system date?? sanity guard so a not-yet-synced/garbage system clock can never be written into the RTC + + rtcNow = Rtc.GetDateTime(); + // Only actually write the RTC if it disagrees with the system time by more than the noise you'd + // expect from normal read/processing latency (>2s on seconds, or any mismatch on minute/hour/ + // day/month/year) - avoids needless I2C writes to the RTC on every single refresh cycle. + if (abs(rtcNow.Second() - second())>2 || (rtcNow.Minute() != minute()) || (rtcNow.Hour() != hour()) + || (rtcNow.Day() != day()) || (rtcNow.Month() != month()) || (rtcNow.Year() != year()) ) { + DPRINTLN("Updating RTC from TimeServer..."); + DPRINT("- DS3231 date:"); DPRINT(rtcNow.Year()); DPRINT("/"); DPRINT(rtcNow.Month()); DPRINT("/"); DPRINT(rtcNow.Day()); + DPRINT(" time:"); DPRINT(rtcNow.Hour()); DPRINT(":"); DPRINT(rtcNow.Minute()); DPRINT(":"); DPRINTLN(rtcNow.Second()); + DPRINT("- Tserver date:"); DPRINT(year()); DPRINT("/"); DPRINT(month()); DPRINT("/"); DPRINT(day()); + DPRINT(" time:"); DPRINT(hour()); DPRINT(":"); DPRINT(minute()); DPRINT(":"); DPRINTLN(second()); + Rtc.SetDateTime(now()-EPOCH_OFFSET); //update RTC with UNIX epoch timestamp to 2000year epoch + } //endif +} + + +// Called from calcTime() on cycles where NTP/GPS did NOT refresh the time this cycle - the DS3231 +// is the fallback: read its current date/time and push it into TimeLib via setTime() so the rest +// of the firmware (display, animations, etc., all of which read time via now()/hour()/etc.) sees a +// continuously-updated clock even with no network/GPS. Also refreshes mvar[] with the (now current) +// date/time fields, which is what the manual editor uses as its starting point when entered next +// (see scanButFLD's IS_LONGPRESSING case calling getRTC() to seed mvar[] with "now"). +void getRTC() { + if (RTCexist) { + if (!Rtc.IsDateTimeValid()) { + if (Rtc.LastError() != 0) { + // I2C communication problem - do not touch system time this cycle; whatever TimeLib + // already has (from the last successful update) is left running/ticking on its own. + DPRINT("RTC communications error = "); DPRINTLN(Rtc.LastError()); + } + else { + // Oscillator Stop Flag set (dead/missing backup battery, or never-yet-set RTC) - the + // chip's stored date/time can't be trusted, so it is deliberately NOT read into + // setTime() here; system time is simply left as-is for this cycle. + DPRINTLN("RTC lost confidence in the DateTime!"); + } + } + else { + rtcNow = Rtc.GetDateTime(); + //DPRINT("\nGet DS3231 data&time: "); DPRINT(rtcNow.Year()); DPRINT("/"); DPRINT(rtcNow.Month()); DPRINT("/"); DPRINT(rtcNow.Day()); + //DPRINT(" "); DPRINT(rtcNow.Hour()); DPRINT(":"); DPRINT(rtcNow.Minute()); DPRINT(":"); DPRINTLN(rtcNow.Second()); + setTime(rtcNow.Hour(),rtcNow.Minute(),rtcNow.Second(),rtcNow.Day(),rtcNow.Month(),rtcNow.Year()); //set the time (hr,min,sec,day,mnth,yr) + } + } + // NOTE: mvar[] is refreshed from now()/year()/month()/etc. below unconditionally, even when + // RTCexist is false (or the RTC read above was skipped due to an invalid/error state) - in that + // case it simply reflects whatever TimeLib's current system time already is (e.g. last NTP time, + // or millis()-derived free-running time), which is a reasonable "best current guess" starting + // point for the manual editor on hardware with no RTC chip (see CLOCK_71 in clocks.h). + mvar[1] = year(); mvar[2] = month(); mvar[3] = day(); + mvar[4] = hour(); mvar[5] = minute(); +} + +// Commits the values edited via the manual FLD/SET buttons (mvar[1..5]) back into the running +// system clock, and - if present - into the DS3231 as well. Called once, when the editor is exited +// (long FLD press while fld>0), only if at least one field was actually changed (see editor()). +void saveRTC() { + setTime(mvar[4],mvar[5],0,mvar[3],mvar[2],mvar[1]); //set the time (hr,min,sec,day,mnth,yr) - seconds are always reset to :00 since there's no seconds field in the editor + + if (RTCexist) { + DPRINTLN("Updating RTC from Manual settings..."); + Rtc.SetDateTime(now()-EPOCH_OFFSET); + } + DPRINT("New Date & Time:"); DPRINT(year()); DPRINT("/"); DPRINT(month()); DPRINT("/"); DPRINT(day()); + DPRINT(" time:"); DPRINT(hour()); DPRINT(":"); DPRINT(minute()); DPRINT(":"); DPRINTLN(second()); +} + +//-------------------------- check buttons ------------------------------------------------ +// FLD ("field") button: +// - Long press (>2s) toggles the editor: OFF->ON (loads current time into mvar[] via getRTC(), +// fld becomes 1 = "Year"); ON->OFF (saves mvar[] back via saveRTC(), fld becomes 0). +// - Short press (<500ms) while already editing (fld>0) advances to the next field, wrapping +// Year->Month->Day->Hour->Minute->Year... Has no effect while the editor is closed (fld==0), +// since a short press there does nothing but a long press is needed to open it. +// Debounce: state changes are only evaluated once per `mill` ms (called with 100ms from editor()), +// so a button transition is only "seen" after at least one 100ms tick, which is enough to reject +// mechanical contact bounce without any extra filtering. +void scanButFLD(unsigned long mill) { +static unsigned long lastRun = millis(); +static unsigned long lastPush = millis();; +static switchStates butState; +byte sw; + + if (PIN_FLD_BUTTON<0) return; + if ((millis() - lastRun) < mill) return; //refresh rate + lastRun = millis(); + + sw = digitalRead(PIN_FLD_BUTTON); + if (pushedFldButtonValue == HIGH) sw = !sw; //inverted button logic - normalize so downstream code can always treat LOW as "pressed" + + switch (butState) { + case IS_OPEN: + if(sw == LOW) butState = IS_PUSHING; + break; + case IS_PUSHING: + butState = IS_PUSHED; + lastPush = millis(); //timestamp the press; used both for the 2s long-press threshold and the 500ms short-press classification on release + DPRINTLN(" PUSHED"); + break; + case IS_PUSHED: + if(sw == HIGH) butState = IS_OPENING; + if((sw == LOW) && ((millis()-lastPush)>2000)) butState = IS_LONGPRESSING; + break; + case IS_LONGPRESSING: + // Entered exactly once per long press (state immediately advances to IS_LONGPRESSED below), + // so this toggle fires a single time per press-and-hold, not repeatedly while held. + butState = IS_LONGPRESSED; + DPRINTLN(" IS_LONGPRESSED"); + if (fld>0) { + fld = 0; + saveRTC(); //leaving edit mode: commit mvar[] to system clock + RTC chip + } + else { + fld = 1; + getRTC(); //entering edit mode: seed mvar[] with the current date/time (field 1 = Year) + } + break; + case IS_LONGPRESSED: + if (sw == HIGH) butState = IS_LONGOPENING; + break; + case IS_OPENING: + butState = IS_OPEN; + if ((millis()-lastPush)<500) { + //short press (press+release completed within 500ms of the initial push) - only meaningful while editing (fld>0), since fld is unaffected when already 0 + LastModify = millis(); + fld++; if (fld>MAXFLD) fld = 1; //wraps back to field 1 (Year), never to 0 - the editor can only be exited via a long press, not by cycling fields + DPRINT(m1[fld].name); DPRINTLN(fld); + } //endif millis() + break; + case IS_LONGOPENING: + // Swallow the release-after-long-press here so it isn't misread as an additional short press + // once control returns to IS_OPEN. + butState = IS_OPEN; + break; + } //end switch +} + +// SET button: changes the value of whichever field FLD currently has selected (mvar[fld]). +// - Short press (<500ms): increments the value by 1 (with wrap-around at m1[fld].lowlimit/uplimit). +// - Held past the long-press threshold (2s): auto-repeats the +1 increment on every subsequent +// 100ms poll tick for as long as the button stays down (see IS_LONGPRESSED case below) - lets +// the user "run" a field up quickly (e.g. scrolling through years) instead of clicking 60 times +// to advance the minute field. +void scanButSET(unsigned long mill) { +static unsigned long lastRun = millis(); +static unsigned long lastPush = millis(); +static switchStates butState; +byte sw; + + if (PIN_SET_BUTTON<0) return; + if ((millis() - lastRun) < mill) return; //refresh rate + lastRun = millis(); + + sw = digitalRead(PIN_SET_BUTTON); + if (pushedSetButtonValue == HIGH) sw = !sw; //inverted button logic + + switch (butState) { + case IS_OPEN: + if(sw == LOW) butState = IS_PUSHING; + break; + case IS_PUSHING: + butState = IS_PUSHED; + lastPush = millis(); + DPRINTLN(" PUSHED"); + break; + case IS_PUSHED: + if(sw == HIGH) butState = IS_OPENING; + if((sw == LOW) && ((millis()-lastPush)>2000)) butState = IS_LONGPRESSING; + break; + case IS_LONGPRESSING: + butState = IS_LONGPRESSED; + DPRINTLN(" IS_LONGPRESSED"); + //if (noZero !=0) noZero =0; else noZero = 1; //dead code (leftover from another clock variant?) - no noZero/EEPROM_NOZERO symbol exists in this codebase + //EEPROM.update(EEPROM_NOZERO, noZero); + break; + case IS_LONGPRESSED: + // Stays in this state (re-entered every poll) for as long as the button remains held, which + // is what produces the auto-repeat effect: mvar[fld] is bumped by 1 on every ~100ms tick. + if (sw == HIGH) butState = IS_LONGOPENING; + // if ((millis()-lastPush)>10000) {factoryReset(); resetFunc();} //reset to factory settings (dead code - factory-reset-via-long-hold was never wired up here) + LastModify = millis(); + mvar[fld]+=1; + if (mvar[fld] < m1[fld].lowlimit) mvar[fld] = m1[fld].uplimit; + if (mvar[fld] > m1[fld].uplimit) mvar[fld] = m1[fld].lowlimit; + DPRINT(fld); DPRINT(" : "); DPRINTLN(mvar[fld]); + break; + case IS_OPENING: + butState = IS_OPEN; + if ((millis()-lastPush)<500) { + LastModify = millis(); + mvar[fld]++; + if (mvar[fld] < m1[fld].lowlimit) mvar[fld] = m1[fld].uplimit; + if (mvar[fld] > m1[fld].uplimit) mvar[fld] = m1[fld].lowlimit; + //if (fld <= 2) mvar[2] = monthday_check(mvar[RTC_Ev], mvar[RTC_Ho], mvar[RTC_Nap]); //leap-year check (szökőév ellenőrzés) -- dead code: monthday_check()/RTC_Ev/RTC_Ho/RTC_Nap are not defined anywhere in this codebase, so a Day value like 30 in February can currently be set and saved without any leap-year/month-length validation + DPRINT(fld); DPRINT(" : "); DPRINTLN(mvar[fld]); + if (fld>MAXFLD) fld = 0; // POSSIBLE BUG: this resets fld (the FIELD SELECTOR), not mvar[fld] (the field's VALUE). fld is only ever changed by scanButFLD(), which already clamps it to 1..MAXFLD, so this condition should never be true in practice - looks like leftover/copy-pasted logic (or was meant to operate on something else) rather than an intentional part of the SET-button behavior. + //flash(fld); + } //endif millis() + break; + case IS_LONGOPENING: + butState = IS_OPEN; + break; + } //end switch +} + + +// Manual I2C bus-recovery routine: bit-bangs SCL/SDA as plain GPIOs (bypassing the Wire/TWI +// peripheral) to unstick an I2C bus where a slave device is stuck holding a line low (e.g. a +// device that was reset/powered mid-transaction and is waiting for clocks it will never see from +// a normal Wire.begin()). Follows the standard "clock out up to 9 pulses, then issue a STOP" +// recovery procedure from the I2C spec / Adafruit/NXP application notes. +// NOTE: currently DEAD CODE from this file's own call graph - not invoked anywhere in rtc.ino; the +// only call site in the whole project is commented out (I2Csensors.ino, near where RTCexist gets +// probed on the bus) rather than being wired into the normal startup/error-recovery path. +#if defined(PIN_SDA) && defined(PIN_SCL) +int I2C_ClearBus() { +#if defined(TWCR) && defined(TWEN) + TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly +#endif + DPRINTLN("I2C_ClearBus() started."); + pinMode(PIN_SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup. + pinMode(PIN_SCL, INPUT_PULLUP); + + Fdelay(2500); // Wait 2.5 secs. This is strictly only necessary on the first power + // up of the DS3231 module to allow it to initialize properly, + // but is also assists in reliable programming of FioV3 boards as it gives the + // IDE a chance to start uploaded the program + // before existing sketch confuses the IDE by sending Serial data. + + boolean SCL_LOW = (digitalRead(PIN_SCL) == LOW); // Check if SCL is Low. + if (SCL_LOW) { //If it is held low, Arduino cannot become the I2C master. + return 1; //I2C bus error. Could not clear SCL clock line held low + } + + boolean SDA_LOW = (digitalRead(PIN_SDA) == LOW); // vi. Check SDA input. + int clockCount = 20; // > 2x9 clock + + while (SDA_LOW && (clockCount > 0)) { // vii. If SDA is Low, + clockCount--; + // Note: I2C bus is open collector so do NOT drive SCL or SDA high. + pinMode(PIN_SCL, INPUT); // release SCL pullup so that when made output it will be LOW + pinMode(PIN_SCL, OUTPUT); // then clock SCL Low + delayMicroseconds(10); // for >5uS + pinMode(PIN_SCL, INPUT); // release SCL LOW + pinMode(PIN_SCL, INPUT_PULLUP); // turn on pullup resistors again + // do not force high as slave may be holding it low for clock stretching. + delayMicroseconds(10); // for >5uS + // The >5uS is so that even the slowest I2C devices are handled. + SCL_LOW = (digitalRead(PIN_SCL) == LOW); // Check if SCL is Low. + int counter = 20; + while (SCL_LOW && (counter > 0)) { // loop waiting for SCL to become High only wait 2sec. + counter--; + delay(100); + SCL_LOW = (digitalRead(PIN_SCL) == LOW); + } + if (SCL_LOW) { // still low after 2 sec error + return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec + } + SDA_LOW = (digitalRead(PIN_SDA) == LOW); // and check SDA input again and loop + } + if (SDA_LOW) { // still low + return 3; // I2C bus error. Could not clear. SDA data line held low + } + + // else pull SDA line low for Start or Repeated Start + pinMode(PIN_SDA, INPUT); // remove pullup. + pinMode(PIN_SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control. + // When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus. + /// A Repeat Start is a Start occurring after a Start with no intervening Stop. + delayMicroseconds(10); // wait >5uS + pinMode(PIN_SDA, INPUT); // remove output low + pinMode(PIN_SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control. + delayMicroseconds(10); // x. wait >5uS + pinMode(PIN_SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset + pinMode(PIN_SCL, INPUT); + return 0; // all ok +} +#else +int I2C_ClearBus() {return 1;} //PIN_SDA/PIN_SCL not defined for this build - can't bit-bang pins that don't exist, report a generic "bus error" instead +#endif + +//------------------------------------------------------------------------------------------ +#else + // USE_RTC not defined for this clock build: provide empty stand-in implementations so the rest + // of the codebase (ESP32_UniClock2.ino's setup()/calcTime()/loop()) can call these unconditionally + // without needing #ifdef USE_RTC guards at every call site. + void updateRTC() {} + void setupRTC() {} + void getRTC() {} + void editor() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/wordclock.ino b/firmware/uniclock/ESP32_UniClock2/wordclock.ino new file mode 100644 index 0000000..26a01f9 --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/wordclock.ino @@ -0,0 +1,823 @@ +// NeoPixel Word Clock +// +// ============================================================================ +// This driver is fundamentally different from the other (tube/VFD/Numitron/ +// LED segment) drivers in this codebase: instead of multiplexing digit tubes, +// it lights up individual letters/words on a WS2812 ("NeoPixel") LED matrix +// mounted behind a laser-cut/printed acrylic panel, so the time is spelled +// out as a sentence (e.g. "IT IS HALF PAST TEN") rather than shown as digits. +// +// Hardware: an XMAX x YMAX (currently 11x11, see clocks.h) grid of WS2812 +// LEDs wired in a single serpentine ("boustrophedon") strip, plus a handful +// of extra individually-addressed LEDs (weekday row, minute dots, wifi/temp +// indicator) appended after the grid. Driven via NeoPixelBrightnessBus. +// A 5x7 pixel font (font5x7.h) is reused from the tube drivers to render +// small numeric overlays (temperature/humidity/date-time editor) on top of +// the same matrix. +// +// Language variants: the actual word layout (the `matrix[][]` character grid +// used to look up each word's on-screen position) and the HH:MM -> sentence +// templates (`sequence[][]`) are NOT in this file -- they live in clocks.h, +// one block per clock model: +// CLOCK_80 = English ("it is quarter past ten") +// CLOCK_81 = German ("es ist viertel nach zehn" style, uses "vor/nach halb") +// CLOCK_82 = Hungarian ("negyed tiz" / "harom negyed tiz" style) +// All three define WORDCLOCK and share this single .ino as their driver; +// only the word/sequence/matrix tables (and START_CORNER wiring direction) +// differ per config. Hungarian time phrasing is grammatically irregular +// (quarters count towards the NEXT hour, e.g. "negyed" = quarter of the +// hour to come, "haromnegyed" = three-quarters/quarter-to) -- see showTime() +// below for how the generic #1/#2 token mechanism copes with that. +// +// Note: wordclock_hun.txt in the repo root is NOT read by this code at +// runtime -- it is a captured serial debug log (DPRINT output) from a past +// firmware build, kept only as human-readable reference documentation of +// the Hungarian matrix/sequence layout, not a data file this .ino loads. +// ============================================================================ + +#ifdef WORDCLOCK + +//#define TEST_WORDCLOCK + +#include "font5x7.h" +char tubeDriver[] = "WORDCLOCK"; //identifies this driver in status/web page; "tubeDriver" name is generic/shared across all drivers +#define NUMDIGITS 4 +int maxDigits = NUMDIGITS; //read by the core (changeDigit(), etc.) even though this driver has no physical digit tubes; keeps digit[]/newDigit[] sized for the numeric overlay (temp/humidity/IP/editor) +long neoBrightness; +int colorStep = 1; +int maxPos; //highest linear pixel index inside the XMAX*YMAX word grid (excludes the extra indicator LEDs appended after it) +int offsetX = 0; //current animation scroll offset (in grid cells), used to slide the whole sentence in/out of view +int offsetY = 0; +int animOffsetX = 0; //target scroll direction/step for the current prm.animMode (see setAnimOffset()) +int animOffsetY = 0; + +enum LEDSTART {LEFTUP, RIGHTUP, LEFTDOWN, RIGHTDOWN }; //physical corner where LED index 0 of the serpentine strip starts -- set per clock model via START_CORNER in clocks.h +enum NEO_RGB_FEATURE {RGB, GRB, BRG, BGR}; + +#define NUMCOLS 5 +#define NUMROWS 7 +boolean _upsidedown = false; +byte row[NUMROWS]; + +char asciiConvert[] = "0123456789 -.APC~%oIF"; //maps a raw digit/code value (used as index) to the matching glyph in font5x7.h, for the numeric overlay display +char DPchar = 0xFF; //decimal point +byte dat[NUMDIGITS][NUMROWS]; //rasterized glyph rows for up to NUMDIGITS characters; in practice only dat[0] and dat[1] (the two digits of a number) are ever filled by writeNumericDisplay() + +#define FPS 60 //frames per second +#define FPS_MSEC 1000/FPS +#define COLORSATURATION 255 +#define WHITE_INDEX 192 +#define RANDOM_WHEEL_DISTANCE 30 //minimum wheel-distance a new random color must keep from the existing color(s) to be accepted, in random mode +#define RANDOM_MAX_COUNTER 100 //maximum number of attempts to find a sufficiently distinct new random color before giving up +#define RANDOM_FROM_ALL_PIXELS true //true or false: when generating a new random color, measure its distance from all pixels' colors, or only from the current pixel's own color + +RgbColor red(COLORSATURATION, 0, 0); +RgbColor red2(COLORSATURATION/2, 0, 0); +RgbColor green(0, COLORSATURATION, 0); +RgbColor blue(0, 0, COLORSATURATION); +RgbColor purple(COLORSATURATION, 0, COLORSATURATION); +RgbColor white(COLORSATURATION/2,COLORSATURATION/2,COLORSATURATION/2); +RgbColor black(0,0,0); +RgbColor yellow(255, 255, 0); +RgbColor orange(255,140,0); +RgbColor turqouise(0xB1,0xFF,0xEC); +RgbColor pink(0xFF,0xA0,0xB0); + +RgbColor dispColor; + +//some definitions are in clocks.h CLOCK_80 and CLOCK_81 +//(also CLOCK_82, the Hungarian variant -- see: struct wordDef, XMAX/YMAX, matrix[][], +// sequence[][], hours[], words[], extraDots[], WEEKDAY_POSX/Y, SENSOR_POSX/Y, START_CORNER) + +#define WORD_MAX sizeof(words)/sizeof(wordDef) //number of words +#define HOUR_MAX sizeof(hours)/sizeof(wordDef) //number of words + +//Total addressable pixel count: the XMAX*YMAX word-matrix grid plus 4 extra +//individually-addressed LEDs wired after it (see extraDots[] in clocks.h) used +//for the 1..4 "minute remainder" dots (word clocks only resolve to 5-minute +//steps in words, see showTime()/m5 below -- these dots show the 0..4 leftover minutes). +const int PixelCount = XMAX*YMAX+4; + + +//NeoGrbFeature gives BRGW (G and B swapped) +//NeoRgbFeature gives RBGW (G and B swapped) +//NeoBrgFeature gives BGRW (G and R swapped) + +#if defined(ESP32) + byte PixelPin = NEOPIXEL_PIN; //on ESP32 usable any pin below 32 + NeoPixelBrightnessBus strip(PixelCount+10,PixelPin); //NeoEsp32I2s1800KbpsMethod is used here because it is more reliable than NeoEsp32Rmt7Ws2812xMethod! +#else + #define NEOPIXEL_PIN 3 + byte PixelPin = 3; // on 8266 it MUST use GPIO3 (RX pin) + NeoPixelBrightnessBus strip(PixelCount+10); +#endif + +NeoGamma colorGamma; + +RgbColor Wheel(byte WheelPos) { + if (WheelPos == WHITE_INDEX) return white; + WheelPos = 255 - WheelPos; + if(WheelPos < 85) { + return RgbColor(255 - WheelPos * 3, 0, WheelPos * 3); + } else if(WheelPos < 170) { + WheelPos -= 85; + return RgbColor(0, WheelPos * 3, 255 - WheelPos * 3); + } else { + WheelPos -= 170; + return RgbColor(WheelPos * 3, 255 - WheelPos * 3, 0); + } +} + +//Converts a logical (column,row) letter position in the word matrix -- 1-based, +//x in [1..XMAX], y in [1..YMAX] -- into the linear NeoPixel strip index. +//This is necessary because the physical strip is wired as a single serpentine +//("boustrophedon": row 0 left-to-right, row 1 right-to-left, row 2 left-to-right, ...) +//starting from whichever physical corner START_CORNER says (set per clock model +//in clocks.h, since different panel builds route the data-in wire to a different +//corner). x/y are first normalized to 0-based, then mirrored/flipped as needed for +//the chosen starting corner, then folded into the zig-zag row order, and finally +//(for the "up" corners) the whole index is mirrored via maxPos-pos because index 0 +//of the physical strip is at the BOTTOM of the panel even when the logical/visual +//starting corner is at the top. +int inline convertXYtoPOS(int x, int y) { //calculate position, depending on the first LED's position + int pos; + //DPRINT("X:"); DPRINT(x); DPRINT(" Y:"); DPRINT(y); + if ((x>XMAX) || (y>YMAX) || (x<1) || (y<1)) { + DPRINT(x); DPRINT("/"); DPRINTLN(y); + return(maxPos+5); //out of area: park it just past the real grid+extraDots range (still inside the strip's PixelCount+10 safety buffer) so an out-of-range coordinate can't corrupt another LED nor crash + } + x--; y--; //move position 1,1 to 0,0 + + switch (START_CORNER) { + case LEFTUP: + y = YMAX-y-1; + if (y%2==0) pos = (y+1)*XMAX - x-1; + else pos = x + y*XMAX; + pos = maxPos - pos; //*prm.ledsPerHold; + break; + case RIGHTUP: + x = XMAX-x-1; + // POSSIBLE BUG: uses XMAX here where LEFTUP (above) uses YMAX for the same + // row-flip computation on y, and the y%2 zig-zag test's polarity is also + // inverted vs. LEFTUP (here "if (y%2)", there "if (y%2==0)") without an + // obvious reason tied to the extra x-mirroring done for this corner. + // With the current hardware XMAX==YMAX==11 this is silently harmless, + // but it looks like a copy/paste-and-tweak mistake from the LEFTUP case + // rather than an intentional difference, and would break on a + // non-square panel (XMAX != YMAX). Left unchanged; needs verification + // against a real RIGHTUP-wired panel (CLOCK_81/CLOCK_82 hardware). + y = XMAX-y-1; + if (y%2) pos = (y+1)*XMAX - x-1; + else pos = x + y*XMAX; + pos = maxPos - pos; //*prm.ledsPerHold; + break; + case LEFTDOWN: + if (y%2) pos = (y+1)*XMAX - x-1; + else pos = x + y*XMAX; + //pos = pos*prm.ledsPerHold; + break; + case RIGHTDOWN: + x = XMAX-x-1; + if (y%2) pos = (y+1)*XMAX - x-1; + else pos = x + y*XMAX; + //pos = pos*prm.ledsPerHold; + break; + default: + pos = 0; + } //end switch + //pos += prm.ledOffset; + //DPRINT(" POS:"); DPRINTLN(pos); DPRINT(" "); + return(pos); +} + + +//Fill the struct's fields with X,Y and len values +//For every entry in a word table (words[] or hours[], defined in clocks.h), find +//where that literal text substring first occurs inside the `matrix[][]` character +//grid (also in clocks.h, one row of text per matrix row) and record its start +//column/row + length. This is how the human-readable word list gets turned into +//pixel coordinates without having to hand-enter X/Y for every word. +//Note: some hour words rely on this "find literal substring" approach in a +//deliberately hacky way to dodge false-positive matches -- e.g. the Hungarian +//hours[] table (clocks.h, CLOCK_82) uses placeholder text like "__1" and +//"TIZEN__1" instead of the real letters "EGY"/"TIZENEGY" ("one"/"eleven"), +//because "EGY" is also a substring of other words in that matrix row (e.g. +//"NEGYED") and strstr() would otherwise lock onto the wrong occurrence. The +//placeholder is unique in the matrix text but still highlights the correct +//physical letters when displayed. +void generateTable(struct wordDef *w,int maxLine) { + DPRINT("\nNumber of lines:"); DPRINTLN(maxLine); + char* ret; + boolean found; + for (int num=0;num=XMAX) { + n = 0; + for (int i=0;i12) hr-=12; + return(hr); +} + +void firstRun() { //switch off (tube-only) settings that don't apply to a word-clock display, e.g. no digit blink/leading-zero-blank/double-blink animation and no cathode-protection exercise cycle exist on a word matrix; called once on first showTime() invocation + prm.enableBlink = false; + prm.showZero = false; + prm.enableTimeDisplay = true; //disable date display + prm.dateRepeatMin = 0; + prm.enableDoubleBlink = false; + prm.interval = 0; //disable cathode protect +} + + + +/* +RgbColor getLedColor(RgbColor in) { + RgbColor out; + + switch (prm.neoRgbFeature) { + case RGB: + out = in; + break; + case GRB: + out.R = in.G; + out.G = in.R; + out.B = in.B; + break; + case BRG: + out.R = in.B; + out.G = in.R; + out.B = in.G; + break; + case BGR: + out.R = in.B; + out.G = in.G; + out.B = in.R; + break; + } + return (out); +} +*/ + +void clearScr(){ //blank the whole panel; either instantly (static display, animMode 0) or as a row-by-row wipe (any scroll animation mode), so a transition between two times has a visible "clean slate" moment + if (prm.animMode==0) { + for (int i=0;itxt)!=0) { + //DPRINT(w->txt); DPRINT(" "); + strncpy(tmp,w->txt,sizeof(tmp)); //vestigial: this comparison/copy only fed the (now commented-out) DPRINT above; with that print disabled, tmp no longer affects the drawing below, which always executes unconditionally + } + + for (int i=0; ilen; i++) { + x1 = w->startX + i + offsetX; + y1 = w->startY + offsetY; + if ((x1>=1) && (x1<=XMAX) && (y1>=1) && (y1<=YMAX)) { + strip.SetPixelColor(convertXYtoPOS(x1,y1),dispColor); + } + } //endfor +} + + +void dispNum(int num){ //display one hour-number word (num must already be in the 1..12 range hours[] is indexed by, see clock24to12()) + #ifdef TEST_WORDCLOCK + if ((num<0) || (num>12)) { + DPRINT("dispNumError:"); DPRINT(num); DPRINT(" "); + } + else { + DPRINT(num); DPRINT(" >> "); DPRINT(hours[num].txt); DPRINT(" "); + } + #endif + disp(&hours[num]); +} + +void dispWord(char* w){ //find a word by its literal text in the words[] table (linear search -- table is tiny, so no need for anything smarter) and light it up + int num=0; + boolean found = false; + do { + if (strcmp(w,words[num].txt)==0) { + found = true; + #ifdef TEST_WORDCLOCK + DPRINT(words[num].txt); DPRINT(" "); + #endif + disp(&words[num]); + return; + } + num++; + } while (num=0;i--) { + for (int i=0;i0) && (offsetX<0)) offsetX += animOffsetX; + else if ((animOffsetX<0) && (offsetX>0)) offsetX += animOffsetX; + else offsetX = 0; + + if ((animOffsetY>0) && (offsetY<0)) offsetY += animOffsetY; + else if ((animOffsetY<0) && (offsetY>0)) offsetY += animOffsetY; + else offsetY = 0; + //DPRINT("\no:"); DPRINT(offsetX); DPRINT("/"); DPRINT(offsetY); DPRINT(" a:");DPRINT(animOffsetX); DPRINT("/"); DPRINT(animOffsetY); + showTime(hour(), minute()); +} + +void writeNumericDisplay(int num,int dot,boolean lowerDigitOnly) { //write big numbers to the LED matrix display (temp, humidity) + char dispChar; + int p,pos; + int st,offset; + + dispColor = askColor(num,dot); + num = abs(num); + for (int i=0;i<=1;i++) { //generate new line + if (i==0) { + dispChar = num/10; //upper digit + if (dispChar==0) dispChar = ' '; //space instead of leading zero + } + else + dispChar = num%10; //lower digit + + if (dispChar=0) { + pos = convertXYtoPOS(SENSOR_POSX+dot, SENSOR_POSY); + strip.SetPixelColor(pos,dispColor); //switch on sensor led; + } + strip.Show(); + delay(50); +} + +void displayAlarmShow() { + static boolean phase = true; + static unsigned long lastRun = 0; + int pos; + + if ((millis()-lastRun)<500) { + return; + } + lastRun = millis(); + + neoBrightness = MAXBRIGHTNESS; + for (int i=0;i0)) { + neoBrightness = (neoBrightness*lx)/long(MAXIMUM_LUX); + if (neoBrightness< c_MinBrightness) + neoBrightness = c_MinBrightness; + } + + neoBrightness = min(neoBrightness,long(RGB_MAX_BRIGHTNESS)); //safety only + if (!radarON) neoBrightness = 0; + + if (editorRunning) dot = 3; //editor mode + else if (newDigit[0] == TEMP_CHARCODE) dot = 0; //Celsius + else if (newDigit[0] == (TEMP_CHARCODE+5)) dot = 1; //Fahrenheit + else if (newDigit[0] == PERCENT_CHARCODE) dot = 2; //Humidity % + //DPRINT(newDigit[0]); DPRINT("/"); DPRINT(dot); DPRINT(" "); + + if (dot>=0) { + for (int i=0;i> 1; + else cmask = cmask << 1; + rows[r] = rows[r] << (7-NUMCOLS); + } +} + +void bPrint(byte in) { + for (int i=7;i>2;i--) { + boolean b = in & (1<28) col = red; + break; + case 1: //fahrenheit + if (num<=32) col = blue; + else if (num>82) col = red; + break; + case 2: //humidity + if (num<40) col = yellow; + else if (num>70) col = blue; + break; + default: + col = white; + break; + } + return(col); +} + + +void showTime(int h,int m){ //write the word-clock sentence to the LED display + static unsigned long lastRun = 0; + int ptrSeq,ptrWord,pos; + char ch; + char tmp[20]; + int m5; + + if (lastRun==0) {firstRun(); lastRun = 1;} + //if ((millis()-lastRun)>200) {DPRINT(h); DPRINT(":"); DPRINT(m); DPRINT(" ");lastRun = millis();} + + if ((h>23) || (m>59)) return; //safety only + + dispColor = Wheel(prm.rgbFixColor); + + strip.SetBrightness(neoBrightness); + #ifdef TEST_WORDCLOCK + DPRINTLN(""); DPRINT(h); DPRINT(":"); DPRINT(m); DPRINT(" "); + #endif + memset(tmp,0,sizeof(tmp)); + m5 = m / 5; //5min step + ptrSeq = 0; //pointer to the sequence chars + ptrWord = 0; //pointer to the found word chars + for (int i=0;i0) { //valid minute? + pos = convertXYtoPOS(WEEKDAY_POSX+weekday()-1, WEEKDAY_POSY); + strip.SetPixelColor(pos,dispColor); //switch on weekday led + + int dot = m % 5; //extra dot for 1..4 minutes + if (dot>0) { + for (int i=0;i0 + if (WiFi.status() == WL_CONNECTED) { + strip.SetPixelColor(convertXYtoPOS(SENSOR_POSX+3, SENSOR_POSY), dispColor); // 'W' sign for wifi + } + } //endif animation finished + strip.Show(); +} + + + +//------------------------------------------------------------- + +void clearTubes() {} +void writeDisplaySingle() {} +#endif diff --git a/firmware/uniclock/ESP32_UniClock2/z_neopixel.ino b/firmware/uniclock/ESP32_UniClock2/z_neopixel.ino new file mode 100644 index 0000000..278701d --- /dev/null +++ b/firmware/uniclock/ESP32_UniClock2/z_neopixel.ino @@ -0,0 +1,621 @@ +// ============================================================================ +// NeoPixel Tube Backlight subsystem +// ---------------------------------------------------------------------------- +// Drives addressable WS2812/WS2812B RGB LEDs mounted behind/around the tubes +// (Nixie/VFD/Numitron/etc.) using the NeoPixelBrightnessBus library (Makuna's +// NeoPixelBus). Each clock config maps its physical LEDs to logical tube +// positions via the `byte tubePixels[]` array defined in clocks.h (see the +// commented examples below), so the same effect code works regardless of how +// many LEDs are wired per tube or in which physical order. +// +// Enabled per-config via #define USE_NEOPIXEL in clocks.h. As of writing this +// is used by the large majority of CLOCK_NN configs that have RGB backlight +// hardware (e.g. CLOCK_4, 6, 8, 15, 17, 18, 22, 26, 30-32, 34-36, 40-46, 48-52, +// 55-57, 57A and others) - grep clocks.h for "#define USE_NEOPIXEL" (not the +// commented-out "//#define" ones) for the authoritative, current list. +// Mutually exclusive in practice with the simpler PWM-based backlight in +// pwmleds.ino (USE_PWMLEDS), which is used instead by configs that only wire +// 3 plain R/G/B PWM pins (e.g. CLOCK_54). +// +// Called from the core program (ESP32_UniClock2.ino): +// - setupNeopixel() : once from setup(), after pins/config are known. +// - doAnimationMakuna() : every loop() iteration, AND from inside Fdelay()'s +// busy-wait loop, so animations keep advancing even +// while the tube digits are mid-transition (Fdelay +// blocks for the tube-switch duration). It rate-limits +// itself internally (see FPS_MSEC / prm.rgbSpeed). +// - darkenNeopixels() : once from alarmSound(), when the alarm's ring +// period naturally times out (not when the alarm is +// silenced early via the button). +// ============================================================================ + +#ifdef USE_NEOPIXEL + +long neoBrightness; +int colorStep = 1; + +#define FPS 60 //frames per second +#define FPS_MSEC 1000/FPS //ms per animation frame, used as the minimum interval between doAnimationMakuna() updates (frame-rate cap) +#define COLORSATURATION 255 +#define WHITE_INDEX 192 //reserved "color wheel" position (out of 0..255) that Wheel() special-cases to return pure white instead of a hue +#define RANDOM_WHEEL_DISTANCE 30 //minimum wheel-distance a new random color must keep from the existing color(s) to be accepted, in random mode +#define RANDOM_MAX_COUNTER 100 //maximum number of attempts to find a sufficiently distinct new random color before giving up +#define RANDOM_FROM_ALL_PIXELS true //true or false: when generating a new random color, measure its distance from all pixels' colors, or only from the current pixel's own color + +RgbColor red(COLORSATURATION, 0, 0); +RgbColor red2(COLORSATURATION/2, 0, 0); +RgbColor green(0, COLORSATURATION, 0); +RgbColor blue(0, 0, COLORSATURATION); +RgbColor purple(COLORSATURATION, 0, COLORSATURATION); +RgbColor white(COLORSATURATION/2,COLORSATURATION/2,COLORSATURATION/2); +RgbColor black(0,0,0); + +//Definition: which pixels are near which tube? Make sure to set all pixels of the strip! +// Valid: 0..(maxDigits-1). If a number is equal to maxDigits or higher, that pixel always stays dark! +//byte tubePixels[] = {0,1,2,3}; //4 tubes, single leds +//byte tubePixels[] = {3,2,1,0}; //4 tubes, single leds, reverse direction +//byte tubePixels[] = {0,9,1,9,2,9,3}; //4 tubes, single leds, 3 leds not used +//byte tubePixels[] = {0,0,1,2,3,3}; //4 tubes, 6 leds +//byte tubePixels[] = {0,1,2,3,4,5}; //6 tubes, single leds +//byte tubePixels[] = {5,4,3,2,1,0}; //6 tubes, single leds, reverse direction +//byte tubePixels[] = {0,1,2,3,4,5,6,7}; //8 tubes, single leds +//byte tubePixels[] = {3,2,6,1,0}; //Numitron 4 tubes, 4 x single leds + 1. The extra led in the middle is unused and always stays dark! +//byte tubePixels[] = {0,1,2,3,3,2,1,0}; //4 tubes, double row, 8 leds +//byte tubePixels[] = {0,0,1,1,2,2,3,3}; //4 tubes, double row, 8 leds +//byte tubePixels[] = {3,3,2,2,1,1,0,0, 0,0,1,1,2,2,3,3}; //4 tubes, double row, 16 leds (GB) +//byte tubePixels[] = {0,0,1,1,2,2,3,3,3,3,2,2,1,1,0,0,0}; //4 tubes, double row, 17 leds (GP) +//byte tubePixels[] = {0,0,0,1,1,2,2,3,3,3,3, 3,3,2,2,2,1,1,0,0}; //4 tubes, double row, 20 leds (Robi) + +// tubePixels is a byte[] so sizeof(tubePixels) already equals its element count; PixelCount is +// set 2 *higher* than that count on purpose, as headroom for a couple of always-dark "spare" +// pixels at the tail of the strip (see the further +4 used when sizing `strip` below). Every loop +// in this file iterates `for (i=0; i= any real maxDigits/tube-index value) for those +// positions, preserving the original "always dark / never matches a real tube" intent safely. +inline byte tubeForPixel(int i) { + return (i < (int)sizeof(tubePixels)) ? tubePixels[i] : 255; +} + +//NeoGrbFeature gives BRGW (G and B swapped) +//NeoRgbFeature gives RBGW (G and B swapped) +//NeoBrgFeature gives BGRW (G and R swapped) + +#if defined(ESP32) + byte PixelPin = NEOPIXEL_PIN; //on ESP32 usable any pin below 32 + //Strip is allocated with PixelCount+4 physical pixels (a few more than + //PixelCount, which is itself already tubePixels-length+2 - see note above) + //as headroom so the driver never writes past its own frame buffer, even + //though every loop only ever indexes strip positions 0..PixelCount-1. + //NeoEsp32I2s1800KbpsMethod drives the strip via the ESP32's I2S peripheral + //(DMA-fed) instead of RMT; this is more tolerant of interrupts/WiFi jitter + //than NeoEsp32Rmt7Ws2812xMethod, which can glitch the strict WS2812 bit + //timing (~1.25us/bit) if an ISR delays the RMT hardware mid-frame. + NeoPixelBrightnessBus strip(PixelCount+4,PixelPin); //NeoEsp32I2s1800KbpsMethod is used here because it is more reliable than NeoEsp32Rmt7Ws2812xMethod! +#else + #define NEOPIXEL_PIN 3 + byte PixelPin = 3; // on 8266 it MUST use GPIO3 (RX pin) + //8266 has no I2S/RMT WS2812 helper here; Neo800KbpsMethod bit-bangs the + //protocol with cycle-counted delays, which is why GPIO3/RX is mandated - + //it's one of the few pins whose timing isn't disturbed by the UART/boot ROM. + NeoPixelBrightnessBus strip(PixelCount+4); +#endif + +//Gamma-correction lookup table object - NOTE: currently declared but never +//applied (colorGamma.Correct(...) is not called anywhere in this file), so +//colors are sent to the strip linearly, not perceptually corrected. Possible +//leftover from an earlier version / an intended-but-unfinished improvement. +NeoGamma colorGamma; + +void setupNeopixel() { + DPRINTLN("Setup NeoPixel LEDS"); + regPin(PixelPin,"NEOPIXEL_PIN"); + DPRINT("Pixel count: "); DPRINTLN(PixelCount); + DPRINT("Brightness:"); DPRINT(c_MinBrightness); DPRINT(" - "); DPRINTLN(c_MaxBrightness); + neoBrightness = prm.rgbBrightness; + strip.Begin(); //initialize the underlying I2S/RMT/bit-bang driver and GPIO + strip.Show(); //push the (still all-black, default-constructed) buffer out so the strip starts dark, not showing stale/garbage pixel data + strip.SetBrightness(neoBrightness); + fixColor(prm.rgbFixColor); //apply the persisted fixed color immediately so the backlight is correct even before the first doAnimationMakuna() tick +} + +//Classic Adafruit/NeoPixel "color wheel": maps a single byte (0..255) onto a +//position around the RGB hue circle, producing a smooth rainbow as WheelPos +//increases. Split into 3 arcs of 85 steps (255/3) - red->blue, blue->green, +//green->red - each linearly interpolating between the two arc endpoints +//(hence the `* 3` scaling: 85*3 = 255, so each arc's channel spans the full +//0..255 range). WHITE_INDEX (192) is carved out of the wheel as a special +//sentinel value that instead returns solid white, since a pure hue rotation +//can never itself produce white (R=G=B). +RgbColor Wheel(byte WheelPos) { + + if (WheelPos == WHITE_INDEX) + return white; + + WheelPos = 255 - WheelPos; //invert so increasing WheelPos visually rotates the same direction as prm.rgbDir expects + if(WheelPos < 85) { + return RgbColor(255 - WheelPos * 3, 0, WheelPos * 3); + } else if(WheelPos < 170) { + WheelPos -= 85; + return RgbColor(0, WheelPos * 3, 255 - WheelPos * 3); + } else { + WheelPos -= 170; + return RgbColor(WheelPos * 3, 255 - WheelPos * 3, 0); + } +} + +//Flashes all tube-backlight pixels white on/off at 2Hz (500ms half-period) +//while the alarm is ringing, overriding whatever normal rgbEffect was active. +//Brightness is hardcoded to 200 here (ignores prm.rgbBrightness/autoBrightness) +//so the alarm flash is always clearly visible regardless of the user's normal +//backlight/dimming settings. Called every doAnimationMakuna() tick while +//alarmON is true (see that function), so the 500ms gate below is what actually +//paces the blink - doAnimationMakuna() itself may be called much more often. +void alarmLight() { + static unsigned long lastRun = 0; + static byte counter; + + if ((millis()-lastRun)<500) return; + lastRun = millis(); + counter++; + strip.SetBrightness(200); + for(volatile int i=0; i=maxDigits marks a "no tube here" filler pixel - always kept dark, even during the flash + strip.SetPixelColor(i, RgbColor(255,255,255)); + else + strip.SetPixelColor(i,black); + } + strip.Show(); +} + +//Called once from the core's alarmSound() when the alarm's ring period times +//out naturally, to make sure the alarmLight() flashing doesn't get left mid- +//flash (i.e. stuck lit white) - forces brightness to 0 and every pixel black, +//then pushes it out immediately rather than waiting for the next +//doAnimationMakuna() tick to restore the normal effect. +void darkenNeopixels() { + strip.SetBrightness(0); + for (int i=0;i smaller multiplier -> shorter delay -> faster +//stepping. max(0, ...) is defensive: rgbSpeed is expected to stay in 1..255 +//per the settings JSON, so (258-speed) should never go negative, but this +//guards against underflow/overflow if it ever does. +void rainbow2() { //Rainbow Stepper + static int16_t j=0; + static int16_t i=0; + static unsigned long lastRun = 0; + int steps = 15; + unsigned long spd = max(0,steps * (258-prm.rgbSpeed)); + + if ((millis()-lastRun)=maxDigits) { //walked off the end of the tubes -> wrap back to the first tube and advance the color phase by a full "steps" jump + if (j>255) j=0; + i=0; + j+=steps; + } //endif + + if (i<0) { //same wrap, but for the reverse (prm.rgbDir==0) direction + if (j>255) j=0; + i=maxDigits-1; + j+=steps; + } //endif + + //DPRINT(i); DPRINT("/"); DPRINTLN(j); + + setPixels(i, Wheel((j==192) ? j+1 : j)); //skip WHITE_INDEX (192) here too, same reasoning as in rainbow() + + if (prm.rgbDir)i++; + else i--; + + strip.Show(); +} + +//All tubes show the same solid color (c), while the overall strip brightness +//ramps down from neoBrightness to c_MinBrightness and back up (a "breathing" +//dimmer), picking a new random color each time it bottoms out. `counter` is +//the amount subtracted from neoBrightness; dir flips its sign at each end of +//the ramp. The `(cnt%3==0)` half of the OR is dead in practice: cnt is a +//static byte that is declared but never incremented anywhere in this +//function, so it is always 0 and `cnt%3==0` is always true - meaning the +//`(neoBrightness-counter)>20` guard is effectively bypassed and counter +//advances by `dir` (i.e. 1) every single call regardless. Net effect: the +//breathing speed is fixed at 1 brightness step per doAnimationMakuna() tick, +//not throttled as the "> 20" condition alone would suggest. +void effect1() { //color dimmer + static int c = 0; //actual color + static int dir = 1; //direction + static long counter = 0; + static byte cnt = 0; + + strip.SetBrightness(max(0l,neoBrightness - counter)); + + for(volatile int i=0; i=maxDigits) + strip.SetPixelColor(i,black); + else + strip.SetPixelColor(i, Wheel(c)); + } + + // POSSIBLE BUG: cnt is never incremented (nor reset), so `cnt%3==0` is + // always true and this condition always fires - see function comment above. + if (((neoBrightness - counter)>20) || (cnt%3 == 0)) { + counter += dir; + //DPRINTLN(counter); + } + + if (counter <= 0) { + dir = 1; //hit the bright end of the ramp -> start dimming again + } + if (counter >= (neoBrightness-c_MinBrightness)) { + c = random(0,256); //hit the dim floor -> pick a new random hue and start brightening back up + dir = -1; + } + strip.Show(); +} + + +//All tubes show the same solid color, but the color itself jumps to a new +//random hue every `100*(258-rgbSpeed)` ms - i.e. higher rgbSpeed means shorter +//wait, so colors change faster. Unlike effect1, there's no fade between colors +//here, just an instant switch once the timer elapses. +void effect2() { //random color picker + static int c = random(0,256); //actual color + static unsigned long lastRun = 0; + + if ((millis()-lastRun)>100*max(0,(258-prm.rgbSpeed))) { + lastRun = millis(); + c = random(0,256); + } + + for(volatile int i=0; i=maxDigits) + strip.SetPixelColor(i, black); + else + strip.SetPixelColor(i, Wheel(c)); + } + strip.Show(); +} + +//Distance between two positions on the 0..255 color wheel, accounting for +//wraparound (the wheel is circular: color 255 is adjacent to color 0). d1 is +//the naive linear distance; d2 is the distance going "the other way around" +//through the wrap point. Returning the smaller of the two gives the true +//shortest hue distance, which the random-color pickers below use to make sure +//a freshly rolled random color is visually distinct from the current one +//(not just numerically different, since e.g. 250 and 5 are close in hue +//despite being far apart as raw numbers). +int colorDistance(int c1,int c2) { + int d1,d2; + d1 = abs(c1-c2); + d2 = (c1 cycle through the fixed palette table c[] +// enableRandom=true -> pick random colors (rejecting ones too +// close to the current color, via +// colorDistance()/RANDOM_WHEEL_DISTANCE) +// eachPixelRandom=true (implies enableRandom) -> every tube gets its own +// independent target color instead of all +// tubes chasing a single shared color +//For each tube, actColor[] is faded step-by-step from oldColor[] towards +//newColor[] (see the fade loop near the bottom); once a tube's actColor +//catches up to its newColor, the "current tube" pointer i advances (direction +//per prm.rgbDir) and, once it wraps around all tubes (or immediately, in +//eachPixelRandom mode), a new target color is picked. `step` controls how many +//wheel-positions actColor moves per call, chosen so the fade from old to new +//color takes roughly 20 calls regardless of how far apart they are. +void effect3(boolean enableRandom,boolean eachPixelRandom) { + static const int c[] = {255,5,12,22,30,40,54,62,78,85,100,110,122,137,177,WHITE_INDEX,210,227,240}; + static const int cMax = sizeof(c) / sizeof(c[0]); //size of array + //Fixed-size [10] per-tube state arrays, indexed 0..maxDigits-1: only safe as + //long as no config's maxDigits exceeds 10 (all loops below use maxDigits as + //the bound, never sizeof(newColor)/sizeof(...), so this isn't re-checked at + //runtime - a config with more than 10 tubes would silently overrun these). + static int newColor[10]; + static int oldColor[10]; + static int actColor[10]; + static int i = 2; + static int step = 1; + static int idx = 0; + static boolean firstRun = true; + static int counter = 0; + static int dir = 1; + int newC = 0; + boolean changeColor = false; + boolean colorOK; + + if (firstRun) { + firstRun = false; + for (volatile int k=0;k=maxDigits) { i=0; changeColor = true;} + else if (i<0) {i=maxDigits-1; changeColor = true;} + + if (eachPixelRandom) { //each pixel is random color + changeColor = true; + } + + if (changeColor) { + changeColor = false; + if (enableRandom) { + counter = 0; + while (true) { //random color + newC = random(0,256); //get a new random color + colorOK = true; + if (RANDOM_FROM_ALL_PIXELS) { + //require the candidate to be far enough (RANDOM_WHEEL_DISTANCE) from + //every tube's current target color, not just this tube's - avoids two + //tubes converging on near-identical hues at the same time + for (int j=0;jRANDOM_MAX_COUNTER)) break; + } //end while + //DPRINT("Pix:"); DPRINT(i); DPRINT(" old:"); DPRINT(newColor[i]); DPRINT(" / newC:"); DPRINT(newC); DPRINT(" No of tries:"); DPRINTLN(counter); + } + else { //new color from table + idx++; if (idx>=cMax) idx = 0; + newC = c[idx]; + } + + if (eachPixelRandom) { + oldColor[i] = newColor[i]; + newColor[i] = newC; + } + else { + for (volatile int k=0;k oldColor[i]) + dir = 1; + else + dir = -1; + + //DPRINT("Old-New simple distance:"); DPRINT(abs(newC-oldColor[i])); DPRINT(" step:"); DPRINTLN(step); + } //endif changeColor + + actColor[i] = oldColor[i]; //starting color + } //endif (newColor[i] == actColor[i]) + + + if (actColor[i] != newColor[i]) { //next rainbow color + if (dir==1) actColor[i] = min(newColor[i],actColor[i]+step); + else actColor[i] = max(newColor[i],actColor[i]-step); + } + + for (int j=0;jnew +//color fade on its own schedule (no shared "current tube" pointer like +//effect3), so all tubes are constantly, independently drifting between random +//hues. Same [10]-element fixed-array-size caveat as effect3 above applies here. +void effect4() { //every pixel is random changer + static int newColor[10]; + static int oldColor[10]; + static int actColor[10]; + static int step[10]; + static boolean firstRun = true; + //unsigned long spd = max(0, 25*(258-prm.rgbSpeed)); + + + //if ((millis()-lastRun)=maxDigits)) + strip.SetPixelColor(i,black); + else + strip.SetPixelColor(i,Wheel(col)); + } + strip.Show(); +} + +//Knight Rider "KITT" scanner: a single lit tube (using the fixed +//prm.rgbFixColor, not the rainbow wheel) bounces back and forth across all +//tubes. counter2 divides doAnimationMakuna()'s call rate by 10 so the scanner +//advances one tube every 10 ticks instead of every tick (prm.rgbSpeed is not +//consulted here, unlike most other effects - the bounce speed is fixed). +void kitt() { + static int dir = 1; //direction + static int counter = 0; + static int counter2 = 0; + + counter2 ++; + if (counter2 >=10) { + counter2 = 0; + counter += dir; + if (counter >= maxDigits-1) dir = -1; //bounce off the last tube + else if (counter <=0 ) dir = 1; //bounce off the first tube + } + for (int i=0;i()` and `is()` +* Add safe bool idiom in `JsonString` +* Add support for NUL in string values (issue #1646) +* Add support for arbitrary array rank in `copyArray()` +* Add support for `char[][]` in `copyArray()` +* Remove `DeserializationError == bool` and `DeserializationError != bool` +* Renamed undocumented function `isUndefined()` to `isUnbound()` +* Fix `JsonVariant::memoryUsage()` for raw strings +* Fix `call of overloaded 'swap(BasicJsonDocument&, BasicJsonDocument&)' is ambiguous` (issue #1678) +* Fix inconsistent pool capacity between `BasicJsonDocument`'s copy and move constructors +* Fix inconsistent pool capacity between `BasicJsonDocument`'s copy and move assignments +* Fix return type of `StaticJsonDocument::operator=` +* Avoid pool reallocation in `BasicJsonDocument`'s copy assignment if capacity is the same +* Avoid including `Arduino.h` when all its features are disabled (issue #1692, PR #1693 by @paulocsanz) +* Assume `PROGMEM` is available as soon as `ARDUINO` is defined (consequence of #1693) + +v6.18.5 (2021-09-28) +------- + +* Set `ARDUINOJSON_EMBEDDED_MODE` to `1` on Nios II (issue #1657) + +v6.18.4 (2021-09-06) +------- + +* Fixed error `'dummy' may be used uninitialized` on GCC 11 +* Fixed error `expected unqualified-id before 'const'` on GCC 11 (issue #1622) +* Filter: exact match takes precedence over wildcard (issue #1628) +* Fixed deserialization of `\u0000` (issue #1646) + +v6.18.3 (2021-07-27) +------- + +* Changed return type of `convertToJson()` and `Converter::toJson()` to `void` +* Added `as()` and `is()` + +v6.18.2 (2021-07-19) +------- + +* Removed a symlink because the Arduino Library Specification forbids it + +v6.18.1 (2021-07-03) +------- + +* Fixed support for `volatile float` and `volatile double` (issue #1557) +* Fixed error `[Pe070]: incomplete type is not allowed` on IAR (issue #1560) +* Fixed `serializeJson(doc, String)` when allocation fails (issue #1572) +* Fixed clang-tidy warnings (issue #1574, PR #1577 by @armandas) +* Added fake class `InvalidConversion` to easily identify invalid conversions (issue #1585) +* Added support for `std::string_view` (issue #1578, PR #1554 by @0xFEEDC0DE64) +* Fixed warning `definition of implicit copy constructor for 'MsgPackDeserializer' is deprecated because it has a user-declared copy assignment operator` +* Added `JsonArray::clear()` (issue #1597) +* Fixed `JsonVariant::as()` (issue #1601) +* Added support for ESP-IDF component build (PR #1562 by @qt1, PR #1599 by @andreaskuster) + +v6.18.0 (2021-05-05) +------- + +* Added support for custom converters (issue #687) +* Added support for `Printable` (issue #1444) +* Removed support for `char` values, see below (issue #1498) +* `deserializeJson()` leaves `\uXXXX` unchanged instead of returning `NotSupported` +* `deserializeMsgPack()` inserts `null` instead of returning `NotSupported` +* Removed `DeserializationError::NotSupported` +* Added `JsonVariant::is()` (issue #1412) +* Added `JsonVariant::is()` (issue #1412) +* Changed `JsonVariantConst::is()` to return `false` (issue #1412) +* Simplified `JsonVariant::as()` to always return `T` (see below) +* Updated folders list in `.mbedignore` (PR #1515 by @AGlass0fMilk) +* Fixed member-call-on-null-pointer in `getMember()` when array is empty +* `serializeMsgPack(doc, buffer, size)` doesn't add null-terminator anymore (issue #1545) +* `serializeJson(doc, buffer, size)` adds null-terminator only if there is enough room +* PlatformIO: set `build.libArchive` to `false` (PR #1550 by @askreet) + +> ### BREAKING CHANGES +> +> #### Support for `char` removed +> +> We cannot cast a `JsonVariant` to a `char` anymore, so the following will break: +> ```c++ +> char age = doc["age"]; // error: no matching function for call to 'variantAs(VariantData*&)' +> ``` +> Instead, you must use another integral type, such as `int8_t`: +> ```c++ +> int8_t age = doc["age"]; // OK +> ``` +> +> Similarly, we cannot assign from a `char` anymore, so the following will break: +> ```c++ +> char age; +> doc["age"] = age; // error: no matching function for call to 'VariantRef::set(const char&)' +> ``` +> Instead, you must use another integral type, such as `int8_t`: +> ```c++ +> int8_t age; +> doc["age"] = age; // OK +> ``` +> A deprecation warning with the message "Support for `char` is deprecated, use `int8_t` or `uint8_t` instead" was added to allow a smooth transition. +> +> #### `as()` always returns `T` +> +> Previously, `JsonVariant::as()` could return a type different from `T`. +> The most common example is `as()` that returned a `const char*`. +> While this feature simplified a few use cases, it was confusing and complicated the +> implementation of custom converters. +> +> Starting from this version, `as` doesn't try to auto-correct the return type and always return `T`, +> which means that you cannot write this anymore: +> +> ```c++ +> Serial.println(doc["sensor"].as()); // error: invalid conversion from 'const char*' to 'char*' [-fpermissive] +> ``` +> +> Instead, you must write: +> +> ```c++ +> Serial.println(doc["sensor"].as()); // OK +> ``` +> +> A deprecation warning with the message "Replace `as()` with `as()`" was added to allow a smooth transition. +> +> #### `DeserializationError::NotSupported` removed +> +> On a different topic, `DeserializationError::NotSupported` has been removed. +> Instead of returning this error: +> +> * `deserializeJson()` leaves `\uXXXX` unchanged (only when `ARDUINOJSON_DECODE_UNICODE` is `0`) +> * `deserializeMsgPack()` replaces unsupported values with `null`s +> +> #### Const-aware `is()` +> +> Lastly, a very minor change concerns `JsonVariantConst::is()`. +> It used to return `true` for `JsonArray` and `JsonOject`, but now it returns `false`. +> Instead, you must use `JsonArrayConst` and `JsonObjectConst`. + +v6.17.3 (2021-02-15) +------- + +* Made `JsonDocument`'s destructor protected (issue #1480) +* Added missing calls to `client.stop()` in `JsonHttpClient.ino` (issue #1485) +* Fixed error `expected ')' before 'char'` when `isdigit()` is a macro (issue #1487) +* Fixed error `definition of implicit copy constructor is deprecated` on Clang 10 +* PlatformIO: set framework compatibility to `*` (PR #1490 by @maxgerhardt) + +v6.17.2 (2020-11-14) +------- + +* Fixed invalid conversion error in `operator|(JsonVariant, char*)` (issue #1432) +* Changed the default value of `ARDUINOJSON_ENABLE_PROGMEM` (issue #1433). + It now checks that the `pgm_read_XXX` macros are defined before enabling `PROGMEM`. + +v6.17.1 (2020-11-07) +------- + +* Fixed error `ambiguous overload for 'operator|'` (issue #1411) +* Fixed `operator|(MemberProxy, JsonObject)` (issue #1415) +* Allowed more than 32767 values in non-embedded mode (issue #1414) + +v6.17.0 (2020-10-19) +------- + +* Added a build failure when nullptr is defined as a macro (issue #1355) +* Added `JsonDocument::overflowed()` which tells if the memory pool was too small (issue #1358) +* Added `DeserializationError::EmptyInput` which tells if the input was empty +* Added `DeserializationError::f_str()` which returns a `const __FlashStringHelper*` (issue #846) +* Added `operator|(JsonVariantConst, JsonVariantConst)` +* Added filtering for MessagePack (issue #1298, PR #1394 by Luca Passarella) +* Moved float convertion tables to PROGMEM +* Fixed `JsonVariant::set((char*)0)` which returned false instead of true (issue #1368) +* Fixed error `No such file or directory #include ` (issue #1381) + +v6.16.1 (2020-08-04) +------- + +* Fixed `deserializeJson()` that stopped reading after `{}` (issue #1335) + +v6.16.0 (2020-08-01) +------- + +* Added comparisons (`>`, `>=`, `==`, `!=`, `<`, and `<=`) between `JsonVariant`s +* Added string deduplication (issue #1303) +* Added `JsonString::operator!=` +* Added wildcard key (`*`) for filters (issue #1309) +* Set `ARDUINOJSON_DECODE_UNICODE` to `1` by default +* Fixed `copyArray()` not working with `String`, `ElementProxy`, and `MemberProxy` +* Fixed error `getOrAddElement is not a member of ElementProxy` (issue #1311) +* Fixed excessive stack usage when compiled with `-Og` (issues #1210 and #1314) +* Fixed `Warning[Pa093]: implicit conversion from floating point to integer` on IAR compiler (PR #1328 by @stawiski) + +v6.15.2 (2020-05-15) +------- + +* CMake: don't build tests when imported in another project +* CMake: made project arch-independent +* Visual Studio: fixed error C2766 with flag `/Zc:__cplusplus` (issue #1250) +* Added support for `JsonDocument` to `copyArray()` (issue #1255) +* Added support for `enum`s in `as()` and `is()` (issue #1256) +* Added `JsonVariant` as an input type for `deserializeXxx()` + For example, you can do: `deserializeJson(doc2, doc1["payload"])` +* Break the build if using 64-bit integers with ARDUINOJSON_USE_LONG_LONG==0 + +v6.15.1 (2020-04-08) +------- + +* Fixed "maybe-uninitialized" warning (issue #1217) +* Fixed "statement is unreachable" warning on IAR (issue #1233) +* Fixed "pointless integer comparison" warning on IAR (issue #1233) +* Added CMake "install" target (issue #1209) +* Disabled alignment on AVR (issue #1231) + +v6.15.0 (2020-03-22) +------- + +* Added `DeserializationOption::Filter` (issue #959) +* Added example `JsonFilterExample.ino` +* Changed the array subscript operator to automatically add missing elements +* Fixed "deprecated-copy" warning on GCC 9 (fixes #1184) +* Fixed `MemberProxy::set(char[])` not duplicating the string (issue #1191) +* Fixed enums serialized as booleans (issue #1197) +* Fixed incorrect string comparison on some platforms (issue #1198) +* Added move-constructor and move-assignment to `BasicJsonDocument` +* Added `BasicJsonDocument::garbageCollect()` (issue #1195) +* Added `StaticJsonDocument::garbageCollect()` +* Changed copy-constructor of `BasicJsonDocument` to preserve the capacity of the source. +* Removed copy-constructor of `JsonDocument` (issue #1189) + +> ### BREAKING CHANGES +> +> #### Copy-constructor of `BasicJsonDocument` +> +> In previous versions, the copy constructor of `BasicJsonDocument` looked at the source's `memoryUsage()` to choose its capacity. +> Now, the copy constructor of `BasicJsonDocument` uses the same capacity as the source. +> +> Example: +> +> ```c++ +> DynamicJsonDocument doc1(64); +> doc1.set(String("example")); +> +> DynamicJsonDocument doc2 = doc1; +> Serial.print(doc2.capacity()); // 8 with ArduinoJson 6.14 +> // 64 with ArduinoJson 6.15 +> ``` +> +> I made this change to get consistent results between copy-constructor and move-constructor, and whether RVO applies or not. +> +> If you use the copy-constructor to optimize your documents, you can use `garbageCollect()` or `shrinkToFit()` instead. +> +> #### Copy-constructor of `JsonDocument` +> +> In previous versions, it was possible to create a function that take a `JsonDocument` by value. +> +> ```c++ +> void myFunction(JsonDocument doc) {} +> ``` +> +> This function gives the wrong clues because it doesn't receive a copy of the `JsonDocument`, only a sliced version. +> It worked because the copy constructor copied the internal pointers, but it was an accident. +> +> From now, if you need to pass a `JsonDocument` to a function, you must use a reference: +> +> ```c++ +> void myFunction(JsonDocument& doc) {} +> ``` + +v6.14.1 (2020-01-27) +------- + +* Fixed regression in UTF16 decoding (issue #1173) +* Fixed `containsKey()` on `JsonVariantConst` +* Added `getElement()` and `getMember()` to `JsonVariantConst` + +v6.14.0 (2020-01-16) +------- + +* Added `BasicJsonDocument::shrinkToFit()` +* Added support of `uint8_t` for `serializeJson()`, `serializeJsonPretty()`, and `serializeMsgPack()` (issue #1142) +* Added `ARDUINOJSON_ENABLE_COMMENTS` to enable support for comments (defaults to 0) +* Auto enable support for `std::string` and `std::stream` on modern compilers (issue #1156) + (No need to define `ARDUINOJSON_ENABLE_STD_STRING` and `ARDUINOJSON_ENABLE_STD_STREAM` anymore) +* Improved decoding of UTF-16 surrogate pairs (PR #1157 by @kaysievers) + (ArduinoJson now produces standard UTF-8 instead of CESU-8) +* Added `measureJson`, `measureJsonPretty`, and `measureMsgPack` to `keywords.txt` + (This file is used for syntax highlighting in the Arduino IDE) +* Fixed `variant.is()` +* Fixed value returned by `serializeJson()`, `serializeJsonPretty()`, and `serializeMsgPack()` when writing to a `String` +* Improved speed of `serializeJson()`, `serializeJsonPretty()`, and `serializeMsgPack()` when writing to a `String` + +> ### BREAKING CHANGES +> +> #### Comments +> +> Support for comments in input is now optional and disabled by default. +> +> If you need support for comments, you must defined `ARDUINOJSON_ENABLE_COMMENTS` to `1`; otherwise, you'll receive `InvalidInput` errors. +> +> ```c++ +> #define ARDUINOJSON_ENABLE_COMMENTS 1 +> #include +> ``` + +v6.13.0 (2019-11-01) +------- + +* Added support for custom writer/reader classes (issue #1088) +* Added conversion from `JsonArray` and `JsonObject` to `bool`, to be consistent with `JsonVariant` +* Fixed `deserializeJson()` when input contains duplicate keys (issue #1095) +* Improved `deserializeMsgPack()` speed by reading several bytes at once +* Added detection of Atmel AVR8/GNU C Compiler (issue #1112) +* Fixed deserializer that stopped reading at the first `0xFF` (PR #1118 by @mikee47) +* Fixed dangling reference in copies of `MemberProxy` and `ElementProxy` (issue #1120) + +v6.12.0 (2019-09-05) +------- + +* Use absolute instead of relative includes (issue #1072) +* Changed `JsonVariant::as()` to return `true` for any non-null value (issue #1005) +* Moved ancillary files to `extras/` (issue #1011) + +v6.11.5 (2019-08-23) +------- + +* Added fallback implementations of `strlen_P()`, `strncmp_P()`, `strcmp_P()`, and `memcpy_P()` (issue #1073) + +v6.11.4 (2019-08-12) +------- + +* Added `measureJson()` to the `ArduinoJson` namespace (PR #1069 by @nomis) +* Added support for `basic_string` (issue #1045) +* Fixed example `JsonConfigFile.ino` for ESP8266 +* Include `Arduino.h` if `ARDUINO` is defined (PR #1071 by @nomis) + +v6.11.3 (2019-07-22) +------- + +* Added operators `==` and `!=` for `JsonDocument`, `ElementProxy`, and `MemberProxy` +* Fixed comparison of `JsonVariant` when one contains a linked string and the other contains an owned string (issue #1051) + +v6.11.2 (2019-07-08) +------- + +* Fixed assignment of `JsonDocument` to `JsonVariant` (issue #1023) +* Fix invalid conversion error on Particle Argon (issue #1035) + +v6.11.1 (2019-06-21) +------- + +* Fixed `serialized()` not working with Flash strings (issue #1030) + +v6.11.0 (2019-05-26) +------- + +* Fixed `deserializeJson()` silently accepting a `Stream*` (issue #978) +* Fixed invalid result from `operator|` (issue #981) +* Made `deserializeJson()` more picky about trailing characters (issue #980) +* Added `ARDUINOJSON_ENABLE_NAN` (default=0) to enable NaN in JSON (issue #973) +* Added `ARDUINOJSON_ENABLE_INFINITY` (default=0) to enable Infinity in JSON +* Removed implicit conversion in comparison operators (issue #998) +* Added lexicographical comparison for `JsonVariant` +* Added support for `nullptr` (issue #998) + +> ### BREAKING CHANGES +> +> #### NaN and Infinity +> +> The JSON specification allows neither NaN not Infinity, but previous +> versions of ArduinoJson supported it. Now, ArduinoJson behaves like most +> other libraries: a NaN or and Infinity in the `JsonDocument`, becomes +> a `null` in the output JSON. Also, `deserializeJson()` returns +> `InvalidInput` if the JSON document contains NaN or Infinity. +> +> This version still supports NaN and Infinity in JSON documents, but +> it's disabled by default to be compatible with other JSON parsers. +> If you need the old behavior back, define `ARDUINOJSON_ENABLE_NAN` and +> `ARDUINOJSON_ENABLE_INFINITY` to `1`;: +> +> ```c++ +> #define ARDUINOJSON_ENABLE_NAN 1 +> #define ARDUINOJSON_ENABLE_INFINITY 1 +> #include +> ``` +> +> #### The "or" operator +> +> This version slightly changes the behavior of the | operator when the +> variant contains a float and the user requests an integer. +> +> Older versions returned the floating point value truncated. +> Now, it returns the default value. +> +> ```c++ +> // suppose variant contains 1.2 +> int value = variant | 3; +> +> // old behavior: +> value == 1 +> +> // new behavior +> value == 3 +> ``` +> +> If you need the old behavior, you must add `if (variant.is())`. + +v6.10.1 (2019-04-23) +------- + +* Fixed error "attributes are not allowed on a function-definition" +* Fixed `deserializeJson()` not being picky enough (issue #969) +* Fixed error "no matching function for call to write(uint8_t)" (issue #972) + +v6.10.0 (2019-03-22) +------- + +* Fixed an integer overflow in the JSON deserializer +* Added overflow handling in `JsonVariant::as()` and `JsonVariant::is()`. + - `as()` returns `0` if the integer `T` overflows + - `is()` returns `false` if the integer `T` overflows +* Added `BasicJsonDocument` to support custom allocator (issue #876) +* Added `JsonDocument::containsKey()` (issue #938) +* Added `JsonVariant::containsKey()` + +v6.9.1 (2019-03-01) +------ + +* Fixed warning "unused variable" with GCC 4.4 (issue #912) +* Fixed warning "cast increases required alignment" (issue #914) +* Fixed warning "conversion may alter value" (issue #914) +* Fixed naming conflict with "CAPACITY" (issue #839) +* Muted warning "will change in GCC 7.1" (issue #914) +* Added a clear error message for `StaticJsonBuffer` and `DynamicJsonBuffer` +* Marked ArduinoJson.h as a "system header" + +v6.9.0 (2019-02-26) +------ + +* Decode escaped Unicode characters like \u00DE (issue #304, PR #791) + Many thanks to Daniel Schulte (aka @trilader) who implemented this feature. +* Added option ARDUINOJSON_DECODE_UNICODE to enable it +* Converted `JsonArray::copyFrom()/copyTo()` to free functions `copyArray()` +* Renamed `JsonArray::copyFrom()` and `JsonObject::copyFrom()` to `set()` +* Renamed `JsonArray::get()` to `getElement()` +* Renamed `JsonArray::add()` (without arg) to `addElement()` +* Renamed `JsonObject::get()` to `getMember()` +* Renamed `JsonObject::getOrCreate()` to `getOrAddMember()` +* Fixed `JsonVariant::isNull()` not returning `true` after `set((char*)0)` +* Fixed segfault after `variant.set(serialized((char*)0))` +* Detect `IncompleteInput` in `false`, `true`, and `null` +* Added `JsonDocument::size()` +* Added `JsonDocument::remove()` +* Added `JsonVariant::clear()` +* Added `JsonVariant::remove()` + +v6.8.0-beta (2019-01-30) +----------- + +* Import functions in the ArduinoJson namespace to get clearer errors +* Improved syntax highlighting in Arduino IDE +* Removed default capacity of `DynamicJsonDocument` +* `JsonArray::copyFrom()` accepts `JsonArrayConst` +* `JsonVariant::set()` accepts `JsonArrayConst` and `JsonObjectConst` +* `JsonDocument` was missing in the ArduinoJson namespace +* Added `memoryUsage()` to `JsonArray`, `JsonObject`, and `JsonVariant` +* Added `nesting()` to `JsonArray`, `JsonDocument`, `JsonObject`, and `JsonVariant` +* Replaced `JsonDocument::nestingLimit` with an additional parameter + to `deserializeJson()` and `deserializeMsgPack()` +* Fixed uninitialized variant in `JsonDocument` +* Fixed `StaticJsonDocument` copy constructor and copy assignment +* The copy constructor of `DynamicJsonDocument` chooses the capacity according to the memory usage of the source, not from the capacity of the source. +* Added the ability to create/assign a `StaticJsonDocument`/`DynamicJsonDocument` from a `JsonArray`/`JsonObject`/`JsonVariant` +* Added `JsonDocument::isNull()` +* Added `JsonDocument::operator[]` +* Added `ARDUINOJSON_TAB` to configure the indentation character +* Reduced the size of the pretty JSON serializer +* Added `add()`, `createNestedArray()` and `createNestedObject()` to `JsonVariant` +* `JsonVariant` automatically promotes to `JsonObject` or `JsonArray` on write. + Calling `JsonVariant::to()` is not required anymore. +* `JsonDocument` now support the same operations as `JsonVariant`. + Calling `JsonDocument::as()` is not required anymore. +* Fixed example `JsonHttpClient.ino` +* User can now use a `JsonString` as a key or a value + +> ### BREAKING CHANGES +> +> #### `DynamicJsonDocument`'s constructor +> +> The parameter to the constructor of `DynamicJsonDocument` is now mandatory +> +> Old code: +> +> ```c++ +> DynamicJsonDocument doc; +> ``` +> +> New code: +> +> ```c++ +> DynamicJsonDocument doc(1024); +> ``` +> +> #### Nesting limit +> +> `JsonDocument::nestingLimit` was replaced with a new parameter to `deserializeJson()` and `deserializeMsgPack()`. +> +> Old code: +> +> ```c++ +> doc.nestingLimit = 15; +> deserializeJson(doc, input); +> ``` +> +> New code: +> +> ```c++ +> deserializeJson(doc, input, DeserializationOption::NestingLimit(15)); +> ``` + +v6.7.0-beta (2018-12-07) +----------- + +* Removed the automatic expansion of `DynamicJsonDocument`, it now has a fixed capacity. +* Restored the monotonic allocator because the code was getting too big +* Reduced the memory usage +* Reduced the code size +* Renamed `JsonKey` to `JsonString` +* Removed spurious files in the Particle library + +v6.6.0-beta (2018-11-13) +----------- + +* Removed `JsonArray::is(i)` and `JsonArray::set(i,v)` +* Removed `JsonObject::is(k)` and `JsonObject::set(k,v)` +* Replaced `T JsonArray::get(i)` with `JsonVariant JsonArray::get(i)` +* Replaced `T JsonObject::get(k)` with `JsonVariant JsonObject::get(k)` +* Added `JSON_STRING_SIZE()` +* ~~Replacing or removing a value now releases the memory~~ +* Added `DeserializationError::code()` to be used in switch statements (issue #846) + +v6.5.0-beta (2018-10-13) +----------- + +* Added implicit conversion from `JsonArray` and `JsonObject` to `JsonVariant` +* Allow mixed configuration in compilation units (issue #809) +* Fixed object keys not being duplicated +* `JsonPair::key()` now returns a `JsonKey` +* Increased the default capacity of `DynamicJsonDocument` +* Fixed `JsonVariant::is()` (closes #763) +* Added `JsonArrayConst`, `JsonObjectConst`, and `JsonVariantConst` +* Added copy-constructor and copy-assignment-operator for `JsonDocument` (issue #827) + +v6.4.0-beta (2018-09-11) +----------- + +* Copy `JsonArray` and `JsonObject`, instead of storing pointers (issue #780) +* Added `JsonVariant::to()` and `JsonVariant::to()` + +v6.3.0-beta (2018-08-31) +----------- + +* Implemented reference semantics for `JsonVariant` +* Replaced `JsonPair`'s `key` and `value` with `key()` and `value()` +* Fixed `serializeJson(obj[key], dst)` (issue #794) + +> ### BREAKING CHANGES +> +> #### JsonVariant +> +> `JsonVariant` now has a semantic similar to `JsonObject` and `JsonArray`. +> It's a reference to a value stored in the `JsonDocument`. +> As a consequence, a `JsonVariant` cannot be used as a standalone variable anymore. +> +> Old code: +> +> ```c++ +> JsonVariant myValue = 42; +> ``` +> +> New code: +> +> ```c++ +> DynamicJsonDocument doc; +> JsonVariant myValue = doc.to(); +> myValue.set(42); +> ``` +> +> #### JsonPair +> +> Old code: +> +> ```c++ +> for(JsonPair p : myObject) { +> Serial.println(p.key); +> Serial.println(p.value.as()); +> } +> ``` +> +> New code: +> +> ```c++ +> for(JsonPair p : myObject) { +> Serial.println(p.key()); +> Serial.println(p.value().as()); +> } +> ``` +> +> CAUTION: the key is now read only! + +v6.2.3-beta (2018-07-19) +----------- + +* Fixed exception when using Flash strings as object keys (issue #784) + +v6.2.2-beta (2018-07-18) +----------- + +* Fixed `invalid application of 'sizeof' to incomplete type '__FlashStringHelper'` (issue #783) +* Fixed `char[]` not duplicated when passed to `JsonVariant::operator[]` + +v6.2.1-beta (2018-07-17) +----------- + +* Fixed `JsonObject` not inserting keys of type `String` (issue #782) + +v6.2.0-beta (2018-07-12) +----------- + +* Disabled lazy number deserialization (issue #772) +* Fixed `JsonVariant::is()` that returned true for empty strings +* Improved float serialization when `-fsingle-precision-constant` is used +* Renamed function `RawJson()` to `serialized()` +* `serializeMsgPack()` now supports values marked with `serialized()` + +> ### BREAKING CHANGES +> +> #### Non quoted strings +> +> Non quoted strings are now forbidden in values, but they are still allowed in keys. +> For example, `{key:"value"}` is accepted, but `{key:value}` is not. +> +> #### Preformatted values +> +> Old code: +> +> ```c++ +> object["values"] = RawJson("[1,2,3,4]"); +> ``` +> +> New code: +> +> ```c++ +> object["values"] = serialized("[1,2,3,4]"); +> ``` + +v6.1.0-beta (2018-07-02) +----------- + +* Return `JsonArray` and `JsonObject` by value instead of reference (issue #309) +* Replaced `success()` with `isNull()` + +> ### BREAKING CHANGES +> +> Old code: +> +> ```c++ +> JsonObject& obj = doc.to(); +> JsonArray& arr = obj.createNestedArray("key"); +> if (!arr.success()) { +> Serial.println("Not enough memory"); +> return; +> } +> ``` +> +> New code: +> +> ```c++ +> JsonObject obj = doc.to(); +> JsonArray arr = obj.createNestedArray("key"); +> if (arr.isNull()) { +> Serial.println("Not enough memory"); +> return; +> } +> ``` + +v6.0.1-beta (2018-06-11) +----------- + +* Fixed conflicts with `isnan()` and `isinf()` macros (issue #752) + +v6.0.0-beta (2018-06-07) +----------- + +* Added `DynamicJsonDocument` and `StaticJsonDocument` +* Added `deserializeJson()` +* Added `serializeJson()` and `serializeJsonPretty()` +* Added `measureJson()` and `measureJsonPretty()` +* Added `serializeMsgPack()`, `deserializeMsgPack()` and `measureMsgPack()` (issue #358) +* Added example `MsgPackParser.ino` (issue #358) +* Added support for non zero-terminated strings (issue #704) +* Removed `JsonBuffer::parseArray()`, `parseObject()` and `parse()` +* Removed `JsonBuffer::createArray()` and `createObject()` +* Removed `printTo()` and `prettyPrintTo()` +* Removed `measureLength()` and `measurePrettyLength()` +* Removed all deprecated features + +> ### BREAKING CHANGES +> +> #### Deserialization +> +> Old code: +> +> ```c++ +> DynamicJsonBuffer jb; +> JsonObject& obj = jb.parseObject(json); +> if (obj.success()) { +> +> } +> ``` +> +> New code: +> +> ```c++ +> DynamicJsonDocument doc; +> DeserializationError error = deserializeJson(doc, json); +> if (error) { +> +> } +> JsonObject& obj = doc.as(); +> ``` +> +> #### Serialization +> +> Old code: +> +> ```c++ +> DynamicJsonBuffer jb; +> JsonObject& obj = jb.createObject(); +> obj["key"] = "value"; +> obj.printTo(Serial); +> ``` +> +> New code: +> +> ```c++ +> DynamicJsonDocument obj; +> JsonObject& obj = doc.to(); +> obj["key"] = "value"; +> serializeJson(doc, Serial); +> ``` diff --git a/firmware/uniclock/lib/ArduinoJson/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/CMakeLists.txt new file mode 100644 index 0000000..f809a58 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/CMakeLists.txt @@ -0,0 +1,25 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +cmake_minimum_required(VERSION 3.15) + +if(ESP_PLATFORM) + # Build ArduinoJson as an ESP-IDF component + idf_component_register(INCLUDE_DIRS src) + return() +endif() + +project(ArduinoJson VERSION 6.19.4) + +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include(CTest) +endif() + +add_subdirectory(src) + +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) + include(extras/CompileOptions.cmake) + add_subdirectory(extras/tests) + add_subdirectory(extras/fuzzing) +endif() diff --git a/firmware/uniclock/lib/ArduinoJson/CONTRIBUTING.md b/firmware/uniclock/lib/ArduinoJson/CONTRIBUTING.md new file mode 100644 index 0000000..d32a04f --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contribution to ArduinoJson + +First, thank you for taking the time to contribute to this project. + +You can submit changes via GitHub Pull Requests. + +Please: + +1. Update the test suite for any change of behavior +2. Use clang-format in "file" mode to format the code diff --git a/firmware/uniclock/lib/ArduinoJson/LICENSE.md b/firmware/uniclock/lib/ArduinoJson/LICENSE.md new file mode 100644 index 0000000..e59620d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/LICENSE.md @@ -0,0 +1,10 @@ +The MIT License (MIT) +--------------------- + +Copyright © 2014-2022, Benoit BLANCHON + +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. diff --git a/firmware/uniclock/lib/ArduinoJson/README.md b/firmware/uniclock/lib/ArduinoJson/README.md new file mode 100644 index 0000000..37f1469 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/README.md @@ -0,0 +1,162 @@ +![ArduinoJson](banner.svg) + +--- + +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/bblanchon/ArduinoJson/Continuous%20Integration?logo=github)](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A6.x) +[![Continuous Integration](https://ci.appveyor.com/api/projects/status/m7s53wav1l0abssg/branch/6.x?svg=true)](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/arduinojson.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson) +[![LGTM Grade](https://img.shields.io/lgtm/grade/cpp/github/bblanchon/ArduinoJson?label=quality&logo=lgtm)](https://lgtm.com/projects/g/bblanchon/ArduinoJson/) +[![Coveralls branch](https://img.shields.io/coveralls/github/bblanchon/ArduinoJson/6.x?logo=coveralls)](https://coveralls.io/github/bblanchon/ArduinoJson?branch=6.x) +[![Arduino Library Manager](https://img.shields.io/static/v1?label=Arduino&message=v6.19.4&logo=arduino&logoColor=white&color=blue)](https://www.ardu-badge.com/ArduinoJson/6.19.4) +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/bblanchon/library/ArduinoJson.svg?version=6.19.4)](https://registry.platformio.org/packages/libraries/bblanchon/ArduinoJson?version=6.19.4) +[![GitHub stars](https://img.shields.io/github/stars/bblanchon/ArduinoJson?style=flat&logo=github)](https://github.com/bblanchon/ArduinoJson/stargazers) +[![GitHub Sponsors](https://img.shields.io/github/sponsors/bblanchon?logo=github)](https://github.com/sponsors/bblanchon) + +ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things). + +## Features + +* [JSON deserialization](https://arduinojson.org/v6/api/json/deserializejson/?utm_source=github&utm_medium=readme) + * [Optionally decodes UTF-16 escape sequences to UTF-8](https://arduinojson.org/v6/api/config/decode_unicode/?utm_source=github&utm_medium=readme) + * [Optionally stores links to the input buffer (zero-copy)](https://arduinojson.org/v6/api/json/deserializejson/?utm_source=github&utm_medium=readme) + * [Optionally supports comments in the input](https://arduinojson.org/v6/api/config/enable_comments/?utm_source=github&utm_medium=readme) + * [Optionally filters the input to keep only desired values](https://arduinojson.org/v6/api/json/deserializejson/?utm_source=github&utm_medium=readme#filtering) + * Supports single quotes as a string delimiter + * Compatible with [NDJSON](http://ndjson.org/) and [JSON Lines](https://jsonlines.org/) +* [JSON serialization](https://arduinojson.org/v6/api/json/serializejson/?utm_source=github&utm_medium=readme) + * [Can write to a buffer or a stream](https://arduinojson.org/v6/api/json/serializejson/?utm_source=github&utm_medium=readme) + * [Optionally indents the document (prettified JSON)](https://arduinojson.org/v6/api/json/serializejsonpretty/?utm_source=github&utm_medium=readme) +* [MessagePack serialization](https://arduinojson.org/v6/api/msgpack/serializemsgpack/?utm_source=github&utm_medium=readme) +* [MessagePack deserialization](https://arduinojson.org/v6/api/msgpack/deserializemsgpack/?utm_source=github&utm_medium=readme) +* Efficient + * [Twice smaller than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/?utm_source=github&utm_medium=readme) + * [Almost 10% faster than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/?utm_source=github&utm_medium=readme) + * [Consumes roughly 10% less RAM than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/?utm_source=github&utm_medium=readme) + * [Fixed memory allocation, no heap fragmentation](https://arduinojson.org/v6/api/jsondocument/?utm_source=github&utm_medium=readme) + * [Optionally works without heap memory (zero malloc)](https://arduinojson.org/v6/api/staticjsondocument/?utm_source=github&utm_medium=readme) + * [Deduplicates strings](https://arduinojson.org/news/2020/08/01/version-6-16-0/?utm_source=github&utm_medium=readme) +* Versatile + * Supports [custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v6/how-to/use-external-ram-on-esp32/?utm_source=github&utm_medium=readme) + * Supports [`String`](https://arduinojson.org/v6/api/config/enable_arduino_string/?utm_source=github&utm_medium=readme), [`std::string`](https://arduinojson.org/v6/api/config/enable_std_string/?utm_source=github&utm_medium=readme), and [`std::string_view`](https://arduinojson.org/v6/api/config/enable_string_view/?utm_source=github&utm_medium=readme) + * Supports [`Stream`](https://arduinojson.org/v6/api/config/enable_arduino_stream/?utm_source=github&utm_medium=readme) and [`std::istream`/`std::ostream`](https://arduinojson.org/v6/api/config/enable_std_stream/?utm_source=github&utm_medium=readme) + * Supports [Flash strings](https://arduinojson.org/v6/api/config/enable_progmem/?utm_source=github&utm_medium=readme) + * Supports [custom readers](https://arduinojson.org/v6/api/json/deserializejson/?utm_source=github&utm_medium=readme#custom-reader) and [custom writers](https://arduinojson.org/v6/api/json/serializejson/?utm_source=github&utm_medium=readme#custom-writer) + * Supports [custom converters](https://arduinojson.org/news/2021/05/04/version-6-18-0/?utm_source=github&utm_medium=readme) +* Portable + * Usable on any C++ project (not limited to Arduino) + * Compatible with C++98, C++11, C++14 and C++17 + * Zero warnings with `-Wall -Wextra -pedantic` and `/W4` + * [Header-only library](https://en.wikipedia.org/wiki/Header-only) + * Works with virtually any board + * Arduino boards: [Uno](https://amzn.to/38aL2ik), [Due](https://amzn.to/36YkWi2), [Micro](https://amzn.to/35WkdwG), [Nano](https://amzn.to/2QTvwRX), [Mega](https://amzn.to/36XWhuf), [Yun](https://amzn.to/30odURc), [Leonardo](https://amzn.to/36XWjlR)... + * Espressif chips: [ESP8266](https://amzn.to/36YluV8), [ESP32](https://amzn.to/2G4pRCB) + * Lolin (WeMos) boards: [D1 mini](https://amzn.to/2QUpz7q), [D1 Mini Pro](https://amzn.to/36UsGSs)... + * Teensy boards: [4.0](https://amzn.to/30ljXGq), [3.2](https://amzn.to/2FT0EuC), [2.0](https://amzn.to/2QXUMXj) + * Particle boards: [Argon](https://amzn.to/2FQHa9X), [Boron](https://amzn.to/36WgLUd), [Electron](https://amzn.to/30vEc4k), [Photon](https://amzn.to/387F9Cd)... + * Texas Instruments boards: [MSP430](https://amzn.to/30nJWgg)... + * Soft cores: [Nios II](https://en.wikipedia.org/wiki/Nios_II)... + * Tested on all major development environments + * [Arduino IDE](https://www.arduino.cc/en/Main/Software) + * [Atmel Studio](http://www.atmel.com/microsite/atmel-studio/) + * [Atollic TrueSTUDIO](https://atollic.com/truestudio/) + * [Energia](http://energia.nu/) + * [IAR Embedded Workbench](https://www.iar.com/iar-embedded-workbench/) + * [Keil uVision](http://www.keil.com/) + * [MPLAB X IDE](http://www.microchip.com/mplab/mplab-x-ide) + * [Particle](https://www.particle.io/) + * [PlatformIO](http://platformio.org/) + * [Sloeber plugin for Eclipse](https://eclipse.baeyens.it/) + * [Visual Micro](http://www.visualmicro.com/) + * [Visual Studio](https://www.visualstudio.com/) + * [Even works with online compilers like wandbox.org](https://wandbox.org/permlink/RlZSKy17DjJ6HcdN) + * [CMake friendly](https://arduinojson.org/v6/how-to/use-arduinojson-with-cmake/?utm_source=github&utm_medium=readme) +* Well designed + * [Elegant API](http://arduinojson.org/v6/example/?utm_source=github&utm_medium=readme) + * [Thread-safe](https://en.wikipedia.org/wiki/Thread_safety) + * Self-contained (no external dependency) + * `const` friendly + * [`for` friendly](https://arduinojson.org/v6/api/jsonobject/begin_end/?utm_source=github&utm_medium=readme) + * [TMP friendly](https://en.wikipedia.org/wiki/Template_metaprogramming) + * Handles [integer overflows](https://arduinojson.org/v6/api/jsonvariant/as/?utm_source=github&utm_medium=readme#integer-overflows) +* Well tested + * [Unit test coverage close to 100%](https://coveralls.io/github/bblanchon/ArduinoJson?branch=6.x) + * Continuously tested on + * [Visual Studio 2010, 2012, 2013, 2015, 2017, 2019, 2022](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x) + * [GCC 4.4, 4.6, 4.7, 4.8, 4.9, 5, 6, 7, 8, 9, 10, 11](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22) + * [Clang 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 5.0, 6.0, 7, 8, 9, 10](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22) + * [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson) + * Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/) +* Well documented + * [Tutorials](https://arduinojson.org/v6/doc/deserialization/?utm_source=github&utm_medium=readme) + * [Examples](https://arduinojson.org/v6/example/?utm_source=github&utm_medium=readme) + * [How-tos](https://arduinojson.org/v6/example/?utm_source=github&utm_medium=readme) + * [FAQ](https://arduinojson.org/v6/faq/?utm_source=github&utm_medium=readme) + * [Troubleshooter](https://arduinojson.org/v6/troubleshooter/?utm_source=github&utm_medium=readme) + * [Book](https://arduinojson.org/book/?utm_source=github&utm_medium=readme) + * [Changelog](CHANGELOG.md) +* Vibrant user community + * Most popular of all Arduino libraries on [GitHub](https://github.com/search?o=desc&q=arduino+library&s=stars&type=Repositories) + * [Used in hundreds of projects](https://www.hackster.io/search?i=projects&q=arduinojson) + * [Responsive support](https://github.com/bblanchon/ArduinoJson/issues?q=is%3Aissue+is%3Aclosed) + * [Discord server](https://discord.gg/DzN6hHHD4h) + +## Quickstart + +### Deserialization + +Here is a program that parses a JSON document with ArduinoJson. + +```c++ +char json[] = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}"; + +DynamicJsonDocument doc(1024); +deserializeJson(doc, json); + +const char* sensor = doc["sensor"]; +long time = doc["time"]; +double latitude = doc["data"][0]; +double longitude = doc["data"][1]; +``` + +See the [tutorial on arduinojson.org](https://arduinojson.org/doc/decoding/?utm_source=github&utm_medium=readme) + +### Serialization + +Here is a program that generates a JSON document with ArduinoJson: + +```c++ +DynamicJsonDocument doc(1024); + +doc["sensor"] = "gps"; +doc["time"] = 1351824120; +doc["data"][0] = 48.756080; +doc["data"][1] = 2.302038; + +serializeJson(doc, Serial); +// This prints: +// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]} +``` + +See the [tutorial on arduinojson.org](https://arduinojson.org/doc/encoding/?utm_source=github&utm_medium=readme) + +## Sponsors + +ArduinoJson is thankful to its sponsors. Please give them a visit; they deserve it! + +

+ + Tech Explorations + + + Programming Electronics Academy + +

+

+ + 1technophile + +

+ +If you run a commercial project that embeds ArduinoJson, think about [sponsoring the library's development](https://github.com/sponsors/bblanchon): it ensures the code that your products rely on stays actively maintained. It can also give your project some exposure to the makers' community. + +If you are an individual user and want to support the development (or give a sign of appreciation), consider purchasing the book [Mastering ArduinoJson](https://arduinojson.org/book/) ❤, or simply [cast a star](https://github.com/bblanchon/ArduinoJson/stargazers) ⭐. diff --git a/firmware/uniclock/lib/ArduinoJson/SUPPORT.md b/firmware/uniclock/lib/ArduinoJson/SUPPORT.md new file mode 100644 index 0000000..c47e1b1 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/SUPPORT.md @@ -0,0 +1,27 @@ +# ArduinoJson Support + +First off, thank you very much for using ArduinoJson. + +We'll be very happy to help you, but first please read the following. + +## Before asking for help + +1. Read the [FAQ](https://arduinojson.org/faq/?utm_source=github&utm_medium=support) +2. Search in the [API Reference](https://arduinojson.org/api/?utm_source=github&utm_medium=support) + +If you did not find the answer, please create a [new issue on GitHub](https://github.com/bblanchon/ArduinoJson/issues/new). + +It is OK to add a comment to a currently opened issue, but please avoid adding comments to a closed issue. + +## Before hitting the Submit button + +Please provide all the relevant information: + +* Good title +* Short description of the problem +* Target platform +* Compiler model and version +* [MVCE](https://stackoverflow.com/help/mcve) +* Compiler output + +Good questions get fast answers! diff --git a/firmware/uniclock/lib/ArduinoJson/appveyor.yml b/firmware/uniclock/lib/ArduinoJson/appveyor.yml new file mode 100644 index 0000000..6e3e458 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/appveyor.yml @@ -0,0 +1,37 @@ +version: 6.19.4.{build} +environment: + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + CMAKE_GENERATOR: Visual Studio 17 2022 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + CMAKE_GENERATOR: Visual Studio 16 2019 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + CMAKE_GENERATOR: Visual Studio 15 2017 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CMAKE_GENERATOR: Visual Studio 14 2015 + - CMAKE_GENERATOR: Visual Studio 12 2013 + - CMAKE_GENERATOR: Visual Studio 11 2012 + - CMAKE_GENERATOR: Visual Studio 10 2010 + - CMAKE_GENERATOR: Ninja + MINGW: MinGW # MinGW 32-bit 5.3.0 + - CMAKE_GENERATOR: Ninja + MINGW32: i686-5.3.0-posix-dwarf-rt_v4-rev0 # MinGW-w64 5.3.0 + - CMAKE_GENERATOR: Ninja + MINGW32: i686-6.3.0-posix-dwarf-rt_v5-rev1 # MinGW-w64 6.3.0 i686 + - CMAKE_GENERATOR: Ninja + MINGW64: x86_64-6.3.0-posix-seh-rt_v5-rev1 # MinGW-w64 6.3.0 x86_64 + - CMAKE_GENERATOR: Ninja + MINGW64: x86_64-7.3.0-posix-seh-rt_v5-rev0 # MinGW-w64 7.3.0 x86_64 + - CMAKE_GENERATOR: Ninja + MINGW64: x86_64-8.1.0-posix-seh-rt_v6-rev0 # MinGW-w64 8.1.0 x86_64 +configuration: Debug +before_build: + - set PATH=%PATH:C:\Program Files\Git\usr\bin;=% # Workaround for CMake not wanting sh.exe on PATH for MinGW + - if defined MINGW set PATH=C:\%MINGW%\bin;%PATH% + - if defined MINGW32 set PATH=C:\mingw-w64\%MINGW32%\mingw32\bin;%PATH% + - if defined MINGW64 set PATH=C:\mingw-w64\%MINGW64%\mingw64\bin;%PATH% + - cmake -DCMAKE_BUILD_TYPE=%CONFIGURATION% -G "%CMAKE_GENERATOR%" . +build_script: + - cmake --build . --config %CONFIGURATION% +test_script: + - ctest -C %CONFIGURATION% --output-on-failure . diff --git a/firmware/uniclock/lib/ArduinoJson/banner.svg b/firmware/uniclock/lib/ArduinoJson/banner.svg new file mode 100644 index 0000000..5176096 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/banner.svg @@ -0,0 +1,367 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/firmware/uniclock/lib/ArduinoJson/component.mk b/firmware/uniclock/lib/ArduinoJson/component.mk new file mode 100644 index 0000000..dc25d1c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_INCLUDEDIRS := src diff --git a/firmware/uniclock/lib/ArduinoJson/examples/JsonConfigFile/JsonConfigFile.ino b/firmware/uniclock/lib/ArduinoJson/examples/JsonConfigFile/JsonConfigFile.ino new file mode 100644 index 0000000..3443ba7 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/JsonConfigFile/JsonConfigFile.ino @@ -0,0 +1,160 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to store your project configuration in a file. +// It uses the SD library but can be easily modified for any other file-system. +// +// The file contains a JSON document with the following content: +// { +// "hostname": "examples.com", +// "port": 2731 +// } +// +// To run this program, you need an SD card connected to the SPI bus as follows: +// * MOSI <-> pin 11 +// * MISO <-> pin 12 +// * CLK <-> pin 13 +// * CS <-> pin 4 +// +// https://arduinojson.org/v6/example/config/ + +#include +#include +#include + +// Our configuration structure. +// +// Never use a JsonDocument to store the configuration! +// A JsonDocument is *not* a permanent storage; it's only a temporary storage +// used during the serialization phase. See: +// https://arduinojson.org/v6/faq/why-must-i-create-a-separate-config-object/ +struct Config { + char hostname[64]; + int port; +}; + +const char *filename = "/config.txt"; // <- SD library uses 8.3 filenames +Config config; // <- global configuration object + +// Loads the configuration from a file +void loadConfiguration(const char *filename, Config &config) { + // Open file for reading + File file = SD.open(filename); + + // Allocate a temporary JsonDocument + // Don't forget to change the capacity to match your requirements. + // Use https://arduinojson.org/v6/assistant to compute the capacity. + StaticJsonDocument<512> doc; + + // Deserialize the JSON document + DeserializationError error = deserializeJson(doc, file); + if (error) + Serial.println(F("Failed to read file, using default configuration")); + + // Copy values from the JsonDocument to the Config + config.port = doc["port"] | 2731; + strlcpy(config.hostname, // <- destination + doc["hostname"] | "example.com", // <- source + sizeof(config.hostname)); // <- destination's capacity + + // Close the file (Curiously, File's destructor doesn't close the file) + file.close(); +} + +// Saves the configuration to a file +void saveConfiguration(const char *filename, const Config &config) { + // Delete existing file, otherwise the configuration is appended to the file + SD.remove(filename); + + // Open file for writing + File file = SD.open(filename, FILE_WRITE); + if (!file) { + Serial.println(F("Failed to create file")); + return; + } + + // Allocate a temporary JsonDocument + // Don't forget to change the capacity to match your requirements. + // Use https://arduinojson.org/assistant to compute the capacity. + StaticJsonDocument<256> doc; + + // Set the values in the document + doc["hostname"] = config.hostname; + doc["port"] = config.port; + + // Serialize JSON to file + if (serializeJson(doc, file) == 0) { + Serial.println(F("Failed to write to file")); + } + + // Close the file + file.close(); +} + +// Prints the content of a file to the Serial +void printFile(const char *filename) { + // Open file for reading + File file = SD.open(filename); + if (!file) { + Serial.println(F("Failed to read file")); + return; + } + + // Extract each characters by one by one + while (file.available()) { + Serial.print((char)file.read()); + } + Serial.println(); + + // Close the file + file.close(); +} + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) continue; + + // Initialize SD library + const int chipSelect = 4; + while (!SD.begin(chipSelect)) { + Serial.println(F("Failed to initialize SD library")); + delay(1000); + } + + // Should load default config if run for the first time + Serial.println(F("Loading configuration...")); + loadConfiguration(filename, config); + + // Create configuration file + Serial.println(F("Saving configuration...")); + saveConfiguration(filename, config); + + // Dump config file + Serial.println(F("Print config file...")); + printFile(filename); +} + +void loop() { + // not used in this example +} + +// Performance issue? +// ------------------ +// +// File is an unbuffered stream, which is not optimal for ArduinoJson. +// See: https://arduinojson.org/v6/how-to/improve-speed/ + +// See also +// -------- +// +// https://arduinojson.org/ contains the documentation for all the functions +// used above. It also includes an FAQ that will help you solve any +// serialization or deserialization problem. +// +// The book "Mastering ArduinoJson" contains a case study of a project that has +// a complex configuration with nested members. +// Contrary to this example, the project in the book uses the SPIFFS filesystem. +// Learn more at https://arduinojson.org/book/ +// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤ diff --git a/firmware/uniclock/lib/ArduinoJson/examples/JsonFilterExample/JsonFilterExample.ino b/firmware/uniclock/lib/ArduinoJson/examples/JsonFilterExample/JsonFilterExample.ino new file mode 100644 index 0000000..f50187a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/JsonFilterExample/JsonFilterExample.ino @@ -0,0 +1,63 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to use DeserializationOpion::Filter +// +// https://arduinojson.org/v6/example/filter/ + +#include + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) continue; + + // The huge input: an extract from OpenWeatherMap response + const __FlashStringHelper* input_json = F( + "{\"cod\":\"200\",\"message\":0,\"list\":[{\"dt\":1581498000,\"main\":{" + "\"temp\":3.23,\"feels_like\":-3.63,\"temp_min\":3.23,\"temp_max\":4.62," + "\"pressure\":1014,\"sea_level\":1014,\"grnd_level\":1010,\"humidity\":" + "58,\"temp_kf\":-1.39},\"weather\":[{\"id\":800,\"main\":\"Clear\"," + "\"description\":\"clear " + "sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":0},\"wind\":{\"speed\":6." + "19,\"deg\":266},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 " + "09:00:00\"},{\"dt\":1581508800,\"main\":{\"temp\":6.09,\"feels_like\":-" + "1.07,\"temp_min\":6.09,\"temp_max\":7.13,\"pressure\":1015,\"sea_" + "level\":1015,\"grnd_level\":1011,\"humidity\":48,\"temp_kf\":-1.04}," + "\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear " + "sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":9},\"wind\":{\"speed\":6." + "64,\"deg\":268},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 " + "12:00:00\"}],\"city\":{\"id\":2643743,\"name\":\"London\",\"coord\":{" + "\"lat\":51.5085,\"lon\":-0.1257},\"country\":\"GB\",\"population\":" + "1000000,\"timezone\":0,\"sunrise\":1581492085,\"sunset\":1581527294}}"); + + // The filter: it contains "true" for each value we want to keep + StaticJsonDocument<200> filter; + filter["list"][0]["dt"] = true; + filter["list"][0]["main"]["temp"] = true; + + // Deserialize the document + StaticJsonDocument<400> doc; + deserializeJson(doc, input_json, DeserializationOption::Filter(filter)); + + // Print the result + serializeJsonPretty(doc, Serial); +} + +void loop() { + // not used in this example +} + +// See also +// -------- +// +// https://arduinojson.org/ contains the documentation for all the functions +// used above. It also includes an FAQ that will help you solve any +// deserialization problem. +// +// The book "Mastering ArduinoJson" contains a tutorial on deserialization. +// It begins with a simple example, like the one above, and then adds more +// features like deserializing directly from a file or an HTTP request. +// Learn more at https://arduinojson.org/book/ +// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤ diff --git a/firmware/uniclock/lib/ArduinoJson/examples/JsonGeneratorExample/JsonGeneratorExample.ino b/firmware/uniclock/lib/ArduinoJson/examples/JsonGeneratorExample/JsonGeneratorExample.ino new file mode 100644 index 0000000..e036209 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/JsonGeneratorExample/JsonGeneratorExample.ino @@ -0,0 +1,77 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to generate a JSON document with ArduinoJson. +// +// https://arduinojson.org/v6/example/generator/ + +#include + +void setup() { + // Initialize Serial port + Serial.begin(9600); + while (!Serial) continue; + + // Allocate the JSON document + // + // Inside the brackets, 200 is the RAM allocated to this document. + // Don't forget to change this value to match your requirement. + // Use https://arduinojson.org/v6/assistant to compute the capacity. + StaticJsonDocument<200> doc; + + // StaticJsonObject allocates memory on the stack, it can be + // replaced by DynamicJsonDocument which allocates in the heap. + // + // DynamicJsonDocument doc(200); + + // Add values in the document + // + doc["sensor"] = "gps"; + doc["time"] = 1351824120; + + // Add an array. + // + JsonArray data = doc.createNestedArray("data"); + data.add(48.756080); + data.add(2.302038); + + // Generate the minified JSON and send it to the Serial port. + // + serializeJson(doc, Serial); + // The above line prints: + // {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]} + + // Start a new line + Serial.println(); + + // Generate the prettified JSON and send it to the Serial port. + // + serializeJsonPretty(doc, Serial); + // The above line prints: + // { + // "sensor": "gps", + // "time": 1351824120, + // "data": [ + // 48.756080, + // 2.302038 + // ] + // } +} + +void loop() { + // not used in this example +} + +// See also +// -------- +// +// https://arduinojson.org/ contains the documentation for all the functions +// used above. It also includes an FAQ that will help you solve any +// serialization problem. +// +// The book "Mastering ArduinoJson" contains a tutorial on serialization. +// It begins with a simple example, like the one above, and then adds more +// features like serializing directly to a file or an HTTP request. +// Learn more at https://arduinojson.org/book/ +// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤ diff --git a/firmware/uniclock/lib/ArduinoJson/examples/JsonHttpClient/JsonHttpClient.ino b/firmware/uniclock/lib/ArduinoJson/examples/JsonHttpClient/JsonHttpClient.ino new file mode 100644 index 0000000..8e2e6d7 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/JsonHttpClient/JsonHttpClient.ino @@ -0,0 +1,126 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to parse a JSON document in an HTTP response. +// It uses the Ethernet library, but can be easily adapted for Wifi. +// +// It performs a GET resquest on https://arduinojson.org/example.json +// Here is the expected response: +// { +// "sensor": "gps", +// "time": 1351824120, +// "data": [ +// 48.756080, +// 2.302038 +// ] +// } +// +// https://arduinojson.org/v6/example/http-client/ + +#include +#include +#include + +void setup() { + // Initialize Serial port + Serial.begin(9600); + while (!Serial) continue; + + // Initialize Ethernet library + byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; + if (!Ethernet.begin(mac)) { + Serial.println(F("Failed to configure Ethernet")); + return; + } + delay(1000); + + Serial.println(F("Connecting...")); + + // Connect to HTTP server + EthernetClient client; + client.setTimeout(10000); + if (!client.connect("arduinojson.org", 80)) { + Serial.println(F("Connection failed")); + return; + } + + Serial.println(F("Connected!")); + + // Send HTTP request + client.println(F("GET /example.json HTTP/1.0")); + client.println(F("Host: arduinojson.org")); + client.println(F("Connection: close")); + if (client.println() == 0) { + Serial.println(F("Failed to send request")); + client.stop(); + return; + } + + // Check HTTP status + char status[32] = {0}; + client.readBytesUntil('\r', status, sizeof(status)); + // It should be "HTTP/1.0 200 OK" or "HTTP/1.1 200 OK" + if (strcmp(status + 9, "200 OK") != 0) { + Serial.print(F("Unexpected response: ")); + Serial.println(status); + client.stop(); + return; + } + + // Skip HTTP headers + char endOfHeaders[] = "\r\n\r\n"; + if (!client.find(endOfHeaders)) { + Serial.println(F("Invalid response")); + client.stop(); + return; + } + + // Allocate the JSON document + // Use https://arduinojson.org/v6/assistant to compute the capacity. + const size_t capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60; + DynamicJsonDocument doc(capacity); + + // Parse JSON object + DeserializationError error = deserializeJson(doc, client); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + client.stop(); + return; + } + + // Extract values + Serial.println(F("Response:")); + Serial.println(doc["sensor"].as()); + Serial.println(doc["time"].as()); + Serial.println(doc["data"][0].as(), 6); + Serial.println(doc["data"][1].as(), 6); + + // Disconnect + client.stop(); +} + +void loop() { + // not used in this example +} + +// Performance issue? +// ------------------ +// +// EthernetClient is an unbuffered stream, which is not optimal for ArduinoJson. +// See: https://arduinojson.org/v6/how-to/improve-speed/ + +// See also +// -------- +// +// https://arduinojson.org/ contains the documentation for all the functions +// used above. It also includes an FAQ that will help you solve any +// serialization problem. +// +// The book "Mastering ArduinoJson" contains a tutorial on deserialization +// showing how to parse the response from GitHub's API. In the last chapter, +// it shows how to parse the huge documents from OpenWeatherMap +// and Reddit. +// Learn more at https://arduinojson.org/book/ +// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤ diff --git a/firmware/uniclock/lib/ArduinoJson/examples/JsonParserExample/JsonParserExample.ino b/firmware/uniclock/lib/ArduinoJson/examples/JsonParserExample/JsonParserExample.ino new file mode 100644 index 0000000..d6492df --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/JsonParserExample/JsonParserExample.ino @@ -0,0 +1,80 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to deserialize a JSON document with ArduinoJson. +// +// https://arduinojson.org/v6/example/parser/ + +#include + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) continue; + + // Allocate the JSON document + // + // Inside the brackets, 200 is the capacity of the memory pool in bytes. + // Don't forget to change this value to match your JSON document. + // Use https://arduinojson.org/v6/assistant to compute the capacity. + StaticJsonDocument<200> doc; + + // StaticJsonDocument allocates memory on the stack, it can be + // replaced by DynamicJsonDocument which allocates in the heap. + // + // DynamicJsonDocument doc(200); + + // JSON input string. + // + // Using a char[], as shown here, enables the "zero-copy" mode. This mode uses + // the minimal amount of memory because the JsonDocument stores pointers to + // the input buffer. + // If you use another type of input, ArduinoJson must copy the strings from + // the input to the JsonDocument, so you need to increase the capacity of the + // JsonDocument. + char json[] = + "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}"; + + // Deserialize the JSON document + DeserializationError error = deserializeJson(doc, json); + + // Test if parsing succeeds. + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + + // Fetch values. + // + // Most of the time, you can rely on the implicit casts. + // In other case, you can do doc["time"].as(); + const char* sensor = doc["sensor"]; + long time = doc["time"]; + double latitude = doc["data"][0]; + double longitude = doc["data"][1]; + + // Print values. + Serial.println(sensor); + Serial.println(time); + Serial.println(latitude, 6); + Serial.println(longitude, 6); +} + +void loop() { + // not used in this example +} + +// See also +// -------- +// +// https://arduinojson.org/ contains the documentation for all the functions +// used above. It also includes an FAQ that will help you solve any +// deserialization problem. +// +// The book "Mastering ArduinoJson" contains a tutorial on deserialization. +// It begins with a simple example, like the one above, and then adds more +// features like deserializing directly from a file or an HTTP request. +// Learn more at https://arduinojson.org/book/ +// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤ diff --git a/firmware/uniclock/lib/ArduinoJson/examples/JsonServer/JsonServer.ino b/firmware/uniclock/lib/ArduinoJson/examples/JsonServer/JsonServer.ino new file mode 100644 index 0000000..52d55c5 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/JsonServer/JsonServer.ino @@ -0,0 +1,117 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to implement an HTTP server that sends a JSON document +// in the response. +// It uses the Ethernet library but can be easily adapted for Wifi. +// +// The JSON document contains the values of the analog and digital pins. +// It looks like that: +// { +// "analog": [0, 76, 123, 158, 192, 205], +// "digital": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0] +// } +// +// https://arduinojson.org/v6/example/http-server/ + +#include +#include +#include + +byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; +EthernetServer server(80); + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) continue; + + // Initialize Ethernet libary + if (!Ethernet.begin(mac)) { + Serial.println(F("Failed to initialize Ethernet library")); + return; + } + + // Start to listen + server.begin(); + + Serial.println(F("Server is ready.")); + Serial.print(F("Please connect to http://")); + Serial.println(Ethernet.localIP()); +} + +void loop() { + // Wait for an incomming connection + EthernetClient client = server.available(); + + // Do we have a client? + if (!client) + return; + + Serial.println(F("New client")); + + // Read the request (we ignore the content in this example) + while (client.available()) client.read(); + + // Allocate a temporary JsonDocument + // Use https://arduinojson.org/v6/assistant to compute the capacity. + StaticJsonDocument<500> doc; + + // Create the "analog" array + JsonArray analogValues = doc.createNestedArray("analog"); + for (int pin = 0; pin < 6; pin++) { + // Read the analog input + int value = analogRead(pin); + + // Add the value at the end of the array + analogValues.add(value); + } + + // Create the "digital" array + JsonArray digitalValues = doc.createNestedArray("digital"); + for (int pin = 0; pin < 14; pin++) { + // Read the digital input + int value = digitalRead(pin); + + // Add the value at the end of the array + digitalValues.add(value); + } + + Serial.print(F("Sending: ")); + serializeJson(doc, Serial); + Serial.println(); + + // Write response headers + client.println(F("HTTP/1.0 200 OK")); + client.println(F("Content-Type: application/json")); + client.println(F("Connection: close")); + client.print(F("Content-Length: ")); + client.println(measureJsonPretty(doc)); + client.println(); + + // Write JSON document + serializeJsonPretty(doc, client); + + // Disconnect + client.stop(); +} + +// Performance issue? +// ------------------ +// +// EthernetClient is an unbuffered stream, which is not optimal for ArduinoJson. +// See: https://arduinojson.org/v6/how-to/improve-speed/ + +// See also +// -------- +// +// https://arduinojson.org/ contains the documentation for all the functions +// used above. It also includes an FAQ that will help you solve any +// serialization problem. +// +// The book "Mastering ArduinoJson" contains a tutorial on serialization. +// It begins with a simple example, then adds more features like serializing +// directly to a file or an HTTP client. +// Learn more at https://arduinojson.org/book/ +// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤ diff --git a/firmware/uniclock/lib/ArduinoJson/examples/JsonUdpBeacon/JsonUdpBeacon.ino b/firmware/uniclock/lib/ArduinoJson/examples/JsonUdpBeacon/JsonUdpBeacon.ino new file mode 100644 index 0000000..87965a3 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/JsonUdpBeacon/JsonUdpBeacon.ino @@ -0,0 +1,106 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to send a JSON document to a UDP socket. +// At regular interval, it sends a UDP packet that contains the status of +// analog and digital pins. +// It looks like that: +// { +// "analog": [0, 76, 123, 158, 192, 205], +// "digital": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0] +// } +// +// If you want to test this program, you need to be able to receive the UDP +// packets. +// For example, you can run netcat on your computer +// $ ncat -ulp 8888 +// See https://nmap.org/ncat/ +// +// https://arduinojson.org/v6/example/udp-beacon/ + +#include +#include +#include + +byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; +IPAddress remoteIp(192, 168, 0, 108); // <- EDIT!!!! +unsigned short remotePort = 8888; +unsigned short localPort = 8888; +EthernetUDP udp; + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) continue; + + // Initialize Ethernet libary + if (!Ethernet.begin(mac)) { + Serial.println(F("Failed to initialize Ethernet library")); + return; + } + + // Enable UDP + udp.begin(localPort); +} + +void loop() { + // Allocate a temporary JsonDocument + // Use https://arduinojson.org/v6/assistant to compute the capacity. + StaticJsonDocument<500> doc; + + // Create the "analog" array + JsonArray analogValues = doc.createNestedArray("analog"); + for (int pin = 0; pin < 6; pin++) { + // Read the analog input + int value = analogRead(pin); + + // Add the value at the end of the array + analogValues.add(value); + } + + // Create the "digital" array + JsonArray digitalValues = doc.createNestedArray("digital"); + for (int pin = 0; pin < 14; pin++) { + // Read the digital input + int value = digitalRead(pin); + + // Add the value at the end of the array + digitalValues.add(value); + } + + // Log + Serial.print(F("Sending to ")); + Serial.print(remoteIp); + Serial.print(F(" on port ")); + Serial.println(remotePort); + serializeJson(doc, Serial); + + // Send UDP packet + udp.beginPacket(remoteIp, remotePort); + serializeJson(doc, udp); + udp.println(); + udp.endPacket(); + + // Wait + delay(10000); +} + +// Performance issue? +// ------------------ +// +// EthernetUDP is an unbuffered stream, which is not optimal for ArduinoJson. +// See: https://arduinojson.org/v6/how-to/improve-speed/ + +// See also +// -------- +// +// https://arduinojson.org/ contains the documentation for all the functions +// used above. It also includes an FAQ that will help you solve any +// serialization problem. +// +// The book "Mastering ArduinoJson" contains a tutorial on serialization. +// It begins with a simple example, then adds more features like serializing +// directly to a file or any stream. +// Learn more at https://arduinojson.org/book/ +// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤ diff --git a/firmware/uniclock/lib/ArduinoJson/examples/MsgPackParser/MsgPackParser.ino b/firmware/uniclock/lib/ArduinoJson/examples/MsgPackParser/MsgPackParser.ino new file mode 100644 index 0000000..a14245b --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/MsgPackParser/MsgPackParser.ino @@ -0,0 +1,75 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to deserialize a MessagePack document with +// ArduinoJson. +// +// https://arduinojson.org/v6/example/msgpack-parser/ + +#include + +void setup() { + // Initialize serial port + Serial.begin(9600); + while (!Serial) continue; + + // Allocate the JSON document + // + // Inside the brackets, 200 is the capacity of the memory pool in bytes. + // Don't forget to change this value to match your JSON document. + // Use https://arduinojson.org/v6/assistant to compute the capacity. + StaticJsonDocument<200> doc; + + // StaticJsonObject allocates memory on the stack, it can be + // replaced by DynamicJsonObject which allocates in the heap. + // + // DynamicJsonObject doc(200); + + // MessagePack input string. + // + // Using a char[], as shown here, enables the "zero-copy" mode. This mode uses + // the minimal amount of memory because the JsonDocument stores pointers to + // the input buffer. + // If you use another type of input, ArduinoJson must copy the strings from + // the input to the JsonDocument, so you need to increase the capacity of the + // JsonDocument. + uint8_t input[] = {131, 166, 115, 101, 110, 115, 111, 114, 163, 103, 112, 115, + 164, 116, 105, 109, 101, 206, 80, 147, 50, 248, 164, 100, + 97, 116, 97, 146, 203, 64, 72, 96, 199, 58, 188, 148, + 112, 203, 64, 2, 106, 146, 230, 33, 49, 169}; + // This MessagePack document contains: + // { + // "sensor": "gps", + // "time": 1351824120, + // "data": [48.75608, 2.302038] + // } + + DeserializationError error = deserializeMsgPack(doc, input); + + // Test if parsing succeeded. + if (error) { + Serial.print("deserializeMsgPack() failed: "); + Serial.println(error.f_str()); + return; + } + + // Fetch values. + // + // Most of the time, you can rely on the implicit casts. + // In other case, you can do doc["time"].as(); + const char* sensor = doc["sensor"]; + long time = doc["time"]; + double latitude = doc["data"][0]; + double longitude = doc["data"][1]; + + // Print values. + Serial.println(sensor); + Serial.println(time); + Serial.println(latitude, 6); + Serial.println(longitude, 6); +} + +void loop() { + // not used in this example +} diff --git a/firmware/uniclock/lib/ArduinoJson/examples/ProgmemExample/ProgmemExample.ino b/firmware/uniclock/lib/ArduinoJson/examples/ProgmemExample/ProgmemExample.ino new file mode 100644 index 0000000..8cb4ddd --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/ProgmemExample/ProgmemExample.ino @@ -0,0 +1,64 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows the different ways you can use Flash strings with +// ArduinoJson. +// +// Use Flash strings sparingly, because ArduinoJson duplicates them in the +// JsonDocument. Prefer plain old char*, as they are more efficient in term of +// code size, speed, and memory usage. +// +// https://arduinojson.org/v6/example/progmem/ + +#include + +void setup() { + DynamicJsonDocument doc(1024); + + // You can use a Flash String as your JSON input. + // WARNING: the strings in the input will be duplicated in the JsonDocument. + deserializeJson(doc, F("{\"sensor\":\"gps\",\"time\":1351824120," + "\"data\":[48.756080,2.302038]}")); + JsonObject obj = doc.as(); + + // You can use a Flash String to get an element of a JsonObject + // No duplication is done. + long time = obj[F("time")]; + + // You can use a Flash String to set an element of a JsonObject + // WARNING: the content of the Flash String will be duplicated in the + // JsonDocument. + obj[F("time")] = time; + + // You can set a Flash String to a JsonObject or JsonArray: + // WARNING: the content of the Flash String will be duplicated in the + // JsonDocument. + obj["sensor"] = F("gps"); + + // It works with serialized() too: + obj["sensor"] = serialized(F("\"gps\"")); + obj["sensor"] = serialized(F("\xA3gps"), 3); + + // You can compare the content of a JsonVariant to a Flash String + if (obj["sensor"] == F("gps")) { + // ... + } +} + +void loop() { + // not used in this example +} + +// See also +// -------- +// +// https://arduinojson.org/ contains the documentation for all the functions +// used above. It also includes an FAQ that will help you solve any memory +// problem. +// +// The book "Mastering ArduinoJson" contains a quick C++ course that explains +// how your microcontroller stores strings in memory. It also tells why you +// should not abuse Flash strings with ArduinoJson. +// Learn more at https://arduinojson.org/book/ +// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤ diff --git a/firmware/uniclock/lib/ArduinoJson/examples/StringExample/StringExample.ino b/firmware/uniclock/lib/ArduinoJson/examples/StringExample/StringExample.ino new file mode 100644 index 0000000..0fd3068 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/examples/StringExample/StringExample.ino @@ -0,0 +1,77 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows the different ways you can use String with ArduinoJson. +// +// Use String objects sparingly, because ArduinoJson duplicates them in the +// JsonDocument. Prefer plain old char[], as they are more efficient in term of +// code size, speed, and memory usage. +// +// https://arduinojson.org/v6/example/string/ + +#include + +void setup() { + DynamicJsonDocument doc(1024); + + // You can use a String as your JSON input. + // WARNING: the string in the input will be duplicated in the JsonDocument. + String input = + "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}"; + deserializeJson(doc, input); + JsonObject obj = doc.as(); + + // You can use a String to get an element of a JsonObject + // No duplication is done. + long time = obj[String("time")]; + + // You can use a String to set an element of a JsonObject + // WARNING: the content of the String will be duplicated in the JsonDocument. + obj[String("time")] = time; + + // You can get a String from a JsonObject or JsonArray: + // No duplication is done, at least not in the JsonDocument. + String sensor = obj["sensor"]; + + // Unfortunately, the following doesn't work (issue #118): + // sensor = obj["sensor"]; // <- error "ambiguous overload for 'operator='" + // As a workaround, you need to replace by: + sensor = obj["sensor"].as(); + + // You can set a String to a JsonObject or JsonArray: + // WARNING: the content of the String will be duplicated in the JsonDocument. + obj["sensor"] = sensor; + + // It works with serialized() too: + obj["sensor"] = serialized(sensor); + + // You can also concatenate strings + // WARNING: the content of the String will be duplicated in the JsonDocument. + obj[String("sen") + "sor"] = String("gp") + "s"; + + // You can compare the content of a JsonObject with a String + if (obj["sensor"] == sensor) { + // ... + } + + // Lastly, you can print the resulting JSON to a String + String output; + serializeJson(doc, output); +} + +void loop() { + // not used in this example +} + +// See also +// -------- +// +// https://arduinojson.org/ contains the documentation for all the functions +// used above. It also includes an FAQ that will help you solve any problem. +// +// The book "Mastering ArduinoJson" contains a quick C++ course that explains +// how your microcontroller stores strings in memory. On several occasions, it +// shows how you can avoid String in your program. +// Learn more at https://arduinojson.org/book/ +// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤ diff --git a/firmware/uniclock/lib/ArduinoJson/extras/ArduinoJsonConfig.cmake.in b/firmware/uniclock/lib/ArduinoJson/extras/ArduinoJsonConfig.cmake.in new file mode 100644 index 0000000..9c15f36 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/ArduinoJsonConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/firmware/uniclock/lib/ArduinoJson/extras/CompileOptions.cmake b/firmware/uniclock/lib/ArduinoJson/extras/CompileOptions.cmake new file mode 100644 index 0000000..82a750a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/CompileOptions.cmake @@ -0,0 +1,101 @@ +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + add_compile_options( + -pedantic + -Wall + -Wcast-align + -Wcast-qual + -Wconversion + -Wctor-dtor-privacy + -Wdisabled-optimization + -Werror + -Wextra + -Wformat=2 + -Winit-self + -Wmissing-include-dirs + -Wnon-virtual-dtor + -Wold-style-cast + -Woverloaded-virtual + -Wparentheses + -Wredundant-decls + -Wshadow + -Wsign-conversion + -Wsign-promo + -Wstrict-aliasing + -Wundef + ) + + if(${COVERAGE}) + set(CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage") + endif() + +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.8) AND (NOT ${COVERAGE})) + add_compile_options(-g -Og) + else() + add_compile_options(-g -O0) + endif() + + add_compile_options( + -Wstrict-null-sentinel + -Wno-vla # Allow VLA in tests + ) + add_definitions(-DHAS_VARIABLE_LENGTH_ARRAY) + + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.5) + add_compile_options(-Wlogical-op) # the flag exists in 4.4 but is buggy + endif() + + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6) + add_compile_options(-Wnoexcept) + endif() +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options( + -Wc++11-compat + -Wdeprecated-register + -Wno-vla-extension # Allow VLA in tests + ) + add_definitions( + -DHAS_VARIABLE_LENGTH_ARRAY + -DSUBSCRIPT_CONFLICTS_WITH_BUILTIN_OPERATOR + ) +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.0) AND (NOT ${COVERAGE})) + add_compile_options(-g -Og) + else() + add_compile_options(-g -O0) + endif() +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0) AND (NOT ${COVERAGE})) + add_compile_options(-g -Og) + else() + add_compile_options(-g -O0) + endif() +endif() + +if(MSVC) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_compile_options( + /W4 # Set warning level + /WX # Treats all compiler warnings as errors. + ) + + if (NOT MSVC_VERSION LESS 1910) # >= Visual Studio 2017 + add_compile_options( + /Zc:__cplusplus # Enable updated __cplusplus macro + ) + endif() +endif() + +if(MINGW) + # Static link on MinGW to avoid linking with the wrong DLLs when multiple + # versions are installed. + add_link_options(-static) +endif() diff --git a/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/CMakeLists.txt new file mode 100644 index 0000000..e3c4475 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/CMakeLists.txt @@ -0,0 +1,8 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(example) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/CMakeLists.txt new file mode 100644 index 0000000..f06bfda --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/CMakeLists.txt @@ -0,0 +1,6 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +idf_component_register(SRCS "main.cpp" + INCLUDE_DIRS "") diff --git a/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/component.mk b/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/main.cpp b/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/main.cpp new file mode 100644 index 0000000..50d7ec0 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/ci/espidf/main/main.cpp @@ -0,0 +1,16 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include + +extern "C" void app_main() { + char buffer[256]; + StaticJsonDocument<200> doc; + + doc["hello"] = "world"; + serializeJson(doc, buffer); + deserializeJson(doc, buffer); + serializeMsgPack(doc, buffer); + deserializeMsgPack(doc, buffer); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/ci/particle.sh b/firmware/uniclock/lib/ArduinoJson/extras/ci/particle.sh new file mode 100644 index 0000000..5566353 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/ci/particle.sh @@ -0,0 +1,10 @@ +#!/bin/sh -ex + +BOARD=$1 + +cd "$(dirname "$0")/../../" + +cp extras/particle/src/smocktest.ino src/ +cp extras/particle/project.properties ./ + +particle compile "$BOARD" diff --git a/firmware/uniclock/lib/ArduinoJson/extras/conf_test/avr.cpp b/firmware/uniclock/lib/ArduinoJson/extras/conf_test/avr.cpp new file mode 100644 index 0000000..cd0b4bd --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/conf_test/avr.cpp @@ -0,0 +1,16 @@ +#include + +static_assert(ARDUINOJSON_USE_LONG_LONG == 0, "ARDUINOJSON_USE_LONG_LONG"); + +static_assert(ARDUINOJSON_SLOT_OFFSET_SIZE == 1, + "ARDUINOJSON_SLOT_OFFSET_SIZE"); + +static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN"); + +static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE"); + +static_assert(sizeof(ARDUINOJSON_NAMESPACE::VariantSlot) == 8, + "sizeof(VariantSlot)"); + +void setup() {} +void loop() {} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/conf_test/esp8266.cpp b/firmware/uniclock/lib/ArduinoJson/extras/conf_test/esp8266.cpp new file mode 100644 index 0000000..bb9988b --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/conf_test/esp8266.cpp @@ -0,0 +1,16 @@ +#include + +static_assert(ARDUINOJSON_USE_LONG_LONG == 1, "ARDUINOJSON_USE_LONG_LONG"); + +static_assert(ARDUINOJSON_SLOT_OFFSET_SIZE == 2, + "ARDUINOJSON_SLOT_OFFSET_SIZE"); + +static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN"); + +static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE"); + +static_assert(sizeof(ARDUINOJSON_NAMESPACE::VariantSlot) == 16, + "sizeof(VariantSlot)"); + +void setup() {} +void loop() {} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/conf_test/x64.cpp b/firmware/uniclock/lib/ArduinoJson/extras/conf_test/x64.cpp new file mode 100644 index 0000000..8cd6c06 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/conf_test/x64.cpp @@ -0,0 +1,15 @@ +#include + +static_assert(ARDUINOJSON_USE_LONG_LONG == 1, "ARDUINOJSON_USE_LONG_LONG"); + +static_assert(ARDUINOJSON_SLOT_OFFSET_SIZE == 4, + "ARDUINOJSON_SLOT_OFFSET_SIZE"); + +static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN"); + +static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE"); + +static_assert(sizeof(ARDUINOJSON_NAMESPACE::VariantSlot) == 32, + "sizeof(VariantSlot)"); + +int main() {} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/conf_test/x86.cpp b/firmware/uniclock/lib/ArduinoJson/extras/conf_test/x86.cpp new file mode 100644 index 0000000..dab9da6 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/conf_test/x86.cpp @@ -0,0 +1,15 @@ +#include + +static_assert(ARDUINOJSON_USE_LONG_LONG == 1, "ARDUINOJSON_USE_LONG_LONG"); + +static_assert(ARDUINOJSON_SLOT_OFFSET_SIZE == 2, + "ARDUINOJSON_SLOT_OFFSET_SIZE"); + +static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN"); + +static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE"); + +static_assert(sizeof(ARDUINOJSON_NAMESPACE::VariantSlot) == 16, + "sizeof(VariantSlot)"); + +int main() {} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/CMakeLists.txt new file mode 100644 index 0000000..e5d6a36 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/CMakeLists.txt @@ -0,0 +1,59 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +if(MSVC) + add_compile_options(-D_CRT_SECURE_NO_WARNINGS) +endif() + +add_executable(msgpack_reproducer + msgpack_fuzzer.cpp + reproducer.cpp +) +target_link_libraries(msgpack_reproducer + ArduinoJson +) + +add_executable(json_reproducer + json_fuzzer.cpp + reproducer.cpp +) +target_link_libraries(json_reproducer + ArduinoJson +) + +macro(add_fuzzer name) + set(FUZZER "${name}_fuzzer") + set(CORPUS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus") + set(SEED_CORPUS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${name}_seed_corpus") + add_executable("${FUZZER}" + "${name}_fuzzer.cpp" + ) + target_link_libraries("${FUZZER}" + ArduinoJson + ) + set_target_properties("${FUZZER}" + PROPERTIES + COMPILE_FLAGS + "-fprofile-instr-generate -fcoverage-mapping -fsanitize=fuzzer -fno-sanitize-recover=all" + LINK_FLAGS + "-fprofile-instr-generate -fcoverage-mapping -fsanitize=fuzzer -fno-sanitize-recover=all" + ) + + add_test( + NAME + "${FUZZER}" + COMMAND + "${FUZZER}" "${CORPUS_DIR}" "${SEED_CORPUS_DIR}" -max_total_time=5 -timeout=1 + ) + + set_tests_properties("${FUZZER}" + PROPERTIES + LABELS "Fuzzing" + ) +endmacro() + +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6) + add_fuzzer(json) + add_fuzzer(msgpack) +endif() diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/Makefile b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/Makefile new file mode 100644 index 0000000..d3c574e --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/Makefile @@ -0,0 +1,22 @@ +# CAUTION: this file is invoked by https://github.com/google/oss-fuzz + +CXXFLAGS += -I../../src -DARDUINOJSON_DEBUG=1 + +all: \ + $(OUT)/json_fuzzer \ + $(OUT)/json_fuzzer_seed_corpus.zip \ + $(OUT)/json_fuzzer.options \ + $(OUT)/msgpack_fuzzer \ + $(OUT)/msgpack_fuzzer_seed_corpus.zip \ + $(OUT)/msgpack_fuzzer.options + +$(OUT)/%_fuzzer: %_fuzzer.cpp $(shell find ../../src -type f) + $(CXX) $(CXXFLAGS) $< -o$@ $(LIB_FUZZING_ENGINE) + +$(OUT)/%_fuzzer_seed_corpus.zip: %_seed_corpus/* + zip -j $@ $? + +$(OUT)/%_fuzzer.options: + @echo "[libfuzzer]" > $@ + @echo "max_len = 256" >> $@ + @echo "timeout = 10" >> $@ diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_fuzzer.cpp b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_fuzzer.cpp new file mode 100644 index 0000000..db9a3d6 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_fuzzer.cpp @@ -0,0 +1,11 @@ +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + DynamicJsonDocument doc(4096); + DeserializationError error = deserializeJson(doc, data, size); + if (!error) { + std::string json; + serializeJson(doc, json); + } + return 0; +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Comments.json b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Comments.json new file mode 100644 index 0000000..bcc4cec --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Comments.json @@ -0,0 +1,10 @@ +//comment +/*comment*/ +[ //comment +/*comment*/"comment"/*comment*/,//comment +/*comment*/{//comment +/* comment*/"key"//comment +: //comment +"value"//comment +}/*comment*/ +]//comment \ No newline at end of file diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/EmptyArray.json b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/EmptyArray.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/EmptyArray.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/EmptyObject.json b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/EmptyObject.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/EmptyObject.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/ExcessiveNesting.json b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/ExcessiveNesting.json new file mode 100644 index 0000000..9285019 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/ExcessiveNesting.json @@ -0,0 +1 @@ +[1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20,[21,[22,[23,[24,[25,[26,[27,[28,[29,[30,[31,[32,[33,[34,[35,[36,[37,[38,[39,[40,[41,[42,[43,[44,[45,[46,[47,[48,[49,[50,[51,[52,[53,[54,[55,[56,[57,[58,[59,[60,[61,[62,[63,[64,[65,[66,[67,[68,[69,[70,[71,[72,[73,[74,[75,[76,[77,[78,[79,[80,[81,[82,[83,[84,[85,[86,[87,[88,[89,[90,[91,[92,[93,[94,[95,[96,[97,[98,[99,[100,[101,[102,[103,[104,[105,[106,[107,[108,[109,[110,[111,[112,[113,[114,[115,[116,[117,[118,[119,[120]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/IntegerOverflow.json b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/IntegerOverflow.json new file mode 100644 index 0000000..3a33cb8 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/IntegerOverflow.json @@ -0,0 +1 @@ +9720730739393920739 diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Numbers.json b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Numbers.json new file mode 100644 index 0000000..f6b9419 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Numbers.json @@ -0,0 +1,24 @@ +[ + 123, + -123, + 123.456, + -123.456, + 12e34, + 12e-34, + 12e+34, + 12E34, + 12E-34, + 12E+34, + 12.34e56, + 12.34e-56, + 12.34e+56, + 12.34E56, + 12.34E-56, + 12.34E+56, + NaN, + -NaN, + +NaN, + Infinity, + +Infinity, + -Infinity +] \ No newline at end of file diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/OpenWeatherMap.json b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/OpenWeatherMap.json new file mode 100644 index 0000000..27d6baf --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/OpenWeatherMap.json @@ -0,0 +1,53 @@ +{ + "coord": { + "lon": -0.13, + "lat": 51.51 + }, + "weather": [ + { + "id": 301, + "main": "Drizzle", + "description": "drizzle", + "icon": "09n" + }, + { + "id": 701, + "main": "Mist", + "description": "mist", + "icon": "50n" + }, + { + "id": 741, + "main": "Fog", + "description": "fog", + "icon": "50n" + } + ], + "base": "stations", + "main": { + "temp": 281.87, + "pressure": 1032, + "humidity": 100, + "temp_min": 281.15, + "temp_max": 283.15 + }, + "visibility": 2900, + "wind": { + "speed": 1.5 + }, + "clouds": { + "all": 90 + }, + "dt": 1483820400, + "sys": { + "type": 1, + "id": 5091, + "message": 0.0226, + "country": "GB", + "sunrise": 1483776245, + "sunset": 1483805443 + }, + "id": 2643743, + "name": "London", + "cod": 200 +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Strings.json b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Strings.json new file mode 100644 index 0000000..3ffa235 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/Strings.json @@ -0,0 +1,8 @@ +[ + "hello", + 'hello', + hello, + {"hello":"world"}, + {'hello':'world'}, + {hello:world} +] \ No newline at end of file diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/WeatherUnderground.json b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/WeatherUnderground.json new file mode 100644 index 0000000..d53ce00 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/json_seed_corpus/WeatherUnderground.json @@ -0,0 +1,90 @@ +{ + "response": { + "version": "0.1", + "termsofService": "http://www.wunderground.com/weather/api/d/terms.html", + "features": { + "conditions": 1 + } + }, + "current_observation": { + "image": { + "url": "http://icons-ak.wxug.com/graphics/wu2/logo_130x80.png", + "title": "Weather Underground", + "link": "http://www.wunderground.com" + }, + "display_location": { + "full": "San Francisco, CA", + "city": "San Francisco", + "state": "CA", + "state_name": "California", + "country": "US", + "country_iso3166": "US", + "zip": "94101", + "latitude": "37.77500916", + "longitude": "-122.41825867", + "elevation": "47.00000000" + }, + "observation_location": { + "full": "SOMA - Near Van Ness, San Francisco, California", + "city": "SOMA - Near Van Ness, San Francisco", + "state": "California", + "country": "US", + "country_iso3166": "US", + "latitude": "37.773285", + "longitude": "-122.417725", + "elevation": "49 ft" + }, + "estimated": {}, + "station_id": "KCASANFR58", + "observation_time": "Last Updated on June 27, 5:27 PM PDT", + "observation_time_rfc822": "Wed, 27 Jun 2012 17:27:13 -0700", + "observation_epoch": "1340843233", + "local_time_rfc822": "Wed, 27 Jun 2012 17:27:14 -0700", + "local_epoch": "1340843234", + "local_tz_short": "PDT", + "local_tz_long": "America/Los_Angeles", + "local_tz_offset": "-0700", + "weather": "Partly Cloudy", + "temperature_string": "66.3 F (19.1 C)", + "temp_f": 66.3, + "temp_c": 19.1, + "relative_humidity": "65%", + "wind_string": "From the NNW at 22.0 MPH Gusting to 28.0 MPH", + "wind_dir": "NNW", + "wind_degrees": 346, + "wind_mph": 22, + "wind_gust_mph": "28.0", + "wind_kph": 35.4, + "wind_gust_kph": "45.1", + "pressure_mb": "1013", + "pressure_in": "29.93", + "pressure_trend": "+", + "dewpoint_string": "54 F (12 C)", + "dewpoint_f": 54, + "dewpoint_c": 12, + "heat_index_string": "NA", + "heat_index_f": "NA", + "heat_index_c": "NA", + "windchill_string": "NA", + "windchill_f": "NA", + "windchill_c": "NA", + "feelslike_string": "66.3 F (19.1 C)", + "feelslike_f": "66.3", + "feelslike_c": "19.1", + "visibility_mi": "10.0", + "visibility_km": "16.1", + "solarradiation": "", + "UV": "5", + "precip_1hr_string": "0.00 in ( 0 mm)", + "precip_1hr_in": "0.00", + "precip_1hr_metric": " 0", + "precip_today_string": "0.00 in (0 mm)", + "precip_today_in": "0.00", + "precip_today_metric": "0", + "icon": "partlycloudy", + "icon_url": "http://icons-ak.wxug.com/i/c/k/partlycloudy.gif", + "forecast_url": "http://www.wunderground.com/US/CA/San_Francisco.html", + "history_url": "http://www.wunderground.com/history/airport/KCASANFR58/2012/6/27/DailyHistory.html", + "ob_url": "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=37.773285,-122.417725" + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_fuzzer.cpp b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_fuzzer.cpp new file mode 100644 index 0000000..e623591 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_fuzzer.cpp @@ -0,0 +1,11 @@ +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + DynamicJsonDocument doc(4096); + DeserializationError error = deserializeMsgPack(doc, data, size); + if (!error) { + std::string json; + serializeMsgPack(doc, json); + } + return 0; +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/array16 b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/array16 new file mode 100644 index 0000000000000000000000000000000000000000..714ba99e70cbed2056b4e4b04c86bb1a2ff7311b GIT binary patch literal 15 Wcmcb^z_c_YH76&3X?cE8P6_}q=LTf} literal 0 HcmV?d00001 diff --git a/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/array32 b/firmware/uniclock/lib/ArduinoJson/extras/fuzzing/msgpack_seed_corpus/array32 new file mode 100644 index 0000000000000000000000000000000000000000..6e3ed7b1b81742fbb90a4135004b55a9a45a5769 GIT binary patch literal 15 Vcmcc1z`($C3P>=Va`5 // size_t +#include // fopen et al. +#include // exit +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); + +std::vector read(const char* path) { + FILE* f = fopen(path, "rb"); + if (!f) { + std::cerr << "Failed to open " << path << std::endl; + exit(1); + } + + fseek(f, 0, SEEK_END); + size_t size = static_cast(ftell(f)); + fseek(f, 0, SEEK_SET); + + std::vector buffer(size); + if (fread(buffer.data(), 1, size, f) != size) { + fclose(f); + std::cerr << "Failed to read " << path << std::endl; + exit(1); + } + + fclose(f); + return buffer; +} + +int main(int argc, const char* argv[]) { + if (argc < 2) { + std::cerr << "Usage: msgpack_fuzzer files" << std::endl; + return 1; + } + + for (int i = 1; i < argc; i++) { + std::cout << "Loading " << argv[i] << std::endl; + std::vector buffer = read(argv[i]); + LLVMFuzzerTestOneInput(buffer.data(), buffer.size()); + } + return 0; +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/particle/project.properties b/firmware/uniclock/lib/ArduinoJson/extras/particle/project.properties new file mode 100644 index 0000000..d39555a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/particle/project.properties @@ -0,0 +1 @@ +name=ArduinoJsonCI diff --git a/firmware/uniclock/lib/ArduinoJson/extras/particle/src/smocktest.ino b/firmware/uniclock/lib/ArduinoJson/extras/particle/src/smocktest.ino new file mode 100644 index 0000000..ca17189 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/particle/src/smocktest.ino @@ -0,0 +1,5 @@ +#include "ArduinoJson.h" + +void setup() {} + +void loop() {} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/build-arduino-package.sh b/firmware/uniclock/lib/ArduinoJson/extras/scripts/build-arduino-package.sh new file mode 100644 index 0000000..339db2f --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/build-arduino-package.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -eu + +INPUT=$1 +OUTPUT=$2 + +cd "$INPUT" + +# remove existing file +rm -f "$OUTPUT" + +# create zip +7z a "$OUTPUT" \ + -xr!.vs \ + CHANGELOG.md \ + examples \ + src \ + keywords.txt \ + library.properties \ + LICENSE.md \ + README.md \ + ArduinoJson.h diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/build-single-header.sh b/firmware/uniclock/lib/ArduinoJson/extras/scripts/build-single-header.sh new file mode 100644 index 0000000..09d91d4 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/build-single-header.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -e + +RE_RELATIVE_INCLUDE='^#[[:space:]]*include[[:space:]]*"(.*)"' +RE_ABSOLUTE_INCLUDE='^#[[:space:]]*include[[:space:]]*<(ArduinoJson/.*)>' +RE_SYSTEM_INCLUDE='^#[[:space:]]*include[[:space:]]*<(.*)>' +RE_EMPTY='^(#[[:space:]]*pragma[[:space:]]+once)?[[:space:]]*(//.*)?$' +SRC_DIRECTORY="$(realpath "$(dirname $0)/../../src")" + + +declare -A INCLUDED + +process() +{ + local PARENT=$1 + local FOLDER=$(dirname $1) + local SHOW_COMMENT=$2 + while IFS= read -r LINE; do + if [[ $LINE =~ $RE_ABSOLUTE_INCLUDE ]]; then + local CHILD=${BASH_REMATCH[1]} + local CHILD_PATH + CHILD_PATH=$(realpath "$SRC_DIRECTORY/$CHILD") + echo "$PARENT -> $CHILD" >&2 + if [[ ! ${INCLUDED[$CHILD_PATH]} ]]; then + INCLUDED[$CHILD_PATH]=true + process "$CHILD" false + fi + elif [[ $LINE =~ $RE_RELATIVE_INCLUDE ]]; then + local CHILD=${BASH_REMATCH[1]} + pushd "$FOLDER" > /dev/null + local CHILD_PATH + CHILD_PATH=$(realpath "$CHILD") + echo "$PARENT -> $CHILD" >&2 + if [[ ! ${INCLUDED[$CHILD_PATH]} ]]; then + INCLUDED[$CHILD_PATH]=true + process "$CHILD" false + fi + popd > /dev/null + elif [[ $LINE =~ $RE_SYSTEM_INCLUDE ]]; then + local CHILD=${BASH_REMATCH[1]} + echo "$PARENT -> <$CHILD>" >&2 + if [[ ! ${INCLUDED[$CHILD]} ]]; then + echo "#include <$CHILD>" + INCLUDED[$CHILD]=true + fi + elif [[ "${SHOW_COMMENT}" = "true" ]] ; then + echo "$LINE" + elif [[ ! $LINE =~ $RE_EMPTY ]]; then + echo "$LINE" + fi + done < $PARENT +} + +simplify_namespaces() { + perl -p0i -e 's|\} // namespace ARDUINOJSON_NAMESPACE\r?\nnamespace ARDUINOJSON_NAMESPACE \{\r?\n||igs' "$1" + rm -f "$1.bak" +} + +INCLUDED=() +INPUT=$1 +OUTPUT=$2 +process "$INPUT" true > "$OUTPUT" +simplify_namespaces "$OUTPUT" diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/get-release-body.sh b/firmware/uniclock/lib/ArduinoJson/extras/scripts/get-release-body.sh new file mode 100644 index 0000000..7c842c2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/get-release-body.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -eu + +TAG="$1" +CHANGELOG="$2" + +cat << END +## Changes + +$(awk '/\* /{ FOUND=1 } /^[[:space:]]*$/ { if(FOUND) exit } { if(FOUND) print }' "$CHANGELOG") + +[View version history](https://github.com/bblanchon/ArduinoJson/blob/$TAG/CHANGELOG.md) +END diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/get-release-page.sh b/firmware/uniclock/lib/ArduinoJson/extras/scripts/get-release-page.sh new file mode 100644 index 0000000..2acb8d8 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/get-release-page.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -eu + +VERSION="$1" +CHANGELOG="$2" +FRONTMATTER="$3" + +cat << END +--- +branch: v6 +version: $VERSION +date: '$(date +'%Y-%m-%d')' +$(cat "$FRONTMATTER") +--- + +$(awk '/\* /{ FOUND=1; print; next } { if (FOUND) exit}' "$CHANGELOG") +END diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/publish-particle-library.sh b/firmware/uniclock/lib/ArduinoJson/extras/scripts/publish-particle-library.sh new file mode 100644 index 0000000..62c3140 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/publish-particle-library.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -eu + +SOURCE_DIR="$(dirname "$0")/../.." +WORK_DIR=$(mktemp -d) +trap 'rm -rf "$WORK_DIR"' EXIT + +cp "$SOURCE_DIR/README.md" "$WORK_DIR/README.md" +cp "$SOURCE_DIR/CHANGELOG.md" "$WORK_DIR/CHANGELOG.md" +cp "$SOURCE_DIR/library.properties" "$WORK_DIR/library.properties" +cp "$SOURCE_DIR/LICENSE.md" "$WORK_DIR/LICENSE.txt" +cp -r "$SOURCE_DIR/src" "$WORK_DIR/" +cp -r "$SOURCE_DIR/examples" "$WORK_DIR/" + +cd "$WORK_DIR" +particle library upload +particle library publish diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/publish.sh b/firmware/uniclock/lib/ArduinoJson/extras/scripts/publish.sh new file mode 100644 index 0000000..c5a4eef --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/publish.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +set -eu + +which awk sed jq 7z curl perl >/dev/null + +cd "$(dirname "$0")/../.." + +if ! git diff --quiet --exit-code; then + echo "Repository contains uncommitted changes" + exit +fi + +VERSION="$1" +DATE=$(date +%F) +TAG="v$VERSION" +VERSION_REGEX='[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9]+)?' + +update_version_in_source () { + IFS=".-" read MAJOR MINOR REVISION EXTRA < <(echo "$VERSION") + UNDERLINE=$(printf -- '-%.0s' $(seq 1 ${#TAG})) + + sed -i~ -bE "1,20{s/$VERSION_REGEX/$VERSION/g}" README.md + rm README.md~ + + sed -i~ -bE "4s/HEAD/$TAG ($DATE)/; 5s/-+/$UNDERLINE/" CHANGELOG.md + rm CHANGELOG.md~ + + sed -i~ -bE "s/(project\\s*\\(ArduinoJson\\s+VERSION\\s+).*?\\)/\\1$MAJOR.$MINOR.$REVISION)/" CMakeLists.txt + rm CMakeLists.txt~ + + sed -i~ -bE "s/\"version\":.*$/\"version\": \"$VERSION\",/" library.json + rm library.json~ + + sed -i~ -bE "s/version=.*$/version=$VERSION/" library.properties + rm library.properties~ + + sed -i~ -bE "s/version: .*$/version: $VERSION.{build}/" appveyor.yml + rm appveyor.yml~ + + sed -i~ -bE \ + -e "s/ARDUINOJSON_VERSION .*$/ARDUINOJSON_VERSION \"$VERSION\"/" \ + -e "s/ARDUINOJSON_VERSION_MAJOR .*$/ARDUINOJSON_VERSION_MAJOR $MAJOR/" \ + -e "s/ARDUINOJSON_VERSION_MINOR .*$/ARDUINOJSON_VERSION_MINOR $MINOR/" \ + -e "s/ARDUINOJSON_VERSION_REVISION .*$/ARDUINOJSON_VERSION_REVISION $REVISION/" \ + src/ArduinoJson/version.hpp + rm src/ArduinoJson/version.hpp*~ +} + +commit_new_version () { + git add src/ArduinoJson/version.hpp README.md CHANGELOG.md library.json library.properties appveyor.yml CMakeLists.txt + git commit -m "Set version to $VERSION" +} + +add_tag () { + CHANGES=$(awk '/\* /{ FOUND=1; print; next } { if (FOUND) exit}' CHANGELOG.md) + git tag -m "ArduinoJson $VERSION"$'\n'"$CHANGES" "$TAG" +} + +push () { + git push --follow-tags +} + +update_version_in_source +commit_new_version +add_tag +push + +extras/scripts/build-arduino-package.sh . "../ArduinoJson-$TAG.zip" +extras/scripts/build-single-header.sh "src/ArduinoJson.h" "../ArduinoJson-$TAG.h" +extras/scripts/build-single-header.sh "src/ArduinoJson.hpp" "../ArduinoJson-$TAG.hpp" +extras/scripts/wandbox/publish.sh "../ArduinoJson-$TAG.h" > "../ArduinoJson-$TAG-wandbox.txt" || echo "Wandbox failed!" +extras/scripts/get-release-page.sh "$VERSION" "CHANGELOG.md" "../ArduinoJson-$TAG-wandbox.txt" > "../ArduinoJson-$TAG.md" + +echo "You can now copy ../ArduinoJson-$TAG.md into arduinojson.org/collections/_versions/$VERSION.md" diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/JsonGeneratorExample.cpp b/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/JsonGeneratorExample.cpp new file mode 100644 index 0000000..0090c84 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/JsonGeneratorExample.cpp @@ -0,0 +1,60 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to generate a JSON document with ArduinoJson. + +#include +#include "ArduinoJson.h" + +int main() { + // Allocate the JSON document + // + // Inside the brackets, 200 is the RAM allocated to this document. + // Don't forget to change this value to match your requirement. + // Use https://arduinojson.org/v6/assistant to compute the capacity. + StaticJsonDocument<200> doc; + + // StaticJsonObject allocates memory on the stack, it can be + // replaced by DynamicJsonDocument which allocates in the heap. + // + // DynamicJsonDocument doc(200); + + // StaticJsonObject allocates memory on the stack, it can be + // replaced by DynamicJsonDocument which allocates in the heap. + // + // DynamicJsonDocument doc(200); + + // Add values in the document + // + doc["sensor"] = "gps"; + doc["time"] = 1351824120; + + // Add an array. + // + JsonArray data = doc.createNestedArray("data"); + data.add(48.756080); + data.add(2.302038); + + // Generate the minified JSON and send it to STDOUT + // + serializeJson(doc, std::cout); + // The above line prints: + // {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]} + + // Start a new line + std::cout << std::endl; + + // Generate the prettified JSON and send it to STDOUT + // + serializeJsonPretty(doc, std::cout); + // The above line prints: + // { + // "sensor": "gps", + // "time": 1351824120, + // "data": [ + // 48.756080, + // 2.302038 + // ] + // } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/JsonParserExample.cpp b/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/JsonParserExample.cpp new file mode 100644 index 0000000..f513caf --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/JsonParserExample.cpp @@ -0,0 +1,59 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to deserialize a JSON document with ArduinoJson. + +#include +#include "ArduinoJson.h" + +int main() { + // Allocate the JSON document + // + // Inside the brackets, 200 is the capacity of the memory pool in bytes. + // Don't forget to change this value to match your JSON document. + // Use https://arduinojson.org/v6/assistant to compute the capacity. + StaticJsonDocument<300> doc; + + // StaticJsonDocument allocates memory on the stack, it can be + // replaced by DynamicJsonDocument which allocates in the heap. + // + // DynamicJsonDocument doc(200); + + // JSON input string. + // + // Using a char[], as shown here, enables the "zero-copy" mode. This mode uses + // the minimal amount of memory because the JsonDocument stores pointers to + // the input buffer. + // If you use another type of input, ArduinoJson must copy the strings from + // the input to the JsonDocument, so you need to increase the capacity of the + // JsonDocument. + char json[] = + "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}"; + + // Deserialize the JSON document + DeserializationError error = deserializeJson(doc, json); + + // Test if parsing succeeds. + if (error) { + std::cerr << "deserializeJson() failed: " << error.c_str() << std::endl; + return 1; + } + + // Fetch values. + // + // Most of the time, you can rely on the implicit casts. + // In other case, you can do doc["time"].as(); + const char* sensor = doc["sensor"]; + long time = doc["time"]; + double latitude = doc["data"][0]; + double longitude = doc["data"][1]; + + // Print values. + std::cout << sensor << std::endl; + std::cout << time << std::endl; + std::cout << latitude << std::endl; + std::cout << longitude << std::endl; + + return 0; +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/MsgPackParserExample.cpp b/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/MsgPackParserExample.cpp new file mode 100644 index 0000000..186816e --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/MsgPackParserExample.cpp @@ -0,0 +1,68 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License +// +// This example shows how to generate a JSON document with ArduinoJson. + +#include +#include "ArduinoJson.h" + +int main() { + // Allocate the JSON document + // + // Inside the brackets, 300 is the size of the memory pool in bytes. + // Don't forget to change this value to match your JSON document. + // Use https://arduinojson.org/assistant to compute the capacity. + StaticJsonDocument<300> doc; + + // StaticJsonObject allocates memory on the stack, it can be + // replaced by DynamicJsonObject which allocates in the heap. + // + // DynamicJsonObject doc(200); + + // MessagePack input string. + // + // It's better to use a char[] as shown here. + // If you use a const char* or a String, ArduinoJson will + // have to make a copy of the input in the JsonBuffer. + uint8_t input[] = {131, 166, 115, 101, 110, 115, 111, 114, 163, 103, 112, 115, + 164, 116, 105, 109, 101, 206, 80, 147, 50, 248, 164, 100, + 97, 116, 97, 146, 203, 64, 72, 96, 199, 58, 188, 148, + 112, 203, 64, 2, 106, 146, 230, 33, 49, 169}; + // This MessagePack document contains: + // { + // "sensor": "gps", + // "time": 1351824120, + // "data": [48.75608, 2.302038] + // } + + // doc of the object tree. + // + // It's a reference to the JsonObject, the actual bytes are inside the + // JsonBuffer with all the other nodes of the object tree. + // Memory is freed when jsonBuffer goes out of scope. + DeserializationError error = deserializeMsgPack(doc, input); + + // Test if parsing succeeds. + if (error) { + std::cerr << "deserializeMsgPack() failed: " << error.c_str() << std::endl; + return 1; + } + + // Fetch values. + // + // Most of the time, you can rely on the implicit casts. + // In other case, you can do doc["time"].as(); + const char* sensor = doc["sensor"]; + long time = doc["time"]; + double latitude = doc["data"][0]; + double longitude = doc["data"][1]; + + // Print values. + std::cout << sensor << std::endl; + std::cout << time << std::endl; + std::cout << latitude << std::endl; + std::cout << longitude << std::endl; + + return 0; +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/publish.sh b/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/publish.sh new file mode 100644 index 0000000..0900cb0 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/scripts/wandbox/publish.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -eu + +ARDUINOJSON_H="$1" + +read_string() { + jq --slurp --raw-input '.' "$1" +} + +compile() { + FILE_PATH="$(dirname $0)/$1.cpp" + cat >parameters.json < + +#include + +TEST_CASE("Issue #1120") { + StaticJsonDocument<500> doc; + constexpr char str[] = + "{\"contents\":[{\"module\":\"Packet\"},{\"module\":\"Analog\"}]}"; + deserializeJson(doc, str); + + SECTION("MemberProxy::isNull()") { + SECTION("returns false") { + auto value = doc[std::string("contents")]; + CHECK(value.isNull() == false); + } + + SECTION("returns true") { + auto value = doc[std::string("zontents")]; + CHECK(value.isNull() == true); + } + } + + SECTION("ElementProxy >::isNull()") { + SECTION("returns false") { // Issue #1120 + auto value = doc["contents"][1]; + CHECK(value.isNull() == false); + } + + SECTION("returns true") { + auto value = doc["contents"][2]; + CHECK(value.isNull() == true); + } + } + + SECTION("MemberProxy, const char*>::isNull()") { + SECTION("returns false") { + auto value = doc["contents"][1]["module"]; + CHECK(value.isNull() == false); + } + + SECTION("returns true") { + auto value = doc["contents"][1]["zodule"]; + CHECK(value.isNull() == true); + } + } + + SECTION("MemberProxy, std::string>::isNull()") { + SECTION("returns false") { + auto value = doc["contents"][1][std::string("module")]; + CHECK(value.isNull() == false); + } + + SECTION("returns true") { + auto value = doc["contents"][1][std::string("zodule")]; + CHECK(value.isNull() == true); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/nullptr.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/nullptr.cpp new file mode 100644 index 0000000..813b9cc --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/nullptr.cpp @@ -0,0 +1,47 @@ +#include + +#include + +#if !ARDUINOJSON_HAS_NULLPTR +# error ARDUINOJSON_HAS_NULLPTR must be set to 1 +#endif + +TEST_CASE("nullptr") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("JsonVariant == nullptr") { + REQUIRE((variant == nullptr)); + REQUIRE_FALSE((variant != nullptr)); + } + + SECTION("JsonVariant != nullptr") { + variant.set(42); + + REQUIRE_FALSE((variant == nullptr)); + REQUIRE((variant != nullptr)); + } + + SECTION("JsonVariant.set(nullptr)") { + variant.set(42); + variant.set(nullptr); + + REQUIRE(variant.isNull()); + } + + SECTION("JsonVariant.set(nullptr) with unbound reference") { + JsonVariant unboundReference; + + unboundReference.set(nullptr); + + REQUIRE(variant.isNull()); + } + + SECTION("JsonVariant.is()") { + variant.set(42); + REQUIRE(variant.is() == false); + + variant.clear(); + REQUIRE(variant.is() == true); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/use_long_long_0.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/use_long_long_0.cpp new file mode 100644 index 0000000..f467aab --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/use_long_long_0.cpp @@ -0,0 +1,16 @@ +#define ARDUINOJSON_USE_LONG_LONG 0 +#include + +#include + +TEST_CASE("ARDUINOJSON_USE_LONG_LONG == 0") { + DynamicJsonDocument doc(4096); + + doc["A"] = 42; + doc["B"] = 84; + + std::string json; + serializeJson(doc, json); + + REQUIRE(json == "{\"A\":42,\"B\":84}"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/use_long_long_1.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/use_long_long_1.cpp new file mode 100644 index 0000000..5127a55 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp11/use_long_long_1.cpp @@ -0,0 +1,17 @@ +#define ARDUINOJSON_USE_LONG_LONG 1 +#include + +#include + +TEST_CASE("ARDUINOJSON_USE_LONG_LONG == 1") { + DynamicJsonDocument doc(4096); + JsonObject root = doc.to(); + + root["A"] = 123456789123456789; + root["B"] = 987654321987654321; + + std::string json; + serializeJson(doc, json); + + REQUIRE(json == "{\"A\":123456789123456789,\"B\":987654321987654321}"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp17/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp17/CMakeLists.txt new file mode 100644 index 0000000..05af200 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp17/CMakeLists.txt @@ -0,0 +1,29 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +if(MSVC_VERSION LESS 1910) + return() +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5) + return() +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7) + return() +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(Cpp17Tests + string_view.cpp +) + +add_test(Cpp17 Cpp17Tests) + +set_tests_properties(Cpp17 + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp17/string_view.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp17/string_view.cpp new file mode 100644 index 0000000..729c59a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp17/string_view.cpp @@ -0,0 +1,100 @@ +#include + +#include +#include + +#if !ARDUINOJSON_ENABLE_STRING_VIEW +# error ARDUINOJSON_ENABLE_STRING_VIEW must be set to 1 +#endif + +TEST_CASE("string_view") { + StaticJsonDocument<256> doc; + JsonVariant variant = doc.to(); + + SECTION("deserializeJson()") { + auto err = deserializeJson(doc, std::string_view("123", 2)); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.as() == 12); + } + + SECTION("JsonDocument::set()") { + doc.set(std::string_view("123", 2)); + REQUIRE(doc.as() == "12"); + } + + SECTION("JsonDocument::operator[]() const") { + doc["ab"] = "Yes"; + doc["abc"] = "No"; + REQUIRE(doc[std::string_view("abc", 2)] == "Yes"); + } + + SECTION("JsonDocument::operator[]()") { + doc[std::string_view("abc", 2)] = "Yes"; + REQUIRE(doc["ab"] == "Yes"); + } + + SECTION("JsonVariant::operator==()") { + variant.set("A"); + REQUIRE(variant == std::string_view("AX", 1)); + REQUIRE_FALSE(variant == std::string_view("BX", 1)); + } + + SECTION("JsonVariant::operator>()") { + variant.set("B"); + REQUIRE(variant > std::string_view("AX", 1)); + REQUIRE_FALSE(variant > std::string_view("CX", 1)); + } + + SECTION("JsonVariant::operator<()") { + variant.set("B"); + REQUIRE(variant < std::string_view("CX", 1)); + REQUIRE_FALSE(variant < std::string_view("AX", 1)); + } + + SECTION("String deduplication") { + doc.add(std::string_view("example one", 7)); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1) + 8); + + doc.add(std::string_view("example two", 7)); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8); + + doc.add(std::string_view("example\0tree", 12)); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(3) + 21); + + doc.add(std::string_view("example\0tree and a half", 12)); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(4) + 21); + } + + SECTION("as()") { + doc["s"] = "Hello World"; + doc["i"] = 42; + REQUIRE(doc["s"].as() == std::string_view("Hello World")); + REQUIRE(doc["i"].as() == std::string_view()); + } + + SECTION("is()") { + doc["s"] = "Hello World"; + doc["i"] = 42; + REQUIRE(doc["s"].is() == true); + REQUIRE(doc["i"].is() == false); + } + + SECTION("String containing NUL") { + doc.set(std::string("hello\0world", 11)); + REQUIRE(doc.as().size() == 11); + REQUIRE(doc.as() == std::string_view("hello\0world", 11)); + } +} + +using ARDUINOJSON_NAMESPACE::adaptString; + +TEST_CASE("StringViewAdapter") { + std::string_view str("bravoXXX", 5); + auto adapter = adaptString(str); + + CHECK(stringCompare(adapter, adaptString("alpha", 5)) > 0); + CHECK(stringCompare(adapter, adaptString("bravo", 5)) == 0); + CHECK(stringCompare(adapter, adaptString("charlie", 7)) < 0); + + CHECK(adapter.size() == 5); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp20/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp20/CMakeLists.txt new file mode 100644 index 0000000..2c9a72b --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp20/CMakeLists.txt @@ -0,0 +1,29 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +if(MSVC_VERSION LESS 1910) + return() +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10) + return() +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10) + return() +endif() + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(Cpp20Tests + smoke_test.cpp +) + +add_test(Cpp20 Cpp20Tests) + +set_tests_properties(Cpp20 + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp20/smoke_test.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp20/smoke_test.cpp new file mode 100644 index 0000000..dc41e87 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Cpp20/smoke_test.cpp @@ -0,0 +1,15 @@ +#include + +#include +#include + +TEST_CASE("C++20 smoke test") { + StaticJsonDocument<128> doc; + + deserializeJson(doc, "{\"hello\":\"world\"}"); + REQUIRE(doc["hello"] == "world"); + + std::string json; + serializeJson(doc, json); + REQUIRE(json == "{\"hello\":\"world\"}"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/CMakeLists.txt new file mode 100644 index 0000000..dc76c91 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/CMakeLists.txt @@ -0,0 +1,48 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +macro(build_should_fail target) + set_target_properties(${target} + PROPERTIES + EXCLUDE_FROM_ALL TRUE + EXCLUDE_FROM_DEFAULT_BUILD TRUE + ) + add_test( + NAME + ${target} + COMMAND + ${CMAKE_COMMAND} --build . --target ${target} --config $ + WORKING_DIRECTORY + ${CMAKE_BINARY_DIR} + ) + set_tests_properties(${target} + PROPERTIES + WILL_FAIL TRUE + LABELS "WillFail;Catch" + ) +endmacro() + + +add_executable(Issue978 Issue978.cpp) +build_should_fail(Issue978) + +add_executable(Issue1189 Issue1189.cpp) +build_should_fail(Issue1189) + +add_executable(read_long_long read_long_long.cpp) +set_property(TARGET read_long_long PROPERTY CXX_STANDARD 11) +build_should_fail(read_long_long) + +add_executable(write_long_long write_long_long.cpp) +set_property(TARGET write_long_long PROPERTY CXX_STANDARD 11) +build_should_fail(write_long_long) + +add_executable(delete_jsondocument delete_jsondocument.cpp) +build_should_fail(delete_jsondocument) + +add_executable(variant_as_char variant_as_char.cpp) +build_should_fail(variant_as_char) + +add_executable(assign_char assign_char.cpp) +build_should_fail(assign_char) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/Issue1189.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/Issue1189.cpp new file mode 100644 index 0000000..e86bb99 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/Issue1189.cpp @@ -0,0 +1,13 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include + +// a function should not be able to get a JsonDocument by value +void f(JsonDocument) {} + +int main() { + DynamicJsonDocument doc(1024); + f(doc); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/Issue978.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/Issue978.cpp new file mode 100644 index 0000000..112d36d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/Issue978.cpp @@ -0,0 +1,13 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include + +struct Stream {}; + +int main() { + Stream* stream = 0; + DynamicJsonDocument doc(1024); + deserializeJson(doc, stream); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/assign_char.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/assign_char.cpp new file mode 100644 index 0000000..633b54d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/assign_char.cpp @@ -0,0 +1,12 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include + +// See issue #1498 + +int main() { + DynamicJsonDocument doc(1024); + doc["dummy"] = 'A'; +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/delete_jsondocument.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/delete_jsondocument.cpp new file mode 100644 index 0000000..ef2b0e8 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/delete_jsondocument.cpp @@ -0,0 +1,12 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include + +struct Stream {}; + +int main() { + JsonDocument* doc = new DynamicJsonDocument(42); + delete doc; +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/read_long_long.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/read_long_long.cpp new file mode 100644 index 0000000..d900a7f --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/read_long_long.cpp @@ -0,0 +1,20 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_USE_LONG_LONG 0 +#include + +#if defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ >= 8 +# error This test requires sizeof(long) < 8 +#endif + +#if !ARDUINOJSON_HAS_LONG_LONG +# error This test requires C++11 +#endif + +ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(long long) +int main() { + DynamicJsonDocument doc(1024); + doc["dummy"].as(); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/variant_as_char.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/variant_as_char.cpp new file mode 100644 index 0000000..41885cf --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/variant_as_char.cpp @@ -0,0 +1,12 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include + +// See issue #1498 + +int main() { + DynamicJsonDocument doc(1024); + doc["dummy"].as(); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/write_long_long.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/write_long_long.cpp new file mode 100644 index 0000000..2213a4d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/FailingBuilds/write_long_long.cpp @@ -0,0 +1,19 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_USE_LONG_LONG 0 +#include + +#if defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ >= 8 +# error This test requires sizeof(long) < 8 +#endif + +#if !ARDUINOJSON_HAS_LONG_LONG +# error This test requires C++11 +#endif + +int main() { + DynamicJsonDocument doc(1024); + doc["dummy"] = static_cast(42); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/Arduino.h b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/Arduino.h new file mode 100644 index 0000000..4f24b26 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/Arduino.h @@ -0,0 +1,12 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include "api/Print.h" +#include "api/Stream.h" +#include "api/String.h" +#include "progmem_emulation.hpp" + +#define ARDUINO_H_INCLUDED 1 diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/CustomReader.hpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/CustomReader.hpp new file mode 100644 index 0000000..8a97b50 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/CustomReader.hpp @@ -0,0 +1,26 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +class CustomReader { + std::stringstream _stream; + + public: + CustomReader(const char* input) : _stream(input) {} + + int read() { + return _stream.get(); + } + + size_t readBytes(char* buffer, size_t length) { + _stream.read(buffer, static_cast(length)); + return static_cast(_stream.gcount()); + } + + private: + CustomReader(const CustomReader&); +}; diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/Print.h b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/Print.h new file mode 100644 index 0000000..864fb41 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/Print.h @@ -0,0 +1,33 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include +#include + +class Print { + public: + virtual ~Print() {} + + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + + size_t write(const char *str) { + if (!str) + return 0; + return write(reinterpret_cast(str), strlen(str)); + } + + size_t write(const char *buffer, size_t size) { + return write(reinterpret_cast(buffer), size); + } +}; + +class Printable { + public: + virtual ~Printable() {} + virtual size_t printTo(Print &p) const = 0; +}; diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/Stream.h b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/Stream.h new file mode 100644 index 0000000..8cc18d2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/Stream.h @@ -0,0 +1,14 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +// Reproduces Arduino's Stream class +class Stream // : public Print +{ + public: + virtual ~Stream() {} + virtual int read() = 0; + virtual size_t readBytes(char *buffer, size_t length) = 0; +}; diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/String.h b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/String.h new file mode 100644 index 0000000..b2d5ac5 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/api/String.h @@ -0,0 +1,69 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +// Reproduces Arduino's String class +class String { + public: + String() : _maxCapacity(1024) {} + explicit String(const char* s) : _str(s), _maxCapacity(1024) {} + + void limitCapacityTo(size_t maxCapacity) { + _maxCapacity = maxCapacity; + } + + unsigned char concat(const char* s) { + return concat(s, strlen(s)); + } + + size_t length() const { + return _str.size(); + } + + const char* c_str() const { + return _str.c_str(); + } + + bool operator==(const char* s) const { + return _str == s; + } + + String& operator=(const char* s) { + _str.assign(s); + return *this; + } + + char operator[](unsigned int index) const { + if (index >= _str.size()) + return 0; + return _str[index]; + } + + friend std::ostream& operator<<(std::ostream& lhs, const ::String& rhs) { + lhs << rhs._str; + return lhs; + } + + protected: + // This function is protected in most Arduino cores + unsigned char concat(const char* s, size_t n) { + if (_str.size() + n > _maxCapacity) + return 0; + _str.append(s, n); + return 1; + } + + private: + std::string _str; + size_t _maxCapacity; +}; + +class StringSumHelper; + +inline bool operator==(const std::string& lhs, const ::String& rhs) { + return lhs == rhs.c_str(); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/progmem_emulation.hpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/progmem_emulation.hpp new file mode 100644 index 0000000..41597dc --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Helpers/progmem_emulation.hpp @@ -0,0 +1,29 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include // uint8_t + +#define PROGMEM + +class __FlashStringHelper; + +inline const void* convertPtrToFlash(const void* s) { + return reinterpret_cast(s) + 42; +} + +inline const void* convertFlashToPtr(const void* s) { + return reinterpret_cast(s) - 42; +} + +#define PSTR(X) reinterpret_cast(convertPtrToFlash(X)) +#define F(X) reinterpret_cast(PSTR(X)) + +inline uint8_t pgm_read_byte(const void* p) { + return *reinterpret_cast(convertFlashToPtr(p)); +} + +#define ARDUINOJSON_DEFINE_PROGMEM_ARRAY(type, name, value) \ + static type const ARDUINOJSON_CONCAT2(name, _progmem)[] = value; \ + static type const* name = reinterpret_cast( \ + convertPtrToFlash(ARDUINOJSON_CONCAT2(name, _progmem))); diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/CMakeLists.txt new file mode 100644 index 0000000..88b5ba6 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/CMakeLists.txt @@ -0,0 +1,24 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(IntegrationTests + gbathree.cpp + issue772.cpp + round_trip.cpp + openweathermap.cpp +) + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_options(IntegrationTests + PUBLIC + -fsingle-precision-constant # issue 544 + ) +endif() + +add_test(IntegrationTests IntegrationTests) + +set_tests_properties(IntegrationTests + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/gbathree.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/gbathree.cpp new file mode 100644 index 0000000..2190eb1 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/gbathree.cpp @@ -0,0 +1,210 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("Gbathree") { + DynamicJsonDocument doc(4096); + + DeserializationError error = deserializeJson( + doc, + "{\"protocol_name\":\"fluorescence\",\"repeats\":1,\"wait\":0," + "\"averages\":1,\"measurements\":3,\"meas2_light\":15,\"meas1_" + "baseline\":0,\"act_light\":20,\"pulsesize\":25,\"pulsedistance\":" + "10000,\"actintensity1\":50,\"actintensity2\":255,\"measintensity\":" + "255,\"calintensity\":255,\"pulses\":[50,50,50],\"act\":[2,1,2,2]," + "\"red\":[2,2,2,2],\"detectors\":[[34,34,34,34],[34,34,34,34],[34," + "34,34,34],[34,34,34,34]],\"alta\":[2,2,2,2],\"altb\":[2,2,2,2]," + "\"measlights\":[[15,15,15,15],[15,15,15,15],[15,15,15,15],[15,15," + "15,15]],\"measlights2\":[[15,15,15,15],[15,15,15,15],[15,15,15,15]," + "[15,15,15,15]],\"altc\":[2,2,2,2],\"altd\":[2,2,2,2]}"); + JsonObject root = doc.as(); + + SECTION("Success") { + REQUIRE(error == DeserializationError::Ok); + } + + SECTION("ProtocolName") { + REQUIRE("fluorescence" == root["protocol_name"]); + } + + SECTION("Repeats") { + REQUIRE(1 == root["repeats"]); + } + + SECTION("Wait") { + REQUIRE(0 == root["wait"]); + } + + SECTION("Measurements") { + REQUIRE(3 == root["measurements"]); + } + + SECTION("Meas2_Light") { + REQUIRE(15 == root["meas2_light"]); + } + + SECTION("Meas1_Baseline") { + REQUIRE(0 == root["meas1_baseline"]); + } + + SECTION("Act_Light") { + REQUIRE(20 == root["act_light"]); + } + + SECTION("Pulsesize") { + REQUIRE(25 == root["pulsesize"]); + } + + SECTION("Pulsedistance") { + REQUIRE(10000 == root["pulsedistance"]); + } + + SECTION("Actintensity1") { + REQUIRE(50 == root["actintensity1"]); + } + + SECTION("Actintensity2") { + REQUIRE(255 == root["actintensity2"]); + } + + SECTION("Measintensity") { + REQUIRE(255 == root["measintensity"]); + } + + SECTION("Calintensity") { + REQUIRE(255 == root["calintensity"]); + } + + SECTION("Pulses") { + // "pulses":[50,50,50] + + JsonArray array = root["pulses"]; + REQUIRE(array.isNull() == false); + + REQUIRE(3 == array.size()); + + for (size_t i = 0; i < 3; i++) { + REQUIRE(50 == array[i]); + } + } + + SECTION("Act") { + // "act":[2,1,2,2] + + JsonArray array = root["act"]; + REQUIRE(array.isNull() == false); + + REQUIRE(4 == array.size()); + REQUIRE(2 == array[0]); + REQUIRE(1 == array[1]); + REQUIRE(2 == array[2]); + REQUIRE(2 == array[3]); + } + + SECTION("Detectors") { + // "detectors":[[34,34,34,34],[34,34,34,34],[34,34,34,34],[34,34,34,34]] + + JsonArray array = root["detectors"]; + REQUIRE(array.isNull() == false); + REQUIRE(4 == array.size()); + + for (size_t i = 0; i < 4; i++) { + JsonArray nestedArray = array[i]; + REQUIRE(4 == nestedArray.size()); + + for (size_t j = 0; j < 4; j++) { + REQUIRE(34 == nestedArray[j]); + } + } + } + + SECTION("Alta") { + // alta:[2,2,2,2] + + JsonArray array = root["alta"]; + REQUIRE(array.isNull() == false); + + REQUIRE(4 == array.size()); + + for (size_t i = 0; i < 4; i++) { + REQUIRE(2 == array[i]); + } + } + + SECTION("Altb") { + // altb:[2,2,2,2] + + JsonArray array = root["altb"]; + REQUIRE(array.isNull() == false); + + REQUIRE(4 == array.size()); + + for (size_t i = 0; i < 4; i++) { + REQUIRE(2 == array[i]); + } + } + + SECTION("Measlights") { + // "measlights":[[15,15,15,15],[15,15,15,15],[15,15,15,15],[15,15,15,15]] + + JsonArray array = root["measlights"]; + REQUIRE(array.isNull() == false); + REQUIRE(4 == array.size()); + + for (size_t i = 0; i < 4; i++) { + JsonArray nestedArray = array[i]; + + REQUIRE(4 == nestedArray.size()); + + for (size_t j = 0; j < 4; j++) { + REQUIRE(15 == nestedArray[j]); + } + } + } + + SECTION("Measlights2") { + // "measlights2":[[15,15,15,15],[15,15,15,15],[15,15,15,15],[15,15,15,15]] + + JsonArray array = root["measlights2"]; + REQUIRE(array.isNull() == false); + REQUIRE(4 == array.size()); + + for (size_t i = 0; i < 4; i++) { + JsonArray nestedArray = array[i]; + REQUIRE(4 == nestedArray.size()); + + for (size_t j = 0; j < 4; j++) { + REQUIRE(15 == nestedArray[j]); + } + } + } + + SECTION("Altc") { + // altc:[2,2,2,2] + + JsonArray array = root["altc"]; + REQUIRE(array.isNull() == false); + + REQUIRE(4 == array.size()); + + for (size_t i = 0; i < 4; i++) { + REQUIRE(2 == array[i]); + } + } + + SECTION("Altd") { + // altd:[2,2,2,2] + + JsonArray array = root["altd"]; + REQUIRE(array.isNull() == false); + + REQUIRE(4 == array.size()); + + for (size_t i = 0; i < 4; i++) { + REQUIRE(2 == array[i]); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/issue772.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/issue772.cpp new file mode 100644 index 0000000..4984b43 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/issue772.cpp @@ -0,0 +1,28 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +// https://github.com/bblanchon/ArduinoJson/issues/772 + +TEST_CASE("Issue772") { + DynamicJsonDocument doc1(4096); + DynamicJsonDocument doc2(4096); + DeserializationError err; + std::string data = + "{\"state\":{\"reported\":{\"timestamp\":\"2018-07-02T09:40:12Z\"," + "\"mac\":\"2C3AE84FC076\",\"firmwareVersion\":\"v0.2.7-5-gf4d4d78\"," + "\"visibleLight\":261,\"infraRed\":255,\"ultraViolet\":0.02," + "\"Temperature\":26.63,\"Pressure\":101145.7,\"Humidity\":54.79883," + "\"Vbat\":4.171261,\"soilMoisture\":0,\"ActB\":0}}}"; + err = deserializeJson(doc1, data); + REQUIRE(err == DeserializationError::Ok); + + data = ""; + serializeMsgPack(doc1, data); + err = deserializeMsgPack(doc2, data); + + REQUIRE(err == DeserializationError::Ok); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/openweathermap.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/openweathermap.cpp new file mode 100644 index 0000000..44e2f76 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/openweathermap.cpp @@ -0,0 +1,68 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("OpenWeatherMap") { + // clang-format off + const char* input_json = "{\"cod\":\"200\",\"message\":0,\"cnt\":40,\"list\":[{\"dt\":1581498000,\"main\":{\"temp\":3.23,\"feels_like\":-3.63,\"temp_min\":3.23,\"temp_max\":4.62,\"pressure\":1014,\"sea_level\":1014,\"grnd_level\":1010,\"humidity\":58,\"temp_kf\":-1.39},\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":0},\"wind\":{\"speed\":6.19,\"deg\":266},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 09:00:00\"},{\"dt\":1581508800,\"main\":{\"temp\":6.09,\"feels_like\":-1.07,\"temp_min\":6.09,\"temp_max\":7.13,\"pressure\":1015,\"sea_level\":1015,\"grnd_level\":1011,\"humidity\":48,\"temp_kf\":-1.04},\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":9},\"wind\":{\"speed\":6.64,\"deg\":268},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 12:00:00\"},{\"dt\":1581519600,\"main\":{\"temp\":6.82,\"feels_like\":0.47,\"temp_min\":6.82,\"temp_max\":7.52,\"pressure\":1015,\"sea_level\":1015,\"grnd_level\":1011,\"humidity\":47,\"temp_kf\":-0.7},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],\"clouds\":{\"all\":97},\"wind\":{\"speed\":5.55,\"deg\":267},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 15:00:00\"},{\"dt\":1581530400,\"main\":{\"temp\":5.76,\"feels_like\":1.84,\"temp_min\":5.76,\"temp_max\":6.11,\"pressure\":1015,\"sea_level\":1015,\"grnd_level\":1010,\"humidity\":57,\"temp_kf\":-0.35},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04n\"}],\"clouds\":{\"all\":99},\"wind\":{\"speed\":2.35,\"deg\":232},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-12 18:00:00\"},{\"dt\":1581541200,\"main\":{\"temp\":5.7,\"feels_like\":1.34,\"temp_min\":5.7,\"temp_max\":5.7,\"pressure\":1012,\"sea_level\":1012,\"grnd_level\":1008,\"humidity\":71,\"temp_kf\":0},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":3.57,\"deg\":198},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-12 21:00:00\"},{\"dt\":1581552000,\"main\":{\"temp\":5.82,\"feels_like\":1.39,\"temp_min\":5.82,\"temp_max\":5.82,\"pressure\":1009,\"sea_level\":1009,\"grnd_level\":1004,\"humidity\":86,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":4.35,\"deg\":169},\"rain\":{\"3h\":0.5},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-13 00:00:00\"},{\"dt\":1581562800,\"main\":{\"temp\":5.9,\"feels_like\":-0.85,\"temp_min\":5.9,\"temp_max\":5.9,\"pressure\":1000,\"sea_level\":1000,\"grnd_level\":997,\"humidity\":86,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":7.69,\"deg\":178},\"rain\":{\"3h\":1.75},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-13 03:00:00\"},{\"dt\":1581573600,\"main\":{\"temp\":7.52,\"feels_like\":1.74,\"temp_min\":7.52,\"temp_max\":7.52,\"pressure\":993,\"sea_level\":993,\"grnd_level\":988,\"humidity\":88,\"temp_kf\":0},\"weather\":[{\"id\":501,\"main\":\"Rain\",\"description\":\"moderate rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":6.84,\"deg\":184},\"rain\":{\"3h\":7.06},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-13 06:00:00\"},{\"dt\":1581584400,\"main\":{\"temp\":7.23,\"feels_like\":0.81,\"temp_min\":7.23,\"temp_max\":7.23,\"pressure\":992,\"sea_level\":992,\"grnd_level\":988,\"humidity\":69,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":49},\"wind\":{\"speed\":6.77,\"deg\":239},\"rain\":{\"3h\":0.25},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-13 09:00:00\"},{\"dt\":1581595200,\"main\":{\"temp\":7.67,\"feels_like\":2.81,\"temp_min\":7.67,\"temp_max\":7.67,\"pressure\":991,\"sea_level\":991,\"grnd_level\":987,\"humidity\":75,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":73},\"wind\":{\"speed\":4.93,\"deg\":235},\"rain\":{\"3h\":0.75},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-13 12:00:00\"},{\"dt\":1581606000,\"main\":{\"temp\":8.83,\"feels_like\":3.23,\"temp_min\":8.83,\"temp_max\":8.83,\"pressure\":993,\"sea_level\":993,\"grnd_level\":990,\"humidity\":64,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":83},\"wind\":{\"speed\":5.7,\"deg\":293},\"rain\":{\"3h\":0.38},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-13 15:00:00\"},{\"dt\":1581616800,\"main\":{\"temp\":7.42,\"feels_like\":1.77,\"temp_min\":7.42,\"temp_max\":7.42,\"pressure\":1000,\"sea_level\":1000,\"grnd_level\":996,\"humidity\":71,\"temp_kf\":0},\"weather\":[{\"id\":803,\"main\":\"Clouds\",\"description\":\"broken clouds\",\"icon\":\"04n\"}],\"clouds\":{\"all\":54},\"wind\":{\"speed\":5.81,\"deg\":307},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-13 18:00:00\"},{\"dt\":1581627600,\"main\":{\"temp\":5.82,\"feels_like\":0.89,\"temp_min\":5.82,\"temp_max\":5.82,\"pressure\":1007,\"sea_level\":1007,\"grnd_level\":1003,\"humidity\":79,\"temp_kf\":0},\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear sky\",\"icon\":\"01n\"}],\"clouds\":{\"all\":6},\"wind\":{\"speed\":4.76,\"deg\":300},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-13 21:00:00\"},{\"dt\":1581638400,\"main\":{\"temp\":5.58,\"feels_like\":2.09,\"temp_min\":5.58,\"temp_max\":5.58,\"pressure\":1011,\"sea_level\":1011,\"grnd_level\":1007,\"humidity\":81,\"temp_kf\":0},\"weather\":[{\"id\":802,\"main\":\"Clouds\",\"description\":\"scattered clouds\",\"icon\":\"03n\"}],\"clouds\":{\"all\":47},\"wind\":{\"speed\":2.73,\"deg\":326},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-14 00:00:00\"},{\"dt\":1581649200,\"main\":{\"temp\":4.27,\"feels_like\":1.72,\"temp_min\":4.27,\"temp_max\":4.27,\"pressure\":1014,\"sea_level\":1014,\"grnd_level\":1010,\"humidity\":85,\"temp_kf\":0},\"weather\":[{\"id\":803,\"main\":\"Clouds\",\"description\":\"broken clouds\",\"icon\":\"04n\"}],\"clouds\":{\"all\":69},\"wind\":{\"speed\":1.24,\"deg\":295},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-14 03:00:00\"},{\"dt\":1581660000,\"main\":{\"temp\":3.91,\"feels_like\":1.54,\"temp_min\":3.91,\"temp_max\":3.91,\"pressure\":1016,\"sea_level\":1016,\"grnd_level\":1012,\"humidity\":87,\"temp_kf\":0},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04n\"}],\"clouds\":{\"all\":85},\"wind\":{\"speed\":0.98,\"deg\":211},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-14 06:00:00\"},{\"dt\":1581670800,\"main\":{\"temp\":4.77,\"feels_like\":0.74,\"temp_min\":4.77,\"temp_max\":4.77,\"pressure\":1017,\"sea_level\":1017,\"grnd_level\":1013,\"humidity\":78,\"temp_kf\":0},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":3.19,\"deg\":184},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-14 09:00:00\"},{\"dt\":1581681600,\"main\":{\"temp\":9.03,\"feels_like\":4,\"temp_min\":9.03,\"temp_max\":9.03,\"pressure\":1016,\"sea_level\":1016,\"grnd_level\":1012,\"humidity\":73,\"temp_kf\":0},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":5.43,\"deg\":206},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-14 12:00:00\"},{\"dt\":1581692400,\"main\":{\"temp\":9.86,\"feels_like\":4.22,\"temp_min\":9.86,\"temp_max\":9.86,\"pressure\":1014,\"sea_level\":1014,\"grnd_level\":1010,\"humidity\":74,\"temp_kf\":0},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":6.58,\"deg\":209},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-14 15:00:00\"},{\"dt\":1581703200,\"main\":{\"temp\":9.48,\"feels_like\":4.8,\"temp_min\":9.48,\"temp_max\":9.48,\"pressure\":1013,\"sea_level\":1013,\"grnd_level\":1009,\"humidity\":83,\"temp_kf\":0},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":5.6,\"deg\":206},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-14 18:00:00\"},{\"dt\":1581714000,\"main\":{\"temp\":10.03,\"feels_like\":6.48,\"temp_min\":10.03,\"temp_max\":10.03,\"pressure\":1013,\"sea_level\":1013,\"grnd_level\":1009,\"humidity\":93,\"temp_kf\":0},\"weather\":[{\"id\":501,\"main\":\"Rain\",\"description\":\"moderate rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":4.75,\"deg\":226},\"rain\":{\"3h\":3.13},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-14 21:00:00\"},{\"dt\":1581724800,\"main\":{\"temp\":9.48,\"feels_like\":6.25,\"temp_min\":9.48,\"temp_max\":9.48,\"pressure\":1013,\"sea_level\":1013,\"grnd_level\":1009,\"humidity\":89,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":3.87,\"deg\":214},\"rain\":{\"3h\":2.38},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-15 00:00:00\"},{\"dt\":1581735600,\"main\":{\"temp\":9.12,\"feels_like\":7.08,\"temp_min\":9.12,\"temp_max\":9.12,\"pressure\":1011,\"sea_level\":1011,\"grnd_level\":1007,\"humidity\":96,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":2.43,\"deg\":194},\"rain\":{\"3h\":1},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-15 03:00:00\"},{\"dt\":1581746400,\"main\":{\"temp\":10.32,\"feels_like\":6.71,\"temp_min\":10.32,\"temp_max\":10.32,\"pressure\":1009,\"sea_level\":1009,\"grnd_level\":1004,\"humidity\":95,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":5.05,\"deg\":196},\"rain\":{\"3h\":1.75},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-15 06:00:00\"},{\"dt\":1581757200,\"main\":{\"temp\":11.57,\"feels_like\":5.85,\"temp_min\":11.57,\"temp_max\":11.57,\"pressure\":1006,\"sea_level\":1006,\"grnd_level\":1002,\"humidity\":85,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":7.91,\"deg\":205},\"rain\":{\"3h\":1.44},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-15 09:00:00\"},{\"dt\":1581768000,\"main\":{\"temp\":12.25,\"feels_like\":4.46,\"temp_min\":12.25,\"temp_max\":12.25,\"pressure\":1003,\"sea_level\":1003,\"grnd_level\":998,\"humidity\":78,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":10.65,\"deg\":201},\"rain\":{\"3h\":1.81},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-15 12:00:00\"},{\"dt\":1581778800,\"main\":{\"temp\":12.19,\"feels_like\":3.17,\"temp_min\":12.19,\"temp_max\":12.19,\"pressure\":998,\"sea_level\":998,\"grnd_level\":994,\"humidity\":80,\"temp_kf\":0},\"weather\":[{\"id\":501,\"main\":\"Rain\",\"description\":\"moderate rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":12.52,\"deg\":204},\"rain\":{\"3h\":3.5},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-15 15:00:00\"},{\"dt\":1581789600,\"main\":{\"temp\":12.25,\"feels_like\":4.15,\"temp_min\":12.25,\"temp_max\":12.25,\"pressure\":996,\"sea_level\":996,\"grnd_level\":992,\"humidity\":83,\"temp_kf\":0},\"weather\":[{\"id\":501,\"main\":\"Rain\",\"description\":\"moderate rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":11.42,\"deg\":215},\"rain\":{\"3h\":4.88},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-15 18:00:00\"},{\"dt\":1581800400,\"main\":{\"temp\":12.64,\"feels_like\":5.85,\"temp_min\":12.64,\"temp_max\":12.64,\"pressure\":994,\"sea_level\":994,\"grnd_level\":990,\"humidity\":76,\"temp_kf\":0},\"weather\":[{\"id\":501,\"main\":\"Rain\",\"description\":\"moderate rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":9.22,\"deg\":217},\"rain\":{\"3h\":6.88},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-15 21:00:00\"},{\"dt\":1581811200,\"main\":{\"temp\":12.96,\"feels_like\":4.03,\"temp_min\":12.96,\"temp_max\":12.96,\"pressure\":988,\"sea_level\":988,\"grnd_level\":984,\"humidity\":83,\"temp_kf\":0},\"weather\":[{\"id\":501,\"main\":\"Rain\",\"description\":\"moderate rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":12.88,\"deg\":211},\"rain\":{\"3h\":5.63},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-16 00:00:00\"},{\"dt\":1581822000,\"main\":{\"temp\":13.13,\"feels_like\":5.17,\"temp_min\":13.13,\"temp_max\":13.13,\"pressure\":987,\"sea_level\":987,\"grnd_level\":982,\"humidity\":82,\"temp_kf\":0},\"weather\":[{\"id\":501,\"main\":\"Rain\",\"description\":\"moderate rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":11.49,\"deg\":246},\"rain\":{\"3h\":7.25},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-16 03:00:00\"},{\"dt\":1581832800,\"main\":{\"temp\":9.07,\"feels_like\":0.79,\"temp_min\":9.07,\"temp_max\":9.07,\"pressure\":990,\"sea_level\":990,\"grnd_level\":986,\"humidity\":75,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":10.18,\"deg\":255},\"rain\":{\"3h\":2},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-16 06:00:00\"},{\"dt\":1581843600,\"main\":{\"temp\":8.05,\"feels_like\":-0.9,\"temp_min\":8.05,\"temp_max\":8.05,\"pressure\":994,\"sea_level\":994,\"grnd_level\":990,\"humidity\":51,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":100},\"wind\":{\"speed\":9.65,\"deg\":245},\"rain\":{\"3h\":1.19},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-16 09:00:00\"},{\"dt\":1581854400,\"main\":{\"temp\":9.54,\"feels_like\":0.13,\"temp_min\":9.54,\"temp_max\":9.54,\"pressure\":996,\"sea_level\":996,\"grnd_level\":991,\"humidity\":41,\"temp_kf\":0},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],\"clouds\":{\"all\":94},\"wind\":{\"speed\":10.03,\"deg\":243},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-16 12:00:00\"},{\"dt\":1581865200,\"main\":{\"temp\":9.08,\"feels_like\":-0.35,\"temp_min\":9.08,\"temp_max\":9.08,\"pressure\":996,\"sea_level\":996,\"grnd_level\":991,\"humidity\":44,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":89},\"wind\":{\"speed\":10.15,\"deg\":246},\"rain\":{\"3h\":0.25},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-16 15:00:00\"},{\"dt\":1581876000,\"main\":{\"temp\":7.41,\"feels_like\":-1.34,\"temp_min\":7.41,\"temp_max\":7.41,\"pressure\":996,\"sea_level\":996,\"grnd_level\":992,\"humidity\":50,\"temp_kf\":0},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04n\"}],\"clouds\":{\"all\":94},\"wind\":{\"speed\":9.21,\"deg\":240},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-16 18:00:00\"},{\"dt\":1581886800,\"main\":{\"temp\":6.42,\"feels_like\":-1.7,\"temp_min\":6.42,\"temp_max\":6.42,\"pressure\":997,\"sea_level\":997,\"grnd_level\":993,\"humidity\":58,\"temp_kf\":0},\"weather\":[{\"id\":803,\"main\":\"Clouds\",\"description\":\"broken clouds\",\"icon\":\"04n\"}],\"clouds\":{\"all\":67},\"wind\":{\"speed\":8.52,\"deg\":236},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-16 21:00:00\"},{\"dt\":1581897600,\"main\":{\"temp\":6.03,\"feels_like\":-2.65,\"temp_min\":6.03,\"temp_max\":6.03,\"pressure\":996,\"sea_level\":996,\"grnd_level\":993,\"humidity\":51,\"temp_kf\":0},\"weather\":[{\"id\":802,\"main\":\"Clouds\",\"description\":\"scattered clouds\",\"icon\":\"03n\"}],\"clouds\":{\"all\":38},\"wind\":{\"speed\":8.94,\"deg\":240},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-17 00:00:00\"},{\"dt\":1581908400,\"main\":{\"temp\":5.62,\"feels_like\":-2.86,\"temp_min\":5.62,\"temp_max\":5.62,\"pressure\":995,\"sea_level\":995,\"grnd_level\":991,\"humidity\":53,\"temp_kf\":0},\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear sky\",\"icon\":\"01n\"}],\"clouds\":{\"all\":0},\"wind\":{\"speed\":8.67,\"deg\":241},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-17 03:00:00\"},{\"dt\":1581919200,\"main\":{\"temp\":5.51,\"feels_like\":-2.41,\"temp_min\":5.51,\"temp_max\":5.51,\"pressure\":995,\"sea_level\":995,\"grnd_level\":991,\"humidity\":61,\"temp_kf\":0},\"weather\":[{\"id\":802,\"main\":\"Clouds\",\"description\":\"scattered clouds\",\"icon\":\"03n\"}],\"clouds\":{\"all\":35},\"wind\":{\"speed\":8.2,\"deg\":244},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2020-02-17 06:00:00\"}],\"city\":{\"id\":2643743,\"name\":\"London\",\"coord\":{\"lat\":51.5085,\"lon\":-0.1257},\"country\":\"GB\",\"population\":1000000,\"timezone\":0,\"sunrise\":1581492085,\"sunset\":1581527294}}"; + + const char* expected_json = "{\"list\":[" + "{\"dt\":1581498000,\"main\":{\"temp\":3.23},\"weather\":[{\"description\":\"clear sky\"}]}," + "{\"dt\":1581508800,\"main\":{\"temp\":6.09},\"weather\":[{\"description\":\"clear sky\"}]}," + "{\"dt\":1581519600,\"main\":{\"temp\":6.82},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581530400,\"main\":{\"temp\":5.76},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581541200,\"main\":{\"temp\":5.7},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581552000,\"main\":{\"temp\":5.82},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581562800,\"main\":{\"temp\":5.9},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581573600,\"main\":{\"temp\":7.52},\"weather\":[{\"description\":\"moderate rain\"}]}," + "{\"dt\":1581584400,\"main\":{\"temp\":7.23},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581595200,\"main\":{\"temp\":7.67},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581606000,\"main\":{\"temp\":8.83},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581616800,\"main\":{\"temp\":7.42},\"weather\":[{\"description\":\"broken clouds\"}]}," + "{\"dt\":1581627600,\"main\":{\"temp\":5.82},\"weather\":[{\"description\":\"clear sky\"}]}," + "{\"dt\":1581638400,\"main\":{\"temp\":5.58},\"weather\":[{\"description\":\"scattered clouds\"}]}," + "{\"dt\":1581649200,\"main\":{\"temp\":4.27},\"weather\":[{\"description\":\"broken clouds\"}]}," + "{\"dt\":1581660000,\"main\":{\"temp\":3.91},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581670800,\"main\":{\"temp\":4.77},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581681600,\"main\":{\"temp\":9.03},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581692400,\"main\":{\"temp\":9.86},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581703200,\"main\":{\"temp\":9.48},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581714000,\"main\":{\"temp\":10.03},\"weather\":[{\"description\":\"moderate rain\"}]}," + "{\"dt\":1581724800,\"main\":{\"temp\":9.48},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581735600,\"main\":{\"temp\":9.12},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581746400,\"main\":{\"temp\":10.32},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581757200,\"main\":{\"temp\":11.57},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581768000,\"main\":{\"temp\":12.25},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581778800,\"main\":{\"temp\":12.19},\"weather\":[{\"description\":\"moderate rain\"}]}," + "{\"dt\":1581789600,\"main\":{\"temp\":12.25},\"weather\":[{\"description\":\"moderate rain\"}]}," + "{\"dt\":1581800400,\"main\":{\"temp\":12.64},\"weather\":[{\"description\":\"moderate rain\"}]}," + "{\"dt\":1581811200,\"main\":{\"temp\":12.96},\"weather\":[{\"description\":\"moderate rain\"}]}," + "{\"dt\":1581822000,\"main\":{\"temp\":13.13},\"weather\":[{\"description\":\"moderate rain\"}]}," + "{\"dt\":1581832800,\"main\":{\"temp\":9.07},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581843600,\"main\":{\"temp\":8.05},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581854400,\"main\":{\"temp\":9.54},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581865200,\"main\":{\"temp\":9.08},\"weather\":[{\"description\":\"light rain\"}]}," + "{\"dt\":1581876000,\"main\":{\"temp\":7.41},\"weather\":[{\"description\":\"overcast clouds\"}]}," + "{\"dt\":1581886800,\"main\":{\"temp\":6.42},\"weather\":[{\"description\":\"broken clouds\"}]}," + "{\"dt\":1581897600,\"main\":{\"temp\":6.03},\"weather\":[{\"description\":\"scattered clouds\"}]}," + "{\"dt\":1581908400,\"main\":{\"temp\":5.62},\"weather\":[{\"description\":\"clear sky\"}]}," + "{\"dt\":1581919200,\"main\":{\"temp\":5.51},\"weather\":[{\"description\":\"scattered clouds\"}]}" + "]}"; + // clang-format on + + StaticJsonDocument<512> filter; + filter["list"][0]["dt"] = true; + filter["list"][0]["main"]["temp"] = true; + filter["list"][0]["weather"][0]["description"] = true; + + DynamicJsonDocument doc(16384); + + REQUIRE( + deserializeJson(doc, input_json, DeserializationOption::Filter(filter)) == + DeserializationError::Ok); + + REQUIRE(doc.as() == expected_json); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/round_trip.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/round_trip.cpp new file mode 100644 index 0000000..edc5538 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/IntegrationTests/round_trip.cpp @@ -0,0 +1,82 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +void check(std::string originalJson) { + DynamicJsonDocument doc(16384); + + std::string prettyJson; + deserializeJson(doc, originalJson); + serializeJsonPretty(doc, prettyJson); + + std::string finalJson; + deserializeJson(doc, originalJson); + serializeJson(doc, finalJson); + + REQUIRE(originalJson == finalJson); +} + +TEST_CASE("Round Trip: parse -> prettyPrint -> parse -> print") { + SECTION("OpenWeatherMap") { + check( + "{\"coord\":{\"lon\":145.77,\"lat\":-16.92},\"sys\":{\"type\":1,\"id\":" + "8166,\"message\":0.1222,\"country\":\"AU\",\"sunrise\":1414784325," + "\"sunset\":1414830137},\"weather\":[{\"id\":801,\"main\":\"Clouds\"," + "\"description\":\"few clouds\",\"icon\":\"02n\"}],\"base\":\"cmc " + "stations\",\"main\":{\"temp\":296.15,\"pressure\":1014,\"humidity\":" + "83,\"temp_min\":296.15,\"temp_max\":296.15},\"wind\":{\"speed\":2.22," + "\"deg\":114.501},\"clouds\":{\"all\":20},\"dt\":1414846800,\"id\":" + "2172797,\"name\":\"Cairns\",\"cod\":200}"); + } + + SECTION("YahooQueryLanguage") { + check( + "{\"query\":{\"count\":40,\"created\":\"2014-11-01T14:16:49Z\"," + "\"lang\":\"fr-FR\",\"results\":{\"item\":[{\"title\":\"Burkina army " + "backs Zida as interim leader\"},{\"title\":\"British jets intercept " + "Russian bombers\"},{\"title\":\"Doubts chip away at nation's most " + "trusted agencies\"},{\"title\":\"Cruise ship stuck off Norway, no " + "damage\"},{\"title\":\"U.S. military launches 10 air strikes in " + "Syria, Iraq\"},{\"title\":\"Blackout hits Bangladesh as line from " + "India fails\"},{\"title\":\"Burkina Faso president in Ivory Coast " + "after ouster\"},{\"title\":\"Kurds in Turkey rally to back city " + "besieged by IS\"},{\"title\":\"A majority of Scots would vote for " + "independence now:poll\"},{\"title\":\"Tunisia elections possible " + "model for region\"},{\"title\":\"Islamic State kills 85 more members " + "of Iraqi tribe\"},{\"title\":\"Iraqi officials:IS extremists line " + "up, kill 50\"},{\"title\":\"Burkina Faso army backs presidential " + "guard official to lead transition\"},{\"title\":\"Kurdish peshmerga " + "arrive with weapons in Syria's Kobani\"},{\"title\":\"Driver sought " + "in crash that killed 3 on Halloween\"},{\"title\":\"Ex-Marine arrives " + "in US after release from Mexico jail\"},{\"title\":\"UN panel " + "scrambling to finish climate report\"},{\"title\":\"Investigators, " + "Branson go to spacecraft crash site\"},{\"title\":\"Soldiers vie for " + "power after Burkina Faso president quits\"},{\"title\":\"For a man " + "without a party, turnout is big test\"},{\"title\":\"'We just had a " + "hunch':US marshals nab Eric Frein\"},{\"title\":\"Boko Haram leader " + "threatens to kill German hostage\"},{\"title\":\"Nurse free to move " + "about as restrictions eased\"},{\"title\":\"Former Burkina president " + "Compaore arrives in Ivory Coast:sources\"},{\"title\":\"Libyan port " + "rebel leader refuses to hand over oil ports to rival " + "group\"},{\"title\":\"Iraqi peshmerga fighters prepare for Syria " + "battle\"},{\"title\":\"1 Dem Senate candidate welcoming Obama's " + "help\"},{\"title\":\"Bikers cancel party after police recover " + "bar\"},{\"title\":\"New question in Texas:Can Davis survive " + "defeat?\"},{\"title\":\"Ukraine rebels to hold election, despite " + "criticism\"},{\"title\":\"Iraqi officials say Islamic State group " + "lines up, kills 50 tribesmen, women in Anbar " + "province\"},{\"title\":\"James rebounds, leads Cavaliers past " + "Bulls\"},{\"title\":\"UK warns travelers they could be terror " + "targets\"},{\"title\":\"Hello Kitty celebrates 40th " + "birthday\"},{\"title\":\"A look at people killed during space " + "missions\"},{\"title\":\"Nigeria's purported Boko Haram leader says " + "has 'married off' girls:AFP\"},{\"title\":\"Mexico orders immediate " + "release of Marine veteran\"},{\"title\":\"As election closes in, " + "Obama on center stage\"},{\"title\":\"Body of Zambian president " + "arrives home\"},{\"title\":\"South Africa arrests 2 Vietnamese for " + "poaching\"}]}}}"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/CMakeLists.txt new file mode 100644 index 0000000..e679718 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/CMakeLists.txt @@ -0,0 +1,28 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(JsonArrayTests + add.cpp + clear.cpp + copyArray.cpp + createNested.cpp + equals.cpp + get.cpp + isNull.cpp + iterator.cpp + memoryUsage.cpp + nesting.cpp + remove.cpp + size.cpp + std_string.cpp + subscript.cpp + unbound.cpp +) + +add_test(JsonArray JsonArrayTests) + +set_tests_properties(JsonArray + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/add.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/add.cpp new file mode 100644 index 0000000..369bcf2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/add.cpp @@ -0,0 +1,138 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray::add()") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + + SECTION("int") { + array.add(123); + REQUIRE(123 == array[0].as()); + REQUIRE(array[0].is()); + REQUIRE(array[0].is()); + } + + SECTION("double") { + array.add(123.45); + REQUIRE(123.45 == array[0].as()); + REQUIRE(array[0].is()); + REQUIRE_FALSE(array[0].is()); + } + + SECTION("bool") { + array.add(true); + REQUIRE(true == array[0].as()); + REQUIRE(array[0].is()); + REQUIRE_FALSE(array[0].is()); + } + + SECTION("const char*") { + const char* str = "hello"; + array.add(str); + REQUIRE(str == array[0].as()); + REQUIRE(array[0].is()); + REQUIRE_FALSE(array[0].is()); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("vla") { + size_t i = 16; + char vla[i]; + strcpy(vla, "world"); + + array.add(vla); + + REQUIRE(std::string("world") == array[0]); + } +#endif + + SECTION("nested array") { + DynamicJsonDocument doc2(4096); + JsonArray arr = doc2.to(); + + array.add(arr); + + REQUIRE(arr == array[0].as()); + REQUIRE(array[0].is()); + REQUIRE_FALSE(array[0].is()); + } + + SECTION("nested object") { + DynamicJsonDocument doc2(4096); + JsonObject obj = doc2.to(); + + array.add(obj); + + REQUIRE(obj == array[0].as()); + REQUIRE(array[0].is()); + REQUIRE_FALSE(array[0].is()); + } + + SECTION("array subscript") { + const char* str = "hello"; + DynamicJsonDocument doc2(4096); + JsonArray arr = doc2.to(); + arr.add(str); + + array.add(arr[0]); + + REQUIRE(str == array[0]); + } + + SECTION("object subscript") { + const char* str = "hello"; + DynamicJsonDocument doc2(4096); + JsonObject obj = doc2.to(); + obj["x"] = str; + + array.add(obj["x"]); + + REQUIRE(str == array[0]); + } + + SECTION("should not duplicate const char*") { + array.add("world"); + const size_t expectedSize = JSON_ARRAY_SIZE(1); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate char*") { + array.add(const_cast("world")); + const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(5); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate std::string") { + array.add(std::string("world")); + const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(5); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should not duplicate serialized(const char*)") { + array.add(serialized("{}")); + const size_t expectedSize = JSON_ARRAY_SIZE(1); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate serialized(char*)") { + array.add(serialized(const_cast("{}"))); + const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(2); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate serialized(std::string)") { + array.add(serialized(std::string("{}"))); + const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(2); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate serialized(std::string)") { + array.add(serialized(std::string("\0XX", 3))); + const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(3); + REQUIRE(expectedSize == doc.memoryUsage()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/clear.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/clear.cpp new file mode 100644 index 0000000..baeb022 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/clear.cpp @@ -0,0 +1,25 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray::clear()") { + SECTION("No-op on null JsonArray") { + JsonArray array; + array.clear(); + REQUIRE(array.isNull() == true); + REQUIRE(array.size() == 0); + } + + SECTION("Removes all elements") { + StaticJsonDocument<64> doc; + JsonArray array = doc.to(); + array.add(1); + array.add(2); + array.clear(); + REQUIRE(array.size() == 0); + REQUIRE(array.isNull() == false); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/copyArray.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/copyArray.cpp new file mode 100644 index 0000000..599821d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/copyArray.cpp @@ -0,0 +1,346 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("copyArray()") { + SECTION("int[] -> JsonArray") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + char json[32]; + int source[] = {1, 2, 3}; + + bool ok = copyArray(source, array); + CHECK(ok); + + serializeJson(array, json); + CHECK(std::string("[1,2,3]") == json); + } + + SECTION("std::string[] -> JsonArray") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + char json[32]; + std::string source[] = {"a", "b", "c"}; + + bool ok = copyArray(source, array); + CHECK(ok); + + serializeJson(array, json); + CHECK(std::string("[\"a\",\"b\",\"c\"]") == json); + } + + SECTION("const char*[] -> JsonArray") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + char json[32]; + const char* source[] = {"a", "b", "c"}; + + bool ok = copyArray(source, array); + CHECK(ok); + + serializeJson(array, json); + CHECK(std::string("[\"a\",\"b\",\"c\"]") == json); + } + + SECTION("const char[][] -> JsonArray") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + char json[32]; + char source[][2] = {"a", "b", "c"}; + + bool ok = copyArray(source, array); + CHECK(ok); + + serializeJson(array, json); + CHECK(std::string("[\"a\",\"b\",\"c\"]") == json); + } + + SECTION("const char[][] -> JsonDocument") { + DynamicJsonDocument doc(4096); + char json[32]; + char source[][2] = {"a", "b", "c"}; + + bool ok = copyArray(source, doc); + CHECK(ok); + + serializeJson(doc, json); + CHECK(std::string("[\"a\",\"b\",\"c\"]") == json); + } + + SECTION("const char[][] -> MemberProxy") { + DynamicJsonDocument doc(4096); + char json[32]; + char source[][2] = {"a", "b", "c"}; + + bool ok = copyArray(source, doc["data"]); + CHECK(ok); + + serializeJson(doc, json); + CHECK(std::string("{\"data\":[\"a\",\"b\",\"c\"]}") == json); + } + + SECTION("int[] -> JsonDocument") { + DynamicJsonDocument doc(4096); + char json[32]; + int source[] = {1, 2, 3}; + + bool ok = copyArray(source, doc); + CHECK(ok); + + serializeJson(doc, json); + CHECK(std::string("[1,2,3]") == json); + } + + SECTION("int[] -> MemberProxy") { + DynamicJsonDocument doc(4096); + char json[32]; + int source[] = {1, 2, 3}; + + bool ok = copyArray(source, doc["data"]); + CHECK(ok); + + serializeJson(doc, json); + CHECK(std::string("{\"data\":[1,2,3]}") == json); + } + + SECTION("int[] -> JsonArray, but not enough memory") { + const size_t SIZE = JSON_ARRAY_SIZE(2); + StaticJsonDocument doc; + JsonArray array = doc.to(); + char json[32]; + int source[] = {1, 2, 3}; + + bool ok = copyArray(source, array); + REQUIRE_FALSE(ok); + + serializeJson(array, json); + CHECK(std::string("[1,2]") == json); + } + + SECTION("int[][] -> JsonArray") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + char json[32]; + int source[][3] = {{1, 2, 3}, {4, 5, 6}}; + + bool ok = copyArray(source, array); + CHECK(ok); + + serializeJson(array, json); + CHECK(std::string("[[1,2,3],[4,5,6]]") == json); + } + + SECTION("int[][] -> MemberProxy") { + DynamicJsonDocument doc(4096); + char json[32]; + int source[][3] = {{1, 2, 3}, {4, 5, 6}}; + + bool ok = copyArray(source, doc["data"]); + CHECK(ok); + + serializeJson(doc, json); + CHECK(std::string("{\"data\":[[1,2,3],[4,5,6]]}") == json); + } + + SECTION("int[][] -> JsonDocument") { + DynamicJsonDocument doc(4096); + char json[32]; + int source[][3] = {{1, 2, 3}, {4, 5, 6}}; + + bool ok = copyArray(source, doc); + CHECK(ok); + + serializeJson(doc, json); + CHECK(std::string("[[1,2,3],[4,5,6]]") == json); + } + + SECTION("int[][] -> JsonArray, but not enough memory") { + const size_t SIZE = + JSON_ARRAY_SIZE(2) + JSON_ARRAY_SIZE(3) + JSON_ARRAY_SIZE(2); + StaticJsonDocument doc; + JsonArray array = doc.to(); + char json[32] = ""; + int source[][3] = {{1, 2, 3}, {4, 5, 6}}; + + CAPTURE(SIZE) + + bool ok = copyArray(source, array); + CAPTURE(doc.memoryUsage()); + CHECK_FALSE(ok); + + serializeJson(array, json); + CHECK(std::string("[[1,2,3],[4,5]]") == json); + } + + SECTION("JsonArray -> int[], with more space than needed") { + DynamicJsonDocument doc(4096); + char json[] = "[1,2,3]"; + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + JsonArray array = doc.as(); + + int destination[4] = {0}; + size_t result = copyArray(array, destination); + + CHECK(3 == result); + CHECK(1 == destination[0]); + CHECK(2 == destination[1]); + CHECK(3 == destination[2]); + CHECK(0 == destination[3]); + } + + SECTION("JsonArray -> int[], without enough space") { + DynamicJsonDocument doc(4096); + char json[] = "[1,2,3]"; + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + JsonArray array = doc.as(); + + int destination[2] = {0}; + size_t result = copyArray(array, destination); + + CHECK(2 == result); + CHECK(1 == destination[0]); + CHECK(2 == destination[1]); + } + + SECTION("JsonArray -> std::string[]") { + DynamicJsonDocument doc(4096); + char json[] = "[\"a\",\"b\",\"c\"]"; + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + JsonArray array = doc.as(); + + std::string destination[4]; + size_t result = copyArray(array, destination); + + CHECK(3 == result); + CHECK("a" == destination[0]); + CHECK("b" == destination[1]); + CHECK("c" == destination[2]); + CHECK("" == destination[3]); + } + + SECTION("JsonArray -> char[N][]") { + DynamicJsonDocument doc(4096); + char json[] = "[\"a12345\",\"b123456\",\"c1234567\"]"; + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + JsonArray array = doc.as(); + + char destination[4][8] = {{0}}; + size_t result = copyArray(array, destination); + + CHECK(3 == result); + CHECK(std::string("a12345") == destination[0]); + CHECK(std::string("b123456") == destination[1]); + CHECK(std::string("c123456") == destination[2]); // truncated + CHECK(std::string("") == destination[3]); + } + + SECTION("JsonDocument -> int[]") { + DynamicJsonDocument doc(4096); + char json[] = "[1,2,3]"; + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + + int destination[4] = {0}; + size_t result = copyArray(doc, destination); + + CHECK(3 == result); + CHECK(1 == destination[0]); + CHECK(2 == destination[1]); + CHECK(3 == destination[2]); + CHECK(0 == destination[3]); + } + + SECTION("MemberProxy -> int[]") { + DynamicJsonDocument doc(4096); + char json[] = "{\"data\":[1,2,3]}"; + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + + int destination[4] = {0}; + size_t result = copyArray(doc["data"], destination); + + CHECK(3 == result); + CHECK(1 == destination[0]); + CHECK(2 == destination[1]); + CHECK(3 == destination[2]); + CHECK(0 == destination[3]); + } + + SECTION("ElementProxy -> int[]") { + DynamicJsonDocument doc(4096); + char json[] = "[[1,2,3]]"; + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + + int destination[4] = {0}; + size_t result = copyArray(doc[0], destination); + + CHECK(3 == result); + CHECK(1 == destination[0]); + CHECK(2 == destination[1]); + CHECK(3 == destination[2]); + CHECK(0 == destination[3]); + } + + SECTION("JsonArray -> int[][]") { + DynamicJsonDocument doc(4096); + char json[] = "[[1,2],[3],[4]]"; + + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + JsonArray array = doc.as(); + + int destination[3][2] = {{0}}; + copyArray(array, destination); + + CHECK(1 == destination[0][0]); + CHECK(2 == destination[0][1]); + CHECK(3 == destination[1][0]); + CHECK(0 == destination[1][1]); + CHECK(4 == destination[2][0]); + CHECK(0 == destination[2][1]); + } + + SECTION("JsonDocument -> int[][]") { + DynamicJsonDocument doc(4096); + char json[] = "[[1,2],[3],[4]]"; + + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + + int destination[3][2] = {{0}}; + copyArray(doc, destination); + + CHECK(1 == destination[0][0]); + CHECK(2 == destination[0][1]); + CHECK(3 == destination[1][0]); + CHECK(0 == destination[1][1]); + CHECK(4 == destination[2][0]); + CHECK(0 == destination[2][1]); + } + + SECTION("MemberProxy -> int[][]") { + DynamicJsonDocument doc(4096); + char json[] = "{\"data\":[[1,2],[3],[4]]}"; + + DeserializationError err = deserializeJson(doc, json); + CHECK(err == DeserializationError::Ok); + + int destination[3][2] = {{0}}; + copyArray(doc["data"], destination); + + CHECK(1 == destination[0][0]); + CHECK(2 == destination[0][1]); + CHECK(3 == destination[1][0]); + CHECK(0 == destination[1][1]); + CHECK(4 == destination[2][0]); + CHECK(0 == destination[2][1]); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/createNested.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/createNested.cpp new file mode 100644 index 0000000..dbc4c30 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/createNested.cpp @@ -0,0 +1,21 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray basics") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + + SECTION("CreateNestedArray") { + JsonArray arr = array.createNestedArray(); + REQUIRE(arr == array[0].as()); + } + + SECTION("CreateNestedObject") { + JsonObject obj = array.createNestedObject(); + REQUIRE(obj == array[0].as()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/equals.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/equals.cpp new file mode 100644 index 0000000..f4f9b52 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/equals.cpp @@ -0,0 +1,69 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray::operator==()") { + DynamicJsonDocument doc1(4096); + JsonArray array1 = doc1.to(); + JsonArrayConst array1c = array1; + + DynamicJsonDocument doc2(4096); + JsonArray array2 = doc2.to(); + JsonArrayConst array2c = array2; + + SECTION("should return false when arrays differ") { + array1.add("coucou"); + array2.add(1); + + REQUIRE_FALSE(array1 == array2); + REQUIRE_FALSE(array1c == array2c); + } + + SECTION("should return false when LHS has more elements") { + array1.add(1); + array1.add(2); + array2.add(1); + + REQUIRE_FALSE(array1 == array2); + REQUIRE_FALSE(array1c == array2c); + } + + SECTION("should return false when RHS has more elements") { + array1.add(1); + array2.add(1); + array2.add(2); + + REQUIRE_FALSE(array1 == array2); + REQUIRE_FALSE(array1c == array2c); + } + + SECTION("should return true when arrays equal") { + array1.add("coucou"); + array2.add("coucou"); + + REQUIRE(array1 == array2); + REQUIRE(array1c == array2c); + } + + SECTION("should return false when RHS is null") { + JsonArray null; + + REQUIRE_FALSE(array1 == null); + } + + SECTION("should return false when LHS is null") { + JsonArray null; + + REQUIRE_FALSE(null == array1); + } + + SECTION("should return true when both are null") { + JsonArray null1; + JsonArray null2; + + REQUIRE(null1 == null2); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/get.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/get.cpp new file mode 100644 index 0000000..895a2f6 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/get.cpp @@ -0,0 +1,16 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray::get()") { + DynamicJsonDocument doc(4096); + deserializeJson(doc, "[1,2,3]"); + JsonArray array = doc.as(); + + SECTION("Overflow") { + REQUIRE(array.getElement(3).isNull()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/isNull.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/isNull.cpp new file mode 100644 index 0000000..ad953b9 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/isNull.cpp @@ -0,0 +1,58 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray::isNull()") { + SECTION("returns true") { + JsonArray arr; + REQUIRE(arr.isNull() == true); + } + + SECTION("returns false") { + DynamicJsonDocument doc(4096); + JsonArray arr = doc.to(); + REQUIRE(arr.isNull() == false); + } +} + +TEST_CASE("JsonArrayConst::isNull()") { + SECTION("returns true") { + JsonArrayConst arr; + REQUIRE(arr.isNull() == true); + } + + SECTION("returns false") { + DynamicJsonDocument doc(4096); + JsonArrayConst arr = doc.to(); + REQUIRE(arr.isNull() == false); + } +} + +TEST_CASE("JsonArray::operator bool()") { + SECTION("returns false") { + JsonArray arr; + REQUIRE(static_cast(arr) == false); + } + + SECTION("returns true") { + DynamicJsonDocument doc(4096); + JsonArray arr = doc.to(); + REQUIRE(static_cast(arr) == true); + } +} + +TEST_CASE("JsonArrayConst::operator bool()") { + SECTION("returns false") { + JsonArrayConst arr; + REQUIRE(static_cast(arr) == false); + } + + SECTION("returns true") { + DynamicJsonDocument doc(4096); + JsonArrayConst arr = doc.to(); + REQUIRE(static_cast(arr) == true); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/iterator.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/iterator.cpp new file mode 100644 index 0000000..435a5bb --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/iterator.cpp @@ -0,0 +1,52 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +template +static void run_iterator_test() { + StaticJsonDocument doc; + JsonArray tmp = doc.to(); + tmp.add(12); + tmp.add(34); + + TArray array = tmp; + typename TArray::iterator it = array.begin(); + typename TArray::iterator end = array.end(); + + REQUIRE(end != it); + REQUIRE(12 == it->template as()); + REQUIRE(12 == static_cast(*it)); + ++it; + REQUIRE(end != it); + REQUIRE(34 == it->template as()); + REQUIRE(34 == static_cast(*it)); + ++it; + REQUIRE(end == it); +} + +TEST_CASE("JsonArray::begin()/end()") { + SECTION("Non null JsonArray") { + run_iterator_test(); + } + + SECTION("Null JsonArray") { + JsonArray array; + + REQUIRE(array.begin() == array.end()); + } +} + +TEST_CASE("JsonArrayConst::begin()/end()") { + SECTION("Non null JsonArrayConst") { + run_iterator_test(); + } + + SECTION("Null JsonArrayConst") { + JsonArrayConst array; + + REQUIRE(array.begin() == array.end()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/memoryUsage.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/memoryUsage.cpp new file mode 100644 index 0000000..05b743f --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/memoryUsage.cpp @@ -0,0 +1,42 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray::memoryUsage()") { + DynamicJsonDocument doc(4096); + JsonArray arr = doc.to(); + + SECTION("return 0 if uninitialized") { + JsonArray unitialized; + REQUIRE(unitialized.memoryUsage() == 0); + } + + SECTION("JSON_ARRAY_SIZE(0) if empty") { + REQUIRE(arr.memoryUsage() == JSON_ARRAY_SIZE(0)); + } + + SECTION("JSON_ARRAY_SIZE(1) after add") { + arr.add("hello"); + REQUIRE(arr.memoryUsage() == JSON_ARRAY_SIZE(1)); + } + + SECTION("includes the size of the string") { + arr.add(std::string("hello")); + REQUIRE(arr.memoryUsage() == JSON_ARRAY_SIZE(1) + 6); + } + + SECTION("includes the size of the nested array") { + JsonArray nested = arr.createNestedArray(); + nested.add(42); + REQUIRE(arr.memoryUsage() == 2 * JSON_ARRAY_SIZE(1)); + } + + SECTION("includes the size of the nested arrect") { + JsonObject nested = arr.createNestedObject(); + nested["hello"] = "world"; + REQUIRE(arr.memoryUsage() == JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(1)); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/nesting.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/nesting.cpp new file mode 100644 index 0000000..2118f5f --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/nesting.cpp @@ -0,0 +1,35 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray::nesting()") { + DynamicJsonDocument doc(4096); + JsonArray arr = doc.to(); + + SECTION("return 0 if uninitialized") { + JsonArray unitialized; + REQUIRE(unitialized.nesting() == 0); + } + + SECTION("returns 1 for empty array") { + REQUIRE(arr.nesting() == 1); + } + + SECTION("returns 1 for flat array") { + arr.add("hello"); + REQUIRE(arr.nesting() == 1); + } + + SECTION("returns 2 with nested array") { + arr.createNestedArray(); + REQUIRE(arr.nesting() == 2); + } + + SECTION("returns 2 with nested object") { + arr.createNestedObject(); + REQUIRE(arr.nesting() == 2); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/remove.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/remove.cpp new file mode 100644 index 0000000..b9e86fd --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/remove.cpp @@ -0,0 +1,89 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray::remove()") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + array.add(1); + array.add(2); + array.add(3); + + SECTION("remove first by index") { + array.remove(0); + + REQUIRE(2 == array.size()); + REQUIRE(array[0] == 2); + REQUIRE(array[1] == 3); + } + + SECTION("remove middle by index") { + array.remove(1); + + REQUIRE(2 == array.size()); + REQUIRE(array[0] == 1); + REQUIRE(array[1] == 3); + } + + SECTION("remove last by index") { + array.remove(2); + + REQUIRE(2 == array.size()); + REQUIRE(array[0] == 1); + REQUIRE(array[1] == 2); + } + + SECTION("remove first by iterator") { + JsonArray::iterator it = array.begin(); + array.remove(it); + + REQUIRE(2 == array.size()); + REQUIRE(array[0] == 2); + REQUIRE(array[1] == 3); + } + + SECTION("remove middle by iterator") { + JsonArray::iterator it = array.begin(); + ++it; + array.remove(it); + + REQUIRE(2 == array.size()); + REQUIRE(array[0] == 1); + REQUIRE(array[1] == 3); + } + + SECTION("remove last bty iterator") { + JsonArray::iterator it = array.begin(); + ++it; + ++it; + array.remove(it); + + REQUIRE(2 == array.size()); + REQUIRE(array[0] == 1); + REQUIRE(array[1] == 2); + } + + SECTION("In a loop") { + for (JsonArray::iterator it = array.begin(); it != array.end(); ++it) { + if (*it == 2) + array.remove(it); + } + + REQUIRE(2 == array.size()); + REQUIRE(array[0] == 1); + REQUIRE(array[1] == 3); + } + + SECTION("remove by index on unbound reference") { + JsonArray unboundArray; + unboundArray.remove(20); + } + + SECTION("remove by iterator on unbound reference") { + JsonArray unboundArray; + unboundArray.remove(unboundArray.begin()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/size.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/size.cpp new file mode 100644 index 0000000..68b10de --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/size.cpp @@ -0,0 +1,31 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonArray::size()") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + + SECTION("returns 0 is empty") { + REQUIRE(0U == array.size()); + } + + SECTION("increases after add()") { + array.add("hello"); + REQUIRE(1U == array.size()); + + array.add("world"); + REQUIRE(2U == array.size()); + } + + SECTION("remains the same after replacing an element") { + array.add("hello"); + REQUIRE(1U == array.size()); + + array[0] = "hello"; + REQUIRE(1U == array.size()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/std_string.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/std_string.cpp new file mode 100644 index 0000000..b7de0ef --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/std_string.cpp @@ -0,0 +1,31 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +static void eraseString(std::string &str) { + char *p = const_cast(str.c_str()); + while (*p) *p++ = '*'; +} + +TEST_CASE("std::string") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + + SECTION("add()") { + std::string value("hello"); + array.add(value); + eraseString(value); + REQUIRE(std::string("hello") == array[0]); + } + + SECTION("operator[]") { + std::string value("world"); + array.add("hello"); + array[0] = value; + eraseString(value); + REQUIRE(std::string("world") == array[0]); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/subscript.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/subscript.cpp new file mode 100644 index 0000000..57bbe14 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/subscript.cpp @@ -0,0 +1,174 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +TEST_CASE("JsonArray::operator[]") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + + SECTION("Pad with null") { + array[2] = 2; + array[5] = 5; + REQUIRE(array.size() == 6); + REQUIRE(array[0].isNull() == true); + REQUIRE(array[1].isNull() == true); + REQUIRE(array[2].isNull() == false); + REQUIRE(array[3].isNull() == true); + REQUIRE(array[4].isNull() == true); + REQUIRE(array[5].isNull() == false); + REQUIRE(array[2] == 2); + REQUIRE(array[5] == 5); + } + + SECTION("int") { + array[0] = 123; + REQUIRE(123 == array[0].as()); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + } + +#if ARDUINOJSON_USE_LONG_LONG + SECTION("long long") { + array[0] = 9223372036854775807; + REQUIRE(9223372036854775807 == array[0].as()); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + REQUIRE(false == array[0].is()); + } +#endif + + SECTION("double") { + array[0] = 123.45; + REQUIRE(123.45 == array[0].as()); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + } + + SECTION("bool") { + array[0] = true; + REQUIRE(true == array[0].as()); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + } + + SECTION("const char*") { + const char* str = "hello"; + + array[0] = str; + REQUIRE(str == array[0].as()); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + } + + SECTION("nested array") { + DynamicJsonDocument doc2(4096); + JsonArray arr2 = doc2.to(); + + array[0] = arr2; + + REQUIRE(arr2 == array[0].as()); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + } + + SECTION("nested object") { + DynamicJsonDocument doc2(4096); + JsonObject obj = doc2.to(); + + array[0] = obj; + + REQUIRE(obj == array[0].as()); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + } + + SECTION("array subscript") { + DynamicJsonDocument doc2(4096); + JsonArray arr2 = doc2.to(); + const char* str = "hello"; + + arr2.add(str); + + array[0] = arr2[0]; + + REQUIRE(str == array[0]); + } + + SECTION("object subscript") { + const char* str = "hello"; + DynamicJsonDocument doc2(4096); + JsonObject obj = doc2.to(); + + obj["x"] = str; + + array[0] = obj["x"]; + + REQUIRE(str == array[0]); + } + + SECTION("should not duplicate const char*") { + array[0] = "world"; + const size_t expectedSize = JSON_ARRAY_SIZE(1); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate char*") { + array[0] = const_cast("world"); + const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(5); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate std::string") { + array[0] = std::string("world"); + const size_t expectedSize = JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(5); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("array[0].to()") { + JsonObject obj = array[0].to(); + REQUIRE(obj.isNull() == false); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("set(VLA)") { + size_t i = 16; + char vla[i]; + strcpy(vla, "world"); + + array.add("hello"); + array[0].set(vla); + + REQUIRE(std::string("world") == array[0]); + } + + SECTION("operator=(VLA)") { + size_t i = 16; + char vla[i]; + strcpy(vla, "world"); + + array.add("hello"); + array[0] = vla; + + REQUIRE(std::string("world") == array[0]); + } +#endif +} + +TEST_CASE("JsonArrayConst::operator[]") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + array.add(0); + + SECTION("int") { + array[0] = 123; + JsonArrayConst carr = array; + + REQUIRE(123 == carr[0].as()); + REQUIRE(true == carr[0].is()); + REQUIRE(false == carr[0].is()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/unbound.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/unbound.cpp new file mode 100644 index 0000000..792feec --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonArray/unbound.cpp @@ -0,0 +1,35 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace Catch::Matchers; + +TEST_CASE("Unbound JsonArray") { + JsonArray array; + + SECTION("SubscriptFails") { + REQUIRE(array[0].isNull()); + } + + SECTION("AddFails") { + array.add(1); + REQUIRE(0 == array.size()); + } + + SECTION("CreateNestedArrayFails") { + REQUIRE(array.createNestedArray().isNull()); + } + + SECTION("CreateNestedObjectFails") { + REQUIRE(array.createNestedObject().isNull()); + } + + SECTION("PrintToWritesBrackets") { + char buffer[32]; + serializeJson(array, buffer, sizeof(buffer)); + REQUIRE_THAT(buffer, Equals("null")); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/CMakeLists.txt new file mode 100644 index 0000000..429c375 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/CMakeLists.txt @@ -0,0 +1,28 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(JsonDeserializerTests + array.cpp + array_static.cpp + DeserializationError.cpp + filter.cpp + incomplete_input.cpp + input_types.cpp + invalid_input.cpp + misc.cpp + nestingLimit.cpp + number.cpp + object.cpp + object_static.cpp + string.cpp +) + +set_target_properties(JsonDeserializerTests PROPERTIES UNITY_BUILD OFF) + +add_test(JsonDeserializer JsonDeserializerTests) + +set_tests_properties(JsonDeserializer + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/DeserializationError.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/DeserializationError.cpp new file mode 100644 index 0000000..01ddaf6 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/DeserializationError.cpp @@ -0,0 +1,120 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +void testStringification(DeserializationError error, std::string expected) { + REQUIRE(error.c_str() == expected); +} + +void testBoolification(DeserializationError error, bool expected) { + // DeserializationError on left-hand side + CHECK(bool(error) == expected); + CHECK(bool(error) != !expected); + CHECK(!bool(error) == !expected); + + // DeserializationError on right-hand side + CHECK(expected == bool(error)); + CHECK(!expected != bool(error)); + CHECK(!expected == !bool(error)); +} + +#define TEST_STRINGIFICATION(symbol) \ + testStringification(DeserializationError::symbol, #symbol) + +#define TEST_BOOLIFICATION(symbol, expected) \ + testBoolification(DeserializationError::symbol, expected) + +TEST_CASE("DeserializationError") { + SECTION("c_str()") { + TEST_STRINGIFICATION(Ok); + TEST_STRINGIFICATION(EmptyInput); + TEST_STRINGIFICATION(IncompleteInput); + TEST_STRINGIFICATION(InvalidInput); + TEST_STRINGIFICATION(NoMemory); + TEST_STRINGIFICATION(TooDeep); + } + + SECTION("as boolean") { + TEST_BOOLIFICATION(Ok, false); + TEST_BOOLIFICATION(EmptyInput, true); + TEST_BOOLIFICATION(IncompleteInput, true); + TEST_BOOLIFICATION(InvalidInput, true); + TEST_BOOLIFICATION(NoMemory, true); + TEST_BOOLIFICATION(TooDeep, true); + } + + SECTION("ostream DeserializationError") { + std::stringstream s; + s << DeserializationError(DeserializationError::InvalidInput); + REQUIRE(s.str() == "InvalidInput"); + } + + SECTION("ostream DeserializationError::Code") { + std::stringstream s; + s << DeserializationError::InvalidInput; + REQUIRE(s.str() == "InvalidInput"); + } + + SECTION("switch") { + DeserializationError err = DeserializationError::InvalidInput; + switch (err.code()) { + case DeserializationError::InvalidInput: + SUCCEED(); + break; + default: + FAIL(); + break; + } + } + + SECTION("Use in a condition") { + DeserializationError invalidInput(DeserializationError::InvalidInput); + DeserializationError ok(DeserializationError::Ok); + + SECTION("if (!err)") { + if (!invalidInput) + FAIL(); + } + + SECTION("if (err)") { + if (ok) + FAIL(); + } + } + + SECTION("Comparisons") { + DeserializationError invalidInput(DeserializationError::InvalidInput); + DeserializationError ok(DeserializationError::Ok); + + SECTION("DeserializationError == Code") { + REQUIRE(invalidInput == DeserializationError::InvalidInput); + REQUIRE(ok == DeserializationError::Ok); + } + + SECTION("Code == DeserializationError") { + REQUIRE(DeserializationError::InvalidInput == invalidInput); + REQUIRE(DeserializationError::Ok == ok); + } + + SECTION("DeserializationError != Code") { + REQUIRE(invalidInput != DeserializationError::Ok); + REQUIRE(ok != DeserializationError::InvalidInput); + } + + SECTION("Code != DeserializationError") { + REQUIRE(DeserializationError::Ok != invalidInput); + REQUIRE(DeserializationError::InvalidInput != ok); + } + + SECTION("DeserializationError == DeserializationError") { + REQUIRE_FALSE(invalidInput == ok); + } + + SECTION("DeserializationError != DeserializationError") { + REQUIRE(invalidInput != ok); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/array.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/array.cpp new file mode 100644 index 0000000..2e0038e --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/array.cpp @@ -0,0 +1,253 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("deserialize JSON array") { + DynamicJsonDocument doc(4096); + + SECTION("An empty array") { + DeserializationError err = deserializeJson(doc, "[]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(0 == arr.size()); + } + + SECTION("Spaces") { + SECTION("Before the opening bracket") { + DeserializationError err = deserializeJson(doc, " []"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(0 == arr.size()); + } + + SECTION("Before first value") { + DeserializationError err = deserializeJson(doc, "[ \t\r\n42]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == 42); + } + + SECTION("After first value") { + DeserializationError err = deserializeJson(doc, "[42 \t\r\n]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == 42); + } + } + + SECTION("Values types") { + SECTION("On integer") { + DeserializationError err = deserializeJson(doc, "[42]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == 42); + } + + SECTION("Two integers") { + DeserializationError err = deserializeJson(doc, "[42,84]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == 42); + REQUIRE(arr[1] == 84); + } + + SECTION("Double") { + DeserializationError err = deserializeJson(doc, "[4.2,1e2]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == 4.2); + REQUIRE(arr[1] == 1e2); + } + + SECTION("Unsigned long") { + DeserializationError err = deserializeJson(doc, "[4294967295]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == 4294967295UL); + } + + SECTION("Boolean") { + DeserializationError err = deserializeJson(doc, "[true,false]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == true); + REQUIRE(arr[1] == false); + } + + SECTION("Null") { + DeserializationError err = deserializeJson(doc, "[null,null]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0].as() == 0); + REQUIRE(arr[1].as() == 0); + } + } + + SECTION("Quotes") { + SECTION("Double quotes") { + DeserializationError err = + deserializeJson(doc, "[ \"hello\" , \"world\" ]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == "hello"); + REQUIRE(arr[1] == "world"); + } + + SECTION("Single quotes") { + DeserializationError err = deserializeJson(doc, "[ 'hello' , 'world' ]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == "hello"); + REQUIRE(arr[1] == "world"); + } + + SECTION("No quotes") { + DeserializationError err = deserializeJson(doc, "[ hello , world ]"); + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("Double quotes (empty strings)") { + DeserializationError err = deserializeJson(doc, "[\"\",\"\"]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == ""); + REQUIRE(arr[1] == ""); + } + + SECTION("Single quotes (empty strings)") { + DeserializationError err = deserializeJson(doc, "[\'\',\'\']"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == ""); + REQUIRE(arr[1] == ""); + } + + SECTION("No quotes (empty strings)") { + DeserializationError err = deserializeJson(doc, "[,]"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("Closing single quotes missing") { + DeserializationError err = deserializeJson(doc, "[\"]"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("Closing double quotes missing") { + DeserializationError err = deserializeJson(doc, "[\']"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + } + + SECTION("Premature null-terminator") { + SECTION("After opening bracket") { + DeserializationError err = deserializeJson(doc, "["); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("After value") { + DeserializationError err = deserializeJson(doc, "[1"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("After comma") { + DeserializationError err = deserializeJson(doc, "[1,"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + } + + SECTION("Premature end of input") { + const char* input = "[1,2]"; + + SECTION("After opening bracket") { + DeserializationError err = deserializeJson(doc, input, 1); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("After value") { + DeserializationError err = deserializeJson(doc, input, 2); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("After comma") { + DeserializationError err = deserializeJson(doc, input, 3); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + } + + SECTION("Misc") { + SECTION("Nested objects") { + char jsonString[] = + " [ { \"a\" : 1 , \"b\" : 2 } , { \"c\" : 3 , \"d\" : 4 } ] "; + + DeserializationError err = deserializeJson(doc, jsonString); + JsonArray arr = doc.as(); + + JsonObject object1 = arr[0]; + const JsonObject object2 = arr[1]; + JsonObject object3 = arr[2]; + + REQUIRE(err == DeserializationError::Ok); + + REQUIRE(object1.isNull() == false); + REQUIRE(object2.isNull() == false); + REQUIRE(object3.isNull() == true); + + REQUIRE(2 == object1.size()); + REQUIRE(2 == object2.size()); + REQUIRE(0 == object3.size()); + + REQUIRE(1 == object1["a"].as()); + REQUIRE(2 == object1["b"].as()); + REQUIRE(3 == object2["c"].as()); + REQUIRE(4 == object2["d"].as()); + REQUIRE(0 == object3["e"].as()); + } + } + + SECTION("Should clear the JsonArray") { + deserializeJson(doc, "[1,2,3,4]"); + deserializeJson(doc, "[]"); + JsonArray arr = doc.as(); + + REQUIRE(arr.size() == 0); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0)); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/array_static.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/array_static.cpp new file mode 100644 index 0000000..d828518 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/array_static.cpp @@ -0,0 +1,89 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("deserialize JSON array with a StaticJsonDocument") { + SECTION("BufferOfTheRightSizeForEmptyArray") { + StaticJsonDocument doc; + char input[] = "[]"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("TooSmallBufferForArrayWithOneValue") { + StaticJsonDocument doc; + char input[] = "[1]"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::NoMemory); + } + + SECTION("BufferOfTheRightSizeForArrayWithOneValue") { + StaticJsonDocument doc; + char input[] = "[1]"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("TooSmallBufferForArrayWithNestedObject") { + StaticJsonDocument doc; + char input[] = "[{}]"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::NoMemory); + } + + SECTION("BufferOfTheRightSizeForArrayWithNestedObject") { + StaticJsonDocument doc; + char input[] = "[{}]"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("CopyStringNotSpaces") { + StaticJsonDocument<100> doc; + + deserializeJson(doc, " [ \"1234567\" ] "); + + REQUIRE(JSON_ARRAY_SIZE(1) + JSON_STRING_SIZE(7) == doc.memoryUsage()); + // note: we use a string of 8 bytes to be sure that the StaticMemoryPool + // will not insert bytes to enforce alignement + } + + SECTION("Should clear the JsonArray") { + StaticJsonDocument doc; + char input[] = "[1,2,3,4]"; + + deserializeJson(doc, input); + deserializeJson(doc, "[]"); + + JsonArray arr = doc.as(); + REQUIRE(arr.size() == 0); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0)); + } + + SECTION("Array") { + StaticJsonDocument doc; + char input[] = "[1,2]"; + + DeserializationError err = deserializeJson(doc, input); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(2)); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 2); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/filter.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/filter.cpp new file mode 100644 index 0000000..9cf85d2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/filter.cpp @@ -0,0 +1,773 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_ENABLE_COMMENTS 1 +#include +#include + +#include +#include + +TEST_CASE("Filtering") { + struct TestCase { + const char* input; + const char* filter; + uint8_t nestingLimit; + DeserializationError error; + const char* output; + size_t memoryUsage; + }; + + // clang-format off + TestCase testCases[] = { + { + "{\"hello\":\"world\"}", // 1. input + "null", // 2. filter + 10, // 3. nestingLimit + DeserializationError::Ok, // 4. error + "null", // 5. output + 0 // 6. memoryUsage + }, + { + "{\"hello\":\"world\"}", + "false", + 10, + DeserializationError::Ok, + "null", + 0 + }, + { + "{\"abcdefg\":\"hijklmn\"}", + "true", + 10, + DeserializationError::Ok, + "{\"abcdefg\":\"hijklmn\"}", + JSON_OBJECT_SIZE(1) + 16 + }, + { + "{\"hello\":\"world\"}", + "{}", + 10, + DeserializationError::Ok, + "{}", + JSON_OBJECT_SIZE(0) + }, + { + // Input in an object, but filter wants an array + "{\"hello\":\"world\"}", + "[]", + 10, + DeserializationError::Ok, + "null", + 0 + }, + { + // Member is a string, but filter wants an array + "{\"example\":\"example\"}", + "{\"example\":[true]}", + 10, + DeserializationError::Ok, + "{\"example\":null}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // Input is an array, but filter wants an object + "[\"hello\",\"world\"]", + "{}", + 10, + DeserializationError::Ok, + "null", + 0 + }, + { + // Input is a bool, but filter wants an object + "true", + "{}", + 10, + DeserializationError::Ok, + "null", + 0 + }, + { + // Input is a string, but filter wants an object + "\"hello\"", + "{}", + 10, + DeserializationError::Ok, + "null", + 0 + }, + { + // skip an integer + "{\"an_integer\":666,example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // skip a float + "{\"a_float\":12.34e-6,example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip a boolean + "{\"a_bool\":false,example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip a double-quoted string + "{\"a_double_quoted_string\":\"hello\",example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip a single-quoted string + "{\"a_single_quoted_string\":'hello',example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip an empty array + "{\"an_empty_array\":[],example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip an empty array with spaces in it + "{\"an_empty_array\":[\t],example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip an array + "{\"an_array\":[1,2,3],example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip an array with spaces in it + "{\"an_array\": [ 1 , 2 , 3 ] ,example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip an empty object + "{\"an_empty_object\":{},example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip an empty object with spaces in it + "{\"an_empty_object\":{ },example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // can skip an object + "{\"an_object\":{a:1,'b':2,\"c\":3},example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // skip an object with spaces in it + "{\"an_object\" : { a : 1 , 'b' : 2 , \"c\" : 3 } ,example:42}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{\"example\":42}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + "{\"an_integer\": 0,\"example\":{\"type\":\"int\",\"outcome\":42}}", + "{\"example\":{\"outcome\":true}}", + 10, + DeserializationError::Ok, + "{\"example\":{\"outcome\":42}}", + 2 * JSON_OBJECT_SIZE(1) + 16 + }, + { + // wildcard + "{\"example\":{\"type\":\"int\",\"outcome\":42}}", + "{\"*\":{\"outcome\":true}}", + 10, + DeserializationError::Ok, + "{\"example\":{\"outcome\":42}}", + 2 * JSON_OBJECT_SIZE(1) + 16 + }, + { + // exclusion filter (issue #1628) + "{\"example\":1,\"ignored\":2}", + "{\"*\":true,\"ignored\":false}", + 10, + DeserializationError::Ok, + "{\"example\":1}", + JSON_OBJECT_SIZE(1) + 8 + }, + { + // only the first element of array counts + "[1,2,3]", + "[true, false]", + 10, + DeserializationError::Ok, + "[1,2,3]", + JSON_ARRAY_SIZE(3) + }, + { + // only the first element of array counts + "[1,2,3]", + "[false, true]", + 10, + DeserializationError::Ok, + "[]", + JSON_ARRAY_SIZE(0) + }, + { + // filter members of object in array + "[{\"example\":1,\"ignore\":2},{\"example\":3,\"ignore\":4}]", + "[{\"example\":true}]", + 10, + DeserializationError::Ok, + "[{\"example\":1},{\"example\":3}]", + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8 + }, + { + "[',2,3]", + "[false,true]", + 10, + DeserializationError::IncompleteInput, + "[]", + JSON_ARRAY_SIZE(0) + }, + { + "[\",2,3]", + "[false,true]", + 10, + DeserializationError::IncompleteInput, + "[]", + JSON_ARRAY_SIZE(0) + }, + { + // detect errors in skipped value + "[!,2,\\]", + "[false]", + 10, + DeserializationError::InvalidInput, + "[]", + JSON_ARRAY_SIZE(0) + }, + { + // detect incomplete string event if it's skipped + "\"ABC", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // detect incomplete string event if it's skipped + "'ABC", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // handle escaped quotes + "'A\\'BC'", + "false", + 10, + DeserializationError::Ok, + "null", + 0 + }, + { + // handle escaped quotes + "\"A\\\"BC\"", + "false", + 10, + DeserializationError::Ok, + "null", + 0 + }, + { + // detect incomplete string in presence of escaped quotes + "'A\\'BC", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // detect incomplete string in presence of escaped quotes + "\"A\\\"BC", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // skip empty array + "[]", + "false", + 10, + DeserializationError::Ok, + "null", + 0 + }, + { + // skip empty array with spaces + " [ ] ", + "false", + 10, + DeserializationError::Ok, + "null", + 0 + }, + { + // bubble up element error even if array is skipped + "[1,'2,3]", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // bubble up member error even if object is skipped + "{'hello':'worl}", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // bubble up colon error even if object is skipped + "{'hello','world'}", + "false", + 10, + DeserializationError::InvalidInput, + "null", + 0 + }, + { + // bubble up key error even if object is skipped + "{'hello:1}", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // detect invalid value in skipped object + "{'hello':!}", + "false", + 10, + DeserializationError::InvalidInput, + "null", + 0 + }, + { + // ignore invalid value in skipped object + "{'hello':\\}", + "false", + 10, + DeserializationError::InvalidInput, + "null", + 0 + }, + { + // check nesting limit even for ignored objects + "{}", + "false", + 0, + DeserializationError::TooDeep, + "null", + 0 + }, + { + // check nesting limit even for ignored objects + "{'hello':{}}", + "false", + 1, + DeserializationError::TooDeep, + "null", + 0 + }, + { + // check nesting limit even for ignored values in objects + "{'hello':{}}", + "{}", + 1, + DeserializationError::TooDeep, + "{}", + JSON_OBJECT_SIZE(0) + }, + { + // check nesting limit even for ignored arrays + "[]", + "false", + 0, + DeserializationError::TooDeep, + "null", + 0 + }, + { + // check nesting limit even for ignored arrays + "[[]]", + "false", + 1, + DeserializationError::TooDeep, + "null", + 0 + }, + { + // check nesting limit even for ignored values in arrays + "[[]]", + "[]", + 1, + DeserializationError::TooDeep, + "[]", + JSON_ARRAY_SIZE(0) + }, + { + // supports back-slash at the end of skipped string + "\"hell\\", + "false", + 1, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // invalid comment at after an element in a skipped array + "[1/]", + "false", + 10, + DeserializationError::InvalidInput, + "null", + 0 + }, + { + // incomplete comment at after an element in a skipped array + "[1/*]", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // missing comma in a skipped array + "[1 2]", + "false", + 10, + DeserializationError::InvalidInput, + "null", + 0 + }, + { + // invalid comment at the beginning of array + "[/1]", + "[false]", + 10, + DeserializationError::InvalidInput, + "[]", + JSON_ARRAY_SIZE(0) + }, + { + // incomplete comment at the begining of an array + "[/*]", + "[false]", + 10, + DeserializationError::IncompleteInput, + "[]", + JSON_ARRAY_SIZE(0) + }, + { + // invalid comment before key + "{/1:2}", + "{}", + 10, + DeserializationError::InvalidInput, + "{}", + JSON_OBJECT_SIZE(0) + }, + { + // incomplete comment before key + "{/*:2}", + "{}", + 10, + DeserializationError::IncompleteInput, + "{}", + JSON_OBJECT_SIZE(0) + }, + { + // invalid comment after key + "{\"example\"/1:2}", + "{}", + 10, + DeserializationError::InvalidInput, + "{}", + JSON_OBJECT_SIZE(0) + }, + { + // incomplete comment after key + "{\"example\"/*:2}", + "{}", + 10, + DeserializationError::IncompleteInput, + "{}", + JSON_OBJECT_SIZE(0) + }, + { + // invalid comment after colon + "{\"example\":/12}", + "{}", + 10, + DeserializationError::InvalidInput, + "{}", + JSON_OBJECT_SIZE(0) + }, + { + // incomplete comment after colon + "{\"example\":/*2}", + "{}", + 10, + DeserializationError::IncompleteInput, + "{}", + JSON_OBJECT_SIZE(0) + }, + { + // comment next to an integer + "{\"ignore\":1//,\"example\":2\n}", + "{\"example\":true}", + 10, + DeserializationError::Ok, + "{}", + JSON_OBJECT_SIZE(0) + }, + { + // invalid comment after opening brace of a skipped object + "{/1:2}", + "false", + 10, + DeserializationError::InvalidInput, + "null", + 0 + }, + { + // incomplete after opening brace of a skipped object + "{/*:2}", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // invalid comment after key of a skipped object + "{\"example\"/:2}", + "false", + 10, + DeserializationError::InvalidInput, + "null", + 0 + }, + { + // incomplete after after key of a skipped object + "{\"example\"/*:2}", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + { + // invalid comment after value in a skipped object + "{\"example\":2/}", + "false", + 10, + DeserializationError::InvalidInput, + "null", + 0 + }, + { + // incomplete after after value of a skipped object + "{\"example\":2/*}", + "false", + 10, + DeserializationError::IncompleteInput, + "null", + 0 + }, + }; // clang-format on + + for (size_t i = 0; i < sizeof(testCases) / sizeof(testCases[0]); i++) { + CAPTURE(i); + + DynamicJsonDocument filter(256); + DynamicJsonDocument doc(256); + TestCase& tc = testCases[i]; + + CAPTURE(tc.filter); + REQUIRE(deserializeJson(filter, tc.filter) == DeserializationError::Ok); + + CAPTURE(tc.input); + CAPTURE(tc.nestingLimit); + CHECK(deserializeJson(doc, tc.input, DeserializationOption::Filter(filter), + DeserializationOption::NestingLimit( + tc.nestingLimit)) == tc.error); + + CHECK(doc.as() == tc.output); + CHECK(doc.memoryUsage() == tc.memoryUsage); + } +} + +TEST_CASE("Zero-copy mode") { // issue #1697 + char input[] = "{\"include\":42,\"exclude\":666}"; + + StaticJsonDocument<256> filter; + filter["include"] = true; + + StaticJsonDocument<256> doc; + DeserializationError err = + deserializeJson(doc, input, DeserializationOption::Filter(filter)); + + REQUIRE(err == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); +} + +TEST_CASE("Overloads") { + StaticJsonDocument<256> doc; + StaticJsonDocument<256> filter; + + using namespace DeserializationOption; + + // deserializeJson(..., Filter) + + SECTION("const char*, Filter") { + deserializeJson(doc, "{}", Filter(filter)); + } + + SECTION("const char*, size_t, Filter") { + deserializeJson(doc, "{}", 2, Filter(filter)); + } + + SECTION("const std::string&, Filter") { + deserializeJson(doc, std::string("{}"), Filter(filter)); + } + + SECTION("std::istream&, Filter") { + std::stringstream s("{}"); + deserializeJson(doc, s, Filter(filter)); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("char[n], Filter") { + size_t i = 4; + char vla[i]; + strcpy(vla, "{}"); + deserializeJson(doc, vla, Filter(filter)); + } +#endif + + // deserializeJson(..., Filter, NestingLimit) + + SECTION("const char*, Filter, NestingLimit") { + deserializeJson(doc, "{}", Filter(filter), NestingLimit(5)); + } + + SECTION("const char*, size_t, Filter, NestingLimit") { + deserializeJson(doc, "{}", 2, Filter(filter), NestingLimit(5)); + } + + SECTION("const std::string&, Filter, NestingLimit") { + deserializeJson(doc, std::string("{}"), Filter(filter), NestingLimit(5)); + } + + SECTION("std::istream&, Filter, NestingLimit") { + std::stringstream s("{}"); + deserializeJson(doc, s, Filter(filter), NestingLimit(5)); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("char[n], Filter, NestingLimit") { + size_t i = 4; + char vla[i]; + strcpy(vla, "{}"); + deserializeJson(doc, vla, Filter(filter), NestingLimit(5)); + } +#endif + + // deserializeJson(..., NestingLimit, Filter) + + SECTION("const char*, NestingLimit, Filter") { + deserializeJson(doc, "{}", NestingLimit(5), Filter(filter)); + } + + SECTION("const char*, size_t, NestingLimit, Filter") { + deserializeJson(doc, "{}", 2, NestingLimit(5), Filter(filter)); + } + + SECTION("const std::string&, NestingLimit, Filter") { + deserializeJson(doc, std::string("{}"), NestingLimit(5), Filter(filter)); + } + + SECTION("std::istream&, NestingLimit, Filter") { + std::stringstream s("{}"); + deserializeJson(doc, s, NestingLimit(5), Filter(filter)); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("char[n], NestingLimit, Filter") { + size_t i = 4; + char vla[i]; + strcpy(vla, "{}"); + deserializeJson(doc, vla, NestingLimit(5), Filter(filter)); + } +#endif +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/incomplete_input.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/incomplete_input.cpp new file mode 100644 index 0000000..4e5bcdb --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/incomplete_input.cpp @@ -0,0 +1,29 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_DECODE_UNICODE 1 +#include +#include + +TEST_CASE("Truncated JSON input") { + const char* testCases[] = {"\"hello", "\'hello", "'\\u", "'\\u00", "'\\u000", + // false + "f", "fa", "fal", "fals", + // true + "t", "tr", "tru", + // null + "n", "nu", "nul", + // object + "{", "{a", "{a:", "{a:1", "{a:1,", "{a:1,"}; + const size_t testCount = sizeof(testCases) / sizeof(testCases[0]); + + DynamicJsonDocument doc(4096); + + for (size_t i = 0; i < testCount; i++) { + const char* input = testCases[i]; + CAPTURE(input); + REQUIRE(deserializeJson(doc, input) == + DeserializationError::IncompleteInput); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/input_types.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/input_types.cpp new file mode 100644 index 0000000..04054c6 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/input_types.cpp @@ -0,0 +1,198 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include + +#include +#include + +#include "CustomReader.hpp" + +TEST_CASE("deserializeJson(char*)") { + StaticJsonDocument<1024> doc; + + SECTION("should not duplicate strings") { + char input[] = "{\"hello\":\"world\"}"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1)); + CHECK(doc.as().memoryUsage() == + JSON_OBJECT_SIZE(1)); // issue #1318 + } +} + +TEST_CASE("deserializeJson(const std::string&)") { + DynamicJsonDocument doc(4096); + + SECTION("should accept const string") { + const std::string input("[42]"); + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("should accept temporary string") { + DeserializationError err = deserializeJson(doc, std::string("[42]")); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("should duplicate content") { + std::string input("[\"hello\"]"); + + DeserializationError err = deserializeJson(doc, input); + input[2] = 'X'; // alter the string tomake sure we made a copy + + JsonArray array = doc.as(); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(std::string("hello") == array[0]); + } +} + +TEST_CASE("deserializeJson(std::istream&)") { + DynamicJsonDocument doc(4096); + + SECTION("array") { + std::istringstream json(" [ 42 ] "); + + DeserializationError err = deserializeJson(doc, json); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(42 == arr[0]); + } + + SECTION("object") { + std::istringstream json(" { hello : 'world' }"); + + DeserializationError err = deserializeJson(doc, json); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == obj.size()); + REQUIRE(std::string("world") == obj["hello"]); + } + + SECTION("Should not read after the closing brace of an empty object") { + std::istringstream json("{}123"); + + deserializeJson(doc, json); + + REQUIRE('1' == char(json.get())); + } + + SECTION("Should not read after the closing brace") { + std::istringstream json("{\"hello\":\"world\"}123"); + + deserializeJson(doc, json); + + REQUIRE('1' == char(json.get())); + } + + SECTION("Should not read after the closing bracket of an empty array") { + std::istringstream json("[]123"); + + deserializeJson(doc, json); + + REQUIRE('1' == char(json.get())); + } + + SECTION("Should not read after the closing bracket") { + std::istringstream json("[\"hello\",\"world\"]123"); + + deserializeJson(doc, json); + + REQUIRE('1' == char(json.get())); + } + + SECTION("Should not read after the closing quote") { + std::istringstream json("\"hello\"123"); + + deserializeJson(doc, json); + + REQUIRE('1' == char(json.get())); + } +} + +#ifdef HAS_VARIABLE_LENGTH_ARRAY +TEST_CASE("deserializeJson(VLA)") { + size_t i = 9; + char vla[i]; + strcpy(vla, "{\"a\":42}"); + + StaticJsonDocument doc; + DeserializationError err = deserializeJson(doc, vla); + + REQUIRE(err == DeserializationError::Ok); +} +#endif + +TEST_CASE("deserializeJson(CustomReader)") { + DynamicJsonDocument doc(4096); + CustomReader reader("[4,2]"); + DeserializationError err = deserializeJson(doc, reader); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.size() == 2); + REQUIRE(doc[0] == 4); + REQUIRE(doc[1] == 2); +} + +TEST_CASE("deserializeJson(JsonDocument&, MemberProxy)") { + DynamicJsonDocument doc1(4096); + doc1["payload"] = "[4,2]"; + + DynamicJsonDocument doc2(4096); + DeserializationError err = deserializeJson(doc2, doc1["payload"]); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc2.size() == 2); + REQUIRE(doc2[0] == 4); + REQUIRE(doc2[1] == 2); +} + +TEST_CASE("deserializeJson(JsonDocument&, JsonVariant)") { + DynamicJsonDocument doc1(4096); + doc1["payload"] = "[4,2]"; + + DynamicJsonDocument doc2(4096); + DeserializationError err = + deserializeJson(doc2, doc1["payload"].as()); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc2.size() == 2); + REQUIRE(doc2[0] == 4); + REQUIRE(doc2[1] == 2); +} + +TEST_CASE("deserializeJson(JsonDocument&, JsonVariantConst)") { + DynamicJsonDocument doc1(4096); + doc1["payload"] = "[4,2]"; + + DynamicJsonDocument doc2(4096); + DeserializationError err = + deserializeJson(doc2, doc1["payload"].as()); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc2.size() == 2); + REQUIRE(doc2[0] == 4); + REQUIRE(doc2[1] == 2); +} + +TEST_CASE("deserializeJson(JsonDocument&, ElementProxy)") { + DynamicJsonDocument doc1(4096); + doc1[0] = "[4,2]"; + + DynamicJsonDocument doc2(4096); + DeserializationError err = deserializeJson(doc2, doc1[0]); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc2.size() == 2); + REQUIRE(doc2[0] == 4); + REQUIRE(doc2[1] == 2); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/invalid_input.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/invalid_input.cpp new file mode 100644 index 0000000..f358008 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/invalid_input.cpp @@ -0,0 +1,42 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_DECODE_UNICODE 1 +#include +#include + +TEST_CASE("Invalid JSON input") { + const char* testCases[] = {"'\\u'", "'\\u000g'", "'\\u000'", "'\\u000G'", + "'\\u000/'", "\\x1234", "6a9", "1,", + "2]", "3}"}; + const size_t testCount = sizeof(testCases) / sizeof(testCases[0]); + + DynamicJsonDocument doc(4096); + + for (size_t i = 0; i < testCount; i++) { + const char* input = testCases[i]; + CAPTURE(input); + REQUIRE(deserializeJson(doc, input) == DeserializationError::InvalidInput); + } +} + +TEST_CASE("Invalid JSON input that should pass") { + const char* testCases[] = { + "nulL", + "tru3", + "fals3", + "'\\ud83d'", // leading surrogate without a trailing surrogate + "'\\udda4'", // trailing surrogate without a leading surrogate + "'\\ud83d\\ud83d'", // two leading surrogates + }; + const size_t testCount = sizeof(testCases) / sizeof(testCases[0]); + + DynamicJsonDocument doc(4096); + + for (size_t i = 0; i < testCount; i++) { + const char* input = testCases[i]; + CAPTURE(input); + REQUIRE(deserializeJson(doc, input) == DeserializationError::Ok); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/misc.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/misc.cpp new file mode 100644 index 0000000..dc8083c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/misc.cpp @@ -0,0 +1,117 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace Catch::Matchers; + +TEST_CASE("deserializeJson(DynamicJsonDocument&)") { + DynamicJsonDocument doc(4096); + + SECTION("Edge cases") { + SECTION("null char*") { + DeserializationError err = deserializeJson(doc, static_cast(0)); + + REQUIRE(err != DeserializationError::Ok); + } + + SECTION("null const char*") { + DeserializationError err = + deserializeJson(doc, static_cast(0)); + + REQUIRE(err != DeserializationError::Ok); + } + + SECTION("Empty input") { + DeserializationError err = deserializeJson(doc, ""); + + REQUIRE(err == DeserializationError::EmptyInput); + } + + SECTION("Only spaces") { + DeserializationError err = deserializeJson(doc, " \t\n\r"); + + REQUIRE(err == DeserializationError::EmptyInput); + } + + SECTION("issue #628") { + DeserializationError err = deserializeJson(doc, "null"); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == false); + } + + SECTION("Garbage") { + DeserializationError err = deserializeJson(doc, "%*$£¤"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + } + + SECTION("Booleans") { + SECTION("True") { + DeserializationError err = deserializeJson(doc, "true"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(doc.as() == true); + } + + SECTION("False") { + DeserializationError err = deserializeJson(doc, "false"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(doc.as() == false); + } + } + + SECTION("Premature null-terminator") { + SECTION("In escape sequence") { + DeserializationError err = deserializeJson(doc, "\"\\"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("In double quoted string") { + DeserializationError err = deserializeJson(doc, "\"hello"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("In single quoted string") { + DeserializationError err = deserializeJson(doc, "'hello"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + } + + SECTION("Premature end of input") { + SECTION("In escape sequence") { + DeserializationError err = deserializeJson(doc, "\"\\n\"", 2); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("In double quoted string") { + DeserializationError err = deserializeJson(doc, "\"hello\"", 6); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("In single quoted string") { + DeserializationError err = deserializeJson(doc, "'hello'", 6); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + } + + SECTION("Should clear the JsonVariant") { + deserializeJson(doc, "[1,2,3]"); + deserializeJson(doc, "{}"); + + REQUIRE(doc.is()); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0)); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/nestingLimit.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/nestingLimit.cpp new file mode 100644 index 0000000..0cdcb3e --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/nestingLimit.cpp @@ -0,0 +1,101 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +#define SHOULD_WORK(expression) REQUIRE(DeserializationError::Ok == expression); +#define SHOULD_FAIL(expression) \ + REQUIRE(DeserializationError::TooDeep == expression); + +TEST_CASE("JsonDeserializer nesting") { + DynamicJsonDocument doc(4096); + + SECTION("Input = const char*") { + SECTION("limit = 0") { + DeserializationOption::NestingLimit nesting(0); + SHOULD_WORK(deserializeJson(doc, "\"toto\"", nesting)); + SHOULD_WORK(deserializeJson(doc, "123", nesting)); + SHOULD_WORK(deserializeJson(doc, "true", nesting)); + SHOULD_FAIL(deserializeJson(doc, "[]", nesting)); + SHOULD_FAIL(deserializeJson(doc, "{}", nesting)); + SHOULD_FAIL(deserializeJson(doc, "[\"toto\"]", nesting)); + SHOULD_FAIL(deserializeJson(doc, "{\"toto\":1}", nesting)); + } + + SECTION("limit = 1") { + DeserializationOption::NestingLimit nesting(1); + SHOULD_WORK(deserializeJson(doc, "[\"toto\"]", nesting)); + SHOULD_WORK(deserializeJson(doc, "{\"toto\":1}", nesting)); + SHOULD_FAIL(deserializeJson(doc, "{\"toto\":{}}", nesting)); + SHOULD_FAIL(deserializeJson(doc, "{\"toto\":[]}", nesting)); + SHOULD_FAIL(deserializeJson(doc, "[[\"toto\"]]", nesting)); + SHOULD_FAIL(deserializeJson(doc, "[{\"toto\":1}]", nesting)); + } + } + + SECTION("char* and size_t") { + SECTION("limit = 0") { + DeserializationOption::NestingLimit nesting(0); + SHOULD_WORK(deserializeJson(doc, "\"toto\"", 6, nesting)); + SHOULD_WORK(deserializeJson(doc, "123", 3, nesting)); + SHOULD_WORK(deserializeJson(doc, "true", 4, nesting)); + SHOULD_FAIL(deserializeJson(doc, "[]", 2, nesting)); + SHOULD_FAIL(deserializeJson(doc, "{}", 2, nesting)); + SHOULD_FAIL(deserializeJson(doc, "[\"toto\"]", 8, nesting)); + SHOULD_FAIL(deserializeJson(doc, "{\"toto\":1}", 10, nesting)); + } + + SECTION("limit = 1") { + DeserializationOption::NestingLimit nesting(1); + SHOULD_WORK(deserializeJson(doc, "[\"toto\"]", 8, nesting)); + SHOULD_WORK(deserializeJson(doc, "{\"toto\":1}", 10, nesting)); + SHOULD_FAIL(deserializeJson(doc, "{\"toto\":{}}", 11, nesting)); + SHOULD_FAIL(deserializeJson(doc, "{\"toto\":[]}", 11, nesting)); + SHOULD_FAIL(deserializeJson(doc, "[[\"toto\"]]", 10, nesting)); + SHOULD_FAIL(deserializeJson(doc, "[{\"toto\":1}]", 12, nesting)); + } + } + + SECTION("Input = std::string") { + SECTION("limit = 0") { + DeserializationOption::NestingLimit nesting(0); + SHOULD_WORK(deserializeJson(doc, std::string("\"toto\""), nesting)); + SHOULD_WORK(deserializeJson(doc, std::string("123"), nesting)); + SHOULD_WORK(deserializeJson(doc, std::string("true"), nesting)); + SHOULD_FAIL(deserializeJson(doc, std::string("[]"), nesting)); + SHOULD_FAIL(deserializeJson(doc, std::string("{}"), nesting)); + SHOULD_FAIL(deserializeJson(doc, std::string("[\"toto\"]"), nesting)); + SHOULD_FAIL(deserializeJson(doc, std::string("{\"toto\":1}"), nesting)); + } + + SECTION("limit = 1") { + DeserializationOption::NestingLimit nesting(1); + SHOULD_WORK(deserializeJson(doc, std::string("[\"toto\"]"), nesting)); + SHOULD_WORK(deserializeJson(doc, std::string("{\"toto\":1}"), nesting)); + SHOULD_FAIL(deserializeJson(doc, std::string("{\"toto\":{}}"), nesting)); + SHOULD_FAIL(deserializeJson(doc, std::string("{\"toto\":[]}"), nesting)); + SHOULD_FAIL(deserializeJson(doc, std::string("[[\"toto\"]]"), nesting)); + SHOULD_FAIL(deserializeJson(doc, std::string("[{\"toto\":1}]"), nesting)); + } + } + + SECTION("Input = std::istream") { + SECTION("limit = 0") { + DeserializationOption::NestingLimit nesting(0); + std::istringstream good("true"); + std::istringstream bad("[]"); + SHOULD_WORK(deserializeJson(doc, good, nesting)); + SHOULD_FAIL(deserializeJson(doc, bad, nesting)); + } + + SECTION("limit = 1") { + DeserializationOption::NestingLimit nesting(1); + std::istringstream good("[\"toto\"]"); + std::istringstream bad("{\"toto\":{}}"); + SHOULD_WORK(deserializeJson(doc, good, nesting)); + SHOULD_FAIL(deserializeJson(doc, bad, nesting)); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/number.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/number.cpp new file mode 100644 index 0000000..44e24cb --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/number.cpp @@ -0,0 +1,133 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_USE_LONG_LONG 0 +#define ARDUINOJSON_ENABLE_NAN 1 +#define ARDUINOJSON_ENABLE_INFINITY 1 + +#include +#include +#include + +namespace my { +using ARDUINOJSON_NAMESPACE::isinf; +using ARDUINOJSON_NAMESPACE::isnan; +} // namespace my + +TEST_CASE("deserialize an integer") { + DynamicJsonDocument doc(4096); + + SECTION("Integer") { + SECTION("0") { + DeserializationError err = deserializeJson(doc, "0"); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == true); + REQUIRE(doc.as() == 0); + REQUIRE(doc.as() == "0"); // issue #808 + } + + SECTION("Negative") { + DeserializationError err = deserializeJson(doc, "-42"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE_FALSE(doc.is()); + REQUIRE(doc.as() == -42); + } + +#if LONG_MAX == 2147483647 + SECTION("LONG_MAX") { + DeserializationError err = deserializeJson(doc, "2147483647"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == true); + REQUIRE(doc.as() == LONG_MAX); + } + + SECTION("LONG_MAX + 1") { + DeserializationError err = deserializeJson(doc, "2147483648"); + + CAPTURE(LONG_MIN); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == false); + REQUIRE(doc.is() == true); + } +#endif + +#if LONG_MIN == -2147483648 + SECTION("LONG_MIN") { + DeserializationError err = deserializeJson(doc, "-2147483648"); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == true); + REQUIRE(doc.as() == LONG_MIN); + } + + SECTION("LONG_MIN - 1") { + DeserializationError err = deserializeJson(doc, "-2147483649"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == false); + REQUIRE(doc.is() == true); + } +#endif + +#if ULONG_MAX == 4294967295 + SECTION("ULONG_MAX") { + DeserializationError err = deserializeJson(doc, "4294967295"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == true); + REQUIRE(doc.as() == ULONG_MAX); + REQUIRE(doc.is() == false); + } + + SECTION("ULONG_MAX + 1") { + DeserializationError err = deserializeJson(doc, "4294967296"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == false); + REQUIRE(doc.is() == true); + } +#endif + } + + SECTION("Floats") { + SECTION("Double") { + DeserializationError err = deserializeJson(doc, "-1.23e+4"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE_FALSE(doc.is()); + REQUIRE(doc.is()); + REQUIRE(doc.as() == Approx(-1.23e+4)); + } + + SECTION("NaN") { + DeserializationError err = deserializeJson(doc, "NaN"); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == true); + REQUIRE(my::isnan(doc.as())); + } + + SECTION("Infinity") { + DeserializationError err = deserializeJson(doc, "Infinity"); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == true); + REQUIRE(my::isinf(doc.as())); + } + + SECTION("+Infinity") { + DeserializationError err = deserializeJson(doc, "+Infinity"); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == true); + REQUIRE(my::isinf(doc.as())); + } + + SECTION("-Infinity") { + DeserializationError err = deserializeJson(doc, "-Infinity"); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is() == true); + REQUIRE(my::isinf(doc.as())); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/object.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/object.cpp new file mode 100644 index 0000000..e5d5de7 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/object.cpp @@ -0,0 +1,315 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("deserialize JSON object") { + DynamicJsonDocument doc(4096); + + SECTION("An empty object") { + DeserializationError err = deserializeJson(doc, "{}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 0); + } + + SECTION("Quotes") { + SECTION("Double quotes") { + DeserializationError err = deserializeJson(doc, "{\"key\":\"value\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("Single quotes") { + DeserializationError err = deserializeJson(doc, "{'key':'value'}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("No quotes") { + DeserializationError err = deserializeJson(doc, "{key:'value'}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("No quotes, allow underscore in key") { + DeserializationError err = deserializeJson(doc, "{_k_e_y_:42}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["_k_e_y_"] == 42); + } + } + + SECTION("Spaces") { + SECTION("Before the key") { + DeserializationError err = deserializeJson(doc, "{ \"key\":\"value\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("After the key") { + DeserializationError err = deserializeJson(doc, "{\"key\" :\"value\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("Before the value") { + DeserializationError err = deserializeJson(doc, "{\"key\": \"value\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("After the value") { + DeserializationError err = deserializeJson(doc, "{\"key\":\"value\" }"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("Before the colon") { + DeserializationError err = + deserializeJson(doc, "{\"key1\":\"value1\" ,\"key2\":\"value2\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == "value1"); + REQUIRE(obj["key2"] == "value2"); + } + + SECTION("After the colon") { + DeserializationError err = + deserializeJson(doc, "{\"key1\":\"value1\" ,\"key2\":\"value2\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == "value1"); + REQUIRE(obj["key2"] == "value2"); + } + } + + SECTION("Values types") { + SECTION("String") { + DeserializationError err = + deserializeJson(doc, "{\"key1\":\"value1\",\"key2\":\"value2\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == "value1"); + REQUIRE(obj["key2"] == "value2"); + } + + SECTION("Integer") { + DeserializationError err = + deserializeJson(doc, "{\"key1\":42,\"key2\":-42}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == 42); + REQUIRE(obj["key2"] == -42); + } + + SECTION("Double") { + DeserializationError err = + deserializeJson(doc, "{\"key1\":12.345,\"key2\":-7E89}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == 12.345); + REQUIRE(obj["key2"] == -7E89); + } + + SECTION("Booleans") { + DeserializationError err = + deserializeJson(doc, "{\"key1\":true,\"key2\":false}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == true); + REQUIRE(obj["key2"] == false); + } + + SECTION("Null") { + DeserializationError err = + deserializeJson(doc, "{\"key1\":null,\"key2\":null}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"].as() == 0); + REQUIRE(obj["key2"].as() == 0); + } + + SECTION("Array") { + char jsonString[] = " { \"ab\" : [ 1 , 2 ] , \"cd\" : [ 3 , 4 ] } "; + + DeserializationError err = deserializeJson(doc, jsonString); + JsonObject obj = doc.as(); + + JsonArray array1 = obj["ab"]; + const JsonArray array2 = obj["cd"]; + JsonArray array3 = obj["ef"]; + + REQUIRE(err == DeserializationError::Ok); + + REQUIRE(array1.isNull() == false); + REQUIRE(array2.isNull() == false); + REQUIRE(array3.isNull() == true); + + REQUIRE(2 == array1.size()); + REQUIRE(2 == array2.size()); + REQUIRE(0 == array3.size()); + + REQUIRE(1 == array1[0].as()); + REQUIRE(2 == array1[1].as()); + + REQUIRE(3 == array2[0].as()); + REQUIRE(4 == array2[1].as()); + + REQUIRE(0 == array3[0].as()); + } + } + + SECTION("Premature null terminator") { + SECTION("After opening brace") { + DeserializationError err = deserializeJson(doc, "{"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("After key") { + DeserializationError err = deserializeJson(doc, "{\"hello\""); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("After colon") { + DeserializationError err = deserializeJson(doc, "{\"hello\":"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("After value") { + DeserializationError err = deserializeJson(doc, "{\"hello\":\"world\""); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("After comma") { + DeserializationError err = deserializeJson(doc, "{\"hello\":\"world\","); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + } + + SECTION("Misc") { + SECTION("A quoted key without value") { + DeserializationError err = deserializeJson(doc, "{\"key\"}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("A non-quoted key without value") { + DeserializationError err = deserializeJson(doc, "{key}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("A dangling comma") { + DeserializationError err = deserializeJson(doc, "{\"key1\":\"value1\",}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("null as a key") { + DeserializationError err = deserializeJson(doc, "{null:\"value\"}"); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("Repeated key") { + DeserializationError err = deserializeJson(doc, "{a:{b:{c:1}},a:2}"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc["a"] == 2); + } + + SECTION("Repeated key with zero copy mode") { // issue #1697 + char input[] = "{a:{b:{c:1}},a:2}"; + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc["a"] == 2); + } + + SECTION("NUL in keys") { // we don't support NULs in keys + DeserializationError err = + deserializeJson(doc, "{\"x\\u0000a\":1,\"x\\u0000b\":2}"); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.as() == "{\"x\":2}"); + } + } + + SECTION("Should clear the JsonObject") { + deserializeJson(doc, "{\"hello\":\"world\"}"); + deserializeJson(doc, "{}"); + JsonObject obj = doc.as(); + + REQUIRE(doc.is()); + REQUIRE(obj.size() == 0); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0)); + } + + SECTION("Issue #1335") { + std::string json("{\"a\":{},\"b\":{}}"); + deserializeJson(doc, json); + CHECK(doc.as() == json); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/object_static.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/object_static.cpp new file mode 100644 index 0000000..a326afe --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/object_static.cpp @@ -0,0 +1,64 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("deserialize JSON object with StaticJsonDocument") { + SECTION("BufferOfTheRightSizeForEmptyObject") { + StaticJsonDocument doc; + char input[] = "{}"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("TooSmallBufferForObjectWithOneValue") { + StaticJsonDocument doc; + char input[] = "{\"a\":1}"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::NoMemory); + } + + SECTION("BufferOfTheRightSizeForObjectWithOneValue") { + StaticJsonDocument doc; + char input[] = "{\"a\":1}"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("TooSmallBufferForObjectWithNestedObject") { + StaticJsonDocument doc; + char input[] = "{\"a\":[]}"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::NoMemory); + } + + SECTION("BufferOfTheRightSizeForObjectWithNestedObject") { + StaticJsonDocument doc; + char input[] = "{\"a\":[]}"; + + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("Should clear the JsonObject") { + StaticJsonDocument doc; + char input[] = "{\"hello\":\"world\"}"; + + deserializeJson(doc, input); + deserializeJson(doc, "{}"); + + REQUIRE(doc.as().size() == 0); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0)); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/string.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/string.cpp new file mode 100644 index 0000000..8f93ac6 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDeserializer/string.cpp @@ -0,0 +1,132 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_DECODE_UNICODE 1 +#include +#include + +TEST_CASE("Valid JSON strings value") { + struct TestCase { + const char* input; + const char* expectedOutput; + }; + + TestCase testCases[] = { + {"\"hello world\"", "hello world"}, + {"\'hello world\'", "hello world"}, + {"'\"'", "\""}, + {"'\\\\'", "\\"}, + {"'\\/'", "/"}, + {"'\\b'", "\b"}, + {"'\\f'", "\f"}, + {"'\\n'", "\n"}, + {"'\\r'", "\r"}, + {"'\\t'", "\t"}, + {"\"1\\\"2\\\\3\\/4\\b5\\f6\\n7\\r8\\t9\"", "1\"2\\3/4\b5\f6\n7\r8\t9"}, + {"'\\u0041'", "A"}, + {"'\\u00e4'", "\xc3\xa4"}, // ä + {"'\\u00E4'", "\xc3\xa4"}, // ä + {"'\\u3042'", "\xe3\x81\x82"}, // あ + {"'\\ud83d\\udda4'", "\xf0\x9f\x96\xa4"}, // 🖤 + {"'\\uF053'", "\xef\x81\x93"}, // issue #1173 + {"'\\uF015'", "\xef\x80\x95"}, // issue #1173 + {"'\\uF054'", "\xef\x81\x94"}, // issue #1173 + }; + const size_t testCount = sizeof(testCases) / sizeof(testCases[0]); + + DynamicJsonDocument doc(4096); + + for (size_t i = 0; i < testCount; i++) { + const TestCase& testCase = testCases[i]; + CAPTURE(testCase.input); + DeserializationError err = deserializeJson(doc, testCase.input); + CHECK(err == DeserializationError::Ok); + CHECK(doc.as() == testCase.expectedOutput); + } +} + +TEST_CASE("\\u0000") { + StaticJsonDocument<200> doc; + + DeserializationError err = deserializeJson(doc, "\"wx\\u0000yz\""); + REQUIRE(err == DeserializationError::Ok); + + const char* result = doc.as(); + CHECK(result[0] == 'w'); + CHECK(result[1] == 'x'); + CHECK(result[2] == 0); + CHECK(result[3] == 'y'); + CHECK(result[4] == 'z'); + CHECK(result[5] == 0); + + CHECK(doc.as().size() == 5); + CHECK(doc.as().size() == 5); +} + +TEST_CASE("Truncated JSON string") { + const char* testCases[] = {"\"hello", "\'hello", "'\\u", "'\\u00", "'\\u000"}; + const size_t testCount = sizeof(testCases) / sizeof(testCases[0]); + + DynamicJsonDocument doc(4096); + + for (size_t i = 0; i < testCount; i++) { + const char* input = testCases[i]; + CAPTURE(input); + REQUIRE(deserializeJson(doc, input) == + DeserializationError::IncompleteInput); + } +} + +TEST_CASE("Invalid JSON string") { + const char* testCases[] = {"'\\u'", "'\\u000g'", "'\\u000'", + "'\\u000G'", "'\\u000/'", "'\\x1234'"}; + const size_t testCount = sizeof(testCases) / sizeof(testCases[0]); + + DynamicJsonDocument doc(4096); + + for (size_t i = 0; i < testCount; i++) { + const char* input = testCases[i]; + CAPTURE(input); + REQUIRE(deserializeJson(doc, input) == DeserializationError::InvalidInput); + } +} + +TEST_CASE("Not enough room to save the key") { + DynamicJsonDocument doc(JSON_OBJECT_SIZE(1) + 8); + + SECTION("Quoted string") { + REQUIRE(deserializeJson(doc, "{\"example\":1}") == + DeserializationError::Ok); + REQUIRE(deserializeJson(doc, "{\"accuracy\":1}") == + DeserializationError::NoMemory); + REQUIRE(deserializeJson(doc, "{\"hello\":1,\"world\"}") == + DeserializationError::NoMemory); // fails in the second string + } + + SECTION("Non-quoted string") { + REQUIRE(deserializeJson(doc, "{example:1}") == DeserializationError::Ok); + REQUIRE(deserializeJson(doc, "{accuracy:1}") == + DeserializationError::NoMemory); + REQUIRE(deserializeJson(doc, "{hello:1,world}") == + DeserializationError::NoMemory); // fails in the second string + } +} + +TEST_CASE("Empty memory pool") { + // NOLINTNEXTLINE(clang-analyzer-optin.portability.UnixAPI) + DynamicJsonDocument doc(0); + + SECTION("Input is const char*") { + REQUIRE(deserializeJson(doc, "\"hello\"") == + DeserializationError::NoMemory); + REQUIRE(deserializeJson(doc, "\"\"") == DeserializationError::NoMemory); + } + + SECTION("Input is const char*") { + char hello[] = "\"hello\""; + REQUIRE(deserializeJson(doc, hello) == DeserializationError::Ok); + char empty[] = "\"hello\""; + REQUIRE(deserializeJson(doc, empty) == DeserializationError::Ok); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/BasicJsonDocument.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/BasicJsonDocument.cpp new file mode 100644 index 0000000..d207440 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/BasicJsonDocument.cpp @@ -0,0 +1,187 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include // malloc, free +#include +#include +#include + +using ARDUINOJSON_NAMESPACE::addPadding; + +class SpyingAllocator { + public: + SpyingAllocator(const SpyingAllocator& src) : _log(src._log) {} + SpyingAllocator(std::ostream& log) : _log(log) {} + + void* allocate(size_t n) { + _log << "A" << n; + return malloc(n); + } + void deallocate(void* p) { + _log << "F"; + free(p); + } + + private: + SpyingAllocator& operator=(const SpyingAllocator& src); + + std::ostream& _log; +}; + +class ControllableAllocator { + public: + ControllableAllocator() : _enabled(true) {} + + void* allocate(size_t n) { + return _enabled ? malloc(n) : 0; + } + + void deallocate(void* p) { + free(p); + } + + void disable() { + _enabled = false; + } + + private: + bool _enabled; +}; + +TEST_CASE("BasicJsonDocument") { + std::stringstream log; + + SECTION("Construct/Destruct") { + { BasicJsonDocument doc(4096, log); } + REQUIRE(log.str() == "A4096F"); + } + + SECTION("Copy construct") { + { + BasicJsonDocument doc1(4096, log); + doc1.set(std::string("The size of this string is 32!!")); + + BasicJsonDocument doc2(doc1); + + REQUIRE(doc1.as() == "The size of this string is 32!!"); + REQUIRE(doc2.as() == "The size of this string is 32!!"); + REQUIRE(doc2.capacity() == 4096); + } + REQUIRE(log.str() == "A4096A4096FF"); + } + +#if ARDUINOJSON_HAS_RVALUE_REFERENCES + SECTION("Move construct") { + { + BasicJsonDocument doc1(4096, log); + doc1.set(std::string("The size of this string is 32!!")); + + BasicJsonDocument doc2(std::move(doc1)); + + REQUIRE(doc2.as() == "The size of this string is 32!!"); + REQUIRE(doc1.as() == "null"); + REQUIRE(doc1.capacity() == 0); + REQUIRE(doc2.capacity() == 4096); + } + REQUIRE(log.str() == "A4096F"); + } +#endif + + SECTION("Copy assign larger") { + { + BasicJsonDocument doc1(4096, log); + doc1.set(std::string("The size of this string is 32!!")); + BasicJsonDocument doc2(8, log); + + doc2 = doc1; + + REQUIRE(doc1.as() == "The size of this string is 32!!"); + REQUIRE(doc2.as() == "The size of this string is 32!!"); + REQUIRE(doc2.capacity() == 4096); + } + REQUIRE(log.str() == "A4096A8FA4096FF"); + } + + SECTION("Copy assign smaller") { + { + BasicJsonDocument doc1(1024, log); + doc1.set(std::string("The size of this string is 32!!")); + BasicJsonDocument doc2(4096, log); + + doc2 = doc1; + + REQUIRE(doc1.as() == "The size of this string is 32!!"); + REQUIRE(doc2.as() == "The size of this string is 32!!"); + REQUIRE(doc2.capacity() == 1024); + } + REQUIRE(log.str() == "A1024A4096FA1024FF"); + } + + SECTION("Copy assign same size") { + { + BasicJsonDocument doc1(1024, log); + doc1.set(std::string("The size of this string is 32!!")); + BasicJsonDocument doc2(1024, log); + + doc2 = doc1; + + REQUIRE(doc1.as() == "The size of this string is 32!!"); + REQUIRE(doc2.as() == "The size of this string is 32!!"); + REQUIRE(doc2.capacity() == 1024); + } + REQUIRE(log.str() == "A1024A1024FF"); + } + +#if ARDUINOJSON_HAS_RVALUE_REFERENCES + SECTION("Move assign") { + { + BasicJsonDocument doc1(4096, log); + doc1.set(std::string("The size of this string is 32!!")); + BasicJsonDocument doc2(8, log); + + doc2 = std::move(doc1); + + REQUIRE(doc2.as() == "The size of this string is 32!!"); + REQUIRE(doc1.as() == "null"); + REQUIRE(doc1.capacity() == 0); + REQUIRE(doc2.capacity() == 4096); + } + REQUIRE(log.str() == "A4096A8FF"); + } +#endif + + SECTION("garbageCollect()") { + BasicJsonDocument doc(4096); + + SECTION("when allocation succeeds") { + deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}"); + REQUIRE(doc.capacity() == 4096); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + doc.remove("blanket"); + + bool result = doc.garbageCollect(); + + REQUIRE(result == true); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + REQUIRE(doc.capacity() == 4096); + REQUIRE(doc.as() == "{\"dancing\":2}"); + } + + SECTION("when allocation fails") { + deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}"); + REQUIRE(doc.capacity() == 4096); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + doc.remove("blanket"); + doc.allocator().disable(); + + bool result = doc.garbageCollect(); + + REQUIRE(result == false); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + REQUIRE(doc.capacity() == 4096); + REQUIRE(doc.as() == "{\"dancing\":2}"); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/CMakeLists.txt new file mode 100644 index 0000000..5356780 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/CMakeLists.txt @@ -0,0 +1,31 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(JsonDocumentTests + add.cpp + BasicJsonDocument.cpp + cast.cpp + compare.cpp + containsKey.cpp + createNested.cpp + DynamicJsonDocument.cpp + ElementProxy.cpp + isNull.cpp + MemberProxy.cpp + nesting.cpp + overflowed.cpp + remove.cpp + shrinkToFit.cpp + size.cpp + StaticJsonDocument.cpp + subscript.cpp + swap.cpp +) + +add_test(JsonDocument JsonDocumentTests) + +set_tests_properties(JsonDocument + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/DynamicJsonDocument.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/DynamicJsonDocument.cpp new file mode 100644 index 0000000..ce050f1 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/DynamicJsonDocument.cpp @@ -0,0 +1,230 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using ARDUINOJSON_NAMESPACE::addPadding; + +static void REQUIRE_JSON(JsonDocument& doc, const std::string& expected) { + std::string json; + serializeJson(doc, json); + REQUIRE(json == expected); +} + +TEST_CASE("DynamicJsonDocument") { + DynamicJsonDocument doc(4096); + + SECTION("serializeJson()") { + JsonObject obj = doc.to(); + obj["hello"] = "world"; + + std::string json; + serializeJson(doc, json); + + REQUIRE(json == "{\"hello\":\"world\"}"); + } + + SECTION("memoryUsage()") { + SECTION("starts at zero") { + REQUIRE(doc.memoryUsage() == 0); + } + + SECTION("JSON_ARRAY_SIZE(0)") { + doc.to(); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0)); + } + + SECTION("JSON_ARRAY_SIZE(1)") { + doc.to().add(42); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1)); + } + + SECTION("JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(0)") { + doc.to().createNestedArray(); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(0)); + } + } + + SECTION("capacity()") { + SECTION("matches constructor argument") { + DynamicJsonDocument doc2(256); + REQUIRE(doc2.capacity() == 256); + } + + SECTION("rounds up constructor argument") { + DynamicJsonDocument doc2(253); + REQUIRE(doc2.capacity() == 256); + } + } + + SECTION("memoryUsage()") { + SECTION("Increases after adding value to array") { + JsonArray arr = doc.to(); + + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(0)); + arr.add(42); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(1)); + arr.add(43); + REQUIRE(doc.memoryUsage() == JSON_ARRAY_SIZE(2)); + } + + SECTION("Increases after adding value to object") { + JsonObject obj = doc.to(); + + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0)); + obj["a"] = 1; + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1)); + obj["b"] = 2; + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2)); + } + } +} + +TEST_CASE("DynamicJsonDocument constructor") { + SECTION("Copy constructor") { + DynamicJsonDocument doc1(1234); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + DynamicJsonDocument doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + + REQUIRE(doc2.capacity() == doc1.capacity()); + } + + SECTION("Construct from StaticJsonDocument") { + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + DynamicJsonDocument doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + REQUIRE(doc2.capacity() == doc1.capacity()); + } + + SECTION("Construct from JsonObject") { + StaticJsonDocument<200> doc1; + JsonObject obj = doc1.to(); + obj["hello"] = "world"; + + DynamicJsonDocument doc2 = obj; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage())); + } + + SECTION("Construct from JsonArray") { + StaticJsonDocument<200> doc1; + JsonArray arr = doc1.to(); + arr.add("hello"); + + DynamicJsonDocument doc2 = arr; + + REQUIRE_JSON(doc2, "[\"hello\"]"); + REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage())); + } + + SECTION("Construct from JsonVariant") { + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "42"); + + DynamicJsonDocument doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage())); + } +} + +TEST_CASE("DynamicJsonDocument assignment") { + SECTION("Copy assignment reallocates when capacity is smaller") { + DynamicJsonDocument doc1(1234); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + DynamicJsonDocument doc2(8); + + doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + REQUIRE(doc2.capacity() == doc1.capacity()); + } + + SECTION("Copy assignment reallocates when capacity is larger") { + DynamicJsonDocument doc1(100); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + DynamicJsonDocument doc2(1234); + + doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + REQUIRE(doc2.capacity() == doc1.capacity()); + } + + SECTION("Assign from StaticJsonDocument") { + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "{\"hello\":\"world\"}"); + DynamicJsonDocument doc2(4096); + doc2.to().set(666); + + doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Assign from JsonObject") { + StaticJsonDocument<200> doc1; + JsonObject obj = doc1.to(); + obj["hello"] = "world"; + + DynamicJsonDocument doc2(4096); + doc2 = obj; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + REQUIRE(doc2.capacity() == 4096); + } + + SECTION("Assign from JsonArray") { + StaticJsonDocument<200> doc1; + JsonArray arr = doc1.to(); + arr.add("hello"); + + DynamicJsonDocument doc2(4096); + doc2 = arr; + + REQUIRE_JSON(doc2, "[\"hello\"]"); + REQUIRE(doc2.capacity() == 4096); + } + + SECTION("Assign from JsonVariant") { + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "42"); + + DynamicJsonDocument doc2(4096); + doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + REQUIRE(doc2.capacity() == 4096); + } + + SECTION("Assign from MemberProxy") { + StaticJsonDocument<200> doc1; + doc1["value"] = 42; + + DynamicJsonDocument doc2(4096); + doc2 = doc1["value"]; + + REQUIRE_JSON(doc2, "42"); + REQUIRE(doc2.capacity() == 4096); + } + + SECTION("Assign from ElementProxy") { + StaticJsonDocument<200> doc1; + doc1[0] = 42; + + DynamicJsonDocument doc2(4096); + doc2 = doc1[0]; + + REQUIRE_JSON(doc2, "42"); + REQUIRE(doc2.capacity() == 4096); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/ElementProxy.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/ElementProxy.cpp new file mode 100644 index 0000000..0fe3c47 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/ElementProxy.cpp @@ -0,0 +1,247 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("ElementProxy::add()") { + DynamicJsonDocument doc(4096); + doc.addElement(); + ElementProxy ep = doc[0]; + + SECTION("add(int)") { + ep.add(42); + + REQUIRE(doc.as() == "[[42]]"); + } + + SECTION("add(const char*)") { + ep.add("world"); + + REQUIRE(doc.as() == "[[\"world\"]]"); + } + + SECTION("set(char[])") { + char s[] = "world"; + ep.add(s); + strcpy(s, "!!!!!"); + + REQUIRE(doc.as() == "[[\"world\"]]"); + } +} + +TEST_CASE("ElementProxy::clear()") { + DynamicJsonDocument doc(4096); + doc.addElement(); + ElementProxy ep = doc[0]; + + SECTION("size goes back to zero") { + ep.add(42); + ep.clear(); + + REQUIRE(ep.size() == 0); + } + + SECTION("isNull() return true") { + ep.add("hello"); + ep.clear(); + + REQUIRE(ep.isNull() == true); + } +} + +TEST_CASE("ElementProxy::operator==()") { + DynamicJsonDocument doc(4096); + + SECTION("1 vs 1") { + doc.add(1); + doc.add(1); + + REQUIRE(doc[0] <= doc[1]); + REQUIRE(doc[0] == doc[1]); + REQUIRE(doc[0] >= doc[1]); + REQUIRE_FALSE(doc[0] != doc[1]); + REQUIRE_FALSE(doc[0] < doc[1]); + REQUIRE_FALSE(doc[0] > doc[1]); + } + + SECTION("1 vs 2") { + doc.add(1); + doc.add(2); + + REQUIRE(doc[0] != doc[1]); + REQUIRE(doc[0] < doc[1]); + REQUIRE(doc[0] <= doc[1]); + REQUIRE_FALSE(doc[0] == doc[1]); + REQUIRE_FALSE(doc[0] > doc[1]); + REQUIRE_FALSE(doc[0] >= doc[1]); + } + + SECTION("'abc' vs 'bcd'") { + doc.add("abc"); + doc.add("bcd"); + + REQUIRE(doc[0] != doc[1]); + REQUIRE(doc[0] < doc[1]); + REQUIRE(doc[0] <= doc[1]); + REQUIRE_FALSE(doc[0] == doc[1]); + REQUIRE_FALSE(doc[0] > doc[1]); + REQUIRE_FALSE(doc[0] >= doc[1]); + } +} + +TEST_CASE("ElementProxy::remove()") { + DynamicJsonDocument doc(4096); + doc.addElement(); + ElementProxy ep = doc[0]; + + SECTION("remove(int)") { + ep.add(1); + ep.add(2); + ep.add(3); + + ep.remove(1); + + REQUIRE(ep.as() == "[1,3]"); + } + + SECTION("remove(const char *)") { + ep["a"] = 1; + ep["b"] = 2; + + ep.remove("a"); + + REQUIRE(ep.as() == "{\"b\":2}"); + } + + SECTION("remove(std::string)") { + ep["a"] = 1; + ep["b"] = 2; + + ep.remove(std::string("b")); + + REQUIRE(ep.as() == "{\"a\":1}"); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("remove(vla)") { + ep["a"] = 1; + ep["b"] = 2; + + size_t i = 4; + char vla[i]; + strcpy(vla, "b"); + ep.remove(vla); + + REQUIRE(ep.as() == "{\"a\":1}"); + } +#endif +} + +TEST_CASE("ElementProxy::set()") { + DynamicJsonDocument doc(4096); + ElementProxy ep = doc[0]; + + SECTION("set(int)") { + ep.set(42); + + REQUIRE(doc.as() == "[42]"); + } + + SECTION("set(const char*)") { + ep.set("world"); + + REQUIRE(doc.as() == "[\"world\"]"); + } + + SECTION("set(char[])") { + char s[] = "world"; + ep.set(s); + strcpy(s, "!!!!!"); + + REQUIRE(doc.as() == "[\"world\"]"); + } +} + +TEST_CASE("ElementProxy::size()") { + DynamicJsonDocument doc(4096); + doc.addElement(); + ElementProxy ep = doc[0]; + + SECTION("returns 0") { + REQUIRE(ep.size() == 0); + } + + SECTION("as an array, returns 2") { + ep.add(1); + ep.add(2); + REQUIRE(ep.size() == 2); + } + + SECTION("as an object, returns 2") { + ep["a"] = 1; + ep["b"] = 2; + REQUIRE(ep.size() == 2); + } +} + +TEST_CASE("ElementProxy::memoryUsage()") { + DynamicJsonDocument doc(4096); + doc.addElement(); + ElementProxy ep = doc[0]; + + SECTION("returns 0 for null") { + REQUIRE(ep.memoryUsage() == 0); + } + + SECTION("returns size for string") { + ep.set(std::string("hello")); + REQUIRE(ep.memoryUsage() == 6); + } +} + +TEST_CASE("ElementProxy::operator[]") { + DynamicJsonDocument doc(4096); + ElementProxy ep = doc[1]; + + SECTION("set member") { + ep["world"] = 42; + + REQUIRE(doc.as() == "[null,{\"world\":42}]"); + } + + SECTION("set element") { + ep[2] = 42; + + REQUIRE(doc.as() == "[null,[null,null,42]]"); + } +} + +TEST_CASE("ElementProxy cast to JsonVariantConst") { + DynamicJsonDocument doc(4096); + doc[0] = "world"; + + const ElementProxy ep = doc[0]; + + JsonVariantConst var = ep; + + CHECK(var.as() == "world"); +} + +TEST_CASE("ElementProxy cast to JsonVariant") { + DynamicJsonDocument doc(4096); + doc[0] = "world"; + + ElementProxy ep = doc[0]; + + JsonVariant var = ep; + + CHECK(var.as() == "world"); + + var.set("toto"); + + CHECK(doc.as() == "[\"toto\"]"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/MemberProxy.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/MemberProxy.cpp new file mode 100644 index 0000000..64cd06a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/MemberProxy.cpp @@ -0,0 +1,287 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("MemberProxy::add()") { + DynamicJsonDocument doc(4096); + MemberProxy mp = doc["hello"]; + + SECTION("add(int)") { + mp.add(42); + + REQUIRE(doc.as() == "{\"hello\":[42]}"); + } + + SECTION("add(const char*)") { + mp.add("world"); + + REQUIRE(doc.as() == "{\"hello\":[\"world\"]}"); + } +} + +TEST_CASE("MemberProxy::clear()") { + DynamicJsonDocument doc(4096); + MemberProxy mp = doc["hello"]; + + SECTION("size goes back to zero") { + mp.add(42); + mp.clear(); + + REQUIRE(mp.size() == 0); + } + + SECTION("isNull() return true") { + mp.add("hello"); + mp.clear(); + + REQUIRE(mp.isNull() == true); + } +} + +TEST_CASE("MemberProxy::operator==()") { + DynamicJsonDocument doc(4096); + + SECTION("1 vs 1") { + doc["a"] = 1; + doc["b"] = 1; + + REQUIRE(doc["a"] <= doc["b"]); + REQUIRE(doc["a"] == doc["b"]); + REQUIRE(doc["a"] >= doc["b"]); + REQUIRE_FALSE(doc["a"] != doc["b"]); + REQUIRE_FALSE(doc["a"] < doc["b"]); + REQUIRE_FALSE(doc["a"] > doc["b"]); + } + + SECTION("1 vs 2") { + doc["a"] = 1; + doc["b"] = 2; + + REQUIRE(doc["a"] != doc["b"]); + REQUIRE(doc["a"] < doc["b"]); + REQUIRE(doc["a"] <= doc["b"]); + REQUIRE_FALSE(doc["a"] == doc["b"]); + REQUIRE_FALSE(doc["a"] > doc["b"]); + REQUIRE_FALSE(doc["a"] >= doc["b"]); + } + + SECTION("'abc' vs 'bcd'") { + doc["a"] = "abc"; + doc["b"] = "bcd"; + + REQUIRE(doc["a"] != doc["b"]); + REQUIRE(doc["a"] < doc["b"]); + REQUIRE(doc["a"] <= doc["b"]); + REQUIRE_FALSE(doc["a"] == doc["b"]); + REQUIRE_FALSE(doc["a"] > doc["b"]); + REQUIRE_FALSE(doc["a"] >= doc["b"]); + } +} + +TEST_CASE("MemberProxy::containsKey()") { + DynamicJsonDocument doc(4096); + MemberProxy mp = doc["hello"]; + + SECTION("containsKey(const char*)") { + mp["key"] = "value"; + + REQUIRE(mp.containsKey("key") == true); + REQUIRE(mp.containsKey("key") == true); + } + + SECTION("containsKey(std::string)") { + mp["key"] = "value"; + + REQUIRE(mp.containsKey(std::string("key")) == true); + REQUIRE(mp.containsKey(std::string("key")) == true); + } +} + +TEST_CASE("MemberProxy::operator|()") { + DynamicJsonDocument doc(4096); + + SECTION("const char*") { + doc["a"] = "hello"; + + REQUIRE((doc["a"] | "world") == std::string("hello")); + REQUIRE((doc["b"] | "world") == std::string("world")); + } + + SECTION("Issue #1411") { + doc["sensor"] = "gps"; + + const char *test = "test"; // <- the literal must be captured in a variable + // to trigger the bug + const char *sensor = doc["sensor"] | test; // "gps" + + REQUIRE(sensor == std::string("gps")); + } + + SECTION("Issue #1415") { + JsonObject object = doc.to(); + object["hello"] = "world"; + + StaticJsonDocument<0> emptyDoc; + JsonObject anotherObject = object["hello"] | emptyDoc.to(); + + REQUIRE(anotherObject.isNull() == false); + REQUIRE(anotherObject.size() == 0); + } +} + +TEST_CASE("MemberProxy::remove()") { + DynamicJsonDocument doc(4096); + MemberProxy mp = doc["hello"]; + + SECTION("remove(int)") { + mp.add(1); + mp.add(2); + mp.add(3); + + mp.remove(1); + + REQUIRE(mp.as() == "[1,3]"); + } + + SECTION("remove(const char *)") { + mp["a"] = 1; + mp["b"] = 2; + + mp.remove("a"); + + REQUIRE(mp.as() == "{\"b\":2}"); + } + + SECTION("remove(std::string)") { + mp["a"] = 1; + mp["b"] = 2; + + mp.remove(std::string("b")); + + REQUIRE(mp.as() == "{\"a\":1}"); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("remove(vla)") { + mp["a"] = 1; + mp["b"] = 2; + + size_t i = 4; + char vla[i]; + strcpy(vla, "b"); + mp.remove(vla); + + REQUIRE(mp.as() == "{\"a\":1}"); + } +#endif +} + +TEST_CASE("MemberProxy::set()") { + DynamicJsonDocument doc(4096); + MemberProxy mp = doc["hello"]; + + SECTION("set(int)") { + mp.set(42); + + REQUIRE(doc.as() == "{\"hello\":42}"); + } + + SECTION("set(const char*)") { + mp.set("world"); + + REQUIRE(doc.as() == "{\"hello\":\"world\"}"); + } + + SECTION("set(char[])") { // issue #1191 + char s[] = "world"; + mp.set(s); + strcpy(s, "!!!!!"); + + REQUIRE(doc.as() == "{\"hello\":\"world\"}"); + } +} + +TEST_CASE("MemberProxy::size()") { + DynamicJsonDocument doc(4096); + MemberProxy mp = doc["hello"]; + + SECTION("returns 0") { + REQUIRE(mp.size() == 0); + } + + SECTION("as an array, return 2") { + mp.add(1); + mp.add(2); + + REQUIRE(mp.size() == 2); + } + + SECTION("as an object, return 2") { + mp["a"] = 1; + mp["b"] = 2; + + REQUIRE(mp.size() == 2); + } +} + +TEST_CASE("MemberProxy::memoryUsage()") { + DynamicJsonDocument doc(4096); + MemberProxy mp = doc["hello"]; + + SECTION("returns 0 when null") { + REQUIRE(mp.memoryUsage() == 0); + } + + SECTION("return the size for a string") { + mp.set(std::string("hello")); + REQUIRE(mp.memoryUsage() == 6); + } +} + +TEST_CASE("MemberProxy::operator[]") { + DynamicJsonDocument doc(4096); + MemberProxy mp = doc["hello"]; + + SECTION("set member") { + mp["world"] = 42; + + REQUIRE(doc.as() == "{\"hello\":{\"world\":42}}"); + } + + SECTION("set element") { + mp[2] = 42; + + REQUIRE(doc.as() == "{\"hello\":[null,null,42]}"); + } +} + +TEST_CASE("MemberProxy cast to JsonVariantConst") { + DynamicJsonDocument doc(4096); + doc["hello"] = "world"; + + const MemberProxy mp = doc["hello"]; + + JsonVariantConst var = mp; + + CHECK(var.as() == "world"); +} + +TEST_CASE("MemberProxy cast to JsonVariant") { + DynamicJsonDocument doc(4096); + doc["hello"] = "world"; + + MemberProxy mp = doc["hello"]; + + JsonVariant var = mp; + + CHECK(var.as() == "world"); + + var.set("toto"); + + CHECK(doc.as() == "{\"hello\":\"toto\"}"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/StaticJsonDocument.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/StaticJsonDocument.cpp new file mode 100644 index 0000000..fe193e4 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/StaticJsonDocument.cpp @@ -0,0 +1,224 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +static void REQUIRE_JSON(JsonDocument& doc, const std::string& expected) { + std::string json; + serializeJson(doc, json); + REQUIRE(json == expected); +} + +TEST_CASE("StaticJsonDocument") { + SECTION("capacity()") { + SECTION("matches template argument") { + StaticJsonDocument<256> doc; + REQUIRE(doc.capacity() == 256); + } + + SECTION("rounds up template argument") { + StaticJsonDocument<253> doc; + REQUIRE(doc.capacity() == 256); + } + } + + SECTION("serializeJson()") { + StaticJsonDocument<200> doc; + JsonObject obj = doc.to(); + obj["hello"] = "world"; + + std::string json; + serializeJson(doc, json); + + REQUIRE(json == "{\"hello\":\"world\"}"); + } + + SECTION("Copy assignment") { + StaticJsonDocument<200> doc1, doc2; + doc1.to().set(666); + deserializeJson(doc2, "{\"hello\":\"world\"}"); + + doc1 = doc2; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Contructor") { + SECTION("Copy constructor") { + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + StaticJsonDocument<200> doc2 = doc1; + + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Construct from StaticJsonDocument of different size") { + StaticJsonDocument<300> doc1; + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + StaticJsonDocument<200> doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Construct from DynamicJsonDocument") { + DynamicJsonDocument doc1(4096); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + StaticJsonDocument<200> doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Construct from JsonObject") { + DynamicJsonDocument doc1(4096); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + StaticJsonDocument<200> doc2 = doc1.as(); + + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Construct from JsonArray") { + DynamicJsonDocument doc1(4096); + deserializeJson(doc1, "[\"hello\",\"world\"]"); + + StaticJsonDocument<200> doc2 = doc1.as(); + + deserializeJson(doc1, "[\"HELLO\",\"WORLD\"]"); + REQUIRE_JSON(doc2, "[\"hello\",\"world\"]"); + } + + SECTION("Construct from JsonVariant") { + DynamicJsonDocument doc1(4096); + deserializeJson(doc1, "42"); + + StaticJsonDocument<200> doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + } + } + + SECTION("Assignment") { + SECTION("Copy assignment") { + StaticJsonDocument<200> doc1, doc2; + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + doc2 = doc1; + + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Assign from StaticJsonDocument of different capacity") { + StaticJsonDocument<200> doc1; + StaticJsonDocument<300> doc2; + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Assign from DynamicJsonDocument") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + doc2 = doc1; + + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Assign from JsonArray") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "[\"hello\",\"world\"]"); + + doc2 = doc1.as(); + + deserializeJson(doc1, "[\"HELLO\",\"WORLD\"]"); + REQUIRE_JSON(doc2, "[\"hello\",\"world\"]"); + } + + SECTION("Assign from JsonArrayConst") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "[\"hello\",\"world\"]"); + + doc2 = doc1.as(); + + deserializeJson(doc1, "[\"HELLO\",\"WORLD\"]"); + REQUIRE_JSON(doc2, "[\"hello\",\"world\"]"); + } + + SECTION("Assign from JsonObject") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + doc2 = doc1.as(); + + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Assign from JsonObjectConst") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + doc2 = doc1.as(); + + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Assign from JsonVariant") { + DynamicJsonDocument doc1(4096); + doc1.to().set(666); + deserializeJson(doc1, "42"); + + StaticJsonDocument<200> doc2; + doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + } + + SECTION("Assign from JsonVariantConst") { + DynamicJsonDocument doc1(4096); + doc1.to().set(666); + deserializeJson(doc1, "42"); + + StaticJsonDocument<200> doc2; + doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + } + } + + SECTION("garbageCollect()") { + StaticJsonDocument<256> doc; + doc[std::string("example")] = std::string("jukebox"); + doc.remove("example"); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 16); + + doc.garbageCollect(); + + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(0)); + REQUIRE_JSON(doc, "{}"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/add.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/add.cpp new file mode 100644 index 0000000..109bd08 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/add.cpp @@ -0,0 +1,22 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonDocument::add()") { + DynamicJsonDocument doc(4096); + + SECTION("integer") { + doc.add(42); + + REQUIRE(doc.as() == "[42]"); + } + + SECTION("const char*") { + doc.add("hello"); + + REQUIRE(doc.as() == "[\"hello\"]"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/cast.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/cast.cpp new file mode 100644 index 0000000..e538f4b --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/cast.cpp @@ -0,0 +1,18 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +#include + +TEST_CASE("Implicit cast to JsonVariant") { + StaticJsonDocument<128> doc; + + doc["hello"] = "world"; + + JsonVariant var = doc; + + CHECK(var.as() == "{\"hello\":\"world\"}"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/compare.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/compare.cpp new file mode 100644 index 0000000..7e9b950 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/compare.cpp @@ -0,0 +1,103 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("DynamicJsonDocument::operator==(const DynamicJsonDocument&)") { + DynamicJsonDocument doc1(4096); + DynamicJsonDocument doc2(4096); + + SECTION("Empty") { + REQUIRE(doc1 == doc2); + REQUIRE_FALSE(doc1 != doc2); + } + + SECTION("With same object") { + doc1["hello"] = "world"; + doc2["hello"] = "world"; + REQUIRE(doc1 == doc2); + REQUIRE_FALSE(doc1 != doc2); + } + SECTION("With different object") { + doc1["hello"] = "world"; + doc2["world"] = "hello"; + REQUIRE_FALSE(doc1 == doc2); + REQUIRE(doc1 != doc2); + } +} + +TEST_CASE("DynamicJsonDocument::operator==(const StaticJsonDocument&)") { + DynamicJsonDocument doc1(4096); + StaticJsonDocument<256> doc2; + + SECTION("Empty") { + REQUIRE(doc1 == doc2); + REQUIRE_FALSE(doc1 != doc2); + } + + SECTION("With same object") { + doc1["hello"] = "world"; + doc2["hello"] = "world"; + REQUIRE(doc1 == doc2); + REQUIRE_FALSE(doc1 != doc2); + } + + SECTION("With different object") { + doc1["hello"] = "world"; + doc2["world"] = "hello"; + REQUIRE_FALSE(doc1 == doc2); + REQUIRE(doc1 != doc2); + } +} + +TEST_CASE("StaticJsonDocument::operator==(const DynamicJsonDocument&)") { + StaticJsonDocument<256> doc1; + DynamicJsonDocument doc2(4096); + + SECTION("Empty") { + REQUIRE(doc1 == doc2); + REQUIRE_FALSE(doc1 != doc2); + } + + SECTION("With same object") { + doc1["hello"] = "world"; + doc2["hello"] = "world"; + REQUIRE(doc1 == doc2); + REQUIRE_FALSE(doc1 != doc2); + } + + SECTION("With different object") { + doc1["hello"] = "world"; + doc2["world"] = "hello"; + REQUIRE_FALSE(doc1 == doc2); + REQUIRE(doc1 != doc2); + } +} + +TEST_CASE("JsonDocument::operator==(const JsonDocument&)") { + StaticJsonDocument<256> doc1; + StaticJsonDocument<256> doc2; + const JsonDocument& ref1 = doc1; + const JsonDocument& ref2 = doc2; + + SECTION("Empty") { + REQUIRE(ref1 == ref2); + REQUIRE_FALSE(ref1 != ref2); + } + + SECTION("With same object") { + doc1["hello"] = "world"; + doc2["hello"] = "world"; + REQUIRE(ref1 == ref2); + REQUIRE_FALSE(ref1 != ref2); + } + + SECTION("With different object") { + doc1["hello"] = "world"; + doc2["world"] = "hello"; + REQUIRE_FALSE(ref1 == ref2); + REQUIRE(ref1 != ref2); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/containsKey.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/containsKey.cpp new file mode 100644 index 0000000..6936043 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/containsKey.cpp @@ -0,0 +1,44 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonDocument::containsKey()") { + DynamicJsonDocument doc(4096); + + SECTION("returns true on object") { + doc["hello"] = "world"; + + REQUIRE(doc.containsKey("hello") == true); + } + + SECTION("returns true when value is null") { + doc["hello"] = static_cast(0); + + REQUIRE(doc.containsKey("hello") == true); + } + + SECTION("returns true when key is a std::string") { + doc["hello"] = "world"; + + REQUIRE(doc.containsKey(std::string("hello")) == true); + } + + SECTION("returns false on object") { + doc["world"] = "hello"; + + REQUIRE(doc.containsKey("hello") == false); + } + + SECTION("returns false on array") { + doc.add("hello"); + + REQUIRE(doc.containsKey("hello") == false); + } + + SECTION("returns false on null") { + REQUIRE(doc.containsKey("hello") == false); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/createNested.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/createNested.cpp new file mode 100644 index 0000000..258f051 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/createNested.cpp @@ -0,0 +1,66 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonDocument::createNestedArray()") { + DynamicJsonDocument doc(4096); + + SECTION("promotes to array") { + doc.createNestedArray(); + + REQUIRE(doc.is()); + } +} + +TEST_CASE("JsonDocument::createNestedArray(key)") { + DynamicJsonDocument doc(4096); + + SECTION("key is const char*") { + SECTION("promotes to object") { + doc.createNestedArray("hello"); + + REQUIRE(doc.is()); + } + } + + SECTION("key is std::string") { + SECTION("promotes to object") { + doc.createNestedArray(std::string("hello")); + + REQUIRE(doc.is()); + } + } +} + +TEST_CASE("JsonDocument::createNestedObject()") { + DynamicJsonDocument doc(4096); + + SECTION("promotes to array") { + doc.createNestedObject(); + + REQUIRE(doc.is()); + } +} + +TEST_CASE("JsonDocument::createNestedObject(key)") { + DynamicJsonDocument doc(4096); + + SECTION("key is const char*") { + SECTION("promotes to object") { + doc.createNestedObject("hello"); + + REQUIRE(doc.is()); + } + } + + SECTION("key is std::string") { + SECTION("promotes to object") { + doc.createNestedObject(std::string("hello")); + + REQUIRE(doc.is()); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/isNull.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/isNull.cpp new file mode 100644 index 0000000..cfb70dd --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/isNull.cpp @@ -0,0 +1,39 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonDocument::isNull()") { + DynamicJsonDocument doc(4096); + + SECTION("returns true if uninitialized") { + REQUIRE(doc.isNull() == true); + } + + SECTION("returns false after to()") { + doc.to(); + REQUIRE(doc.isNull() == false); + } + + SECTION("returns false after to()") { + doc.to(); + REQUIRE(doc.isNull() == false); + } + + SECTION("returns true after to()") { + REQUIRE(doc.isNull() == true); + } + + SECTION("returns false after set()") { + doc.to().set(42); + REQUIRE(doc.isNull() == false); + } + + SECTION("returns true after clear()") { + doc.to(); + doc.clear(); + REQUIRE(doc.isNull() == true); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/nesting.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/nesting.cpp new file mode 100644 index 0000000..3284dd1 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/nesting.cpp @@ -0,0 +1,30 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonDocument::nesting()") { + DynamicJsonDocument doc(4096); + + SECTION("return 0 if uninitialized") { + REQUIRE(doc.nesting() == 0); + } + + SECTION("returns 0 for string") { + JsonVariant var = doc.to(); + var.set("hello"); + REQUIRE(doc.nesting() == 0); + } + + SECTION("returns 1 for empty object") { + doc.to(); + REQUIRE(doc.nesting() == 1); + } + + SECTION("returns 1 for empty array") { + doc.to(); + REQUIRE(doc.nesting() == 1); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/overflowed.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/overflowed.cpp new file mode 100644 index 0000000..cea0db9 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/overflowed.cpp @@ -0,0 +1,85 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonDocument::overflowed()") { + SECTION("returns false on a fresh object") { + StaticJsonDocument<0> doc; + CHECK(doc.overflowed() == false); + } + + SECTION("returns true after a failed insertion") { + StaticJsonDocument<0> doc; + doc.add(0); + CHECK(doc.overflowed() == true); + } + + SECTION("returns false after successful insertion") { + StaticJsonDocument doc; + doc.add(0); + CHECK(doc.overflowed() == false); + } + + SECTION("returns true after a failed string copy") { + StaticJsonDocument doc; + doc.add(std::string("example")); + CHECK(doc.overflowed() == true); + } + + SECTION("returns false after a successful string copy") { + StaticJsonDocument doc; + doc.add(std::string("example")); + CHECK(doc.overflowed() == false); + } + + SECTION("returns true after a failed member add") { + StaticJsonDocument<1> doc; + doc["example"] = true; + CHECK(doc.overflowed() == true); + } + + SECTION("returns true after a failed deserialization") { + StaticJsonDocument doc; + deserializeJson(doc, "[\"example\"]"); + CHECK(doc.overflowed() == true); + } + + SECTION("returns false after a successful deserialization") { + StaticJsonDocument doc; + deserializeJson(doc, "[\"example\"]"); + CHECK(doc.overflowed() == false); + } + + SECTION("returns false after clear()") { + StaticJsonDocument<0> doc; + doc.add(0); + doc.clear(); + CHECK(doc.overflowed() == false); + } + + SECTION("remains false after shrinkToFit()") { + DynamicJsonDocument doc(JSON_ARRAY_SIZE(1)); + doc.add(0); + doc.shrinkToFit(); + CHECK(doc.overflowed() == false); + } + + SECTION("remains true after shrinkToFit()") { + DynamicJsonDocument doc(JSON_ARRAY_SIZE(1)); + doc.add(0); + doc.add(0); + doc.shrinkToFit(); + CHECK(doc.overflowed() == true); + } + + SECTION("return false after garbageCollect()") { + DynamicJsonDocument doc(JSON_ARRAY_SIZE(1)); + doc.add(0); + doc.add(0); + doc.garbageCollect(); + CHECK(doc.overflowed() == false); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/remove.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/remove.cpp new file mode 100644 index 0000000..b777073 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/remove.cpp @@ -0,0 +1,52 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonDocument::remove()") { + DynamicJsonDocument doc(4096); + + SECTION("remove(int)") { + doc.add(1); + doc.add(2); + doc.add(3); + + doc.remove(1); + + REQUIRE(doc.as() == "[1,3]"); + } + + SECTION("remove(const char *)") { + doc["a"] = 1; + doc["b"] = 2; + + doc.remove("a"); + + REQUIRE(doc.as() == "{\"b\":2}"); + } + + SECTION("remove(std::string)") { + doc["a"] = 1; + doc["b"] = 2; + + doc.remove(std::string("b")); + + REQUIRE(doc.as() == "{\"a\":1}"); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("remove(vla)") { + doc["a"] = 1; + doc["b"] = 2; + + size_t i = 4; + char vla[i]; + strcpy(vla, "b"); + doc.remove(vla); + + REQUIRE(doc.as() == "{\"a\":1}"); + } +#endif +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/shrinkToFit.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/shrinkToFit.cpp new file mode 100644 index 0000000..0eb34e8 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/shrinkToFit.cpp @@ -0,0 +1,154 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +#include // malloc, free +#include + +using ARDUINOJSON_NAMESPACE::addPadding; + +class ArmoredAllocator { + public: + ArmoredAllocator() : _ptr(0), _size(0) {} + + void* allocate(size_t size) { + _ptr = malloc(size); + _size = size; + return _ptr; + } + + void deallocate(void* ptr) { + REQUIRE(ptr == _ptr); + free(ptr); + _ptr = 0; + _size = 0; + } + + void* reallocate(void* ptr, size_t new_size) { + REQUIRE(ptr == _ptr); + // don't call realloc, instead alloc a new buffer and erase the old one + // this way we make sure we support relocation + void* new_ptr = malloc(new_size); + memcpy(new_ptr, _ptr, std::min(new_size, _size)); + memset(_ptr, '#', _size); // erase + free(_ptr); + _ptr = new_ptr; + return new_ptr; + } + + private: + void* _ptr; + size_t _size; +}; + +typedef BasicJsonDocument ShrinkToFitTestDocument; + +void testShrinkToFit(ShrinkToFitTestDocument& doc, std::string expected_json, + size_t expected_size) { + // test twice: shrinkToFit() should be idempotent + for (int i = 0; i < 2; i++) { + doc.shrinkToFit(); + + REQUIRE(doc.capacity() == expected_size); + REQUIRE(doc.memoryUsage() == expected_size); + + std::string json; + serializeJson(doc, json); + REQUIRE(json == expected_json); + } +} + +TEST_CASE("BasicJsonDocument::shrinkToFit()") { + ShrinkToFitTestDocument doc(4096); + + SECTION("null") { + testShrinkToFit(doc, "null", 0); + } + + SECTION("empty object") { + deserializeJson(doc, "{}"); + testShrinkToFit(doc, "{}", JSON_OBJECT_SIZE(0)); + } + + SECTION("empty array") { + deserializeJson(doc, "[]"); + testShrinkToFit(doc, "[]", JSON_ARRAY_SIZE(0)); + } + + SECTION("linked string") { + doc.set("hello"); + testShrinkToFit(doc, "\"hello\"", 0); + } + + SECTION("owned string") { + doc.set(std::string("abcdefg")); + testShrinkToFit(doc, "\"abcdefg\"", 8); + } + + SECTION("linked raw") { + doc.set(serialized("[{},123]")); + testShrinkToFit(doc, "[{},123]", 0); + } + + SECTION("owned raw") { + doc.set(serialized(std::string("[{},12]"))); + testShrinkToFit(doc, "[{},12]", 8); + } + + SECTION("linked key") { + doc["key"] = 42; + testShrinkToFit(doc, "{\"key\":42}", JSON_OBJECT_SIZE(1)); + } + + SECTION("owned key") { + doc[std::string("abcdefg")] = 42; + testShrinkToFit(doc, "{\"abcdefg\":42}", JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("linked string in array") { + doc.add("hello"); + testShrinkToFit(doc, "[\"hello\"]", JSON_ARRAY_SIZE(1)); + } + + SECTION("owned string in array") { + doc.add(std::string("abcdefg")); + testShrinkToFit(doc, "[\"abcdefg\"]", JSON_ARRAY_SIZE(1) + 8); + } + + SECTION("linked string in object") { + doc["key"] = "hello"; + testShrinkToFit(doc, "{\"key\":\"hello\"}", JSON_OBJECT_SIZE(1)); + } + + SECTION("owned string in object") { + doc["key"] = std::string("abcdefg"); + testShrinkToFit(doc, "{\"key\":\"abcdefg\"}", JSON_ARRAY_SIZE(1) + 8); + } + + SECTION("unaligned") { + doc.add(std::string("?")); // two bytes in the string pool + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 2); + + doc.shrinkToFit(); + + // the new capacity should be padded to align the pointers + REQUIRE(doc.capacity() == JSON_OBJECT_SIZE(1) + sizeof(void*)); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 2); + REQUIRE(doc[0] == "?"); + } +} + +TEST_CASE("DynamicJsonDocument::shrinkToFit()") { + DynamicJsonDocument doc(4096); + + deserializeJson(doc, "{\"hello\":[\"world\"]"); + + doc.shrinkToFit(); + + std::string json; + serializeJson(doc, json); + REQUIRE(json == "{\"hello\":[\"world\"]}"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/size.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/size.cpp new file mode 100644 index 0000000..4fbb6e2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/size.cpp @@ -0,0 +1,28 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonDocument::size()") { + DynamicJsonDocument doc(4096); + + SECTION("returns 0") { + REQUIRE(doc.size() == 0); + } + + SECTION("as an array, return 2") { + doc.add(1); + doc.add(2); + + REQUIRE(doc.size() == 2); + } + + SECTION("as an object, return 2") { + doc["a"] = 1; + doc["b"] = 2; + + REQUIRE(doc.size() == 2); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/subscript.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/subscript.cpp new file mode 100644 index 0000000..4ba6ff8 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/subscript.cpp @@ -0,0 +1,53 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonDocument::operator[]") { + DynamicJsonDocument doc(4096); + const JsonDocument& cdoc = doc; + + SECTION("object") { + deserializeJson(doc, "{\"hello\":\"world\"}"); + + SECTION("const char*") { + REQUIRE(doc["hello"] == "world"); + REQUIRE(cdoc["hello"] == "world"); + } + + SECTION("std::string") { + REQUIRE(doc[std::string("hello")] == "world"); + REQUIRE(cdoc[std::string("hello")] == "world"); + } + + SECTION("supports operator|") { + REQUIRE((doc["hello"] | "nope") == std::string("world")); + REQUIRE((doc["world"] | "nope") == std::string("nope")); + } + } + + SECTION("array") { + deserializeJson(doc, "[\"hello\",\"world\"]"); + + REQUIRE(doc[1] == "world"); + REQUIRE(cdoc[1] == "world"); + } +} + +TEST_CASE("JsonDocument automatically promotes to object") { + DynamicJsonDocument doc(4096); + + doc["one"]["two"]["three"] = 4; + + REQUIRE(doc["one"]["two"]["three"] == 4); +} + +TEST_CASE("JsonDocument automatically promotes to array") { + DynamicJsonDocument doc(4096); + + doc[2] = 2; + + REQUIRE(doc.as() == "[null,null,2]"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/swap.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/swap.cpp new file mode 100644 index 0000000..60d672f --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonDocument/swap.cpp @@ -0,0 +1,27 @@ +#include + +#include +#include +#include + +using namespace std; + +TEST_CASE("std::swap") { + SECTION("DynamicJsonDocument*") { + DynamicJsonDocument *p1, *p2; + swap(p1, p2); // issue #1678 + } + + SECTION("DynamicJsonDocument") { + DynamicJsonDocument doc1(0x10), doc2(0x20); + doc1.set("hello"); + doc2.set("world"); + + swap(doc1, doc2); + + CHECK(doc1.capacity() == 0x20); + CHECK(doc1.as() == "world"); + CHECK(doc2.capacity() == 0x10); + CHECK(doc2.as() == "hello"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/CMakeLists.txt new file mode 100644 index 0000000..9e5ac48 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/CMakeLists.txt @@ -0,0 +1,28 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(JsonObjectTests + clear.cpp + containsKey.cpp + copy.cpp + createNestedArray.cpp + createNestedObject.cpp + equals.cpp + invalid.cpp + isNull.cpp + iterator.cpp + memoryUsage.cpp + nesting.cpp + remove.cpp + size.cpp + std_string.cpp + subscript.cpp +) + +add_test(JsonObject JsonObjectTests) + +set_tests_properties(JsonObject + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/clear.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/clear.cpp new file mode 100644 index 0000000..da17b2c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/clear.cpp @@ -0,0 +1,25 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonObject::clear()") { + SECTION("No-op on null JsonObject") { + JsonObject obj; + obj.clear(); + REQUIRE(obj.isNull() == true); + REQUIRE(obj.size() == 0); + } + + SECTION("Removes all elements") { + StaticJsonDocument<64> doc; + JsonObject obj = doc.to(); + obj["hello"] = 1; + obj["world"] = 2; + obj.clear(); + REQUIRE(obj.size() == 0); + REQUIRE(obj.isNull() == false); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/containsKey.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/containsKey.cpp new file mode 100644 index 0000000..4f37f7e --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/containsKey.cpp @@ -0,0 +1,39 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonObject::containsKey()") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + obj["hello"] = 42; + + SECTION("returns true only if key is present") { + REQUIRE(false == obj.containsKey("world")); + REQUIRE(true == obj.containsKey("hello")); + } + + SECTION("works with JsonObjectConst") { + JsonObjectConst cobj = obj; + REQUIRE(false == cobj.containsKey("world")); + REQUIRE(true == cobj.containsKey("hello")); + } + + SECTION("returns false after remove()") { + obj.remove("hello"); + + REQUIRE(false == obj.containsKey("hello")); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("key is a VLA") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + REQUIRE(true == obj.containsKey(vla)); + } +#endif +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/copy.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/copy.cpp new file mode 100644 index 0000000..c5514a2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/copy.cpp @@ -0,0 +1,115 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonObject::set()") { + DynamicJsonDocument doc1(4096); + DynamicJsonDocument doc2(4096); + + JsonObject obj1 = doc1.to(); + JsonObject obj2 = doc2.to(); + + SECTION("doesn't copy static string in key or value") { + obj1["hello"] = "world"; + + bool success = obj2.set(obj1); + + REQUIRE(success == true); + REQUIRE(doc1.memoryUsage() == doc2.memoryUsage()); + REQUIRE(obj2["hello"] == std::string("world")); + } + + SECTION("copy local string value") { + obj1["hello"] = std::string("world"); + + bool success = obj2.set(obj1); + + REQUIRE(success == true); + REQUIRE(doc1.memoryUsage() == doc2.memoryUsage()); + REQUIRE(obj2["hello"] == std::string("world")); + } + + SECTION("copy local key") { + obj1[std::string("hello")] = "world"; + + bool success = obj2.set(obj1); + + REQUIRE(success == true); + REQUIRE(doc1.memoryUsage() == doc2.memoryUsage()); + REQUIRE(obj2["hello"] == std::string("world")); + } + + SECTION("copy string from deserializeJson()") { + deserializeJson(doc1, "{'hello':'world'}"); + + bool success = obj2.set(obj1); + + REQUIRE(success == true); + REQUIRE(doc1.memoryUsage() == doc2.memoryUsage()); + REQUIRE(obj2["hello"] == std::string("world")); + } + + SECTION("copy string from deserializeMsgPack()") { + deserializeMsgPack(doc1, "\x81\xA5hello\xA5world"); + + bool success = obj2.set(obj1); + + REQUIRE(success == true); + REQUIRE(doc1.memoryUsage() == doc2.memoryUsage()); + REQUIRE(obj2["hello"] == std::string("world")); + } + + SECTION("should work with JsonObjectConst") { + obj1["hello"] = "world"; + + obj2.set(static_cast(obj1)); + + REQUIRE(doc1.memoryUsage() == doc2.memoryUsage()); + REQUIRE(obj2["hello"] == std::string("world")); + } + + SECTION("destination too small to store the key") { + StaticJsonDocument doc3; + JsonObject obj3 = doc3.to(); + + obj1[std::string("hello")] = "world"; + + bool success = obj3.set(obj1); + + REQUIRE(success == false); + REQUIRE(doc3.as() == "{}"); + } + + SECTION("destination too small to store the value") { + StaticJsonDocument doc3; + JsonObject obj3 = doc3.to(); + + obj1["hello"] = std::string("world"); + + bool success = obj3.set(obj1); + + REQUIRE(success == false); + REQUIRE(doc3.as() == "{\"hello\":null}"); + } + + SECTION("destination is null") { + JsonObject null; + obj1["hello"] = "world"; + + bool success = null.set(obj1); + + REQUIRE(success == false); + } + + SECTION("source is null") { + JsonObject null; + obj1["hello"] = "world"; + + bool success = obj1.set(null); + + REQUIRE(success == false); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/createNestedArray.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/createNestedArray.cpp new file mode 100644 index 0000000..4d43038 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/createNestedArray.cpp @@ -0,0 +1,27 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonObject::createNestedArray()") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + + SECTION("key is a const char*") { + JsonArray arr = obj.createNestedArray("hello"); + REQUIRE(arr.isNull() == false); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("key is a VLA") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + JsonArray arr = obj.createNestedArray(vla); + REQUIRE(arr.isNull() == false); + } +#endif +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/createNestedObject.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/createNestedObject.cpp new file mode 100644 index 0000000..29f4d39 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/createNestedObject.cpp @@ -0,0 +1,25 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonObject::createNestedObject()") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + + SECTION("key is a const char*") { + obj.createNestedObject("hello"); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("key is a VLA") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + obj.createNestedObject(vla); + } +#endif +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/equals.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/equals.cpp new file mode 100644 index 0000000..101c612 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/equals.cpp @@ -0,0 +1,67 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonObject::operator==()") { + DynamicJsonDocument doc1(4096); + JsonObject obj1 = doc1.to(); + JsonObjectConst obj1c = obj1; + + DynamicJsonDocument doc2(4096); + JsonObject obj2 = doc2.to(); + JsonObjectConst obj2c = obj2; + + SECTION("should return false when objs differ") { + obj1["hello"] = "coucou"; + obj2["world"] = 1; + + REQUIRE_FALSE(obj1 == obj2); + REQUIRE_FALSE(obj1c == obj2c); + } + + SECTION("should return false when LHS has more elements") { + obj1["hello"] = "coucou"; + obj1["world"] = 666; + obj2["hello"] = "coucou"; + + REQUIRE_FALSE(obj1 == obj2); + REQUIRE_FALSE(obj1c == obj2c); + } + + SECTION("should return false when RKS has more elements") { + obj1["hello"] = "coucou"; + obj2["hello"] = "coucou"; + obj2["world"] = 666; + + REQUIRE_FALSE(obj1 == obj2); + REQUIRE_FALSE(obj1c == obj2c); + } + + SECTION("should return true when objs equal") { + obj1["hello"] = "world"; + obj1["anwser"] = 42; + // insert in different order + obj2["anwser"] = 42; + obj2["hello"] = "world"; + + REQUIRE(obj1 == obj2); + REQUIRE(obj1c == obj2c); + } + + SECTION("should return false when RHS is null") { + JsonObject null; + + REQUIRE_FALSE(obj1 == null); + REQUIRE_FALSE(obj1c == null); + } + + SECTION("should return false when LHS is null") { + JsonObject null; + + REQUIRE_FALSE(null == obj2); + REQUIRE_FALSE(null == obj2c); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/invalid.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/invalid.cpp new file mode 100644 index 0000000..199c8f0 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/invalid.cpp @@ -0,0 +1,35 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace Catch::Matchers; + +TEST_CASE("JsonObject::invalid()") { + JsonObject obj; + + SECTION("SubscriptFails") { + REQUIRE(obj["key"].isNull()); + } + + SECTION("AddFails") { + obj["hello"] = "world"; + REQUIRE(0 == obj.size()); + } + + SECTION("CreateNestedArrayFails") { + REQUIRE(obj.createNestedArray("hello").isNull()); + } + + SECTION("CreateNestedObjectFails") { + REQUIRE(obj.createNestedObject("world").isNull()); + } + + SECTION("serialize to 'null'") { + char buffer[32]; + serializeJson(obj, buffer, sizeof(buffer)); + REQUIRE_THAT(buffer, Equals("null")); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/isNull.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/isNull.cpp new file mode 100644 index 0000000..4440348 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/isNull.cpp @@ -0,0 +1,58 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonObject::isNull()") { + SECTION("returns true") { + JsonObject obj; + REQUIRE(obj.isNull() == true); + } + + SECTION("returns false") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + REQUIRE(obj.isNull() == false); + } +} + +TEST_CASE("JsonObjectConst::isNull()") { + SECTION("returns true") { + JsonObjectConst obj; + REQUIRE(obj.isNull() == true); + } + + SECTION("returns false") { + DynamicJsonDocument doc(4096); + JsonObjectConst obj = doc.to(); + REQUIRE(obj.isNull() == false); + } +} + +TEST_CASE("JsonObject::operator bool()") { + SECTION("returns false") { + JsonObject obj; + REQUIRE(static_cast(obj) == false); + } + + SECTION("returns true") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + REQUIRE(static_cast(obj) == true); + } +} + +TEST_CASE("JsonObjectConst::operator bool()") { + SECTION("returns false") { + JsonObjectConst obj; + REQUIRE(static_cast(obj) == false); + } + + SECTION("returns true") { + DynamicJsonDocument doc(4096); + JsonObjectConst obj = doc.to(); + REQUIRE(static_cast(obj) == true); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/iterator.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/iterator.cpp new file mode 100644 index 0000000..e9f2cd2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/iterator.cpp @@ -0,0 +1,73 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace Catch::Matchers; + +TEST_CASE("JsonObject::begin()/end()") { + StaticJsonDocument doc; + JsonObject obj = doc.to(); + obj["ab"] = 12; + obj["cd"] = 34; + + SECTION("NonConstIterator") { + JsonObject::iterator it = obj.begin(); + REQUIRE(obj.end() != it); + REQUIRE(it->key() == "ab"); + REQUIRE(12 == it->value()); + ++it; + REQUIRE(obj.end() != it); + REQUIRE(it->key() == "cd"); + REQUIRE(34 == it->value()); + ++it; + REQUIRE(obj.end() == it); + } + + SECTION("Dereferencing end() is safe") { + REQUIRE(obj.end()->key().isNull()); + REQUIRE(obj.end()->value().isNull()); + } + + SECTION("null JsonObject") { + JsonObject null; + REQUIRE(null.begin() == null.end()); + } +} + +TEST_CASE("JsonObjectConst::begin()/end()") { + StaticJsonDocument doc; + JsonObject obj = doc.to(); + obj["ab"] = 12; + obj["cd"] = 34; + + JsonObjectConst cobj = obj; + + SECTION("Iteration") { + JsonObjectConst::iterator it = cobj.begin(); + REQUIRE(cobj.end() != it); + REQUIRE(it->key() == "ab"); + REQUIRE(12 == it->value()); + + ++it; + REQUIRE(cobj.end() != it); + JsonPairConst pair = *it; + REQUIRE(pair.key() == "cd"); + REQUIRE(34 == pair.value()); + + ++it; + REQUIRE(cobj.end() == it); + } + + SECTION("Dereferencing end() is safe") { + REQUIRE(cobj.end()->key().isNull()); + REQUIRE(cobj.end()->value().isNull()); + } + + SECTION("null JsonObjectConst") { + JsonObjectConst null; + REQUIRE(null.begin() == null.end()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/memoryUsage.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/memoryUsage.cpp new file mode 100644 index 0000000..835ee4a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/memoryUsage.cpp @@ -0,0 +1,43 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +TEST_CASE("JsonObject::memoryUsage()") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + + SECTION("return 0 if uninitialized") { + JsonObject unitialized; + REQUIRE(unitialized.memoryUsage() == 0); + } + + SECTION("JSON_OBJECT_SIZE(0) for empty object") { + REQUIRE(obj.memoryUsage() == JSON_OBJECT_SIZE(0)); + } + + SECTION("JSON_OBJECT_SIZE(1) after add") { + obj["hello"] = 42; + REQUIRE(obj.memoryUsage() == JSON_OBJECT_SIZE(1)); + } + + SECTION("includes the size of the key") { + obj[std::string("hello")] = 42; + REQUIRE(obj.memoryUsage() == JSON_OBJECT_SIZE(1) + 6); + } + + SECTION("includes the size of the nested array") { + JsonArray nested = obj.createNestedArray("nested"); + nested.add(42); + REQUIRE(obj.memoryUsage() == JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(1)); + } + + SECTION("includes the size of the nested object") { + JsonObject nested = obj.createNestedObject("nested"); + nested["hello"] = "world"; + REQUIRE(obj.memoryUsage() == 2 * JSON_OBJECT_SIZE(1)); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/nesting.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/nesting.cpp new file mode 100644 index 0000000..7937c28 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/nesting.cpp @@ -0,0 +1,35 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonObject::nesting()") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + + SECTION("return 0 if uninitialized") { + JsonObject unitialized; + REQUIRE(unitialized.nesting() == 0); + } + + SECTION("returns 1 for empty object") { + REQUIRE(obj.nesting() == 1); + } + + SECTION("returns 1 for flat object") { + obj["hello"] = "world"; + REQUIRE(obj.nesting() == 1); + } + + SECTION("returns 2 with nested array") { + obj.createNestedArray("nested"); + REQUIRE(obj.nesting() == 2); + } + + SECTION("returns 2 with nested object") { + obj.createNestedObject("nested"); + REQUIRE(obj.nesting() == 2); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/remove.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/remove.cpp new file mode 100644 index 0000000..f1cb20c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/remove.cpp @@ -0,0 +1,82 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +TEST_CASE("JsonObject::remove()") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + obj["a"] = 0; + obj["b"] = 1; + obj["c"] = 2; + std::string result; + + SECTION("remove(key)") { + SECTION("Remove first") { + obj.remove("a"); + serializeJson(obj, result); + REQUIRE("{\"b\":1,\"c\":2}" == result); + } + + SECTION("Remove middle") { + obj.remove("b"); + serializeJson(obj, result); + REQUIRE("{\"a\":0,\"c\":2}" == result); + } + + SECTION("Remove last") { + obj.remove("c"); + serializeJson(obj, result); + REQUIRE("{\"a\":0,\"b\":1}" == result); + } + } + + SECTION("remove(iterator)") { + JsonObject::iterator it = obj.begin(); + + SECTION("Remove first") { + obj.remove(it); + serializeJson(obj, result); + REQUIRE("{\"b\":1,\"c\":2}" == result); + } + + SECTION("Remove middle") { + ++it; + obj.remove(it); + serializeJson(obj, result); + REQUIRE("{\"a\":0,\"c\":2}" == result); + } + + SECTION("Remove last") { + it += 2; + obj.remove(it); + serializeJson(obj, result); + REQUIRE("{\"a\":0,\"b\":1}" == result); + } + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("key is a vla") { + size_t i = 16; + char vla[i]; + strcpy(vla, "b"); + obj.remove(vla); + + serializeJson(obj, result); + REQUIRE("{\"a\":0,\"c\":2}" == result); + } +#endif + + SECTION("remove by key on unbound reference") { + JsonObject unboundObject; + unboundObject.remove("key"); + } + + SECTION("remove by iterator on unbound reference") { + JsonObject unboundObject; + unboundObject.remove(unboundObject.begin()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/size.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/size.cpp new file mode 100644 index 0000000..0d82cfb --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/size.cpp @@ -0,0 +1,39 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +TEST_CASE("JsonObject::size()") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + + SECTION("initial size is zero") { + REQUIRE(0 == obj.size()); + } + + SECTION("increases when values are added") { + obj["hello"] = 42; + REQUIRE(1 == obj.size()); + } + + SECTION("decreases when values are removed") { + obj["hello"] = 42; + obj.remove("hello"); + REQUIRE(0 == obj.size()); + } + + SECTION("doesn't increase when the same key is added twice") { + obj["hello"] = 1; + obj["hello"] = 2; + REQUIRE(1 == obj.size()); + } + + SECTION("doesn't decrease when another key is removed") { + obj["hello"] = 1; + obj.remove("world"); + REQUIRE(1 == obj.size()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/std_string.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/std_string.cpp new file mode 100644 index 0000000..e985421 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/std_string.cpp @@ -0,0 +1,109 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +static void eraseString(std::string &str) { + char *p = const_cast(str.c_str()); + while (*p) *p++ = '*'; +} + +TEST_CASE("std::string") { + DynamicJsonDocument doc(4096); + + SECTION("operator[]") { + char json[] = "{\"key\":\"value\"}"; + + deserializeJson(doc, json); + JsonObject obj = doc.as(); + + REQUIRE(std::string("value") == obj[std::string("key")]); + } + + SECTION("operator[] const") { + char json[] = "{\"key\":\"value\"}"; + + deserializeJson(doc, json); + JsonObject obj = doc.as(); + + REQUIRE(std::string("value") == obj[std::string("key")]); + } + + SECTION("createNestedObject()") { + JsonObject obj = doc.to(); + std::string key = "key"; + char json[64]; + obj.createNestedObject(key); + eraseString(key); + serializeJson(doc, json, sizeof(json)); + REQUIRE(std::string("{\"key\":{}}") == json); + } + + SECTION("createNestedArray()") { + JsonObject obj = doc.to(); + std::string key = "key"; + char json[64]; + obj.createNestedArray(key); + eraseString(key); + serializeJson(doc, json, sizeof(json)); + REQUIRE(std::string("{\"key\":[]}") == json); + } + + SECTION("containsKey()") { + char json[] = "{\"key\":\"value\"}"; + deserializeJson(doc, json); + JsonObject obj = doc.as(); + REQUIRE(true == obj.containsKey(std::string("key"))); + } + + SECTION("remove()") { + JsonObject obj = doc.to(); + obj["key"] = "value"; + + obj.remove(std::string("key")); + + REQUIRE(0 == obj.size()); + } + + SECTION("operator[], set key") { + std::string key("hello"); + JsonObject obj = doc.to(); + obj[key] = "world"; + eraseString(key); + REQUIRE(std::string("world") == obj["hello"]); + } + + SECTION("operator[], set value") { + std::string value("world"); + JsonObject obj = doc.to(); + obj["hello"] = value; + eraseString(value); + REQUIRE(std::string("world") == obj["hello"]); + } + + SECTION("memoryUsage() increases when adding a new key") { + std::string key1("hello"), key2("world"); + JsonObject obj = doc.to(); + + obj[key1] = 1; + size_t sizeBefore = doc.memoryUsage(); + obj[key2] = 2; + size_t sizeAfter = doc.memoryUsage(); + + REQUIRE(sizeAfter - sizeBefore >= key2.size()); + } + + SECTION("memoryUsage() remains when adding the same key") { + std::string key("hello"); + JsonObject obj = doc.to(); + + obj[key] = 1; + size_t sizeBefore = doc.memoryUsage(); + obj[key] = 2; + size_t sizeAfter = doc.memoryUsage(); + + REQUIRE(sizeBefore == sizeAfter); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/subscript.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/subscript.cpp new file mode 100644 index 0000000..e8242f6 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonObject/subscript.cpp @@ -0,0 +1,233 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonObject::operator[]") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + + SECTION("int") { + obj["hello"] = 123; + + REQUIRE(123 == obj["hello"].as()); + REQUIRE(true == obj["hello"].is()); + REQUIRE(false == obj["hello"].is()); + } + + SECTION("volatile int") { // issue #415 + volatile int i = 123; + obj["hello"] = i; + + REQUIRE(123 == obj["hello"].as()); + REQUIRE(true == obj["hello"].is()); + REQUIRE(false == obj["hello"].is()); + } + + SECTION("double") { + obj["hello"] = 123.45; + + REQUIRE(true == obj["hello"].is()); + REQUIRE(false == obj["hello"].is()); + REQUIRE(123.45 == obj["hello"].as()); + } + + SECTION("bool") { + obj["hello"] = true; + + REQUIRE(true == obj["hello"].is()); + REQUIRE(false == obj["hello"].is()); + REQUIRE(true == obj["hello"].as()); + } + + SECTION("const char*") { + obj["hello"] = "h3110"; + + REQUIRE(true == obj["hello"].is()); + REQUIRE(false == obj["hello"].is()); + REQUIRE(std::string("h3110") == obj["hello"].as()); + } + + SECTION("array") { + DynamicJsonDocument doc2(4096); + JsonArray arr = doc2.to(); + + obj["hello"] = arr; + + REQUIRE(arr == obj["hello"].as()); + REQUIRE(true == obj["hello"].is()); + REQUIRE(false == obj["hello"].is()); + } + + SECTION("object") { + DynamicJsonDocument doc2(4096); + JsonObject obj2 = doc2.to(); + + obj["hello"] = obj2; + + REQUIRE(obj2 == obj["hello"].as()); + REQUIRE(true == obj["hello"].is()); + REQUIRE(false == obj["hello"].is()); + } + + SECTION("array subscript") { + DynamicJsonDocument doc2(4096); + JsonArray arr = doc2.to(); + arr.add(42); + + obj["a"] = arr[0]; + + REQUIRE(42 == obj["a"]); + } + + SECTION("object subscript") { + DynamicJsonDocument doc2(4096); + JsonObject obj2 = doc2.to(); + obj2["x"] = 42; + + obj["a"] = obj2["x"]; + + REQUIRE(42 == obj["a"]); + } + + SECTION("char key[]") { // issue #423 + char key[] = "hello"; + obj[key] = 42; + REQUIRE(42 == obj[key]); + } + + SECTION("should not duplicate const char*") { + obj["hello"] = "world"; + const size_t expectedSize = JSON_OBJECT_SIZE(1); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate char* value") { + obj["hello"] = const_cast("world"); + const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate char* key") { + obj[const_cast("hello")] = "world"; + const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate char* key&value") { + obj[const_cast("hello")] = const_cast("world"); + const size_t expectedSize = JSON_OBJECT_SIZE(1) + 2 * JSON_STRING_SIZE(5); + REQUIRE(expectedSize <= doc.memoryUsage()); + } + + SECTION("should duplicate std::string value") { + obj["hello"] = std::string("world"); + const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate std::string key") { + obj[std::string("hello")] = "world"; + const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should duplicate std::string key&value") { + obj[std::string("hello")] = std::string("world"); + const size_t expectedSize = JSON_OBJECT_SIZE(1) + 2 * JSON_STRING_SIZE(5); + REQUIRE(expectedSize <= doc.memoryUsage()); + } + + SECTION("should duplicate a non-static JsonString key") { + obj[JsonString("hello", JsonString::Copied)] = "world"; + const size_t expectedSize = JSON_OBJECT_SIZE(1) + JSON_STRING_SIZE(5); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should not duplicate a static JsonString key") { + obj[JsonString("hello", JsonString::Linked)] = "world"; + const size_t expectedSize = JSON_OBJECT_SIZE(1); + REQUIRE(expectedSize == doc.memoryUsage()); + } + + SECTION("should ignore null key") { + // object must have a value to make a call to strcmp() + obj["dummy"] = 42; + + const char* null = 0; + obj[null] = 666; + + REQUIRE(obj.size() == 1); + REQUIRE(obj[null] == null); + } + + SECTION("obj[key].to()") { + JsonArray arr = obj["hello"].to(); + + REQUIRE(arr.isNull() == false); + } + +#if defined(HAS_VARIABLE_LENGTH_ARRAY) && \ + !defined(SUBSCRIPT_CONFLICTS_WITH_BUILTIN_OPERATOR) + SECTION("obj[VLA] = str") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + obj[vla] = "world"; + + REQUIRE(std::string("world") == obj["hello"]); + } + + SECTION("obj[str] = VLA") { // issue #416 + size_t i = 32; + char vla[i]; + strcpy(vla, "world"); + + obj["hello"] = vla; + + REQUIRE(std::string("world") == obj["hello"].as()); + } + + SECTION("obj.set(VLA, str)") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + obj[vla] = "world"; + + REQUIRE(std::string("world") == obj["hello"]); + } + + SECTION("obj.set(str, VLA)") { + size_t i = 32; + char vla[i]; + strcpy(vla, "world"); + + obj["hello"].set(vla); + + REQUIRE(std::string("world") == obj["hello"].as()); + } + + SECTION("obj[VLA]") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + deserializeJson(doc, "{\"hello\":\"world\"}"); + + obj = doc.as(); + REQUIRE(std::string("world") == obj[vla]); + } +#endif + + SECTION("chain") { + obj.createNestedObject("hello")["world"] = 123; + + REQUIRE(123 == obj["hello"]["world"].as()); + REQUIRE(true == obj["hello"]["world"].is()); + REQUIRE(false == obj["hello"]["world"].is()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/CMakeLists.txt new file mode 100644 index 0000000..ece6dbc --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/CMakeLists.txt @@ -0,0 +1,22 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(JsonSerializerTests + CustomWriter.cpp + JsonArray.cpp + JsonArrayPretty.cpp + JsonObject.cpp + JsonObjectPretty.cpp + JsonVariant.cpp + misc.cpp + std_stream.cpp + std_string.cpp +) + +add_test(JsonSerializer JsonSerializerTests) + +set_tests_properties(JsonSerializer + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/CustomWriter.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/CustomWriter.cpp new file mode 100644 index 0000000..05d8704 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/CustomWriter.cpp @@ -0,0 +1,52 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +class CustomWriter { + public: + CustomWriter() {} + + size_t write(uint8_t c) { + _str.append(1, static_cast(c)); + return 1; + } + + size_t write(const uint8_t *s, size_t n) { + _str.append(reinterpret_cast(s), n); + return n; + } + + const std::string &str() const { + return _str; + } + + private: + CustomWriter(const CustomWriter &); // non-copiable + CustomWriter &operator=(const CustomWriter &); + + std::string _str; +}; + +TEST_CASE("CustomWriter") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + array.add(4); + array.add(2); + + SECTION("serializeJson()") { + CustomWriter writer; + serializeJson(array, writer); + + REQUIRE("[4,2]" == writer.str()); + } + + SECTION("serializeJsonPretty") { + CustomWriter writer; + serializeJsonPretty(array, writer); + + REQUIRE("[\r\n 4,\r\n 2\r\n]" == writer.str()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonArray.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonArray.cpp new file mode 100644 index 0000000..e018de7 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonArray.cpp @@ -0,0 +1,129 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +static void check(JsonArray array, std::string expected) { + std::string actual; + size_t actualLen = serializeJson(array, actual); + REQUIRE(expected == actual); + REQUIRE(actualLen == expected.size()); + size_t measuredLen = measureJson(array); + REQUIRE(measuredLen == expected.size()); +} + +TEST_CASE("serializeJson(JsonArray)") { + StaticJsonDocument doc; + JsonArray array = doc.to(); + + SECTION("Empty") { + check(array, "[]"); + } + + SECTION("Null") { + array.add(static_cast(0)); + + check(array, "[null]"); + } + + SECTION("OneString") { + array.add("hello"); + + check(array, "[\"hello\"]"); + } + + SECTION("TwoStrings") { + array.add("hello"); + array.add("world"); + + check(array, "[\"hello\",\"world\"]"); + } + + SECTION("OneStringOverCapacity") { + array.add("hello"); + array.add("world"); + array.add("lost"); + + check(array, "[\"hello\",\"world\"]"); + } + + SECTION("One double") { + array.add(3.1415927); + check(array, "[3.1415927]"); + } + + SECTION("OneInteger") { + array.add(1); + + check(array, "[1]"); + } + + SECTION("TwoIntegers") { + array.add(1); + array.add(2); + + check(array, "[1,2]"); + } + + SECTION("serialized(const char*)") { + array.add(serialized("{\"key\":\"value\"}")); + + check(array, "[{\"key\":\"value\"}]"); + } + + SECTION("serialized(char*)") { + char tmp[] = "{\"key\":\"value\"}"; + array.add(serialized(tmp)); + + check(array, "[{\"key\":\"value\"}]"); + } + + SECTION("OneIntegerOverCapacity") { + array.add(1); + array.add(2); + array.add(3); + + check(array, "[1,2]"); + } + + SECTION("OneTrue") { + array.add(true); + + check(array, "[true]"); + } + + SECTION("OneFalse") { + array.add(false); + + check(array, "[false]"); + } + + SECTION("TwoBooleans") { + array.add(false); + array.add(true); + + check(array, "[false,true]"); + } + + SECTION("OneBooleanOverCapacity") { + array.add(false); + array.add(true); + array.add(false); + + check(array, "[false,true]"); + } + + SECTION("OneEmptyNestedArray") { + array.createNestedArray(); + + check(array, "[[]]"); + } + + SECTION("OneEmptyNestedHash") { + array.createNestedObject(); + + check(array, "[{}]"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonArrayPretty.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonArrayPretty.cpp new file mode 100644 index 0000000..8c319fc --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonArrayPretty.cpp @@ -0,0 +1,75 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +static void checkArray(JsonArray array, std::string expected) { + std::string actual; + size_t actualLen = serializeJsonPretty(array, actual); + size_t measuredLen = measureJsonPretty(array); + CHECK(actualLen == expected.size()); + CHECK(measuredLen == expected.size()); + REQUIRE(expected == actual); +} + +TEST_CASE("serializeJsonPretty(JsonArray)") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + + SECTION("Empty") { + checkArray(array, "[]"); + } + + SECTION("OneElement") { + array.add(1); + + checkArray(array, + "[\r\n" + " 1\r\n" + "]"); + } + + SECTION("TwoElements") { + array.add(1); + array.add(2); + + checkArray(array, + "[\r\n" + " 1,\r\n" + " 2\r\n" + "]"); + } + + SECTION("EmptyNestedArrays") { + array.createNestedArray(); + array.createNestedArray(); + + checkArray(array, + "[\r\n" + " [],\r\n" + " []\r\n" + "]"); + } + + SECTION("NestedArrays") { + JsonArray nested1 = array.createNestedArray(); + nested1.add(1); + nested1.add(2); + + JsonObject nested2 = array.createNestedObject(); + nested2["key"] = 3; + + checkArray(array, + "[\r\n" + " [\r\n" + " 1,\r\n" + " 2\r\n" + " ],\r\n" + " {\r\n" + " \"key\": 3\r\n" + " }\r\n" + "]"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonObject.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonObject.cpp new file mode 100644 index 0000000..b457c66 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonObject.cpp @@ -0,0 +1,119 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +static void checkObject(const JsonObject obj, const std::string &expected) { + char actual[256]; + memset(actual, '!', sizeof(actual)); + + size_t actualLen = serializeJson(obj, actual); + size_t measuredLen = measureJson(obj); + + REQUIRE(expected.size() == measuredLen); + REQUIRE(expected.size() == actualLen); + REQUIRE(actual[actualLen] == 0); // serializeJson() adds a null terminator + REQUIRE(expected == actual); +} + +TEST_CASE("serializeJson(JsonObject)") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + + SECTION("EmptyObject") { + checkObject(obj, "{}"); + } + + SECTION("TwoStrings") { + obj["key1"] = "value1"; + obj["key2"] = "value2"; + + checkObject(obj, "{\"key1\":\"value1\",\"key2\":\"value2\"}"); + } + + SECTION("RemoveFirst") { + obj["key1"] = "value1"; + obj["key2"] = "value2"; + obj.remove("key1"); + + checkObject(obj, "{\"key2\":\"value2\"}"); + } + + SECTION("RemoveLast") { + obj["key1"] = "value1"; + obj["key2"] = "value2"; + obj.remove("key2"); + + checkObject(obj, "{\"key1\":\"value1\"}"); + } + + SECTION("RemoveUnexistingKey") { + obj["key1"] = "value1"; + obj["key2"] = "value2"; + obj.remove("key3"); + + checkObject(obj, "{\"key1\":\"value1\",\"key2\":\"value2\"}"); + } + + SECTION("ReplaceExistingKey") { + obj["key"] = "value1"; + obj["key"] = "value2"; + + checkObject(obj, "{\"key\":\"value2\"}"); + } + + SECTION("TwoIntegers") { + obj["a"] = 1; + obj["b"] = 2; + checkObject(obj, "{\"a\":1,\"b\":2}"); + } + + SECTION("serialized(const char*)") { + obj["a"] = serialized("[1,2]"); + obj["b"] = serialized("[4,5]"); + checkObject(obj, "{\"a\":[1,2],\"b\":[4,5]}"); + } + + SECTION("Two doubles") { + obj["a"] = 12.34; + obj["b"] = 56.78; + checkObject(obj, "{\"a\":12.34,\"b\":56.78}"); + } + + SECTION("TwoNull") { + obj["a"] = static_cast(0); + obj["b"] = static_cast(0); + checkObject(obj, "{\"a\":null,\"b\":null}"); + } + + SECTION("TwoBooleans") { + obj["a"] = true; + obj["b"] = false; + checkObject(obj, "{\"a\":true,\"b\":false}"); + } + + SECTION("ThreeNestedArrays") { + DynamicJsonDocument b(4096); + DynamicJsonDocument c(4096); + + obj.createNestedArray("a"); + obj["b"] = b.to(); + obj["c"] = c.to(); + + checkObject(obj, "{\"a\":[],\"b\":[],\"c\":[]}"); + } + + SECTION("ThreeNestedObjects") { + DynamicJsonDocument b(4096); + DynamicJsonDocument c(4096); + + obj.createNestedObject("a"); + obj["b"] = b.to(); + obj["c"] = c.to(); + + checkObject(obj, "{\"a\":{},\"b\":{},\"c\":{}}"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonObjectPretty.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonObjectPretty.cpp new file mode 100644 index 0000000..ec4ae71 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonObjectPretty.cpp @@ -0,0 +1,77 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +static void checkObjectPretty(const JsonObject obj, + const std::string expected) { + char json[256]; + + size_t actualLen = serializeJsonPretty(obj, json); + size_t measuredLen = measureJsonPretty(obj); + + REQUIRE(json == expected); + REQUIRE(expected.size() == actualLen); + REQUIRE(expected.size() == measuredLen); +} + +TEST_CASE("serializeJsonPretty(JsonObject)") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + + SECTION("EmptyObject") { + checkObjectPretty(obj, "{}"); + } + + SECTION("OneMember") { + obj["key"] = "value"; + + checkObjectPretty(obj, + "{\r\n" + " \"key\": \"value\"\r\n" + "}"); + } + + SECTION("TwoMembers") { + obj["key1"] = "value1"; + obj["key2"] = "value2"; + + checkObjectPretty(obj, + "{\r\n" + " \"key1\": \"value1\",\r\n" + " \"key2\": \"value2\"\r\n" + "}"); + } + + SECTION("EmptyNestedContainers") { + obj.createNestedObject("key1"); + obj.createNestedArray("key2"); + + checkObjectPretty(obj, + "{\r\n" + " \"key1\": {},\r\n" + " \"key2\": []\r\n" + "}"); + } + + SECTION("NestedContainers") { + JsonObject nested1 = obj.createNestedObject("key1"); + nested1["a"] = 1; + + JsonArray nested2 = obj.createNestedArray("key2"); + nested2.add(2); + + checkObjectPretty(obj, + "{\r\n" + " \"key1\": {\r\n" + " \"a\": 1\r\n" + " },\r\n" + " \"key2\": [\r\n" + " 2\r\n" + " ]\r\n" + "}"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonVariant.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonVariant.cpp new file mode 100644 index 0000000..18d51f5 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/JsonVariant.cpp @@ -0,0 +1,121 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +template +void check(T value, const std::string &expected) { + DynamicJsonDocument doc(4096); + doc.to().set(value); + char buffer[256] = ""; + size_t returnValue = serializeJson(doc, buffer, sizeof(buffer)); + REQUIRE(expected == buffer); + REQUIRE(expected.size() == returnValue); +} + +TEST_CASE("serializeJson(JsonVariant)") { + SECTION("Undefined") { + check(JsonVariant(), "null"); + } + + SECTION("Null string") { + check(static_cast(0), "null"); + } + + SECTION("const char*") { + check("hello", "\"hello\""); + } + + SECTION("string") { + check(std::string("hello"), "\"hello\""); + + SECTION("Escape quotation mark") { + check(std::string("hello \"world\""), "\"hello \\\"world\\\"\""); + } + + SECTION("Escape reverse solidus") { + check(std::string("hello\\world"), "\"hello\\\\world\""); + } + + SECTION("Don't escape solidus") { + check(std::string("fifty/fifty"), "\"fifty/fifty\""); + } + + SECTION("Escape backspace") { + check(std::string("hello\bworld"), "\"hello\\bworld\""); + } + + SECTION("Escape formfeed") { + check(std::string("hello\fworld"), "\"hello\\fworld\""); + } + + SECTION("Escape linefeed") { + check(std::string("hello\nworld"), "\"hello\\nworld\""); + } + + SECTION("Escape carriage return") { + check(std::string("hello\rworld"), "\"hello\\rworld\""); + } + + SECTION("Escape tab") { + check(std::string("hello\tworld"), "\"hello\\tworld\""); + } + + SECTION("NUL char") { + check(std::string("hello\0world", 11), "\"hello\\u0000world\""); + } + } + + SECTION("SerializedValue") { + check(serialized("[1,2]"), "[1,2]"); + } + + SECTION("SerializedValue") { + check(serialized(std::string("[1,2]")), "[1,2]"); + } + + SECTION("Double") { + check(3.1415927, "3.1415927"); + } + + SECTION("Zero") { + check(0, "0"); + } + + SECTION("Integer") { + check(42, "42"); + } + + SECTION("NegativeLong") { + check(-42, "-42"); + } + + SECTION("UnsignedLong") { + check(4294967295UL, "4294967295"); + } + + SECTION("True") { + check(true, "true"); + } + + SECTION("OneFalse") { + check(false, "false"); + } + +#if ARDUINOJSON_USE_LONG_LONG + SECTION("NegativeInt64") { + check(-9223372036854775807 - 1, "-9223372036854775808"); + } + + SECTION("PositiveInt64") { + check(9223372036854775807, "9223372036854775807"); + } + + SECTION("UInt64") { + check(18446744073709551615U, "18446744073709551615"); + } +#endif +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/misc.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/misc.cpp new file mode 100644 index 0000000..59c09eb --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/misc.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +TEST_CASE("serializeJson(MemberProxy)") { + DynamicJsonDocument doc(4096); + deserializeJson(doc, "{\"hello\":42}"); + JsonObject obj = doc.as(); + std::string result; + + serializeJson(obj["hello"], result); + + REQUIRE(result == "42"); +} + +TEST_CASE("serializeJson(ElementProxy)") { + DynamicJsonDocument doc(4096); + deserializeJson(doc, "[42]"); + JsonArray arr = doc.as(); + std::string result; + + serializeJson(arr[0], result); + + REQUIRE(result == "42"); +} + +TEST_CASE("serializeJson(JsonVariantSubscript)") { + DynamicJsonDocument doc(4096); + deserializeJson(doc, "[42]"); + JsonVariant var = doc.as(); + std::string result; + + serializeJson(var[0], result); + + REQUIRE(result == "42"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/std_stream.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/std_stream.cpp new file mode 100644 index 0000000..7452028 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/std_stream.cpp @@ -0,0 +1,66 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +TEST_CASE("operator<<(std::ostream)") { + DynamicJsonDocument doc(4096); + std::ostringstream os; + + SECTION("JsonVariant containing false") { + JsonVariant variant = doc.to(); + + variant.set(false); + os << variant; + + REQUIRE("false" == os.str()); + } + + SECTION("JsonVariant containing string") { + JsonVariant variant = doc.to(); + + variant.set("coucou"); + os << variant; + + REQUIRE("\"coucou\"" == os.str()); + } + + SECTION("JsonObject") { + JsonObject object = doc.to(); + object["key"] = "value"; + + os << object; + + REQUIRE("{\"key\":\"value\"}" == os.str()); + } + + SECTION("MemberProxy") { + JsonObject object = doc.to(); + object["key"] = "value"; + + os << object["key"]; + + REQUIRE("\"value\"" == os.str()); + } + + SECTION("JsonArray") { + JsonArray array = doc.to(); + array.add("value"); + + os << array; + + REQUIRE("[\"value\"]" == os.str()); + } + + SECTION("ElementProxy") { + JsonArray array = doc.to(); + array.add("value"); + + os << array[0]; + + REQUIRE("\"value\"" == os.str()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/std_string.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/std_string.cpp new file mode 100644 index 0000000..7a145da --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonSerializer/std_string.cpp @@ -0,0 +1,57 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("serialize JsonArray to std::string") { + DynamicJsonDocument doc(4096); + JsonArray array = doc.to(); + array.add(4); + array.add(2); + + SECTION("serializeJson()") { + std::string json; + serializeJson(array, json); + + REQUIRE("[4,2]" == json); + } + + SECTION("serializeJsonPretty") { + std::string json; + serializeJsonPretty(array, json); + + REQUIRE("[\r\n 4,\r\n 2\r\n]" == json); + } +} + +TEST_CASE("serialize JsonObject to std::string") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + obj["key"] = "value"; + + SECTION("object") { + std::string json; + serializeJson(doc, json); + + REQUIRE("{\"key\":\"value\"}" == json); + } + + SECTION("serializeJsonPretty") { + std::string json; + serializeJsonPretty(doc, json); + + REQUIRE("{\r\n \"key\": \"value\"\r\n}" == json); + } +} + +TEST_CASE("serialize an std::string containing a NUL") { + StaticJsonDocument<256> doc; + doc.set(std::string("hello\0world", 11)); + CHECK(doc.memoryUsage() == 12); + + std::string json; + serializeJson(doc, json); + CHECK("\"hello\\u0000world\"" == json); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/CMakeLists.txt new file mode 100644 index 0000000..134c675 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/CMakeLists.txt @@ -0,0 +1,33 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(JsonVariantTests + add.cpp + as.cpp + clear.cpp + compare.cpp + containsKey.cpp + copy.cpp + converters.cpp + createNested.cpp + is.cpp + isnull.cpp + memoryUsage.cpp + misc.cpp + nesting.cpp + or.cpp + overflow.cpp + remove.cpp + set.cpp + subscript.cpp + types.cpp + unbound.cpp +) + +add_test(JsonVariant JsonVariantTests) + +set_tests_properties(JsonVariant + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/add.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/add.cpp new file mode 100644 index 0000000..526e914 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/add.cpp @@ -0,0 +1,46 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +TEST_CASE("JsonVariant::add()") { + DynamicJsonDocument doc(4096); + JsonVariant var = doc.to(); + + SECTION("add integer to new variant") { + var.add(42); + + REQUIRE(var.as() == "[42]"); + } + + SECTION("add const char* to new variant") { + var.add("hello"); + + REQUIRE(var.as() == "[\"hello\"]"); + } + + SECTION("add std::string to new variant") { + var.add(std::string("hello")); + + REQUIRE(var.as() == "[\"hello\"]"); + } + + SECTION("add integer to integer") { + var.set(123); + + var.add(456); // no-op + + REQUIRE(var.as() == "123"); + } + + SECTION("add integer to object") { + var["val"] = 123; + + var.add(456); // no-op + + REQUIRE(var.as() == "{\"val\":123}"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/as.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/as.cpp new file mode 100644 index 0000000..c995677 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/as.cpp @@ -0,0 +1,270 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +namespace my { +using ARDUINOJSON_NAMESPACE::isinf; +} // namespace my + +enum MY_ENUM { ONE = 1, TWO = 2 }; + +TEST_CASE("JsonVariant::as()") { + static const char* null = 0; + + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("not set") { + REQUIRE(false == variant.as()); + REQUIRE(0 == variant.as()); + REQUIRE(0.0f == variant.as()); + REQUIRE(0 == variant.as()); + REQUIRE("null" == variant.as()); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(4.2)") { + variant.set(4.2); + + REQUIRE(variant.as()); + REQUIRE(0 == variant.as()); + REQUIRE(variant.as() == "4.2"); + REQUIRE(variant.as() == 4L); + REQUIRE(variant.as() == 4U); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(0.0)") { + variant.set(0.0); + + REQUIRE(variant.as() == false); + REQUIRE(variant.as() == 0L); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(false)") { + variant.set(false); + + REQUIRE(false == variant.as()); + REQUIRE(variant.as() == 0.0); + REQUIRE(variant.as() == 0L); + REQUIRE(variant.as() == "false"); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(true)") { + variant.set(true); + + REQUIRE(variant.as()); + REQUIRE(variant.as() == 1.0); + REQUIRE(variant.as() == 1L); + REQUIRE(variant.as() == "true"); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(42)") { + variant.set(42); + + REQUIRE(variant.as() == true); + REQUIRE(variant.as() == 42.0); + REQUIRE(variant.as() == 42); + REQUIRE(variant.as() == 42U); // issue #1601 + REQUIRE(variant.as() == "42"); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(42L)") { + variant.set(42L); + + REQUIRE(variant.as() == true); + REQUIRE(variant.as() == 42.0); + REQUIRE(variant.as() == "42"); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(-42L)") { + variant.set(-42L); + + REQUIRE(variant.as() == -42.0); + REQUIRE(variant.as() == "-42"); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(42UL)") { + variant.set(42UL); + + REQUIRE(variant.as() == true); + REQUIRE(variant.as() == 42.0); + REQUIRE(variant.as() == "42"); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(0L)") { + variant.set(0L); + + REQUIRE(variant.as() == false); + REQUIRE(variant.as() == 0.0); + REQUIRE(variant.as() == "0"); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(0UL)") { + variant.set(0UL); + + REQUIRE(variant.as() == false); + REQUIRE(variant.as() == 0.0); + REQUIRE(variant.as() == "0"); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(null)") { + variant.set(null); + + REQUIRE(variant.as() == false); + REQUIRE(variant.as() == 0.0); + REQUIRE(variant.as() == 0L); + REQUIRE(variant.as() == "null"); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(\"42\")") { + variant.set("42"); + + REQUIRE(variant.as() == 42L); + REQUIRE(variant.as() == "42"); + REQUIRE(variant.as().isLinked() == true); + } + + SECTION("set(\"hello\")") { + variant.set("hello"); + + REQUIRE(variant.as() == true); + REQUIRE(variant.as() == 0L); + REQUIRE(variant.as() == std::string("hello")); + REQUIRE(variant.as() == std::string("hello")); + REQUIRE(variant.as() == std::string("hello")); + REQUIRE(variant.as() == "hello"); + } + + SECTION("set(std::string(\"4.2\"))") { + variant.set(std::string("4.2")); + + REQUIRE(variant.as() == true); + REQUIRE(variant.as() == 4L); + REQUIRE(variant.as() == 4.2); + REQUIRE(variant.as() == std::string("4.2")); + REQUIRE(variant.as() == std::string("4.2")); + REQUIRE(variant.as() == "4.2"); + REQUIRE(variant.as().isLinked() == false); + } + + SECTION("set(\"true\")") { + variant.set("true"); + + REQUIRE(variant.as() == true); + REQUIRE(variant.as() == 0); + REQUIRE(variant.as() == "true"); + } + + SECTION("set(-1e300)") { + variant.set(-1e300); + + REQUIRE(variant.as() == true); + REQUIRE(variant.as() == -1e300); + REQUIRE(variant.as() < 0); + REQUIRE(my::isinf(variant.as())); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(1e300)") { + variant.set(1e300); + + REQUIRE(variant.as() == true); + REQUIRE(variant.as() == 1e300); + REQUIRE(variant.as() > 0); + REQUIRE(my::isinf(variant.as())); + REQUIRE(variant.as().isNull()); + } + + SECTION("set(1e-300)") { + variant.set(1e-300); + + REQUIRE(variant.as() == true); + REQUIRE(variant.as() == 1e-300); + REQUIRE(variant.as() == 0); + REQUIRE(variant.as().isNull()); + } + + SECTION("to()") { + JsonObject obj = variant.to(); + obj["key"] = "value"; + + SECTION("as()") { + REQUIRE(variant.as() == true); + } + + SECTION("as()") { + REQUIRE(variant.as() == std::string("{\"key\":\"value\"}")); + } + + SECTION("ObjectAsJsonObject") { + JsonObject o = variant.as(); + REQUIRE(o.size() == 1); + REQUIRE(o["key"] == std::string("value")); + } + } + + SECTION("to()") { + JsonArray arr = variant.to(); + arr.add(4); + arr.add(2); + + SECTION("as()") { + REQUIRE(variant.as() == true); + } + + SECTION("as()") { + REQUIRE(variant.as() == std::string("[4,2]")); + } + + SECTION("as()") { + JsonArray a = variant.as(); + REQUIRE(a.size() == 2); + REQUIRE(a[0] == 4); + REQUIRE(a[1] == 2); + } + } + +#if ARDUINOJSON_USE_LONG_LONG + SECTION("Smallest int64 negative") { + variant.set("-9223372036854775808"); + REQUIRE(variant.as() == -9223372036854775807 - 1); + } + + SECTION("Biggerst int64 positive") { + variant.set("9223372036854775807"); + REQUIRE(variant.as() == 9223372036854775807); + } +#endif + + SECTION("should work on JsonVariantConst") { + variant.set("hello"); + + JsonVariantConst cvar = variant; + + REQUIRE(cvar.as() == true); + REQUIRE(cvar.as() == 0L); + REQUIRE(cvar.as() == std::string("hello")); + REQUIRE(cvar.as() == std::string("hello")); + } + + SECTION("as()") { + variant.set(1); + + REQUIRE(variant.as() == ONE); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/clear.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/clear.cpp new file mode 100644 index 0000000..2b40e32 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/clear.cpp @@ -0,0 +1,26 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +TEST_CASE("JsonVariant::clear()") { + DynamicJsonDocument doc(4096); + JsonVariant var = doc.to(); + + SECTION("size goes back to zero") { + var.add(42); + var.clear(); + + REQUIRE(var.size() == 0); + } + + SECTION("isNull() return true") { + var.add("hello"); + var.clear(); + + REQUIRE(var.isNull() == true); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/compare.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/compare.cpp new file mode 100644 index 0000000..63adbc2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/compare.cpp @@ -0,0 +1,316 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +// Most code is already covered by arithmeticCompare.cpp. +// Here, we're just filling the holes + +TEST_CASE("Compare JsonVariant with value") { + StaticJsonDocument<256> doc; + JsonVariant a = doc.addElement(); + + SECTION("null vs (char*)0") { + char* b = 0; + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("42 vs 42") { + a.set(42); + int b = 42; + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } +} + +TEST_CASE("Compare JsonVariant with JsonVariant") { + StaticJsonDocument<256> doc; + JsonVariant a = doc.addElement(); + JsonVariant b = doc.addElement(); + + SECTION("'abc' vs 'abc'") { + a.set("abc"); + b.set("abc"); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("'abc' vs 'bcd'") { + a.set("abc"); + b.set("bcd"); + + CHECK(a != b); + CHECK(a < b); + CHECK(a <= b); + CHECK_FALSE(a == b); + CHECK_FALSE(a > b); + CHECK_FALSE(a >= b); + } + + SECTION("'bcd' vs 'abc'") { + a.set("bcd"); + b.set("abc"); + + CHECK(a != b); + CHECK(a > b); + CHECK(a >= b); + CHECK_FALSE(a < b); + CHECK_FALSE(a <= b); + CHECK_FALSE(a == b); + } + + SECTION("serialized('abc') vs serialized('abc')") { + a.set(serialized("abc")); + b.set(serialized("abc")); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("serialized('abc') vs serialized('bcd')") { + a.set(serialized("abc")); + b.set(serialized("bcd")); + + CHECK(a != b); + CHECK(a < b); + CHECK(a <= b); + CHECK_FALSE(a == b); + CHECK_FALSE(a > b); + CHECK_FALSE(a >= b); + } + + SECTION("serialized('bcd') vs serialized('abc')") { + a.set(serialized("bcd")); + b.set(serialized("abc")); + + CHECK(a != b); + CHECK(a > b); + CHECK(a >= b); + CHECK_FALSE(a < b); + CHECK_FALSE(a <= b); + CHECK_FALSE(a == b); + } + + SECTION("false vs true") { + a.set(false); + b.set(true); + + CHECK(a != b); + CHECK(a < b); + CHECK(a <= b); + CHECK_FALSE(a == b); + CHECK_FALSE(a > b); + CHECK_FALSE(a >= b); + } + + SECTION("false vs -1") { + a.set(false); + b.set(-1); + + CHECK(a != b); + CHECK(a > b); + CHECK(a >= b); + CHECK_FALSE(a < b); + CHECK_FALSE(a <= b); + CHECK_FALSE(a == b); + } + + SECTION("null vs null") { + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("42 vs 42") { + a.set(42); + b.set(42); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("42 vs 42U") { + a.set(42); + b.set(42U); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("42 vs 42.0") { + a.set(42); + b.set(42.0); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("42.0 vs 42") { + a.set(42.0); + b.set(42); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("-42 vs -42") { + a.set(-42); + b.set(-42); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("-42 vs 42") { + a.set(-42); + b.set(42); + + CHECK(a != b); + CHECK(a < b); + CHECK(a <= b); + CHECK_FALSE(a == b); + CHECK_FALSE(a > b); + CHECK_FALSE(a >= b); + } + + SECTION("42 vs -42") { + a.set(42); + b.set(-42); + + CHECK(a != b); + CHECK(a > b); + CHECK(a >= b); + CHECK_FALSE(a < b); + CHECK_FALSE(a <= b); + CHECK_FALSE(a == b); + } + + SECTION("42.0 vs -42") { + a.set(42.0); + b.set(-42); + + CHECK(a != b); + CHECK(a > b); + CHECK(a >= b); + CHECK_FALSE(a < b); + CHECK_FALSE(a <= b); + CHECK_FALSE(a == b); + } + + SECTION("42U vs 42U") { + a.set(42U); + b.set(42U); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("42U vs 42") { + a.set(42U); + b.set(42); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("[1] vs [1]") { + a.add(1); + b.add(1); + + CHECK(a <= b); + CHECK(a == b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("[1] vs [2]") { + a.add(1); + b.add(2); + + CHECK(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a <= b); + CHECK_FALSE(a == b); + CHECK_FALSE(a > b); + CHECK_FALSE(a >= b); + } + + SECTION("{x:1} vs {x:1}") { + a["x"] = 1; + b["x"] = 1; + + CHECK(a <= b); + CHECK(a == b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("{x:1} vs {x:2}") { + a["x"] = 1; + b["x"] = 2; + + CHECK(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a <= b); + CHECK_FALSE(a == b); + CHECK_FALSE(a > b); + CHECK_FALSE(a >= b); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/containsKey.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/containsKey.cpp new file mode 100644 index 0000000..51bf500 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/containsKey.cpp @@ -0,0 +1,44 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +static const char* null = 0; + +TEST_CASE("JsonVariant::containsKey()") { + DynamicJsonDocument doc(4096); + JsonVariant var = doc.to(); + + SECTION("containsKey(const char*) returns true") { + var["hello"] = "world"; + + REQUIRE(var.containsKey("hello") == true); + REQUIRE(var.containsKey("world") == false); + } + + SECTION("containsKey(std::string) returns true") { + var["hello"] = "world"; + + REQUIRE(var.containsKey(std::string("hello")) == true); + REQUIRE(var.containsKey(std::string("world")) == false); + } +} + +TEST_CASE("JsonVariantConst::containsKey()") { + DynamicJsonDocument doc(4096); + doc["hello"] = "world"; + JsonVariantConst cvar = doc.as(); + + SECTION("containsKey(const char*) returns true") { + REQUIRE(cvar.containsKey("hello") == true); + REQUIRE(cvar.containsKey("world") == false); + } + + SECTION("containsKey(std::string) returns true") { + REQUIRE(cvar.containsKey(std::string("hello")) == true); + REQUIRE(cvar.containsKey(std::string("world")) == false); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/converters.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/converters.cpp new file mode 100644 index 0000000..3c4ad6d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/converters.cpp @@ -0,0 +1,142 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +namespace { +struct Date { + int day; + int month; + int year; +}; + +void convertToJson(const Date& src, JsonVariant dst) { + dst["day"] = src.day; + dst["month"] = src.month; + dst["year"] = src.year; +} + +void convertFromJson(JsonVariantConst src, Date& dst) { + dst.day = src["day"]; + dst.month = src["month"]; + dst.year = src["year"]; +} + +bool canConvertFromJson(JsonVariantConst src, const Date&) { + return src["day"].is() && src["month"].is() && + src["year"].is(); +} +} // namespace + +TEST_CASE("Custom converter with overloading") { + DynamicJsonDocument doc(4096); + + SECTION("convert JSON to Date") { + doc["date"]["day"] = 2; + doc["date"]["month"] = 3; + doc["date"]["year"] = 2021; + + Date date = doc["date"]; + + REQUIRE(date.day == 2); + REQUIRE(date.month == 3); + REQUIRE(date.year == 2021); + } + + SECTION("is() returns true") { + doc["date"]["day"] = 2; + doc["date"]["month"] = 3; + doc["date"]["year"] = 2021; + + REQUIRE(doc["date"].is()); + } + + SECTION("is() returns false") { + doc["date"]["day"] = 2; + doc["date"]["month"] = 3; + doc["date"]["year"] = "2021"; + + REQUIRE(doc["date"].is() == false); + } + + SECTION("convert Date to JSON") { + Date date = {19, 3, 2021}; + doc["date"] = date; + + REQUIRE(doc["date"]["day"] == 19); + REQUIRE(doc["date"]["month"] == 3); + REQUIRE(doc["date"]["year"] == 2021); + } +} + +class Complex { + public: + explicit Complex(double r, double i) : _real(r), _imag(i) {} + + double real() const { + return _real; + } + + double imag() const { + return _imag; + } + + private: + double _real, _imag; +}; + +namespace ARDUINOJSON_NAMESPACE { +template <> +struct Converter { + static void toJson(const Complex& src, VariantRef dst) { + dst["real"] = src.real(); + dst["imag"] = src.imag(); + } + + static Complex fromJson(VariantConstRef src) { + return Complex(src["real"], src["imag"]); + } + + static bool checkJson(VariantConstRef src) { + return src["real"].is() && src["imag"].is(); + } +}; +} // namespace ARDUINOJSON_NAMESPACE + +TEST_CASE("Custom converter with specialization") { + DynamicJsonDocument doc(4096); + + SECTION("convert JSON to Complex") { + doc["value"]["real"] = 2; + doc["value"]["imag"] = 3; + + Complex value = doc["value"]; + + REQUIRE(value.real() == 2); + REQUIRE(value.imag() == 3); + } + + SECTION("is() returns true") { + doc["value"]["real"] = 2; + doc["value"]["imag"] = 3; + + REQUIRE(doc["value"].is()); + } + + SECTION("is() returns false") { + doc["value"]["real"] = 2; + doc["value"]["imag"] = "3"; + + REQUIRE(doc["value"].is() == false); + } + + SECTION("convert value to JSON") { + doc["value"] = Complex(19, 3); + + REQUIRE(doc["value"]["real"] == 19); + REQUIRE(doc["value"]["imag"] == 3); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/copy.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/copy.cpp new file mode 100644 index 0000000..7784f0c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/copy.cpp @@ -0,0 +1,95 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonVariant::set(JsonVariant)") { + DynamicJsonDocument doc1(4096); + DynamicJsonDocument doc2(4096); + JsonVariant var1 = doc1.to(); + JsonVariant var2 = doc2.to(); + + SECTION("stores JsonArray by copy") { + JsonArray arr = doc2.to(); + JsonObject obj = arr.createNestedObject(); + obj["hello"] = "world"; + + var1.set(arr); + + arr[0] = 666; + REQUIRE(var1.as() == "[{\"hello\":\"world\"}]"); + } + + SECTION("stores JsonObject by copy") { + JsonObject obj = doc2.to(); + JsonArray arr = obj.createNestedArray("value"); + arr.add(42); + + var1.set(obj); + + obj["value"] = 666; + REQUIRE(var1.as() == "{\"value\":[42]}"); + } + + SECTION("stores const char* by reference") { + var1.set("hello!!"); + var2.set(var1); + + REQUIRE(doc1.memoryUsage() == 0); + REQUIRE(doc2.memoryUsage() == 0); + } + + SECTION("stores char* by copy") { + char str[] = "hello!!"; + + var1.set(str); + var2.set(var1); + + REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(7)); + REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(7)); + } + + SECTION("stores std::string by copy") { + var1.set(std::string("hello!!")); + var2.set(var1); + + REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(7)); + REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(7)); + } + + SECTION("stores Serialized by reference") { + var1.set(serialized("hello!!", 8)); + var2.set(var1); + + REQUIRE(doc1.memoryUsage() == 0); + REQUIRE(doc2.memoryUsage() == 0); + } + + SECTION("stores Serialized by copy") { + char str[] = "hello!!"; + var1.set(serialized(str, 7)); + var2.set(var1); + + REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(7)); + REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(7)); + } + + SECTION("stores Serialized by copy") { + var1.set(serialized(std::string("hello!!"))); + var2.set(var1); + + REQUIRE(doc1.memoryUsage() == JSON_STRING_SIZE(7)); + REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(7)); + } + + SECTION("destination is unbound") { + JsonVariant unboundVariant; + + unboundVariant.set(var1); + + REQUIRE(unboundVariant.isUnbound()); + REQUIRE(unboundVariant.isNull()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/createNested.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/createNested.cpp new file mode 100644 index 0000000..34c74c4 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/createNested.cpp @@ -0,0 +1,86 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +TEST_CASE("JsonVariant::createNestedObject()") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("promotes to array") { + JsonObject obj = variant.createNestedObject(); + obj["value"] = 42; + + REQUIRE(variant.is() == true); + REQUIRE(variant[0]["value"] == 42); + REQUIRE(obj.isNull() == false); + } + + SECTION("works on MemberProxy") { + JsonObject obj = variant["items"].createNestedObject(); + obj["value"] = 42; + + REQUIRE(variant["items"][0]["value"] == 42); + } +} + +TEST_CASE("JsonVariant::createNestedArray()") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("promotes to array") { + JsonArray arr = variant.createNestedArray(); + + REQUIRE(variant.is() == true); + REQUIRE(arr.isNull() == false); + } + + SECTION("works on MemberProxy") { + JsonArray arr = variant["items"].createNestedArray(); + arr.add(42); + + REQUIRE(variant["items"][0][0] == 42); + } +} + +TEST_CASE("JsonVariant::createNestedObject(key)") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("promotes to object") { + JsonObject obj = variant.createNestedObject("weather"); + obj["temp"] = 42; + + REQUIRE(variant.is() == true); + REQUIRE(variant["weather"]["temp"] == 42); + } + + SECTION("works on MemberProxy") { + JsonObject obj = variant["status"].createNestedObject("weather"); + obj["temp"] = 42; + + REQUIRE(variant["status"]["weather"]["temp"] == 42); + } +} + +TEST_CASE("JsonVariant::createNestedArray(key)") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("promotes to object") { + JsonArray arr = variant.createNestedArray("items"); + + REQUIRE(variant.is() == true); + REQUIRE(arr.isNull() == false); + } + + SECTION("works on MemberProxy") { + JsonArray arr = variant["weather"].createNestedArray("temp"); + arr.add(42); + + REQUIRE(variant["weather"]["temp"][0] == 42); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/is.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/is.cpp new file mode 100644 index 0000000..6d04f0a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/is.cpp @@ -0,0 +1,319 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +enum MYENUM2 { ONE = 1, TWO = 2 }; + +TEST_CASE("JsonVariant::is()") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("unbound") { + variant = JsonVariant(); + + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + } + + SECTION("null") { + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + } + + SECTION("true") { + variant.set(true); + + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + } + + SECTION("false") { + variant.set(false); + + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + } + + SECTION("int") { + variant.set(42); + + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + } + + SECTION("double") { + variant.set(4.2); + + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + } + + SECTION("const char*") { + variant.set("4.2"); + + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + } + + SECTION("JsonArray") { + variant.to(); + + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + } + + SECTION("JsonObject") { + variant.to(); + + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + } +} + +TEST_CASE("JsonVariantConst::is()") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + JsonVariantConst cvariant = variant; + + SECTION("unbound") { + cvariant = JsonVariantConst(); + + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("null") { + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("true") { + variant.set(true); + + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("false") { + variant.set(false); + + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("int") { + variant.set(42); + + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("double") { + variant.set(4.2); + + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("const char*") { + variant.set("4.2"); + + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("JsonArray") { + variant.to(); + + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("JsonObject") { + variant.to(); + + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/isnull.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/isnull.cpp new file mode 100644 index 0000000..7e29039 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/isnull.cpp @@ -0,0 +1,80 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonVariant::isNull()") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("return true when Undefined") { + REQUIRE(variant.isNull() == true); + } + + SECTION("return false when Integer") { + variant.set(42); + + REQUIRE(variant.isNull() == false); + } + + SECTION("return false when EmptyArray") { + DynamicJsonDocument doc2(4096); + JsonArray array = doc2.to(); + + variant.set(array); + REQUIRE(variant.isNull() == false); + } + + SECTION("return false when EmptyObject") { + DynamicJsonDocument doc2(4096); + JsonObject obj = doc2.to(); + + variant.set(obj); + REQUIRE(variant.isNull() == false); + } + + SECTION("return true after set(JsonArray())") { + variant.set(JsonArray()); + REQUIRE(variant.isNull() == true); + } + + SECTION("return true after set(JsonObject())") { + variant.set(JsonObject()); + REQUIRE(variant.isNull() == true); + } + + SECTION("return false after set('hello')") { + variant.set("hello"); + REQUIRE(variant.isNull() == false); + } + + SECTION("return true after set((char*)0)") { + variant.set(static_cast(0)); + REQUIRE(variant.isNull() == true); + } + + SECTION("return true after set((const char*)0)") { + variant.set(static_cast(0)); + REQUIRE(variant.isNull() == true); + } + + SECTION("return true after set(serialized((char*)0))") { + variant.set(serialized(static_cast(0))); + REQUIRE(variant.isNull() == true); + } + + SECTION("return true after set(serialized((const char*)0))") { + variant.set(serialized(static_cast(0))); + REQUIRE(variant.isNull() == true); + } + + SECTION("works with JsonVariantConst") { + variant.set(42); + + JsonVariantConst cvar = variant; + + REQUIRE(cvar.isNull() == false); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/memoryUsage.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/memoryUsage.cpp new file mode 100644 index 0000000..59587db --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/memoryUsage.cpp @@ -0,0 +1,41 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +TEST_CASE("JsonVariant::memoryUsage()") { + DynamicJsonDocument doc(4096); + JsonVariant var = doc.to(); + + SECTION("returns 0 if uninitialized") { + JsonVariant unitialized; + REQUIRE(unitialized.memoryUsage() == 0); + } + + SECTION("returns size of object") { + JsonObject obj = var.to(); + obj["hello"] = 42; + REQUIRE(var.memoryUsage() == JSON_OBJECT_SIZE(1)); + } + + SECTION("returns size of array") { + JsonArray arr = var.to(); + arr.add(42); + REQUIRE(var.memoryUsage() == JSON_ARRAY_SIZE(1)); + } + + SECTION("returns size of owned string") { + var.set(std::string("hello")); + REQUIRE(var.memoryUsage() == 6); + REQUIRE(var.memoryUsage() == doc.memoryUsage()); + } + + SECTION("returns size of owned raw") { + var.set(serialized(std::string("hello"))); + REQUIRE(var.memoryUsage() == 6); + REQUIRE(var.memoryUsage() == doc.memoryUsage()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/misc.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/misc.cpp new file mode 100644 index 0000000..05976c3 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/misc.cpp @@ -0,0 +1,50 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonVariant from JsonArray") { + SECTION("JsonArray is null") { + JsonArray arr; + JsonVariant v = arr; + REQUIRE(v.isNull() == true); + } + + SECTION("JsonArray is not null") { + DynamicJsonDocument doc(4096); + JsonArray arr = doc.to(); + arr.add(12); + arr.add(34); + + JsonVariant v = arr; + + REQUIRE(v.is() == true); + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 12); + REQUIRE(v[1] == 34); + } +} + +TEST_CASE("JsonVariant from JsonObject") { + SECTION("JsonObject is null") { + JsonObject obj; + JsonVariant v = obj; + REQUIRE(v.isNull() == true); + } + + SECTION("JsonObject is not null") { + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + obj["a"] = 12; + obj["b"] = 34; + + JsonVariant v = obj; + + REQUIRE(v.is() == true); + REQUIRE(v.size() == 2); + REQUIRE(v["a"] == 12); + REQUIRE(v["b"] == 34); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/nesting.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/nesting.cpp new file mode 100644 index 0000000..3e936fb --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/nesting.cpp @@ -0,0 +1,31 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonVariant::nesting()") { + DynamicJsonDocument doc(4096); + JsonVariant var = doc.to(); + + SECTION("return 0 if uninitialized") { + JsonVariant unitialized; + REQUIRE(unitialized.nesting() == 0); + } + + SECTION("returns 0 for string") { + var.set("hello"); + REQUIRE(var.nesting() == 0); + } + + SECTION("returns 1 for empty object") { + var.to(); + REQUIRE(var.nesting() == 1); + } + + SECTION("returns 1 for empty array") { + var.to(); + REQUIRE(var.nesting() == 1); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/or.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/or.cpp new file mode 100644 index 0000000..7d1c190 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/or.cpp @@ -0,0 +1,159 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonVariant::operator|()") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc["value"].to(); + + SECTION("null") { + SECTION("null | const char*") { + std::string result = variant | "default"; + REQUIRE(result == "default"); + } + + SECTION("null | int") { + int result = variant | 42; + REQUIRE(result == 42); + } + + SECTION("null | bool") { + bool result = variant | true; + REQUIRE(result == true); + } + + SECTION("null | ElementProxy") { + doc["array"][0] = 42; + + JsonVariantConst result = variant | doc["array"][0]; + REQUIRE(result == 42); + } + + SECTION("null | MemberProxy") { + doc["other"] = 42; + + JsonVariantConst result = variant | doc["other"]; + REQUIRE(result == 42); + } + + SECTION("ElementProxy | ElementProxy") { + doc["array"][0] = 42; + + JsonVariantConst result = doc["array"][1] | doc["array"][0]; + REQUIRE(result == 42); + } + } + + SECTION("null") { + variant.set(static_cast(0)); + + SECTION("null | const char*") { + std::string result = variant | "default"; + REQUIRE(result == "default"); + } + + SECTION("null | int") { + int result = variant | 42; + REQUIRE(result == 42); + } + + SECTION("null | bool") { + bool result = variant | true; + REQUIRE(result == true); + } + + SECTION("null | ElementProxy") { + doc["array"][0] = 42; + + JsonVariantConst result = variant | doc["array"][0]; + REQUIRE(result == 42); + } + + SECTION("null | MemberProxy") { + doc["other"] = 42; + + JsonVariantConst result = variant | doc["other"]; + REQUIRE(result == 42); + } + } + + SECTION("int | const char*") { + variant.set(42); + std::string result = variant | "default"; + REQUIRE(result == "default"); + } + + SECTION("int | uint8_t (out of range)") { + variant.set(666); + uint8_t result = variant | static_cast(42); + REQUIRE(result == 42); + } + + SECTION("int | ElementProxy") { + variant.set(42); + doc["array"][0] = 666; + JsonVariantConst result = variant | doc["array"][0]; + REQUIRE(result == 42); + } + + SECTION("int | MemberProxy") { + variant.set(42); + doc["other"] = 666; + JsonVariantConst result = variant | doc["other"]; + REQUIRE(result == 42); + } + + SECTION("int | int") { + variant.set(0); + int result = variant | 666; + REQUIRE(result == 0); + } + + SECTION("double | int") { + // NOTE: changed the behavior to fix #981 + variant.set(666.0); + int result = variant | 42; + REQUIRE(result == 42); + } + + SECTION("bool | bool") { + variant.set(false); + bool result = variant | true; + REQUIRE(result == false); + } + + SECTION("int | bool") { + variant.set(0); + bool result = variant | true; + REQUIRE(result == true); + } + + SECTION("const char* | const char*") { + variant.set("not default"); + std::string result = variant | "default"; + REQUIRE(result == "not default"); + } + + SECTION("const char* | char*") { + char dflt[] = "default"; + variant.set("not default"); + std::string result = variant | dflt; + REQUIRE(result == "not default"); + } + + SECTION("int | char*") { + char dflt[] = "default"; + variant.set(42); + std::string result = variant | dflt; + REQUIRE(result == "default"); + } + + SECTION("const char* | int") { + variant.set("not default"); + int result = variant | 42; + REQUIRE(result == 42); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/overflow.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/overflow.cpp new file mode 100644 index 0000000..ff0e3aa --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/overflow.cpp @@ -0,0 +1,72 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +template +void shouldBeOk(TIn value) { + StaticJsonDocument<1> doc; + JsonVariant var = doc.to(); + var.set(value); + REQUIRE(var.as() == TOut(value)); +} + +template +void shouldOverflow(TIn value) { + StaticJsonDocument<1> doc; + JsonVariant var = doc.to(); + var.set(value); + REQUIRE(var.as() == 0); + REQUIRE(var.is() == false); +} + +TEST_CASE("Handle integer overflow in stored integer") { + SECTION("int8_t") { + // ok + shouldBeOk(-128); + shouldBeOk(42.0); + shouldBeOk(127); + + // too low + shouldOverflow(-128.1); + shouldOverflow(-129); + + // too high + shouldOverflow(128); + shouldOverflow(127.1); + } + + SECTION("int16_t") { + // ok + shouldBeOk(-32768); + shouldBeOk(-32767.9); + shouldBeOk(32766.9); + shouldBeOk(32767); + + // too low + shouldOverflow(-32768.1); + shouldOverflow(-32769); + + // too high + shouldOverflow(32767.1); + shouldOverflow(32768); + } + + SECTION("uint8_t") { + // ok + shouldBeOk(1); + shouldBeOk(42.0); + shouldBeOk(255); + + // too low + shouldOverflow(-1); + shouldOverflow(-0.1); + + // to high + shouldOverflow(255.1); + shouldOverflow(256); + shouldOverflow(257); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/remove.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/remove.cpp new file mode 100644 index 0000000..c744e19 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/remove.cpp @@ -0,0 +1,42 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +static const char* null = 0; + +TEST_CASE("JsonVariant::remove()") { + DynamicJsonDocument doc(4096); + JsonVariant var = doc.to(); + + SECTION("remove(int)") { + var.add(1); + var.add(2); + var.add(3); + + var.remove(1); + + REQUIRE(var.as() == "[1,3]"); + } + + SECTION("remove(const char *)") { + var["a"] = 1; + var["b"] = 2; + + var.remove("a"); + + REQUIRE(var.as() == "{\"b\":2}"); + } + + SECTION("remove(std::string)") { + var["a"] = 1; + var["b"] = 2; + + var.remove(std::string("b")); + + REQUIRE(var.as() == "{\"a\":1}"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/set.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/set.cpp new file mode 100644 index 0000000..899ef6a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/set.cpp @@ -0,0 +1,172 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +enum ErrorCode { ERROR_01 = 1, ERROR_10 = 10 }; + +TEST_CASE("JsonVariant::set() when there is enough memory") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("const char*") { + char str[16]; + + strcpy(str, "hello"); + bool result = variant.set(static_cast(str)); + strcpy(str, "world"); + + REQUIRE(result == true); + REQUIRE(variant == "world"); // stores by pointer + } + + SECTION("(const char*)0") { + bool result = variant.set(static_cast(0)); + + REQUIRE(result == true); + REQUIRE(variant.isNull()); + } + + SECTION("char*") { + char str[16]; + + strcpy(str, "hello"); + bool result = variant.set(str); + strcpy(str, "world"); + + REQUIRE(result == true); + REQUIRE(variant == "hello"); // stores by copy + } + + SECTION("(char*)0") { + bool result = variant.set(static_cast(0)); + + REQUIRE(result == true); + REQUIRE(variant.isNull()); + } + + SECTION("unsigned char*") { + char str[16]; + + strcpy(str, "hello"); + bool result = variant.set(reinterpret_cast(str)); + strcpy(str, "world"); + + REQUIRE(result == true); + REQUIRE(variant == "hello"); // stores by copy + } + + SECTION("signed char*") { + char str[16]; + + strcpy(str, "hello"); + bool result = variant.set(reinterpret_cast(str)); + strcpy(str, "world"); + + REQUIRE(result == true); + REQUIRE(variant == "hello"); // stores by copy + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("VLA") { + size_t n = 16; + char str[n]; + + strcpy(str, "hello"); + bool result = variant.set(str); + strcpy(str, "world"); + + REQUIRE(result == true); + REQUIRE(variant == "hello"); // stores by copy + } +#endif + + SECTION("std::string") { + std::string str; + + str = "hello"; + bool result = variant.set(str); + str.replace(0, 5, "world"); + + REQUIRE(result == true); + REQUIRE(variant == "hello"); // stores by copy + } + + SECTION("static JsonString") { + char str[16]; + + strcpy(str, "hello"); + bool result = variant.set(JsonString(str, JsonString::Linked)); + strcpy(str, "world"); + + REQUIRE(result == true); + REQUIRE(variant == "world"); // stores by pointer + } + + SECTION("non-static JsonString") { + char str[16]; + + strcpy(str, "hello"); + bool result = variant.set(JsonString(str, JsonString::Copied)); + strcpy(str, "world"); + + REQUIRE(result == true); + REQUIRE(variant == "hello"); // stores by copy + } + + SECTION("enum") { + ErrorCode code = ERROR_10; + + bool result = variant.set(code); + + REQUIRE(result == true); + REQUIRE(variant.is() == true); + REQUIRE(variant.as() == 10); + } +} + +TEST_CASE("JsonVariant::set() with not enough memory") { + StaticJsonDocument<1> doc; + + JsonVariant v = doc.to(); + + SECTION("std::string") { + bool result = v.set(std::string("hello world!!")); + + REQUIRE(result == false); + REQUIRE(v.isNull()); + } + + SECTION("Serialized") { + bool result = v.set(serialized(std::string("hello world!!"))); + + REQUIRE(result == false); + REQUIRE(v.isNull()); + } + + SECTION("char*") { + char s[] = "hello world!!"; + bool result = v.set(s); + + REQUIRE(result == false); + REQUIRE(v.isNull()); + } +} + +TEST_CASE("JsonVariant::set(DynamicJsonDocument)") { + DynamicJsonDocument doc1(1024); + doc1["hello"] = "world"; + + DynamicJsonDocument doc2(1024); + JsonVariant v = doc2.to(); + + // Should copy the doc + v.set(doc1); + doc1.clear(); + + std::string json; + serializeJson(doc2, json); + REQUIRE(json == "{\"hello\":\"world\"}"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/subscript.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/subscript.cpp new file mode 100644 index 0000000..f466cd2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/subscript.cpp @@ -0,0 +1,204 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonVariant::operator[]") { + DynamicJsonDocument doc(4096); + JsonVariant var = doc.to(); + + SECTION("The JsonVariant is null") { + REQUIRE(0 == var.size()); + REQUIRE(var["0"].isNull()); + REQUIRE(var[0].isNull()); + } + + SECTION("The JsonVariant is a string") { + var.set("hello world"); + REQUIRE(0 == var.size()); + REQUIRE(var["0"].isNull()); + REQUIRE(var[0].isNull()); + } + + SECTION("The JsonVariant is a JsonArray") { + JsonArray array = var.to(); + + SECTION("get value") { + array.add("element at index 0"); + array.add("element at index 1"); + + REQUIRE(2 == var.size()); + var[0].as(); + // REQUIRE(std::string("element at index 0") == ); + REQUIRE(std::string("element at index 1") == var[1]); + REQUIRE(std::string("element at index 0") == + var[static_cast(0)]); // issue #381 + REQUIRE(var[666].isNull()); + REQUIRE(var[3].isNull()); + REQUIRE(var["0"].isNull()); + } + + SECTION("set value") { + array.add("hello"); + + var[1] = "world"; + + REQUIRE(var.size() == 2); + REQUIRE(std::string("world") == var[1]); + } + + SECTION("set value in a nested object") { + array.createNestedObject(); + + var[0]["hello"] = "world"; + + REQUIRE(1 == var.size()); + REQUIRE(1 == var[0].size()); + REQUIRE(std::string("world") == var[0]["hello"]); + } + + SECTION("variant[0] when variant contains an integer") { + var.set(123); + + var[0] = 345; // no-op + + REQUIRE(var.is()); + REQUIRE(var.as() == 123); + } + } + + SECTION("The JsonVariant is a JsonObject") { + JsonObject object = var.to(); + + SECTION("get value") { + object["a"] = "element at key \"a\""; + object["b"] = "element at key \"b\""; + + REQUIRE(2 == var.size()); + REQUIRE(std::string("element at key \"a\"") == var["a"]); + REQUIRE(std::string("element at key \"b\"") == var["b"]); + REQUIRE(var["c"].isNull()); + REQUIRE(var[0].isNull()); + } + + SECTION("set value, key is a const char*") { + var["hello"] = "world"; + + REQUIRE(1 == var.size()); + REQUIRE(std::string("world") == var["hello"]); + } + + SECTION("set value, key is a char[]") { + char key[] = "hello"; + var[key] = "world"; + key[0] = '!'; // make sure the key is duplicated + + REQUIRE(1 == var.size()); + REQUIRE(std::string("world") == var["hello"]); + } + + SECTION("var[key].to()") { + JsonArray arr = var["hello"].to(); + REQUIRE(arr.isNull() == false); + } + } + +#if defined(HAS_VARIABLE_LENGTH_ARRAY) && \ + !defined(SUBSCRIPT_CONFLICTS_WITH_BUILTIN_OPERATOR) + SECTION("key is a VLA") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + deserializeJson(doc, "{\"hello\":\"world\"}"); + JsonVariant variant = doc.as(); + + REQUIRE(std::string("world") == variant[vla]); + } + + SECTION("key is a VLA, const JsonVariant") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + deserializeJson(doc, "{\"hello\":\"world\"}"); + const JsonVariant variant = doc.as(); + + REQUIRE(std::string("world") == variant[vla]); + } +#endif +} + +TEST_CASE("JsonVariantConst::operator[]") { + DynamicJsonDocument doc(4096); + JsonVariant var = doc.to(); + JsonVariantConst cvar = var; + + SECTION("The JsonVariant is null") { + REQUIRE(0 == cvar.size()); + REQUIRE(cvar["0"].isNull()); + REQUIRE(cvar[0].isNull()); + } + + SECTION("The JsonVariant is a string") { + var.set("hello world"); + REQUIRE(0 == cvar.size()); + REQUIRE(cvar["0"].isNull()); + REQUIRE(cvar[0].isNull()); + } + + SECTION("The JsonVariant is a JsonArray") { + JsonArray array = var.to(); + + SECTION("get value") { + array.add("element at index 0"); + array.add("element at index 1"); + + REQUIRE(2 == cvar.size()); + REQUIRE(std::string("element at index 0") == cvar[0]); + REQUIRE(std::string("element at index 1") == cvar[1]); + REQUIRE(std::string("element at index 0") == + var[static_cast(0)]); // issue #381 + REQUIRE(cvar[666].isNull()); + REQUIRE(cvar[3].isNull()); + REQUIRE(cvar["0"].isNull()); + } + } + + SECTION("The JsonVariant is a JsonObject") { + JsonObject object = var.to(); + + SECTION("get value") { + object["a"] = "element at key \"a\""; + object["b"] = "element at key \"b\""; + + REQUIRE(2 == cvar.size()); + REQUIRE(std::string("element at key \"a\"") == cvar["a"]); + REQUIRE(std::string("element at key \"b\"") == cvar["b"]); + REQUIRE(cvar["c"].isNull()); + REQUIRE(cvar[0].isNull()); + } + } + + SECTION("Auto promote null JsonVariant to JsonObject") { + var["hello"] = "world"; + + REQUIRE(var.is() == true); + } + + SECTION("Don't auto promote non-null JsonVariant to JsonObject") { + var.set(42); + var["hello"] = "world"; + + REQUIRE(var.is() == false); + } + + SECTION("Don't auto promote null JsonVariant to JsonObject when reading") { + const char* value = var["hello"]; + + REQUIRE(var.is() == false); + REQUIRE(value == 0); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/types.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/types.cpp new file mode 100644 index 0000000..448d41a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/types.cpp @@ -0,0 +1,163 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include +#include + +template +void checkValue(T expected) { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + variant.set(expected); + REQUIRE(expected == variant.as()); +} + +template +void checkReference(T &expected) { + JsonVariant variant = expected; + REQUIRE(expected == variant.as()); +} + +template +void checkNumericType() { + DynamicJsonDocument docMin(4096), docMax(4096); + JsonVariant variantMin = docMin.to(); + JsonVariant variantMax = docMax.to(); + + T min = std::numeric_limits::min(); + T max = std::numeric_limits::max(); + + variantMin.set(min); + variantMax.set(max); + + REQUIRE(min == variantMin.as()); + REQUIRE(max == variantMax.as()); +} + +TEST_CASE("JsonVariant set()/get()") { +#if ARDUINOJSON_USE_LONG_LONG + SECTION("SizeOfJsonInteger") { + REQUIRE(8 == sizeof(JsonInteger)); + } +#endif + + SECTION("Null") { + checkValue(NULL); + } + SECTION("const char*") { + checkValue("hello"); + } + SECTION("std::string") { + checkValue("hello"); + } + + SECTION("False") { + checkValue(false); + } + SECTION("True") { + checkValue(true); + } + + SECTION("Double") { + checkNumericType(); + } + SECTION("Float") { + checkNumericType(); + } + SECTION("SChar") { + checkNumericType(); + } + SECTION("SInt") { + checkNumericType(); + } + SECTION("SLong") { + checkNumericType(); + } + SECTION("SShort") { + checkNumericType(); + } + SECTION("UChar") { + checkNumericType(); + } + SECTION("UInt") { + checkNumericType(); + } + SECTION("ULong") { + checkNumericType(); + } + SECTION("UShort") { + checkNumericType(); + } +#if ARDUINOJSON_USE_LONG_LONG + SECTION("LongLong") { + checkNumericType(); + } + SECTION("ULongLong") { + checkNumericType(); + } +#endif + + SECTION("Int8") { + checkNumericType(); + } + SECTION("Uint8") { + checkNumericType(); + } + SECTION("Int16") { + checkNumericType(); + } + SECTION("Uint16") { + checkNumericType(); + } + SECTION("Int32") { + checkNumericType(); + } + SECTION("Uint32") { + checkNumericType(); + } +#if ARDUINOJSON_USE_LONG_LONG + SECTION("Int64") { + checkNumericType(); + } + SECTION("Uint64") { + checkNumericType(); + } +#endif + + SECTION("CanStoreObject") { + DynamicJsonDocument doc(4096); + JsonObject object = doc.to(); + + checkValue(object); + } +} + +TEST_CASE("volatile") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("volatile int") { + volatile int f = 42; + variant.set(f); + CHECK(variant.is() == true); + CHECK(variant.as() == 42); + } + + SECTION("volatile float") { // issue #1557 + volatile float f = 3.14f; + variant.set(f); + CHECK(variant.is() == true); + CHECK(variant.as() == 3.14f); + } + + SECTION("volatile double") { + volatile double f = 3.14; + variant.set(f); + CHECK(variant.is() == true); + CHECK(variant.as() == 3.14); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/unbound.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/unbound.cpp new file mode 100644 index 0000000..8089e1c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/JsonVariant/unbound.cpp @@ -0,0 +1,66 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("Unbound JsonVariant") { + JsonVariant variant; + + SECTION("as()") { + CHECK(variant.as() == false); + CHECK(variant.as() == 0); + CHECK(variant.as() == 0.0f); + CHECK(variant.as() == 0); + CHECK(variant.as() == "null"); + CHECK(variant.as().isNull()); + CHECK(variant.as().isNull()); + CHECK(variant.as().isNull()); + CHECK(variant.as().isNull()); + CHECK(variant.as().isNull()); + CHECK(variant.as().isNull()); + CHECK(variant.as().isNull()); + } + + SECTION("is()") { + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + CHECK_FALSE(variant.is()); + } + + SECTION("set()") { + CHECK_FALSE(variant.set("42")); + CHECK_FALSE(variant.set(42.0)); + CHECK_FALSE(variant.set(42L)); + CHECK_FALSE(variant.set(42U)); + CHECK_FALSE(variant.set(serialized("42"))); + CHECK_FALSE(variant.set(true)); + } + + SECTION("add()") { + CHECK_FALSE(variant.add("42")); + CHECK_FALSE(variant.add(42.0)); + CHECK_FALSE(variant.add(42L)); + CHECK_FALSE(variant.add(42U)); + CHECK_FALSE(variant.add(serialized("42"))); + CHECK_FALSE(variant.add(true)); + } + + SECTION("operator[]") { + CHECK(variant[0].isNull()); + CHECK(variant["key"].isNull()); + CHECK_FALSE(variant[0].set(1)); + CHECK_FALSE(variant["key"].set(1)); + CHECK_FALSE(variant[std::string("key")].set(1)); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/CMakeLists.txt new file mode 100644 index 0000000..80ba3c6 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/CMakeLists.txt @@ -0,0 +1,18 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(MemoryPoolTests + allocVariant.cpp + clear.cpp + saveString.cpp + size.cpp + StringCopier.cpp +) + +add_test(MemoryPool MemoryPoolTests) + +set_tests_properties(MemoryPool + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/StringCopier.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/StringCopier.cpp new file mode 100644 index 0000000..ba39cfd --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/StringCopier.cpp @@ -0,0 +1,93 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("StringCopier") { + char buffer[4096]; + + SECTION("Works when buffer is big enough") { + MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(5))); + StringCopier str(pool); + + str.startString(); + str.append("hello"); + + REQUIRE(str.isValid() == true); + REQUIRE(str.str() == "hello"); + REQUIRE(pool.overflowed() == false); + } + + SECTION("Returns null when too small") { + MemoryPool pool(buffer, sizeof(void*)); + StringCopier str(pool); + + str.startString(); + str.append("hello world!"); + + REQUIRE(str.isValid() == false); + REQUIRE(pool.overflowed() == true); + } + + SECTION("Increases size of memory pool") { + MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(6))); + StringCopier str(pool); + + str.startString(); + str.save(); + + REQUIRE(1 == pool.size()); + REQUIRE(pool.overflowed() == false); + } + + SECTION("Works when memory pool is 0 bytes") { + MemoryPool pool(buffer, 0); + StringCopier str(pool); + + str.startString(); + REQUIRE(str.isValid() == false); + REQUIRE(pool.overflowed() == true); + } +} + +static const char* addStringToPool(MemoryPool& pool, const char* s) { + StringCopier str(pool); + str.startString(); + str.append(s); + return str.save().c_str(); +} + +TEST_CASE("StringCopier::save() deduplicates strings") { + char buffer[4096]; + MemoryPool pool(buffer, 4096); + + SECTION("Basic") { + const char* s1 = addStringToPool(pool, "hello"); + const char* s2 = addStringToPool(pool, "world"); + const char* s3 = addStringToPool(pool, "hello"); + + REQUIRE(s1 == s3); + REQUIRE(s2 != s3); + REQUIRE(pool.size() == 12); + } + + SECTION("Requires terminator") { + const char* s1 = addStringToPool(pool, "hello world"); + const char* s2 = addStringToPool(pool, "hello"); + + REQUIRE(s2 != s1); + REQUIRE(pool.size() == 12 + 6); + } + + SECTION("Don't overrun") { + const char* s1 = addStringToPool(pool, "hello world"); + const char* s2 = addStringToPool(pool, "wor"); + + REQUIRE(s2 != s1); + REQUIRE(pool.size() == 12 + 4); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/allocVariant.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/allocVariant.cpp new file mode 100644 index 0000000..606d6d7 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/allocVariant.cpp @@ -0,0 +1,50 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("MemoryPool::allocVariant()") { + char buffer[4096]; + + SECTION("Returns different pointer") { + MemoryPool pool(buffer, sizeof(buffer)); + + VariantSlot* s1 = pool.allocVariant(); + REQUIRE(s1 != 0); + VariantSlot* s2 = pool.allocVariant(); + REQUIRE(s2 != 0); + + REQUIRE(s1 != s2); + } + + SECTION("Returns aligned pointers") { + MemoryPool pool(buffer, sizeof(buffer)); + + REQUIRE(isAligned(pool.allocVariant())); + REQUIRE(isAligned(pool.allocVariant())); + } + + SECTION("Returns zero if capacity is 0") { + MemoryPool pool(buffer, 0); + + REQUIRE(pool.allocVariant() == 0); + } + + SECTION("Returns zero if buffer is null") { + MemoryPool pool(0, sizeof(buffer)); + + REQUIRE(pool.allocVariant() == 0); + } + + SECTION("Returns zero if capacity is insufficient") { + MemoryPool pool(buffer, sizeof(VariantSlot)); + + pool.allocVariant(); + + REQUIRE(pool.allocVariant() == 0); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/clear.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/clear.cpp new file mode 100644 index 0000000..f4ab796 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/clear.cpp @@ -0,0 +1,32 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +static const size_t poolCapacity = 512; + +TEST_CASE("MemoryPool::clear()") { + char buffer[poolCapacity]; + MemoryPool pool(buffer, sizeof(buffer)); + + SECTION("Discards allocated variants") { + pool.allocVariant(); + + pool.clear(); + REQUIRE(pool.size() == 0); + } + + SECTION("Discards allocated strings") { + pool.saveString(adaptString(const_cast("123456789"))); + REQUIRE(pool.size() == 10); + + pool.clear(); + + REQUIRE(pool.size() == 0); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/saveString.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/saveString.cpp new file mode 100644 index 0000000..0c16b67 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/saveString.cpp @@ -0,0 +1,106 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +static const char *saveString(MemoryPool &pool, const char *s) { + return pool.saveString(adaptString(const_cast(s))); +} + +static const char *saveString(MemoryPool &pool, const char *s, size_t n) { + return pool.saveString(adaptString(s, n)); +} + +TEST_CASE("MemoryPool::saveString()") { + char buffer[32]; + MemoryPool pool(buffer, 32); + + SECTION("Duplicates different strings") { + const char *a = saveString(pool, "hello"); + const char *b = saveString(pool, "world"); + REQUIRE(a != b); + REQUIRE(pool.size() == 6 + 6); + } + + SECTION("Deduplicates identical strings") { + const char *a = saveString(pool, "hello"); + const char *b = saveString(pool, "hello"); + REQUIRE(a == b); + REQUIRE(pool.size() == 6); + } + + SECTION("Deduplicates identical strings that contain NUL") { + const char *a = saveString(pool, "hello\0world", 11); + const char *b = saveString(pool, "hello\0world", 11); + REQUIRE(a == b); + REQUIRE(pool.size() == 12); + } + + SECTION("Reuse part of a string if it ends with NUL") { + const char *a = saveString(pool, "hello\0world", 11); + const char *b = saveString(pool, "hello"); + REQUIRE(a == b); + REQUIRE(pool.size() == 12); + } + + SECTION("Don't stop on first NUL") { + const char *a = saveString(pool, "hello"); + const char *b = saveString(pool, "hello\0world", 11); + REQUIRE(a != b); + REQUIRE(pool.size() == 18); + } + + SECTION("Returns NULL when full") { + REQUIRE(pool.capacity() == 32); + + const void *p1 = saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + REQUIRE(p1 != 0); + REQUIRE(pool.size() == 32); + + const void *p2 = saveString(pool, "b"); + REQUIRE(p2 == 0); + } + + SECTION("Returns NULL when pool is too small") { + const void *p = saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + REQUIRE(0 == p); + } + + SECTION("Returns NULL when buffer is NULL") { + MemoryPool pool2(0, 32); + REQUIRE(0 == saveString(pool2, "a")); + } + + SECTION("Returns NULL when capacity is 0") { + MemoryPool pool2(buffer, 0); + REQUIRE(0 == saveString(pool2, "a")); + } + + SECTION("Returns same address after clear()") { + const void *a = saveString(pool, "hello"); + pool.clear(); + const void *b = saveString(pool, "world"); + + REQUIRE(a == b); + } + + SECTION("Can use full capacity when fresh") { + const void *a = saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + REQUIRE(a != 0); + } + + SECTION("Can use full capacity after clear") { + saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + pool.clear(); + + const void *a = saveString(pool, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + + REQUIRE(a != 0); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/size.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/size.cpp new file mode 100644 index 0000000..47603fe --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MemoryPool/size.cpp @@ -0,0 +1,35 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("MemoryPool::capacity()") { + char buffer[4096]; + const size_t capacity = 64; + MemoryPool pool(buffer, capacity); + REQUIRE(capacity == pool.capacity()); +} + +TEST_CASE("MemoryPool::size()") { + char buffer[4096]; + MemoryPool pool(buffer, sizeof(buffer)); + + SECTION("Initial size is 0") { + REQUIRE(0 == pool.size()); + } + + SECTION("Doesn't grow when memory pool is full") { + const size_t variantCount = sizeof(buffer) / sizeof(VariantSlot); + + for (size_t i = 0; i < variantCount; i++) pool.allocVariant(); + size_t size = pool.size(); + + pool.allocVariant(); + + REQUIRE(size == pool.size()); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/CMakeLists.txt new file mode 100644 index 0000000..114ebb7 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/CMakeLists.txt @@ -0,0 +1,30 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(MiscTests + arithmeticCompare.cpp + conflicts.cpp + deprecated.cpp + FloatParts.cpp + JsonString.cpp + NoArduinoHeader.cpp + printable.cpp + Readers.cpp + StringAdapters.cpp + StringWriter.cpp + TypeTraits.cpp + unsigned_char.cpp + Utf16.cpp + Utf8.cpp + version.cpp +) + +set_target_properties(MiscTests PROPERTIES UNITY_BUILD OFF) + +add_test(Misc MiscTests) + +set_tests_properties(Misc + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/FloatParts.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/FloatParts.cpp new file mode 100644 index 0000000..736b176 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/FloatParts.cpp @@ -0,0 +1,44 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("FloatParts") { + SECTION("1.7976931348623157E+308") { + FloatParts parts(1.7976931348623157E+308); + REQUIRE(parts.integral == 1); + REQUIRE(parts.decimal == 797693135); + REQUIRE(parts.decimalPlaces == 9); + REQUIRE(parts.exponent == 308); + } + + SECTION("4.94065645841247e-324") { + FloatParts parts(4.94065645841247e-324); + REQUIRE(parts.integral == 4); + REQUIRE(parts.decimal == 940656458); + REQUIRE(parts.decimalPlaces == 9); + REQUIRE(parts.exponent == -324); + } +} + +TEST_CASE("FloatParts") { + SECTION("3.4E+38") { + FloatParts parts(3.4E+38f); + REQUIRE(parts.integral == 3); + REQUIRE(parts.decimal == 4); + REQUIRE(parts.decimalPlaces == 1); + REQUIRE(parts.exponent == 38); + } + + SECTION("1.17549435e−38") { + FloatParts parts(1.17549435e-38f); + REQUIRE(parts.integral == 1); + REQUIRE(parts.decimal == 175494); + REQUIRE(parts.decimalPlaces == 6); + REQUIRE(parts.exponent == -38); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/JsonString.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/JsonString.cpp new file mode 100644 index 0000000..557f993 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/JsonString.cpp @@ -0,0 +1,103 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +#include + +TEST_CASE("JsonString") { + SECTION("Default constructor creates a null JsonString") { + JsonString s; + + CHECK(s.isNull() == true); + CHECK(s.c_str() == 0); + CHECK(s.isLinked() == true); + CHECK(s == JsonString()); + CHECK(s != ""); + } + + SECTION("Null converts to false") { + JsonString s; + + CHECK(bool(s) == false); + } + + SECTION("Empty string converts to true") { + JsonString s(""); + + CHECK(bool(s) == true); + } + + SECTION("Non-empty string converts to true") { + JsonString s(""); + + CHECK(bool(s) == true); + } + + SECTION("Null strings equals each others") { + JsonString a, b; + + CHECK(a == b); + CHECK_FALSE(a != b); + } + + SECTION("Null and empty strings differ") { + JsonString a, b(""); + + CHECK_FALSE(a == b); + CHECK(a != b); + + CHECK_FALSE(b == a); + CHECK(b != a); + } + + SECTION("Null and non-empty strings differ") { + JsonString a, b("hello"); + + CHECK_FALSE(a == b); + CHECK(a != b); + + CHECK_FALSE(b == a); + CHECK(b != a); + } + + SECTION("Compare different strings") { + JsonString a("hello"), b("world"); + + CHECK_FALSE(a == b); + CHECK(a != b); + } + + SECTION("Compare identical by pointer") { + JsonString a("hello"), b("hello"); + + CHECK(a == b); + CHECK_FALSE(a != b); + } + + SECTION("Compare identical by value") { + char s1[] = "hello"; + char s2[] = "hello"; + JsonString a(s1), b(s2); + + CHECK(a == b); + CHECK_FALSE(a != b); + } + + SECTION("std::stream") { + std::stringstream ss; + ss << JsonString("hello world!"); + CHECK(ss.str() == "hello world!"); + } + + SECTION("Construct with a size") { + JsonString s("hello world", 5); + + CHECK(s.size() == 5); + CHECK(s.isLinked() == true); + CHECK(s == "hello"); + CHECK(s != "hello world"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/NoArduinoHeader.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/NoArduinoHeader.cpp new file mode 100644 index 0000000..43a82b2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/NoArduinoHeader.cpp @@ -0,0 +1,20 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINO 1 +#define ARDUINOJSON_ENABLE_PROGMEM 0 +#define ARDUINOJSON_ENABLE_ARDUINO_STRING 0 +#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0 +#define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0 +#include + +#include + +TEST_CASE("Arduino.h") { +#ifdef ARDUINO_H_INCLUDED + FAIL("Arduino.h should not be included"); +#else + INFO("Arduino.h not included"); +#endif +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Readers.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Readers.cpp new file mode 100644 index 0000000..e46ad1e --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Readers.cpp @@ -0,0 +1,225 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("Reader") { + SECTION("read()") { + std::istringstream src("\x01\xFF"); + Reader reader(src); + + REQUIRE(reader.read() == 0x01); + REQUIRE(reader.read() == 0xFF); + REQUIRE(reader.read() == -1); + } + + SECTION("readBytes() all at once") { + std::istringstream src("ABC"); + Reader reader(src); + + char buffer[8] = "abcd"; + REQUIRE(reader.readBytes(buffer, 4) == 3); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'd'); + } + + SECTION("readBytes() in two parts") { + std::istringstream src("ABCDEF"); + Reader reader(src); + + char buffer[12] = "abcdefg"; + REQUIRE(reader.readBytes(buffer, 4) == 4); + REQUIRE(reader.readBytes(buffer + 4, 4) == 2); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'D'); + REQUIRE(buffer[4] == 'E'); + REQUIRE(buffer[5] == 'F'); + REQUIRE(buffer[6] == 'g'); + } +} + +TEST_CASE("BoundedReader") { + SECTION("read") { + BoundedReader reader("\x01\xFF", 2); + REQUIRE(reader.read() == 0x01); + REQUIRE(reader.read() == 0xFF); + REQUIRE(reader.read() == -1); + REQUIRE(reader.read() == -1); + } + + SECTION("readBytes() all at once") { + BoundedReader reader("ABCD", 3); + + char buffer[8] = "abcd"; + REQUIRE(reader.readBytes(buffer, 4) == 3); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'd'); + } + + SECTION("readBytes() in two parts") { + BoundedReader reader("ABCDEF", 6); + + char buffer[8] = "abcdefg"; + REQUIRE(reader.readBytes(buffer, 4) == 4); + REQUIRE(reader.readBytes(buffer + 4, 4) == 2); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'D'); + REQUIRE(buffer[4] == 'E'); + REQUIRE(buffer[5] == 'F'); + REQUIRE(buffer[6] == 'g'); + } +} + +TEST_CASE("Reader") { + SECTION("read()") { + Reader reader("\x01\xFF\x00\x12"); + REQUIRE(reader.read() == 0x01); + REQUIRE(reader.read() == 0xFF); + REQUIRE(reader.read() == 0); + REQUIRE(reader.read() == 0x12); + } + + SECTION("readBytes() all at once") { + Reader reader("ABCD"); + + char buffer[8] = "abcd"; + REQUIRE(reader.readBytes(buffer, 3) == 3); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'd'); + } + + SECTION("readBytes() in two parts") { + Reader reader("ABCDEF"); + + char buffer[8] = "abcdefg"; + REQUIRE(reader.readBytes(buffer, 4) == 4); + REQUIRE(reader.readBytes(buffer + 4, 2) == 2); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'D'); + REQUIRE(buffer[4] == 'E'); + REQUIRE(buffer[5] == 'F'); + REQUIRE(buffer[6] == 'g'); + } +} + +TEST_CASE("IteratorReader") { + SECTION("read()") { + std::string src("\x01\xFF"); + IteratorReader reader(src.begin(), src.end()); + + REQUIRE(reader.read() == 0x01); + REQUIRE(reader.read() == 0xFF); + REQUIRE(reader.read() == -1); + } + + SECTION("readBytes() all at once") { + std::string src("ABC"); + IteratorReader reader(src.begin(), src.end()); + + char buffer[8] = "abcd"; + REQUIRE(reader.readBytes(buffer, 4) == 3); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'd'); + } + + SECTION("readBytes() in two parts") { + std::string src("ABCDEF"); + IteratorReader reader(src.begin(), src.end()); + + char buffer[12] = "abcdefg"; + REQUIRE(reader.readBytes(buffer, 4) == 4); + REQUIRE(reader.readBytes(buffer + 4, 4) == 2); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'D'); + REQUIRE(buffer[4] == 'E'); + REQUIRE(buffer[5] == 'F'); + REQUIRE(buffer[6] == 'g'); + } +} + +class StreamStub : public Stream { + public: + StreamStub(const char* s) : _stream(s) {} + + int read() { + return _stream.get(); + } + + size_t readBytes(char* buffer, size_t length) { + _stream.read(buffer, static_cast(length)); + return static_cast(_stream.gcount()); + } + + private: + std::istringstream _stream; +}; + +TEST_CASE("Reader") { + SECTION("read()") { + StreamStub src("\x01\xFF"); + Reader reader(src); + + REQUIRE(reader.read() == 0x01); + REQUIRE(reader.read() == 0xFF); + REQUIRE(reader.read() == -1); + } + + SECTION("readBytes() all at once") { + StreamStub src("ABC"); + Reader reader(src); + + char buffer[8] = "abcd"; + REQUIRE(reader.readBytes(buffer, 4) == 3); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'd'); + } + + SECTION("readBytes() in two parts") { + StreamStub src("ABCDEF"); + Reader reader(src); + + char buffer[12] = "abcdefg"; + REQUIRE(reader.readBytes(buffer, 4) == 4); + REQUIRE(reader.readBytes(buffer + 4, 4) == 2); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'D'); + REQUIRE(buffer[4] == 'E'); + REQUIRE(buffer[5] == 'F'); + REQUIRE(buffer[6] == 'g'); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/StringAdapters.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/StringAdapters.cpp new file mode 100644 index 0000000..82139ee --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/StringAdapters.cpp @@ -0,0 +1,202 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_ENABLE_PROGMEM 1 +#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1 + +#include "custom_string.hpp" +#include "weird_strcmp.hpp" + +#include + +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("ZeroTerminatedRamString") { + SECTION("null") { + ZeroTerminatedRamString s = adaptString(static_cast(0)); + + CHECK(s.isNull() == true); + CHECK(s.size() == 0); + } + + SECTION("non-null") { + ZeroTerminatedRamString s = adaptString("bravo"); + + CHECK(s.isNull() == false); + CHECK(s.size() == 5); + } +} + +TEST_CASE("SizedRamString") { + SECTION("null") { + SizedRamString s = adaptString(static_cast(0), 10); + + CHECK(s.isNull() == true); + } + + SECTION("non-null") { + SizedRamString s = adaptString("bravo", 5); + + CHECK(s.isNull() == false); + CHECK(s.size() == 5); + } +} + +TEST_CASE("FlashString") { + SECTION("null") { + FlashString s = adaptString(static_cast(0)); + + CHECK(s.isNull() == true); + CHECK(s.size() == 0); + } + + SECTION("non-null") { + FlashString s = adaptString(F("bravo")); + + CHECK(s.isNull() == false); + CHECK(s.size() == 5); + } +} + +TEST_CASE("std::string") { + std::string orig("bravo"); + SizedRamString s = adaptString(orig); + + CHECK(s.isNull() == false); + CHECK(s.size() == 5); +} + +TEST_CASE("Arduino String") { + ::String orig("bravo"); + SizedRamString s = adaptString(orig); + + CHECK(s.isNull() == false); + CHECK(s.size() == 5); +} + +TEST_CASE("custom_string") { + custom_string orig("bravo"); + SizedRamString s = adaptString(orig); + + CHECK(s.isNull() == false); + CHECK(s.size() == 5); +} + +TEST_CASE("IsString") { + SECTION("std::string") { + CHECK(IsString::value == true); + } + + SECTION("basic_string") { + CHECK(IsString >::value == false); + } + + SECTION("custom_string") { + CHECK(IsString::value == true); + } + + SECTION("const __FlashStringHelper*") { + CHECK(IsString::value == true); + } + + SECTION("const char*") { + CHECK(IsString::value == true); + } + + SECTION("const char[]") { + CHECK(IsString::value == true); + } +} + +TEST_CASE("stringCompare") { + SECTION("ZeroTerminatedRamString vs ZeroTerminatedRamString") { + CHECK(stringCompare(adaptString("bravo"), adaptString("alpha")) > 0); + CHECK(stringCompare(adaptString("bravo"), adaptString("bravo")) == 0); + CHECK(stringCompare(adaptString("bravo"), adaptString("charlie")) < 0); + } + + SECTION("ZeroTerminatedRamString vs SizedRamString") { + CHECK(stringCompare(adaptString("bravo"), adaptString("alpha?", 5)) > 0); + CHECK(stringCompare(adaptString("bravo"), adaptString("bravo?", 4)) > 0); + CHECK(stringCompare(adaptString("bravo"), adaptString("bravo?", 5)) == 0); + CHECK(stringCompare(adaptString("bravo"), adaptString("bravo?", 6)) < 0); + CHECK(stringCompare(adaptString("bravo"), adaptString("charlie?", 7)) < 0); + } + + SECTION("SizedRamString vs SizedRamString") { + // clang-format off + CHECK(stringCompare(adaptString("bravo!", 5), adaptString("alpha?", 5)) > 0); + CHECK(stringCompare(adaptString("bravo!", 5), adaptString("bravo?", 5)) == 0); + CHECK(stringCompare(adaptString("bravo!", 5), adaptString("charlie?", 7)) < 0); + + CHECK(stringCompare(adaptString("bravo!", 5), adaptString("bravo!", 4)) > 0); + CHECK(stringCompare(adaptString("bravo!", 5), adaptString("bravo!", 5)) == 0); + CHECK(stringCompare(adaptString("bravo!", 5), adaptString("bravo!", 6)) < 0); + // clang-format on + } + + SECTION("FlashString vs FlashString") { + // clang-format off + CHECK(stringCompare(adaptString(F("bravo")), adaptString(F("alpha"))) > 0); + CHECK(stringCompare(adaptString(F("bravo")), adaptString(F("bravo"))) == 0); + CHECK(stringCompare(adaptString(F("bravo")), adaptString(F("charlie"))) < 0); + // clang-format on + } + + SECTION("FlashString vs SizedRamString") { + // clang-format off + CHECK(stringCompare(adaptString(F("bravo")), adaptString("alpha?", 5)) > 0); + CHECK(stringCompare(adaptString(F("bravo")), adaptString("bravo?", 5)) == 0); + CHECK(stringCompare(adaptString(F("bravo")), adaptString("charlie?", 7)) < 0); + + CHECK(stringCompare(adaptString(F("bravo")), adaptString("bravo!", 4)) > 0); + CHECK(stringCompare(adaptString(F("bravo")), adaptString("bravo!", 5)) == 0); + CHECK(stringCompare(adaptString(F("bravo")), adaptString("bravo!", 6)) < 0); + // clang-format on + } + + SECTION("ZeroTerminatedRamString vs FlashString") { + // clang-format off + CHECK(stringCompare(adaptString("bravo"), adaptString(F("alpha?"), 5)) > 0); + CHECK(stringCompare(adaptString("bravo"), adaptString(F("bravo?"), 4)) > 0); + CHECK(stringCompare(adaptString("bravo"), adaptString(F("bravo?"), 5)) == 0); + CHECK(stringCompare(adaptString("bravo"), adaptString(F("bravo?"), 6)) < 0); + CHECK(stringCompare(adaptString("bravo"), adaptString(F("charlie?"), 7)) < 0); + // clang-format on + } +} + +TEST_CASE("stringEquals()") { + SECTION("ZeroTerminatedRamString vs ZeroTerminatedRamString") { + CHECK(stringEquals(adaptString("bravo"), adaptString("brav")) == false); + CHECK(stringEquals(adaptString("bravo"), adaptString("bravo")) == true); + CHECK(stringEquals(adaptString("bravo"), adaptString("bravo!")) == false); + } + + SECTION("ZeroTerminatedRamString vs SizedRamString") { + // clang-format off + CHECK(stringEquals(adaptString("bravo"), adaptString("bravo!", 4)) == false); + CHECK(stringEquals(adaptString("bravo"), adaptString("bravo!", 5)) == true); + CHECK(stringEquals(adaptString("bravo"), adaptString("bravo!", 6)) == false); + // clang-format on + } + + SECTION("FlashString vs SizedRamString") { + // clang-format off + CHECK(stringEquals(adaptString(F("bravo")), adaptString("bravo!", 4)) == false); + CHECK(stringEquals(adaptString(F("bravo")), adaptString("bravo!", 5)) == true); + CHECK(stringEquals(adaptString(F("bravo")), adaptString("bravo!", 6)) == false); + // clang-format on + } + + SECTION("SizedRamString vs SizedRamString") { + // clang-format off + CHECK(stringEquals(adaptString("bravo?", 5), adaptString("bravo!", 4)) == false); + CHECK(stringEquals(adaptString("bravo?", 5), adaptString("bravo!", 5)) == true); + CHECK(stringEquals(adaptString("bravo?", 5), adaptString("bravo!", 6)) == false); + // clang-format on + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/StringWriter.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/StringWriter.cpp new file mode 100644 index 0000000..bdb5bdb --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/StringWriter.cpp @@ -0,0 +1,153 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1 +#define ARDUINOJSON_STRING_BUFFER_SIZE 5 +#include +#include +#include "custom_string.hpp" + +using namespace ARDUINOJSON_NAMESPACE; + +template +static size_t print(StringWriter& writer, const char* s) { + return writer.write(reinterpret_cast(s), strlen(s)); +} + +template +static size_t print(StringWriter& writer, char c) { + return writer.write(static_cast(c)); +} + +template +void common_tests(StringWriter& writer, const String& output) { + SECTION("InitialState") { + REQUIRE(std::string("") == output); + } + + SECTION("EmptyString") { + REQUIRE(0 == print(writer, "")); + REQUIRE(std::string("") == output); + } + + SECTION("OneString") { + REQUIRE(4 == print(writer, "ABCD")); + REQUIRE(std::string("ABCD") == output); + } + + SECTION("TwoStrings") { + REQUIRE(4 == print(writer, "ABCD")); + REQUIRE(4 == print(writer, "EFGH")); + REQUIRE(std::string("ABCDEFGH") == output); + } +} + +TEST_CASE("StaticStringWriter") { + char output[20] = {0}; + StaticStringWriter writer(output, sizeof(output)); + + common_tests(writer, static_cast(output)); + + SECTION("OverCapacity") { + REQUIRE(20 == print(writer, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")); + REQUIRE(0 == print(writer, "ABC")); + REQUIRE(0 == print(writer, 'D')); + REQUIRE("ABCDEFGHIJKLMNOPQRST" == std::string(output, 20)); + } +} + +TEST_CASE("Writer") { + std::string output; + Writer writer(output); + common_tests(writer, output); +} + +TEST_CASE("Writer") { + ::String output; + Writer< ::String> writer(output); + + SECTION("write(char)") { + SECTION("writes to temporary buffer") { + // accumulate in buffer + writer.write('a'); + writer.write('b'); + writer.write('c'); + writer.write('d'); + REQUIRE(output == ""); + + // flush when full + writer.write('e'); + REQUIRE(output == "abcd"); + + // flush on destruction + writer.write('f'); + writer.~Writer(); + REQUIRE(output == "abcdef"); + } + + SECTION("returns 1 on success") { + for (int i = 0; i < ARDUINOJSON_STRING_BUFFER_SIZE; i++) { + REQUIRE(writer.write('x') == 1); + } + } + + SECTION("returns 0 on error") { + output.limitCapacityTo(1); + + REQUIRE(writer.write('a') == 1); + REQUIRE(writer.write('b') == 1); + REQUIRE(writer.write('c') == 1); + REQUIRE(writer.write('d') == 1); + REQUIRE(writer.write('e') == 0); + REQUIRE(writer.write('f') == 0); + } + } + + SECTION("write(char*, size_t)") { + SECTION("empty string") { + REQUIRE(0 == print(writer, "")); + writer.flush(); + REQUIRE(output == ""); + } + + SECTION("writes to temporary buffer") { + // accumulate in buffer + print(writer, "abc"); + REQUIRE(output == ""); + + // flush when full, and continue to accumulate + print(writer, "de"); + REQUIRE(output == "abcd"); + + // flush on destruction + writer.~Writer(); + REQUIRE(output == "abcde"); + } + } +} + +TEST_CASE("Writer") { + custom_string output; + Writer writer(output); + + REQUIRE(4 == print(writer, "ABCD")); + REQUIRE("ABCD" == output); +} + +TEST_CASE("serializeJson(doc, String)") { + StaticJsonDocument<1024> doc; + doc["hello"] = "world"; + ::String output; + + SECTION("sufficient capacity") { + serializeJson(doc, output); + REQUIRE(output == "{\"hello\":\"world\"}"); + } + + SECTION("unsufficient capacity") { // issue #1561 + output.limitCapacityTo(10); + serializeJson(doc, output); + REQUIRE(output == "{\"hello\""); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/TypeTraits.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/TypeTraits.cpp new file mode 100644 index 0000000..0b2b1ce --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/TypeTraits.cpp @@ -0,0 +1,212 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +class EmptyClass {}; +enum EmptyEnum {}; + +TEST_CASE("Polyfills/type_traits") { + SECTION("is_base_of") { + REQUIRE_FALSE( + static_cast(is_base_of::value)); + REQUIRE( + static_cast(is_base_of::value)); + } + + SECTION("is_array") { + REQUIRE_FALSE((is_array::value)); + REQUIRE((is_array::value)); + REQUIRE((is_array::value)); + } + + SECTION("is_const") { + CHECK(is_const::value == false); + CHECK(is_const::value == true); + } + + SECTION("is_integral") { + CHECK(is_integral::value == false); + CHECK(is_integral::value == false); + CHECK(is_integral::value == false); + CHECK(is_integral::value == false); + CHECK(is_integral::value == false); + CHECK(is_integral::value == false); + CHECK(is_integral::value == false); + CHECK(is_integral::value == false); + + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + CHECK(is_integral::value == true); + + CHECK(is_integral::value == true); + } + + SECTION("is_signed") { + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == false); + + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == false); + + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == false); + + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == true); + CHECK(is_signed::value == false); + } + + SECTION("is_unsigned") { + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == false); + CHECK(is_unsigned::value == false); + CHECK(is_unsigned::value == false); + + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == false); + CHECK(is_unsigned::value == false); + CHECK(is_unsigned::value == false); + + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == false); + CHECK(is_unsigned::value == false); + CHECK(is_unsigned::value == false); + + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == true); + CHECK(is_unsigned::value == false); + CHECK(is_unsigned::value == false); + CHECK(is_unsigned::value == false); + } + + SECTION("is_floating_point") { + CHECK(is_floating_point::value == false); + CHECK(is_floating_point::value == true); + CHECK(is_floating_point::value == true); + CHECK(is_floating_point::value == true); + CHECK(is_floating_point::value == true); + CHECK(is_floating_point::value == true); + CHECK(is_floating_point::value == true); + CHECK(is_floating_point::value == true); + CHECK(is_floating_point::value == true); + } + + SECTION("is_convertible") { + CHECK((is_convertible::value == true)); + CHECK((is_convertible::value == true)); + CHECK((is_convertible::value == true)); + CHECK((is_convertible::value == false)); + CHECK((is_convertible::value == false)); + } + + SECTION("is_class") { + CHECK((is_class::value == false)); + CHECK((is_class::value == false)); + CHECK((is_class::value == false)); + CHECK((is_class::value == true)); + } + + SECTION("is_enum") { + CHECK(is_enum::value == false); + CHECK(is_enum::value == true); + CHECK(is_enum::value == false); + CHECK(is_enum::value == false); + CHECK(is_enum::value == false); + CHECK(is_enum::value == false); + } + + SECTION("IsVisitable") { + CHECK(IsVisitable::value == false); + CHECK(IsVisitable::value == false); + CHECK(IsVisitable::value == true); + CHECK(IsVisitable::value == true); + CHECK(IsVisitable::value == true); + CHECK(IsVisitable >::value == true); + CHECK(IsVisitable::value == true); + CHECK(IsVisitable::value == true); + CHECK((IsVisitable >::value == true)); + CHECK(IsVisitable::value == true); + CHECK(IsVisitable::value == true); + CHECK(IsVisitable >::value == true); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Utf16.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Utf16.cpp new file mode 100644 index 0000000..31ab7ec --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Utf16.cpp @@ -0,0 +1,68 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +static void testUtf16Codepoint(uint16_t codeunit, uint32_t expectedCodepoint) { + Utf16::Codepoint cp; + REQUIRE(cp.append(codeunit) == true); + REQUIRE(cp.value() == expectedCodepoint); +} + +static void testUtf16Codepoint(uint16_t codeunit1, uint16_t codeunit2, + uint32_t expectedCodepoint) { + Utf16::Codepoint cp; + REQUIRE(cp.append(codeunit1) == false); + REQUIRE(cp.append(codeunit2) == true); + REQUIRE(cp.value() == expectedCodepoint); +} + +TEST_CASE("Utf16::Codepoint()") { + SECTION("U+0000") { + testUtf16Codepoint(0x0000, 0x000000); + } + + SECTION("U+0001") { + testUtf16Codepoint(0x0001, 0x000001); + } + + SECTION("U+D7FF") { + testUtf16Codepoint(0xD7FF, 0x00D7FF); + } + + SECTION("U+E000") { + testUtf16Codepoint(0xE000, 0x00E000); + } + + SECTION("U+FFFF") { + testUtf16Codepoint(0xFFFF, 0x00FFFF); + } + + SECTION("U+010000") { + testUtf16Codepoint(0xD800, 0xDC00, 0x010000); + } + + SECTION("U+010001") { + testUtf16Codepoint(0xD800, 0xDC01, 0x010001); + } + + SECTION("U+0103FF") { + testUtf16Codepoint(0xD800, 0xDFFF, 0x0103FF); + } + + SECTION("U+010400") { + testUtf16Codepoint(0xD801, 0xDC00, 0x010400); + } + + SECTION("U+010400") { + testUtf16Codepoint(0xDBFF, 0xDC00, 0x10FC00); + } + + SECTION("U+10FFFF") { + testUtf16Codepoint(0xDBFF, 0xDFFF, 0x10FFFF); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Utf8.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Utf8.cpp new file mode 100644 index 0000000..38e839a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/Utf8.cpp @@ -0,0 +1,60 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +#include + +using namespace ARDUINOJSON_NAMESPACE; + +static void testCodepoint(uint32_t codepoint, std::string expected) { + char buffer[4096]; + MemoryPool pool(buffer, 4096); + StringCopier str(pool); + str.startString(); + + CAPTURE(codepoint); + Utf8::encodeCodepoint(codepoint, str); + + REQUIRE(str.str().c_str() == expected); +} + +TEST_CASE("Utf8::encodeCodepoint()") { + SECTION("U+0000") { + testCodepoint(0x0000, ""); + } + + SECTION("U+0001") { + testCodepoint(0x0001, "\x01"); + } + + SECTION("U+007F") { + testCodepoint(0x007F, "\x7f"); + } + + SECTION("U+0080") { + testCodepoint(0x0080, "\xc2\x80"); + } + + SECTION("U+07FF") { + testCodepoint(0x07FF, "\xdf\xbf"); + } + + SECTION("U+0800") { + testCodepoint(0x0800, "\xe0\xa0\x80"); + } + + SECTION("U+FFFF") { + testCodepoint(0xFFFF, "\xef\xbf\xbf"); + } + + SECTION("U+10000") { + testCodepoint(0x10000, "\xf0\x90\x80\x80"); + } + + SECTION("U+10FFFF") { + testCodepoint(0x10FFFF, "\xf4\x8f\xbf\xbf"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/arithmeticCompare.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/arithmeticCompare.cpp new file mode 100644 index 0000000..a67943e --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/arithmeticCompare.cpp @@ -0,0 +1,103 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("arithmeticCompare()") { + SECTION("int vs uint8_t") { + CHECK((arithmeticCompare(256, 1) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(41, 42) == COMPARE_RESULT_LESS)); + CHECK((arithmeticCompare(42, 42) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(43, 42) == COMPARE_RESULT_GREATER)); + } + + SECTION("unsigned vs int") { + CHECK((arithmeticCompare(0, -1) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(42, 41) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(42, 42) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(42, 43) == COMPARE_RESULT_LESS)); + } + + SECTION("float vs int") { + CHECK((arithmeticCompare(42, 41) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(42, 42) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(42, 43) == COMPARE_RESULT_LESS)); + } + + SECTION("int vs unsigned") { + CHECK((arithmeticCompare(-1, 0) == COMPARE_RESULT_LESS)); + CHECK((arithmeticCompare(0, 0) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(1, 0) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(42, 41) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(42, 42) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(42, 43) == COMPARE_RESULT_LESS)); + } + + SECTION("unsigned vs unsigned") { + CHECK((arithmeticCompare(42, 41) == + COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(42, 42) == + COMPARE_RESULT_EQUAL)); + CHECK( + (arithmeticCompare(42, 43) == COMPARE_RESULT_LESS)); + } + + SECTION("bool vs bool") { + CHECK( + (arithmeticCompare(false, false) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(true, true) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(false, true) == COMPARE_RESULT_LESS)); + CHECK( + (arithmeticCompare(true, false) == COMPARE_RESULT_GREATER)); + } + + SECTION("bool vs int") { + CHECK((arithmeticCompare(false, -1) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(false, 0) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(false, 1) == COMPARE_RESULT_LESS)); + CHECK((arithmeticCompare(true, 0) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(true, 1) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(true, 2) == COMPARE_RESULT_LESS)); + } + + SECTION("bool vs int") { + CHECK((arithmeticCompare(0, false) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(1, true) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompare(1, false) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompare(0, true) == COMPARE_RESULT_LESS)); + } +} + +TEST_CASE("arithmeticCompareNegateLeft()") { + SECTION("unsigned vs int") { + CHECK((arithmeticCompareNegateLeft(0, 1) == COMPARE_RESULT_LESS)); + CHECK((arithmeticCompareNegateLeft(42, -41) == COMPARE_RESULT_LESS)); + CHECK((arithmeticCompareNegateLeft(42, -42) == COMPARE_RESULT_EQUAL)); + CHECK( + (arithmeticCompareNegateLeft(42, -43) == COMPARE_RESULT_GREATER)); + } + + SECTION("unsigned vs unsigned") { + CHECK( + (arithmeticCompareNegateLeft(42, 42) == COMPARE_RESULT_LESS)); + } +} + +TEST_CASE("arithmeticCompareNegateRight()") { + SECTION("int vs unsigned") { + CHECK((arithmeticCompareNegateRight(1, 0) == COMPARE_RESULT_GREATER)); + CHECK( + (arithmeticCompareNegateRight(-41, 42) == COMPARE_RESULT_GREATER)); + CHECK((arithmeticCompareNegateRight(-42, 42) == COMPARE_RESULT_EQUAL)); + CHECK((arithmeticCompareNegateRight(-43, 42) == COMPARE_RESULT_LESS)); + } + + SECTION("unsigned vs unsigned") { + CHECK((arithmeticCompareNegateRight(42, 42) == + COMPARE_RESULT_GREATER)); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/conflicts.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/conflicts.cpp new file mode 100644 index 0000000..991a94e --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/conflicts.cpp @@ -0,0 +1,56 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +// Include any header that might use the conflicting macros +#include +#include +#include + +// All cores +#define bit() +#define constrain() +#define DEFAULT +#define DISABLED +#define HIGH +#define INPUT +#define LOW +#define max() +#define min() +#define OUTPUT +#define round() +#define sq() +#define word() +#define bitRead() +#define bitSet() +#define bitClear() +#define bitWrite() +#define interrupts() +#define lowByte() +#define highByte() +#define DEC +#define HEX +#define OCT +#define BIN +#define cbi() +#define sbi() + +// ESP8266 +#define _max() +#define _min() + +// Realtek Ameba +#define isdigit(c) (((c) >= '0') && ((c) <= '9')) +#define isprint(c) +#define isxdigit(c) +#define isspace(c) +#define isupper(c) +#define islower(c) +#define isalpha(c) + +// issue #839 +#define BLOCKSIZE +#define CAPACITY + +// catch.hpp mutes several warnings, this file also allows to detect them +#include "ArduinoJson.h" diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/custom_string.hpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/custom_string.hpp new file mode 100644 index 0000000..3c5b7c1 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/custom_string.hpp @@ -0,0 +1,12 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +struct custom_char_traits : std::char_traits {}; +struct custom_allocator : std::allocator {}; +typedef std::basic_string + custom_string; diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/deprecated.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/deprecated.cpp new file mode 100644 index 0000000..646ca05 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/deprecated.cpp @@ -0,0 +1,115 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_DEPRECATED(msg) // nothing + +#include +#include + +TEST_CASE("Deprecated features") { + StaticJsonDocument<256> doc; + const char* s = "hello"; + doc["s"] = s; + doc["c"] = 42; + doc["a"].add(s); + doc["a"].add(42); + + SECTION("JsonVariant::add(char)") { + JsonVariant v = doc.to(); + v.add('*'); + REQUIRE(v[0] == 42); + } + + SECTION("JsonVariant::as()") { + JsonVariant v = doc["s"]; + REQUIRE(v.as() == s); + } + + SECTION("JsonVariant::as()") { + JsonVariant v = doc["c"]; + REQUIRE(v.as() == '*'); + } + + SECTION("JsonVariant::is()") { + JsonVariant v = doc["s"]; + REQUIRE(v.is() == true); + } + + SECTION("JsonVariant::is()") { + JsonVariant v = doc["c"]; + REQUIRE(v.is() == true); + } + + SECTION("JsonVariant::set(char)") { + JsonVariant v = doc.to(); + v.set('*'); + REQUIRE(v.as() == 42); + } + + SECTION("JsonVariantConst::as()") { + JsonVariantConst v = doc["s"]; + REQUIRE(v.as() == s); + } + + SECTION("JsonVariantConst::as()") { + JsonVariantConst v = doc["c"]; + REQUIRE(v.as() == '*'); + } + + SECTION("JsonVariantConst::is()") { + JsonVariantConst v = doc["s"]; + REQUIRE(v.is() == true); + } + + SECTION("JsonVariantConst::is()") { + JsonVariantConst v = doc["c"]; + REQUIRE(v.is() == true); + } + + SECTION("MemberProxy::as()") { + REQUIRE(doc["s"].as() == s); + } + + SECTION("MemberProxy::as()") { + REQUIRE(doc["c"].as() == '*'); + } + + SECTION("MemberProxy::as()") { + doc["x"].set('*'); + REQUIRE(doc["x"] == 42); + } + + SECTION("MemberProxy::is()") { + REQUIRE(doc["s"].is() == true); + REQUIRE(doc["c"].is() == false); + } + + SECTION("MemberProxy::is()") { + REQUIRE(doc["c"].is() == true); + REQUIRE(doc["s"].is() == false); + } + + SECTION("ElementProxy::as()") { + REQUIRE(doc["a"][0].as() == s); + } + + SECTION("ElementProxy::as()") { + REQUIRE(doc["a"][1].as() == '*'); + } + + SECTION("ElementProxy::as()") { + doc["a"][0].set('*'); + REQUIRE(doc["a"][0] == 42); + } + + SECTION("ElementProxy::is()") { + REQUIRE(doc["a"][0].is() == true); + REQUIRE(doc["a"][1].is() == false); + } + + SECTION("ElementProxy::is()") { + REQUIRE(doc["a"][1].is() == true); + REQUIRE(doc["a"][0].is() == false); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/printable.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/printable.cpp new file mode 100644 index 0000000..7cec8f9 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/printable.cpp @@ -0,0 +1,144 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1 +#include + +struct PrintOneCharacterAtATime { + static size_t printStringTo(const std::string& s, Print& p) { + size_t result = 0; + for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) { + size_t n = p.write(uint8_t(*it)); + if (n == 0) + break; + result += n; + } + return result; + } +}; + +struct PrintAllAtOnce { + static size_t printStringTo(const std::string& s, Print& p) { + return p.write(s.data(), s.size()); + } +}; + +template +struct PrintableString : public Printable { + PrintableString(const char* s) : _str(s), _total(0) {} + + virtual size_t printTo(Print& p) const { + size_t result = PrintPolicy::printStringTo(_str, p); + _total += result; + return result; + } + + size_t totalBytesWritten() const { + return _total; + } + + private: + std::string _str; + mutable size_t _total; +}; + +TEST_CASE("Printable") { + SECTION("Doesn't overflow") { + StaticJsonDocument<8> doc; + const char* value = "example"; // == 7 chars + + doc.set(666); // to make sure we override the value + + SECTION("Via Print::write(char)") { + PrintableString printable(value); + CHECK(doc.set(printable) == true); + CHECK(doc.as() == value); + CHECK(printable.totalBytesWritten() == 7); + CHECK(doc.overflowed() == false); + CHECK(doc.memoryUsage() == 8); + CHECK(doc.as().memoryUsage() == 8); + } + + SECTION("Via Print::write(const char* size_t)") { + PrintableString printable(value); + CHECK(doc.set(printable) == true); + CHECK(doc.as() == value); + CHECK(printable.totalBytesWritten() == 7); + CHECK(doc.overflowed() == false); + CHECK(doc.memoryUsage() == 8); + CHECK(doc.as().memoryUsage() == 8); + } + } + + SECTION("Overflows early") { + StaticJsonDocument<8> doc; + const char* value = "hello world"; // > 8 chars + + doc.set(666); // to make sure we override the value + + SECTION("Via Print::write(char)") { + PrintableString printable(value); + CHECK(doc.set(printable) == false); + CHECK(doc.isNull()); + CHECK(printable.totalBytesWritten() == 8); + CHECK(doc.overflowed() == true); + CHECK(doc.memoryUsage() == 0); + } + + SECTION("Via Print::write(const char*, size_t)") { + PrintableString printable(value); + CHECK(doc.set(printable) == false); + CHECK(doc.isNull()); + CHECK(printable.totalBytesWritten() == 0); + CHECK(doc.overflowed() == true); + CHECK(doc.memoryUsage() == 0); + } + } + + SECTION("Overflows adding terminator") { + StaticJsonDocument<8> doc; + const char* value = "overflow"; // == 8 chars + + doc.set(666); // to make sure we override the value + + SECTION("Via Print::write(char)") { + PrintableString printable(value); + CHECK(doc.set(printable) == false); + CHECK(doc.isNull()); + CHECK(printable.totalBytesWritten() == 8); + CHECK(doc.overflowed() == true); + CHECK(doc.memoryUsage() == 0); + } + + SECTION("Via Print::write(const char*, size_t)") { + PrintableString printable(value); + CHECK(doc.set(printable) == false); + CHECK(doc.isNull()); + CHECK(printable.totalBytesWritten() == 0); + CHECK(doc.overflowed() == true); + CHECK(doc.memoryUsage() == 0); + } + } + + SECTION("Null variant") { + JsonVariant var; + PrintableString printable = "Hello World!"; + CHECK(var.set(printable) == false); + CHECK(var.isNull()); + CHECK(printable.totalBytesWritten() == 0); + } + + SECTION("String deduplication") { + StaticJsonDocument<128> doc; + doc.add(PrintableString("Hello World!")); + doc.add(PrintableString("Hello World!")); + REQUIRE(doc.size() == 2); + CHECK(doc[0] == "Hello World!"); + CHECK(doc[1] == "Hello World!"); + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 13); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/unsigned_char.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/unsigned_char.cpp new file mode 100644 index 0000000..7a5e3ee --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/unsigned_char.cpp @@ -0,0 +1,271 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +#if defined(__clang__) +# define CONFLICTS_WITH_BUILTIN_OPERATOR +#endif + +TEST_CASE("unsigned char[]") { + SECTION("deserializeJson()") { + unsigned char input[] = "{\"a\":42}"; + + StaticJsonDocument doc; + DeserializationError err = deserializeJson(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("deserializeMsgPack()") { + unsigned char input[] = "\xDE\x00\x01\xA5Hello\xA5world"; + + StaticJsonDocument doc; + DeserializationError err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("serializeMsgPack(unsigned char[])") { + unsigned char buffer[32]; + StaticJsonDocument doc; + doc["hello"] = "world"; + + size_t n = serializeMsgPack(doc, buffer); + + REQUIRE(n == 13); + REQUIRE(memcmp(buffer, "\x81\xA5hello\xA5world", 13) == 0); + } + + SECTION("serializeMsgPack(unsigned char*)") { + unsigned char buffer[32]; + StaticJsonDocument doc; + doc["hello"] = "world"; + + size_t n = serializeMsgPack(doc, buffer, sizeof(buffer)); + + REQUIRE(n == 13); + REQUIRE(memcmp(buffer, "\x81\xA5hello\xA5world", 13) == 0); + } + + SECTION("serializeJson(unsigned char[])") { + unsigned char buffer[32]; + StaticJsonDocument doc; + doc["hello"] = "world"; + + size_t n = serializeJson(doc, buffer); + + REQUIRE(n == 17); + REQUIRE(memcmp(buffer, "{\"hello\":\"world\"}", n) == 0); + } + + SECTION("serializeJson(unsigned char*)") { + unsigned char buffer[32]; + StaticJsonDocument doc; + doc["hello"] = "world"; + + size_t n = serializeJson(doc, buffer, sizeof(buffer)); + + REQUIRE(n == 17); + REQUIRE(memcmp(buffer, "{\"hello\":\"world\"}", n) == 0); + } + + SECTION("serializeJsonPretty(unsigned char[])") { + unsigned char buffer[32]; + StaticJsonDocument doc; + doc["hello"] = "world"; + + size_t n = serializeJsonPretty(doc, buffer); + + REQUIRE(n == 24); + } + + SECTION("serializeJsonPretty(unsigned char*)") { + unsigned char buffer[32]; + StaticJsonDocument doc; + doc["hello"] = "world"; + + size_t n = serializeJsonPretty(doc, buffer, sizeof(buffer)); + + REQUIRE(n == 24); + } + + SECTION("JsonVariant") { + DynamicJsonDocument doc(4096); + + SECTION("set") { + unsigned char value[] = "42"; + + JsonVariant variant = doc.to(); + variant.set(value); + + REQUIRE(42 == variant.as()); + } + +#ifndef CONFLICTS_WITH_BUILTIN_OPERATOR + SECTION("operator[]") { + unsigned char key[] = "hello"; + + deserializeJson(doc, "{\"hello\":\"world\"}"); + JsonVariant variant = doc.as(); + + REQUIRE(std::string("world") == variant[key]); + } +#endif + +#ifndef CONFLICTS_WITH_BUILTIN_OPERATOR + SECTION("operator[] const") { + unsigned char key[] = "hello"; + + deserializeJson(doc, "{\"hello\":\"world\"}"); + const JsonVariant variant = doc.as(); + + REQUIRE(std::string("world") == variant[key]); + } +#endif + + SECTION("operator==") { + unsigned char comparand[] = "hello"; + + JsonVariant variant = doc.to(); + variant.set("hello"); + + REQUIRE(comparand == variant); + REQUIRE(variant == comparand); + REQUIRE_FALSE(comparand != variant); + REQUIRE_FALSE(variant != comparand); + } + + SECTION("operator!=") { + unsigned char comparand[] = "hello"; + + JsonVariant variant = doc.to(); + variant.set("world"); + + REQUIRE(comparand != variant); + REQUIRE(variant != comparand); + REQUIRE_FALSE(comparand == variant); + REQUIRE_FALSE(variant == comparand); + } + } + + SECTION("JsonObject") { +#ifndef CONFLICTS_WITH_BUILTIN_OPERATOR + SECTION("operator[]") { + unsigned char key[] = "hello"; + + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + obj[key] = "world"; + + REQUIRE(std::string("world") == obj["hello"]); + } + + SECTION("JsonObject::operator[] const") { + unsigned char key[] = "hello"; + + DynamicJsonDocument doc(4096); + deserializeJson(doc, "{\"hello\":\"world\"}"); + + JsonObject obj = doc.as(); + REQUIRE(std::string("world") == obj[key]); + } +#endif + + SECTION("containsKey()") { + unsigned char key[] = "hello"; + + DynamicJsonDocument doc(4096); + deserializeJson(doc, "{\"hello\":\"world\"}"); + JsonObject obj = doc.as(); + REQUIRE(true == obj.containsKey(key)); + } + + SECTION("remove()") { + unsigned char key[] = "hello"; + + DynamicJsonDocument doc(4096); + deserializeJson(doc, "{\"hello\":\"world\"}"); + JsonObject obj = doc.as(); + obj.remove(key); + + REQUIRE(0 == obj.size()); + } + + SECTION("createNestedArray()") { + unsigned char key[] = "hello"; + + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + obj.createNestedArray(key); + } + + SECTION("createNestedObject()") { + unsigned char key[] = "hello"; + + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + obj.createNestedObject(key); + } + } + + SECTION("MemberProxy") { + SECTION("operator=") { // issue #416 + unsigned char value[] = "world"; + + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + obj["hello"] = value; + + REQUIRE(std::string("world") == obj["hello"]); + } + + SECTION("set()") { + unsigned char value[] = "world"; + + DynamicJsonDocument doc(4096); + JsonObject obj = doc.to(); + obj["hello"].set(value); + + REQUIRE(std::string("world") == obj["hello"]); + } + } + + SECTION("JsonArray") { + SECTION("add()") { + unsigned char value[] = "world"; + + DynamicJsonDocument doc(4096); + JsonArray arr = doc.to(); + arr.add(value); + + REQUIRE(std::string("world") == arr[0]); + } + } + + SECTION("ElementProxy") { + SECTION("set()") { + unsigned char value[] = "world"; + + DynamicJsonDocument doc(4096); + JsonArray arr = doc.to(); + arr.add("hello"); + arr[0].set(value); + + REQUIRE(std::string("world") == arr[0]); + } + + SECTION("operator=") { + unsigned char value[] = "world"; + + DynamicJsonDocument doc(4096); + JsonArray arr = doc.to(); + arr.add("hello"); + arr[0] = value; + + REQUIRE(std::string("world") == arr[0]); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/version.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/version.cpp new file mode 100644 index 0000000..486a867 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/version.cpp @@ -0,0 +1,18 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +using Catch::Matchers::StartsWith; + +TEST_CASE("ARDUINOJSON_VERSION") { + std::stringstream version; + + version << ARDUINOJSON_VERSION_MAJOR << "." << ARDUINOJSON_VERSION_MINOR + << "." << ARDUINOJSON_VERSION_REVISION; + + REQUIRE_THAT(ARDUINOJSON_VERSION, StartsWith(version.str())); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/weird_strcmp.hpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/weird_strcmp.hpp new file mode 100644 index 0000000..ff755ef --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Misc/weird_strcmp.hpp @@ -0,0 +1,29 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include + +#include // strcmp, strncmp + +// Issue #1198: strcmp() implementation that returns a value larger than 8-bit + +namespace ARDUINOJSON_NAMESPACE { +int strcmp(const char* a, const char* b) { + int result = ::strcmp(a, b); + if (result > 0) + return 2147483647; + if (result < 0) + return -214748364; + return 0; +} + +int strncmp(const char* a, const char* b, size_t n) { + int result = ::strncmp(a, b, n); + if (result > 0) + return 2147483647; + if (result < 0) + return -214748364; + return 0; +} +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/CMakeLists.txt new file mode 100644 index 0000000..01d1216 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/CMakeLists.txt @@ -0,0 +1,31 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(MixedConfigurationTests + decode_unicode_0.cpp + decode_unicode_1.cpp + enable_alignment_0.cpp + enable_alignment_1.cpp + enable_comments_0.cpp + enable_comments_1.cpp + enable_infinity_0.cpp + enable_infinity_1.cpp + enable_nan_0.cpp + enable_nan_1.cpp + enable_progmem_1.cpp + enable_string_deduplication_0.cpp + enable_string_deduplication_1.cpp + issue1707.cpp + use_double_0.cpp + use_double_1.cpp +) + +set_target_properties(MixedConfigurationTests PROPERTIES UNITY_BUILD OFF) + +add_test(MixedConfiguration MixedConfigurationTests) + +set_tests_properties(MixedConfiguration + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_0.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_0.cpp new file mode 100644 index 0000000..b5dc1f1 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_0.cpp @@ -0,0 +1,12 @@ +#define ARDUINOJSON_DECODE_UNICODE 0 +#include + +#include + +TEST_CASE("ARDUINOJSON_DECODE_UNICODE == 0") { + DynamicJsonDocument doc(2048); + DeserializationError err = deserializeJson(doc, "\"\\uD834\\uDD1E\""); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.as() == "\\uD834\\uDD1E"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_1.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_1.cpp new file mode 100644 index 0000000..2b5b652 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/decode_unicode_1.cpp @@ -0,0 +1,11 @@ +#define ARDUINOJSON_DECODE_UNICODE 1 +#include + +#include + +TEST_CASE("ARDUINOJSON_DECODE_UNICODE == 1") { + DynamicJsonDocument doc(2048); + DeserializationError err = deserializeJson(doc, "\"\\uD834\\uDD1E\""); + + REQUIRE(err == DeserializationError::Ok); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_alignment_0.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_alignment_0.cpp new file mode 100644 index 0000000..426f516 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_alignment_0.cpp @@ -0,0 +1,41 @@ +#define ARDUINOJSON_NAMESPACE ArduinoJson_NoAlignment +#define ARDUINOJSON_ENABLE_ALIGNMENT 0 +#include + +#include + +TEST_CASE("ARDUINOJSON_ENABLE_ALIGNMENT == 0") { + using namespace ARDUINOJSON_NAMESPACE; + + const size_t N = sizeof(void*); + + SECTION("isAligned()") { + CHECK(isAligned(0) == true); + CHECK(isAligned(1) == true); + CHECK(isAligned(N) == true); + CHECK(isAligned(N + 1) == true); + CHECK(isAligned(2 * N) == true); + CHECK(isAligned(2 * N + 1) == true); + } + + SECTION("addPadding()") { + CHECK(addPadding(0) == 0); + CHECK(addPadding(1) == 1); + CHECK(addPadding(N) == N); + CHECK(addPadding(N + 1) == N + 1); + } + + SECTION("AddPadding<>") { + const size_t a = AddPadding<0>::value; + CHECK(a == 0); + + const size_t b = AddPadding<1>::value; + CHECK(b == 1); + + const size_t c = AddPadding::value; + CHECK(c == N); + + const size_t d = AddPadding::value; + CHECK(d == N + 1); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_alignment_1.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_alignment_1.cpp new file mode 100644 index 0000000..9abf50c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_alignment_1.cpp @@ -0,0 +1,40 @@ +#define ARDUINOJSON_ENABLE_ALIGNMENT 1 +#include + +#include + +TEST_CASE("ARDUINOJSON_ENABLE_ALIGNMENT == 1") { + using namespace ARDUINOJSON_NAMESPACE; + + const size_t N = sizeof(void*); + + SECTION("isAligned()") { + CHECK(isAligned(0) == true); + CHECK(isAligned(1) == false); + CHECK(isAligned(N) == true); + CHECK(isAligned(N + 1) == false); + CHECK(isAligned(2 * N) == true); + CHECK(isAligned(2 * N + 1) == false); + } + + SECTION("addPadding()") { + CHECK(addPadding(0) == 0); + CHECK(addPadding(1) == N); + CHECK(addPadding(N) == N); + CHECK(addPadding(N + 1) == 2 * N); + } + + SECTION("AddPadding<>") { + const size_t a = AddPadding<0>::value; + CHECK(a == 0); + + const size_t b = AddPadding<1>::value; + CHECK(b == N); + + const size_t c = AddPadding::value; + CHECK(c == N); + + const size_t d = AddPadding::value; + CHECK(d == 2 * N); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_0.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_0.cpp new file mode 100644 index 0000000..6329021 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_0.cpp @@ -0,0 +1,54 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_ENABLE_COMMENTS 0 +#include + +#include + +TEST_CASE("Comments should produce InvalidInput") { + DynamicJsonDocument doc(2048); + + const char* testCases[] = { + "/*COMMENT*/ [\"hello\"]", + "[/*COMMENT*/ \"hello\"]", + "[\"hello\"/*COMMENT*/]", + "[\"hello\"/*COMMENT*/,\"world\"]", + "[\"hello\",/*COMMENT*/ \"world\"]", + "[/*/\n]", + "[/*COMMENT]", + "[/*COMMENT*]", + "//COMMENT\n\t[\"hello\"]", + "[//COMMENT\n\"hello\"]", + "[\"hello\"//COMMENT\r\n]", + "[\"hello\"//COMMENT\n,\"world\"]", + "[\"hello\",//COMMENT\n\"world\"]", + "[/COMMENT\n]", + "[//COMMENT", + "/*COMMENT*/ {\"hello\":\"world\"}", + "{/*COMMENT*/\"hello\":\"world\"}", + "{\"hello\"/*COMMENT*/:\"world\"}", + "{\"hello\":/*COMMENT*/\"world\"}", + "{\"hello\":\"world\"/*COMMENT*/}", + "//COMMENT\n {\"hello\":\"world\"}", + "{//COMMENT\n\"hello\":\"world\"}", + "{\"hello\"//COMMENT\n:\"world\"}", + "{\"hello\"://COMMENT\n\"world\"}", + "{\"hello\":\"world\"//COMMENT\n}", + "/{\"hello\":\"world\"}", + "{/\"hello\":\"world\"}", + "{\"hello\"/:\"world\"}", + "{\"hello\":/\"world\"}", + "{\"hello\":\"world\"/}", + "{\"hello\":\"world\"/,\"answer\":42}", + "{\"hello\":\"world\",/\"answer\":42}", + }; + const size_t testCount = sizeof(testCases) / sizeof(testCases[0]); + + for (size_t i = 0; i < testCount; i++) { + const char* input = testCases[i]; + CAPTURE(input); + REQUIRE(deserializeJson(doc, input) == DeserializationError::InvalidInput); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_1.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_1.cpp new file mode 100644 index 0000000..1846292 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_comments_1.cpp @@ -0,0 +1,411 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_ENABLE_COMMENTS 1 +#include + +#include + +TEST_CASE("Comments in arrays") { + DynamicJsonDocument doc(2048); + + SECTION("Block comments") { + SECTION("Before opening bracket") { + DeserializationError err = + deserializeJson(doc, "/*COMMENT*/ [\"hello\"]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == "hello"); + } + + SECTION("After opening bracket") { + DeserializationError err = + deserializeJson(doc, "[/*COMMENT*/ \"hello\"]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == "hello"); + } + + SECTION("Before closing bracket") { + DeserializationError err = deserializeJson(doc, "[\"hello\"/*COMMENT*/]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == "hello"); + } + + SECTION("After closing bracket") { + DeserializationError err = deserializeJson(doc, "[\"hello\"]/*COMMENT*/"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == "hello"); + } + + SECTION("Before comma") { + DeserializationError err = + deserializeJson(doc, "[\"hello\"/*COMMENT*/,\"world\"]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == "hello"); + REQUIRE(arr[1] == "world"); + } + + SECTION("After comma") { + DeserializationError err = + deserializeJson(doc, "[\"hello\",/*COMMENT*/ \"world\"]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == "hello"); + REQUIRE(arr[1] == "world"); + } + + SECTION("/*/") { + DeserializationError err = deserializeJson(doc, "[/*/\n]"); + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("Unfinished comment") { + DeserializationError err = deserializeJson(doc, "[/*COMMENT]"); + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("Final slash missing") { + DeserializationError err = deserializeJson(doc, "[/*COMMENT*]"); + REQUIRE(err == DeserializationError::IncompleteInput); + } + } + + SECTION("Trailing comments") { + SECTION("Before opening bracket") { + DeserializationError err = + deserializeJson(doc, "//COMMENT\n\t[\"hello\"]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == "hello"); + } + + SECTION("After opening bracket") { + DeserializationError err = deserializeJson(doc, "[//COMMENT\n\"hello\"]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == "hello"); + } + + SECTION("Before closing bracket") { + DeserializationError err = + deserializeJson(doc, "[\"hello\"//COMMENT\r\n]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == "hello"); + } + + SECTION("After closing bracket") { + DeserializationError err = deserializeJson(doc, "[\"hello\"]//COMMENT\n"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(1 == arr.size()); + REQUIRE(arr[0] == "hello"); + } + + SECTION("Before comma") { + DeserializationError err = + deserializeJson(doc, "[\"hello\"//COMMENT\n,\"world\"]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == "hello"); + REQUIRE(arr[1] == "world"); + } + + SECTION("After comma") { + DeserializationError err = + deserializeJson(doc, "[\"hello\",//COMMENT\n\"world\"]"); + JsonArray arr = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(2 == arr.size()); + REQUIRE(arr[0] == "hello"); + REQUIRE(arr[1] == "world"); + } + + SECTION("Invalid comment") { + DeserializationError err = deserializeJson(doc, "[/COMMENT\n]"); + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("End document with comment") { + DeserializationError err = deserializeJson(doc, "[//COMMENT"); + REQUIRE(err == DeserializationError::IncompleteInput); + } + } +} + +TEST_CASE("Comments in objects") { + DynamicJsonDocument doc(2048); + + SECTION("Block comments") { + SECTION("Before opening brace") { + DeserializationError err = + deserializeJson(doc, "/*COMMENT*/ {\"hello\":\"world\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("After opening brace") { + DeserializationError err = + deserializeJson(doc, "{/*COMMENT*/\"hello\":\"world\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("Before colon") { + DeserializationError err = + deserializeJson(doc, "{\"hello\"/*COMMENT*/:\"world\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("After colon") { + DeserializationError err = + deserializeJson(doc, "{\"hello\":/*COMMENT*/\"world\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("Before closing brace") { + DeserializationError err = + deserializeJson(doc, "{\"hello\":\"world\"/*COMMENT*/}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("After closing brace") { + DeserializationError err = + deserializeJson(doc, "{\"hello\":\"world\"}/*COMMENT*/"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("Before comma") { + DeserializationError err = deserializeJson( + doc, "{\"hello\":\"world\"/*COMMENT*/,\"answer\":42}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + REQUIRE(obj["answer"] == 42); + } + + SECTION("After comma") { + DeserializationError err = deserializeJson( + doc, "{\"hello\":\"world\",/*COMMENT*/\"answer\":42}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + REQUIRE(obj["answer"] == 42); + } + } + + SECTION("Trailing comments") { + SECTION("Before opening brace") { + DeserializationError err = + deserializeJson(doc, "//COMMENT\n {\"hello\":\"world\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("After opening brace") { + DeserializationError err = + deserializeJson(doc, "{//COMMENT\n\"hello\":\"world\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("Before colon") { + DeserializationError err = + deserializeJson(doc, "{\"hello\"//COMMENT\n:\"world\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("After colon") { + DeserializationError err = + deserializeJson(doc, "{\"hello\"://COMMENT\n\"world\"}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("Before closing brace") { + DeserializationError err = + deserializeJson(doc, "{\"hello\":\"world\"//COMMENT\n}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("After closing brace") { + DeserializationError err = + deserializeJson(doc, "{\"hello\":\"world\"}//COMMENT\n"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("Before comma") { + DeserializationError err = deserializeJson( + doc, "{\"hello\":\"world\"//COMMENT\n,\"answer\":42}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + REQUIRE(obj["answer"] == 42); + } + + SECTION("After comma") { + DeserializationError err = deserializeJson( + doc, "{\"hello\":\"world\",//COMMENT\n\"answer\":42}"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + REQUIRE(obj["answer"] == 42); + } + } + + SECTION("Dangling slash") { + SECTION("Before opening brace") { + DeserializationError err = deserializeJson(doc, "/{\"hello\":\"world\"}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("After opening brace") { + DeserializationError err = deserializeJson(doc, "{/\"hello\":\"world\"}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("Before colon") { + DeserializationError err = deserializeJson(doc, "{\"hello\"/:\"world\"}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("After colon") { + DeserializationError err = deserializeJson(doc, "{\"hello\":/\"world\"}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("Before closing brace") { + DeserializationError err = deserializeJson(doc, "{\"hello\":\"world\"/}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("After closing brace") { + DeserializationError err = deserializeJson(doc, "{\"hello\":\"world\"}/"); + JsonObject obj = doc.as(); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(obj["hello"] == "world"); + } + + SECTION("Before comma") { + DeserializationError err = + deserializeJson(doc, "{\"hello\":\"world\"/,\"answer\":42}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("After comma") { + DeserializationError err = + deserializeJson(doc, "{\"hello\":\"world\",/\"answer\":42}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + } +} + +TEST_CASE("Comments alone") { + DynamicJsonDocument doc(2048); + + SECTION("Just a trailing comment with no line break") { + DeserializationError err = deserializeJson(doc, "// comment"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("Just a trailing comment with no a break") { + DeserializationError err = deserializeJson(doc, "// comment\n"); + + REQUIRE(err == DeserializationError::EmptyInput); + } + + SECTION("Just a block comment") { + DeserializationError err = deserializeJson(doc, "/*comment*/"); + + REQUIRE(err == DeserializationError::EmptyInput); + } + + SECTION("Just a slash") { + DeserializationError err = deserializeJson(doc, "/"); + + REQUIRE(err == DeserializationError::InvalidInput); + } + + SECTION("Premature terminator") { + DeserializationError err = deserializeJson(doc, "/* comment"); + + REQUIRE(err == DeserializationError::IncompleteInput); + } + + SECTION("Premature end on sized input") { + DeserializationError err = deserializeJson(doc, "/* comment */", 10); + + REQUIRE(err == DeserializationError::IncompleteInput); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_0.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_0.cpp new file mode 100644 index 0000000..521fb84 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_0.cpp @@ -0,0 +1,35 @@ +#define ARDUINOJSON_ENABLE_INFINITY 0 +#include + +#include +#include + +static void assertParseFails(const char* json) { + DynamicJsonDocument doc(4096); + DeserializationError err = deserializeJson(doc, json); + + REQUIRE(err == DeserializationError::InvalidInput); +} + +static void assertJsonEquals(const JsonDocument& doc, + std::string expectedJson) { + std::string actualJson; + serializeJson(doc, actualJson); + REQUIRE(actualJson == expectedJson); +} + +TEST_CASE("ARDUINOJSON_ENABLE_INFINITY == 0") { + SECTION("serializeJson()") { + DynamicJsonDocument doc(4096); + doc.add(std::numeric_limits::infinity()); + doc.add(-std::numeric_limits::infinity()); + + assertJsonEquals(doc, "[null,null]"); + } + + SECTION("deserializeJson()") { + assertParseFails("{\"X\":Infinity}"); + assertParseFails("{\"X\":-Infinity}"); + assertParseFails("{\"X\":+Infinity}"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_1.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_1.cpp new file mode 100644 index 0000000..19e0d5b --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_infinity_1.cpp @@ -0,0 +1,39 @@ +#define ARDUINOJSON_ENABLE_INFINITY 1 +#include + +#include +#include + +namespace my { +using ARDUINOJSON_NAMESPACE::isinf; +} // namespace my + +TEST_CASE("ARDUINOJSON_ENABLE_INFINITY == 1") { + DynamicJsonDocument doc(4096); + + SECTION("serializeJson()") { + doc.add(std::numeric_limits::infinity()); + doc.add(-std::numeric_limits::infinity()); + + std::string json; + serializeJson(doc, json); + + REQUIRE(json == "[Infinity,-Infinity]"); + } + + SECTION("deserializeJson()") { + DeserializationError err = + deserializeJson(doc, "[Infinity,-Infinity,+Infinity]"); + float a = doc[0]; + float b = doc[1]; + float c = doc[2]; + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(my::isinf(a)); + REQUIRE(a > 0); + REQUIRE(my::isinf(b)); + REQUIRE(b < 0); + REQUIRE(my::isinf(c)); + REQUIRE(c > 0); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_0.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_0.cpp new file mode 100644 index 0000000..e015a0d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_0.cpp @@ -0,0 +1,25 @@ +#define ARDUINOJSON_ENABLE_NAN 0 +#include + +#include +#include + +TEST_CASE("ARDUINOJSON_ENABLE_NAN == 0") { + DynamicJsonDocument doc(4096); + JsonObject root = doc.to(); + + SECTION("serializeJson()") { + root["X"] = std::numeric_limits::signaling_NaN(); + + std::string json; + serializeJson(doc, json); + + REQUIRE(json == "{\"X\":null}"); + } + + SECTION("deserializeJson()") { + DeserializationError err = deserializeJson(doc, "{\"X\":NaN}"); + + REQUIRE(err == DeserializationError::InvalidInput); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_1.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_1.cpp new file mode 100644 index 0000000..34d94fc --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_nan_1.cpp @@ -0,0 +1,31 @@ +#define ARDUINOJSON_ENABLE_NAN 1 +#include + +#include +#include + +namespace my { +using ARDUINOJSON_NAMESPACE::isnan; +} // namespace my + +TEST_CASE("ARDUINOJSON_ENABLE_NAN == 1") { + DynamicJsonDocument doc(4096); + JsonObject root = doc.to(); + + SECTION("serializeJson()") { + root["X"] = std::numeric_limits::signaling_NaN(); + + std::string json; + serializeJson(doc, json); + + REQUIRE(json == "{\"X\":NaN}"); + } + + SECTION("deserializeJson()") { + DeserializationError err = deserializeJson(doc, "{\"X\":NaN}"); + float x = doc["X"]; + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(my::isnan(x)); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_progmem_1.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_progmem_1.cpp new file mode 100644 index 0000000..9ddf7f2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_progmem_1.cpp @@ -0,0 +1,191 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_ENABLE_PROGMEM 1 +#include + +#include + +TEST_CASE("Flash strings") { + DynamicJsonDocument doc(2048); + + SECTION("deserializeJson()") { + DeserializationError err = deserializeJson(doc, F("{'hello':'world'}")); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc["hello"] == "world"); + } + + SECTION("JsonDocument::operator[]") { + doc[F("hello")] = F("world"); + + REQUIRE(doc["hello"] == "world"); + } + + SECTION("JsonDocument::add()") { + doc.add(F("world")); + + REQUIRE(doc[0] == "world"); + } + + SECTION("JsonVariant::set()") { + JsonVariant var = doc.to(); + + var.set(F("world")); + + REQUIRE(var == "world"); + } + + SECTION("MemberProxy::operator==") { + doc["hello"] = "world"; + + REQUIRE(doc["hello"] == F("world")); + } + + SECTION("ElementProxy::operator==") { + doc.add("world"); + + REQUIRE(doc[0] == F("world")); + } +} + +TEST_CASE("parseNumber()") { // tables are in Flash + using ARDUINOJSON_NAMESPACE::parseNumber; + + CHECK(parseNumber("1") == 1.f); + CHECK(parseNumber("1.23") == 1.23f); + CHECK(parseNumber("-1.23e34") == -1.23e34f); +} + +TEST_CASE("strlen_P") { + CHECK(strlen_P(PSTR("")) == 0); + CHECK(strlen_P(PSTR("a")) == 1); + CHECK(strlen_P(PSTR("ac")) == 2); +} + +TEST_CASE("strncmp_P") { + CHECK(strncmp_P("a", PSTR("b"), 0) == 0); + CHECK(strncmp_P("a", PSTR("b"), 1) == -1); + CHECK(strncmp_P("b", PSTR("a"), 1) == 1); + CHECK(strncmp_P("a", PSTR("a"), 0) == 0); + CHECK(strncmp_P("a", PSTR("b"), 2) == -1); + CHECK(strncmp_P("b", PSTR("a"), 2) == 1); + CHECK(strncmp_P("a", PSTR("a"), 2) == 0); +} + +TEST_CASE("strcmp_P") { + CHECK(strcmp_P("a", PSTR("b")) == -1); + CHECK(strcmp_P("b", PSTR("a")) == 1); + CHECK(strcmp_P("a", PSTR("a")) == 0); + CHECK(strcmp_P("aa", PSTR("ab")) == -1); + CHECK(strcmp_P("ab", PSTR("aa")) == 1); + CHECK(strcmp_P("aa", PSTR("aa")) == 0); +} + +TEST_CASE("memcpy_P") { + char dst[4]; + CHECK(memcpy_P(dst, PSTR("ABC"), 4) == dst); + CHECK(dst[0] == 'A'); + CHECK(dst[1] == 'B'); + CHECK(dst[2] == 'C'); + CHECK(dst[3] == 0); +} + +TEST_CASE("BoundedReader") { + using namespace ARDUINOJSON_NAMESPACE; + + SECTION("read") { + BoundedReader reader(F("\x01\xFF"), 2); + REQUIRE(reader.read() == 0x01); + REQUIRE(reader.read() == 0xFF); + REQUIRE(reader.read() == -1); + REQUIRE(reader.read() == -1); + } + + SECTION("readBytes() all at once") { + BoundedReader reader(F("ABCD"), 3); + + char buffer[8] = "abcd"; + REQUIRE(reader.readBytes(buffer, 4) == 3); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'd'); + } + + SECTION("readBytes() in two parts") { + BoundedReader reader(F("ABCDEF"), 6); + + char buffer[8] = "abcdefg"; + REQUIRE(reader.readBytes(buffer, 4) == 4); + REQUIRE(reader.readBytes(buffer + 4, 4) == 2); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'D'); + REQUIRE(buffer[4] == 'E'); + REQUIRE(buffer[5] == 'F'); + REQUIRE(buffer[6] == 'g'); + } +} + +TEST_CASE("Reader") { + using namespace ARDUINOJSON_NAMESPACE; + + SECTION("read()") { + Reader reader(F("\x01\xFF\x00\x12")); + REQUIRE(reader.read() == 0x01); + REQUIRE(reader.read() == 0xFF); + REQUIRE(reader.read() == 0); + REQUIRE(reader.read() == 0x12); + } + + SECTION("readBytes() all at once") { + Reader reader(F("ABCD")); + + char buffer[8] = "abcd"; + REQUIRE(reader.readBytes(buffer, 3) == 3); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'd'); + } + + SECTION("readBytes() in two parts") { + Reader reader(F("ABCDEF")); + + char buffer[8] = "abcdefg"; + REQUIRE(reader.readBytes(buffer, 4) == 4); + REQUIRE(reader.readBytes(buffer + 4, 2) == 2); + + REQUIRE(buffer[0] == 'A'); + REQUIRE(buffer[1] == 'B'); + REQUIRE(buffer[2] == 'C'); + REQUIRE(buffer[3] == 'D'); + REQUIRE(buffer[4] == 'E'); + REQUIRE(buffer[5] == 'F'); + REQUIRE(buffer[6] == 'g'); + } +} + +static void testStringification(DeserializationError error, + std::string expected) { + const __FlashStringHelper* s = error.f_str(); + CHECK(reinterpret_cast(convertFlashToPtr(s)) == expected); +} + +#define TEST_STRINGIFICATION(symbol) \ + testStringification(DeserializationError::symbol, #symbol) + +TEST_CASE("DeserializationError::f_str()") { + TEST_STRINGIFICATION(Ok); + TEST_STRINGIFICATION(EmptyInput); + TEST_STRINGIFICATION(IncompleteInput); + TEST_STRINGIFICATION(InvalidInput); + TEST_STRINGIFICATION(NoMemory); + TEST_STRINGIFICATION(TooDeep); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_0.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_0.cpp new file mode 100644 index 0000000..d9ca166 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_0.cpp @@ -0,0 +1,123 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1 +#define ARDUINOJSON_ENABLE_PROGMEM 1 +#define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 0 +#include + +#include + +TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 0") { + StaticJsonDocument<1024> doc; + + SECTION("deserializeJson()") { + SECTION("Deduplicate values") { + deserializeJson(doc, "[\"example\",\"example\"]"); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16); + CHECK(doc[0].as() != doc[1].as()); + } + + SECTION("Deduplicate keys") { + deserializeJson(doc, "[{\"example\":1},{\"example\":2}]"); + + CHECK(doc.memoryUsage() == + 2 * JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(2) + 16); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + + CHECK(key1 != key2); + } + } + + SECTION("JsonDocument") { + SECTION("values") { + SECTION("std::string") { + doc.add(std::string("example")); + doc.add(std::string("example")); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16); + CHECK(doc[0].as() != doc[1].as()); + } + + SECTION("char*") { + char value[] = "example"; + doc.add(value); + doc.add(value); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16); + CHECK(doc[0].as() != doc[1].as()); + } + + SECTION("Arduino String") { + doc.add(String("example")); + doc.add(String("example")); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16); + CHECK(doc[0].as() != doc[1].as()); + } + + SECTION("Flash string") { + doc.add(F("example")); + doc.add(F("example")); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16); + CHECK(doc[0].as() != doc[1].as()); + } + } + + SECTION("keys") { + SECTION("std::string") { + doc[0][std::string("example")] = 1; + doc[1][std::string("example")] = 2; + + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + CHECK(key1 != key2); + } + + SECTION("char*") { + char key[] = "example"; + doc[0][key] = 1; + doc[1][key] = 2; + + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + CHECK(key1 != key2); + } + + SECTION("Arduino String") { + doc[0][String("example")] = 1; + doc[1][String("example")] = 2; + + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + CHECK(key1 != key2); + } + + SECTION("Flash string") { + doc[0][F("example")] = 1; + doc[1][F("example")] = 2; + + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + CHECK(key1 != key2); + } + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_1.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_1.cpp new file mode 100644 index 0000000..27fc18d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/enable_string_deduplication_1.cpp @@ -0,0 +1,122 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1 +#define ARDUINOJSON_ENABLE_PROGMEM 1 +#define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 1 +#include + +#include + +TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 1") { + StaticJsonDocument<1024> doc; + + SECTION("deserializeJson()") { + SECTION("Deduplicate values") { + deserializeJson(doc, "[\"example\",\"example\"]"); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8); + CHECK(doc[0].as() == doc[1].as()); + } + + SECTION("Deduplicate keys") { + deserializeJson(doc, "[{\"example\":1},{\"example\":2}]"); + + CHECK(doc.memoryUsage() == + 2 * JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(2) + 8); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + CHECK(key1 == key2); + } + } + + SECTION("JsonDocument") { + SECTION("values") { + SECTION("std::string") { + doc.add(std::string("example")); + doc.add(std::string("example")); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8); + CHECK(doc[0].as() == doc[1].as()); + } + + SECTION("char*") { + char value[] = "example"; + doc.add(value); + doc.add(value); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8); + CHECK(doc[0].as() == doc[1].as()); + } + + SECTION("Arduino String") { + doc.add(String("example")); + doc.add(String("example")); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8); + CHECK(doc[0].as() == doc[1].as()); + } + + SECTION("Flash string") { + doc.add(F("example")); + doc.add(F("example")); + + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8); + CHECK(doc[0].as() == doc[1].as()); + } + } + + SECTION("keys") { + SECTION("std::string") { + doc[0][std::string("example")] = 1; + doc[1][std::string("example")] = 2; + + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + CHECK(key1 == key2); + } + + SECTION("char*") { + char key[] = "example"; + doc[0][key] = 1; + doc[1][key] = 2; + + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + CHECK(key1 == key2); + } + + SECTION("Arduino String") { + doc[0][String("example")] = 1; + doc[1][String("example")] = 2; + + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + CHECK(key1 == key2); + } + + SECTION("Flash string") { + doc[0][F("example")] = 1; + doc[1][F("example")] = 2; + + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8); + + const char* key1 = doc[0].as().begin()->key().c_str(); + const char* key2 = doc[1].as().begin()->key().c_str(); + CHECK(key1 == key2); + } + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/issue1707.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/issue1707.cpp new file mode 100644 index 0000000..befdee5 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/issue1707.cpp @@ -0,0 +1,17 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINO +#define memcpy_P(dest, src, n) memcpy((dest), (src), (n)) + +#include + +#include + +TEST_CASE("Issue1707") { + StaticJsonDocument<128> doc; + + DeserializationError err = deserializeJson(doc, F("{\"hello\":12}")); + REQUIRE(err == DeserializationError::Ok); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/use_double_0.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/use_double_0.cpp new file mode 100644 index 0000000..f38ee1f --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/use_double_0.cpp @@ -0,0 +1,17 @@ +#define ARDUINOJSON_USE_DOUBLE 0 +#include + +#include + +TEST_CASE("ARDUINOJSON_USE_DOUBLE == 0") { + DynamicJsonDocument doc(4096); + JsonObject root = doc.to(); + + root["pi"] = 3.14; + root["e"] = 2.72; + + std::string json; + serializeJson(doc, json); + + REQUIRE(json == "{\"pi\":3.14,\"e\":2.72}"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/use_double_1.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/use_double_1.cpp new file mode 100644 index 0000000..ff4a0e3 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MixedConfiguration/use_double_1.cpp @@ -0,0 +1,17 @@ +#define ARDUINOJSON_USE_DOUBLE 1 +#include + +#include + +TEST_CASE("ARDUINOJSON_USE_DOUBLE == 1") { + DynamicJsonDocument doc(4096); + JsonObject root = doc.to(); + + root["pi"] = 3.14; + root["e"] = 2.72; + + std::string json; + serializeJson(doc, json); + + REQUIRE(json == "{\"pi\":3.14,\"e\":2.72}"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/CMakeLists.txt new file mode 100644 index 0000000..f243527 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/CMakeLists.txt @@ -0,0 +1,24 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(MsgPackDeserializerTests + deserializeArray.cpp + deserializeObject.cpp + deserializeStaticVariant.cpp + deserializeVariant.cpp + doubleToFloat.cpp + filter.cpp + incompleteInput.cpp + input_types.cpp + misc.cpp + nestingLimit.cpp + notSupported.cpp +) + +add_test(MsgPackDeserializer MsgPackDeserializerTests) + +set_tests_properties(MsgPackDeserializer + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeArray.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeArray.cpp new file mode 100644 index 0000000..4540bd0 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeArray.cpp @@ -0,0 +1,83 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("deserialize MsgPack array") { + DynamicJsonDocument doc(4096); + + SECTION("fixarray") { + SECTION("empty") { + const char* input = "\x90"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonArray array = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(array.size() == 0); + } + + SECTION("two integers") { + const char* input = "\x92\x01\x02"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonArray array = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(array.size() == 2); + REQUIRE(array[0] == 1); + REQUIRE(array[1] == 2); + } + } + + SECTION("array 16") { + SECTION("empty") { + const char* input = "\xDC\x00\x00"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonArray array = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(array.size() == 0); + } + + SECTION("two strings") { + const char* input = "\xDC\x00\x02\xA5hello\xA5world"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonArray array = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(array.size() == 2); + REQUIRE(array[0] == "hello"); + REQUIRE(array[1] == "world"); + } + } + + SECTION("array 32") { + SECTION("empty") { + const char* input = "\xDD\x00\x00\x00\x00"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonArray array = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(array.size() == 0); + } + + SECTION("two floats") { + const char* input = + "\xDD\x00\x00\x00\x02\xCA\x00\x00\x00\x00\xCA\x40\x48\xF5\xC3"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonArray array = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(array.size() == 2); + REQUIRE(array[0] == 0.0f); + REQUIRE(array[1] == 3.14f); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeObject.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeObject.cpp new file mode 100644 index 0000000..7a0e7b8 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeObject.cpp @@ -0,0 +1,130 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("deserialize MsgPack object") { + DynamicJsonDocument doc(4096); + + SECTION("fixmap") { + SECTION("empty") { + const char* input = "\x80"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonObject obj = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 0); + } + + SECTION("two integers") { + const char* input = "\x82\xA3one\x01\xA3two\x02"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonObject obj = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["one"] == 1); + REQUIRE(obj["two"] == 2); + } + + SECTION("key is str 8") { + const char* input = "\x82\xd9\x03one\x01\xd9\x03two\x02"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonObject obj = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["one"] == 1); + REQUIRE(obj["two"] == 2); + } + + SECTION("key is str 16") { + const char* input = "\x82\xda\x00\x03one\x01\xda\x00\x03two\x02"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonObject obj = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["one"] == 1); + REQUIRE(obj["two"] == 2); + } + + SECTION("key is str 32") { + const char* input = + "\x82\xdb\x00\x00\x00\x03one\x01\xdb\x00\x00\x00\x03two\x02"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonObject obj = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["one"] == 1); + REQUIRE(obj["two"] == 2); + } + } + + SECTION("map 16") { + SECTION("empty") { + const char* input = "\xDE\x00\x00"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonObject obj = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 0); + } + + SECTION("two strings") { + const char* input = "\xDE\x00\x02\xA1H\xA5hello\xA1W\xA5world"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonObject obj = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["H"] == "hello"); + REQUIRE(obj["W"] == "world"); + } + } + + SECTION("map 32") { + SECTION("empty") { + const char* input = "\xDF\x00\x00\x00\x00"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonObject obj = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 0); + } + + SECTION("two floats") { + const char* input = + "\xDF\x00\x00\x00\x02\xA4zero\xCA\x00\x00\x00\x00\xA2pi\xCA\x40\x48" + "\xF5\xC3"; + + DeserializationError error = deserializeMsgPack(doc, input); + JsonObject obj = doc.as(); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["zero"] == 0.0f); + REQUIRE(obj["pi"] == 3.14f); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeStaticVariant.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeStaticVariant.cpp new file mode 100644 index 0000000..2d4093c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeStaticVariant.cpp @@ -0,0 +1,152 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +template +static void check(const char* input, DeserializationError expected) { + StaticJsonDocument doc; + + DeserializationError error = deserializeMsgPack(doc, input); + + CAPTURE(input); + REQUIRE(error == expected); +} + +template +static void checkString(const char* input, DeserializationError expected) { + check(input, expected); +} + +TEST_CASE("deserializeMsgPack(StaticJsonDocument&)") { + SECTION("single values always fit") { + check<0>("\xc0", DeserializationError::Ok); // nil + check<0>("\xc2", DeserializationError::Ok); // false + check<0>("\xc3", DeserializationError::Ok); // true + check<0>("\xcc\x00", DeserializationError::Ok); // uint 8 + check<0>("\xcd\x30\x39", DeserializationError::Ok); // uint 16 + check<0>("\xCE\x12\x34\x56\x78", DeserializationError::Ok); // uint 32 + } + + SECTION("fixstr") { + checkString<8>("\xA0", DeserializationError::Ok); + checkString<8>("\xA7ZZZZZZZ", DeserializationError::Ok); + checkString<8>("\xA8ZZZZZZZZ", DeserializationError::NoMemory); + checkString<16>("\xAFZZZZZZZZZZZZZZZ", DeserializationError::Ok); + checkString<16>("\xB0ZZZZZZZZZZZZZZZZ", DeserializationError::NoMemory); + } + + SECTION("str 8") { + checkString<8>("\xD9\x00", DeserializationError::Ok); + checkString<8>("\xD9\x07ZZZZZZZ", DeserializationError::Ok); + checkString<8>("\xD9\x08ZZZZZZZZ", DeserializationError::NoMemory); + checkString<16>("\xD9\x0FZZZZZZZZZZZZZZZ", DeserializationError::Ok); + checkString<16>("\xD9\x10ZZZZZZZZZZZZZZZZ", DeserializationError::NoMemory); + } + + SECTION("str 16") { + checkString<8>("\xDA\x00\x00", DeserializationError::Ok); + checkString<8>("\xDA\x00\x07ZZZZZZZ", DeserializationError::Ok); + checkString<8>("\xDA\x00\x08ZZZZZZZZ", DeserializationError::NoMemory); + checkString<16>("\xDA\x00\x0FZZZZZZZZZZZZZZZ", DeserializationError::Ok); + checkString<16>("\xDA\x00\x10ZZZZZZZZZZZZZZZZ", + DeserializationError::NoMemory); + } + + SECTION("str 32") { + checkString<8>("\xDB\x00\x00\x00\x00", DeserializationError::Ok); + checkString<8>("\xDB\x00\x00\x00\x07ZZZZZZZ", DeserializationError::Ok); + checkString<8>("\xDB\x00\x00\x00\x08ZZZZZZZZ", + DeserializationError::NoMemory); + checkString<16>("\xDB\x00\x00\x00\x0FZZZZZZZZZZZZZZZ", + DeserializationError::Ok); + checkString<16>("\xDB\x00\x00\x00\x10ZZZZZZZZZZZZZZZZ", + DeserializationError::NoMemory); + } + + SECTION("fixarray") { + check("\x90", DeserializationError::Ok); // [] + check("\x91\x01", + DeserializationError::NoMemory); // [1] + check("\x91\x01", DeserializationError::Ok); // [1] + check("\x92\x01\x02", + DeserializationError::NoMemory); // [1,2] + } + + SECTION("array 16") { + check("\xDC\x00\x00", DeserializationError::Ok); + check("\xDC\x00\x01\x01", + DeserializationError::NoMemory); + check("\xDC\x00\x01\x01", DeserializationError::Ok); + check("\xDC\x00\x02\x01\x02", + DeserializationError::NoMemory); + } + + SECTION("array 32") { + check("\xDD\x00\x00\x00\x00", DeserializationError::Ok); + check("\xDD\x00\x00\x00\x01\x01", + DeserializationError::NoMemory); + check("\xDD\x00\x00\x00\x01\x01", + DeserializationError::Ok); + check("\xDD\x00\x00\x00\x02\x01\x02", + DeserializationError::NoMemory); + } + + SECTION("fixmap") { + SECTION("{}") { + check("\x80", DeserializationError::Ok); + } + SECTION("{H:1}") { + check("\x81\xA1H\x01", + DeserializationError::NoMemory); + check( + "\x81\xA1H\x01", DeserializationError::Ok); + } + SECTION("{H:1,W:2}") { + check( + "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory); + check( + "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok); + } + } + + SECTION("map 16") { + SECTION("{}") { + check("\xDE\x00\x00", DeserializationError::Ok); + } + SECTION("{H:1}") { + check("\xDE\x00\x01\xA1H\x01", + DeserializationError::NoMemory); + check( + "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok); + } + SECTION("{H:1,W:2}") { + check( + "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::NoMemory); + check( + "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok); + } + } + + SECTION("map 32") { + SECTION("{}") { + check("\xDF\x00\x00\x00\x00", + DeserializationError::Ok); + } + SECTION("{H:1}") { + check("\xDF\x00\x00\x00\x01\xA1H\x01", + DeserializationError::NoMemory); + check( + "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok); + } + SECTION("{H:1,W:2}") { + check( + "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", + DeserializationError::NoMemory); + check( + "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeVariant.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeVariant.cpp new file mode 100644 index 0000000..be20b9c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/deserializeVariant.cpp @@ -0,0 +1,147 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +template +static void check(const char* input, U expected) { + DynamicJsonDocument doc(4096); + + DeserializationError error = deserializeMsgPack(doc, input); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + REQUIRE(doc.as() == expected); +} + +#if ARDUINOJSON_USE_LONG_LONG == 0 +static void checkNotSupported(const char* input) { + DynamicJsonDocument doc(4096); + DeserializationError error = deserializeMsgPack(doc, input); + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.isNull()); +} +#endif + +static void checkIsNull(const char* input) { + DynamicJsonDocument doc(4096); + + DeserializationError error = deserializeMsgPack(doc, input); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.as().isNull()); +} + +TEST_CASE("deserialize MsgPack value") { + SECTION("nil") { + checkIsNull("\xc0"); + } + + SECTION("bool") { + check("\xc2", false); + check("\xc3", true); + } + + SECTION("positive fixint") { + check("\x00", 0); + check("\x7F", 127); + } + + SECTION("negative fixint") { + check("\xe0", -32); + check("\xff", -1); + } + + SECTION("uint 8") { + check("\xcc\x00", 0); + check("\xcc\xff", 255); + } + + SECTION("uint 16") { + check("\xcd\x00\x00", 0); + check("\xcd\xFF\xFF", 65535); + check("\xcd\x30\x39", 12345); + } + + SECTION("uint 32") { + check("\xCE\x00\x00\x00\x00", 0x00000000U); + check("\xCE\xFF\xFF\xFF\xFF", 0xFFFFFFFFU); + check("\xCE\x12\x34\x56\x78", 0x12345678U); + } + + SECTION("uint 64") { +#if ARDUINOJSON_USE_LONG_LONG + check("\xCF\x00\x00\x00\x00\x00\x00\x00\x00", 0U); + check("\xCF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + 0xFFFFFFFFFFFFFFFFU); + check("\xCF\x12\x34\x56\x78\x9A\xBC\xDE\xF0", + 0x123456789ABCDEF0U); +#else + checkNotSupported("\xCF\x00\x00\x00\x00\x00\x00\x00\x00"); + checkNotSupported("\xCF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"); + checkNotSupported("\xCF\x12\x34\x56\x78\x9A\xBC\xDE\xF0"); +#endif + } + + SECTION("int 8") { + check("\xd0\x00", 0); + check("\xd0\xff", -1); + } + + SECTION("int 16") { + check("\xD1\x00\x00", 0); + check("\xD1\xFF\xFF", -1); + check("\xD1\xCF\xC7", -12345); + } + + SECTION("int 32") { + check("\xD2\x00\x00\x00\x00", 0); + check("\xD2\xFF\xFF\xFF\xFF", -1); + check("\xD2\xB6\x69\xFD\x2E", -1234567890); + } + + SECTION("int 64") { +#if ARDUINOJSON_USE_LONG_LONG + check("\xD3\x00\x00\x00\x00\x00\x00\x00\x00", int64_t(0U)); + check("\xD3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + int64_t(0xFFFFFFFFFFFFFFFFU)); + check("\xD3\x12\x34\x56\x78\x9A\xBC\xDE\xF0", + int64_t(0x123456789ABCDEF0)); +#else + checkNotSupported("\xD3\x00\x00\x00\x00\x00\x00\x00\x00"); + checkNotSupported("\xD3\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"); + checkNotSupported("\xD3\x12\x34\x56\x78\x9A\xBC\xDE\xF0"); +#endif + } + + SECTION("float 32") { + check("\xCA\x00\x00\x00\x00", 0.0f); + check("\xCA\x40\x48\xF5\xC3", 3.14f); + } + + SECTION("float 64") { + check("\xCB\x00\x00\x00\x00\x00\x00\x00\x00", 0.0); + check("\xCB\x40\x09\x21\xCA\xC0\x83\x12\x6F", 3.1415); + } + + SECTION("fixstr") { + check("\xA0", std::string("")); + check("\xABhello world", std::string("hello world")); + check("\xBFhello world hello world hello !", + std::string("hello world hello world hello !")); + } + + SECTION("str 8") { + check("\xd9\x05hello", std::string("hello")); + } + + SECTION("str 16") { + check("\xda\x00\x05hello", std::string("hello")); + } + + SECTION("str 32") { + check("\xdb\x00\x00\x00\x05hello", std::string("hello")); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/doubleToFloat.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/doubleToFloat.cpp new file mode 100644 index 0000000..b0470de --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/doubleToFloat.cpp @@ -0,0 +1,25 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +template +static void check(const char* input, T expected) { + T actual; + uint8_t* f = reinterpret_cast(&actual); + const uint8_t* d = reinterpret_cast(input); + doubleToFloat(d, f); + fixEndianess(actual); + CHECK(actual == expected); +} + +TEST_CASE("doubleToFloat()") { + check("\x40\x09\x21\xCA\xC0\x83\x12\x6F", 3.1415f); + check("\x00\x00\x00\x00\x00\x00\x00\x00", 0.0f); + check("\x80\x00\x00\x00\x00\x00\x00\x00", -0.0f); + check("\xC0\x5E\xDC\xCC\xCC\xCC\xCC\xCD", -123.45f); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/filter.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/filter.cpp new file mode 100644 index 0000000..57dd7be --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/filter.cpp @@ -0,0 +1,1133 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("deserializeMsgPack() filter") { + StaticJsonDocument<4096> doc; + DeserializationError error; + + StaticJsonDocument<200> filter; + DeserializationOption::Filter filterOpt(filter); + + SECTION("root is fixmap") { + SECTION("filter = {include:true,ignore:false)") { + filter["include"] = true; + filter["ignore"] = false; + + SECTION("input truncated after ignored key") { + error = deserializeMsgPack(doc, "\x82\xA6ignore", 8, filterOpt); + + CHECK(error == DeserializationError::IncompleteInput); + CHECK(doc.as() == "{}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(0)); + } + + SECTION("input truncated after inside skipped uint 8") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xCC\x2A\xA7include\x2A", + 9, filterOpt); + + CHECK(error == DeserializationError::IncompleteInput); + CHECK(doc.as() == "{}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(0)); + } + + SECTION("input truncated after before skipped string size") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xd9", 9, filterOpt); + + CHECK(error == DeserializationError::IncompleteInput); + CHECK(doc.as() == "{}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(0)); + } + + SECTION("input truncated after before skipped ext size") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xC7", 9, filterOpt); + + CHECK(error == DeserializationError::IncompleteInput); + CHECK(doc.as() == "{}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(0)); + } + + SECTION("skip nil") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xC0\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("reject 0xc1") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xC1\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::InvalidInput); + } + + SECTION("skip false") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xC2\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip true") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xC3\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip positive fixint") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\x2A\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip negative fixint") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xFF\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip uint 8") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xCC\x2A\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip int 8") { + error = deserializeMsgPack(doc, "\x82\xA6ignore\xD0\x2A\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip uint 16") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xcd\x30\x39\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip int 16") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xD1\xCF\xC7\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip uint 32") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xCE\x12\x34\x56\x78\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip int 32") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xD2\xB6\x69\xFD\x2E\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip uint 64") { + error = deserializeMsgPack( + doc, + "\x82\xA6ignore\xCF\x12\x34\x56\x78\x9A\xBC\xDE\xF0\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip int 64") { + error = deserializeMsgPack( + doc, + "\x82\xA6ignore\xD3\x12\x34\x56\x78\x9A\xBC\xDE\xF0\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip float 32") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xCA\x40\x48\xF5\xC3\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip float 64") { + error = deserializeMsgPack( + doc, + "\x82\xA6ignore\xCB\x40\x09\x21\xCA\xC0\x83\x12\x6F\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip fixstr") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xABhello world\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip str 8") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xd9\x05hello\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip str 16") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xda\x00\x05hello\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip str 32") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xdb\x00\x00\x00\x05hello\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip bin 8") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xC4\x05hello\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip bin 16") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xC5\x00\x05hello\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip bin 32") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xC6\x00\x00\x00\x05hello\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip fixarray") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\x92\x01\x02\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip array 16") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\xDC\x00\x02\xA5hello\xA5world\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip array 32") { + error = deserializeMsgPack( + doc, + "\x82\xA6ignore" + "\xDD\x00\x00\x00\x02\xCA\x00\x00\x00\x00\xCA\x40\x48\xF5\xC3" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip fixmap") { + error = deserializeMsgPack( + doc, "\x82\xA6ignore\x82\xA3one\x01\xA3two\x02\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip map 16") { + error = deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xDE\x00\x02\xA1H\xA5hello\xA1W\xA5world" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip map 32") { + error = deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xDF\x00\x00\x00\x02" + "\xA4zero\xCA\x00\x00\x00\x00" + "\xA2pi\xCA\x40\x48\xF5\xC3" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip fixext 1") { + error = deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xd4\x01\x02" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip fixext 2") { + error = deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xd5\x01\x02\x03" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip fixext 4") { + error = deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xd6\x01\x02\x03\x04\x05" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip fixext 8") { + error = deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xd7\x01\x02\x03\x04\x05\x06\x07\x08\x09" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip fixext 16") { + error = + deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xd8\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A" + "\x0B\x0C\x0D\x0E\x0F\x10\x11" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip ext 8") { + error = deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xc7\x02\x00\x01\x02" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip ext 16") { + error = deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xc8\x00\x02\x00\x01\x02" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + + SECTION("skip ext 32") { + error = deserializeMsgPack(doc, + "\x82\xA6ignore" + "\xc9\x00\x00\x00\x02\x00\x01\x02" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + } + } + + SECTION("Filter = {arronly:[{measure:true}],include:true}") { + filter["onlyarr"][0]["measure"] = true; + filter["include"] = true; + + CAPTURE(filter.as()); + + SECTION("include fixarray") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyarr\x92" + "\x82\xA8location\x01\xA7measure\x02" + "\x82\xA8location\x02\xA7measure\x04" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == + "{\"onlyarr\":[{\"measure\":2},{\"measure\":4}],\"include\":42}"); + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(2) + 24); + } + + SECTION("include array 16") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyarr" + "\xDC\x00\x02" + "\x82\xA8location\x01\xA7measure\x02" + "\x82\xA8location\x02\xA7measure\x04" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == + "{\"onlyarr\":[{\"measure\":2},{\"measure\":4}],\"include\":42}"); + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(2) + 24); + } + + SECTION("include array 32") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyarr" + "\xDD\x00\x00\x00\x02" + "\x82\xA8location\x01\xA7measure\x02" + "\x82\xA8location\x02\xA7measure\x04" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == + "{\"onlyarr\":[{\"measure\":2},{\"measure\":4}],\"include\":42}"); + CHECK(doc.memoryUsage() == + JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(2) + 24); + } + + SECTION("skip null") { + error = deserializeMsgPack(doc, "\x82\xA7onlyarr\xC0\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip false") { + error = deserializeMsgPack(doc, "\x82\xA7onlyarr\xC2\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip true") { + error = deserializeMsgPack(doc, "\x82\xA7onlyarr\xC3\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip positive fixint") { + error = deserializeMsgPack(doc, "\x82\xA7onlyarr\x2A\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip negative fixint") { + error = deserializeMsgPack(doc, "\x82\xA7onlyarr\xFF\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip uint 8") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xCC\x2A\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip uint 16") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xcd\x30\x39\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip uint 32") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xCE\x12\x34\x56\x78\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip uint 64") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyarr\xCF\x12\x34\x56\x78\x9A\xBC" + "\xDE\xF0\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip int 8") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xD0\x2A\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip int 16") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xD1\xCF\xC7\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip int 32") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xD2\xB6\x69\xFD\x2E\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip int 64") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyarr\xD3\x12\x34\x56\x78\x9A\xBC" + "\xDE\xF0\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip float 32") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xCA\x40\x48\xF5\xC3\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip float 64") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyarr\xCB\x40\x09\x21\xCA\xC0\x83" + "\x12\x6F\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip fixstr") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xABhello world\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip str 8") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xd9\x05hello\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + } + + SECTION("skip str 16") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xda\x00\x05hello\xA7include\x2A", filterOpt); + + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + } + + SECTION("skip str 32") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\xdb\x00\x00\x00\x05hello\xA7include\x2A", + filterOpt); + + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip fixmap") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyarr\x82\xA3one\x01\xA3two\x02\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip map 16") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyarr" + "\xDE\x00\x02\xA1H\xA5hello\xA1W\xA5world" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip map 32") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyarr" + "\xDF\x00\x00\x00\x02" + "\xA4zero\xCA\x00\x00\x00\x00" + "\xA2pi\xCA\x40\x48\xF5\xC3" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyarr\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + } + } + + SECTION("root is fixarray") { + SECTION("filter = [false, true]") { + filter[0] = false; // only the first elment of the filter matters + filter[1] = true; // so this one is ignored + + SECTION("input = [1,2,3]") { + error = deserializeMsgPack(doc, "\x93\x01\x02\x03", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "[]"); + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(0)); + } + } + + SECTION("filter = [true, false]") { + filter[0] = true; // only the first elment of the filter matters + filter[1] = false; // so this one is ignored + + SECTION("input = [1,2,3]") { + error = deserializeMsgPack(doc, "\x93\x01\x02\x03", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "[1,2,3]"); + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(3)); + } + } + } + + SECTION("Filter = {onlyobj:{measure:true},include:true}") { + filter["onlyobj"]["measure"] = true; + filter["include"] = true; + + CAPTURE(filter.as()); + + SECTION("include fixmap") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyobj" + "\x82\xA8location\x01\xA7measure\x02" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == + "{\"onlyobj\":{\"measure\":2},\"include\":42}"); + CHECK(doc.memoryUsage() == + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(1) + 24); + } + + SECTION("include map 16") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyobj" + "\xDE\x00\x02\xA8location\x01\xA7measure\x02" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == + "{\"onlyobj\":{\"measure\":2},\"include\":42}"); + CHECK(doc.memoryUsage() == + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(1) + 24); + } + + SECTION("include map 32") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyobj" + "\xDF\x00\x00\x00\x02" + "\xA8location\x01\xA7measure\x02" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == + "{\"onlyobj\":{\"measure\":2},\"include\":42}"); + CHECK(doc.memoryUsage() == + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(1) + 24); + } + + SECTION("skip null") { + error = deserializeMsgPack(doc, "\x82\xA7onlyobj\xC0\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip false") { + error = deserializeMsgPack(doc, "\x82\xA7onlyobj\xC2\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip true") { + error = deserializeMsgPack(doc, "\x82\xA7onlyobj\xC3\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip positive fixint") { + error = deserializeMsgPack(doc, "\x82\xA7onlyobj\x2A\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip negative fixint") { + error = deserializeMsgPack(doc, "\x82\xA7onlyobj\xFF\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip uint 8") { + error = deserializeMsgPack(doc, "\x82\xA7onlyobj\xCC\x2A\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip uint 16") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\xcd\x30\x39\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip uint 32") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\xCE\x12\x34\x56\x78\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip uint 64") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyobj\xCF\x12\x34\x56\x78\x9A\xBC" + "\xDE\xF0\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip int 8") { + error = deserializeMsgPack(doc, "\x82\xA7onlyobj\xD0\x2A\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip int 16") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\xD1\xCF\xC7\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip int 32") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\xD2\xB6\x69\xFD\x2E\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip int 64") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyobj\xD3\x12\x34\x56\x78\x9A\xBC" + "\xDE\xF0\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip float 32") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\xCA\x40\x48\xF5\xC3\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip float 64") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyobj\xCB\x40\x09\x21\xCA\xC0\x83" + "\x12\x6F\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip fixstr") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\xABhello world\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip str 8") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\xd9\x05hello\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + } + + SECTION("skip str 16") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\xda\x00\x05hello\xA7include\x2A", filterOpt); + + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + } + + SECTION("skip str 32") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\xdb\x00\x00\x00\x05hello\xA7include\x2A", + filterOpt); + + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip fixarray") { + error = deserializeMsgPack( + doc, "\x82\xA7onlyobj\x92\x01\x02\xA7include\x2A", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip array 16") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyobj\xDC\x00\x01\xA7" + "example\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + + SECTION("skip array 32") { + error = deserializeMsgPack(doc, + "\x82\xA7onlyobj" + "\xDD\x00\x00\x00\x02\x01\x02" + "\xA7include\x2A", + filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.as() == "{\"onlyobj\":null,\"include\":42}"); + CHECK(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + } + } + + SECTION("filter = true") { + filter.set(true); + + error = deserializeMsgPack(doc, "\x90", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.is() == true); + CHECK(doc.size() == 0); + } + + SECTION("filter = false") { + filter.set(false); + + SECTION("input = fixarray") { + error = deserializeMsgPack(doc, "\x92\x01\x02", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.isNull() == true); + } + + SECTION("input = array 16") { + error = deserializeMsgPack(doc, "\xDC\x00\x02\x01\x02", filterOpt); + + CHECK(error == DeserializationError::Ok); + CHECK(doc.isNull() == true); + } + + SECTION("array too deep") { + error = deserializeMsgPack(doc, "\x91\x91\x91\x91\x91", 5, filterOpt, + DeserializationOption::NestingLimit(4)); + + CHECK(error == DeserializationError::TooDeep); + } + + SECTION("object too deep") { + error = deserializeMsgPack( + doc, "\x81\xA1z\x81\xA1z\x81\xA1z\x81\xA1z\x81\xA1z", 15, filterOpt, + DeserializationOption::NestingLimit(4)); + + CHECK(error == DeserializationError::TooDeep); + } + } +} + +TEST_CASE("Zero-copy mode") { // issue #1697 + char input[] = "\x82\xA7include\x01\xA6ignore\x02"; + + StaticJsonDocument<256> filter; + filter["include"] = true; + + StaticJsonDocument<256> doc; + DeserializationError err = + deserializeMsgPack(doc, input, 18, DeserializationOption::Filter(filter)); + + CHECK(err == DeserializationError::Ok); + CHECK(doc.as() == "{\"include\":1}"); +} + +TEST_CASE("Overloads") { + StaticJsonDocument<256> doc; + StaticJsonDocument<256> filter; + + using namespace DeserializationOption; + + // deserializeMsgPack(..., Filter) + + SECTION("const char*, Filter") { + deserializeMsgPack(doc, "{}", Filter(filter)); + } + + SECTION("const char*, size_t, Filter") { + deserializeMsgPack(doc, "{}", 2, Filter(filter)); + } + + SECTION("const std::string&, Filter") { + deserializeMsgPack(doc, std::string("{}"), Filter(filter)); + } + + SECTION("std::istream&, Filter") { + std::stringstream s("{}"); + deserializeMsgPack(doc, s, Filter(filter)); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("char[n], Filter") { + size_t i = 4; + char vla[i]; + strcpy(vla, "{}"); + deserializeMsgPack(doc, vla, Filter(filter)); + } +#endif + + // deserializeMsgPack(..., Filter, NestingLimit) + + SECTION("const char*, Filter, NestingLimit") { + deserializeMsgPack(doc, "{}", Filter(filter), NestingLimit(5)); + } + + SECTION("const char*, size_t, Filter, NestingLimit") { + deserializeMsgPack(doc, "{}", 2, Filter(filter), NestingLimit(5)); + } + + SECTION("const std::string&, Filter, NestingLimit") { + deserializeMsgPack(doc, std::string("{}"), Filter(filter), NestingLimit(5)); + } + + SECTION("std::istream&, Filter, NestingLimit") { + std::stringstream s("{}"); + deserializeMsgPack(doc, s, Filter(filter), NestingLimit(5)); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("char[n], Filter, NestingLimit") { + size_t i = 4; + char vla[i]; + strcpy(vla, "{}"); + deserializeMsgPack(doc, vla, Filter(filter), NestingLimit(5)); + } +#endif + + // deserializeMsgPack(..., NestingLimit, Filter) + + SECTION("const char*, NestingLimit, Filter") { + deserializeMsgPack(doc, "{}", NestingLimit(5), Filter(filter)); + } + + SECTION("const char*, size_t, NestingLimit, Filter") { + deserializeMsgPack(doc, "{}", 2, NestingLimit(5), Filter(filter)); + } + + SECTION("const std::string&, NestingLimit, Filter") { + deserializeMsgPack(doc, std::string("{}"), NestingLimit(5), Filter(filter)); + } + + SECTION("std::istream&, NestingLimit, Filter") { + std::stringstream s("{}"); + deserializeMsgPack(doc, s, NestingLimit(5), Filter(filter)); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("char[n], NestingLimit, Filter") { + size_t i = 4; + char vla[i]; + strcpy(vla, "{}"); + deserializeMsgPack(doc, vla, NestingLimit(5), Filter(filter)); + } +#endif +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/incompleteInput.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/incompleteInput.cpp new file mode 100644 index 0000000..029facf --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/incompleteInput.cpp @@ -0,0 +1,158 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +DeserializationError deserialize(const char* input, size_t len) { + DynamicJsonDocument doc(4096); + + return deserializeMsgPack(doc, input, len); +} + +void checkAllSizes(const char* input, size_t len) { + REQUIRE(deserialize(input, len) == DeserializationError::Ok); + + while (--len) { + REQUIRE(deserialize(input, len) == DeserializationError::IncompleteInput); + } +} + +TEST_CASE("deserializeMsgPack() returns IncompleteInput") { + SECTION("empty input") { + checkAllSizes("\x00", 1); + } + + SECTION("fixarray") { + checkAllSizes("\x91\x01", 2); + } + + SECTION("array 16") { + checkAllSizes("\xDC\x00\x01\x01", 4); + } + + SECTION("array 32") { + checkAllSizes("\xDD\x00\x00\x00\x01\x01", 6); + } + + SECTION("fixmap") { + checkAllSizes("\x81\xA3one\x01", 6); + } + + SECTION("map 16") { + checkAllSizes("\xDE\x00\x01\xA3one\x01", 8); + } + + SECTION("map 32") { + checkAllSizes("\xDF\x00\x00\x00\x01\xA3one\x01", 10); + checkAllSizes("\xDF\x00\x00\x00\x01\xd9\x03one\x01", 11); + } + + SECTION("uint 8") { + checkAllSizes("\xcc\x01", 2); + } + + SECTION("uint 16") { + checkAllSizes("\xcd\x00\x01", 3); + } + + SECTION("uint 32") { + checkAllSizes("\xCE\x00\x00\x00\x01", 5); + } + +#if ARDUINOJSON_USE_LONG_LONG + SECTION("uint 64") { + checkAllSizes("\xCF\x00\x00\x00\x00\x00\x00\x00\x00", 9); + } +#endif + + SECTION("int 8") { + checkAllSizes("\xD0\x01", 2); + } + + SECTION("int 16") { + checkAllSizes("\xD1\x00\x01", 3); + } + + SECTION("int 32") { + checkAllSizes("\xD2\x00\x00\x00\x01", 5); + } + +#if ARDUINOJSON_USE_LONG_LONG + SECTION("int 64") { + checkAllSizes("\xD3\x00\x00\x00\x00\x00\x00\x00\x00", 9); + } +#endif + + SECTION("float 32") { + checkAllSizes("\xCA\x40\x48\xF5\xC3", 5); + } + + SECTION("float 64") { + checkAllSizes("\xCB\x40\x09\x21\xCA\xC0\x83\x12\x6F", 9); + } + + SECTION("fixstr") { + checkAllSizes("\xABhello world", 12); + } + + SECTION("str 8") { + checkAllSizes("\xd9\x05hello", 7); + } + + SECTION("str 16") { + checkAllSizes("\xda\x00\x05hello", 8); + } + + SECTION("str 32") { + checkAllSizes("\xdb\x00\x00\x00\x05hello", 10); + } + + SECTION("bin 8") { + checkAllSizes("\xc4\x01X", 3); + } + + SECTION("bin 16") { + checkAllSizes("\xc5\x00\x01X", 4); + } + + SECTION("bin 32") { + checkAllSizes("\xc6\x00\x00\x00\x01X", 6); + } + + SECTION("ext 8") { + checkAllSizes("\xc7\x01\x01\x01", 4); + } + + SECTION("ext 16") { + checkAllSizes("\xc8\x00\x01\x01\x01", 5); + } + + SECTION("ext 32") { + checkAllSizes("\xc9\x00\x00\x00\x01\x01\x01", 7); + } + + SECTION("fixext 1") { + checkAllSizes("\xd4\x01\x01", 3); + } + + SECTION("fixext 2") { + checkAllSizes("\xd5\x01\x01\x02", 4); + } + + SECTION("fixext 4") { + checkAllSizes("\xd6\x01\x01\x02\x03\x04", 6); + } + + SECTION("fixext 8") { + checkAllSizes("\xd7\x01\x01\x02\x03\x04\x05\x06\x07\x08", 10); + } + + SECTION("fixext 16") { + checkAllSizes( + "\xd8\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E" + "\x0F\x10", + 18); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/input_types.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/input_types.cpp new file mode 100644 index 0000000..f46d469 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/input_types.cpp @@ -0,0 +1,95 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +#include "CustomReader.hpp" + +TEST_CASE("deserializeMsgPack(const std::string&)") { + DynamicJsonDocument doc(4096); + + SECTION("should accept const string") { + const std::string input("\x92\x01\x02"); + + DeserializationError err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("should accept temporary string") { + DeserializationError err = + deserializeMsgPack(doc, std::string("\x92\x01\x02")); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("should duplicate content") { + std::string input("\x91\xA5hello"); + + DeserializationError err = deserializeMsgPack(doc, input); + input[2] = 'X'; // alter the string tomake sure we made a copy + + JsonArray array = doc.as(); + REQUIRE(err == DeserializationError::Ok); + REQUIRE(std::string("hello") == array[0]); + } + + SECTION("should accept a zero in input") { + DeserializationError err = + deserializeMsgPack(doc, std::string("\x92\x00\x02", 3)); + + REQUIRE(err == DeserializationError::Ok); + JsonArray arr = doc.as(); + REQUIRE(arr[0] == 0); + REQUIRE(arr[1] == 2); + } +} + +TEST_CASE("deserializeMsgPack(std::istream&)") { + DynamicJsonDocument doc(4096); + + SECTION("should accept a zero in input") { + std::istringstream input(std::string("\x92\x00\x02", 3)); + + DeserializationError err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::Ok); + JsonArray arr = doc.as(); + REQUIRE(arr[0] == 0); + REQUIRE(arr[1] == 2); + } + + SECTION("should detect incomplete input") { + std::istringstream input("\x92\x00\x02"); + + DeserializationError err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::IncompleteInput); + } +} + +#ifdef HAS_VARIABLE_LENGTH_ARRAY +TEST_CASE("deserializeMsgPack(VLA)") { + size_t i = 16; + char vla[i]; + memcpy(vla, "\xDE\x00\x01\xA5Hello\xA5world", 15); + + StaticJsonDocument doc; + DeserializationError err = deserializeMsgPack(doc, vla); + + REQUIRE(err == DeserializationError::Ok); +} +#endif + +TEST_CASE("deserializeMsgPack(CustomReader)") { + DynamicJsonDocument doc(4096); + CustomReader reader("\x92\xA5Hello\xA5world"); + DeserializationError err = deserializeMsgPack(doc, reader); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.size() == 2); + REQUIRE(doc[0] == "Hello"); + REQUIRE(doc[1] == "world"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/misc.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/misc.cpp new file mode 100644 index 0000000..0717171 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/misc.cpp @@ -0,0 +1,24 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("deserializeMsgPack() returns EmptyInput") { + StaticJsonDocument<100> doc; + + SECTION("from sized buffer") { + DeserializationError err = deserializeMsgPack(doc, "", 0); + + REQUIRE(err == DeserializationError::EmptyInput); + } + + SECTION("from stream") { + std::istringstream input(""); + + DeserializationError err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::EmptyInput); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/nestingLimit.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/nestingLimit.cpp new file mode 100644 index 0000000..6005f55 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/nestingLimit.cpp @@ -0,0 +1,85 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +#define SHOULD_WORK(expression) REQUIRE(DeserializationError::Ok == expression); +#define SHOULD_FAIL(expression) \ + REQUIRE(DeserializationError::TooDeep == expression); + +TEST_CASE("JsonDeserializer nesting") { + DynamicJsonDocument doc(4096); + + SECTION("Input = const char*") { + SECTION("limit = 0") { + DeserializationOption::NestingLimit nesting(0); + SHOULD_WORK(deserializeMsgPack(doc, "\xA1H", nesting)); // "H" + SHOULD_FAIL(deserializeMsgPack(doc, "\x90", nesting)); // [] + SHOULD_FAIL(deserializeMsgPack(doc, "\x80", nesting)); // {} + } + + SECTION("limit = 1") { + DeserializationOption::NestingLimit nesting(1); + SHOULD_WORK(deserializeMsgPack(doc, "\x90", nesting)); // {} + SHOULD_WORK(deserializeMsgPack(doc, "\x80", nesting)); // [] + SHOULD_FAIL(deserializeMsgPack(doc, "\x81\xA1H\x80", nesting)); // {H:{}} + SHOULD_FAIL(deserializeMsgPack(doc, "\x91\x90", nesting)); // [[]] + } + } + + SECTION("char* and size_t") { + SECTION("limit = 0") { + DeserializationOption::NestingLimit nesting(0); + SHOULD_WORK(deserializeMsgPack(doc, "\xA1H", 2, nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, "\x90", 1, nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, "\x80", 1, nesting)); + } + + SECTION("limit = 1") { + DeserializationOption::NestingLimit nesting(1); + SHOULD_WORK(deserializeMsgPack(doc, "\x90", 1, nesting)); + SHOULD_WORK(deserializeMsgPack(doc, "\x80", 1, nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, "\x81\xA1H\x80", 4, nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, "\x91\x90", 2, nesting)); + } + } + + SECTION("Input = std::string") { + using std::string; + + SECTION("limit = 0") { + DeserializationOption::NestingLimit nesting(0); + SHOULD_WORK(deserializeMsgPack(doc, string("\xA1H"), nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, string("\x90"), nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, string("\x80"), nesting)); + } + + SECTION("limit = 1") { + DeserializationOption::NestingLimit nesting(1); + SHOULD_WORK(deserializeMsgPack(doc, string("\x90"), nesting)); + SHOULD_WORK(deserializeMsgPack(doc, string("\x80"), nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, string("\x81\xA1H\x80"), nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, string("\x91\x90"), nesting)); + } + } + + SECTION("Input = std::istream") { + SECTION("limit = 0") { + DeserializationOption::NestingLimit nesting(0); + std::istringstream good("\xA1H"); // "H" + std::istringstream bad("\x90"); // [] + SHOULD_WORK(deserializeMsgPack(doc, good, nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, bad, nesting)); + } + + SECTION("limit = 1") { + DeserializationOption::NestingLimit nesting(1); + std::istringstream good("\x90"); // [] + std::istringstream bad("\x91\x90"); // [[]] + SHOULD_WORK(deserializeMsgPack(doc, good, nesting)); + SHOULD_FAIL(deserializeMsgPack(doc, bad, nesting)); + } + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/notSupported.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/notSupported.cpp new file mode 100644 index 0000000..5e204cd --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackDeserializer/notSupported.cpp @@ -0,0 +1,82 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +static void checkMsgPackDocument(const char* input, size_t inputSize, + const char* expectedJson) { + DynamicJsonDocument doc(4096); + + DeserializationError error = deserializeMsgPack(doc, input, inputSize); + + REQUIRE(error == DeserializationError::Ok); + std::string actualJson; + serializeJson(doc, actualJson); + REQUIRE(actualJson == expectedJson); +} + +static void checkMsgPackError(const char* input, size_t inputSize, + DeserializationError expectedError) { + DynamicJsonDocument doc(4096); + + DeserializationError error = deserializeMsgPack(doc, input, inputSize); + + REQUIRE(error == expectedError); +} + +TEST_CASE("deserializeMsgPack() return NotSupported") { + SECTION("bin 8") { + checkMsgPackDocument("\x92\xc4\x01X\x2A", 5, "[null,42]"); + } + + SECTION("bin 16") { + checkMsgPackDocument("\x92\xc5\x00\x01X\x2A", 6, "[null,42]"); + } + + SECTION("bin 32") { + checkMsgPackDocument("\x92\xc6\x00\x00\x00\x01X\x2A", 8, "[null,42]"); + } + + SECTION("ext 8") { + checkMsgPackDocument("\x92\xc7\x01\x01\x01\x2A", 6, "[null,42]"); + } + + SECTION("ext 16") { + checkMsgPackDocument("\x92\xc8\x00\x01\x01\x01\x2A", 7, "[null,42]"); + } + + SECTION("ext 32") { + checkMsgPackDocument("\x92\xc9\x00\x00\x00\x01\x01\x01\x2A", 9, + "[null,42]"); + } + + SECTION("fixext 1") { + checkMsgPackDocument("\x92\xd4\x01\x01\x2A", 5, "[null,42]"); + } + + SECTION("fixext 2") { + checkMsgPackDocument("\x92\xd5\x01\x01\x02\x2A", 6, "[null,42]"); + } + + SECTION("fixext 4") { + checkMsgPackDocument("\x92\xd6\x01\x01\x02\x03\x04\x2A", 8, "[null,42]"); + } + + SECTION("fixext 8") { + checkMsgPackDocument("\x92\xd7\x01\x01\x02\x03\x04\x05\x06\x07\x08\x2A", 12, + "[null,42]"); + } + + SECTION("fixext 16") { + checkMsgPackDocument( + "\x92\xd8\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E" + "\x0F\x10\x2A", + 20, "[null,42]"); + } + + SECTION("integer as key") { + checkMsgPackError("\x81\x01\xA1H", 3, DeserializationError::InvalidInput); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/CMakeLists.txt new file mode 100644 index 0000000..9a7721e --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/CMakeLists.txt @@ -0,0 +1,19 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(MsgPackSerializerTests + destination_types.cpp + measure.cpp + misc.cpp + serializeArray.cpp + serializeObject.cpp + serializeVariant.cpp +) + +add_test(MsgPackSerializer MsgPackSerializerTests) + +set_tests_properties(MsgPackSerializer + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/destination_types.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/destination_types.cpp new file mode 100644 index 0000000..ae58063 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/destination_types.cpp @@ -0,0 +1,59 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("serialize MsgPack to various destination types") { + DynamicJsonDocument doc(4096); + JsonObject object = doc.to(); + object["hello"] = "world"; + const char *expected_result = "\x81\xA5hello\xA5world"; + const size_t expected_length = 13; + + SECTION("std::string") { + std::string result; + size_t len = serializeMsgPack(object, result); + + REQUIRE(expected_result == result); + REQUIRE(expected_length == len); + } + + /* SECTION("std::vector") { + std::vector result; + size_t len = serializeMsgPack(object, result); + + REQUIRE(std::vector(expected_result, expected_result + 13) == + result); + REQUIRE(expected_length == len); + } */ + + SECTION("char[] larger than needed") { + char result[64]; + memset(result, 42, sizeof(result)); + size_t len = serializeMsgPack(object, result); + + REQUIRE(expected_length == len); + REQUIRE(std::string(expected_result, len) == std::string(result, len)); + REQUIRE(result[len] == 42); + } + + SECTION("char[] of the right size") { // #1545 + char result[13]; + size_t len = serializeMsgPack(object, result); + + REQUIRE(expected_length == len); + REQUIRE(std::string(expected_result, len) == std::string(result, len)); + } + + SECTION("char*") { + char result[64]; + memset(result, 42, sizeof(result)); + size_t len = serializeMsgPack(object, result, 64); + + REQUIRE(expected_length == len); + REQUIRE(std::string(expected_result, len) == std::string(result, len)); + REQUIRE(result[len] == 42); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/measure.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/measure.cpp new file mode 100644 index 0000000..d49bcd9 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/measure.cpp @@ -0,0 +1,14 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("measureMsgPack()") { + DynamicJsonDocument doc(4096); + JsonObject object = doc.to(); + object["hello"] = "world"; + + REQUIRE(measureMsgPack(doc) == 13); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/misc.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/misc.cpp new file mode 100644 index 0000000..52c53a7 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/misc.cpp @@ -0,0 +1,46 @@ +#include +#include +#include + +template +void check(T value, const std::string &expected) { + DynamicJsonDocument doc(4096); + doc.to().set(value); + char buffer[256] = ""; + size_t returnValue = serializeMsgPack(doc, buffer, sizeof(buffer)); + REQUIRE(expected == buffer); + REQUIRE(expected.size() == returnValue); +} + +TEST_CASE("serializeMsgPack(MemberProxy)") { + DynamicJsonDocument doc(4096); + deserializeJson(doc, "{\"hello\":42}"); + JsonObject obj = doc.as(); + std::string result; + + serializeMsgPack(obj["hello"], result); + + REQUIRE(result == "*"); +} + +TEST_CASE("serializeMsgPack(ElementProxy)") { + DynamicJsonDocument doc(4096); + deserializeJson(doc, "[42]"); + JsonArray arr = doc.as(); + std::string result; + + serializeMsgPack(arr[0], result); + + REQUIRE(result == "*"); +} + +TEST_CASE("serializeMsgPack(JsonVariantSubscript)") { + DynamicJsonDocument doc(4096); + deserializeJson(doc, "[42]"); + JsonVariant var = doc.as(); + std::string result; + + serializeMsgPack(var[0], result); + + REQUIRE(result == "*"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeArray.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeArray.cpp new file mode 100644 index 0000000..74de8f1 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeArray.cpp @@ -0,0 +1,58 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +static void check(const JsonArray array, const char* expected_data, + size_t expected_len) { + std::string expected(expected_data, expected_data + expected_len); + std::string actual; + size_t len = serializeMsgPack(array, actual); + CAPTURE(array); + REQUIRE(len == expected_len); + REQUIRE(actual == expected); +} + +template +static void check(const JsonArray array, const char (&expected_data)[N]) { + const size_t expected_len = N - 1; + check(array, expected_data, expected_len); +} + +static void check(const JsonArray array, const std::string& expected) { + check(array, expected.data(), expected.length()); +} + +TEST_CASE("serialize MsgPack array") { + DynamicJsonDocument doc(JSON_ARRAY_SIZE(65536)); + JsonArray array = doc.to(); + + SECTION("empty") { + check(array, "\x90"); + } + + SECTION("fixarray") { + array.add("hello"); + array.add("world"); + + check(array, "\x92\xA5hello\xA5world"); + } + + SECTION("array 16") { + for (int i = 0; i < 16; i++) array.add(i); + + check(array, + "\xDC\x00\x10\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D" + "\x0E\x0F"); + } + + SECTION("array 32") { + const char* nil = 0; + for (int i = 0; i < 65536; i++) array.add(nil); + + check(array, + std::string("\xDD\x00\x01\x00\x00", 5) + std::string(65536, '\xc0')); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp new file mode 100644 index 0000000..e5c167a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeObject.cpp @@ -0,0 +1,83 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +static void check(const JsonObject object, const char* expected_data, + size_t expected_len) { + std::string expected(expected_data, expected_data + expected_len); + std::string actual; + size_t len = serializeMsgPack(object, actual); + CAPTURE(object); + REQUIRE(len == expected_len); + REQUIRE(actual == expected); +} + +template +static void check(const JsonObject object, const char (&expected_data)[N]) { + const size_t expected_len = N - 1; + check(object, expected_data, expected_len); +} + +// TODO: used by the commented test +// static void check(const JsonObject object, const std::string& expected) { +// check(object, expected.data(), expected.length()); +//} + +TEST_CASE("serialize MsgPack object") { + DynamicJsonDocument doc(4096); + JsonObject object = doc.to(); + + SECTION("empty") { + check(object, "\x80"); + } + + SECTION("fixmap") { + object["hello"] = "world"; + + check(object, "\x81\xA5hello\xA5world"); + } + + SECTION("map 16") { + for (int i = 0; i < 16; ++i) { + char key[16]; + sprintf(key, "i%X", i); + object[key] = i; + } + + check(object, + "\xDE\x00\x10\xA2i0\x00\xA2i1\x01\xA2i2\x02\xA2i3\x03\xA2i4\x04\xA2i5" + "\x05\xA2i6\x06\xA2i7\x07\xA2i8\x08\xA2i9\x09\xA2iA\x0A\xA2iB\x0B\xA2" + "iC\x0C\xA2iD\x0D\xA2iE\x0E\xA2iF\x0F"); + } + + // TODO: improve performance and uncomment + // SECTION("map 32") { + // std::string expected("\xDF\x00\x01\x00\x00", 5); + // + // for (int i = 0; i < 65536; ++i) { + // char kv[16]; + // sprintf(kv, "%04x", i); + // object[kv] = kv; + // expected += '\xA4'; + // expected += kv; + // expected += '\xA4'; + // expected += kv; + // } + // + // check(object, expected); + // } + + SECTION("serialized(const char*)") { + object["hello"] = serialized("\xDB\x00\x01\x00\x00", 5); + check(object, "\x81\xA5hello\xDB\x00\x01\x00\x00"); + } + + SECTION("serialized(std::string)") { + object["hello"] = serialized(std::string("\xDB\x00\x01\x00\x00", 5)); + check(object, "\x81\xA5hello\xDB\x00\x01\x00\x00"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeVariant.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeVariant.cpp new file mode 100644 index 0000000..1fd8ba1 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/MsgPackSerializer/serializeVariant.cpp @@ -0,0 +1,161 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +template +static void checkVariant(T value, const char* expected_data, + size_t expected_len) { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + variant.set(value); + std::string expected(expected_data, expected_data + expected_len); + std::string actual; + size_t len = serializeMsgPack(variant, actual); + CAPTURE(variant); + REQUIRE(len == expected_len); + REQUIRE(actual == expected); +} + +template +static void checkVariant(T value, const char (&expected_data)[N]) { + const size_t expected_len = N - 1; + checkVariant(value, expected_data, expected_len); +} + +template +static void checkVariant(T value, const std::string& expected) { + checkVariant(value, expected.data(), expected.length()); +} + +TEST_CASE("serialize MsgPack value") { + SECTION("unbound") { + checkVariant(JsonVariant(), "\xC0"); // we represent undefined as nil + } + + SECTION("nil") { + const char* nil = 0; // ArduinoJson uses a string for null + checkVariant(nil, "\xC0"); + } + + SECTION("bool") { + checkVariant(false, "\xC2"); + checkVariant(true, "\xC3"); + } + + SECTION("positive fixint") { + SECTION("signed") { + checkVariant(0, "\x00"); + checkVariant(127, "\x7F"); + } + SECTION("unsigned") { + checkVariant(0U, "\x00"); + checkVariant(127U, "\x7F"); + } + } + + SECTION("uint 8") { + checkVariant(128, "\xCC\x80"); + checkVariant(255, "\xCC\xFF"); + } + + SECTION("uint 16") { + checkVariant(256, "\xCD\x01\x00"); + checkVariant(0xFFFF, "\xCD\xFF\xFF"); + } + + SECTION("uint 32") { + checkVariant(0x00010000U, "\xCE\x00\x01\x00\x00"); + checkVariant(0x12345678U, "\xCE\x12\x34\x56\x78"); + checkVariant(0xFFFFFFFFU, "\xCE\xFF\xFF\xFF\xFF"); + } + +#if ARDUINOJSON_USE_LONG_LONG + SECTION("uint 64") { + checkVariant(0x0001000000000000U, "\xCF\x00\x01\x00\x00\x00\x00\x00\x00"); + checkVariant(0x123456789ABCDEF0U, "\xCF\x12\x34\x56\x78\x9A\xBC\xDE\xF0"); + checkVariant(0xFFFFFFFFFFFFFFFFU, "\xCF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"); + } +#endif + + SECTION("negative fixint") { + checkVariant(-1, "\xFF"); + checkVariant(-32, "\xE0"); + } + + SECTION("int 8") { + checkVariant(-33, "\xD0\xDF"); + checkVariant(-128, "\xD0\x80"); + } + + SECTION("int 16") { + checkVariant(-129, "\xD1\xFF\x7F"); + checkVariant(-32768, "\xD1\x80\x00"); + } + + SECTION("int 32") { + checkVariant(-32769, "\xD2\xFF\xFF\x7F\xFF"); + checkVariant(-2147483647 - 1, "\xD2\x80\x00\x00\x00"); + } + +#if ARDUINOJSON_USE_LONG_LONG + SECTION("int 64") { + checkVariant(int64_t(0xFEDCBA9876543210), + "\xD3\xFE\xDC\xBA\x98\x76\x54\x32\x10"); + } +#endif + + SECTION("float 32") { + checkVariant(1.25, "\xCA\x3F\xA0\x00\x00"); + } + + SECTION("float 64") { + checkVariant(3.1415, "\xCB\x40\x09\x21\xCA\xC0\x83\x12\x6F"); + } + + SECTION("fixstr") { + checkVariant("", "\xA0"); + checkVariant("hello world hello world hello !", + "\xBFhello world hello world hello !"); + } + + SECTION("str 8") { + checkVariant("hello world hello world hello !!", + "\xD9\x20hello world hello world hello !!"); + } + + SECTION("str 16") { + std::string shortest(256, '?'); + checkVariant(shortest.c_str(), std::string("\xDA\x01\x00", 3) + shortest); + + std::string longest(65535, '?'); + checkVariant(longest.c_str(), std::string("\xDA\xFF\xFF", 3) + longest); + } + + SECTION("str 32") { + std::string shortest(65536, '?'); + checkVariant(shortest.c_str(), + std::string("\xDB\x00\x01\x00\x00", 5) + shortest); + } + + SECTION("serialized(const char*)") { + checkVariant(serialized("\xDA\xFF\xFF"), "\xDA\xFF\xFF"); + checkVariant(serialized("\xDB\x00\x01\x00\x00", 5), "\xDB\x00\x01\x00\x00"); + } + + SECTION("serialize round double as integer") { // Issue #1718 + checkVariant(-32768.0, "\xD1\x80\x00"); + checkVariant(-129.0, "\xD1\xFF\x7F"); + checkVariant(-128.0, "\xD0\x80"); + checkVariant(-33.0, "\xD0\xDF"); + checkVariant(-32.0, "\xE0"); + checkVariant(-1.0, "\xFF"); + checkVariant(0.0, "\x00"); + checkVariant(127.0, "\x7F"); + checkVariant(128.0, "\xCC\x80"); + checkVariant(255.0, "\xCC\xFF"); + checkVariant(256.0, "\xCD\x01\x00"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/CMakeLists.txt new file mode 100644 index 0000000..9bcf334 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/CMakeLists.txt @@ -0,0 +1,19 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(NumbersTests + convertNumber.cpp + parseFloat.cpp + parseDouble.cpp + parseInteger.cpp + parseNumber.cpp +) + + +add_test(Numbers NumbersTests) + +set_tests_properties(Numbers + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/convertNumber.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/convertNumber.cpp new file mode 100644 index 0000000..1c36aa0 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/convertNumber.cpp @@ -0,0 +1,78 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("canConvertNumber()") { + SECTION("int8_t -> int8_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(127)) == true); + CHECK((canConvertNumber(-128)) == true); + } + + SECTION("int8_t -> int16_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(127)) == true); + CHECK((canConvertNumber(-128)) == true); + } + + SECTION("int8_t -> uint8_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(127)) == true); + CHECK((canConvertNumber(-128)) == false); + } + + SECTION("int8_t -> uint16_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(127)) == true); + CHECK((canConvertNumber(-128)) == false); + } + + SECTION("int16_t -> int8_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(127)) == true); + CHECK((canConvertNumber(128)) == false); + CHECK((canConvertNumber(-128)) == true); + CHECK((canConvertNumber(-129)) == false); + } + + SECTION("int16_t -> uint8_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(255)) == true); + CHECK((canConvertNumber(256)) == false); + CHECK((canConvertNumber(-1)) == false); + } + + SECTION("uint8_t -> int8_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(127)) == true); + CHECK((canConvertNumber(128)) == false); + CHECK((canConvertNumber(255)) == false); + } + + SECTION("uint8_t -> int16_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(127)) == true); + CHECK((canConvertNumber(128)) == true); + CHECK((canConvertNumber(255)) == true); + } + + SECTION("uint8_t -> uint8_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(127)) == true); + CHECK((canConvertNumber(128)) == true); + CHECK((canConvertNumber(255)) == true); + } + + SECTION("uint8_t -> uint16_t") { + CHECK((canConvertNumber(0)) == true); + CHECK((canConvertNumber(127)) == true); + CHECK((canConvertNumber(128)) == true); + CHECK((canConvertNumber(255)) == true); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseDouble.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseDouble.cpp new file mode 100644 index 0000000..1ce9a02 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseDouble.cpp @@ -0,0 +1,96 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_USE_DOUBLE 1 +#define ARDUINOJSON_ENABLE_NAN 1 +#define ARDUINOJSON_ENABLE_INFINITY 1 + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +void checkDouble(const char* input, double expected) { + CAPTURE(input); + REQUIRE(parseNumber(input) == Approx(expected)); +} + +void checkDoubleNaN(const char* input) { + CAPTURE(input); + double result = parseNumber(input); + REQUIRE(result != result); +} + +void checkDoubleInf(const char* input, bool negative) { + CAPTURE(input); + double x = parseNumber(input); + if (negative) + REQUIRE(x < 0); + else + REQUIRE(x > 0); + REQUIRE(x == x); // not a NaN + REQUIRE(x * 2 == x); // a property of infinity +} + +TEST_CASE("parseNumber()") { + SECTION("Short_NoExponent") { + checkDouble("3.14", 3.14); + checkDouble("-3.14", -3.14); + checkDouble("+3.14", +3.14); + } + + SECTION("Short_NoDot") { + checkDouble("1E+308", 1E+308); + checkDouble("-1E+308", -1E+308); + checkDouble("+1E-308", +1E-308); + checkDouble("+1e+308", +1e+308); + checkDouble("-1e-308", -1e-308); + } + + SECTION("Max") { + checkDouble(".017976931348623147e+310", 1.7976931348623147e+308); + checkDouble(".17976931348623147e+309", 1.7976931348623147e+308); + checkDouble("1.7976931348623147e+308", 1.7976931348623147e+308); + checkDouble("17.976931348623147e+307", 1.7976931348623147e+308); + checkDouble("179.76931348623147e+306", 1.7976931348623147e+308); + } + + SECTION("Min") { + checkDouble(".022250738585072014e-306", 2.2250738585072014e-308); + checkDouble(".22250738585072014e-307", 2.2250738585072014e-308); + checkDouble("2.2250738585072014e-308", 2.2250738585072014e-308); + checkDouble("22.250738585072014e-309", 2.2250738585072014e-308); + checkDouble("222.50738585072014e-310", 2.2250738585072014e-308); + } + + SECTION("VeryLong") { + checkDouble("0.00000000000000000000000000000001", 1e-32); + checkDouble("100000000000000000000000000000000.0", 1e+32); + checkDouble( + "100000000000000000000000000000000.00000000000000000000000000000", + 1e+32); + } + + SECTION("MantissaTooLongToFit") { + checkDouble("0.179769313486231571111111111111", 0.17976931348623157); + checkDouble("17976931348623157.11111111111111", 17976931348623157.0); + checkDouble("1797693.134862315711111111111111", 1797693.1348623157); + + checkDouble("-0.179769313486231571111111111111", -0.17976931348623157); + checkDouble("-17976931348623157.11111111111111", -17976931348623157.0); + checkDouble("-1797693.134862315711111111111111", -1797693.1348623157); + } + + SECTION("ExponentTooBig") { + checkDoubleInf("1e309", false); + checkDoubleInf("-1e309", true); + checkDoubleInf("1e65535", false); + checkDouble("1e-65535", 0.0); + } + + SECTION("NaN") { + checkDoubleNaN("NaN"); + checkDoubleNaN("nan"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseFloat.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseFloat.cpp new file mode 100644 index 0000000..ec69cab --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseFloat.cpp @@ -0,0 +1,101 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#define ARDUINOJSON_USE_DOUBLE 0 +#define ARDUINOJSON_ENABLE_NAN 1 +#define ARDUINOJSON_ENABLE_INFINITY 1 + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +void checkFloat(const char* input, float expected) { + CAPTURE(input); + REQUIRE(parseNumber(input) == Approx(expected)); +} + +void checkFloatNaN(const char* input) { + CAPTURE(input); + float result = parseNumber(input); + REQUIRE(result != result); +} + +void checkFloatInf(const char* input, bool negative) { + CAPTURE(input); + float x = parseNumber(input); + if (negative) + REQUIRE(x < 0); + else + REQUIRE(x > 0); + REQUIRE(x == x); // not a NaN + REQUIRE(x * 2 == x); // a property of infinity +} + +TEST_CASE("parseNumber()") { + SECTION("Float_Short_NoExponent") { + checkFloat("3.14", 3.14f); + checkFloat("-3.14", -3.14f); + checkFloat("+3.14", +3.14f); + } + + SECTION("Short_NoDot") { + checkFloat("1E+38", 1E+38f); + checkFloat("-1E+38", -1E+38f); + checkFloat("+1E-38", +1E-38f); + checkFloat("+1e+38", +1e+38f); + checkFloat("-1e-38", -1e-38f); + } + + SECTION("Max") { + checkFloat("340.2823e+36", 3.402823e+38f); + checkFloat("34.02823e+37", 3.402823e+38f); + checkFloat("3.402823e+38", 3.402823e+38f); + checkFloat("0.3402823e+39", 3.402823e+38f); + checkFloat("0.03402823e+40", 3.402823e+38f); + checkFloat("0.003402823e+41", 3.402823e+38f); + } + + SECTION("VeryLong") { + checkFloat("0.00000000000000000000000000000001", 1e-32f); + checkFloat("100000000000000000000000000000000.0", 1e+32f); + checkFloat( + "100000000000000000000000000000000.00000000000000000000000000000", + 1e+32f); + } + + SECTION("MantissaTooLongToFit") { + checkFloat("0.340282346638528861111111111111", 0.34028234663852886f); + checkFloat("34028234663852886.11111111111111", 34028234663852886.0f); + checkFloat("34028234.66385288611111111111111", 34028234.663852886f); + + checkFloat("-0.340282346638528861111111111111", -0.34028234663852886f); + checkFloat("-34028234663852886.11111111111111", -34028234663852886.0f); + checkFloat("-34028234.66385288611111111111111", -34028234.663852886f); + } + + SECTION("ExponentTooBig") { + checkFloatInf("1e39", false); + checkFloatInf("-1e39", true); + checkFloatInf("1e255", false); + checkFloat("1e-255", 0.0f); + } + + SECTION("NaN") { + checkFloatNaN("NaN"); + checkFloatNaN("nan"); + } + + SECTION("Infinity") { + checkFloatInf("Infinity", false); + checkFloatInf("+Infinity", false); + checkFloatInf("-Infinity", true); + checkFloatInf("inf", false); + checkFloatInf("+inf", false); + checkFloatInf("-inf", true); + + checkFloatInf("1e300", false); + checkFloatInf("-1e300", true); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseInteger.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseInteger.cpp new file mode 100644 index 0000000..aae66e8 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseInteger.cpp @@ -0,0 +1,68 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +template +void checkInteger(const char* input, T expected) { + CAPTURE(input); + T actual = parseNumber(input); + REQUIRE(expected == actual); +} + +TEST_CASE("parseNumber()") { + checkInteger("-128", -128); + checkInteger("127", 127); + checkInteger("+127", 127); + checkInteger("3.14", 3); + checkInteger("x42", 0); + checkInteger("128", 0); // overflow + checkInteger("-129", 0); // overflow +} + +TEST_CASE("parseNumber()") { + checkInteger("-32768", -32768); + checkInteger("32767", 32767); + checkInteger("+32767", 32767); + checkInteger("3.14", 3); + checkInteger("x42", 0); + checkInteger("-32769", 0); // overflow + checkInteger("32768", 0); // overflow +} + +TEST_CASE("parseNumber()") { + checkInteger("-2147483648", (-2147483647 - 1)); + checkInteger("2147483647", 2147483647); + checkInteger("+2147483647", 2147483647); + checkInteger("3.14", 3); + checkInteger("x42", 0); + checkInteger("-2147483649", 0); // overflow + checkInteger("2147483648", 0); // overflow +} + +TEST_CASE("parseNumber()") { + checkInteger("0", 0); + checkInteger("-0", 0); + checkInteger("255", 255); + checkInteger("+255", 255); + checkInteger("3.14", 3); + checkInteger("x42", 0); + checkInteger("-1", 0); + checkInteger("256", 0); +} + +TEST_CASE("parseNumber()") { + checkInteger("0", 0); + checkInteger("65535", 65535); + checkInteger("+65535", 65535); + checkInteger("3.14", 3); + // checkInteger(" 42", 0); + checkInteger("x42", 0); + checkInteger("-1", 0); + checkInteger("65536", 0); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseNumber.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseNumber.cpp new file mode 100644 index 0000000..eb52f3a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/Numbers/parseNumber.cpp @@ -0,0 +1,57 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +TEST_CASE("Test unsigned integer overflow") { + VariantData first, second; + first.init(); + second.init(); + + // Avoids MSVC warning C4127 (conditional expression is constant) + size_t integerSize = sizeof(Integer); + + if (integerSize == 8) { + parseNumber("18446744073709551615", first); + parseNumber("18446744073709551616", second); + } else { + parseNumber("4294967295", first); + parseNumber("4294967296", second); + } + + REQUIRE(first.type() == uint8_t(VALUE_IS_UNSIGNED_INTEGER)); + REQUIRE(second.type() == uint8_t(VALUE_IS_FLOAT)); +} + +TEST_CASE("Test signed integer overflow") { + VariantData first, second; + first.init(); + second.init(); + + // Avoids MSVC warning C4127 (conditional expression is constant) + size_t integerSize = sizeof(Integer); + + if (integerSize == 8) { + parseNumber("-9223372036854775808", first); + parseNumber("-9223372036854775809", second); + } else { + parseNumber("-2147483648", first); + parseNumber("-2147483649", second); + } + + REQUIRE(first.type() == uint8_t(VALUE_IS_SIGNED_INTEGER)); + REQUIRE(second.type() == uint8_t(VALUE_IS_FLOAT)); +} + +TEST_CASE("Invalid value") { + VariantData result; + result.init(); + + parseNumber("6a3", result); + + REQUIRE(result.type() == uint8_t(VALUE_IS_NULL)); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/CMakeLists.txt new file mode 100644 index 0000000..57a3f85 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/CMakeLists.txt @@ -0,0 +1,18 @@ +# ArduinoJson - https://arduinojson.org +# Copyright © 2014-2022, Benoit BLANCHON +# MIT License + +add_executable(TextFormatterTests + writeFloat.cpp + writeInteger.cpp + writeString.cpp +) + +set_target_properties(TextFormatterTests PROPERTIES UNITY_BUILD OFF) + +add_test(TextFormatter TextFormatterTests) + +set_tests_properties(TextFormatter + PROPERTIES + LABELS "Catch" +) diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeFloat.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeFloat.cpp new file mode 100644 index 0000000..87eef3d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeFloat.cpp @@ -0,0 +1,119 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +#define ARDUINOJSON_ENABLE_NAN 1 +#define ARDUINOJSON_ENABLE_INFINITY 1 +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +template +void check(TFloat input, const std::string& expected) { + std::string output; + Writer sb(output); + TextFormatter > writer(sb); + writer.writeFloat(input); + REQUIRE(writer.bytesWritten() == output.size()); + CHECK(expected == output); +} + +TEST_CASE("TextFormatter::writeFloat(double)") { + SECTION("Pi") { + check(3.14159265359, "3.141592654"); + } + + SECTION("Signaling NaN") { + double nan = std::numeric_limits::signaling_NaN(); + check(nan, "NaN"); + } + + SECTION("Quiet NaN") { + double nan = std::numeric_limits::quiet_NaN(); + check(nan, "NaN"); + } + + SECTION("Infinity") { + double inf = std::numeric_limits::infinity(); + check(inf, "Infinity"); + check(-inf, "-Infinity"); + } + + SECTION("Zero") { + check(0.0, "0"); + check(-0.0, "0"); + } + + SECTION("Espilon") { + check(2.2250738585072014E-308, "2.225073859e-308"); + check(-2.2250738585072014E-308, "-2.225073859e-308"); + } + + SECTION("Max double") { + check(1.7976931348623157E+308, "1.797693135e308"); + check(-1.7976931348623157E+308, "-1.797693135e308"); + } + + SECTION("Big exponent") { + // this test increases coverage of normalize() + check(1e255, "1e255"); + check(1e-255, "1e-255"); + } + + SECTION("Exponentation when <= 1e-5") { + check(1e-4, "0.0001"); + check(1e-5, "1e-5"); + + check(-1e-4, "-0.0001"); + check(-1e-5, "-1e-5"); + } + + SECTION("Exponentation when >= 1e7") { + check(9999999.999, "9999999.999"); + check(10000000.0, "1e7"); + + check(-9999999.999, "-9999999.999"); + check(-10000000.0, "-1e7"); + } + + SECTION("Rounding when too many decimals") { + check(0.000099999999999, "0.0001"); + check(0.0000099999999999, "1e-5"); + check(0.9999999996, "1"); + } + + SECTION("9 decimal places") { + check(0.100000001, "0.100000001"); + check(0.999999999, "0.999999999"); + + check(9.000000001, "9.000000001"); + check(9.999999999, "9.999999999"); + } + + SECTION("10 decimal places") { + check(0.1000000001, "0.1"); + check(0.9999999999, "1"); + + check(9.0000000001, "9"); + check(9.9999999999, "10"); + } +} + +TEST_CASE("TextFormatter::writeFloat(float)") { + SECTION("Pi") { + check(3.14159265359f, "3.141593"); + } + + SECTION("999.9") { // issue #543 + check(999.9f, "999.9"); + } + + SECTION("24.3") { // # issue #588 + check(24.3f, "24.3"); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeInteger.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeInteger.cpp new file mode 100644 index 0000000..cc29fc0 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeInteger.cpp @@ -0,0 +1,55 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include +#include + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +template +void checkWriteInteger(T value, std::string expected) { + char output[64] = {0}; + StaticStringWriter sb(output, sizeof(output)); + TextFormatter writer(sb); + writer.writeInteger(value); + REQUIRE(expected == output); + REQUIRE(writer.bytesWritten() == expected.size()); +} + +TEST_CASE("int8_t") { + checkWriteInteger(0, "0"); + checkWriteInteger(-128, "-128"); + checkWriteInteger(127, "127"); +} + +TEST_CASE("uint8_t") { + checkWriteInteger(0, "0"); + checkWriteInteger(255, "255"); +} + +TEST_CASE("int16_t") { + checkWriteInteger(0, "0"); + checkWriteInteger(-32768, "-32768"); + checkWriteInteger(32767, "32767"); +} + +TEST_CASE("uint16_t") { + checkWriteInteger(0, "0"); + checkWriteInteger(65535, "65535"); +} + +TEST_CASE("int32_t") { + checkWriteInteger(0, "0"); + checkWriteInteger(-2147483647 - 1, "-2147483648"); + checkWriteInteger(2147483647, "2147483647"); +} + +TEST_CASE("uint32_t") { + checkWriteInteger(0, "0"); + checkWriteInteger(4294967295U, "4294967295"); +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeString.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeString.cpp new file mode 100644 index 0000000..67b12b5 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/TextFormatter/writeString.cpp @@ -0,0 +1,57 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include + +#include +#include + +using namespace ARDUINOJSON_NAMESPACE; + +void check(const char* input, std::string expected) { + char output[64] = {0}; + StaticStringWriter sb(output, sizeof(output)); + TextFormatter writer(sb); + writer.writeString(input); + REQUIRE(expected == output); + REQUIRE(writer.bytesWritten() == expected.size()); +} + +TEST_CASE("TextFormatter::writeString()") { + SECTION("EmptyString") { + check("", "\"\""); + } + + SECTION("QuotationMark") { + check("\"", "\"\\\"\""); + } + + SECTION("ReverseSolidus") { + check("\\", "\"\\\\\""); + } + + SECTION("Solidus") { + check("/", "\"/\""); // but the JSON format allows \/ + } + + SECTION("Backspace") { + check("\b", "\"\\b\""); + } + + SECTION("Formfeed") { + check("\f", "\"\\f\""); + } + + SECTION("Newline") { + check("\n", "\"\\n\""); + } + + SECTION("CarriageReturn") { + check("\r", "\"\\r\""); + } + + SECTION("HorizontalTab") { + check("\t", "\"\\t\""); + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/catch/CMakeLists.txt b/firmware/uniclock/lib/ArduinoJson/extras/tests/catch/CMakeLists.txt new file mode 100644 index 0000000..776a07a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/catch/CMakeLists.txt @@ -0,0 +1,21 @@ +# ArduinoJson - https://arduinojson.org +# Copyright Benoit Blanchon 2014-2021 +# MIT License + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED OFF) + +add_library(catch + catch.hpp + catch.cpp +) + +target_include_directories(catch + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + # prevent "xxx will change in GCC x.x" with arm-linux-gnueabihf-gcc + target_compile_options(catch PRIVATE -Wno-psabi) +endif() diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/catch/catch.cpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/catch/catch.cpp new file mode 100644 index 0000000..e6c8548 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/catch/catch.cpp @@ -0,0 +1,6 @@ +// ArduinoJson - https://arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/firmware/uniclock/lib/ArduinoJson/extras/tests/catch/catch.hpp b/firmware/uniclock/lib/ArduinoJson/extras/tests/catch/catch.hpp new file mode 100644 index 0000000..fdb046f --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/extras/tests/catch/catch.hpp @@ -0,0 +1,11689 @@ +/* + * Catch v1.12.2 + * Generated: 2018-05-14 15:10:01.112442 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// #included from: internal/catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wparentheses" + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? +// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported +// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? +// CATCH_CONFIG_CPP11_OVERRIDE : is override supported? +// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) +// CATCH_CONFIG_CPP11_SHUFFLE : is std::shuffle supported? +// CATCH_CONFIG_CPP11_TYPE_TRAITS : are type_traits and enable_if supported? + +// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 + +#ifdef __cplusplus + +# if __cplusplus >= 201103L +# define CATCH_CPP11_OR_GREATER +# endif + +# if __cplusplus >= 201402L +# define CATCH_CPP14_OR_GREATER +# endif + +#endif + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# if defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) + +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# endif + +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE + +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +# if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH + +#if (_MSC_VER >= 1600) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE +#define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS + +#endif + +// Use __COUNTER__ if the compiler supports it +#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ + ( defined __GNUC__ && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3 )) ) || \ + ( defined __clang__ && __clang_major__ >= 3 ) + +// Use of __COUNTER__ is suppressed during code analysis in CLion/AppCode 2017.2.x and former, +// because __COUNTER__ is not properly handled by it. +// This does not affect compilation +#if ( !defined __JETBRAINS_IDE__ || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(CATCH_CPP11_OR_GREATER) + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# endif + +# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) +# define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) +# define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) +# define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) +# define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS +# endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NULLPTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_IS_ENUM +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TUPLE +#endif +#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) +# define CATCH_CONFIG_VARIADIC_MACROS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_LONG_LONG +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_UNIQUE_PTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_SHUFFLE +#endif +# if defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_NO_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TYPE_TRAITS +# endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_NULL nullptr +#else +# define CATCH_NULL NULL +#endif + +// override support +#ifdef CATCH_CONFIG_CPP11_OVERRIDE +# define CATCH_OVERRIDE override +#else +# define CATCH_OVERRIDE +#endif + +// unique_ptr support +#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR +# define CATCH_AUTO_PTR( T ) std::unique_ptr +#else +# define CATCH_AUTO_PTR( T ) std::auto_ptr +#endif + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include + +namespace Catch { + + struct IConfig; + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { +#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SourceLineInfo(SourceLineInfo const& other) = default; + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( CATCH_NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = CATCH_NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == CATCH_NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +void registerTestCase + ( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + +struct AutoReg { + + AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg + ( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + registerTestCase + ( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ \ + struct TestName : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ \ + struct TestCaseName : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + void TestCaseName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct DecomposedExpression + { + virtual ~DecomposedExpression() {} + virtual bool isBinaryExpression() const { + return false; + } + virtual void reconstructExpression( std::string& dest ) const = 0; + + // Only simple binary comparisons can be decomposed. + // If more complex check is required then wrap sub-expressions in parentheses. + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator % ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& ); + + private: + DecomposedExpression& operator = (DecomposedExpression const&); + }; + + struct AssertionInfo + { + AssertionInfo(); + AssertionInfo( char const * _macroName, + SourceLineInfo const& _lineInfo, + char const * _capturedExpression, + ResultDisposition::Flags _resultDisposition, + char const * _secondArg = ""); + + char const * macroName; + SourceLineInfo lineInfo; + char const * capturedExpression; + ResultDisposition::Flags resultDisposition; + char const * secondArg; + }; + + struct AssertionResultData + { + AssertionResultData() : decomposedExpression( CATCH_NULL ) + , resultType( ResultWas::Unknown ) + , negated( false ) + , parenthesized( false ) {} + + void negate( bool parenthesize ) { + negated = !negated; + parenthesized = parenthesize; + if( resultType == ResultWas::Ok ) + resultType = ResultWas::ExpressionFailed; + else if( resultType == ResultWas::ExpressionFailed ) + resultType = ResultWas::Ok; + } + + std::string const& reconstructExpression() const { + if( decomposedExpression != CATCH_NULL ) { + decomposedExpression->reconstructExpression( reconstructedExpression ); + if( parenthesized ) { + reconstructedExpression.insert( 0, 1, '(' ); + reconstructedExpression.append( 1, ')' ); + } + if( negated ) { + reconstructedExpression.insert( 0, 1, '!' ); + } + decomposedExpression = CATCH_NULL; + } + return reconstructedExpression; + } + + mutable DecomposedExpression const* decomposedExpression; + mutable std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + bool negated; + bool parenthesized; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + void discardDecomposedExpression() const; + void expandDecomposedExpression() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// #included from: catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template struct MatchAllOf; + template struct MatchAnyOf; + template struct MatchNotOf; + + class MatcherUntypedBase { + public: + std::string toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + private: + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ); + }; + + template + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + template + struct MatcherMethod { + virtual bool match( PtrT* arg ) const = 0; + }; + + template + struct MatcherBase : MatcherUntypedBase, MatcherMethod { + + MatchAllOf operator && ( MatcherBase const& other ) const; + MatchAnyOf operator || ( MatcherBase const& other ) const; + MatchNotOf operator ! () const; + }; + + template + struct MatchAllOf : MatcherBase { + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (!m_matchers[i]->match(arg)) + return false; + } + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + description += " and "; + description += m_matchers[i]->toString(); + } + description += " )"; + return description; + } + + MatchAllOf& operator && ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + template + struct MatchAnyOf : MatcherBase { + + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (m_matchers[i]->match(arg)) + return true; + } + return false; + } + virtual std::string describe() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + description += " or "; + description += m_matchers[i]->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf& operator || ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + + template + struct MatchNotOf : MatcherBase { + + MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + return !m_underlyingMatcher.match( arg ); + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase const& m_underlyingMatcher; + }; + + template + MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { + return MatchAllOf() && *this && other; + } + template + MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { + return MatchAnyOf() || *this || other; + } + template + MatchNotOf MatcherBase::operator ! () const { + return MatchNotOf( *this ); + } + + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + // - deprecated: prefer ||, && and ! + template + Impl::MatchNotOf Not( Impl::MatcherBase const& underlyingMatcher ) { + return Impl::MatchNotOf( underlyingMatcher ); + } + template + Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { + return Impl::MatchAllOf() && m1 && m2; + } + template + Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { + return Impl::MatchAllOf() && m1 && m2 && m3; + } + template + Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { + return Impl::MatchAnyOf() || m1 || m2; + } + template + Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { + return Impl::MatchAnyOf() || m1 || m2 || m3; + } + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template class ExpressionLhs; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(std::string()); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder : public DecomposedExpression { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition, + char const* secondArg = "" ); + ~ResultBuilder(); + + template + ExpressionLhs operator <= ( T const& operand ); + ExpressionLhs operator <= ( bool value ); + + template + ResultBuilder& operator << ( T const& value ) { + stream().oss << value; + return *this; + } + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + + void endExpression( DecomposedExpression const& expr ); + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE; + + AssertionResult build() const; + AssertionResult build( DecomposedExpression const& expr ) const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void captureExpectedException( std::string const& expectedMessage ); + void captureExpectedException( Matchers::Impl::MatcherBase const& matcher ); + void handleResult( AssertionResult const& result ); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + template + void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString ); + + void setExceptionGuard(); + void unsetExceptionGuard(); + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + + CopyableStream &stream() + { + if(!m_usedStream) + { + m_usedStream = true; + m_stream().oss.str(""); + } + return m_stream(); + } + + static CopyableStream &m_stream() + { + static CopyableStream s; + return s; + } + + bool m_shouldDebugBreak; + bool m_shouldThrow; + bool m_guardException; + bool m_usedStream; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + struct Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return bool( opCast( lhs ) == opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) != opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) < opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) > opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) >= opCast( rhs ) ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) <= opCast( rhs ) ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + // long long to unsigned X + template bool compare( long long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // unsigned long long to X + template bool compare( unsigned long long lhs, int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long long (when comparing against NULL) + template bool compare( long long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } +#endif // CATCH_CONFIG_CPP11_LONG_LONG + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template bool compare( std::nullptr_t, T* rhs ) { + return Evaluator::evaluate( nullptr, rhs ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, nullptr ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CONFIG_CPP11_TUPLE +#include +#endif + +#ifdef CATCH_CONFIG_CPP11_IS_ENUM +#include +#endif + +namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ); +std::string toString( unsigned long long value ); +#endif + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG & nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern const std::string unprintableString; + + #if !defined(CATCH_CONFIG_CPP11_STREAM_INSERTABLE_CHECK) + struct BorgType { + template BorgType( T const& ); + }; + + struct TrueType { char sizer[1]; }; + struct FalseType { char sizer[2]; }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; +#else + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype( std::declval() << std::declval(), std::true_type() ); + + template + static auto test(...) -> std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; +#endif + +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif + template + struct StringMakerBase { +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return "NULL"; + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return "NULL"; + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CONFIG_CPP11_TUPLE + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif // CATCH_CONFIG_CPP11_TUPLE + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( T const& value ) { + return StringMaker::convert( value ); +} + + namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +template +class BinaryExpression; + +template +class MatchExpression; + +// Wraps the LHS of an expression and overloads comparison operators +// for also capturing those and RHS (if any) +template +class ExpressionLhs : public DecomposedExpression { +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ), m_truthy(false) {} + + ExpressionLhs& operator = ( const ExpressionLhs& ); + + template + BinaryExpression + operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + BinaryExpression + operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + BinaryExpression operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + BinaryExpression operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + m_truthy = m_lhs ? true : false; + m_rb + .setResultType( m_truthy ) + .endExpression( *this ); + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + dest = Catch::toString( m_lhs ); + } + +private: + template + BinaryExpression captureExpression( RhsT& rhs ) const { + return BinaryExpression( m_rb, m_lhs, rhs ); + } + + template + BinaryExpression captureExpression( bool rhs ) const { + return BinaryExpression( m_rb, m_lhs, rhs ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; + bool m_truthy; +}; + +template +class BinaryExpression : public DecomposedExpression { +public: + BinaryExpression( ResultBuilder& rb, LhsT lhs, RhsT rhs ) + : m_rb( rb ), m_lhs( lhs ), m_rhs( rhs ) {} + + BinaryExpression& operator = ( BinaryExpression& ); + + void endExpression() const { + m_rb + .setResultType( Internal::compare( m_lhs, m_rhs ) ) + .endExpression( *this ); + } + + virtual bool isBinaryExpression() const CATCH_OVERRIDE { + return true; + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + std::string lhs = Catch::toString( m_lhs ); + std::string rhs = Catch::toString( m_rhs ); + char delim = lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ? ' ' : '\n'; + dest.reserve( 7 + lhs.size() + rhs.size() ); + // 2 for spaces around operator + // 2 for operator + // 2 for parentheses (conditionally added later) + // 1 for negation (conditionally added later) + dest = lhs; + dest += delim; + dest += Internal::OperatorTraits::getName(); + dest += delim; + dest += rhs; + } + +private: + ResultBuilder& m_rb; + LhsT m_lhs; + RhsT m_rhs; +}; + +template +class MatchExpression : public DecomposedExpression { +public: + MatchExpression( ArgT arg, MatcherT matcher, char const* matcherString ) + : m_arg( arg ), m_matcher( matcher ), m_matcherString( matcherString ) {} + + virtual bool isBinaryExpression() const CATCH_OVERRIDE { + return true; + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + std::string matcherAsString = m_matcher.toString(); + dest = Catch::toString( m_arg ); + dest += ' '; + if( matcherAsString == Detail::unprintableString ) + dest += m_matcherString; + else + dest += matcherAsString; + } + +private: + ArgT m_arg; + MatcherT m_matcher; + char const* m_matcherString; +}; + +} // end namespace Catch + + +namespace Catch { + + template + ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { + return ExpressionLhs( *this, value ); + } + + template + void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher, + char const* matcherString ) { + MatchExpression expr( arg, matcher, matcherString ); + setResultType( matcher.match( arg ) ); + endExpression( expr ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void exceptionEarlyReported() = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + virtual void assertionRun() = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +# define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +# define CATCH_PLATFORM_IPHONE +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +# define CATCH_PLATFORM_WINDOWS +# if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINES_NOMINMAX +# endif +# if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# endif +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_TRAP() \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ) /* NOLINT */ + #else + #define CATCH_TRAP() __asm__("int $3\n" : : /* NOLINT */ ) + #endif + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) +# define CATCH_INTERNAL_STRINGIFY(expr) #expr +#else +# define CATCH_INTERNAL_STRINGIFY(expr) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif + +#if defined(CATCH_CONFIG_FAST_COMPILE) +/////////////////////////////////////////////////////////////////////////////// +// We can speedup compilation significantly by breaking into debugger lower in +// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER +// macro in each assertion +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +// This can potentially cause false negative, if the test code catches +// the exception before it propagates back up to the runner. +#define INTERNAL_CATCH_TEST_NO_TRY( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition ); \ + __catchResult.setExceptionGuard(); \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + ( __catchResult <= expr ).endExpression(); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + __catchResult.unsetExceptionGuard(); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && static_cast( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look +// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +#define INTERNAL_CHECK_THAT_NO_TRY( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + __catchResult.setExceptionGuard(); \ + __catchResult.captureMatch( arg, matcher, CATCH_INTERNAL_STRINGIFY(matcher) ); \ + __catchResult.unsetExceptionGuard(); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +#else +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition ); \ + try { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + ( __catchResult <= expr ).endExpression(); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && static_cast( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, expr ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, expr ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition ); \ + try { \ + static_cast(expr); \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, matcher, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition, CATCH_INTERNAL_STRINGIFY(matcher) ); \ + if( __catchResult.allowThrows() ) \ + try { \ + static_cast(expr); \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureExpectedException( matcher ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + static_cast(expr); \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + try { \ + __catchResult.captureMatch( arg, matcher, CATCH_INTERNAL_STRINGIFY(matcher) ); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +#include + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) + : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef _MSC_VER + +namespace Catch { + typedef unsigned long long UInt64; +} +#else +#include +namespace Catch { + typedef uint64_t UInt64; +} +#endif + +namespace Catch { + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + UInt64 m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + m_composed.insert( m_composed.end(), other.m_composed.begin(), other.m_composed.end() ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +#include + +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + struct ITagAliasRegistry; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; + virtual void registerListener( Ptr const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator; + typedef std::vector ExceptionTranslators; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { + try { + if( it == itEnd ) + throw; + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) +#include +#endif + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 1.0 ), + m_value( value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + +#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) + + template ::value>::type> + Approx operator()( T value ) { + Approx approx( static_cast(value) ); + approx.epsilon( m_epsilon ); + approx.margin( m_margin ); + approx.scale( m_scale ); + return approx; + } + + template ::value>::type> + explicit Approx( T value ): Approx(static_cast(value)) + {} + + template ::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + auto lhs_v = double(lhs); + bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + (std::max)(std::fabs(lhs_v), std::fabs(rhs.m_value))); + if (relativeOK) { + return true; + } + + return std::fabs(lhs_v - rhs.m_value) <= rhs.m_margin; + } + + template ::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator != ( T lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>::type> + friend bool operator != ( Approx const& lhs, T rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator <= ( T lhs, Approx const& rhs ) { + return double(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator <= ( Approx const& lhs, T rhs ) { + return lhs.m_value < double(rhs) || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( T lhs, Approx const& rhs ) { + return double(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( Approx const& lhs, T rhs ) { + return lhs.m_value > double(rhs) || lhs == rhs; + } + + template ::value>::type> + Approx& epsilon( T newEpsilon ) { + m_epsilon = double(newEpsilon); + return *this; + } + + template ::value>::type> + Approx& margin( T newMargin ) { + m_margin = double(newMargin); + return *this; + } + + template ::value>::type> + Approx& scale( T newScale ) { + m_scale = double(newScale); + return *this; + } + +#else + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.margin( m_margin ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) ); + if (relativeOK) { + return true; + } + return std::fabs(lhs - rhs.m_value) <= rhs.m_margin; + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + friend bool operator <= ( double lhs, Approx const& rhs ) { + return lhs < rhs.m_value || lhs == rhs; + } + + friend bool operator <= ( Approx const& lhs, double rhs ) { + return lhs.m_value < rhs || lhs == rhs; + } + + friend bool operator >= ( double lhs, Approx const& rhs ) { + return lhs > rhs.m_value || lhs == rhs; + } + + friend bool operator >= ( Approx const& lhs, double rhs ) { + return lhs.m_value > rhs || lhs == rhs; + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& margin( double newMargin ) { + m_margin = newMargin; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } +#endif + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers_string.h +#define TWOBLUECUBES_CATCH_MATCHERS_STRING_H_INCLUDED + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct StringMatcherBase : MatcherBase { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + virtual std::string describe() const CATCH_OVERRIDE; + + CasedString m_comparator; + std::string m_operation; + }; + + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + + } // namespace StdString + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// #included from: internal/catch_matchers_vector.h +#define TWOBLUECUBES_CATCH_MATCHERS_VECTOR_H_INCLUDED + +namespace Catch { +namespace Matchers { + + namespace Vector { + + template + struct ContainsElementMatcher : MatcherBase, T> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector const &v) const CATCH_OVERRIDE { + return std::find(v.begin(), v.end(), m_comparator) != v.end(); + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "Contains: " + Catch::toString( m_comparator ); + } + + T const& m_comparator; + }; + + template + struct ContainsMatcher : MatcherBase, std::vector > { + + ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const CATCH_OVERRIDE { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (size_t i = 0; i < m_comparator.size(); ++i) + if (std::find(v.begin(), v.end(), m_comparator[i]) == v.end()) + return false; + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + return "Contains: " + Catch::toString( m_comparator ); + } + + std::vector const& m_comparator; + }; + + template + struct EqualsMatcher : MatcherBase, std::vector > { + + EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const CATCH_OVERRIDE { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + return "Equals: " + Catch::toString( m_comparator ); + } + std::vector const& m_comparator; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template + Vector::ContainsMatcher Contains( std::vector const& comparator ) { + return Vector::ContainsMatcher( comparator ); + } + + template + Vector::ContainsElementMatcher VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher( comparator ); + } + + template + Vector::EqualsMatcher Equals( std::vector const& comparator ) { + return Vector::EqualsMatcher( comparator ); + } + +} // namespace Matchers +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string const& _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( CATCH_NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = CATCH_NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != CATCH_NULL; } + bool none() const { return nullableValue == CATCH_NULL; } + + bool operator !() const { return nullableValue == CATCH_NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T *nullableValue; + union { + char storage[sizeof(T)]; + + // These are here to force alignment for the storage + long double dummy1; + void (*dummy2)(); + long double dummy3; +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + long long dummy4; +#endif + }; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( CATCH_NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + struct StringHolder : MatcherBase{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + virtual bool match( NSString* arg ) const CATCH_OVERRIDE { + return false; + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const CATCH_OVERRIDE { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL + +// !TBD: Move the leak detector code into a separate header +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include +class LeakDetector { +public: + LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +}; +#else +class LeakDetector {}; +#endif + +LeakDetector leakDetector; + +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_session.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_wildcard_pattern.hpp +#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_wildcard( NoWildcard ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~WildcardPattern(); + virtual bool matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard; + std::string m_pattern; + }; +} + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { + if( !(*it)->matches( testCase ) ) + return false; + } + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + std::vector m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) :m_mode(None), m_exclusion(false), m_start(0), m_pos(0), m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + for( size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include +#include +#include +#include + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + + struct IStream { + virtual ~IStream() CATCH_NOEXCEPT; + virtual std::ostream& stream() const = 0; + }; + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( std::string const& filename ); + virtual ~FileStream() CATCH_NOEXCEPT; + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + CoutStream(); + virtual ~CoutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class DebugOutStream : public IStream { + CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream(); + virtual ~DebugOutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; +} + +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + listExtraInfo( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + filenamesAsTags( false ), + libIdentify( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ), + useColour( UseColour::Auto ), + waitForKeypress( WaitForKeypress::Never ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + bool listExtraInfo; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool filenamesAsTags; + bool libIdentify; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + UseColour::YesOrNo useColour; + WaitForKeypress::When waitForKeypress; + + std::string outputFilename; + std::string name; + std::string processName; + + std::vector reporterNames; + std::vector testsOrTags; + std::vector sectionsToRun; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() {} + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + bool listExtraInfo() const { return m_data.listExtraInfo; } + + std::string getProcessName() const { return m_data.processName; } + + std::vector const& getReporterNames() const { return m_data.reporterNames; } + std::vector const& getSectionsToRun() const CATCH_OVERRIDE { return m_data.sectionsToRun; } + + virtual TestSpec const& testSpec() const CATCH_OVERRIDE { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + + // IConfig interface + virtual bool allowThrows() const CATCH_OVERRIDE { return !m_data.noThrow; } + virtual std::ostream& stream() const CATCH_OVERRIDE { return m_stream->stream(); } + virtual std::string name() const CATCH_OVERRIDE { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const CATCH_OVERRIDE { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const CATCH_OVERRIDE { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const CATCH_OVERRIDE { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const CATCH_OVERRIDE { return m_data.runOrder; } + virtual unsigned int rngSeed() const CATCH_OVERRIDE { return m_data.rngSeed; } + virtual UseColour::YesOrNo useColour() const CATCH_OVERRIDE { return m_data.useColour; } + virtual bool shouldDebugBreak() const CATCH_OVERRIDE { return m_data.shouldDebugBreak; } + virtual int abortAfter() const CATCH_OVERRIDE { return m_data.abortAfter; } + virtual bool showInvisibles() const CATCH_OVERRIDE { return m_data.showInvisibles; } + + private: + + IStream const* openStream() { + if( m_data.outputFilename.empty() ) + return new CoutStream(); + else if( m_data.outputFilename[0] == '%' ) { + if( m_data.outputFilename == "%debug" ) + return new DebugOutStream(); + else + throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); + } + else + return new FileStream( m_data.outputFilename ); + } + ConfigData m_data; + + CATCH_AUTO_PTR( IStream const ) m_stream; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Version 0.0.2.4 + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +// ----------- #included from clara_compilers.h ----------- + +#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED +#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CLARA_CONFIG_CPP11_OVERRIDE : is override supported? +// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) + +// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_ form +// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 + +#ifdef __clang__ + +#if __has_feature(cxx_nullptr) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#if __has_feature(cxx_noexcept) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(__cplusplus) && __cplusplus >= 201103L + +#define CLARA_CPP11_OR_GREATER + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) +#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE +#endif +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NULLPTR +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_UNIQUE_PTR +#endif + +// noexcept support: +#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) +#define CLARA_NOEXCEPT noexcept +# define CLARA_NOEXCEPT_IS(x) noexcept(x) +#else +#define CLARA_NOEXCEPT throw() +# define CLARA_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CLARA_CONFIG_CPP11_NULLPTR +#define CLARA_NULL nullptr +#else +#define CLARA_NULL NULL +#endif + +// override support +#ifdef CLARA_CONFIG_CPP11_OVERRIDE +#define CLARA_OVERRIDE override +#else +#define CLARA_OVERRIDE +#endif + +// unique_ptr support +#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR +# define CLARA_AUTO_PTR( T ) std::unique_ptr +#else +# define CLARA_AUTO_PTR( T ) std::auto_ptr +#endif + +#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// ----------- end of #include from clara_compilers.h ----------- +// ........... back in clara.h + +#include +#include +#include + +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CLARA_PLATFORM_WINDOWS +#endif + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} +#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +#endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( CLARA_NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != CLARA_NULL; + } + private: + IArgFunction* functionObj; + }; + + template + struct NullBinder : IArgFunction{ + virtual void set( C&, std::string const& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction* clone() const { return new NullBinder( *this ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + inline std::vector argsToVector( int argc, char const* const* const argv ) { + std::vector args( static_cast( argc ) ); + for( std::size_t i = 0; i < static_cast( argc ); ++i ) + args[i] = argv[i]; + + return args; + } + + class Parser { + enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; + Mode mode; + std::size_t from; + bool inQuotes; + public: + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + Parser() : mode( None ), from( 0 ), inQuotes( false ){} + + void parseIntoTokens( std::vector const& args, std::vector& tokens ) { + const std::string doubleDash = "--"; + for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) + parseIntoTokens( args[i], tokens); + } + + void parseIntoTokens( std::string const& arg, std::vector& tokens ) { + for( std::size_t i = 0; i < arg.size(); ++i ) { + char c = arg[i]; + if( c == '"' ) + inQuotes = !inQuotes; + mode = handleMode( i, c, arg, tokens ); + } + mode = handleMode( arg.size(), '\0', arg, tokens ); + } + Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + switch( mode ) { + case None: return handleNone( i, c ); + case MaybeShortOpt: return handleMaybeShortOpt( i, c ); + case ShortOpt: + case LongOpt: + case SlashOpt: return handleOpt( i, c, arg, tokens ); + case Positional: return handlePositional( i, c, arg, tokens ); + default: throw std::logic_error( "Unknown mode" ); + } + } + + Mode handleNone( std::size_t i, char c ) { + if( inQuotes ) { + from = i; + return Positional; + } + switch( c ) { + case '-': return MaybeShortOpt; +#ifdef CLARA_PLATFORM_WINDOWS + case '/': from = i+1; return SlashOpt; +#endif + default: from = i; return Positional; + } + } + Mode handleMaybeShortOpt( std::size_t i, char c ) { + switch( c ) { + case '-': from = i+1; return LongOpt; + default: from = i; return ShortOpt; + } + } + + Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) + return mode; + + std::string optName = arg.substr( from, i-from ); + if( mode == ShortOpt ) + for( std::size_t j = 0; j < optName.size(); ++j ) + tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); + else if( mode == SlashOpt && optName.size() == 1 ) + tokens.push_back( Token( Token::ShortOpt, optName ) ); + else + tokens.push_back( Token( Token::LongOpt, optName ) ); + return None; + } + Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) + return mode; + + std::string data = arg.substr( from, i-from ); + tokens.push_back( Token( Token::Positional, data ) ); + return None; + } + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( std::vector const& args ) const { + ConfigT config; + parseInto( args, config ); + return config; + } + + std::vector parseInto( std::vector const& args, ConfigT& config ) const { + std::string processName = args.empty() ? std::string() : args[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector tokens; + Parser parser; + parser.parseIntoTokens( args, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.set( config, "true" ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); } + inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + '\'' ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + '\'' ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argument to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void setUseColour( ConfigData& config, std::string const& value ) { + std::string mode = toLower( value ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); + } + inline void setWaitForKeypress( ConfigData& config, std::string const& keypress ) { + std::string keypressLc = toLower( keypress ); + if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + throw std::runtime_error( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); + }; + + inline void forceColour( ConfigData& config ) { + config.useColour = UseColour::Yes; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + addTestOrTags( config, line + ',' ); + } + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &addReporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes|no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + cli["-#"]["--filenames-as-tags"] + .describe( "adds a tag for the filename" ) + .bind( &ConfigData::filenamesAsTags ); + + cli["-c"]["--section"] + .describe( "specify section to run" ) + .bind( &addSectionToRun, "section name" ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-extra-info"] + .describe( "list all/matching test cases with more info" ) + .bind( &ConfigData::listExtraInfo ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output (deprecated)" ) + .bind( &forceColour ); + + cli["--use-colour"] + .describe( "should output be colourised" ) + .bind( &setUseColour, "yes|no" ); + + cli["--libidentify"] + .describe( "report name and version according to libidentify standard" ) + .bind( &ConfigData::libIdentify ); + + cli["--wait-for-keypress"] + .describe( "waits for a keypress before exiting" ) + .bind( &setWaitForKeypress, "start|exit|both" ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + const std::string wrappableBeforeChars = "[({<\t"; + const std::string wrappableAfterChars = "])}>-,./|\\"; + const std::string wrappableInsteadOfChars = " \n\r"; + std::string indent = _attr.initialIndent != std::string::npos + ? std::string( _attr.initialIndent, ' ' ) + : std::string( _attr.indent, ' ' ); + + typedef std::string::const_iterator iterator; + iterator it = _str.begin(); + const iterator strEnd = _str.end(); + + while( it != strEnd ) { + + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + + std::string suffix; + std::size_t width = (std::min)( static_cast( strEnd-it ), _attr.width-static_cast( indent.size() ) ); + iterator itEnd = it+width; + iterator itNext = _str.end(); + + iterator itNewLine = std::find( it, itEnd, '\n' ); + if( itNewLine != itEnd ) + itEnd = itNewLine; + + if( itEnd != strEnd ) { + bool foundWrapPoint = false; + iterator findIt = itEnd; + do { + if( wrappableAfterChars.find( *findIt ) != std::string::npos && findIt != itEnd ) { + itEnd = findIt+1; + itNext = findIt+1; + foundWrapPoint = true; + } + else if( findIt > it && wrappableBeforeChars.find( *findIt ) != std::string::npos ) { + itEnd = findIt; + itNext = findIt; + foundWrapPoint = true; + } + else if( wrappableInsteadOfChars.find( *findIt ) != std::string::npos ) { + itNext = findIt+1; + itEnd = findIt; + foundWrapPoint = true; + } + if( findIt == it ) + break; + else + --findIt; + } + while( !foundWrapPoint ); + + if( !foundWrapPoint ) { + // No good wrap char, so we'll break mid word and add a hyphen + --itEnd; + itNext = itEnd; + suffix = "-"; + } + else { + while( itEnd > it && wrappableInsteadOfChars.find( *(itEnd-1) ) != std::string::npos ) + --itEnd; + } + } + lines.push_back( indent + std::string( it, itEnd ) + suffix ); + + if( indent.size() != _attr.indent ) + indent = std::string( _attr.indent, ' ' ); + it = itNext; + } + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + class MultipleReporters; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } + }; + + struct IReporterFactory : IShared { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map > FactoryMap; + typedef std::vector > Listeners; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + + Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, descAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + descAttr.setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( config.listExtraInfo() ) { + Catch::cout() << " " << testCaseInfo.lineInfo << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Text( description, descAttr ) << std::endl; + } + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << '\n' << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.listExtraInfo() ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ':' + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << '\n'; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() || ( config.listExtraInfo() && !config.listTestNamesOnly() ) ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_run_context.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include +#include +#include +#include + +CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + }; + + struct ITracker : SharedImpl<> { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( Ptr const& child ) = 0; + virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + Ptr m_rootTracker; + ITracker* m_currentTracker; + RunState m_runState; + + public: + + static TrackerContext& instance() { + static TrackerContext s_instance; + return s_instance; + } + + TrackerContext() + : m_currentTracker( CATCH_NULL ), + m_runState( NotStarted ) + {} + + ITracker& startRun(); + + void endRun() { + m_rootTracker.reset(); + m_currentTracker = CATCH_NULL; + m_runState = NotStarted; + } + + void startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void completeCycle() { + m_runState = CompletedCycle; + } + + bool completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& currentTracker() { + return *m_currentTracker; + } + void setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + class TrackerHasName { + NameAndLocation m_nameAndLocation; + public: + TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} + bool operator ()( Ptr const& tracker ) { + return + tracker->nameAndLocation().name == m_nameAndLocation.name && + tracker->nameAndLocation().location == m_nameAndLocation.location; + } + }; + typedef std::vector > Children; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState; + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ), + m_runState( NotStarted ) + {} + virtual ~TrackerBase(); + + virtual NameAndLocation const& nameAndLocation() const CATCH_OVERRIDE { + return m_nameAndLocation; + } + virtual bool isComplete() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully; + } + virtual bool isOpen() const CATCH_OVERRIDE { + return m_runState != NotStarted && !isComplete(); + } + virtual bool hasChildren() const CATCH_OVERRIDE { + return !m_children.empty(); + } + + virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { + m_children.push_back( child ); + } + + virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) CATCH_OVERRIDE { + Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); + return( it != m_children.end() ) + ? it->get() + : CATCH_NULL; + } + virtual ITracker& parent() CATCH_OVERRIDE { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + virtual void openChild() CATCH_OVERRIDE { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } + virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } + + void open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + virtual void close() CATCH_OVERRIDE { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NotStarted: + case CompletedSuccessfully: + case Failed: + throw std::logic_error( "Illogical state" ); + + case NeedsAnotherRun: + break;; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + default: + throw std::logic_error( "Unexpected state" ); + } + moveToParent(); + m_ctx.completeCycle(); + } + virtual void fail() CATCH_OVERRIDE { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { + m_runState = NeedsAnotherRun; + } + private: + void moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void moveToThis() { + m_ctx.setCurrentTracker( this ); + } + }; + + class SectionTracker : public TrackerBase { + std::vector m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + virtual ~SectionTracker(); + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + SectionTracker* section = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = static_cast( childTracker ); + } + else { + section = new SectionTracker( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void addInitialFilters( std::vector const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void addNextFilters( std::vector const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ), + m_index( -1 ) + {} + virtual ~IndexTracker(); + + virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } + + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + IndexTracker* tracker = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = static_cast( childTracker ); + } + else { + tracker = new IndexTracker( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int index() const { return m_index; } + + void moveNext() { + m_index++; + m_children.clear(); + } + + virtual void close() CATCH_OVERRIDE { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + }; + + inline ITracker& TrackerContext::startRun() { + m_rootTracker = new SectionTracker( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, CATCH_NULL ); + m_currentTracker = CATCH_NULL; + m_runState = Executing; + return *m_rootTracker; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition + inline void reportFatal( std::string const& message ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// +// #included from: catch_windows_h_proxy.h + +#define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED + +#ifdef CATCH_DEFINES_NOMINMAX +# define NOMINMAX +#endif +#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINES_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + + +# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct FatalConditionHandler { + void reset() {} + }; +} + +# else // CATCH_CONFIG_WINDOWS_SEH is defined + +namespace Catch { + + struct SignalDefs { DWORD id; const char* name; }; + extern SignalDefs signalDefs[]; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = CATCH_NULL; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + static void reset() { + if (isSet) { + // Unregister handler and restore the old guarantee + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = CATCH_NULL; + isSet = false; + } + } + + ~FatalConditionHandler() { + reset(); + } + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL; + +} // namespace Catch + +# endif // CATCH_CONFIG_WINDOWS_SEH + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace Catch { + struct FatalConditionHandler { + void reset() {} + }; +} + +# else // CATCH_CONFIG_POSIX_SIGNALS is defined + +#include + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)]; + static stack_t oldSigStack; + static char altStackMem[SIGSTKSZ]; + + static void handleSignal( int sig ) { + std::string name = ""; + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + SignalDefs &def = signalDefs[i]; + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = SIGSTKSZ; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { 0 }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { + reset(); + } + static void reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL); + } + // Return the old stack + sigaltstack(&oldSigStack, CATCH_NULL); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; + +} // namespace Catch + +# endif // CATCH_CONFIG_POSIX_SIGNALS + +#endif // not Windows + +#include +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes and cannot use StreamRedirect on its own + class StdErrRedirect { + public: + StdErrRedirect(std::string& targetString) + :m_cerrBuf( cerr().rdbuf() ), m_clogBuf(clog().rdbuf()), + m_targetString(targetString){ + cerr().rdbuf(m_oss.rdbuf()); + clog().rdbuf(m_oss.rdbuf()); + } + ~StdErrRedirect() { + m_targetString += m_oss.str(); + cerr().rdbuf(m_cerrBuf); + clog().rdbuf(m_clogBuf); + } + private: + std::streambuf* m_cerrBuf; + std::streambuf* m_clogBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& _config, Ptr const& reporter ) + : m_runInfo( _config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( CATCH_NULL ), + m_config( _config ), + m_reporter( reporter ), + m_shouldReportUnexpected ( true ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + + do { + ITracker& rootTracker = m_trackerContext.startRun(); + assert( rootTracker.isSectionTracker() ); + static_cast( rootTracker ).addInitialFilters( m_config->getSectionsToRun() ); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( testInfo.name, testInfo.lineInfo ) ); + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); + } + // !TBD: deprecated - this will be replaced by indexed trackers + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = CATCH_NULL; + m_testCaseTracker = CATCH_NULL; + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool lastAssertionPassed() + { + return m_totals.assertions.passed == (m_prevPassed + 1); + } + + virtual void assertionPassed() + { + m_totals.assertions.passed++; + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"; + m_lastAssertionInfo.macroName = ""; + } + + virtual void assertionRun() + { + m_prevPassed = m_totals.assertions.passed; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( sectionInfo.name, sectionInfo.lineInfo ) ); + if( !sectionTracker.isOpen() ) + return false; + m_activeSections.push_back( §ionTracker ); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 ) + return false; + if( !m_config->warnAboutMissingAssertions() ) + return false; + if( m_trackerContext.currentTracker().hasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionEndInfo const& endInfo ) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( !m_activeSections.empty() ) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { + if( m_unfinishedSections.empty() ) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back( endInfo ); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult; + tempResult.resultType = ResultWas::FatalErrorCondition; + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + getResultCapture().assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + std::string(), + std::string(), + false ) ); + m_totals.testCases.failed++; + testGroupEnded( std::string(), m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + + seedRng( *m_config ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StdErrRedirect errRedir( redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if (m_shouldReportUnexpected) { + makeUnexpectedResultBuilder().useActiveException(); + } + } + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName, + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression, + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( *it ); + m_unfinishedSections.clear(); + } + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + ITracker* m_testCaseTracker; + ITracker* m_currentSectionTracker; + AssertionResult m_lastResult; + + Ptr m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + size_t m_prevPassed; + bool m_shouldReportUnexpected; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + + private: + void operator=( Version const& ); + }; + + inline Version libraryVersion(); +} + +#include +#include +#include + +namespace Catch { + + Ptr createReporter( std::string const& reporterName, Ptr const& config ) { + Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); + if( !reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + return reporter; + } + +#if !defined(CATCH_CONFIG_DEFAULT_REPORTER) +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + + Ptr makeReporter( Ptr const& config ) { + std::vector reporters = config->getReporterNames(); + if( reporters.empty() ) + reporters.push_back( CATCH_CONFIG_DEFAULT_REPORTER ); + + Ptr reporter; + for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); + it != itEnd; + ++it ) + reporter = addReporter( reporter, createReporter( *it, config ) ); + return reporter; + } + Ptr addListeners( Ptr const& config, Ptr reporters ) { + IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); + for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); + it != itEnd; + ++it ) + reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); + return reporters; + } + + Totals runTests( Ptr const& config ) { + + Ptr iconfig = config.get(); + + Ptr reporter = makeReporter( config ); + reporter = addListeners( iconfig, reporter ); + + RunContext context( iconfig, reporter ); + + Totals totals; + + context.testGroupStarting( config->name(), 1, 1 ); + + TestSpec testSpec = config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector const& allTestCases = getAllTestCasesSorted( *iconfig ); + for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); + it != itEnd; + ++it ) { + if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) + totals += context.runTest( *it ); + else + reporter->skipTest( *it ); + } + + context.testGroupEnded( iconfig->name(), totals, 1, 1 ); + return totals; + } + + void applyFilenamesAsTags( IConfig const& config ) { + std::vector const& tests = getAllTestCasesSorted( config ); + for(std::size_t i = 0; i < tests.size(); ++i ) { + TestCase& test = const_cast( tests[i] ); + std::set tags = test.tags; + + std::string filename = test.lineInfo.file; + std::string::size_type lastSlash = filename.find_last_of( "\\/" ); + if( lastSlash != std::string::npos ) + filename = filename.substr( lastSlash+1 ); + + std::string::size_type lastDot = filename.find_last_of( '.' ); + if( lastDot != std::string::npos ) + filename = filename.substr( 0, lastDot ); + + tags.insert( '#' + filename ); + setTags( test, tags ); + } + } + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion() << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + void libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; + } + + int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + if( m_configData.libIdentify ) + libIdentify(); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char const* const* const argv ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + #if defined(WIN32) && defined(UNICODE) + int run( int argc, wchar_t const* const* const argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + if( returnCode == 0 ) + returnCode = run(); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } + #endif + + int run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast(std::getchar()); + } + return exitCode; + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + private: + + int runInternal() { + if( m_configData.showHelp || m_configData.libIdentify ) + return 0; + + try + { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runTests( m_config ).assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + struct RandomNumberGenerator { + typedef unsigned int result_type; + + result_type operator()( result_type n ) const { return std::rand() % n; } + +#ifdef CATCH_CONFIG_CPP11_SHUFFLE + static constexpr result_type (min)() { return 0; } + static constexpr result_type (max)() { return 1000000; } + result_type operator()() const { return std::rand() % (max)(); } +#endif + template + static void shuffle( V& vector ) { + RandomNumberGenerator rng; +#ifdef CATCH_CONFIG_CPP11_SHUFFLE + std::shuffle( vector.begin(), vector.end(), rng ); +#else + std::random_shuffle( vector.begin(), vector.end(), rng ); +#endif + } + }; + + inline std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + { + seedRng( config ); + RandomNumberGenerator::shuffle( sorted ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); + it != itEnd; + ++it ) { + std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); + if( !prev.second ) { + std::ostringstream ss; + + ss << Colour( Colour::Red ) + << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << '\n' + << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + + throw std::runtime_error(ss.str()); + } + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) + if( matchTest( *it, testSpec, config ) ) + filtered.push_back( *it ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + class TestRegistry : public ITestCaseRegistry { + public: + TestRegistry() + : m_currentSortOrder( RunTests::InDeclarationOrder ), + m_unnamedCount( 0 ) + {} + virtual ~TestRegistry(); + + virtual void registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + std::ostringstream oss; + oss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( oss.str() ) ); + } + m_functions.push_back( testCase ); + } + + virtual std::vector const& getAllTests() const { + return m_functions; + } + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + private: + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder; + mutable std::vector m_sortedFunctions; + size_t m_unnamedCount; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + void registerTestCase + ( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase + ( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCaseFunction( function, lineInfo, nameAndDesc ); + } + + AutoReg::~AutoReg() {} + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() CATCH_OVERRIDE {} + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return CATCH_NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, Ptr const& factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + void registerListener( Ptr const& factory ) { + m_listeners.push_back( factory ); + } + + virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { + return m_factories; + } + virtual Listeners const& getListeners() const CATCH_OVERRIDE { + return m_listeners; + } + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + + std::string tryTranslators() const { + if( m_translators.empty() ) + throw; + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + } + + private: + std::vector m_translators; + }; +} + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { + return m_exceptionTranslatorRegistry; + } + virtual ITagAliasRegistry const& getTagAliasRegistry() const CATCH_OVERRIDE { + return m_tagAliasRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerListener( factory ); + } + virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) CATCH_OVERRIDE { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = CATCH_NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = CATCH_NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + FileStream::FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << filename << '\''; + throw std::domain_error( oss.str() ); + } + } + + std::ostream& FileStream::stream() const { + return m_ofs; + } + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + DebugOutStream::DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) + {} + + std::ostream& DebugOutStream::stream() const { + return m_os; + } + + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream::CoutStream() + : m_os( Catch::cout().rdbuf() ) + {} + + std::ostream& CoutStream::stream() const { + return m_os; + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } + std::ostream& clog() { + return std::clog; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: + virtual ~Context() { + deleteAllValues( m_generatorsByTestName ); + } + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : CATCH_NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = CATCH_NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + void cleanUpContext() { + delete currentContext; + currentContext = CATCH_NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +// #included from: catch_errno_guard.hpp +#define TWOBLUECUBES_CATCH_ERRNO_GUARD_HPP_INCLUDED + +#include + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard():m_oldErrno(errno){} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; + }; + +} + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = !isDebuggerActive() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo():macroName(""), capturedExpression(""), resultDisposition(ResultDisposition::Normal), secondArg(""){} + + AssertionInfo::AssertionInfo( char const * _macroName, + SourceLineInfo const& _lineInfo, + char const * _capturedExpression, + ResultDisposition::Flags _resultDisposition, + char const * _secondArg) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ), + secondArg( _secondArg ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return m_info.capturedExpression[0] != 0; + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string capturedExpressionWithSecondArgument( char const * capturedExpression, char const * secondArg ) { + return (secondArg[0] == 0 || secondArg[0] == '"' && secondArg[1] == '"') + ? capturedExpression + : std::string(capturedExpression) + ", " + secondArg; + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!(" + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg) + ")"; + else + return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName[0] == 0 ) + return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); + else + return std::string(m_info.macroName) + "( " + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg) + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructExpression(); + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + + void AssertionResult::discardDecomposedExpression() const { + m_resultData.decomposedExpression = CATCH_NULL; + } + + void AssertionResult::expandDecomposedExpression() const { + m_resultData.reconstructExpression(); + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +#include + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + std::ostringstream ss; + ss << Colour(Colour::Red) + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << Colour(Colour::FileName) + << _lineInfo << '\n'; + throw std::runtime_error(ss.str()); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) + { + testCaseInfo.tags = tags; + testCaseInfo.lcaseTags.clear(); + + std::ostringstream oss; + for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { + oss << '[' << *it << ']'; + std::string lcaseTag = toLower( *it ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.insert( lcaseTag ); + } + testCaseInfo.tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + inline Version libraryVersion() { + static Version version( 1, 12, 2, "", 0 ); + return version; + } + +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17 +#endif + ScopedMessage::~ScopedMessage() { + if ( !std::uncaught_exception() ){ + getResultCapture().popScopedMessage(m_info); + } + } +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS + +#else + +#include + +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + UInt64 getCurrentTicks() { + static UInt64 hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); + } + UInt64 t; + QueryPerformanceCounter( reinterpret_cast( &t ) ); + return ((t-hzo)*1000000)/hz; + } +#else + UInt64 getCurrentTicks() { + timeval t; + gettimeofday(&t,CATCH_NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +#include +#include + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + + SourceLineInfo::SourceLineInfo() : file(""), line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + bool SourceLineInfo::empty() const { + return file[0] == '\0'; + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) + std::srand( config.rngSeed() ); + } + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << '\''; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17 +#endif + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( std::uncaught_exception() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include + #include + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return '"' + s + '"'; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + 'f'; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + if ( value == '\r' ) + return "'\\r'"; + if ( value == '\f' ) + return "'\\f'"; + if ( value == '\n' ) + return "'\\n'"; + if ( value == '\t' ) + return "'\\t'"; + if ( '\0' <= value && value < ' ' ) + return toString( static_cast( value ) ); + char chstr[] = "' '"; + chstr[1] = value; + return chstr; +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} +std::string toString( unsigned long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} +#endif + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG & nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +#include + +namespace Catch { + + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition, + char const* secondArg ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition, secondArg ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ), + m_guardException( false ), + m_usedStream( false ) + {} + + ResultBuilder::~ResultBuilder() { +#if defined(CATCH_CONFIG_FAST_COMPILE) + if ( m_guardException ) { + stream().oss << "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + captureResult( ResultWas::ThrewException ); + getCurrentContext().getResultCapture()->exceptionEarlyReported(); + } +#endif + } + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + + void ResultBuilder::endExpression( DecomposedExpression const& expr ) { + // Flip bool results if FalseTest flag is set + if( isFalseTest( m_assertionInfo.resultDisposition ) ) { + m_data.negate( expr.isBinaryExpression() ); + } + + getResultCapture().assertionRun(); + + if(getCurrentContext().getConfig()->includeSuccessfulResults() || m_data.resultType != ResultWas::Ok) + { + AssertionResult result = build( expr ); + handleResult( result ); + } + else + getResultCapture().assertionPassed(); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + stream().oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { + if( expectedMessage.empty() ) + captureExpectedException( Matchers::Impl::MatchAllOf() ); + else + captureExpectedException( Matchers::Equals( expectedMessage ) ); + } + + void ResultBuilder::captureExpectedException( Matchers::Impl::MatcherBase const& matcher ) { + + assert( !isFalseTest( m_assertionInfo.resultDisposition ) ); + AssertionResultData data = m_data; + data.resultType = ResultWas::Ok; + data.reconstructedExpression = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); + + std::string actualMessage = Catch::translateActiveException(); + if( !matcher.match( actualMessage ) ) { + data.resultType = ResultWas::ExpressionFailed; + data.reconstructedExpression = actualMessage; + } + AssertionResult result( m_assertionInfo, data ); + handleResult( result ); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + handleResult( result ); + } + + void ResultBuilder::handleResult( AssertionResult const& result ) + { + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) + m_shouldThrow = true; + } + } + + void ResultBuilder::react() { +#if defined(CATCH_CONFIG_FAST_COMPILE) + if (m_shouldDebugBreak) { + /////////////////////////////////////////////////////////////////// + // To inspect the state during test, you need to go one level up the callstack + // To go back to the test and change execution, jump over the throw statement + /////////////////////////////////////////////////////////////////// + CATCH_BREAK_INTO_DEBUGGER(); + } +#endif + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + return build( *this ); + } + + // CAVEAT: The returned AssertionResult stores a pointer to the argument expr, + // a temporary DecomposedExpression, which in turn holds references to + // operands, possibly temporary as well. + // It should immediately be passed to handleResult; if the expression + // needs to be reported, its string expansion must be composed before + // the temporaries are destroyed. + AssertionResult ResultBuilder::build( DecomposedExpression const& expr ) const + { + assert( m_data.resultType != ResultWas::Unknown ); + AssertionResultData data = m_data; + + if(m_usedStream) + data.message = m_stream().oss.str(); + data.decomposedExpression = &expr; // for lazy reconstruction + return AssertionResult( m_assertionInfo, data ); + } + + void ResultBuilder::reconstructExpression( std::string& dest ) const { + dest = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); + } + + void ResultBuilder::setExceptionGuard() { + m_guardException = true; + } + void ResultBuilder::unsetExceptionGuard() { + m_guardException = false; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) { + std::ostringstream oss; + oss << Colour( Colour::Red ) + << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" + << Colour( Colour::FileName ) + << lineInfo << '\n'; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << Colour( Colour::Red ) + << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " + << Colour( Colour::Red ) << find(alias)->lineInfo << '\n' + << Colour( Colour::Red ) << "\tRedefined at " + << Colour( Colour::FileName) << lineInfo << '\n'; + throw std::domain_error( oss.str().c_str() ); + } + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + getMutableRegistryHub().registerTagAlias( alias, tag, lineInfo ); + } + +} // end namespace Catch + +// #included from: catch_matchers_string.hpp + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + +} // namespace Matchers +} // namespace Catch +// #included from: ../reporters/catch_reporter_multi.hpp +#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED + +namespace Catch { + +class MultipleReporters : public SharedImpl { + typedef std::vector > Reporters; + Reporters m_reporters; + +public: + void add( Ptr const& reporter ) { + m_reporters.push_back( reporter ); + } + +public: // IStreamingReporter + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporters[0]->getPreferences(); + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->noMatchingTestCases( spec ); + } + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunStarting( testRunInfo ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseStarting( testInfo ); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionStarting( sectionInfo ); + } + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + bool clearBuffer = false; + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + clearBuffer |= (*it)->assertionEnded( assertionStats ); + return clearBuffer; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionEnded( sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupEnded( testGroupStats ); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunEnded( testRunStats ); + } + + virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->skipTest( testInfo ); + } + + virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { + return this; + } + +}; + +Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { + Ptr resultingReporter; + + if( existingReporter ) { + MultipleReporters* multi = existingReporter->tryAsMulti(); + if( !multi ) { + multi = new MultipleReporters; + resultingReporter = Ptr( multi ); + if( existingReporter ) + multi->add( existingReporter ); + } + else + resultingReporter = existingReporter; + multi->add( additionalReporter ); + } + else + resultingReporter = additionalReporter; + + return resultingReporter; +} + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + namespace { + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + } + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + } + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual ~StreamingReporterBase() CATCH_OVERRIDE; + + virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr const& node ) const { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + } + ~CumulativeReporterBase(); + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} + virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} + + virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression( sectionNode.assertions.back().assertionResult ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void prepareExpandedExpression( AssertionResult& result ) const { + if( result.isOk() ) + result.discardDecomposedExpression(); + else + result.expandDecomposedExpression(); + } + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + ReporterPreferences m_reporterPrefs; + + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { + return false; + } + }; + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public SharedImpl { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ListenerRegistrar { + + class ListenerFactory : public SharedImpl { + + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + virtual std::string getDescription() const { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( new ListenerFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +// Deprecated - use the form without INTERNAL_ +#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void encodeTo( std::ostream& os ) const { + + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t i = 0; i < m_str.size(); ++ i ) { + char c = m_str[i]; + switch( c ) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) + os << ">"; + else + os << c; + break; + + case '\"': + if( m_forWhat == ForAttributes ) + os << """; + else + os << c; + break; + + default: + // Escape control chars - based on contribution by @espenalb in PR #465 and + // by @mrpi PR #588 + if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) { + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast( c ); + } + else + os << c; + } + } + } + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = CATCH_NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( Catch::cout() ) + { + writeDeclaration(); + } + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( os ) + { + writeDeclaration(); + } + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::ostringstream oss; + oss << attribute; + return writeAttribute( name, oss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << ""; + m_needsNewline = true; + return *this; + } + + void writeStylesheetRef( std::string const& url ) { + m_os << "\n"; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + void writeDeclaration() { + m_os << "\n"; + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()), + m_sectionDepth( 0 ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + virtual ~XmlReporter() CATCH_OVERRIDE; + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + virtual std::string getStylesheetRef() const { + return std::string(); + } + + void writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + public: // StreamingReporterBase + + virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in tags. + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + } + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ), + unexpectedExceptions( 0 ), + m_okToFail( false ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + virtual ~JunitReporter() CATCH_OVERRIDE; + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE { + m_okToFail = testCaseInfo.okToFail(); + } + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() CATCH_OVERRIDE { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << '\n'; + for( std::vector::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << '\n'; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + bool m_okToFail; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter() CATCH_OVERRIDE; + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return false; + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, includeResults ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_config->showDurations() == ShowDurations::Always ) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if( m_headerPrinted ) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << '\n'; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << '\n'; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << '\n'; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ':' << '\n'; + for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << '\n'; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << '\n'; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = ' ' + *it; + while( it->size() > row.size() ) + row = ' ' + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ')' + << '\n'; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << ' ' << it->label; + } + } + stream << '\n'; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << '\n'; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void sectionEnded(SectionStats const& _sectionStats) CATCH_OVERRIDE { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ':'; + } + + void printResultType( Colour::Code colour, std::string const& passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue( std::string const& issue ) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ';'; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ':'; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << '\''; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? std::string() : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : std::string(); + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << '.'; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << '.'; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + // These are all here to avoid warnings about not having any out of line + // virtual methods + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + IStream::~IStream() CATCH_NOEXCEPT {} + FileStream::~FileStream() CATCH_NOEXCEPT {} + CoutStream::~CoutStream() CATCH_NOEXCEPT {} + DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + WildcardPattern::~WildcardPattern() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + Matchers::Impl::MatcherUntypedBase::~MatcherUntypedBase() {} + + void Config::dummy() {} + + namespace TestCaseTracking { + ITracker::~ITracker() {} + TrackerBase::~TrackerBase() {} + SectionTracker::~SectionTracker() {} + IndexTracker::~IndexTracker() {} + } +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + int result = Catch::Session().run( argc, argv ); + return ( result < 0xff ? result : 0xff ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return ( result < 0xff ? result : 0xff ); +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#else +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#endif + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) + +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#else +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) + #define CATCH_FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) + +#else +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#endif + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#else +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#else +#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) + #define FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) +#define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) +#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) +#define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) +#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) + +using Catch::Detail::Approx; + +// #included from: internal/catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/firmware/uniclock/lib/ArduinoJson/keywords.txt b/firmware/uniclock/lib/ArduinoJson/keywords.txt new file mode 100644 index 0000000..3dff06b --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/keywords.txt @@ -0,0 +1,40 @@ +# Macros +JSON_ARRAY_SIZE KEYWORD2 +JSON_OBJECT_SIZE KEYWORD2 +JSON_STRING_SIZE KEYWORD2 + +# Free functions +deserializeJson KEYWORD2 +deserializeMsgPack KEYWORD2 +serialized KEYWORD2 +serializeJson KEYWORD2 +serializeJsonPretty KEYWORD2 +serializeMsgPack KEYWORD2 +measureJson KEYWORD2 +measureJsonPretty KEYWORD2 +measureMsgPack KEYWORD2 + +# Methods +add KEYWORD2 +as KEYWORD2 +createNestedArray KEYWORD2 +createNestedObject KEYWORD2 +get KEYWORD2 +set KEYWORD2 +to KEYWORD2 + +# Type names +DeserializationError KEYWORD1 DATA_TYPE +DynamicJsonDocument KEYWORD1 DATA_TYPE +JsonArray KEYWORD1 DATA_TYPE +JsonArrayConst KEYWORD1 DATA_TYPE +JsonDocument KEYWORD1 DATA_TYPE +JsonFloat KEYWORD1 DATA_TYPE +JsonInteger KEYWORD1 DATA_TYPE +JsonObject KEYWORD1 DATA_TYPE +JsonObjectConst KEYWORD1 DATA_TYPE +JsonString KEYWORD1 DATA_TYPE +JsonUInt KEYWORD1 DATA_TYPE +JsonVariant KEYWORD1 DATA_TYPE +JsonVariantConst KEYWORD1 DATA_TYPE +StaticJsonDocument KEYWORD1 DATA_TYPE diff --git a/firmware/uniclock/lib/ArduinoJson/library.json b/firmware/uniclock/lib/ArduinoJson/library.json new file mode 100644 index 0000000..24a9794 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/library.json @@ -0,0 +1,24 @@ +{ + "name": "ArduinoJson", + "keywords": "json, rest, http, web", + "description": "A simple and efficient JSON library for embedded C++. ArduinoJson supports ✔ serialization, ✔ deserialization, ✔ MessagePack, ✔ fixed allocation, ✔ zero-copy, ✔ streams, ✔ filtering, and more. It is the most popular Arduino library on GitHub ❤❤❤❤❤. Check out arduinojson.org for a comprehensive documentation.", + "homepage": "https://arduinojson.org/?utm_source=meta&utm_medium=library.json", + "repository": { + "type": "git", + "url": "https://github.com/bblanchon/ArduinoJson.git" + }, + "version": "6.19.4", + "authors": { + "name": "Benoit Blanchon", + "url": "https://blog.benoitblanchon.fr" + }, + "exclude": [ + ".github", + "extras" + ], + "frameworks": "*", + "platforms": "*", + "build": { + "libArchive": false + } +} diff --git a/firmware/uniclock/lib/ArduinoJson/library.properties b/firmware/uniclock/lib/ArduinoJson/library.properties new file mode 100644 index 0000000..aa20cfc --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/library.properties @@ -0,0 +1,11 @@ +name=ArduinoJson +version=6.19.4 +author=Benoit Blanchon +maintainer=Benoit Blanchon +sentence=A simple and efficient JSON library for embedded C++. +paragraph=ArduinoJson supports ✔ serialization, ✔ deserialization, ✔ MessagePack, ✔ fixed allocation, ✔ zero-copy, ✔ streams, ✔ filtering, and more. It is the most popular Arduino library on GitHub ❤❤❤❤❤. Check out arduinojson.org for a comprehensive documentation. +category=Data Processing +url=https://arduinojson.org/?utm_source=meta&utm_medium=library.properties +architectures=* +repository=https://github.com/bblanchon/ArduinoJson.git +license=MIT diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson.h b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson.h new file mode 100644 index 0000000..f79212d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson.h @@ -0,0 +1,17 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#ifdef __cplusplus + +# include "ArduinoJson.hpp" + +using namespace ArduinoJson; + +#else + +#error ArduinoJson requires a C++ compiler, please change file extension to .cc or .cpp + +#endif diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson.hpp new file mode 100644 index 0000000..abb8dd1 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson.hpp @@ -0,0 +1,80 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include "ArduinoJson/Configuration.hpp" + +// Include Arduino.h before stdlib.h to avoid conflict with atexit() +// https://github.com/bblanchon/ArduinoJson/pull/1693#issuecomment-1001060240 +#if ARDUINOJSON_ENABLE_ARDUINO_STRING || ARDUINOJSON_ENABLE_ARDUINO_STREAM || \ + ARDUINOJSON_ENABLE_ARDUINO_PRINT || ARDUINOJSON_ENABLE_PROGMEM +# include +#endif + +#if !ARDUINOJSON_DEBUG +# ifdef __clang__ +# pragma clang system_header +# elif defined __GNUC__ +# pragma GCC system_header +# endif +#endif + +#include "ArduinoJson/Array/ArrayRef.hpp" +#include "ArduinoJson/Object/ObjectRef.hpp" +#include "ArduinoJson/Variant/VariantRef.hpp" + +#include "ArduinoJson/Document/DynamicJsonDocument.hpp" +#include "ArduinoJson/Document/StaticJsonDocument.hpp" + +#include "ArduinoJson/Array/ArrayImpl.hpp" +#include "ArduinoJson/Array/ElementProxy.hpp" +#include "ArduinoJson/Array/Utilities.hpp" +#include "ArduinoJson/Collection/CollectionImpl.hpp" +#include "ArduinoJson/Object/MemberProxy.hpp" +#include "ArduinoJson/Object/ObjectImpl.hpp" +#include "ArduinoJson/Variant/ConverterImpl.hpp" +#include "ArduinoJson/Variant/VariantCompare.hpp" +#include "ArduinoJson/Variant/VariantImpl.hpp" + +#include "ArduinoJson/Json/JsonDeserializer.hpp" +#include "ArduinoJson/Json/JsonSerializer.hpp" +#include "ArduinoJson/Json/PrettyJsonSerializer.hpp" +#include "ArduinoJson/MsgPack/MsgPackDeserializer.hpp" +#include "ArduinoJson/MsgPack/MsgPackSerializer.hpp" + +#include "ArduinoJson/compatibility.hpp" + +namespace ArduinoJson { +typedef ARDUINOJSON_NAMESPACE::ArrayConstRef JsonArrayConst; +typedef ARDUINOJSON_NAMESPACE::ArrayRef JsonArray; +typedef ARDUINOJSON_NAMESPACE::Float JsonFloat; +typedef ARDUINOJSON_NAMESPACE::Integer JsonInteger; +typedef ARDUINOJSON_NAMESPACE::ObjectConstRef JsonObjectConst; +typedef ARDUINOJSON_NAMESPACE::ObjectRef JsonObject; +typedef ARDUINOJSON_NAMESPACE::Pair JsonPair; +typedef ARDUINOJSON_NAMESPACE::PairConst JsonPairConst; +typedef ARDUINOJSON_NAMESPACE::String JsonString; +typedef ARDUINOJSON_NAMESPACE::UInt JsonUInt; +typedef ARDUINOJSON_NAMESPACE::VariantConstRef JsonVariantConst; +typedef ARDUINOJSON_NAMESPACE::VariantRef JsonVariant; +using ARDUINOJSON_NAMESPACE::BasicJsonDocument; +using ARDUINOJSON_NAMESPACE::copyArray; +using ARDUINOJSON_NAMESPACE::DeserializationError; +using ARDUINOJSON_NAMESPACE::deserializeJson; +using ARDUINOJSON_NAMESPACE::deserializeMsgPack; +using ARDUINOJSON_NAMESPACE::DynamicJsonDocument; +using ARDUINOJSON_NAMESPACE::JsonDocument; +using ARDUINOJSON_NAMESPACE::measureJson; +using ARDUINOJSON_NAMESPACE::serialized; +using ARDUINOJSON_NAMESPACE::serializeJson; +using ARDUINOJSON_NAMESPACE::serializeJsonPretty; +using ARDUINOJSON_NAMESPACE::serializeMsgPack; +using ARDUINOJSON_NAMESPACE::StaticJsonDocument; + +namespace DeserializationOption { +using ARDUINOJSON_NAMESPACE::Filter; +using ARDUINOJSON_NAMESPACE::NestingLimit; +} // namespace DeserializationOption +} // namespace ArduinoJson diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayFunctions.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayFunctions.hpp new file mode 100644 index 0000000..0b4d342 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayFunctions.hpp @@ -0,0 +1,31 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +namespace ARDUINOJSON_NAMESPACE { + +inline VariantData *arrayAdd(CollectionData *arr, MemoryPool *pool) { + return arr ? arr->addElement(pool) : 0; +} + +template +inline typename TVisitor::result_type arrayAccept(const CollectionData *arr, + TVisitor &visitor) { + if (arr) + return visitor.visitArray(*arr); + else + return visitor.visitNull(); +} + +inline bool arrayEquals(const CollectionData *lhs, const CollectionData *rhs) { + if (lhs == rhs) + return true; + if (!lhs || !rhs) + return false; + return lhs->equalsArray(*rhs); +} +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp new file mode 100644 index 0000000..dcb4ff0 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp @@ -0,0 +1,28 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +inline ArrayRef ArrayShortcuts::createNestedArray() const { + return impl()->addElement().template to(); +} + +template +inline ObjectRef ArrayShortcuts::createNestedObject() const { + return impl()->addElement().template to(); +} + +template +inline ElementProxy ArrayShortcuts::operator[]( + size_t index) const { + return ElementProxy(*impl(), index); +} + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayIterator.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayIterator.hpp new file mode 100644 index 0000000..cde9a24 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayIterator.hpp @@ -0,0 +1,121 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include + +namespace ARDUINOJSON_NAMESPACE { + +class VariantPtr { + public: + VariantPtr(MemoryPool *pool, VariantData *data) : _variant(pool, data) {} + + VariantRef *operator->() { + return &_variant; + } + + VariantRef &operator*() { + return _variant; + } + + private: + VariantRef _variant; +}; + +class ArrayIterator { + public: + ArrayIterator() : _slot(0) {} + explicit ArrayIterator(MemoryPool *pool, VariantSlot *slot) + : _pool(pool), _slot(slot) {} + + VariantRef operator*() const { + return VariantRef(_pool, _slot->data()); + } + VariantPtr operator->() { + return VariantPtr(_pool, _slot->data()); + } + + bool operator==(const ArrayIterator &other) const { + return _slot == other._slot; + } + + bool operator!=(const ArrayIterator &other) const { + return _slot != other._slot; + } + + ArrayIterator &operator++() { + _slot = _slot->next(); + return *this; + } + + ArrayIterator &operator+=(size_t distance) { + _slot = _slot->next(distance); + return *this; + } + + VariantSlot *internal() { + return _slot; + } + + private: + MemoryPool *_pool; + VariantSlot *_slot; +}; + +class VariantConstPtr { + public: + VariantConstPtr(const VariantData *data) : _variant(data) {} + + VariantConstRef *operator->() { + return &_variant; + } + + VariantConstRef &operator*() { + return _variant; + } + + private: + VariantConstRef _variant; +}; + +class ArrayConstRefIterator { + public: + ArrayConstRefIterator() : _slot(0) {} + explicit ArrayConstRefIterator(const VariantSlot *slot) : _slot(slot) {} + + VariantConstRef operator*() const { + return VariantConstRef(_slot->data()); + } + VariantConstPtr operator->() { + return VariantConstPtr(_slot->data()); + } + + bool operator==(const ArrayConstRefIterator &other) const { + return _slot == other._slot; + } + + bool operator!=(const ArrayConstRefIterator &other) const { + return _slot != other._slot; + } + + ArrayConstRefIterator &operator++() { + _slot = _slot->next(); + return *this; + } + + ArrayConstRefIterator &operator+=(size_t distance) { + _slot = _slot->next(distance); + return *this; + } + + const VariantSlot *internal() { + return _slot; + } + + private: + const VariantSlot *_slot; +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp new file mode 100644 index 0000000..a5df7ab --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp @@ -0,0 +1,213 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include +#include + +// Returns the size (in bytes) of an array with n elements. +// Can be very handy to determine the size of a StaticMemoryPool. +#define JSON_ARRAY_SIZE(NUMBER_OF_ELEMENTS) \ + ((NUMBER_OF_ELEMENTS) * sizeof(ARDUINOJSON_NAMESPACE::VariantSlot)) + +namespace ARDUINOJSON_NAMESPACE { + +class ObjectRef; +template +class ElementProxy; + +template +class ArrayRefBase { + public: + operator VariantConstRef() const { + const void* data = _data; // prevent warning cast-align + return VariantConstRef(reinterpret_cast(data)); + } + + template + FORCE_INLINE typename TVisitor::result_type accept(TVisitor& visitor) const { + return arrayAccept(_data, visitor); + } + + FORCE_INLINE bool isNull() const { + return _data == 0; + } + + FORCE_INLINE operator bool() const { + return _data != 0; + } + + FORCE_INLINE size_t memoryUsage() const { + return _data ? _data->memoryUsage() : 0; + } + + FORCE_INLINE size_t nesting() const { + return _data ? _data->nesting() : 0; + } + + FORCE_INLINE size_t size() const { + return _data ? _data->size() : 0; + } + + protected: + ArrayRefBase(TData* data) : _data(data) {} + TData* _data; +}; + +class ArrayConstRef : public ArrayRefBase, + public Visitable { + friend class ArrayRef; + typedef ArrayRefBase base_type; + + public: + typedef ArrayConstRefIterator iterator; + + FORCE_INLINE iterator begin() const { + if (!_data) + return iterator(); + return iterator(_data->head()); + } + + FORCE_INLINE iterator end() const { + return iterator(); + } + + FORCE_INLINE ArrayConstRef() : base_type(0) {} + FORCE_INLINE ArrayConstRef(const CollectionData* data) : base_type(data) {} + + FORCE_INLINE bool operator==(ArrayConstRef rhs) const { + return arrayEquals(_data, rhs._data); + } + + FORCE_INLINE VariantConstRef operator[](size_t index) const { + return getElement(index); + } + + FORCE_INLINE VariantConstRef getElement(size_t index) const { + return VariantConstRef(_data ? _data->getElement(index) : 0); + } +}; + +class ArrayRef : public ArrayRefBase, + public ArrayShortcuts, + public Visitable { + typedef ArrayRefBase base_type; + + public: + typedef ArrayIterator iterator; + + FORCE_INLINE ArrayRef() : base_type(0), _pool(0) {} + FORCE_INLINE ArrayRef(MemoryPool* pool, CollectionData* data) + : base_type(data), _pool(pool) {} + + operator VariantRef() { + void* data = _data; // prevent warning cast-align + return VariantRef(_pool, reinterpret_cast(data)); + } + + operator ArrayConstRef() const { + return ArrayConstRef(_data); + } + + VariantRef addElement() const { + return VariantRef(_pool, arrayAdd(_data, _pool)); + } + + FORCE_INLINE iterator begin() const { + if (!_data) + return iterator(); + return iterator(_pool, _data->head()); + } + + FORCE_INLINE iterator end() const { + return iterator(); + } + + // Copy a ArrayRef + FORCE_INLINE bool set(ArrayConstRef src) const { + if (!_data || !src._data) + return false; + return _data->copyFrom(*src._data, _pool); + } + + FORCE_INLINE bool operator==(ArrayRef rhs) const { + return arrayEquals(_data, rhs._data); + } + + // Internal use + FORCE_INLINE VariantRef getOrAddElement(size_t index) const { + return VariantRef(_pool, _data ? _data->getOrAddElement(index, _pool) : 0); + } + + // Gets the value at the specified index. + FORCE_INLINE VariantRef getElement(size_t index) const { + return VariantRef(_pool, _data ? _data->getElement(index) : 0); + } + + // Removes element at specified position. + FORCE_INLINE void remove(iterator it) const { + if (!_data) + return; + _data->removeSlot(it.internal()); + } + + // Removes element at specified index. + FORCE_INLINE void remove(size_t index) const { + if (!_data) + return; + _data->removeElement(index); + } + + void clear() const { + if (!_data) + return; + _data->clear(); + } + + private: + MemoryPool* _pool; +}; + +template <> +struct Converter { + static void toJson(VariantConstRef src, VariantRef dst) { + variantCopyFrom(getData(dst), getData(src), getPool(dst)); + } + + static ArrayConstRef fromJson(VariantConstRef src) { + return ArrayConstRef(variantAsArray(getData(src))); + } + + static bool checkJson(VariantConstRef src) { + const VariantData* data = getData(src); + return data && data->isArray(); + } +}; + +template <> +struct Converter { + static void toJson(VariantConstRef src, VariantRef dst) { + variantCopyFrom(getData(dst), getData(src), getPool(dst)); + } + + static ArrayRef fromJson(VariantRef src) { + VariantData* data = getData(src); + MemoryPool* pool = getPool(src); + return ArrayRef(pool, data != 0 ? data->asArray() : 0); + } + + static InvalidConversion fromJson(VariantConstRef); + + static bool checkJson(VariantConstRef) { + return false; + } + + static bool checkJson(VariantRef src) { + VariantData* data = getData(src); + return data && data->isArray(); + } +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayShortcuts.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayShortcuts.hpp new file mode 100644 index 0000000..1854a8c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ArrayShortcuts.hpp @@ -0,0 +1,49 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include + +namespace ARDUINOJSON_NAMESPACE { +// Forward declarations. +class ArrayRef; +class ObjectRef; +template +class ElementProxy; + +template +class ArrayShortcuts { + public: + // Returns the element at specified index if the variant is an array. + FORCE_INLINE ElementProxy operator[](size_t index) const; + + FORCE_INLINE ObjectRef createNestedObject() const; + + FORCE_INLINE ArrayRef createNestedArray() const; + + // Adds the specified value at the end of the array. + // + // bool add(TValue); + // TValue = bool, long, int, short, float, double, serialized, VariantRef, + // std::string, String, ObjectRef + template + FORCE_INLINE bool add(const T &value) const { + return impl()->addElement().set(value); + } + // + // bool add(TValue); + // TValue = char*, const char*, const __FlashStringHelper* + template + FORCE_INLINE bool add(T *value) const { + return impl()->addElement().set(value); + } + + private: + const TArray *impl() const { + return static_cast(this); + } +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp new file mode 100644 index 0000000..821cfba --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp @@ -0,0 +1,197 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include +#include +#include + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4522) +#endif + +namespace ARDUINOJSON_NAMESPACE { + +template +class ElementProxy : public VariantOperators >, + public VariantShortcuts >, + public Visitable, + public VariantTag { + typedef ElementProxy this_type; + + public: + typedef VariantRef variant_type; + + FORCE_INLINE ElementProxy(TArray array, size_t index) + : _array(array), _index(index) {} + + FORCE_INLINE ElementProxy(const ElementProxy& src) + : _array(src._array), _index(src._index) {} + + FORCE_INLINE this_type& operator=(const this_type& src) { + getOrAddUpstreamElement().set(src.as()); + return *this; + } + + // Replaces the value + // + // operator=(const TValue&) + // TValue = bool, long, int, short, float, double, serialized, VariantRef, + // std::string, String, ArrayRef, ObjectRef + template + FORCE_INLINE this_type& operator=(const T& src) { + getOrAddUpstreamElement().set(src); + return *this; + } + // + // operator=(TValue) + // TValue = char*, const char*, const __FlashStringHelper* + template + FORCE_INLINE this_type& operator=(T* src) { + getOrAddUpstreamElement().set(src); + return *this; + } + + FORCE_INLINE void clear() const { + getUpstreamElement().clear(); + } + + FORCE_INLINE bool isNull() const { + return getUpstreamElement().isNull(); + } + + template + FORCE_INLINE typename enable_if::value, T>::type as() + const { + return getUpstreamElement().template as(); + } + + template + FORCE_INLINE typename enable_if::value, const char*>::type + ARDUINOJSON_DEPRECATED("Replace as() with as()") + as() const { + return as(); + } + + template + FORCE_INLINE operator T() const { + return getUpstreamElement(); + } + + template + FORCE_INLINE bool is() const { + return getUpstreamElement().template is(); + } + + template + FORCE_INLINE typename VariantTo::type to() const { + return getOrAddUpstreamElement().template to(); + } + + // Replaces the value + // + // bool set(const TValue&) + // TValue = bool, long, int, short, float, double, serialized, VariantRef, + // std::string, String, ArrayRef, ObjectRef + template + FORCE_INLINE bool set(const TValue& value) const { + return getOrAddUpstreamElement().set(value); + } + // + // bool set(TValue) + // TValue = char*, const char*, const __FlashStringHelper* + template + FORCE_INLINE bool set(TValue* value) const { + return getOrAddUpstreamElement().set(value); + } + + template + typename TVisitor::result_type accept(TVisitor& visitor) const { + return getUpstreamElement().accept(visitor); + } + + FORCE_INLINE size_t size() const { + return getUpstreamElement().size(); + } + + FORCE_INLINE size_t memoryUsage() const { + return getUpstreamElement().memoryUsage(); + } + + template + VariantRef getMember(TNestedKey* key) const { + return getUpstreamElement().getMember(key); + } + + template + VariantRef getMember(const TNestedKey& key) const { + return getUpstreamElement().getMember(key); + } + + template + VariantRef getOrAddMember(TNestedKey* key) const { + return getOrAddUpstreamElement().getOrAddMember(key); + } + + template + VariantRef getOrAddMember(const TNestedKey& key) const { + return getOrAddUpstreamElement().getOrAddMember(key); + } + + VariantRef addElement() const { + return getOrAddUpstreamElement().addElement(); + } + + VariantRef getElement(size_t index) const { + return getOrAddUpstreamElement().getElement(index); + } + + VariantRef getOrAddElement(size_t index) const { + return getOrAddUpstreamElement().getOrAddElement(index); + } + + FORCE_INLINE void remove(size_t index) const { + getUpstreamElement().remove(index); + } + // remove(char*) const + // remove(const char*) const + // remove(const __FlashStringHelper*) const + template + FORCE_INLINE typename enable_if::value>::type remove( + TChar* key) const { + getUpstreamElement().remove(key); + } + // remove(const std::string&) const + // remove(const String&) const + template + FORCE_INLINE typename enable_if::value>::type remove( + const TString& key) const { + getUpstreamElement().remove(key); + } + + private: + FORCE_INLINE VariantRef getUpstreamElement() const { + return _array.getElement(_index); + } + + FORCE_INLINE VariantRef getOrAddUpstreamElement() const { + return _array.getOrAddElement(_index); + } + + friend void convertToJson(const this_type& src, VariantRef dst) { + dst.set(src.getUpstreamElement()); + } + + TArray _array; + const size_t _index; +}; + +} // namespace ARDUINOJSON_NAMESPACE + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp new file mode 100644 index 0000000..b8e87f2 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp @@ -0,0 +1,103 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include + +namespace ARDUINOJSON_NAMESPACE { + +// Trivial form to stop the recursion +template +inline typename enable_if::value, bool>::type copyArray( + const T& src, VariantRef dst) { + return dst.set(src); +} + +// Copy array to a JsonArray/JsonVariant/MemberProxy/ElementProxy +template +inline typename enable_if::value, + bool>::type +copyArray(T (&src)[N], const TDestination& dst) { + return copyArray(src, N, dst); +} + +// Copy ptr+size to a JsonArray/JsonVariant/MemberProxy/ElementProxy +template +inline typename enable_if::value, + bool>::type +copyArray(const T* src, size_t len, const TDestination& dst) { + bool ok = true; + for (size_t i = 0; i < len; i++) { + ok &= copyArray(src[i], dst.addElement()); + } + return ok; +} + +// Special case for char[] which much be treated as const char* +template +inline bool copyArray(const char* src, size_t, const TDestination& dst) { + return dst.set(src); +} + +// Copy array to a JsonDocument +template +inline bool copyArray(const T& src, JsonDocument& dst) { + return copyArray(src, dst.to()); +} + +// Copy a ptr+size array to a JsonDocument +template +inline bool copyArray(const T* src, size_t len, JsonDocument& dst) { + return copyArray(src, len, dst.to()); +} + +// Trivial case form to stop the recursion +template +inline typename enable_if::value, size_t>::type copyArray( + VariantConstRef src, T& dst) { + dst = src.as(); + return 1; +} + +// Copy a JsonArray to array +template +inline size_t copyArray(ArrayConstRef src, T (&dst)[N]) { + return copyArray(src, dst, N); +} + +// Copy a JsonArray to ptr+size +template +inline size_t copyArray(ArrayConstRef src, T* dst, size_t len) { + size_t i = 0; + for (ArrayConstRef::iterator it = src.begin(); it != src.end() && i < len; + ++it) + copyArray(*it, dst[i++]); + return i; +} + +// Special case for char[] which must be treated as a string +template +inline size_t copyArray(VariantConstRef src, char (&dst)[N]) { + String s = src; + size_t len = N - 1; + if (len > s.size()) + len = s.size(); + memcpy(dst, s.c_str(), len); + dst[len] = 0; + return 1; +} + +// Copy a JsonDocument to an array +// (JsonDocument doesn't implicitly convert to JsonArrayConst) +template +inline typename enable_if::value && + is_base_of::value, + size_t>::type +copyArray(const TSource& src, T& dst) { + return copyArray(src.template as(), dst); +} + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp new file mode 100644 index 0000000..74b6054 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp @@ -0,0 +1,89 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include + +#include // size_t + +namespace ARDUINOJSON_NAMESPACE { + +class MemoryPool; +class VariantData; +class VariantSlot; + +class CollectionData { + VariantSlot *_head; + VariantSlot *_tail; + + public: + // Must be a POD! + // - no constructor + // - no destructor + // - no virtual + // - no inheritance + + // Array only + + VariantData *addElement(MemoryPool *pool); + + VariantData *getElement(size_t index) const; + + VariantData *getOrAddElement(size_t index, MemoryPool *pool); + + void removeElement(size_t index); + + bool equalsArray(const CollectionData &other) const; + + // Object only + + template + VariantData *addMember(TAdaptedString key, MemoryPool *pool, TStoragePolicy); + + template + VariantData *getMember(TAdaptedString key) const; + + template + VariantData *getOrAddMember(TAdaptedString key, MemoryPool *pool, + TStoragePolicy); + + template + void removeMember(TAdaptedString key) { + removeSlot(getSlot(key)); + } + + template + bool containsKey(const TAdaptedString &key) const; + + bool equalsObject(const CollectionData &other) const; + + // Generic + + void clear(); + size_t memoryUsage() const; + size_t nesting() const; + size_t size() const; + + VariantSlot *addSlot(MemoryPool *); + void removeSlot(VariantSlot *slot); + + bool copyFrom(const CollectionData &src, MemoryPool *pool); + + VariantSlot *head() const { + return _head; + } + + void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance); + + private: + VariantSlot *getSlot(size_t index) const; + + template + VariantSlot *getSlot(TAdaptedString key) const; + + VariantSlot *getPreviousSlot(VariantSlot *) const; +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp new file mode 100644 index 0000000..35678ee --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp @@ -0,0 +1,237 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include +#include +#include + +namespace ARDUINOJSON_NAMESPACE { + +inline bool variantEquals(const VariantData* a, const VariantData* b) { + return variantCompare(a, b) == COMPARE_RESULT_EQUAL; +} + +inline VariantSlot* CollectionData::addSlot(MemoryPool* pool) { + VariantSlot* slot = pool->allocVariant(); + if (!slot) + return 0; + + if (_tail) { + _tail->setNextNotNull(slot); + _tail = slot; + } else { + _head = slot; + _tail = slot; + } + + slot->clear(); + return slot; +} + +inline VariantData* CollectionData::addElement(MemoryPool* pool) { + return slotData(addSlot(pool)); +} + +template +inline VariantData* CollectionData::addMember(TAdaptedString key, + MemoryPool* pool, + TStoragePolicy storage) { + VariantSlot* slot = addSlot(pool); + if (!slotSetKey(slot, key, pool, storage)) { + removeSlot(slot); + return 0; + } + return slot->data(); +} + +inline void CollectionData::clear() { + _head = 0; + _tail = 0; +} + +template +inline bool CollectionData::containsKey(const TAdaptedString& key) const { + return getSlot(key) != 0; +} + +inline bool CollectionData::copyFrom(const CollectionData& src, + MemoryPool* pool) { + clear(); + for (VariantSlot* s = src._head; s; s = s->next()) { + VariantData* var; + if (s->key() != 0) { + String key(s->key(), s->ownsKey() ? String::Copied : String::Linked); + var = addMember(adaptString(key), pool, getStringStoragePolicy(key)); + } else { + var = addElement(pool); + } + if (!var) + return false; + if (!var->copyFrom(*s->data(), pool)) + return false; + } + return true; +} + +inline bool CollectionData::equalsObject(const CollectionData& other) const { + size_t count = 0; + for (VariantSlot* slot = _head; slot; slot = slot->next()) { + VariantData* v1 = slot->data(); + VariantData* v2 = other.getMember(adaptString(slot->key())); + if (!variantEquals(v1, v2)) + return false; + count++; + } + return count == other.size(); +} + +inline bool CollectionData::equalsArray(const CollectionData& other) const { + VariantSlot* s1 = _head; + VariantSlot* s2 = other._head; + for (;;) { + if (s1 == s2) + return true; + if (!s1 || !s2) + return false; + if (!variantEquals(s1->data(), s2->data())) + return false; + s1 = s1->next(); + s2 = s2->next(); + } +} + +template +inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const { + if (key.isNull()) + return 0; + VariantSlot* slot = _head; + while (slot) { + if (stringEquals(key, adaptString(slot->key()))) + break; + slot = slot->next(); + } + return slot; +} + +inline VariantSlot* CollectionData::getSlot(size_t index) const { + if (!_head) + return 0; + return _head->next(index); +} + +inline VariantSlot* CollectionData::getPreviousSlot(VariantSlot* target) const { + VariantSlot* current = _head; + while (current) { + VariantSlot* next = current->next(); + if (next == target) + return current; + current = next; + } + return 0; +} + +template +inline VariantData* CollectionData::getMember(TAdaptedString key) const { + VariantSlot* slot = getSlot(key); + return slot ? slot->data() : 0; +} + +template +inline VariantData* CollectionData::getOrAddMember( + TAdaptedString key, MemoryPool* pool, TStoragePolicy storage_policy) { + // ignore null key + if (key.isNull()) + return 0; + + // search a matching key + VariantSlot* slot = getSlot(key); + if (slot) + return slot->data(); + + return addMember(key, pool, storage_policy); +} + +inline VariantData* CollectionData::getElement(size_t index) const { + VariantSlot* slot = getSlot(index); + return slot ? slot->data() : 0; +} + +inline VariantData* CollectionData::getOrAddElement(size_t index, + MemoryPool* pool) { + VariantSlot* slot = _head; + while (slot && index > 0) { + slot = slot->next(); + index--; + } + if (!slot) + index++; + while (index > 0) { + slot = addSlot(pool); + index--; + } + return slotData(slot); +} + +inline void CollectionData::removeSlot(VariantSlot* slot) { + if (!slot) + return; + VariantSlot* prev = getPreviousSlot(slot); + VariantSlot* next = slot->next(); + if (prev) + prev->setNext(next); + else + _head = next; + if (!next) + _tail = prev; +} + +inline void CollectionData::removeElement(size_t index) { + removeSlot(getSlot(index)); +} + +inline size_t CollectionData::memoryUsage() const { + size_t total = 0; + for (VariantSlot* s = _head; s; s = s->next()) { + total += sizeof(VariantSlot) + s->data()->memoryUsage(); + if (s->ownsKey()) + total += strlen(s->key()) + 1; + } + return total; +} + +inline size_t CollectionData::nesting() const { + size_t maxChildNesting = 0; + for (VariantSlot* s = _head; s; s = s->next()) { + size_t childNesting = s->data()->nesting(); + if (childNesting > maxChildNesting) + maxChildNesting = childNesting; + } + return maxChildNesting + 1; +} + +inline size_t CollectionData::size() const { + return slotSize(_head); +} + +template +inline void movePointer(T*& p, ptrdiff_t offset) { + if (!p) + return; + p = reinterpret_cast( + reinterpret_cast(reinterpret_cast(p) + offset)); + ARDUINOJSON_ASSERT(isAligned(p)); +} + +inline void CollectionData::movePointers(ptrdiff_t stringDistance, + ptrdiff_t variantDistance) { + movePointer(_head, variantDistance); + movePointer(_tail, variantDistance); + for (VariantSlot* slot = _head; slot; slot = slot->next()) + slot->movePointers(stringDistance, variantDistance); +} + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp new file mode 100644 index 0000000..ed5687a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp @@ -0,0 +1,236 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#if __cplusplus >= 201103L +# define ARDUINOJSON_HAS_LONG_LONG 1 +# define ARDUINOJSON_HAS_RVALUE_REFERENCES 1 +#else +# define ARDUINOJSON_HAS_LONG_LONG 0 +# define ARDUINOJSON_HAS_RVALUE_REFERENCES 0 +#endif + +#ifndef ARDUINOJSON_HAS_NULLPTR +# if __cplusplus >= 201103L +# define ARDUINOJSON_HAS_NULLPTR 1 +# else +# define ARDUINOJSON_HAS_NULLPTR 0 +# endif +#endif + +#if defined(_MSC_VER) && !ARDUINOJSON_HAS_LONG_LONG +# define ARDUINOJSON_HAS_INT64 1 +#else +# define ARDUINOJSON_HAS_INT64 0 +#endif + +// Support std::istream and std::ostream +#ifndef ARDUINOJSON_ENABLE_STD_STREAM +# ifdef __has_include +# if __has_include() && \ + __has_include() && \ + !defined(min) && \ + !defined(max) +# define ARDUINOJSON_ENABLE_STD_STREAM 1 +# else +# define ARDUINOJSON_ENABLE_STD_STREAM 0 +# endif +# else +# ifdef ARDUINO +# define ARDUINOJSON_ENABLE_STD_STREAM 0 +# else +# define ARDUINOJSON_ENABLE_STD_STREAM 1 +# endif +# endif +#endif + +// Support std::string +#ifndef ARDUINOJSON_ENABLE_STD_STRING +# ifdef __has_include +# if __has_include() && !defined(min) && !defined(max) +# define ARDUINOJSON_ENABLE_STD_STRING 1 +# else +# define ARDUINOJSON_ENABLE_STD_STRING 0 +# endif +# else +# ifdef ARDUINO +# define ARDUINOJSON_ENABLE_STD_STRING 0 +# else +# define ARDUINOJSON_ENABLE_STD_STRING 1 +# endif +# endif +#endif + +// Support for std::string_view +#ifndef ARDUINOJSON_ENABLE_STRING_VIEW +# ifdef __has_include +# if __has_include() && __cplusplus >= 201703L +# define ARDUINOJSON_ENABLE_STRING_VIEW 1 +# else +# define ARDUINOJSON_ENABLE_STRING_VIEW 0 +# endif +# else +# define ARDUINOJSON_ENABLE_STRING_VIEW 0 +# endif +#endif + +// Store floating-point values with float (0) or double (1) +#ifndef ARDUINOJSON_USE_DOUBLE +# define ARDUINOJSON_USE_DOUBLE 1 +#endif + +// Store integral values with long (0) or long long (1) +#ifndef ARDUINOJSON_USE_LONG_LONG +# if ARDUINOJSON_HAS_LONG_LONG && defined(__SIZEOF_POINTER__) && \ + __SIZEOF_POINTER__ >= 4 || \ + defined(_MSC_VER) +# define ARDUINOJSON_USE_LONG_LONG 1 +# endif +#endif +#ifndef ARDUINOJSON_USE_LONG_LONG +# define ARDUINOJSON_USE_LONG_LONG 0 +#endif + +// Limit nesting as the stack is likely to be small +#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT +# define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10 +#endif + +// Number of bits to store the pointer to next node +// (saves RAM but limits the number of values in a document) +#ifndef ARDUINOJSON_SLOT_OFFSET_SIZE +# if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ <= 2 +// Address space == 16-bit => max 127 values +# define ARDUINOJSON_SLOT_OFFSET_SIZE 1 +# elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ >= 8 || \ + defined(_WIN64) && _WIN64 +// Address space == 64-bit => max 2147483647 values +# define ARDUINOJSON_SLOT_OFFSET_SIZE 4 +# else +// Address space == 32-bit => max 32767 values +# define ARDUINOJSON_SLOT_OFFSET_SIZE 2 +# endif +#endif + +#ifdef ARDUINO + +// Enable support for Arduino's String class +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING +# define ARDUINOJSON_ENABLE_ARDUINO_STRING 1 +# endif + +// Enable support for Arduino's Stream class +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM +# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1 +# endif + +// Enable support for Arduino's Print class +# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT +# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 1 +# endif + +// Enable support for PROGMEM +# ifndef ARDUINOJSON_ENABLE_PROGMEM +# define ARDUINOJSON_ENABLE_PROGMEM 1 +# endif + +#else // ARDUINO + +// Disable support for Arduino's String class +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING +# define ARDUINOJSON_ENABLE_ARDUINO_STRING 0 +# endif + +// Disable support for Arduino's Stream class +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM +# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0 +# endif + +// Disable support for Arduino's Print class +# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT +# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0 +# endif + +// Disable support for PROGMEM +# ifndef ARDUINOJSON_ENABLE_PROGMEM +# define ARDUINOJSON_ENABLE_PROGMEM 0 +# endif + +#endif // ARDUINO + +// Convert unicode escape sequence (\u0123) to UTF-8 +#ifndef ARDUINOJSON_DECODE_UNICODE +# define ARDUINOJSON_DECODE_UNICODE 1 +#endif + +// Ignore comments in input +#ifndef ARDUINOJSON_ENABLE_COMMENTS +# define ARDUINOJSON_ENABLE_COMMENTS 0 +#endif + +// Support NaN in JSON +#ifndef ARDUINOJSON_ENABLE_NAN +# define ARDUINOJSON_ENABLE_NAN 0 +#endif + +// Support Infinity in JSON +#ifndef ARDUINOJSON_ENABLE_INFINITY +# define ARDUINOJSON_ENABLE_INFINITY 0 +#endif + +// Control the exponentiation threshold for big numbers +// CAUTION: cannot be more that 1e9 !!!! +#ifndef ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD +# define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e7 +#endif + +// Control the exponentiation threshold for small numbers +#ifndef ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD +# define ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD 1e-5 +#endif + +#ifndef ARDUINOJSON_LITTLE_ENDIAN +# if defined(_MSC_VER) || \ + (defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ + defined(__LITTLE_ENDIAN__) || defined(__i386) || defined(__x86_64) +# define ARDUINOJSON_LITTLE_ENDIAN 1 +# else +# define ARDUINOJSON_LITTLE_ENDIAN 0 +# endif +#endif + +#ifndef ARDUINOJSON_ENABLE_ALIGNMENT +# if defined(__AVR) +# define ARDUINOJSON_ENABLE_ALIGNMENT 0 +# else +# define ARDUINOJSON_ENABLE_ALIGNMENT 1 +# endif +#endif + +#ifndef ARDUINOJSON_TAB +# define ARDUINOJSON_TAB " " +#endif + +#ifndef ARDUINOJSON_ENABLE_STRING_DEDUPLICATION +# define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 1 +#endif + +#ifndef ARDUINOJSON_STRING_BUFFER_SIZE +# define ARDUINOJSON_STRING_BUFFER_SIZE 32 +#endif + +#ifndef ARDUINOJSON_DEBUG +# ifdef __PLATFORMIO_BUILD_DEBUG__ +# define ARDUINOJSON_DEBUG 1 +# else +# define ARDUINOJSON_DEBUG 0 +# endif +#endif + +#if ARDUINOJSON_HAS_NULLPTR && defined(nullptr) +# error nullptr is defined as a macro. Remove the faulty #define or #undef nullptr +// See https://github.com/bblanchon/ArduinoJson/issues/1355 +#endif diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp new file mode 100644 index 0000000..96b3a17 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp @@ -0,0 +1,107 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include +#include +#include + +#if ARDUINOJSON_ENABLE_STD_STREAM +# include +#endif + +namespace ARDUINOJSON_NAMESPACE { + +class DeserializationError : public SafeBoolIdom { + public: + enum Code { + Ok, + EmptyInput, + IncompleteInput, + InvalidInput, + NoMemory, + TooDeep + }; + + DeserializationError() {} + DeserializationError(Code c) : _code(c) {} + + // Compare with DeserializationError + friend bool operator==(const DeserializationError& lhs, + const DeserializationError& rhs) { + return lhs._code == rhs._code; + } + friend bool operator!=(const DeserializationError& lhs, + const DeserializationError& rhs) { + return lhs._code != rhs._code; + } + + // Compare with Code + friend bool operator==(const DeserializationError& lhs, Code rhs) { + return lhs._code == rhs; + } + friend bool operator==(Code lhs, const DeserializationError& rhs) { + return lhs == rhs._code; + } + friend bool operator!=(const DeserializationError& lhs, Code rhs) { + return lhs._code != rhs; + } + friend bool operator!=(Code lhs, const DeserializationError& rhs) { + return lhs != rhs._code; + } + + // Behaves like a bool + operator bool_type() const { + return _code != Ok ? safe_true() : safe_false(); + } + + // Returns internal enum, useful for switch statement + Code code() const { + return _code; + } + + const char* c_str() const { + static const char* messages[] = { + "Ok", "EmptyInput", "IncompleteInput", + "InvalidInput", "NoMemory", "TooDeep"}; + ARDUINOJSON_ASSERT(static_cast(_code) < + sizeof(messages) / sizeof(messages[0])); + return messages[_code]; + } + +#if ARDUINOJSON_ENABLE_PROGMEM + const __FlashStringHelper* f_str() const { + ARDUINOJSON_DEFINE_STATIC_ARRAY(char, s0, "Ok"); + ARDUINOJSON_DEFINE_STATIC_ARRAY(char, s1, "EmptyInput"); + ARDUINOJSON_DEFINE_STATIC_ARRAY(char, s2, "IncompleteInput"); + ARDUINOJSON_DEFINE_STATIC_ARRAY(char, s3, "InvalidInput"); + ARDUINOJSON_DEFINE_STATIC_ARRAY(char, s4, "NoMemory"); + ARDUINOJSON_DEFINE_STATIC_ARRAY(char, s5, "TooDeep"); + ARDUINOJSON_DEFINE_STATIC_ARRAY( + const char*, messages, ARDUINOJSON_EXPAND6({s0, s1, s2, s3, s4, s5})); + return ARDUINOJSON_READ_STATIC_ARRAY(const __FlashStringHelper*, messages, + _code); + } +#endif + + private: + Code _code; +}; + +#if ARDUINOJSON_ENABLE_STD_STREAM +inline std::ostream& operator<<(std::ostream& s, + const DeserializationError& e) { + s << e.c_str(); + return s; +} + +inline std::ostream& operator<<(std::ostream& s, DeserializationError::Code c) { + s << DeserializationError(c).c_str(); + return s; +} +#endif + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp new file mode 100644 index 0000000..8a091bb --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp @@ -0,0 +1,66 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +namespace ARDUINOJSON_NAMESPACE { + +class Filter { + public: + explicit Filter(VariantConstRef v) : _variant(v) {} + + bool allow() const { + return _variant; + } + + bool allowArray() const { + return _variant == true || _variant.is(); + } + + bool allowObject() const { + return _variant == true || _variant.is(); + } + + bool allowValue() const { + return _variant == true; + } + + template + Filter operator[](const TKey& key) const { + if (_variant == true) // "true" means "allow recursively" + return *this; + VariantConstRef member = _variant[key]; + return Filter(member.isNull() ? _variant["*"] : member); + } + + private: + VariantConstRef _variant; +}; + +struct AllowAllFilter { + bool allow() const { + return true; + } + + bool allowArray() const { + return true; + } + + bool allowObject() const { + return true; + } + + bool allowValue() const { + return true; + } + + template + AllowAllFilter operator[](const TKey&) const { + return AllowAllFilter(); + } +}; + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp new file mode 100644 index 0000000..af5724d --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp @@ -0,0 +1,29 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include + +namespace ARDUINOJSON_NAMESPACE { + +class NestingLimit { + public: + NestingLimit() : _value(ARDUINOJSON_DEFAULT_NESTING_LIMIT) {} + explicit NestingLimit(uint8_t n) : _value(n) {} + + NestingLimit decrement() const { + ARDUINOJSON_ASSERT(_value > 0); + return NestingLimit(static_cast(_value - 1)); + } + + bool reached() const { + return _value == 0; + } + + private: + uint8_t _value; +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp new file mode 100644 index 0000000..4e1958c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp @@ -0,0 +1,56 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +#include // for size_t + +namespace ARDUINOJSON_NAMESPACE { + +// The default reader is a simple wrapper for Readers that are not copiable +template +struct Reader { + public: + Reader(TSource& source) : _source(&source) {} + + int read() { + return _source->read(); // Error here? You passed an unsupported input type + } + + size_t readBytes(char* buffer, size_t length) { + return _source->readBytes(buffer, length); + } + + private: + TSource* _source; +}; + +template +struct BoundedReader { + // no default implementation because we need to pass the size to the + // constructor +}; +} // namespace ARDUINOJSON_NAMESPACE + +#include +#include +#include + +#if ARDUINOJSON_ENABLE_ARDUINO_STREAM +# include +#endif + +#if ARDUINOJSON_ENABLE_ARDUINO_STRING +# include +#endif + +#if ARDUINOJSON_ENABLE_PROGMEM +# include +#endif + +#if ARDUINOJSON_ENABLE_STD_STREAM +# include +#endif diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp new file mode 100644 index 0000000..179e376 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp @@ -0,0 +1,31 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +struct Reader::value>::type> { + public: + explicit Reader(Stream& stream) : _stream(&stream) {} + + int read() { + // don't use _stream.read() as it ignores the timeout + char c; + return _stream->readBytes(&c, 1) ? static_cast(c) : -1; + } + + size_t readBytes(char* buffer, size_t length) { + return _stream->readBytes(buffer, length); + } + + private: + Stream* _stream; +}; + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp new file mode 100644 index 0000000..652409a --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp @@ -0,0 +1,19 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +struct Reader::value>::type> + : BoundedReader { + explicit Reader(const ::String& s) + : BoundedReader(s.c_str(), s.length()) {} +}; + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp new file mode 100644 index 0000000..c48248c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp @@ -0,0 +1,55 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +namespace ARDUINOJSON_NAMESPACE { + +template <> +struct Reader { + const char* _ptr; + + public: + explicit Reader(const __FlashStringHelper* ptr) + : _ptr(reinterpret_cast(ptr)) {} + + int read() { + return pgm_read_byte(_ptr++); + } + + size_t readBytes(char* buffer, size_t length) { + memcpy_P(buffer, _ptr, length); + _ptr += length; + return length; + } +}; + +template <> +struct BoundedReader { + const char* _ptr; + const char* _end; + + public: + explicit BoundedReader(const __FlashStringHelper* ptr, size_t size) + : _ptr(reinterpret_cast(ptr)), _end(_ptr + size) {} + + int read() { + if (_ptr < _end) + return pgm_read_byte(_ptr++); + else + return -1; + } + + size_t readBytes(char* buffer, size_t length) { + size_t available = static_cast(_end - _ptr); + if (available < length) + length = available; + memcpy_P(buffer, _ptr, length); + _ptr += length; + return length; + } +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp new file mode 100644 index 0000000..1c470c0 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp @@ -0,0 +1,43 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +namespace ARDUINOJSON_NAMESPACE { + +template +class IteratorReader { + TIterator _ptr, _end; + + public: + explicit IteratorReader(TIterator begin, TIterator end) + : _ptr(begin), _end(end) {} + + int read() { + if (_ptr < _end) + return static_cast(*_ptr++); + else + return -1; + } + + size_t readBytes(char* buffer, size_t length) { + size_t i = 0; + while (i < length && _ptr < _end) buffer[i++] = *_ptr++; + return i; + } +}; + +template +struct void_ { + typedef void type; +}; + +template +struct Reader::type> + : IteratorReader { + explicit Reader(const TSource& source) + : IteratorReader(source.begin(), + source.end()) {} +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp new file mode 100644 index 0000000..1181297 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp @@ -0,0 +1,50 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +struct IsCharOrVoid { + static const bool value = + is_same::value || is_same::value || + is_same::value || is_same::value; +}; + +template +struct IsCharOrVoid : IsCharOrVoid {}; + +template +struct Reader::value>::type> { + const char* _ptr; + + public: + explicit Reader(const void* ptr) + : _ptr(ptr ? reinterpret_cast(ptr) : "") {} + + int read() { + return static_cast(*_ptr++); + } + + size_t readBytes(char* buffer, size_t length) { + for (size_t i = 0; i < length; i++) buffer[i] = *_ptr++; + return length; + } +}; + +template +struct BoundedReader::value>::type> + : public IteratorReader { + public: + explicit BoundedReader(const void* ptr, size_t len) + : IteratorReader(reinterpret_cast(ptr), + reinterpret_cast(ptr) + len) {} +}; + +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp new file mode 100644 index 0000000..c92f793 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp @@ -0,0 +1,29 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +struct Reader::value>::type> { + public: + explicit Reader(std::istream& stream) : _stream(&stream) {} + + int read() { + return _stream->get(); + } + + size_t readBytes(char* buffer, size_t length) { + _stream->read(buffer, static_cast(length)); + return static_cast(_stream->gcount()); + } + + private: + std::istream* _stream; +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp new file mode 100644 index 0000000..569fa2c --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp @@ -0,0 +1,34 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include + +namespace ARDUINOJSON_NAMESPACE { + +template +struct Reader, void> : Reader { + explicit Reader(const ElementProxy& x) + : Reader(x.template as()) {} +}; + +template +struct Reader, void> : Reader { + explicit Reader(const MemberProxy& x) + : Reader(x.template as()) {} +}; + +template <> +struct Reader : Reader { + explicit Reader(VariantRef x) : Reader(x.as()) {} +}; + +template <> +struct Reader : Reader { + explicit Reader(VariantConstRef x) + : Reader(x.as()) {} +}; +} // namespace ARDUINOJSON_NAMESPACE diff --git a/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp new file mode 100644 index 0000000..805cbb0 --- /dev/null +++ b/firmware/uniclock/lib/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp @@ -0,0 +1,71 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include +#include +#include +#include + +namespace ARDUINOJSON_NAMESPACE { + +template