Skip to content

Commit

Permalink
RSDK-7769, RSDK-7770 Add AppRobotClient and LogOutput dial option (#233)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjirewis authored Jun 25, 2024
1 parent 602498e commit c832369
Show file tree
Hide file tree
Showing 9 changed files with 1,065 additions and 604 deletions.
42 changes: 42 additions & 0 deletions lib/src/app/robot.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'dart:async';

import 'package:logger/logger.dart';
import 'package:viam_sdk/src/gen/google/protobuf/timestamp.pb.dart';

import '../gen/app/v1/robot.pbgrpc.dart';
import '../gen/common/v1/common.pb.dart';

/// gRPC client for connecting to app's RobotService.
///
/// All calls must be authenticated.
class AppRobotClient {
final RobotServiceClient _client;

AppRobotClient(this._client);

/// Log the OutputEvent to app with the given partId.
Future<void> log(String partId, host, loggerName, OutputEvent event) async {
late String level;
switch (event.level) {
case Level.debug:
level = 'debug';
case Level.warning:
level = 'warning';
case Level.error:
level = 'error';
default:
// Assume info level if none of the above.
level = 'info';
}

// Assume log was just output (OutputEvent has no timestamp field).
final Timestamp protoTs = Timestamp.fromDateTime(DateTime.now());

// Join lines with '\n' and suffix with '\n'.
final String message = '${event.lines.join('\n')}\n';

final LogEntry entry = LogEntry(host: host, level: level, time: protoTs, loggerName: loggerName, message: message);
final request = LogRequest(id: partId, logs: [entry]);
await _client.log(request);
}
}
15 changes: 13 additions & 2 deletions lib/src/rpc/dial.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ import '../utils.dart';
import 'grpc/grpc_or_grpcweb_channel.dart';
import 'web_rtc/web_rtc_client.dart';

final _logger = Logger(printer: PrettyPrinter(printTime: true));
Logger _newDialLogger(LogOutput? output) {
// Use a SimplePrinter, as flutter dial logs from the RC app are sent to app.viam.com,
// and pretty-printed logs are overly formatted.
return Logger(output: output, printer: SimplePrinter(colors: false));
}

var _logger = _newDialLogger(null);

/// Describes the behavior for connecting to a robot
class DialOptions {
Expand Down Expand Up @@ -56,6 +62,9 @@ class DialOptions {

/// Timeout is the timeout for dial.
Duration timeout = Duration(seconds: 10);

/// If specified, a custom log output for dial logs.
LogOutput? logOutput;
}

/// The credentials used for connecting to the robot
Expand Down Expand Up @@ -113,9 +122,11 @@ class DialWebRtcOptions {

/// Connect to a robot at the provided address with the given options
Future<ClientChannelBase> dial(String address, DialOptions? options, String Function() sessionCallback) async {
final opts = options ?? DialOptions();
_logger = _newDialLogger(opts.logOutput);

final dialSW = Stopwatch()..start();
_logger.i('Connecting to address $address');
final opts = options ?? DialOptions();

if (opts.attemptMdns) {
final mdnsSW = Stopwatch()..start();
Expand Down
4 changes: 4 additions & 0 deletions lib/src/viam_sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './app/app.dart';
import './app/billing.dart';
import './app/data.dart';
import './app/provisioning.dart';
import './app/robot.dart';
import './gen/app/v1/app.pbgrpc.dart';
import './robot/client.dart';
import './viam_sdk_impl.dart';
Expand All @@ -20,6 +21,9 @@ abstract class Viam {
/// A client to communicate with Viam's app service
AppClient get appClient;

/// A client to communicate with Viam's robot service
AppRobotClient get appRobotClient;

/// A client to communicate with Viam's billing services
BillingClient get billingClient;

Expand Down
10 changes: 10 additions & 0 deletions lib/src/viam_sdk_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,28 @@ import 'package:viam_sdk/protos/app/dataset.dart';
import '../protos/app/app.dart';
import '../protos/app/billing.dart';
import '../protos/app/data.dart';
import '../protos/app/robot.dart';
import '../protos/provisioning/provisioning.dart';
import './app/app.dart';
import './app/billing.dart';
import './app/data.dart';
import './app/provisioning.dart';
import './app/robot.dart';
import './robot/client.dart';
import './rpc/dial.dart';
import './viam_sdk.dart';

class ViamImpl implements Viam {
final ClientChannelBase _clientChannelBase;
late AppClient _appClient;
late AppRobotClient _appRobotClient;
late BillingClient _billingClient;
late DataClient _dataClient;
late ProvisioningClient _provisioningClient;

ViamImpl._withChannel(this._clientChannelBase) {
_appClient = AppClient(AppServiceClient(_clientChannelBase));
_appRobotClient = AppRobotClient(RobotServiceClient(_clientChannelBase));
_billingClient = BillingClient(BillingServiceClient(_clientChannelBase));
_dataClient = DataClient(
DataServiceClient(_clientChannelBase), DataSyncServiceClient(_clientChannelBase), DatasetServiceClient(_clientChannelBase));
Expand All @@ -33,6 +37,7 @@ class ViamImpl implements Viam {
ViamImpl.withAccessToken(String accessToken, {String serviceHost = 'app.viam.com', int servicePort = 443})
: _clientChannelBase = AuthenticatedChannel(serviceHost, servicePort, accessToken, servicePort == 443 ? false : true) {
_appClient = AppClient(AppServiceClient(_clientChannelBase));
_appRobotClient = AppRobotClient(RobotServiceClient(_clientChannelBase));
_billingClient = BillingClient(BillingServiceClient(_clientChannelBase));
_dataClient = DataClient(
DataServiceClient(_clientChannelBase), DataSyncServiceClient(_clientChannelBase), DatasetServiceClient(_clientChannelBase));
Expand Down Expand Up @@ -63,6 +68,11 @@ class ViamImpl implements Viam {
return _appClient;
}

@override
AppRobotClient get appRobotClient {
return _appRobotClient;
}

@override
BillingClient get billingClient {
return _billingClient;
Expand Down
1 change: 1 addition & 0 deletions lib/viam_sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ library viam_sdk;
export 'src/app/data.dart' hide DataClient;
export 'src/app/permissions.dart';
export 'src/app/provisioning.dart';
export 'src/app/robot.dart';

/// Components
export 'src/components/arm/arm.dart';
Expand Down
28 changes: 28 additions & 0 deletions test/unit_test/app/app_robot_client_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:logger/logger.dart';
import 'package:mockito/mockito.dart';
import 'package:viam_sdk/src/gen/app/v1/robot.pbgrpc.dart';
import 'package:viam_sdk/viam_sdk.dart';

import '../mocks/mock_response_future.dart';
import '../mocks/service_clients_mocks.mocks.dart';

void main() {
late MockAppRobotServiceClient serviceClient;
late AppRobotClient appRobotClient;

setUp(() {
serviceClient = MockAppRobotServiceClient();
appRobotClient = AppRobotClient(serviceClient);
});

group('App Robot RPC Client Tests', () {
test('log', () async {
when(serviceClient.log(any)).thenAnswer((_) => MockResponseFuture.value(LogResponse()));

final event = LogEvent(Level.info, 'fake log message');
await appRobotClient.log('fakePartId', 'fakeHost', 'fakeLoggerName', OutputEvent(event, List.empty()));
verify(serviceClient.log(any)).called(1);
});
});
}
31 changes: 31 additions & 0 deletions test/unit_test/dial/dial_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:logger/logger.dart';
import 'package:viam_sdk/viam_sdk.dart';

class TestLogOutput extends LogOutput {
final List<OutputEvent> _outputtedEvents = List.empty(growable: true);
TestLogOutput();

@override
void output(OutputEvent event) {
_outputtedEvents.add(event);
}

List<OutputEvent> outputtedEvents() {
return _outputtedEvents;
}
}

void main() {
group('dial', () {
test('custom log output', () {
final output = TestLogOutput();
final dialOpts = DialOptions()..logOutput = output;
// Dial to arbitrary address and assert that logs get redirected to TestLogOutput.
dial('foo', dialOpts, () => '');

final events = output.outputtedEvents();
expect(events.isNotEmpty, true);
});
});
}
2 changes: 2 additions & 0 deletions test/unit_test/mocks/service_clients_mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import 'package:viam_sdk/protos/app/ml_training.dart';
import 'package:viam_sdk/src/gen/app/data/v1/data.pbgrpc.dart';
import 'package:viam_sdk/src/gen/app/v1/app.pbgrpc.dart';
import 'package:viam_sdk/src/gen/app/v1/billing.pbgrpc.dart';
import 'package:viam_sdk/src/gen/app/v1/robot.pbgrpc.dart' as app_robot;
import 'package:viam_sdk/src/gen/provisioning/v1/provisioning.pbgrpc.dart';
import 'package:viam_sdk/src/gen/robot/v1/robot.pbgrpc.dart';
import 'package:viam_sdk/src/gen/service/vision/v1/vision.pbgrpc.dart';

@GenerateNiceMocks([
MockSpec<ClientChannelBase>(),
MockSpec<RobotServiceClient>(),
MockSpec<app_robot.RobotServiceClient>(as: Symbol('MockAppRobotServiceClient')),
MockSpec<AppServiceClient>(),
MockSpec<DataServiceClient>(),
MockSpec<ProvisioningServiceClient>(),
Expand Down
Loading

0 comments on commit c832369

Please sign in to comment.