From 6b24a38d33e4edf67dbfd84829202c563aff7c47 Mon Sep 17 00:00:00 2001 From: Mitsuru Nakada Date: Mon, 20 May 2024 22:57:16 +0900 Subject: [PATCH 1/4] homekit-server: add pairing API - GET api/homekit/pairing - get the pairing information. - DELETE api/homekit/pairing?[stream='stream' | name='mDNSname' | device_id='deviceID' ] - remove the pairing that has been deleted by Query. --- internal/homekit/api.go | 45 +++++++++++++++++++++++++++++++++++++ internal/homekit/homekit.go | 1 + internal/homekit/server.go | 6 ++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/internal/homekit/api.go b/internal/homekit/api.go index abd8e97c..cb84057f 100644 --- a/internal/homekit/api.go +++ b/internal/homekit/api.go @@ -137,3 +137,48 @@ func findHomeKitURLs() map[string]*url.URL { } return urls } + +type PairingInfo struct { + Name string `json:"name"` + DeviceID string `json:"device_id"` + Pin string `json:"pin"` + Status string `json:"status"` +} + +func getPairingInfo(host string, s *server) PairingInfo { + status := "unpaired" + if len(s.pairings) > 0 { + status = "paired" + } + return PairingInfo { + Name: s.mdns.Name , + DeviceID: s.hap.DeviceID, + Pin: s.hap.Pin, + Status: status, + } +} + +func apiPairingHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + pairingInfo := map[string]PairingInfo{} + for host, s := range servers { + pairingInfo[s.stream] = getPairingInfo(host, s) + } + api.ResponseJSON(w, pairingInfo) + + case "DELETE": + query := r.URL.Query() + name := query.Get("name") + stream := query.Get("stream") + device_id := query.Get("device_id") + for _, s := range servers { + if name == s.mdns.Name || stream == s.stream || device_id == s.hap.DeviceID { + s.pairings = nil + s.UpdateStatus() + s.PatchConfig() + break; + } + } + } +} diff --git a/internal/homekit/homekit.go b/internal/homekit/homekit.go index bfe3e971..ceadcaec 100644 --- a/internal/homekit/homekit.go +++ b/internal/homekit/homekit.go @@ -36,6 +36,7 @@ func Init() { streams.HandleFunc("homekit", streamHandler) api.HandleFunc("api/homekit", apiHandler) + api.HandleFunc("api/homekit/pairing", apiPairingHandler) if cfg.Mod == nil { return diff --git a/internal/homekit/server.go b/internal/homekit/server.go index cb114fea..7aa79f22 100644 --- a/internal/homekit/server.go +++ b/internal/homekit/server.go @@ -214,7 +214,11 @@ func (s *server) DelPair(conn net.Conn, id string) { continue } - s.pairings = append(s.pairings[:i], s.pairings[i+1:]...) + if strings.Contains(pairing, "permissions=1") { + s.pairings = nil + } else { + s.pairings = append(s.pairings[:i], s.pairings[i+1:]...) + } s.UpdateStatus() s.PatchConfig() break From 3b332e40d0a41b37be65a1cdb5fa1bf01d0ed539 Mon Sep 17 00:00:00 2001 From: Mitsuru Nakada Date: Tue, 21 May 2024 06:15:38 +0900 Subject: [PATCH 2/4] Update internal/homekit/api.go Co-authored-by: Sergey Krashevich --- internal/homekit/api.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/homekit/api.go b/internal/homekit/api.go index cb84057f..fd43c0be 100644 --- a/internal/homekit/api.go +++ b/internal/homekit/api.go @@ -139,10 +139,10 @@ func findHomeKitURLs() map[string]*url.URL { } type PairingInfo struct { - Name string `json:"name"` - DeviceID string `json:"device_id"` - Pin string `json:"pin"` - Status string `json:"status"` + Name string `yaml:"name"` + DeviceID string `yaml:"device_id"` + Pin string `yaml:"pin"` + Status string `yaml:"status"` } func getPairingInfo(host string, s *server) PairingInfo { From 88f1f149839cfbc50e494a77490736784d8256e4 Mon Sep 17 00:00:00 2001 From: Mitsuru Nakada Date: Wed, 22 May 2024 22:54:42 +0900 Subject: [PATCH 3/4] I would add a call to discovery() at startup, pairing and unpairing as it waits a long time to be notified of updates. That will cause a MultiCast Query and Response in mDNS to update the homekit information. --- internal/homekit/api.go | 1 + internal/homekit/homekit.go | 1 + internal/homekit/server.go | 2 ++ 3 files changed, 4 insertions(+) diff --git a/internal/homekit/api.go b/internal/homekit/api.go index fd43c0be..79207f3a 100644 --- a/internal/homekit/api.go +++ b/internal/homekit/api.go @@ -180,5 +180,6 @@ func apiPairingHandler(w http.ResponseWriter, r *http.Request) { break; } } + discovery() } } diff --git a/internal/homekit/homekit.go b/internal/homekit/homekit.go index ceadcaec..79065dcd 100644 --- a/internal/homekit/homekit.go +++ b/internal/homekit/homekit.go @@ -129,6 +129,7 @@ func Init() { log.Error().Err(err).Caller().Send() } }() + discovery() } var log zerolog.Logger diff --git a/internal/homekit/server.go b/internal/homekit/server.go index 7aa79f22..fa40f40b 100644 --- a/internal/homekit/server.go +++ b/internal/homekit/server.go @@ -203,6 +203,7 @@ func (s *server) AddPair(conn net.Conn, id string, public []byte, permissions by s.UpdateStatus() s.PatchConfig() } + discovery() } func (s *server) DelPair(conn net.Conn, id string) { @@ -223,6 +224,7 @@ func (s *server) DelPair(conn net.Conn, id string) { s.PatchConfig() break } + discovery() } func (s *server) PatchConfig() { From a4466bcc704aafda1110e7072e95f875f25e06df Mon Sep 17 00:00:00 2001 From: Mitsuru Nakada Date: Sat, 25 May 2024 15:04:19 +0900 Subject: [PATCH 4/4] HomeKit QR-Code Current problem: HomeKit's QR-Code registration requires that the DeviceID and SetupHash information be announced in mDNS. The SetupHash is a string Hash value that combines the SetupID and DeviceID. In the current code, the SetupHash is a Hash value of DeviceID only. For this reason, QR-Code reading does not recognize it as a HomeKit accessory. Suggested fixes: 1. Add setup_id to homekit's option in the config file and add it when SetupHash is calculated. 2. Add API to get string for QR-Code using these values. 3. Add a HomeKit section to the web's links page. --- README.md | 3 ++- internal/homekit/api.go | 8 ++++++++ internal/homekit/homekit.go | 3 +++ pkg/hap/server.go | 3 ++- www/links.html | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0ec1f0d0..04bb9a8c 100644 --- a/README.md +++ b/README.md @@ -970,7 +970,7 @@ HomeKit module can work in two modes: streams: dahua1: rtsp://admin:password@192.168.1.123/cam/realmonitor?channel=1&subtype=0 homekit: - dahua1: # same stream ID from streams list, default PIN - 19550224 + dahua1: # same stream ID from streams list, default PIN - 19550224, default setup_id - HMXS ``` **Full config** @@ -985,6 +985,7 @@ streams: homekit: dahua1: # same stream ID from streams list pin: 12345678 # custom PIN, default: 19550224 + setup_id: ABCD # custom setup ID, default: HMXS name: Dahua camera # custom camera name, default: generated from stream ID device_id: dahua1 # custom ID, default: generated from stream ID device_private: dahua1 # custom key, default: generated from stream ID diff --git a/internal/homekit/api.go b/internal/homekit/api.go index 79207f3a..e853b9ae 100644 --- a/internal/homekit/api.go +++ b/internal/homekit/api.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" "strings" + "strconv" "github.com/AlexxIT/go2rtc/internal/api" "github.com/AlexxIT/go2rtc/internal/app" @@ -143,9 +144,15 @@ type PairingInfo struct { DeviceID string `yaml:"device_id"` Pin string `yaml:"pin"` Status string `yaml:"status"` + SetupURI string `yaml:"setup_uri"` } func getPairingInfo(host string, s *server) PairingInfo { + // for QR-Code + category, _ := strconv.ParseInt(hap.CategoryCamera, 10, 64) + pin, _ := strconv.ParseInt(strings.Replace(s.hap.Pin, "-", "", -1), 10, 64) + payload := "00000000" + strconv.FormatInt(category << 31 + 1 << 28 + pin, 36) + uri := strings.ToUpper("X-HM://" + payload[len(payload)-9:] + s.hap.SetupID[:4]) status := "unpaired" if len(s.pairings) > 0 { status = "paired" @@ -153,6 +160,7 @@ func getPairingInfo(host string, s *server) PairingInfo { return PairingInfo { Name: s.mdns.Name , DeviceID: s.hap.DeviceID, + SetupURI: uri, Pin: s.hap.Pin, Status: status, } diff --git a/internal/homekit/homekit.go b/internal/homekit/homekit.go index 79065dcd..1ac7d857 100644 --- a/internal/homekit/homekit.go +++ b/internal/homekit/homekit.go @@ -26,6 +26,7 @@ func Init() { Name string `yaml:"name"` DeviceID string `yaml:"device_id"` DevicePrivate string `yaml:"device_private"` + SetupID string `yaml:"setup_id"` Pairings []string `yaml:"pairings"` } `yaml:"homekit"` } @@ -63,6 +64,7 @@ func Init() { } deviceID := calcDeviceID(conf.DeviceID, id) // random MAC-address + setupID := (conf.SetupID + "HMXS")[:4] // default setup ID name := calcName(conf.Name, deviceID) srv := &server{ @@ -74,6 +76,7 @@ func Init() { srv.hap = &hap.Server{ Pin: pin, DeviceID: deviceID, + SetupID: setupID, DevicePrivate: calcDevicePrivate(conf.DevicePrivate, id), GetPair: srv.GetPair, AddPair: srv.AddPair, diff --git a/pkg/hap/server.go b/pkg/hap/server.go index 2a912324..a4b61715 100644 --- a/pkg/hap/server.go +++ b/pkg/hap/server.go @@ -23,6 +23,7 @@ type HandlerFunc func(net.Conn) error type Server struct { Pin string DeviceID string + SetupID string DevicePrivate []byte GetPair func(conn net.Conn, id string) []byte @@ -45,7 +46,7 @@ func (s *Server) ServerPublic() []byte { func (s *Server) SetupHash() string { // should be setup_id (random 4 alphanum) + device_id (mac address) // but device_id is random, so OK - b := sha512.Sum512([]byte(s.DeviceID)) + b := sha512.Sum512([]byte(s.SetupID[:4] + s.DeviceID)) return base64.StdEncoding.EncodeToString(b[:4]) } diff --git a/www/links.html b/www/links.html index 940de9fd..96b80997 100644 --- a/www/links.html +++ b/www/links.html @@ -218,5 +218,40 @@

WebRTC Magic

webrtcLinksUpdate(); +
+

HomeKit

+
+
+
+
+ +