move HL extraction to wyze client
This commit is contained in:
+15
-48
@@ -176,7 +176,7 @@ func (c *DTLSConn) AVClientStart(timeout time.Duration) error {
|
|||||||
return fmt.Errorf("av login 1 failed: %w", err)
|
return fmt.Errorf("av login 1 failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
if _, err := c.clientConn.Write(pkt2); err != nil {
|
if _, err := c.clientConn.Write(pkt2); err != nil {
|
||||||
return fmt.Errorf("av login 2 failed: %w", err)
|
return fmt.Errorf("av login 2 failed: %w", err)
|
||||||
@@ -239,7 +239,7 @@ func (c *DTLSConn) AVServStart() error {
|
|||||||
|
|
||||||
// Wait for AV Login request from camera
|
// Wait for AV Login request from camera
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||||
n, err := conn.Read(buf)
|
n, err := conn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
go conn.Close()
|
go conn.Close()
|
||||||
@@ -267,6 +267,10 @@ func (c *DTLSConn) AVServStart() error {
|
|||||||
return fmt.Errorf("write av login response: %w", err)
|
return fmt.Errorf("write av login response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.verbose {
|
||||||
|
fmt.Printf("[SERVER] AV Login response sent, waiting for possible resend...\n")
|
||||||
|
}
|
||||||
|
|
||||||
// Camera may resend, respond again
|
// Camera may resend, respond again
|
||||||
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
||||||
if n, _ = conn.Read(buf); n > 0 {
|
if n, _ = conn.Read(buf); n > 0 {
|
||||||
@@ -377,7 +381,7 @@ func (c *DTLSConn) WriteAndWait(req []byte, ok func(res []byte) bool) ([]byte, e
|
|||||||
})
|
})
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
|
|
||||||
_ = c.conn.SetDeadline(time.Now().Add(5000 * time.Millisecond))
|
_ = c.conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||||
defer c.conn.SetDeadline(time.Time{})
|
defer c.conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
buf := make([]byte, 2048)
|
buf := make([]byte, 2048)
|
||||||
@@ -404,7 +408,7 @@ func (c *DTLSConn) WriteAndWait(req []byte, ok func(res []byte) bool) ([]byte, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DTLSConn) WriteAndWaitIOCtrl(cmd uint16, payload []byte, expectCmd uint16, timeout time.Duration) ([]byte, error) {
|
func (c *DTLSConn) WriteAndWaitIOCtrl(payload []byte, match func([]byte) bool, timeout time.Duration) ([]byte, error) {
|
||||||
frame := c.msgIOCtrl(payload)
|
frame := c.msgIOCtrl(payload)
|
||||||
var t *time.Timer
|
var t *time.Timer
|
||||||
t = time.AfterFunc(1, func() {
|
t = time.AfterFunc(1, func() {
|
||||||
@@ -432,19 +436,11 @@ func (c *DTLSConn) WriteAndWaitIOCtrl(cmd uint16, payload []byte, expectCmd uint
|
|||||||
ack := c.msgACK()
|
ack := c.msgACK()
|
||||||
c.clientConn.Write(ack)
|
c.clientConn.Write(ack)
|
||||||
|
|
||||||
if gotCmd, payload, ok := ParseHL(data); ok {
|
if match(data) {
|
||||||
if c.verbose {
|
|
||||||
fmt.Printf("[DTLS RX] Got rawCmd K%d, expecting K%d, payload=%d bytes\n", gotCmd, expectCmd, len(payload))
|
|
||||||
if gotCmd != expectCmd && len(payload) > 0 {
|
|
||||||
fmt.Printf("[DTLS RX] K%d payload:\n%s", gotCmd, hexDump(payload))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if gotCmd == expectCmd {
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
return nil, fmt.Errorf("timeout waiting for K%d", expectCmd)
|
return nil, fmt.Errorf("timeout waiting for response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -507,7 +503,7 @@ func (c *DTLSConn) discovery() error {
|
|||||||
pktCC51 := c.msgDiscoCC51(0, 0, false)
|
pktCC51 := c.msgDiscoCC51(0, 0, false)
|
||||||
|
|
||||||
buf := make([]byte, 2048)
|
buf := make([]byte, 2048)
|
||||||
deadline := time.Now().Add(5000 * time.Millisecond)
|
deadline := time.Now().Add(5 * time.Second)
|
||||||
|
|
||||||
for time.Now().Before(deadline) {
|
for time.Now().Before(deadline) {
|
||||||
c.conn.WriteToUDP(pktIOTC, c.addr)
|
c.conn.WriteToUDP(pktIOTC, c.addr)
|
||||||
@@ -618,50 +614,21 @@ func (c *DTLSConn) worker() {
|
|||||||
case magicAVLoginResp:
|
case magicAVLoginResp:
|
||||||
c.queue(c.rawCmd, data)
|
c.queue(c.rawCmd, data)
|
||||||
|
|
||||||
case magicIOCtrl:
|
case magicIOCtrl, magicChannelMsg:
|
||||||
if hlData := FindHL(data, 32); hlData != nil {
|
c.queue(c.rawCmd, data)
|
||||||
if c.verbose {
|
|
||||||
if cmd, _, ok := ParseHL(hlData); ok {
|
|
||||||
fmt.Printf("[DTLS RX] IOCtrl HL command K%d\n", cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.queue(c.rawCmd, hlData)
|
|
||||||
}
|
|
||||||
|
|
||||||
case magicChannelMsg:
|
|
||||||
if len(data) >= 36 && data[16] == 0x00 {
|
|
||||||
if hlData := FindHL(data, 36); hlData != nil {
|
|
||||||
if c.verbose {
|
|
||||||
if cmd, _, ok := ParseHL(hlData); ok {
|
|
||||||
fmt.Printf("[DTLS RX] ChannelMsg HL command K%d\n", cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.queue(c.rawCmd, hlData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case protoVersion:
|
case protoVersion:
|
||||||
|
// Seq-Tracking
|
||||||
if len(data) >= 8 {
|
if len(data) >= 8 {
|
||||||
// Extract seq number at byte 4-5 (uint16 of uint32 AVSeq)
|
|
||||||
seq := binary.LittleEndian.Uint16(data[4:])
|
seq := binary.LittleEndian.Uint16(data[4:])
|
||||||
if !c.rxSeqInit {
|
if !c.rxSeqInit {
|
||||||
c.rxSeqInit = true
|
c.rxSeqInit = true
|
||||||
}
|
}
|
||||||
// Track highest received sequence
|
|
||||||
if seq > c.rxSeqEnd || c.rxSeqEnd == 0xffff {
|
if seq > c.rxSeqEnd || c.rxSeqEnd == 0xffff {
|
||||||
c.rxSeqEnd = seq
|
c.rxSeqEnd = seq
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for HL command response
|
|
||||||
if hlData := FindHL(data, 32); hlData != nil {
|
|
||||||
if c.verbose {
|
|
||||||
if cmd, _, ok := ParseHL(hlData); ok {
|
|
||||||
fmt.Printf("[DTLS RX] ProtoVersion HL command K%d\n", cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.queue(c.rawCmd, hlData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
c.queue(c.rawCmd, data)
|
||||||
|
|
||||||
case magicACK:
|
case magicACK:
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
|
|||||||
+38
-9
@@ -179,24 +179,24 @@ func (c *Client) SetResolution(quality byte) error {
|
|||||||
// Use K10052 (doorbell format) for certain models
|
// Use K10052 (doorbell format) for certain models
|
||||||
if c.useDoorbellResolution() {
|
if c.useDoorbellResolution() {
|
||||||
k10052 := c.buildK10052(frameSize, bitrate)
|
k10052 := c.buildK10052(frameSize, bitrate)
|
||||||
_, err := c.conn.WriteAndWaitIOCtrl(KCmdSetResolutionDB, k10052, KCmdSetResolutionDBRes, 5*time.Second)
|
_, err := c.conn.WriteAndWaitIOCtrl(k10052, c.matchHL(KCmdSetResolutionDBRes), 5*time.Second)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
k10056 := c.buildK10056(frameSize, bitrate)
|
k10056 := c.buildK10056(frameSize, bitrate)
|
||||||
_, err := c.conn.WriteAndWaitIOCtrl(KCmdSetResolution, k10056, KCmdSetResolutionResp, 5*time.Second)
|
_, err := c.conn.WriteAndWaitIOCtrl(k10056, c.matchHL(KCmdSetResolutionResp), 5*time.Second)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) StartVideo() error {
|
func (c *Client) StartVideo() error {
|
||||||
k10010 := c.buildK10010(MediaTypeVideo, true)
|
k10010 := c.buildK10010(MediaTypeVideo, true)
|
||||||
_, err := c.conn.WriteAndWaitIOCtrl(KCmdControlChannel, k10010, KCmdControlChannelResp, 5*time.Second)
|
_, err := c.conn.WriteAndWaitIOCtrl(k10010, c.matchHL(KCmdControlChannelResp), 5*time.Second)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) StartAudio() error {
|
func (c *Client) StartAudio() error {
|
||||||
k10010 := c.buildK10010(MediaTypeAudio, true)
|
k10010 := c.buildK10010(MediaTypeAudio, true)
|
||||||
_, err := c.conn.WriteAndWaitIOCtrl(KCmdControlChannel, k10010, KCmdControlChannelResp, 5*time.Second)
|
_, err := c.conn.WriteAndWaitIOCtrl(k10010, c.matchHL(KCmdControlChannelResp), 5*time.Second)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,10 +210,14 @@ func (c *Client) StartIntercom() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
k10010 := c.buildK10010(MediaTypeReturnAudio, true)
|
k10010 := c.buildK10010(MediaTypeReturnAudio, true)
|
||||||
if _, err := c.conn.WriteAndWaitIOCtrl(KCmdControlChannel, k10010, KCmdControlChannelResp, 5*time.Second); err != nil {
|
if _, err := c.conn.WriteAndWaitIOCtrl(k10010, c.matchHL(KCmdControlChannelResp), 5*time.Second); err != nil {
|
||||||
return fmt.Errorf("enable return audio: %w", err)
|
return fmt.Errorf("enable return audio: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.verbose {
|
||||||
|
fmt.Printf("[Wyze] Speaker channel enabled, waiting for readiness...\n")
|
||||||
|
}
|
||||||
|
|
||||||
return c.conn.AVServStart()
|
return c.conn.AVServStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,12 +333,13 @@ func (c *Client) doAVLogin() error {
|
|||||||
|
|
||||||
func (c *Client) doKAuth() error {
|
func (c *Client) doKAuth() error {
|
||||||
// Step 1: K10000 -> K10001 (Challenge)
|
// Step 1: K10000 -> K10001 (Challenge)
|
||||||
data, err := c.conn.WriteAndWaitIOCtrl(KCmdAuth, c.buildK10000(), KCmdChallenge, 10*time.Second)
|
data, err := c.conn.WriteAndWaitIOCtrl(c.buildK10000(), c.matchHL(KCmdChallenge), 5*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wyze: K10001 failed: %w", err)
|
return fmt.Errorf("wyze: K10001 failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
challenge, status, err := c.parseK10001(data)
|
hlData := c.extractHL(data)
|
||||||
|
challenge, status, err := c.parseK10001(hlData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wyze: K10001 parse failed: %w", err)
|
return fmt.Errorf("wyze: K10001 parse failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -344,13 +349,14 @@ func (c *Client) doKAuth() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: K10002 -> K10003 (Auth)
|
// Step 2: K10002 -> K10003 (Auth)
|
||||||
data, err = c.conn.WriteAndWaitIOCtrl(KCmdChallengeResp, c.buildK10002(challenge, status), KCmdAuthResult, 10*time.Second)
|
data, err = c.conn.WriteAndWaitIOCtrl(c.buildK10002(challenge, status), c.matchHL(KCmdAuthResult), 5*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wyze: K10002 failed: %w", err)
|
return fmt.Errorf("wyze: K10002 failed: %w", err)
|
||||||
}
|
}
|
||||||
|
hlData = c.extractHL(data)
|
||||||
|
|
||||||
// Parse K10003 response
|
// Parse K10003 response
|
||||||
authResp, err := c.parseK10003(data)
|
authResp, err := c.parseK10003(hlData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wyze: K10003 parse failed: %w", err)
|
return fmt.Errorf("wyze: K10003 parse failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -548,6 +554,29 @@ func (c *Client) isFloodlight() bool {
|
|||||||
return c.model == "HL_CFL2"
|
return c.model == "HL_CFL2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) matchHL(expectCmd uint16) func([]byte) bool {
|
||||||
|
return func(data []byte) bool {
|
||||||
|
hlData := c.extractHL(data)
|
||||||
|
if hlData == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
cmd, _, ok := tutk.ParseHL(hlData)
|
||||||
|
return ok && cmd == expectCmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) extractHL(data []byte) []byte {
|
||||||
|
// Try offset 32 (magicIOCtrl, protoVersion)
|
||||||
|
if hlData := tutk.FindHL(data, 32); hlData != nil {
|
||||||
|
return hlData
|
||||||
|
}
|
||||||
|
// Try offset 36 (magicChannelMsg)
|
||||||
|
if len(data) >= 36 && data[16] == 0x00 {
|
||||||
|
return tutk.FindHL(data, 36)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
statusDefault byte = 1
|
statusDefault byte = 1
|
||||||
statusENR16 byte = 3
|
statusENR16 byte = 3
|
||||||
|
|||||||
Reference in New Issue
Block a user