From 2f17c352c3c71882585a91929a43a0eac785c89a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 19:33:31 +0000 Subject: [PATCH 01/16] Implement I2C driver with ZMQ transport for host emulation This commit adds a complete I2C driver implementation following the same patterns used for Pin and UART drivers. The implementation includes: C++ Changes: - Added I2C message types (I2CEmulatorRequest/Response) to support Send and Receive operations with I2C device addresses - Updated HostI2CController to use ZMQ transport instead of local buffers - Added JSON encoding/decoding support for I2C messages - Integrated I2C controller into HostBoard with proper transport wiring - I2C operations are synchronous and always initiated by the application Python Emulator Changes: - Added I2C class to emulate I2C peripherals at different addresses - Implemented device buffer storage per I2C address (address -> bytearray) - Added Send/Receive operation handlers in the emulator message loop - Provided helper methods for testing (write_to_device, read_from_device) Key Design Decisions: - I2C uses int error codes (as defined in i2c.hpp interface) instead of common::Error, with a helper function to convert between them - All I2C operations are blocking and synchronous - Data buffers are maintained per I2C address (up to 256 bytes each) - Interrupt and DMA variants delegate to blocking implementations for now The implementation enables I2C communication testing through the host emulation environment without requiring actual hardware. --- py/host-emulator/src/emulator.py | 111 ++++++++++++++++++ src/libs/board/host/host_board.cpp | 2 +- src/libs/board/host/host_board.hpp | 3 +- .../host/emulator_message_json_encoder.hpp | 7 ++ src/libs/mcu/host/host_emulator_messages.hpp | 32 ++++- src/libs/mcu/host/host_i2c.cpp | 66 ++++++++++- src/libs/mcu/host/host_i2c.hpp | 7 +- 7 files changed, 220 insertions(+), 8 deletions(-) diff --git a/py/host-emulator/src/emulator.py b/py/host-emulator/src/emulator.py index afeb9d7..bd4479b 100755 --- a/py/host-emulator/src/emulator.py +++ b/py/host-emulator/src/emulator.py @@ -211,6 +211,102 @@ def handle_message(self, message): return self.handle_response(message) +class I2C: + def __init__(self, name): + self.name = name + # Store data for each I2C address (address -> bytearray) + self.device_buffers = {} + self.on_response = None + self.on_request = None + + def handle_request(self, message): + response = { + "type": "Response", + "object": "I2C", + "name": self.name, + "address": message.get("address", 0), + "data": [], + "bytes_transferred": 0, + "status": Status.InvalidOperation.name, + } + + address = message.get("address", 0) + + if message["operation"] == "Send": + # Device is sending data to I2C peripheral + # Store the data in the buffer for this address + data = message.get("data", []) + if address not in self.device_buffers: + self.device_buffers[address] = bytearray() + self.device_buffers[address] = bytearray(data) + response.update( + { + "bytes_transferred": len(data), + "status": Status.Ok.name, + } + ) + print(f"[I2C {self.name}] Wrote {len(data)} bytes to address 0x{address:02X}: {bytes(data)}") + + elif message["operation"] == "Receive": + # Device is receiving data from I2C peripheral + # Return data from the buffer for this address + size = message.get("size", 0) + if address in self.device_buffers: + bytes_to_send = min(size, len(self.device_buffers[address])) + data = list(self.device_buffers[address][:bytes_to_send]) + else: + # No data available, return empty + bytes_to_send = 0 + data = [] + response.update( + { + "data": data, + "bytes_transferred": bytes_to_send, + "status": Status.Ok.name, + } + ) + print(f"[I2C {self.name}] Read {bytes_to_send} bytes from address 0x{address:02X}: {bytes(data)}") + + if self.on_request: + self.on_request(message) + return json.dumps(response) + + def handle_response(self, message): + print(f"[I2C {self.name}] Received response: {message}") + if self.on_response: + self.on_response(message) + return None + + def set_on_request(self, on_request): + self.on_request = on_request + + def set_on_response(self, on_response): + self.on_response = on_response + + def handle_message(self, message): + if message["object"] != "I2C": + return None + if message["name"] != self.name: + return None + if message["type"] == "Request": + return self.handle_request(message) + if message["type"] == "Response": + return self.handle_response(message) + + def write_to_device(self, address, data): + """Write data to a simulated I2C device (for testing)""" + if address not in self.device_buffers: + self.device_buffers[address] = bytearray() + self.device_buffers[address] = bytearray(data) + print(f"[I2C {self.name}] Device buffer at 0x{address:02X} set to: {bytes(data)}") + + def read_from_device(self, address): + """Read data from a simulated I2C device (for testing)""" + if address in self.device_buffers: + return bytes(self.device_buffers[address]) + return b"" + + class DeviceEmulator: def __init__(self): print("Creating DeviceEmulator") @@ -233,6 +329,9 @@ def __init__(self): self.uart_1 = Uart("UART 1", self.to_device_socket) self.uarts = [self.uart_1] + self.i2c_1 = I2C("I2C 1") + self.i2cs = [self.i2c_1] + def user_led1(self): return self.led_1 @@ -245,6 +344,9 @@ def user_button1(self): def uart1(self): return self.uart_1 + def i2c1(self): + return self.i2c_1 + def run(self): print("Starting emulator thread") try: @@ -277,6 +379,15 @@ def run(self): break else: raise UnhandledMessageError(message, " - Uart not found") + elif json_message["object"] == "I2C": + for i2c in self.i2cs: + if response := i2c.handle_message(json_message): + print(f"[Emulator] Sending response: {response}") + from_device_socket.send_string(response) + print("") + break + else: + raise UnhandledMessageError(message, " - I2C not found") else: raise UnhandledMessageError( message, f" - unknown object type: {json_message['object']}" diff --git a/src/libs/board/host/host_board.cpp b/src/libs/board/host/host_board.cpp index 983df68..40d4ded 100644 --- a/src/libs/board/host/host_board.cpp +++ b/src/libs/board/host/host_board.cpp @@ -20,6 +20,6 @@ auto HostBoard::Init() -> std::expected { auto HostBoard::UserLed1() -> mcu::OutputPin& { return user_led_1_; } auto HostBoard::UserLed2() -> mcu::OutputPin& { return user_led_2_; } auto HostBoard::UserButton1() -> mcu::InputPin& { return user_button_1_; } -auto HostBoard::I2C1() -> mcu::I2CController& { return i2c1_; } +auto HostBoard::I2C1() -> mcu::I2CController& { return i2c_1_; } auto HostBoard::Uart1() -> mcu::Uart& { return uart_1_; } } // namespace board diff --git a/src/libs/board/host/host_board.hpp b/src/libs/board/host/host_board.hpp index fbe4dd4..2867380 100644 --- a/src/libs/board/host/host_board.hpp +++ b/src/libs/board/host/host_board.hpp @@ -41,6 +41,7 @@ class HostBoard : public Board { {IsJson, user_led_2_}, {IsJson, user_button_1_}, {IsJson, uart_1_}, + {IsJson, i2c_1_}, }; mcu::Dispatcher dispatcher_{receiver_map_}; mcu::ZmqTransport zmq_transport_{"ipc:///tmp/device_emulator.ipc", @@ -50,6 +51,6 @@ class HostBoard : public Board { mcu::HostPin user_led_2_{"LED 2", zmq_transport_}; mcu::HostPin user_button_1_{"Button 1", zmq_transport_}; mcu::HostUart uart_1_{"UART 1", zmq_transport_}; - mcu::HostI2CController i2c1_{}; + mcu::HostI2CController i2c_1_{"I2C 1", zmq_transport_}; }; } // namespace board diff --git a/src/libs/mcu/host/emulator_message_json_encoder.hpp b/src/libs/mcu/host/emulator_message_json_encoder.hpp index ab58e0a..b8f4278 100644 --- a/src/libs/mcu/host/emulator_message_json_encoder.hpp +++ b/src/libs/mcu/host/emulator_message_json_encoder.hpp @@ -53,6 +53,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(OperationType, NLOHMANN_JSON_SERIALIZE_ENUM(ObjectType, { {ObjectType::kPin, "Pin"}, {ObjectType::kUart, "Uart"}, + {ObjectType::kI2C, "I2C"}, }) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PinEmulatorRequest, type, object, name, @@ -67,6 +68,12 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(UartEmulatorRequest, type, object, name, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(UartEmulatorResponse, type, object, name, data, bytes_transferred, status) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(I2CEmulatorRequest, type, object, name, + operation, address, data, size) + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(I2CEmulatorResponse, type, object, name, + address, data, bytes_transferred, status) + template inline auto Encode(const T& obj) -> std::string { return nlohmann::json(obj).dump(); diff --git a/src/libs/mcu/host/host_emulator_messages.hpp b/src/libs/mcu/host/host_emulator_messages.hpp index f62abd7..4966023 100644 --- a/src/libs/mcu/host/host_emulator_messages.hpp +++ b/src/libs/mcu/host/host_emulator_messages.hpp @@ -12,7 +12,7 @@ namespace mcu { enum class MessageType { kRequest = 1, kResponse }; enum class OperationType { kSet = 1, kGet, kSend, kReceive }; -enum class ObjectType { kPin = 1, kUart }; +enum class ObjectType { kPin = 1, kUart, kI2C }; struct PinEmulatorRequest { MessageType type{MessageType::kRequest}; @@ -67,4 +67,34 @@ struct UartEmulatorResponse { } }; +struct I2CEmulatorRequest { + MessageType type{MessageType::kRequest}; + ObjectType object{ObjectType::kI2C}; + std::string name; + OperationType operation; + uint16_t address{0}; + std::vector data; // For Send operation + size_t size{0}; // For Receive operation (buffer size) + auto operator==(const I2CEmulatorRequest& other) const -> bool { + return type == other.type && object == other.object && name == other.name && + operation == other.operation && address == other.address && + data == other.data && size == other.size; + } +}; + +struct I2CEmulatorResponse { + MessageType type{MessageType::kResponse}; + ObjectType object{ObjectType::kI2C}; + std::string name; + uint16_t address{0}; + std::vector data; // Received data + size_t bytes_transferred{0}; + common::Error status; + auto operator==(const I2CEmulatorResponse& other) const -> bool { + return type == other.type && object == other.object && name == other.name && + address == other.address && data == other.data && + bytes_transferred == other.bytes_transferred && status == other.status; + } +}; + } // namespace mcu diff --git a/src/libs/mcu/host/host_i2c.cpp b/src/libs/mcu/host/host_i2c.cpp index 49c04c7..4569a9d 100644 --- a/src/libs/mcu/host/host_i2c.cpp +++ b/src/libs/mcu/host/host_i2c.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "libs/common/error.hpp" #include "libs/mcu/host/emulator_message_json_encoder.hpp" @@ -12,17 +13,74 @@ #include "libs/mcu/i2c.hpp" namespace mcu { + +// Helper to convert common::Error to int error code +static auto ErrorToInt(common::Error error) -> int { + return error == common::Error::kOk ? 0 : -1; +} + auto HostI2CController::SendData(uint16_t address, std::span data) -> std::expected { - std::ranges::copy(data, data_buffers_[address].begin()); - return {}; + const I2CEmulatorRequest request{ + .type = MessageType::kRequest, + .object = ObjectType::kI2C, + .name = name_, + .operation = OperationType::kSend, + .address = address, + .data = std::vector(data.begin(), data.end()), + .size = 0, + }; + + return transport_.Send(Encode(request)) + .and_then([this]() { return transport_.Receive(); }) + .and_then([](const std::string& response_str) + -> std::expected { + const auto response = Decode(response_str); + if (response.status != common::Error::kOk) { + return std::unexpected(ErrorToInt(response.status)); + } + return {}; + }) + .or_else([](common::Error error) -> std::expected { + return std::unexpected(ErrorToInt(error)); + }); } auto HostI2CController::ReceiveData(uint16_t address, size_t size) -> std::expected, int> { - return std::span{data_buffers_[address].data(), - std::min(size, data_buffers_[address].size())}; + const I2CEmulatorRequest request{ + .type = MessageType::kRequest, + .object = ObjectType::kI2C, + .name = name_, + .operation = OperationType::kReceive, + .address = address, + .data = {}, + .size = size, + }; + + return transport_.Send(Encode(request)) + .and_then([this]() { return transport_.Receive(); }) + .transform([](const std::string& response_str) { + return Decode(response_str); + }) + .and_then([this, address](const I2CEmulatorResponse& response) + -> std::expected, int> { + if (response.status != common::Error::kOk) { + return std::unexpected(ErrorToInt(response.status)); + } + + // Store received data in buffer for this address + auto& buffer = data_buffers_[address]; + const size_t bytes_to_copy{ + std::min(response.data.size(), buffer.size())}; + std::copy_n(response.data.begin(), bytes_to_copy, buffer.begin()); + + return std::span{buffer.data(), bytes_to_copy}; + }) + .or_else([](common::Error error) -> std::expected, int> { + return std::unexpected(ErrorToInt(error)); + }); } auto HostI2CController::SendDataInterrupt( diff --git a/src/libs/mcu/host/host_i2c.hpp b/src/libs/mcu/host/host_i2c.hpp index 720da6f..9fd6252 100644 --- a/src/libs/mcu/host/host_i2c.hpp +++ b/src/libs/mcu/host/host_i2c.hpp @@ -4,16 +4,19 @@ #include #include #include +#include #include #include "libs/mcu/host/receiver.hpp" +#include "libs/mcu/host/transport.hpp" #include "libs/mcu/i2c.hpp" namespace mcu { class HostI2CController final : public I2CController, public Receiver { public: - HostI2CController() = default; + explicit HostI2CController(std::string name, Transport& transport) + : name_{std::move(name)}, transport_{transport} {} HostI2CController(const HostI2CController&) = delete; HostI2CController(HostI2CController&&) = delete; auto operator=(const HostI2CController&) -> HostI2CController& = delete; @@ -44,6 +47,8 @@ class HostI2CController final : public I2CController, public Receiver { -> std::expected override; private: + const std::string name_; + Transport& transport_; std::unordered_map> data_buffers_; }; } // namespace mcu From a1a69b1ebebaf42cfcc1a426579cc91d67c2f15a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 19:37:03 +0000 Subject: [PATCH 02/16] Apply clang-format to I2C driver implementation --- src/libs/board/host/host_board.hpp | 7 ++---- src/libs/mcu/host/host_emulator_messages.hpp | 3 ++- src/libs/mcu/host/host_i2c.cpp | 23 ++++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/libs/board/host/host_board.hpp b/src/libs/board/host/host_board.hpp index 2867380..390864c 100644 --- a/src/libs/board/host/host_board.hpp +++ b/src/libs/board/host/host_board.hpp @@ -37,11 +37,8 @@ class HostBoard : public Board { } const mcu::ReceiverMap receiver_map_{ - {IsJson, user_led_1_}, - {IsJson, user_led_2_}, - {IsJson, user_button_1_}, - {IsJson, uart_1_}, - {IsJson, i2c_1_}, + {IsJson, user_led_1_}, {IsJson, user_led_2_}, {IsJson, user_button_1_}, + {IsJson, uart_1_}, {IsJson, i2c_1_}, }; mcu::Dispatcher dispatcher_{receiver_map_}; mcu::ZmqTransport zmq_transport_{"ipc:///tmp/device_emulator.ipc", diff --git a/src/libs/mcu/host/host_emulator_messages.hpp b/src/libs/mcu/host/host_emulator_messages.hpp index 4966023..0288196 100644 --- a/src/libs/mcu/host/host_emulator_messages.hpp +++ b/src/libs/mcu/host/host_emulator_messages.hpp @@ -93,7 +93,8 @@ struct I2CEmulatorResponse { auto operator==(const I2CEmulatorResponse& other) const -> bool { return type == other.type && object == other.object && name == other.name && address == other.address && data == other.data && - bytes_transferred == other.bytes_transferred && status == other.status; + bytes_transferred == other.bytes_transferred && + status == other.status; } }; diff --git a/src/libs/mcu/host/host_i2c.cpp b/src/libs/mcu/host/host_i2c.cpp index 4569a9d..1960e99 100644 --- a/src/libs/mcu/host/host_i2c.cpp +++ b/src/libs/mcu/host/host_i2c.cpp @@ -34,14 +34,14 @@ auto HostI2CController::SendData(uint16_t address, return transport_.Send(Encode(request)) .and_then([this]() { return transport_.Receive(); }) - .and_then([](const std::string& response_str) - -> std::expected { - const auto response = Decode(response_str); - if (response.status != common::Error::kOk) { - return std::unexpected(ErrorToInt(response.status)); - } - return {}; - }) + .and_then( + [](const std::string& response_str) -> std::expected { + const auto response = Decode(response_str); + if (response.status != common::Error::kOk) { + return std::unexpected(ErrorToInt(response.status)); + } + return {}; + }) .or_else([](common::Error error) -> std::expected { return std::unexpected(ErrorToInt(error)); }); @@ -78,9 +78,10 @@ auto HostI2CController::ReceiveData(uint16_t address, size_t size) return std::span{buffer.data(), bytes_to_copy}; }) - .or_else([](common::Error error) -> std::expected, int> { - return std::unexpected(ErrorToInt(error)); - }); + .or_else( + [](common::Error error) -> std::expected, int> { + return std::unexpected(ErrorToInt(error)); + }); } auto HostI2CController::SendDataInterrupt( From 80125638e5cea20689d130cb4702317952784391 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 19:40:41 +0000 Subject: [PATCH 03/16] Fix ruff line length errors in Python emulator I2C code --- py/host-emulator/src/emulator.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/py/host-emulator/src/emulator.py b/py/host-emulator/src/emulator.py index bd4479b..037d9aa 100755 --- a/py/host-emulator/src/emulator.py +++ b/py/host-emulator/src/emulator.py @@ -245,7 +245,10 @@ def handle_request(self, message): "status": Status.Ok.name, } ) - print(f"[I2C {self.name}] Wrote {len(data)} bytes to address 0x{address:02X}: {bytes(data)}") + print( + f"[I2C {self.name}] Wrote {len(data)} bytes to address " + f"0x{address:02X}: {bytes(data)}" + ) elif message["operation"] == "Receive": # Device is receiving data from I2C peripheral @@ -265,7 +268,10 @@ def handle_request(self, message): "status": Status.Ok.name, } ) - print(f"[I2C {self.name}] Read {bytes_to_send} bytes from address 0x{address:02X}: {bytes(data)}") + print( + f"[I2C {self.name}] Read {bytes_to_send} bytes from address " + f"0x{address:02X}: {bytes(data)}" + ) if self.on_request: self.on_request(message) @@ -298,7 +304,10 @@ def write_to_device(self, address, data): if address not in self.device_buffers: self.device_buffers[address] = bytearray() self.device_buffers[address] = bytearray(data) - print(f"[I2C {self.name}] Device buffer at 0x{address:02X} set to: {bytes(data)}") + print( + f"[I2C {self.name}] Device buffer at 0x{address:02X} set to: " + f"{bytes(data)}" + ) def read_from_device(self, address): """Read data from a simulated I2C device (for testing)""" From cdd780c2b09ec62c00d8a1ee387d2663f6063370 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 19:46:26 +0000 Subject: [PATCH 04/16] Apply ruff format to Python emulator --- py/host-emulator/src/emulator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/py/host-emulator/src/emulator.py b/py/host-emulator/src/emulator.py index 037d9aa..02941a6 100755 --- a/py/host-emulator/src/emulator.py +++ b/py/host-emulator/src/emulator.py @@ -305,8 +305,7 @@ def write_to_device(self, address, data): self.device_buffers[address] = bytearray() self.device_buffers[address] = bytearray(data) print( - f"[I2C {self.name}] Device buffer at 0x{address:02X} set to: " - f"{bytes(data)}" + f"[I2C {self.name}] Device buffer at 0x{address:02X} set to: {bytes(data)}" ) def read_from_device(self, address): From 8fded1ea428e346d7c84e9c28970075a94cc9b3b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 19:54:14 +0000 Subject: [PATCH 05/16] Fix I2C compilation errors: use anonymous namespace and simplify error handling --- src/libs/mcu/host/host_i2c.cpp | 77 ++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/src/libs/mcu/host/host_i2c.cpp b/src/libs/mcu/host/host_i2c.cpp index 1960e99..5b4b5f5 100644 --- a/src/libs/mcu/host/host_i2c.cpp +++ b/src/libs/mcu/host/host_i2c.cpp @@ -14,10 +14,12 @@ namespace mcu { +namespace { // Helper to convert common::Error to int error code -static auto ErrorToInt(common::Error error) -> int { +auto ErrorToInt(common::Error error) -> int { return error == common::Error::kOk ? 0 : -1; } +} // namespace auto HostI2CController::SendData(uint16_t address, std::span data) @@ -32,19 +34,22 @@ auto HostI2CController::SendData(uint16_t address, .size = 0, }; - return transport_.Send(Encode(request)) - .and_then([this]() { return transport_.Receive(); }) - .and_then( - [](const std::string& response_str) -> std::expected { - const auto response = Decode(response_str); - if (response.status != common::Error::kOk) { - return std::unexpected(ErrorToInt(response.status)); - } - return {}; - }) - .or_else([](common::Error error) -> std::expected { - return std::unexpected(ErrorToInt(error)); - }); + auto send_result = transport_.Send(Encode(request)); + if (!send_result) { + return std::unexpected(ErrorToInt(send_result.error())); + } + + auto receive_result = transport_.Receive(); + if (!receive_result) { + return std::unexpected(ErrorToInt(receive_result.error())); + } + + const auto response = Decode(receive_result.value()); + if (response.status != common::Error::kOk) { + return std::unexpected(ErrorToInt(response.status)); + } + + return {}; } auto HostI2CController::ReceiveData(uint16_t address, size_t size) @@ -59,29 +64,27 @@ auto HostI2CController::ReceiveData(uint16_t address, size_t size) .size = size, }; - return transport_.Send(Encode(request)) - .and_then([this]() { return transport_.Receive(); }) - .transform([](const std::string& response_str) { - return Decode(response_str); - }) - .and_then([this, address](const I2CEmulatorResponse& response) - -> std::expected, int> { - if (response.status != common::Error::kOk) { - return std::unexpected(ErrorToInt(response.status)); - } - - // Store received data in buffer for this address - auto& buffer = data_buffers_[address]; - const size_t bytes_to_copy{ - std::min(response.data.size(), buffer.size())}; - std::copy_n(response.data.begin(), bytes_to_copy, buffer.begin()); - - return std::span{buffer.data(), bytes_to_copy}; - }) - .or_else( - [](common::Error error) -> std::expected, int> { - return std::unexpected(ErrorToInt(error)); - }); + auto send_result = transport_.Send(Encode(request)); + if (!send_result) { + return std::unexpected(ErrorToInt(send_result.error())); + } + + auto receive_result = transport_.Receive(); + if (!receive_result) { + return std::unexpected(ErrorToInt(receive_result.error())); + } + + const auto response = Decode(receive_result.value()); + if (response.status != common::Error::kOk) { + return std::unexpected(ErrorToInt(response.status)); + } + + // Store received data in buffer for this address + auto& buffer = data_buffers_[address]; + const size_t bytes_to_copy{std::min(response.data.size(), buffer.size())}; + std::copy_n(response.data.begin(), bytes_to_copy, buffer.begin()); + + return std::span{buffer.data(), bytes_to_copy}; } auto HostI2CController::SendDataInterrupt( From 31ff33566a4bcb733628a4a92b33e454255f9bba Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 19:56:00 +0000 Subject: [PATCH 06/16] Change I2C interface to use common::Error instead of int --- src/libs/mcu/host/host_i2c.cpp | 37 +++++++++++++++------------------- src/libs/mcu/host/host_i2c.hpp | 23 +++++++++++---------- src/libs/mcu/i2c.hpp | 26 +++++++++++++----------- 3 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/libs/mcu/host/host_i2c.cpp b/src/libs/mcu/host/host_i2c.cpp index 5b4b5f5..e17ac49 100644 --- a/src/libs/mcu/host/host_i2c.cpp +++ b/src/libs/mcu/host/host_i2c.cpp @@ -14,16 +14,9 @@ namespace mcu { -namespace { -// Helper to convert common::Error to int error code -auto ErrorToInt(common::Error error) -> int { - return error == common::Error::kOk ? 0 : -1; -} -} // namespace - auto HostI2CController::SendData(uint16_t address, std::span data) - -> std::expected { + -> std::expected { const I2CEmulatorRequest request{ .type = MessageType::kRequest, .object = ObjectType::kI2C, @@ -36,24 +29,24 @@ auto HostI2CController::SendData(uint16_t address, auto send_result = transport_.Send(Encode(request)); if (!send_result) { - return std::unexpected(ErrorToInt(send_result.error())); + return std::unexpected(send_result.error()); } auto receive_result = transport_.Receive(); if (!receive_result) { - return std::unexpected(ErrorToInt(receive_result.error())); + return std::unexpected(receive_result.error()); } const auto response = Decode(receive_result.value()); if (response.status != common::Error::kOk) { - return std::unexpected(ErrorToInt(response.status)); + return std::unexpected(response.status); } return {}; } auto HostI2CController::ReceiveData(uint16_t address, size_t size) - -> std::expected, int> { + -> std::expected, common::Error> { const I2CEmulatorRequest request{ .type = MessageType::kRequest, .object = ObjectType::kI2C, @@ -66,17 +59,17 @@ auto HostI2CController::ReceiveData(uint16_t address, size_t size) auto send_result = transport_.Send(Encode(request)); if (!send_result) { - return std::unexpected(ErrorToInt(send_result.error())); + return std::unexpected(send_result.error()); } auto receive_result = transport_.Receive(); if (!receive_result) { - return std::unexpected(ErrorToInt(receive_result.error())); + return std::unexpected(receive_result.error()); } const auto response = Decode(receive_result.value()); if (response.status != common::Error::kOk) { - return std::unexpected(ErrorToInt(response.status)); + return std::unexpected(response.status); } // Store received data in buffer for this address @@ -89,30 +82,32 @@ auto HostI2CController::ReceiveData(uint16_t address, size_t size) auto HostI2CController::SendDataInterrupt( uint16_t address, std::span data, - void (*callback)(std::expected)) -> std::expected { + void (*callback)(std::expected)) + -> std::expected { callback(SendData(address, data)); return {}; } auto HostI2CController::ReceiveDataInterrupt( uint16_t address, size_t size, - void (*callback)(std::expected, int>)) - -> std::expected { + void (*callback)(std::expected, common::Error>)) + -> std::expected { callback(ReceiveData(address, size)); return {}; } auto HostI2CController::SendDataDma( uint16_t address, std::span data, - void (*callback)(std::expected)) -> std::expected { + void (*callback)(std::expected)) + -> std::expected { callback(SendData(address, data)); return {}; } auto HostI2CController::ReceiveDataDma( uint16_t address, size_t size, - void (*callback)(std::expected, int>)) - -> std::expected { + void (*callback)(std::expected, common::Error>)) + -> std::expected { callback(ReceiveData(address, size)); return {}; } diff --git a/src/libs/mcu/host/host_i2c.hpp b/src/libs/mcu/host/host_i2c.hpp index 9fd6252..0640caf 100644 --- a/src/libs/mcu/host/host_i2c.hpp +++ b/src/libs/mcu/host/host_i2c.hpp @@ -24,25 +24,26 @@ class HostI2CController final : public I2CController, public Receiver { ~HostI2CController() override = default; auto SendData(uint16_t address, std::span data) - -> std::expected override; + -> std::expected override; auto ReceiveData(uint16_t address, size_t size) - -> std::expected, int> override; + -> std::expected, common::Error> override; auto SendDataInterrupt(uint16_t address, std::span data, - void (*callback)(std::expected)) - -> std::expected override; + void (*callback)(std::expected)) + -> std::expected override; auto ReceiveDataInterrupt( uint16_t address, size_t size, - void (*callback)(std::expected, int>)) - -> std::expected override; + void (*callback)(std::expected, common::Error>)) + -> std::expected override; auto SendDataDma(uint16_t address, std::span data, - void (*callback)(std::expected)) - -> std::expected override; - auto ReceiveDataDma(uint16_t address, size_t size, - void (*callback)(std::expected, int>)) - -> std::expected override; + void (*callback)(std::expected)) + -> std::expected override; + auto ReceiveDataDma( + uint16_t address, size_t size, + void (*callback)(std::expected, common::Error>)) + -> std::expected override; auto Receive(const std::string_view& message) -> std::expected override; diff --git a/src/libs/mcu/i2c.hpp b/src/libs/mcu/i2c.hpp index 31d156a..7b7700d 100644 --- a/src/libs/mcu/i2c.hpp +++ b/src/libs/mcu/i2c.hpp @@ -4,6 +4,8 @@ #include #include +#include "libs/common/error.hpp" + namespace mcu { class I2CController { @@ -11,27 +13,27 @@ class I2CController { virtual ~I2CController() = default; virtual auto SendData(uint16_t address, std::span data) - -> std::expected = 0; + -> std::expected = 0; virtual auto ReceiveData(uint16_t address, size_t size) - -> std::expected, int> = 0; + -> std::expected, common::Error> = 0; - virtual auto SendDataInterrupt(uint16_t address, - std::span data, - void (*callback)(std::expected)) - -> std::expected = 0; + virtual auto SendDataInterrupt( + uint16_t address, std::span data, + void (*callback)(std::expected)) + -> std::expected = 0; virtual auto ReceiveDataInterrupt( uint16_t address, size_t size, - void (*callback)(std::expected, int>)) - -> std::expected = 0; + void (*callback)(std::expected, common::Error>)) + -> std::expected = 0; virtual auto SendDataDma(uint16_t address, std::span data, - void (*callback)(std::expected)) - -> std::expected = 0; + void (*callback)(std::expected)) + -> std::expected = 0; virtual auto ReceiveDataDma( uint16_t address, size_t size, - void (*callback)(std::expected, int>)) - -> std::expected = 0; + void (*callback)(std::expected, common::Error>)) + -> std::expected = 0; }; } // namespace mcu From b621cb6925c351ffeab34f3029c53c0db6ce8f7d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 20:03:56 +0000 Subject: [PATCH 07/16] Add unit tests for I2C driver implementation Created comprehensive unit tests for HostI2CController following the same pattern as the existing UART tests. The tests cover: - Basic Send/Receive operations - Multiple I2C device addresses - Partial data reception - Receive from uninitialized devices - Interrupt-based operations (SendDataInterrupt, ReceiveDataInterrupt) - DMA-based operations (SendDataDma, ReceiveDataDma) The test fixture sets up a ZMQ-based emulator that simulates I2C device buffers, allowing thorough testing of the I2C driver without hardware. Test coverage includes: - HostI2CTest.SendData - HostI2CTest.SendReceiveData - HostI2CTest.MultipleAddresses - HostI2CTest.ReceiveWithoutSend - HostI2CTest.ReceivePartialData - HostI2CTest.SendDataInterrupt - HostI2CTest.ReceiveDataInterrupt - HostI2CTest.SendDataDma - HostI2CTest.ReceiveDataDma --- src/libs/mcu/host/CMakeLists.txt | 14 ++ src/libs/mcu/host/test_host_i2c.cpp | 337 ++++++++++++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 src/libs/mcu/host/test_host_i2c.cpp diff --git a/src/libs/mcu/host/CMakeLists.txt b/src/libs/mcu/host/CMakeLists.txt index cf7dfa1..32f631b 100644 --- a/src/libs/mcu/host/CMakeLists.txt +++ b/src/libs/mcu/host/CMakeLists.txt @@ -52,12 +52,25 @@ target_link_libraries(test_host_uart cppzmq ) +add_executable(test_host_i2c test_host_i2c.cpp) +target_compile_options(test_host_i2c PRIVATE ${COMMON_COMPILE_OPTIONS}) + +target_link_libraries(test_host_i2c + PRIVATE + GTest::GTest + host_mcu + host_transport + nlohmann_json::nlohmann_json + cppzmq + ) + include(GoogleTest) gtest_discover_tests(test_host_transport) gtest_discover_tests(test_messages) gtest_discover_tests(test_dispatcher) gtest_discover_tests(test_host_uart) +gtest_discover_tests(test_host_i2c) # Code coverage configuration if(CODE_COVERAGE) @@ -71,4 +84,5 @@ if(CODE_COVERAGE) target_code_coverage(test_messages AUTO ALL) target_code_coverage(test_dispatcher AUTO ALL) target_code_coverage(test_host_uart AUTO ALL) + target_code_coverage(test_host_i2c AUTO ALL) endif() diff --git a/src/libs/mcu/host/test_host_i2c.cpp b/src/libs/mcu/host/test_host_i2c.cpp new file mode 100644 index 0000000..555ad67 --- /dev/null +++ b/src/libs/mcu/host/test_host_i2c.cpp @@ -0,0 +1,337 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libs/mcu/host/dispatcher.hpp" +#include "libs/mcu/host/emulator_message_json_encoder.hpp" +#include "libs/mcu/host/host_emulator_messages.hpp" +#include "libs/mcu/host/host_i2c.hpp" +#include "libs/mcu/host/zmq_transport.hpp" +#include "libs/mcu/i2c.hpp" + +class HostI2CTest : public ::testing::Test { + protected: + static constexpr auto IsJson(const std::string_view& message) -> bool { + return message.starts_with("{") && message.ends_with("}"); + } + + void SetUp() override { + // Start emulator thread + emulator_running_ = true; + emulator_thread_ = std::thread{[this]() { EmulatorLoop(); }}; + + // Give emulator time to start + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Create dispatcher with empty receiver map (will update via reference + // later) + dispatcher_ = std::make_unique(receiver_map_storage_); + + // Create transport + device_transport_ = std::make_unique( + "ipc:///tmp/test_i2c_device_emulator.ipc", + "ipc:///tmp/test_i2c_emulator_device.ipc", *dispatcher_); + + // Now create I2C with transport + i2c_ = std::make_unique("I2C 1", *device_transport_); + + // Add I2C to receiver map (dispatcher holds reference, so this updates it) + receiver_map_storage_.emplace_back(IsJson, std::ref(*i2c_)); + + // Give transport time to connect + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + void TearDown() override { + i2c_.reset(); + device_transport_.reset(); + dispatcher_.reset(); + + emulator_running_ = false; + if (emulator_thread_.joinable()) { + emulator_context_.shutdown(); + emulator_context_.close(); + emulator_thread_.join(); + } + } + + void EmulatorLoop() { + // Simulate I2C device buffers (address -> data) + std::map> i2c_device_buffers; + + try { + zmq::socket_t socket{emulator_context_, zmq::socket_type::pair}; + socket.bind("ipc:///tmp/test_i2c_device_emulator.ipc"); + + while (emulator_running_) { + std::array items = { + {{.socket = static_cast(socket), + .fd = 0, + .events = ZMQ_POLLIN, + .revents = 0}}}; + + const int ret{ + zmq::poll(items.data(), 1, std::chrono::milliseconds{50})}; + + if (ret == 0) { + continue; // Timeout + } + if (ret <= 0) { + continue; + } + + zmq::message_t message{}; + if (!socket.recv(message, zmq::recv_flags::none)) { + continue; + } + + const std::string_view message_str{ + static_cast(message.data()), message.size()}; + + const auto request = + mcu::Decode(std::string{message_str}); + mcu::I2CEmulatorResponse response{ + .type = mcu::MessageType::kResponse, + .object = mcu::ObjectType::kI2C, + .name = request.name, + .address = request.address, + .data = {}, + .bytes_transferred = 0, + .status = common::Error::kOk, + }; + + if (request.operation == mcu::OperationType::kSend) { + // Device sent data to I2C peripheral - store in device buffer + i2c_device_buffers[request.address] = request.data; + response.bytes_transferred = request.data.size(); + } else if (request.operation == mcu::OperationType::kReceive) { + // Device wants to receive data from I2C peripheral + if (i2c_device_buffers.contains(request.address)) { + const auto& buffer = i2c_device_buffers[request.address]; + const size_t bytes_to_send{std::min(request.size, buffer.size())}; + response.data = std::vector( + buffer.begin(), + buffer.begin() + static_cast(bytes_to_send)); + response.bytes_transferred = bytes_to_send; + } + } + + const auto response_str = mcu::Encode(response); + socket.send(zmq::buffer(response_str), zmq::send_flags::none); + } + } catch (const zmq::error_t& e) { + // Socket closed during shutdown, expected behavior + if (e.num() != ETERM) { + throw; + } + } + } + + std::vector< + std::pair, mcu::Receiver&>> + receiver_map_storage_; + std::unique_ptr dispatcher_; + std::unique_ptr device_transport_; + std::unique_ptr i2c_; + zmq::context_t emulator_context_{1}; + std::thread emulator_thread_; + std::atomic emulator_running_{false}; +}; + +TEST_F(HostI2CTest, SendData) { + const uint16_t device_address{0x42}; + const std::array send_data{0xDE, 0xAD, 0xBE, 0xEF}; + + auto result = i2c_->SendData(device_address, send_data); + EXPECT_TRUE(result); +} + +TEST_F(HostI2CTest, SendReceiveData) { + const uint16_t device_address{0x50}; + const std::array send_data{0x01, 0x02, 0x03, 0x04, 0x05}; + + // Send data to device + auto send_result = i2c_->SendData(device_address, send_data); + ASSERT_TRUE(send_result); + + // Receive data back from same device + auto recv_result = i2c_->ReceiveData(device_address, send_data.size()); + ASSERT_TRUE(recv_result); + + const auto received_span = recv_result.value(); + EXPECT_EQ(received_span.size(), send_data.size()); + + // Compare received data with sent data + EXPECT_TRUE( + std::equal(received_span.begin(), received_span.end(), send_data.begin(), + send_data.end())); +} + +TEST_F(HostI2CTest, MultipleAddresses) { + const uint16_t address1{0x50}; + const uint16_t address2{0x51}; + const std::array data1{0xAA, 0xBB, 0xCC}; + const std::array data2{0x11, 0x22, 0x33, 0x44}; + + // Send to first address + auto send1_result = i2c_->SendData(address1, data1); + ASSERT_TRUE(send1_result); + + // Send to second address + auto send2_result = i2c_->SendData(address2, data2); + ASSERT_TRUE(send2_result); + + // Receive from first address + auto recv1_result = i2c_->ReceiveData(address1, data1.size()); + ASSERT_TRUE(recv1_result); + const auto received1_span = recv1_result.value(); + EXPECT_EQ(received1_span.size(), data1.size()); + EXPECT_TRUE(std::equal(received1_span.begin(), received1_span.end(), + data1.begin(), data1.end())); + + // Receive from second address + auto recv2_result = i2c_->ReceiveData(address2, data2.size()); + ASSERT_TRUE(recv2_result); + const auto received2_span = recv2_result.value(); + EXPECT_EQ(received2_span.size(), data2.size()); + EXPECT_TRUE(std::equal(received2_span.begin(), received2_span.end(), + data2.begin(), data2.end())); +} + +TEST_F(HostI2CTest, ReceiveWithoutSend) { + const uint16_t device_address{0x60}; + + // Try to receive from device that has no data + auto result = i2c_->ReceiveData(device_address, 10); + ASSERT_TRUE(result); + + // Should return empty span + const auto received_span = result.value(); + EXPECT_EQ(received_span.size(), 0); +} + +TEST_F(HostI2CTest, ReceivePartialData) { + const uint16_t device_address{0x70}; + const std::array send_data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + // Send 10 bytes + auto send_result = i2c_->SendData(device_address, send_data); + ASSERT_TRUE(send_result); + + // Request only 5 bytes + auto recv_result = i2c_->ReceiveData(device_address, 5); + ASSERT_TRUE(recv_result); + + const auto received_span = recv_result.value(); + EXPECT_EQ(received_span.size(), 5); + + // Should receive first 5 bytes + EXPECT_TRUE(std::equal(received_span.begin(), received_span.end(), + send_data.begin(), send_data.begin() + 5)); +} + +TEST_F(HostI2CTest, SendDataInterrupt) { + const uint16_t device_address{0x42}; + const std::array send_data{0xAA, 0xBB, 0xCC}; + + bool callback_called{false}; + std::expected callback_result{}; + + auto result = i2c_->SendDataInterrupt( + device_address, send_data, + [&callback_called, + &callback_result](std::expected result) { + callback_called = true; + callback_result = result; + }); + + EXPECT_TRUE(result); + EXPECT_TRUE(callback_called); + EXPECT_TRUE(callback_result); +} + +TEST_F(HostI2CTest, ReceiveDataInterrupt) { + const uint16_t device_address{0x50}; + const std::array send_data{0x01, 0x02, 0x03, 0x04}; + + // First send data + auto send_result = i2c_->SendData(device_address, send_data); + ASSERT_TRUE(send_result); + + bool callback_called{false}; + std::expected, common::Error> callback_result{ + std::unexpected(common::Error::kUnknown)}; + + auto result = i2c_->ReceiveDataInterrupt( + device_address, send_data.size(), + [&callback_called, &callback_result]( + std::expected, common::Error> result) { + callback_called = true; + callback_result = result; + }); + + EXPECT_TRUE(result); + EXPECT_TRUE(callback_called); + ASSERT_TRUE(callback_result); + + const auto received_span = callback_result.value(); + EXPECT_EQ(received_span.size(), send_data.size()); + EXPECT_TRUE(std::equal(received_span.begin(), received_span.end(), + send_data.begin(), send_data.end())); +} + +TEST_F(HostI2CTest, SendDataDma) { + const uint16_t device_address{0x42}; + const std::array send_data{0xDE, 0xAD, 0xBE}; + + bool callback_called{false}; + std::expected callback_result{}; + + auto result = i2c_->SendDataDma( + device_address, send_data, + [&callback_called, + &callback_result](std::expected result) { + callback_called = true; + callback_result = result; + }); + + EXPECT_TRUE(result); + EXPECT_TRUE(callback_called); + EXPECT_TRUE(callback_result); +} + +TEST_F(HostI2CTest, ReceiveDataDma) { + const uint16_t device_address{0x55}; + const std::array send_data{0x10, 0x20, 0x30, 0x40, 0x50}; + + // First send data + auto send_result = i2c_->SendData(device_address, send_data); + ASSERT_TRUE(send_result); + + bool callback_called{false}; + std::expected, common::Error> callback_result{ + std::unexpected(common::Error::kUnknown)}; + + auto result = i2c_->ReceiveDataDma( + device_address, send_data.size(), + [&callback_called, &callback_result]( + std::expected, common::Error> result) { + callback_called = true; + callback_result = result; + }); + + EXPECT_TRUE(result); + EXPECT_TRUE(callback_called); + ASSERT_TRUE(callback_result); + + const auto received_span = callback_result.value(); + EXPECT_EQ(received_span.size(), send_data.size()); + EXPECT_TRUE(std::equal(received_span.begin(), received_span.end(), + send_data.begin(), send_data.end())); +} From c102765259a07274772977071617aefa946b8a06 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 20:14:44 +0000 Subject: [PATCH 08/16] Apply clang-format to I2C unit tests --- src/libs/mcu/host/test_host_i2c.cpp | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/libs/mcu/host/test_host_i2c.cpp b/src/libs/mcu/host/test_host_i2c.cpp index 555ad67..ecbe777 100644 --- a/src/libs/mcu/host/test_host_i2c.cpp +++ b/src/libs/mcu/host/test_host_i2c.cpp @@ -39,7 +39,8 @@ class HostI2CTest : public ::testing::Test { "ipc:///tmp/test_i2c_emulator_device.ipc", *dispatcher_); // Now create I2C with transport - i2c_ = std::make_unique("I2C 1", *device_transport_); + i2c_ = + std::make_unique("I2C 1", *device_transport_); // Add I2C to receiver map (dispatcher holds reference, so this updates it) receiver_map_storage_.emplace_back(IsJson, std::ref(*i2c_)); @@ -168,9 +169,8 @@ TEST_F(HostI2CTest, SendReceiveData) { EXPECT_EQ(received_span.size(), send_data.size()); // Compare received data with sent data - EXPECT_TRUE( - std::equal(received_span.begin(), received_span.end(), send_data.begin(), - send_data.end())); + EXPECT_TRUE(std::equal(received_span.begin(), received_span.end(), + send_data.begin(), send_data.end())); } TEST_F(HostI2CTest, MultipleAddresses) { @@ -243,13 +243,13 @@ TEST_F(HostI2CTest, SendDataInterrupt) { bool callback_called{false}; std::expected callback_result{}; - auto result = i2c_->SendDataInterrupt( - device_address, send_data, - [&callback_called, - &callback_result](std::expected result) { - callback_called = true; - callback_result = result; - }); + auto result = + i2c_->SendDataInterrupt(device_address, send_data, + [&callback_called, &callback_result]( + std::expected result) { + callback_called = true; + callback_result = result; + }); EXPECT_TRUE(result); EXPECT_TRUE(callback_called); @@ -293,13 +293,13 @@ TEST_F(HostI2CTest, SendDataDma) { bool callback_called{false}; std::expected callback_result{}; - auto result = i2c_->SendDataDma( - device_address, send_data, - [&callback_called, - &callback_result](std::expected result) { - callback_called = true; - callback_result = result; - }); + auto result = + i2c_->SendDataDma(device_address, send_data, + [&callback_called, &callback_result]( + std::expected result) { + callback_called = true; + callback_result = result; + }); EXPECT_TRUE(result); EXPECT_TRUE(callback_called); From 5bce1cae9c39ec4eeadd80e9f086c64b0430a228 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 20:21:35 +0000 Subject: [PATCH 09/16] Change I2C callbacks from function pointers to std::function Lambdas with captures cannot be converted to function pointers, which was causing compilation errors in the unit tests. Changed the I2C interface to use std::function instead, matching the pattern used in UART. This allows tests to use lambdas with captures for callback verification. --- src/libs/mcu/host/host_i2c.cpp | 12 ++++++------ src/libs/mcu/host/host_i2c.hpp | 17 +++++++++-------- src/libs/mcu/i2c.hpp | 16 +++++++++------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/libs/mcu/host/host_i2c.cpp b/src/libs/mcu/host/host_i2c.cpp index e17ac49..2fae354 100644 --- a/src/libs/mcu/host/host_i2c.cpp +++ b/src/libs/mcu/host/host_i2c.cpp @@ -82,7 +82,7 @@ auto HostI2CController::ReceiveData(uint16_t address, size_t size) auto HostI2CController::SendDataInterrupt( uint16_t address, std::span data, - void (*callback)(std::expected)) + std::function)> callback) -> std::expected { callback(SendData(address, data)); return {}; @@ -90,15 +90,15 @@ auto HostI2CController::SendDataInterrupt( auto HostI2CController::ReceiveDataInterrupt( uint16_t address, size_t size, - void (*callback)(std::expected, common::Error>)) - -> std::expected { + std::function, common::Error>)> + callback) -> std::expected { callback(ReceiveData(address, size)); return {}; } auto HostI2CController::SendDataDma( uint16_t address, std::span data, - void (*callback)(std::expected)) + std::function)> callback) -> std::expected { callback(SendData(address, data)); return {}; @@ -106,8 +106,8 @@ auto HostI2CController::SendDataDma( auto HostI2CController::ReceiveDataDma( uint16_t address, size_t size, - void (*callback)(std::expected, common::Error>)) - -> std::expected { + std::function, common::Error>)> + callback) -> std::expected { callback(ReceiveData(address, size)); return {}; } diff --git a/src/libs/mcu/host/host_i2c.hpp b/src/libs/mcu/host/host_i2c.hpp index 0640caf..c46c1c9 100644 --- a/src/libs/mcu/host/host_i2c.hpp +++ b/src/libs/mcu/host/host_i2c.hpp @@ -29,21 +29,22 @@ class HostI2CController final : public I2CController, public Receiver { auto ReceiveData(uint16_t address, size_t size) -> std::expected, common::Error> override; - auto SendDataInterrupt(uint16_t address, std::span data, - void (*callback)(std::expected)) + auto SendDataInterrupt( + uint16_t address, std::span data, + std::function)> callback) -> std::expected override; auto ReceiveDataInterrupt( uint16_t address, size_t size, - void (*callback)(std::expected, common::Error>)) - -> std::expected override; + std::function, common::Error>)> + callback) -> std::expected override; auto SendDataDma(uint16_t address, std::span data, - void (*callback)(std::expected)) - -> std::expected override; + std::function)> + callback) -> std::expected override; auto ReceiveDataDma( uint16_t address, size_t size, - void (*callback)(std::expected, common::Error>)) - -> std::expected override; + std::function, common::Error>)> + callback) -> std::expected override; auto Receive(const std::string_view& message) -> std::expected override; diff --git a/src/libs/mcu/i2c.hpp b/src/libs/mcu/i2c.hpp index 7b7700d..d5e2973 100644 --- a/src/libs/mcu/i2c.hpp +++ b/src/libs/mcu/i2c.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "libs/common/error.hpp" @@ -20,20 +21,21 @@ class I2CController { virtual auto SendDataInterrupt( uint16_t address, std::span data, - void (*callback)(std::expected)) + std::function)> callback) -> std::expected = 0; virtual auto ReceiveDataInterrupt( uint16_t address, size_t size, - void (*callback)(std::expected, common::Error>)) - -> std::expected = 0; + std::function, common::Error>)> + callback) -> std::expected = 0; - virtual auto SendDataDma(uint16_t address, std::span data, - void (*callback)(std::expected)) + virtual auto SendDataDma( + uint16_t address, std::span data, + std::function)> callback) -> std::expected = 0; virtual auto ReceiveDataDma( uint16_t address, size_t size, - void (*callback)(std::expected, common::Error>)) - -> std::expected = 0; + std::function, common::Error>)> + callback) -> std::expected = 0; }; } // namespace mcu From 63fcbb4b5c28e36a1a5e6e8e4f04bc36abede0dc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 21:40:08 +0000 Subject: [PATCH 10/16] Add I2C test application and integration tests Created a complete I2C test application that demonstrates I2C driver usage and comprehensive Python integration tests. C++ Application (i2c_test): - Writes a test pattern (0xDE, 0xAD, 0xBE, 0xEF) to I2C device at 0x50 - Reads the data back and verifies it matches - Toggles LED1 when data verification succeeds - Toggles LED2 as a heartbeat indicator - Handles write/read errors gracefully Python Integration Tests (test_i2c_test.py): - test_i2c_test_starts: Verifies app starts successfully - test_i2c_test_write_read_cycle: Validates I2C write/read operations - test_i2c_test_toggles_leds: Checks LED toggling behavior - test_i2c_test_data_mismatch: Tests error handling with wrong data Updated conftest.py to add i2c_test fixture with --i2c-test CLI option. The tests use the Python emulator's I2C device buffer simulation to verify correct I2C communication without requiring hardware. --- py/host-emulator/tests/conftest.py | 27 +++++ py/host-emulator/tests/test_i2c_test.py | 134 ++++++++++++++++++++++++ src/apps/CMakeLists.txt | 3 +- src/apps/i2c_test/CMakeLists.txt | 5 + src/apps/i2c_test/i2c_test.cpp | 105 +++++++++++++++++++ src/apps/i2c_test/i2c_test.hpp | 17 +++ 6 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 py/host-emulator/tests/test_i2c_test.py create mode 100644 src/apps/i2c_test/CMakeLists.txt create mode 100644 src/apps/i2c_test/i2c_test.cpp create mode 100644 src/apps/i2c_test/i2c_test.hpp diff --git a/py/host-emulator/tests/conftest.py b/py/host-emulator/tests/conftest.py index a133785..df1e82e 100644 --- a/py/host-emulator/tests/conftest.py +++ b/py/host-emulator/tests/conftest.py @@ -18,6 +18,12 @@ def pytest_addoption(parser): default=None, help="Path to the uart_echo executable", ) + parser.addoption( + "--i2c-test", + action="store", + default=None, + help="Path to the i2c_test executable", + ) # Emulator must be stopped manually within each test @@ -73,3 +79,24 @@ def uart_echo(request): uart_echo_process.kill() uart_echo_process.wait(timeout=1) print(f"[Fixture] UartEcho return code: {uart_echo_process.returncode}") + + +# I2CTest must be stopped manually within each test +@fixture() +def i2c_test(request): + i2c_test_arg = request.config.getoption("--i2c-test") + i2c_test_executable = pathlib.Path(i2c_test_arg).resolve() + assert i2c_test_executable.exists() + i2c_test_process = subprocess.Popen( + [str(i2c_test_executable)], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + yield i2c_test_process + + if i2c_test_process.poll() is None: + print("[Fixture] Stopping i2c_test") + i2c_test_process.kill() + i2c_test_process.wait(timeout=1) + print(f"[Fixture] I2CTest return code: {i2c_test_process.returncode}") diff --git a/py/host-emulator/tests/test_i2c_test.py b/py/host-emulator/tests/test_i2c_test.py new file mode 100644 index 0000000..b1f3356 --- /dev/null +++ b/py/host-emulator/tests/test_i2c_test.py @@ -0,0 +1,134 @@ +"""Integration tests for I2C test application.""" + +import time + + +def test_i2c_test_starts(emulator, i2c_test): + """Test that i2c_test starts successfully.""" + try: + # Give i2c_test time to initialize + time.sleep(0.5) + + # Check that the process is still running + assert i2c_test.poll() is None, "i2c_test process terminated unexpectedly" + + finally: + emulator.stop() + i2c_test.terminate() + i2c_test.wait(timeout=1) + + +def test_i2c_test_write_read_cycle(emulator, i2c_test): + """Test that i2c_test writes and reads from I2C device.""" + try: + device_address = 0x50 + test_pattern = [0xDE, 0xAD, 0xBE, 0xEF] + write_count = 0 + read_count = 0 + + def i2c_handler(message): + nonlocal write_count, read_count + if message.get("operation") == "Send": + # Device is writing to I2C peripheral + write_count += 1 + data = message.get("data", []) + address = message.get("address", 0) + + # Verify the data and address + assert address == device_address, f"Wrong address: 0x{address:02X}" + assert data == test_pattern, f"Wrong data: {data}" + + elif message.get("operation") == "Receive": + # Device is reading from I2C peripheral + read_count += 1 + address = message.get("address", 0) + assert address == device_address, f"Wrong address: 0x{address:02X}" + + emulator.i2c1().set_on_request(i2c_handler) + + # Pre-populate I2C device buffer with test pattern + emulator.i2c1().write_to_device(device_address, test_pattern) + + # Give i2c_test time to run a few cycles + time.sleep(1.5) + + # Verify that writes and reads occurred + assert write_count > 0, "No I2C writes occurred" + assert read_count > 0, "No I2C reads occurred" + assert write_count == read_count, ( + f"Write/read mismatch: {write_count} writes, {read_count} reads" + ) + + finally: + emulator.stop() + i2c_test.terminate() + i2c_test.wait(timeout=1) + + +def test_i2c_test_toggles_leds(emulator, i2c_test): + """Test that i2c_test toggles LEDs based on I2C operations.""" + try: + device_address = 0x50 + test_pattern = [0xDE, 0xAD, 0xBE, 0xEF] + + # Pre-populate I2C device buffer with correct test pattern + emulator.i2c1().write_to_device(device_address, test_pattern) + + # Give i2c_test time to initialize + time.sleep(0.5) + + # Record initial LED states + initial_led1 = emulator.get_pin_state("LED 1") + initial_led2 = emulator.get_pin_state("LED 2") + + # Wait for a few cycles + time.sleep(1.0) + + # Check that LEDs have toggled + final_led1 = emulator.get_pin_state("LED 1") + final_led2 = emulator.get_pin_state("LED 2") + + # LED2 should have toggled (heartbeat) + assert final_led2 != initial_led2, ( + f"LED2 didn't toggle: {initial_led2} -> {final_led2}" + ) + + # LED1 should have toggled (data verification success) + assert final_led1 != initial_led1, ( + f"LED1 didn't toggle: {initial_led1} -> {final_led1}" + ) + + finally: + emulator.stop() + i2c_test.terminate() + i2c_test.wait(timeout=1) + + +def test_i2c_test_data_mismatch(emulator, i2c_test): + """Test that i2c_test handles data mismatch correctly.""" + try: + device_address = 0x50 + wrong_pattern = [0x00, 0x11, 0x22, 0x33] # Different from test pattern + + # Pre-populate I2C device buffer with wrong data + emulator.i2c1().write_to_device(device_address, wrong_pattern) + + # Give i2c_test time to run a few cycles + time.sleep(1.0) + + # LED1 should be off due to data mismatch + led1_state = emulator.get_pin_state("LED 1") + assert led1_state.name == "Low", f"LED1 should be off, but is {led1_state.name}" + + # LED2 should still be blinking (alive indicator) + initial_led2 = emulator.get_pin_state("LED 2") + time.sleep(0.6) + final_led2 = emulator.get_pin_state("LED 2") + assert final_led2 != initial_led2, ( + f"LED2 didn't toggle: {initial_led2} -> {final_led2}" + ) + + finally: + emulator.stop() + i2c_test.terminate() + i2c_test.wait(timeout=1) diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index 1bb5d35..07bdc0a 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -5,4 +5,5 @@ target_compile_options(app INTERFACE ${COMMON_COMPILE_OPTIONS}) target_link_libraries(app INTERFACE board error) add_subdirectory(blinky) -add_subdirectory(uart_echo) \ No newline at end of file +add_subdirectory(uart_echo) +add_subdirectory(i2c_test) \ No newline at end of file diff --git a/src/apps/i2c_test/CMakeLists.txt b/src/apps/i2c_test/CMakeLists.txt new file mode 100644 index 0000000..7e45c35 --- /dev/null +++ b/src/apps/i2c_test/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.27) + +add_executable(i2c_test i2c_test.cpp) +target_compile_options(i2c_test PRIVATE ${COMMON_COMPILE_OPTIONS}) +target_link_libraries(i2c_test PRIVATE error sys mcu) diff --git a/src/apps/i2c_test/i2c_test.cpp b/src/apps/i2c_test/i2c_test.cpp new file mode 100644 index 0000000..681364f --- /dev/null +++ b/src/apps/i2c_test/i2c_test.cpp @@ -0,0 +1,105 @@ +#include "i2c_test.hpp" + +#include +#include +#include + +#include "apps/app.hpp" +#include "libs/board/board.hpp" +#include "libs/common/error.hpp" +#include "libs/mcu/delay.hpp" +#include "libs/mcu/i2c.hpp" + +namespace app { +using std::chrono::operator""ms; + +auto AppMain(board::Board& board) -> std::expected { + I2CTest i2c_test{board}; + if (!i2c_test.Init()) { + return std::unexpected(common::Error::kUnknown); + } + if (!i2c_test.Run()) { + return std::unexpected(common::Error::kUnknown); + } + return {}; +} + +auto I2CTest::Init() -> std::expected { + return board_.Init(); +} + +auto I2CTest::Run() -> std::expected { + // I2C device address to test + constexpr uint16_t kDeviceAddress{0x50}; + + // Test pattern to write/read + const std::array test_pattern{0xDE, 0xAD, 0xBE, 0xEF}; + + // Main loop - write pattern, read it back, verify + while (true) { + // Write test pattern to I2C device + auto write_result = board_.I2C1().SendData(kDeviceAddress, test_pattern); + if (!write_result) { + // Turn off LED1 on write error + std::ignore = board_.UserLed1().SetLow(); + mcu::Delay(100ms); + continue; + } + + // Small delay between write and read + mcu::Delay(50ms); + + // Read data back from I2C device + auto read_result = + board_.I2C1().ReceiveData(kDeviceAddress, test_pattern.size()); + if (!read_result) { + // Turn off LED1 on read error + std::ignore = board_.UserLed1().SetLow(); + mcu::Delay(100ms); + continue; + } + + // Verify received data matches test pattern + const auto received_span = read_result.value(); + bool data_matches{true}; + if (received_span.size() == test_pattern.size()) { + for (size_t i{0}; i < test_pattern.size(); ++i) { + if (received_span[i] != test_pattern[i]) { + data_matches = false; + break; + } + } + } else { + data_matches = false; + } + + // Toggle LED1 based on verification result + if (data_matches) { + // Data matches - toggle LED1 + const auto led_state = board_.UserLed1().Get(); + if (led_state && led_state.value() == mcu::PinState::kHigh) { + std::ignore = board_.UserLed1().SetLow(); + } else { + std::ignore = board_.UserLed1().SetHigh(); + } + } else { + // Data mismatch - turn off LED1 + std::ignore = board_.UserLed1().SetLow(); + } + + // Toggle LED2 to show we're alive + const auto led2_state = board_.UserLed2().Get(); + if (led2_state && led2_state.value() == mcu::PinState::kHigh) { + std::ignore = board_.UserLed2().SetLow(); + } else { + std::ignore = board_.UserLed2().SetHigh(); + } + + // Delay before next iteration + mcu::Delay(500ms); + } + + return {}; +} + +} // namespace app diff --git a/src/apps/i2c_test/i2c_test.hpp b/src/apps/i2c_test/i2c_test.hpp new file mode 100644 index 0000000..3316264 --- /dev/null +++ b/src/apps/i2c_test/i2c_test.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "libs/board/board.hpp" + +namespace app { + +class I2CTest { + public: + explicit I2CTest(board::Board& board) : board_(board) {} + auto Init() -> std::expected; + auto Run() -> std::expected; + + private: + board::Board& board_; +}; + +} // namespace app From 523b4bbafba72b1b2e33343a3ded3c5db2d08d9c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:45:18 +0000 Subject: [PATCH 11/16] Initial plan From 2068e327c260de51f17f2c7aee8c228304e1d005 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:56:16 +0000 Subject: [PATCH 12/16] Rename i2c_test to i2c_demo throughout codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename directory: src/apps/i2c_test → src/apps/i2c_demo - Rename files: i2c_test.cpp/hpp → i2c_demo.cpp/hpp - Update class name: I2CTest → I2CDemo - Update all CMakeLists.txt references - Rename Python test: test_i2c_test.py → test_i2c_demo.py - Update Python fixtures and test functions - Update conftest.py: --i2c-test → --i2c-demo Co-authored-by: nehalkpatel <20796598+nehalkpatel@users.noreply.github.com> --- py/host-emulator/tests/conftest.py | 30 ++++++------- .../{test_i2c_test.py => test_i2c_demo.py} | 44 +++++++++---------- src/apps/CMakeLists.txt | 2 +- src/apps/i2c_demo/CMakeLists.txt | 5 +++ .../i2c_test.cpp => i2c_demo/i2c_demo.cpp} | 12 ++--- .../i2c_test.hpp => i2c_demo/i2c_demo.hpp} | 4 +- src/apps/i2c_test/CMakeLists.txt | 5 --- 7 files changed, 51 insertions(+), 51 deletions(-) rename py/host-emulator/tests/{test_i2c_test.py => test_i2c_demo.py} (77%) create mode 100644 src/apps/i2c_demo/CMakeLists.txt rename src/apps/{i2c_test/i2c_test.cpp => i2c_demo/i2c_demo.cpp} (92%) rename src/apps/{i2c_test/i2c_test.hpp => i2c_demo/i2c_demo.hpp} (75%) delete mode 100644 src/apps/i2c_test/CMakeLists.txt diff --git a/py/host-emulator/tests/conftest.py b/py/host-emulator/tests/conftest.py index df1e82e..23f3eb1 100644 --- a/py/host-emulator/tests/conftest.py +++ b/py/host-emulator/tests/conftest.py @@ -19,10 +19,10 @@ def pytest_addoption(parser): help="Path to the uart_echo executable", ) parser.addoption( - "--i2c-test", + "--i2c-demo", action="store", default=None, - help="Path to the i2c_test executable", + help="Path to the i2c_demo executable", ) @@ -81,22 +81,22 @@ def uart_echo(request): print(f"[Fixture] UartEcho return code: {uart_echo_process.returncode}") -# I2CTest must be stopped manually within each test +# I2CDemo must be stopped manually within each test @fixture() -def i2c_test(request): - i2c_test_arg = request.config.getoption("--i2c-test") - i2c_test_executable = pathlib.Path(i2c_test_arg).resolve() - assert i2c_test_executable.exists() - i2c_test_process = subprocess.Popen( - [str(i2c_test_executable)], +def i2c_demo(request): + i2c_demo_arg = request.config.getoption("--i2c-demo") + i2c_demo_executable = pathlib.Path(i2c_demo_arg).resolve() + assert i2c_demo_executable.exists() + i2c_demo_process = subprocess.Popen( + [str(i2c_demo_executable)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) - yield i2c_test_process + yield i2c_demo_process - if i2c_test_process.poll() is None: - print("[Fixture] Stopping i2c_test") - i2c_test_process.kill() - i2c_test_process.wait(timeout=1) - print(f"[Fixture] I2CTest return code: {i2c_test_process.returncode}") + if i2c_demo_process.poll() is None: + print("[Fixture] Stopping i2c_demo") + i2c_demo_process.kill() + i2c_demo_process.wait(timeout=1) + print(f"[Fixture] I2CDemo return code: {i2c_demo_process.returncode}") diff --git a/py/host-emulator/tests/test_i2c_test.py b/py/host-emulator/tests/test_i2c_demo.py similarity index 77% rename from py/host-emulator/tests/test_i2c_test.py rename to py/host-emulator/tests/test_i2c_demo.py index b1f3356..7536cbe 100644 --- a/py/host-emulator/tests/test_i2c_test.py +++ b/py/host-emulator/tests/test_i2c_demo.py @@ -1,25 +1,25 @@ -"""Integration tests for I2C test application.""" +"""Integration tests for I2C demo application.""" import time -def test_i2c_test_starts(emulator, i2c_test): - """Test that i2c_test starts successfully.""" +def test_i2c_demo_starts(emulator, i2c_demo): + """Test that i2c_demo starts successfully.""" try: - # Give i2c_test time to initialize + # Give i2c_demo time to initialize time.sleep(0.5) # Check that the process is still running - assert i2c_test.poll() is None, "i2c_test process terminated unexpectedly" + assert i2c_demo.poll() is None, "i2c_demo process terminated unexpectedly" finally: emulator.stop() - i2c_test.terminate() - i2c_test.wait(timeout=1) + i2c_demo.terminate() + i2c_demo.wait(timeout=1) -def test_i2c_test_write_read_cycle(emulator, i2c_test): - """Test that i2c_test writes and reads from I2C device.""" +def test_i2c_demo_write_read_cycle(emulator, i2c_demo): + """Test that i2c_demo writes and reads from I2C device.""" try: device_address = 0x50 test_pattern = [0xDE, 0xAD, 0xBE, 0xEF] @@ -49,7 +49,7 @@ def i2c_handler(message): # Pre-populate I2C device buffer with test pattern emulator.i2c1().write_to_device(device_address, test_pattern) - # Give i2c_test time to run a few cycles + # Give i2c_demo time to run a few cycles time.sleep(1.5) # Verify that writes and reads occurred @@ -61,12 +61,12 @@ def i2c_handler(message): finally: emulator.stop() - i2c_test.terminate() - i2c_test.wait(timeout=1) + i2c_demo.terminate() + i2c_demo.wait(timeout=1) -def test_i2c_test_toggles_leds(emulator, i2c_test): - """Test that i2c_test toggles LEDs based on I2C operations.""" +def test_i2c_demo_toggles_leds(emulator, i2c_demo): + """Test that i2c_demo toggles LEDs based on I2C operations.""" try: device_address = 0x50 test_pattern = [0xDE, 0xAD, 0xBE, 0xEF] @@ -74,7 +74,7 @@ def test_i2c_test_toggles_leds(emulator, i2c_test): # Pre-populate I2C device buffer with correct test pattern emulator.i2c1().write_to_device(device_address, test_pattern) - # Give i2c_test time to initialize + # Give i2c_demo time to initialize time.sleep(0.5) # Record initial LED states @@ -100,12 +100,12 @@ def test_i2c_test_toggles_leds(emulator, i2c_test): finally: emulator.stop() - i2c_test.terminate() - i2c_test.wait(timeout=1) + i2c_demo.terminate() + i2c_demo.wait(timeout=1) -def test_i2c_test_data_mismatch(emulator, i2c_test): - """Test that i2c_test handles data mismatch correctly.""" +def test_i2c_demo_data_mismatch(emulator, i2c_demo): + """Test that i2c_demo handles data mismatch correctly.""" try: device_address = 0x50 wrong_pattern = [0x00, 0x11, 0x22, 0x33] # Different from test pattern @@ -113,7 +113,7 @@ def test_i2c_test_data_mismatch(emulator, i2c_test): # Pre-populate I2C device buffer with wrong data emulator.i2c1().write_to_device(device_address, wrong_pattern) - # Give i2c_test time to run a few cycles + # Give i2c_demo time to run a few cycles time.sleep(1.0) # LED1 should be off due to data mismatch @@ -130,5 +130,5 @@ def test_i2c_test_data_mismatch(emulator, i2c_test): finally: emulator.stop() - i2c_test.terminate() - i2c_test.wait(timeout=1) + i2c_demo.terminate() + i2c_demo.wait(timeout=1) diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index 07bdc0a..af5273c 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -6,4 +6,4 @@ target_link_libraries(app INTERFACE board error) add_subdirectory(blinky) add_subdirectory(uart_echo) -add_subdirectory(i2c_test) \ No newline at end of file +add_subdirectory(i2c_demo) \ No newline at end of file diff --git a/src/apps/i2c_demo/CMakeLists.txt b/src/apps/i2c_demo/CMakeLists.txt new file mode 100644 index 0000000..18410d7 --- /dev/null +++ b/src/apps/i2c_demo/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.27) + +add_executable(i2c_demo i2c_demo.cpp) +target_compile_options(i2c_demo PRIVATE ${COMMON_COMPILE_OPTIONS}) +target_link_libraries(i2c_demo PRIVATE error sys mcu) diff --git a/src/apps/i2c_test/i2c_test.cpp b/src/apps/i2c_demo/i2c_demo.cpp similarity index 92% rename from src/apps/i2c_test/i2c_test.cpp rename to src/apps/i2c_demo/i2c_demo.cpp index 681364f..ee0f652 100644 --- a/src/apps/i2c_test/i2c_test.cpp +++ b/src/apps/i2c_demo/i2c_demo.cpp @@ -1,4 +1,4 @@ -#include "i2c_test.hpp" +#include "i2c_demo.hpp" #include #include @@ -14,21 +14,21 @@ namespace app { using std::chrono::operator""ms; auto AppMain(board::Board& board) -> std::expected { - I2CTest i2c_test{board}; - if (!i2c_test.Init()) { + I2CDemo i2c_demo{board}; + if (!i2c_demo.Init()) { return std::unexpected(common::Error::kUnknown); } - if (!i2c_test.Run()) { + if (!i2c_demo.Run()) { return std::unexpected(common::Error::kUnknown); } return {}; } -auto I2CTest::Init() -> std::expected { +auto I2CDemo::Init() -> std::expected { return board_.Init(); } -auto I2CTest::Run() -> std::expected { +auto I2CDemo::Run() -> std::expected { // I2C device address to test constexpr uint16_t kDeviceAddress{0x50}; diff --git a/src/apps/i2c_test/i2c_test.hpp b/src/apps/i2c_demo/i2c_demo.hpp similarity index 75% rename from src/apps/i2c_test/i2c_test.hpp rename to src/apps/i2c_demo/i2c_demo.hpp index 3316264..0f1105b 100644 --- a/src/apps/i2c_test/i2c_test.hpp +++ b/src/apps/i2c_demo/i2c_demo.hpp @@ -4,9 +4,9 @@ namespace app { -class I2CTest { +class I2CDemo { public: - explicit I2CTest(board::Board& board) : board_(board) {} + explicit I2CDemo(board::Board& board) : board_(board) {} auto Init() -> std::expected; auto Run() -> std::expected; diff --git a/src/apps/i2c_test/CMakeLists.txt b/src/apps/i2c_test/CMakeLists.txt deleted file mode 100644 index 7e45c35..0000000 --- a/src/apps/i2c_test/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -cmake_minimum_required(VERSION 3.27) - -add_executable(i2c_test i2c_test.cpp) -target_compile_options(i2c_test PRIVATE ${COMMON_COMPILE_OPTIONS}) -target_link_libraries(i2c_test PRIVATE error sys mcu) From f829b060b27a84d3e92bf77de8d21c9d8538a344 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Mon, 24 Nov 2025 23:16:24 +0000 Subject: [PATCH 13/16] Reduce complexity in i2c_demo --- src/apps/i2c_demo/i2c_demo.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/apps/i2c_demo/i2c_demo.cpp b/src/apps/i2c_demo/i2c_demo.cpp index ee0f652..92c8174 100644 --- a/src/apps/i2c_demo/i2c_demo.cpp +++ b/src/apps/i2c_demo/i2c_demo.cpp @@ -50,8 +50,8 @@ auto I2CDemo::Run() -> std::expected { mcu::Delay(50ms); // Read data back from I2C device - auto read_result = - board_.I2C1().ReceiveData(kDeviceAddress, test_pattern.size()); + auto read_result{ + board_.I2C1().ReceiveData(kDeviceAddress, test_pattern.size())}; if (!read_result) { // Turn off LED1 on read error std::ignore = board_.UserLed1().SetLow(); @@ -60,16 +60,9 @@ auto I2CDemo::Run() -> std::expected { } // Verify received data matches test pattern - const auto received_span = read_result.value(); + const auto received_span{read_result.value()}; bool data_matches{true}; - if (received_span.size() == test_pattern.size()) { - for (size_t i{0}; i < test_pattern.size(); ++i) { - if (received_span[i] != test_pattern[i]) { - data_matches = false; - break; - } - } - } else { + if (received_span != test_pattern) { data_matches = false; } From d26a35846c2526da82f8c7e0dbd6a582c14623ee Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Mon, 24 Nov 2025 23:23:13 +0000 Subject: [PATCH 14/16] Use ranges comparsion for span and array --- src/apps/i2c_demo/i2c_demo.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/apps/i2c_demo/i2c_demo.cpp b/src/apps/i2c_demo/i2c_demo.cpp index 92c8174..9084e1d 100644 --- a/src/apps/i2c_demo/i2c_demo.cpp +++ b/src/apps/i2c_demo/i2c_demo.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "apps/app.hpp" #include "libs/board/board.hpp" @@ -62,7 +63,7 @@ auto I2CDemo::Run() -> std::expected { // Verify received data matches test pattern const auto received_span{read_result.value()}; bool data_matches{true}; - if (received_span != test_pattern) { + if (std::ranges::equal(received_span, test_pattern)) { data_matches = false; } From 7a9dad1eae268cd582bb813ba7960623f0cfc178 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Mon, 24 Nov 2025 23:29:50 +0000 Subject: [PATCH 15/16] Fix typo and logic. --- src/apps/i2c_demo/i2c_demo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/i2c_demo/i2c_demo.cpp b/src/apps/i2c_demo/i2c_demo.cpp index 9084e1d..014149b 100644 --- a/src/apps/i2c_demo/i2c_demo.cpp +++ b/src/apps/i2c_demo/i2c_demo.cpp @@ -63,7 +63,7 @@ auto I2CDemo::Run() -> std::expected { // Verify received data matches test pattern const auto received_span{read_result.value()}; bool data_matches{true}; - if (std::ranges::equal(received_span, test_pattern)) { + if (!std::ranges::equals(received_span, test_pattern)) { data_matches = false; } From 9605addd5ef5e5bd51c8e9a6b7a7dd652e685b38 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Tue, 25 Nov 2025 00:04:29 +0000 Subject: [PATCH 16/16] Try to find std::ranges::equal --- src/apps/i2c_demo/i2c_demo.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/apps/i2c_demo/i2c_demo.cpp b/src/apps/i2c_demo/i2c_demo.cpp index 014149b..88e0731 100644 --- a/src/apps/i2c_demo/i2c_demo.cpp +++ b/src/apps/i2c_demo/i2c_demo.cpp @@ -1,5 +1,6 @@ #include "i2c_demo.hpp" +#include #include #include #include @@ -63,7 +64,7 @@ auto I2CDemo::Run() -> std::expected { // Verify received data matches test pattern const auto received_span{read_result.value()}; bool data_matches{true}; - if (!std::ranges::equals(received_span, test_pattern)) { + if (!std::ranges::equal(received_span, test_pattern)) { data_matches = false; }