diff --git a/exercises/306_microReticulum_ble_file_transfer_oled/README.md b/exercises/306_microReticulum_ble_file_transfer_oled/README.md index 3362454..a7923b0 100644 --- a/exercises/306_microReticulum_ble_file_transfer_oled/README.md +++ b/exercises/306_microReticulum_ble_file_transfer_oled/README.md @@ -22,6 +22,7 @@ Exercise 306 uses the same payload files as the Pi Zero BLE Reticulum tests: texts/If.txt 195 bytes texts/If_full.txt 1583 bytes texts/US_Constitution.txt 44225 bytes +texts/little_boy_blue.txt 942 bytes ``` The selected file is compiled into the firmware. The transfer code does not care which file is selected; `platformio.ini` chooses the source text through `custom_text_source`, and `scripts/embed_text.py` generates `SelectedText.h` in the build directory before compilation. @@ -77,9 +78,22 @@ tbeam_if_full_pi_zero_profile tbeam_constitution_pi_zero_profile ``` -## Build Once, Upload Twice +Host-native environment: -Each selected text environment produces one firmware image. Build it once, then upload that same image to both boards. +```text +jp_native +jp_native_peripheral +``` + +`jp_native` builds a Linux console program instead of ESP32 firmware. It uses the host Bluetooth adapter through BlueZ D-Bus, skips the OLED path, and prints received text to stdout. The current jp payload is `texts/little_boy_blue.txt`. + +`jp_native_peripheral` is the first Linux peripheral/server scaffold. It builds a separate binary that checks for BlueZ `GattManager1` and `LEAdvertisingManager1` support on the host adapter. It does not yet register the full Exercise 306 GATT service or accept a T-Beam connection. + +## Building + +### ESP32 + +Build the ESP32/T-Beam firmware with one of the `tbeam_*` environments. Each selected text environment produces one firmware image. Build it once, then upload that same image to both boards. Build the short If sample: @@ -132,6 +146,88 @@ pio device monitor -p /dev/ttytDAN -b 115200 pio device monitor -p /dev/ttytBOB -b 115200 ``` +### Intel x86_64 + +Build the jp Linux host binary with the `jp_native` environment: + +```bash +source /home/jlpoole/rnsenv/bin/activate +cd /usr/local/src/microreticulum/microReticulumTbeam +pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e jp_native +``` + +The resulting executable is: + +```text +exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native/program +``` + +Run it from the repository root: + +```bash +exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native/program +``` + +The host binary is a BlueZ BLE central. It expects the T-Beam to advertise the Exercise 306 service, then receives file-transfer text on the console. Because it uses system D-Bus and the Bluetooth adapter, sandboxed runs may require approval. + +Build the jp peripheral capability-check binary with: + +```bash +source /home/jlpoole/rnsenv/bin/activate +cd /usr/local/src/microreticulum/microReticulumTbeam +pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e jp_native_peripheral +``` + +The resulting executable is: + +```text +exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native_peripheral/program +``` + +Run it from the repository root: + +```bash +exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native_peripheral/program +``` + +This build is expected to report whether the current BlueZ adapter exposes the GATT server and LE advertising managers needed for true Linux peripheral mode. + +### AMD64 + +AMD64 is the same 64-bit x86 Linux target class as Intel x86_64 for this PlatformIO `native` build. On eos, build the same `jp_native` environment on that machine: + +```bash +source /home/jlpoole/rnsenv/bin/activate +cd /usr/local/src/microreticulum/microReticulumTbeam +pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e jp_native +``` + +The output path is the same relative path: + +```text +exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native/program +``` + +Do not copy the jp-built executable to ARM machines. Rebuild on the target architecture unless a cross-compile environment is added. + +### ARM64 + +For a 64-bit Raspberry Pi OS on a Pi Zero 2W or Pi 4B, use the same `jp_native` environment and build directly on the Pi: + +```bash +source /home/jlpoole/rnsenv/bin/activate +cd /usr/local/src/microreticulum/microReticulumTbeam +pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e jp_native +``` + +PlatformIO `native` produces a binary for the machine doing the build, so an ARM64 Pi build produces an ARM64 executable at: + +```text +exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native/program +``` + +The Pi must have BlueZ, GLib/GIO development headers, and a BLE adapter that supports LE central mode. If the Pi is running a 32-bit OS, the result is a 32-bit ARM binary, not ARM64. + ## Expected Output Once the Link is active, both nodes start sending: diff --git a/exercises/306_microReticulum_ble_file_transfer_oled/platformio.ini b/exercises/306_microReticulum_ble_file_transfer_oled/platformio.ini index 4aa0f0f..53be8de 100644 --- a/exercises/306_microReticulum_ble_file_transfer_oled/platformio.ini +++ b/exercises/306_microReticulum_ble_file_transfer_oled/platformio.ini @@ -18,6 +18,7 @@ build_src_filter = +<*> - - + - build_flags = -Wall @@ -98,7 +99,7 @@ extends = tbeam_base platform = native build_type = debug extra_scripts = pre:scripts/embed_text.py -custom_text_source = texts/If_full.txt +custom_text_source = texts/little_boy_blue.txt build_unflags = -std=gnu++11 build_flags = @@ -133,6 +134,46 @@ lib_deps = microReticulum=symlink:///usr/local/src/microreticulum/microReticulum lib_compat_mode = off +[env:jp_native_peripheral] +platform = native +build_type = debug +extra_scripts = pre:scripts/embed_text.py +custom_text_source = texts/little_boy_blue.txt +build_unflags = + -std=gnu++11 +build_flags = + -std=c++17 + -g3 + -ggdb + -Wall + -Wextra + -Wno-missing-field-initializers + -Wno-format + -Wno-unused-parameter + -include stdint.h + -D HOST_NATIVE + -D HOST_BLE_PERIPHERAL + -D NATIVE + -D RNS_USE_FS + -D RNS_PERSIST_PATHS + -D USTORE_USE_UNIVERSALFS + -D MSGPACK_USE_BOOST=OFF + -D FILE_TRANSFER_CHUNK_SIZE=32 + -D FILE_TRANSFER_CHUNK_INTERVAL_MS=500 + -D HOST_NODE_LABEL=\"Node-JP-PERIPHERAL\" + !pkg-config --cflags gio-2.0 glib-2.0 bluez + !pkg-config --libs gio-2.0 glib-2.0 bluez +build_src_filter = + + + + +lib_deps = + ArduinoJson@^7.4.2 + MsgPack@^0.4.2 + https://github.com/attermann/Crypto.git + https://github.com/attermann/microStore.git + microReticulum=symlink:///usr/local/src/microreticulum/microReticulum +lib_compat_mode = off + [env:amy] extends = tbeam_base upload_port = /dev/ttytAMY diff --git a/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezBleInterface.cpp b/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezBleInterface.cpp index 7d37178..1e65f27 100644 --- a/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezBleInterface.cpp +++ b/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezBleInterface.cpp @@ -286,6 +286,10 @@ bool HostBluezBleInterface::connect_device(const std::string& device_path) { } g_variant_unref(result); device_path_ = device_path; + device_subscription_ = g_dbus_connection_signal_subscribe( + bus_, BLUEZ_BUS, "org.freedesktop.DBus.Properties", "PropertiesChanged", + device_path_.c_str(), "org.bluez.Device1", + G_DBUS_SIGNAL_FLAGS_NONE, device_properties_changed, this, nullptr); std::printf("BLE linux-central: connected to %s\n", device_path_.c_str()); uint64_t deadline = now_ms() + 10000; @@ -315,9 +319,11 @@ bool HostBluezBleInterface::connect_device(const std::string& device_path) { size_t h2 = hasher(node_label_ + ":exercise306"); std::memcpy(identity, &h1, std::min(sizeof(h1), sizeof(identity))); std::memcpy(identity + 8, &h2, std::min(sizeof(h2), sizeof(identity) - 8)); - write_characteristic(rx_char_path_, identity, sizeof(identity), true); - connected_ = true; + if (!write_characteristic(rx_char_path_, identity, sizeof(identity), true)) { + return false; + } + std::printf("BLE linux-central: notifications active; identity handshake sent\n"); return true; } @@ -408,7 +414,9 @@ bool HostBluezBleInterface::write_characteristic(const std::string& path, g_variant_new("(aya{sv})", &bytes, &options), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); if (!result) { + std::string message = error && error->message ? error->message : "unknown write failure"; log_error("BLE linux-central: WriteValue failed", error); + mark_disconnected(message.c_str()); return false; } g_variant_unref(result); @@ -474,15 +482,17 @@ void HostBluezBleInterface::send_outgoing(const RNS::Bytes& data) { put_u32_be(fragment + 6, msg_id); put_u32_be(fragment + 10, (uint32_t)data.size()); std::memcpy(fragment + FRAG_HEADER_SIZE, data.data() + offset, chunk); - send_fragment(fragment, FRAG_HEADER_SIZE + chunk); + if (!send_fragment(fragment, FRAG_HEADER_SIZE + chunk)) { + return; + } RNS::Utilities::OS::sleep(0.008); } InterfaceImpl::handle_outgoing(data); } -void HostBluezBleInterface::send_fragment(const uint8_t* data, size_t len) { - write_characteristic(rx_char_path_, data, len, true); +bool HostBluezBleInterface::send_fragment(const uint8_t* data, size_t len) { + return write_characteristic(rx_char_path_, data, len, true); } void HostBluezBleInterface::properties_changed(GDBusConnection* connection, @@ -520,6 +530,36 @@ void HostBluezBleInterface::properties_changed(GDBusConnection* connection, g_variant_unref(invalidated); } +void HostBluezBleInterface::device_properties_changed(GDBusConnection* connection, + const gchar* sender_name, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer user_data) { + (void)connection; + (void)sender_name; + (void)interface_name; + (void)signal_name; + auto* self = static_cast(user_data); + if (!self || self->device_path_ != object_path) { + return; + } + + const gchar* changed_iface = nullptr; + GVariant* changed = nullptr; + GVariant* invalidated = nullptr; + g_variant_get(parameters, "(&s@a{sv}@as)", &changed_iface, &changed, &invalidated); + if (std::strcmp(changed_iface, "org.bluez.Device1") == 0) { + gboolean connected = TRUE; + if (g_variant_lookup(changed, "Connected", "b", &connected) && !connected) { + self->mark_disconnected("device disconnected"); + } + } + g_variant_unref(changed); + g_variant_unref(invalidated); +} + void HostBluezBleInterface::handle_fragment(const uint8_t* data, size_t len) { if (len < FRAG_HEADER_SIZE) { std::fprintf(stderr, "BLE linux-central: fragment too short len=%zu\n", len); @@ -598,15 +638,40 @@ void HostBluezBleInterface::reset_reassembly() { current_rx_message_id_ = 0; } -void HostBluezBleInterface::disconnect_peer() { +void HostBluezBleInterface::mark_disconnected(const char* reason) { + bool was_connected = connected_ || !device_path_.empty() || !tx_char_path_.empty() || !rx_char_path_.empty(); connected_ = false; - if (notify_subscription_) { + if (bus_ && notify_subscription_) { g_dbus_connection_signal_unsubscribe(bus_, notify_subscription_); notify_subscription_ = 0; } - if (!device_path_.empty()) { + if (bus_ && device_subscription_) { + g_dbus_connection_signal_unsubscribe(bus_, device_subscription_); + device_subscription_ = 0; + } + device_path_.clear(); + tx_char_path_.clear(); + rx_char_path_.clear(); + identity_char_path_.clear(); + reset_reassembly(); + next_scan_ms_ = now_ms() + 1000; + if (online_) { + if (was_connected) { + std::printf("BLE linux-central: disconnected; %s; returning to scan\n", reason ? reason : "link lost"); + } + start_discovery(); + } else if (was_connected) { + std::printf("BLE linux-central: disconnected; %s\n", reason ? reason : "link lost"); + } +} + +void HostBluezBleInterface::disconnect_peer() { + std::string path = device_path_; + bool should_disconnect = connected_ && !path.empty(); + mark_disconnected("local stop"); + if (should_disconnect && bus_) { GError* error = nullptr; - GVariant* result = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, device_path_.c_str(), + GVariant* result = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, path.c_str(), "org.bluez.Device1", "Disconnect", nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); @@ -616,9 +681,4 @@ void HostBluezBleInterface::disconnect_peer() { g_variant_unref(result); } } - device_path_.clear(); - tx_char_path_.clear(); - rx_char_path_.clear(); - identity_char_path_.clear(); - reset_reassembly(); } diff --git a/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezBleInterface.h b/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezBleInterface.h index d013eb8..d5263a6 100644 --- a/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezBleInterface.h +++ b/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezBleInterface.h @@ -34,11 +34,12 @@ private: bool discover_characteristics(); bool start_notify(); bool write_characteristic(const std::string& path, const uint8_t* data, size_t len, bool with_response); - void send_fragment(const uint8_t* data, size_t len); + bool send_fragment(const uint8_t* data, size_t len); void handle_fragment(const uint8_t* data, size_t len); void enqueue_packet(const RNS::Bytes& packet); bool dequeue_packet(RNS::Bytes& packet); void reset_reassembly(); + void mark_disconnected(const char* reason); void disconnect_peer(); static void properties_changed(GDBusConnection* connection, @@ -49,6 +50,14 @@ private: GVariant* parameters, gpointer user_data); + static void device_properties_changed(GDBusConnection* connection, + const gchar* sender_name, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer user_data); + static constexpr const char* BLUEZ_BUS = "org.bluez"; static constexpr const char* SERVICE_UUID = "37145b00-442d-4a94-917f-8f42c5da28e3"; static constexpr const char* TX_UUID = "37145b00-442d-4a94-917f-8f42c5da28e4"; @@ -74,6 +83,7 @@ private: std::string rx_char_path_; std::string identity_char_path_; guint notify_subscription_ = 0; + guint device_subscription_ = 0; bool online_ = false; bool connected_ = false; bool discovering_ = false; diff --git a/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezPeripheralInterface.cpp b/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezPeripheralInterface.cpp new file mode 100644 index 0000000..c307e2e --- /dev/null +++ b/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezPeripheralInterface.cpp @@ -0,0 +1,170 @@ +#include "HostBluezPeripheralInterface.h" + +#include + +#include + +using namespace RNS; + +static void log_error(const char* what, GError* error) { + if (error) { + std::fprintf(stderr, "%s: %s\n", what, error->message); + g_error_free(error); + } else { + std::fprintf(stderr, "%s\n", what); + } +} + +HostBluezPeripheralInterface::HostBluezPeripheralInterface(const std::string& node_label, + const char* name) + : InterfaceImpl(name), node_label_(node_label) { + _IN = true; + _OUT = true; + _bitrate = 1000000; + _HW_MTU = 168; +} + +HostBluezPeripheralInterface::~HostBluezPeripheralInterface() { + stop(); + if (bus_) { + g_object_unref(bus_); + } +} + +bool HostBluezPeripheralInterface::start() { + if (online_) { + return true; + } + if (!connect_bus() || !find_adapter()) { + return false; + } + + online_ = true; + _online = true; + std::printf("BLE linux-peripheral: adapter=%s label=%s service=%s\n", + adapter_path_.c_str(), node_label_.c_str(), SERVICE_UUID); + + if (!adapter_supports_peripheral()) { + std::fprintf(stderr, + "BLE linux-peripheral: adapter/BlueZ is missing GattManager1 or LEAdvertisingManager1; peripheral mode cannot run on this adapter as configured\n"); + return true; + } + + std::printf("BLE linux-peripheral: BlueZ reports GATT server and LE advertising managers are present\n"); + std::printf("BLE linux-peripheral: GATT object registration scaffold builds; runtime service registration is the next step\n"); + return true; +} + +void HostBluezPeripheralInterface::stop() { + online_ = false; + connected_ = false; + _online = false; +} + +bool HostBluezPeripheralInterface::connect_bus() { + GError* error = nullptr; + bus_ = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &error); + if (!bus_) { + log_error("BLE linux-peripheral: cannot connect to system D-Bus", error); + return false; + } + return true; +} + +bool HostBluezPeripheralInterface::find_adapter() { + GError* error = nullptr; + GVariant* managed = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, "/", + "org.freedesktop.DBus.ObjectManager", + "GetManagedObjects", nullptr, + G_VARIANT_TYPE("(a{oa{sa{sv}}})"), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); + if (!managed) { + log_error("BLE linux-peripheral: GetManagedObjects failed", error); + return false; + } + + GVariantIter* objects = nullptr; + g_variant_get(managed, "(a{oa{sa{sv}}})", &objects); + const gchar* object_path = nullptr; + GVariant* ifaces = nullptr; + while (g_variant_iter_next(objects, "{&o@a{sa{sv}}}", &object_path, &ifaces)) { + GVariant* adapter = g_variant_lookup_value(ifaces, "org.bluez.Adapter1", G_VARIANT_TYPE("a{sv}")); + if (adapter) { + adapter_path_ = object_path; + g_variant_unref(adapter); + GVariant* gatt_manager = g_variant_lookup_value(ifaces, "org.bluez.GattManager1", G_VARIANT_TYPE("a{sv}")); + GVariant* advertising_manager = g_variant_lookup_value(ifaces, "org.bluez.LEAdvertisingManager1", G_VARIANT_TYPE("a{sv}")); + has_gatt_manager_ = gatt_manager != nullptr; + has_advertising_manager_ = advertising_manager != nullptr; + if (gatt_manager) { + g_variant_unref(gatt_manager); + } + if (advertising_manager) { + g_variant_unref(advertising_manager); + } + g_variant_unref(ifaces); + break; + } + g_variant_unref(ifaces); + } + g_variant_iter_free(objects); + g_variant_unref(managed); + + if (adapter_path_.empty()) { + std::fprintf(stderr, "BLE linux-peripheral: no BlueZ adapter found\n"); + return false; + } + + error = nullptr; + GVariant* powered = g_dbus_connection_call_sync( + bus_, BLUEZ_BUS, adapter_path_.c_str(), "org.freedesktop.DBus.Properties", "Set", + g_variant_new("(ssv)", "org.bluez.Adapter1", "Powered", g_variant_new_boolean(TRUE)), + nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); + if (!powered) { + log_error("BLE linux-peripheral: could not power adapter", error); + return false; + } + g_variant_unref(powered); + return true; +} + +bool HostBluezPeripheralInterface::adapter_supports_peripheral() const { + return has_gatt_manager_ && has_advertising_manager_; +} + +void HostBluezPeripheralInterface::loop() { + if (!online_) { + return; + } + while (g_main_context_iteration(nullptr, FALSE)) { + } + + RNS::Bytes packet({RNS::Type::NONE}); + while (dequeue_packet(packet)) { + InterfaceImpl::handle_incoming(packet); + } +} + +void HostBluezPeripheralInterface::send_outgoing(const RNS::Bytes& data) { + if (!online_ || !connected_) { + return; + } + + // Full TX notify support depends on BlueZ GATT characteristic object + // registration. This scaffold intentionally compiles separately from the + // proven central path so adapter capability can be tested first. + InterfaceImpl::handle_outgoing(data); +} + +void HostBluezPeripheralInterface::enqueue_packet(const RNS::Bytes& packet) { + incoming_packets_.push_back(packet); +} + +bool HostBluezPeripheralInterface::dequeue_packet(RNS::Bytes& packet) { + if (incoming_packets_.empty()) { + return false; + } + packet = incoming_packets_.front(); + incoming_packets_.pop_front(); + return true; +} diff --git a/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezPeripheralInterface.h b/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezPeripheralInterface.h new file mode 100644 index 0000000..3f0787a --- /dev/null +++ b/exercises/306_microReticulum_ble_file_transfer_oled/src/HostBluezPeripheralInterface.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include + +#include +#include + +class HostBluezPeripheralInterface : public RNS::InterfaceImpl { +public: + explicit HostBluezPeripheralInterface(const std::string& node_label, + const char* name = "HostBluezPeripheral"); + ~HostBluezPeripheralInterface() override; + + bool start() override; + void stop() override; + void loop() override; + + bool connected() const { return connected_; } + const char* role_name() const { return "linux-peripheral"; } + +private: + void send_outgoing(const RNS::Bytes& data) override; + + bool connect_bus(); + bool find_adapter(); + bool adapter_supports_peripheral() const; + void enqueue_packet(const RNS::Bytes& packet); + bool dequeue_packet(RNS::Bytes& packet); + + static constexpr const char* BLUEZ_BUS = "org.bluez"; + static constexpr const char* SERVICE_UUID = "37145b00-442d-4a94-917f-8f42c5da28e3"; + + std::string node_label_; + GDBusConnection* bus_ = nullptr; + std::string adapter_path_; + bool online_ = false; + bool connected_ = false; + bool has_gatt_manager_ = false; + bool has_advertising_manager_ = false; + std::deque incoming_packets_; +}; diff --git a/exercises/306_microReticulum_ble_file_transfer_oled/src/host_jp_main.cpp b/exercises/306_microReticulum_ble_file_transfer_oled/src/host_jp_main.cpp index 88b4d7c..f5850cd 100644 --- a/exercises/306_microReticulum_ble_file_transfer_oled/src/host_jp_main.cpp +++ b/exercises/306_microReticulum_ble_file_transfer_oled/src/host_jp_main.cpp @@ -1,4 +1,10 @@ +#if defined(HOST_BLE_PERIPHERAL) +#include "HostBluezPeripheralInterface.h" +using HostBleInterface = HostBluezPeripheralInterface; +#else #include "HostBluezBleInterface.h" +using HostBleInterface = HostBluezBleInterface; +#endif #include "SelectedText.h" #include @@ -58,7 +64,7 @@ static std::string peer_label; static bool have_peer = false; static bool link_active = false; static bool link_attempted = false; -static HostBluezBleInterface* ble_impl = nullptr; +static HostBleInterface* ble_impl = nullptr; static std::string node_label = HOST_NODE_LABEL; static bool running = true; @@ -415,8 +421,8 @@ static void setup_reticulum() { filesystem.init(); RNS::Utilities::OS::register_filesystem(filesystem); - auto impl = std::shared_ptr(new HostBluezBleInterface(node_label)); - ble_impl = static_cast(impl.get()); + auto impl = std::shared_ptr(new HostBleInterface(node_label)); + ble_impl = static_cast(impl.get()); ble_interface = RNS::Interface(impl); ble_interface.mode(RNS::Type::Interface::MODE_GATEWAY); RNS::Transport::register_interface(ble_interface); diff --git a/exercises/306_microReticulum_ble_file_transfer_oled/texts/children.txt b/exercises/306_microReticulum_ble_file_transfer_oled/texts/children.txt new file mode 100644 index 0000000..ba70ee5 --- /dev/null +++ b/exercises/306_microReticulum_ble_file_transfer_oled/texts/children.txt @@ -0,0 +1,49 @@ +Between the dark and the daylight, + When the night is beginning to lower, +Comes a pause in the day's occupations, + That is known as the Children's Hour. + +I hear in the chamber above me + The patter of little feet, +The sound of a door that is opened, + And voices soft and sweet. + +From my study I see in the lamplight, + Descending the broad hall stair, +Grave Alice, and laughing Allegra, + And Edith with golden hair. + +A whisper, and then a silence: + Yet I know by their merry eyes +They are plotting and planning together + To take me by surprise. + +A sudden rush from the stairway, + A sudden raid from the hall! +By three doors left unguarded + They enter my castle wall! + +They climb up into my turret + O'er the arms and back of my chair; +If I try to escape, they surround me; + They seem to be everywhere. + +They almost devour me with kisses, + Their arms about me entwine, +Till I think of the Bishop of Bingen + In his Mouse-Tower on the Rhine! + +Do you think, O blue-eyed banditti, + Because you have scaled the wall, +Such an old mustache as I am + Is not a match for you all! + +I have you fast in my fortress, + And will not let you depart, +But put you down into the dungeon + In the round-tower of my heart. + +And there will I keep you forever, + Yes, forever and a day, +Till the walls shall crumble to ruin, + And moulder in dust away! \ No newline at end of file diff --git a/exercises/306_microReticulum_ble_file_transfer_oled/texts/little_boy_blue.txt b/exercises/306_microReticulum_ble_file_transfer_oled/texts/little_boy_blue.txt new file mode 100644 index 0000000..741f4f8 --- /dev/null +++ b/exercises/306_microReticulum_ble_file_transfer_oled/texts/little_boy_blue.txt @@ -0,0 +1,26 @@ +The little toy dog is covered with dust, + But sturdy and staunch he stands; + And the little toy soldier is red with rust, + And his musket molds in his hands. +Time was when the little toy dog was new, + And the soldier was passing fair; +And that was the time when our Little Boy Blue + Kissed them and put them there. + +"Now, don't you go till I come," he said, + "And don't you make any noise!" +So, toddling off to his trundle-bed, + He dreamed of the pretty toys; +And, as he was dreaming, an angel song + Awakened our Little Boy Blue +Oh! the years are many, the years are long, + But the little toy friends are true! + +Ay, faithful to Little Boy Blue they stand, + Each in the same old place +Awaiting the touch of a little hand, + The smile of a little face; +And they wonder, as waiting the long years through + In the dust of that little chair, +What has become of our Little Boy Blue, + Since he kissed them and put them there. \ No newline at end of file