Skip to content

Commit

Permalink
Merge branch 'main' of github.com:foxglove/foxglove-sdk into adrian/t…
Browse files Browse the repository at this point in the history
…s-package
  • Loading branch information
amacneil committed Feb 5, 2025
2 parents 0561c8b + 6f37f82 commit 6b1eb43
Show file tree
Hide file tree
Showing 29 changed files with 481 additions and 486 deletions.
9 changes: 9 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import foxglove from "@foxglove/eslint-plugin";
import globals from "globals";
import tseslint from "typescript-eslint";

export default tseslint.config(
{
ignores: ["**/dist"],
},
...foxglove.configs.base,
{
files: ["**/*.js"],
languageOptions: {
globals: {
...globals.node,
},
},
},
...foxglove.configs.typescript.map((config) => ({
...config,
files: ["**/*.ts"],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-prettier": "5.2.3",
"globals": "^15.14.0",
"jest": "29.7.0",
"prettier": "^3.4.2",
"prettier-plugin-toml": "^2.0.1",
Expand Down
17 changes: 17 additions & 0 deletions rust/foxglove/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Foxglove Rust SDK

## Development

### Generate Protobuf schemas

```bash
cargo run --bin foxglove-proto-gen
```

### Run the example server

1. `cargo run --features unstable --example ws-server`
2. Open the Foxglove desktop app
3. From the dashboard, click "Open connection..."
4. Confirm the WebSocket URL and click "Open"
5. Create a Raw Messages panel and enter "topic" in the message path field at the top
14 changes: 3 additions & 11 deletions rust/foxglove/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ description = "Foxglove SDK"
edition = "2021"
repository = "https://github.com/foxglove/foxglove-sdk"
license = "MIT"
readme = false

[features]
unstable = []
Expand Down Expand Up @@ -39,21 +38,14 @@ env_logger = "0.11.5"
futures-util = "0.3.31"
quaternion = "2.0.0"
tempfile = "3.15.0"
serde_json = "1.0.138"
tracing-test = "0.2.5"
serde.workspace = true

[[example]]
name = "ws-server"
path = "examples/ws-server-unstable.rs"
name = "client-publish"
path = "examples/unstable/client-publish.rs"
required-features = ["unstable"]

[[example]]
name = "param-server"
path = "examples/param-server-unstable.rs"
required-features = ["unstable"]

[[test]]
name = "websocket-unstable"
path = "tests/websocket-unstable.rs"
path = "examples/unstable/param-server.rs"
required-features = ["unstable"]
38 changes: 26 additions & 12 deletions rust/foxglove/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
# Foxglove Rust SDK
# Foxglove

## Development
The official [Foxglove] SDK.

### Generate Protobuf schemas
This crate provides support for integrating with the Foxglove platform. It can be used to log
events to local [MCAP] files or a local visualization server that communicates with the
Foxglove app.

```bash
cargo run --bin foxglove-proto-gen
```
[Foxglove]: https://docs.foxglove.dev/
[MCAP]: https://mcap.dev/

### Run the example server
# Overview

1. `cargo run --features unstable --example ws-server`
2. Open the Foxglove desktop app
3. From the dashboard, click "Open connection..."
4. Confirm the WebSocket URL and click "Open"
5. Create a Raw Messages panel and enter "topic" in the message path field at the top
To record messages, you need at least one sink and at least one channel.

A "sink" is a destination for logged messages — either an MCAP file or a live visualization server. Use `McapWriter::new()` to register a new MCAP sink. Use `WebSocketServer::new` to create a new live visualization server.

A "channel" gives a way to log related messages which have the same schema. Each channel is instantiated with a unique topic name.

The SDK provides structs for well-known schemas. These can be used in conjunction with
`TypedChannel` for type-safe logging, which ensures at compile time that
messages logged to a channel all share a common schema.

You can also define your own custom data types by implementing the `Encode` trait. This
allows you to log arbitrary custom data types. Notably, the `Encode` trait is
automatically implemented for types that implement `serde::Serialize` and
`schemars::JsonSchema`. This makes it easy to define new custom messages.

# Get Started

For more information and examples, see [docs.rs](https://docs.rs/foxglove).
69 changes: 0 additions & 69 deletions rust/foxglove/examples/param-server-unstable.rs

This file was deleted.

105 changes: 105 additions & 0 deletions rust/foxglove/examples/unstable/client-publish.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//! Example websocket server with client publsih
//!
//! This example uses the 'unstable' feature to expose capabilities.
//!
//! Usage:
//! ```text
//! cargo run --features unstable --example client-publish
//! ```
use clap::Parser;
use foxglove::schemas::log::Level;
use foxglove::schemas::Log;
use foxglove::{
Capability, ClientChannelId, PartialMetadata, ServerListener, TypedChannel, WebSocketServer,
};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use tokio_util::sync::CancellationToken;

#[derive(Debug, Parser)]
struct Cli {
#[arg(short, long, default_value_t = 8765)]
port: u16,
#[arg(long, default_value = "127.0.0.1")]
host: String,
#[arg(long, default_value = "example-json-server")]
server_name: String,
}

struct ExampleCallbackHandler;
impl ServerListener for ExampleCallbackHandler {
fn on_message_data(&self, channel_id: ClientChannelId, message: &[u8]) {
let json: serde_json::Value =
serde_json::from_slice(message).expect("Failed to parse message");
println!("Received message on channel {channel_id}: {json}");
}
}

#[tokio::main]
async fn main() {
let env = env_logger::Env::default().default_filter_or("info");
env_logger::init_from_env(env);

let args = Cli::parse();

let server = WebSocketServer::new()
.name("client-publish")
.bind(args.host, args.port)
.capabilities([Capability::ClientPublish])
.listener(Arc::new(ExampleCallbackHandler))
.start()
.await
.expect("Failed to start server");

let shutdown = watch_ctrl_c();
tokio::select! {
() = shutdown.cancelled() => (),
() = log_forever() => (),
};

server.stop().await;
}

async fn log_forever() {
let channel = TypedChannel::new("/log").expect("Failed to create channel");
let start = Instant::now();
let mut sequence = 0;
let mut interval = tokio::time::interval(Duration::from_secs(1));
loop {
interval.tick().await;
let msg = Log {
timestamp: Some(SystemTime::now().into()),
message: format!("It's been {:?}", start.elapsed()),
level: Level::Info.into(),
..Default::default()
};
let meta = PartialMetadata {
sequence: Some(sequence),
publish_time: Some(nanoseconds_since_epoch()),
..Default::default()
};
channel.log_with_meta(&msg, meta);
sequence += 1;
}
}

fn watch_ctrl_c() -> CancellationToken {
let token = CancellationToken::new();
tokio::spawn({
let token = token.clone();
async move {
tokio::signal::ctrl_c().await.ok();
token.cancel();
}
});
token
}

pub(crate) fn nanoseconds_since_epoch() -> u64 {
let now = SystemTime::now();
if let Ok(duration) = now.duration_since(UNIX_EPOCH) {
return duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64;
}
0
}
74 changes: 74 additions & 0 deletions rust/foxglove/examples/unstable/param-server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! Example of a parameter server using the Foxglove SDK.
//!
//! This example uses the 'unstable' feature to expose capabilities.
//!
//! Usage:
//! ```text
//! cargo run --features unstable --example param-server
//! ```
use std::time::{Duration, Instant};

use clap::Parser;
use foxglove::{
Capability, Parameter, ParameterType, ParameterValue, WebSocketServer, WebSocketServerHandle,
};
use tokio_util::sync::CancellationToken;

#[derive(Debug, Parser)]
struct Cli {
#[arg(short, long, default_value_t = 8765)]
port: u16,
#[arg(long, default_value = "127.0.0.1")]
host: String,
}

#[tokio::main]
async fn main() {
let env = env_logger::Env::default().default_filter_or("debug");
env_logger::init_from_env(env);

let args = Cli::parse();

let server = WebSocketServer::new()
.name("param server")
.capabilities([Capability::Parameters])
.bind(args.host, args.port)
.start()
.await
.expect("Failed to start server");

let shutdown = watch_ctrl_c();
tokio::select! {
() = shutdown.cancelled() => (),
() = update_parameters(&server) => (),
};

server.stop().await;
}

async fn update_parameters(server: &WebSocketServerHandle) {
let start = Instant::now();
let mut interval = tokio::time::interval(Duration::from_secs(1));
loop {
interval.tick().await;
let parameter = Parameter {
name: "elapsed".to_string(),
value: Some(ParameterValue::Number(start.elapsed().as_secs_f64())),
r#type: Some(ParameterType::Float64),
};
server.publish_parameter_values(vec![parameter]).await;
}
}

fn watch_ctrl_c() -> CancellationToken {
let token = CancellationToken::new();
tokio::spawn({
let token = token.clone();
async move {
tokio::signal::ctrl_c().await.ok();
token.cancel();
}
});
token
}
Loading

0 comments on commit 6b1eb43

Please sign in to comment.