pw_rpc_system_server: Local pw_rpc support
Using socket or sys_io to connect to server and client on host.
Test: Manually tested
Requires: pigweed:25460
Change-Id: I174121b90cea69621a885202b0d4d2df78a9aba8
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/20040
Commit-Queue: Jiaming (Charlie) Wang <jiamingw@google.com>
Reviewed-by: Alexei Frolov <frolv@google.com>
diff --git a/pw_hdlc_lite/BUILD b/pw_hdlc_lite/BUILD
index 125064a..ef4e127 100644
--- a/pw_hdlc_lite/BUILD
+++ b/pw_hdlc_lite/BUILD
@@ -32,7 +32,6 @@
hdrs = [
"public/pw_hdlc_lite/decoder.h",
"public/pw_hdlc_lite/encoder.h",
- "public/pw_hdlc_lite/sys_io_stream.h",
],
includes = ["public"],
deps = [
diff --git a/pw_hdlc_lite/BUILD.gn b/pw_hdlc_lite/BUILD.gn
index 75eb537..877526c 100644
--- a/pw_hdlc_lite/BUILD.gn
+++ b/pw_hdlc_lite/BUILD.gn
@@ -50,15 +50,13 @@
pw_source_set("encoder") {
public_configs = [ ":default_config" ]
- public = [
- "public/pw_hdlc_lite/encoder.h",
- "public/pw_hdlc_lite/sys_io_stream.h",
- ]
+ public = [ "public/pw_hdlc_lite/encoder.h" ]
sources = [
"encoder.cc",
"pw_hdlc_lite_private/protocol.h",
]
public_deps = [
+ "$dir_pw_stream:sys_io_stream",
dir_pw_bytes,
dir_pw_status,
dir_pw_stream,
diff --git a/pw_hdlc_lite/public/pw_hdlc_lite/rpc_packets.h b/pw_hdlc_lite/public/pw_hdlc_lite/rpc_packets.h
index de2a191..8d66c32 100644
--- a/pw_hdlc_lite/public/pw_hdlc_lite/rpc_packets.h
+++ b/pw_hdlc_lite/public/pw_hdlc_lite/rpc_packets.h
@@ -31,4 +31,4 @@
std::span<std::byte> decode_buffer,
unsigned rpc_address = kDefaultRpcAddress);
-} // namespace pw::hdlc_lite
+} // namespace pw::hdlc_lite
\ No newline at end of file
diff --git a/pw_hdlc_lite/py/pw_hdlc_lite/rpc.py b/pw_hdlc_lite/py/pw_hdlc_lite/rpc.py
index 642f2f3..50ed66d 100644
--- a/pw_hdlc_lite/py/pw_hdlc_lite/rpc.py
+++ b/pw_hdlc_lite/py/pw_hdlc_lite/rpc.py
@@ -88,7 +88,7 @@
class HdlcRpcClient:
"""An RPC client configured to run over HDLC."""
def __init__(self,
- device: BinaryIO,
+ device: Any,
proto_paths_or_modules: Iterable[python_protos.PathOrModule],
output: Callable[[bytes], Any] = write_to_file,
channels: Iterable[pw_rpc.Channel] = None,
@@ -96,9 +96,10 @@
"""Creates an RPC client configured to communicate using HDLC.
Args:
- device: serial.Serial (or any BinaryIO class) for reading/writing data
- proto_paths_or_modules: paths to .proto files or proto modules
- output: where to write "stdout" output from the device
+ device: serial.Serial (or any class that implements read and
+ write) for reading/writing data proto_paths_or_modules: paths
+ to .proto files or proto modules output: where to write
+ "stdout" output from the device
"""
self.device = device
self.protos = python_protos.Library.from_paths(proto_paths_or_modules)
diff --git a/pw_hdlc_lite/py/pw_hdlc_lite/rpc_console.py b/pw_hdlc_lite/py/pw_hdlc_lite/rpc_console.py
index 0dbef7b..2325c61 100644
--- a/pw_hdlc_lite/py/pw_hdlc_lite/rpc_console.py
+++ b/pw_hdlc_lite/py/pw_hdlc_lite/rpc_console.py
@@ -36,7 +36,8 @@
import logging
from pathlib import Path
import sys
-from typing import BinaryIO, Collection, Iterable, Iterator
+from typing import Any, Collection, Iterable, Iterator
+import socket
import IPython # type: ignore
import serial # type: ignore
@@ -45,14 +46,17 @@
_LOG = logging.getLogger(__name__)
+PW_RPC_MAX_PACKET_SIZE = 256
+SOCKET_SERVER = 'localhost'
+SOCKET_PORT = 33000
+MKFIFO_MODE = 0o666
+
def _parse_args():
"""Parses and returns the command line arguments."""
parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('-d',
- '--device',
- required=True,
- help='the serial port to use')
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument('-d', '--device', help='the serial port to use')
parser.add_argument('-b',
'--baudrate',
type=int,
@@ -65,6 +69,11 @@
default=sys.stdout.buffer,
help=('The file to which to write device output (HDLC channel 1); '
'provide - or omit for stdout.'))
+ group.add_argument('-s',
+ '--socket-addr',
+ type=str,
+ help='use socket to connect to server, type default for\
+ localhost:33000, or manually input the server address:port')
parser.add_argument('proto_globs',
nargs='+',
help='glob pattern for .proto files')
@@ -91,8 +100,32 @@
local_ns=local_variables, module=argparse.Namespace())
+class SocketClientImpl():
+ def __init__(self, config: str):
+
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ socket_server = ''
+ socket_port = 0
+ if config == 'default':
+ socket_server = SOCKET_SERVER
+ socket_port = SOCKET_PORT
+ else:
+ socket_server, socket_port_str = config.split(':')
+ try:
+ socket_port = int(socket_port_str)
+ except ValueError as err:
+ raise Exception('Invalid port number provided') from err
+ self.socket.connect((socket_server, socket_port))
+
+ def write(self, data: bytes):
+ self.socket.sendall(data)
+
+ def read(self, num_bytes: int = PW_RPC_MAX_PACKET_SIZE):
+ return self.socket.recv(num_bytes)
+
+
def console(device: str, baudrate: int, proto_globs: Collection[str],
- output: BinaryIO) -> int:
+ socket_addr: str, output: Any) -> int:
"""Starts an interactive RPC console for HDLC."""
# argparse.FileType doesn't correctly handle '-' for binary files.
if output is sys.stdout:
@@ -112,8 +145,17 @@
_LOG.debug('Found %d .proto files found with %s', len(protos),
', '.join(proto_globs))
+ if socket_addr is None:
+ client_device = serial.Serial(device, baudrate)
+ else:
+ try:
+ client_device = SocketClientImpl(socket_addr)
+ except ValueError as err:
+ print("ValueError: {0}".format(err), file=sys.stderr)
+ sys.exit(1)
+
_start_ipython_terminal(
- HdlcRpcClient(serial.Serial(device, baudrate), protos,
+ HdlcRpcClient(client_device, protos,
lambda data: write_to_file(data, output)))
return 0
diff --git a/pw_hdlc_lite/rpc_example/BUILD.gn b/pw_hdlc_lite/rpc_example/BUILD.gn
index e0e31fa..42f6c0c 100644
--- a/pw_hdlc_lite/rpc_example/BUILD.gn
+++ b/pw_hdlc_lite/rpc_example/BUILD.gn
@@ -31,6 +31,7 @@
deps = [
"$dir_pw_rpc:server",
"$dir_pw_rpc/nanopb:echo_service",
+ "$dir_pw_rpc/system_server",
"..:pw_rpc",
dir_pw_hdlc_lite,
dir_pw_log,
diff --git a/pw_hdlc_lite/rpc_example/docs.rst b/pw_hdlc_lite/rpc_example/docs.rst
index 492ec0e..11aa87b 100644
--- a/pw_hdlc_lite/rpc_example/docs.rst
+++ b/pw_hdlc_lite/rpc_example/docs.rst
@@ -98,3 +98,37 @@
The payload was msg: "Hello"
The device says: Goodbye!
+
+-------------------------
+Local RPC example project
+-------------------------
+
+This example is similar to the above example, except it use socket to
+connect server and client running on the host.
+
+1. Build Pigweed
+================
+Activate the Pigweed environment and build the code.
+
+.. code-block:: sh
+
+ source activate.sh
+ gn gen out
+ pw watch
+
+2. Start client side and server side
+====================================
+
+Run pw_rpc client (i.e. use echo.proto)
+
+.. code-block:: sh
+
+ python -m pw_hdlc_lite.rpc_console path/to/echo.proto -s localhost:33000
+
+Run pw_rpc server
+
+.. code-block:: sh
+
+ out/host_clang_debug/obj/pw_hdlc_lite/rpc_example/bin/rpc_example
+
+Then you can invoke RPCs from the interactive console on the client side.
\ No newline at end of file
diff --git a/pw_hdlc_lite/rpc_example/hdlc_rpc_server.cc b/pw_hdlc_lite/rpc_example/hdlc_rpc_server.cc
index 5d3f376..0da47ff 100644
--- a/pw_hdlc_lite/rpc_example/hdlc_rpc_server.cc
+++ b/pw_hdlc_lite/rpc_example/hdlc_rpc_server.cc
@@ -17,59 +17,32 @@
#include <string_view>
#include "pw_hdlc_lite/encoder.h"
-#include "pw_hdlc_lite/rpc_channel.h"
#include "pw_hdlc_lite/rpc_packets.h"
-#include "pw_hdlc_lite/sys_io_stream.h"
#include "pw_log/log.h"
#include "pw_rpc/echo_service_nanopb.h"
#include "pw_rpc/server.h"
-#include "rpc_task_loop.h"
+#include "pw_rpc_system_server/rpc_server.h"
namespace hdlc_example {
namespace {
using std::byte;
-constexpr size_t kMaxTransmissionUnit = 256;
-
-// Used to write HDLC data to pw::sys_io.
-pw::stream::SysIoWriter writer;
-
-// Set up the output channel for the pw_rpc server to use to use.
-pw::hdlc_lite::RpcChannelOutputBuffer<kMaxTransmissionUnit> hdlc_channel_output(
- writer, pw::hdlc_lite::kDefaultRpcAddress, "HDLC channel");
-
-pw::rpc::Channel channels[] = {
- pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
-
-// Declare the pw_rpc server with the HDLC channel.
-pw::rpc::Server server(channels);
-
pw::rpc::EchoService echo_service;
-void RegisterServices() { server.RegisterService(echo_service); }
-
-static void PumpServices(void*){};
+void RegisterServices() {
+ pw::rpc_system_server::Server().RegisterService(echo_service);
+}
} // namespace
void Start() {
- // Send log messages to HDLC address 1. This prevents logs from interfering
- // with pw_rpc communications.
- pw::log_basic::SetOutput([](std::string_view log) {
- pw::hdlc_lite::WriteInformationFrame(
- 1, std::as_bytes(std::span(log)), writer);
- });
-
+ pw::rpc_system_server::Init();
// Set up the server and start processing data.
RegisterServices();
- // Declare a buffer for decoding incoming HDLC frames.
- std::array<std::byte, kMaxTransmissionUnit> input_buffer;
-
PW_LOG_INFO("Starting pw_rpc server");
- RpcTaskLoop::RunForever(
- server, hdlc_channel_output, input_buffer, PumpServices);
+ pw::rpc_system_server::Start();
}
} // namespace hdlc_example
diff --git a/pw_log_tokenized/base64_over_hdlc.cc b/pw_log_tokenized/base64_over_hdlc.cc
index b8c7bdf..ee2f5d6 100644
--- a/pw_log_tokenized/base64_over_hdlc.cc
+++ b/pw_log_tokenized/base64_over_hdlc.cc
@@ -20,7 +20,7 @@
#include <span>
#include "pw_hdlc_lite/encoder.h"
-#include "pw_hdlc_lite/sys_io_stream.h"
+#include "pw_stream/sys_io_stream.h"
#include "pw_tokenizer/base64.h"
#include "pw_tokenizer/tokenize_to_global_handler_with_payload.h"
diff --git a/pw_rpc/system_server/BUILD b/pw_rpc/system_server/BUILD
new file mode 100644
index 0000000..a2fb600
--- /dev/null
+++ b/pw_rpc/system_server/BUILD
@@ -0,0 +1,45 @@
+# Copyright 2020 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.
+
+load(
+ "//pw_build:pigweed.bzl",
+ "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+pw_cc_library(
+ name = "facade",
+ hdrs = ["public/pw_rpc_system_server/rpc_server.h"],
+ includes = ["public"],
+ deps = [
+ "//pw_span",
+ "//pw_status",
+ ],
+)
+
+pw_cc_library(
+ name = "system_server",
+ hdrs = ["public/pw_rpc_system_server/rpc_server.h"],
+ deps = [
+ ":facade",
+ "//pw_span",
+ "//pw_status",
+ # For now, hard-code to depend on stdio until bazel build is updated
+ # to support multiple target configurations.
+ "//pw_rpc/system_server:sys_io",
+ ],
+)
diff --git a/pw_rpc/system_server/BUILD.gn b/pw_rpc/system_server/BUILD.gn
new file mode 100644
index 0000000..b5b3c8a
--- /dev/null
+++ b/pw_rpc/system_server/BUILD.gn
@@ -0,0 +1,44 @@
+# Copyright 2020 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.
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/facade.gni")
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+declare_args() {
+ # Backend for the pw_rpc_system_server module.
+ pw_rpc_system_server_BACKEND = ""
+}
+
+config("default_config") {
+ include_dirs = [ "public" ]
+}
+
+pw_facade("system_server") {
+ backend = pw_rpc_system_server_BACKEND
+ public_configs = [ ":default_config" ]
+ public_deps = [
+ "$dir_pw_rpc:server",
+ "$dir_pw_stream",
+ ]
+ public = [ "public/pw_rpc_system_server/rpc_server.h" ]
+}
+
+pw_source_set("socket") {
+ public_deps = [ "pw_rpc_system_server_socket" ]
+}
+
+pw_source_set("sys_io") {
+ public_deps = [ "pw_rpc_system_server_sys_io" ]
+}
diff --git a/pw_rpc/system_server/public/pw_rpc_system_server/rpc_server.h b/pw_rpc/system_server/public/pw_rpc_system_server/rpc_server.h
new file mode 100644
index 0000000..5a3071b
--- /dev/null
+++ b/pw_rpc/system_server/public/pw_rpc_system_server/rpc_server.h
@@ -0,0 +1,25 @@
+// Copyright 2020 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_rpc/server.h"
+#include "pw_stream/stream.h"
+namespace pw::rpc_system_server {
+// Initialization.
+void Init();
+// Get the reference of RPC Server instance.
+pw::rpc::Server& Server();
+// Start the server and processing packets. May not return.
+Status Start();
+} // namespace pw::rpc_system_server
diff --git a/pw_rpc/system_server/pw_rpc_system_server_socket/BUILD b/pw_rpc/system_server/pw_rpc_system_server_socket/BUILD
new file mode 100644
index 0000000..0d23a86
--- /dev/null
+++ b/pw_rpc/system_server/pw_rpc_system_server_socket/BUILD
@@ -0,0 +1,31 @@
+# Copyright 2020 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.
+
+load(
+ "//pw_build:pigweed.bzl",
+ "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"]) # Apache License 2.0
+
+pw_cc_library(
+ name = "pw_rpc_system_server_socket",
+ srcs = ["rpc_server.cc"],
+ deps = [
+ "//pw_rpc/system_server:facade",
+ "//pw_hdlc_lite:pw_rpc",
+ ],
+)
diff --git a/pw_rpc/system_server/pw_rpc_system_server_socket/BUILD.gn b/pw_rpc/system_server/pw_rpc_system_server_socket/BUILD.gn
new file mode 100644
index 0000000..3e54063
--- /dev/null
+++ b/pw_rpc/system_server/pw_rpc_system_server_socket/BUILD.gn
@@ -0,0 +1,26 @@
+# Copyright 2020 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.
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+pw_source_set("pw_rpc_system_server_socket") {
+ deps = [
+ "$dir_pw_hdlc_lite:pw_rpc",
+ "$dir_pw_rpc/system_server:facade",
+ "$dir_pw_stream:socket_stream",
+ dir_pw_log,
+ ]
+ sources = [ "rpc_server.cc" ]
+}
diff --git a/pw_rpc/system_server/pw_rpc_system_server_socket/rpc_server.cc b/pw_rpc/system_server/pw_rpc_system_server_socket/rpc_server.cc
new file mode 100644
index 0000000..1f0970c
--- /dev/null
+++ b/pw_rpc/system_server/pw_rpc_system_server_socket/rpc_server.cc
@@ -0,0 +1,65 @@
+// Copyright 2020 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_rpc_system_server/rpc_server.h"
+
+#include "pw_hdlc_lite/rpc_channel.h"
+#include "pw_hdlc_lite/rpc_packets.h"
+#include "pw_log/log.h"
+#include "pw_stream/socket_stream.h"
+
+namespace pw::rpc_system_server {
+constexpr size_t kMaxTransmissionUnit = 256;
+constexpr uint32_t kMaxHdlcFrameSize = 256;
+constexpr uint32_t kSocketPort = 33000;
+inline constexpr uint8_t kDefaultRpcAddress = 'R';
+
+pw::stream::SocketStream socket_stream;
+pw::hdlc_lite::RpcChannelOutputBuffer<kMaxTransmissionUnit> hdlc_channel_output(
+ socket_stream, pw::hdlc_lite::kDefaultRpcAddress, "HDLC channel");
+pw::rpc::Channel channels[] = {
+ pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
+pw::rpc::Server server(channels);
+
+void Init() {
+ pw::log_basic::SetOutput([](std::string_view log) {
+ pw::hdlc_lite::WriteInformationFrame(
+ 1, std::as_bytes(std::span(log)), socket_stream);
+ });
+
+ socket_stream.Init(kSocketPort);
+}
+
+pw::rpc::Server& Server() { return server; }
+
+Status Start() {
+ // Declare a buffer for decoding incoming HDLC frames.
+ std::array<std::byte, kMaxTransmissionUnit> input_buffer;
+ hdlc_lite::Decoder decoder(input_buffer);
+
+ while (true) {
+ std::array<std::byte, kMaxHdlcFrameSize> data;
+ auto ret_val = socket_stream.Read(data);
+ if (ret_val.ok()) {
+ for (auto byte : ret_val.value()) {
+ if (auto result = decoder.Process(byte); result.ok()) {
+ hdlc_lite::Frame& frame = result.value();
+ if (frame.address() == kDefaultRpcAddress) {
+ server.ProcessPacket(frame.data(), hdlc_channel_output);
+ }
+ }
+ }
+ }
+ }
+}
+} // namespace pw::rpc_system_server
diff --git a/pw_rpc/system_server/pw_rpc_system_server_sys_io/BUILD b/pw_rpc/system_server/pw_rpc_system_server_sys_io/BUILD
new file mode 100644
index 0000000..60834c2
--- /dev/null
+++ b/pw_rpc/system_server/pw_rpc_system_server_sys_io/BUILD
@@ -0,0 +1,31 @@
+# Copyright 2020 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.
+
+load(
+ "//pw_build:pigweed.bzl",
+ "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"]) # Apache License 2.0
+
+pw_cc_library(
+ name = "pw_rpc_system_server_sys_io",
+ srcs = ["rpc_server.cc"],
+ deps = [
+ "//pw_rpc/system_server:facade",
+ "//pw_hdlc_lite:pw_rpc",
+ ],
+)
diff --git a/pw_rpc/system_server/pw_rpc_system_server_sys_io/BUILD.gn b/pw_rpc/system_server/pw_rpc_system_server_sys_io/BUILD.gn
new file mode 100644
index 0000000..4ea0203
--- /dev/null
+++ b/pw_rpc/system_server/pw_rpc_system_server_sys_io/BUILD.gn
@@ -0,0 +1,26 @@
+# Copyright 2020 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.
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+pw_source_set("pw_rpc_system_server_sys_io") {
+ deps = [
+ "$dir_pw_hdlc_lite:pw_rpc",
+ "$dir_pw_rpc/system_server:facade",
+ "$dir_pw_stream:sys_io_stream",
+ dir_pw_log,
+ ]
+ sources = [ "rpc_server.cc" ]
+}
diff --git a/pw_rpc/system_server/pw_rpc_system_server_sys_io/rpc_server.cc b/pw_rpc/system_server/pw_rpc_system_server_sys_io/rpc_server.cc
new file mode 100644
index 0000000..528b604
--- /dev/null
+++ b/pw_rpc/system_server/pw_rpc_system_server_sys_io/rpc_server.cc
@@ -0,0 +1,68 @@
+// Copyright 2020 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_rpc_system_server/rpc_server.h"
+
+#include "pw_hdlc_lite/rpc_channel.h"
+#include "pw_hdlc_lite/rpc_packets.h"
+#include "pw_log/log.h"
+#include "pw_stream/sys_io_stream.h"
+
+namespace pw::rpc_system_server {
+constexpr size_t kMaxTransmissionUnit = 256;
+constexpr uint32_t kMaxHdlcFrameSize = 256;
+inline constexpr uint8_t kDefaultRpcAddress = 'R';
+
+// Used to write HDLC data to pw::sys_io.
+pw::stream::SysIoWriter writer;
+pw::stream::SysIoReader reader;
+
+// Set up the output channel for the pw_rpc server to use.
+pw::hdlc_lite::RpcChannelOutputBuffer<kMaxTransmissionUnit> hdlc_channel_output(
+ writer, pw::hdlc_lite::kDefaultRpcAddress, "HDLC channel");
+pw::rpc::Channel channels[] = {
+ pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
+pw::rpc::Server server(channels);
+
+void Init() {
+ // Send log messages to HDLC address 1. This prevents logs from interfering
+ // with pw_rpc communications.
+ pw::log_basic::SetOutput([](std::string_view log) {
+ pw::hdlc_lite::WriteInformationFrame(
+ 1, std::as_bytes(std::span(log)), writer);
+ });
+}
+
+pw::rpc::Server& Server() { return server; }
+
+Status Start() {
+ // Declare a buffer for decoding incoming HDLC frames.
+ std::array<std::byte, kMaxTransmissionUnit> input_buffer;
+ hdlc_lite::Decoder decoder(input_buffer);
+
+ while (true) {
+ std::byte byte;
+ Status ret_val = pw::sys_io::ReadByte(&byte);
+ if (!ret_val.ok()) {
+ return ret_val;
+ }
+ if (auto result = decoder.Process(byte); result.ok()) {
+ hdlc_lite::Frame& frame = result.value();
+ if (frame.address() == kDefaultRpcAddress) {
+ server.ProcessPacket(frame.data(), hdlc_channel_output);
+ }
+ }
+ }
+}
+
+} // namespace pw::rpc_system_server
diff --git a/pw_stream/BUILD b/pw_stream/BUILD
index a28ebae..242d460 100644
--- a/pw_stream/BUILD
+++ b/pw_stream/BUILD
@@ -44,6 +44,25 @@
],
)
+pw_cc_library(
+ name = "pw_stream_socket",
+ srcs = ["socket_stream.cc"],
+ hdrs = ["public/pw_stream/socket_stream.h"],
+ deps = [
+ "//pw_sys_io",
+ "//pw_stream",
+ ],
+)
+
+pw_cc_library(
+ name = "pw_stream_sys_io",
+ hdrs = ["public/pw_stream/sys_io_stream.h"],
+ deps = [
+ "//pw_sys_io",
+ "//pw_stream",
+ ],
+)
+
pw_cc_test(
name = "memory_stream_test",
srcs = [
diff --git a/pw_stream/BUILD.gn b/pw_stream/BUILD.gn
index cfd3583..a8efd15 100644
--- a/pw_stream/BUILD.gn
+++ b/pw_stream/BUILD.gn
@@ -39,6 +39,25 @@
]
}
+pw_source_set("socket_stream") {
+ public_configs = [ ":default_config" ]
+ deps = [
+ "$dir_pw_stream",
+ "$dir_pw_sys_io",
+ ]
+ sources = [ "socket_stream.cc" ]
+ public = [ "public/pw_stream/socket_stream.h" ]
+}
+
+pw_source_set("sys_io_stream") {
+ public_configs = [ ":default_config" ]
+ deps = [
+ "$dir_pw_stream",
+ "$dir_pw_sys_io",
+ ]
+ public = [ "public/pw_stream/sys_io_stream.h" ]
+}
+
pw_doc_group("docs") {
sources = [ "docs.rst" ]
}
diff --git a/pw_stream/public/pw_stream/socket_stream.h b/pw_stream/public/pw_stream/socket_stream.h
new file mode 100644
index 0000000..ac8e9e0
--- /dev/null
+++ b/pw_stream/public/pw_stream/socket_stream.h
@@ -0,0 +1,60 @@
+// Copyright 2020 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 <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <array>
+#include <cstddef>
+#include <limits>
+#include <span>
+
+#include "pw_stream/stream.h"
+
+namespace pw::stream {
+
+static constexpr int kExitCode = -1;
+static constexpr int kInvalidFd = -1;
+
+class SocketStream : public Writer, public Reader {
+ public:
+ explicit SocketStream() {}
+
+ // Listen to the port and return after a client is connected
+ Status Init(uint16_t port);
+
+ size_t ConservativeWriteLimit() const override {
+ return std::numeric_limits<size_t>::max();
+ }
+
+ size_t ConservativeReadLimit() const override {
+ return std::numeric_limits<size_t>::max();
+ }
+
+ private:
+ Status DoWrite(std::span<const std::byte> data) override;
+
+ StatusWithSize DoRead(ByteSpan dest) override;
+
+ uint16_t listen_port_ = 0;
+ int socket_fd_ = kInvalidFd;
+ int conn_fd_ = kInvalidFd;
+ struct sockaddr_in sockaddr_client_;
+};
+
+} // namespace pw::stream
diff --git a/pw_hdlc_lite/public/pw_hdlc_lite/sys_io_stream.h b/pw_stream/public/pw_stream/sys_io_stream.h
similarity index 80%
rename from pw_hdlc_lite/public/pw_hdlc_lite/sys_io_stream.h
rename to pw_stream/public/pw_stream/sys_io_stream.h
index 219d8eb..e4c79ce 100644
--- a/pw_hdlc_lite/public/pw_hdlc_lite/sys_io_stream.h
+++ b/pw_stream/public/pw_stream/sys_io_stream.h
@@ -35,4 +35,16 @@
}
};
+class SysIoReader : public Reader {
+ public:
+ size_t ConservativeReadLimit() const override {
+ return std::numeric_limits<size_t>::max();
+ }
+
+ private:
+ StatusWithSize DoRead(ByteSpan dest) override {
+ return pw::sys_io::ReadBytes(dest);
+ }
+};
+
} // namespace pw::stream
diff --git a/pw_stream/socket_stream.cc b/pw_stream/socket_stream.cc
new file mode 100644
index 0000000..1146797
--- /dev/null
+++ b/pw_stream/socket_stream.cc
@@ -0,0 +1,71 @@
+// Copyright 2020 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_stream/socket_stream.h"
+namespace pw::stream {
+
+static constexpr uint32_t kMaxConcurrentUser = 1;
+
+// Listen to the port and return after a client is connected
+Status SocketStream::Init(uint16_t port) {
+ listen_port_ = port;
+ socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
+ if (socket_fd_ == kInvalidFd) {
+ return Status::Internal();
+ }
+
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(listen_port_);
+ addr.sin_addr.s_addr = INADDR_ANY;
+
+ int result =
+ bind(socket_fd_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
+ if (result < 0) {
+ return Status::Internal();
+ }
+
+ result = listen(socket_fd_, kMaxConcurrentUser);
+ if (result < 0) {
+ return Status::Internal();
+ }
+
+ socklen_t len = sizeof(sockaddr_client_);
+
+ conn_fd_ =
+ accept(socket_fd_, reinterpret_cast<sockaddr*>(&sockaddr_client_), &len);
+ if (conn_fd_ < 0) {
+ return Status::Internal();
+ }
+ return Status::Ok();
+}
+
+Status SocketStream::DoWrite(std::span<const std::byte> data) {
+ ssize_t bytes_sent = send(conn_fd_, data.data(), data.size_bytes(), 0);
+
+ if (bytes_sent < 0 || static_cast<uint64_t>(bytes_sent) != data.size()) {
+ return Status::Internal();
+ }
+ return Status::Ok();
+}
+
+StatusWithSize SocketStream::DoRead(ByteSpan dest) {
+ ssize_t bytes_rcvd = recv(conn_fd_, dest.data(), dest.size_bytes(), 0);
+ if (bytes_rcvd < 0) {
+ return StatusWithSize::Internal();
+ }
+ return StatusWithSize::Ok(bytes_rcvd);
+}
+
+}; // namespace pw::stream
\ No newline at end of file
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index cd89d8f..9cf16d3 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -35,6 +35,9 @@
# Configure backend for pw_sys_io facade.
pw_sys_io_BACKEND = "$dir_pw_sys_io_stdio"
+ # Configure backend for pw_rpc_system_server.
+ pw_rpc_system_server_BACKEND = "$dir_pw_rpc/system_server:socket"
+
# Configure backend for trace facade.
pw_trace_BACKEND = "$dir_pw_trace_tokenized"
diff --git a/targets/stm32f429i-disc1/target_toolchains.gni b/targets/stm32f429i-disc1/target_toolchains.gni
index b6a95a7..6344f81 100644
--- a/targets/stm32f429i-disc1/target_toolchains.gni
+++ b/targets/stm32f429i-disc1/target_toolchains.gni
@@ -48,6 +48,7 @@
pw_cpu_exception_SUPPORT_BACKEND = "$dir_pw_cpu_exception_armv7m:support"
pw_log_BACKEND = dir_pw_log_basic
pw_sys_io_BACKEND = dir_pw_sys_io_baremetal_stm32f429
+ pw_rpc_system_server_BACKEND = "$dir_pw_rpc/system_server:sys_io"
pw_malloc_BACKEND = dir_pw_malloc_freelist
pw_boot_armv7m_LINK_CONFIG_DEFINES = [