Skip to content

Commit

Permalink
Implemented support for tuya version 3.2
Browse files Browse the repository at this point in the history
This is completely untested! may work, prabaly not. No real way to konw
without some hardware to test with...
  • Loading branch information
EmilSodergren committed Jan 5, 2024
1 parent 4a5043e commit 274d073
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 19 deletions.
6 changes: 3 additions & 3 deletions src/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn maybe_strip_header(version: &TuyaVersion, data: &[u8]) -> Vec<u8> {
if data.len() > 3 && &data[..3] == version.as_bytes() {
match version {
TuyaVersion::ThreeOne => data.split_at(19).1.to_vec(),
TuyaVersion::ThreeThree => data.split_at(15).1.to_vec(),
TuyaVersion::ThreeTwo | TuyaVersion::ThreeThree => data.split_at(15).1.to_vec(),
}
} else {
data.to_vec()
Expand All @@ -34,7 +34,7 @@ impl TuyaCipher {
let res = encrypt(self.cipher, &self.key, None, data)?;
match self.version {
TuyaVersion::ThreeOne => Ok(general_purpose::STANDARD.encode(res).as_bytes().to_vec()),
TuyaVersion::ThreeThree => Ok(res),
TuyaVersion::ThreeTwo | TuyaVersion::ThreeThree => Ok(res),
}
}

Expand All @@ -44,7 +44,7 @@ impl TuyaCipher {
// 3.1 is base64 encoded, 3.3 is not
let data = match self.version {
TuyaVersion::ThreeOne => general_purpose::STANDARD.decode(&data)?,
TuyaVersion::ThreeThree => data.to_vec(),
TuyaVersion::ThreeTwo | TuyaVersion::ThreeThree => data.to_vec(),
};
let res = decrypt(self.cipher, &self.key, None, &data)?;

Expand Down
24 changes: 15 additions & 9 deletions src/mesparse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ pub enum CommandType {
Udp = 0,
ApConfig = 1,
Active = 2,
Bind = 3,
RenameGw = 4,
RenameDevice = 5,
SessionKeyNegotiationStart = 3,
SessionKeyNegotiationResponse = 4,
SessionKeyNegotiationFinialize = 5,
Unbind = 6,
Control = 7,
Status = 8,
Expand Down Expand Up @@ -71,15 +71,17 @@ pub enum CommandType {
}

#[derive(Debug, PartialEq, Clone)]
pub(crate) enum TuyaVersion {
pub enum TuyaVersion {
ThreeOne,
ThreeTwo,
ThreeThree,
}

impl TuyaVersion {
pub fn as_bytes(&self) -> &[u8] {
match &self {
TuyaVersion::ThreeOne => b"3.1",
TuyaVersion::ThreeTwo => b"3.2",
TuyaVersion::ThreeThree => b"3.3",
}
}
Expand All @@ -88,8 +90,9 @@ impl TuyaVersion {
impl FromStr for TuyaVersion {
type Err = ErrorKind;

fn from_str(s: &str) -> Result<Self> {
let version: Vec<&str> = s.split('.').collect();
fn from_str(ver_str: &str) -> Result<Self> {
let version: Vec<&str> = ver_str.split('.').collect();
// Version is not correctly given
if version.len() < 2 || !version[0].ends_with('3') {
return Err(ErrorKind::VersionError(
"Unknown".to_string(),
Expand All @@ -98,6 +101,7 @@ impl FromStr for TuyaVersion {
}
match version[1] {
"1" => Ok(TuyaVersion::ThreeOne),
"2" => Ok(TuyaVersion::ThreeTwo),
"3" => Ok(TuyaVersion::ThreeThree),
_ => Err(ErrorKind::VersionError(
version[0].to_string(),
Expand Down Expand Up @@ -156,7 +160,7 @@ pub struct MessageParser {
/// not need decrypting.
impl MessageParser {
pub fn create(ver: &str, key: Option<&str>) -> Result<MessageParser> {
let version = TuyaVersion::from_str(ver)?;
let version: TuyaVersion = ver.parse()?;
let key = verify_key(key)?;
let cipher = TuyaCipher::create(&key, version.clone());
Ok(MessageParser { version, cipher })
Expand Down Expand Up @@ -205,7 +209,7 @@ impl MessageParser {
mes.payload.clone().try_into()
}
}
TuyaVersion::ThreeThree => match mes.command {
TuyaVersion::ThreeTwo | TuyaVersion::ThreeThree => match mes.command {
Some(CommandType::DpQuery) | Some(CommandType::DpRefresh) => {
let payload: Vec<u8> = mes.payload.clone().try_into()?;
self.cipher.encrypt(&payload)
Expand All @@ -220,7 +224,9 @@ impl MessageParser {
payload_with_header.extend(self.version.as_bytes());
match self.version {
TuyaVersion::ThreeOne => payload_with_header.extend(vec![0; 12]),
TuyaVersion::ThreeThree => payload_with_header.extend(self.cipher.md5(&payload)),
TuyaVersion::ThreeTwo | TuyaVersion::ThreeThree => {
payload_with_header.extend(self.cipher.md5(&payload))
}
}
payload_with_header.extend(self.cipher.encrypt(&payload)?);
Ok(payload_with_header)
Expand Down
55 changes: 48 additions & 7 deletions src/tuyadevice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//!
//! The TuyaDevice is the high level device communication API. To get in to the nitty gritty
//! details, create a MessageParser.
use crate::mesparse::{CommandType, Message, MessageParser};
use crate::mesparse::{CommandType, Message, MessageParser, TuyaVersion};
use crate::transports::{Transport, TuyaTransport};
use crate::{Payload, Result};
use log::{debug, info};
Expand All @@ -15,12 +15,18 @@ pub struct TuyaDevice {
mp: MessageParser,
addr: SocketAddr,
transport: Transport,
ver: TuyaVersion,
}

impl TuyaDevice {
pub fn create(ver: &str, key: Option<&str>, addr: IpAddr) -> Result<TuyaDevice> {
let mp = MessageParser::create(ver, key)?;
Ok(TuyaDevice::create_with_mp(mp, addr, Transport::TCP(6668)))
Ok(TuyaDevice::create_with_mp(
mp,
addr,
Transport::TCP(6668),
ver.parse()?,
))
}

pub fn create_with_transport(
Expand All @@ -30,21 +36,56 @@ impl TuyaDevice {
transport: Transport,
) -> Result<TuyaDevice> {
let mp = MessageParser::create(ver, key)?;
Ok(TuyaDevice::create_with_mp(mp, addr, transport))
Ok(TuyaDevice::create_with_mp(
mp,
addr,
transport,
ver.parse()?,
))
}

pub fn create_with_mp(mp: MessageParser, addr: IpAddr, transport: Transport) -> TuyaDevice {
pub fn create_with_mp(
mp: MessageParser,
addr: IpAddr,
transport: Transport,
ver: TuyaVersion,
) -> TuyaDevice {
match transport {
Transport::TCP(port) | Transport::UDP(port) => TuyaDevice {
mp,
addr: SocketAddr::new(addr, port),
transport,
ver,
},
}
}

fn set_commandtype(&self) -> CommandType {
match self.ver {
TuyaVersion::ThreeOne | TuyaVersion::ThreeTwo | TuyaVersion::ThreeThree => {
CommandType::Control
}
}
}

fn get_commandtype(&self) -> CommandType {
match self.ver {
TuyaVersion::ThreeOne | TuyaVersion::ThreeThree => CommandType::DpQuery,
TuyaVersion::ThreeTwo => CommandType::Control,
}
}

fn refresh_commandtype(&self) -> CommandType {
match self.ver {
TuyaVersion::ThreeOne | TuyaVersion::ThreeTwo | TuyaVersion::ThreeThree => {
CommandType::DpRefresh
}
}
}

//TODO: There are code duplication here... do we really need three methods??
pub fn set(&self, tuya_payload: Payload, seq_id: u32) -> Result<()> {
let mes = Message::new(tuya_payload, CommandType::Control, Some(seq_id));
let mes = Message::new(tuya_payload, self.set_commandtype(), Some(seq_id));
let replies = self.send(&mes, seq_id)?;
replies
.iter()
Expand All @@ -53,7 +94,7 @@ impl TuyaDevice {
}

pub fn get(&self, tuya_payload: Payload, seq_id: u32) -> Result<Vec<Message>> {
let mes = Message::new(tuya_payload, CommandType::DpQuery, Some(seq_id));
let mes = Message::new(tuya_payload, self.get_commandtype(), Some(seq_id));
let replies = self.send(&mes, seq_id)?;
replies
.iter()
Expand All @@ -62,7 +103,7 @@ impl TuyaDevice {
}

pub fn refresh(&self, tuya_payload: Payload, seq_id: u32) -> Result<Vec<Message>> {
let mes = Message::new(tuya_payload, CommandType::DpRefresh, Some(seq_id));
let mes = Message::new(tuya_payload, self.refresh_commandtype(), Some(seq_id));
let replies = self.send(&mes, seq_id)?;
replies
.iter()
Expand Down

0 comments on commit 274d073

Please sign in to comment.