diff --git a/pkg/tutk/conn_dtls.go b/pkg/tutk/conn_dtls.go index 294990c2..61e716ea 100644 --- a/pkg/tutk/conn_dtls.go +++ b/pkg/tutk/conn_dtls.go @@ -176,7 +176,7 @@ func (c *DTLSConn) AVClientStart(timeout time.Duration) error { 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 { 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 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) if err != nil { go conn.Close() @@ -267,6 +267,10 @@ func (c *DTLSConn) AVServStart() error { 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 conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) 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() - _ = c.conn.SetDeadline(time.Now().Add(5000 * time.Millisecond)) + _ = c.conn.SetDeadline(time.Now().Add(5 * time.Second)) defer c.conn.SetDeadline(time.Time{}) 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) var t *time.Timer t = time.AfterFunc(1, func() { @@ -432,19 +436,11 @@ func (c *DTLSConn) WriteAndWaitIOCtrl(cmd uint16, payload []byte, expectCmd uint ack := c.msgACK() c.clientConn.Write(ack) - if gotCmd, payload, ok := ParseHL(data); ok { - 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 - } + if match(data) { + return data, nil } 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) buf := make([]byte, 2048) - deadline := time.Now().Add(5000 * time.Millisecond) + deadline := time.Now().Add(5 * time.Second) for time.Now().Before(deadline) { c.conn.WriteToUDP(pktIOTC, c.addr) @@ -618,50 +614,21 @@ func (c *DTLSConn) worker() { case magicAVLoginResp: c.queue(c.rawCmd, data) - case magicIOCtrl: - if hlData := FindHL(data, 32); hlData != nil { - 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 magicIOCtrl, magicChannelMsg: + c.queue(c.rawCmd, data) case protoVersion: + // Seq-Tracking if len(data) >= 8 { - // Extract seq number at byte 4-5 (uint16 of uint32 AVSeq) seq := binary.LittleEndian.Uint16(data[4:]) if !c.rxSeqInit { c.rxSeqInit = true } - // Track highest received sequence if seq > c.rxSeqEnd || c.rxSeqEnd == 0xffff { 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: c.mu.RLock() diff --git a/pkg/wyze/client.go b/pkg/wyze/client.go index 6515c49b..46c996e0 100644 --- a/pkg/wyze/client.go +++ b/pkg/wyze/client.go @@ -179,24 +179,24 @@ func (c *Client) SetResolution(quality byte) error { // Use K10052 (doorbell format) for certain models if c.useDoorbellResolution() { 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 } 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 } func (c *Client) StartVideo() error { 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 } func (c *Client) StartAudio() error { 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 } @@ -210,10 +210,14 @@ func (c *Client) StartIntercom() error { } 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) } + if c.verbose { + fmt.Printf("[Wyze] Speaker channel enabled, waiting for readiness...\n") + } + return c.conn.AVServStart() } @@ -329,12 +333,13 @@ func (c *Client) doAVLogin() error { func (c *Client) doKAuth() error { // 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 { 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 { return fmt.Errorf("wyze: K10001 parse failed: %w", err) } @@ -344,13 +349,14 @@ func (c *Client) doKAuth() error { } // 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 { return fmt.Errorf("wyze: K10002 failed: %w", err) } + hlData = c.extractHL(data) // Parse K10003 response - authResp, err := c.parseK10003(data) + authResp, err := c.parseK10003(hlData) if err != nil { return fmt.Errorf("wyze: K10003 parse failed: %w", err) } @@ -548,6 +554,29 @@ func (c *Client) isFloodlight() bool { 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 ( statusDefault byte = 1 statusENR16 byte = 3