#api-client #parser #api #markup-language #vbml

vestaboard

a wrapper for the Vestaboard API with support for v2

3 releases

0.1.2 May 3, 2024
0.1.1 May 3, 2024
0.1.0 May 3, 2024

#10 in #markup-language

MIT license

81KB
1.5K SLoC

vestaboard-rs

vestaboard-rs is a VBML (Vestaboard Markup Language) parser and api client for the Vestaboard. It supports the v2 read/write api, subscription api, and local api.

the full docs can be found at https://docs.rs/vestaboard

features

  • VBML parser
  • read/write api
  • subscription api
  • local api
  • serialization and deserialization of Vestaboard messages
  • async/await
  • support for multiple Vestaboard sizes (if another size is released)

installation

cargo add vestaboard -F full

feature flags

  • full: enables all features
  • parser: enables the VBML parser (Vestaboard Markup Language) (default)
  • rw: enables the read/write api
  • subscription: enables the subscription api
  • local: enables the local api

helpful type and structs

  • board::FLAGSHIP_ROWS and board::FLAGSHIP_COLS: the dimensions of the flagship Vestaboard
  • board::Board<ROWS, COLS>: a type equivalent to [[u8; COLS]; ROWS] which represents a Vestaboard state
  • BoardData<ROWS, COLS>: a struct that wraps a Board<ROWS, COLS> and has several helper methods
  • VBML: a struct that represents a VBML message and can be parsed into a BoardData<ROWS, COLS>
  • Vestaboard<Config>: the main struct that is used to interact with the Vestaboard api

VBML usage

with ROWS and COLS specified:

let string = "{\"props\":{},\"style\":{},\"components\":[]}"; // any valid VBML string
let vbml: Vbml<6, 22> = string.parse().unwrap();

let board_data: Result<BoardData<6, 22>, VbmlError> = vbml.parse();

when ROWS and COLS are not specified, the default is board::FLAGSHIP_ROWS and board::FLAGSHIP_COLS:

let string = "{\"props\":{},\"style\":{},\"components\":[]}"; // any valid VBML string
let vbml: Vbml = string.parse().unwrap();

let board_data: Result<BoardData, VbmlError> = vbml.parse();

api usage

use vestaboard::{Vestaboard, RWConfig, SubscriptionConfig, LocalConfig};

#[tokio::main]
async fn main() {
  let rw_config = RWConfig { read_write_key: "<YOUR_RW_API_KEY>" };
  let rw_api: Vestaboard<RWConfig> = Vestaboard::new_rw_api(rw_config);

  let subscription_config = SubscriptionConfig {
    api_key: "<YOUR_SUBSCRIPTION_API_KEY>",
    api_secret: "<YOUR_SUBSCRIPTION_API_SECRET>",
  };
  let subscription_api: Vestaboard<SubscriptionConfig> = Vestaboard::new_subscription_api(subscription_config);

  let local_config = LocalConfig {
    api_key: "<YOUR_LOCAL_API_KEY>",
    host: "<YOUR_VESTABOARD_IP_ADDRESS>",
  };
  let local_api: Vestaboard<LocalConfig> = Vestaboard::new_local_api(local_config);
}

(note that you must have the Vestaboard<Config> type specified due to this rust issue)

read/write api

use vestaboard::{Vestaboard, RWConfig};

#[tokio::main]
async fn main() {
  let rw_config = RWConfig { read_write_key: "<YOUR_RW_API_KEY>" };
  let rw_api: Vestaboard<RWConfig> = Vestaboard::new_rw_api(rw_config);

  let message: Result<RWApiReadMessage, RWApiError> = rw_api.read().await;
  let write_res: Result<String, RWApiError> = rw_api.write(BoardData<ROWS, COLS>).await;
}

subscription api

use vestaboard::{Vestaboard, SubscriptionConfig};

#[tokio::main]
async fn main() {
  let subscription_config = SubscriptionConfig {
    api_key: "<YOUR_SUBSCRIPTION_API_KEY>",
    api_secret: "<YOUR_SUBSCRIPTION_API_SECRET>",
  };
  let subscription_api: Vestaboard<SubscriptionConfig> = Vestaboard::new_subscription_api(subscription_config);

  let subscriptions: Result<SubscriptionsList, SubscriptionApiError> = subscription_api.get_subscriptions().await;
  let write_res: Result<SubscriptionMessageResponse, SubscriptionApiError> = subscription_api.write(BoardData<ROWS, COLS>).await;
}

local api

use vestaboard::{Vestaboard, LocalConfig};

#[tokio::main]
async fn main() {
  // if you have not enabled the local api, you can use the following method to do so.
  // note that the local api can only be enabled once per board, so make sure to save
  // the resulting api key. to get the enablement token, visit https://www.vestaboard.com/local-api
  let local_api_enablement: Result<String, LocalApiError> = Vestaboard.get_local_api_key(
      Some("<YOUR_VESTABOARD_IP_ADDRESS>".parse().unwrap()),
      "<YOUR_LOCAL_API_ENABLEMENT_KEY>",
    ).await;

  let local_config = LocalConfig {
    api_key: "<YOUR_LOCAL_API_KEY>",
    ip_address: "<YOUR_VESTABOARD_IP_ADDRESS>".parse().unwrap(),
  };

  let local_api: Vestaboard<LocalConfig> = Vestaboard::new_local_api(local_config);

  let message: Result<BoardData<ROWS, COLS>, LocalApiError> = local_api.read().await;
  let write_res: Result<(), LocalApiError> = local_api.write(BoardData<ROWS, COLS>).await;
}

benchmarks

this library is set up to be benchmarked against the official JavaScript VBML parsing library. benchmarks can be run using just with the following command:

just bench

on a Ryzen 9 7950X on Arch Linux with Node v18.20.0 and rustc 1.77.2, the benchmark results are as follows:

test name js μs/it rs μs/it difference % faster
Default Template 21.23μs 0.40μs -20.83μs 5,338%
Half Height Center 19.07μs 0.40μs -18.68μs 4,792%
Justify Left 18.33μs 0.39μs -17.94μs 4,669%
Justify Right 19.07μs 0.39μs -18.68μs 4,838%
Justify Center 21.23μs 0.39μs -20.84μs 5,483%
Justify Justified 20.11μs 0.39μs -19.73μs 5,192%
Align Center 21.43μs 0.39μs -21.05μs 5,535%
Align Top 18.98μs 0.39μs -18.58μs 4,812%
Align Bottom 21.39μs 0.39μs -21.00μs 5,511%
Align Justified 21.36μs 0.39μs -20.97μs 5,415%
Justify Justified Align Justified 20.11μs 0.38μs -19.73μs 5,277%
Split Align Justify 24.49μs 0.42μs -24.08μs 5,897%
Uneven Split 23.85μs 0.41μs -23.43μs 5,770%
Uneven Split 2 21.64μs 0.35μs -21.29μs 6,244%
Rev Split Align Justify 24.55μs 0.42μs -24.13μs 5,850%
Two Column 21.61μs 0.35μs -21.26μs 6,153%
All Justified 27.32μs 0.42μs -26.90μs 6,490%
Justified Right 15.76μs 0.24μs -15.51μs 6,438%
Centered Right 15.69μs 0.24μs -15.45μs 6,472%
2x2x2x2 Grid 37.68μs 0.76μs -36.91μs 4,933%
2x2 Neighbors 18.90μs 0.37μs -18.53μs 5,046%
Plain Text 13.44μs 0.24μs -13.20μs 5,564%
Centered 17.02μs 0.24μs -16.78μs 7,055%
Newline 16.02μs 0.26μs -15.76μs 6,230%
Character Codes 227.70μs 17.94μs -209.77μs 1,270%
Character Codes with Characters 34.43μs 1.52μs -32.91μs 2,269%
Dynamic Props 14.48μs 0.65μs -13.82μs 2,223%
Dynamic Props with Character Codes 17.75μs 1.13μs -16.62μs 1,571%
Multiple Components 49.01μs 2.34μs -46.68μs 2,099%
Raw Characters 0.90μs 0.04μs -0.86μs 2,126%
Absolute Position Components 24.33μs 0.73μs -23.60μs 3,311%
Complex Layout with Multiple Components 70.41μs 1.95μs -68.46μs 3,619%
Complex Layout with Multiple Components 2 76.98μs 2.12μs -74.86μs 3,634%
Diff Height Components Newline 30.00μs 0.60μs -29.41μs 5,028%
JS Spec: Absolute Layout 18.19μs 0.32μs -17.87μs 5,714%
JS Spec: Absolute Layout 2 18.15μs 0.32μs -17.83μs 5,704%
JS Spec: Absolute and Raw Components 12.08μs 0.48μs -11.59μs 2,496%

there is definitely further room for optimization, but the current performance is decent.

Dependencies

~4–18MB
~213K SLoC