From a9bcdf439009817c9d9fbaba0ad95784205c8cc2 Mon Sep 17 00:00:00 2001 From: Ralph Weng Date: Mon, 8 Dec 2025 23:03:48 +1100 Subject: [PATCH] Add I2C device management API with HT16K33 LED matrix support New files: - raspi_i2c.h: Header with i2c_device type and functions - raspi_i2c.cpp: Implementation following ADC pattern - unit_test_raspi_i2c.cpp: Unit tests for I2C API Core I2C functions: - open_i2c_device(): Open I2C device by name, bus, address, type - i2c_write_byte(), i2c_write_register(): Write to device - i2c_read_byte(), i2c_read_register(): Read from device - close_i2c_device(), close_all_i2c_devices(): Cleanup - has_i2c_device(), i2c_device_named(): Registry lookups HT16K33 LED Matrix helpers: - ht16k33_init(): Initialize display - ht16k33_set_brightness(): Set brightness 0-15 - ht16k33_clear(): Clear display - ht16k33_draw_pattern(): Draw 8x8 pattern - ht16k33_turn_off(): Turn off display Also: - Added I2C_DEVICE_PTR to backend_types.h - Added i2c_device_type enum to types.h - Unit tests verify registry functions and null pointer handling --- coresdk/src/backend/backend_types.h | 3 +- coresdk/src/coresdk/raspi_i2c.cpp | 369 ++++++++++++++++++ coresdk/src/coresdk/raspi_i2c.h | 199 ++++++++++ coresdk/src/coresdk/types.h | 16 + .../test/unit_tests/unit_test_raspi_i2c.cpp | 193 +++++++++ 5 files changed, 779 insertions(+), 1 deletion(-) create mode 100644 coresdk/src/coresdk/raspi_i2c.cpp create mode 100644 coresdk/src/coresdk/raspi_i2c.h create mode 100644 coresdk/src/test/unit_tests/unit_test_raspi_i2c.cpp diff --git a/coresdk/src/backend/backend_types.h b/coresdk/src/backend/backend_types.h index 03083a86..c3c6cc64 100644 --- a/coresdk/src/backend/backend_types.h +++ b/coresdk/src/backend/backend_types.h @@ -63,7 +63,8 @@ namespace splashkit_lib JSON_PTR = 0x4a534f4e, //'JSON'; ADC_PTR= 0x41444350, //'ADCP'; MOTOR_DRIVER_PTR = 0x4d444950, //'MDIP'; - SERVO_DRIVER_PTR = 0x53455256, //'SERV'; + SERVO_DRIVER_PTR = 0x53455256, //'SERV'; + I2C_DEVICE_PTR = 0x49324344, //'I2CD'; NONE_PTR = 0x4e4f4e45 //'NONE'; }; diff --git a/coresdk/src/coresdk/raspi_i2c.cpp b/coresdk/src/coresdk/raspi_i2c.cpp new file mode 100644 index 00000000..decc5694 --- /dev/null +++ b/coresdk/src/coresdk/raspi_i2c.cpp @@ -0,0 +1,369 @@ +// raspi_i2c.cpp +// splashkit +// This file is part of the SplashKit Core Library. +// SplashKit Team + +#include "raspi_i2c.h" +#include "gpio_driver.h" // Provides sk_i2c_open, sk_i2c_close, sk_i2c_write_byte, etc. +#include "backend_types.h" +#include "easylogging++.h" +#include +#include + +using std::map; +using std::string; + +namespace splashkit_lib +{ + // Internal structure for the I2C device. + struct _i2c_device_data + { + pointer_identifier id; // Should be I2C_DEVICE_PTR + int i2c_handle; // I2C handle (obtained from sk_i2c_open) + int bus; // I2C bus number + int address; // I2C address for the device + i2c_device_type type; // Device type + string name; // Device name + }; + + // Static map to manage loaded I2C devices (keyed by name) + static map _i2c_devices; + + // Default addresses for known device types + static int _get_default_address(i2c_device_type type) + { + switch (type) + { + case I2C_DEVICE_HT16K33_8X8: + case I2C_DEVICE_HT16K33_14SEG: + return 0x70; // Default HT16K33 address + case I2C_DEVICE_PCF8574: + return 0x20; // Default PCF8574 address + case I2C_DEVICE_GENERIC: + default: + return -1; // No default address for generic devices + } + } + + bool has_i2c_device(const string &name) + { + return _i2c_devices.count(name) > 0; + } + + i2c_device i2c_device_named(const string &name) + { + if (has_i2c_device(name)) + return _i2c_devices[name]; + else + { + LOG(WARNING) << "I2C device not loaded with name: " << name; + return nullptr; + } + } + + static i2c_device _load_i2c_device(const string &name, int bus, int address, i2c_device_type type) + { +#ifdef RASPBERRY_PI + if (has_i2c_device(name)) + return i2c_device_named(name); + + i2c_device result = new _i2c_device_data(); + result->id = I2C_DEVICE_PTR; + result->bus = bus; + result->address = address; + result->name = name; + result->type = type; + + // Open the I2C channel to the device + result->i2c_handle = sk_i2c_open(bus, address); + if (result->i2c_handle < 0) + { + LOG(WARNING) << "Error opening I2C device " << name + << " on bus " << bus << " at address 0x" << std::hex << address; + delete result; + return nullptr; + } + + _i2c_devices[name] = result; + LOG(INFO) << "Opened I2C device " << name << " on bus " << bus + << " at address 0x" << std::hex << address; + return result; +#else + LOG(ERROR) << "I2C not supported on this platform"; + return nullptr; +#endif + } + + i2c_device open_i2c_device(const string &name, int bus, int address, i2c_device_type type) + { +#ifdef RASPBERRY_PI + if (has_i2c_device(name)) + { + LOG(WARNING) << "I2C device " << name << " already loaded."; + return i2c_device_named(name); + } + return _load_i2c_device(name, bus, address, type); +#else + LOG(ERROR) << "I2C not supported on this platform"; + return nullptr; +#endif + } + + i2c_device open_i2c_device(const string &name, i2c_device_type type) + { +#ifdef RASPBERRY_PI + int default_address = _get_default_address(type); + if (default_address < 0) + { + LOG(ERROR) << "No default address for device type, please specify address explicitly"; + return nullptr; + } + const int default_bus = 1; + return _load_i2c_device(name, default_bus, default_address, type); +#else + LOG(ERROR) << "I2C not supported on this platform"; + return nullptr; +#endif + } + + void i2c_write_byte(i2c_device dev, int data) + { +#ifdef RASPBERRY_PI + if (dev == nullptr || dev->id != I2C_DEVICE_PTR) + { + LOG(WARNING) << "Invalid I2C device passed to i2c_write_byte"; + return; + } + sk_i2c_write_byte(dev->i2c_handle, data); +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + void i2c_write_register(i2c_device dev, int reg, int data, int bytes) + { +#ifdef RASPBERRY_PI + if (dev == nullptr || dev->id != I2C_DEVICE_PTR) + { + LOG(WARNING) << "Invalid I2C device passed to i2c_write_register"; + return; + } + switch (bytes) + { + case 1: + sk_i2c_write_byte_data(dev->i2c_handle, reg, data); + break; + case 2: + sk_i2c_write_word_data(dev->i2c_handle, reg, data); + break; + default: + LOG(ERROR) << "i2c_write_register: bytes must be 1 or 2"; + break; + } +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + int i2c_read_byte(i2c_device dev) + { +#ifdef RASPBERRY_PI + if (dev == nullptr || dev->id != I2C_DEVICE_PTR) + { + LOG(WARNING) << "Invalid I2C device passed to i2c_read_byte"; + return -1; + } + return sk_i2c_read_byte(dev->i2c_handle); +#else + LOG(ERROR) << "I2C not supported on this platform"; + return -1; +#endif + } + + int i2c_read_register(i2c_device dev, int reg) + { +#ifdef RASPBERRY_PI + if (dev == nullptr || dev->id != I2C_DEVICE_PTR) + { + LOG(WARNING) << "Invalid I2C device passed to i2c_read_register"; + return -1; + } + return sk_i2c_read_byte_data(dev->i2c_handle, reg); +#else + LOG(ERROR) << "I2C not supported on this platform"; + return -1; +#endif + } + + static void _close_i2c_device(i2c_device dev) + { +#ifdef RASPBERRY_PI + if (dev && dev->id == I2C_DEVICE_PTR) + { + sk_i2c_close(dev->i2c_handle); + _i2c_devices.erase(dev->name); + dev->id = NONE_PTR; + delete dev; + } + else + { + LOG(WARNING) << "Attempting to close an invalid I2C device"; + } +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + void close_i2c_device(i2c_device dev) + { +#ifdef RASPBERRY_PI + if (dev != nullptr) + _close_i2c_device(dev); + else + LOG(WARNING) << "Attempted to close null I2C device"; +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + void close_i2c_device(const string &name) + { +#ifdef RASPBERRY_PI + i2c_device dev = i2c_device_named(name); + close_i2c_device(dev); +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + void close_all_i2c_devices() + { +#ifdef RASPBERRY_PI + // Create a copy of device names to avoid iterator invalidation + vector names; + for (auto &entry : _i2c_devices) + { + names.push_back(entry.first); + } + for (auto &name : names) + { + close_i2c_device(name); + } +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + // ==================== HT16K33 LED Matrix Functions ==================== + + // Helper to reverse bits (needed for LED matrix row data) + static unsigned char _reverse_bits(unsigned char b) + { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; + } + + void ht16k33_init(i2c_device dev) + { +#ifdef RASPBERRY_PI + if (dev == nullptr || dev->id != I2C_DEVICE_PTR) + { + LOG(WARNING) << "Invalid I2C device passed to ht16k33_init"; + return; + } + + // Turn on system oscillator + i2c_write_byte(dev, 0b00100001); + + // Set display on, no blink + i2c_write_byte(dev, 0b10000001); + + // Clear display + ht16k33_clear(dev); + + LOG(INFO) << "HT16K33 device " << dev->name << " initialized"; +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + void ht16k33_set_brightness(i2c_device dev, int brightness) + { +#ifdef RASPBERRY_PI + if (dev == nullptr || dev->id != I2C_DEVICE_PTR) + { + LOG(WARNING) << "Invalid I2C device passed to ht16k33_set_brightness"; + return; + } + + // Clamp brightness to 0-15 + if (brightness < 0) brightness = 0; + if (brightness > 15) brightness = 15; + + // Brightness command: 0xE0 | brightness value + i2c_write_byte(dev, 0xE0 | brightness); +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + void ht16k33_clear(i2c_device dev) + { +#ifdef RASPBERRY_PI + if (dev == nullptr || dev->id != I2C_DEVICE_PTR) + { + LOG(WARNING) << "Invalid I2C device passed to ht16k33_clear"; + return; + } + + // Clear all 16 registers (8 rows * 2 bytes each for common cathode) + for (int i = 0; i < 16; i++) + { + i2c_write_register(dev, i, 0, 1); + } +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + void ht16k33_draw_pattern(i2c_device dev, const unsigned char pattern[8]) + { +#ifdef RASPBERRY_PI + if (dev == nullptr || dev->id != I2C_DEVICE_PTR) + { + LOG(WARNING) << "Invalid I2C device passed to ht16k33_draw_pattern"; + return; + } + + for (int y = 0; y < 8; y++) + { + // Reverse and rotate bits to match LED matrix physical layout + unsigned char row_data = _reverse_bits(pattern[y]); + row_data = (row_data >> 1) | (row_data << 7); + + // Write to row register (each row is at address y*2) + i2c_write_register(dev, y * 2, row_data, 1); + } +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } + + void ht16k33_turn_off(i2c_device dev) + { +#ifdef RASPBERRY_PI + if (dev == nullptr || dev->id != I2C_DEVICE_PTR) + { + LOG(WARNING) << "Invalid I2C device passed to ht16k33_turn_off"; + return; + } + + // Turn off system oscillator + i2c_write_byte(dev, 0b00100000); +#else + LOG(ERROR) << "I2C not supported on this platform"; +#endif + } +} diff --git a/coresdk/src/coresdk/raspi_i2c.h b/coresdk/src/coresdk/raspi_i2c.h new file mode 100644 index 00000000..e052b016 --- /dev/null +++ b/coresdk/src/coresdk/raspi_i2c.h @@ -0,0 +1,199 @@ +/** + * @header raspi_i2c + * @brief Provides support for communicating with I2C devices on the Raspberry Pi. + * @author SplashKit Team + * + * @attribute group raspberry + * @attribute static raspberry + */ + +#ifndef raspi_i2c_hpp +#define raspi_i2c_hpp + +#include "types.h" +#include + +namespace splashkit_lib +{ + /** + * The `i2c_device` type is used to refer to I2C devices that can be + * managed by the SplashKit I2C code, such as LED matrices (HT16K33) + * and other I2C peripherals. + * + * I2C devices are: + * - opened with `open_i2c_device`, + * - accessed using `i2c_device_named` or checked with `has_i2c_device`, + * - read/written using `i2c_read_byte`, `i2c_write_byte`, etc., + * - and must be closed using `close_i2c_device` (to release a specific + * device) or `close_all_i2c_devices` (to release all loaded devices). + * + * @attribute class i2c_device + */ + typedef struct _i2c_device_data *i2c_device; + + /** + * Checks if an I2C device with the given name has been loaded. + * + * @param name The name used to identify the I2C device. + * @returns true if an I2C device with the supplied name exists. + */ + bool has_i2c_device(const string &name); + + /** + * Retrieve an I2C device that has been loaded. + * + * @param name The name of the I2C device. + * @returns The i2c_device pointer if found; otherwise, nullptr. + */ + i2c_device i2c_device_named(const string &name); + + /** + * Opens an I2C device on the specified bus at a given address. + * + * @param name The name to assign this I2C device. + * @param bus The I2C bus number (usually 1 on Raspberry Pi). + * @param address The I2C address of the device (e.g., 0x70 for HT16K33). + * @param type The type of I2C device. + * @returns A valid i2c_device on success, or nullptr on failure. + * + * @attribute class i2c_device + * @attribute constructor true + * @attribute suffix with_bus + */ + i2c_device open_i2c_device(const string &name, int bus, int address, i2c_device_type type); + + /** + * Opens an I2C device with the specified name and type using default bus and address. + * Default bus is 1, default address depends on device type: + * - HT16K33: 0x70 + * - PCF8574: 0x20 + * - Generic: 0x00 (must be specified manually) + * + * @param name The name of the I2C device to open. + * @param type The type of I2C device. + * @returns A valid i2c_device on success, or nullptr on failure. + * + * @attribute class i2c_device + * @attribute constructor true + */ + i2c_device open_i2c_device(const string &name, i2c_device_type type); + + /** + * Writes a single byte to the I2C device. + * + * @param dev The I2C device to write to. + * @param data The byte value to write. + * + * @attribute class i2c_device + * @attribute self dev + * @attribute method write_byte + */ + void i2c_write_byte(i2c_device dev, int data); + + /** + * Writes data to a specific register on the I2C device. + * + * @param dev The I2C device to write to. + * @param reg The register address to write to. + * @param data The data to write. + * @param bytes The number of bytes to write (1 or 2). + * + * @attribute class i2c_device + * @attribute self dev + * @attribute method write_register + */ + void i2c_write_register(i2c_device dev, int reg, int data, int bytes); + + /** + * Reads a single byte from the I2C device. + * + * @param dev The I2C device to read from. + * @returns The byte value read, or -1 on error. + * + * @attribute class i2c_device + * @attribute self dev + * @attribute method read_byte + */ + int i2c_read_byte(i2c_device dev); + + /** + * Reads data from a specific register on the I2C device. + * + * @param dev The I2C device to read from. + * @param reg The register address to read from. + * @returns The byte value read, or -1 on error. + * + * @attribute class i2c_device + * @attribute self dev + * @attribute method read_register + */ + int i2c_read_register(i2c_device dev, int reg); + + /** + * Closes an I2C device given its pointer. + * + * @param dev The I2C device to close. + * + * @attribute class i2c_device + * @attribute self dev + * @attribute destructor true + * @attribute method close + */ + void close_i2c_device(i2c_device dev); + + /** + * Closes an I2C device given its name. + * + * @param name The name of the I2C device to close. + * + * @attribute suffix named + */ + void close_i2c_device(const string &name); + + /** + * Closes all I2C devices that have been opened. + */ + void close_all_i2c_devices(); + + // ==================== HT16K33 LED Matrix Functions ==================== + + /** + * Initializes an HT16K33-based LED matrix device. + * This turns on the system oscillator and enables the display. + * + * @param dev The HT16K33 device to initialize. + */ + void ht16k33_init(i2c_device dev); + + /** + * Sets the brightness of an HT16K33-based LED device. + * + * @param dev The HT16K33 device. + * @param brightness The brightness level (0-15, where 15 is maximum). + */ + void ht16k33_set_brightness(i2c_device dev, int brightness); + + /** + * Clears the display on an HT16K33-based LED device. + * + * @param dev The HT16K33 device to clear. + */ + void ht16k33_clear(i2c_device dev); + + /** + * Draws an 8-byte pattern on an 8x8 HT16K33 LED matrix. + * Each byte represents one row of the display. + * + * @param dev The HT16K33 device. + * @param pattern An 8-element array where each byte is a row pattern. + */ + void ht16k33_draw_pattern(i2c_device dev, const unsigned char pattern[8]); + + /** + * Turns off an HT16K33-based LED device by disabling the system oscillator. + * + * @param dev The HT16K33 device to turn off. + */ + void ht16k33_turn_off(i2c_device dev); +} +#endif /* raspi_i2c_hpp */ diff --git a/coresdk/src/coresdk/types.h b/coresdk/src/coresdk/types.h index 3573af9f..bda5a647 100644 --- a/coresdk/src/coresdk/types.h +++ b/coresdk/src/coresdk/types.h @@ -528,6 +528,22 @@ namespace splashkit_lib // A4988 = 6, }; + /** + * I2C Device Types: + * + * @constant I2C_DEVICE_GENERIC Generic I2C device with no specific functionality. + * @constant I2C_DEVICE_HT16K33_8X8 HT16K33-based 8x8 LED Matrix. + * @constant I2C_DEVICE_HT16K33_14SEG HT16K33-based 14-segment display. + * @constant I2C_DEVICE_PCF8574 PCF8574 I/O Expander. + */ + enum i2c_device_type + { + I2C_DEVICE_GENERIC = 0, + I2C_DEVICE_HT16K33_8X8 = 1, + I2C_DEVICE_HT16K33_14SEG = 2, + I2C_DEVICE_PCF8574 = 3, + }; + /** * Use these interface styles as a way to quickly * customize your interface. diff --git a/coresdk/src/test/unit_tests/unit_test_raspi_i2c.cpp b/coresdk/src/test/unit_tests/unit_test_raspi_i2c.cpp new file mode 100644 index 00000000..9b27c940 --- /dev/null +++ b/coresdk/src/test/unit_tests/unit_test_raspi_i2c.cpp @@ -0,0 +1,193 @@ +/** + * Raspi I2C Unit Tests + * + * Tests for the I2C device management API. + * Note: Hardware-dependent tests (actual I2C communication) require a Raspberry Pi. + * These tests focus on the device registry and error handling that works on all platforms. + */ + +#include "catch.hpp" + +#include "types.h" +#include "raspi_i2c.h" +#include "logging_handling.h" + +using namespace splashkit_lib; + +TEST_CASE("I2C device registry functions work correctly", "[i2c][raspi]") +{ + // Clean up any devices from previous tests + close_all_i2c_devices(); + + SECTION("has_i2c_device returns false for non-existent device") + { + REQUIRE_FALSE(has_i2c_device("non_existent_device")); + } + + SECTION("i2c_device_named returns nullptr for non-existent device") + { + disable_logging(WARNING); // Disable "I2C device not loaded" warning + i2c_device dev = i2c_device_named("non_existent_device"); + enable_logging(WARNING); + + REQUIRE(dev == nullptr); + } + + SECTION("close_i2c_device handles null pointer gracefully") + { + disable_logging(WARNING); // Disable warning about null device + close_i2c_device(nullptr); + enable_logging(WARNING); + + // Should not crash + REQUIRE(true); + } + + SECTION("close_i2c_device handles non-existent name gracefully") + { + disable_logging(WARNING); // Disable warning about non-existent device + close_i2c_device("non_existent_device"); + enable_logging(WARNING); + + // Should not crash + REQUIRE(true); + } + + SECTION("close_all_i2c_devices handles empty registry gracefully") + { + close_all_i2c_devices(); + + // Should not crash and registry should still be empty + REQUIRE_FALSE(has_i2c_device("any_device")); + } +} + +TEST_CASE("I2C device type enum has expected values", "[i2c][types]") +{ + SECTION("I2C device types have correct values") + { + REQUIRE(I2C_DEVICE_GENERIC == 0); + REQUIRE(I2C_DEVICE_HT16K33_8X8 == 1); + REQUIRE(I2C_DEVICE_HT16K33_14SEG == 2); + REQUIRE(I2C_DEVICE_PCF8574 == 3); + } +} + +TEST_CASE("I2C read/write functions handle null device gracefully", "[i2c][raspi]") +{ + SECTION("i2c_write_byte handles null device") + { + disable_logging(WARNING); + i2c_write_byte(nullptr, 0x00); + enable_logging(WARNING); + + // Should not crash + REQUIRE(true); + } + + SECTION("i2c_write_register handles null device") + { + disable_logging(WARNING); + i2c_write_register(nullptr, 0x00, 0x00, 1); + enable_logging(WARNING); + + // Should not crash + REQUIRE(true); + } + + SECTION("i2c_read_byte handles null device") + { + disable_logging(WARNING); + int result = i2c_read_byte(nullptr); + enable_logging(WARNING); + + REQUIRE(result == -1); + } + + SECTION("i2c_read_register handles null device") + { + disable_logging(WARNING); + int result = i2c_read_register(nullptr, 0x00); + enable_logging(WARNING); + + REQUIRE(result == -1); + } +} + +TEST_CASE("HT16K33 functions handle null device gracefully", "[i2c][ht16k33][raspi]") +{ + SECTION("ht16k33_init handles null device") + { + disable_logging(WARNING); + ht16k33_init(nullptr); + enable_logging(WARNING); + + // Should not crash + REQUIRE(true); + } + + SECTION("ht16k33_set_brightness handles null device") + { + disable_logging(WARNING); + ht16k33_set_brightness(nullptr, 8); + enable_logging(WARNING); + + // Should not crash + REQUIRE(true); + } + + SECTION("ht16k33_clear handles null device") + { + disable_logging(WARNING); + ht16k33_clear(nullptr); + enable_logging(WARNING); + + // Should not crash + REQUIRE(true); + } + + SECTION("ht16k33_draw_pattern handles null device") + { + unsigned char pattern[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + disable_logging(WARNING); + ht16k33_draw_pattern(nullptr, pattern); + enable_logging(WARNING); + + // Should not crash + REQUIRE(true); + } + + SECTION("ht16k33_turn_off handles null device") + { + disable_logging(WARNING); + ht16k33_turn_off(nullptr); + enable_logging(WARNING); + + // Should not crash + REQUIRE(true); + } +} + +#ifndef RASPBERRY_PI +TEST_CASE("I2C functions return appropriate errors on non-Pi platforms", "[i2c][raspi]") +{ + SECTION("open_i2c_device returns nullptr on non-Pi") + { + disable_logging(ERROR); + i2c_device dev = open_i2c_device("test_device", 1, 0x70, I2C_DEVICE_HT16K33_8X8); + enable_logging(ERROR); + + REQUIRE(dev == nullptr); + } + + SECTION("open_i2c_device with defaults returns nullptr on non-Pi") + { + disable_logging(ERROR); + i2c_device dev = open_i2c_device("test_device", I2C_DEVICE_HT16K33_8X8); + enable_logging(ERROR); + + REQUIRE(dev == nullptr); + } +} +#endif