mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2026-04-22 23:57:20 +08:00
8a21809f18
- implement ONVIF motion watcher to handle motion events - add configuration options for motion hold time and ONVIF URL - remap motion mode from "onvif" to "api" for compatibility - log ONVIF motion watcher activity for better debugging feat(onvif): implement event subscription for motion detection - create PullPoint subscription to receive motion events - implement methods for pulling messages and renewing subscriptions - handle event requests and responses specific to motion detection test(onvif): add unit tests for motion event parsing and subscription - create tests for parsing various motion event XML responses - verify correct handling of multiple notifications and edge cases - test resolving event addresses for ONVIF clients fix(hksv): improve motion detection logging - log warnings when accessory or character not found during motion detection - log number of listeners notified during motion state changes feat(hap): add listener count method - introduce method to retrieve the number of listeners for a character feat(onvif): enhance ONVIF client with event URL handling - extract event URL from ONVIF device response for subscription management
154 lines
3.0 KiB
Go
154 lines
3.0 KiB
Go
package hap
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/AlexxIT/go2rtc/pkg/hap/tlv8"
|
|
)
|
|
|
|
// Character - Aqara props order
|
|
// Value should be omit for PW
|
|
// Value may be empty for PR
|
|
type Character struct {
|
|
Desc string `json:"description,omitempty"`
|
|
|
|
IID uint64 `json:"iid"`
|
|
Type string `json:"type"`
|
|
Format string `json:"format"`
|
|
Value any `json:"value,omitempty"`
|
|
Perms []string `json:"perms"`
|
|
|
|
//MaxLen int `json:"maxLen,omitempty"`
|
|
//Unit string `json:"unit,omitempty"`
|
|
//MinValue any `json:"minValue,omitempty"`
|
|
//MaxValue any `json:"maxValue,omitempty"`
|
|
//MinStep any `json:"minStep,omitempty"`
|
|
//ValidVal []any `json:"valid-values,omitempty"`
|
|
|
|
listeners map[io.Writer]bool
|
|
}
|
|
|
|
func (c *Character) AddListener(w io.Writer) {
|
|
// TODO: sync.Mutex
|
|
if c.listeners == nil {
|
|
c.listeners = map[io.Writer]bool{}
|
|
}
|
|
c.listeners[w] = true
|
|
}
|
|
|
|
func (c *Character) RemoveListener(w io.Writer) {
|
|
delete(c.listeners, w)
|
|
|
|
if len(c.listeners) == 0 {
|
|
c.listeners = nil
|
|
}
|
|
}
|
|
|
|
func (c *Character) ListenerCount() int {
|
|
return len(c.listeners)
|
|
}
|
|
|
|
func (c *Character) NotifyListeners(ignore io.Writer) error {
|
|
if c.listeners == nil {
|
|
return nil
|
|
}
|
|
|
|
data, err := c.GenerateEvent()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for w := range c.listeners {
|
|
if w == ignore {
|
|
continue
|
|
}
|
|
if _, err = w.Write(data); err != nil {
|
|
// error not a problem - just remove listener
|
|
c.RemoveListener(w)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenerateEvent with raw HTTP headers
|
|
func (c *Character) GenerateEvent() (data []byte, err error) {
|
|
v := JSONCharacters{
|
|
Value: []JSONCharacter{
|
|
{AID: DeviceAID, IID: c.IID, Value: c.Value},
|
|
},
|
|
}
|
|
if data, err = json.Marshal(v); err != nil {
|
|
return
|
|
}
|
|
|
|
res := http.Response{
|
|
StatusCode: http.StatusOK,
|
|
ProtoMajor: 1,
|
|
ProtoMinor: 0,
|
|
Header: http.Header{"Content-Type": []string{MimeJSON}},
|
|
ContentLength: int64(len(data)),
|
|
Body: io.NopCloser(bytes.NewReader(data)),
|
|
}
|
|
|
|
buf := bytes.NewBuffer([]byte{0})
|
|
if err = res.Write(buf); err != nil {
|
|
return
|
|
}
|
|
copy(buf.Bytes(), "EVENT")
|
|
|
|
return buf.Bytes(), err
|
|
}
|
|
|
|
// Set new value and NotifyListeners
|
|
func (c *Character) Set(v any) (err error) {
|
|
if err = c.Write(v); err != nil {
|
|
return
|
|
}
|
|
return c.NotifyListeners(nil)
|
|
}
|
|
|
|
// Write new value with right format
|
|
func (c *Character) Write(v any) (err error) {
|
|
switch c.Format {
|
|
case "tlv8":
|
|
c.Value, err = tlv8.MarshalBase64(v)
|
|
|
|
case "bool":
|
|
switch v := v.(type) {
|
|
case bool:
|
|
c.Value = v
|
|
case float64:
|
|
c.Value = v != 0
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// ReadTLV8 value to right struct
|
|
func (c *Character) ReadTLV8(v any) (err error) {
|
|
if s, ok := c.Value.(string); ok {
|
|
return tlv8.UnmarshalBase64(s, v)
|
|
}
|
|
return fmt.Errorf("hap: can't read value: %v", v)
|
|
}
|
|
|
|
func (c *Character) ReadBool() (bool, error) {
|
|
if v, ok := c.Value.(bool); ok {
|
|
return v, nil
|
|
}
|
|
return false, fmt.Errorf("hap: can't read value: %v", c.Value)
|
|
}
|
|
|
|
func (c *Character) String() string {
|
|
data, err := json.Marshal(c)
|
|
if err != nil {
|
|
return "ERROR"
|
|
}
|
|
return string(data)
|
|
}
|