diff --git a/kuksa-common b/kuksa-common new file mode 160000 index 0000000..355e2ad --- /dev/null +++ b/kuksa-common @@ -0,0 +1 @@ +Subproject commit 355e2adcd9972c7369f21620576ba9aa85629192 diff --git a/kuksa-cpp-client/.clangd b/kuksa-cpp-client/.clangd new file mode 100644 index 0000000..4712845 --- /dev/null +++ b/kuksa-cpp-client/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: build/Release diff --git a/kuksa-cpp-client/.gitignore b/kuksa-cpp-client/.gitignore new file mode 100644 index 0000000..cbe57b2 --- /dev/null +++ b/kuksa-cpp-client/.gitignore @@ -0,0 +1,3 @@ +build/ +CMakeUserPresets.json +_vss/ diff --git a/kuksa-cpp-client/.pre-commit-config.yaml b/kuksa-cpp-client/.pre-commit-config.yaml new file mode 100644 index 0000000..4e97a26 --- /dev/null +++ b/kuksa-cpp-client/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +default_language_version: + node: 16.15.1 +exclude: + "proto/*" +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-docstring-first + - id: check-yaml + - repo: meta + hooks: + - id: check-hooks-apply + - id: check-useless-excludes + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v17.0.6 + hooks: + - id: clang-format + args: ["-style=file", "-i"] + files: "^.*(\\.cpp|\\.c|\\.h)$" + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.39.0 + hooks: + - id: markdownlint + - repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format diff --git a/kuksa-cpp-client/CMakeLists.txt b/kuksa-cpp-client/CMakeLists.txt new file mode 100644 index 0000000..f664011 --- /dev/null +++ b/kuksa-cpp-client/CMakeLists.txt @@ -0,0 +1,32 @@ +# ******************************************************************************** +# Copyright (c) 2023 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional information +# regarding copyright ownership. +# +# This program and the accompanying materials are made available under the terms +# of the Apache License 2.0 which is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# *******************************************************************************/ + +cmake_minimum_required(VERSION 3.15) +project(kuksaclient) + +# Specify the C++ standard +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +find_package(gRPC REQUIRED) +find_package(Protobuf REQUIRED) +find_package(spdlog REQUIRED) + +add_subdirectory(src) +add_subdirectory(example) + +include(cmake/config.cmake) diff --git a/kuksa-cpp-client/LICENSE b/kuksa-cpp-client/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/kuksa-cpp-client/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. diff --git a/kuksa-cpp-client/ReadMe.md b/kuksa-cpp-client/ReadMe.md new file mode 100644 index 0000000..b63216c --- /dev/null +++ b/kuksa-cpp-client/ReadMe.md @@ -0,0 +1,60 @@ +# CPP based Kuksa client + +A pluggable C++ based library to talk to [kuksa-databroker](https://github.com/eclipse-kuksa/kuksa-databroker) +based on kuksa-proto v2 API + +Supported APIs from proto files + +| Kuksa v2 API | C++ client status | +| ------------------ | -------------------| +| GetValue | :white_check_mark: | +| GetValues | :white_check_mark: | +| Subscribe | :white_check_mark: | +| SubscribeById | :x:| +| Actuate | :white_check_mark: | +| BatchActuate | :x:| +| ListMetadata | :x:| +| PublishValue | :white_check_mark: | +| OpenProviderStream | :x:| +| GetServerInfo | :white_check_mark: | + +> [!NOTE] +> Refer [example_v2](./example/example_v2.cpp) for API usage + +## How to build + +This repo contains a [justfile](https://github.com/casey/just) to help in +setting up and installing dependencies + +```shell +# Install conan and setup remote +just prepare +# pull in the conan deps +just configure +# build the project +just build +``` + +## Proto + +Proto sources are available in the submodule kuksa-common + +## Examples + +There are two examples provided + +- based on kuksa::v1 API [example_v1](example/example_v1.cpp) +- based on kuksa::v2 API [example_v2](example/example_v2.cpp) + +### Running examples + +```shell +just run-example-v2 +``` + +### Debug hints + +```shell +export GRPC_TRACE=all +export GRPC_VERBOSITY=DEBUG +``` diff --git a/kuksa-cpp-client/cmake/config.cmake b/kuksa-cpp-client/cmake/config.cmake new file mode 100644 index 0000000..a67157b --- /dev/null +++ b/kuksa-cpp-client/cmake/config.cmake @@ -0,0 +1,37 @@ +# ******************************************************************************** +# Copyright (c) 2023 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional information +# regarding copyright ownership. +# +# This program and the accompanying materials are made available under the terms +# of the Apache License 2.0 which is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# *******************************************************************************/ +include(GNUInstallDirs) +set(INCLUDE_INSTALL_DIR + ${CMAKE_INSTALL_INCLUDEDIR}/kuksaclient + CACHE PATH "Location of header files") + +set(SYSCONFIG_INSTALL_DIR + ${CMAKE_INSTALL_SYSCONFDIR}/kuksaclient + CACHE PATH "Location of configuration files") + +include(CMakePackageConfigHelpers) + +configure_package_config_file( + ${PROJECT_SOURCE_DIR}/cmake/kuksaclientConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/kuksaclientConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/kuksaclient + PATH_VARS INCLUDE_INSTALL_DIR SYSCONFIG_INSTALL_DIR) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/kuksaclientConfigVersion.cmake + VERSION 0.0.1 + COMPATIBILITY SameMajorVersion) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kuksaclientConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/kuksaclientConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/kuksaclient) diff --git a/kuksa-cpp-client/cmake/kuksaclientConfig.cmake.in b/kuksa-cpp-client/cmake/kuksaclientConfig.cmake.in new file mode 100644 index 0000000..d85ff4e --- /dev/null +++ b/kuksa-cpp-client/cmake/kuksaclientConfig.cmake.in @@ -0,0 +1,18 @@ +#******************************************************************************** +# Copyright (c) 2023 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License 2.0 which is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +#*******************************************************************************/ +set(KUKSACLIENT_VERSION 0.0.1) + +set_and_check(KUKSACLIENT_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") +set_and_check(KUKSACLIENT_SYSCONFIG_DIR "@PACKAGE_SYSCONFIG_INSTALL_DIR@") + +check_required_components(kuksaclient) diff --git a/kuksa-cpp-client/conan/profile_x86_64_Debug.txt b/kuksa-cpp-client/conan/profile_x86_64_Debug.txt new file mode 100644 index 0000000..73fec78 --- /dev/null +++ b/kuksa-cpp-client/conan/profile_x86_64_Debug.txt @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=gcc +compiler.cppstd=gnu17 +compiler.libcxx=libstdc++11 +compiler.version=11 +os=Linux diff --git a/kuksa-cpp-client/conan/profile_x86_64_Release.txt b/kuksa-cpp-client/conan/profile_x86_64_Release.txt new file mode 100644 index 0000000..73fec78 --- /dev/null +++ b/kuksa-cpp-client/conan/profile_x86_64_Release.txt @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=gcc +compiler.cppstd=gnu17 +compiler.libcxx=libstdc++11 +compiler.version=11 +os=Linux diff --git a/kuksa-cpp-client/conanfile.py b/kuksa-cpp-client/conanfile.py new file mode 100644 index 0000000..21ded81 --- /dev/null +++ b/kuksa-cpp-client/conanfile.py @@ -0,0 +1,37 @@ +# /******************************************************************************** +# * Copyright (c) 2022 Contributors to the Eclipse Foundation +# * +# * See the NOTICE file(s) distributed with this work for additional +# * information regarding copyright ownership. +# * +# * This program and the accompanying materials are made available under the +# * terms of the Apache License 2.0 which is available at +# * http://www.apache.org/licenses/LICENSE-2.0 +# * +# * SPDX-License-Identifier: Apache-2.0 +# ********************************************************************************/ + + +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout + + +class KuksaCppClient(ConanFile): + name = "kuksa-cpp-client" + version = "0.1" + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeToolchain", "CMakeDeps" + + def layout(self): + cmake_layout(self) + + # Dependencies + def requirements(self): + self.requires("grpc/1.50.0") + self.requires("spdlog/1.15.0") + + # Building with CMake + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() diff --git a/kuksa-cpp-client/example/CMakeLists.txt b/kuksa-cpp-client/example/CMakeLists.txt new file mode 100644 index 0000000..072a35a --- /dev/null +++ b/kuksa-cpp-client/example/CMakeLists.txt @@ -0,0 +1,25 @@ +# ******************************************************************************** +# Copyright (c) 2023 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional information +# regarding copyright ownership. +# +# This program and the accompanying materials are made available under the terms +# of the Apache License 2.0 which is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# *******************************************************************************/ + +set(EXAMPLES example_v1 example_v2) + +foreach(EXAMPLE ${EXAMPLES}) + add_executable(${EXAMPLE} ${EXAMPLE}.cpp) + + target_link_libraries(${EXAMPLE} kuksaclient) + target_link_directories(${EXAMPLE} PRIVATE ${CMAKE_BINARY_DIR}) + target_include_directories(${EXAMPLE} PUBLIC ${PROJECT_SOURCE_DIR}/include) + + set_target_properties(${EXAMPLE} PROPERTIES RUNTIME_OUTPUT_DIRECTORY + ${CMAKE_BINARY_DIR}/bin) +endforeach() diff --git a/kuksa-cpp-client/example/example_v1.cpp b/kuksa-cpp-client/example/example_v1.cpp new file mode 100644 index 0000000..d2b31a4 --- /dev/null +++ b/kuksa-cpp-client/example/example_v1.cpp @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include +#include +#include + +#include "kuksa/val/v1/types.pb.h" +#include "kuksaclient.h" + +using namespace kuksa; + +void handleValue(const kuksa::val::v1::Datapoint &value) { + switch (value.value_case()) { + case kuksa::val::v1::Datapoint::ValueCase::kString: + std::cout << "String value: " << value.string() << std::endl; + break; + case kuksa::val::v1::Datapoint::ValueCase::kBool: + std::cout << "Bool value: " << value.bool_() << std::endl; + break; + case kuksa::val::v1::Datapoint::ValueCase::kInt32: + std::cout << "Int32 value: " << value.int32() << std::endl; + break; + case kuksa::val::v1::Datapoint::ValueCase::kFloat: + std::cout << "Float value: " << value.float_() << std::endl; + break; + case kuksa::val::v1::Datapoint::ValueCase::kDouble: + std::cout << "Double value: " << value.double_() << std::endl; + break; + default: + std::cout << "Unsupported value type" << std::endl; + break; + } +} + +void on_data_reception_v1(const std::string &path, + const kuksa::val::v1::Datapoint &value) { + std::cout << "Received " << path << std::endl; + handleValue(value); +} + +int main() { + std::cout << "Starting example for v1 ..." << std::endl; + KuksaClient instance; + // connect to databroker + bool connectionStatus = instance.connect_v1("127.0.0.1:55555"); + printf("Connection is %s \n", + (connectionStatus == true) ? "Succesfull" : "Failed"); + + sleep(2); + + kuksa::val::v1::Datapoint value{}; + // fetch values from databroker + if (instance.get("Vehicle.Speed", value)) { + handleValue(value); + } + sleep(1); + + // set values into databroker + value.set_float_(41.4f); + instance.set("Vehicle.Speed", value); + + // subscribe to data points + std::vector signals = {"Vehicle.Speed", "Vehicle.Width"}; + instance.subscribe(signals, on_data_reception_v1); + + sleep(10); + + return 0; +} diff --git a/kuksa-cpp-client/example/example_v2.cpp b/kuksa-cpp-client/example/example_v2.cpp new file mode 100644 index 0000000..3aedbcc --- /dev/null +++ b/kuksa-cpp-client/example/example_v2.cpp @@ -0,0 +1,129 @@ +/******************************************************************************** + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include +#include +#include + +#include "kuksa/val/v2/val.pb.h" +#include "kuksaclient.h" +#include +#include + +using namespace kuksa; + +void handleValue(const kuksa::val::v2::Value &value) { + switch (value.typed_value_case()) { + case kuksa::val::v2::Value::kString: + std::cout << "String value: " << value.string() << std::endl; + break; + case kuksa::val::v2::Value::kBool: + std::cout << "Bool value: " << value.bool_() << std::endl; + break; + case kuksa::val::v2::Value::kInt32: + std::cout << "Int32 value: " << value.int32() << std::endl; + break; + case kuksa::val::v2::Value::kUint32: + std::cout << "Uint32 value: " << value.uint32() << std::endl; + break; + + case kuksa::val::v2::Value::kFloat: + std::cout << "Float value: " << value.float_() << std::endl; + break; + case kuksa::val::v2::Value::kDouble: + std::cout << "Double value: " << value.double_() << std::endl; + break; + // Handle initial callback on subscription confirmation + // No value is set by the broker + case kuksa::val::v2::Value::TYPED_VALUE_NOT_SET: + break; + default: + std::cout << "Unsupported value type: " << value.typed_value_case() + << std::endl; + break; + } +} + +void on_data_reception_v2(const std::string &path, + const kuksa::val::v2::Value &value) { + std::cout << "Subscription callback invoked on VSS point " << path + << std::endl; + + handleValue(value); +} + +void on_data_reception_v1(const std::string &path, + const kuksa::val::v1::Datapoint &value) { + std::cout << "Received " << path << std::endl; +} + +int main() { + std::cout << "Starting example for v2 ..." << std::endl; + KuksaClient instance; + + // Connect to the databroker + bool connectionStatus = instance.connect_v2("127.0.0.1:55555"); + printf("Connection is %s \n", + (connectionStatus == true) ? "Succesfull" : "Failed"); + + sleep(2); + + // Get info of the databroker server + kuksa::val::v2::GetServerInfoResponse serverInfo{}; + if (instance.getServerInfo(serverInfo)) { + std::cout << "Server Name: " << serverInfo.name() << std::endl; + std::cout << "Version : " << serverInfo.version() << std::endl; + std::cout << "Commit Hash: " << serverInfo.commit_hash() << std::endl; + } + + // Publish Vehicle.Speed signal + kuksa::val::v2::Value value{}; + value.set_float_(52.47f); + instance.publishValue("Vehicle.Speed", value); + + // Read back the value + if (instance.getValue("Vehicle.Speed", value)) { + handleValue(value); + } + + kuksa::val::v2::Value value_1{}; + value_1.set_uint32(73); + instance.publishValue("Vehicle.Chassis.Accelerator.PedalPosition", value_1); + + std::vector datapoints; + std::vector signals_to_publish = { + "Vehicle.Speed", "Vehicle.Chassis.Accelerator.PedalPosition"}; + + datapoints = instance.getValues(signals_to_publish); + + if (!datapoints.empty()) { + for (const auto &datapoint : datapoints) { + handleValue(datapoint.value()); + } + } + + sleep(1); + + // Subscribe to multiple signals + std::vector signals = { + "Vehicle.Speed", "Vehicle.Powertrain.ElectricMotor.Temperature"}; + instance.subscribe(signals, on_data_reception_v2); + + // Actuate via signal + value.set_bool_(true); + // This will fail in the absence of a provider + instance.actuate("Vehicle.Body.Trunk.Rear.IsOpen", value); + + sleep(10); + + return 0; +} diff --git a/kuksa-cpp-client/include/kuksaclient.h b/kuksa-cpp-client/include/kuksaclient.h new file mode 100644 index 0000000..23b2784 --- /dev/null +++ b/kuksa-cpp-client/include/kuksaclient.h @@ -0,0 +1,130 @@ +/******************************************************************************** + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef KUKSACLIENT_H +#define KUKSACLIENT_H +#include "kuksa/val/v1/types.pb.h" +#include "kuksa/val/v2/types.pb.h" +#include "kuksa/val/v2/val.pb.h" +#include +#include +#include +#include + +namespace kuksa { + +using kuksaCallbackV1 = std::function; + +/** + * @brief Callback function type for v2 Kuksa data subscriptions. + * + * This callback is invoked when a subscribed data point's value changes. + * + * @param path The VSS name of the data point. + * @param value The new value of the data point (v2 format). + */ +using kuksaCallbackV2 = std::function; + +/** + * @class KuksaClient + * @brief Client class for interacting with the Kuksa data broker. + * + * This class provides methods to connect and interact with a Kuksa databroker + * server. + */ +class KuksaClient { +public: + KuksaClient(); + ~KuksaClient(); + + bool connect_v1(const std::string &server); + bool get(const std::string &datapoint, kuksa::val::v1::Datapoint &value); + bool set(const std::string &datapoint, + kuksa::val::v1::Datapoint const &value); + void subscribe(const std::vector &datapoints, + kuksaCallbackV1 callback); + + /** + * @brief Connects to a Kuksa server using the v2 API. + * + * @param server The server address (ex: "localhost:55555"). + * @return True if the connection was successful, false otherwise. + */ + bool connect_v2(const std::string &server); + + /** + * @brief Retrieves the value of a data point using the v2 API. + * + * @param datapoint VSS name of the datapoints. + * @param value The retrieved data point value (v2 format). + * @return True if the retrieval was successful, false otherwise. + */ + bool getValue(const std::string &datapoint, kuksa::val::v2::Value &value); + + /** + * @brief Retrieves the values of a set of data points using the v2 API. + * + * @param datapoints A vector of VSS datapoint names. + * @return A vector of retrieved data point values (v2 format). + */ + std::vector + getValues(const std::vector &datapoints); + + /** + * @brief Subscribes to updates of a set of data points using the v2 API. + * + * @param datapoints A vector of VSS data point names. + * @param callback The callback function to be invoked when a subscribed data + * point changes. + */ + void subscribe(const std::vector &datapoints, + kuksaCallbackV2 callback); + + /** + * @brief Actuates a data point using the v2 API. + * i.e set the target value of a VSS point. + * The actuation can fail if there is no provider registered in the broker + * + * @param datapoint The VSS name of the data point. + * @param value The value to set (v2 format). + * @return True if the actuation was successful, false otherwise. + */ + bool actuate(const std::string &datapoint, + kuksa::val::v2::Value const &value); + + /** + * @brief Publishes a value using the v2 API. + * i.e. set the current value of a VSS point + * + * @param datapoint The VSS name of the data point. + * @param value The value to publish (v2 format). + * @return True if the publish operation was successful, false otherwise. + */ + bool publishValue(const std::string &datapoint, + kuksa::val::v2::Value const &value); + + /** + * @brief Retrieves server information using the v2 API. + * + * @param response The server information response. + * @return True if the operation was successful, false otherwise. + */ + bool getServerInfo(kuksa::val::v2::GetServerInfoResponse &response); + +private: + class KuksaClientImpl; + std::unique_ptr mKuksaClient; +}; +} // namespace kuksa +#endif // KUKSACLIENT_H diff --git a/kuksa-cpp-client/justfile b/kuksa-cpp-client/justfile new file mode 100644 index 0000000..1a7ab78 --- /dev/null +++ b/kuksa-cpp-client/justfile @@ -0,0 +1,41 @@ +#******************************************************************************** +# Copyright (c) 2023 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License 2.0 which is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +#*******************************************************************************/ + +#!/usr/bin/env just --justfile +# This is a justfile. It's a handy way to organize shell commands. +# Also see: https://just.systems/man/en/ + +set dotenv-load := true +set shell := ["/bin/bash", "-c", "-l"] + +default: + @just --list + +prepare: + pip install conan==2.11.0 + conan remote add conancenter https://center2.conan.io --force + @cp -r ../kuksa-common/vss _vss + +configure: + conan install . -pr:b conan/profile_x86_64_Release.txt -pr:h conan/profile_x86_64_Release.txt --build=missing + +build: + conan build . -pr:b conan/profile_x86_64_Release.txt -pr:h conan/profile_x86_64_Release.txt + +# run the databroker in a seperate windows +run-databroker: + tmux new-window -n kuksa docker run --rm -it -p 55555:55555 -v $(pwd):/tmp ghcr.io/eclipse-kuksa/kuksa-databroker:main --insecure --vss /tmp/_vss/vss_release_4.0.json + +run-example-v2: + just run-databroker + tmux new-window -n example-v2 "./build/Release/bin/example_v2 || echo 'Process terminated. Press Enter to close.'; read" diff --git a/kuksa-cpp-client/proto/kuksa/val/v1/README.md b/kuksa-cpp-client/proto/kuksa/val/v1/README.md new file mode 100644 index 0000000..a93e2e4 --- /dev/null +++ b/kuksa-cpp-client/proto/kuksa/val/v1/README.md @@ -0,0 +1,6 @@ +# kuksa.val.v1 protobuf API + +This directory contain a Protobuf API supported by KUKSA Databroker. + +This API is deprecated. It is recommended to use +the [kuksa.val.v2](../v2/val.proto). diff --git a/kuksa-cpp-client/proto/kuksa/val/v1/types.proto b/kuksa-cpp-client/proto/kuksa/val/v1/types.proto new file mode 100644 index 0000000..8914e7a --- /dev/null +++ b/kuksa-cpp-client/proto/kuksa/val/v1/types.proto @@ -0,0 +1,288 @@ +/******************************************************************************** + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +syntax = "proto3"; + +// I added V1 as in databroker. Is this good practice? +package kuksa.val.v1; +import "google/protobuf/timestamp.proto"; + +option go_package = "kuksa/val/v1"; + +// Describes a VSS entry +// When requesting an entry, the amount of information returned can +// be controlled by specifying either a `View` or a set of `Field`s. +message DataEntry { + // Defines the full VSS path of the entry. + string path = 1; // [field: FIELD_PATH] + + // The value (datapoint) + Datapoint value = 2; // [field: FIELD_VALUE] + + // Actuator target (only used if the entry is an actuator) + Datapoint actuator_target = 3; // [field: FIELD_ACTUATOR_TARGET] + + // Metadata for this entry + Metadata metadata = 10; // [field: FIELD_METADATA] +} + +message Datapoint { + google.protobuf.Timestamp timestamp = 1; + + oneof value { + string string = 11; + bool bool = 12; + sint32 int32 = 13; + sint64 int64 = 14; + uint32 uint32 = 15; + uint64 uint64 = 16; + float float = 17; + double double = 18; + StringArray string_array = 21; + BoolArray bool_array = 22; + Int32Array int32_array = 23; + Int64Array int64_array = 24; + Uint32Array uint32_array = 25; + Uint64Array uint64_array = 26; + FloatArray float_array = 27; + DoubleArray double_array = 28; + } +} + +message Metadata { + // Data type + // The VSS data type of the entry (i.e. the value, min, max etc). + // + // NOTE: protobuf doesn't have int8, int16, uint8 or uint16 which means + // that these values must be serialized as int32 and uint32 respectively. + DataType data_type = 11; // [field: FIELD_METADATA_DATA_TYPE] + + // Entry type + EntryType entry_type = 12; // [field: FIELD_METADATA_ENTRY_TYPE] + + // Description + // Describes the meaning and content of the entry. + optional string description = 13; // [field: FIELD_METADATA_DESCRIPTION] + + // Comment [optional] + // A comment can be used to provide additional informal information + // on a entry. + optional string comment = 14; // [field: FIELD_METADATA_COMMENT] + + // Deprecation [optional] + // Whether this entry is deprecated. Can contain recommendations of what + // to use instead. + optional string deprecation = 15; // [field: FIELD_METADATA_DEPRECATION] + + // Unit [optional] + // The unit of measurement + optional string unit = 16; // [field: FIELD_METADATA_UNIT] + + // Value restrictions [optional] + // Restrict which values are allowed. + // Only restrictions matching the DataType {datatype} above are valid. + ValueRestriction value_restriction = 17; // [field: FIELD_METADATA_VALUE_RESTRICTION] + + // Entry type specific metadata + oneof entry_specific { + Actuator actuator = 20; // [field: FIELD_METADATA_ACTUATOR] + Sensor sensor = 30; // [field: FIELD_METADATA_SENSOR] + Attribute attribute = 40; // [field: FIELD_METADATA_ATTRIBUTE] + } +} + +/////////////////////// +// Actuator specific fields +message Actuator { + // Nothing for now +} + +//////////////////////// +// Sensor specific +message Sensor { + // Nothing for now +} + +//////////////////////// +// Attribute specific +message Attribute { + // Nothing for now. +} + +// Value restriction +// +// One ValueRestriction{type} for each type, since +// they don't make sense unless the types match +// +message ValueRestriction { + oneof type { + ValueRestrictionString string = 21; + // For signed VSS integers + ValueRestrictionInt signed = 22; + // For unsigned VSS integers + ValueRestrictionUint unsigned = 23; + // For floating point VSS values (float and double) + ValueRestrictionFloat floating_point = 24; + } +} + +message ValueRestrictionInt { + optional sint64 min = 1; + optional sint64 max = 2; + repeated sint64 allowed_values = 3; +} + +message ValueRestrictionUint { + optional uint64 min = 1; + optional uint64 max = 2; + repeated uint64 allowed_values = 3; +} + +message ValueRestrictionFloat { + optional double min = 1; + optional double max = 2; + + // allowed for doubles/floats not recommended + repeated double allowed_values = 3; +} + +// min, max doesn't make much sense for a string +message ValueRestrictionString { + repeated string allowed_values = 3; +} + +// VSS Data type of a signal +// +// Protobuf doesn't support int8, int16, uint8 or uint16. +// These are mapped to int32 and uint32 respectively. +// +enum DataType { + DATA_TYPE_UNSPECIFIED = 0; + DATA_TYPE_STRING = 1; + DATA_TYPE_BOOLEAN = 2; + DATA_TYPE_INT8 = 3; + DATA_TYPE_INT16 = 4; + DATA_TYPE_INT32 = 5; + DATA_TYPE_INT64 = 6; + DATA_TYPE_UINT8 = 7; + DATA_TYPE_UINT16 = 8; + DATA_TYPE_UINT32 = 9; + DATA_TYPE_UINT64 = 10; + DATA_TYPE_FLOAT = 11; + DATA_TYPE_DOUBLE = 12; + DATA_TYPE_TIMESTAMP = 13; + DATA_TYPE_STRING_ARRAY = 20; + DATA_TYPE_BOOLEAN_ARRAY = 21; + DATA_TYPE_INT8_ARRAY = 22; + DATA_TYPE_INT16_ARRAY = 23; + DATA_TYPE_INT32_ARRAY = 24; + DATA_TYPE_INT64_ARRAY = 25; + DATA_TYPE_UINT8_ARRAY = 26; + DATA_TYPE_UINT16_ARRAY = 27; + DATA_TYPE_UINT32_ARRAY = 28; + DATA_TYPE_UINT64_ARRAY = 29; + DATA_TYPE_FLOAT_ARRAY = 30; + DATA_TYPE_DOUBLE_ARRAY = 31; + DATA_TYPE_TIMESTAMP_ARRAY = 32; +} + +// Entry type +enum EntryType { + ENTRY_TYPE_UNSPECIFIED = 0; + ENTRY_TYPE_ATTRIBUTE = 1; + ENTRY_TYPE_SENSOR = 2; + ENTRY_TYPE_ACTUATOR = 3; +} + +// A `View` specifies a set of fields which should +// be populated in a `DataEntry` (in a response message) +enum View { + VIEW_UNSPECIFIED = 0; // Unspecified. Equivalent to VIEW_CURRENT_VALUE unless `fields` are explicitly set. + VIEW_CURRENT_VALUE = 1; // Populate DataEntry with value. + VIEW_TARGET_VALUE = 2; // Populate DataEntry with actuator target. + VIEW_METADATA = 3; // Populate DataEntry with metadata. + VIEW_FIELDS = 10; // Populate DataEntry only with requested fields. + VIEW_ALL = 20; // Populate DataEntry with everything. +} + +// A `Field` corresponds to a specific field of a `DataEntry`. +// +// It can be used to: +// * populate only specific fields of a `DataEntry` response. +// * specify which fields of a `DataEntry` should be set as +// part of a `Set` request. +// * subscribe to only specific fields of a data entry. +// * convey which fields of an updated `DataEntry` have changed. +enum Field { + FIELD_UNSPECIFIED = 0; // "*" i.e. everything + FIELD_PATH = 1; // path + FIELD_VALUE = 2; // value + FIELD_ACTUATOR_TARGET = 3; // actuator_target + FIELD_METADATA = 10; // metadata.* + FIELD_METADATA_DATA_TYPE = 11; // metadata.data_type + FIELD_METADATA_DESCRIPTION = 12; // metadata.description + FIELD_METADATA_ENTRY_TYPE = 13; // metadata.entry_type + FIELD_METADATA_COMMENT = 14; // metadata.comment + FIELD_METADATA_DEPRECATION = 15; // metadata.deprecation + FIELD_METADATA_UNIT = 16; // metadata.unit + FIELD_METADATA_VALUE_RESTRICTION = 17; // metadata.value_restriction.* + FIELD_METADATA_ACTUATOR = 20; // metadata.actuator.* + FIELD_METADATA_SENSOR = 30; // metadata.sensor.* + FIELD_METADATA_ATTRIBUTE = 40; // metadata.attribute.* +} + +// Error response shall be an HTTP-like code. +// Should follow https://www.w3.org/TR/viss2-transport/#status-codes. +message Error { + uint32 code = 1; + string reason = 2; + string message = 3; +} + +// Used in get/set requests to report errors for specific entries +message DataEntryError { + string path = 1; // vss path + Error error = 2; +} + +message StringArray { + repeated string values = 1; +} + +message BoolArray { + repeated bool values = 1; +} + +message Int32Array { + repeated sint32 values = 1; +} + +message Int64Array { + repeated sint64 values = 1; +} + +message Uint32Array { + repeated uint32 values = 1; +} + +message Uint64Array { + repeated uint64 values = 1; +} + +message FloatArray { + repeated float values = 1; +} + +message DoubleArray { + repeated double values = 1; +} diff --git a/kuksa-cpp-client/proto/kuksa/val/v1/val.proto b/kuksa-cpp-client/proto/kuksa/val/v1/val.proto new file mode 100644 index 0000000..dde49b5 --- /dev/null +++ b/kuksa-cpp-client/proto/kuksa/val/v1/val.proto @@ -0,0 +1,126 @@ +/******************************************************************************** + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +syntax = "proto3"; + +package kuksa.val.v1; + +option go_package = "kuksa/val/v1"; + +import "kuksa/val/v1/types.proto"; + +// Note on authorization: +// Tokens (auth-token or auth-uuid) are sent as (GRPC / http2) metadata. +// +// The auth-token is a JWT compliant token as the examples found here: +// https://github.com/eclipse-kuksa/kuksa-databroker/tree/main/certificates/jwt +// +// See also https://github.com/eclipse-kuksa/kuksa-databroker/blob/main/doc/authorization.md#jwt-access-token +// +// Upon reception of auth-token, server shall generate an auth-uuid in metadata +// that the client can use instead of auth-token in subsequent calls. + +service VAL { + // Get entries + rpc Get(GetRequest) returns (GetResponse); + + // Set entries + rpc Set(SetRequest) returns (SetResponse); + + rpc StreamedUpdate(stream StreamedUpdateRequest) returns (stream StreamedUpdateResponse); + + // Subscribe to a set of entries + // + // Returns a stream of notifications. + // + // InvalidArgument is returned if the request is malformed. + rpc Subscribe(SubscribeRequest) returns (stream SubscribeResponse); + + // Shall return information that allows the client to determine + // what server/server implementation/version it is talking to + // eg. kuksa-databroker 0.5.1 + rpc GetServerInfo(GetServerInfoRequest) returns (GetServerInfoResponse); +} + +// Define which data we want +message EntryRequest { + string path = 1; + View view = 2; + repeated Field fields = 3; +} + +// Request a set of entries. +message GetRequest { + repeated EntryRequest entries = 1; +} + +// Global errors are specified in `error`. +// Errors for individual entries are specified in `errors`. +message GetResponse { + repeated DataEntry entries = 1; + repeated DataEntryError errors = 2; + Error error = 3; +} + +// Define the data we want to set +message EntryUpdate { + DataEntry entry = 1; + repeated Field fields = 2; +} + +// A list of entries to be updated +message SetRequest { + repeated EntryUpdate updates = 1; +} + +// Global errors are specified in `error`. +// Errors for individual entries are specified in `errors`. +message SetResponse { + Error error = 1; + repeated DataEntryError errors = 2; +} + +message StreamedUpdateRequest { + repeated EntryUpdate updates = 1; +} + +message StreamedUpdateResponse { + Error error = 1; + repeated DataEntryError errors = 2; +} + +// Define what to subscribe to +message SubscribeEntry { + string path = 1; + View view = 2; + repeated Field fields = 3; +} + +// Subscribe to changes in datapoints. +message SubscribeRequest { + repeated SubscribeEntry entries = 1; +} + +// A subscription response +message SubscribeResponse { + repeated EntryUpdate updates = 1; +} + +message GetServerInfoRequest { + // Nothing yet +} + +message GetServerInfoResponse { + string name = 1; + string version = 2; +} diff --git a/kuksa-cpp-client/proto/kuksa/val/v2/README.md b/kuksa-cpp-client/proto/kuksa/val/v2/README.md new file mode 100644 index 0000000..23f5724 --- /dev/null +++ b/kuksa-cpp-client/proto/kuksa/val/v2/README.md @@ -0,0 +1,7 @@ +# kuksa.val.v2 protobuf API + +This directory contain a Protobuf API supported by KUKSA Databroker. + +This API replaces: +[kuksa.val.v1](https://github.com/eclipse-kuksa/kuksa-databroker/tree/main/proto/kuksa/val/v1) API and +[sdv.databroker.v1](https://github.com/eclipse-kuksa/kuksa-databroker/tree/main/proto/sdv/databroker/v1) API. diff --git a/kuksa-cpp-client/proto/kuksa/val/v2/types.proto b/kuksa-cpp-client/proto/kuksa/val/v2/types.proto new file mode 100644 index 0000000..8955f6d --- /dev/null +++ b/kuksa-cpp-client/proto/kuksa/val/v2/types.proto @@ -0,0 +1,188 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +syntax = "proto3"; +// Please do not add optional fields due to older proto3 versions limitations + +package kuksa.val.v2; +import "google/protobuf/timestamp.proto"; + +option go_package = "kuksa/val/v2"; + +// A Datapoint represents a timestamped value. +// The 'value' field can be explicitly 'None', meaning the Datapoint exists but no value is present. +message Datapoint { + google.protobuf.Timestamp timestamp = 1; // The timestamp of the datapoint. + Value value = 2; // The value associated with the timestamp. If no value is present, this field can be 'None'. +} + +message Value { + oneof typed_value { + string string = 11; + bool bool = 12; + sint32 int32 = 13; + sint64 int64 = 14; + uint32 uint32 = 15; + uint64 uint64 = 16; + float float = 17; + double double = 18; + StringArray string_array = 21; + BoolArray bool_array = 22; + Int32Array int32_array = 23; + Int64Array int64_array = 24; + Uint32Array uint32_array = 25; + Uint64Array uint64_array = 26; + FloatArray float_array = 27; + DoubleArray double_array = 28; + } +} + +message SignalID { + oneof signal { + // Numeric identifier to the signal + // As of today Databroker assigns arbitrary unique numbers to each registered signal + // at startup, meaning that identifiers may change after restarting Databroker. + // A mechanism for static identifiers may be introduced in the future. + int32 id = 1; + // Full VSS-style path to a specific signal, like "Vehicle.Speed" + // Wildcards and paths to branches are not supported. + // The given path must be known by the Databroker. + string path = 2; + } +} + +message Error { + ErrorCode code = 1; + string message = 2; +} + +enum ErrorCode { + ERROR_CODE_UNSPECIFIED = 0; // Default value, never to be explicitly set, + ERROR_CODE_OK = 1; + ERROR_CODE_INVALID_ARGUMENT = 2; + ERROR_CODE_NOT_FOUND = 3; + ERROR_CODE_PERMISSION_DENIED = 4; +} + +message Metadata { + // ID field + int32 id = 10; + + // Data type + // The VSS data type of the entry (i.e. the value, min, max etc). + // + // NOTE: protobuf doesn't have int8, int16, uint8 or uint16 which means + // that these values must be serialized as int32 and uint32 respectively. + DataType data_type = 11; + + // Entry type + EntryType entry_type = 12; + + // Description + // Describes the meaning and content of the entry. + string description = 13; + + // Comment + // A comment can be used to provide additional informal information + // on a entry. + string comment = 14; + + // Deprecation + // Whether this entry is deprecated. Can contain recommendations of what + // to use instead. + string deprecation = 15; + + // Unit + // The unit of measurement + string unit = 16; + + // Value restrictions checked/enforced by Databroker + Value allowed_values = 17; // Must be of array type + Value min = 18; + Value max = 19; +} + +// VSS Data type of a signal +// +// Protobuf doesn't support int8, int16, uint8 or uint16. +// These are mapped to int32 and uint32 respectively. +// +enum DataType { + DATA_TYPE_UNSPECIFIED = 0; + DATA_TYPE_STRING = 1; + DATA_TYPE_BOOLEAN = 2; + DATA_TYPE_INT8 = 3; + DATA_TYPE_INT16 = 4; + DATA_TYPE_INT32 = 5; + DATA_TYPE_INT64 = 6; + DATA_TYPE_UINT8 = 7; + DATA_TYPE_UINT16 = 8; + DATA_TYPE_UINT32 = 9; + DATA_TYPE_UINT64 = 10; + DATA_TYPE_FLOAT = 11; + DATA_TYPE_DOUBLE = 12; + DATA_TYPE_TIMESTAMP = 13; + DATA_TYPE_STRING_ARRAY = 20; + DATA_TYPE_BOOLEAN_ARRAY = 21; + DATA_TYPE_INT8_ARRAY = 22; + DATA_TYPE_INT16_ARRAY = 23; + DATA_TYPE_INT32_ARRAY = 24; + DATA_TYPE_INT64_ARRAY = 25; + DATA_TYPE_UINT8_ARRAY = 26; + DATA_TYPE_UINT16_ARRAY = 27; + DATA_TYPE_UINT32_ARRAY = 28; + DATA_TYPE_UINT64_ARRAY = 29; + DATA_TYPE_FLOAT_ARRAY = 30; + DATA_TYPE_DOUBLE_ARRAY = 31; + DATA_TYPE_TIMESTAMP_ARRAY = 32; +} + +// Entry type +enum EntryType { + ENTRY_TYPE_UNSPECIFIED = 0; + ENTRY_TYPE_ATTRIBUTE = 1; + ENTRY_TYPE_SENSOR = 2; + ENTRY_TYPE_ACTUATOR = 3; +} + +message StringArray { + repeated string values = 1; +} + +message BoolArray { + repeated bool values = 1; +} + +message Int32Array { + repeated sint32 values = 1; +} + +message Int64Array { + repeated sint64 values = 1; +} + +message Uint32Array { + repeated uint32 values = 1; +} + +message Uint64Array { + repeated uint64 values = 1; +} + +message FloatArray { + repeated float values = 1; +} + +message DoubleArray { + repeated double values = 1; +} diff --git a/kuksa-cpp-client/proto/kuksa/val/v2/val.proto b/kuksa-cpp-client/proto/kuksa/val/v2/val.proto new file mode 100644 index 0000000..2756ac1 --- /dev/null +++ b/kuksa-cpp-client/proto/kuksa/val/v2/val.proto @@ -0,0 +1,332 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +syntax = "proto3"; +// Please do not add optional fields due to older proto3 versions limitations + +package kuksa.val.v2; + +option go_package = "kuksa/val/v2"; + +import "kuksa/val/v2/types.proto"; + +service VAL { + // Get the latest value of a signal + // If the signal exist but does not have a valid value + // a DataPoint where value is None shall be returned. + // + // Returns (GRPC error code): + // NOT_FOUND if the requested signal doesn't exist + // UNAUTHENTICATED if no credentials provided or credentials has expired + // PERMISSION_DENIED if access is denied + // INVALID_ARGUMENT if the request is empty or provided path is too long + // - MAX_REQUEST_PATH_LENGTH: usize = 1000; + // + rpc GetValue(GetValueRequest) returns (GetValueResponse); + + // Get the latest values of a set of signals. + // The returned list of data points has the same order as the list of the request. + // If a requested signal has no value a DataPoint where value is None will be returned. + // + // Returns (GRPC error code): + // NOT_FOUND if any of the requested signals doesn't exist. + // UNAUTHENTICATED if no credentials provided or credentials has expired + // PERMISSION_DENIED if access is denied for any of the requested signals. + // INVALID_ARGUMENT if the request is empty or provided path is too long + // - MAX_REQUEST_PATH_LENGTH: usize = 1000; + // + rpc GetValues(GetValuesRequest) returns (GetValuesResponse); + + // Subscribe to a set of signals using string path parameters + // Returns (GRPC error code): + // NOT_FOUND if any of the signals are non-existant. + // UNAUTHENTICATED if no credentials provided or credentials has expired + // PERMISSION_DENIED if access is denied for any of the signals. + // INVALID_ARGUMENT + // - if the request is empty or provided path is too long + // MAX_REQUEST_PATH_LENGTH: usize = 1000; + // - if buffer_size exceeds the maximum permitted + // MAX_BUFFER_SIZE: usize = 1000; + // + // When subscribing, Databroker shall immediately return the value for all + // subscribed entries. + // If a value isn't available when subscribing to a it, it should return None + // + // If a subscriber is slow to consume signals, messages will be buffered up + // to the specified buffer_size before the oldest messages are dropped. + // + rpc Subscribe(SubscribeRequest) returns (stream SubscribeResponse); + + // Subscribe to a set of signals using i32 id parameters + // Returns (GRPC error code): + // NOT_FOUND if any of the signals are non-existant. + // UNAUTHENTICATED if no credentials provided or credentials has expired + // PERMISSION_DENIED if access is denied for any of the signals. + // INVALID_ARGUMENT + // - if the request is empty or provided path is too long + // MAX_REQUEST_PATH_LENGTH: usize = 1000; + // - if buffer_size exceeds the maximum permitted + // MAX_BUFFER_SIZE: usize = 1000; + // + // When subscribing, Databroker shall immediately return the value for all + // subscribed entries. + // If a value isn't available when subscribing to a it, it should return None + // + // If a subscriber is slow to consume signals, messages will be buffered up + // to the specified buffer_size before the oldest messages are dropped. + // + rpc SubscribeById(SubscribeByIdRequest) returns (stream SubscribeByIdResponse); + + // Actuate a single actuator + // + // Returns (GRPC error code): + // NOT_FOUND if the actuator does not exist. + // PERMISSION_DENIED if access is denied for the actuator. + // UNAUTHENTICATED if no credentials provided or credentials has expired + // UNAVAILABLE if there is no provider currently providing the actuator + // DATA_LOSS is there is a internal TransmissionFailure + // INVALID_ARGUMENT + // - if the provided path is not an actuator. + // - if the data type used in the request does not match + // the data type of the addressed signal + // - if the requested value is not accepted, + // e.g. if sending an unsupported enum value + // - if the provided value is out of the min/max range specified + // + rpc Actuate(ActuateRequest) returns (ActuateResponse); + + // Actuate simultaneously multiple actuators. + // If any error occurs, the entire operation will be aborted + // and no single actuator value will be forwarded to the provider. + // + // Returns (GRPC error code): + // NOT_FOUND if any of the actuators are non-existant. + // PERMISSION_DENIED if access is denied for any of the actuators. + // UNAUTHENTICATED if no credentials provided or credentials has expired + // UNAVAILABLE if there is no provider currently providing an actuator + // DATA_LOSS is there is a internal TransmissionFailure + // INVALID_ARGUMENT + // - if any of the provided path is not an actuator. + // - if the data type used in the request does not match + // the data type of the addressed signal + // - if the requested value is not accepted, + // e.g. if sending an unsupported enum value + // - if any of the provided actuators values are out of the min/max range specified + // + rpc BatchActuate(BatchActuateRequest) returns (BatchActuateResponse); + + // List metadata of signals matching the request. + // + // Returns (GRPC error code): + // NOT_FOUND if the specified root branch does not exist. + // UNAUTHENTICATED if no credentials provided or credentials has expired + // INVALID_ARGUMENT if the provided path or wildcard is wrong. + // + rpc ListMetadata(ListMetadataRequest) returns (ListMetadataResponse); + + // Publish a signal value. Used for low frequency signals (e.g. attributes). + // + // Returns (GRPC error code): + // NOT_FOUND if any of the signals are non-existant. + // PERMISSION_DENIED + // - if access is denied for any of the signals. + // UNAUTHENTICATED if no credentials provided or credentials has expired + // INVALID_ARGUMENT + // - if the data type used in the request does not match + // the data type of the addressed signal + // - if the published value is not accepted, + // e.g. if sending an unsupported enum value + // - if the published value is out of the min/max range specified + // + rpc PublishValue(PublishValueRequest) returns (PublishValueResponse); + + // Open a stream used to provide actuation and/or publishing values using + // a streaming interface. Used to provide actuators and to enable high frequency + // updates of values. + // + // The open stream is used for request / response type communication between the + // provider and server (where the initiator of a request can vary). + // + // Errors: + // - Provider sends ProvideActuationRequest -> Databroker returns ProvideActuationResponse + // Returns (GRPC error code) and closes the stream call (strict case). + // NOT_FOUND if any of the signals are non-existant. + // PERMISSION_DENIED if access is denied for any of the signals. + // UNAUTHENTICATED if no credentials provided or credentials has expired + // ALREADY_EXISTS if a provider already claimed the ownership of an actuator + // + // - Provider sends PublishValuesRequest -> Databroker returns PublishValuesResponse upon error, and nothing upon success + // GRPC errors are returned as messages in the stream + // response with the signal id `map status = 2;` (permissive case) + // NOT_FOUND if a signal is non-existant. + // PERMISSION_DENIED + // - if access is denied for a signal. + // INVALID_ARGUMENT + // - if the data type used in the request does not match + // the data type of the addressed signal + // - if the published value is not accepted, + // e.g. if sending an unsupported enum value + // - if the published value is out of the min/max range specified + // + // - Databroker sends BatchActuateStreamRequest -> Provider shall return a BatchActuateStreamResponse, + // for every signal requested to indicate if the request was accepted or not. + // It is up to the provider to decide if the stream shall be closed, + // as of today Databroker will not react on the received error message. + // + rpc OpenProviderStream(stream OpenProviderStreamRequest) returns (stream OpenProviderStreamResponse); + + // Get server information + rpc GetServerInfo(GetServerInfoRequest) returns (GetServerInfoResponse); +} + +message GetValueRequest { + SignalID signal_id = 1; +} + +message GetValueResponse { + Datapoint data_point = 1; +} + +message GetValuesRequest { + repeated SignalID signal_ids = 1; +} + +message GetValuesResponse { + repeated Datapoint data_points = 1; +} + +message SubscribeRequest { + repeated string signal_paths = 1; + + // Specifies the number of messages that can be buffered for + // slow subscribers before the oldest messages are dropped. + // Default (0) results in that only latest message is kept. + // Maximum value supported is implementation dependent. + uint32 buffer_size = 2; +} + +message SubscribeResponse { + map entries = 1; +} + +message SubscribeByIdRequest { + repeated int32 signal_ids = 1; + + // Specifies the number of messages that can be buffered for + // slow subscribers before the oldest messages are dropped. + // Default (0) results in that only latest message is kept. + // Maximum value supported is implementation dependent. + uint32 buffer_size = 2; +} + +message SubscribeByIdResponse { + map entries = 1; +} + +message ActuateRequest { + SignalID signal_id = 1; + Value value = 2; +} + +message ActuateResponse { +} + +message BatchActuateRequest { + repeated ActuateRequest actuate_requests = 1; +} + +message BatchActuateResponse { +} + +message ListMetadataRequest { + // Root path to be used when listing metadata + // Shall correspond to a VSS branch, e.g. "Vehicle", "Vehicle.Cabin" + // Metadata for all signals under that branch will be returned unless filtered by filter. + // NOTE: Currently Databroker supports also signals and wildcards in root but that may + // be removed in a future release! + string root = 1; + // NOTE : Currently not considered by Databroker, all signals matching root are returned + string filter = 2; +} + +message ListMetadataResponse { + repeated Metadata metadata = 1; +} + +message PublishValueRequest { + SignalID signal_id = 1; + Datapoint data_point = 2; +} + +message PublishValueResponse { +} + +message PublishValuesRequest { + int32 request_id = 1; /// Unique request id for the stream that can be used to identify the response. + map data_points = 2; +} + +message PublishValuesResponse { + int32 request_id = 1; + map status = 2; +} + +message ProvideActuationRequest { + repeated SignalID actuator_identifiers = 1; +} + +message ProvideActuationResponse { +} + +message BatchActuateStreamRequest { + repeated ActuateRequest actuate_requests = 1; +} + +// Message that shall be used by provider to indicate if an actuation request was accepted. +message BatchActuateStreamResponse { + SignalID signal_id = 1; + Error error = 2; +} + +message OpenProviderStreamRequest { + oneof action { + // Inform server of an actuator this provider provides. + ProvideActuationRequest provide_actuation_request = 1; + // Publish a value. + PublishValuesRequest publish_values_request = 2; + // Sent to acknowledge the acceptance of a batch actuate + // request. + BatchActuateStreamResponse batch_actuate_stream_response = 3; + } +} + +message OpenProviderStreamResponse { + oneof action { + // Response to a provide actuator request. + ProvideActuationResponse provide_actuation_response = 1; + // Acknowledgement that a published value was received. + PublishValuesResponse publish_values_response = 2; + // Send a batch actuate request to a provider. + BatchActuateStreamRequest batch_actuate_stream_request = 3; + } +} + +message GetServerInfoRequest { + // Nothing yet +} + +message GetServerInfoResponse { + string name = 1; + string version = 2; + string commit_hash = 3; +} diff --git a/kuksa-cpp-client/proto/sdv/databroker/v1/README.md b/kuksa-cpp-client/proto/sdv/databroker/v1/README.md new file mode 100644 index 0000000..2fd44c8 --- /dev/null +++ b/kuksa-cpp-client/proto/sdv/databroker/v1/README.md @@ -0,0 +1,8 @@ +# sdv.databroker.v1 protobuf API + +This directory contain a Protobuf API supported by KUKSA Databroker. + +To enable the legacy `sdv.databroker.v1` API you must start Databroker with the `--enable-databroker-v1` argument. + +This API is deprecated. It is recommended to use +the [kuksa.val.v2](../../../kuksa/val/v2/val.proto). diff --git a/kuksa-cpp-client/proto/sdv/databroker/v1/broker.proto b/kuksa-cpp-client/proto/sdv/databroker/v1/broker.proto new file mode 100644 index 0000000..f8ffaf8 --- /dev/null +++ b/kuksa-cpp-client/proto/sdv/databroker/v1/broker.proto @@ -0,0 +1,96 @@ +/******************************************************************************** + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +syntax = "proto3"; + +package sdv.databroker.v1; + +import "sdv/databroker/v1/types.proto"; + +service Broker { + // Request a set of datapoints (values) + // + // Returns a list of requested data points. + // + // InvalidArgument is returned if the request is malformed. + rpc GetDatapoints(GetDatapointsRequest) returns (GetDatapointsReply); + + // Set a datapoint (values) + rpc SetDatapoints(SetDatapointsRequest) returns (SetDatapointsReply); + + // Subscribe to a set of data points or conditional expressions + // using the Data Broker Query Syntax (described in QUERY.md) + // + // Returns a stream of replies. + // + // InvalidArgument is returned if the request is malformed. + rpc Subscribe(SubscribeRequest) returns (stream SubscribeReply); + + // Request the metadata of a set of datapoints + // + // Returns metadata of the requested data points that exist. + rpc GetMetadata(GetMetadataRequest) returns (GetMetadataReply); +} + +message GetDatapointsRequest { + // A list of requested data points. + repeated string datapoints = 1; +} + +message GetDatapointsReply { + // Contains the values of the requested data points. + // If a requested data point is not available, the corresponding Datapoint + // will have the respective failure value set. + map datapoints = 1; +} + +message SetDatapointsRequest { + // A map of data points to set + map datapoints = 1; +} + +message SetDatapointsReply { + // A map of errors (if any) + map errors = 1; +} + +message SubscribeRequest { + // Subscribe to a set of data points (or expressions) described + // by the provided query. + // The query syntax is a subset of SQL and is described in more + // detail in the QUERY.md file. + string query = 2; +} + +message SubscribeReply { + // Contains the fields specified by the query. + // If a requested data point value is not available, the corresponding + // Datapoint will have it's respective failure value set. + map fields = 1; +} + +message GetMetadataRequest { + // Request metadata for a list of data points referenced by their names. + // e.g. "Vehicle.Cabin.Seat.Row1.Pos1.Position" or "Vehicle.Speed". + // + // If no names are provided, metadata for all known data points will be + // returned. + repeated string names = 1; +} + +message GetMetadataReply { + // Contains metadata of the requested data points. If a data point + // doesn't exist (i.e. not known to the Data Broker) the corresponding + // Metadata isn't part of the returned list. + repeated Metadata list = 1; +} diff --git a/kuksa-cpp-client/proto/sdv/databroker/v1/collector.proto b/kuksa-cpp-client/proto/sdv/databroker/v1/collector.proto new file mode 100644 index 0000000..c67a5de --- /dev/null +++ b/kuksa-cpp-client/proto/sdv/databroker/v1/collector.proto @@ -0,0 +1,97 @@ +/******************************************************************************** + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +syntax = "proto3"; + +import "sdv/databroker/v1/types.proto"; + +package sdv.databroker.v1; + +service Collector { + // Register new datapoint (metadata) + // + // If the registration of at least one of the passed data point fails, the overall registration + // is rejected and the gRPC status code ABORTED is returned (to indicate the "aborted" registration). + // The details, which data point(s) caused the failure and the reason, is passed in back in human- + // readable form in the status message. Possible failure resaons are: + // * PERMISSION_DENIED - Not allowed to register this name + // * ALREADY_REGISTERED - The data point is already registered by some other feeder + // * RE_REGISTRATION_MISMATCH - Already registered by this feeder but with differing metadata + // * INVALID_NAME - The passed name of the datapoint has an invalid structure + // * INVALID_VALUE_TYPE - The passed ValueType is not supported + // * INVALID_CHANGE_TYPE - The passed ChangeType is not supported + rpc RegisterDatapoints(RegisterDatapointsRequest) returns (RegisterDatapointsReply); + + // Provide a set of updated datapoint values to the broker. + // This is the unary equivalent of `StreamDatapoints` below and is better suited for cases + // where the frequency of updates is rather low. + // + // NOTE: The values provided in a single request are handled as a single update in the + // data broker. This ensures that any clients requesting (or subscribing to) a set of + // datapoints will get a consistent update, i.e. that either all values are updated or + // none are. + // + // Returns: any errors encountered updating the datapoints + // + rpc UpdateDatapoints(UpdateDatapointsRequest) returns (UpdateDatapointsReply); + + // Provide a stream with updated datapoint values to the broker. + // This is the streaming equivalent of `UpdateDatapoints` above and is better suited for + // cases where the frequency of updates is high. + // + // NOTE: The values provided in a single request are handled as a single update in the + // data broker. This ensures that any clients requesting (or subscribing to) a set of + // datapoints will get a consistent update, i.e. that either all values are updated or + // none are. + // + // Returns: any errors encountered updating the datapoints + // + rpc StreamDatapoints(stream StreamDatapointsRequest) returns (stream StreamDatapointsReply); +} + +message UpdateDatapointsRequest { + map datapoints = 1; +} + +message UpdateDatapointsReply { + map errors = 1; // If empty, everything went well +} + +message StreamDatapointsRequest { + map datapoints = 1; +} + +message StreamDatapointsReply { + map errors = 1; // If empty, everything went well +} + +message RegisterDatapointsRequest { + repeated RegistrationMetadata list = 1; +} + +message RegistrationMetadata { + // Name of the data point + // (e.g. "Vehicle.Cabin.Seat.Row1.Pos1.Position" or "Vehicle.Speed") + string name = 1; + DataType data_type = 2; + string description = 3; + ChangeType change_type = 4; + + // int32 min_update_hz = 10; // Only for CONTINUOUS + // int32 max_update_hz = 11; // Only for CONTINUOUS +}; + +message RegisterDatapointsReply { + // Maps each data point name passed in RegisterDatapointsRequest to a data point id + map results = 1; +} diff --git a/kuksa-cpp-client/proto/sdv/databroker/v1/types.proto b/kuksa-cpp-client/proto/sdv/databroker/v1/types.proto new file mode 100644 index 0000000..4c00219 --- /dev/null +++ b/kuksa-cpp-client/proto/sdv/databroker/v1/types.proto @@ -0,0 +1,184 @@ +/******************************************************************************** + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +syntax = "proto3"; +// Please do not add optional fields due to older proto3 versions limitations + +import "google/protobuf/timestamp.proto"; + +package sdv.databroker.v1; + +// Data type of a signal +// +// Protobuf doesn't support int8, int16, uint8 or uint16. +// These are mapped to sint32 and uint32 respectively. +// +enum DataType { + STRING = 0; + BOOL = 1; + INT8 = 2; + INT16 = 3; + INT32 = 4; + INT64 = 5; + UINT8 = 6; + UINT16 = 7; + UINT32 = 8; + UINT64 = 9; + FLOAT = 10; + DOUBLE = 11; + STRING_ARRAY = 20; + BOOL_ARRAY = 21; + INT8_ARRAY = 22; + INT16_ARRAY = 23; + INT32_ARRAY = 24; + INT64_ARRAY = 25; + UINT8_ARRAY = 26; + UINT16_ARRAY = 27; + UINT32_ARRAY = 28; + UINT64_ARRAY = 29; + FLOAT_ARRAY = 30; + DOUBLE_ARRAY = 31; +} + +enum DatapointError { + UNKNOWN_DATAPOINT = 0; + INVALID_TYPE = 1; + ACCESS_DENIED = 2; + INTERNAL_ERROR = 3; + OUT_OF_BOUNDS = 4; +} + +enum EntryType { + ENTRY_TYPE_UNSPECIFIED = 0; + ENTRY_TYPE_SENSOR = 1; + ENTRY_TYPE_ACTUATOR = 2; + ENTRY_TYPE_ATTRIBUTE = 3; +} + +enum ChangeType { + STATIC = 0; // Value never changes + ON_CHANGE = 1; // Updates are provided every time the value changes (i.e. + // window is open / closed) + CONTINUOUS = 2; // Value is updated continuously. Broker needs to tell + // provider the preferred (update) frequency. +} + +message StringArray { + repeated string values = 1; +} + +message BoolArray { + repeated bool values = 1; +} + +message Int32Array { + repeated sint32 values = 1; +} + +message Int64Array { + repeated sint64 values = 1; +} + +message Uint32Array { + repeated uint32 values = 1; +} + +message Uint64Array { + repeated uint64 values = 1; +} + +message FloatArray { + repeated float values = 1; +} + +message DoubleArray { + repeated double values = 1; +} + +message Datapoint { + // Timestamp of the value + google.protobuf.Timestamp timestamp = 1; + + // values + oneof value { + Failure failure_value = 10; + string string_value = 11; + bool bool_value = 12; + sint32 int32_value = 13; + sint64 int64_value = 14; + uint32 uint32_value = 15; + uint64 uint64_value = 16; + float float_value = 17; + double double_value = 18; + StringArray string_array = 21; + BoolArray bool_array = 22; + Int32Array int32_array = 23; + Int64Array int64_array = 24; + Uint32Array uint32_array = 25; + Uint64Array uint64_array = 26; + FloatArray float_array = 27; + DoubleArray double_array = 28; + } + + enum Failure { + // The data point is known, but doesn't have a valid value + INVALID_VALUE = 0; + // The data point is known, but no value is available + NOT_AVAILABLE = 1; + // Unknown datapoint + UNKNOWN_DATAPOINT = 2; + // Access denied + ACCESS_DENIED = 3; + // Unexpected internal error + INTERNAL_ERROR = 4; + } +} + +message Metadata { + int32 id = 1; + EntryType entry_type = 2; + string name = 4; + DataType data_type = 5; + ChangeType change_type = 6; // CONTINUOUS or STATIC or ON_CHANGE + string description = 7; + + // Value restrictions checked/enforced by Databroker. + Allowed allowed = 10; + ValueRestriction min = 11; + ValueRestriction max = 12; +} + +message Allowed { + oneof values { + StringArray string_values = 1; + Int32Array int32_values = 3; + Int64Array int64_values = 4; + Uint32Array uint32_values = 5; + Uint64Array uint64_values = 6; + FloatArray float_values = 7; + DoubleArray double_values = 8; + } +} + +message ValueRestriction { + oneof typed_value { + string string = 1; + bool bool = 2; + sint32 int32 = 3; + sint64 int64 = 4; + uint32 uint32 = 5; + uint64 uint64 = 6; + float float = 7; + double double = 8; + } +} diff --git a/kuksa-cpp-client/src/CMakeLists.txt b/kuksa-cpp-client/src/CMakeLists.txt new file mode 100644 index 0000000..5431aa0 --- /dev/null +++ b/kuksa-cpp-client/src/CMakeLists.txt @@ -0,0 +1,32 @@ +# ******************************************************************************** +# Copyright (c) 2023 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional information +# regarding copyright ownership. +# +# This program and the accompanying materials are made available under the terms +# of the Apache License 2.0 which is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# *******************************************************************************/ +add_library(${PROJECT_NAME} SHARED kuksa_client.cpp) + +target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_link_libraries(${PROJECT_NAME} spdlog::spdlog gRPC::grpc++ + protobuf::libprotobuf proto-objects) + +set_target_properties( + ${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +include(generate.cmake) + +install( + TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install(FILES ${PROJECT_SOURCE_DIR}/include/kuksaclient.h + DESTINATION include/kuksaclient) diff --git a/kuksa-cpp-client/src/generate.cmake b/kuksa-cpp-client/src/generate.cmake new file mode 100644 index 0000000..fafcf03 --- /dev/null +++ b/kuksa-cpp-client/src/generate.cmake @@ -0,0 +1,61 @@ +# ******************************************************************************** +# Copyright (c) 2023 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional information +# regarding copyright ownership. +# +# This program and the accompanying materials are made available under the terms +# of the Apache License 2.0 which is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# *******************************************************************************/ +# Recursively find all .proto files +file(GLOB_RECURSE proto_files "${CMAKE_SOURCE_DIR}/proto/*.proto") + +add_library(proto-objects OBJECT ${PROTO_FILES}) + +target_link_libraries(proto-objects PUBLIC protobuf::libprotobuf gRPC::grpc++) + +set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/proto") +set(PROTO_IMPORT_DIRS "${CMAKE_SOURCE_DIR}/proto") + +# Ensure the output directory exists +file(MAKE_DIRECTORY ${PROTO_BINARY_DIR}) + +target_include_directories(proto-objects + PUBLIC "$") + +foreach(proto_file ${proto_files}) + # Generate protobuf code for each file + protobuf_generate( + TARGET + proto-objects + IMPORT_DIRS + ${PROTO_IMPORT_DIRS} + PROTOC_OUT_DIR + "${PROTO_BINARY_DIR}/${PROTO_DIR}" + PROTOS + ${proto_file}) + + # Generate gRPC services + protobuf_generate( + TARGET + proto-objects + LANGUAGE + grpc + GENERATE_EXTENSIONS + .grpc.pb.h + .grpc.pb.cc + PLUGIN + "protoc-gen-grpc=$" + IMPORT_DIRS + ${PROTO_IMPORT_DIRS} + PROTOC_OUT_DIR + "${PROTO_BINARY_DIR}/${PROTO_DIR}" + PROTOS + ${proto_file}) +endforeach() + +# install the proto generated types +install(DIRECTORY ${PROTO_BINARY_DIR}/ DESTINATION include/) diff --git a/kuksa-cpp-client/src/kuksa_client.cpp b/kuksa-cpp-client/src/kuksa_client.cpp new file mode 100644 index 0000000..4e8b364 --- /dev/null +++ b/kuksa-cpp-client/src/kuksa_client.cpp @@ -0,0 +1,481 @@ +/******************************************************************************** + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License 2.0 which is available at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "kuksa/val/v1/types.pb.h" +#include "kuksa/val/v1/val.pb.h" +#include "kuksa/val/v2/types.pb.h" +#include "kuksa/val/v2/val.pb.h" +#include "kuksaclient.h" +#include +#include +#include +#include +#include +#include +#include + +namespace kuksa { + +const std::string loggerName = "kuksaClient"; + +class KuksaClient::KuksaClientImpl { +public: + KuksaClientImpl() { + spdlog::set_level(spdlog::level::debug); + mLogger = spdlog::stdout_color_mt(loggerName); + mIsRunning = false; + } + + ~KuksaClientImpl() { + if (mIsRunning.load()) { + mIsRunning.store(false); + mLogger->info("Stopping active subscription..."); + + // Ensure the thread joins if necessary + if (mSubscribeThread.joinable()) { + mSubscribeThread.join(); + } + } + }; + + // --------------------------- V1 APIs ------------------------------- + + bool connect_v1(const std::string &server) { + mLogger->info("Connect V1 called on {}", server); + mChannel = grpc::CreateChannel(server, grpc::InsecureChannelCredentials()); + mStubV1 = kuksa::val::v1::VAL::NewStub(mChannel); + + auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(2); + auto connected = mChannel->WaitForConnected(deadline); + + if (!connected) { + mLogger->debug("Failed to connect to server within deadline"); + return false; + } + + return true; + } + + bool get(const std::string &datapoint, kuksa::val::v1::Datapoint &value) { + mLogger->info("get v1 invoked on {}", datapoint); + + if (!mStubV1) { + return false; + } + + grpc::ClientContext context; + kuksa::val::v1::GetRequest request; + kuksa::val::v1::GetResponse response; + + const grpc::Status status = mStubV1->Get(&context, request, &response); + + if (!status.ok()) { + mLogger->debug("RPC failed: {}", status.error_message()); + return false; + } + + return true; + } + + bool set(const std::string &datapoint, + const kuksa::val::v1::Datapoint &value) { + mLogger->info("set v1 invoked on {}", datapoint); + + if (!mStubV1) { + mLogger->debug("Client not connected"); + return false; + } + + grpc::ClientContext context; + kuksa::val::v1::SetRequest request; + kuksa::val::v1::SetResponse response; + + kuksa::val::v1::EntryUpdate *update = request.add_updates(); + kuksa::val::v1::DataEntry *data_entry = update->mutable_entry(); + + // Set the path of the DataEntry (e.g., the datapoint) + data_entry->set_path(datapoint); + + // Set the value in the DataEntry + data_entry->mutable_value()->CopyFrom(value); + + const grpc::Status status = mStubV1->Set(&context, request, &response); + + if (!status.ok()) { + mLogger->debug("RPC failed: {}", status.error_message()); + return false; + } + + return true; + } + + void subscribe(const std::vector &datapoints, + kuksaCallbackV1 callback) { + + std::for_each(datapoints.begin(), datapoints.end(), + [this](const std::string &datapoint) { + mLogger->info("Starting subscription on {}", datapoint); + }); + + if (mIsRunning) { + mLogger->debug("Subscription already active"); + } + + auto ctx = std::make_unique(); + kuksa::val::v1::SubscribeRequest request; + + for (const auto &datapoint : datapoints) { + auto entry = request.add_entries(); + entry->set_path(datapoint); + entry->set_view(::kuksa::val::v1::VIEW_CURRENT_VALUE); + entry->add_fields(::kuksa::val::v1::Field::FIELD_VALUE); + entry->add_fields(::kuksa::val::v1::Field::FIELD_METADATA); + } + + // Create subscription stream + auto reader = mStubV1->Subscribe(ctx.get(), request); + mIsRunning.store(true); + + mSubscribeThread = + std::thread([this, reader = std::move(reader), ctx = std::move(ctx), + callback = std::move(callback)]() { + kuksa::val::v1::SubscribeResponse response; + mLogger->debug("in lambda..."); + + while (mIsRunning.load()) { + if (!reader->Read(&response)) { + mLogger->debug("Stream disconnected"); + break; + } + + for (const auto &entry : response.updates()) { + const auto &path = entry.entry().path(); + const auto &datapoint = entry.entry().actuator_target(); + + mLogger->debug("Received update for datapoint: {}", path); + + if (callback) { + callback(path, datapoint); + } + } + } + + // Handle stream completion + auto status = reader->Finish(); + }); + + mSubscribeThread.detach(); + } + + // --------------------------- V2 APIs ------------------------------- + + bool connect_v2(const std::string &server) { + mLogger->info("Connect V2 called on {}", server); + mChannel = grpc::CreateChannel(server, grpc::InsecureChannelCredentials()); + mStubV2 = kuksa::val::v2::VAL::NewStub(mChannel); + + auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(2); + auto connected = mChannel->WaitForConnected(deadline); + + if (!connected) { + mLogger->debug("Failed to connect to server within deadline"); + return false; + } + + return true; + } + + bool getValue(const std::string &datapoint, kuksa::val::v2::Value &value) { + mLogger->info("getValue invoked on {}", datapoint); + + if (!mStubV2) { + return false; + } + + grpc::ClientContext context; + kuksa::val::v2::GetValueRequest request; + kuksa::val::v2::GetValueResponse response; + + request.mutable_signal_id()->set_path(datapoint); + + const grpc::Status status = mStubV2->GetValue(&context, request, &response); + + if (!status.ok()) { + mLogger->debug("RPC failed: {}", status.error_message()); + return false; + } + + if (!response.has_data_point()) { + mLogger->debug("Response has no data point"); + return false; + } + + const auto &data_point = response.data_point(); + + if (!data_point.has_value()) { + mLogger->debug("Data point has no value"); + return false; + } + + value = data_point.value(); + + return true; + } + + void subscribe(const std::vector &datapoints, + kuksaCallbackV2 callback) { + + std::for_each(datapoints.begin(), datapoints.end(), + [this](const std::string &datapoint) { + mLogger->info("Starting subscription on {}", datapoint); + }); + + if (mIsRunning) { + mLogger->debug("Subscription already active"); + } + + auto ctx = std::make_unique(); + kuksa::val::v2::SubscribeRequest request; + + // Add paths and buffer size to request + for (const auto &datapoint : datapoints) { + request.add_signal_paths(datapoint); + } + request.set_buffer_size(10); + + // Create subscription stream + auto reader = mStubV2->Subscribe(ctx.get(), request); + mIsRunning.store(true); + + mSubscribeThread = + std::thread([this, reader = std::move(reader), ctx = std::move(ctx), + callback = std::move(callback)]() { + kuksa::val::v2::SubscribeResponse response; + + while (mIsRunning.load()) { + if (!reader->Read(&response)) { + mLogger->debug("Stream disconnected"); + break; + } + + for (const auto &entry : response.entries()) { + const auto &path = entry.first; + const auto &datapoint = entry.second; + + mLogger->debug("Received update for datapoint: {}", path); + + if (callback) { + callback(path, datapoint.value()); + } + } + } + + // Handle stream completion + auto status = reader->Finish(); + }); + + mSubscribeThread.detach(); + } + + bool actuate(const std::string &datapoint, + const kuksa::val::v2::Value &value) { + mLogger->info("actuate invoked on {}", datapoint); + + if (!mStubV2) { + mLogger->debug("Client not connected"); + return false; + } + + grpc::ClientContext context; + kuksa::val::v2::ActuateRequest request; + kuksa::val::v2::ActuateResponse response; + + request.mutable_signal_id()->set_path(datapoint); + *request.mutable_value() = value; + + const grpc::Status status = mStubV2->Actuate(&context, request, &response); + + if (!status.ok()) { + mLogger->debug("RPC failed: {}", status.error_message()); + return false; + } + + return true; + } + + bool publishValue(const std::string &datapoint, + const kuksa::val::v2::Value &value) { + mLogger->info("publishValue invoked on {}", datapoint); + + if (!mStubV2) { + mLogger->debug("Client not connected"); + return false; + } + + grpc::ClientContext context; + kuksa::val::v2::PublishValueRequest request; + kuksa::val::v2::PublishValueResponse response; + + request.mutable_signal_id()->set_path(datapoint); + auto *data_point = request.mutable_data_point(); + *data_point->mutable_value() = value; + + // create timestamp + auto now = std::chrono::system_clock::now(); + auto seconds = std::chrono::duration_cast( + now.time_since_epoch()); + auto nanos = std::chrono::duration_cast( + now.time_since_epoch() - seconds); + + auto *timestamp = data_point->mutable_timestamp(); + timestamp->set_seconds(seconds.count()); + timestamp->set_nanos(nanos.count()); + + const grpc::Status status = + mStubV2->PublishValue(&context, request, &response); + + if (!status.ok()) { + mLogger->debug("RPC failed: {}", status.error_message()); + return false; + } + + return true; + } + + bool getServerInfo(kuksa::val::v2::GetServerInfoResponse &response) { + mLogger->info("getServerInfo invoked"); + + if (!mStubV2) { + mLogger->debug("Client not connected"); + return false; + } + + grpc::ClientContext context; + kuksa::val::v2::GetServerInfoRequest request; + + const grpc::Status status = + mStubV2->GetServerInfo(&context, request, &response); + + if (!status.ok()) { + mLogger->debug("RPC failed: {}", status.error_message()); + return false; + } + + return true; + } + + std::vector + getValues(const std::vector &datapoints) { + + std::for_each(datapoints.begin(), datapoints.end(), + [this](const std::string &datapoint) { + mLogger->info("GetValues invoked on {}", datapoint); + }); + + if (!mStubV2) { + return {}; + } + + grpc::ClientContext context; + kuksa::val::v2::GetValuesRequest request; + kuksa::val::v2::GetValuesResponse response; + + for (const auto &datapoint : datapoints) { + kuksa::val::v2::SignalID *signal_id = request.add_signal_ids(); + signal_id->set_path(datapoint); + } + + const grpc::Status status = + mStubV2->GetValues(&context, request, &response); + + if (status.ok()) { + std::vector datapoints; + for (const auto &dp : response.data_points()) { + datapoints.push_back(dp); + } + // happy path + return datapoints; + } else { + mLogger->debug("RPC failed: {}", status.error_message()); + return {}; + } + } + +private: + std::shared_ptr mChannel; + std::unique_ptr mStubV1; + std::unique_ptr mStubV2; + std::shared_ptr mLogger; + std::thread mSubscribeThread; + std::atomic mIsRunning; +}; + +// Public interface implementations +KuksaClient::KuksaClient() + : mKuksaClient(std::make_unique()) {} + +KuksaClient::~KuksaClient(){}; + +bool KuksaClient::connect_v1(const std::string &server) { + return mKuksaClient->connect_v1(server); +} + +bool KuksaClient::connect_v2(const std::string &server) { + return mKuksaClient->connect_v2(server); +} + +bool KuksaClient::get(const std::string &datapoint, + kuksa::val::v1::Datapoint &value) { + return mKuksaClient->get(datapoint, value); +} + +bool KuksaClient::getValue(const std::string &datapoint, + kuksa::val::v2::Value &value) { + return mKuksaClient->getValue(datapoint, value); +} + +bool KuksaClient::set(const std::string &datapoint, + const kuksa::val::v1::Datapoint &value) { + return mKuksaClient->set(datapoint, value); +} + +void KuksaClient::subscribe(const std::vector &datapoints, + kuksaCallbackV1 callback) { + return mKuksaClient->subscribe(datapoints, callback); +} + +void KuksaClient::subscribe(const std::vector &datapoints, + kuksaCallbackV2 callback) { + return mKuksaClient->subscribe(datapoints, callback); +} + +bool KuksaClient::actuate(const std::string &datapoint, + const kuksa::val::v2::Value &value) { + return mKuksaClient->actuate(datapoint, value); +} + +bool KuksaClient::publishValue(const std::string &datapoint, + const kuksa::val::v2::Value &value) { + return mKuksaClient->publishValue(datapoint, value); +} + +bool KuksaClient::getServerInfo( + kuksa::val::v2::GetServerInfoResponse &response) { + return mKuksaClient->getServerInfo(response); +} + +std::vector +KuksaClient::getValues(const std::vector &datapoints) { + return mKuksaClient->getValues(datapoints); +} + +} // namespace kuksa