use anyhow::{anyhow, bail, Error, Result};
use derive_new::new;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::convert::{TryFrom, TryInto};
use crate::{ble::Uuid, decode::decode, encode::encode, uuid};
mod note;
pub use note::Note;
pub const UUID_SERVICE: Uuid = uuid!("10b20100 5b3b 4571 9508 cf3efcd7bbae");
pub const UUID_ID: Uuid = uuid!("10b20101 5b3b 4571 9508 cf3efcd7bbae");
pub const UUID_MOTOR: Uuid = uuid!("10b20102 5b3b 4571 9508 cf3efcd7bbae");
pub const UUID_LIGHT: Uuid = uuid!("10b20103 5b3b 4571 9508 cf3efcd7bbae");
pub const UUID_SOUND: Uuid = uuid!("10b20104 5b3b 4571 9508 cf3efcd7bbae");
pub const UUID_MOTION: Uuid = uuid!("10b20106 5b3b 4571 9508 cf3efcd7bbae");
pub const UUID_BUTTON: Uuid = uuid!("10b20107 5b3b 4571 9508 cf3efcd7bbae");
pub const UUID_BATTERY: Uuid = uuid!("10b20108 5b3b 4571 9508 cf3efcd7bbae");
pub const UUID_CONFIG: Uuid = uuid!("10b201ff 5b3b 4571 9508 cf3efcd7bbae");
macro_rules! msg {
($uuid:expr;
$(#[$attr:meta])?pub enum $name:tt {
$(
$(#[$vattr:meta])?
$variant:ident$(($value:ident))? = $id:literal,
)*
}) => {
$(#[$attr])?
#[derive(Debug, Clone, PartialEq, Eq, new)]
pub enum $name {
$(
$(#[$vattr])?
$variant$(($value))?,
)*
}
#[allow(non_snake_case)]
impl TryFrom<&[u8]> for $name {
type Error = Error;
fn try_from(v: &[u8]) -> Result<Self> {
match v.get(0) {
$(Some($id) => Ok(Self::$variant$((decode::<$value>(&v[1..])?))? ),)*
Some(v) => Err(anyhow!("Invalid type {} for {}", v, stringify!(Self))),
None => Err(anyhow!("Empty bytes for {}", stringify!(Self))),
}
}
}
impl TryFrom<Vec<u8>> for $name {
type Error = Error;
fn try_from(v: Vec<u8>) -> Result<Self> {
Self::try_from(&v as &[u8])
}
}
#[allow(non_snake_case, unused_mut)]
impl TryFrom<$name> for Vec<u8> {
type Error = Error;
fn try_from(v: $name) -> Result<Self> {
match &v {
$($name::$variant$(($value))? => {
let mut buf = vec![$id];
$(encode(&mut buf, &$value)?;)?
Ok(buf)
},)*
}
}
}
impl TryFrom<$name> for (Uuid, Vec<u8>) {
type Error = Error;
fn try_from(v: $name) -> Result<Self> {
Ok(($uuid, v.try_into()?))
}
}
};
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, new)]
pub struct IdPos {
pub cube_x: u16,
pub cube_y: u16,
pub cube_angle: u16,
pub sensor_x: u16,
pub sensor_y: u16,
pub sensor_angle: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, new)]
pub struct IdStd {
pub value: u32,
pub angle: u16,
}
msg!(
UUID_ID;
#[doc = "Message from the id reader."]
pub enum Id {
#[doc = "The content of the position id."]
Pos(IdPos) = 0x01,
#[doc = "The content of the standard id."]
Std(IdStd) = 0x02,
#[doc = "Indicates the cube goes out of the positionn id area."]
PosMissed = 0x03,
#[doc = "Indicates the cube goes out of the standard id area."]
StdMissed = 0x04,
}
);
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Posture {
HeadUp = 0x01,
BottomUp = 0x02,
BackUp = 0x03,
FrontUp = 0x04,
RightSideUp = 0x05,
LeftSideUp = 0x06,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct MotionDetect {
pub level: bool,
pub collision: bool,
pub double_tap: bool,
pub posture: Posture,
}
msg!(
UUID_MOTION;
#[doc = "Message from the motion sensor."]
pub enum Motion {
#[doc = "The state of the motion sensor."]
Detect(MotionDetect) = 0x01,
}
);
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum ButtonState {
Released = 0x00,
Pressed = 0x80,
}
msg!(
UUID_BUTTON;
#[doc = "Message from the button."]
pub enum Button {
#[doc = "The state of the button."]
Func(ButtonState) = 0x01,
}
);
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum MotorId {
Left = 0x01,
Right = 0x02,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum MotorDir {
Forward = 0x01,
Backward = 0x02,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct MotorSimple {
pub motor1: MotorId,
pub dir1: MotorDir,
pub speed1: u8,
pub motor2: MotorId,
pub dir2: MotorDir,
pub speed2: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct MotorTimed {
pub motor1: MotorId,
pub dir1: MotorDir,
pub speed1: u8,
pub motor2: MotorId,
pub dir2: MotorDir,
pub speed2: u8,
pub duration: u8,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum MoveType {
Curve = 0x00,
ForwardOnly = 0x01,
Straight = 0x02,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum SpeedChange {
Const = 0x00,
Acc = 0x01,
Dec = 0x02,
AccDec = 0x03,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct MotorTarget {
pub id: u8,
pub timeout: u8,
pub move_type: MoveType,
pub max_speed: u8,
pub speed_change: SpeedChange,
#[new(default)]
pub reserved: u8,
pub x: u16,
pub y: u16,
pub angle: u16,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum WriteOpt {
Overwrite = 0x00,
Append = 0x01,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct Target {
pub x: u16,
pub y: u16,
pub angle: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct MotorMultiTarget {
pub id: u8,
pub timeout: u8,
pub move_type: MoveType,
pub max_speed: u8,
pub speed_change: SpeedChange,
#[new(default)]
pub reserved: u8,
pub writeopt: WriteOpt,
pub targets: Vec<Target>,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum MotorCubeDir {
Forward = 0x00,
Backward = 0x01,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum MotorPriority {
Translation = 0x00,
Rotation = 0x01,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct MotorAcc {
pub id: u8,
pub speed: u8,
pub acc: u8,
pub rotate_speed: u16,
pub rotate_dir: MotorCubeDir,
pub trans_dir: MotorCubeDir,
pub prio: MotorPriority,
pub duration: u8,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum TargetResValue {
Ok = 0x00,
Timeout = 0x01,
IdMissed = 0x02,
InvalidParam = 0x03,
InvalidState = 0x04,
OtherWrite = 0x05,
Unsupported = 0x06,
Full = 0x07,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct MotorTargetRes {
pub id: u8,
pub res: TargetResValue,
}
msg!(
UUID_MOTOR;
#[doc = "Message from/to the motor."]
pub enum Motor {
#[doc = "Simple request."]
Simple(MotorSimple) = 0x01,
#[doc = "Request with timeout."]
Timed(MotorTimed) = 0x02,
#[doc = "Request with target position."]
Target(MotorTarget) = 0x03,
#[doc = "Request with multiple target positions."]
MultiTarget(MotorMultiTarget) = 0x04,
#[doc = "Request with acceleration."]
Acc(MotorAcc) = 0x05,
#[doc = "Response to the request with target."]
TargetRes(MotorTargetRes) = 0x83,
#[doc = "Response to the request with multiple target."]
MultiTargetRes(MotorTargetRes) = 0x84,
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct LightOff {
#[new(value = "1")]
pub num: u8,
#[new(value = "1")]
pub id: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct LightOn {
pub duration: u8,
#[new(value = "1")]
pub num: u8,
#[new(value = "1")]
pub id: u8,
pub red: u8,
pub green: u8,
pub blue: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct LightCtrl {
pub repeat: u8,
pub num: u8,
pub ops: Vec<LightOn>,
}
msg!(
UUID_LIGHT;
#[doc = "Message to lights."]
pub enum Light {
#[doc = "Turns off all the lights."]
AllOff = 0x01,
#[doc = "Turns off a light."]
Off(LightOff) = 0x02,
#[doc = "Turns on a light."]
On(LightOn) = 0x03,
#[doc = "Control lights with the list of operations."]
Ctrl(LightCtrl) = 0x04,
}
);
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum SoundPresetId {
Enter = 0,
Selected = 1,
Cancel = 2,
Cursor = 3,
MatIn = 4,
MatOut = 5,
Get1 = 6,
Get2 = 7,
Get3 = 8,
Effect1 = 9,
Effect2 = 10,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct SoundPreset {
pub id: SoundPresetId,
pub vol: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct SoundOp {
pub duration: u8,
pub note: Note,
pub vol: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct SoundPlay {
pub repeat: u8,
pub num: u8,
pub ops: Vec<SoundOp>,
}
msg!(
UUID_SOUND;
#[doc = "Message to the sound device."]
pub enum Sound {
#[doc = "Stops."]
Stop = 0x01,
#[doc = "Plays the preset sound."]
Preset(SoundPreset) = 0x02,
#[doc = "Plays the sound with the list of operations.x"]
Play(SoundPlay) = 0x03,
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct ConfigVersion {
#[new(default)]
pub reserve: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct ConfigLevel {
#[new(default)]
pub reserved: u8,
pub threshold: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct ConfigCollision {
#[new(default)]
pub reserved: u8,
pub threshold: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct ConfigDoubleTap {
#[new(default)]
pub reserved: u8,
pub interval: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, new)]
pub struct ConfigVersionRes {
#[new(default)]
pub reserved: u8,
pub version: String,
}
msg!(
UUID_CONFIG;
#[doc = "Message from/to configuration."]
pub enum Config {
#[doc = "Requests protocol version."]
Version(ConfigVersion) = 0x01,
#[doc = "Changes the settings of level detection."]
Level(ConfigLevel) = 0x02,
#[doc = "Changes the settings of collision detection."]
Collision(ConfigCollision) = 0x03,
#[doc = "Changes the settings of double-tap detection."]
DoubleTap(ConfigDoubleTap) = 0x04,
#[doc = "The protocol version information."]
VersionRes(ConfigVersionRes) = 0x81,
}
);
#[derive(Debug, Clone, PartialEq, Eq, new)]
pub enum Message {
Id(Id),
Motion(Motion),
Button(Button),
Battery(u8),
Motor(Motor),
Light(Light),
Sound(Sound),
Config(Config),
}
fn unpack_battery(v: &[u8]) -> Result<u8> {
v.get(0)
.cloned()
.ok_or_else(|| anyhow!("Battery field is empty"))
}
impl TryFrom<(Uuid, &[u8])> for Message {
type Error = Error;
fn try_from((uuid, buf): (Uuid, &[u8])) -> Result<Self> {
let msg = match uuid {
UUID_ID => Message::Id(buf.try_into()?),
UUID_MOTION => Message::Motion(buf.try_into()?),
UUID_BUTTON => Message::Button(buf.try_into()?),
UUID_BATTERY => Message::Battery(unpack_battery(buf)?),
UUID_MOTOR => Message::Motor(buf.try_into()?),
UUID_LIGHT => Message::Light(buf.try_into()?),
UUID_SOUND => Message::Sound(buf.try_into()?),
UUID_CONFIG => Message::Config(buf.try_into()?),
uuid => bail!("Unknown uuid: {}", uuid),
};
Ok(msg)
}
}
impl TryFrom<(Uuid, Vec<u8>)> for Message {
type Error = Error;
fn try_from((uuid, buf): (Uuid, Vec<u8>)) -> Result<Self> {
(uuid, &buf as &[u8]).try_into()
}
}
impl TryFrom<Message> for (Uuid, Vec<u8>) {
type Error = Error;
fn try_from(msg: Message) -> Result<Self> {
let v = match msg {
Message::Id(v) => (UUID_ID, v.try_into()?),
Message::Motion(v) => (UUID_MOTION, v.try_into()?),
Message::Button(v) => (UUID_BUTTON, v.try_into()?),
Message::Battery(v) => (UUID_BATTERY, vec![v]),
Message::Motor(v) => (UUID_MOTOR, v.try_into()?),
Message::Light(v) => (UUID_LIGHT, v.try_into()?),
Message::Sound(v) => (UUID_SOUND, v.try_into()?),
Message::Config(v) => (UUID_CONFIG, v.try_into()?),
};
Ok(v)
}
}