use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use chrono::prelude::*;
use crc::{crc16, crc8};
use drone_state::{FlightData, LightInfo, LogMessage, WifiInfo};
use std::convert::TryFrom;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::net::{SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicU16, Ordering};
use std::time::SystemTime;
pub mod command_mode;
mod crc;
pub mod drone_state;
pub mod odometry;
mod rc_state;
pub use command_mode::CommandMode;
pub use drone_state::DroneMeta;
pub use rc_state::RCState;
static SEQ_NO: AtomicU16 = AtomicU16::new(1);
type Result = std::result::Result<(), ()>;
#[derive(Debug, Clone)]
struct VideoSettings {
pub port: u16,
pub enabled: bool,
pub mode: VideoMode,
pub level: u8,
pub encoding_rate: u8,
pub last_video_poll: SystemTime,
}
#[derive(Debug)]
pub struct Drone {
peer_ip: String,
socket: UdpSocket,
video_socket: Option<UdpSocket>,
video: VideoSettings,
last_stick_command: SystemTime,
pub rc_state: RCState,
pub drone_meta: DroneMeta,
status_counter: u32,
}
const START_OF_PACKET: u8 = 0xcc;
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u16)]
pub enum CommandIds {
Undefined = 0x0000,
SsidMsg = 0x0011,
SsidCmd = 0x0012,
SsidPasswordMsg = 0x0013,
SsidPasswordCmd = 0x0014,
WifiRegionMsg = 0x0015,
WifiRegionCmd = 0x0016,
WifiMsg = 0x001a,
VideoEncoderRateCmd = 0x0020,
VideoDynAdjRateCmd = 0x0021,
EisCmd = 0x0024,
VideoStartCmd = 0x0025,
VideoRateQuery = 0x0028,
TakePictureCommand = 0x0030,
VideoModeCmd = 0x0031,
VideoRecordCmd = 0x0032,
ExposureCmd = 0x0034,
LightMsg = 0x0035,
JpegQualityMsg = 0x0037,
Error1Msg = 0x0043,
Error2Msg = 0x0044,
VersionMsg = 0x0045,
TimeCmd = 0x0046,
ActivationTimeMsg = 0x0047,
LoaderVersionMsg = 0x0049,
StickCmd = 0x0050,
TakeoffCmd = 0x0054,
LandCmd = 0x0055,
FlightMsg = 0x0056,
AltLimitCmd = 0x0058,
FlipCmd = 0x005c,
ThrowAndGoCmd = 0x005d,
PalmLandCmd = 0x005e,
TelloCmdFileSize = 0x0062,
TelloCmdFileData = 0x0063,
TelloCmdFileComplete = 0x0064,
SmartVideoCmd = 0x0080,
SmartVideoStatusMsg = 0x0081,
LogHeaderMsg = 0x1050,
LogDataMsg = 0x1051,
LogConfigMsg = 0x1052,
BounceCmd = 0x1053,
CalibrateCmd = 0x1054,
LowBatThresholdCmd = 0x1055,
AltLimitMsg = 0x1056,
LowBatThresholdMsg = 0x1057,
AttLimitCmd = 0x1058,
AttLimitMsg = 0x1059,
}
impl From<u16> for CommandIds {
fn from(value: u16) -> CommandIds {
match value {
0x0011 => CommandIds::SsidMsg,
0x0012 => CommandIds::SsidCmd,
0x0013 => CommandIds::SsidPasswordMsg,
0x0014 => CommandIds::SsidPasswordCmd,
0x0015 => CommandIds::WifiRegionMsg,
0x0016 => CommandIds::WifiRegionCmd,
0x001a => CommandIds::WifiMsg,
0x0020 => CommandIds::VideoEncoderRateCmd,
0x0021 => CommandIds::VideoDynAdjRateCmd,
0x0024 => CommandIds::EisCmd,
0x0025 => CommandIds::VideoStartCmd,
0x0028 => CommandIds::VideoRateQuery,
0x0030 => CommandIds::TakePictureCommand,
0x0031 => CommandIds::VideoModeCmd,
0x0032 => CommandIds::VideoRecordCmd,
0x0034 => CommandIds::ExposureCmd,
0x0035 => CommandIds::LightMsg,
0x0037 => CommandIds::JpegQualityMsg,
0x0043 => CommandIds::Error1Msg,
0x0044 => CommandIds::Error2Msg,
0x0045 => CommandIds::VersionMsg,
0x0046 => CommandIds::TimeCmd,
0x0047 => CommandIds::ActivationTimeMsg,
0x0049 => CommandIds::LoaderVersionMsg,
0x0050 => CommandIds::StickCmd,
0x0054 => CommandIds::TakeoffCmd,
0x0055 => CommandIds::LandCmd,
0x0056 => CommandIds::FlightMsg,
0x0058 => CommandIds::AltLimitCmd,
0x005c => CommandIds::FlipCmd,
0x005d => CommandIds::ThrowAndGoCmd,
0x005e => CommandIds::PalmLandCmd,
0x0062 => CommandIds::TelloCmdFileSize,
0x0063 => CommandIds::TelloCmdFileData,
0x0064 => CommandIds::TelloCmdFileComplete,
0x0080 => CommandIds::SmartVideoCmd,
0x0081 => CommandIds::SmartVideoStatusMsg,
0x1050 => CommandIds::LogHeaderMsg,
0x1051 => CommandIds::LogDataMsg,
0x1052 => CommandIds::LogConfigMsg,
0x1053 => CommandIds::BounceCmd,
0x1054 => CommandIds::CalibrateCmd,
0x1055 => CommandIds::LowBatThresholdCmd,
0x1056 => CommandIds::AltLimitMsg,
0x1057 => CommandIds::LowBatThresholdMsg,
0x1058 => CommandIds::AttLimitCmd,
0x1059 => CommandIds::AttLimitMsg,
_ => CommandIds::Undefined,
}
}
}
#[derive(Debug, Clone)]
pub enum ResponseMsg {
Connected(String),
UnknownCommand(CommandIds),
}
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum PackageTypes {
X48 = 0x48,
X50 = 0x50,
X60 = 0x60,
X70 = 0x70,
X68 = 0x68,
}
pub enum Flip {
Forward = 0,
Left = 1,
Back = 2,
Right = 3,
ForwardLeft = 4,
BackLeft = 5,
BackRight = 6,
ForwardRight = 7,
}
#[derive(Debug, Clone)]
pub enum VideoMode {
M960x720 = 0,
M1280x720 = 1,
}
impl Drone {
pub fn new(ip: &str) -> Drone {
let peer_ip = ip.to_string();
let socket = UdpSocket::bind(&SocketAddr::from(([0, 0, 0, 0], 8889)))
.expect("couldn't bind to command address");
socket.set_nonblocking(true).unwrap();
socket.connect(ip).expect("connect command socket failed");
let video = VideoSettings {
port: 0,
enabled: false,
mode: VideoMode::M960x720,
level: 1,
encoding_rate: 4,
last_video_poll: SystemTime::now(),
};
let rc_state = RCState::default();
let drone_meta = DroneMeta::default();
Drone {
peer_ip,
socket,
video_socket: None,
video,
status_counter: 0,
last_stick_command: SystemTime::now(),
rc_state,
drone_meta,
}
}
pub fn connect(&mut self, video_port: u16) -> usize {
let mut data = b"conn_req: ".to_vec();
let mut cur = Cursor::new(&mut data);
cur.set_position(9);
cur.write_u16::<LittleEndian>(video_port).unwrap();
self.video.port = video_port;
self.start_video().unwrap();
let video_socket = UdpSocket::bind(&SocketAddr::from(([0, 0, 0, 0], self.video.port)))
.expect("couldn't bind to video address");
video_socket.set_nonblocking(true).unwrap();
self.video_socket = Some(video_socket);
self.socket.send(&data).expect("network should be usable")
}
pub fn send(&self, command: UdpCommand) -> Result {
let data: Vec<u8> = command.into();
if self.socket.send(&data).is_ok() {
Ok(())
} else {
Err(())
}
}
fn send_ack_log(&self, id: u16) -> Result {
let mut cmd = UdpCommand::new_with_zero_sqn(CommandIds::LogHeaderMsg, PackageTypes::X50);
cmd.write_u16(id);
self.send(cmd)
}
fn receive_video_frame(&self, socket: &UdpSocket) -> Option<Message> {
let mut read_buf = [0; 1440];
socket.set_nonblocking(true).unwrap();
if let Ok(received) = socket.recv(&mut read_buf) {
let active_frame_id = read_buf[0];
let mut sqn = read_buf[1];
let mut frame_buffer = read_buf[2..received].to_owned();
if sqn != 0 {
return None;
}
socket.set_nonblocking(false).unwrap();
'recVideo: loop {
if sqn >= 120 {
break 'recVideo Some(Message::Frame(active_frame_id, frame_buffer));
}
if let Ok(received) = socket.recv(&mut read_buf) {
let frame_id = read_buf[0];
if frame_id != active_frame_id {
break 'recVideo None;
}
sqn = read_buf[1];
let mut data = read_buf[2..received].to_owned();
frame_buffer.append(&mut data);
} else {
break 'recVideo None;
}
}
} else {
None
}
}
pub fn poll(&mut self) -> Option<Message> {
let now = SystemTime::now();
let delta = now.duration_since(self.last_stick_command).unwrap();
if delta.as_millis() > 1000 / 30 {
let (pitch, nick, roll, yaw, fast) = self.rc_state.get_stick_parameter();
self.send_stick(pitch, nick, roll, yaw, fast).unwrap();
self.last_stick_command = now.clone();
}
if self.video.enabled {
let delta = now.duration_since(self.video.last_video_poll).unwrap();
if delta.as_secs() > 1 {
self.video.last_video_poll = now;
self.poll_key_frame().unwrap();
}
if let Some(socket) = self.video_socket.as_ref() {
let frame = self.receive_video_frame(&socket);
if frame.is_some() {
return frame;
}
}
}
let mut read_buf = [0; 1440];
if let Ok(received) = self.socket.recv(&mut read_buf) {
let data = read_buf[..received].to_vec();
match Message::try_from(data) {
Ok(msg) => {
match &msg {
Message::Response(ResponseMsg::Connected(_)) => self.status_counter = 0,
Message::Data(Package {
data: PackageData::LogMessage(log),
..
}) => self.send_ack_log(log.id).unwrap(),
Message::Data(Package { cmd, .. }) if *cmd == CommandIds::TimeCmd => {
self.send_date_time().unwrap()
}
Message::Data(Package { cmd, data, .. })
if *cmd == CommandIds::FlightMsg =>
{
self.drone_meta.update(&data);
self.status_counter += 1;
if self.status_counter == 3 {
self.get_version().unwrap();
self.set_video_bitrate(4).unwrap();
self.get_alt_limit().unwrap();
self.get_battery_threshold().unwrap();
self.get_att_angle().unwrap();
self.get_region().unwrap();
self.set_exposure(2).unwrap();
};
}
Message::Data(Package { data, .. }) => {
self.drone_meta.update(&data);
}
_ => (),
};
Some(msg)
}
Err(_e) => None,
}
} else {
None
}
}
}
impl Drone {
pub fn command_mode(self) -> CommandMode {
CommandMode::from(self.peer_ip.parse::<SocketAddr>().unwrap())
}
}
impl Drone {
pub fn take_off(&self) -> Result {
self.send(UdpCommand::new(CommandIds::TakeoffCmd, PackageTypes::X68))
}
pub fn throw_and_go(&self) -> Result {
let mut cmd = UdpCommand::new(CommandIds::ThrowAndGoCmd, PackageTypes::X48);
cmd.write_u8(0);
self.send(cmd)
}
pub fn land(&self) -> Result {
let mut command = UdpCommand::new(CommandIds::LandCmd, PackageTypes::X68);
command.write_u8(0x00);
self.send(command)
}
pub fn stop_land(&self) -> Result {
let mut command = UdpCommand::new(CommandIds::LandCmd, PackageTypes::X68);
command.write_u8(0x00);
self.send(command)
}
pub fn palm_land(&self) -> Result {
let mut cmd = UdpCommand::new(CommandIds::PalmLandCmd, PackageTypes::X68);
cmd.write_u8(0);
self.send(cmd)
}
pub fn flip(&self, direction: Flip) -> Result {
let mut cmd = UdpCommand::new_with_zero_sqn(CommandIds::FlipCmd, PackageTypes::X70);
cmd.write_u8(direction as u8);
self.send(cmd)
}
pub fn bounce(&self) -> Result {
let mut cmd = UdpCommand::new(CommandIds::BounceCmd, PackageTypes::X68);
cmd.write_u8(0x30);
self.send(cmd)
}
pub fn bounce_stop(&self) -> Result {
let mut cmd = UdpCommand::new(CommandIds::BounceCmd, PackageTypes::X68);
cmd.write_u8(0x31);
self.send(cmd)
}
pub fn get_version(&self) -> Result {
self.send(UdpCommand::new(CommandIds::VersionMsg, PackageTypes::X48))
}
pub fn get_alt_limit(&self) -> Result {
self.send(UdpCommand::new(CommandIds::AltLimitMsg, PackageTypes::X68))
}
pub fn set_alt_limit(&self, limit: u8) -> Result {
let mut cmd = UdpCommand::new(CommandIds::AltLimitCmd, PackageTypes::X68);
cmd.write_u8(limit);
cmd.write_u8(0);
self.send(cmd)
}
pub fn get_att_angle(&self) -> Result {
self.send(UdpCommand::new(CommandIds::AttLimitMsg, PackageTypes::X68))
}
pub fn set_att_angle(&self) -> Result {
let mut cmd = UdpCommand::new(CommandIds::AttLimitCmd, PackageTypes::X68);
cmd.write_u8(0);
cmd.write_u8(0);
cmd.write_u8(10);
cmd.write_u8(0x41);
self.send(cmd)
}
pub fn get_battery_threshold(&self) -> Result {
self.send(UdpCommand::new(
CommandIds::LowBatThresholdMsg,
PackageTypes::X68,
))
}
pub fn set_battery_threshold(&self, threshold: u8) -> Result {
let mut cmd = UdpCommand::new(CommandIds::LowBatThresholdCmd, PackageTypes::X68);
cmd.write_u8(threshold);
self.send(cmd)
}
pub fn get_region(&self) -> Result {
self.send(UdpCommand::new(
CommandIds::WifiRegionCmd,
PackageTypes::X48,
))
}
pub fn send_stick(&self, pitch: f32, nick: f32, roll: f32, yaw: f32, fast: bool) -> Result {
let mut cmd = UdpCommand::new_with_zero_sqn(CommandIds::StickCmd, PackageTypes::X60);
let pitch_u = (1024.0 + 660.0 * pitch) as i64;
let nick_u = (1024.0 + 660.0 * nick) as i64;
let roll_u = (1024.0 + 660.0 * roll) as i64;
let yaw_u = (1024.0 + 660.0 * yaw) as i64;
let throttle_u = if fast { 1i64 } else { 0i64 };
let packed_axis: i64 = (roll_u & 0x7FF)
| (nick_u & 0x7FF) << 11
| (pitch_u & 0x7FF) << 22
| (yaw_u & 0x7FF) << 33
| throttle_u << 44;
cmd.write_u8(((packed_axis) & 0xFF) as u8);
cmd.write_u8(((packed_axis >> 8) & 0xFF) as u8);
cmd.write_u8(((packed_axis >> 16) & 0xFF) as u8);
cmd.write_u8(((packed_axis >> 24) & 0xFF) as u8);
cmd.write_u8(((packed_axis >> 32) & 0xFF) as u8);
cmd.write_u8(((packed_axis >> 40) & 0xFF) as u8);
self.send(Drone::add_time(cmd))
}
pub fn send_date_time(&self) -> Result {
let command = UdpCommand::new(CommandIds::TimeCmd, PackageTypes::X50);
self.send(Drone::add_date_time(command))
}
pub fn add_time(mut command: UdpCommand) -> UdpCommand {
let now = Local::now();
let millis = now.nanosecond() / 1_000_000;
command.write_u8(now.hour() as u8);
command.write_u8(now.minute() as u8);
command.write_u8(now.second() as u8);
command.write_u16(millis as u16);
command
}
pub fn add_date_time(mut command: UdpCommand) -> UdpCommand {
let now = Local::now();
let millis = now.nanosecond() / 1_000_000;
command.write_u8(0);
command.write_u16(now.year() as u16);
command.write_u16(now.month() as u16);
command.write_u16(now.day() as u16);
command.write_u16(now.hour() as u16);
command.write_u16(now.minute() as u16);
command.write_u16(now.second() as u16);
command.write_u16(millis as u16);
command
}
}
impl Drone {
pub fn start_video(&mut self) -> Result {
self.video.enabled = true;
self.video.last_video_poll = SystemTime::now();
self.send(UdpCommand::new_with_zero_sqn(
CommandIds::VideoStartCmd,
PackageTypes::X60,
))
}
pub fn poll_key_frame(&mut self) -> Result {
self.start_video()
}
pub fn set_video_mode(&mut self, mode: VideoMode) -> Result {
self.video.mode = mode.clone();
let mut cmd = UdpCommand::new_with_zero_sqn(CommandIds::VideoStartCmd, PackageTypes::X68);
cmd.write_u8(mode as u8);
self.send(cmd)
}
pub fn set_exposure(&mut self, level: u8) -> Result {
let mut cmd = UdpCommand::new(CommandIds::ExposureCmd, PackageTypes::X48);
cmd.write_u8(level);
self.send(cmd)
}
pub fn set_video_bitrate(&mut self, rate: u8) -> Result {
self.video.encoding_rate = rate;
let mut cmd = UdpCommand::new(CommandIds::VideoEncoderRateCmd, PackageTypes::X68);
cmd.write_u8(rate);
self.send(cmd)
}
pub fn take_picture(&self) -> Result {
self.send(UdpCommand::new(
CommandIds::TakePictureCommand,
PackageTypes::X68,
))
}
}
#[derive(Debug, Clone)]
pub struct UdpCommand {
cmd: CommandIds,
pkt_type: PackageTypes,
zero_sqn: bool,
inner: Vec<u8>,
}
impl UdpCommand {
pub fn new(cmd: CommandIds, pkt_type: PackageTypes) -> UdpCommand {
UdpCommand {
cmd,
pkt_type,
zero_sqn: false,
inner: Vec::new(),
}
}
pub fn new_with_zero_sqn(cmd: CommandIds, pkt_type: PackageTypes) -> UdpCommand {
UdpCommand {
cmd,
pkt_type,
zero_sqn: true,
inner: Vec::new(),
}
}
}
impl UdpCommand {
pub fn write(&mut self, bytes: &[u8]) {
self.inner.append(&mut bytes.to_owned())
}
pub fn write_u8(&mut self, byte: u8) {
self.inner.push(byte)
}
pub fn write_u16(&mut self, value: u16) {
let mut cur = Cursor::new(&mut self.inner);
cur.seek(SeekFrom::End(0)).expect("");
cur.write_u16::<LittleEndian>(value).expect("");
}
pub fn write_u64(&mut self, value: u64) {
let mut cur = Cursor::new(&mut self.inner);
cur.seek(SeekFrom::End(0)).expect("");
cur.write_u64::<LittleEndian>(value).expect("");
}
}
impl Into<Vec<u8>> for UdpCommand {
fn into(self) -> Vec<u8> {
let mut data = {
let lng = self.inner.len();
let data: &[u8] = &self.inner;
let mut cur = Cursor::new(Vec::new());
cur.write_u8(START_OF_PACKET).expect("");
cur.write_u16::<LittleEndian>((lng as u16 + 11) << 3)
.expect("");
cur.write_u8(crc8(cur.clone().into_inner())).expect("");
cur.write_u8(self.pkt_type as u8).expect("");
cur.write_u16::<LittleEndian>(self.cmd as u16).expect("");
if self.zero_sqn {
cur.write_u16::<LittleEndian>(0).expect("");
} else {
let nr = SEQ_NO.fetch_add(1, Ordering::SeqCst);
cur.write_u16::<LittleEndian>(nr).expect("");
}
if lng > 0 {
cur.write_all(&data).unwrap();
}
cur.into_inner()
};
data.write_u16::<LittleEndian>(crc16(data.clone()))
.expect("");
data
}
}
#[derive(Debug, Clone)]
pub struct Package {
pub cmd: CommandIds,
pub size: u16,
pub sq_nr: u16,
pub data: PackageData,
}
#[derive(Debug, Clone)]
pub enum Message {
Data(Package),
Response(ResponseMsg),
Frame(u8, Vec<u8>),
}
impl TryFrom<Vec<u8>> for Message {
type Error = String;
fn try_from(data: Vec<u8>) -> std::result::Result<Self, Self::Error> {
let mut cur = Cursor::new(data);
if let Ok(START_OF_PACKET) = cur.read_u8() {
let size = (cur.read_u16::<LittleEndian>().unwrap() >> 3) - 11;
let _crc8 = cur.read_u8().unwrap();
let _pkt_type = cur.read_u8().unwrap();
let cmd = CommandIds::from(cur.read_u16::<LittleEndian>().unwrap());
let sq_nr = cur.read_u16::<LittleEndian>().unwrap();
let data = if size > 0 {
let mut data: Vec<u8> = Vec::with_capacity(size as usize);
cur.read_to_end(&mut data).unwrap();
if data.len() >= 2 {
let _crc16: u16 =
(data.pop().unwrap() as u16) + ((data.pop().unwrap() as u16) << 8);
}
match cmd {
CommandIds::FlightMsg => PackageData::FlightData(FlightData::from(data)),
CommandIds::WifiMsg => PackageData::WifiInfo(WifiInfo::from(data)),
CommandIds::LightMsg => PackageData::LightInfo(LightInfo::from(data)),
CommandIds::VersionMsg => PackageData::Version(
String::from_utf8(data[1..].to_vec())
.expect("version is not valid")
.trim_matches(char::from(0))
.to_string(),
),
CommandIds::AltLimitMsg => {
let mut c = Cursor::new(data);
let _ = c.read_u8().unwrap();
let h = c.read_u16::<LittleEndian>().unwrap();
PackageData::AtlInfo(h)
}
CommandIds::LogHeaderMsg => PackageData::LogMessage(LogMessage::from(data)),
_ => PackageData::Unknown(data),
}
} else {
PackageData::NoData()
};
Ok(Message::Data(Package {
cmd,
size,
sq_nr,
data,
}))
} else {
let data = cur.into_inner();
if data[0..9].to_vec() == b"conn_ack:" {
return Ok(Message::Response(ResponseMsg::Connected(
String::from_utf8(data).unwrap(),
)));
} else if data[0..16].to_vec() == b"unknown command:" {
let mut cur = Cursor::new(data[17..].to_owned());
let command = CommandIds::from(cur.read_u16::<LittleEndian>().unwrap());
return Ok(Message::Response(ResponseMsg::UnknownCommand(command)));
}
let msg = String::from_utf8(data.clone()[0..5].to_vec()).unwrap_or_default();
Err(format!("invalid package {:x?}", msg))
}
}
}
#[derive(Debug, Clone)]
pub enum PackageData {
NoData(),
AtlInfo(u16),
FlightData(FlightData),
LightInfo(LightInfo),
LogMessage(LogMessage),
Version(String),
WifiInfo(WifiInfo),
Unknown(Vec<u8>),
}