#![deny(missing_docs)]
#[macro_use]
extern crate serde_derive;
#[cfg(test)]
extern crate serde_json;
extern crate indexmap;
extern crate uuid;
extern crate xio_base_datatypes;
extern crate xio_hwdb;
extern crate xio_jobset;
mod may_be_skipped;
pub use may_be_skipped::{MayBeSkipped, MustNotBeSkipped};
use indexmap::{IndexMap, IndexSet};
use std::time::Duration;
use uuid::Uuid;
use xio_base_datatypes as base;
use xio_hwdb as hwdb;
use xio_jobset as jobset;
pub fn api_uuid() -> Uuid {
Uuid::from_bytes([
0x82, 0x52, 0x2d, 0xeb, 0x24, 0xb0, 0x44, 0x4c, 0xa3, 0xc7, 0xd2,
0x69, 0xb5, 0xdf, 0xb6, 0x7d,
])
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ApiVersion {
V1,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct ApiDescription {
pub version: ApiVersion,
pub description: String,
pub uuid: Uuid,
}
impl MustNotBeSkipped for ApiDescription {}
impl Default for ApiDescription {
fn default() -> ApiDescription {
ApiDescription {
version: ApiVersion::V1,
description: "XIO web API".to_string(),
uuid: api_uuid(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "code")]
pub enum Response<T: Default + MayBeSkipped> {
Ok {
#[serde(
default,
skip_serializing_if = "MayBeSkipped::may_be_skipped"
)]
data: T,
},
Error {
message: String,
},
}
impl<T: Default + MayBeSkipped> Response<T> {
pub fn ok(data: T) -> Self {
Response::Ok { data }
}
pub fn error<E>(error: E) -> Self
where
E: std::fmt::Display,
{
Response::Error {
message: format!("{}", error),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "state")]
pub enum ModuleState {
Unknown,
Uninitialized,
Ready {
assigned_to_jobset: bool,
},
Running {
assigned_to_jobset: bool,
},
Error,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "action")]
pub enum ModuleAction {
Initialize,
Start,
Stop,
Uninitialize,
}
impl Default for ModuleAction {
fn default() -> Self {
ModuleAction::Initialize
}
}
impl MustNotBeSkipped for ModuleAction {}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "action")]
pub enum JobControlAction {
StartJob {
job: String,
},
StopJob,
}
impl Default for JobControlAction {
fn default() -> Self {
JobControlAction::StartJob {
job: "".to_string(),
}
}
}
impl MustNotBeSkipped for JobControlAction {}
#[cfg(test)]
mod job_control_action_tests {
use {serde_json, JobControlAction};
#[test]
fn serialize_start_job() {
let msg = JobControlAction::StartJob {
job: "init".to_string(),
};
let serialized = serde_json::to_string(&msg).unwrap();
assert_eq!(
"{\"action\":\"start_job\",\"job\":\"init\"}",
serialized
);
}
#[test]
fn serialize_stop_job() {
let msg = JobControlAction::StopJob;
let serialized = serde_json::to_string(&msg).unwrap();
assert_eq!("{\"action\":\"stop_job\"}", serialized);
}
}
impl Default for ModuleState {
fn default() -> ModuleState {
ModuleState::Unknown
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "code")]
pub enum ControllerState {
Uninitialized,
FlashingFirmware,
UnsupportedFrameFormatVersion {
required: u8,
found: Option<u8>,
},
UnsupportedCommandSetVersion {
required: u16,
found: Option<u16>,
},
Stale,
Ready,
JobSetDeployed {
deployed_jobs: IndexSet<String>,
},
JobRunning {
deployed_jobs: IndexSet<String>,
running: String,
},
}
impl Default for ControllerState {
fn default() -> Self {
ControllerState::Uninitialized
}
}
#[cfg(test)]
mod controller_state_tests {
use {serde_json, ControllerState};
#[test]
fn serialize_uninitialized() {
let msg = ControllerState::Uninitialized;
let serialized = serde_json::to_string(&msg).unwrap();
assert_eq!("{\"code\":\"uninitialized\"}", serialized);
}
#[test]
fn serialize_unsupported_frame_format_version() {
let msg = ControllerState::UnsupportedFrameFormatVersion {
required: 3,
found: Some(5),
};
let serialized = serde_json::to_string(&msg).unwrap();
assert_eq!(
"{\"code\":\"unsupported_frame_format_version\",\
\"required\":3,\"found\":5}",
serialized
);
}
}
impl ToString for ControllerState {
fn to_string(&self) -> std::string::String {
format!("{:?}", self)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Default)]
pub struct ControllerStatus {
pub uuid: Uuid,
pub model_uuid: Uuid,
pub model_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub commandset_version: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub frame_format_version: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub firmware_version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub firmware_version_available: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub manufacturer: Option<String>,
pub state: ControllerState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub job_set: Option<jobset::JobSet>,
pub modules: IndexMap<String, ModuleState>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct Progress {
pub current: u64,
pub overall: u64,
}
impl MustNotBeSkipped for ControllerStatus {}
impl ControllerStatus {
pub fn new(
uuid: Uuid,
model_uuid: Uuid,
model_id: String,
board_description: &hwdb::HardwareBoardDescription,
) -> Self {
let modules = board_description
.capabilities
.keys()
.map(|id| (id.to_string(), ModuleState::default()))
.collect::<IndexMap<String, ModuleState>>();
ControllerStatus {
uuid,
model_uuid,
model_id,
modules,
commandset_version: None,
frame_format_version: None,
firmware_version: None,
firmware_version_available: None,
manufacturer: None,
job_set: None,
state: ControllerState::Uninitialized,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "event")]
pub enum DeviceEvent {
Updated {
status: ControllerStatus,
},
Removed {
uuid: Uuid,
},
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "event")]
pub enum ControllerEvent {
ModuleStateChanged {
module_id: String,
state: ModuleState,
},
StateChanged {
state: ControllerState,
},
FlashingProgressMessage {
message: String,
progress: Option<Progress>,
},
FlashingErrorMessage {
message: String,
},
FlashingFinished {
success: bool,
message: String,
},
}
#[cfg(test)]
mod controller_event_tests {
use {serde_json, ControllerEvent, Progress};
#[test]
fn serialize_flashing_information_progress() {
let msg = ControllerEvent::FlashingProgressMessage {
message: "working".to_string(),
progress: Some(Progress {
current: 45,
overall: 1000,
}),
};
let serialized = serde_json::to_string(&msg).unwrap();
assert_eq!(
"{\"event\":\"flashing_progress_message\",\
\"message\":\"working\",\
\"progress\":{\"current\":45,\"overall\":1000}}",
serialized
);
}
#[test]
fn serialize_flashing_information_finished() {
let msg = ControllerEvent::FlashingFinished {
message: "successfully flashed".to_string(),
success: true,
};
let serialized = serde_json::to_string(&msg).unwrap();
assert_eq!(
"{\"event\":\"flashing_finished\",\"success\":true,\"message\":\"successfully flashed\"}",
serialized
);
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "e")]
pub enum JobEvent {
Started {
job: String,
tags: IndexMap<String, Vec<String>>,
#[serde(rename = "i")]
instant: u64,
},
#[serde(rename = "d")]
Data {
#[serde(rename = "t")]
tag: String,
#[serde(rename = "v")]
values: Vec<base::DataValueDescriptive>,
#[serde(rename = "i")]
instant: u64,
},
Position {
command: u16,
#[serde(
rename = "_message",
default,
skip_serializing_if = "String::is_empty"
)]
message: String,
description: String,
#[serde(rename = "i")]
instant: u64,
},
Stopped {
details: JobStoppedReason,
#[serde(rename = "i")]
instant: u64,
},
}
impl JobEvent {
pub fn instant(&self) -> u64 {
use JobEvent::*;
match *self {
Started { instant, .. } => instant,
Data { instant, .. } => instant,
Position { instant, .. } => instant,
Stopped { instant, .. } => instant,
}
}
pub fn instant_duration(&self) -> Duration {
Duration::from_millis(self.instant())
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct PositionWithDescription {
pub position: u16,
#[serde(
rename = "_message",
default,
skip_serializing_if = "String::is_empty"
)]
pub message: String,
pub description: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "reason")]
pub enum JobStoppedReason {
Finished,
StoppedByUser,
ExitCriterionMatched {
command: PositionWithDescription,
condition: PositionWithDescription,
},
Failed {
error: base::ErrorCode,
},
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct FirmwareFlashRequest {}