From cbee09be85756d14226f5c16356809c4d396dd9f Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Mon, 17 Mar 2025 10:14:41 +0100 Subject: [PATCH] initial virtual sensor with protobuf --- database.proto | 21 ++++ random-sensor-protobuf/meson.build | 26 ++++ random-sensor-protobuf/random-sensor.cpp | 154 +++++++++++++++++++++++ sensor-query-cli/main.cpp | 30 +++++ sensor-query-cli/meson.build | 26 ++++ sensor.lt | 25 ++++ sensor.proto | 44 +++++++ 7 files changed, 326 insertions(+) create mode 100644 database.proto create mode 100644 random-sensor-protobuf/meson.build create mode 100644 random-sensor-protobuf/random-sensor.cpp create mode 100644 sensor-query-cli/main.cpp create mode 100644 sensor-query-cli/meson.build create mode 100644 sensor.lt create mode 100644 sensor.proto diff --git a/database.proto b/database.proto new file mode 100644 index 0000000..a7a46d3 --- /dev/null +++ b/database.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + + +message DatabaseStatus { + +} + +message TimeInterval { + +} + +message DataSlice { + +} + +service Database { + rpc get_status () returns (DatabaseStatus) {} + rpc fetch_data (TimeInterval) returns (DataSlice) {} +} + + diff --git a/random-sensor-protobuf/meson.build b/random-sensor-protobuf/meson.build new file mode 100644 index 0000000..0dc1f78 --- /dev/null +++ b/random-sensor-protobuf/meson.build @@ -0,0 +1,26 @@ +project('random-sensor-protobuf', 'cpp', +default_options: ['cpp_std=c++20']) + +protobuf_dep = dependency('protobuf') +grpc_dep = dependency('grpc++', required: true) + +proto_files = files('../sensor.proto') + +protobuf_gen = custom_target( + 'generate_protobuf', + input : proto_files, + output : ['sensor.pb.cc', 'sensor.pb.h', 'sensor.grpc.pb.cc', 'sensor.grpc.pb.h'], + command : [ + 'protoc', + '--proto_path=../../', + '--cpp_out', '.', + '--grpc_out', '.', + '--plugin=protoc-gen-grpc=/usr/bin/grpc_cpp_plugin', + '@INPUT@' + ], +) + +executable('random-sensor-protobuf', + ['random-sensor.cpp', protobuf_gen], + dependencies: [protobuf_dep, grpc_dep] +) diff --git a/random-sensor-protobuf/random-sensor.cpp b/random-sensor-protobuf/random-sensor.cpp new file mode 100644 index 0000000..90cbea2 --- /dev/null +++ b/random-sensor-protobuf/random-sensor.cpp @@ -0,0 +1,154 @@ +#include <iostream> +#include <chrono> +#include <cmath> +#include <random> +#include <semaphore> +#include <google/protobuf/arena.h> +#include <thread> + +#include <grpc/grpc.h> +#include <grpcpp/security/server_credentials.h> +#include <grpcpp/server.h> +#include <grpcpp/server_builder.h> +#include <grpcpp/server_context.h> + +#include "sensor.pb.h" +#include "sensor.grpc.pb.h" + +template < size_t T_Capacity > +struct RandomSensor + : public Sensor::Service +{ +private: + google::protobuf::Arena arena; + + std::chrono::time_point<std::chrono::high_resolution_clock> online_since; + std::chrono::duration<uint64_t, std::milli> period_duration; + uint32_t chunk_size; + uint32_t next_free; + uint32_t next_full; + + float bat_charge; + + std::counting_semaphore<> sem_full; + std::counting_semaphore<> sem_free; + + float data[ T_Capacity ]; + std::thread sensor_thread; + + void generate_value() + { + sem_free.acquire(); + + data[ next_free ] = (float) next_free / 100.0; + next_free = (next_free + 1) % T_Capacity; + + sem_full.release(); + } + +public: + RandomSensor() + : online_since(std::chrono::high_resolution_clock::now()) + , period_duration( 10 ) + , chunk_size( 8192 ) + , next_free( 0 ) + , next_full( 0 ) + , bat_charge( 8200.0 ) + , sem_full( 0 ) + , sem_free( T_Capacity ) + , sensor_thread([&] { + while(bat_charge > 0) { + generate_value(); + bat_charge -= 0.01; + std::this_thread::sleep_for( period_duration ); + } + }) + {} + + ~RandomSensor() { + sensor_thread.join(); + } + + uint32_t n_chunks_capacity() const { + return T_Capacity / chunk_size; + } + + uint32_t n_full_chunks() const { + return (( next_free - next_full ) % T_Capacity) / chunk_size; + } + + uint32_t n_empty_chunks() const { + return n_chunks_capacity() - n_full_chunks(); + } + + grpc::Status set_sampling_period( + grpc::ServerContext * context, + Duration const * new_sampling_period, + SetSamplingPeriodResult * result + ) override { + // todo + return grpc::Status::OK; + } + + grpc::Status pop_data_chunk( + grpc::ServerContext * context, + DataChunkRequest const * request, + DataChunk * chunk + ) override { + Duration * rate = google::protobuf::Arena::Create<Duration>(&arena); + rate->set_millis(period_duration.count()); + + chunk->set_allocated_period(rate); + + for( uint32_t i = 0; i < chunk_size; ++i ) { + Temperature * temp = chunk->add_data(); + + sem_full.acquire(); + + temp->set_celsius( data[next_full] ); + next_full = (next_full + 1) % T_Capacity; + + sem_free.release(); + } + + return grpc::Status::OK; + } + + grpc::Status get_status( + grpc::ServerContext * context, + StatusRequest const * request, + SensorStatus * status + ) override + { + status->set_name("random sensor (protobuf)"); + status->mutable_online_since()->set_millis_since_epoch( + std::chrono::duration_cast<std::chrono::milliseconds>(online_since.time_since_epoch()).count() + ); + status->mutable_battery_capacity()->set_mah(8200); + status->mutable_battery_charge()->set_mah(bat_charge); + status->mutable_cur_sampling_period()->set_millis(period_duration.count()); + status->mutable_max_sampling_period()->set_millis(10); + status->set_max_chunk_size(T_Capacity); + status->set_cur_chunk_size(chunk_size); + status->set_n_chunk_capacity( n_chunks_capacity() ); + status->set_n_full_data_chunks( n_full_chunks() ); + status->set_n_empty_data_chunks( n_empty_chunks() ); + + return grpc::Status::OK; + } +}; + +int main() { + RandomSensor< 1048576 > sensor; + + std::string server_address("0.0.0.0:50051"); + + grpc::ServerBuilder builder; + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + builder.RegisterService(&sensor); + std::unique_ptr<grpc::Server> server(builder.BuildAndStart()); + std::cout << "Server listening on " << server_address << std::endl; + server->Wait(); + + return 0; +} diff --git a/sensor-query-cli/main.cpp b/sensor-query-cli/main.cpp new file mode 100644 index 0000000..b0c71b5 --- /dev/null +++ b/sensor-query-cli/main.cpp @@ -0,0 +1,30 @@ +#include <iostream> +#include <memory> +#include <grpc/grpc.h> +#include <grpcpp/channel.h> +#include <grpcpp/client_context.h> +#include <grpcpp/create_channel.h> +#include <grpcpp/security/credentials.h> + +#include "sensor.pb.h" +#include "sensor.grpc.pb.h" + +int main() { + auto channel = std::shared_ptr(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials())); + auto stub = Sensor::NewStub(channel); + + grpc::ClientContext context; + + StatusRequest request; + SensorStatus status; + grpc::Status rpc_status = stub->get_status(&context, request, &status); + + if( rpc_status.ok() ) { + std::cout << "Sensor Status:" << std::endl; + std::cout << status.DebugString() << std::endl; + } else { + std::cout << "get_status() failed" << std::endl; + } + + return 0; +} diff --git a/sensor-query-cli/meson.build b/sensor-query-cli/meson.build new file mode 100644 index 0000000..859e30d --- /dev/null +++ b/sensor-query-cli/meson.build @@ -0,0 +1,26 @@ +project('sensor-query-cli', 'cpp', +default_options: ['cpp_std=c++20']) + +protobuf_dep = dependency('protobuf') +grpc_dep = dependency('grpc++', required: true) + +proto_files = files('../sensor.proto') + +protobuf_gen = custom_target( + 'generate_protobuf', + input : proto_files, + output : ['sensor.pb.cc', 'sensor.pb.h', 'sensor.grpc.pb.cc', 'sensor.grpc.pb.h'], + command : [ + 'protoc', + '--proto_path=../../', + '--cpp_out', '.', + '--grpc_out', '.', + '--plugin=protoc-gen-grpc=/usr/bin/grpc_cpp_plugin', + '@INPUT@' + ], +) + +executable('sensor-query-cli', + ['main.cpp', protobuf_gen], + dependencies: [protobuf_dep, grpc_dep] +) diff --git a/sensor.lt b/sensor.lt new file mode 100644 index 0000000..7172b65 --- /dev/null +++ b/sensor.lt @@ -0,0 +1,25 @@ + +trait SensorStatus = { + name : [ Char ] ; + online_since : TimePoint ; + battery_charge : Energy ; + battery_capacity : Energy ; + min_sampling_period : Duration ; + cur_sampling_period : Duration ; + max_chunk_size : ℤ ; + cur_chunk_size : ℤ ; + n_chunk_capacity : ℤ ; + n_full_data_chunks : ℤ ; + n_empty_data_chunks : ℤ ; +} + +trait DataChunk = { + begin : TimePoint ; + data : [ Temperature ] ; +} + +trait Sensor = { + get_status : {} -> SensorStatus ; + set_sampling_period : Duration -> (Ok | OutOfRange); + pop_data_chunk : {} -> DataChunk ; +} diff --git a/sensor.proto b/sensor.proto new file mode 100644 index 0000000..e3cbbc7 --- /dev/null +++ b/sensor.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +message Energy { + uint32 mAh = 1; +} +message TimePoint { + uint64 millis_since_epoch = 1; +} +message Duration { + uint32 millis = 1; +} +message Temperature { + double celsius = 1; +} + +message StatusRequest {} +message SensorStatus { + string name = 1; + TimePoint online_since = 2; + Energy battery_charge = 3; + Energy battery_capacity = 4; + Duration max_sampling_period = 5; + Duration cur_sampling_period = 6; + uint32 max_chunk_size = 7; + uint32 cur_chunk_size = 8; + uint32 n_chunk_capacity = 9; + uint32 n_full_data_chunks = 10; + uint32 n_empty_data_chunks = 11; +} + +message DataChunkRequest {} +message DataChunk { + TimePoint begin = 1; + Duration period = 2; + repeated Temperature data = 3; +} + +message SetSamplingPeriodResult {} + +service Sensor { + rpc get_status (StatusRequest) returns (SensorStatus) {} + rpc set_sampling_period (Duration) returns (SetSamplingPeriodResult) {} + rpc pop_data_chunk (DataChunkRequest) returns (DataChunk) {} +}