diff --git a/src/viam/sdk/robot/client.cpp b/src/viam/sdk/robot/client.cpp index f0833f85e..a1e49e256 100644 --- a/src/viam/sdk/robot/client.cpp +++ b/src/viam/sdk/robot/client.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -36,9 +37,16 @@ namespace viam { namespace sdk { using google::protobuf::RepeatedPtrField; +using viam::app::packages::v1::PackageType; using viam::common::v1::Transform; +using viam::robot::v1::ConfigStatus; using viam::robot::v1::FrameSystemConfig; +using viam::robot::v1::GetMachineStatusResponse; +using viam::robot::v1::JobStatus; +using viam::robot::v1::ModuleStatus; using viam::robot::v1::Operation; +using viam::robot::v1::PackageStatus; +using viam::robot::v1::ResourceStatus; using viam::robot::v1::RobotService; // gRPC responses are frequently coming back with a spurious `Stream removed` @@ -76,6 +84,164 @@ RobotClient::operation from_proto_impl::operator()(const Operation* p return op; } +RobotClient::resource_status from_proto_impl::operator()( + const ResourceStatus* proto) const { + RobotClient::resource_status rs; + rs.name = from_proto(proto->name()); + switch (proto->state()) { + case robot::v1::ResourceStatus_State_STATE_UNCONFIGURED: + rs.state = RobotClient::resource_status::resource_state::k_unconfigured; + break; + case robot::v1::ResourceStatus_State_STATE_CONFIGURING: + rs.state = RobotClient::resource_status::resource_state::k_configuring; + break; + case robot::v1::ResourceStatus_State_STATE_READY: + rs.state = RobotClient::resource_status::resource_state::k_ready; + break; + case robot::v1::ResourceStatus_State_STATE_REMOVING: + rs.state = RobotClient::resource_status::resource_state::k_removing; + break; + case robot::v1::ResourceStatus_State_STATE_UNHEALTHY: + rs.state = RobotClient::resource_status::resource_state::k_unhealthy; + break; + case robot::v1::ResourceStatus_State_STATE_UNSPECIFIED: + default: + rs.state = RobotClient::resource_status::resource_state::k_unspecified; + } + if (proto->has_last_updated()) { + rs.last_updated = from_proto(proto->last_updated()); + } + rs.revision = proto->revision(); + rs.error = proto->error(); + return rs; +} + +RobotClient::config_status from_proto_impl::operator()( + const ConfigStatus* proto) const { + RobotClient::config_status cs; + cs.revision = proto->revision(); + if (proto->has_last_updated()) { + cs.last_updated = from_proto(proto->last_updated()); + } + return cs; +} + +RobotClient::module_status from_proto_impl::operator()( + const ModuleStatus* proto) const { + RobotClient::module_status ms; + ms.module_name = proto->module_name(); + switch (proto->state()) { + case robot::v1::ModuleStatus_State_STATE_PENDING: + ms.state = RobotClient::module_status::module_state::k_pending; + break; + case robot::v1::ModuleStatus_State_STATE_STARTING: + ms.state = RobotClient::module_status::module_state::k_starting; + break; + case robot::v1::ModuleStatus_State_STATE_READY: + ms.state = RobotClient::module_status::module_state::k_ready; + break; + case robot::v1::ModuleStatus_State_STATE_UNHEALTHY: + ms.state = RobotClient::module_status::module_state::k_unhealthy; + break; + case robot::v1::ModuleStatus_State_STATE_CLOSING: + ms.state = RobotClient::module_status::module_state::k_closing; + break; + case robot::v1::ModuleStatus_State_STATE_UNSPECIFIED: + default: + ms.state = RobotClient::module_status::module_state::k_unspecified; + } + if (proto->has_last_updated()) { + ms.last_updated = from_proto(proto->last_updated()); + } + ms.error = proto->error(); + ms.consecutive_failures = proto->consecutive_failures(); + return ms; +} + +RobotClient::package_status from_proto_impl::operator()( + const PackageStatus* proto) const { + RobotClient::package_status ps; + ps.name = proto->name(); + switch (proto->type()) { + case PackageType::PACKAGE_TYPE_ARCHIVE: + ps.type = RobotClient::package_status::package_type::k_archive; + break; + case PackageType::PACKAGE_TYPE_ML_MODEL: + ps.type = RobotClient::package_status::package_type::k_ml_model; + break; + case PackageType::PACKAGE_TYPE_MODULE: + ps.type = RobotClient::package_status::package_type::k_module; + break; + case PackageType::PACKAGE_TYPE_SLAM_MAP: + ps.type = RobotClient::package_status::package_type::k_slam_map; + break; + case PackageType::PACKAGE_TYPE_ML_TRAINING: + ps.type = RobotClient::package_status::package_type::k_ml_training; + break; + case PackageType::PACKAGE_TYPE_UNSPECIFIED: + default: + ps.type = RobotClient::package_status::package_type::k_unspecified; + } + switch (proto->state()) { + case robot::v1::PackageStatus_State_STATE_DOWNLOADING: + ps.state = RobotClient::package_status::package_state::k_downloading; + break; + case robot::v1::PackageStatus_State_STATE_LOADING: + ps.state = RobotClient::package_status::package_state::k_loading; + break; + case robot::v1::PackageStatus_State_STATE_FIRST_RUN: + ps.state = RobotClient::package_status::package_state::k_first_run; + break; + case robot::v1::PackageStatus_State_STATE_READY: + ps.state = RobotClient::package_status::package_state::k_ready; + break; + case robot::v1::PackageStatus_State_STATE_FAILED: + ps.state = RobotClient::package_status::package_state::k_failed; + break; + case robot::v1::PackageStatus_State_STATE_UNSPECIFIED: + default: + ps.state = RobotClient::package_status::package_state::k_unspecified; + } + ps.error = proto->error(); + if (proto->has_last_updated()) { + ps.last_updated = from_proto(proto->last_updated()); + } + ps.version = proto->version(); + ps.bytes_downloaded = proto->bytes_downloaded(); + ps.total_bytes = proto->total_bytes(); + return ps; +} + +RobotClient::job_status from_proto_impl::operator()(const JobStatus* proto) const { + RobotClient::job_status js; + js.job_name = proto->job_name(); + js.recent_successful_runs = impl::from_repeated_field(proto->recent_successful_runs()); + js.recent_failed_runs = impl::from_repeated_field(proto->recent_failed_runs()); + return js; +} + +RobotClient::machine_status from_proto_impl::operator()( + const GetMachineStatusResponse* proto) const { + RobotClient::machine_status ms; + switch (proto->state()) { + case robot::v1::GetMachineStatusResponse_State_STATE_INITIALIZING: + ms.state = RobotClient::status::k_initializing; + break; + case robot::v1::GetMachineStatusResponse_State_STATE_RUNNING: + ms.state = RobotClient::status::k_running; + break; + case robot::v1::GetMachineStatusResponse_State_STATE_UNSPECIFIED: + default: + ms.state = RobotClient::status::k_unspecified; + } + ms.resources = impl::from_repeated_field(proto->resources()); + ms.config = from_proto(proto->config()); + ms.modules = impl::from_repeated_field(proto->modules()); + ms.packages = impl::from_repeated_field(proto->packages()); + ms.job_statuses = impl::from_repeated_field(proto->job_statuses()); + return ms; +} + } // namespace proto_convert_details bool operator==(const RobotClient::frame_system_config& lhs, @@ -89,6 +255,40 @@ bool operator==(const RobotClient::operation& lhs, const RobotClient::operation& lhs.arguments == rhs.arguments && lhs.started == rhs.started; } +bool operator==(const RobotClient::resource_status& lhs, const RobotClient::resource_status& rhs) { + return lhs.name == rhs.name && lhs.state == rhs.state && lhs.last_updated == rhs.last_updated && + lhs.revision == rhs.revision && lhs.error == rhs.error; +} + +bool operator==(const RobotClient::config_status& lhs, const RobotClient::config_status& rhs) { + return lhs.revision == rhs.revision && lhs.last_updated == rhs.last_updated; +} + +bool operator==(const RobotClient::module_status& lhs, const RobotClient::module_status& rhs) { + return lhs.module_name == rhs.module_name && lhs.state == rhs.state && + lhs.last_updated == rhs.last_updated && lhs.error == rhs.error && + lhs.consecutive_failures == rhs.consecutive_failures; +} + +bool operator==(const RobotClient::package_status& lhs, const RobotClient::package_status& rhs) { + return lhs.name == rhs.name && lhs.type == rhs.type && lhs.state == rhs.state && + lhs.error == rhs.error && lhs.last_updated == rhs.last_updated && + lhs.version == rhs.version && lhs.bytes_downloaded == rhs.bytes_downloaded && + lhs.total_bytes == rhs.total_bytes; +} + +bool operator==(const RobotClient::job_status& lhs, const RobotClient::job_status& rhs) { + return lhs.job_name == rhs.job_name && + lhs.recent_successful_runs == rhs.recent_successful_runs && + lhs.recent_failed_runs == rhs.recent_failed_runs; +} + +bool operator==(const RobotClient::machine_status& lhs, const RobotClient::machine_status& rhs) { + return lhs.state == rhs.state && lhs.resources == rhs.resources && lhs.config == rhs.config && + lhs.modules == rhs.modules && lhs.packages == rhs.packages && + lhs.job_statuses == rhs.job_statuses; +} + struct RobotClient::impl { impl(std::unique_ptr stub, ViamChannel& channel) : stub(std::move(stub)), channel_(&channel) {} @@ -488,19 +688,9 @@ pose_in_frame RobotClient::get_pose(const std::string& component_name, .invoke([](const auto& resp) { return from_proto(resp.pose()); }); } -RobotClient::status RobotClient::get_machine_status() const { +RobotClient::machine_status RobotClient::get_machine_status() const { return impl::client_helper(impl_, &RobotService::Stub::GetMachineStatus) - .invoke([](const auto& resp) { - switch (resp.state()) { - case robot::v1::GetMachineStatusResponse_State_STATE_INITIALIZING: - return RobotClient::status::k_initializing; - case robot::v1::GetMachineStatusResponse_State_STATE_RUNNING: - return RobotClient::status::k_running; - case robot::v1::GetMachineStatusResponse_State_STATE_UNSPECIFIED: - default: - return RobotClient::status::k_unspecified; - } - }); + .invoke([](const auto& resp) { return from_proto(resp); }); } } // namespace sdk diff --git a/src/viam/sdk/robot/client.hpp b/src/viam/sdk/robot/client.hpp index 9416b3d89..eea023e4b 100644 --- a/src/viam/sdk/robot/client.hpp +++ b/src/viam/sdk/robot/client.hpp @@ -23,8 +23,14 @@ namespace viam { namespace robot { namespace v1 { +class ConfigStatus; class FrameSystemConfig; +class GetMachineStatusResponse; +class JobStatus; +class ModuleStatus; class Operation; +class PackageStatus; +class ResourceStatus; class SendTracesRequest; } // namespace v1 @@ -73,6 +79,95 @@ class RobotClient { friend bool operator==(const operation& lhs, const operation& rhs); }; + struct resource_status { + enum class resource_state : uint8_t { + k_unspecified, + k_unconfigured, + k_configuring, + k_ready, + k_removing, + k_unhealthy, + }; + + Name name; + resource_state state; + boost::optional last_updated; + std::string revision; + std::string error; + friend bool operator==(const resource_status& lhs, const resource_status& rhs); + }; + + struct config_status { + std::string revision; + boost::optional last_updated; + friend bool operator==(const config_status& lhs, const config_status& rhs); + }; + + struct module_status { + enum class module_state : uint8_t { + k_unspecified, + k_pending, + k_starting, + k_ready, + k_unhealthy, + k_closing, + }; + + std::string module_name; + module_state state; + boost::optional last_updated; + std::string error; + std::uint32_t consecutive_failures; + friend bool operator==(const module_status& lhs, const module_status& rhs); + }; + + struct package_status { + enum class package_type : uint8_t { + k_unspecified, + k_archive, + k_ml_model, + k_module, + k_slam_map, + k_ml_training, + }; + + enum class package_state : uint8_t { + k_unspecified, + k_downloading, + k_loading, + k_first_run, + k_ready, + k_failed, + }; + + std::string name; + package_type type; + package_state state; + std::string error; + boost::optional last_updated; + std::string version; + std::uint64_t bytes_downloaded; + std::uint64_t total_bytes; + friend bool operator==(const package_status& lhs, const package_status& rhs); + }; + + struct job_status { + std::string job_name; + std::vector recent_successful_runs; + std::vector recent_failed_runs; + friend bool operator==(const job_status& lhs, const job_status& rhs); + }; + + struct machine_status { + status state; + std::vector resources; + config_status config; + std::vector modules; + std::vector packages; + std::vector job_statuses; + friend bool operator==(const machine_status& lhs, const machine_status& rhs); + }; + explicit RobotClient(ViamChannel channel); ~RobotClient(); @@ -183,7 +278,7 @@ class RobotClient { const ProtoStruct& extra); /// @brief gets the current status of the machine - status get_machine_status() const; + machine_status get_machine_status() const; private: friend class ModuleService; @@ -245,6 +340,36 @@ struct from_proto_impl { RobotClient::frame_system_config operator()(const robot::v1::FrameSystemConfig*) const; }; +template <> +struct from_proto_impl { + RobotClient::resource_status operator()(const robot::v1::ResourceStatus*) const; +}; + +template <> +struct from_proto_impl { + RobotClient::config_status operator()(const robot::v1::ConfigStatus*) const; +}; + +template <> +struct from_proto_impl { + RobotClient::module_status operator()(const robot::v1::ModuleStatus*) const; +}; + +template <> +struct from_proto_impl { + RobotClient::package_status operator()(const robot::v1::PackageStatus*) const; +}; + +template <> +struct from_proto_impl { + RobotClient::job_status operator()(const robot::v1::JobStatus*) const; +}; + +template <> +struct from_proto_impl { + RobotClient::machine_status operator()(const robot::v1::GetMachineStatusResponse*) const; +}; + } // namespace proto_convert_details } // namespace sdk } // namespace viam diff --git a/src/viam/sdk/tests/mocks/mock_robot.cpp b/src/viam/sdk/tests/mocks/mock_robot.cpp index 0b9e0b592..5f5193feb 100644 --- a/src/viam/sdk/tests/mocks/mock_robot.cpp +++ b/src/viam/sdk/tests/mocks/mock_robot.cpp @@ -4,6 +4,7 @@ #include +#include #include #include @@ -102,6 +103,70 @@ PoseInFrame mock_proto_transform_response() { return response; } +RobotClient::machine_status mock_machine_status_response() { + RobotClient::machine_status ms; + ms.state = RobotClient::status::k_running; + + RobotClient::resource_status rs; + rs.name = Name({kRDK, kComponent, "motor"}, "", "mock_motor"); + rs.state = RobotClient::resource_status::resource_state::k_ready; + rs.revision = "rev1"; + ms.resources = {rs}; + + ms.config.revision = "config_rev"; + + RobotClient::module_status mod; + mod.module_name = "mock_module"; + mod.state = RobotClient::module_status::module_state::k_ready; + mod.consecutive_failures = 0; + ms.modules = {mod}; + + RobotClient::package_status pkg; + pkg.name = "mock_package"; + pkg.type = RobotClient::package_status::package_type::k_module; + pkg.state = RobotClient::package_status::package_state::k_ready; + pkg.version = "1.0.0"; + pkg.bytes_downloaded = 100; + pkg.total_bytes = 100; + ms.packages = {pkg}; + + RobotClient::job_status job; + job.job_name = "mock_job"; + ms.job_statuses = {job}; + + return ms; +} + +viam::robot::v1::GetMachineStatusResponse mock_proto_machine_status_response() { + viam::robot::v1::GetMachineStatusResponse response; + response.set_state(viam::robot::v1::GetMachineStatusResponse_State_STATE_RUNNING); + + auto* rs = response.add_resources(); + *rs->mutable_name() = to_proto(Name({kRDK, kComponent, "motor"}, "", "mock_motor")); + rs->set_state(viam::robot::v1::ResourceStatus_State_STATE_READY); + rs->set_revision("rev1"); + + response.mutable_config()->set_revision("config_rev"); + + auto* mod = response.add_modules(); + mod->set_module_name("mock_module"); + mod->set_state(viam::robot::v1::ModuleStatus_State_STATE_READY); + mod->set_consecutive_failures(0); + + auto* pkg = response.add_packages(); + pkg->set_name("mock_package"); + pkg->set_type(viam::app::packages::v1::PACKAGE_TYPE_MODULE); + pkg->set_state(viam::robot::v1::PackageStatus_State_STATE_READY); + pkg->set_version("1.0.0"); + pkg->set_bytes_downloaded(100); + pkg->set_total_bytes(100); + + auto* job = response.add_job_statuses(); + job->set_job_name("mock_job"); + + return response; +} + std::vector mock_resource_names_response() { Name camera = Name({kRDK, kComponent, "camera"}, "", "mock_camera"); Name motor = Name({kRDK, kComponent, "motor"}, "", "mock_motor"); @@ -337,7 +402,7 @@ ::grpc::Status MockRobotService::GetMachineStatus( "viam_client info not properly set in metadata"); } - response->set_state(::viam::robot::v1::GetMachineStatusResponse_State_STATE_RUNNING); + *response = mock_proto_machine_status_response(); return ::grpc::Status(); } diff --git a/src/viam/sdk/tests/mocks/mock_robot.hpp b/src/viam/sdk/tests/mocks/mock_robot.hpp index 6ef384be6..09df251eb 100644 --- a/src/viam/sdk/tests/mocks/mock_robot.hpp +++ b/src/viam/sdk/tests/mocks/mock_robot.hpp @@ -68,6 +68,8 @@ std::vector mock_config_response(); std::vector mock_proto_config_response(); pose_in_frame mock_transform_response(); common::v1::PoseInFrame mock_proto_transform_response(); +RobotClient::machine_status mock_machine_status_response(); +viam::robot::v1::GetMachineStatusResponse mock_proto_machine_status_response(); } // namespace robot } // namespace sdktests diff --git a/src/viam/sdk/tests/test_robot.cpp b/src/viam/sdk/tests/test_robot.cpp index 18137d258..b86d69f6a 100644 --- a/src/viam/sdk/tests/test_robot.cpp +++ b/src/viam/sdk/tests/test_robot.cpp @@ -192,8 +192,9 @@ BOOST_AUTO_TEST_CASE(test_get_machine_status) { robot_client_to_mocks_pipeline( [](std::shared_ptr client, MockRobotService& service) -> void { auto status = client->get_machine_status(); + auto mock_status = mock_machine_status_response(); - BOOST_CHECK_EQUAL(status, RobotClient::status::k_running); + BOOST_CHECK(status == mock_status); }); }