Skip to content

Quick start

Choice of Technology

See Technology Explainer for more information on the two available API choices for Command and Control. gRPC and JSON will both support control of Sonardyne instruments listed here. gRPC is a more expandable interface which will be extended to include real-time data and more configuration options, and as such is recommended for most integrations.

Connecting to the API

Configuring gRPC or JSON via Web UI

The 'External Control' page in the webUI (located under Configuration>System>Connections) allows selection of a gRPC or JSON interface. The JSON interface operates via any of the ports specified under the 'Network' tab, whereas the gRPC interface operates a TCP server on the port number specified in the webUI.

Network Ports

Please note there is currently no cross-validation between the gRPC server and the other Network ports, you must ensure that the gRPC port number is not already in use in the Network tab.

Example screenshot from SPRINT-Nav Mini Web UI

Accessing .proto files

The .proto files which define the API will be needed before messages can be sent or received via gRPC. These files can be downloaded from the Releases page of the son-idl repo. They can also be found in the idl folder of this repo, alongside an explanation of all fields, provided by the README.md in the idl folder.

Sending initial commands

The API implements three functions:

  • GetVersion will return the API version number
  • GetState will return the settings of the instrument
  • SetState will update the instrument settings

Development/Debugging Tools

See external reference.

Development of a gRPC client


Python Example

These steps are largely based on the grpc Python tutorial.

  • Install gRPC and gRPC tools (protoc and code generation): python -m pip install grpcio grpcio-tools
  • Download .proto files and put them in a protos directory in your python project.
  • Generate python code from the .protos: python -m grpc_tools.protoc --proto_path=./protos --python_out=. --grpc_python_out=. ./protos/*.proto

This will generate a few files - as a grpc client we are primarily interested in importing service_pb2_grpc, which will allow us to use GetState, SetState, and GetVersion.

Example python code
example_code/py/GrpcClient.py
#Copyright 2024 Sonardyne

#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the “Software”), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is furnished
#to do so, subject to the following conditions:

#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the software.

#THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
#WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import proto_generate
proto_generate.proto_generate()

import grpc
import google.protobuf.any_pb2

from sonardyne_public.idl.common import version_pb2
from sonardyne_public.idl.configuration import dvl_configuration_pb2
from sonardyne_public.idl.services import state_service_pb2_grpc
from sonardyne_public.idl.configuration import reset_configuration_pb2
from sonardyne_public.idl.configuration import aiding_configuration_pb2
from sonardyne_public.idl.configuration import configuration_envelope_pb2
from sonardyne_public.idl.configuration import sound_velocity_configuration_pb2

from google.protobuf.json_format import MessageToJson


def SetExampleState(stub):    
    # Create the envelope containing the new config.
    #  Envelopes also contain 'timestamp' and 'result' fields which are populated in the instrument reply.
    configuration_envelope = configuration_envelope_pb2.ConfigurationEnvelope()

    # Each type of configuration is stored as a list. This is reserved for future use as not all units support multiple configurations.

    # Create and add a reset config to perform a soft reset
    reset_config = reset_configuration_pb2.ResetConfiguration(reset_type="SOFT_RESET")        
    configuration_envelope.configuration.add().CopyFrom(google.protobuf.any_pb2.Any().Pack(reset_config))

    # Create and add an aiding config to enable GNSS aiding and disable USBL aiding
    aiding_config_enable_gnss = aiding_configuration_pb2.AidingConfiguration(enable_gnss="ENABLED", enable_usbl="DISABLED")
    configuration_envelope.configuration.add().CopyFrom(google.protobuf.any_pb2.Any().Pack(aiding_config_enable_gnss))

    # Create and add a sound velocity config to set the SV source to salinity-derived and set the SV salinity to an arbitrary value
    sv_config_salinity_derived = sound_velocity_configuration_pb2.SoundVelocityConfiguration(sound_velocity_type="INTERNAL_SALINITY", manual_salinity_value=32.1)
    configuration_envelope.configuration.add().CopyFrom(google.protobuf.any_pb2.Any().Pack(sv_config_salinity_derived))

    # Create and add a DVL config to set the update rate to an arbitrary choice
    dvl_config_1Hz = dvl_configuration_pb2.DopplerVelocityLogConfiguration(update_rate="FIXED_1HZ")
    configuration_envelope.configuration.add().CopyFrom(google.protobuf.any_pb2.Any().Pack(dvl_config_1Hz))    

    # Perform the SetState with the new envelope
    result_envelope = stub.SetState(configuration_envelope)

    # Returned envelope contains the current state of the updated settings, as well as a result field
    print("Received ConfigurationEnvelope reply:\n{}".format(result_envelope))

with grpc.insecure_channel('127.0.0.1:1234') as channel: # NOTE: Change the IP Address and Port to match the Instrument and its gRPC configuration.
    stub = state_service_pb2_grpc.StateServiceStub(channel)

    # Get the version number of the API
    version_result = stub.GetVersion(version_pb2.VersionRequest())
    print("Called GetVersion: v{}.{}".format(version_result.major, version_result.minor))

    # Get the current state of the instrument
    result_config_envelope = stub.GetState(configuration_envelope_pb2.ConfigurationRequest(requestor="PyGrpcClient"))

    for (config) in result_config_envelope.configuration:        
        if config.Is(aiding_configuration_pb2.AidingConfiguration.DESCRIPTOR):
            aiding_config = aiding_configuration_pb2.AidingConfiguration()
            config.Unpack(aiding_config)
            print("Current Aiding Configuration: {}".format(MessageToJson(aiding_config)))
        if config.Is(dvl_configuration_pb2.DvlConfiguration.DESCRIPTOR):
            dvl_config = dvl_configuration_pb2.DvlConfiguration()
            config.Unpack(dvl_config)
            print("Current DVL Configuration: {}".format(MessageToJson(dvl_config)))
        if config.Is(reset_configuration_pb2.ResetConfiguration.DESCRIPTOR):
            reset_config = reset_configuration_pb2.ResetConfiguration()
            config.Unpack(reset_config)
            print("Current Reset Configuration: {}".format(MessageToJson(reset_config)))
        if config.Is(sound_velocity_configuration_pb2.SoundVelocityConfiguration.DESCRIPTOR):
            sound_velocity_config = sound_velocity_configuration_pb2.SoundVelocityConfiguration()
            config.Unpack(sound_velocity_config)        
            print("Current Sound Velocity Configuration: {}".format(MessageToJson(sound_velocity_config)))        

C# Example

The recommended implementation for gRPC with .NET is now grpc-dotnet. Microsoft provide a tutorial on its use.

The .csproj file needs to be modified to include the proto files and proto directory, and to specify the project as a gRPC client, e.g.
<Protobuf Include="service.proto" ProtoRoot="protos/" GrpcServices="Client"/>
Note that a wildcard * can be used e.g. Include="*.proto", however this was found to cause problems with certain ProtoRoot directories and be less clear when debugging.

Example C# code
example_code/cs/SonGrpcClient.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable> 
  </PropertyGroup>

  <ItemGroup>
    <Compile Remove="sonardyne-public\idl\common\Primitives.cs" />
    <Compile Remove="sonardyne-public\idl\common\Result.cs" />
    <Compile Remove="sonardyne-public\idl\common\Timestamp.cs" />
    <Compile Remove="sonardyne-public\idl\common\Uid.cs" />
    <Compile Remove="sonardyne-public\idl\common\Version.cs" />
    <Compile Remove="sonardyne-public\idl\configuration\AidingConfiguration.cs" />
    <Compile Remove="sonardyne-public\idl\configuration\ConfigurationEnvelope.cs" />
    <Compile Remove="sonardyne-public\idl\configuration\DvlConfiguration.cs" />
    <Compile Remove="sonardyne-public\idl\configuration\ResetConfiguration.cs" />
    <Compile Remove="sonardyne-public\idl\configuration\ShutdownConfiguration.cs" />
    <Compile Remove="sonardyne-public\idl\configuration\SoundVelocityConfiguration.cs" />
    <Compile Remove="sonardyne-public\idl\services\StateService.cs" />
    <Compile Remove="sonardyne-public\idl\services\StateServiceGrpc.cs" />
  </ItemGroup>

  <ItemGroup>
    <None Include="sonardyne-public\idl\common\Primitives.cs" />
    <None Include="sonardyne-public\idl\common\Result.cs" />
    <None Include="sonardyne-public\idl\common\Timestamp.cs" />
    <None Include="sonardyne-public\idl\common\Uid.cs" />
    <None Include="sonardyne-public\idl\common\Version.cs" />
    <None Include="sonardyne-public\idl\configuration\AidingConfiguration.cs" />
    <None Include="sonardyne-public\idl\configuration\ConfigurationEnvelope.cs" />
    <None Include="sonardyne-public\idl\configuration\DvlConfiguration.cs" />
    <None Include="sonardyne-public\idl\configuration\ResetConfiguration.cs" />
    <None Include="sonardyne-public\idl\configuration\ShutdownConfiguration.cs" />
    <None Include="sonardyne-public\idl\configuration\SoundVelocityConfiguration.cs" />
    <None Include="sonardyne-public\idl\services\StateService.cs" />
    <None Include="sonardyne-public\idl\services\StateServiceGrpc.cs" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.27.2" />
    <PackageReference Include="Grpc.Net.client" Version="2.63.0" />
    <PackageReference Include="Grpc.Tools" Version="2.64.0">
      <PrivateAssets>all</PrivateAssets>      
    </PackageReference>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\common\primitives.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\common\result.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\common\timestamp.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\common\uid.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\common\version.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\configuration\reset_configuration.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\configuration\aiding_configuration.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None"> 
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\configuration\configuration_envelope.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\configuration\dvl_configuration.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\configuration\shutdown_configuration.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\configuration\sound_velocity_configuration.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcServices="None">
      </Protobuf>
      <Protobuf Include="$(ProjectDir)..\..\..\sonardyne-public\idl\services\state_service.proto" ProtoRoot="$(ProjectDir)..\..\..\" OutputDir="$(ProjectDir)" GrpcOutputDir="$(ProjectDir)" GrpcServices="Client">
      </Protobuf>
  </ItemGroup>

</Project>
example_code/cs/Program.cs
//Copyright 2024 Sonardyne

//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the “Software”), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is furnished
//to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the software.

//THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using Grpc.Net.Client;
using Google.Protobuf.WellKnownTypes;
using Sonardyne.Api.Pub.Common;
using Sonardyne.Api.Pub.Configuration;

namespace Sonardyne.Api.Example
{
    internal class Program
    {
        // This example demonstrates how to connect to and get the current state of the instrument.
        static void Main(string[] args)
        {
            // NOTE: Change the IP Address and Port to match the instrument and its gRPC configuration.
            const string address = "http://127.0.0.1:1234"; 

            var channel = GrpcChannel.ForAddress(address); 
            var client = new StateService.StateServiceClient(channel);

            // Get the version
            var getVersionReply = client.GetVersion(new VersionRequest());
            Console.WriteLine($"\nCalled GetVersion: v.{getVersionReply.Major}.{getVersionReply.Minor}");

            // Get the current state of the instrument            
            var getStateReply = client.GetState(new ConfigurationRequest());

            if (getStateReply?.Configuration.Count > 0)
            {
                OutputState(getStateReply);
            }

            // Create a device configuration.
            // Create the envelope containing the new config.
            // Envelopes also contain 'timestamp' and 'result' fields which are populated in the instrument reply.
            var configurationEnvelope = new ConfigurationEnvelope();

            // Create and add a reset config to perform a soft reset
            var resetConfig = new ResetConfiguration();

            resetConfig.ResetType = new ResetType { Value = ResetType.Types.ResetTypeEnum.SoftReset };            
            configurationEnvelope.Configuration.Add(Any.Pack(resetConfig));

            // Create and add an aiding config to enable GNSS aiding and disable USBL aiding
            var aidingConfig = new AidingConfiguration();
            aidingConfig.EnableGnss = new AidingState { Value = AidingState.Types.AidingStateEnum.Enabled };
            aidingConfig.EnableUsbl = new AidingState { Value = AidingState.Types.AidingStateEnum.Disabled };
            configurationEnvelope.Configuration.Add(Any.Pack(aidingConfig));

            // Create and add a sound velocity config to set the SV source to salinity-derived and set the SV salinity to an arbitrary value
            var svConfig = new SoundVelocityConfiguration();
            svConfig.SoundVelocityType = new SoundVelocityType { Value = SoundVelocityType.Types.SoundVelocityTypeEnum.External };
            svConfig.ManualSalinityValuePartsPerThousand = new BoundedDouble { Value = 32.1 };
            configurationEnvelope.Configuration.Add(Any.Pack(svConfig));

            // Create and add a DVL config to set the update rate to an arbitrary choice
            var dvlConfig = new DvlConfiguration();
            dvlConfig.UpdateRate = new DvlUpdateRate { Value = DvlUpdateRate.Types.DvlUpdateRateEnum.Fixed1Hz };
            configurationEnvelope.Configuration.Add(Any.Pack(dvlConfig));

            // Perform the SetState with the new envelope
            Console.WriteLine($"\nSetting State...");            
            var replyEnvelope = client.SetState(configurationEnvelope);

            // Returned envelope contains the current state of the updated settings, as well as a result field
            if (getStateReply?.Configuration.Count > 0)
            {
                OutputState(replyEnvelope);
            }            
        }        

        static void OutputState(ConfigurationEnvelope configurationEnvelope)
        {
            foreach (var configuration in configurationEnvelope.Configuration)
            {
                if (configuration.Is(AidingConfiguration.Descriptor))
                {
                    Console.WriteLine($"Aiding configuration rceived = {configuration.Unpack<AidingConfiguration>()}");
                }                
                else if (configuration.Is(ResetConfiguration.Descriptor))
                {
                    Console.WriteLine($"Reset configuration rceived = {configuration.Unpack<ResetConfiguration>()}");
                }
                else if (configuration.Is(SoundVelocityConfiguration.Descriptor))
                {
                    Console.WriteLine($"Sound velocity configuration rceived = {configuration.Unpack<SoundVelocityConfiguration>()}");
                }
                else if (configuration.Is(DvlConfiguration.Descriptor))
                {
                    Console.WriteLine($"DVL configuration rceived = {configuration.Unpack<DvlConfiguration>()}");
                }                                
                else
                {
                    Console.WriteLine("Unknown configuration");
                }
            }
        }
    }
}

C++ Example

As C++ has no universally accepted standard for managing project dependencies, gRPC supports several methods. The supplied C++ example code makes use of cmake, using find_package. This can only find software installed on your system, so gRPC will need to be installed from source using cmake. The installation location will then be referenced in cmake options to allow find_package to work.

The .proto files can be built automatically using a custom command in cmake, as per the example code below. Alternatively, the source/header files can be generated from the .proto files manually with the following command:

protoc --cpp_out=./proto-cpp-out/ --grpc_out=./proto-cpp-out/ --proto_path=protos/ protos/*.proto

  • --cpp_out specifies where to put the generated proto source/header files
  • --grpc_out specifies where to put the generated grpc source/header files
  • --proto_path specifies where the .proto files are located (used when proto files reference each other)
  • The final parameter specifies the .proto file(s) to be compiled.

protoc may require an extra parameter to specify the location of the gRPC C++ plugin, for example under Linux this could be:
--plugin=protoc-gen-grpc='which grpc_cpp_plugin'

The example client code below made use of this guide.

Example C++ code
example_code/cpp/CMakeLists.txt
#Copyright 2023 Sonardyne

#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
#documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
#rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
#permit persons to whom the Software is furnished to do so, subject to the following conditions:

#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
#Software.

#THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
#WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

cmake_minimum_required(VERSION 3.26)
project(grpc_proto_example_cpp)

set(CMAKE_CXX_STANDARD 17)

find_package(Protobuf CONFIG REQUIRED)
message("Protobuf v" ${Protobuf_VERSION})

if(CMAKE_CROSSCOMPILING)
    find_program(_PROTOBUF_PROTOC protoc)
else()
    set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
endif()

find_package(gRPC CONFIG REQUIRED)
message("gRPC v" ${gRPC_VERSION})

if(CMAKE_CROSSCOMPILING)
    find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
else()
    set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:gRPC::grpc_cpp_plugin>)
endif()

add_subdirectory(src)
example_code/cpp/src/CMakeLists.txt
#Copyright 2024 Sonardyne

#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
#documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
#rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
#permit persons to whom the Software is furnished to do so, subject to the following conditions:

#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
#Software.

#THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
#WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

project (son_grpc_proto)

message("CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")

file(GLOB COMMON_PROTO        "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../sonardyne-public/idl/common/*.proto")
file(GLOB CONFIGURATION_PROTO "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../sonardyne-public/idl/configuration/*.proto")
file(GLOB SERVICES_PROTO      "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../sonardyne-public/idl/services/*.proto")

add_library(${PROJECT_NAME} OBJECT ${PROTO_SRC_COMMON_FILES}
                                   ${PROTO_SRC_CONFIGURATION}
                                   ${PROTO_SRC_SERVICE})

target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_BINARY_DIR}/sonardyne-public/")
target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_BINARY_DIR}/sonardyne-public/idl/common/")
target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_BINARY_DIR}/sonardyne-public/idl/configuration/")
target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_BINARY_DIR}/sonardyne-public/idl/services/")
target_include_directories(${PROJECT_NAME} PUBLIC "$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>")


target_sources(${PROJECT_NAME} PUBLIC ${COMMON_PROTO}
                                      ${CONFIGURATION_PROTO}
                                      ${SERVICES_PROTO})

target_link_libraries(${PROJECT_NAME} PUBLIC protobuf::libprotobuf gRPC::grpc++)

#Generate the protobuf files
protobuf_generate(TARGET ${PROJECT_NAME} IMPORT_DIRS ../../../../../
                         PROTOC_OUT_DIR
                         ${CMAKE_BINARY_DIR}
                         LANGUAGE cpp)

#Generate the gRPC files
protobuf_generate(TARGET ${PROJECT_NAME} LANGUAGE grpc
                         GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc PLUGIN "protoc-gen-grpc=\$<TARGET_FILE:gRPC::grpc_cpp_plugin>"
                         IMPORT_DIRS ../../../../../
                         PROTOC_OUT_DIR ${CMAKE_BINARY_DIR})

add_executable(son_grpc_client main.cpp)
target_link_libraries(son_grpc_client ${PROJECT_NAME})
example_code/cpp/src/main.cpp
//Copyright 2023 Sonardyne

//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
//documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
//rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
//permit persons to whom the Software is furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
//Software.

//THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
//OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include <iostream>

#include <grpc/grpc.h>
#include <grpcpp/channel.h>
#include <grpcpp/client_context.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#include <google/protobuf/util/json_util.h>

#include <state_service.grpc.pb.h>
#include <aiding_configuration.pb.h>

using namespace sonardyne::api;
using namespace sonardyne::api::pub::common;
using namespace sonardyne::api::pub::configuration;

class SonGrpcClient {
public:
  SonGrpcClient(std::string target_ipport) : _channel(grpc::CreateChannel(target_ipport, grpc::InsecureChannelCredentials())),
                                             _stub(StateService::NewStub(_channel)) {

  }

  void PrintVersion() {
    std::cout << "Calling GetVersion..." << std::endl;

    VersionRequest version_request;
    VersionResponse version_response;
    grpc::ClientContext client_context;
    grpc::Status get_version_status = _stub->GetVersion(&client_context, version_request, &version_response);
    if (get_version_status.ok()) {
      std::cout << "Called GetVersion: V" << version_response.major() << "." << version_response.minor() << std::endl;
    }
    else {
      std::cout << "GetVersion failed." << std::endl;
    }
  }

  void PrintAllStatesAsJSON(){
    ConfigurationRequest config_request;
    config_request.set_requestor(_requestor_name);
    ConfigurationEnvelope config_envelope;
    grpc::ClientContext client_context;
    std::cout << "Calling GetState..." << std::endl;
    grpc::Status get_state_status = _stub->GetState(&client_context, config_request, &config_envelope);

    if (get_state_status.ok()) {
      for(const auto& configuration : config_envelope.configuration()) {
        std::string json_string;
        auto status =google::protobuf::json::MessageToJsonString
            (configuration, &json_string);
        if(status.ok()) {
          std::cout << "Configuration: " << json_string << std::endl;
        }
      }
    }
    else {
      std::cout << "GetState Failed." << std::endl;
    }
  }
  void PrintAidingStates() {
    ConfigurationRequest config_request;
    config_request.set_requestor(_requestor_name);
    ConfigurationEnvelope config_envelope;
    grpc::ClientContext client_context;
    std::cout << "Calling GetState..." << std::endl;
    grpc::Status get_state_status = _stub->GetState(&client_context, config_request, &config_envelope);

    if (get_state_status.ok()) {
      for(const auto& configuration : config_envelope.configuration()) {
        std::cout << "Configuration: " << configuration.DebugString() << std::endl;

        if(configuration.Is<AidingConfiguration>()){
          AidingConfiguration aiding_config;
          configuration.UnpackTo(&aiding_config);

          std::cout << "Called GetState:" << std::endl;
          PrintAnAidingState("GNSS", aiding_config.enable_gnss().value());
          PrintAnAidingState("XPOS", aiding_config.enable_xpos().value());
          PrintAnAidingState("USBL", aiding_config.enable_xpos().value());
        }
      }
    }
    else {
      std::cout << "GetState Failed." << std::endl;
    }
  }

  grpc::Status SetState(ConfigurationEnvelope request_config_envelope) {
    ConfigurationEnvelope response_config_envelope;
    grpc::ClientContext client_context;
    std::cout << "Calling SetState..." << std::endl;
    return _stub->SetState(&client_context, request_config_envelope, &response_config_envelope);
  }

private:
  std::shared_ptr<grpc::Channel> _channel;
  std::unique_ptr<StateService::Stub> _stub;
  std::string _requestor_name = "C++ Client";

  void PrintAnAidingState(std::string aiding_source, AidingState_AidingStateEnum aiding_state) {
    if (aiding_state == AidingState_AidingStateEnum_ENABLED) {
      std::cout << aiding_source << " Aiding is enabled." << std::endl;
    }
    else if (aiding_state == AidingState_AidingStateEnum_DISABLED) {
      std::cout << aiding_source << " Aiding is disabled." << std::endl;
    }
    else {
      std::cout << aiding_source << " Aiding state unknown." << std::endl;
    }
  }
};

int main (int argc, char *argv[]) {
  // NOTE: Change the IP Address and Port to match the Instrument and its gRPC configuration.
  SonGrpcClient client("192.168.179.11:7777");
  client.PrintVersion();
  client.PrintAidingStates();

  ConfigurationEnvelope request_config_envelope;
  AidingConfiguration aiding_config;
  aiding_config.mutable_enable_gnss()->set_value(AidingState_AidingStateEnum_ENABLED);
  aiding_config.mutable_enable_xpos()->set_value(AidingState_AidingStateEnum_ENABLED);
  aiding_config.mutable_enable_usbl()->set_value(AidingState_AidingStateEnum_ENABLED);
  request_config_envelope.add_configuration()->PackFrom(aiding_config);

  client.SetState(request_config_envelope);

  client.PrintAidingStates();
  client.PrintAllStatesAsJSON();

  return 0;
}