diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 000000000..a769a976d --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,52 @@ +# This container is used to build Meshtastic with the libraries required by the fuzzer. +# ClusterFuzzLite starts the container, runs the build.sh script, and then exits. + +# As this is not a long running service, health-checks are not required. ClusterFuzzLite +# also only works if the user remains unchanged from the base image (it expects to run +# as root). +# trunk-ignore-all(trivy/DS026): No healthcheck is needed for this builder container +# trunk-ignore-all(checkov/CKV_DOCKER_2): No healthcheck is needed for this builder container +# trunk-ignore-all(checkov/CKV_DOCKER_3): We must run as root for this container +# trunk-ignore-all(trivy/DS002): We must run as root for this container +# trunk-ignore-all(checkov/CKV_DOCKER_8): We must run as root for this container +# trunk-ignore-all(hadolint/DL3002): We must run as root for this container + +FROM gcr.io/oss-fuzz-base/base-builder:v1 + +ENV PIP_ROOT_USER_ACTION=ignore + +# trunk-ignore(hadolint/DL3008): apt packages are not pinned. +# trunk-ignore(terrascan/AC_DOCKER_0002): apt packages are not pinned. +RUN apt-get update && apt-get install --no-install-recommends -y \ + cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \ + libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \ + libusb-1.0-0-dev libssl-dev pkg-config && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + pip install --no-cache-dir -U \ + platformio==6.1.16 \ + grpcio-tools==1.68.1 \ + meshtastic==2.5.9 + +# Ugly hack to avoid clang detecting a conflict between the math "log" function and the "log" function in framework-portduino/cores/portduino/logging.h +RUN sed -i -e 's/__MATHCALL_VEC (log,, (_Mdouble_ __x));//' /usr/include/x86_64-linux-gnu/bits/mathcalls.h + +# A few dependencies are too old on the base-builder image. More recent versions are built from source. +WORKDIR $SRC +RUN git config --global advice.detachedHead false && \ + git clone --depth 1 --branch 0.8.0 https://github.com/jbeder/yaml-cpp.git && \ + git clone --depth 1 --branch v2.3.3 https://github.com/babelouest/orcania.git && \ + git clone --depth 1 --branch v1.4.20 https://github.com/babelouest/yder.git && \ + git clone --depth 1 --branch v2.7.15 https://github.com/babelouest/ulfius.git + +COPY ./.clusterfuzzlite/build.sh $SRC/ + +WORKDIR $SRC/firmware +COPY . $SRC/firmware/ + +# https://docs.platformio.org/en/latest/envvars.html +ENV PLATFORMIO_CORE_DIR=$SRC/pio/core \ + PLATFORMIO_LIBDEPS_DIR=$SRC/pio/libdeps \ + PLATFORMIO_PACKAGES_DIR=$SRC/pio/packages \ + PLATFORMIO_SETTING_ENABLE_CACHE=No \ + PIO_ENV=buildroot +RUN platformio pkg install --environment $PIO_ENV diff --git a/.clusterfuzzlite/README.md b/.clusterfuzzlite/README.md new file mode 100644 index 000000000..f6e4089a3 --- /dev/null +++ b/.clusterfuzzlite/README.md @@ -0,0 +1,59 @@ +# ClusterFuzzLite for Meshtastic + +This directory contains the fuzzer implementation for Meshtastic using the ClusterFuzzLite framework. +See the [ClusterFuzzLite documentation](https://google.github.io/clusterfuzzlite/) for more details. + +## Running locally + +ClusterFuzzLite uses the OSS-Fuzz toolchain. To build the fuzzer manually, first grab a copy of OSS-Fuzz. + +```shell +git clone https://github.com/google/oss-fuzz.git +cd oss-fuzz +``` + +To build the fuzzer, run: + +```shell +python3 infra/helper.py build_image --external $PATH_TO_MESHTASTIC_FIRMWARE_DIRECTORY +python3 infra/helper.py build_fuzzers --external $PATH_TO_MESHTASTIC_FIRMWARE_DIRECTORY --sanitizer address +``` + +To run the fuzzer, run: + +```shell +python3 infra/helper.py run_fuzzer --external --corpus-dir= $PATH_TO_MESHTASTIC_FIRMWARE_DIRECTORY router_fuzzer +``` + +More background on these commands can be found in the +[ClusterFuzzLite documentation](https://google.github.io/clusterfuzzlite/build-integration/#testing-locally). + +## router_fuzzer.cpp + +This fuzzer submits MeshPacket protos to the `Router::enqueueReceivedMessage` method. It takes the binary +data from the fuzzer and decodes that data to a MeshPacket using nanopb. A few fields in +the MeshPacket are modified by the fuzzer. + +- If the `to` field is 0, it will be replaced with the NodeID of the running node. +- If the `from` field is 0, it will be replaced with the NodeID of the running node. +- If the `id` field is 0, it will be replaced with an incrementing counter value. +- If the `pki_encrypted` field is true, the `public_key` field will be populated with the first admin key. + +The `router_fuzzer_seed_corpus.py` file contains a list of MeshPackets. It is run from inside build.sh and +writes the binary MeshPacket protos to files. These files are use used by the fuzzer as its initial seed data, +helping the fuzzer to start off with a few known inputs. + +### Interpreting a fuzzer crash + +If the fuzzer crashes, it'll write the input bytes used for the test case to a file and notify about the +location of that file. The contents of the file are a binary serialized MeshPacket protobuf. The following +snippet of Python code can be used to parse the file into a human readable form. + +```python +from meshtastic.protobuf import mesh_pb2 + +mesh_pb2.MeshPacket.FromString(open("crash-XXXX-file", "rb").read()) +``` + +Consider adding any such crash results to the `router_fuzzer_seed_corpus.py` file to ensure there a isn't +a future regression for that crash test case. diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100644 index 000000000..10a2db0bd --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,71 @@ +#!/bin/bash -eu + +# Build Meshtastic and a few needed dependencies using clang++ +# and the OSS-Fuzz required build flags. + +env + +cd "$SRC" +NPROC=$(nproc || echo 1) + +LDFLAGS=-lpthread cmake -S "$SRC/yaml-cpp" -B "$WORK/yaml-cpp/$SANITIZER" \ + -DBUILD_SHARED_LIBS=OFF +cmake --build "$WORK/yaml-cpp/$SANITIZER" -j "$NPROC" +cmake --install "$WORK/yaml-cpp/$SANITIZER" --prefix /usr + +cmake -S "$SRC/orcania" -B "$WORK/orcania/$SANITIZER" \ + -DBUILD_STATIC=ON +cmake --build "$WORK/orcania/$SANITIZER" -j "$NPROC" +cmake --install "$WORK/orcania/$SANITIZER" --prefix /usr + +cmake -S "$SRC/yder" -B "$WORK/yder/$SANITIZER" \ + -DBUILD_STATIC=ON -DWITH_JOURNALD=OFF +cmake --build "$WORK/yder/$SANITIZER" -j "$NPROC" +cmake --install "$WORK/yder/$SANITIZER" --prefix /usr + +cmake -S "$SRC/ulfius" -B "$WORK/ulfius/$SANITIZER" \ + -DBUILD_STATIC=ON -DWITH_JANSSON=OFF -DWITH_CURL=OFF -DWITH_WEBSOCKET=OFF +cmake --build "$WORK/ulfius/$SANITIZER" -j "$NPROC" +cmake --install "$WORK/ulfius/$SANITIZER" --prefix /usr + +cd "$SRC/firmware" + +PLATFORMIO_EXTRA_SCRIPTS=$(echo -e "pre:.clusterfuzzlite/platformio-clusterfuzzlite-pre.py\npost:.clusterfuzzlite/platformio-clusterfuzzlite-post.py") +STATIC_LIBS=$(pkg-config --libs --static libulfius openssl libgpiod yaml-cpp bluez --silence-errors) +export PLATFORMIO_EXTRA_SCRIPTS +export STATIC_LIBS +export PLATFORMIO_WORKSPACE_DIR="$WORK/pio/$SANITIZER" +export TARGET_CC=$CC +export TARGET_CXX=$CXX +export TARGET_LD=$CXX +export TARGET_AR=llvm-ar +export TARGET_AS=llvm-as +export TARGET_OBJCOPY=llvm-objcopy +export TARGET_RANLIB=llvm-ranlib + +mkdir -p "$OUT/lib" + +cp .clusterfuzzlite/*_fuzzer.options "$OUT/" + +for f in .clusterfuzzlite/*_fuzzer.cpp; do + fuzzer=$(basename "$f" .cpp) + cp -f "$f" src/fuzzer.cpp + pio run -vvv --environment "$PIO_ENV" + program="$PLATFORMIO_WORKSPACE_DIR/build/$PIO_ENV/program" + cp "$program" "$OUT/$fuzzer" + + # Copy shared libraries used by the fuzzer. + read -d '' -ra shared_libs < <(ldd "$program" | sed -n 's/[^=]\+=> \([^ ]\+\).*/\1/p') || true + cp -f "${shared_libs[@]}" "$OUT/lib/" + + # Build the initial fuzzer seed corpus. + corpus_name="${fuzzer}_seed_corpus" + corpus_generator="$PWD/.clusterfuzzlite/${corpus_name}.py" + if [[ -f $corpus_generator ]]; then + mkdir "$corpus_name" + pushd "$corpus_name" + python3 "$corpus_generator" + popd + zip -D "$OUT/${corpus_name}.zip" "$corpus_name"/* + fi +done diff --git a/.clusterfuzzlite/platformio-clusterfuzzlite-post.py b/.clusterfuzzlite/platformio-clusterfuzzlite-post.py new file mode 100644 index 000000000..f62078bb3 --- /dev/null +++ b/.clusterfuzzlite/platformio-clusterfuzzlite-post.py @@ -0,0 +1,35 @@ +"""PlatformIO build script (post: runs after other Meshtastic scripts).""" + +import os +import shlex + +from SCons.Script import DefaultEnvironment + +env = DefaultEnvironment() + +# Remove any static libraries from the LIBS environment. Static libraries are +# handled in platformio-clusterfuzzlite-pre.py. +static_libs = set(lib[2:] for lib in shlex.split(os.getenv("STATIC_LIBS"))) +env.Replace( + LIBS=[ + lib for lib in env["LIBS"] if not (isinstance(lib, str) and lib in static_libs) + ], +) + +# FrameworkArduino/portduino/main.cpp contains the "main" function the binary. +# The fuzzing framework also provides a "main" function and needs to be run +# before Meshtastic is started. We rename the "main" function for Meshtastic to +# "portduino_main" here so that it can be called inside the fuzzer. +env.AddPostAction( + "$BUILD_DIR/FrameworkArduino/portduino/main.cpp.o", + env.VerboseAction( + " ".join( + [ + "$OBJCOPY", + "--redefine-sym=main=portduino_main", + "$BUILD_DIR/FrameworkArduino/portduino/main.cpp.o", + ] + ), + "Renaming main symbol to portduino_main", + ), +) diff --git a/.clusterfuzzlite/platformio-clusterfuzzlite-pre.py b/.clusterfuzzlite/platformio-clusterfuzzlite-pre.py new file mode 100644 index 000000000..a70630cf0 --- /dev/null +++ b/.clusterfuzzlite/platformio-clusterfuzzlite-pre.py @@ -0,0 +1,52 @@ +"""PlatformIO build script (pre: runs before other Meshtastic scripts). + +ClusterFuzzLite executes in a different container from the build. During the build, +attempt to link statically to as many dependencies as possible. For dependencies that +do not have static libraries, the shared library files are copied to the output +directory by the build.sh script. +""" + +import glob +import os +import shlex + +from SCons.Script import DefaultEnvironment, Literal + +env = DefaultEnvironment() + +cxxflags = shlex.split(os.getenv("CXXFLAGS")) +sanitizer_flags = shlex.split(os.getenv("SANITIZER_FLAGS")) +lib_fuzzing_engine = shlex.split(os.getenv("LIB_FUZZING_ENGINE")) +statics = glob.glob("/usr/lib/lib*.a") + glob.glob("/usr/lib/*/lib*.a") +no_static = set(("-ldl",)) + + +def replaceStatic(lib): + """Replace -l with the static .a file for the library.""" + if not lib.startswith("-l") or lib in no_static: + return lib + static_name = f"/lib{lib[2:]}.a" + static = [s for s in statics if s.endswith(static_name)] + if len(static) == 1: + return static[0] + return lib + + +# Setup the environment for building with Clang and the OSS-Fuzz required build flags. +env.Append( + CFLAGS=os.getenv("CFLAGS"), + CXXFLAGS=cxxflags, + LIBSOURCE_DIRS=["/usr/lib/x86_64-linux-gnu"], + LINKFLAGS=cxxflags + + sanitizer_flags + + lib_fuzzing_engine + + ["-stdlib=libc++", "-std=c++17"], + _LIBFLAGS=[replaceStatic(s) for s in shlex.split(os.getenv("STATIC_LIBS"))] + + [ + "/usr/lib/x86_64-linux-gnu/libunistring.a", # Needs to be at the end. + # Find the shared libraries in a subdirectory named lib + # within the same directory as the binary. + Literal("-Wl,-rpath,$ORIGIN/lib"), + "-Wl,-z,origin", + ], +) diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml new file mode 100644 index 000000000..b4788012b --- /dev/null +++ b/.clusterfuzzlite/project.yaml @@ -0,0 +1 @@ +language: c++ diff --git a/.clusterfuzzlite/router_fuzzer.cpp b/.clusterfuzzlite/router_fuzzer.cpp new file mode 100644 index 000000000..bc4d248db --- /dev/null +++ b/.clusterfuzzlite/router_fuzzer.cpp @@ -0,0 +1,206 @@ +// Fuzzer implementation that sends MeshPackets to Router::enqueueReceivedMessage. +#include +#include +#include +#include +#include +#include +#include + +#include "PortduinoGPIO.h" +#include "PortduinoGlue.h" +#include "PowerFSM.h" +#include "mesh/MeshTypes.h" +#include "mesh/NodeDB.h" +#include "mesh/Router.h" +#include "mesh/TypeConversions.h" +#include "mesh/mesh-pb-constants.h" + +namespace +{ +constexpr uint32_t nodeId = 0x12345678; +// Set to true when lateInitVariant finishes. Used to ensure lateInitVariant was called during startup. +bool hasBeenConfigured = false; + +// These are used to block the Arduino loop() function until a fuzzer input is ready. This is +// an optimization that prevents a sleep from happening before the loop is run. The Arduino loop +// function calls loopCanSleep() before sleeping. loopCanSleep is implemented here in the fuzzer +// and blocks until runLoopOnce() is called to signal for the loop to run. +bool fuzzerRunning = false; // Set to true once LLVMFuzzerTestOneInput has started running. +bool loopCanRun = true; // The main Arduino loop() can run when this is true. +bool loopIsWaiting = false; // The main Arduino loop() is waiting to be signaled to run. +bool loopShouldExit = false; // Indicates that the main Arduino thread should exit by throwing ShouldExitException. +std::mutex loopLock; +std::condition_variable loopCV; +std::thread meshtasticThread; + +// This exception is thrown when the portuino main thread should exit. +class ShouldExitException : public std::runtime_error +{ + public: + using std::runtime_error::runtime_error; +}; + +// Start the loop for one test case and wait till the loop has completed. This ensures fuzz +// test cases do not overlap with one another. This helps the fuzzer attribute a crash to the +// single, currently running, test case. +void runLoopOnce() +{ + realHardware = true; // Avoids delay(100) within portduino/main.cpp + std::unique_lock lck(loopLock); + fuzzerRunning = true; + loopCanRun = true; + loopCV.notify_one(); + loopCV.wait(lck, [] { return !loopCanRun && loopIsWaiting; }); +} +} // namespace + +// Called in the main Arduino loop function to determine if the loop can delay/sleep before running again. +// We use this as a way to block the loop from sleeping and to start the loop function immediately when a +// fuzzer input is ready. +bool loopCanSleep() +{ + std::unique_lock lck(loopLock); + loopIsWaiting = true; + loopCV.notify_one(); + loopCV.wait(lck, [] { return loopCanRun || loopShouldExit; }); + loopIsWaiting = false; + if (loopShouldExit) + throw ShouldExitException("exit"); + if (!fuzzerRunning) + return true; // The loop can sleep before the fuzzer starts. + loopCanRun = false; // Only run the loop once before waiting again. + return false; +} + +// Called just prior to starting Meshtastic. Allows for setting config values before startup. +void lateInitVariant() +{ + settingsMap[logoutputlevel] = level_error; + channelFile.channels[0] = meshtastic_Channel{ + .has_settings = true, + .settings = + meshtastic_ChannelSettings{ + .psk = {.size = 1, .bytes = {/*defaultpskIndex=*/1}}, + .name = "LongFast", + .uplink_enabled = true, + .has_module_settings = true, + .module_settings = {.position_precision = 16}, + }, + .role = meshtastic_Channel_Role_PRIMARY, + }; + config.security.admin_key[0] = { + .size = 32, + .bytes = {0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a, + 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c}, + }; + config.security.admin_key_count = 1; + config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US; + moduleConfig.has_mqtt = true; + moduleConfig.mqtt = meshtastic_ModuleConfig_MQTTConfig{ + .enabled = true, + .proxy_to_client_enabled = true, + }; + moduleConfig.has_store_forward = true; + moduleConfig.store_forward = meshtastic_ModuleConfig_StoreForwardConfig{ + .enabled = true, + .history_return_max = 4, + .history_return_window = 600, + .is_server = true, + }; + meshtastic_Position fixedGPS = meshtastic_Position{ + .has_latitude_i = true, + .latitude_i = static_cast(1 * 1e7), + .has_longitude_i = true, + .longitude_i = static_cast(3 * 1e7), + .has_altitude = true, + .altitude = 64, + .location_source = meshtastic_Position_LocSource_LOC_MANUAL, + }; + nodeDB->setLocalPosition(fixedGPS); + config.has_position = true; + config.position.fixed_position = true; + meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(nodeDB->getNodeNum()); + info->has_position = true; + info->position = TypeConversions::ConvertToPositionLite(fixedGPS); + hasBeenConfigured = true; +} + +extern "C" { +int portduino_main(int argc, char **argv); // Renamed "main" function from Meshtastic binary. + +// Start Meshtastic in a thread and wait till it has reached the ON state. +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + settingsMap[maxtophone] = 5; + + meshtasticThread = std::thread([program = *argv[0]]() { + char nodeIdStr[12]; + strcpy(nodeIdStr, std::to_string(nodeId).c_str()); + int argc = 7; + char *argv[] = {program, "-d", "/tmp/meshtastic", "-h", nodeIdStr, "-p", "0", nullptr}; + try { + portduino_main(argc, argv); + } catch (const ShouldExitException &) { + } + }); + std::atexit([] { + { + const std::lock_guard lck(loopLock); + loopShouldExit = true; + loopCV.notify_one(); + } + meshtasticThread.join(); + }); + + // Wait for startup. + for (int i = 1; i < 20; ++i) { + if (powerFSM.getState() == &stateON) { + assert(hasBeenConfigured); + assert(router); + assert(nodeDB); + return 0; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + return 1; +} + +// This is the main entrypoint for the fuzzer (the fuzz target). The fuzzer will provide an array of bytes to be +// interpreted by this method. To keep things simple, the bytes are interpreted as a binary serialized MeshPacket +// proto. Any crashes discovered by the fuzzer will be written to a file. Unserialize that file to print the MeshPacket +// that caused the failure. +// +// This guide provides best practices for writing a fuzzer target. +// https://github.com/google/fuzzing/blob/master/docs/good-fuzz-target.md +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t length) +{ + meshtastic_MeshPacket p = meshtastic_MeshPacket_init_default; + pb_istream_t stream = pb_istream_from_buffer(data, length); + // Ignore any inputs that fail to decode or have fields set that are not transmitted over LoRa. + if (!pb_decode(&stream, &meshtastic_MeshPacket_msg, &p) || p.rx_time || p.rx_snr || p.priority || p.rx_rssi || p.delayed || + p.public_key.size || p.next_hop || p.relay_node || p.tx_after) + return -1; // Reject: The input will not be added to the corpus. + if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + meshtastic_Data d; + stream = pb_istream_from_buffer(p.decoded.payload.bytes, p.decoded.payload.size); + if (!pb_decode(&stream, &meshtastic_Data_msg, &d)) + return -1; // Reject: The input will not be added to the corpus. + } + + // Provide default values for a few fields so the fuzzer doesn't need to guess them. + if (p.from == 0) + p.from = nodeDB->getNodeNum(); + if (p.to == 0) + p.to = nodeDB->getNodeNum(); + static uint32_t packetId = 0; + if (p.id == 0) + p.id == ++packetId; + if (p.pki_encrypted && config.security.admin_key_count) + memcpy(&p.public_key, &config.security.admin_key[0], sizeof(p.public_key)); + + router->enqueueReceivedMessage(packetPool.allocCopy(p)); + runLoopOnce(); + return 0; // Accept: The input may be added to the corpus. +} +} \ No newline at end of file diff --git a/.clusterfuzzlite/router_fuzzer.options b/.clusterfuzzlite/router_fuzzer.options new file mode 100644 index 000000000..7cbd646dc --- /dev/null +++ b/.clusterfuzzlite/router_fuzzer.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len=256 diff --git a/.clusterfuzzlite/router_fuzzer_seed_corpus.py b/.clusterfuzzlite/router_fuzzer_seed_corpus.py new file mode 100644 index 000000000..71736c147 --- /dev/null +++ b/.clusterfuzzlite/router_fuzzer_seed_corpus.py @@ -0,0 +1,168 @@ +"""Generate an initial set of MeshPackets. + +The fuzzer uses these MeshPackets as an initial seed of test candidates. + +It's also good to add any previously discovered crash test cases to this list +to avoid future regressions. + +If left unset, the following values will be automatically set by the fuzzer. + - to: automatically set to the running node's NodeID + - from: automatically set to the running node's NodeID + - id: automatically set to the value of an incrementing counter + +Additionally, if `pki_encrypted` is populated in the packet, the first admin key +will be copied into the `public_key` field. +""" + +import base64 + +from meshtastic import BROADCAST_NUM +from meshtastic.protobuf import ( + admin_pb2, + atak_pb2, + mesh_pb2, + portnums_pb2, + telemetry_pb2, +) + + +def From(node: int = 9): + """Return a dict suitable for **kwargs for populating the 'from' field. + + 'from' is a reserved keyword in Python. It can't be used directly as an + argument to the MeshPacket constructor. Rather **From() can be used as + the final argument to provide the from node as a **kwarg. + + Defaults to 9 if no value is provided. + """ + return {"from": node} + + +packets = ( + ( + "position", + mesh_pb2.MeshPacket( + decoded=mesh_pb2.Data( + portnum=portnums_pb2.PortNum.POSITION_APP, + payload=mesh_pb2.Position( + latitude_i=int(1 * 1e7), + longitude_i=int(2 * 1e7), + altitude=5, + precision_bits=32, + ).SerializeToString(), + ), + to=BROADCAST_NUM, + **From(), + ), + ), + ( + "telemetry", + mesh_pb2.MeshPacket( + decoded=mesh_pb2.Data( + portnum=portnums_pb2.PortNum.TELEMETRY_APP, + payload=telemetry_pb2.Telemetry( + time=1736192207, + device_metrics=telemetry_pb2.DeviceMetrics( + battery_level=101, + channel_utilization=8, + air_util_tx=2, + uptime_seconds=42, + ), + ).SerializeToString(), + ), + to=BROADCAST_NUM, + **From(), + ), + ), + ( + "text", + mesh_pb2.MeshPacket( + decoded=mesh_pb2.Data( + portnum=portnums_pb2.PortNum.TEXT_MESSAGE_APP, + payload=b"Hello world", + ), + to=BROADCAST_NUM, + **From(), + ), + ), + ( + "user", + mesh_pb2.MeshPacket( + decoded=mesh_pb2.Data( + portnum=portnums_pb2.PortNum.NODEINFO_APP, + payload=mesh_pb2.User( + id="!00000009", + long_name="Node 9", + short_name="N9", + macaddr=b"\x00\x00\x00\x00\x00\x09", + hw_model=mesh_pb2.HardwareModel.RAK4631, + public_key=base64.b64decode( + "L0ih/6F41itofdE8mYyHk1SdfOJ/QRM1KQ+pO4vEEjQ=" + ), + ).SerializeToString(), + ), + **From(), + ), + ), + ( + "traceroute", + mesh_pb2.MeshPacket( + decoded=mesh_pb2.Data( + portnum=portnums_pb2.PortNum.TRACEROUTE_APP, + payload=mesh_pb2.RouteDiscovery( + route=[10], + ).SerializeToString(), + ), + **From(), + ), + ), + ( + "routing", + mesh_pb2.MeshPacket( + decoded=mesh_pb2.Data( + portnum=portnums_pb2.PortNum.ROUTING_APP, + payload=mesh_pb2.Routing( + error_reason=mesh_pb2.Routing.NO_RESPONSE, + ).SerializeToString(), + ), + **From(), + ), + ), + ( + "admin", + mesh_pb2.MeshPacket( + decoded=mesh_pb2.Data( + portnum=portnums_pb2.PortNum.ADMIN_APP, + payload=admin_pb2.AdminMessage( + get_owner_request=True, + ).SerializeToString(), + ), + pki_encrypted=True, + **From(), + ), + ), + ( + "atak", + mesh_pb2.MeshPacket( + decoded=mesh_pb2.Data( + portnum=portnums_pb2.PortNum.ATAK_PLUGIN, + payload=atak_pb2.TAKPacket( + is_compressed=True, + # Note, the strings are not valid for a compressed message, but will + # give the fuzzer a starting point. + contact=atak_pb2.Contact( + callsign="callsign", device_callsign="device_callsign" + ), + chat=atak_pb2.GeoChat( + message="message", to="to", to_callsign="to_callsign" + ), + ).SerializeToString(), + ), + **From(), + ), + ), +) + +for name, packet in packets: + with open(f"{name}.MeshPacket", "wb") as f: + f.write(packet.SerializeToString()) diff --git a/.dockerignore b/.dockerignore new file mode 120000 index 000000000..3e4e48b0b --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.gitignore \ No newline at end of file diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml index b3303d393..b24a5fc12 100644 --- a/.github/actions/build-variant/action.yml +++ b/.github/actions/build-variant/action.yml @@ -68,6 +68,12 @@ runs: sed -i '/DDEBUG_HEAP/d' ${INI_FILE} done + - name: PlatformIO ${{ inputs.arch }} download cache + uses: actions/cache@v4 + with: + path: ~/.platformio/.cache + key: pio-cache-${{ inputs.arch }}-${{ hashFiles('.github/actions/**', '**.ini') }} + - name: Build ${{ inputs.board }} shell: bash run: ${{ inputs.build-script-path }} ${{ inputs.board }} @@ -83,13 +89,13 @@ runs: - name: Get release version string shell: bash - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Store binaries as an artifact uses: actions/upload-artifact@v4 with: - name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip overwrite: true path: | ${{ inputs.artifact-paths }} diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index c0f6c4e66..7364c4ddb 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -20,19 +20,16 @@ runs: shell: bash run: | sudo apt-get -y update --fix-missing - sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev + sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release - name: Setup Python uses: actions/setup-python@v5 with: python-version: 3.x - - # - name: Cache python libs - # uses: actions/cache@v4 - # id: cache-pip # needed in if test - # with: - # path: ~/.cache/pip - # key: ${{ runner.os }}-pip + cache: pip + cache-dependency-path: | + .github/actions/** + **.ini - name: Upgrade python tools shell: bash diff --git a/.github/workflows/build_debian_src.yml b/.github/workflows/build_debian_src.yml new file mode 100644 index 000000000..714542047 --- /dev/null +++ b/.github/workflows/build_debian_src.yml @@ -0,0 +1,72 @@ +name: Build Debian Source Package + +on: + workflow_call: + secrets: + PPA_GPG_PRIVATE_KEY: + required: true + inputs: + series: + description: Ubuntu/Debian series to target + required: true + type: string + build_location: + description: Location where build will execute + required: true + type: string + +permissions: + contents: write + packages: write + +jobs: + build-debian-src: + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + path: meshtasticd + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Install deps + shell: bash + working-directory: meshtasticd + run: | + sudo apt-get update -y --fix-missing + sudo apt-get install -y software-properties-common build-essential devscripts equivs + sudo add-apt-repository ppa:meshtastic/build-tools -y + sudo apt-get update -y --fix-missing + sudo mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control + + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} + id: gpg + + - name: Get release version string + working-directory: meshtasticd + run: | + echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT + env: + BUILD_LOCATION: ${{ inputs.build_location }} + id: version + + - name: Fetch libdeps, package debian source + working-directory: meshtasticd + run: debian/ci_pack_sdeb.sh + env: + SERIES: ${{ inputs.series }} + GPG_KEY_ID: ${{ steps.gpg.outputs.keyid }} + PKG_VERSION: ${{ steps.version.outputs.deb }} + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src + overwrite: true + path: | + meshtasticd_${{ steps.version.outputs.deb }}* diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml index 13817a8cf..18787f16a 100644 --- a/.github/workflows/build_docker.yml +++ b/.github/workflows/build_docker.yml @@ -18,7 +18,7 @@ jobs: repository: ${{github.event.pull_request.head.repo.full_name}} - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Docker login @@ -39,7 +39,7 @@ jobs: context: . file: ./Dockerfile push: true - tags: meshtastic/meshtasticd:${{ steps.version.outputs.version }} + tags: meshtastic/meshtasticd:${{ steps.version.outputs.long }} - name: Docker build and push if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml index 11ba09ad7..cca839328 100644 --- a/.github/workflows/build_native.yml +++ b/.github/workflows/build_native.yml @@ -25,13 +25,13 @@ jobs: run: bin/build-native.sh - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Store binaries as an artifact uses: actions/upload-artifact@v4 with: - name: firmware-native-${{ steps.version.outputs.version }}.zip + name: firmware-native-${{ steps.version.outputs.long }}.zip overwrite: true path: | release/meshtasticd_linux_x86_64 diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml index ac63dfea4..646c6c9f3 100644 --- a/.github/workflows/build_raspbian.yml +++ b/.github/workflows/build_raspbian.yml @@ -39,13 +39,13 @@ jobs: run: bin/build-native.sh - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Store binaries as an artifact uses: actions/upload-artifact@v4 with: - name: firmware-raspbian-${{ steps.version.outputs.version }}.zip + name: firmware-raspbian-${{ steps.version.outputs.long }}.zip overwrite: true path: | release/meshtasticd_linux_aarch64 diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml index 565d9a0dc..21b1aea79 100644 --- a/.github/workflows/build_raspbian_armv7l.yml +++ b/.github/workflows/build_raspbian_armv7l.yml @@ -39,13 +39,13 @@ jobs: run: bin/build-native.sh - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Store binaries as an artifact uses: actions/upload-artifact@v4 with: - name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip + name: firmware-raspbian-armv7l-${{ steps.version.outputs.long }}.zip overwrite: true path: | release/meshtasticd_linux_armv7l diff --git a/.github/workflows/daily_packaging.yml b/.github/workflows/daily_packaging.yml new file mode 100644 index 000000000..14daae74d --- /dev/null +++ b/.github/workflows/daily_packaging.yml @@ -0,0 +1,45 @@ +name: Daily Packaging +on: + schedule: + - cron: 0 9 * * * + workflow_dispatch: + push: + branches: + - master + paths: + - debian/** + - "*.rpkg" + - .github/workflows/nightly_packaging.yml + - .github/workflows/build_debian_src.yml + - .github/workflows/package_ppa.yml + - .github/workflows/package_obs.yml + - .github/workflows/hook_copr.yml + +permissions: + contents: write + packages: write + +jobs: + package-ppa: + strategy: + fail-fast: false + matrix: + series: [plucky, oracular, noble, jammy] + uses: ./.github/workflows/package_ppa.yml + with: + ppa_repo: ppa:meshtastic/daily + series: ${{ matrix.series }} + secrets: inherit + + package-obs: + uses: ./.github/workflows/package_obs.yml + with: + obs_project: home:meshtastic:daily + series: unstable + secrets: inherit + + hook-copr: + uses: ./.github/workflows/hook_copr.yml + with: + copr_project: daily + secrets: inherit diff --git a/.github/workflows/hook_copr.yml b/.github/workflows/hook_copr.yml new file mode 100644 index 000000000..94aaca49c --- /dev/null +++ b/.github/workflows/hook_copr.yml @@ -0,0 +1,38 @@ +name: Trigger COPR build + +on: + workflow_call: + secrets: + COPR_API_CONFIG: + inputs: + copr_project: + description: COPR project to target + required: true + type: string + +permissions: + contents: write + packages: write + +jobs: + build-copr-hook: + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{ github.ref }} + repository: ${{ github.repository }} + + - name: Trigger COPR build + uses: vidplace7/copr-build@main + id: copr_build + env: + COPR_API_TOKEN_CONFIG: ${{ secrets.COPR_API_CONFIG }} + with: + owner: "@meshtastic" + package-name: meshtasticd + project-name: ${{ inputs.copr_project }} + git-remote: "${{ github.server_url }}/${{ github.repository }}.git" + committish: ${{ github.sha }} diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index a437411b5..0a0ea9954 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -137,6 +137,13 @@ jobs: package-native: uses: ./.github/workflows/package_amd64.yml + build-debian-src: + uses: ./.github/workflows/build_debian_src.yml + with: + series: UNRELEASED + build_location: local + secrets: inherit + test-native: uses: ./.github/workflows/test_native.yml @@ -192,7 +199,7 @@ jobs: run: ls -R - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Move files up @@ -201,7 +208,7 @@ jobs: - name: Repackage in single firmware zip uses: actions/upload-artifact@v4 with: - name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }} + name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} overwrite: true path: | ./firmware-*.bin @@ -218,7 +225,7 @@ jobs: - uses: actions/download-artifact@v4 with: - name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }} + name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} merge-multiple: true path: ./output @@ -232,12 +239,12 @@ jobs: chmod +x ./output/device-update.sh - name: Zip firmware - run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./output + run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output - name: Repackage in single elfs zip uses: actions/upload-artifact@v4 with: - name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip + name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip overwrite: true path: ./*.elf retention-days: 30 @@ -245,8 +252,8 @@ jobs: - uses: scruplelesswizard/comment-artifact@main if: ${{ github.event_name == 'pull_request' }} with: - name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }} - description: "Download firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip. This artifact will be available for 90 days from creation" + name: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} + description: "Download firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" github-token: ${{ secrets.GITHUB_TOKEN }} release-artifacts: @@ -260,6 +267,7 @@ jobs: package-raspbian, package-raspbian-armv7l, package-native, + build-debian-src, ] steps: - name: Checkout @@ -271,73 +279,70 @@ jobs: python-version: 3.x - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: | + echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT id: version + env: + BUILD_LOCATION: local - name: Create release - uses: actions/create-release@v1 + uses: softprops/action-gh-release@v2 id: create_release with: draft: true prerelease: true - release_name: Meshtastic Firmware ${{ steps.version.outputs.version }} Alpha - tag_name: v${{ steps.version.outputs.version }} + name: Meshtastic Firmware ${{ steps.version.outputs.long }} Alpha + tag_name: v${{ steps.version.outputs.long }} body: | Autogenerated by github action, developer should edit as required before publishing... - env: - GITHUB_TOKEN: ${{ github.token }} - name: Download deb files uses: actions/download-artifact@v4 with: - pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb + pattern: meshtasticd_${{ steps.version.outputs.long }}_*.deb merge-multiple: true path: ./output + - name: Download source deb + uses: actions/download-artifact@v4 + with: + pattern: firmware-debian-${{ steps.version.outputs.deb }}~UNRELEASED-src + merge-multiple: true + path: ./output/debian-src + + - name: Zip source deb + working-directory: output + run: zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src + # For diagnostics - name: Display structure of downloaded files run: ls -lR - - name: Add raspbian aarch64 .deb - uses: actions/upload-release-asset@v1 + - name: Add deb files to release + run: | + gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_arm64.deb + gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_armhf.deb + gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_amd64.deb + gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_arm64.deb - asset_name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb - asset_content_type: application/vnd.debian.binary-package - - - name: Add raspbian armv7l .deb - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_armhf.deb - asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb - asset_content_type: application/vnd.debian.binary-package - - - name: Add raspbian amd64 .deb - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb - asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb - asset_content_type: application/vnd.debian.binary-package + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Bump version.properties run: >- bin/bump_version.py + - name: Update debian changelog + run: >- + debian/ci_changelog.sh + - name: Create version.properties pull request uses: peter-evans/create-pull-request@v7 with: title: Bump version.properties add-paths: | version.properties + debian/changelog release-firmware: strategy: @@ -357,12 +362,12 @@ jobs: python-version: 3.x - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - uses: actions/download-artifact@v4 with: - pattern: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }} + pattern: firmware-${{matrix.arch}}-${{ steps.version.outputs.long }} merge-multiple: true path: ./output @@ -375,37 +380,24 @@ jobs: chmod +x ./output/device-update.sh - name: Zip firmware - run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./output + run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./output - uses: actions/download-artifact@v4 with: - name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip + name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip merge-multiple: true path: ./elfs - - name: Zip firmware - run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./elfs + - name: Zip debug elfs + run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip ./elfs # For diagnostics - name: Display structure of downloaded files run: ls -lR - - name: Add bins to release - uses: actions/upload-release-asset@v1 + - name: Add bins and debug elfs to release + run: | + gh release upload v${{ steps.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip + gh release upload v${{ steps.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.long }}.zip env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{needs.release-artifacts.outputs.upload_url}} - asset_path: ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip - asset_name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip - asset_content_type: application/zip - - - name: Add debug elfs to release - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{needs.release-artifacts.outputs.upload_url}} - asset_path: ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip - asset_name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip - asset_content_type: application/zip + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml index c6e82e1be..d9f041736 100644 --- a/.github/workflows/package_amd64.yml +++ b/.github/workflows/package_amd64.yml @@ -32,13 +32,13 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Download artifacts uses: actions/download-artifact@v4 with: - name: firmware-native-${{ steps.version.outputs.version }}.zip + name: firmware-native-${{ steps.version.outputs.long }}.zip merge-multiple: true - name: Display structure of downloaded files @@ -77,14 +77,14 @@ jobs: package: meshtasticd package_root: .debpkg maintainer: Jonathan Bennett - version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.* + version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.* arch: amd64 depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0 desc: Native Linux Meshtastic binary. - uses: actions/upload-artifact@v4 with: - name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb + name: meshtasticd_${{ steps.version.outputs.long }}_amd64.deb overwrite: true path: | ./*.deb diff --git a/.github/workflows/package_obs.yml b/.github/workflows/package_obs.yml new file mode 100644 index 000000000..275ffce0e --- /dev/null +++ b/.github/workflows/package_obs.yml @@ -0,0 +1,110 @@ +name: Package for OpenSUSE Build Service + +on: + workflow_call: + secrets: + OBS_PASSWORD: + required: true + PPA_GPG_PRIVATE_KEY: + required: true + inputs: + obs_project: + description: Meshtastic OBS project to target + required: true + type: string + series: + description: Debian series to target + required: true + type: string + +permissions: + contents: write + packages: write + +jobs: + build-debian-src: + uses: ./.github/workflows/build_debian_src.yml + secrets: inherit + with: + series: ${{ inputs.series }} + build_location: obs + + package-obs: + runs-on: ubuntu-24.04 + needs: build-debian-src + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + path: meshtasticd + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Install OpenSUSE Build Service deps + shell: bash + run: | + echo 'deb http://download.opensuse.org/repositories/openSUSE:/Tools/xUbuntu_24.04/ /' | sudo tee /etc/apt/sources.list.d/openSUSE:Tools.list + curl -fsSL https://download.opensuse.org/repositories/openSUSE:Tools/xUbuntu_24.04/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/openSUSE_Tools.gpg > /dev/null + sudo apt-get update -y --fix-missing + sudo apt-get install -y osc + + - name: Get release version string + working-directory: meshtasticd + run: | + echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT + env: + BUILD_LOCATION: obs + id: version + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src + merge-multiple: true + + - name: Display structure of downloaded files + run: ls -lah + + - name: Configure osc + env: + OBS_USERNAME: meshtastic + run: | + # Setup OpenSUSE Build Service credentials + mkdir -p ~/.config/osc + echo "[general]" > ~/.config/osc/oscrc + echo "apiurl=https://api.opensuse.org" >> ~/.config/osc/oscrc + echo "[https://api.opensuse.org]" >> ~/.config/osc/oscrc + echo "user=${{ env.OBS_USERNAME }}" >> ~/.config/osc/oscrc + echo "pass=${{ secrets.OBS_PASSWORD }}" >> ~/.config/osc/oscrc + echo "credentials_mgr_class=osc.credentials.PlaintextConfigFileCredentialsManager" >> ~/.config/osc/oscrc + # Create a temporary directory for osc checkout + mkdir -p osc + + # Intentionally fail if credentials are invalid + # Update secrets if this returns `401` + - name: Verify OBS authentication + run: osc token + + - name: Upload package to OBS + shell: bash + working-directory: osc + env: + OBS_PROJECT: ${{ inputs.obs_project }} + OBS_PACKAGE: meshtasticd + run: | + # Initialize the package in the current directory + osc checkout --output-dir . $OBS_PROJECT $OBS_PACKAGE + + # Remove the existing package files + rm -rf *.dsc *.tar.xz + + # Copy new package files to the directory + cp $GITHUB_WORKSPACE/*.dsc . + cp $GITHUB_WORKSPACE/*.tar.xz . + + # Add/Remove the files + osc addremove + + # Commit changes and push to OpenSUSE Build Service + osc commit -m "GitHub Actions: ${{ steps.version.outputs.deb }}~${{ inputs.series }}" diff --git a/.github/workflows/package_ppa.yml b/.github/workflows/package_ppa.yml new file mode 100644 index 000000000..a54b0bd36 --- /dev/null +++ b/.github/workflows/package_ppa.yml @@ -0,0 +1,74 @@ +name: Package for Launchpad PPA + +on: + workflow_call: + secrets: + PPA_GPG_PRIVATE_KEY: + required: true + inputs: + ppa_repo: + description: Meshtastic PPA to target + required: true + type: string + series: + description: Ubuntu series to target + required: true + type: string + +permissions: + contents: write + packages: write + +jobs: + build-debian-src: + uses: ./.github/workflows/build_debian_src.yml + secrets: inherit + with: + series: ${{ inputs.series }} + build_location: ppa + + package-ppa: + runs-on: ubuntu-24.04 + needs: build-debian-src + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + path: meshtasticd + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Install deps + shell: bash + run: | + sudo apt-get update -y --fix-missing + sudo apt-get install -y dput + + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} + id: gpg + + - name: Get release version string + working-directory: meshtasticd + run: | + echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT + env: + BUILD_LOCATION: ppa + id: version + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src + merge-multiple: true + + - name: Display structure of downloaded files + run: ls -lah + + - name: Publish with dput + if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }} + run: | + dput ${{ inputs.ppa_repo }} meshtasticd_${{ steps.version.outputs.deb }}~${{ inputs.series }}_source.changes diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml index a4cd49573..62613f85f 100644 --- a/.github/workflows/package_raspbian.yml +++ b/.github/workflows/package_raspbian.yml @@ -32,13 +32,13 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Download artifacts uses: actions/download-artifact@v4 with: - name: firmware-raspbian-${{ steps.version.outputs.version }}.zip + name: firmware-raspbian-${{ steps.version.outputs.long }}.zip merge-multiple: true - name: Display structure of downloaded files @@ -77,14 +77,14 @@ jobs: package: meshtasticd package_root: .debpkg maintainer: Jonathan Bennett - version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.* + version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.* arch: arm64 depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0 desc: Native Linux Meshtastic binary. - uses: actions/upload-artifact@v4 with: - name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb + name: meshtasticd_${{ steps.version.outputs.long }}_arm64.deb overwrite: true path: | ./*.deb diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml index c4cc5c673..8a9df1710 100644 --- a/.github/workflows/package_raspbian_armv7l.yml +++ b/.github/workflows/package_raspbian_armv7l.yml @@ -32,13 +32,13 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Download artifacts uses: actions/download-artifact@v4 with: - name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip + name: firmware-raspbian-armv7l-${{ steps.version.outputs.long }}.zip merge-multiple: true - name: Display structure of downloaded files @@ -77,14 +77,14 @@ jobs: package: meshtasticd package_root: .debpkg maintainer: Jonathan Bennett - version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.* + version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.* arch: armhf depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0 desc: Native Linux Meshtastic binary. - uses: actions/upload-artifact@v4 with: - name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb + name: meshtasticd_${{ steps.version.outputs.long }}_armhf.deb overwrite: true path: | ./*.deb diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml new file mode 100644 index 000000000..afb7319ed --- /dev/null +++ b/.github/workflows/release_channels.yml @@ -0,0 +1,38 @@ +name: Trigger release workflows upon Publish + +on: + release: + types: [published, released] + +permissions: + contents: write + packages: write + +jobs: + package-ppa: + strategy: + fail-fast: false + matrix: + series: [plucky, oracular, noble, jammy] + uses: ./.github/workflows/package_ppa.yml + with: + ppa_repo: |- + ppa:meshtastic/${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }} + series: ${{ matrix.series }} + secrets: inherit + + package-obs: + uses: ./.github/workflows/package_obs.yml + with: + obs_project: |- + home:meshtastic:${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }} + series: |- + ${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }} + secrets: inherit + + # hook-copr: + # uses: ./.github/workflows/hook_copr.yml + # with: + # copr_project: |- + # ${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }} + # secrets: inherit diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml index d016635ef..c7b0ef34c 100644 --- a/.github/workflows/test_native.yml +++ b/.github/workflows/test_native.yml @@ -55,14 +55,14 @@ jobs: - name: Get release version string if: always() # run this step even if previous step failed - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Save coverage information uses: actions/upload-artifact@v4 if: always() # run this step even if previous step failed with: - name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.version }}.zip + name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}.zip overwrite: true path: ./coverage_*.info @@ -81,7 +81,7 @@ jobs: uses: ./.github/actions/setup-native - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version # Disable (comment-out) BUILD_EPOCH. It causes a full rebuild between tests and resets the @@ -90,13 +90,13 @@ jobs: run: sed -i 's/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini - name: PlatformIO Tests - run: platformio test -e coverage --junit-output-path testreport.xml + run: platformio test -e coverage -v --junit-output-path testreport.xml - name: Save test results if: always() # run this step even if previous step failed uses: actions/upload-artifact@v4 with: - name: platformio-test-report-${{ steps.version.outputs.version }}.zip + name: platformio-test-report-${{ steps.version.outputs.long }}.zip overwrite: true path: ./testreport.xml @@ -111,7 +111,7 @@ jobs: uses: actions/upload-artifact@v4 if: always() # run this step even if previous step failed with: - name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.version }}.zip + name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}.zip overwrite: true path: ./coverage_*.info @@ -133,13 +133,13 @@ jobs: repository: ${{github.event.pull_request.head.repo.full_name}} - name: Get release version string - run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT id: version - name: Download test artifacts uses: actions/download-artifact@v4 with: - name: platformio-test-report-${{ steps.version.outputs.version }}.zip + name: platformio-test-report-${{ steps.version.outputs.long }}.zip merge-multiple: true - name: Test Report @@ -152,7 +152,7 @@ jobs: - name: Download coverage artifacts uses: actions/download-artifact@v4 with: - pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.version }}.zip + pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip path: code-coverage-report merge-multiple: true @@ -165,5 +165,5 @@ jobs: - name: Save Code Coverage Report uses: actions/upload-artifact@v4 with: - name: code-coverage-report-${{ steps.version.outputs.version }}.zip + name: code-coverage-report-${{ steps.version.outputs.long }}.zip path: code-coverage-report diff --git a/.github/workflows/trunk_format_pr.yml b/.github/workflows/trunk_format_pr.yml index c5c20b465..0d6eb6041 100644 --- a/.github/workflows/trunk_format_pr.yml +++ b/.github/workflows/trunk_format_pr.yml @@ -22,12 +22,16 @@ jobs: - name: Run Trunk Fmt run: trunk fmt + - name: Get release version string + run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + - name: Commit and push changes run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" git add . - git commit -m "Add firmware version ${{ steps.version.outputs.version }}" + git commit -m "Add firmware version ${{ steps.version.outputs.long }}" git push - name: Comment on PR diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 2732ab760..e7b3c1f40 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -12,6 +12,7 @@ jobs: submodules: true - name: Update submodule + if: ${{ github.ref == 'refs/heads/master' }} run: | git submodule update --remote protobufs diff --git a/.gitignore b/.gitignore index 28f9a24cc..803aee139 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ .pio +pio +pio.tar +web +web.tar # ignore vscode IDE settings files .vscode/* @@ -30,4 +34,4 @@ release/ .vscode/extensions.json /compile_commands.json src/mesh/raspihttp/certificate.pem -src/mesh/raspihttp/private_key.pem +src/mesh/raspihttp/private_key.pem \ No newline at end of file diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 27590c6f8..9b478bdfc 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -25,8 +25,8 @@ lib_deps = ${networking_base.lib_deps} ${radiolib_base.lib_deps} rweather/Crypto@^0.4.0 - https://github.com/lovyan03/LovyanGFX.git#1401c28a47646fe00538d487adcb2eb3c72de805 - https://github.com/pine64/libch341-spi-userspace#8695637adeabf5abf5601d8e82cb0ba19ce9ec46 + lovyan03/LovyanGFX@^1.2.0 + https://github.com/pine64/libch341-spi-userspace#a9b17e3452f7fb747000d9b4ad4409155b39f6ef build_flags = ${arduino_base.build_flags} @@ -41,4 +41,4 @@ build_flags = -lgpiod -lyaml-cpp -li2c - -std=c++17 \ No newline at end of file + -std=c++17 diff --git a/arch/rp2xx0/rp2350.ini b/arch/rp2xx0/rp2350.ini index c5849ff2a..ab16e24b4 100644 --- a/arch/rp2xx0/rp2350.ini +++ b/arch/rp2xx0/rp2350.ini @@ -7,12 +7,12 @@ platform_packages = framework-arduinopico@https://github.com/earlephilhower/ardu board_build.core = earlephilhower board_build.filesystem_size = 0.5m build_flags = - ${arduino_base.build_flags} -Wno-unused-variable + ${arduino_base.build_flags} -Wno-unused-variable -Wcast-align -Isrc/platform/rp2xx0 - -D__PLAT_RP2040__ + -D__PLAT_RP2350__ # -D _POSIX_THREADS build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - lib_ignore = BluetoothOTA diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index e68b01ba3..c8f181308 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -23,6 +23,47 @@ Lora: # Busy: 20 # Reset: 18 +### The Radxa Zero 3E/W employs multiple gpio chips. +### Each gpio pin must be unique, but can be assigned to a specific gpio chip and line. +### In case solely a no. is given, the default gpio chip and pin == line will be employed. +### +# Module: sx1262 # Radxa Zero 3E/W + Ebyte E22-900M30S +# DIO2_AS_RF_SWITCH: true +# DIO3_TCXO_VOLTAGE: 1.8 +# CS: # NSS PIN_24 -> chip 4, line 22 +# pin: 24 +# gpiochip: 4 +# line: 22 +# SCK: # SCK PIN_23 -> chip 4, line 18 +# pin: 23 +# gpiochip: 4 +# line: 18 +# Busy: # BUSY PIN_29 -> chip 3!, line 11 +# pin: 29 +# gpiochip: 3 +# line: 11 +# MOSI: # MOSI PIN_19 -> chip 4, line 19 +# pin: 19 +# gpiochip: 4 +# line: 19 +# MISO: # MISO PIN_21 -> chip 4, line 21 +# pin: 21 +# gpiochip: 4 +# line: 21 +# Reset: # NRST PIN_27 -> chip 4, line 10 +# pin: 27 +# gpiochip: 4 +# line: 10 +# IRQ: # DIO1 PIN_28 -> chip 4, line 11 +# pin: 28 +# gpiochip: 4 +# line: 11 +# RXen: # RXEN PIN_22 -> chip 3!, line 17 +# pin: 22 +# gpiochip: 3 +# line: 17 +# TXen: RADIOLIB_NC # TXEN no PIN, no line, fallback to default gpio chip + # Module: sx1268 # SX1268-based modules, tested with Ebyte E22 400M33S # CS: 21 # IRQ: 16 @@ -39,7 +80,7 @@ Lora: # spiSpeed: 2000000 -### Set gpio chip to use in /dev/. Defaults to 0. +### Set default/fallback gpio chip to use in /dev/. Defaults to 0. ### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4 # gpiochip: 4 @@ -147,4 +188,4 @@ General: MaxMessageQueue: 100 ConfigDirectory: /etc/meshtasticd/config.d/ # MACAddress: AA:BB:CC:DD:EE:FF -# MACAddressSource: eth0 \ No newline at end of file +# MACAddressSource: eth0 diff --git a/bin/device-install.sh b/bin/device-install.sh index e09c61ba6..4698b88e5 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -73,7 +73,7 @@ shift "$((OPTIND - 1))" if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then echo "Trying to flash ${FILENAME}, but first erasing and writing system information" $ESPTOOL_CMD erase_flash - $ESPTOOL_CMD write_flash 0x00 ${FILENAME} + $ESPTOOL_CMD write_flash 0x00 "${FILENAME}" # Account for S3 board's different OTA partition if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ] && [ -n "${FILENAME##*"unphone"*}" ]; then if [ -n "${FILENAME##*"esp32c3"*}" ]; then diff --git a/bin/readprops.py b/bin/readprops.py index 4b730658a..731a3d0d3 100644 --- a/bin/readprops.py +++ b/bin/readprops.py @@ -1,6 +1,8 @@ import configparser import subprocess - +import os +run_number = os.getenv('GITHUB_RUN_NUMBER', '0') +build_location = os.getenv('BUILD_LOCATION', 'local') def readProps(prefsLoc): """Read the version of our project as a string""" @@ -11,6 +13,7 @@ def readProps(prefsLoc): verObj = dict( short="{}.{}.{}".format(version["major"], version["minor"], version["build"]), long="unset", + deb="unset", ) # Try to find current build SHA if if the workspace is clean. This could fail if git is not installed @@ -27,16 +30,16 @@ def readProps(prefsLoc): # if isDirty: # # short for 'dirty', we want to keep our verstrings source for protobuf reasons # suffix = sha + "-d" - verObj["long"] = "{}.{}.{}.{}".format( - version["major"], version["minor"], version["build"], suffix - ) + verObj["long"] = "{}.{}".format(verObj["short"], suffix) + verObj["deb"] = "{}.{}~{}{}".format(verObj["short"], run_number, build_location, sha) except: # print("Unexpected error:", sys.exc_info()[0]) # traceback.print_exc() verObj["long"] = verObj["short"] + verObj["deb"] = "{}.{}~{}".format(verObj["short"], run_number, build_location) # print("firmware version " + verStr) return verObj -# print("path is" + ','.join(sys.path)) +# print("path is" + ','.join(sys.path)) \ No newline at end of file diff --git a/bin/rpkg.macros b/bin/rpkg.macros new file mode 100644 index 000000000..2bbb203de --- /dev/null +++ b/bin/rpkg.macros @@ -0,0 +1,12 @@ +function meshtastic_version { + meshtastic_version=$(python3 bin/buildinfo.py short) + echo -n "$meshtastic_version" +} +function git_commits_num { + total_commits=$(git rev-list --all --count) + echo -n "$total_commits" +} +function git_commit_sha { + commit_sha=$(git rev-parse --short HEAD) + echo -n "$commit_sha" +} \ No newline at end of file diff --git a/debian/changelog b/debian/changelog index 5dd6fb1c0..a1a359cfb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,7 @@ -meshtasticd (2.5.19) unstable; urgency=medium +meshtasticd (2.5.20.0) UNRELEASED; urgency=medium * Initial packaging + * GitHub Actions Automatic version bump + * GitHub Actions Automatic version bump - -- Austin Lane Thu, 02 Jan 2025 12:00:00 +0000 \ No newline at end of file + -- Austin Lane Wed, 15 Jan 2025 14:08:54 +0000 diff --git a/debian/ci_changelog.sh b/debian/ci_changelog.sh new file mode 100755 index 000000000..f7e875977 --- /dev/null +++ b/debian/ci_changelog.sh @@ -0,0 +1,7 @@ +#!/usr/bin/bash +export DEBEMAIL="github-actions[bot]@users.noreply.github.com" +PKG_VERSION=$(python3 bin/buildinfo.py short) + +dch --newversion "$PKG_VERSION.0" \ + --distribution UNRELEASED \ + "GitHub Actions Automatic version bump" diff --git a/debian/ci_pack_sdeb.sh b/debian/ci_pack_sdeb.sh new file mode 100755 index 000000000..1f311af93 --- /dev/null +++ b/debian/ci_pack_sdeb.sh @@ -0,0 +1,23 @@ +#!/usr/bin/bash +export DEBEMAIL="jbennett@incomsystems.biz" +export PLATFORMIO_LIBDEPS_DIR=pio/libdeps +export PLATFORMIO_PACKAGES_DIR=pio/packages +export PLATFORMIO_CORE_DIR=pio/core + +# Download libraries to `pio` +platformio pkg install -e native +platformio pkg install -e native -t platformio/tool-scons@4.40502.0 +# Compress `pio` directory to prevent dh_clean from sanitizing it +tar -cf pio.tar pio/ +rm -rf pio +# Download the latest meshtastic/web release build.tar to `web.tar` +curl -L https://github.com/meshtastic/web/releases/download/latest/build.tar -o web.tar + +package=$(dpkg-parsechangelog --show-field Source) + +rm -rf debian/changelog +dch --create --distribution "$SERIES" --package "$package" --newversion "$PKG_VERSION~$SERIES" \ + "GitHub Actions Automatic packaging for $PKG_VERSION~$SERIES" + +# Build the source deb +debuild -S -nc -k"$GPG_KEY_ID" diff --git a/debian/control b/debian/control index b00c6d78e..bb79d1958 100644 --- a/debian/control +++ b/debian/control @@ -3,8 +3,11 @@ Section: misc Priority: optional Maintainer: Austin Lane Build-Depends: debhelper-compat (= 13), - python3-pip, - python3-venv, + tar, + gzip, + platformio, + python3-protobuf, + python3-grpcio, git, g++, pkg-config, @@ -12,7 +15,11 @@ Build-Depends: debhelper-compat (= 13), libgpiod-dev, libbluetooth-dev, libusb-1.0-0-dev, - libi2c-dev + libi2c-dev, + openssl, + libssl-dev, + libulfius-dev, + liborcania-dev Standards-Version: 4.6.2 Homepage: https://github.com/meshtastic/firmware Rules-Requires-Root: no @@ -22,4 +29,4 @@ Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends} Description: Meshtastic daemon for communicating with Meshtastic devices Meshtastic is an off-grid text communication platform that uses inexpensive - LoRa radios. + LoRa radios. \ No newline at end of file diff --git a/debian/meshtasticd.dirs b/debian/meshtasticd.dirs index cf1ba7a37..5f57ff7be 100644 --- a/debian/meshtasticd.dirs +++ b/debian/meshtasticd.dirs @@ -1,3 +1,4 @@ etc/meshtasticd etc/meshtasticd/config.d etc/meshtasticd/available.d +usr/share/meshtasticd/web \ No newline at end of file diff --git a/debian/meshtasticd.install b/debian/meshtasticd.install index 04bf34daf..da1b0685d 100644 --- a/debian/meshtasticd.install +++ b/debian/meshtasticd.install @@ -4,3 +4,5 @@ bin/config.yaml etc/meshtasticd bin/config.d/* etc/meshtasticd/available.d bin/meshtasticd.service lib/systemd/system + +web/* usr/share/meshtasticd/web \ No newline at end of file diff --git a/debian/rules b/debian/rules index c60d611a4..a1a27c2f2 100755 --- a/debian/rules +++ b/debian/rules @@ -1,17 +1,23 @@ #!/usr/bin/make -f +# export DH_VERBOSE = 1 # Use the "dh" sequencer %: dh $@ +# https://docs.platformio.org/en/latest/envvars.html +PIO_ENV:=\ + PLATFORMIO_CORE_DIR=pio/core \ + PLATFORMIO_LIBDEPS_DIR=pio/libdeps \ + PLATFORMIO_PACKAGES_DIR=pio/packages + override_dh_auto_build: - # Terrible hack to use modern platformio to build the native version - # python3 -m venv venv - # . venv/bin/activate - pip install platformio --break-system-packages - platformio run -e native - # deactivate - # rm -rf venv + # Extract tarballs within source deb + tar -xf pio.tar + mkdir -p web && tar -xf web.tar -C web + gunzip web/ -r + # Build with platformio + $(PIO_ENV) platformio run -e native # Move the binary and default config to the correct name mv .pio/build/native/program .pio/build/native/meshtasticd cp bin/config-dist.yaml bin/config.yaml diff --git a/debian/source/include-binaries b/debian/source/include-binaries new file mode 100644 index 000000000..0c9848b72 --- /dev/null +++ b/debian/source/include-binaries @@ -0,0 +1,2 @@ +pio.tar +web.tar \ No newline at end of file diff --git a/debian/source/options b/debian/source/options new file mode 100644 index 000000000..0553b485d --- /dev/null +++ b/debian/source/options @@ -0,0 +1 @@ +extend-diff-ignore = "\.pio" \ No newline at end of file diff --git a/debian/update_changelog.sh b/debian/update_changelog.sh deleted file mode 100644 index 60af34511..000000000 --- a/debian/update_changelog.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/bash -export DEBEMAIL="github-actions[bot]@users.noreply.github.com" -dch --newversion "$(python3 bin/buildinfo.py short)-1" \ - --distribution unstable \ - "GitHub Actions Automatic version bump" diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg new file mode 100644 index 000000000..1819897b0 --- /dev/null +++ b/meshtasticd.spec.rpkg @@ -0,0 +1,91 @@ +# meshtasticd spec file for RPM-based distributions +# +# Build locally with: +# ``` +# sudo dnf install rpkg-util +# rpkg local +# ``` +# +# See: +# - https://docs.pagure.org/rpkg-util/v3/index.html +# - https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/ + +Name: meshtasticd +# Version Ex: 2.5.19 +Version: {{{ meshtastic_version }}} +# Release Ex: 9127.daily.gitd7f5f620.fc41 +Release: {{{ git_commits_num }}}%{?copr_projectname:.%{copr_projectname}}.git{{{ git_commit_sha }}}%{?dist} +VCS: {{{ git_dir_vcs }}} +Summary: Meshtastic daemon for communicating with Meshtastic devices + +License: GPL-3.0 +URL: https://github.com/meshtastic/firmware +Source0: {{{ git_dir_pack }}} +Source1: https://github.com/meshtastic/web/releases/download/latest/build.tar + +BuildRequires: systemd-rpm-macros +BuildRequires: python3-devel +BuildRequires: platformio +BuildRequires: python3dist(protobuf) +BuildRequires: python3dist(grpcio[protobuf]) +BuildRequires: python3dist(grpcio-tools) +BuildRequires: git-core +BuildRequires: gcc-c++ +BuildRequires: pkgconfig(yaml-cpp) +BuildRequires: pkgconfig(libgpiod) +BuildRequires: pkgconfig(bluez) +BuildRequires: pkgconfig(libusb-1.0) +BuildRequires: libi2c-devel +# Web components: +BuildRequires: pkgconfig(openssl) +BuildRequires: pkgconfig(liborcania) +BuildRequires: pkgconfig(libyder) +BuildRequires: pkgconfig(libulfius) + +%description +Meshtastic daemon for controlling Meshtastic devices. Meshtastic is an off-grid +text communication platform that uses inexpensive LoRa radios. + +%prep +{{{ git_dir_setup_macro }}} +# Unpack the web files +mkdir -p web +tar -xf %{SOURCE1} -C web +gzip -dr web + +%build +# Use the “native” environment from platformio to build a Linux binary +platformio run -e native + +%install +mkdir -p %{buildroot}%{_sbindir} +install -m 0755 .pio/build/native/program %{buildroot}%{_sbindir}/meshtasticd + +mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd +install -m 0644 bin/config-dist.yaml %{buildroot}%{_sysconfdir}/meshtasticd/config.yaml +mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/config.d +mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/available.d +cp -r bin/config.d/* %{buildroot}%{_sysconfdir}/meshtasticd/available.d + +install -D -m 0644 bin/meshtasticd.service %{buildroot}%{_unitdir}/meshtasticd.service + +# Install the web files under /usr/share/meshtasticd/web +mkdir -p %{buildroot}%{_datadir}/meshtasticd/web +cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web + +%files +%license LICENSE +%doc README.md +%{_sbindir}/meshtasticd +%dir %{_sysconfdir}/meshtasticd +%dir %{_sysconfdir}/meshtasticd/config.d +%dir %{_sysconfdir}/meshtasticd/available.d +%config(noreplace) %{_sysconfdir}/meshtasticd/config.yaml +%config %{_sysconfdir}/meshtasticd/available.d/* +%{_unitdir}/meshtasticd.service +%dir %{_datadir}/meshtasticd +%dir %{_datadir}/meshtasticd/web +%{_datadir}/meshtasticd/web/* + +%changelog +%autochangelog \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 76ba2cfe1..d552826ff 100644 --- a/platformio.ini +++ b/platformio.ini @@ -3,43 +3,7 @@ [platformio] default_envs = tbeam -;default_envs = pico -;default_envs = tbeam-s3-core -;default_envs = tbeam0.7 -;default_envs = heltec-v1 -;default_envs = heltec-v2_0 -;default_envs = heltec-v2_1 -;default_envs = heltec-wireless-tracker -;default_envs = chatter2 -;default_envs = tlora-v1 -;default_envs = tlora_v1_3 -;default_envs = tlora-v2 -;default_envs = tlora-v2-1-1_6 -;default_envs = tlora-v2-1-1_6-tcxo -;default_envs = tlora-v3-3-0-tcxo -;default_envs = tlora-t3s3-v1 -;default_envs = t-echo -;default_envs = canaryone -;default_envs = native -;default_envs = nano-g1 -;default_envs = pca10059_diy_eink -;default_envs = meshtastic-diy-v1 -;default_envs = meshtastic-diy-v1_1 -;default_envs = meshtastic-dr-dev -;default_envs = m5stack-coreink -;default_envs = rak4631 -;default_envs = rak4631_eth_gw -;default_envs = rak2560 -;default_envs = rak11310 -;default_envs = rak_wismeshtap -;default_envs = wio-e5 -;default_envs = radiomaster_900_bandit_nano -;default_envs = radiomaster_900_bandit_micro -;default_envs = radiomaster_900_bandit -;default_envs = heltec_vision_master_t190 -;default_envs = heltec_vision_master_e213 -;default_envs = heltec_vision_master_e290 -;default_envs = heltec_mesh_node_t114 + extra_configs = arch/*/*.ini variants/*/platformio.ini @@ -56,7 +20,7 @@ extra_scripts = bin/platformio-custom.py build_flags = -Wno-missing-field-initializers -Wno-format - -Isrc -Isrc/mesh -Isrc/mesh/generated -Isrc/gps -Isrc/buzz -Wl,-Map,.pio/build/output.map + -Isrc -Isrc/mesh -Isrc/mesh/generated -Isrc/gps -Isrc/buzz -Wl,-Map,${platformio.build_dir}/output.map -DUSE_THREAD_NAMES -DTINYGPS_OPTION_NO_CUSTOM_FIELDS -DPB_ENABLE_MALLOC=1 @@ -85,7 +49,7 @@ build_flags = -Wno-missing-field-initializers -DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1 -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware #-DBUILD_EPOCH=$UNIX_TIME - ;-D OLED_PL + #-D OLED_PL=1 monitor_speed = 115200 monitor_filters = direct @@ -126,8 +90,7 @@ lib_deps = [radiolib_base] lib_deps = - ; jgromes/RadioLib@7.1.0 - https://github.com/jgromes/RadioLib.git#92b687821ff4e6c358d866f84566f66672ab02b8 + jgromes/RadioLib@7.1.2 ; Common libs for environmental measurements in telemetry module ; (not included in native / portduino) @@ -165,6 +128,7 @@ lib_deps = mprograms/QMC5883LCompass@1.2.3 dfrobot/DFRobot_RTU@1.0.3 https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d + https://github.com/DFRobot/DFRobot_RainfallSensor#38fea5e02b40a5430be6dab39a99a6f6347d667e robtillaart/INA226@0.6.0 ; Health Sensor Libraries diff --git a/protobufs b/protobufs index c55f120a9..fde27e4ef 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c55f120a9c1ce90c85e4826907a0b9bcb2d5f5a2 +Subproject commit fde27e4ef0fcee967063ba353422ed5f9a1c4790 diff --git a/rpkg.conf b/rpkg.conf new file mode 100644 index 000000000..f574f9a13 --- /dev/null +++ b/rpkg.conf @@ -0,0 +1,2 @@ +[rpkg] +user_macros = "${git_props:root}/bin/rpkg.macros" diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 3f64b3b3e..5175a2680 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -190,6 +190,20 @@ int32_t ButtonThread::runOnce() case 4: digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW); break; +#endif +#if defined(RAK_4631) + // 5 clicks: start accelerometer/magenetometer calibration for 30 seconds + case 5: + if (accelerometerThread) { + accelerometerThread->calibrate(30); + } + break; + // 6 clicks: start accelerometer/magenetometer calibration for 60 seconds + case 6: + if (accelerometerThread) { + accelerometerThread->calibrate(60); + } + break; #endif // No valid multipress action default: diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 6d8ff835c..1f2994b29 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -203,7 +203,7 @@ std::vector getFiles(const char *dirname, uint8_t levels) file.close(); } } else { - meshtastic_FileInfo fileInfo = {"", file.size()}; + meshtastic_FileInfo fileInfo = {"", static_cast(file.size())}; #ifdef ARCH_ESP32 strcpy(fileInfo.file_name, file.path()); #else diff --git a/src/SafeFile.cpp b/src/SafeFile.cpp index c5d7b335e..f874164ae 100644 --- a/src/SafeFile.cpp +++ b/src/SafeFile.cpp @@ -6,6 +6,13 @@ static File openFile(const char *filename, bool fullAtomic) { concurrency::LockGuard g(spiLock); + LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic); +#ifdef ARCH_NRF52 + lfs_assert_failed = false; + File file = FSCom.open(filename, FILE_O_WRITE); + file.seek(0); + return file; +#endif if (!fullAtomic) FSCom.remove(filename); // Nuke the old file to make space (ignore if it !exists) @@ -14,7 +21,6 @@ static File openFile(const char *filename, bool fullAtomic) // clear any previous LFS errors lfs_assert_failed = false; - return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE); } @@ -55,8 +61,15 @@ bool SafeFile::close() return false; spiLock->lock(); +#ifdef ARCH_NRF52 + f.truncate(); +#endif f.close(); spiLock->unlock(); + +#ifdef ARCH_NRF52 + return true; +#endif if (!testReadback()) return false; diff --git a/src/configuration.h b/src/configuration.h index 2c77b55e3..6f5255ec9 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -145,6 +145,7 @@ along with this program. If not, see . #define OPT3001_ADDR_ALT 0x44 #define MLX90632_ADDR 0x3A #define DFROBOT_LARK_ADDR 0x42 +#define DFROBOT_RAIN_ADDR 0x1d #define NAU7802_ADDR 0x2A #define MAX30102_ADDR 0x57 #define MLX90614_ADDR_DEF 0x5A diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 2561a8e17..faa94c7d3 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -66,6 +66,7 @@ class ScanI2C CGRADSENS, INA226, NXP_SE050, + DFROBOT_RAIN, } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 96e20999d..99f0d9fbf 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -84,23 +84,33 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const return o_probe; } uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation ®isterLocation, - ScanI2CTwoWire::ResponseWidth responseWidth) const + ScanI2CTwoWire::ResponseWidth responseWidth, bool zeropad = false) const { uint16_t value = 0x00; TwoWire *i2cBus = fetchI2CBus(registerLocation.i2cAddress); i2cBus->beginTransmission(registerLocation.i2cAddress.address); i2cBus->write(registerLocation.registerAddress); + if (zeropad) { + // Lark Commands need the argument list length in 2 bytes. + i2cBus->write((int)0); + i2cBus->write((int)0); + } i2cBus->endTransmission(); delay(20); i2cBus->requestFrom(registerLocation.i2cAddress.address, responseWidth); - if (i2cBus->available() == 2) { + if (i2cBus->available() > 1) { // Read MSB, then LSB value = (uint16_t)i2cBus->read() << 8; value |= i2cBus->read(); } else if (i2cBus->available()) { value = i2cBus->read(); } + // Drain excess bytes + for (uint8_t i = 0; i < responseWidth - 1; i++) { + if (i2cBus->available()) + i2cBus->read(); + } return value; } @@ -286,7 +296,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) RESPONSE_PAYLOAD 0x01 RESPONSE_PAYLOAD+1 0x00 */ - registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x05), 2); + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x05), 6, true); LOG_DEBUG("Register MFG_UID 05: 0x%x", registerValue); if (registerValue == 0x5305) { logFoundDevice("DFRobot Lark", (uint8_t)addr.address); @@ -402,6 +412,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address); + SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); #ifdef HAS_TPS65233 SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address); #endif diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index d0af7cde6..6988091ad 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -53,7 +53,7 @@ class ScanI2CTwoWire : public ScanI2C concurrency::Lock lock; - uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const; + uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth, bool) const; DeviceType probeOLED(ScanI2C::DeviceAddress) const; diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index e88e774bd..863f956cf 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -35,7 +35,11 @@ template std::size_t array_count(const T (&)[N]) } #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) +#if defined(RAK2560) +HardwareSerial *GPS::_serial_gps = &Serial2; +#else HardwareSerial *GPS::_serial_gps = &Serial1; +#endif #elif defined(ARCH_RP2040) SerialUART *GPS::_serial_gps = &Serial1; #else diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 3cb39e8ec..ce416156f 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -278,6 +278,10 @@ class Screen : public concurrency::OSThread bool hasHeading() { return hasCompass; } long getHeading() { return compassHeading; } + + void setEndCalibration(uint32_t _endCalibrationAt) { endCalibrationAt = _endCalibrationAt; } + uint32_t getEndCalibration() { return endCalibrationAt; } + // functions for display brightness void increaseBrightness(); void decreaseBrightness(); @@ -673,6 +677,8 @@ class Screen : public concurrency::OSThread bool hasCompass = false; float compassHeading; + uint32_t endCalibrationAt; + /// Holds state for debug information DebugInfo debugInfo; diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 81eb717cd..f6abec0f5 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -16,49 +16,61 @@ #include "graphics/fonts/OLEDDisplayFontsCS.h" #endif +#ifdef OLED_PL +#define FONT_SMALL_LOCAL ArialMT_Plain_10_PL +#else +#ifdef OLED_RU +#define FONT_SMALL_LOCAL ArialMT_Plain_10_RU +#else +#ifdef OLED_UA +#define FONT_SMALL_LOCAL ArialMT_Plain_10_UA // Height: 13 +#else +#ifdef OLED_CS +#define FONT_SMALL_LOCAL ArialMT_Plain_10_CS +#else +#define FONT_SMALL_LOCAL ArialMT_Plain_10 // Height: 13 +#endif +#endif +#endif +#endif +#ifdef OLED_PL +#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_PL // Height: 19 +#else +#ifdef OLED_UA +#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_UA // Height: 19 +#else +#ifdef OLED_CS +#define FONT_MEDIUM_LOCAL ArialMT_Plain_16_CS +#else +#define FONT_MEDIUM_LOCAL ArialMT_Plain_16 // Height: 19 +#endif +#endif +#endif +#ifdef OLED_PL +#define FONT_LARGE_LOCAL ArialMT_Plain_24_PL // Height: 28 +#else +#ifdef OLED_UA +#define FONT_LARGE_LOCAL ArialMT_Plain_24_UA // Height: 28 +#else +#ifdef OLED_CS +#define FONT_LARGE_LOCAL ArialMT_Plain_24_CS // Height: 28 +#else +#define FONT_LARGE_LOCAL ArialMT_Plain_24 // Height: 28 +#endif +#endif +#endif + #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts -#define FONT_SMALL ArialMT_Plain_16 // Height: 19 -#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 -#define FONT_LARGE ArialMT_Plain_24 // Height: 28 +#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 +#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28 +#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 #else -#ifdef OLED_PL -#define FONT_SMALL ArialMT_Plain_10_PL -#else -#ifdef OLED_RU -#define FONT_SMALL ArialMT_Plain_10_RU -#else -#ifdef OLED_UA -#define FONT_SMALL ArialMT_Plain_10_UA // Height: 13 -#else -#ifdef OLED_CS -#define FONT_SMALL ArialMT_Plain_10_CS -#else -#define FONT_SMALL ArialMT_Plain_10 // Height: 13 -#endif -#endif -#endif -#endif -#ifdef OLED_UA -#define FONT_MEDIUM ArialMT_Plain_16_UA // Height: 19 -#else -#ifdef OLED_CS -#define FONT_MEDIUM ArialMT_Plain_16_CS -#else -#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 -#endif -#endif -#ifdef OLED_UA -#define FONT_LARGE ArialMT_Plain_24_UA // Height: 28 -#else -#ifdef OLED_CS -#define FONT_LARGE ArialMT_Plain_24_CS // Height: 28 -#else -#define FONT_LARGE ArialMT_Plain_24 // Height: 28 -#endif -#endif +#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13 +#define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19 +#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 #endif #define _fontHeight(font) ((font)[1] + 1) // height is position 1 diff --git a/src/graphics/fonts/OLEDDisplayFontsCS.cpp b/src/graphics/fonts/OLEDDisplayFontsCS.cpp index 5c17e9177..67208b4d9 100644 --- a/src/graphics/fonts/OLEDDisplayFontsCS.cpp +++ b/src/graphics/fonts/OLEDDisplayFontsCS.cpp @@ -7,7 +7,7 @@ const uint8_t ArialMT_Plain_10_CS[] PROGMEM = { 0x20, // First char: 32 0xE0, // Number of chars: 224 // Jump Table: - 0xFF, 0xFF, 0x00, 0x0A, // 32 + 0xFF, 0xFF, 0x00, 0x03, // 32 0x00, 0x00, 0x04, 0x03, // 33 0x00, 0x04, 0x05, 0x04, // 34 0x00, 0x09, 0x09, 0x06, // 35 @@ -453,7 +453,7 @@ const uint8_t ArialMT_Plain_16_CS[] PROGMEM = { 0x20, // First char: 32 0xE0, // Number of chars: 224 // Jump Table: - 0xFF, 0xFF, 0x00, 0x10, // 32 + 0xFF, 0xFF, 0x00, 0x04, // 32 0x00, 0x00, 0x08, 0x04, // 33 0x00, 0x08, 0x0D, 0x06, // 34 0x00, 0x15, 0x1A, 0x0A, // 35 @@ -1036,7 +1036,7 @@ const uint8_t ArialMT_Plain_24_CS[] PROGMEM = { 0x20, // First char: 32 0xE0, // Number of chars: 224 // Jump Table: - 0xFF, 0xFF, 0x00, 0x18, // 32 + 0xFF, 0xFF, 0x00, 0x06, // 32 0x00, 0x00, 0x13, 0x06, // 33 0x00, 0x13, 0x1A, 0x08, // 34 0x00, 0x2D, 0x33, 0x0E, // 35 diff --git a/src/graphics/fonts/OLEDDisplayFontsPL.cpp b/src/graphics/fonts/OLEDDisplayFontsPL.cpp index 03fdab5fa..1f43967aa 100644 --- a/src/graphics/fonts/OLEDDisplayFontsPL.cpp +++ b/src/graphics/fonts/OLEDDisplayFontsPL.cpp @@ -1,440 +1,1312 @@ #include "OLEDDisplayFontsPL.h" -// Font generated or edited with the glyphEditor const uint8_t ArialMT_Plain_10_PL[] PROGMEM = { - 0x0A, // Width: 10 - 0x0D, // Height: 13 - 0x20, // First char: 32 - 0xE0, // Number of chars: 224 +0x0A, // Width: 10 +0x0D, // Height: 13 +0x20, // First char: 32 +0xE0, // Number of chars: 224 +// Jump Table: +0xFF, 0xFF, 0x00, 0x03, // 32 +0x00, 0x00, 0x04, 0x03, // 33 +0x00, 0x04, 0x05, 0x04, // 34 +0x00, 0x09, 0x09, 0x06, // 35 +0x00, 0x12, 0x0A, 0x06, // 36 +0x00, 0x1C, 0x10, 0x09, // 37 +0x00, 0x2C, 0x0E, 0x08, // 38 +0x00, 0x3A, 0x01, 0x02, // 39 +0x00, 0x3B, 0x06, 0x04, // 40 +0x00, 0x41, 0x06, 0x04, // 41 +0x00, 0x47, 0x05, 0x04, // 42 +0x00, 0x4C, 0x09, 0x06, // 43 +0x00, 0x55, 0x04, 0x03, // 44 +0x00, 0x59, 0x03, 0x03, // 45 +0x00, 0x5C, 0x04, 0x03, // 46 +0x00, 0x60, 0x05, 0x04, // 47 +0x00, 0x65, 0x0A, 0x06, // 48 +0x00, 0x6F, 0x08, 0x05, // 49 +0x00, 0x77, 0x0A, 0x06, // 50 +0x00, 0x81, 0x0A, 0x06, // 51 +0x00, 0x8B, 0x0B, 0x07, // 52 +0x00, 0x96, 0x0A, 0x06, // 53 +0x00, 0xA0, 0x0A, 0x06, // 54 +0x00, 0xAA, 0x09, 0x06, // 55 +0x00, 0xB3, 0x0A, 0x06, // 56 +0x00, 0xBD, 0x0A, 0x06, // 57 +0x00, 0xC7, 0x04, 0x03, // 58 +0x00, 0xCB, 0x04, 0x03, // 59 +0x00, 0xCF, 0x0A, 0x06, // 60 +0x00, 0xD9, 0x09, 0x06, // 61 +0x00, 0xE2, 0x09, 0x06, // 62 +0x00, 0xEB, 0x0B, 0x07, // 63 +0x00, 0xF6, 0x14, 0x0B, // 64 +0x01, 0x0A, 0x0E, 0x08, // 65 +0x01, 0x18, 0x0C, 0x07, // 66 +0x01, 0x24, 0x0C, 0x07, // 67 +0x01, 0x30, 0x0B, 0x07, // 68 +0x01, 0x3B, 0x0C, 0x07, // 69 +0x01, 0x47, 0x09, 0x06, // 70 +0x01, 0x50, 0x0D, 0x08, // 71 +0x01, 0x5D, 0x0C, 0x07, // 72 +0x01, 0x69, 0x04, 0x03, // 73 +0x01, 0x6D, 0x08, 0x05, // 74 +0x01, 0x75, 0x0E, 0x08, // 75 +0x01, 0x83, 0x0C, 0x07, // 76 +0x01, 0x8F, 0x10, 0x09, // 77 +0x01, 0x9F, 0x0C, 0x07, // 78 +0x01, 0xAB, 0x0E, 0x08, // 79 +0x01, 0xB9, 0x0B, 0x07, // 80 +0x01, 0xC4, 0x0E, 0x08, // 81 +0x01, 0xD2, 0x0C, 0x07, // 82 +0x01, 0xDE, 0x0C, 0x07, // 83 +0x01, 0xEA, 0x0B, 0x07, // 84 +0x01, 0xF5, 0x0C, 0x07, // 85 +0x02, 0x01, 0x0D, 0x08, // 86 +0x02, 0x0E, 0x11, 0x0A, // 87 +0x02, 0x1F, 0x0E, 0x08, // 88 +0x02, 0x2D, 0x0D, 0x08, // 89 +0x02, 0x3A, 0x0C, 0x07, // 90 +0x02, 0x46, 0x06, 0x04, // 91 +0x02, 0x4C, 0x06, 0x04, // 92 +0x02, 0x52, 0x04, 0x03, // 93 +0x02, 0x56, 0x09, 0x06, // 94 +0x02, 0x5F, 0x0C, 0x07, // 95 +0x02, 0x6B, 0x03, 0x03, // 96 +0x02, 0x6E, 0x0A, 0x06, // 97 +0x02, 0x78, 0x0A, 0x06, // 98 +0x02, 0x82, 0x0A, 0x06, // 99 +0x02, 0x8C, 0x0A, 0x06, // 100 +0x02, 0x96, 0x0A, 0x06, // 101 +0x02, 0xA0, 0x05, 0x04, // 102 +0x02, 0xA5, 0x0A, 0x06, // 103 +0x02, 0xAF, 0x0A, 0x06, // 104 +0x02, 0xB9, 0x04, 0x03, // 105 +0x02, 0xBD, 0x04, 0x03, // 106 +0x02, 0xC1, 0x08, 0x05, // 107 +0x02, 0xC9, 0x04, 0x03, // 108 +0x02, 0xCD, 0x10, 0x09, // 109 +0x02, 0xDD, 0x0A, 0x06, // 110 +0x02, 0xE7, 0x0A, 0x06, // 111 +0x02, 0xF1, 0x0A, 0x06, // 112 +0x02, 0xFB, 0x0A, 0x06, // 113 +0x03, 0x05, 0x05, 0x04, // 114 +0x03, 0x0A, 0x08, 0x05, // 115 +0x03, 0x12, 0x06, 0x04, // 116 +0x03, 0x18, 0x0A, 0x06, // 117 +0x03, 0x22, 0x09, 0x06, // 118 +0x03, 0x2B, 0x0E, 0x08, // 119 +0x03, 0x39, 0x0A, 0x06, // 120 +0x03, 0x43, 0x09, 0x06, // 121 +0x03, 0x4C, 0x0A, 0x06, // 122 +0x03, 0x56, 0x06, 0x04, // 123 +0x03, 0x5C, 0x04, 0x03, // 124 +0x03, 0x60, 0x05, 0x04, // 125 +0x03, 0x65, 0x09, 0x06, // 126 +0xFF, 0xFF, 0x00, 0x0A, // 127 +0xFF, 0xFF, 0x00, 0x0A, // 128 +0x03, 0x6E, 0x0C, 0x07, // 129 +0x03, 0x7A, 0x05, 0x04, // 130 +0x03, 0x7F, 0x0C, 0x07, // 131 +0x03, 0x8B, 0x0E, 0x08, // 132 +0x03, 0x99, 0x0A, 0x06, // 133 +0x03, 0xA3, 0x0C, 0x07, // 134 +0x03, 0xAF, 0x0A, 0x06, // 135 +0x03, 0xB9, 0x0A, 0x06, // 136 +0x03, 0xC3, 0x0A, 0x06, // 137 +0xFF, 0xFF, 0x00, 0x0A, // 138 +0xFF, 0xFF, 0x00, 0x0A, // 139 +0xFF, 0xFF, 0x00, 0x0A, // 140 +0xFF, 0xFF, 0x00, 0x0A, // 141 +0xFF, 0xFF, 0x00, 0x0A, // 142 +0xFF, 0xFF, 0x00, 0x0A, // 143 +0xFF, 0xFF, 0x00, 0x0A, // 144 +0xFF, 0xFF, 0x00, 0x0A, // 145 +0xFF, 0xFF, 0x00, 0x0A, // 146 +0x03, 0xCD, 0x0E, 0x08, // 147 +0x03, 0xDB, 0x0A, 0x06, // 148 +0xFF, 0xFF, 0x00, 0x0A, // 149 +0xFF, 0xFF, 0x00, 0x0A, // 150 +0xFF, 0xFF, 0x00, 0x0A, // 151 +0x03, 0xE5, 0x0C, 0x07, // 152 +0x03, 0xF1, 0x0A, 0x06, // 153 +0x03, 0xFB, 0x0C, 0x07, // 154 +0x04, 0x07, 0x08, 0x05, // 155 +0xFF, 0xFF, 0x00, 0x0A, // 156 +0xFF, 0xFF, 0x00, 0x0A, // 157 +0xFF, 0xFF, 0x00, 0x0A, // 158 +0xFF, 0xFF, 0x00, 0x0A, // 159 +0xFF, 0xFF, 0x00, 0x0A, // 160 +0x04, 0x0F, 0x04, 0x03, // 161 +0x04, 0x13, 0x0A, 0x06, // 162 +0x04, 0x1D, 0x0C, 0x07, // 163 +0x04, 0x29, 0x0A, 0x06, // 164 +0x04, 0x33, 0x0A, 0x06, // 165 +0x04, 0x3D, 0x04, 0x03, // 166 +0x04, 0x41, 0x0A, 0x06, // 167 +0x04, 0x4B, 0x05, 0x04, // 168 +0x04, 0x50, 0x0D, 0x08, // 169 +0x04, 0x5D, 0x07, 0x05, // 170 +0x04, 0x64, 0x0A, 0x06, // 171 +0x04, 0x6E, 0x09, 0x06, // 172 +0x04, 0x77, 0x03, 0x03, // 173 +0x04, 0x7A, 0x0D, 0x08, // 174 +0x04, 0x87, 0x0B, 0x07, // 175 +0x04, 0x92, 0x07, 0x05, // 176 +0x04, 0x99, 0x0A, 0x06, // 177 +0x04, 0xA3, 0x05, 0x04, // 178 +0x04, 0xA8, 0x05, 0x04, // 179 +0x04, 0xAD, 0x05, 0x04, // 180 +0x04, 0xB2, 0x0A, 0x06, // 181 +0x04, 0xBC, 0x09, 0x06, // 182 +0x04, 0xC5, 0x03, 0x03, // 183 +0x04, 0xC8, 0x06, 0x04, // 184 +0x04, 0xCE, 0x0C, 0x07, // 185 +0x04, 0xDA, 0x07, 0x05, // 186 +0x04, 0xE1, 0x0C, 0x07, // 187 +0x04, 0xED, 0x0A, 0x06, // 188 +0x04, 0xF7, 0x10, 0x09, // 189 +0x05, 0x07, 0x10, 0x09, // 190 +0x05, 0x17, 0x0A, 0x06, // 191 +0x05, 0x21, 0x0E, 0x08, // 192 +0x05, 0x2F, 0x0E, 0x08, // 193 +0x05, 0x3D, 0x0E, 0x08, // 194 +0x05, 0x4B, 0x0E, 0x08, // 195 +0x05, 0x59, 0x0E, 0x08, // 196 +0x05, 0x67, 0x0E, 0x08, // 197 +0x05, 0x75, 0x12, 0x0A, // 198 +0x05, 0x87, 0x0C, 0x07, // 199 +0x05, 0x93, 0x0C, 0x07, // 200 +0x05, 0x9F, 0x0C, 0x07, // 201 +0x05, 0xAB, 0x0C, 0x07, // 202 +0x05, 0xB7, 0x0C, 0x07, // 203 +0x05, 0xC3, 0x05, 0x04, // 204 +0x05, 0xC8, 0x04, 0x03, // 205 +0x05, 0xCC, 0x04, 0x03, // 206 +0x05, 0xD0, 0x05, 0x04, // 207 +0x05, 0xD5, 0x0B, 0x07, // 208 +0x05, 0xE0, 0x0C, 0x07, // 209 +0x05, 0xEC, 0x0E, 0x08, // 210 +0x05, 0xFA, 0x0E, 0x08, // 211 +0x06, 0x08, 0x0E, 0x08, // 212 +0x06, 0x16, 0x0E, 0x08, // 213 +0x06, 0x24, 0x0E, 0x08, // 214 +0x06, 0x32, 0x0A, 0x06, // 215 +0x06, 0x3C, 0x0D, 0x08, // 216 +0x06, 0x49, 0x0C, 0x07, // 217 +0x06, 0x55, 0x0C, 0x07, // 218 +0x06, 0x61, 0x0C, 0x07, // 219 +0x06, 0x6D, 0x0C, 0x07, // 220 +0x06, 0x79, 0x0D, 0x08, // 221 +0x06, 0x86, 0x0B, 0x07, // 222 +0x06, 0x91, 0x0C, 0x07, // 223 +0x06, 0x9D, 0x0A, 0x06, // 224 +0x06, 0xA7, 0x0A, 0x06, // 225 +0x06, 0xB1, 0x0A, 0x06, // 226 +0x06, 0xBB, 0x0A, 0x06, // 227 +0x06, 0xC5, 0x0A, 0x06, // 228 +0x06, 0xCF, 0x0A, 0x06, // 229 +0x06, 0xD9, 0x10, 0x09, // 230 +0x06, 0xE9, 0x0A, 0x06, // 231 +0x06, 0xF3, 0x0A, 0x06, // 232 +0x06, 0xFD, 0x0A, 0x06, // 233 +0x07, 0x07, 0x0A, 0x06, // 234 +0x07, 0x11, 0x0A, 0x06, // 235 +0x07, 0x1B, 0x05, 0x04, // 236 +0x07, 0x20, 0x04, 0x03, // 237 +0x07, 0x24, 0x05, 0x04, // 238 +0x07, 0x29, 0x05, 0x04, // 239 +0x07, 0x2E, 0x0A, 0x06, // 240 +0x07, 0x38, 0x0A, 0x06, // 241 +0x07, 0x42, 0x0A, 0x06, // 242 +0x07, 0x4C, 0x0A, 0x06, // 243 +0x07, 0x56, 0x0A, 0x06, // 244 +0x07, 0x60, 0x0A, 0x06, // 245 +0x07, 0x6A, 0x0A, 0x06, // 246 +0x07, 0x74, 0x09, 0x06, // 247 +0x07, 0x7D, 0x0A, 0x06, // 248 +0x07, 0x87, 0x0A, 0x06, // 249 +0x07, 0x91, 0x0A, 0x06, // 250 +0x07, 0x9B, 0x0A, 0x06, // 251 +0x07, 0xA5, 0x0A, 0x06, // 252 +0x07, 0xAF, 0x09, 0x06, // 253 +0x07, 0xB8, 0x0A, 0x06, // 254 +0x07, 0xC2, 0x09, 0x06, // 255 +// Font Data: +0x00, 0x00, 0xF8, 0x02, // 33 +0x38, 0x00, 0x00, 0x00, 0x38, // 34 +0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35 +0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36 +0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37 +0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38 +0x38, // 39 +0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40 +0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41 +0x28, 0x00, 0x18, 0x00, 0x28, // 42 +0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43 +0x00, 0x00, 0x00, 0x06, // 44 +0x80, 0x00, 0x80, // 45 +0x00, 0x00, 0x00, 0x02, // 46 +0x00, 0x03, 0xE0, 0x00, 0x18, // 47 +0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48 +0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49 +0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50 +0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51 +0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52 +0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53 +0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54 +0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55 +0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56 +0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57 +0x00, 0x00, 0x20, 0x02, // 58 +0x00, 0x00, 0x20, 0x06, // 59 +0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60 +0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61 +0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62 +0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63 +0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0, 0x04, // 64 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67 +0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70 +0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71 +0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72 +0x00, 0x00, 0xF8, 0x03, // 73 +0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74 +0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75 +0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76 +0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77 +0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82 +0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83 +0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84 +0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85 +0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86 +0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87 +0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88 +0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89 +0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90 +0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91 +0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92 +0x08, 0x08, 0xF8, 0x0F, // 93 +0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94 +0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95 +0x08, 0x00, 0x10, // 96 +0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97 +0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100 +0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101 +0x20, 0x00, 0xF0, 0x03, 0x28, // 102 +0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103 +0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104 +0x00, 0x00, 0xE8, 0x03, // 105 +0x00, 0x08, 0xE8, 0x07, // 106 +0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107 +0x00, 0x00, 0xF8, 0x03, // 108 +0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109 +0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111 +0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113 +0x00, 0x00, 0xE0, 0x03, 0x20, // 114 +0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115 +0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116 +0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117 +0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118 +0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119 +0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120 +0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121 +0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122 +0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123 +0x00, 0x00, 0xF8, 0x0F, // 124 +0x08, 0x08, 0x78, 0x0F, 0x80, // 125 +0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126 +0x00, 0x00, 0xF8, 0x03, 0x40, 0x02, 0x20, 0x02, 0x00, 0x02, 0x00, 0x02, // 129 +0x40, 0x00, 0xF8, 0x03, 0x20, // 130 +0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x44, 0x00, 0x82, 0x01, 0xF8, 0x03, // 131 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x0D, 0x00, 0x0A, // 132 +0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x0E, 0xE0, 0x0B, // 133 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x10, 0x01, // 134 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0x44, 0x01, // 135 +0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x28, 0x00, 0xC4, 0x03, // 136 +0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x64, 0x02, 0x20, 0x02, // 137 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 147 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0xC4, 0x01, // 148 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x0E, 0x48, 0x0A, 0x48, 0x02, // 152 +0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x0E, 0xC0, 0x0A, // 153 +0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x90, 0x01, // 154 +0x40, 0x02, 0xA0, 0x02, 0xA8, 0x02, 0x24, 0x01, // 155 +0x00, 0x00, 0xA0, 0x0F, // 161 +0x00, 0x00, 0xC0, 0x01, 0xA0, 0x0F, 0x78, 0x02, 0x40, 0x01, // 162 +0x40, 0x02, 0x70, 0x03, 0xC8, 0x02, 0x48, 0x02, 0x08, 0x02, 0x10, 0x02, // 163 +0x00, 0x00, 0xE0, 0x01, 0x20, 0x01, 0x20, 0x01, 0xE0, 0x01, // 164 +0x48, 0x01, 0x70, 0x01, 0xC0, 0x03, 0x70, 0x01, 0x48, 0x01, // 165 +0x00, 0x00, 0x38, 0x0F, // 166 +0xD0, 0x04, 0x28, 0x09, 0x48, 0x09, 0x48, 0x0A, 0x90, 0x05, // 167 +0x08, 0x00, 0x00, 0x00, 0x08, // 168 +0xE0, 0x00, 0x10, 0x01, 0x48, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0x10, 0x01, 0xE0, // 169 +0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x78, // 170 +0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, // 171 +0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xE0, // 172 +0x80, 0x00, 0x80, // 173 +0xE0, 0x00, 0x10, 0x01, 0xE8, 0x02, 0x68, 0x02, 0xC8, 0x02, 0x10, 0x01, 0xE0, // 174 +0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 175 +0x00, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, // 176 +0x40, 0x02, 0x40, 0x02, 0xF0, 0x03, 0x40, 0x02, 0x40, 0x02, // 177 +0x48, 0x00, 0x68, 0x00, 0x58, // 178 +0x48, 0x00, 0x58, 0x00, 0x68, // 179 +0x00, 0x00, 0x10, 0x00, 0x08, // 180 +0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 181 +0x70, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0xF8, 0x0F, 0x08, // 182 +0x00, 0x00, 0x40, // 183 +0x00, 0x00, 0x00, 0x14, 0x00, 0x18, // 184 +0x08, 0x03, 0x88, 0x02, 0xCA, 0x02, 0x69, 0x02, 0x38, 0x02, 0x18, 0x02, // 185 +0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 186 +0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x6A, 0x02, 0x38, 0x02, 0x18, 0x02, // 187 +0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x60, 0x02, 0x20, 0x02, // 188 +0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0x80, 0x00, 0x60, 0x00, 0x50, 0x02, 0x48, 0x03, 0xC0, 0x02, // 189 +0x48, 0x00, 0x58, 0x00, 0x68, 0x03, 0x80, 0x00, 0x60, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 190 +0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0xA0, 0x09, 0x00, 0x04, // 191 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 192 +0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 193 +0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 194 +0x00, 0x02, 0xC2, 0x01, 0xB1, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 195 +0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x88, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 196 +0x00, 0x02, 0xC0, 0x01, 0xBE, 0x00, 0x8A, 0x00, 0xBE, 0x00, 0xC0, 0x01, 0x00, 0x02, // 197 +0x00, 0x03, 0xC0, 0x00, 0xE0, 0x00, 0x98, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 198 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x16, 0x08, 0x1A, 0x10, 0x01, // 199 +0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 200 +0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 201 +0x00, 0x00, 0xFA, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 202 +0x00, 0x00, 0xF8, 0x03, 0x4A, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x48, 0x02, // 203 +0x00, 0x00, 0xF9, 0x03, 0x02, // 204 +0x02, 0x00, 0xF9, 0x03, // 205 +0x01, 0x00, 0xFA, 0x03, // 206 +0x02, 0x00, 0xF8, 0x03, 0x02, // 207 +0x40, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x10, 0x01, 0xE0, // 208 +0x00, 0x00, 0xFA, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 209 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 210 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x08, 0x02, 0xF0, 0x01, // 211 +0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0xF0, 0x01, // 212 +0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 213 +0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 214 +0x10, 0x01, 0xA0, 0x00, 0xE0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 215 +0x00, 0x00, 0xF0, 0x02, 0x08, 0x03, 0xC8, 0x02, 0x28, 0x02, 0x18, 0x03, 0xE8, // 216 +0x00, 0x00, 0xF8, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0xF8, 0x01, // 217 +0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0xF8, 0x01, // 218 +0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0xF8, 0x01, // 219 +0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0xF8, 0x01, // 220 +0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC2, 0x03, 0x21, 0x00, 0x10, 0x00, 0x08, // 221 +0x00, 0x00, 0xF8, 0x03, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222 +0x00, 0x00, 0xF0, 0x03, 0x08, 0x01, 0x48, 0x02, 0xB0, 0x02, 0x80, 0x01, // 223 +0x00, 0x00, 0x00, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE0, 0x03, // 224 +0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE0, 0x03, // 225 +0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE8, 0x03, // 226 +0x00, 0x00, 0x08, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE4, 0x03, // 227 +0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA0, 0x02, 0xE8, 0x03, // 228 +0x00, 0x00, 0x00, 0x03, 0xAE, 0x02, 0xAA, 0x02, 0xEE, 0x03, // 229 +0x00, 0x00, 0x40, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 230 +0x00, 0x00, 0xC0, 0x01, 0x20, 0x16, 0x20, 0x1A, 0x40, 0x01, // 231 +0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC0, 0x02, // 232 +0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC0, 0x02, // 233 +0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC8, 0x02, // 234 +0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA0, 0x02, 0xC8, 0x02, // 235 +0x00, 0x00, 0xE4, 0x03, 0x08, // 236 +0x08, 0x00, 0xE4, 0x03, // 237 +0x08, 0x00, 0xE4, 0x03, 0x08, // 238 +0x08, 0x00, 0xE0, 0x03, 0x08, // 239 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x38, 0x02, 0xE0, 0x01, // 240 +0x00, 0x00, 0xE8, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 241 +0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC0, 0x01, // 242 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC0, 0x01, // 243 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC8, 0x01, // 244 +0x00, 0x00, 0xC8, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC4, 0x01, // 245 +0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x20, 0x02, 0xC8, 0x01, // 246 +0x40, 0x00, 0x40, 0x00, 0x50, 0x01, 0x40, 0x00, 0x40, // 247 +0x00, 0x00, 0xC0, 0x02, 0xA0, 0x03, 0x60, 0x02, 0xA0, 0x01, // 248 +0x00, 0x00, 0xE0, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 249 +0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x04, 0x02, 0xE0, 0x03, // 250 +0x00, 0x00, 0xE8, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 251 +0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x00, 0x02, 0xE8, 0x03, // 252 +0x20, 0x00, 0xC0, 0x09, 0x08, 0x06, 0xC4, 0x01, 0x20, // 253 +0x00, 0x00, 0xF8, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 254 +0x20, 0x00, 0xC8, 0x09, 0x00, 0x06, 0xC8, 0x01, 0x20, // 255 +}; - // Jump Table: - 0xFF, 0xFF, 0x00, 0x03, // 32:65535 - 0x00, 0x00, 0x04, 0x03, // 33 - 0x00, 0x04, 0x05, 0x04, // 34 - 0x00, 0x09, 0x09, 0x06, // 35 - 0x00, 0x12, 0x0A, 0x06, // 36 - 0x00, 0x1C, 0x10, 0x09, // 37 - 0x00, 0x2C, 0x0E, 0x08, // 38 - 0x00, 0x3A, 0x01, 0x02, // 39 - 0x00, 0x3B, 0x06, 0x04, // 40 - 0x00, 0x41, 0x06, 0x04, // 41 - 0x00, 0x47, 0x05, 0x04, // 42 - 0x00, 0x4C, 0x09, 0x06, // 43 - 0x00, 0x55, 0x04, 0x03, // 44 - 0x00, 0x59, 0x03, 0x03, // 45 - 0x00, 0x5C, 0x04, 0x03, // 46 - 0x00, 0x60, 0x05, 0x04, // 47 - 0x00, 0x65, 0x0A, 0x06, // 48 - 0x00, 0x6F, 0x08, 0x05, // 49 - 0x00, 0x77, 0x0A, 0x06, // 50 - 0x00, 0x81, 0x0A, 0x06, // 51 - 0x00, 0x8B, 0x0B, 0x07, // 52 - 0x00, 0x96, 0x0A, 0x06, // 53 - 0x00, 0xA0, 0x0A, 0x06, // 54 - 0x00, 0xAA, 0x09, 0x06, // 55 - 0x00, 0xB3, 0x0A, 0x06, // 56 - 0x00, 0xBD, 0x0A, 0x06, // 57 - 0x00, 0xC7, 0x04, 0x03, // 58 - 0x00, 0xCB, 0x04, 0x03, // 59 - 0x00, 0xCF, 0x0A, 0x06, // 60 - 0x00, 0xD9, 0x09, 0x06, // 61 - 0x00, 0xE2, 0x09, 0x06, // 62 - 0x00, 0xEB, 0x0B, 0x07, // 63 - 0x00, 0xF6, 0x14, 0x0B, // 64 - 0x01, 0x0A, 0x0E, 0x08, // 65 - 0x01, 0x18, 0x0C, 0x07, // 66 - 0x01, 0x24, 0x0C, 0x07, // 67 - 0x01, 0x30, 0x0B, 0x07, // 68 - 0x01, 0x3B, 0x0C, 0x07, // 69 - 0x01, 0x47, 0x09, 0x06, // 70 - 0x01, 0x50, 0x0D, 0x08, // 71 - 0x01, 0x5D, 0x0C, 0x07, // 72 - 0x01, 0x69, 0x04, 0x03, // 73 - 0x01, 0x6D, 0x08, 0x05, // 74 - 0x01, 0x75, 0x0E, 0x08, // 75 - 0x01, 0x83, 0x0C, 0x07, // 76 - 0x01, 0x8F, 0x10, 0x09, // 77 - 0x01, 0x9F, 0x0C, 0x07, // 78 - 0x01, 0xAB, 0x0E, 0x08, // 79 - 0x01, 0xB9, 0x0B, 0x07, // 80 - 0x01, 0xC4, 0x0E, 0x08, // 81 - 0x01, 0xD2, 0x0C, 0x07, // 82 - 0x01, 0xDE, 0x0C, 0x07, // 83 - 0x01, 0xEA, 0x0B, 0x07, // 84 - 0x01, 0xF5, 0x0C, 0x07, // 85 - 0x02, 0x01, 0x0D, 0x08, // 86 - 0x02, 0x0E, 0x11, 0x0A, // 87 - 0x02, 0x1F, 0x0E, 0x08, // 88 - 0x02, 0x2D, 0x0D, 0x08, // 89 - 0x02, 0x3A, 0x0C, 0x07, // 90 - 0x02, 0x46, 0x06, 0x04, // 91 - 0x02, 0x4C, 0x06, 0x04, // 92 - 0x02, 0x52, 0x04, 0x03, // 93 - 0x02, 0x56, 0x09, 0x06, // 94 - 0x02, 0x5F, 0x0C, 0x07, // 95 - 0x02, 0x6B, 0x03, 0x03, // 96 - 0x02, 0x6E, 0x0A, 0x06, // 97 - 0x02, 0x78, 0x0A, 0x06, // 98 - 0x02, 0x82, 0x0A, 0x06, // 99 - 0x02, 0x8C, 0x0A, 0x06, // 100 - 0x02, 0x96, 0x0A, 0x06, // 101 - 0x02, 0xA0, 0x05, 0x04, // 102 - 0x02, 0xA5, 0x0A, 0x06, // 103 - 0x02, 0xAF, 0x0A, 0x06, // 104 - 0x02, 0xB9, 0x04, 0x03, // 105 - 0x02, 0xBD, 0x04, 0x03, // 106 - 0x02, 0xC1, 0x08, 0x05, // 107 - 0x02, 0xC9, 0x04, 0x03, // 108 - 0x02, 0xCD, 0x10, 0x09, // 109 - 0x02, 0xDD, 0x0A, 0x06, // 110 - 0x02, 0xE7, 0x0A, 0x06, // 111 - 0x02, 0xF1, 0x0A, 0x06, // 112 - 0x02, 0xFB, 0x0A, 0x06, // 113 - 0x03, 0x05, 0x05, 0x04, // 114 - 0x03, 0x0A, 0x08, 0x05, // 115 - 0x03, 0x12, 0x06, 0x04, // 116 - 0x03, 0x18, 0x0A, 0x06, // 117 - 0x03, 0x22, 0x09, 0x06, // 118 - 0x03, 0x2B, 0x0E, 0x08, // 119 - 0x03, 0x39, 0x0A, 0x06, // 120 - 0x03, 0x43, 0x09, 0x06, // 121 - 0x03, 0x4C, 0x0A, 0x06, // 122 - 0x03, 0x56, 0x06, 0x04, // 123 - 0x03, 0x5C, 0x04, 0x03, // 124 - 0x03, 0x60, 0x05, 0x04, // 125 - 0x03, 0x65, 0x09, 0x06, // 126 - 0xFF, 0xFF, 0x00, 0x0A, // 127 - 0xFF, 0xFF, 0x00, 0x0A, // 128 - 0x03, 0x6E, 0x0C, 0x07, // 129 - 0x03, 0x7A, 0x05, 0x04, // 130 - 0x03, 0x7F, 0x0C, 0x07, // 131 - 0x03, 0x8B, 0x0E, 0x08, // 132 - 0x03, 0x99, 0x0C, 0x07, // 133 - 0x03, 0xA5, 0x0C, 0x07, // 134 - 0x03, 0xB1, 0x0A, 0x06, // 135 - 0x03, 0xBB, 0x0A, 0x06, // 136 - 0x03, 0xC5, 0x0A, 0x06, // 137 - 0xFF, 0xFF, 0x00, 0x0A, // 138 - 0xFF, 0xFF, 0x00, 0x0A, // 139 - 0xFF, 0xFF, 0x00, 0x0A, // 140 - 0xFF, 0xFF, 0x00, 0x0A, // 141 - 0xFF, 0xFF, 0x00, 0x0A, // 142 - 0xFF, 0xFF, 0x00, 0x0A, // 143 - 0xFF, 0xFF, 0x00, 0x0A, // 144 - 0xFF, 0xFF, 0x00, 0x0A, // 145 - 0xFF, 0xFF, 0x00, 0x0A, // 146 - 0x03, 0xCF, 0x0E, 0x08, // 147 - 0x03, 0xDD, 0x0A, 0x06, // 148 - 0xFF, 0xFF, 0x00, 0x0A, // 149 - 0xFF, 0xFF, 0x00, 0x0A, // 150 - 0xFF, 0xFF, 0x00, 0x0A, // 151 - 0x03, 0xE7, 0x0C, 0x07, // 152 - 0x03, 0xF3, 0x0C, 0x07, // 153 - 0x03, 0xFF, 0x0C, 0x07, // 154 - 0x04, 0x0B, 0x08, 0x05, // 155 - 0xFF, 0xFF, 0x00, 0x0A, // 156 - 0xFF, 0xFF, 0x00, 0x0A, // 157 - 0xFF, 0xFF, 0x00, 0x0A, // 158 - 0xFF, 0xFF, 0x00, 0x0A, // 159 - 0xFF, 0xFF, 0x00, 0x0A, // 160 - 0x04, 0x13, 0x04, 0x03, // 161 - 0x04, 0x17, 0x0A, 0x06, // 162 - 0x04, 0x21, 0x0C, 0x07, // 163 - 0x04, 0x2D, 0x0A, 0x06, // 164 - 0x04, 0x37, 0x0A, 0x06, // 165 - 0x04, 0x41, 0x04, 0x03, // 166 - 0x04, 0x45, 0x0A, 0x06, // 167 - 0x04, 0x4F, 0x05, 0x04, // 168 - 0x04, 0x54, 0x0D, 0x08, // 169 - 0x04, 0x61, 0x07, 0x05, // 170 - 0x04, 0x68, 0x0A, 0x06, // 171 - 0x04, 0x72, 0x09, 0x06, // 172 - 0x04, 0x7B, 0x03, 0x03, // 173 - 0x04, 0x7E, 0x0D, 0x08, // 174 - 0x04, 0x8B, 0x0B, 0x07, // 175 - 0x04, 0x96, 0x07, 0x05, // 176 - 0x04, 0x9D, 0x0A, 0x06, // 177 - 0x04, 0xA7, 0x05, 0x04, // 178 - 0x04, 0xAC, 0x05, 0x04, // 179 - 0x04, 0xB1, 0x05, 0x04, // 180 - 0x04, 0xB6, 0x0A, 0x06, // 181 - 0x04, 0xC0, 0x09, 0x06, // 182 - 0x04, 0xC9, 0x03, 0x03, // 183 - 0x04, 0xCC, 0x06, 0x04, // 184 - 0x04, 0xD2, 0x0C, 0x07, // 185 - 0x04, 0xDE, 0x07, 0x05, // 186 - 0x04, 0xE5, 0x0C, 0x07, // 187 - 0x04, 0xF1, 0x0A, 0x06, // 188 - 0x04, 0xFB, 0x10, 0x09, // 189 - 0x05, 0x0B, 0x10, 0x09, // 190 - 0x05, 0x1B, 0x0A, 0x06, // 191 - 0x05, 0x25, 0x0E, 0x08, // 192 - 0x05, 0x33, 0x0E, 0x08, // 193 - 0x05, 0x41, 0x0E, 0x08, // 194 - 0x05, 0x4F, 0x0E, 0x08, // 195 - 0x05, 0x5D, 0x0E, 0x08, // 196 - 0x05, 0x6B, 0x0E, 0x08, // 197 - 0x05, 0x79, 0x12, 0x0A, // 198 - 0x05, 0x8B, 0x0C, 0x07, // 199 - 0x05, 0x97, 0x0C, 0x07, // 200 - 0x05, 0xA3, 0x0C, 0x07, // 201 - 0x05, 0xAF, 0x0C, 0x07, // 202 - 0x05, 0xBB, 0x0C, 0x07, // 203 - 0x05, 0xC7, 0x05, 0x04, // 204 - 0x05, 0xCC, 0x04, 0x03, // 205 - 0x05, 0xD0, 0x04, 0x03, // 206 - 0x05, 0xD4, 0x05, 0x04, // 207 - 0x05, 0xD9, 0x0B, 0x07, // 208 - 0x05, 0xE4, 0x0C, 0x07, // 209 - 0x05, 0xF0, 0x0E, 0x08, // 210 - 0x05, 0xFE, 0x0E, 0x08, // 211 - 0x06, 0x0C, 0x0E, 0x08, // 212 - 0x06, 0x1A, 0x0E, 0x08, // 213 - 0x06, 0x28, 0x0E, 0x08, // 214 - 0x06, 0x36, 0x0A, 0x06, // 215 - 0x06, 0x40, 0x0D, 0x08, // 216 - 0x06, 0x4D, 0x0C, 0x07, // 217 - 0x06, 0x59, 0x0C, 0x07, // 218 - 0x06, 0x65, 0x0C, 0x07, // 219 - 0x06, 0x71, 0x0C, 0x07, // 220 - 0x06, 0x7D, 0x0D, 0x08, // 221 - 0x06, 0x8A, 0x0B, 0x07, // 222 - 0x06, 0x95, 0x0C, 0x07, // 223 - 0x06, 0xA1, 0x0A, 0x06, // 224 - 0x06, 0xAB, 0x0A, 0x06, // 225 - 0x06, 0xB5, 0x0A, 0x06, // 226 - 0x06, 0xBF, 0x0A, 0x06, // 227 - 0x06, 0xC9, 0x0A, 0x06, // 228 - 0x06, 0xD3, 0x0A, 0x06, // 229 - 0x06, 0xDD, 0x10, 0x09, // 230 - 0x06, 0xED, 0x0A, 0x06, // 231 - 0x06, 0xF7, 0x0A, 0x06, // 232 - 0x07, 0x01, 0x0A, 0x06, // 233 - 0x07, 0x0B, 0x0A, 0x06, // 234 - 0x07, 0x15, 0x0A, 0x06, // 235 - 0x07, 0x1F, 0x05, 0x04, // 236 - 0x07, 0x24, 0x04, 0x03, // 237 - 0x07, 0x28, 0x05, 0x04, // 238 - 0x07, 0x2D, 0x05, 0x04, // 239 - 0x07, 0x32, 0x0A, 0x06, // 240 - 0x07, 0x3C, 0x0A, 0x06, // 241 - 0x07, 0x46, 0x0A, 0x06, // 242 - 0x07, 0x50, 0x0A, 0x06, // 243 - 0x07, 0x5A, 0x0A, 0x06, // 244 - 0x07, 0x64, 0x0A, 0x06, // 245 - 0x07, 0x6E, 0x0A, 0x06, // 246 - 0x07, 0x78, 0x09, 0x06, // 247 - 0x07, 0x81, 0x0A, 0x06, // 248 - 0x07, 0x8B, 0x0A, 0x06, // 249 - 0x07, 0x95, 0x0A, 0x06, // 250 - 0x07, 0x9F, 0x0A, 0x06, // 251 - 0x07, 0xA9, 0x0A, 0x06, // 252 - 0x07, 0xB3, 0x09, 0x06, // 253 - 0x07, 0xBC, 0x0A, 0x06, // 254 - 0x07, 0xC6, 0x09, 0x06, // 255 - // Font Data: - 0x00, 0x00, 0xF8, 0x02, // 33 - 0x38, 0x00, 0x00, 0x00, 0x38, // 34 - 0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35 - 0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36 - 0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37 - 0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38 - 0x38, // 39 - 0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40 - 0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41 - 0x28, 0x00, 0x18, 0x00, 0x28, // 42 - 0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43 - 0x00, 0x00, 0x00, 0x06, // 44 - 0x80, 0x00, 0x80, // 45 - 0x00, 0x00, 0x00, 0x02, // 46 - 0x00, 0x03, 0xE0, 0x00, 0x18, // 47 - 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48 - 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49 - 0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50 - 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51 - 0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52 - 0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53 - 0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54 - 0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55 - 0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56 - 0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57 - 0x00, 0x00, 0x20, 0x02, // 58 - 0x00, 0x00, 0x20, 0x06, // 59 - 0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60 - 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61 - 0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62 - 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63 - 0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0, 0x04, // 64 - 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65 - 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66 - 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67 - 0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68 - 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69 - 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70 - 0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71 - 0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72 - 0x00, 0x00, 0xF8, 0x03, // 73 - 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74 - 0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75 - 0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76 - 0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77 - 0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78 - 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79 - 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80 - 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81 - 0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82 - 0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83 - 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84 - 0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85 - 0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86 - 0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87 - 0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88 - 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89 - 0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90 - 0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91 - 0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92 - 0x08, 0x08, 0xF8, 0x0F, // 93 - 0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94 - 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95 - 0x08, 0x00, 0x10, // 96 - 0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97 - 0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98 - 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99 - 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100 - 0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101 - 0x20, 0x00, 0xF0, 0x03, 0x28, // 102 - 0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103 - 0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104 - 0x00, 0x00, 0xE8, 0x03, // 105 - 0x00, 0x08, 0xE8, 0x07, // 106 - 0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107 - 0x00, 0x00, 0xF8, 0x03, // 108 - 0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109 - 0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110 - 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111 - 0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112 - 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113 - 0x00, 0x00, 0xE0, 0x03, 0x20, // 114 - 0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115 - 0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116 - 0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117 - 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118 - 0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119 - 0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120 - 0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121 - 0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122 - 0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123 - 0x00, 0x00, 0xF8, 0x0F, // 124 - 0x08, 0x08, 0x78, 0x0F, 0x80, // 125 - 0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126 - 0x00, 0x00, 0xF8, 0x03, 0x40, 0x02, 0x20, 0x02, 0x00, 0x02, 0x00, 0x02, // 129 - 0x40, 0x00, 0xF8, 0x03, 0x20, // 130 - 0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x44, 0x00, 0x82, 0x01, 0xF8, 0x03, // 131 - 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x05, 0x00, 0x0A, // 132 - 0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x07, 0x00, 0x08, // 133 - 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x10, 0x01, // 134 - 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0x44, 0x01, // 135 - 0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x28, 0x00, 0xC4, 0x03, // 136 - 0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x64, 0x02, 0x20, 0x02, // 137 - 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 147 - 0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x28, 0x02, 0xC4, 0x01, // 148 - 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x06, 0x48, 0x0A, // 152 - 0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x06, 0x00, 0x08, // 153 - 0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x90, 0x01, // 154 - 0x40, 0x02, 0xA0, 0x02, 0xA8, 0x02, 0x24, 0x01, // 155 - 0x00, 0x00, 0xA0, 0x0F, // 161 - 0x00, 0x00, 0xC0, 0x01, 0xA0, 0x0F, 0x78, 0x02, 0x40, 0x01, // 162 - 0x40, 0x02, 0x70, 0x03, 0xC8, 0x02, 0x48, 0x02, 0x08, 0x02, 0x10, 0x02, // 163 - 0x00, 0x00, 0xE0, 0x01, 0x20, 0x01, 0x20, 0x01, 0xE0, 0x01, // 164 - 0x48, 0x01, 0x70, 0x01, 0xC0, 0x03, 0x70, 0x01, 0x48, 0x01, // 165 - 0x00, 0x00, 0x38, 0x0F, // 166 - 0xD0, 0x04, 0x28, 0x09, 0x48, 0x09, 0x48, 0x0A, 0x90, 0x05, // 167 - 0x08, 0x00, 0x00, 0x00, 0x08, // 168 - 0xE0, 0x00, 0x10, 0x01, 0x48, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0x10, 0x01, 0xE0, // 169 - 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x78, // 170 - 0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, // 171 - 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xE0, // 172 - 0x80, 0x00, 0x80, // 173 - 0xE0, 0x00, 0x10, 0x01, 0xE8, 0x02, 0x68, 0x02, 0xC8, 0x02, 0x10, 0x01, 0xE0, // 174 - 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 175 - 0x00, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, // 176 - 0x40, 0x02, 0x40, 0x02, 0xF0, 0x03, 0x40, 0x02, 0x40, 0x02, // 177 - 0x48, 0x00, 0x68, 0x00, 0x58, // 178 - 0x48, 0x00, 0x58, 0x00, 0x68, // 179 - 0x00, 0x00, 0x10, 0x00, 0x08, // 180 - 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 181 - 0x70, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0xF8, 0x0F, 0x08, // 182 - 0x00, 0x00, 0x40, // 183 - 0x00, 0x00, 0x00, 0x14, 0x00, 0x18, // 184 - 0x08, 0x03, 0x88, 0x02, 0xCA, 0x02, 0x69, 0x02, 0x38, 0x02, 0x18, 0x02, // 185 - 0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 186 - 0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x6A, 0x02, 0x38, 0x02, 0x18, 0x02, // 187 - 0x20, 0x02, 0x20, 0x03, 0xA8, 0x02, 0x60, 0x02, 0x20, 0x02, // 188 - 0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0x80, 0x00, 0x60, 0x00, 0x50, 0x02, 0x48, 0x03, 0xC0, 0x02, // 189 - 0x48, 0x00, 0x58, 0x00, 0x68, 0x03, 0x80, 0x00, 0x60, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 190 - 0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0xA0, 0x09, 0x00, 0x04, // 191 - 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 192 - 0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 193 - 0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 194 - 0x00, 0x02, 0xC2, 0x01, 0xB1, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 195 - 0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x88, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 196 - 0x00, 0x02, 0xC0, 0x01, 0xBE, 0x00, 0x8A, 0x00, 0xBE, 0x00, 0xC0, 0x01, 0x00, 0x02, // 197 - 0x00, 0x03, 0xC0, 0x00, 0xE0, 0x00, 0x98, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 198 - 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x16, 0x08, 0x1A, 0x10, 0x01, // 199 - 0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 200 - 0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 201 - 0x00, 0x00, 0xFA, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 202 - 0x00, 0x00, 0xF8, 0x03, 0x4A, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x48, 0x02, // 203 - 0x00, 0x00, 0xF9, 0x03, 0x02, // 204 - 0x02, 0x00, 0xF9, 0x03, // 205 - 0x01, 0x00, 0xFA, 0x03, // 206 - 0x02, 0x00, 0xF8, 0x03, 0x02, // 207 - 0x40, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x10, 0x01, 0xE0, // 208 - 0x00, 0x00, 0xFA, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 209 - 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 210 - 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x08, 0x02, 0xF0, 0x01, // 211 - 0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0xF0, 0x01, // 212 - 0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 213 - 0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 214 - 0x10, 0x01, 0xA0, 0x00, 0xE0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 215 - 0x00, 0x00, 0xF0, 0x02, 0x08, 0x03, 0xC8, 0x02, 0x28, 0x02, 0x18, 0x03, 0xE8, // 216 - 0x00, 0x00, 0xF8, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0xF8, 0x01, // 217 - 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0xF8, 0x01, // 218 - 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0xF8, 0x01, // 219 - 0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0xF8, 0x01, // 220 - 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC2, 0x03, 0x21, 0x00, 0x10, 0x00, 0x08, // 221 - 0x00, 0x00, 0xF8, 0x03, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222 - 0x00, 0x00, 0xF0, 0x03, 0x08, 0x01, 0x48, 0x02, 0xB0, 0x02, 0x80, 0x01, // 223 - 0x00, 0x00, 0x00, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE0, 0x03, // 224 - 0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE0, 0x03, // 225 - 0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE8, 0x03, // 226 - 0x00, 0x00, 0x08, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE4, 0x03, // 227 - 0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA0, 0x02, 0xE8, 0x03, // 228 - 0x00, 0x00, 0x00, 0x03, 0xAE, 0x02, 0xAA, 0x02, 0xEE, 0x03, // 229 - 0x00, 0x00, 0x40, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 230 - 0x00, 0x00, 0xC0, 0x01, 0x20, 0x16, 0x20, 0x1A, 0x40, 0x01, // 231 - 0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC0, 0x02, // 232 - 0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC0, 0x02, // 233 - 0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC8, 0x02, // 234 - 0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA0, 0x02, 0xC8, 0x02, // 235 - 0x00, 0x00, 0xE4, 0x03, 0x08, // 236 - 0x08, 0x00, 0xE4, 0x03, // 237 - 0x08, 0x00, 0xE4, 0x03, 0x08, // 238 - 0x08, 0x00, 0xE0, 0x03, 0x08, // 239 - 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x38, 0x02, 0xE0, 0x01, // 240 - 0x00, 0x00, 0xE8, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 241 - 0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC0, 0x01, // 242 - 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC0, 0x01, // 243 - 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC8, 0x01, // 244 - 0x00, 0x00, 0xC8, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC4, 0x01, // 245 - 0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x20, 0x02, 0xC8, 0x01, // 246 - 0x40, 0x00, 0x40, 0x00, 0x50, 0x01, 0x40, 0x00, 0x40, // 247 - 0x00, 0x00, 0xC0, 0x02, 0xA0, 0x03, 0x60, 0x02, 0xA0, 0x01, // 248 - 0x00, 0x00, 0xE0, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 249 - 0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x04, 0x02, 0xE0, 0x03, // 250 - 0x00, 0x00, 0xE8, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 251 - 0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x00, 0x02, 0xE8, 0x03, // 252 - 0x20, 0x00, 0xC0, 0x09, 0x08, 0x06, 0xC4, 0x01, 0x20, // 253 - 0x00, 0x00, 0xF8, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 254 - 0x20, 0x00, 0xC8, 0x09, 0x00, 0x06, 0xC8, 0x01, 0x20, // 255 +const uint8_t ArialMT_Plain_16_PL[] PROGMEM = { +0x10, // Width: 16 +0x13, // Height: 19 +0x20, // First char: 32 +0xE0, // Number of chars: 224 +// Jump Table: +0xFF, 0xFF, 0x00, 0x04, // 32 +0x00, 0x00, 0x08, 0x04, // 33 +0x00, 0x08, 0x0D, 0x06, // 34 +0x00, 0x15, 0x1A, 0x0A, // 35 +0x00, 0x2F, 0x17, 0x09, // 36 +0x00, 0x46, 0x26, 0x0E, // 37 +0x00, 0x6C, 0x1D, 0x0B, // 38 +0x00, 0x89, 0x04, 0x03, // 39 +0x00, 0x8D, 0x0C, 0x05, // 40 +0x00, 0x99, 0x0B, 0x05, // 41 +0x00, 0xA4, 0x0D, 0x06, // 42 +0x00, 0xB1, 0x17, 0x09, // 43 +0x00, 0xC8, 0x09, 0x04, // 44 +0x00, 0xD1, 0x0B, 0x05, // 45 +0x00, 0xDC, 0x08, 0x04, // 46 +0x00, 0xE4, 0x0A, 0x05, // 47 +0x00, 0xEE, 0x17, 0x09, // 48 +0x01, 0x05, 0x11, 0x07, // 49 +0x01, 0x16, 0x17, 0x09, // 50 +0x01, 0x2D, 0x17, 0x09, // 51 +0x01, 0x44, 0x17, 0x09, // 52 +0x01, 0x5B, 0x17, 0x09, // 53 +0x01, 0x72, 0x17, 0x09, // 54 +0x01, 0x89, 0x16, 0x09, // 55 +0x01, 0x9F, 0x17, 0x09, // 56 +0x01, 0xB6, 0x17, 0x09, // 57 +0x01, 0xCD, 0x05, 0x03, // 58 +0x01, 0xD2, 0x06, 0x03, // 59 +0x01, 0xD8, 0x17, 0x09, // 60 +0x01, 0xEF, 0x17, 0x09, // 61 +0x02, 0x06, 0x17, 0x09, // 62 +0x02, 0x1D, 0x16, 0x09, // 63 +0x02, 0x33, 0x2F, 0x11, // 64 +0x02, 0x62, 0x1D, 0x0B, // 65 +0x02, 0x7F, 0x1D, 0x0B, // 66 +0x02, 0x9C, 0x20, 0x0C, // 67 +0x02, 0xBC, 0x20, 0x0C, // 68 +0x02, 0xDC, 0x1D, 0x0B, // 69 +0x02, 0xF9, 0x19, 0x0A, // 70 +0x03, 0x12, 0x20, 0x0C, // 71 +0x03, 0x32, 0x1D, 0x0B, // 72 +0x03, 0x4F, 0x05, 0x03, // 73 +0x03, 0x54, 0x14, 0x08, // 74 +0x03, 0x68, 0x1D, 0x0B, // 75 +0x03, 0x85, 0x17, 0x09, // 76 +0x03, 0x9C, 0x23, 0x0D, // 77 +0x03, 0xBF, 0x1D, 0x0B, // 78 +0x03, 0xDC, 0x20, 0x0C, // 79 +0x03, 0xFC, 0x1C, 0x0B, // 80 +0x04, 0x18, 0x20, 0x0C, // 81 +0x04, 0x38, 0x1D, 0x0B, // 82 +0x04, 0x55, 0x1D, 0x0B, // 83 +0x04, 0x72, 0x19, 0x0A, // 84 +0x04, 0x8B, 0x1D, 0x0B, // 85 +0x04, 0xA8, 0x1C, 0x0B, // 86 +0x04, 0xC4, 0x2B, 0x10, // 87 +0x04, 0xEF, 0x20, 0x0C, // 88 +0x05, 0x0F, 0x19, 0x0A, // 89 +0x05, 0x28, 0x1A, 0x0A, // 90 +0x05, 0x42, 0x0C, 0x05, // 91 +0x05, 0x4E, 0x0B, 0x05, // 92 +0x05, 0x59, 0x09, 0x04, // 93 +0x05, 0x62, 0x14, 0x08, // 94 +0x05, 0x76, 0x1B, 0x0A, // 95 +0x05, 0x91, 0x07, 0x04, // 96 +0x05, 0x98, 0x17, 0x09, // 97 +0x05, 0xAF, 0x17, 0x09, // 98 +0x05, 0xC6, 0x14, 0x08, // 99 +0x05, 0xDA, 0x17, 0x09, // 100 +0x05, 0xF1, 0x17, 0x09, // 101 +0x06, 0x08, 0x0A, 0x05, // 102 +0x06, 0x12, 0x17, 0x09, // 103 +0x06, 0x29, 0x14, 0x08, // 104 +0x06, 0x3D, 0x05, 0x03, // 105 +0x06, 0x42, 0x06, 0x03, // 106 +0x06, 0x48, 0x17, 0x09, // 107 +0x06, 0x5F, 0x05, 0x03, // 108 +0x06, 0x64, 0x23, 0x0D, // 109 +0x06, 0x87, 0x14, 0x08, // 110 +0x06, 0x9B, 0x17, 0x09, // 111 +0x06, 0xB2, 0x17, 0x09, // 112 +0x06, 0xC9, 0x18, 0x09, // 113 +0x06, 0xE1, 0x0D, 0x06, // 114 +0x06, 0xEE, 0x14, 0x08, // 115 +0x07, 0x02, 0x0B, 0x05, // 116 +0x07, 0x0D, 0x14, 0x08, // 117 +0x07, 0x21, 0x13, 0x08, // 118 +0x07, 0x34, 0x1F, 0x0C, // 119 +0x07, 0x53, 0x14, 0x08, // 120 +0x07, 0x67, 0x13, 0x08, // 121 +0x07, 0x7A, 0x14, 0x08, // 122 +0x07, 0x8E, 0x0F, 0x06, // 123 +0x07, 0x9D, 0x06, 0x03, // 124 +0x07, 0xA3, 0x0E, 0x06, // 125 +0x07, 0xB1, 0x17, 0x09, // 126 +0xFF, 0xFF, 0x00, 0x10, // 127 +0xFF, 0xFF, 0x00, 0x10, // 128 +0x07, 0xC8, 0x17, 0x09, // 129 +0x07, 0xDF, 0x07, 0x04, // 130 +0x07, 0xE6, 0x1D, 0x0B, // 131 +0x08, 0x03, 0x1E, 0x0B, // 132 +0x08, 0x21, 0x1B, 0x0A, // 133 +0x08, 0x3C, 0x20, 0x0C, // 134 +0x08, 0x5C, 0x14, 0x08, // 135 +0x08, 0x70, 0x14, 0x08, // 136 +0x08, 0x84, 0x14, 0x08, // 137 +0xFF, 0xFF, 0x00, 0x10, // 138 +0xFF, 0xFF, 0x00, 0x10, // 139 +0xFF, 0xFF, 0x00, 0x10, // 140 +0xFF, 0xFF, 0x00, 0x10, // 141 +0xFF, 0xFF, 0x00, 0x10, // 142 +0xFF, 0xFF, 0x00, 0x10, // 143 +0xFF, 0xFF, 0x00, 0x10, // 144 +0xFF, 0xFF, 0x00, 0x10, // 145 +0xFF, 0xFF, 0x00, 0x10, // 146 +0x08, 0x98, 0x20, 0x0C, // 147 +0x08, 0xB8, 0x17, 0x09, // 148 +0xFF, 0xFF, 0x00, 0x10, // 149 +0xFF, 0xFF, 0x00, 0x10, // 150 +0xFF, 0xFF, 0x00, 0x10, // 151 +0x08, 0xCF, 0x1D, 0x0B, // 152 +0x08, 0xEC, 0x17, 0x09, // 153 +0x09, 0x03, 0x1D, 0x0B, // 154 +0x09, 0x20, 0x14, 0x08, // 155 +0xFF, 0xFF, 0x00, 0x10, // 156 +0xFF, 0xFF, 0x00, 0x10, // 157 +0xFF, 0xFF, 0x00, 0x10, // 158 +0xFF, 0xFF, 0x00, 0x10, // 159 +0xFF, 0xFF, 0x00, 0x10, // 160 +0x09, 0x34, 0x09, 0x04, // 161 +0x09, 0x3D, 0x17, 0x09, // 162 +0x09, 0x54, 0x17, 0x09, // 163 +0x09, 0x6B, 0x14, 0x08, // 164 +0x09, 0x7F, 0x1A, 0x0A, // 165 +0x09, 0x99, 0x06, 0x03, // 166 +0x09, 0x9F, 0x17, 0x09, // 167 +0x09, 0xB6, 0x07, 0x04, // 168 +0x09, 0xBD, 0x23, 0x0D, // 169 +0x09, 0xE0, 0x0E, 0x06, // 170 +0x09, 0xEE, 0x14, 0x08, // 171 +0x0A, 0x02, 0x17, 0x09, // 172 +0x0A, 0x19, 0x0B, 0x05, // 173 +0x0A, 0x24, 0x23, 0x0D, // 174 +0x0A, 0x47, 0x19, 0x0A, // 175 +0x0A, 0x60, 0x0D, 0x06, // 176 +0x0A, 0x6D, 0x17, 0x09, // 177 +0x0A, 0x84, 0x0E, 0x06, // 178 +0x0A, 0x92, 0x0D, 0x06, // 179 +0x0A, 0x9F, 0x0A, 0x05, // 180 +0x0A, 0xA9, 0x17, 0x09, // 181 +0x0A, 0xC0, 0x19, 0x0A, // 182 +0x0A, 0xD9, 0x08, 0x04, // 183 +0x0A, 0xE1, 0x0C, 0x05, // 184 +0x0A, 0xED, 0x1A, 0x0A, // 185 +0x0B, 0x07, 0x0D, 0x06, // 186 +0x0B, 0x14, 0x1A, 0x0A, // 187 +0x0B, 0x2E, 0x14, 0x08, // 188 +0x0B, 0x42, 0x26, 0x0E, // 189 +0x0B, 0x68, 0x26, 0x0E, // 190 +0x0B, 0x8E, 0x1A, 0x0A, // 191 +0x0B, 0xA8, 0x1D, 0x0B, // 192 +0x0B, 0xC5, 0x1D, 0x0B, // 193 +0x0B, 0xE2, 0x1D, 0x0B, // 194 +0x0B, 0xFF, 0x1D, 0x0B, // 195 +0x0C, 0x1C, 0x1D, 0x0B, // 196 +0x0C, 0x39, 0x1D, 0x0B, // 197 +0x0C, 0x56, 0x2C, 0x10, // 198 +0x0C, 0x82, 0x20, 0x0C, // 199 +0x0C, 0xA2, 0x1D, 0x0B, // 200 +0x0C, 0xBF, 0x1D, 0x0B, // 201 +0x0C, 0xDC, 0x1D, 0x0B, // 202 +0x0C, 0xF9, 0x1D, 0x0B, // 203 +0x0D, 0x16, 0x05, 0x03, // 204 +0x0D, 0x1B, 0x07, 0x04, // 205 +0x0D, 0x22, 0x0A, 0x05, // 206 +0x0D, 0x2C, 0x07, 0x04, // 207 +0x0D, 0x33, 0x20, 0x0C, // 208 +0x0D, 0x53, 0x1D, 0x0B, // 209 +0x0D, 0x70, 0x20, 0x0C, // 210 +0x0D, 0x90, 0x20, 0x0C, // 211 +0x0D, 0xB0, 0x20, 0x0C, // 212 +0x0D, 0xD0, 0x20, 0x0C, // 213 +0x0D, 0xF0, 0x20, 0x0C, // 214 +0x0E, 0x10, 0x17, 0x09, // 215 +0x0E, 0x27, 0x20, 0x0C, // 216 +0x0E, 0x47, 0x1D, 0x0B, // 217 +0x0E, 0x64, 0x1D, 0x0B, // 218 +0x0E, 0x81, 0x1D, 0x0B, // 219 +0x0E, 0x9E, 0x1D, 0x0B, // 220 +0x0E, 0xBB, 0x19, 0x0A, // 221 +0x0E, 0xD4, 0x1D, 0x0B, // 222 +0x0E, 0xF1, 0x17, 0x09, // 223 +0x0F, 0x08, 0x17, 0x09, // 224 +0x0F, 0x1F, 0x17, 0x09, // 225 +0x0F, 0x36, 0x17, 0x09, // 226 +0x0F, 0x4D, 0x17, 0x09, // 227 +0x0F, 0x64, 0x17, 0x09, // 228 +0x0F, 0x7B, 0x17, 0x09, // 229 +0x0F, 0x92, 0x29, 0x0F, // 230 +0x0F, 0xBB, 0x14, 0x08, // 231 +0x0F, 0xCF, 0x17, 0x09, // 232 +0x0F, 0xE6, 0x17, 0x09, // 233 +0x0F, 0xFD, 0x17, 0x09, // 234 +0x10, 0x14, 0x17, 0x09, // 235 +0x10, 0x2B, 0x05, 0x03, // 236 +0x10, 0x30, 0x07, 0x04, // 237 +0x10, 0x37, 0x0A, 0x05, // 238 +0x10, 0x41, 0x07, 0x04, // 239 +0x10, 0x48, 0x17, 0x09, // 240 +0x10, 0x5F, 0x14, 0x08, // 241 +0x10, 0x73, 0x17, 0x09, // 242 +0x10, 0x8A, 0x17, 0x09, // 243 +0x10, 0xA1, 0x17, 0x09, // 244 +0x10, 0xB8, 0x17, 0x09, // 245 +0x10, 0xCF, 0x17, 0x09, // 246 +0x10, 0xE6, 0x17, 0x09, // 247 +0x10, 0xFD, 0x17, 0x09, // 248 +0x11, 0x14, 0x14, 0x08, // 249 +0x11, 0x28, 0x14, 0x08, // 250 +0x11, 0x3C, 0x14, 0x08, // 251 +0x11, 0x50, 0x14, 0x08, // 252 +0x11, 0x64, 0x13, 0x08, // 253 +0x11, 0x77, 0x17, 0x09, // 254 +0x11, 0x8E, 0x13, 0x08, // 255 +// Font Data: +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x5F, // 33 +0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, // 34 +0x80, 0x08, 0x00, 0x80, 0x78, 0x00, 0xC0, 0x0F, 0x00, 0xB8, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x78, 0x00, 0xC0, 0x0F, 0x00, 0xB8, 0x08, 0x00, 0x80, 0x08, // 35 +0x00, 0x00, 0x00, 0xE0, 0x10, 0x00, 0x10, 0x21, 0x00, 0x08, 0x41, 0x00, 0xFC, 0xFF, 0x00, 0x08, 0x42, 0x00, 0x10, 0x22, 0x00, 0x20, 0x1C, // 36 +0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x61, 0x00, 0xF0, 0x18, 0x00, 0x00, 0x06, 0x00, 0xC0, 0x01, 0x00, 0x30, 0x3C, 0x00, 0x08, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x3C, // 37 +0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x70, 0x22, 0x00, 0x88, 0x41, 0x00, 0x08, 0x43, 0x00, 0x88, 0x44, 0x00, 0x70, 0x28, 0x00, 0x00, 0x10, 0x00, 0x00, 0x28, 0x00, 0x00, 0x44, // 38 +0x00, 0x00, 0x00, 0x78, // 39 +0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x70, 0xC0, 0x01, 0x08, 0x00, 0x02, // 40 +0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x70, 0xC0, 0x01, 0x80, 0x3F, // 41 +0x10, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x38, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x10, // 42 +0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, // 43 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, // 44 +0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, // 45 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, // 46 +0x00, 0x60, 0x00, 0x00, 0x1E, 0x00, 0xE0, 0x01, 0x00, 0x18, // 47 +0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0xE0, 0x1F, // 48 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0xF8, 0x7F, // 49 +0x00, 0x00, 0x00, 0x20, 0x40, 0x00, 0x10, 0x60, 0x00, 0x08, 0x50, 0x00, 0x08, 0x48, 0x00, 0x08, 0x44, 0x00, 0x10, 0x43, 0x00, 0xE0, 0x40, // 50 +0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x88, 0x41, 0x00, 0xF0, 0x22, 0x00, 0x00, 0x1C, // 51 +0x00, 0x0C, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x09, 0x00, 0xC0, 0x08, 0x00, 0x20, 0x08, 0x00, 0x10, 0x08, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x08, // 52 +0x00, 0x00, 0x00, 0xC0, 0x11, 0x00, 0xB8, 0x20, 0x00, 0x88, 0x40, 0x00, 0x88, 0x40, 0x00, 0x88, 0x40, 0x00, 0x08, 0x21, 0x00, 0x08, 0x1E, // 53 +0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x10, 0x21, 0x00, 0x88, 0x40, 0x00, 0x88, 0x40, 0x00, 0x88, 0x40, 0x00, 0x10, 0x21, 0x00, 0x20, 0x1E, // 54 +0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x78, 0x00, 0x08, 0x07, 0x00, 0xC8, 0x00, 0x00, 0x28, 0x00, 0x00, 0x18, // 55 +0x00, 0x00, 0x00, 0x60, 0x1C, 0x00, 0x90, 0x22, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x90, 0x22, 0x00, 0x60, 0x1C, // 56 +0x00, 0x00, 0x00, 0xE0, 0x11, 0x00, 0x10, 0x22, 0x00, 0x08, 0x44, 0x00, 0x08, 0x44, 0x00, 0x08, 0x44, 0x00, 0x10, 0x22, 0x00, 0xE0, 0x1F, // 57 +0x00, 0x00, 0x00, 0x40, 0x40, // 58 +0x00, 0x00, 0x00, 0x40, 0xC0, 0x01, // 59 +0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x40, 0x10, // 60 +0x00, 0x00, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, // 61 +0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x00, 0x00, 0x02, // 62 +0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x5C, 0x00, 0x08, 0x02, 0x00, 0x10, 0x01, 0x00, 0xE0, // 63 +0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0xC0, 0x40, 0x00, 0x20, 0x80, 0x00, 0x10, 0x1E, 0x01, 0x10, 0x21, 0x01, 0x88, 0x40, 0x02, 0x48, 0x40, 0x02, 0x48, 0x40, 0x02, 0x48, 0x20, 0x02, 0x88, 0x7C, 0x02, 0xC8, 0x43, 0x02, 0x10, 0x40, 0x02, 0x10, 0x20, 0x01, 0x60, 0x10, 0x01, 0x80, 0x8F, // 64 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x70, 0x04, 0x00, 0x08, 0x04, 0x00, 0x70, 0x04, 0x00, 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 65 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x90, 0x22, 0x00, 0x60, 0x1C, // 66 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, // 67 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 68 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 69 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, // 70 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x10, 0x22, 0x00, 0x20, 0x12, 0x00, 0x00, 0x0E, // 71 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF8, 0x7F, // 72 +0x00, 0x00, 0x00, 0xF8, 0x7F, // 73 +0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0xF8, 0x3F, // 74 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x80, 0x03, 0x00, 0x40, 0x04, 0x00, 0x20, 0x18, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, // 75 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, // 76 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x30, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x00, 0x00, 0xF8, 0x7F, // 77 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x10, 0x00, 0x00, 0x60, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x18, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x7F, // 78 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 79 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x10, 0x01, 0x00, 0xE0, // 80 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x50, 0x00, 0x08, 0x50, 0x00, 0x10, 0x20, 0x00, 0x20, 0x70, 0x00, 0xC0, 0x4F, // 81 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x06, 0x00, 0x08, 0x1A, 0x00, 0x10, 0x21, 0x00, 0xE0, 0x40, // 82 +0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x90, 0x20, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x10, 0x22, 0x00, 0x20, 0x1C, // 83 +0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, // 84 +0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 85 +0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x18, 0x00, 0x00, 0x60, 0x00, 0x00, 0x18, 0x00, 0x00, 0x07, 0x00, 0xE0, 0x00, 0x00, 0x18, // 86 +0x18, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x03, 0x00, 0x70, 0x00, 0x00, 0x08, 0x00, 0x00, 0x70, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1E, 0x00, 0xE0, 0x01, 0x00, 0x18, // 87 +0x00, 0x40, 0x00, 0x08, 0x20, 0x00, 0x10, 0x10, 0x00, 0x60, 0x0C, 0x00, 0x80, 0x02, 0x00, 0x00, 0x01, 0x00, 0x80, 0x02, 0x00, 0x60, 0x0C, 0x00, 0x10, 0x10, 0x00, 0x08, 0x20, 0x00, 0x00, 0x40, // 88 +0x08, 0x00, 0x00, 0x30, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x7E, 0x00, 0x80, 0x01, 0x00, 0x40, 0x00, 0x00, 0x30, 0x00, 0x00, 0x08, // 89 +0x00, 0x40, 0x00, 0x08, 0x60, 0x00, 0x08, 0x58, 0x00, 0x08, 0x44, 0x00, 0x08, 0x43, 0x00, 0x88, 0x40, 0x00, 0x68, 0x40, 0x00, 0x18, 0x40, 0x00, 0x08, 0x40, // 90 +0x00, 0x00, 0x00, 0xF8, 0xFF, 0x03, 0x08, 0x00, 0x02, 0x08, 0x00, 0x02, // 91 +0x18, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x60, // 92 +0x08, 0x00, 0x02, 0x08, 0x00, 0x02, 0xF8, 0xFF, 0x03, // 93 +0x00, 0x01, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x08, 0x00, 0x00, 0x30, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x01, // 94 +0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, // 95 +0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, // 96 +0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x42, 0x00, 0x40, 0x22, 0x00, 0x80, 0x7F, // 97 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1F, // 98 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, // 99 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0xF8, 0x7F, // 100 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x24, 0x00, 0x00, 0x17, // 101 +0x40, 0x00, 0x00, 0xF0, 0x7F, 0x00, 0x48, 0x00, 0x00, 0x48, // 102 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x01, 0x80, 0x20, 0x02, 0x40, 0x40, 0x02, 0x40, 0x40, 0x02, 0x40, 0x40, 0x02, 0x80, 0x20, 0x01, 0xC0, 0xFF, // 103 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x7F, // 104 +0x00, 0x00, 0x00, 0xC8, 0x7F, // 105 +0x00, 0x00, 0x02, 0xC8, 0xFF, 0x01, // 106 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x06, 0x00, 0x00, 0x19, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, // 107 +0x00, 0x00, 0x00, 0xF8, 0x7F, // 108 +0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x7F, // 109 +0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x7F, // 110 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1F, // 111 +0x00, 0x00, 0x00, 0xC0, 0xFF, 0x03, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1F, // 112 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0xC0, 0xFF, 0x03, // 113 +0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, // 114 +0x00, 0x00, 0x00, 0x80, 0x23, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x38, // 115 +0x40, 0x00, 0x00, 0xF0, 0x7F, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, // 116 +0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x7F, // 117 +0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x03, 0x00, 0xC0, // 118 +0xC0, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1F, 0x00, 0xC0, // 119 +0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x04, 0x00, 0x00, 0x1B, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, // 120 +0xC0, 0x01, 0x00, 0x00, 0x06, 0x02, 0x00, 0x38, 0x02, 0x00, 0xE0, 0x01, 0x00, 0x38, 0x00, 0x00, 0x07, 0x00, 0xC0, // 121 +0x40, 0x40, 0x00, 0x40, 0x60, 0x00, 0x40, 0x58, 0x00, 0x40, 0x44, 0x00, 0x40, 0x43, 0x00, 0xC0, 0x40, 0x00, 0x40, 0x40, // 122 +0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0xF0, 0xFB, 0x01, 0x08, 0x00, 0x02, 0x08, 0x00, 0x02, // 123 +0x00, 0x00, 0x00, 0xF8, 0xFF, 0x03, // 124 +0x08, 0x00, 0x02, 0x08, 0x00, 0x02, 0xF0, 0xFB, 0x01, 0x00, 0x04, 0x00, 0x00, 0x04, // 125 +0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, // 126 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x42, 0x00, 0x00, 0x41, 0x00, 0x80, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, // 129 +0x00, 0x01, 0x00, 0xF8, 0x7F, 0x00, 0x80, // 130 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x10, 0x00, 0x00, 0x60, 0x00, 0x00, 0x80, 0x00, 0x00, 0x08, 0x03, 0x00, 0x04, 0x04, 0x00, 0x02, 0x18, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x7F, // 131 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x70, 0x04, 0x00, 0x08, 0x04, 0x00, 0x70, 0x04, 0x00, 0x80, 0x07, 0x00, 0x00, 0x9C, 0x01, 0x00, 0x60, 0x02, // 132 +0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x42, 0x00, 0x40, 0x22, 0x00, 0x80, 0x7F, 0x03, 0x00, 0x80, 0x04, // 133 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x09, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, // 134 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x50, 0x40, 0x00, 0x48, 0x40, 0x00, 0x80, 0x20, // 135 +0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x50, 0x00, 0x00, 0x48, 0x00, 0x00, 0x80, 0x7F, // 136 +0x40, 0x40, 0x00, 0x40, 0x60, 0x00, 0x40, 0x58, 0x00, 0x50, 0x44, 0x00, 0x48, 0x43, 0x00, 0xC0, 0x40, 0x00, 0x40, 0x40, // 137 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x09, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 147 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x50, 0x40, 0x00, 0x48, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1F, // 148 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0xC1, 0x01, 0x08, 0x41, 0x02, 0x08, 0x41, 0x00, 0x08, 0x40, // 152 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x03, 0x40, 0xC4, 0x04, 0x80, 0x24, 0x00, 0x00, 0x17, // 153 +0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x90, 0x20, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x09, 0x42, 0x00, 0x08, 0x42, 0x00, 0x10, 0x22, 0x00, 0x20, 0x1C, // 154 +0x00, 0x00, 0x00, 0x80, 0x23, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x80, 0x38, // 155 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xFF, 0x03, // 161 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x03, 0x40, 0xF0, 0x00, 0x40, 0x4E, 0x00, 0xC0, 0x41, 0x00, 0xB8, 0x20, 0x00, 0x00, 0x11, // 162 +0x00, 0x41, 0x00, 0xE0, 0x31, 0x00, 0x10, 0x2F, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x40, 0x00, 0x10, 0x40, 0x00, 0x20, 0x20, // 163 +0x00, 0x00, 0x00, 0x40, 0x0B, 0x00, 0x80, 0x04, 0x00, 0x40, 0x08, 0x00, 0x40, 0x08, 0x00, 0x80, 0x04, 0x00, 0x40, 0x0B, // 164 +0x08, 0x0A, 0x00, 0x10, 0x0A, 0x00, 0x60, 0x0A, 0x00, 0x80, 0x0B, 0x00, 0x00, 0x7E, 0x00, 0x80, 0x0B, 0x00, 0x60, 0x0A, 0x00, 0x10, 0x0A, 0x00, 0x08, 0x0A, // 165 +0x00, 0x00, 0x00, 0xF8, 0xF1, 0x03, // 166 +0x00, 0x86, 0x00, 0x70, 0x09, 0x01, 0xC8, 0x10, 0x02, 0x88, 0x10, 0x02, 0x08, 0x21, 0x02, 0x08, 0x61, 0x02, 0x30, 0xD2, 0x01, 0x00, 0x0C, // 167 +0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // 168 +0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0xC8, 0x47, 0x00, 0x28, 0x48, 0x00, 0x28, 0x48, 0x00, 0x28, 0x48, 0x00, 0x28, 0x48, 0x00, 0x48, 0x44, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 169 +0xD0, 0x00, 0x00, 0x48, 0x01, 0x00, 0x28, 0x01, 0x00, 0x28, 0x01, 0x00, 0xF0, 0x01, // 170 +0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x1B, 0x00, 0x80, 0x20, 0x00, 0x00, 0x04, 0x00, 0x00, 0x1B, 0x00, 0x80, 0x20, // 171 +0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x0F, // 172 +0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, // 173 +0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0xE8, 0x4F, 0x00, 0x28, 0x41, 0x00, 0x28, 0x41, 0x00, 0x28, 0x43, 0x00, 0x28, 0x45, 0x00, 0xC8, 0x48, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 174 +0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, // 175 +0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x48, 0x00, 0x00, 0x48, 0x00, 0x00, 0x30, // 176 +0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0xE0, 0x4F, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x41, // 177 +0x10, 0x01, 0x00, 0x88, 0x01, 0x00, 0x48, 0x01, 0x00, 0x48, 0x01, 0x00, 0x30, 0x01, // 178 +0x90, 0x00, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x28, 0x01, 0x00, 0xD8, // 179 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, // 180 +0x00, 0x00, 0x00, 0xC0, 0xFF, 0x03, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x7F, // 181 +0xF0, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xF8, 0x01, 0x00, 0xF8, 0x01, 0x00, 0xF8, 0xFF, 0x03, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0xFF, 0x03, 0x08, // 182 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // 183 +0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x80, 0x02, 0x00, 0x00, 0x03, // 184 +0x00, 0x40, 0x00, 0x08, 0x60, 0x00, 0x08, 0x58, 0x00, 0x08, 0x44, 0x00, 0x0A, 0x43, 0x00, 0x89, 0x40, 0x00, 0x68, 0x40, 0x00, 0x18, 0x40, 0x00, 0x08, 0x40, // 185 +0xF0, 0x00, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0xF0, // 186 +0x00, 0x40, 0x00, 0x08, 0x60, 0x00, 0x08, 0x58, 0x00, 0x08, 0x44, 0x00, 0x0A, 0x43, 0x00, 0x88, 0x40, 0x00, 0x68, 0x40, 0x00, 0x18, 0x40, 0x00, 0x08, 0x40, // 187 +0x40, 0x40, 0x00, 0x40, 0x60, 0x00, 0x40, 0x58, 0x00, 0x50, 0x44, 0x00, 0x40, 0x43, 0x00, 0xC0, 0x40, 0x00, 0x40, 0x40, // 188 +0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x40, 0x00, 0xF8, 0x31, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x60, 0x44, 0x00, 0x10, 0x62, 0x00, 0x08, 0x52, 0x00, 0x00, 0x52, 0x00, 0x00, 0x4C, // 189 +0x90, 0x00, 0x00, 0x08, 0x01, 0x00, 0x08, 0x41, 0x00, 0x28, 0x21, 0x00, 0xD8, 0x18, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x40, 0x30, 0x00, 0x30, 0x28, 0x00, 0x08, 0x24, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x20, // 190 +0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x10, 0x01, 0x00, 0x08, 0x02, 0x40, 0x07, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0xC0, // 191 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x71, 0x04, 0x00, 0x0A, 0x04, 0x00, 0x70, 0x04, 0x00, 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 192 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x70, 0x04, 0x00, 0x0A, 0x04, 0x00, 0x71, 0x04, 0x00, 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 193 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x72, 0x04, 0x00, 0x09, 0x04, 0x00, 0x71, 0x04, 0x00, 0x82, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 194 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x72, 0x04, 0x00, 0x09, 0x04, 0x00, 0x72, 0x04, 0x00, 0x81, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 195 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x72, 0x04, 0x00, 0x08, 0x04, 0x00, 0x72, 0x04, 0x00, 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 196 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x07, 0x00, 0x7E, 0x04, 0x00, 0x0A, 0x04, 0x00, 0x7E, 0x04, 0x00, 0x80, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x60, // 197 +0x00, 0x60, 0x00, 0x00, 0x18, 0x00, 0x00, 0x06, 0x00, 0x80, 0x05, 0x00, 0x60, 0x04, 0x00, 0x18, 0x04, 0x00, 0x08, 0x04, 0x00, 0x08, 0x04, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, // 198 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x02, 0x08, 0xC0, 0x02, 0x08, 0x40, 0x03, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, // 199 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x09, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 200 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x09, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 201 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x09, 0x41, 0x00, 0x09, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 202 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x08, 0x41, 0x00, 0x0A, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x41, 0x00, 0x08, 0x40, // 203 +0x01, 0x00, 0x00, 0xFA, 0x7F, // 204 +0x00, 0x00, 0x00, 0xFA, 0x7F, 0x00, 0x01, // 205 +0x02, 0x00, 0x00, 0xF9, 0x7F, 0x00, 0x01, 0x00, 0x00, 0x02, // 206 +0x02, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x02, // 207 +0x00, 0x02, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x42, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 208 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x10, 0x00, 0x00, 0x60, 0x00, 0x00, 0x82, 0x00, 0x00, 0x01, 0x03, 0x00, 0x02, 0x04, 0x00, 0x01, 0x18, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x7F, // 209 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x09, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 210 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x09, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 211 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x0A, 0x40, 0x00, 0x09, 0x40, 0x00, 0x09, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 212 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x0A, 0x40, 0x00, 0x09, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x09, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 213 +0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x20, 0x10, 0x00, 0x10, 0x20, 0x00, 0x08, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x08, 0x40, 0x00, 0x0A, 0x40, 0x00, 0x10, 0x20, 0x00, 0x20, 0x10, 0x00, 0xC0, 0x0F, // 214 +0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, 0x07, 0x00, 0x00, 0x05, 0x00, 0x80, 0x08, 0x00, 0x40, 0x10, // 215 +0x00, 0x00, 0x00, 0xC0, 0x4F, 0x00, 0x20, 0x30, 0x00, 0x10, 0x30, 0x00, 0x08, 0x4C, 0x00, 0x08, 0x42, 0x00, 0x08, 0x41, 0x00, 0xC8, 0x40, 0x00, 0x30, 0x20, 0x00, 0x30, 0x10, 0x00, 0xC8, 0x0F, // 216 +0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x01, 0x40, 0x00, 0x02, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 217 +0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x02, 0x40, 0x00, 0x01, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 218 +0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x02, 0x40, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00, 0x02, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 219 +0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x02, 0x40, 0x00, 0x00, 0x40, 0x00, 0x02, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x1F, // 220 +0x08, 0x00, 0x00, 0x30, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x01, 0x00, 0x02, 0x7E, 0x00, 0x81, 0x01, 0x00, 0x40, 0x00, 0x00, 0x30, 0x00, 0x00, 0x08, // 221 +0x00, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x20, 0x10, 0x00, 0x40, 0x08, 0x00, 0x80, 0x07, // 222 +0x00, 0x00, 0x00, 0xE0, 0x7F, 0x00, 0x10, 0x00, 0x00, 0x08, 0x20, 0x00, 0x88, 0x43, 0x00, 0x70, 0x42, 0x00, 0x00, 0x44, 0x00, 0x00, 0x38, // 223 +0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x48, 0x44, 0x00, 0x50, 0x42, 0x00, 0x40, 0x22, 0x00, 0x80, 0x7F, // 224 +0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x42, 0x00, 0x40, 0x22, 0x00, 0x80, 0x7F, // 225 +0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x48, 0x42, 0x00, 0x50, 0x22, 0x00, 0x80, 0x7F, // 226 +0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x50, 0x42, 0x00, 0x48, 0x22, 0x00, 0x80, 0x7F, // 227 +0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x50, 0x44, 0x00, 0x40, 0x44, 0x00, 0x50, 0x42, 0x00, 0x40, 0x22, 0x00, 0x80, 0x7F, // 228 +0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x5C, 0x44, 0x00, 0x54, 0x44, 0x00, 0x5C, 0x42, 0x00, 0x40, 0x22, 0x00, 0x80, 0x7F, // 229 +0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x80, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x42, 0x00, 0x40, 0x22, 0x00, 0x80, 0x3F, 0x00, 0x80, 0x24, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x24, 0x00, 0x00, 0x17, // 230 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x02, 0x40, 0xC0, 0x02, 0x40, 0x40, 0x03, 0x80, 0x20, // 231 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x48, 0x44, 0x00, 0x50, 0x44, 0x00, 0x40, 0x44, 0x00, 0x80, 0x24, 0x00, 0x00, 0x17, // 232 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x40, 0x44, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x80, 0x24, 0x00, 0x00, 0x17, // 233 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x50, 0x44, 0x00, 0x48, 0x44, 0x00, 0x48, 0x44, 0x00, 0x90, 0x24, 0x00, 0x00, 0x17, // 234 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x24, 0x00, 0x50, 0x44, 0x00, 0x40, 0x44, 0x00, 0x50, 0x44, 0x00, 0x80, 0x24, 0x00, 0x00, 0x17, // 235 +0x08, 0x00, 0x00, 0xD0, 0x7F, // 236 +0x00, 0x00, 0x00, 0xD0, 0x7F, 0x00, 0x08, // 237 +0x10, 0x00, 0x00, 0xC8, 0x7F, 0x00, 0x08, 0x00, 0x00, 0x10, // 238 +0x10, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x10, // 239 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0xA0, 0x20, 0x00, 0x68, 0x40, 0x00, 0x58, 0x40, 0x00, 0x70, 0x40, 0x00, 0xE8, 0x20, 0x00, 0x00, 0x1F, // 240 +0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0x90, 0x00, 0x00, 0x48, 0x00, 0x00, 0x50, 0x00, 0x00, 0x48, 0x00, 0x00, 0x80, 0x7F, // 241 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x48, 0x40, 0x00, 0x50, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1F, // 242 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x50, 0x40, 0x00, 0x48, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1F, // 243 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x50, 0x40, 0x00, 0x48, 0x40, 0x00, 0x48, 0x40, 0x00, 0x90, 0x20, 0x00, 0x00, 0x1F, // 244 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x50, 0x40, 0x00, 0x48, 0x40, 0x00, 0x50, 0x40, 0x00, 0x88, 0x20, 0x00, 0x00, 0x1F, // 245 +0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x80, 0x20, 0x00, 0x50, 0x40, 0x00, 0x40, 0x40, 0x00, 0x50, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1F, // 246 +0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x80, 0x0A, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, // 247 +0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x80, 0x30, 0x00, 0x40, 0x48, 0x00, 0x40, 0x44, 0x00, 0x40, 0x42, 0x00, 0x80, 0x21, 0x00, 0x40, 0x1F, // 248 +0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x40, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x7F, // 249 +0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x10, 0x40, 0x00, 0x08, 0x20, 0x00, 0xC0, 0x7F, // 250 +0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x10, 0x40, 0x00, 0x08, 0x40, 0x00, 0x08, 0x40, 0x00, 0x10, 0x20, 0x00, 0xC0, 0x7F, // 251 +0x00, 0x00, 0x00, 0xD0, 0x3F, 0x00, 0x00, 0x40, 0x00, 0x10, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x7F, // 252 +0xC0, 0x01, 0x00, 0x00, 0x06, 0x02, 0x00, 0x38, 0x02, 0x10, 0xE0, 0x01, 0x08, 0x38, 0x00, 0x00, 0x07, 0x00, 0xC0, // 253 +0x00, 0x00, 0x00, 0xF8, 0xFF, 0x03, 0x80, 0x20, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x40, 0x40, 0x00, 0x80, 0x20, 0x00, 0x00, 0x1F, // 254 +0xC0, 0x01, 0x00, 0x00, 0x06, 0x02, 0x10, 0x38, 0x02, 0x00, 0xE0, 0x01, 0x10, 0x38, 0x00, 0x00, 0x07, 0x00, 0xC0, // 255 +}; + +const uint8_t ArialMT_Plain_24_PL[] PROGMEM = { +0x18, // Width: 24 +0x1C, // Height: 28 +0x20, // First char: 32 +0xE0, // Number of chars: 224 +// Jump Table: +0xFF, 0xFF, 0x00, 0x06, // 32 +0x00, 0x00, 0x13, 0x06, // 33 +0x00, 0x13, 0x1A, 0x08, // 34 +0x00, 0x2D, 0x33, 0x0E, // 35 +0x00, 0x60, 0x2F, 0x0D, // 36 +0x00, 0x8F, 0x4F, 0x15, // 37 +0x00, 0xDE, 0x3B, 0x10, // 38 +0x01, 0x19, 0x0A, 0x04, // 39 +0x01, 0x23, 0x1C, 0x08, // 40 +0x01, 0x3F, 0x1B, 0x08, // 41 +0x01, 0x5A, 0x21, 0x0A, // 42 +0x01, 0x7B, 0x32, 0x0E, // 43 +0x01, 0xAD, 0x10, 0x05, // 44 +0x01, 0xBD, 0x1B, 0x08, // 45 +0x01, 0xD8, 0x0F, 0x05, // 46 +0x01, 0xE7, 0x19, 0x08, // 47 +0x02, 0x00, 0x2F, 0x0D, // 48 +0x02, 0x2F, 0x23, 0x0A, // 49 +0x02, 0x52, 0x2F, 0x0D, // 50 +0x02, 0x81, 0x2F, 0x0D, // 51 +0x02, 0xB0, 0x2F, 0x0D, // 52 +0x02, 0xDF, 0x2F, 0x0D, // 53 +0x03, 0x0E, 0x2F, 0x0D, // 54 +0x03, 0x3D, 0x2D, 0x0D, // 55 +0x03, 0x6A, 0x2F, 0x0D, // 56 +0x03, 0x99, 0x2F, 0x0D, // 57 +0x03, 0xC8, 0x0F, 0x05, // 58 +0x03, 0xD7, 0x10, 0x05, // 59 +0x03, 0xE7, 0x2F, 0x0D, // 60 +0x04, 0x16, 0x2F, 0x0D, // 61 +0x04, 0x45, 0x2E, 0x0D, // 62 +0x04, 0x73, 0x2E, 0x0D, // 63 +0x04, 0xA1, 0x5B, 0x18, // 64 +0x04, 0xFC, 0x3B, 0x10, // 65 +0x05, 0x37, 0x3B, 0x10, // 66 +0x05, 0x72, 0x3F, 0x11, // 67 +0x05, 0xB1, 0x3F, 0x11, // 68 +0x05, 0xF0, 0x3B, 0x10, // 69 +0x06, 0x2B, 0x35, 0x0F, // 70 +0x06, 0x60, 0x43, 0x12, // 71 +0x06, 0xA3, 0x3B, 0x10, // 72 +0x06, 0xDE, 0x0F, 0x05, // 73 +0x06, 0xED, 0x27, 0x0B, // 74 +0x07, 0x14, 0x3F, 0x11, // 75 +0x07, 0x53, 0x2F, 0x0D, // 76 +0x07, 0x82, 0x43, 0x12, // 77 +0x07, 0xC5, 0x3B, 0x10, // 78 +0x08, 0x00, 0x47, 0x13, // 79 +0x08, 0x47, 0x3A, 0x10, // 80 +0x08, 0x81, 0x47, 0x13, // 81 +0x08, 0xC8, 0x3F, 0x11, // 82 +0x09, 0x07, 0x3B, 0x10, // 83 +0x09, 0x42, 0x35, 0x0F, // 84 +0x09, 0x77, 0x3B, 0x10, // 85 +0x09, 0xB2, 0x39, 0x10, // 86 +0x09, 0xEB, 0x59, 0x18, // 87 +0x0A, 0x44, 0x3B, 0x10, // 88 +0x0A, 0x7F, 0x3D, 0x11, // 89 +0x0A, 0xBC, 0x37, 0x0F, // 90 +0x0A, 0xF3, 0x14, 0x06, // 91 +0x0B, 0x07, 0x1B, 0x08, // 92 +0x0B, 0x22, 0x18, 0x07, // 93 +0x0B, 0x3A, 0x2A, 0x0C, // 94 +0x0B, 0x64, 0x34, 0x0E, // 95 +0x0B, 0x98, 0x11, 0x06, // 96 +0x0B, 0xA9, 0x2F, 0x0D, // 97 +0x0B, 0xD8, 0x33, 0x0E, // 98 +0x0C, 0x0B, 0x2B, 0x0C, // 99 +0x0C, 0x36, 0x2F, 0x0D, // 100 +0x0C, 0x65, 0x2F, 0x0D, // 101 +0x0C, 0x94, 0x1A, 0x08, // 102 +0x0C, 0xAE, 0x2F, 0x0D, // 103 +0x0C, 0xDD, 0x2F, 0x0D, // 104 +0x0D, 0x0C, 0x0F, 0x05, // 105 +0x0D, 0x1B, 0x10, 0x05, // 106 +0x0D, 0x2B, 0x2F, 0x0D, // 107 +0x0D, 0x5A, 0x0F, 0x05, // 108 +0x0D, 0x69, 0x47, 0x13, // 109 +0x0D, 0xB0, 0x2F, 0x0D, // 110 +0x0D, 0xDF, 0x2F, 0x0D, // 111 +0x0E, 0x0E, 0x33, 0x0E, // 112 +0x0E, 0x41, 0x30, 0x0D, // 113 +0x0E, 0x71, 0x1E, 0x09, // 114 +0x0E, 0x8F, 0x2B, 0x0C, // 115 +0x0E, 0xBA, 0x1B, 0x08, // 116 +0x0E, 0xD5, 0x2F, 0x0D, // 117 +0x0F, 0x04, 0x2A, 0x0C, // 118 +0x0F, 0x2E, 0x42, 0x12, // 119 +0x0F, 0x70, 0x2B, 0x0C, // 120 +0x0F, 0x9B, 0x2A, 0x0C, // 121 +0x0F, 0xC5, 0x2B, 0x0C, // 122 +0x0F, 0xF0, 0x1C, 0x08, // 123 +0x10, 0x0C, 0x10, 0x05, // 124 +0x10, 0x1C, 0x1B, 0x08, // 125 +0x10, 0x37, 0x32, 0x0E, // 126 +0xFF, 0xFF, 0x00, 0x18, // 127 +0xFF, 0xFF, 0x00, 0x18, // 128 +0x10, 0x69, 0x2F, 0x0D, // 129 +0x10, 0x98, 0x16, 0x07, // 130 +0x10, 0xAE, 0x3B, 0x10, // 131 +0x10, 0xE9, 0x40, 0x11, // 132 +0x11, 0x29, 0x34, 0x0E, // 133 +0x11, 0x5D, 0x3F, 0x11, // 134 +0x11, 0x9C, 0x2B, 0x0C, // 135 +0x11, 0xC7, 0x2F, 0x0D, // 136 +0x11, 0xF6, 0x2B, 0x0C, // 137 +0xFF, 0xFF, 0x00, 0x18, // 138 +0xFF, 0xFF, 0x00, 0x18, // 139 +0xFF, 0xFF, 0x00, 0x18, // 140 +0xFF, 0xFF, 0x00, 0x18, // 141 +0xFF, 0xFF, 0x00, 0x18, // 142 +0xFF, 0xFF, 0x00, 0x18, // 143 +0xFF, 0xFF, 0x00, 0x18, // 144 +0xFF, 0xFF, 0x00, 0x18, // 145 +0xFF, 0xFF, 0x00, 0x18, // 146 +0x12, 0x21, 0x47, 0x13, // 147 +0x12, 0x68, 0x2F, 0x0D, // 148 +0xFF, 0xFF, 0x00, 0x18, // 149 +0xFF, 0xFF, 0x00, 0x18, // 150 +0xFF, 0xFF, 0x00, 0x18, // 151 +0x12, 0x97, 0x3B, 0x10, // 152 +0x12, 0xD2, 0x2F, 0x0D, // 153 +0x13, 0x01, 0x3B, 0x10, // 154 +0x13, 0x3C, 0x2B, 0x0C, // 155 +0xFF, 0xFF, 0x00, 0x18, // 156 +0xFF, 0xFF, 0x00, 0x18, // 157 +0xFF, 0xFF, 0x00, 0x18, // 158 +0xFF, 0xFF, 0x00, 0x18, // 159 +0xFF, 0xFF, 0x00, 0x18, // 160 +0x13, 0x67, 0x14, 0x06, // 161 +0x13, 0x7B, 0x2B, 0x0C, // 162 +0x13, 0xA6, 0x2F, 0x0D, // 163 +0x13, 0xD5, 0x33, 0x0E, // 164 +0x14, 0x08, 0x31, 0x0E, // 165 +0x14, 0x39, 0x10, 0x05, // 166 +0x14, 0x49, 0x2F, 0x0D, // 167 +0x14, 0x78, 0x19, 0x08, // 168 +0x14, 0x91, 0x46, 0x13, // 169 +0x14, 0xD7, 0x1A, 0x08, // 170 +0x14, 0xF1, 0x27, 0x0B, // 171 +0x15, 0x18, 0x2F, 0x0D, // 172 +0x15, 0x47, 0x1B, 0x08, // 173 +0x15, 0x62, 0x46, 0x13, // 174 +0x15, 0xA8, 0x31, 0x0E, // 175 +0x15, 0xD9, 0x1E, 0x09, // 176 +0x15, 0xF7, 0x33, 0x0E, // 177 +0x16, 0x2A, 0x1A, 0x08, // 178 +0x16, 0x44, 0x1A, 0x08, // 179 +0x16, 0x5E, 0x19, 0x08, // 180 +0x16, 0x77, 0x2F, 0x0D, // 181 +0x16, 0xA6, 0x31, 0x0E, // 182 +0x16, 0xD7, 0x12, 0x06, // 183 +0x16, 0xE9, 0x18, 0x07, // 184 +0x17, 0x01, 0x37, 0x0F, // 185 +0x17, 0x38, 0x1E, 0x09, // 186 +0x17, 0x56, 0x37, 0x0F, // 187 +0x17, 0x8D, 0x2B, 0x0C, // 188 +0x17, 0xB8, 0x4B, 0x14, // 189 +0x18, 0x03, 0x4B, 0x14, // 190 +0x18, 0x4E, 0x33, 0x0E, // 191 +0x18, 0x81, 0x3B, 0x10, // 192 +0x18, 0xBC, 0x3B, 0x10, // 193 +0x18, 0xF7, 0x3B, 0x10, // 194 +0x19, 0x32, 0x3B, 0x10, // 195 +0x19, 0x6D, 0x3B, 0x10, // 196 +0x19, 0xA8, 0x3B, 0x10, // 197 +0x19, 0xE3, 0x5B, 0x18, // 198 +0x1A, 0x3E, 0x3F, 0x11, // 199 +0x1A, 0x7D, 0x3B, 0x10, // 200 +0x1A, 0xB8, 0x3B, 0x10, // 201 +0x1A, 0xF3, 0x3B, 0x10, // 202 +0x1B, 0x2E, 0x3B, 0x10, // 203 +0x1B, 0x69, 0x11, 0x06, // 204 +0x1B, 0x7A, 0x11, 0x06, // 205 +0x1B, 0x8B, 0x15, 0x07, // 206 +0x1B, 0xA0, 0x15, 0x07, // 207 +0x1B, 0xB5, 0x3F, 0x11, // 208 +0x1B, 0xF4, 0x3B, 0x10, // 209 +0x1C, 0x2F, 0x47, 0x13, // 210 +0x1C, 0x76, 0x47, 0x13, // 211 +0x1C, 0xBD, 0x47, 0x13, // 212 +0x1D, 0x04, 0x47, 0x13, // 213 +0x1D, 0x4B, 0x47, 0x13, // 214 +0x1D, 0x92, 0x2B, 0x0C, // 215 +0x1D, 0xBD, 0x47, 0x13, // 216 +0x1E, 0x04, 0x3B, 0x10, // 217 +0x1E, 0x3F, 0x3B, 0x10, // 218 +0x1E, 0x7A, 0x3B, 0x10, // 219 +0x1E, 0xB5, 0x3B, 0x10, // 220 +0x1E, 0xF0, 0x3D, 0x11, // 221 +0x1F, 0x2D, 0x3A, 0x10, // 222 +0x1F, 0x67, 0x37, 0x0F, // 223 +0x1F, 0x9E, 0x2F, 0x0D, // 224 +0x1F, 0xCD, 0x2F, 0x0D, // 225 +0x1F, 0xFC, 0x2F, 0x0D, // 226 +0x20, 0x2B, 0x2F, 0x0D, // 227 +0x20, 0x5A, 0x2F, 0x0D, // 228 +0x20, 0x89, 0x2F, 0x0D, // 229 +0x20, 0xB8, 0x53, 0x16, // 230 +0x21, 0x0B, 0x2B, 0x0C, // 231 +0x21, 0x36, 0x2F, 0x0D, // 232 +0x21, 0x65, 0x2F, 0x0D, // 233 +0x21, 0x94, 0x2F, 0x0D, // 234 +0x21, 0xC3, 0x2F, 0x0D, // 235 +0x21, 0xF2, 0x11, 0x06, // 236 +0x22, 0x03, 0x11, 0x06, // 237 +0x22, 0x14, 0x15, 0x07, // 238 +0x22, 0x29, 0x15, 0x07, // 239 +0x22, 0x3E, 0x2F, 0x0D, // 240 +0x22, 0x6D, 0x2F, 0x0D, // 241 +0x22, 0x9C, 0x2F, 0x0D, // 242 +0x22, 0xCB, 0x2F, 0x0D, // 243 +0x22, 0xFA, 0x2F, 0x0D, // 244 +0x23, 0x29, 0x2F, 0x0D, // 245 +0x23, 0x58, 0x2F, 0x0D, // 246 +0x23, 0x87, 0x32, 0x0E, // 247 +0x23, 0xB9, 0x33, 0x0E, // 248 +0x23, 0xEC, 0x2F, 0x0D, // 249 +0x24, 0x1B, 0x2F, 0x0D, // 250 +0x24, 0x4A, 0x2F, 0x0D, // 251 +0x24, 0x79, 0x2F, 0x0D, // 252 +0x24, 0xA8, 0x2A, 0x0C, // 253 +0x24, 0xD2, 0x2F, 0x0D, // 254 +0x25, 0x01, 0x2A, 0x0C, // 255 +// Font Data: +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x33, 0x00, 0xE0, 0xFF, 0x33, // 33 +0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xE0, 0x07, // 34 +0x00, 0x0C, 0x03, 0x00, 0x00, 0x0C, 0x33, 0x00, 0x00, 0x0C, 0x3F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x80, 0xFF, 0x03, 0x00, 0xE0, 0x0F, 0x03, 0x00, 0x60, 0x0C, 0x33, 0x00, 0x00, 0x0C, 0x3F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x80, 0xFF, 0x03, 0x00, 0xE0, 0x0F, 0x03, 0x00, 0x60, 0x0C, 0x03, 0x00, 0x00, 0x0C, 0x03, // 35 +0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x06, 0x00, 0xC0, 0x0F, 0x1E, 0x00, 0xC0, 0x18, 0x1C, 0x00, 0x60, 0x18, 0x38, 0x00, 0x60, 0x30, 0x30, 0x00, 0xF0, 0xFF, 0xFF, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x60, 0x38, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0xC1, 0x1F, 0x00, 0x00, 0x81, 0x07, // 36 +0x00, 0x00, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x20, 0x20, 0x20, 0x00, 0x60, 0x30, 0x38, 0x00, 0xC0, 0x1F, 0x1E, 0x00, 0x80, 0x8F, 0x0F, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x8F, 0x0F, 0x00, 0xC0, 0xC3, 0x1F, 0x00, 0xE0, 0x60, 0x30, 0x00, 0x20, 0x20, 0x20, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x80, 0x0F, // 37 +0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x80, 0xE3, 0x1C, 0x00, 0xC0, 0x77, 0x38, 0x00, 0xE0, 0x3C, 0x30, 0x00, 0x60, 0x38, 0x30, 0x00, 0x60, 0x78, 0x30, 0x00, 0xE0, 0xEC, 0x38, 0x00, 0xC0, 0x8F, 0x1B, 0x00, 0x80, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0xC0, 0x38, 0x00, 0x00, 0x00, 0x10, // 38 +0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xE0, 0x07, // 39 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xFE, 0x7F, 0x00, 0x80, 0x0F, 0xF0, 0x01, 0xC0, 0x01, 0x80, 0x03, 0x60, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x04, // 40 +0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x04, 0x60, 0x00, 0x00, 0x06, 0xC0, 0x01, 0x80, 0x03, 0x80, 0x0F, 0xF0, 0x01, 0x00, 0xFE, 0x7F, 0x00, 0x00, 0xF0, 0x0F, // 41 +0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x80, // 42 +0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 43 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0xF0, 0x01, // 44 +0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, // 45 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, // 46 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x60, // 47 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x38, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0x00, 0xFE, 0x03, // 48 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 49 +0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x00, 0xC0, 0x03, 0x38, 0x00, 0xC0, 0x00, 0x3C, 0x00, 0x60, 0x00, 0x36, 0x00, 0x60, 0x00, 0x33, 0x00, 0x60, 0x80, 0x31, 0x00, 0x60, 0xC0, 0x30, 0x00, 0x60, 0x60, 0x30, 0x00, 0xC0, 0x30, 0x30, 0x00, 0xC0, 0x1F, 0x30, 0x00, 0x00, 0x0F, 0x30, // 50 +0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xC0, 0x01, 0x0E, 0x00, 0xC0, 0x00, 0x1C, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0xC0, 0x38, 0x30, 0x00, 0xC0, 0x6F, 0x18, 0x00, 0x80, 0xC7, 0x0F, 0x00, 0x00, 0x80, 0x07, // 51 +0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x3C, 0x03, 0x00, 0x00, 0x0E, 0x03, 0x00, 0x80, 0x07, 0x03, 0x00, 0xC0, 0x01, 0x03, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, // 52 +0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x06, 0x00, 0x80, 0x3F, 0x0E, 0x00, 0xE0, 0x1F, 0x18, 0x00, 0x60, 0x08, 0x30, 0x00, 0x60, 0x0C, 0x30, 0x00, 0x60, 0x0C, 0x30, 0x00, 0x60, 0x0C, 0x30, 0x00, 0x60, 0x0C, 0x30, 0x00, 0x60, 0x18, 0x1C, 0x00, 0x60, 0xF0, 0x0F, 0x00, 0x00, 0xE0, 0x03, // 53 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x03, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0x63, 0x1C, 0x00, 0xC0, 0x30, 0x38, 0x00, 0x60, 0x18, 0x30, 0x00, 0x60, 0x18, 0x30, 0x00, 0x60, 0x18, 0x30, 0x00, 0x60, 0x18, 0x30, 0x00, 0xE0, 0x30, 0x18, 0x00, 0xC0, 0xF1, 0x0F, 0x00, 0x80, 0xC1, 0x07, // 54 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x60, 0x80, 0x3F, 0x00, 0x60, 0xE0, 0x03, 0x00, 0x60, 0x78, 0x00, 0x00, 0x60, 0x0E, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x60, // 55 +0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x80, 0xC7, 0x1F, 0x00, 0xC0, 0x6F, 0x18, 0x00, 0xE0, 0x38, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0xE0, 0x38, 0x30, 0x00, 0xC0, 0x6F, 0x18, 0x00, 0x80, 0xC7, 0x1F, 0x00, 0x00, 0x80, 0x07, // 56 +0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x0C, 0x00, 0x80, 0x7F, 0x1C, 0x00, 0xC0, 0x61, 0x38, 0x00, 0x60, 0xC0, 0x30, 0x00, 0x60, 0xC0, 0x30, 0x00, 0x60, 0xC0, 0x30, 0x00, 0x60, 0xC0, 0x30, 0x00, 0x60, 0x60, 0x18, 0x00, 0xC0, 0x31, 0x1E, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0x00, 0xFE, 0x01, // 57 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, // 58 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x30, 0x03, 0x00, 0x06, 0xF0, 0x01, // 59 +0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x03, 0x06, // 60 +0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, // 61 +0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0x8C, 0x01, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x00, 0xD8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x20, // 62 +0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x60, 0x80, 0x33, 0x00, 0x60, 0xC0, 0x33, 0x00, 0x60, 0xE0, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0xC0, 0x38, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x00, 0x07, // 63 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x1E, 0xF0, 0x00, 0x00, 0x07, 0xC0, 0x01, 0x80, 0xC3, 0x87, 0x01, 0xC0, 0xF1, 0x9F, 0x03, 0xC0, 0x38, 0x18, 0x03, 0xC0, 0x0C, 0x30, 0x03, 0x60, 0x0E, 0x30, 0x06, 0x60, 0x06, 0x30, 0x06, 0x60, 0x06, 0x18, 0x06, 0x60, 0x06, 0x0C, 0x06, 0x60, 0x0C, 0x1E, 0x06, 0x60, 0xF8, 0x3F, 0x06, 0xE0, 0xFE, 0x31, 0x06, 0xC0, 0x0E, 0x30, 0x06, 0xC0, 0x01, 0x18, 0x03, 0x80, 0x03, 0x1C, 0x03, 0x00, 0x07, 0x8F, 0x01, 0x00, 0xFE, 0x87, 0x01, 0x00, 0xF8, 0xC1, 0x00, 0x00, 0x00, 0x40, // 64 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x80, 0x8F, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x80, 0x8F, 0x01, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 65 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0xC0, 0x78, 0x30, 0x00, 0xC0, 0xFF, 0x18, 0x00, 0x80, 0xC7, 0x1F, 0x00, 0x00, 0x80, 0x07, // 66 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0F, 0x00, 0x00, 0x02, 0x03, // 67 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0E, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 68 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 69 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, // 70 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xE0, 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x60, 0x30, 0x00, 0x60, 0x60, 0x30, 0x00, 0xE0, 0x60, 0x38, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0x61, 0x18, 0x00, 0x80, 0xE3, 0x0F, 0x00, 0x00, 0xE2, 0x0F, // 71 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 72 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 73 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0xE0, 0xFF, 0x0F, // 74 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0xE7, 0x01, 0x00, 0x80, 0x83, 0x07, 0x00, 0xC0, 0x01, 0x0F, 0x00, 0xE0, 0x00, 0x1E, 0x00, 0x60, 0x00, 0x38, 0x00, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00, 0x20, // 75 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, // 76 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 77 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 78 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 79 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x00, 0x0F, // 80 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x0C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xE0, 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x36, 0x00, 0x60, 0x00, 0x36, 0x00, 0xE0, 0x00, 0x3C, 0x00, 0xC0, 0x00, 0x1C, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x3F, 0x00, 0x00, 0xFF, 0x77, 0x00, 0x00, 0xFC, 0x61, // 81 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x70, 0x00, 0x00, 0x60, 0xF0, 0x00, 0x00, 0x60, 0xF0, 0x03, 0x00, 0x60, 0xB0, 0x07, 0x00, 0xE0, 0x18, 0x1F, 0x00, 0xC0, 0x1F, 0x3C, 0x00, 0x80, 0x0F, 0x30, 0x00, 0x00, 0x00, 0x20, // 82 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0x0F, 0x00, 0xC0, 0x1F, 0x1C, 0x00, 0xC0, 0x18, 0x18, 0x00, 0x60, 0x38, 0x38, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x70, 0x30, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0xE1, 0x18, 0x00, 0x80, 0xC3, 0x0F, 0x00, 0x00, 0x83, 0x07, // 83 +0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 84 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 85 +0x20, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x3E, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x20, // 86 +0x60, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x80, 0x1F, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x80, 0xFF, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x60, // 87 +0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x30, 0x00, 0x60, 0x00, 0x3C, 0x00, 0xE0, 0x01, 0x1E, 0x00, 0xC0, 0x83, 0x07, 0x00, 0x00, 0xCF, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xCF, 0x03, 0x00, 0xC0, 0x03, 0x07, 0x00, 0xE0, 0x01, 0x1E, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00, 0x20, // 88 +0x20, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x20, // 89 +0x00, 0x00, 0x30, 0x00, 0x60, 0x00, 0x38, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x60, 0x00, 0x37, 0x00, 0x60, 0x80, 0x33, 0x00, 0x60, 0xC0, 0x31, 0x00, 0x60, 0xE0, 0x30, 0x00, 0x60, 0x38, 0x30, 0x00, 0x60, 0x1C, 0x30, 0x00, 0x60, 0x0E, 0x30, 0x00, 0x60, 0x07, 0x30, 0x00, 0xE0, 0x01, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, // 90 +0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x60, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, // 91 +0x60, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 92 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, // 93 +0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x20, // 94 +0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, // 95 +0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x80, // 96 +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x00, 0x86, 0x31, 0x00, 0x00, 0x86, 0x31, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x18, 0x00, 0x00, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x20, // 97 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xE0, 0x03, // 98 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x18, 0x0C, // 99 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 100 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xDC, 0x18, 0x00, 0x00, 0xF8, 0x0C, 0x00, 0x00, 0xF0, 0x04, // 101 +0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xC0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x06, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x60, 0x06, // 102 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x01, 0x00, 0xF8, 0x8F, 0x03, 0x00, 0x1C, 0x1C, 0x07, 0x00, 0x0E, 0x38, 0x06, 0x00, 0x06, 0x30, 0x06, 0x00, 0x06, 0x30, 0x06, 0x00, 0x06, 0x30, 0x06, 0x00, 0x0C, 0x18, 0x07, 0x00, 0x18, 0x8C, 0x03, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0xFE, 0xFF, // 103 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 104 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0x60, 0xFE, 0x3F, // 105 +0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x60, 0xFE, 0xFF, 0x07, 0x60, 0xFE, 0xFF, 0x03, // 106 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x98, 0x07, 0x00, 0x00, 0x0C, 0x0E, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x02, 0x30, 0x00, 0x00, 0x00, 0x20, // 107 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 108 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 109 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 110 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x07, // 111 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xE0, 0x03, // 112 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0xFE, 0xFF, 0x07, // 113 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, // 114 +0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x0C, 0x00, 0x00, 0x7C, 0x1C, 0x00, 0x00, 0xEE, 0x38, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x31, 0x00, 0x00, 0xC6, 0x31, 0x00, 0x00, 0x8E, 0x39, 0x00, 0x00, 0x9C, 0x1F, 0x00, 0x00, 0x18, 0x0F, // 115 +0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xC0, 0xFF, 0x1F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, // 116 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 117 +0x00, 0x06, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 118 +0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x0E, // 119 +0x00, 0x02, 0x20, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x1E, 0x3C, 0x00, 0x00, 0x38, 0x0E, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x38, 0x0E, 0x00, 0x00, 0x1C, 0x3C, 0x00, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x02, 0x20, // 120 +0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 121 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x06, 0x3E, 0x00, 0x00, 0x06, 0x37, 0x00, 0x00, 0xC6, 0x33, 0x00, 0x00, 0xE6, 0x30, 0x00, 0x00, 0x76, 0x30, 0x00, 0x00, 0x3E, 0x30, 0x00, 0x00, 0x1E, 0x30, 0x00, 0x00, 0x06, 0x30, // 122 +0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x03, 0x00, 0xC0, 0x7F, 0xFE, 0x03, 0xE0, 0x3F, 0xFC, 0x07, 0x60, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, // 123 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFF, 0x0F, // 124 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, 0xE0, 0x3F, 0xFC, 0x07, 0xC0, 0x7F, 0xFF, 0x03, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x80, 0x01, // 125 +0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x60, // 126 +0x00, 0x60, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, // 129 +0x00, 0x60, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x06, // 130 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x3C, 0x00, 0x00, 0x1C, 0x70, 0x00, 0x00, 0x0C, 0xE0, 0x01, 0x00, 0x04, 0x80, 0x03, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 131 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x80, 0x8F, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x80, 0x8F, 0x01, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xB0, 0x03, 0x00, 0x00, 0x00, 0x03, // 132 +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x00, 0x86, 0x31, 0x00, 0x00, 0x86, 0x31, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x18, 0x00, 0x00, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8, 0xFF, 0x01, 0x00, 0x00, 0xA0, 0x03, 0x00, 0x00, 0x00, 0x03, // 133 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x68, 0x00, 0x30, 0x00, 0x6E, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x62, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0F, 0x00, 0x00, 0x02, 0x03, // 134 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x80, 0x06, 0x30, 0x00, 0xE0, 0x06, 0x30, 0x00, 0x60, 0x06, 0x30, 0x00, 0x20, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x18, 0x0C, // 135 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x80, 0x06, 0x00, 0x00, 0xE0, 0x06, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x20, 0x0E, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 136 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x06, 0x3E, 0x00, 0x00, 0x06, 0x37, 0x00, 0x80, 0xC6, 0x33, 0x00, 0xE0, 0xE6, 0x30, 0x00, 0x60, 0x76, 0x30, 0x00, 0x20, 0x3E, 0x30, 0x00, 0x00, 0x1E, 0x30, 0x00, 0x00, 0x06, 0x30, // 137 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x68, 0x00, 0x30, 0x00, 0x6E, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0xE2, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 147 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x06, 0x30, 0x00, 0x80, 0x06, 0x30, 0x00, 0xE0, 0x06, 0x30, 0x00, 0x60, 0x0E, 0x38, 0x00, 0x20, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x07, // 148 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0xF0, 0x01, 0x60, 0x30, 0xB0, 0x03, 0x60, 0x30, 0x30, 0x03, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 152 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0xF0, 0x01, 0x00, 0xC6, 0xB0, 0x03, 0x00, 0xCE, 0x38, 0x03, 0x00, 0xDC, 0x18, 0x00, 0x00, 0xF8, 0x0C, 0x00, 0x00, 0xF0, 0x04, // 153 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0x0F, 0x00, 0xC0, 0x1F, 0x1C, 0x00, 0xC0, 0x18, 0x18, 0x00, 0x60, 0x38, 0x38, 0x00, 0x60, 0x30, 0x30, 0x00, 0x68, 0x30, 0x30, 0x00, 0x6E, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x62, 0x70, 0x30, 0x00, 0xC0, 0x60, 0x18, 0x00, 0xC0, 0xE1, 0x18, 0x00, 0x80, 0xC3, 0x0F, 0x00, 0x00, 0x83, 0x07, // 154 +0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x0C, 0x00, 0x00, 0x7C, 0x1C, 0x00, 0x00, 0xEE, 0x38, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x80, 0xC6, 0x30, 0x00, 0xE0, 0xC6, 0x31, 0x00, 0x60, 0xC6, 0x31, 0x00, 0x20, 0x8E, 0x39, 0x00, 0x00, 0x9C, 0x1F, 0x00, 0x00, 0x18, 0x0F, // 155 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0x07, 0x00, 0xE6, 0xFF, 0x07, // 161 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x9C, 0x07, 0x00, 0x0E, 0x78, 0x00, 0x00, 0x06, 0x3F, 0x00, 0x00, 0xF6, 0x30, 0x00, 0x00, 0x0E, 0x30, 0x00, 0xE0, 0x0D, 0x1C, 0x00, 0x00, 0x1C, 0x0E, 0x00, 0x00, 0x10, 0x06, // 162 +0x00, 0x60, 0x10, 0x00, 0x00, 0x60, 0x38, 0x00, 0x00, 0x7F, 0x1C, 0x00, 0xC0, 0xFF, 0x1F, 0x00, 0xE0, 0xE0, 0x19, 0x00, 0x60, 0x60, 0x18, 0x00, 0x60, 0x60, 0x18, 0x00, 0x60, 0x60, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0xC0, 0x01, 0x30, 0x00, 0x80, 0x01, 0x38, 0x00, 0x00, 0x00, 0x10, // 163 +0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0xF7, 0x0E, 0x00, 0x00, 0xFE, 0x07, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0xFE, 0x07, 0x00, 0x00, 0xF7, 0x0E, 0x00, 0x00, 0x02, 0x04, // 164 +0xE0, 0x60, 0x06, 0x00, 0xC0, 0x61, 0x06, 0x00, 0x80, 0x67, 0x06, 0x00, 0x00, 0x7E, 0x06, 0x00, 0x00, 0x7C, 0x06, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x7C, 0x06, 0x00, 0x00, 0x7E, 0x06, 0x00, 0x80, 0x67, 0x06, 0x00, 0xC0, 0x61, 0x06, 0x00, 0xE0, 0x60, 0x06, 0x00, 0x20, // 165 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x7F, 0xF8, 0x0F, 0xE0, 0x7F, 0xF8, 0x0F, // 166 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x80, 0xF3, 0xC1, 0x00, 0xC0, 0x1F, 0xC3, 0x03, 0xE0, 0x0C, 0x07, 0x03, 0x60, 0x1C, 0x06, 0x06, 0x60, 0x18, 0x0C, 0x06, 0x60, 0x30, 0x1C, 0x06, 0xE0, 0x70, 0x38, 0x07, 0xC0, 0xE1, 0xF4, 0x03, 0x80, 0xC1, 0xE7, 0x01, 0x00, 0x80, 0x03, // 167 +0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 168 +0x00, 0xF8, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0x07, 0x07, 0x00, 0x80, 0x01, 0x0C, 0x00, 0xC0, 0x79, 0x1C, 0x00, 0xC0, 0xFE, 0x19, 0x00, 0x60, 0x86, 0x31, 0x00, 0x60, 0x03, 0x33, 0x00, 0x60, 0x03, 0x33, 0x00, 0x60, 0x03, 0x33, 0x00, 0x60, 0x03, 0x33, 0x00, 0x60, 0x87, 0x33, 0x00, 0xC0, 0x86, 0x19, 0x00, 0xC0, 0x85, 0x1C, 0x00, 0x80, 0x01, 0x0C, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xF8, // 169 +0x00, 0x00, 0x00, 0x00, 0xC0, 0x1C, 0x00, 0x00, 0xE0, 0x3E, 0x00, 0x00, 0x60, 0x32, 0x00, 0x00, 0x60, 0x32, 0x00, 0x00, 0xE0, 0x3F, 0x00, 0x00, 0xC0, 0x3F, // 170 +0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x78, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x84, 0x10, 0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x78, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x04, 0x10, // 171 +0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFC, 0x01, // 172 +0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, // 173 +0x00, 0xF8, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0x07, 0x07, 0x00, 0x80, 0x01, 0x0C, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0xFE, 0x1B, 0x00, 0x60, 0xFE, 0x33, 0x00, 0x60, 0x66, 0x30, 0x00, 0x60, 0x66, 0x30, 0x00, 0x60, 0xE6, 0x30, 0x00, 0x60, 0xFE, 0x31, 0x00, 0x60, 0x3C, 0x33, 0x00, 0xC0, 0x00, 0x1A, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x01, 0x0C, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xF8, // 174 +0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, // 175 +0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x80, 0x03, // 176 +0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x60, 0x30, // 177 +0x40, 0x20, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x20, 0x38, 0x00, 0x00, 0x20, 0x2C, 0x00, 0x00, 0x20, 0x26, 0x00, 0x00, 0xE0, 0x23, 0x00, 0x00, 0xC0, 0x21, // 178 +0x40, 0x10, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x20, 0x22, 0x00, 0x00, 0x20, 0x22, 0x00, 0x00, 0xE0, 0x3D, 0x00, 0x00, 0xC0, 0x1D, // 179 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x20, // 180 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 181 +0x00, 0x0F, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0xE0, 0x7F, 0x00, 0x00, 0xE0, 0x7F, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x60, 0x00, 0x00, 0x00, 0x60, // 182 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, // 183 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xC0, 0x02, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x01, // 184 +0x00, 0x00, 0x30, 0x00, 0x60, 0x00, 0x38, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x60, 0x00, 0x37, 0x00, 0x60, 0x80, 0x33, 0x00, 0x60, 0xC0, 0x31, 0x00, 0x68, 0xE0, 0x30, 0x00, 0x6E, 0x38, 0x30, 0x00, 0x66, 0x1C, 0x30, 0x00, 0x62, 0x0E, 0x30, 0x00, 0x60, 0x07, 0x30, 0x00, 0xE0, 0x01, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, // 185 +0x00, 0x00, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0xE0, 0x38, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0xE0, 0x38, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x80, 0x0F, // 186 +0x00, 0x00, 0x30, 0x00, 0x60, 0x00, 0x38, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x60, 0x00, 0x37, 0x00, 0x60, 0x80, 0x33, 0x00, 0x60, 0xC0, 0x31, 0x00, 0x6C, 0xE0, 0x30, 0x00, 0x6C, 0x38, 0x30, 0x00, 0x60, 0x1C, 0x30, 0x00, 0x60, 0x0E, 0x30, 0x00, 0x60, 0x07, 0x30, 0x00, 0xE0, 0x01, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, // 187 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x06, 0x3E, 0x00, 0x00, 0x06, 0x37, 0x00, 0xC0, 0xC6, 0x33, 0x00, 0xC0, 0xE6, 0x30, 0x00, 0x00, 0x76, 0x30, 0x00, 0x00, 0x3E, 0x30, 0x00, 0x00, 0x1E, 0x30, 0x00, 0x00, 0x06, 0x30, // 188 +0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x20, 0x00, 0xE0, 0x3F, 0x30, 0x00, 0xE0, 0x3F, 0x1C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x4E, 0x20, 0x00, 0x00, 0x67, 0x30, 0x00, 0xC0, 0x21, 0x38, 0x00, 0xE0, 0x20, 0x2C, 0x00, 0x60, 0x20, 0x26, 0x00, 0x00, 0xE0, 0x27, 0x00, 0x00, 0xC0, 0x21, // 189 +0x40, 0x10, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x20, 0x22, 0x20, 0x00, 0x20, 0x22, 0x30, 0x00, 0xE0, 0x3D, 0x38, 0x00, 0xC0, 0x1D, 0x0E, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0E, 0x0C, 0x00, 0x00, 0x07, 0x0E, 0x00, 0x80, 0x83, 0x0B, 0x00, 0xE0, 0xC0, 0x08, 0x00, 0x60, 0xE0, 0x3F, 0x00, 0x20, 0xE0, 0x3F, 0x00, 0x00, 0x00, 0x08, // 190 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xF8, 0x03, 0x00, 0x00, 0x1E, 0x03, 0x00, 0x00, 0x07, 0x07, 0x00, 0xE6, 0x03, 0x06, 0x00, 0xE6, 0x01, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xC0, // 191 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x82, 0x8F, 0x01, 0x00, 0xE6, 0x83, 0x01, 0x00, 0x6E, 0x80, 0x01, 0x00, 0xE8, 0x83, 0x01, 0x00, 0x80, 0x8F, 0x01, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 192 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x80, 0x8F, 0x01, 0x00, 0xE8, 0x83, 0x01, 0x00, 0x6E, 0x80, 0x01, 0x00, 0xE6, 0x83, 0x01, 0x00, 0x82, 0x8F, 0x01, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 193 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x88, 0x8F, 0x01, 0x00, 0xEC, 0x83, 0x01, 0x00, 0x66, 0x80, 0x01, 0x00, 0xE6, 0x83, 0x01, 0x00, 0x8C, 0x8F, 0x01, 0x00, 0x08, 0xFE, 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 194 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x0C, 0xFE, 0x01, 0x00, 0x8E, 0x8F, 0x01, 0x00, 0xE6, 0x83, 0x01, 0x00, 0x66, 0x80, 0x01, 0x00, 0xEC, 0x83, 0x01, 0x00, 0x8C, 0x8F, 0x01, 0x00, 0x0E, 0xFE, 0x01, 0x00, 0x06, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 195 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x8C, 0x8F, 0x01, 0x00, 0xEC, 0x83, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0xE0, 0x83, 0x01, 0x00, 0x8C, 0x8F, 0x01, 0x00, 0x0C, 0xFE, 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 196 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x9C, 0x8F, 0x01, 0x00, 0xE2, 0x83, 0x01, 0x00, 0x62, 0x80, 0x01, 0x00, 0xE2, 0x83, 0x01, 0x00, 0x9C, 0x8F, 0x01, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x30, // 197 +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0xBC, 0x01, 0x00, 0x00, 0x8F, 0x01, 0x00, 0xC0, 0x83, 0x01, 0x00, 0xE0, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 198 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x60, 0x00, 0x30, 0x02, 0x60, 0x00, 0x30, 0x02, 0x60, 0x00, 0xF0, 0x02, 0x60, 0x00, 0xB0, 0x03, 0x60, 0x00, 0x30, 0x01, 0x60, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0F, 0x00, 0x00, 0x02, 0x03, // 199 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x62, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x6E, 0x30, 0x30, 0x00, 0x68, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 200 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x68, 0x30, 0x30, 0x00, 0x6E, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x62, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 201 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x68, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x66, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x68, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 202 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x6C, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, // 203 +0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0x3F, 0x00, 0xEE, 0xFF, 0x3F, 0x00, 0x08, // 204 +0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xEE, 0xFF, 0x3F, 0x00, 0xE6, 0xFF, 0x3F, 0x00, 0x02, // 205 +0x08, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0x3F, 0x00, 0xE6, 0xFF, 0x3F, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, // 206 +0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, // 207 +0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x30, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x03, 0x0E, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 208 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x8C, 0x03, 0x00, 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x06, 0x70, 0x00, 0x00, 0x0C, 0xE0, 0x01, 0x00, 0x0C, 0x80, 0x03, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x06, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, // 209 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x62, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x6E, 0x00, 0x30, 0x00, 0x68, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 210 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x60, 0x00, 0x30, 0x00, 0x68, 0x00, 0x30, 0x00, 0x6E, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x62, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 211 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x68, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0xE8, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 212 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xCC, 0x00, 0x18, 0x00, 0xEE, 0x00, 0x38, 0x00, 0x66, 0x00, 0x30, 0x00, 0x66, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x6E, 0x00, 0x30, 0x00, 0xE6, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 213 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x80, 0x07, 0x0F, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x6C, 0x00, 0x30, 0x00, 0xEC, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x01, 0x1C, 0x00, 0x80, 0x07, 0x0F, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0xFC, 0x01, // 214 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x8E, 0x03, 0x00, 0x00, 0xDC, 0x01, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0xDC, 0x01, 0x00, 0x00, 0x8E, 0x03, 0x00, 0x00, 0x06, 0x03, // 215 +0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x21, 0x00, 0x00, 0xFF, 0x77, 0x00, 0x80, 0x07, 0x3F, 0x00, 0xC0, 0x01, 0x1E, 0x00, 0xC0, 0x00, 0x1F, 0x00, 0xE0, 0x80, 0x3B, 0x00, 0x60, 0xC0, 0x31, 0x00, 0x60, 0xE0, 0x30, 0x00, 0x60, 0x70, 0x30, 0x00, 0x60, 0x38, 0x30, 0x00, 0x60, 0x1C, 0x30, 0x00, 0xE0, 0x0E, 0x38, 0x00, 0xC0, 0x07, 0x18, 0x00, 0xC0, 0x03, 0x1C, 0x00, 0xE0, 0x07, 0x0F, 0x00, 0x70, 0xFF, 0x07, 0x00, 0x20, 0xFC, 0x01, // 216 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x02, 0x00, 0x30, 0x00, 0x06, 0x00, 0x30, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 217 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x06, 0x00, 0x30, 0x00, 0x02, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 218 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x06, 0x00, 0x30, 0x00, 0x06, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x08, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 219 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x03, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1C, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0x03, // 220 +0x20, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x08, 0xF0, 0x3F, 0x00, 0x0E, 0xF0, 0x3F, 0x00, 0x06, 0x3C, 0x00, 0x00, 0x02, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x20, // 221 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0xE0, 0xFF, 0x3F, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x86, 0x03, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00, 0xF8, // 222 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x3F, 0x00, 0xC0, 0xFF, 0x3F, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x08, 0x00, 0x60, 0x00, 0x1C, 0x00, 0x60, 0x00, 0x38, 0x00, 0xE0, 0x78, 0x30, 0x00, 0xC0, 0x7F, 0x30, 0x00, 0x80, 0xC7, 0x30, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x0F, // 223 +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x20, 0x86, 0x31, 0x00, 0x60, 0x86, 0x31, 0x00, 0xE0, 0xC6, 0x30, 0x00, 0x80, 0xC6, 0x18, 0x00, 0x00, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x20, // 224 +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x00, 0x86, 0x31, 0x00, 0x80, 0x86, 0x31, 0x00, 0xE0, 0xC6, 0x30, 0x00, 0x60, 0xC6, 0x18, 0x00, 0x20, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x20, // 225 +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x80, 0x8C, 0x39, 0x00, 0xC0, 0x86, 0x31, 0x00, 0x60, 0x86, 0x31, 0x00, 0x60, 0xC6, 0x30, 0x00, 0xC0, 0xC6, 0x18, 0x00, 0x80, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x20, // 226 +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0xC0, 0x1C, 0x1F, 0x00, 0xE0, 0x8C, 0x39, 0x00, 0x60, 0x86, 0x31, 0x00, 0x60, 0x86, 0x31, 0x00, 0xC0, 0xC6, 0x30, 0x00, 0xC0, 0xC6, 0x18, 0x00, 0xE0, 0xCE, 0x0C, 0x00, 0x60, 0xFC, 0x1F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x20, // 227 +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0xC0, 0x8C, 0x39, 0x00, 0xC0, 0x86, 0x31, 0x00, 0x00, 0x86, 0x31, 0x00, 0x00, 0xC6, 0x30, 0x00, 0xC0, 0xC6, 0x18, 0x00, 0xC0, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x20, // 228 +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0E, 0x00, 0x00, 0x1C, 0x1F, 0x00, 0x00, 0x8C, 0x39, 0x00, 0x70, 0x86, 0x31, 0x00, 0x88, 0x86, 0x31, 0x00, 0x88, 0xC6, 0x30, 0x00, 0x88, 0xC6, 0x18, 0x00, 0x70, 0xCE, 0x0C, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x20, // 229 +0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0F, 0x00, 0x00, 0x9C, 0x1F, 0x00, 0x00, 0xCC, 0x39, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0x66, 0x18, 0x00, 0x00, 0x6E, 0x1C, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0xCC, 0x1C, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xCC, 0x18, 0x00, 0x00, 0xF8, 0x0C, 0x00, 0x00, 0xE0, 0x04, // 230 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x02, 0x00, 0x06, 0x30, 0x02, 0x00, 0x06, 0xF0, 0x02, 0x00, 0x06, 0xB0, 0x03, 0x00, 0x0E, 0x38, 0x01, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x18, 0x0C, // 231 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0x20, 0xCE, 0x38, 0x00, 0x60, 0xC6, 0x30, 0x00, 0xE0, 0xC6, 0x30, 0x00, 0x80, 0xC6, 0x30, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x00, 0xDC, 0x18, 0x00, 0x00, 0xF8, 0x0C, 0x00, 0x00, 0xF0, 0x04, // 232 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0x00, 0xCE, 0x38, 0x00, 0x80, 0xC6, 0x30, 0x00, 0xE0, 0xC6, 0x30, 0x00, 0x60, 0xC6, 0x30, 0x00, 0x20, 0xCE, 0x38, 0x00, 0x00, 0xDC, 0x18, 0x00, 0x00, 0xF8, 0x0C, 0x00, 0x00, 0xF0, 0x04, // 233 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0x80, 0xCE, 0x38, 0x00, 0xC0, 0xC6, 0x30, 0x00, 0x60, 0xC6, 0x30, 0x00, 0x60, 0xC6, 0x30, 0x00, 0xC0, 0xCE, 0x38, 0x00, 0x80, 0xDC, 0x18, 0x00, 0x00, 0xF8, 0x0C, 0x00, 0x00, 0xF0, 0x04, // 234 +0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xDC, 0x1C, 0x00, 0xC0, 0xCE, 0x38, 0x00, 0xC0, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0x00, 0xC6, 0x30, 0x00, 0xC0, 0xCE, 0x38, 0x00, 0xC0, 0xDC, 0x18, 0x00, 0x00, 0xF8, 0x0C, 0x00, 0x00, 0xF0, 0x04, // 235 +0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0xE0, 0xFE, 0x3F, 0x00, 0x80, // 236 +0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xE0, 0xFE, 0x3F, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0x20, // 237 +0x80, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0x60, 0xFE, 0x3F, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x80, // 238 +0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, // 239 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1D, 0x1C, 0x00, 0xA0, 0x0F, 0x38, 0x00, 0xA0, 0x06, 0x30, 0x00, 0xE0, 0x06, 0x30, 0x00, 0xC0, 0x06, 0x30, 0x00, 0xC0, 0x0F, 0x38, 0x00, 0x20, 0x1F, 0x1C, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0xE0, 0x07, // 240 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0xC0, 0xFE, 0x3F, 0x00, 0xE0, 0x18, 0x00, 0x00, 0x60, 0x0C, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0xC0, 0x06, 0x00, 0x00, 0xC0, 0x06, 0x00, 0x00, 0xE0, 0x0E, 0x00, 0x00, 0x60, 0xFC, 0x3F, 0x00, 0x00, 0xF8, 0x3F, // 241 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x20, 0x0E, 0x38, 0x00, 0x60, 0x06, 0x30, 0x00, 0xE0, 0x06, 0x30, 0x00, 0x80, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x07, // 242 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x80, 0x06, 0x30, 0x00, 0xE0, 0x06, 0x30, 0x00, 0x60, 0x06, 0x30, 0x00, 0x20, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x07, // 243 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x80, 0x0E, 0x38, 0x00, 0xC0, 0x06, 0x30, 0x00, 0x60, 0x06, 0x30, 0x00, 0x60, 0x06, 0x30, 0x00, 0xC0, 0x0E, 0x38, 0x00, 0x80, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x07, // 244 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0xC0, 0x1C, 0x1C, 0x00, 0xE0, 0x0E, 0x38, 0x00, 0x60, 0x06, 0x30, 0x00, 0x60, 0x06, 0x30, 0x00, 0xC0, 0x06, 0x30, 0x00, 0xC0, 0x0E, 0x38, 0x00, 0xE0, 0x1C, 0x1C, 0x00, 0x60, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x07, // 245 +0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0xC0, 0x0E, 0x38, 0x00, 0xC0, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0xC0, 0x0E, 0x38, 0x00, 0xC0, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x07, // 246 +0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xB6, 0x01, 0x00, 0x00, 0xB6, 0x01, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, // 247 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x67, 0x00, 0x00, 0xF8, 0x7F, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0x0E, 0x3F, 0x00, 0x00, 0x86, 0x33, 0x00, 0x00, 0xE6, 0x31, 0x00, 0x00, 0x76, 0x30, 0x00, 0x00, 0x3E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x00, 0xF3, 0x07, // 248 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x20, 0x00, 0x38, 0x00, 0x60, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 249 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x00, 0x30, 0x00, 0xE0, 0x00, 0x30, 0x00, 0x60, 0x00, 0x18, 0x00, 0x20, 0x00, 0x0C, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 250 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x80, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0x80, 0x00, 0x0C, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 251 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0xC0, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x18, 0x00, 0xC0, 0x00, 0x0C, 0x00, 0x00, 0xFE, 0x3F, 0x00, 0x00, 0xFE, 0x3F, // 252 +0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0x00, 0xF0, 0x01, 0x06, 0x00, 0x80, 0x0F, 0x07, 0x80, 0x00, 0xFE, 0x03, 0xE0, 0x00, 0xFC, 0x00, 0x60, 0xC0, 0x1F, 0x00, 0x20, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 253 +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00, 0x0E, 0x38, 0x00, 0x00, 0x1C, 0x1C, 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0xF0, 0x03, // 254 +0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x06, 0xC0, 0xF0, 0x01, 0x06, 0xC0, 0x80, 0x0F, 0x07, 0x00, 0x00, 0xFE, 0x03, 0x00, 0x00, 0xFC, 0x00, 0xC0, 0xC0, 0x1F, 0x00, 0xC0, 0xF8, 0x03, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x06, // 255 }; \ No newline at end of file diff --git a/src/graphics/fonts/OLEDDisplayFontsPL.h b/src/graphics/fonts/OLEDDisplayFontsPL.h index 59dd92c41..1e6790e07 100644 --- a/src/graphics/fonts/OLEDDisplayFontsPL.h +++ b/src/graphics/fonts/OLEDDisplayFontsPL.h @@ -6,6 +6,7 @@ #elif __MBED__ #define PROGMEM #endif - extern const uint8_t ArialMT_Plain_10_PL[] PROGMEM; +extern const uint8_t ArialMT_Plain_16_PL[] PROGMEM; +extern const uint8_t ArialMT_Plain_24_PL[] PROGMEM; #endif \ No newline at end of file diff --git a/src/input/ScanAndSelect.cpp b/src/input/ScanAndSelect.cpp index d8767fab8..1262f99b4 100644 --- a/src/input/ScanAndSelect.cpp +++ b/src/input/ScanAndSelect.cpp @@ -7,6 +7,9 @@ #include "ScanAndSelect.h" #include "modules/CannedMessageModule.h" #include +#ifdef ARCH_PORTDUINO // Only to check for pin conflict with user button +#include "platform/portduino/PortduinoGlue.h" +#endif // Config static const char name[] = "scanAndSelect"; // should match "allow input source" string @@ -30,7 +33,9 @@ bool ScanAndSelectInput::init() if (strcasecmp(moduleConfig.canned_message.allow_input_source, name) != 0) return false; - // Use any available inputbroker pin as the button + // Determine which pin to use for the single scan-and-select button + // User can specify this by setting any of the inputbroker pins + // If all values are zero, we'll assume the user *does* want GPIO0 if (moduleConfig.canned_message.inputbroker_pin_press) pin = moduleConfig.canned_message.inputbroker_pin_press; else if (moduleConfig.canned_message.inputbroker_pin_a) @@ -38,7 +43,25 @@ bool ScanAndSelectInput::init() else if (moduleConfig.canned_message.inputbroker_pin_b) pin = moduleConfig.canned_message.inputbroker_pin_b; else - return false; // Short circuit: no button found + pin = 0; // GPIO 0 then + + // Short circuit: if selected pin conficts with the user button +#if defined(ARCH_PORTDUINO) + int pinUserButton = 0; + if (settingsMap.count(user) != 0) { + pinUserButton = settingsMap[user]; + } +#elif defined(USERPREFS_BUTTON_PIN) + int pinUserButton = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; +#elif defined(BUTTON_PIN) + int pinUserButton = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; +#else + int pinUserButton = config.device.button_gpio; +#endif + if (pin == pinUserButton) { + LOG_ERROR("ScanAndSelect conflict with user button"); + return false; + } // Set-up the button pinMode(pin, INPUT_PULLUP); diff --git a/src/main.cpp b/src/main.cpp index c2b20b1c1..24fc71749 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -92,6 +92,7 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include "mesh/raspihttp/PiWebServer.h" #include "platform/portduino/PortduinoGlue.h" #include "platform/portduino/USBHal.h" +#include #include #include #include @@ -609,6 +610,7 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::CGRADSENS, meshtastic_TelemetrySensorType_RADSENS); + scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN); i2cScanner.reset(); #endif @@ -825,116 +827,56 @@ void setup() #endif #ifdef ARCH_PORTDUINO - if (settingsMap[use_sx1262]) { - if (!rIf) { - LOG_DEBUG("Activate sx1262 radio on SPI port %s", settingsStrings[spidev].c_str()); + const struct { + configNames cfgName; + std::string strName; + } loraModules[] = {{use_rf95, "RF95"}, {use_sx1262, "sx1262"}, {use_sx1268, "sx1268"}, {use_sx1280, "sx1280"}, + {use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, {use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}}; + // as one can't use a function pointer to the class constructor: + auto loraModuleInterface = [](configNames cfgName, LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, + RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) { + switch (cfgName) { + case use_rf95: + return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); + case use_sx1262: + return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy); + case use_sx1268: + return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy); + case use_sx1280: + return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy); + case use_lr1110: + return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy); + case use_lr1120: + return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy); + case use_lr1121: + return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); + case use_llcc68: + return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); + default: + assert(0); // shouldn't happen + return (RadioInterface *)nullptr; + } + }; + for (auto &loraModule : loraModules) { + if (settingsMap[loraModule.cfgName] && !rIf) { + LOG_DEBUG("Activate %s radio on SPI port %s", loraModule.strName.c_str(), settingsStrings[spidev].c_str()); if (settingsStrings[spidev] == "ch341") { RadioLibHAL = ch341Hal; } else { RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); } - rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], - settingsMap[busy]); + rIf = loraModuleInterface(loraModule.cfgName, (LockingArduinoHal *)RadioLibHAL, settingsMap[cs_pin], + settingsMap[irq_pin], settingsMap[reset_pin], settingsMap[busy_pin]); if (!rIf->init()) { - LOG_WARN("No SX1262 radio"); - delete rIf; - exit(EXIT_FAILURE); - } else { - LOG_INFO("SX1262 init success"); - } - } - } else if (settingsMap[use_rf95]) { - if (!rIf) { - LOG_DEBUG("Activate rf95 radio on SPI port %s", settingsStrings[spidev].c_str()); - RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], - settingsMap[busy]); - if (!rIf->init()) { - LOG_WARN("No RF95 radio"); + LOG_WARN("No %s radio", loraModule.strName.c_str()); delete rIf; rIf = NULL; exit(EXIT_FAILURE); } else { - LOG_INFO("RF95 init success"); - } - } - } else if (settingsMap[use_sx1280]) { - if (!rIf) { - LOG_DEBUG("Activate sx1280 radio on SPI port %s", settingsStrings[spidev].c_str()); - RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], - settingsMap[busy]); - if (!rIf->init()) { - LOG_WARN("No SX1280 radio"); - delete rIf; - rIf = NULL; - exit(EXIT_FAILURE); - } else { - LOG_INFO("SX1280 init success"); - } - } - } else if (settingsMap[use_lr1110]) { - if (!rIf) { - LOG_DEBUG("Activate lr1110 radio on SPI port %s", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - rIf = new LR1110Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], - settingsMap[busy]); - if (!rIf->init()) { - LOG_WARN("No LR1110 radio"); - delete rIf; - rIf = NULL; - exit(EXIT_FAILURE); - } else { - LOG_INFO("LR1110 init success"); - } - } - } else if (settingsMap[use_lr1120]) { - if (!rIf) { - LOG_DEBUG("Activate lr1120 radio on SPI port %s", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - rIf = new LR1120Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], - settingsMap[busy]); - if (!rIf->init()) { - LOG_WARN("No LR1120 radio"); - delete rIf; - rIf = NULL; - exit(EXIT_FAILURE); - } else { - LOG_INFO("LR1120 init success"); - } - } - } else if (settingsMap[use_lr1121]) { - if (!rIf) { - LOG_DEBUG("Activate lr1121 radio on SPI port %s", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - rIf = new LR1121Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], - settingsMap[busy]); - if (!rIf->init()) { - LOG_WARN("No LR1121 radio"); - delete rIf; - rIf = NULL; - exit(EXIT_FAILURE); - } else { - LOG_INFO("LR1121 init success"); - } - } - } else if (settingsMap[use_sx1268]) { - if (!rIf) { - LOG_DEBUG("Activate sx1268 radio on SPI port %s", settingsStrings[spidev].c_str()); - RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - rIf = new SX1268Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], - settingsMap[busy]); - if (!rIf->init()) { - LOG_WARN("No SX1268 radio"); - delete rIf; - rIf = NULL; - exit(EXIT_FAILURE); - } else { - LOG_INFO("SX1268 init success"); + LOG_INFO("%s init success", loraModule.strName.c_str()); } } } - #elif defined(HW_SPI1_DEVICE) LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); #else // HW_SPI1_DEVICE @@ -1159,6 +1101,7 @@ void setup() #if __has_include() if (settingsMap[webserverport] != -1) { piwebServerThread = new PiWebServerThread(); + std::atexit([] { delete piwebServerThread; }); } #endif initApiServer(TCPPort); diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index d7ee65800..7dd84639d 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -69,7 +69,11 @@ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p) { // no space - try to replace a lower priority packet in the queue if (queue.size() >= maxLen) { - return replaceLowerPriorityPacket(p); + bool replaced = replaceLowerPriorityPacket(p); + if (!replaced) { + LOG_WARN("TX queue is full, and there is no lower-priority packet available to evict in favour of 0x%08x", p->id); + } + return replaced; } // Find the correct position using upper_bound to maintain a stable order @@ -113,7 +117,10 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool t return NULL; } -/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */ +/** + * Attempt to find a lower-priority packet in the queue and replace it with the provided one. + * @return True if the replacement succeeded, false otherwise + */ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p) { @@ -122,11 +129,12 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p) } // Check if the packet at the back has a lower priority than the new packet - auto &backPacket = queue.back(); + auto *backPacket = queue.back(); if (!backPacket->tx_after && backPacket->priority < p->priority) { + LOG_WARN("Dropping packet 0x%08x to make room in the TX queue for higher-priority packet 0x%08x", backPacket->id, p->id); // Remove the back packet - packetPool.release(backPacket); queue.pop_back(); + packetPool.release(backPacket); // Insert the new packet in the correct order enqueue(p); return true; @@ -139,8 +147,12 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p) for (; refPacket->tx_after && it != queue.begin(); refPacket = *--it) ; if (!refPacket->tx_after && refPacket->priority < p->priority) { + LOG_WARN("Dropping non-late packet 0x%08x to make room in the TX queue for higher-priority packet 0x%08x", + refPacket->id, p->id); + queue.erase(it); packetPool.release(refPacket); - enqueue(refPacket); + // Insert the new packet in the correct order + enqueue(p); return true; } } diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 268c4308f..42f701d5c 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -64,7 +64,8 @@ class MeshService return true; } return p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP || - p->decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP; + p->decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP || + p->decoded.portnum == meshtastic_PortNum_ALERT_APP; } /// Called when some new packets have arrived from one of the radios Observable fromNumChanged; @@ -142,7 +143,7 @@ class MeshService void sendToPhone(meshtastic_MeshPacket *p); /// Send an MQTT message to the phone for client proxying - void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m); + virtual void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m); /// Send a ClientNotification to the phone void sendClientNotification(meshtastic_ClientNotification *cn); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8e084c99d..762982287 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -197,9 +197,8 @@ NodeDB::NodeDB() uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile)); int saveWhat = 0; - // bool hasUniqueId = false; // Get device unique id -#if defined(ARCH_ESP32) && defined(ESP_EFUSE_OPTIONAL_UNIQUE_ID) +#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) uint32_t unique_id[4]; // ESP32 factory burns a unique id in efuse for S2+ series and evidently C3+ series // This is used for HMACs in the esp-rainmaker AIOT platform and seems to be a good choice for us @@ -207,7 +206,6 @@ NodeDB::NodeDB() if (err == ESP_OK) { memcpy(myNodeInfo.device_id.bytes, unique_id, sizeof(unique_id)); myNodeInfo.device_id.size = 16; - hasUniqueId = true; } else { LOG_WARN("Failed to read unique id from efuse"); } @@ -221,12 +219,12 @@ NodeDB::NodeDB() memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end)); myNodeInfo.device_id.size = 16; // Uncomment below to print the device id - // hasUniqueId = true; + #else // FIXME - implement for other platforms #endif - // if (hasUniqueId) { + // if (myNodeInfo.device_id.size == 16) { // std::string deviceIdHex; // for (size_t i = 0; i < myNodeInfo.device_id.size; ++i) { // char buf[3]; @@ -958,7 +956,7 @@ void NodeDB::loadFromDisk() #endif // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM - auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES_FS * sizeof(meshtastic_NodeInfo), + auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES_FS * meshtastic_NodeInfoLite_size, sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate); // See https://github.com/meshtastic/firmware/issues/4184#issuecomment-2269390786 @@ -1147,8 +1145,9 @@ bool NodeDB::saveDeviceStateToDisk() #endif // Note: if MAX_NUM_NODES=100 and meshtastic_NodeInfoLite_size=166, so will be approximately 17KB // Because so huge we _must_ not use fullAtomic, because the filesystem is probably too small to hold two copies of this - return saveProto(prefFileName, sizeof(devicestate) + numMeshNodes * meshtastic_NodeInfoLite_size, &meshtastic_DeviceState_msg, - &devicestate, false); + size_t deviceStateSize; + pb_get_encoded_size(&deviceStateSize, meshtastic_DeviceState_fields, &devicestate); + return saveProto(prefFileName, deviceStateSize, &meshtastic_DeviceState_msg, &devicestate, false); } bool NodeDB::saveToDiskNoRetry(int saveWhat) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index c8c0d3170..d244a94ba 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -145,7 +145,7 @@ class NodeDB return &meshNodes->at(x); } - meshtastic_NodeInfoLite *getMeshNode(NodeNum n); + virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n); size_t getNumMeshNodes() { return numMeshNodes; } // returns true if the maximum number of nodes is reached or we are running low on memory diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index 89d237a02..0417d0997 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -4,7 +4,11 @@ #include /// We clear our old flood record 10 minutes after we see the last of it +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#define FLOOD_EXPIRE_TIME (5 * 1000L) // Don't allow too many packets to accumulate when fuzzing. +#else #define FLOOD_EXPIRE_TIME (10 * 60 * 1000L) +#endif /** * A record of a recent message broadcast diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 9ef045099..d4d9ad23c 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -91,16 +91,16 @@ void RF95Interface::setTransmitEnable(bool txon) #ifdef RF95_TXEN digitalWrite(RF95_TXEN, txon ? 1 : 0); #elif ARCH_PORTDUINO - if (settingsMap[txen] != RADIOLIB_NC) { - digitalWrite(settingsMap[txen], txon ? 1 : 0); + if (settingsMap[txen_pin] != RADIOLIB_NC) { + digitalWrite(settingsMap[txen_pin], txon ? 1 : 0); } #endif #ifdef RF95_RXEN digitalWrite(RF95_RXEN, txon ? 0 : 1); #elif ARCH_PORTDUINO - if (settingsMap[rxen] != RADIOLIB_NC) { - digitalWrite(settingsMap[rxen], txon ? 0 : 1); + if (settingsMap[rxen_pin] != RADIOLIB_NC) { + digitalWrite(settingsMap[rxen_pin], txon ? 0 : 1); } #endif } @@ -164,13 +164,13 @@ bool RF95Interface::init() digitalWrite(RF95_RXEN, 1); #endif #if ARCH_PORTDUINO - if (settingsMap[txen] != RADIOLIB_NC) { - pinMode(settingsMap[txen], OUTPUT); - digitalWrite(settingsMap[txen], 0); + if (settingsMap[txen_pin] != RADIOLIB_NC) { + pinMode(settingsMap[txen_pin], OUTPUT); + digitalWrite(settingsMap[txen_pin], 0); } - if (settingsMap[rxen] != RADIOLIB_NC) { - pinMode(settingsMap[rxen], OUTPUT); - digitalWrite(settingsMap[rxen], 0); + if (settingsMap[rxen_pin] != RADIOLIB_NC) { + pinMode(settingsMap[rxen_pin], OUTPUT); + digitalWrite(settingsMap[rxen_pin], 0); } #endif setTransmitEnable(false); @@ -337,4 +337,4 @@ bool RF95Interface::sleep() return true; } -#endif \ No newline at end of file +#endif diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index b1403f3b6..d91cba116 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -297,7 +297,7 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr) void printPacket(const char *prefix, const meshtastic_MeshPacket *p) { -#ifdef DEBUG_PORT +#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) std::string out = DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id, p->from, p->to, p->want_ack, p->hop_limit, p->channel); if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { @@ -637,4 +637,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} +} \ No newline at end of file diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 997b1d6fe..e31f0b3e2 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -271,6 +271,7 @@ void RadioLibInterface::onNotify(uint32_t notification) uint32_t xmitMsec = getPacketTime(txp); airTime->logAirtime(TX_LOG, xmitMsec); } + LOG_DEBUG("%d packets remain in the TX queue", txQueue.getMaxLen() - txQueue.getFree()); } } } @@ -297,8 +298,8 @@ void RadioLibInterface::setTransmitDelay() if (p->tx_after) { unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr) : getTxDelayMsec(); unsigned long now = millis(); - p->tx_after = max(p->tx_after + add_delay, now + add_delay); - notifyLater(now - p->tx_after, TRANSMIT_DELAY_COMPLETED, false); + p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr)); + notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, false); } else if (p->rx_snr == 0 && p->rx_rssi == 0) { /* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally. * This assumption is valid because of the offset generated by the radio to account for the noise @@ -431,6 +432,7 @@ void RadioLibInterface::handleReceiveInterrupt() // nodes. meshtastic_MeshPacket *mp = packetPool.allocZeroed(); + // Keep the assigned fields in sync with src/mqtt/MQTT.cpp:onReceiveProto mp->from = radioBuffer.header.from; mp->to = radioBuffer.header.to; mp->id = radioBuffer.header.id; @@ -500,14 +502,13 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp) powerMon->clearState(meshtastic_PowerMon_State_Lora_TXOn); // Transmitter off now startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode) } else { + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register + // bits + enableInterrupt(isrTxLevel0); lastTxStart = millis(); printPacket("Started Tx", txp); } - // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register - // bits - enableInterrupt(isrTxLevel0); - return res == RADIOLIB_ERR_NONE; } } \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index f55e7cc5a..bfd4c45fd 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -187,7 +187,7 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) } // don't override if a channel was requested and no need to set it when PKI is enforced - if (!p->channel && !p->pki_encrypted) { + if (!p->channel && !p->pki_encrypted && !isBroadcast(p->to)) { meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to); if (node) { p->channel = node->channel; @@ -679,4 +679,4 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) // cache/learn of the existence of nodes (i.e. FloodRouter) that they should not handleReceived(p); packetPool.release(p); -} \ No newline at end of file +} diff --git a/src/mesh/Router.h b/src/mesh/Router.h index da44d67df..0fe2bc551 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -71,7 +71,7 @@ class Router : protected concurrency::OSThread * RadioInterface calls this to queue up packets that have been received from the radio. The router is now responsible for * freeing the packet */ - void enqueueReceivedMessage(meshtastic_MeshPacket *p); + virtual void enqueueReceivedMessage(meshtastic_MeshPacket *p); /** * Send a packet on a suitable interface. This routine will diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index ed0267c5b..8a7bc7670 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -51,9 +51,9 @@ template bool SX126xInterface::init() #if ARCH_PORTDUINO float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000; - if (settingsMap[sx126x_ant_sw] != RADIOLIB_NC) { - digitalWrite(settingsMap[sx126x_ant_sw], HIGH); - pinMode(settingsMap[sx126x_ant_sw], OUTPUT); + if (settingsMap[sx126x_ant_sw_pin] != RADIOLIB_NC) { + digitalWrite(settingsMap[sx126x_ant_sw_pin], HIGH); + pinMode(settingsMap[sx126x_ant_sw_pin], OUTPUT); } // FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE #elif !defined(SX126X_DIO3_TCXO_VOLTAGE) @@ -121,8 +121,9 @@ template bool SX126xInterface::init() // no effect #if ARCH_PORTDUINO if (res == RADIOLIB_ERR_NONE) { - LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", settingsMap[rxen], settingsMap[txen]); - lora.setRfSwitchPins(settingsMap[rxen], settingsMap[txen]); + LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", settingsMap[rxen_pin], + settingsMap[txen_pin]); + lora.setRfSwitchPins(settingsMap[rxen_pin], settingsMap[txen_pin]); } #else #ifndef SX126X_RXEN @@ -341,4 +342,4 @@ template bool SX126xInterface::sleep() return true; } -#endif \ No newline at end of file +#endif diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 013164bca..ee3408456 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -38,13 +38,13 @@ template bool SX128xInterface::init() #endif #if ARCH_PORTDUINO - if (settingsMap[rxen] != RADIOLIB_NC) { - pinMode(settingsMap[rxen], OUTPUT); - digitalWrite(settingsMap[rxen], LOW); // Set low before becoming an output + if (settingsMap[rxen_pin] != RADIOLIB_NC) { + pinMode(settingsMap[rxen_pin], OUTPUT); + digitalWrite(settingsMap[rxen_pin], LOW); // Set low before becoming an output } - if (settingsMap[txen] != RADIOLIB_NC) { - pinMode(settingsMap[txen], OUTPUT); - digitalWrite(settingsMap[txen], LOW); // Set low before becoming an output + if (settingsMap[txen_pin] != RADIOLIB_NC) { + pinMode(settingsMap[txen_pin], OUTPUT); + digitalWrite(settingsMap[txen_pin], LOW); // Set low before becoming an output } #else #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // set not rx or tx mode @@ -93,8 +93,8 @@ template bool SX128xInterface::init() lora.setRfSwitchPins(SX128X_RXEN, SX128X_TXEN); } #elif ARCH_PORTDUINO - if (res == RADIOLIB_ERR_NONE && settingsMap[rxen] != RADIOLIB_NC && settingsMap[txen] != RADIOLIB_NC) { - lora.setRfSwitchPins(settingsMap[rxen], settingsMap[txen]); + if (res == RADIOLIB_ERR_NONE && settingsMap[rxen_pin] != RADIOLIB_NC && settingsMap[txen_pin] != RADIOLIB_NC) { + lora.setRfSwitchPins(settingsMap[rxen_pin], settingsMap[txen_pin]); } #endif @@ -174,11 +174,11 @@ template void SX128xInterface::setStandby() LOG_ERROR("SX128x standby %s%d", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); #if ARCH_PORTDUINO - if (settingsMap[rxen] != RADIOLIB_NC) { - digitalWrite(settingsMap[rxen], LOW); + if (settingsMap[rxen_pin] != RADIOLIB_NC) { + digitalWrite(settingsMap[rxen_pin], LOW); } - if (settingsMap[txen] != RADIOLIB_NC) { - digitalWrite(settingsMap[txen], LOW); + if (settingsMap[txen_pin] != RADIOLIB_NC) { + digitalWrite(settingsMap[txen_pin], LOW); } #else #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power @@ -210,11 +210,11 @@ template void SX128xInterface::addReceiveMetadata(meshtastic_Mes template void SX128xInterface::configHardwareForSend() { #if ARCH_PORTDUINO - if (settingsMap[txen] != RADIOLIB_NC) { - digitalWrite(settingsMap[txen], HIGH); + if (settingsMap[txen_pin] != RADIOLIB_NC) { + digitalWrite(settingsMap[txen_pin], HIGH); } - if (settingsMap[rxen] != RADIOLIB_NC) { - digitalWrite(settingsMap[rxen], LOW); + if (settingsMap[rxen_pin] != RADIOLIB_NC) { + digitalWrite(settingsMap[rxen_pin], LOW); } #else @@ -241,11 +241,11 @@ template void SX128xInterface::startReceive() setStandby(); #if ARCH_PORTDUINO - if (settingsMap[rxen] != RADIOLIB_NC) { - digitalWrite(settingsMap[rxen], HIGH); + if (settingsMap[rxen_pin] != RADIOLIB_NC) { + digitalWrite(settingsMap[rxen_pin], HIGH); } - if (settingsMap[txen] != RADIOLIB_NC) { - digitalWrite(settingsMap[txen], LOW); + if (settingsMap[txen_pin] != RADIOLIB_NC) { + digitalWrite(settingsMap[txen_pin], LOW); } #else @@ -315,4 +315,4 @@ template bool SX128xInterface::sleep() return true; } -#endif \ No newline at end of file +#endif diff --git a/src/mesh/TypedQueue.h b/src/mesh/TypedQueue.h index f7d016f10..47d7200a5 100644 --- a/src/mesh/TypedQueue.h +++ b/src/mesh/TypedQueue.h @@ -74,11 +74,17 @@ template class TypedQueue { std::queue q; concurrency::OSThread *reader = NULL; + int maxElements; public: - explicit TypedQueue(int maxElements) {} + explicit TypedQueue(int _maxElements) : maxElements(_maxElements) {} - int numFree() { return 1; } // Always claim 1 free, because we can grow to any size + int numFree() + { + if (maxElements <= 0) + return 1; // Always claim 1 free, because we can grow to any size + return maxElements - numUsed(); + } bool isEmpty() { return q.empty(); } @@ -86,6 +92,9 @@ template class TypedQueue bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { + if (numFree() <= 0) + return false; + if (reader) { reader->setInterval(0); concurrency::mainDelay.interrupt(); @@ -112,4 +121,4 @@ template class TypedQueue void setReader(concurrency::OSThread *t) { reader = t; } }; -#endif +#endif \ No newline at end of file diff --git a/src/mesh/generated/meshtastic/config.pb.cpp b/src/mesh/generated/meshtastic/config.pb.cpp index 6fd2161ae..5512584a7 100644 --- a/src/mesh/generated/meshtastic/config.pb.cpp +++ b/src/mesh/generated/meshtastic/config.pb.cpp @@ -63,6 +63,8 @@ PB_BIND(meshtastic_Config_SessionkeyConfig, meshtastic_Config_SessionkeyConfig, + + diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 5e105ab17..14aed9dfe 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -139,6 +139,14 @@ typedef enum _meshtastic_Config_NetworkConfig_AddressMode { meshtastic_Config_NetworkConfig_AddressMode_STATIC = 1 } meshtastic_Config_NetworkConfig_AddressMode; +/* Available flags auxiliary network protocols */ +typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags { + /* Do not broadcast packets over any network protocol */ + meshtastic_Config_NetworkConfig_ProtocolFlags_NO_BROADCAST = 0, + /* Enable broadcasting packets via UDP over the local network */ + meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1 +} meshtastic_Config_NetworkConfig_ProtocolFlags; + /* How the GPS coordinates are displayed on the OLED screen. */ typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat { /* GPS coordinates are displayed in the normal decimal degrees format: @@ -429,6 +437,8 @@ typedef struct _meshtastic_Config_NetworkConfig { meshtastic_Config_NetworkConfig_IpV4Config ipv4_config; /* rsyslog Server and Port */ char rsyslog_server[33]; + /* Flags for enabling/disabling network protocols */ + uint32_t enabled_protocols; } meshtastic_Config_NetworkConfig; /* Display Config */ @@ -613,6 +623,10 @@ extern "C" { #define _meshtastic_Config_NetworkConfig_AddressMode_MAX meshtastic_Config_NetworkConfig_AddressMode_STATIC #define _meshtastic_Config_NetworkConfig_AddressMode_ARRAYSIZE ((meshtastic_Config_NetworkConfig_AddressMode)(meshtastic_Config_NetworkConfig_AddressMode_STATIC+1)) +#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MIN meshtastic_Config_NetworkConfig_ProtocolFlags_NO_BROADCAST +#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST +#define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1)) + #define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC #define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR #define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1)) @@ -674,7 +688,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""} +#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} @@ -685,7 +699,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""} +#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} @@ -739,6 +753,7 @@ extern "C" { #define meshtastic_Config_NetworkConfig_address_mode_tag 7 #define meshtastic_Config_NetworkConfig_ipv4_config_tag 8 #define meshtastic_Config_NetworkConfig_rsyslog_server_tag 9 +#define meshtastic_Config_NetworkConfig_enabled_protocols_tag 10 #define meshtastic_Config_DisplayConfig_screen_on_secs_tag 1 #define meshtastic_Config_DisplayConfig_gps_format_tag 2 #define meshtastic_Config_DisplayConfig_auto_screen_carousel_secs_tag 3 @@ -867,7 +882,8 @@ X(a, STATIC, SINGULAR, STRING, ntp_server, 5) \ X(a, STATIC, SINGULAR, BOOL, eth_enabled, 6) \ X(a, STATIC, SINGULAR, UENUM, address_mode, 7) \ X(a, STATIC, OPTIONAL, MESSAGE, ipv4_config, 8) \ -X(a, STATIC, SINGULAR, STRING, rsyslog_server, 9) +X(a, STATIC, SINGULAR, STRING, rsyslog_server, 9) \ +X(a, STATIC, SINGULAR, UINT32, enabled_protocols, 10) #define meshtastic_Config_NetworkConfig_CALLBACK NULL #define meshtastic_Config_NetworkConfig_DEFAULT NULL #define meshtastic_Config_NetworkConfig_ipv4_config_MSGTYPE meshtastic_Config_NetworkConfig_IpV4Config @@ -972,12 +988,12 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; #define meshtastic_Config_DisplayConfig_size 30 #define meshtastic_Config_LoRaConfig_size 85 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 -#define meshtastic_Config_NetworkConfig_size 196 +#define meshtastic_Config_NetworkConfig_size 202 #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 #define meshtastic_Config_SecurityConfig_size 178 #define meshtastic_Config_SessionkeyConfig_size 0 -#define meshtastic_Config_size 199 +#define meshtastic_Config_size 205 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h index 0c4f5384e..f090b5b4f 100644 --- a/src/mesh/generated/meshtastic/device_ui.pb.h +++ b/src/mesh/generated/meshtastic/device_ui.pb.h @@ -51,6 +51,8 @@ typedef enum _meshtastic_Language { meshtastic_Language_GREEK = 13, /* Norwegian */ meshtastic_Language_NORWEGIAN = 14, + /* Slovenian */ + meshtastic_Language_SLOVENIAN = 15, /* Simplified Chinese (experimental) */ meshtastic_Language_SIMPLIFIED_CHINESE = 30, /* Traditional Chinese (experimental) */ @@ -71,6 +73,8 @@ typedef struct _meshtastic_NodeFilter { bool position_switch; /* Filter nodes by matching name string */ char node_name[16]; + /* Filter based on channel */ + int8_t channel; } meshtastic_NodeFilter; typedef struct _meshtastic_NodeHighlight { @@ -138,10 +142,10 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}} -#define meshtastic_NodeFilter_init_default {0, 0, 0, 0, 0, ""} +#define meshtastic_NodeFilter_init_default {0, 0, 0, 0, 0, "", 0} #define meshtastic_NodeHighlight_init_default {0, 0, 0, 0, ""} #define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}} -#define meshtastic_NodeFilter_init_zero {0, 0, 0, 0, 0, ""} +#define meshtastic_NodeFilter_init_zero {0, 0, 0, 0, 0, "", 0} #define meshtastic_NodeHighlight_init_zero {0, 0, 0, 0, ""} /* Field tags (for use in manual encoding/decoding) */ @@ -151,6 +155,7 @@ extern "C" { #define meshtastic_NodeFilter_hops_away_tag 4 #define meshtastic_NodeFilter_position_switch_tag 5 #define meshtastic_NodeFilter_node_name_tag 6 +#define meshtastic_NodeFilter_channel_tag 7 #define meshtastic_NodeHighlight_chat_switch_tag 1 #define meshtastic_NodeHighlight_position_switch_tag 2 #define meshtastic_NodeHighlight_telemetry_switch_tag 3 @@ -198,7 +203,8 @@ X(a, STATIC, SINGULAR, BOOL, offline_switch, 2) \ X(a, STATIC, SINGULAR, BOOL, public_key_switch, 3) \ X(a, STATIC, SINGULAR, INT32, hops_away, 4) \ X(a, STATIC, SINGULAR, BOOL, position_switch, 5) \ -X(a, STATIC, SINGULAR, STRING, node_name, 6) +X(a, STATIC, SINGULAR, STRING, node_name, 6) \ +X(a, STATIC, SINGULAR, INT32, channel, 7) #define meshtastic_NodeFilter_CALLBACK NULL #define meshtastic_NodeFilter_DEFAULT NULL @@ -222,8 +228,8 @@ extern const pb_msgdesc_t meshtastic_NodeHighlight_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size -#define meshtastic_DeviceUIConfig_size 117 -#define meshtastic_NodeFilter_size 36 +#define meshtastic_DeviceUIConfig_size 128 +#define meshtastic_NodeFilter_size 47 #define meshtastic_NodeHighlight_size 25 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 30f70ed90..dc0f507c9 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size -#define meshtastic_LocalConfig_size 735 +#define meshtastic_LocalConfig_size 741 #define meshtastic_LocalModuleConfig_size 699 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 85fe4bdc1..bb612d870 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -81,7 +81,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* ClimateGuard RadSens, radiation, Geiger-Muller Tube */ meshtastic_TelemetrySensorType_RADSENS = 33, /* High accuracy current and voltage */ - meshtastic_TelemetrySensorType_INA226 = 34 + meshtastic_TelemetrySensorType_INA226 = 34, + /* DFRobot Gravity tipping bucket rain gauge */ + meshtastic_TelemetrySensorType_DFROBOT_RAIN = 35 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -162,6 +164,12 @@ typedef struct _meshtastic_EnvironmentMetrics { /* Radiation in µR/h */ bool has_radiation; float radiation; + /* Rainfall in the last hour in mm */ + bool has_rainfall_1h; + float rainfall_1h; + /* Rainfall in the last 24 hours in mm */ + bool has_rainfall_24h; + float rainfall_24h; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -306,8 +314,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_INA226 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_INA226+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_DFROBOT_RAIN +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_DFROBOT_RAIN+1)) @@ -320,7 +328,7 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -328,7 +336,7 @@ extern "C" { #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -360,6 +368,8 @@ extern "C" { #define meshtastic_EnvironmentMetrics_wind_gust_tag 16 #define meshtastic_EnvironmentMetrics_wind_lull_tag 17 #define meshtastic_EnvironmentMetrics_radiation_tag 18 +#define meshtastic_EnvironmentMetrics_rainfall_1h_tag 19 +#define meshtastic_EnvironmentMetrics_rainfall_24h_tag 20 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -431,7 +441,9 @@ X(a, STATIC, OPTIONAL, FLOAT, wind_speed, 14) \ X(a, STATIC, OPTIONAL, FLOAT, weight, 15) \ X(a, STATIC, OPTIONAL, FLOAT, wind_gust, 16) \ X(a, STATIC, OPTIONAL, FLOAT, wind_lull, 17) \ -X(a, STATIC, OPTIONAL, FLOAT, radiation, 18) +X(a, STATIC, OPTIONAL, FLOAT, radiation, 18) \ +X(a, STATIC, OPTIONAL, FLOAT, rainfall_1h, 19) \ +X(a, STATIC, OPTIONAL, FLOAT, rainfall_24h, 20) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -530,12 +542,12 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size #define meshtastic_AirQualityMetrics_size 78 #define meshtastic_DeviceMetrics_size 27 -#define meshtastic_EnvironmentMetrics_size 91 +#define meshtastic_EnvironmentMetrics_size 103 #define meshtastic_HealthMetrics_size 11 #define meshtastic_LocalStats_size 60 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 30 -#define meshtastic_Telemetry_size 98 +#define meshtastic_Telemetry_size 110 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/raspihttp/PiWebServer.cpp b/src/mesh/raspihttp/PiWebServer.cpp index 846d70723..9d2625410 100644 --- a/src/mesh/raspihttp/PiWebServer.cpp +++ b/src/mesh/raspihttp/PiWebServer.cpp @@ -82,8 +82,6 @@ char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html" volatile bool isWebServerReady; volatile bool isCertReady; -HttpAPI webAPI; - PiWebServerThread *piwebServerThread; /** @@ -247,7 +245,7 @@ int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, vo portduinoVFS->mountpoint(configWeb.rootPath); LOG_DEBUG("Received %d bytes from PUT request", s); - webAPI.handleToRadio(buffer, s); + static_cast(user_data)->handleToRadio(buffer, s); LOG_DEBUG("end web->radio "); return U_CALLBACK_COMPLETE; } @@ -279,7 +277,7 @@ int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, if (valueAll == "true") { while (len) { - len = webAPI.getFromRadio(txBuf); + len = static_cast(user_data)->getFromRadio(txBuf); ulfius_set_response_properties(res, U_OPT_STATUS, 200, U_OPT_BINARY_BODY, txBuf, len); const char *tmpa = (const char *)txBuf; ulfius_set_string_body_response(res, 200, tmpa); @@ -289,7 +287,7 @@ int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, } // Otherwise, just return one protobuf } else { - len = webAPI.getFromRadio(txBuf); + len = static_cast(user_data)->getFromRadio(txBuf); const char *tmpa = (const char *)txBuf; ulfius_set_binary_body_response(res, 200, tmpa, len); // LOG_DEBUG("\n----webAPI response:"); @@ -497,10 +495,10 @@ PiWebServerThread::PiWebServerThread() u_map_put(instanceWeb.default_headers, "Access-Control-Allow-Origin", "*"); // Maximum body size sent by the client is 1 Kb instanceWeb.max_post_body_size = 1024; - ulfius_add_endpoint_by_val(&instanceWeb, "GET", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, NULL); - ulfius_add_endpoint_by_val(&instanceWeb, "OPTIONS", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, NULL); - ulfius_add_endpoint_by_val(&instanceWeb, "PUT", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, configWeb.rootPath); - ulfius_add_endpoint_by_val(&instanceWeb, "OPTIONS", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, NULL); + ulfius_add_endpoint_by_val(&instanceWeb, "GET", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, &webAPI); + ulfius_add_endpoint_by_val(&instanceWeb, "OPTIONS", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, &webAPI); + ulfius_add_endpoint_by_val(&instanceWeb, "PUT", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, &webAPI); + ulfius_add_endpoint_by_val(&instanceWeb, "OPTIONS", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, &webAPI); // Add callback function to all endpoints for the Web Server ulfius_add_endpoint_by_val(&instanceWeb, "GET", NULL, "/*", 2, &callback_static_file, &configWeb); @@ -525,13 +523,12 @@ PiWebServerThread::~PiWebServerThread() u_map_clean(&configWeb.mime_types); ulfius_stop_framework(&instanceWeb); - ulfius_stop_framework(&instanceWeb); + ulfius_clean_instance(&instanceWeb); free(configWeb.rootPath); - ulfius_clean_instance(&instanceService); - ulfius_clean_instance(&instanceService); + free(key_pem); free(cert_pem); LOG_INFO("End framework"); } #endif -#endif +#endif \ No newline at end of file diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h index c4c49e919..b45348cf3 100644 --- a/src/mesh/raspihttp/PiWebServer.h +++ b/src/mesh/raspihttp/PiWebServer.h @@ -23,24 +23,6 @@ struct _file_config { char *rootPath; }; -class PiWebServerThread -{ - private: - char *key_pem = NULL; - char *cert_pem = NULL; - // struct _u_map mime_types; - std::string webrootpath; - - public: - PiWebServerThread(); - ~PiWebServerThread(); - int CreateSSLCertificate(); - int CheckSSLandLoad(); - uint32_t requestRestart = 0; - struct _u_instance instanceWeb; - struct _u_instance instanceService; -}; - class HttpAPI : public PhoneAPI { @@ -55,6 +37,24 @@ class HttpAPI : public PhoneAPI virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this }; +class PiWebServerThread +{ + private: + char *key_pem = NULL; + char *cert_pem = NULL; + // struct _u_map mime_types; + std::string webrootpath; + HttpAPI webAPI; + + public: + PiWebServerThread(); + ~PiWebServerThread(); + int CreateSSLCertificate(); + int CheckSSLandLoad(); + uint32_t requestRestart = 0; + struct _u_instance instanceWeb; +}; + extern PiWebServerThread *piwebServerThread; #endif diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 2f8138921..dcfcdc047 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -143,6 +143,11 @@ static int32_t reconnectWiFi() delay(5000); if (!WiFi.isConnected()) { +#ifdef CONFIG_IDF_TARGET_ESP32C3 + WiFi.mode(WIFI_MODE_NULL); + WiFi.useStaticBuffers(true); + WiFi.mode(WIFI_STA); +#endif WiFi.begin(wifiName, wifiPsw); } isReconnecting = false; diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp index d242e5501..a51ef54c3 100644 --- a/src/modules/AtakPluginModule.cpp +++ b/src/modules/AtakPluginModule.cpp @@ -131,7 +131,6 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast } // Decompress for Phone (EUD) - auto decompressedCopy = packetPool.allocCopy(mp); auto uncompressed = cloneTAKPacketData(t); uncompressed.is_compressed = false; if (t->has_contact) { @@ -188,6 +187,7 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast LOG_DEBUG("Decompressed chat to_callsign: %d bytes", length); } } + auto decompressedCopy = packetPool.allocCopy(mp); decompressedCopy->decoded.payload.size = pb_encode_to_bytes(decompressedCopy->decoded.payload.bytes, sizeof(decompressedCopy->decoded.payload), meshtastic_TAKPacket_fields, &uncompressed); diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h index 7c34c5bc9..c047f6e29 100644 --- a/src/modules/RoutingModule.h +++ b/src/modules/RoutingModule.h @@ -13,7 +13,8 @@ class RoutingModule : public ProtobufModule */ RoutingModule(); - void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0); + virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, + uint8_t hopLimit = 0); // Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index e72b03bd4..1af6347f2 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -27,6 +27,7 @@ #include "Sensor/BMP280Sensor.h" #include "Sensor/BMP3XXSensor.h" #include "Sensor/CGRadSensSensor.h" +#include "Sensor/DFRobotGravitySensor.h" #include "Sensor/DFRobotLarkSensor.h" #include "Sensor/LPS22HBSensor.h" #include "Sensor/MCP9808Sensor.h" @@ -56,6 +57,7 @@ RCWL9620Sensor rcwl9620Sensor; AHT10Sensor aht10Sensor; MLX90632Sensor mlx90632Sensor; DFRobotLarkSensor dfRobotLarkSensor; +DFRobotGravitySensor dfRobotGravitySensor; NAU7802Sensor nau7802Sensor; BMP3XXSensor bmp3xxSensor; CGRadSensSensor cgRadSens; @@ -115,6 +117,8 @@ int32_t EnvironmentTelemetryModule::runOnce() #elif !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL if (dfRobotLarkSensor.hasSensor()) result = dfRobotLarkSensor.runOnce(); + if (dfRobotGravitySensor.hasSensor()) + result = dfRobotGravitySensor.runOnce(); if (bmp085Sensor.hasSensor()) result = bmp085Sensor.runOnce(); if (bmp280Sensor.hasSensor()) @@ -222,7 +226,11 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt } // Display "Env. From: ..." on its own - display->drawString(x, y, "Env. From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); + display->drawString(x, y, "Env. From: " + String(lastSender) + " (" + String(agoSecs) + "s)"); + + // Prepare sensor data strings + String sensorData[10]; + int sensorCount = 0; if (lastMeasurement.variant.environment_metrics.has_temperature || lastMeasurement.variant.environment_metrics.has_relative_humidity) { @@ -232,38 +240,83 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt String(UnitConversions::CelsiusToFahrenheit(lastMeasurement.variant.environment_metrics.temperature), 0) + "°F"; } - // Continue with the remaining details - display->drawString(x, y += _fontHeight(FONT_SMALL), - "Temp/Hum: " + last_temp + " / " + - String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%"); + sensorData[sensorCount++] = + "Temp/Hum: " + last_temp + " / " + String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%"; } if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) { - display->drawString(x, y += _fontHeight(FONT_SMALL), - "Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA"); + sensorData[sensorCount++] = + "Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA"; } if (lastMeasurement.variant.environment_metrics.voltage != 0) { - display->drawString(x, y += _fontHeight(FONT_SMALL), - "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " + - String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"); + sensorData[sensorCount++] = "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " + + String(lastMeasurement.variant.environment_metrics.current, 0) + "mA"; } if (lastMeasurement.variant.environment_metrics.iaq != 0) { - display->drawString(x, y += _fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq)); + sensorData[sensorCount++] = "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq); } - if (lastMeasurement.variant.environment_metrics.distance != 0) - display->drawString(x, y += _fontHeight(FONT_SMALL), - "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm"); + if (lastMeasurement.variant.environment_metrics.distance != 0) { + sensorData[sensorCount++] = "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm"; + } - if (lastMeasurement.variant.environment_metrics.weight != 0) - display->drawString(x, y += _fontHeight(FONT_SMALL), - "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg"); + if (lastMeasurement.variant.environment_metrics.weight != 0) { + sensorData[sensorCount++] = "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg"; + } - if (lastMeasurement.variant.environment_metrics.radiation != 0) - display->drawString(x, y += _fontHeight(FONT_SMALL), - "Rad: " + String(lastMeasurement.variant.environment_metrics.radiation, 2) + "µR/h"); + if (lastMeasurement.variant.environment_metrics.radiation != 0) { + sensorData[sensorCount++] = "Rad: " + String(lastMeasurement.variant.environment_metrics.radiation, 2) + "µR/h"; + } + + if (lastMeasurement.variant.environment_metrics.lux != 0) { + sensorData[sensorCount++] = "Illuminance: " + String(lastMeasurement.variant.environment_metrics.lux, 2) + "lx"; + } + + if (lastMeasurement.variant.environment_metrics.white_lux != 0) { + sensorData[sensorCount++] = "W_Lux: " + String(lastMeasurement.variant.environment_metrics.white_lux, 2) + "lx"; + } + + static int scrollOffset = 0; + static bool scrollingDown = true; + static uint32_t lastScrollTime = millis(); + + // Determine how many lines we can fit on display + // Calculated once only: display dimensions don't change during runtime. + static int maxLines = 0; + if (!maxLines) { + const int16_t paddingTop = _fontHeight(FONT_SMALL); // Heading text + const int16_t paddingBottom = 8; // Indicator dots + maxLines = (display->getHeight() - paddingTop - paddingBottom) / _fontHeight(FONT_SMALL); + assert(maxLines > 0); + } + + // Draw as many lines of data as we can fit + int linesToShow = min(maxLines, sensorCount); + for (int i = 0; i < linesToShow; i++) { + int index = (scrollOffset + i) % sensorCount; + display->drawString(x, y += _fontHeight(FONT_SMALL), sensorData[index]); + } + + // Only scroll if there are more than 3 sensor data lines + if (sensorCount > 3) { + // Update scroll offset every 5 seconds + if (millis() - lastScrollTime > 5000) { + if (scrollingDown) { + scrollOffset++; + if (scrollOffset + linesToShow >= sensorCount) { + scrollingDown = false; + } + } else { + scrollOffset--; + if (scrollOffset <= 0) { + scrollingDown = true; + } + } + lastScrollTime = millis(); + } + } } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -277,8 +330,10 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current, t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity, t->variant.environment_metrics.temperature); - LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f", sender, t->variant.environment_metrics.voltage, - t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux); + LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f, white_lux=%f", sender, + t->variant.environment_metrics.voltage, t->variant.environment_metrics.iaq, + t->variant.environment_metrics.distance, t->variant.environment_metrics.lux, + t->variant.environment_metrics.white_lux); LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg", sender, t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction, @@ -317,6 +372,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && dfRobotLarkSensor.getMetrics(m); hasSensor = true; } + if (dfRobotGravitySensor.hasSensor()) { + valid = valid && dfRobotGravitySensor.getMetrics(m); + hasSensor = true; + } if (sht31Sensor.hasSensor()) { valid = valid && sht31Sensor.getMetrics(m); hasSensor = true; @@ -518,6 +577,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (dfRobotGravitySensor.hasSensor()) { + result = dfRobotGravitySensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } if (sht31Sensor.hasSensor()) { result = sht31Sensor.handleAdminMessage(mp, request, response); if (result != AdminMessageHandleResult::NOT_HANDLED) diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 10133fca5..9c794e31e 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -99,44 +99,45 @@ bool PowerTelemetryModule::wantUIFrame() void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); - display->drawString(x, y, "Power Telemetry"); + display->setFont(FONT_SMALL); + if (lastMeasurementPacket == nullptr) { - display->setFont(FONT_SMALL); - display->drawString(x, y += _fontHeight(FONT_MEDIUM), "No measurement"); + // In case of no valid packet, display "Power Telemetry", "No measurement" + display->drawString(x, y, "Power Telemetry"); + display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement"); return; } + // Decode the last power packet meshtastic_Telemetry lastMeasurement; - uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket); const char *lastSender = getSenderShortName(*lastMeasurementPacket); const meshtastic_Data &p = lastMeasurementPacket->decoded; if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) { - display->setFont(FONT_SMALL); - display->drawString(x, y += _fontHeight(FONT_MEDIUM), "Measurement Error"); + display->drawString(x, y, "Measurement Error"); LOG_ERROR("Unable to decode last packet"); return; } + // Display "Pow. From: ..." + display->drawString(x, y, "Pow. From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); + // Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags - display->setFont(FONT_SMALL); - display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); if (lastMeasurement.variant.power_metrics.has_ch1_voltage || lastMeasurement.variant.power_metrics.has_ch1_current) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Ch1 Volt: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) + - "V / Curr: " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA"); + "Ch1: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) + + "V " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA"); } if (lastMeasurement.variant.power_metrics.has_ch2_voltage || lastMeasurement.variant.power_metrics.has_ch2_current) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Ch2 Volt: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) + - "V / Curr: " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA"); + "Ch2: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) + + "V " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA"); } if (lastMeasurement.variant.power_metrics.has_ch3_voltage || lastMeasurement.variant.power_metrics.has_ch3_current) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Ch3 Volt: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) + - "V / Curr: " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); + "Ch3: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) + + "V " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); } } diff --git a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp new file mode 100644 index 000000000..c7fa29966 --- /dev/null +++ b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp @@ -0,0 +1,44 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "DFRobotGravitySensor.h" +#include "TelemetrySensor.h" +#include +#include + +DFRobotGravitySensor::DFRobotGravitySensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_RAIN, "DFROBOT_RAIN") {} + +int32_t DFRobotGravitySensor::runOnce() +{ + LOG_INFO("Init sensor: %s", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second); + status = gravity.begin(); + + return initI2CSensor(); +} + +void DFRobotGravitySensor::setup() +{ + LOG_DEBUG("%s VID: %x, PID: %x, Version: %s", sensorName, gravity.vid, gravity.pid, gravity.getFirmwareVersion().c_str()); +} + +bool DFRobotGravitySensor::getMetrics(meshtastic_Telemetry *measurement) +{ + measurement->variant.environment_metrics.has_rainfall_1h = true; + measurement->variant.environment_metrics.has_rainfall_24h = true; + + measurement->variant.environment_metrics.rainfall_1h = gravity.getRainfall(1); + measurement->variant.environment_metrics.rainfall_24h = gravity.getRainfall(24); + + LOG_INFO("Rain 1h: %f mm", measurement->variant.environment_metrics.rainfall_1h); + LOG_INFO("Rain 24h: %f mm", measurement->variant.environment_metrics.rainfall_24h); + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h new file mode 100644 index 000000000..8bd7335b5 --- /dev/null +++ b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h @@ -0,0 +1,29 @@ +#pragma once + +#ifndef _MT_DFROBOTGRAVITYSENSOR_H +#define _MT_DFROBOTGRAVITYSENSOR_H +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include +#include + +class DFRobotGravitySensor : public TelemetrySensor +{ + private: + DFRobot_RainfallSensor_I2C gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second); + + protected: + virtual void setup() override; + + public: + DFRobotGravitySensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif +#endif \ No newline at end of file diff --git a/src/motion/AccelerometerThread.h b/src/motion/AccelerometerThread.h index 95f09910f..6e517d6b0 100755 --- a/src/motion/AccelerometerThread.h +++ b/src/motion/AccelerometerThread.h @@ -48,6 +48,13 @@ class AccelerometerThread : public concurrency::OSThread setIntervalFromNow(0); }; + void calibrate(uint16_t forSeconds) + { + if (sensor) { + sensor->calibrate(forSeconds); + } + } + protected: int32_t runOnce() override { diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp index 6562a651c..06cea3229 100755 --- a/src/motion/BMX160Sensor.cpp +++ b/src/motion/BMX160Sensor.cpp @@ -25,19 +25,21 @@ bool BMX160Sensor::init() int32_t BMX160Sensor::runOnce() { +#if !defined(MESHTASTIC_EXCLUDE_SCREEN) sBmx160SensorData_t magAccel; sBmx160SensorData_t gAccel; /* Get a new sensor event */ sensor.getAllData(&magAccel, NULL, &gAccel); -#if !defined(MESHTASTIC_EXCLUDE_SCREEN) - // experimental calibrate routine. Limited to between 10 and 30 seconds after boot - if (millis() > 12 * 1000 && millis() < 30 * 1000) { + if (doCalibration) { + if (!showingScreen) { + powerFSM.trigger(EVENT_PRESS); // keep screen alive during calibration showingScreen = true; screen->startAlert((FrameCallback)drawFrameCalibration); } + if (magAccel.x > highestX) highestX = magAccel.x; if (magAccel.x < lowestX) @@ -50,9 +52,17 @@ int32_t BMX160Sensor::runOnce() highestZ = magAccel.z; if (magAccel.z < lowestZ) lowestZ = magAccel.z; - } else if (showingScreen && millis() >= 30 * 1000) { - showingScreen = false; - screen->endAlert(); + + uint32_t now = millis(); + if (now > endCalibrationAt) { + doCalibration = false; + endCalibrationAt = 0; + showingScreen = false; + screen->endAlert(); + } + + // LOG_DEBUG("BMX160 min_x: %.4f, max_X: %.4f, min_Y: %.4f, max_Y: %.4f, min_Z: %.4f, max_Z: %.4f", lowestX, highestX, + // lowestY, highestY, lowestZ, highestZ); } int highestRealX = highestX - (highestX + lowestX) / 2; @@ -93,12 +103,25 @@ int32_t BMX160Sensor::runOnce() heading += 270; break; } + screen->setHeading(heading); #endif return MOTION_SENSOR_CHECK_INTERVAL_MS; } +void BMX160Sensor::calibrate(uint16_t forSeconds) +{ +#if !defined(MESHTASTIC_EXCLUDE_SCREEN) + LOG_DEBUG("BMX160 calibration started for %is", forSeconds); + + doCalibration = true; + uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided + endCalibrationAt = millis() + calibrateFor; + screen->setEndCalibration(endCalibrationAt); +#endif +} + #endif #endif \ No newline at end of file diff --git a/src/motion/BMX160Sensor.h b/src/motion/BMX160Sensor.h index 26f477271..9031b4504 100755 --- a/src/motion/BMX160Sensor.h +++ b/src/motion/BMX160Sensor.h @@ -23,6 +23,7 @@ class BMX160Sensor : public MotionSensor explicit BMX160Sensor(ScanI2C::FoundDevice foundDevice); virtual bool init() override; virtual int32_t runOnce() override; + virtual void calibrate(uint16_t forSeconds) override; }; #else diff --git a/src/motion/MotionSensor.cpp b/src/motion/MotionSensor.cpp index 242e3709f..d87380085 100755 --- a/src/motion/MotionSensor.cpp +++ b/src/motion/MotionSensor.cpp @@ -2,6 +2,8 @@ #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C +char timeRemainingBuffer[12]; + // screen is defined in main.cpp extern graphics::Screen *screen; @@ -37,6 +39,12 @@ void MotionSensor::drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_MEDIUM); display->drawString(x, y, "Calibrating\nCompass"); + + uint8_t timeRemaining = (screen->getEndCalibration() - millis()) / 1000; + sprintf(timeRemainingBuffer, "( %02d )", timeRemaining); + display->setFont(FONT_SMALL); + display->drawString(x, y + 40, timeRemainingBuffer); + int16_t compassX = 0, compassY = 0; uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); diff --git a/src/motion/MotionSensor.h b/src/motion/MotionSensor.h index 78eec54ce..1f4d093bf 100755 --- a/src/motion/MotionSensor.h +++ b/src/motion/MotionSensor.h @@ -40,6 +40,8 @@ class MotionSensor // Refer to /src/concurrency/OSThread.h for more information inline virtual int32_t runOnce() { return MOTION_SENSOR_CHECK_INTERVAL_MS; }; + virtual void calibrate(uint16_t forSeconds){}; + protected: // Turn on the screen when a tap or motion is detected virtual void wakeScreen(); @@ -53,6 +55,10 @@ class MotionSensor #endif ScanI2C::FoundDevice device; + + // Do calibration if true + bool doCalibration = false; + uint32_t endCalibrationAt = 0; }; namespace MotionSensorI2C diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 3db3c37bb..f642af231 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -76,12 +76,22 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length) return; } LOG_INFO("Received MQTT topic %s, len=%u", topic, length); + if (e.packet->hop_limit > HOP_MAX || e.packet->hop_start > HOP_MAX) { + LOG_INFO("Invalid hop_limit(%u) or hop_start(%u)", e.packet->hop_limit, e.packet->hop_start); + return; + } - UniquePacketPoolPacket p = packetPool.allocUniqueCopy(*e.packet); + UniquePacketPoolPacket p = packetPool.allocUniqueZeroed(); + p->from = e.packet->from; + p->to = e.packet->to; + p->id = e.packet->id; + p->channel = e.packet->channel; + p->hop_limit = e.packet->hop_limit; + p->hop_start = e.packet->hop_start; + p->want_ack = e.packet->want_ack; p->via_mqtt = true; // Mark that the packet was received via MQTT - // Unset received SNR/RSSI which might have been added by the MQTT gateway - p->rx_snr = 0; - p->rx_rssi = 0; + p->which_payload_variant = e.packet->which_payload_variant; + memcpy(&p->decoded, &e.packet->decoded, std::max(sizeof(p->decoded), sizeof(p->encrypted))); if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) { if (moduleConfig.mqtt.encryption_enabled) { diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 31bbc7fa9..b98620f33 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -44,11 +44,7 @@ class BluetoothPhoneAPI : public PhoneAPI } /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override - { - BLEConnection *connection = Bluefruit.Connection(connectionHandle); - return connection->connected(); - } + virtual bool checkIsConnected() override { return Bluefruit.connected(connectionHandle); } }; static BluetoothPhoneAPI *bluetoothPhoneAPI; @@ -214,7 +210,10 @@ void NRF52Bluetooth::shutdown() LOG_INFO("NRF52 bluetooth disconnecting handle %d", i); Bluefruit.disconnect(i); } - delay(100); // wait for ondisconnect; + // Wait for disconnection + while (Bluefruit.connected()) + yield(); + LOG_INFO("All bluetooth connections ended"); } Bluefruit.Advertising.stop(); } diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index b042510f5..ab78baa1a 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -21,6 +21,10 @@ #include #include +#ifdef PORTDUINO_LINUX_HARDWARE +#include +#endif + #include "platform/portduino/USBHal.h" std::map settingsMap; @@ -134,21 +138,10 @@ void portduinoSetup() { printf("Set up Meshtastic on Portduino...\n"); int max_GPIO = 0; - const configNames GPIO_lines[] = {cs, - irq, - busy, - reset, - sx126x_ant_sw, - txen, - rxen, - displayDC, - displayCS, - displayBacklight, - displayBacklightPWMChannel, - displayReset, - touchscreenCS, - touchscreenIRQ, - user}; + const configNames GPIO_lines[] = { + cs_pin, irq_pin, busy_pin, reset_pin, sx126x_ant_sw_pin, txen_pin, + rxen_pin, displayDC, displayCS, displayBacklight, displayBacklightPWMChannel, displayReset, + touchscreenCS, touchscreenIRQ, user}; std::string gpioChipName = "gpiochip"; settingsStrings[i2cdev] = ""; @@ -207,15 +200,12 @@ void portduinoSetup() // if we're using a usermode driver, we need to initialize it here, to get a serial number back for mac address uint8_t dmac[6] = {0}; if (settingsStrings[spidev] == "ch341") { - ch341Hal = new Ch341Hal(0); - if (settingsStrings[lora_usb_serial_num] != "") { - ch341Hal->serial = settingsStrings[lora_usb_serial_num]; - } - ch341Hal->vid = settingsMap[lora_usb_vid]; - ch341Hal->pid = settingsMap[lora_usb_pid]; - ch341Hal->init(); - if (!ch341Hal->isInit()) { - std::cout << "Could not initialize CH341 device!" << std::endl; + try { + ch341Hal = + new Ch341Hal(0, settingsStrings[lora_usb_serial_num], settingsMap[lora_usb_vid], settingsMap[lora_usb_pid]); + } catch (std::exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Could not initialize CH341 device!" << std::endl; exit(EXIT_FAILURE); } char serial[9] = {0}; @@ -247,7 +237,7 @@ void portduinoSetup() // Rather important to set this, if not running simulated. randomSeed(time(NULL)); - gpioChipName += std::to_string(settingsMap[gpiochip]); + std::string defaultGpioChipName = gpioChipName + std::to_string(settingsMap[default_gpiochip]); for (configNames i : GPIO_lines) { if (settingsMap.count(i) && settingsMap[i] > max_GPIO) @@ -257,65 +247,51 @@ void portduinoSetup() gpioInit(max_GPIO + 1); // Done here so we can inform Portduino how many GPIOs we need. // Need to bind all the configured GPIO pins so they're not simulated - // TODO: Can we do this in the for loop above? // TODO: If one of these fails, we should log and terminate if (settingsMap.count(user) > 0 && settingsMap[user] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[user], gpioChipName) != ERRNO_OK) { + if (initGPIOPin(settingsMap[user], defaultGpioChipName, settingsMap[user]) != ERRNO_OK) { settingsMap[user] = RADIOLIB_NC; } } if (settingsMap[displayPanel] != no_screen) { if (settingsMap[displayCS] > 0) - initGPIOPin(settingsMap[displayCS], gpioChipName); + initGPIOPin(settingsMap[displayCS], defaultGpioChipName, settingsMap[displayCS]); if (settingsMap[displayDC] > 0) - initGPIOPin(settingsMap[displayDC], gpioChipName); + initGPIOPin(settingsMap[displayDC], defaultGpioChipName, settingsMap[displayDC]); if (settingsMap[displayBacklight] > 0) - initGPIOPin(settingsMap[displayBacklight], gpioChipName); + initGPIOPin(settingsMap[displayBacklight], defaultGpioChipName, settingsMap[displayBacklight]); if (settingsMap[displayReset] > 0) - initGPIOPin(settingsMap[displayReset], gpioChipName); + initGPIOPin(settingsMap[displayReset], defaultGpioChipName, settingsMap[displayReset]); } if (settingsMap[touchscreenModule] != no_touchscreen) { if (settingsMap[touchscreenCS] > 0) - initGPIOPin(settingsMap[touchscreenCS], gpioChipName); + initGPIOPin(settingsMap[touchscreenCS], defaultGpioChipName, settingsMap[touchscreenCS]); if (settingsMap[touchscreenIRQ] > 0) - initGPIOPin(settingsMap[touchscreenIRQ], gpioChipName); + initGPIOPin(settingsMap[touchscreenIRQ], defaultGpioChipName, settingsMap[touchscreenIRQ]); } // Only initialize the radio pins when dealing with real, kernel controlled SPI hardware if (settingsStrings[spidev] != "" && settingsStrings[spidev] != "ch341") { - if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) { - settingsMap[cs] = RADIOLIB_NC; - } - } - if (settingsMap.count(irq) > 0 && settingsMap[irq] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[irq], gpioChipName) != ERRNO_OK) { - settingsMap[irq] = RADIOLIB_NC; - } - } - if (settingsMap.count(busy) > 0 && settingsMap[busy] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[busy], gpioChipName) != ERRNO_OK) { - settingsMap[busy] = RADIOLIB_NC; - } - } - if (settingsMap.count(reset) > 0 && settingsMap[reset] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[reset], gpioChipName) != ERRNO_OK) { - settingsMap[reset] = RADIOLIB_NC; - } - } - if (settingsMap.count(sx126x_ant_sw) > 0 && settingsMap[sx126x_ant_sw] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[sx126x_ant_sw], gpioChipName) != ERRNO_OK) { - settingsMap[sx126x_ant_sw] = RADIOLIB_NC; - } - } - if (settingsMap.count(rxen) > 0 && settingsMap[rxen] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[rxen], gpioChipName) != ERRNO_OK) { - settingsMap[rxen] = RADIOLIB_NC; - } - } - if (settingsMap.count(txen) > 0 && settingsMap[txen] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[txen], gpioChipName) != ERRNO_OK) { - settingsMap[txen] = RADIOLIB_NC; + const struct { + configNames pin; + configNames gpiochip; + configNames line; + } pinMappings[] = {{cs_pin, cs_gpiochip, cs_line}, + {irq_pin, irq_gpiochip, irq_line}, + {busy_pin, busy_gpiochip, busy_line}, + {reset_pin, reset_gpiochip, reset_line}, + {rxen_pin, rxen_gpiochip, rxen_line}, + {txen_pin, txen_gpiochip, txen_line}, + {sx126x_ant_sw_pin, sx126x_ant_sw_gpiochip, sx126x_ant_sw_line}}; + for (auto &pinMap : pinMappings) { + auto setMapIter = settingsMap.find(pinMap.pin); + if (setMapIter != settingsMap.end() && setMapIter->second != RADIOLIB_NC) { + if (initGPIOPin(setMapIter->second, gpioChipName + std::to_string(settingsMap[pinMap.gpiochip]), + settingsMap[pinMap.line]) != ERRNO_OK) { + printf("Error setting pin number %d. It may not exist, or may already be in use.\n", + settingsMap[pinMap.line]); + exit(EXIT_FAILURE); + } } } SPI.begin(settingsStrings[spidev].c_str()); @@ -332,19 +308,19 @@ void portduinoSetup() return; } -int initGPIOPin(int pinNum, const std::string gpioChipName) +int initGPIOPin(int pinNum, const std::string gpioChipName, int line) { #ifdef PORTDUINO_LINUX_HARDWARE std::string gpio_name = "GPIO" + std::to_string(pinNum); try { GPIOPin *csPin; - csPin = new LinuxGPIOPin(pinNum, gpioChipName.c_str(), pinNum, gpio_name.c_str()); + csPin = new LinuxGPIOPin(pinNum, gpioChipName.c_str(), line, gpio_name.c_str()); csPin->setSilent(); gpioBind(csPin); return ERRNO_OK; } catch (...) { - std::exception_ptr p = std::current_exception(); - std::cout << "Warning, cannot claim pin " << gpio_name << (p ? p.__cxa_exception_type()->name() : "null") << std::endl; + const std::type_info *t = abi::__cxa_current_exception_type(); + std::cout << "Warning, cannot claim pin " << gpio_name << (t ? t->name() : "null") << std::endl; return ERRNO_DISABLED; } #else @@ -376,42 +352,58 @@ bool loadConfig(const char *configPath) } } if (yamlConfig["Lora"]) { - settingsMap[use_sx1262] = false; - settingsMap[use_rf95] = false; - settingsMap[use_sx1280] = false; - settingsMap[use_lr1110] = false; - settingsMap[use_lr1120] = false; - settingsMap[use_lr1121] = false; - settingsMap[use_sx1268] = false; - - if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "sx1262") { - settingsMap[use_sx1262] = true; - } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "RF95") { - settingsMap[use_rf95] = true; - } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "sx1280") { - settingsMap[use_sx1280] = true; - } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "lr1110") { - settingsMap[use_lr1110] = true; - } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "lr1120") { - settingsMap[use_lr1120] = true; - } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "lr1121") { - settingsMap[use_lr1121] = true; - } else if (yamlConfig["Lora"]["Module"] && yamlConfig["Lora"]["Module"].as("") == "sx1268") { - settingsMap[use_sx1268] = true; + const struct { + configNames cfgName; + std::string strName; + } loraModules[] = {{use_rf95, "RF95"}, {use_sx1262, "sx1262"}, {use_sx1268, "sx1268"}, {use_sx1280, "sx1280"}, + {use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, {use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}}; + for (auto &loraModule : loraModules) { + settingsMap[loraModule.cfgName] = false; } + if (yamlConfig["Lora"]["Module"]) { + for (auto &loraModule : loraModules) { + if (yamlConfig["Lora"]["Module"].as("") == loraModule.strName) { + settingsMap[loraModule.cfgName] = true; + break; + } + } + } + settingsMap[dio2_as_rf_switch] = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as(false); settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as(0) * 1000; if (settingsMap[dio3_tcxo_voltage] == 0 && yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as(false)) { settingsMap[dio3_tcxo_voltage] = 1800; // default millivolts for "true" } - settingsMap[cs] = yamlConfig["Lora"]["CS"].as(RADIOLIB_NC); - settingsMap[irq] = yamlConfig["Lora"]["IRQ"].as(RADIOLIB_NC); - settingsMap[busy] = yamlConfig["Lora"]["Busy"].as(RADIOLIB_NC); - settingsMap[reset] = yamlConfig["Lora"]["Reset"].as(RADIOLIB_NC); - settingsMap[txen] = yamlConfig["Lora"]["TXen"].as(RADIOLIB_NC); - settingsMap[rxen] = yamlConfig["Lora"]["RXen"].as(RADIOLIB_NC); - settingsMap[sx126x_ant_sw] = yamlConfig["Lora"]["SX126X_ANT_SW"].as(RADIOLIB_NC); - settingsMap[gpiochip] = yamlConfig["Lora"]["gpiochip"].as(0); + + // backwards API compatibility and to globally set gpiochip once + int defaultGpioChip = settingsMap[default_gpiochip] = yamlConfig["Lora"]["gpiochip"].as(0); + + const struct { + configNames pin; + configNames gpiochip; + configNames line; + std::string strName; + } pinMappings[] = { + {cs_pin, cs_gpiochip, cs_line, "CS"}, + {irq_pin, irq_gpiochip, irq_line, "IRQ"}, + {busy_pin, busy_gpiochip, busy_line, "Busy"}, + {reset_pin, reset_gpiochip, reset_line, "Reset"}, + {txen_pin, txen_gpiochip, txen_line, "TXen"}, + {rxen_pin, rxen_gpiochip, rxen_line, "RXen"}, + {sx126x_ant_sw_pin, sx126x_ant_sw_gpiochip, sx126x_ant_sw_line, "SX126X_ANT_SW"}, + }; + for (auto &pinMap : pinMappings) { + if (yamlConfig["Lora"][pinMap.strName].IsMap()) { + settingsMap[pinMap.pin] = yamlConfig["Lora"][pinMap.strName]["pin"].as(RADIOLIB_NC); + settingsMap[pinMap.line] = yamlConfig["Lora"][pinMap.strName]["line"].as(settingsMap[pinMap.pin]); + settingsMap[pinMap.gpiochip] = yamlConfig["Lora"][pinMap.strName]["gpiochip"].as(defaultGpioChip); + } else { // backwards API compatibility + settingsMap[pinMap.pin] = yamlConfig["Lora"][pinMap.strName].as(RADIOLIB_NC); + settingsMap[pinMap.line] = settingsMap[pinMap.pin]; + settingsMap[pinMap.gpiochip] = defaultGpioChip; + } + } + settingsMap[spiSpeed] = yamlConfig["Lora"]["spiSpeed"].as(2000000); settingsStrings[lora_usb_serial_num] = yamlConfig["Lora"]["USB_Serialnum"].as(""); settingsMap[lora_usb_pid] = yamlConfig["Lora"]["USB_PID"].as(0x5512); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 5bc07df6a..d1e91956d 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -5,27 +5,42 @@ #include "platform/portduino/USBHal.h" enum configNames { - use_sx1262, - cs, - irq, - busy, - reset, - sx126x_ant_sw, - txen, - rxen, + default_gpiochip, + cs_pin, + cs_line, + cs_gpiochip, + irq_pin, + irq_line, + irq_gpiochip, + busy_pin, + busy_line, + busy_gpiochip, + reset_pin, + reset_line, + reset_gpiochip, + txen_pin, + txen_line, + txen_gpiochip, + rxen_pin, + rxen_line, + rxen_gpiochip, + sx126x_ant_sw_pin, + sx126x_ant_sw_line, + sx126x_ant_sw_gpiochip, dio2_as_rf_switch, dio3_tcxo_voltage, use_rf95, + use_sx1262, + use_sx1268, use_sx1280, use_lr1110, use_lr1120, use_lr1121, - use_sx1268, + use_llcc68, lora_usb_serial_num, lora_usb_pid, lora_usb_vid, user, - gpiochip, spidev, spiSpeed, i2cdev, @@ -75,7 +90,7 @@ extern std::map settingsMap; extern std::map settingsStrings; extern std::ofstream traceFile; extern Ch341Hal *ch341Hal; -int initGPIOPin(int pinNum, std::string gpioChipname); +int initGPIOPin(int pinNum, std::string gpioChipname, int line); bool loadConfig(const char *configPath); static bool ends_with(std::string_view str, std::string_view suffix); void getMacAddr(uint8_t *dmac); diff --git a/src/platform/portduino/USBHal.h b/src/platform/portduino/USBHal.h index 064f7ae36..0d6b361f4 100644 --- a/src/platform/portduino/USBHal.h +++ b/src/platform/portduino/USBHal.h @@ -5,6 +5,7 @@ #include "platform/portduino/PortduinoGlue.h" #include #include +#include #include #include @@ -26,30 +27,42 @@ class Ch341Hal : public RadioLibHal { public: // default constructor - initializes the base HAL and any needed private members - explicit Ch341Hal(uint8_t spiChannel, uint32_t spiSpeed = 2000000, uint8_t spiDevice = 0, uint8_t gpioDevice = 0) + explicit Ch341Hal(uint8_t spiChannel, std::string serial = "", uint32_t vid = 0x1A86, uint32_t pid = 0x5512, + uint32_t spiSpeed = 2000000, uint8_t spiDevice = 0, uint8_t gpioDevice = 0) : RadioLibHal(PI_INPUT, PI_OUTPUT, PI_LOW, PI_HIGH, PI_RISING, PI_FALLING) { + if (serial != "") { + strncpy(pinedio.serial_number, serial.c_str(), 8); + pinedio_set_option(&pinedio, PINEDIO_OPTION_SEARCH_SERIAL, 1); + } + // LOG_INFO("USB Serial: %s", pinedio.serial_number); + + // There is no vendor with 0x0 -> so check + if (vid != 0x0) { + pinedio_set_option(&pinedio, PINEDIO_OPTION_VID, vid); + pinedio_set_option(&pinedio, PINEDIO_OPTION_PID, pid); + } + int32_t ret = pinedio_init(&pinedio, NULL); + if (ret != 0) { + std::string s = "Could not open SPI: "; + throw(s + std::to_string(ret)); + } + + pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0); + pinedio_set_pin_mode(&pinedio, 3, true); + pinedio_set_pin_mode(&pinedio, 5, true); } + ~Ch341Hal() { pinedio_deinit(&pinedio); } + void getSerialString(char *_serial, size_t len) { - if (!pinedio_is_init) { - return; - } + len = len > 8 ? 8 : len; strncpy(_serial, pinedio.serial_number, len); } - void init() override - { - // now the SPI - spiBegin(); - } - - void term() override - { - // stop the SPI - spiEnd(); - } + void init() override {} + void term() override {} // GPIO-related methods (pinMode, digitalWrite etc.) should check // RADIOLIB_NC as an alias for non-connected pins @@ -79,7 +92,7 @@ class Ch341Hal : public RadioLibHal void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override { - if ((interruptNum == RADIOLIB_NC)) { + if (interruptNum == RADIOLIB_NC) { return; } // LOG_DEBUG("Attach interrupt to pin %d", interruptNum); @@ -88,23 +101,14 @@ class Ch341Hal : public RadioLibHal void detachInterrupt(uint32_t interruptNum) override { - if ((interruptNum == RADIOLIB_NC)) { + if (interruptNum == RADIOLIB_NC) { return; } // LOG_DEBUG("Detach interrupt from pin %d", interruptNum); - pinedio_deattach_interrupt(&this->pinedio, (pinedio_int_pin)interruptNum); } - void delay(unsigned long ms) override - { - if (ms == 0) { - sched_yield(); - return; - } - - usleep(ms * 1000); - } + void delay(unsigned long ms) override { delayMicroseconds(ms * 1000); } void delayMicroseconds(unsigned long us) override { @@ -133,62 +137,26 @@ class Ch341Hal : public RadioLibHal long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override { - fprintf(stderr, "pulseIn for pin %u is not supported!\n", pin); + std::cerr << "pulseIn for pin " << pin << "is not supported!" << std::endl; return 0; } - void spiBegin() - { - if (!pinedio_is_init) { - if (serial != "") { - strncpy(pinedio.serial_number, serial.c_str(), 8); - pinedio_set_option(&pinedio, PINEDIO_OPTION_SEARCH_SERIAL, 1); - } - pinedio_set_option(&pinedio, PINEDIO_OPTION_PID, pid); - pinedio_set_option(&pinedio, PINEDIO_OPTION_VID, vid); - int32_t ret = pinedio_init(&pinedio, NULL); - if (ret != 0) { - fprintf(stderr, "Could not open SPI: %d\n", ret); - } else { - pinedio_is_init = true; - // LOG_INFO("USB Serial: %s", pinedio.serial_number); - pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0); - pinedio_set_pin_mode(&pinedio, 3, true); - pinedio_set_pin_mode(&pinedio, 5, true); - } - } - } - + void spiBegin() {} void spiBeginTransaction() {} void spiTransfer(uint8_t *out, size_t len, uint8_t *in) { - int32_t result = pinedio_transceive(&this->pinedio, out, in, len); - if (result < 0) { - fprintf(stderr, "Could not perform SPI transfer: %d\n", result); + int32_t ret = pinedio_transceive(&this->pinedio, out, in, len); + if (ret < 0) { + std::cerr << "Could not perform SPI transfer: " << ret << std::endl; } } void spiEndTransaction() {} - - void spiEnd() - { - if (pinedio_is_init) { - pinedio_deinit(&pinedio); - pinedio_is_init = false; - } - } - - bool isInit() { return pinedio_is_init; } - - std::string serial = ""; - uint32_t pid = 0x5512; - uint32_t vid = 0x1A86; + void spiEnd() {} private: - // the HAL can contain any additional private members pinedio_inst pinedio = {0}; - bool pinedio_is_init = false; }; -#endif \ No newline at end of file +#endif diff --git a/src/platform/rp2xx0/main-rp2xx0.cpp b/src/platform/rp2xx0/main-rp2xx0.cpp index a46b0face..6c73e385a 100644 --- a/src/platform/rp2xx0/main-rp2xx0.cpp +++ b/src/platform/rp2xx0/main-rp2xx0.cpp @@ -2,14 +2,11 @@ #include "hardware/xosc.h" #include #include -#include #include #include -void setBluetoothEnable(bool enable) -{ - // not needed -} +#ifdef __PLAT_RP2040__ +#include static bool awake; @@ -66,7 +63,20 @@ void cpuDeepSleep(uint32_t msecs) rp2040.reboot(); /* Set RP2040 in dormant mode. Will not wake up. */ - // xosc_dormant(); + // xosc_dormant(); +} + +#else +void cpuDeepSleep(uint32_t msecs) +{ + /* Set RP2040 in dormant mode. Will not wake up. */ + xosc_dormant(); +} +#endif + +void setBluetoothEnable(bool enable) +{ + // not needed } void updateBatteryLevel(uint8_t level) diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp new file mode 100644 index 000000000..55ba479e2 --- /dev/null +++ b/test/test_mqtt/MQTT.cpp @@ -0,0 +1,856 @@ +#include "DebugConfiguration.h" +#include "TestUtil.h" +#include + +#ifdef ARCH_PORTDUINO +#include "mesh/CryptoEngine.h" +#include "mesh/Default.h" +#include "mesh/MeshService.h" +#include "mesh/NodeDB.h" +#include "mesh/Router.h" +#include "modules/RoutingModule.h" +#include "mqtt/MQTT.h" +#include "mqtt/ServiceEnvelope.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +// Minimal router needed to receive messages from MQTT. +class MockRouter : public Router +{ + public: + ~MockRouter() + { + // cryptLock is created in the constructor for Router. + delete cryptLock; + cryptLock = NULL; + } + void enqueueReceivedMessage(meshtastic_MeshPacket *p) override + { + packets_.emplace_back(*p); + packetPool.release(p); + } + std::list packets_; // Packets received by the Router. +}; + +// Minimal MeshService needed to receive messages from MQTT for testing PKI channel. +class MockMeshService : public MeshService +{ + public: + void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m) override + { + messages_.emplace_back(*m); + releaseMqttClientProxyMessageToPool(m); + } + std::list messages_; // Messages received from the MeshService. +}; + +// Minimal NodeDB needed to return values from getMeshNode. +class MockNodeDB : public NodeDB +{ + public: + meshtastic_NodeInfoLite *getMeshNode(NodeNum n) override { return &emptyNode; } + meshtastic_NodeInfoLite emptyNode = {}; +}; + +// Minimal RoutingModule needed to return values from sendAckNak. +class MockRoutingModule : public RoutingModule +{ + public: + void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, + uint8_t hopLimit = 0) override + { + ackNacks_.emplace_back(err, to, idFrom, chIndex, hopLimit); + } + std::list> + ackNacks_; // ackNacks received by the RoutingModule. +}; + +// A WiFi client used by the MQTT::PubSubClient. Implements a minimal pub/sub server. +// There isn't an easy way to mock PubSubClient due to it not having virtual methods, so we mock using +// the WiFiClinet that PubSubClient uses. +class MockPubSubServer : public WiFiClient +{ + public: + static constexpr char kTextTopic[] = "TextTopic"; + uint8_t connected() override { return connected_; } + void flush() override {} + IPAddress remoteIP() const override { return IPAddress(htonl(ipAddress_)); } + void stop() override { connected_ = false; } + + int connect(IPAddress ip, uint16_t port) override + { + if (refuseConnection_) + return 0; + connected_ = true; + return 1; + } + int connect(const char *host, uint16_t port) override + { + if (refuseConnection_) + return 0; + connected_ = true; + return 1; + } + + int available() override + { + if (buffer_.empty()) + return 0; + return buffer_.front().size(); + } + + int read() override + { + assert(available()); + std::string &front = buffer_.front(); + char ch = front[0]; + front = front.substr(1, front.size()); + if (front.empty()) + buffer_.pop_front(); + return ch; + } + + size_t write(uint8_t data) override { return write(&data, 1); } + size_t write(const uint8_t *buf, size_t size) override + { + command_ += std::string(reinterpret_cast(buf), size); + if (command_.size() < 2) + return size; + const int len = (uint8_t)command_[1] + 2; + if (command_.size() < len) + return size; + handleCommand(command_[0], command_.substr(2, len)); + command_ = command_.substr(len, command_.size()); + return size; + } + + // The pub/sub "server". + // https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/MQTT_V3.1_Protocol_Specific.pdf + void handleCommand(uint8_t header, std::string_view message) + { + switch (header & 0xf0) { + case MQTTCONNECT: + LOG_DEBUG("MQTTCONNECT"); + buffer_.push_back(std::string("\x20\x02\x00\x00", 4)); + break; + + case MQTTSUBSCRIBE: { + LOG_DEBUG("MQTTSUBSCRIBE"); + assert(message.size() >= 5); + message.remove_prefix(2); // skip messageId + + while (message.size() >= 3) { + const uint16_t topicSize = ((uint8_t)message[0]) << 8 | (uint8_t)message[1]; + message.remove_prefix(2); + + assert(message.size() >= topicSize + 1); + std::string topic(message.data(), topicSize); + message.remove_prefix(topicSize + 1); + + LOG_DEBUG("Subscribed to topic: %s", topic.c_str()); + subscriptions_.insert(std::move(topic)); + } + break; + } + + case MQTTPINGREQ: + LOG_DEBUG("MQTTPINGREQ"); + buffer_.push_back(std::string("\xd0\x00", 2)); + break; + + case MQTTPUBLISH: { + LOG_DEBUG("MQTTPUBLISH"); + assert(message.size() >= 3); + const uint16_t topicSize = ((uint8_t)message[0]) << 8 | (uint8_t)message[1]; + message.remove_prefix(2); + + assert(message.size() >= topicSize); + std::string topic(message.data(), topicSize); + message.remove_prefix(topicSize); + + if (topic == kTextTopic) { + published_.emplace_back(std::move(topic), std::string(message.data(), message.size())); + } else { + published_.emplace_back( + std::move(topic), DecodedServiceEnvelope(reinterpret_cast(message.data()), message.size())); + } + break; + } + } + } + + bool connected_ = false; + bool refuseConnection_ = false; // Simulate a failed connection. + uint32_t ipAddress_ = 0x01010101; // IP address of the MQTT server. + std::list buffer_; // Buffer of messages for the pubSub client to receive. + std::string command_; // Current command received from the pubSub client. + std::set subscriptions_; // Topics that the pubSub client has subscribed to. + std::list>> + published_; // Messages published from the pubSub client. Each list element is a pair containing the topic name and either + // a text message (if from the kTextTopic topic) or a DecodedServiceEnvelope. +}; + +// Instances of our mocks. +class MQTTUnitTest; +MQTTUnitTest *unitTest; +MockPubSubServer *pubsub; +MockRoutingModule *mockRoutingModule; +MockMeshService *mockMeshService; +MockRouter *mockRouter; + +// Keep running the loop until either conditionMet returns true or 4 seconds elapse. +// Returns true if conditionMet returns true, returns false on timeout. +bool loopUntil(std::function conditionMet) +{ + long start = millis(); + while (start + 4000 > millis()) { + long delayMsec = concurrency::mainController.runOrDelay(); + if (conditionMet()) + return true; + concurrency::mainDelay.delay(std::min(delayMsec, 5L)); + } + return false; +} + +// Used to access protected/private members of MQTT for unit testing. +class MQTTUnitTest : public MQTT +{ + public: + MQTTUnitTest() : MQTT(std::make_unique()) + { + pubsub = reinterpret_cast(mqttClient.get()); + } + ~MQTTUnitTest() + { + // Needed because WiFiClient does not have a virtual destructor. + mqttClient.release(); + delete pubsub; + } + int queueSize() { return mqttQueue.numUsed(); } + void reportToMap(std::optional precision = std::nullopt) + { + if (precision.has_value()) + map_position_precision = precision.value(); + map_publish_interval_msecs = 0; + perhapsReportToMap(); + } + void publish(const meshtastic_MeshPacket *p, std::string gateway = "!87654321", std::string channel = "test") + { + std::stringstream topic; + topic << "msh/2/e/" << channel << "/!" << gateway; + const meshtastic_ServiceEnvelope env = {.packet = const_cast(p), + .channel_id = const_cast(channel.c_str()), + .gateway_id = const_cast(gateway.c_str())}; + uint8_t bytes[256]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &env); + mqttCallback(const_cast(topic.str().c_str()), bytes, numBytes); + } + static void restart() + { + if (mqtt != NULL) { + delete mqtt; + mqtt = unitTest = NULL; + } + mqtt = unitTest = new MQTTUnitTest(); + mqtt->start(); + + if (!moduleConfig.mqtt.enabled || moduleConfig.mqtt.proxy_to_client_enabled || *moduleConfig.mqtt.root) { + loopUntil([] { return true; }); // Loop once + return; + } + // Wait for MQTT to subscribe to all topics. + TEST_ASSERT_TRUE(loopUntil( + [] { return pubsub->subscriptions_.count("msh/2/e/test/+") && pubsub->subscriptions_.count("msh/2/e/PKI/+"); })); + } + PubSubClient &getPubSub() { return pubSub; } +}; + +// Packets used in unit tests. +const meshtastic_MeshPacket decoded = { + .from = 1, + .to = 2, + .which_payload_variant = meshtastic_MeshPacket_decoded_tag, + .decoded = {.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP, .has_bitfield = true, .bitfield = BITFIELD_OK_TO_MQTT_MASK}, + .id = 4, +}; +const meshtastic_MeshPacket encrypted = { + .from = 1, + .to = 2, + .which_payload_variant = meshtastic_MeshPacket_encrypted_tag, + .encrypted = {.size = 0}, + .id = 3, +}; +} // namespace + +// Initialize mocks and configuration before running each test. +void setUp(void) +{ + moduleConfig.mqtt = + meshtastic_ModuleConfig_MQTTConfig{.enabled = true, .map_reporting_enabled = true, .has_map_report_settings = true}; + channelFile.channels[0] = meshtastic_Channel{ + .index = 0, + .has_settings = true, + .settings = {.name = "test", .uplink_enabled = true, .downlink_enabled = true}, + .role = meshtastic_Channel_Role_PRIMARY, + }; + channelFile.channels_count = 1; + owner = meshtastic_User{.id = "!12345678"}; + myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 10}; + localPosition = + meshtastic_Position{.has_latitude_i = true, .latitude_i = 7 * 1e7, .has_longitude_i = true, .longitude_i = 3 * 1e7}; + + router = mockRouter = new MockRouter(); + service = mockMeshService = new MockMeshService(); + routingModule = mockRoutingModule = new MockRoutingModule(); + MQTTUnitTest::restart(); +} + +// Deinitialize all objects created in setUp. +void tearDown(void) +{ + delete unitTest; + mqtt = unitTest = NULL; + delete mockRoutingModule; + routingModule = mockRoutingModule = NULL; + delete mockMeshService; + service = mockMeshService = NULL; + delete mockRouter; + router = mockRouter = NULL; +} + +// Test that the decoded MeshPacket is published when encryption_enabled = false. +void test_sendDirectlyConnectedDecoded(void) +{ + mqtt->onSend(encrypted, decoded, 0); + + TEST_ASSERT_EQUAL(1, pubsub->published_.size()); + const auto &[topic, payload] = pubsub->published_.front(); + const DecodedServiceEnvelope &env = std::get(payload); + TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", topic.c_str()); + TEST_ASSERT_TRUE(env.validDecode); + TEST_ASSERT_EQUAL(decoded.id, env.packet->id); +} + +// Test that the encrypted MeshPacket is published when encryption_enabled = true. +void test_sendDirectlyConnectedEncrypted(void) +{ + moduleConfig.mqtt.encryption_enabled = true; + + mqtt->onSend(encrypted, decoded, 0); + + TEST_ASSERT_EQUAL(1, pubsub->published_.size()); + const auto &[topic, payload] = pubsub->published_.front(); + const DecodedServiceEnvelope &env = std::get(payload); + TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", topic.c_str()); + TEST_ASSERT_TRUE(env.validDecode); + TEST_ASSERT_EQUAL(encrypted.id, env.packet->id); +} + +// Verify that the decoded MeshPacket is proxied through the MeshService when encryption_enabled = false. +void test_proxyToMeshServiceDecoded(void) +{ + moduleConfig.mqtt.proxy_to_client_enabled = true; + MQTTUnitTest::restart(); + + mqtt->onSend(encrypted, decoded, 0); + + TEST_ASSERT_EQUAL(1, mockMeshService->messages_.size()); + const meshtastic_MqttClientProxyMessage &message = mockMeshService->messages_.front(); + TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", message.topic); + TEST_ASSERT_EQUAL(meshtastic_MqttClientProxyMessage_data_tag, message.which_payload_variant); + const DecodedServiceEnvelope env(message.payload_variant.data.bytes, message.payload_variant.data.size); + TEST_ASSERT_TRUE(env.validDecode); + TEST_ASSERT_EQUAL(decoded.id, env.packet->id); +} + +// Verify that the encrypted MeshPacket is proxied through the MeshService when encryption_enabled = true. +void test_proxyToMeshServiceEncrypted(void) +{ + moduleConfig.mqtt.proxy_to_client_enabled = true; + moduleConfig.mqtt.encryption_enabled = true; + MQTTUnitTest::restart(); + + mqtt->onSend(encrypted, decoded, 0); + + TEST_ASSERT_EQUAL(1, mockMeshService->messages_.size()); + const meshtastic_MqttClientProxyMessage &message = mockMeshService->messages_.front(); + TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", message.topic); + TEST_ASSERT_EQUAL(meshtastic_MqttClientProxyMessage_data_tag, message.which_payload_variant); + const DecodedServiceEnvelope env(message.payload_variant.data.bytes, message.payload_variant.data.size); + TEST_ASSERT_TRUE(env.validDecode); + TEST_ASSERT_EQUAL(encrypted.id, env.packet->id); +} + +// A packet without the OK to MQTT bit set should not be published to a public server. +void test_dontMqttMeOnPublicServer(void) +{ + meshtastic_MeshPacket p = decoded; + p.decoded.bitfield = 0; + p.decoded.has_bitfield = 0; + + mqtt->onSend(encrypted, p, 0); + + TEST_ASSERT_TRUE(pubsub->published_.empty()); +} + +// A packet without the OK to MQTT bit set should be published to a private server. +void test_okToMqttOnPrivateServer(void) +{ + // Cause a disconnect. + pubsub->connected_ = false; + pubsub->refuseConnection_ = true; + TEST_ASSERT_TRUE(loopUntil([] { return !unitTest->getPubSub().connected(); })); + + // Use 127.0.0.1 for the server's IP. + pubsub->ipAddress_ = 0x7f000001; + + // Reconnect. + pubsub->refuseConnection_ = false; + TEST_ASSERT_TRUE(loopUntil([] { return unitTest->getPubSub().connected(); })); + + // Send the same packet as test_dontMqttMeOnPublicServer. + meshtastic_MeshPacket p = decoded; + p.decoded.bitfield = 0; + p.decoded.has_bitfield = 0; + + mqtt->onSend(encrypted, p, 0); + + TEST_ASSERT_EQUAL(1, pubsub->published_.size()); +} + +// Range tests messages are not uplinked to the default server. +void test_noRangeTestAppOnDefaultServer(void) +{ + meshtastic_MeshPacket p = decoded; + p.decoded.portnum = meshtastic_PortNum_RANGE_TEST_APP; + + mqtt->onSend(encrypted, p, 0); + + TEST_ASSERT_TRUE(pubsub->published_.empty()); +} + +// Detection sensor messages are not uplinked to the default server. +void test_noDetectionSensorAppOnDefaultServer(void) +{ + meshtastic_MeshPacket p = decoded; + p.decoded.portnum = meshtastic_PortNum_DETECTION_SENSOR_APP; + + mqtt->onSend(encrypted, p, 0); + + TEST_ASSERT_TRUE(pubsub->published_.empty()); +} + +// Test that a MeshPacket is queued while the MQTT server is disconnected. +void test_sendQueued(void) +{ + // Cause a disconnect. + pubsub->connected_ = false; + pubsub->refuseConnection_ = true; + TEST_ASSERT_TRUE(loopUntil([] { return !unitTest->getPubSub().connected(); })); + + // Send while disconnected. + mqtt->onSend(encrypted, decoded, 0); + TEST_ASSERT_EQUAL(1, unitTest->queueSize()); + TEST_ASSERT_TRUE(pubsub->published_.empty()); + TEST_ASSERT_FALSE(unitTest->getPubSub().connected()); + + // Allow reconnect to happen. Expect to see the packet published now. + pubsub->refuseConnection_ = false; + TEST_ASSERT_TRUE(loopUntil([] { return !pubsub->published_.empty(); })); + + TEST_ASSERT_EQUAL(0, unitTest->queueSize()); + const auto &[topic, payload] = pubsub->published_.front(); + const DecodedServiceEnvelope &env = std::get(payload); + TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", topic.c_str()); + TEST_ASSERT_TRUE(env.validDecode); + TEST_ASSERT_EQUAL(decoded.id, env.packet->id); +} + +// Verify reconnecting with the proxy enabled does not reconnect to a MQTT server. +void test_reconnectProxyDoesNotReconnectMqtt(void) +{ + moduleConfig.mqtt.proxy_to_client_enabled = true; + MQTTUnitTest::restart(); + + mqtt->reconnect(); + + TEST_ASSERT_FALSE(pubsub->connected_); +} + +// Test receiving an empty MeshPacket on a subscribed topic. +void test_receiveEmptyMeshPacket(void) +{ + unitTest->publish(NULL); + + TEST_ASSERT_TRUE(mockRouter->packets_.empty()); + TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty()); +} + +// Test receiving a decoded MeshPacket on a subscribed topic. +void test_receiveDecodedProto(void) +{ + unitTest->publish(&decoded); + + TEST_ASSERT_EQUAL(1, mockRouter->packets_.size()); + const meshtastic_MeshPacket &p = mockRouter->packets_.front(); + TEST_ASSERT_EQUAL(decoded.id, p.id); + TEST_ASSERT_TRUE(p.via_mqtt); +} + +// Test receiving a decoded MeshPacket from the phone proxy. +void test_receiveDecodedProtoFromProxy(void) +{ + const meshtastic_ServiceEnvelope env = { + .packet = const_cast(&decoded), .channel_id = "test", .gateway_id = "!87654321"}; + meshtastic_MqttClientProxyMessage message = meshtastic_MqttClientProxyMessage_init_default; + strcat(message.topic, "msh/2/e/test/!87654321"); + message.which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; + message.payload_variant.data.size = pb_encode_to_bytes( + message.payload_variant.data.bytes, sizeof(message.payload_variant.data.bytes), &meshtastic_ServiceEnvelope_msg, &env); + + mqtt->onClientProxyReceive(message); + + TEST_ASSERT_EQUAL(1, mockRouter->packets_.size()); + const meshtastic_MeshPacket &p = mockRouter->packets_.front(); + TEST_ASSERT_EQUAL(decoded.id, p.id); + TEST_ASSERT_TRUE(p.via_mqtt); +} + +// Properly handles the case where the received message is empty. +void test_receiveEmptyDataFromProxy(void) +{ + meshtastic_MqttClientProxyMessage message = meshtastic_MqttClientProxyMessage_init_default; + message.which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; + + mqtt->onClientProxyReceive(message); + + TEST_ASSERT_TRUE(mockRouter->packets_.empty()); +} + +// Packets should be ignored if downlink is not enabled. +void test_receiveWithoutChannelDownlink(void) +{ + channelFile.channels[0].settings.downlink_enabled = false; + + unitTest->publish(&decoded); + + TEST_ASSERT_TRUE(mockRouter->packets_.empty()); +} + +// Test receiving an encrypted MeshPacket on the PKI topic. +void test_receiveEncryptedPKITopicToUs(void) +{ + meshtastic_MeshPacket e = encrypted; + e.to = myNodeInfo.my_node_num; + + unitTest->publish(&e, "!87654321", "PKI"); + + TEST_ASSERT_EQUAL(1, mockRouter->packets_.size()); + const meshtastic_MeshPacket &p = mockRouter->packets_.front(); + TEST_ASSERT_EQUAL(encrypted.id, p.id); + TEST_ASSERT_TRUE(p.via_mqtt); +} + +// Should ignore messages published to MQTT by this gateway. +void test_receiveIgnoresOwnPublishedMessages(void) +{ + unitTest->publish(&decoded, owner.id); + + TEST_ASSERT_TRUE(mockRouter->packets_.empty()); + TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty()); +} + +// Considers receiving one of our packets an acknowledgement of it being sent. +void test_receiveAcksOwnSentMessages(void) +{ + meshtastic_MeshPacket p = decoded; + p.from = myNodeInfo.my_node_num; + + unitTest->publish(&p, owner.id); + + TEST_ASSERT_TRUE(mockRouter->packets_.empty()); + TEST_ASSERT_EQUAL(1, mockRoutingModule->ackNacks_.size()); + const auto &[err, to, idFrom, chIndex, hopLimit] = mockRoutingModule->ackNacks_.front(); + TEST_ASSERT_EQUAL(meshtastic_Routing_Error_NONE, err); + TEST_ASSERT_EQUAL(myNodeInfo.my_node_num, to); + TEST_ASSERT_EQUAL(p.id, idFrom); +} + +// Should ignore our own messages from MQTT that were heard by other nodes. +void test_receiveIgnoresSentMessagesFromOthers(void) +{ + meshtastic_MeshPacket p = decoded; + p.from = myNodeInfo.my_node_num; + + unitTest->publish(&p); + + TEST_ASSERT_TRUE(mockRouter->packets_.empty()); + TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty()); +} + +// Decoded MQTT messages should be ignored when encryption is enabled. +void test_receiveIgnoresDecodedWhenEncryptionEnabled(void) +{ + moduleConfig.mqtt.encryption_enabled = true; + + unitTest->publish(&decoded); + + TEST_ASSERT_TRUE(mockRouter->packets_.empty()); +} + +// Non-encrypted messages for the Admin App should be ignored. +void test_receiveIgnoresDecodedAdminApp(void) +{ + meshtastic_MeshPacket p = decoded; + p.decoded.portnum = meshtastic_PortNum_ADMIN_APP; + + unitTest->publish(&p); + + TEST_ASSERT_TRUE(mockRouter->packets_.empty()); +} + +// Only the same fields that are transmitted over LoRa should be set in MQTT messages. +void test_receiveIgnoresUnexpectedFields(void) +{ + meshtastic_MeshPacket input = decoded; + input.rx_snr = 10; + input.rx_rssi = 20; + + unitTest->publish(&input); + + TEST_ASSERT_EQUAL(1, mockRouter->packets_.size()); + const meshtastic_MeshPacket &p = mockRouter->packets_.front(); + TEST_ASSERT_EQUAL(0, p.rx_snr); + TEST_ASSERT_EQUAL(0, p.rx_rssi); +} + +// Messages with an invalid hop_limit are ignored. +void test_receiveIgnoresInvalidHopLimit(void) +{ + meshtastic_MeshPacket p = decoded; + p.hop_limit = 10; + + unitTest->publish(&p); + + TEST_ASSERT_TRUE(mockRouter->packets_.empty()); +} + +// Publishing to a text channel. +void test_publishTextMessageDirect(void) +{ + TEST_ASSERT_TRUE(mqtt->publish(MockPubSubServer::kTextTopic, "payload", 0)); + + TEST_ASSERT_EQUAL(1, pubsub->published_.size()); + const auto &[topic, payload] = pubsub->published_.front(); + TEST_ASSERT_EQUAL_STRING("payload", std::get(payload).c_str()); +} + +// Publishing to a text channel via the MQTT client proxy. +void test_publishTextMessageWithProxy(void) +{ + moduleConfig.mqtt.proxy_to_client_enabled = true; + + TEST_ASSERT_TRUE(mqtt->publish(MockPubSubServer::kTextTopic, "payload", 0)); + + TEST_ASSERT_EQUAL(1, mockMeshService->messages_.size()); + const meshtastic_MqttClientProxyMessage &message = mockMeshService->messages_.front(); + TEST_ASSERT_EQUAL_STRING(MockPubSubServer::kTextTopic, message.topic); + TEST_ASSERT_EQUAL(meshtastic_MqttClientProxyMessage_text_tag, message.which_payload_variant); + TEST_ASSERT_EQUAL_STRING("payload", message.payload_variant.text); +} + +// Helper method to verify the expected latitude/longitude was received. +void verifyLatLong(const DecodedServiceEnvelope &env, uint32_t latitude, uint32_t longitude) +{ + TEST_ASSERT_TRUE(env.validDecode); + const meshtastic_MeshPacket &p = *env.packet; + TEST_ASSERT_EQUAL(NODENUM_BROADCAST, p.to); + TEST_ASSERT_EQUAL(meshtastic_MeshPacket_decoded_tag, p.which_payload_variant); + TEST_ASSERT_EQUAL(meshtastic_PortNum_MAP_REPORT_APP, p.decoded.portnum); + + meshtastic_MapReport mapReport; + TEST_ASSERT_TRUE( + pb_decode_from_bytes(p.decoded.payload.bytes, p.decoded.payload.size, &meshtastic_MapReport_msg, &mapReport)); + TEST_ASSERT_EQUAL(latitude, mapReport.latitude_i); + TEST_ASSERT_EQUAL(longitude, mapReport.longitude_i); +} + +// Map reporting defaults to an imprecise location. +void test_reportToMapDefaultImprecise(void) +{ + unitTest->reportToMap(); + + TEST_ASSERT_EQUAL(1, pubsub->published_.size()); + const auto &[topic, payload] = pubsub->published_.front(); + TEST_ASSERT_EQUAL_STRING("msh/2/map/", topic.c_str()); + verifyLatLong(std::get(payload), 70123520, 30015488); +} + +// Precise location is reported when configured. +void test_reportToMapPrecise(void) +{ + unitTest->reportToMap(/*precision=*/32); + + TEST_ASSERT_EQUAL(1, pubsub->published_.size()); + const auto &[topic, payload] = pubsub->published_.front(); + TEST_ASSERT_EQUAL_STRING("msh/2/map/", topic.c_str()); + verifyLatLong(std::get(payload), localPosition.latitude_i, localPosition.longitude_i); +} + +// Location is sent over the phone proxy. +void test_reportToMapPreciseProxied(void) +{ + moduleConfig.mqtt.proxy_to_client_enabled = true; + MQTTUnitTest::restart(); + + unitTest->reportToMap(/*precision=*/32); + + TEST_ASSERT_EQUAL(1, mockMeshService->messages_.size()); + const meshtastic_MqttClientProxyMessage &message = mockMeshService->messages_.front(); + TEST_ASSERT_EQUAL_STRING("msh/2/map/", message.topic); + TEST_ASSERT_EQUAL(meshtastic_MqttClientProxyMessage_data_tag, message.which_payload_variant); + const DecodedServiceEnvelope env(message.payload_variant.data.bytes, message.payload_variant.data.size); + verifyLatLong(env, localPosition.latitude_i, localPosition.longitude_i); +} + +// No location is reported when the precision is invalid. +void test_reportToMapInvalidPrecision(void) +{ + unitTest->reportToMap(/*precision=*/0); + + TEST_ASSERT_TRUE(pubsub->published_.empty()); +} + +// isUsingDefaultServer returns true when using the default server. +void test_usingDefaultServer(void) +{ + TEST_ASSERT_TRUE(mqtt->isUsingDefaultServer()); +} + +// isUsingDefaultServer returns true when using the default server and a port. +void test_usingDefaultServerWithPort(void) +{ + std::string server = default_mqtt_address; + server += ":1883"; + strcpy(moduleConfig.mqtt.address, server.c_str()); + MQTTUnitTest::restart(); + + TEST_ASSERT_TRUE(mqtt->isUsingDefaultServer()); +} + +// isUsingDefaultServer returns true when using the default server and invalid port. +void test_usingDefaultServerWithInvalidPort(void) +{ + std::string server = default_mqtt_address; + server += ":invalid"; + strcpy(moduleConfig.mqtt.address, server.c_str()); + MQTTUnitTest::restart(); + + TEST_ASSERT_TRUE(mqtt->isUsingDefaultServer()); +} + +// isUsingDefaultServer returns false when not using the default server. +void test_usingCustomServer(void) +{ + strcpy(moduleConfig.mqtt.address, "custom"); + MQTTUnitTest::restart(); + + TEST_ASSERT_FALSE(mqtt->isUsingDefaultServer()); +} + +// Test that isEnabled returns true the MQTT module is enabled. +void test_enabled(void) +{ + TEST_ASSERT_TRUE(mqtt->isEnabled()); +} + +// Test that isEnabled returns false the MQTT module not enabled. +void test_disabled(void) +{ + moduleConfig.mqtt.enabled = false; + MQTTUnitTest::restart(); + + TEST_ASSERT_FALSE(mqtt->isEnabled()); +} + +// Subscriptions contain the moduleConfig.mqtt.root prefix. +void test_customMqttRoot(void) +{ + strcpy(moduleConfig.mqtt.root, "custom"); + MQTTUnitTest::restart(); + + TEST_ASSERT_TRUE(loopUntil( + [] { return pubsub->subscriptions_.count("custom/2/e/test/+") && pubsub->subscriptions_.count("custom/2/e/PKI/+"); })); +} + +void setup() +{ + initializeTestEnvironment(); + const std::unique_ptr mockNodeDB(new MockNodeDB()); + nodeDB = mockNodeDB.get(); + + UNITY_BEGIN(); + RUN_TEST(test_sendDirectlyConnectedDecoded); + RUN_TEST(test_sendDirectlyConnectedEncrypted); + RUN_TEST(test_proxyToMeshServiceDecoded); + RUN_TEST(test_proxyToMeshServiceEncrypted); + RUN_TEST(test_dontMqttMeOnPublicServer); + RUN_TEST(test_okToMqttOnPrivateServer); + RUN_TEST(test_noRangeTestAppOnDefaultServer); + RUN_TEST(test_noDetectionSensorAppOnDefaultServer); + RUN_TEST(test_sendQueued); + RUN_TEST(test_reconnectProxyDoesNotReconnectMqtt); + RUN_TEST(test_receiveEmptyMeshPacket); + RUN_TEST(test_receiveDecodedProto); + RUN_TEST(test_receiveDecodedProtoFromProxy); + RUN_TEST(test_receiveEmptyDataFromProxy); + RUN_TEST(test_receiveWithoutChannelDownlink); + RUN_TEST(test_receiveEncryptedPKITopicToUs); + RUN_TEST(test_receiveIgnoresOwnPublishedMessages); + RUN_TEST(test_receiveAcksOwnSentMessages); + RUN_TEST(test_receiveIgnoresSentMessagesFromOthers); + RUN_TEST(test_receiveIgnoresDecodedWhenEncryptionEnabled); + RUN_TEST(test_receiveIgnoresDecodedAdminApp); + RUN_TEST(test_receiveIgnoresUnexpectedFields); + RUN_TEST(test_receiveIgnoresInvalidHopLimit); + RUN_TEST(test_publishTextMessageDirect); + RUN_TEST(test_publishTextMessageWithProxy); + RUN_TEST(test_reportToMapDefaultImprecise); + RUN_TEST(test_reportToMapPrecise); + RUN_TEST(test_reportToMapPreciseProxied); + RUN_TEST(test_reportToMapInvalidPrecision); + RUN_TEST(test_usingDefaultServer); + RUN_TEST(test_usingDefaultServerWithPort); + RUN_TEST(test_usingDefaultServerWithInvalidPort); + RUN_TEST(test_usingCustomServer); + RUN_TEST(test_enabled); + RUN_TEST(test_disabled); + RUN_TEST(test_customMqttRoot); + exit(UNITY_END()); +} +#else +void setup() +{ + initializeTestEnvironment(); + LOG_WARN("This test requires the ARCH_PORTDUINO variant of WiFiClient"); + UNITY_BEGIN(); + UNITY_END(); +} +#endif +void loop() {} \ No newline at end of file diff --git a/variants/canaryone/variant.h b/variants/canaryone/variant.h index 140b605ad..836fa74a3 100644 --- a/variants/canaryone/variant.h +++ b/variants/canaryone/variant.h @@ -123,7 +123,7 @@ static const uint8_t A0 = PIN_A0; */ #define HAS_GPS 1 #define GPS_UBLOX -#define GPS_BAUDRATE 38400 +#define GPS_BAUDRATE 9600 // #define PIN_GPS_WAKE (GPIO_PORT1 + 2) // An output to wake GPS, low means allow sleep, high means force wake // Seems to be missing on this new board diff --git a/variants/chatter2/platformio.ini b/variants/chatter2/platformio.ini index 1f086cf07..83e00d0c4 100644 --- a/variants/chatter2/platformio.ini +++ b/variants/chatter2/platformio.ini @@ -9,4 +9,4 @@ build_flags = lib_deps = ${esp32_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file + lovyan03/LovyanGFX@^1.2.0 diff --git a/variants/heltec_wireless_tracker/platformio.ini b/variants/heltec_wireless_tracker/platformio.ini index c7ecce8ea..4f686d289 100644 --- a/variants/heltec_wireless_tracker/platformio.ini +++ b/variants/heltec_wireless_tracker/platformio.ini @@ -11,4 +11,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 + lovyan03/LovyanGFX@^1.2.0 diff --git a/variants/heltec_wireless_tracker_V1_0/platformio.ini b/variants/heltec_wireless_tracker_V1_0/platformio.ini index 0e48c72f2..5f512b816 100644 --- a/variants/heltec_wireless_tracker_V1_0/platformio.ini +++ b/variants/heltec_wireless_tracker_V1_0/platformio.ini @@ -10,4 +10,4 @@ build_flags = ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file + lovyan03/LovyanGFX@^1.2.0 diff --git a/variants/heltec_wsl_v2.1/platformio.ini b/variants/heltec_wsl_v2.1/platformio.ini new file mode 100644 index 000000000..f4fff9698 --- /dev/null +++ b/variants/heltec_wsl_v2.1/platformio.ini @@ -0,0 +1,7 @@ +[env:heltec-wsl-v2_1] +extends = esp32_base +board = heltec_wireless_stick_lite +board_level = extra +build_flags = + ${esp32_base.build_flags} -D PRIVATE_HW -I variants/heltec_wsl_v2.1 + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. \ No newline at end of file diff --git a/variants/heltec_wsl_v2.1/variant.h b/variants/heltec_wsl_v2.1/variant.h new file mode 100644 index 000000000..3927a89d6 --- /dev/null +++ b/variants/heltec_wsl_v2.1/variant.h @@ -0,0 +1,29 @@ +#define I2C_SCL SCL +#define I2C_SDA SDA + +#define LED_PIN LED + +// active low, powers the Battery reader, but no lora antenna boost (?) +// #define VEXT_ENABLE Vext +// #define VEXT_ON_VALUE LOW + +#define BUTTON_PIN 0 + +#define ADC_CTRL 21 +#define ADC_CTRL_ENABLED LOW +#define BATTERY_PIN 37 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_CHANNEL_1 +// ratio of voltage divider = 3.20 (R1=100k, R2=220k) +#define ADC_MULTIPLIER 3.2 + +#define USE_RF95 // RFM95/SX127x + +#define LORA_DIO0 26 +#define LORA_RESET 14 +#define LORA_DIO1 35 +#define LORA_DIO2 34 + +#define LORA_SCK 5 +#define LORA_MISO 19 +#define LORA_MOSI 27 +#define LORA_CS 18 diff --git a/variants/m5stack_core/platformio.ini b/variants/m5stack_core/platformio.ini index 95f5aea9f..7418d9e17 100644 --- a/variants/m5stack_core/platformio.ini +++ b/variants/m5stack_core/platformio.ini @@ -25,4 +25,4 @@ lib_ignore = m5stack-core lib_deps = ${esp32_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 \ No newline at end of file + lovyan03/LovyanGFX@^1.2.0 diff --git a/variants/mesh-tab/platformio.ini b/variants/mesh-tab/platformio.ini index a1007a7f4..d6fd1a3ac 100644 --- a/variants/mesh-tab/platformio.ini +++ b/variants/mesh-tab/platformio.ini @@ -54,20 +54,12 @@ build_src_filter = ${esp32_base.build_src_filter} +<../lib/device-ui/locale> +<../lib/device-ui/source> lib_deps = ${esp32_base.lib_deps} - lovyan03/LovyanGFX@^1.1.16 + lovyan03/LovyanGFX@^1.2.0 -; 3.2" TN TFT ST7789 / XPT2046: https://vi.aliexpress.com/item/1005005933490544.html -[env:mesh-tab-3-2-TN-resistive] +[mesh_tab_xpt2046] extends = mesh_tab_base build_flags = ${mesh_tab_base.build_flags} - -D LGFX_SCREEN_WIDTH=240 - -D LGFX_SCREEN_HEIGHT=320 - -D LGFX_PANEL=ST7789 - -D LGFX_INVERT_COLOR=false - -D LGFX_RGB_ORDER=false - -D LGFX_ROTATION=3 -D LGFX_TOUCH=XPT2046 - -D SPI_FREQUENCY=60000000 -D LGFX_TOUCH_SPI_FREQ=2500000 -D LGFX_TOUCH_SPI_HOST=2 -D LGFX_TOUCH_CS=7 @@ -78,156 +70,109 @@ build_flags = ${mesh_tab_base.build_flags} -D LGFX_TOUCH_X_MAX=3900 -D LGFX_TOUCH_Y_MIN=400 -D LGFX_TOUCH_Y_MAX=3900 + +[mesh_tab_ft5x06] +extends = mesh_tab_base +build_flags = ${mesh_tab_base.build_flags} + -D LGFX_TOUCH=FT5x06 + -D LGFX_TOUCH_I2C_FREQ=400000 + -D LGFX_TOUCH_I2C_PORT=0 + -D LGFX_TOUCH_I2C_ADDR=0x38 + -D LGFX_TOUCH_I2C_SDA=8 + -D LGFX_TOUCH_I2C_SCL=9 + -D LGFX_TOUCH_RST=7 + +; 3.2" TN TFT ST7789 / XPT2046: https://vi.aliexpress.com/item/1005005933490544.html +[env:mesh-tab-3-2-TN-resistive] +extends = mesh_tab_base +build_flags = ${mesh_tab_xpt2046.build_flags} + -D SPI_FREQUENCY=60000000 + -D LGFX_SCREEN_WIDTH=240 + -D LGFX_SCREEN_HEIGHT=320 + -D LGFX_PANEL=ST7789 + -D LGFX_INVERT_COLOR=false + -D LGFX_ROTATION=3 -D LGFX_TOUCH_ROTATION=4 ; 3.2" IPS TFT ILI9341 / XPT2046: https://www.aliexpress.com/item/1005006258575617.html [env:mesh-tab-3-2-IPS-resistive] extends = mesh_tab_base -build_flags = ${mesh_tab_base.build_flags} +build_flags = ${mesh_tab_xpt2046.build_flags} + -D SPI_FREQUENCY=60000000 ; if image is distorted then lower to 40 MHz -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 -D LGFX_PANEL=ILI9341 - -D LGFX_INVERT_COLOR=true - -D LGFX_RGB_ORDER=false -D LGFX_ROTATION=1 - -D LGFX_TOUCH=XPT2046 - -D SPI_FREQUENCY=60000000 ; if image is distorted then lower to 40 MHz - -D LGFX_TOUCH_SPI_FREQ=2500000 - -D LGFX_TOUCH_SPI_HOST=2 - -D LGFX_TOUCH_CS=7 - -D LGFX_TOUCH_CLK=12 - -D LGFX_TOUCH_DO=11 - -D LGFX_TOUCH_DIN=13 - -D LGFX_TOUCH_X_MIN=300 - -D LGFX_TOUCH_X_MAX=3900 - -D LGFX_TOUCH_Y_MIN=400 - -D LGFX_TOUCH_Y_MAX=3900 -D LGFX_TOUCH_ROTATION=4 ; 3.5" IPS TFT ILI9488 / XPT2046: https://vi.aliexpress.com/item/1005006333922639.html [env:mesh-tab-3-5-IPS-resistive] extends = mesh_tab_base -build_flags = ${mesh_tab_base.build_flags} +build_flags = ${mesh_tab_xpt2046.build_flags} + -D SPI_FREQUENCY=60000000 ; may go higher upto 40/60/80 MHz -D DISPLAY_SET_RESOLUTION -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 -D LGFX_PANEL=ILI9488 - -D LGFX_INVERT_COLOR=true - -D LGFX_RGB_ORDER=false - -D LGFX_DLEN_16BITS=false -D LGFX_ROTATION=0 - -D LGFX_TOUCH=XPT2046 - -D SPI_FREQUENCY=40000000 ; may go higher upto 40/60/80 MHz - -D LGFX_TOUCH_SPI_FREQ=2500000 - -D LGFX_TOUCH_SPI_HOST=2 - -D LGFX_TOUCH_CS=7 - -D LGFX_TOUCH_CLK=12 - -D LGFX_TOUCH_DO=11 - -D LGFX_TOUCH_DIN=13 - -D LGFX_TOUCH_X_MIN=300 - -D LGFX_TOUCH_X_MAX=3900 - -D LGFX_TOUCH_Y_MIN=400 - -D LGFX_TOUCH_Y_MAX=3900 -D LGFX_TOUCH_ROTATION=0 ; 3.5" TN TFT ILI9488 / XPT2046: https://vi.aliexpress.com/item/32985467436.html [env:mesh-tab-3-5-TN-resistive] extends = mesh_tab_base -build_flags = ${mesh_tab_base.build_flags} +build_flags = ${mesh_tab_xpt2046.build_flags} + -D SPI_FREQUENCY=60000000 -D DISPLAY_SET_RESOLUTION -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 -D LGFX_PANEL=HX8357B - -D SPI_FREQUENCY=60000000 -D LGFX_INVERT_COLOR=false - -D LGFX_RGB_ORDER=false - -D LGFX_DLEN_16BITS=false -D LGFX_ROTATION=4 - -D LGFX_TOUCH=XPT2046 - -D LGFX_TOUCH_SPI_FREQ=2500000 - -D LGFX_TOUCH_SPI_HOST=2 - -D LGFX_TOUCH_CS=7 - -D LGFX_TOUCH_CLK=12 - -D LGFX_TOUCH_DO=11 - -D LGFX_TOUCH_DIN=13 - -D LGFX_TOUCH_X_MIN=300 - -D LGFX_TOUCH_X_MAX=3900 - -D LGFX_TOUCH_Y_MIN=400 - -D LGFX_TOUCH_Y_MAX=3900 -D LGFX_TOUCH_ROTATION=2 ; 3.2" IPS TFT ILI9341 / FT6236: https://vi.aliexpress.com/item/1005006624072350.html [env:mesh-tab-3-2-IPS-capacitive] extends = mesh_tab_base -build_flags = ${mesh_tab_base.build_flags} +build_flags = ${mesh_tab_ft5x06.build_flags} + -D SPI_FREQUENCY=75000000 ; may go higher upto 60/80 MHz -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 -D LGFX_PANEL=ILI9341 - -D LGFX_INVERT_COLOR=true - -D LGFX_RGB_ORDER=false -D LGFX_ROTATION=1 - -D LGFX_TOUCH=FT5x06 - -D SPI_FREQUENCY=40000000 ; may go higher upto 60/80 MHz - -D LGFX_TOUCH_I2C_PORT=0 - -D LGFX_TOUCH_I2C_ADDR=0x38 - -D LGFX_TOUCH_I2C_SDA=8 - -D LGFX_TOUCH_I2C_SCL=9 - -D LGFX_TOUCH_RST=7 + -D LGFX_TOUCH_X_MIN=0 + -D LGFX_TOUCH_X_MAX=239 + -D LGFX_TOUCH_Y_MIN=0 + -D LGFX_TOUCH_Y_MAX=319 + -D LGFX_TOUCH_ROTATION=2 + +; 3.5" IPS TFT ILI9488 / FT6236: https://vi.aliexpress.com/item/1005006893699919.html +[env:mesh-tab-3-5-IPS-capacitive] +extends = mesh_tab_base +build_flags = ${mesh_tab_ft5x06.build_flags} + -D SPI_FREQUENCY=75000000 ; may go higher upto 40/60/80 MHz + -D DISPLAY_SET_RESOLUTION + -D LGFX_SCREEN_WIDTH=320 + -D LGFX_SCREEN_HEIGHT=480 + -D LGFX_PANEL=ILI9488 + -D LGFX_ROTATION=2 -D LGFX_TOUCH_X_MIN=0 -D LGFX_TOUCH_X_MAX=319 -D LGFX_TOUCH_Y_MIN=0 -D LGFX_TOUCH_Y_MAX=479 -D LGFX_TOUCH_ROTATION=0 - -D LGFX_TOUCH_I2C_FREQ=400000 - -; 3.5" IPS TFT ILI9488 / FT6236: https://vi.aliexpress.com/item/1005006893699919.html -[env:mesh-tab-3-5-IPS-capacitive] -extends = mesh_tab_base -build_flags = ${mesh_tab_base.build_flags} - -D DISPLAY_SET_RESOLUTION - -D LGFX_SCREEN_WIDTH=320 - -D LGFX_SCREEN_HEIGHT=480 - -D LGFX_PANEL=ILI9488 - -D LGFX_INVERT_COLOR=true - -D LGFX_RGB_ORDER=false - -D LGFX_DLEN_16BITS=false - -D LGFX_ROTATION=1 - -D LGFX_TOUCH=FT5x06 - -D SPI_FREQUENCY=30000000 ; may go higher upto 40/60/80 MHz - -D LGFX_TOUCH_I2C_PORT=0 - -D LGFX_TOUCH_I2C_ADDR=0x38 - -D LGFX_TOUCH_I2C_SDA=8 - -D LGFX_TOUCH_I2C_SCL=9 - -D LGFX_TOUCH_RST=7 - -D LGFX_TOUCH_X_MIN=0 - -D LGFX_TOUCH_X_MAX=319 - -D LGFX_TOUCH_Y_MIN=0 - -D LGFX_TOUCH_Y_MAX=479 - -D LGFX_TOUCH_ROTATION=1 - -D LGFX_TOUCH_I2C_FREQ=400000 ; 4.0" IPS TFT ILI9488 / FT6236: https://vi.aliexpress.com/item/1005007082906950.html [env:mesh-tab-4-0-IPS-capacitive] extends = mesh_tab_base -build_flags = ${mesh_tab_base.build_flags} +build_flags = ${mesh_tab_ft5x06.build_flags} + -D SPI_FREQUENCY=75000000 -D DISPLAY_SET_RESOLUTION -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 -D LGFX_PANEL=HX8357B - -D LGFX_INVERT_COLOR=true - -D LGFX_RGB_ORDER=false - -D LGFX_DLEN_16BITS=false -D LGFX_ROTATION=4 - -D LGFX_TOUCH=FT5x06 - -D SPI_FREQUENCY=30000000 ; may go higher upto 40/60/80 MHz - -D LGFX_TOUCH_I2C_PORT=0 - -D LGFX_TOUCH_I2C_ADDR=0x38 - -D LGFX_TOUCH_I2C_SDA=8 - -D LGFX_TOUCH_I2C_SCL=9 - -D LGFX_TOUCH_RST=7 -D LGFX_TOUCH_X_MIN=0 -D LGFX_TOUCH_X_MAX=319 -D LGFX_TOUCH_Y_MIN=0 -D LGFX_TOUCH_Y_MAX=479 - -D LGFX_TOUCH_ROTATION=1 - -D LGFX_TOUCH_I2C_FREQ=400000 \ No newline at end of file + -D LGFX_TOUCH_ROTATION=6 diff --git a/variants/picomputer-s3/platformio.ini b/variants/picomputer-s3/platformio.ini index 202cd05e7..5a05d7b90 100644 --- a/variants/picomputer-s3/platformio.ini +++ b/variants/picomputer-s3/platformio.ini @@ -14,4 +14,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 + lovyan03/LovyanGFX@^1.2.0 diff --git a/variants/portduino/platformio.ini b/variants/portduino/platformio.ini index cad87ea8c..2c7030b5b 100644 --- a/variants/portduino/platformio.ini +++ b/variants/portduino/platformio.ini @@ -12,4 +12,4 @@ build_src_filter = ${portduino_base.build_src_filter} [env:coverage] extends = env:native -build_flags = -lgcov --coverage -fprofile-abs-path ${env:native.build_flags} +build_flags = -lgcov --coverage -fprofile-abs-path -fsanitize=address ${env:native.build_flags} diff --git a/variants/rak2560/platformio.ini b/variants/rak2560/platformio.ini index 93c7acf71..cd5225a22 100644 --- a/variants/rak2560/platformio.ini +++ b/variants/rak2560/platformio.ini @@ -1,4 +1,4 @@ -; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 +; Firmware for the WisMesh HUB RAK2560, including a onewire module to talk to the RAK 9154 solar battery. [env:rak2560] extends = nrf52840_base board = wiscore_rak4631 @@ -6,17 +6,18 @@ board_check = true build_flags = ${nrf52840_base.build_flags} -Ivariants/rak2560 -D RAK_4631 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. - -DMESHTASTIC_EXCLUDE_GPS=1 + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 -DHAS_RAKPROT=1 ; Define if RAk OneWireSerial is used (disables GPS) -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak2560> + + + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak2560> + + lib_deps = ${nrf52840_base.lib_deps} ${networking_base.lib_deps} melopero/Melopero RV3028@^1.1.0 - https://github.com/RAKWireless/RAK13800-W5100S.git#1.0.2 rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 beegee-tokyo/RAKwireless RAK12034@^1.0.0 - https://github.com/beegee-tokyo/RAK-OneWireSerial.git + https://github.com/beegee-tokyo/RAK-OneWireSerial.git#0.0.2 debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink diff --git a/variants/rak2560/variant.h b/variants/rak2560/variant.h index 8e5d90553..a03fc3933 100644 --- a/variants/rak2560/variant.h +++ b/variants/rak2560/variant.h @@ -250,8 +250,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define HAS_RTC 1 -#define HAS_ETHERNET 1 - #define RAK_4631 1 #define HALF_UART_PIN PIN_SERIAL1_RX @@ -265,9 +263,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #error pin 15 collision #endif -#define PIN_ETHERNET_RESET 21 -#define PIN_ETHERNET_SS PIN_EINK_CS -#define ETH_SPI_PORT SPI1 #define AQ_SET_PIN 10 #ifdef __cplusplus diff --git a/variants/rak3172/platformio.ini b/variants/rak3172/platformio.ini index 9e617e01e..58ea32088 100644 --- a/variants/rak3172/platformio.ini +++ b/variants/rak3172/platformio.ini @@ -28,6 +28,8 @@ build_flags = -DHAL_TIM_MODULE_DISABLED -DHAL_WWDG_MODULE_DISABLED -DHAL_EXTI_MODULE_DISABLED + -DHAL_SAI_MODULE_DISABLED + -DHAL_ICACHE_MODULE_DISABLED -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 diff --git a/variants/t-deck/platformio.ini b/variants/t-deck/platformio.ini index d2f09f50b..003dd184d 100644 --- a/variants/t-deck/platformio.ini +++ b/variants/t-deck/platformio.ini @@ -14,6 +14,6 @@ build_flags = ${esp32s3_base.build_flags} -Ivariants/t-deck lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.9 + lovyan03/LovyanGFX@^1.2.0 earlephilhower/ESP8266Audio@^1.9.9 earlephilhower/ESP8266SAM@^1.0.1 diff --git a/variants/t-watch-s3/platformio.ini b/variants/t-watch-s3/platformio.ini index 005c4d021..8f48cf6c4 100644 --- a/variants/t-watch-s3/platformio.ini +++ b/variants/t-watch-s3/platformio.ini @@ -12,7 +12,7 @@ build_flags = ${esp32_base.build_flags} -DHAS_BMA423=1 lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.9 + lovyan03/LovyanGFX@^1.2.0 lewisxhe/PCF8563_Library@1.0.1 adafruit/Adafruit DRV2605 Library@^1.2.2 earlephilhower/ESP8266Audio@^1.9.9 diff --git a/variants/tracksenger/platformio.ini b/variants/tracksenger/platformio.ini index d3e31264f..796a3b7d5 100644 --- a/variants/tracksenger/platformio.ini +++ b/variants/tracksenger/platformio.ini @@ -11,7 +11,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 + lovyan03/LovyanGFX@^1.2.0 [env:tracksenger-lcd] extends = esp32s3_base @@ -26,7 +26,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 + lovyan03/LovyanGFX@^1.2.0 [env:tracksenger-oled] extends = esp32s3_base diff --git a/variants/unphone/platformio.ini b/variants/unphone/platformio.ini index 489c70f99..e21e9ed77 100644 --- a/variants/unphone/platformio.ini +++ b/variants/unphone/platformio.ini @@ -26,7 +26,7 @@ build_flags = ${esp32_base.build_flags} build_src_filter = ${esp32_base.build_src_filter} +<../variants/unphone> lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@ 1.1.12 + lovyan03/LovyanGFX@ 1.2.0 https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 adafruit/Adafruit NeoPixel @ ^1.12.0 @@ -72,6 +72,6 @@ build_src_filter = ${esp32_base.build_src_filter} +<../variants/unphone> +<../lib/device-ui/source> lib_deps = ${esp32s3_base.lib_deps} - lovyan03/LovyanGFX@^1.1.12 + lovyan03/LovyanGFX@^1.2.0 https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 - adafruit/Adafruit NeoPixel@1.12.0 \ No newline at end of file + adafruit/Adafruit NeoPixel@1.12.0 diff --git a/variants/wio-e5/platformio.ini b/variants/wio-e5/platformio.ini index 29c4a0a37..e9d4ca946 100644 --- a/variants/wio-e5/platformio.ini +++ b/variants/wio-e5/platformio.ini @@ -28,6 +28,8 @@ build_flags = -DHAL_TIM_MODULE_DISABLED -DHAL_WWDG_MODULE_DISABLED -DHAL_EXTI_MODULE_DISABLED + -DHAL_SAI_MODULE_DISABLED + -DHAL_ICACHE_MODULE_DISABLED -DRADIOLIB_EXCLUDE_SX128X=1 -DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_LR11X0=1 diff --git a/variants/wiphone/platformio.ini b/variants/wiphone/platformio.ini index 0218f8930..362102731 100644 --- a/variants/wiphone/platformio.ini +++ b/variants/wiphone/platformio.ini @@ -8,6 +8,6 @@ build_flags = ${esp32_base.build_flags} -D WIPHONE -I variants/wiphone lib_deps = ${esp32_base.lib_deps} - lovyan03/LovyanGFX@^1.1.8 + lovyan03/LovyanGFX@^1.2.0 sparkfun/SX1509 IO Expander@^3.0.5 - pololu/APA102@^3.0.0 \ No newline at end of file + pololu/APA102@^3.0.0 diff --git a/version.properties b/version.properties index 800529e39..4312ae59a 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 19 +build = 20