pw_i2c: Add Device class

This object will wrap the initiator along with an address.
Device is a high level wrapper to be used to stream the reading
or writing of arbitrary data to the device.

Testing:
Host test -- OK

Change-Id: I28afd9a956917b28d4db04e8b1be2788981b12c7
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/41163
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
Commit-Queue: Kevin Zeng <zengk@google.com>
diff --git a/pw_i2c/BUILD b/pw_i2c/BUILD
index 8831e42..910bff2 100644
--- a/pw_i2c/BUILD
+++ b/pw_i2c/BUILD
@@ -49,6 +49,21 @@
     ],
 )
 
+pw_cc_library(
+    name = "device",
+    hdrs = [
+        "public/pw_i2c/device.h",
+    ],
+    includes = ["public"],
+    deps = [
+        ":address",
+        ":initiator",
+        "//pw_bytes",
+        "//pw_chrono:system_clock",
+        "//pw_status",
+    ],
+)
+
 pw_cc_test(
     name = "address_test",
     srcs = [
@@ -59,3 +74,14 @@
         "//pw_unit_test",
     ],
 )
+
+pw_cc_test(
+    name = "device_test",
+    srcs = [
+        "device_test.cc",
+    ],
+    deps = [
+        ":device",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_i2c/BUILD.gn b/pw_i2c/BUILD.gn
index b939587..ed64c41 100644
--- a/pw_i2c/BUILD.gn
+++ b/pw_i2c/BUILD.gn
@@ -15,6 +15,7 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/target_types.gni")
+import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
@@ -40,8 +41,22 @@
   ]
 }
 
+pw_source_set("device") {
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_i2c/device.h" ]
+  public_deps = [
+    ":address",
+    ":initiator",
+    "$dir_pw_bytes",
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_status",
+  ]
+}
 pw_test_group("tests") {
-  tests = [ ":address_test" ]
+  tests = [
+    ":address_test",
+    ":device_test",
+  ]
 }
 
 pw_test("address_test") {
@@ -49,6 +64,12 @@
   deps = [ ":address" ]
 }
 
+pw_test("device_test") {
+  enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
+  sources = [ "device_test.cc" ]
+  deps = [ ":device" ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_i2c/device_test.cc b/pw_i2c/device_test.cc
new file mode 100644
index 0000000..50a7c81
--- /dev/null
+++ b/pw_i2c/device_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#include "pw_i2c/device.h"
+
+#include "gtest/gtest.h"
+#include "pw_bytes/byte_builder.h"
+
+namespace pw {
+namespace i2c {
+namespace {
+
+// Dummy test initiator that's used for testing.
+class TestInitiator : public Initiator {
+ public:
+  explicit TestInitiator() {}
+
+ private:
+  Status DoWriteReadFor(Address,
+                        ConstByteSpan,
+                        ByteSpan,
+                        chrono::SystemClock::duration) override {
+    // Empty implementation.
+    return OkStatus();
+  }
+
+  ByteBuffer<10> write_buffer_;
+  ByteBuffer<10> read_buffer_;
+};
+
+// This test just checks to make sure the Device object compiles.
+// TODO(b/185609270): Full test coverage.
+TEST(DeviceCompilationTest, CompileOk) {
+  constexpr Address kDummyDeviceAddress = Address::SevenBit<0x3F>();
+
+  TestInitiator initiator;
+  Device device = Device(initiator, kDummyDeviceAddress);
+}
+
+}  // namespace
+}  // namespace i2c
+}  // namespace pw
diff --git a/pw_i2c/docs.rst b/pw_i2c/docs.rst
index a82fb23..5cb2592 100644
--- a/pw_i2c/docs.rst
+++ b/pw_i2c/docs.rst
@@ -18,3 +18,10 @@
 The common interface for initiating transactions with devices on an I2C bus.
 Other documentation sources may call this style of interface an I2C "master",
 "central" or "controller".
+
+pw::i2c::Device
+---------------
+The common interface for interfacing with generic I2C devices. This object
+contains ``pw::i2c::Address`` and wraps the ``pw::i2c::Initiator`` API.
+Common use case includes streaming arbitrary data (Read/Write). Only works
+with devices with a single device address.
diff --git a/pw_i2c/public/pw_i2c/device.h b/pw_i2c/public/pw_i2c/device.h
new file mode 100644
index 0000000..8091fec
--- /dev/null
+++ b/pw_i2c/public/pw_i2c/device.h
@@ -0,0 +1,175 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+#pragma once
+
+#include "pw_bytes/span.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_i2c/address.h"
+#include "pw_i2c/initiator.h"
+#include "pw_status/status.h"
+
+namespace pw {
+namespace i2c {
+
+// Device is used to write/read arbitrary chunks of data over a bus to a device.
+// This object essentially just wrap the Initiator API with a fixed I2C device
+// address.
+class Device {
+ public:
+  constexpr Device(Initiator& initiator, Address device_address)
+      : initiator_(initiator), device_address_(device_address) {}
+
+  Device(const Device&) = delete;
+  ~Device() = default;
+
+  // Write bytes and then read bytes as either one atomic or two independent I2C
+  // transaction.
+  // The signal on the bus should appear as follows:
+  // 1) Write Only:
+  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
+  // 2) Read Only:
+  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
+  // 3A) Write + Read (atomic):
+  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES +
+  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
+  // 3B) Write + Read (separate):
+  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
+  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
+  //
+  // The timeout defines the minimum duration one may block waiting for both
+  // exclusive bus access and the completion of the I2C transaction.
+  //
+  // Preconditions:
+  // The Address must be supported by the Initiator, i.e. do not use a 10
+  //     address if the Initiator only supports 7 bit. This will assert.
+  //
+  // Returns:
+  // Ok - Success.
+  // InvalidArgument - device_address is larger than the 10 bit address space.
+  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
+  //   and complete the I2C transaction in time.
+  // Unavailable - NACK condition occurred, meaning the addressed device did
+  //   not respond or was unable to process the request.
+  // FailedPrecondition - The interface is not currently initialized and/or
+  //    enabled.
+  Status WriteReadFor(ConstByteSpan tx_buffer,
+                      ByteSpan rx_buffer,
+                      chrono::SystemClock::duration for_at_least) {
+    return initiator_.WriteReadFor(
+        device_address_, tx_buffer, rx_buffer, for_at_least);
+  }
+  Status WriteReadFor(const void* tx_buffer,
+                      size_t tx_size_bytes,
+                      void* rx_buffer,
+                      size_t rx_size_bytes,
+                      chrono::SystemClock::duration for_at_least) {
+    return initiator_.WriteReadFor(device_address_,
+                                   tx_buffer,
+                                   tx_size_bytes,
+                                   rx_buffer,
+                                   rx_size_bytes,
+                                   for_at_least);
+  }
+
+  // Write bytes. The signal on the bus should appear as follows:
+  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
+  //
+  // The timeout defines the minimum duration one may block waiting for both
+  // exclusive bus access and the completion of the I2C transaction.
+  //
+  // Preconditions:
+  // The Address must be supported by the Initiator, i.e. do not use a 10
+  //     address if the Initiator only supports 7 bit. This will assert.
+  //
+  // Returns:
+  // Ok - Success.
+  // InvalidArgument - device_address is larger than the 10 bit address space.
+  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
+  //   and complete the I2C transaction in time.
+  // Unavailable - NACK condition occurred, meaning the addressed device did
+  //   not respond or was unable to process the request.
+  // FailedPrecondition - The interface is not currently initialized and/or
+  //    enabled.
+  Status WriteFor(ConstByteSpan tx_buffer,
+                  chrono::SystemClock::duration for_at_least) {
+    return initiator_.WriteFor(device_address_, tx_buffer, for_at_least);
+  }
+  Status WriteFor(const void* tx_buffer,
+                  size_t tx_size_bytes,
+                  chrono::SystemClock::duration for_at_least) {
+    return initiator_.WriteFor(
+        device_address_, tx_buffer, tx_size_bytes, for_at_least);
+  }
+
+  // Read bytes. The signal on the bus should appear as follows:
+  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
+  //
+  // The timeout defines the minimum duration one may block waiting for both
+  // exclusive bus access and the completion of the I2C transaction.
+  //
+  // Preconditions:
+  // The Address must be supported by the Initiator, i.e. do not use a 10
+  //     address if the Initiator only supports 7 bit. This will assert.
+  //
+  // Returns:
+  // Ok - Success.
+  // InvalidArgument - device_address is larger than the 10 bit address space.
+  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
+  //   and complete the I2C transaction in time.
+  // Unavailable - NACK condition occurred, meaning the addressed device did
+  //   not respond or was unable to process the request.
+  // FailedPrecondition - The interface is not currently initialized and/or
+  //    enabled.
+  Status ReadFor(ByteSpan rx_buffer,
+                 chrono::SystemClock::duration for_at_least) {
+    return initiator_.ReadFor(device_address_, rx_buffer, for_at_least);
+  }
+  Status ReadFor(void* rx_buffer,
+                 size_t rx_size_bytes,
+                 chrono::SystemClock::duration for_at_least) {
+    return initiator_.ReadFor(
+        device_address_, rx_buffer, rx_size_bytes, for_at_least);
+  }
+
+  // Probes the device for an I2C ACK after only writing the address.
+  // This is done by attempting to read a single byte from the specified device.
+  //
+  // The timeout defines the minimum duration one may block waiting for both
+  // exclusive bus access and the completion of the I2C transaction.
+  //
+  // Preconditions:
+  // The Address must be supported by the Initiator, i.e. do not use a 10
+  //     address if the Initiator only supports 7 bit. This will assert.
+  //
+  // Returns:
+  // Ok - Success.
+  // InvalidArgument - device_address is larger than the 10 bit address space.
+  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
+  //   and complete the I2C transaction in time.
+  // Unavailable - NACK condition occurred, meaning the addressed device did
+  //   not respond or was unable to process the request.
+  // FailedPrecondition - The interface is not currently initialized and/or
+  //    enabled.
+  Status ProbeFor(chrono::SystemClock::duration for_at_least) {
+    return initiator_.ProbeDeviceFor(device_address_, for_at_least);
+  }
+
+ private:
+  Initiator& initiator_;
+  const Address device_address_;
+};
+
+}  // namespace i2c
+}  // namespace pw