Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 1 | .. _module-pw_transfer: |
| 2 | |
| 3 | =========== |
| 4 | pw_transfer |
| 5 | =========== |
| 6 | |
| 7 | .. attention:: |
| 8 | |
| 9 | ``pw_transfer`` is under construction and so is its documentation. |
| 10 | |
Alexei Frolov | 0c94fc2 | 2021-07-08 16:05:24 -0700 | [diff] [blame] | 11 | ----- |
| 12 | Usage |
| 13 | ----- |
| 14 | |
Alexei Frolov | f93cb26 | 2021-07-14 16:05:15 -0700 | [diff] [blame] | 15 | C++ |
| 16 | === |
| 17 | The transfer service is defined and registered with an RPC server like any other |
| 18 | RPC service. |
| 19 | |
| 20 | To know how to read data from or write data to device, a ``TransferHandler`` |
| 21 | interface is defined (``pw_transfer/public/pw_transfer/handler.h``). Transfer |
| 22 | handlers wrap a stream reader and/or writer with initialization and completion |
| 23 | code. Custom transfer handler implementations should derive from |
| 24 | ``ReadOnlyHandler``, ``WriteOnlyHandler``, or ``ReadWriteHandler`` as |
| 25 | appropriate and override Prepare and Finalize methods if necessary. |
| 26 | |
| 27 | A transfer handler should be implemented and instantiated for each unique data |
| 28 | transfer to or from a device. These handlers are then registered with the |
| 29 | transfer service using their transfer IDs. |
| 30 | |
| 31 | **Example** |
| 32 | |
| 33 | .. code-block:: cpp |
| 34 | |
| 35 | #include "pw_transfer/transfer.h" |
| 36 | |
| 37 | namespace { |
| 38 | |
| 39 | // Simple transfer handler which reads data from an in-memory buffer. |
| 40 | class SimpleBufferReadHandler : public pw::transfer::ReadOnlyHandler { |
| 41 | public: |
| 42 | SimpleReadTransfer(uint32_t transfer_id, pw::ConstByteSpan data) |
| 43 | : ReadOnlyHandler(transfer_id), reader_(data) { |
| 44 | set_reader(reader_); |
| 45 | } |
| 46 | |
| 47 | private: |
| 48 | pw::stream::MemoryReader reader_; |
| 49 | }; |
| 50 | |
Alexei Frolov | 563946f | 2021-08-05 18:58:48 -0700 | [diff] [blame] | 51 | // The maximum amount of data that can be sent in a single chunk, excluding |
| 52 | // transport layer overhead. |
| 53 | constexpr size_t kMaxChunkSizeBytes = 256; |
| 54 | |
| 55 | // In a write transfer, the maximum number of bytes to receive at one time, |
| 56 | // (potentially across multiple chunks), unless specified otherwise by the |
| 57 | // transfer handler's stream::Writer. |
| 58 | constexpr size_t kDefaultMaxBytesToReceive = 1024; |
| 59 | |
Alexei Frolov | f93cb26 | 2021-07-14 16:05:15 -0700 | [diff] [blame] | 60 | // Instantiate a static transfer service. |
Alexei Frolov | 563946f | 2021-08-05 18:58:48 -0700 | [diff] [blame] | 61 | pw::transfer::TransferService transfer_service( |
| 62 | kMaxChunkSizeBytes, kDefaultMaxBytesToReceive); |
Alexei Frolov | f93cb26 | 2021-07-14 16:05:15 -0700 | [diff] [blame] | 63 | |
| 64 | // Instantiate a handler for the the data to be transferred. |
| 65 | constexpr uint32_t kBufferTransferId = 1; |
| 66 | char buffer_to_transfer[256] = { /* ... */ }; |
| 67 | SimpleBufferReadHandler buffer_handler(kBufferTransferId, buffer_to_transfer); |
| 68 | |
| 69 | } // namespace |
| 70 | |
| 71 | void InitTransfer() { |
| 72 | // Register the handler with the transfer service, then the transfer service |
| 73 | // with an RPC server. |
| 74 | transfer_service.RegisterHandler(buffer_handler); |
| 75 | GetSystemRpcServer().RegisterService(transfer_service); |
| 76 | } |
| 77 | |
Alexei Frolov | 0c94fc2 | 2021-07-08 16:05:24 -0700 | [diff] [blame] | 78 | Python |
| 79 | ====== |
| 80 | .. automodule:: pw_transfer.transfer |
| 81 | :members: Manager, Error |
| 82 | |
| 83 | **Example** |
| 84 | |
| 85 | .. code-block:: python |
| 86 | |
| 87 | from pw_transfer import transfer |
| 88 | |
| 89 | # Initialize a Pigweed RPC client; see pw_rpc docs for more info. |
| 90 | rpc_client = CustomRpcClient() |
| 91 | rpcs = rpc_client.channel(1).rpcs |
| 92 | |
| 93 | transfer_service = rpcs.pw.transfer.Transfer |
| 94 | transfer_manager = transfer.Manager(transfer_service) |
| 95 | |
| 96 | try: |
| 97 | # Read transfer_id 3 from the server. |
| 98 | data = transfer_manager.read(3) |
| 99 | except transfer.Error as err: |
| 100 | print('Failed to read:', err.status) |
| 101 | |
| 102 | try: |
| 103 | # Send some data to the server. The transfer manager does not have to be |
| 104 | # reinitialized. |
| 105 | transfer_manager.write(2, b'hello, world') |
| 106 | except transfer.Error as err: |
| 107 | print('Failed to write:', err.status) |
| 108 | |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 109 | -------- |
| 110 | Protocol |
| 111 | -------- |
| 112 | |
| 113 | Protocol buffer definition |
| 114 | ========================== |
| 115 | .. literalinclude:: transfer.proto |
| 116 | :language: protobuf |
| 117 | :lines: 14- |
| 118 | |
| 119 | Server to client transfer (read) |
| 120 | ================================ |
| 121 | .. seqdiag:: |
| 122 | :scale: 110 |
| 123 | |
| 124 | seqdiag { |
| 125 | default_note_color = aliceblue; |
| 126 | |
| 127 | client -> server [ |
| 128 | label = "set transfer parameters", |
| 129 | leftnote = "transfer_id\noffset\npending_bytes\nmax_chunk_size\nchunk_delay" |
| 130 | ]; |
| 131 | |
| 132 | client <-- server [ |
| 133 | noactivate, |
| 134 | label = "requested bytes\n(zero or more chunks)", |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 135 | rightnote = "transfer_id\noffset\ndata\n(remaining_bytes)" |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 136 | ]; |
| 137 | |
| 138 | client --> server [ |
| 139 | noactivate, |
| 140 | label = "update transfer parameters\n(as needed)", |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 141 | leftnote = "transfer_id\noffset\npending_bytes\n(max_chunk_size)\n(chunk_delay)" |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 142 | ]; |
| 143 | |
| 144 | client <- server [ |
| 145 | noactivate, |
| 146 | label = "final chunk", |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 147 | rightnote = "transfer_id\noffset\ndata\nremaining_bytes=0" |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 148 | ]; |
| 149 | |
| 150 | client -> server [ |
| 151 | noactivate, |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 152 | label = "acknowledge completion", |
| 153 | leftnote = "transfer_id\nstatus=OK" |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 154 | ]; |
| 155 | } |
| 156 | |
| 157 | Client to server transfer (write) |
| 158 | ================================= |
| 159 | .. seqdiag:: |
| 160 | :scale: 110 |
| 161 | |
| 162 | seqdiag { |
| 163 | default_note_color = aliceblue; |
| 164 | |
| 165 | client -> server [ |
| 166 | label = "start", |
| 167 | leftnote = "transfer_id" |
| 168 | ]; |
| 169 | |
| 170 | client <- server [ |
| 171 | noactivate, |
| 172 | label = "set transfer parameters", |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 173 | rightnote = "transfer_id\noffset\npending_bytes\nmax_chunk_size\nchunk_delay" |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 174 | ]; |
| 175 | |
| 176 | client --> server [ |
| 177 | noactivate, |
| 178 | label = "requested bytes\n(zero or more chunks)", |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 179 | leftnote = "transfer_id\noffset\ndata\n(remaining_bytes)" |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 180 | ]; |
| 181 | |
| 182 | client <-- server [ |
| 183 | noactivate, |
| 184 | label = "update transfer parameters\n(as needed)", |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 185 | rightnote = "transfer_id\noffset\npending_bytes\n(max_chunk_size)\n(chunk_delay)" |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 186 | ]; |
| 187 | |
| 188 | client -> server [ |
| 189 | noactivate, |
| 190 | label = "final chunk", |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 191 | leftnote = "transfer_id\noffset\ndata\nremaining_bytes=0" |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 192 | ]; |
| 193 | |
| 194 | client <- server [ |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 195 | noactivate, |
| 196 | label = "acknowledge completion", |
| 197 | rightnote = "transfer_id\nstatus=OK" |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 198 | ]; |
| 199 | } |
| 200 | |
Alexei Frolov | 41cd250 | 2021-07-08 16:03:36 -0700 | [diff] [blame] | 201 | Errors |
| 202 | ====== |
| 203 | At any point, either the client or server may terminate the transfer by sending |
| 204 | an error chunk with the transfer ID and a non-OK status. |
| 205 | |
Wyatt Hepler | 1e63673 | 2021-06-14 18:30:13 -0700 | [diff] [blame] | 206 | Transmitter flow |
| 207 | ================ |
| 208 | .. mermaid:: |
| 209 | |
| 210 | graph TD |
| 211 | start([Client initiates<br>transfer]) -->data_request |
| 212 | data_request[Receive transfer<br>parameters]-->send_chunk |
| 213 | |
| 214 | send_chunk[Send chunk]-->sent_all |
| 215 | |
| 216 | sent_all{Sent final<br>chunk?} -->|yes|wait |
| 217 | sent_all-->|no|sent_requested |
| 218 | |
| 219 | sent_requested{Sent all<br>pending?}-->|yes|data_request |
| 220 | sent_requested-->|no|send_chunk |
| 221 | |
| 222 | wait[Wait for receiver]-->is_done |
| 223 | |
| 224 | is_done{Received<br>final chunk?}-->|yes|done |
| 225 | is_done-->|no|data_request |
| 226 | |
| 227 | done([Transfer complete]) |
| 228 | |
| 229 | Receiver flow |
| 230 | ============= |
| 231 | .. mermaid:: |
| 232 | |
| 233 | graph TD |
| 234 | start([Client initiates<br>transfer]) -->request_bytes |
| 235 | request_bytes[Set transfer<br>parameters]-->wait |
| 236 | |
| 237 | wait[Wait for chunk]-->received_chunk |
| 238 | |
| 239 | received_chunk{Received<br>chunk by<br>deadline?}-->|no|request_bytes |
| 240 | received_chunk-->|yes|check_chunk |
| 241 | |
| 242 | check_chunk{Correct<br>offset?} -->|yes|process_chunk |
| 243 | check_chunk --> |no|request_bytes |
| 244 | |
| 245 | process_chunk[Process chunk]-->final_chunk |
| 246 | |
| 247 | final_chunk{Final<br>chunk?}-->|yes|signal_completion |
| 248 | final_chunk{Final<br>chunk?}-->|no|received_requested |
| 249 | |
| 250 | received_requested{Received all<br>pending?}-->|yes|request_bytes |
| 251 | received_requested-->|no|wait |
| 252 | |
| 253 | signal_completion[Signal completion]-->done |
| 254 | |
| 255 | done([Transfer complete]) |