#![forbid(unsafe_code, unstable_features)]
#![warn(
missing_docs,
clippy::missing_docs_in_private_items,
clippy::nursery,
clippy::pedantic
)]
#![allow(
clippy::too_many_arguments,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions
)]
#![doc =include_str!("../README.md")]
pub use error::{Error, Result};
use std::{
fmt::{self, Debug, Formatter},
io::{Cursor, Write},
sync::Arc,
};
#[macro_use]
extern crate tracing;
#[cfg(feature = "strum")]
pub use strum::IntoEnumIterator;
mod error;
pub mod motor;
mod protocol;
pub mod sensor;
mod socket;
pub mod system;
#[cfg(feature = "usb")]
pub use socket::usb::Usb;
#[cfg(feature = "bluetooth")]
pub use socket::bluetooth::Bluetooth;
use motor::{OutMode, OutPort, OutputState, RegulationMode, RunState};
use protocol::{Opcode, Packet};
use sensor::{InPort, InputValues, SensorMode, SensorType};
use socket::Socket;
use system::{
BufType, DeviceInfo, FileHandle, FindFileHandle, FwVersion, ModuleHandle,
};
pub const MAX_MESSAGE_LEN: usize = 58;
const MAX_NAME_LEN: usize = 15;
pub const MAX_INBOX_ID: u8 = 19;
const MOD_DISPLAY: u32 = 0xa0001;
const DISPLAY_DATA_OFFSET: u16 = 119;
pub const DISPLAY_WIDTH: usize = 100;
pub const DISPLAY_HEIGHT: usize = 64;
pub const DISPLAY_DATA_LEN: usize = DISPLAY_WIDTH * DISPLAY_HEIGHT / 8;
const DISPLAY_DATA_CHUNK_SIZE: u16 = 32;
#[allow(clippy::cast_possible_truncation)]
const DISPLAY_NUM_CHUNKS: u16 =
DISPLAY_DATA_LEN as u16 / DISPLAY_DATA_CHUNK_SIZE;
#[derive(Clone)]
pub struct Nxt {
device: Arc<dyn Socket + Send + Sync>,
name: String,
}
impl Debug for Nxt {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
fmt.debug_struct("NXT")
.field("name", &self.name)
.finish_non_exhaustive()
}
}
impl Nxt {
#[cfg(feature = "usb")]
pub async fn first_usb() -> Result<Self> {
let device = socket::usb::Usb::first()?;
Self::init(device).await
}
#[cfg(feature = "usb")]
pub async fn all_usb() -> Result<Vec<Self>> {
let devices = socket::usb::Usb::all()?;
futures::future::try_join_all(devices.into_iter().map(Self::init)).await
}
pub async fn init<D: Socket + Send + Sync + 'static>(
device: D,
) -> Result<Self> {
debug!("Initialise NXT from {} device", std::any::type_name::<D>());
let mut nxt = Self {
device: Arc::new(device),
name: String::new(),
};
let info = nxt.get_device_info().await?;
debug!("Connected device is named `{}`", info.name);
nxt.name = info.name;
Ok(nxt)
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
async fn send(&self, pkt: &Packet, check_status: bool) -> Result<()> {
let mut buf = [0; 64];
let serialised = pkt.serialise(&mut buf)?;
let written = self.device.send(serialised).await?;
if written == serialised.len() {
if check_status {
let _recv = self.recv(pkt.opcode).await?;
}
Ok(())
} else {
Err(Error::Write)
}
}
async fn recv(&self, opcode: Opcode) -> Result<Packet> {
let mut buf = [0; 64];
let buf = self.device.recv(&mut buf).await?;
let mut recv = Packet::parse(buf)?;
recv.check_status()?;
if recv.opcode == opcode {
Ok(recv)
} else {
Err(Error::ReplyMismatch)
}
}
async fn send_recv(&self, pkt: &Packet) -> Result<Packet> {
self.send(pkt, false).await?;
self.recv(pkt.opcode).await
}
pub async fn get_display_data(&self) -> Result<[u8; DISPLAY_DATA_LEN]> {
let out = [0; DISPLAY_DATA_LEN];
let mut cur = Cursor::new(out);
for chunk_idx in 0..DISPLAY_NUM_CHUNKS {
let data = self
.read_io_map(
MOD_DISPLAY,
DISPLAY_DATA_OFFSET + chunk_idx * DISPLAY_DATA_CHUNK_SIZE,
DISPLAY_DATA_CHUNK_SIZE,
)
.await?;
assert_eq!(data.len(), DISPLAY_DATA_CHUNK_SIZE.into());
cur.write_all(&data)?;
}
Ok(cur.into_inner())
}
pub async fn get_battery_level(&self) -> Result<u16> {
let pkt = Packet::new(Opcode::DirectGetBattLevel);
let mut recv = self.send_recv(&pkt).await?;
recv.read_u16()
}
pub async fn get_firmware_version(&self) -> Result<FwVersion> {
let pkt = Packet::new(Opcode::SystemVersions);
let mut recv = self.send_recv(&pkt).await?;
let prot_min = recv.read_u8()?;
let prot_maj = recv.read_u8()?;
let fw_min = recv.read_u8()?;
let fw_maj = recv.read_u8()?;
Ok(FwVersion {
prot: (prot_maj, prot_min),
fw: (fw_maj, fw_min),
})
}
pub async fn start_program(&self, name: &str) -> Result<()> {
let mut pkt = Packet::new(Opcode::DirectStartProgram);
pkt.push_filename(name)?;
self.send(&pkt, true).await
}
pub async fn stop_program(&self) -> Result<()> {
let pkt = Packet::new(Opcode::DirectStopProgram);
self.send(&pkt, true).await
}
pub async fn play_sound(&self, file: &str, loop_: bool) -> Result<()> {
let mut pkt = Packet::new(Opcode::DirectPlaySoundFile);
pkt.push_bool(loop_);
pkt.push_filename(file)?;
self.send(&pkt, true).await
}
pub async fn play_tone(&self, freq: u16, duration_ms: u16) -> Result<()> {
let mut pkt = Packet::new(Opcode::DirectPlayTone);
pkt.push_u16(freq);
pkt.push_u16(duration_ms);
self.send(&pkt, true).await
}
pub async fn set_output_state(
&self,
port: OutPort,
power: i8,
mode: OutMode,
regulation_mode: RegulationMode,
turn_ratio: i8,
run_state: RunState,
tacho_limit: u32,
) -> Result<()> {
let mut pkt = Packet::new(Opcode::DirectSetOutState);
pkt.push_u8(port as u8);
pkt.push_i8(power);
pkt.push_u8(mode.0);
pkt.push_u8(regulation_mode as u8);
pkt.push_i8(turn_ratio);
pkt.push_u8(run_state as u8);
pkt.push_u32(tacho_limit);
self.send(&pkt, true).await
}
pub async fn set_input_mode(
&self,
port: InPort,
sensor_type: SensorType,
sensor_mode: SensorMode,
) -> Result<()> {
let mut pkt = Packet::new(Opcode::DirectSetInMode);
pkt.push_u8(port as u8);
pkt.push_u8(sensor_type as u8);
pkt.push_u8(sensor_mode as u8);
self.send(&pkt, true).await
}
pub async fn get_output_state(&self, port: OutPort) -> Result<OutputState> {
let mut pkt = Packet::new(Opcode::DirectGetOutState);
pkt.push_u8(port as u8);
self.send(&pkt, false).await?;
let mut recv = self.recv(Opcode::DirectGetOutState).await?;
let port = recv.read_u8()?.try_into()?;
let power = recv.read_i8()?;
let mode = recv.read_u8()?.into();
let regulation_mode = recv.read_u8()?.try_into()?;
let turn_ratio = recv.read_i8()?;
let run_state = recv.read_u8()?.try_into()?;
let tacho_limit = recv.read_u32()?;
let tacho_count = recv.read_i32()?;
let block_tacho_count = recv.read_i32()?;
let rotation_count = recv.read_i32()?;
Ok(OutputState {
port,
power,
mode,
regulation_mode,
turn_ratio,
run_state,
tacho_limit,
tacho_count,
block_tacho_count,
rotation_count,
})
}
pub async fn get_input_values(&self, port: InPort) -> Result<InputValues> {
let mut pkt = Packet::new(Opcode::DirectGetInVals);
pkt.push_u8(port as u8);
let mut recv = self.send_recv(&pkt).await?;
let port = recv.read_u8()?.try_into()?;
let valid = recv.read_bool()?;
let calibrated = recv.read_bool()?;
let sensor_type = recv.read_u8()?.try_into()?;
let sensor_mode = recv.read_u8()?.try_into()?;
let raw_value = recv.read_u16()?;
let normalised_value = recv.read_u16()?;
let scaled_value = recv.read_i16()?;
let calibrated_value = recv.read_i16()?;
Ok(InputValues {
port,
valid,
calibrated,
sensor_type,
sensor_mode,
raw_value,
normalised_value,
scaled_value,
calibrated_value,
})
}
pub async fn reset_input_scaled_value(&self, port: InPort) -> Result<()> {
let mut pkt = Packet::new(Opcode::DirectResetInVal);
pkt.push_u8(port as u8);
self.send(&pkt, true).await
}
pub async fn message_write(&self, inbox: u8, message: &[u8]) -> Result<()> {
if inbox > MAX_INBOX_ID {
return Err(Error::Serialise("Invalid mailbox ID"));
}
if message.len() > MAX_MESSAGE_LEN {
return Err(Error::Serialise("Message too long (max 58 bytes)"));
}
let mut pkt = Packet::new(Opcode::DirectMessageWrite);
pkt.push_u8(inbox);
#[allow(clippy::cast_possible_truncation)]
pkt.push_u8(message.len() as u8 + 1);
pkt.push_slice(message);
pkt.push_u8(0);
self.send(&pkt, true).await
}
pub async fn reset_motor_position(
&self,
port: OutPort,
relative: bool,
) -> Result<()> {
let mut pkt = Packet::new(Opcode::DirectResetPosition);
pkt.push_u8(port as u8);
pkt.push_bool(relative);
self.send(&pkt, true).await
}
pub async fn stop_sound_playback(&self) -> Result<()> {
let pkt = Packet::new(Opcode::DirectStopSound);
self.send(&pkt, true).await
}
pub async fn keep_alive(&self) -> Result<u32> {
let pkt = Packet::new(Opcode::DirectKeepAlive);
self.send(&pkt, false).await?;
let mut recv = self.recv(Opcode::DirectKeepAlive).await?;
recv.read_u32()
}
pub async fn ls_get_status(&self, port: InPort) -> Result<u8> {
let mut pkt = Packet::new(Opcode::DirectLsGetStatus);
pkt.push_u8(port as u8);
self.send(&pkt, false).await?;
let mut recv = self.recv(Opcode::DirectLsGetStatus).await?;
recv.read_u8()
}
pub async fn ls_write(
&self,
port: InPort,
tx_data: &[u8],
rx_bytes: u8,
) -> Result<()> {
if tx_data.len() > MAX_MESSAGE_LEN {
return Err(Error::Serialise("Data too long"));
}
let mut pkt = Packet::new(Opcode::DirectLsWrite);
pkt.push_u8(port as u8);
#[allow(clippy::cast_possible_truncation)]
pkt.push_u8(tx_data.len() as u8);
pkt.push_u8(rx_bytes);
pkt.push_slice(tx_data);
self.send(&pkt, true).await
}
pub async fn ls_read(&self, port: InPort) -> Result<Vec<u8>> {
let mut pkt = Packet::new(Opcode::DirectLsRead);
pkt.push_u8(port as u8);
self.send(&pkt, false).await?;
let mut recv = self.recv(Opcode::DirectLsRead).await?;
let len = recv.read_u8()?;
let data = recv.read_slice(len as usize)?;
Ok(data.to_vec())
}
pub async fn get_current_program_name(&self) -> Result<String> {
let pkt = Packet::new(Opcode::DirectGetCurrProgram);
self.send(&pkt, false).await?;
let mut recv = self.recv(Opcode::DirectGetCurrProgram).await?;
recv.read_filename()
}
pub async fn message_read(
&self,
remote_inbox: u8,
local_inbox: u8,
remove: bool,
) -> Result<Vec<u8>> {
let mut pkt = Packet::new(Opcode::DirectMessageRead);
pkt.push_u8(remote_inbox);
pkt.push_u8(local_inbox);
pkt.push_bool(remove);
self.send(&pkt, false).await?;
let mut recv = self.recv(Opcode::DirectMessageRead).await?;
let _local_inbox = recv.read_u8()?;
let len = recv.read_u8()?;
let data = recv.read_slice(len as usize)?;
Ok(data.to_vec())
}
pub async fn file_open_write(
&self,
name: &str,
len: u32,
) -> Result<FileHandle> {
let mut pkt = Packet::new(Opcode::SystemOpenwrite);
pkt.push_filename(name)?;
pkt.push_u32(len);
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
Ok(FileHandle { handle, len })
}
pub async fn file_write(
&self,
handle: &FileHandle,
data: &[u8],
) -> Result<u32> {
let mut pkt = Packet::new(Opcode::SystemWrite);
pkt.push_u8(handle.handle);
pkt.push_slice(data);
let mut recv = self.send_recv(&pkt).await?;
let _handle = recv.read_u8()?;
recv.read_u32()
}
pub async fn file_open_write_data(
&self,
name: &str,
len: u32,
) -> Result<FileHandle> {
let mut pkt = Packet::new(Opcode::SystemOpenwritedata);
pkt.push_filename(name)?;
pkt.push_u32(len);
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
Ok(FileHandle { handle, len })
}
pub async fn file_open_append_data(
&self,
name: &str,
) -> Result<FileHandle> {
let mut pkt = Packet::new(Opcode::SystemOpenappenddata);
pkt.push_filename(name)?;
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
let len = recv.read_u32()?;
Ok(FileHandle { handle, len })
}
pub async fn file_close(&self, handle: &FileHandle) -> Result<()> {
let mut pkt = Packet::new(Opcode::SystemClose);
pkt.push_u8(handle.handle);
self.send(&pkt, true).await
}
pub async fn file_open_read(&self, name: &str) -> Result<FileHandle> {
let mut pkt = Packet::new(Opcode::SystemOpenread);
pkt.push_filename(name)?;
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
let len = recv.read_u32()?;
Ok(FileHandle { handle, len })
}
pub async fn file_read(
&self,
handle: &FileHandle,
len: u32,
) -> Result<Vec<u8>> {
let mut pkt = Packet::new(Opcode::SystemOpenread);
pkt.push_u8(handle.handle);
pkt.push_u32(len);
let mut recv = self.send_recv(&pkt).await?;
let _handle = recv.read_u8()?;
let len = recv.read_u8()?;
let data = recv.read_slice(len as usize)?;
Ok(data.to_vec())
}
pub async fn file_delete(&self, name: &str) -> Result<()> {
let mut pkt = Packet::new(Opcode::SystemDelete);
pkt.push_filename(name)?;
self.send(&pkt, true).await
}
pub async fn file_find_first(
&self,
pattern: &str,
) -> Result<FindFileHandle> {
let mut pkt = Packet::new(Opcode::SystemFindfirst);
pkt.push_filename(pattern)?;
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
let name = recv.read_filename()?;
let len = recv.read_u32()?;
Ok(FindFileHandle { handle, name, len })
}
pub async fn file_find_next(
&self,
handle: &FindFileHandle,
) -> Result<FindFileHandle> {
let mut pkt = Packet::new(Opcode::SystemFindnext);
pkt.push_u8(handle.handle);
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
let name = recv.read_filename()?;
let len = recv.read_u32()?;
Ok(FindFileHandle { handle, name, len })
}
pub async fn file_open_read_linear(
&self,
name: &str,
len: u32,
) -> Result<FileHandle> {
let mut pkt = Packet::new(Opcode::SystemOpenreadlinear);
pkt.push_filename(name)?;
pkt.push_u32(len);
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
Ok(FileHandle { handle, len })
}
pub async fn file_open_write_linear(
&self,
name: &str,
len: u32,
) -> Result<FileHandle> {
let mut pkt = Packet::new(Opcode::SystemOpenwritelinear);
pkt.push_filename(name)?;
pkt.push_u32(len);
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
Ok(FileHandle { handle, len })
}
pub async fn module_find_first(
&self,
pattern: &str,
) -> Result<ModuleHandle> {
let mut pkt = Packet::new(Opcode::SystemFindfirstmodule);
pkt.push_filename(pattern)?;
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
let name = recv.read_filename()?;
let id = recv.read_u32()?;
let len = recv.read_u32()?;
let iomap_len = recv.read_u16()?;
Ok(ModuleHandle {
handle,
name,
id,
len,
iomap_len,
})
}
pub async fn module_find_next(
&self,
handle: &ModuleHandle,
) -> Result<ModuleHandle> {
let mut pkt = Packet::new(Opcode::SystemFindnextmodule);
pkt.push_u8(handle.handle);
let mut recv = self.send_recv(&pkt).await?;
let handle = recv.read_u8()?;
let name = recv.read_filename()?;
let id = recv.read_u32()?;
let len = recv.read_u32()?;
let iomap_len = recv.read_u16()?;
Ok(ModuleHandle {
handle,
name,
id,
len,
iomap_len,
})
}
pub async fn module_close(&self, handle: &ModuleHandle) -> Result<()> {
let mut pkt = Packet::new(Opcode::SystemClosemodhandle);
pkt.push_u8(handle.handle);
self.send(&pkt, true).await
}
pub async fn read_io_map(
&self,
mod_id: u32,
offset: u16,
count: u16,
) -> Result<Vec<u8>> {
let mut pkt = Packet::new(Opcode::SystemIomapread);
pkt.push_u32(mod_id);
pkt.push_u16(offset);
pkt.push_u16(count);
let mut recv = self.send_recv(&pkt).await?;
let _mod_id = recv.read_u32()?;
let len = recv.read_u16()?;
let data = recv.read_slice(len as usize)?;
Ok(data.to_vec())
}
pub async fn write_io_map(
&self,
mod_id: u32,
offset: u16,
data: &[u8],
) -> Result<u16> {
let mut pkt = Packet::new(Opcode::SystemIomapwrite);
pkt.push_u32(mod_id);
pkt.push_u16(offset);
pkt.push_u16(data.len().try_into()?);
pkt.push_slice(data);
let mut recv = self.send_recv(&pkt).await?;
let _mod_id = recv.read_u32()?;
recv.read_u16()
}
pub async fn boot(&self, sure: bool) -> Result<Vec<u8>> {
if !sure {
return Err(Error::Serialise(
"Are you sure? This is not recoverable",
));
}
let mut pkt = Packet::new(Opcode::SystemBootcmd);
pkt.push_slice(b"Let's dance: SAMBA\0");
let mut recv = self.send_recv(&pkt).await?;
Ok(recv.read_slice(4)?.to_vec())
}
pub async fn set_brick_name(&self, name: &str) -> Result<()> {
let mut pkt = Packet::new(Opcode::SystemSetbrickname);
pkt.push_str(name, MAX_NAME_LEN)?;
self.send(&pkt, true).await
}
pub async fn get_bt_addr(&self) -> Result<[u8; 6]> {
let pkt = Packet::new(Opcode::SystemBtgetaddr);
let mut recv = self.send_recv(&pkt).await?;
let addr = recv.read_slice(6)?;
Ok(addr.try_into().unwrap())
}
pub async fn get_device_info(&self) -> Result<DeviceInfo> {
let pkt = Packet::new(Opcode::SystemDeviceinfo);
let mut recv = self.send_recv(&pkt).await?;
let name = recv.read_string(MAX_NAME_LEN)?;
let bt_addr = [
recv.read_u8()?,
recv.read_u8()?,
recv.read_u8()?,
recv.read_u8()?,
recv.read_u8()?,
recv.read_u8()?,
];
recv.read_u8()?;
let signal_strength = (
recv.read_u8()?,
recv.read_u8()?,
recv.read_u8()?,
recv.read_u8()?,
);
let flash = recv.read_u32()?;
Ok(DeviceInfo {
name,
bt_addr,
signal_strength,
flash,
})
}
pub async fn delete_user_flash(&self) -> Result<()> {
let pkt = Packet::new(Opcode::SystemDeleteuserflash);
self.send(&pkt, true).await
}
pub async fn poll_command_length(&self, buf: BufType) -> Result<u8> {
let mut pkt = Packet::new(Opcode::SystemPollcmdlen);
pkt.push_u8(buf as u8);
let mut recv = self.send_recv(&pkt).await?;
let _buf_num = recv.read_u8()?;
recv.read_u8()
}
pub async fn poll_command(&self, buf: BufType, len: u8) -> Result<Vec<u8>> {
let mut pkt = Packet::new(Opcode::SystemPollcmd);
pkt.push_u8(buf as u8);
pkt.push_u8(len);
let mut recv = self.send_recv(&pkt).await?;
let _buf = recv.read_u8()?;
let len = recv.read_u8()?;
let data = recv.read_slice(len as usize)?;
Ok(data.to_vec())
}
pub async fn bluetooth_factory_reset(&self) -> Result<()> {
let pkt = Packet::new(Opcode::SystemBtfactoryreset);
self.send(&pkt, true).await
}
}