mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2026-04-22 23:57:20 +08:00
212 lines
4.7 KiB
Go
212 lines
4.7 KiB
Go
package homekit
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/AlexxIT/go2rtc/internal/api"
|
|
"github.com/AlexxIT/go2rtc/internal/app"
|
|
"github.com/AlexxIT/go2rtc/internal/srtp"
|
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/AlexxIT/go2rtc/pkg/hap"
|
|
"github.com/AlexxIT/go2rtc/pkg/hap/camera"
|
|
"github.com/AlexxIT/go2rtc/pkg/homekit"
|
|
"github.com/AlexxIT/go2rtc/pkg/mdns"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
func Init() {
|
|
var cfg struct {
|
|
Mod map[string]struct {
|
|
Pin string `yaml:"pin"`
|
|
Name string `yaml:"name"`
|
|
DeviceID string `yaml:"device_id"`
|
|
DevicePrivate string `yaml:"device_private"`
|
|
CategoryID string `yaml:"category_id"`
|
|
Pairings []string `yaml:"pairings"`
|
|
} `yaml:"homekit"`
|
|
}
|
|
app.LoadConfig(&cfg)
|
|
|
|
log = app.GetLogger("homekit")
|
|
|
|
streams.HandleFunc("homekit", streamHandler)
|
|
|
|
api.HandleFunc("api/homekit", apiHomekit)
|
|
api.HandleFunc("api/homekit/accessories", apiHomekitAccessories)
|
|
api.HandleFunc("api/discovery/homekit", apiDiscovery)
|
|
|
|
if cfg.Mod == nil {
|
|
return
|
|
}
|
|
|
|
hosts = map[string]*server{}
|
|
servers = map[string]*server{}
|
|
var entries []*mdns.ServiceEntry
|
|
|
|
for id, conf := range cfg.Mod {
|
|
stream := streams.Get(id)
|
|
if stream == nil {
|
|
log.Warn().Msgf("[homekit] missing stream: %s", id)
|
|
continue
|
|
}
|
|
|
|
if conf.Pin == "" {
|
|
conf.Pin = "19550224" // default PIN
|
|
}
|
|
|
|
pin, err := hap.SanitizePin(conf.Pin)
|
|
if err != nil {
|
|
log.Error().Err(err).Caller().Send()
|
|
continue
|
|
}
|
|
|
|
deviceID := calcDeviceID(conf.DeviceID, id) // random MAC-address
|
|
name := calcName(conf.Name, deviceID)
|
|
setupID := calcSetupID(id)
|
|
|
|
srv := &server{
|
|
stream: id,
|
|
pairings: conf.Pairings,
|
|
setupID: setupID,
|
|
}
|
|
|
|
srv.hap = &hap.Server{
|
|
Pin: pin,
|
|
DeviceID: deviceID,
|
|
DevicePrivate: calcDevicePrivate(conf.DevicePrivate, id),
|
|
GetClientPublic: srv.GetPair,
|
|
}
|
|
|
|
srv.mdns = &mdns.ServiceEntry{
|
|
Name: name,
|
|
Port: uint16(api.Port),
|
|
Info: map[string]string{
|
|
hap.TXTConfigNumber: "1",
|
|
hap.TXTFeatureFlags: "0",
|
|
hap.TXTDeviceID: deviceID,
|
|
hap.TXTModel: app.UserAgent,
|
|
hap.TXTProtoVersion: "1.1",
|
|
hap.TXTStateNumber: "1",
|
|
hap.TXTStatusFlags: hap.StatusNotPaired,
|
|
hap.TXTCategory: calcCategoryID(conf.CategoryID),
|
|
hap.TXTSetupHash: hap.SetupHash(setupID, deviceID),
|
|
},
|
|
}
|
|
entries = append(entries, srv.mdns)
|
|
|
|
srv.UpdateStatus()
|
|
|
|
if url := findHomeKitURL(stream.Sources()); url != "" {
|
|
// 1. Act as transparent proxy for HomeKit camera
|
|
srv.proxyURL = url
|
|
} else {
|
|
// 2. Act as basic HomeKit camera
|
|
srv.accessory = camera.NewAccessory("AlexxIT", "go2rtc", name, "-", app.Version)
|
|
}
|
|
|
|
host := srv.mdns.Host(mdns.ServiceHAP)
|
|
hosts[host] = srv
|
|
servers[id] = srv
|
|
|
|
log.Trace().Msgf("[homekit] new server: %s", srv.mdns)
|
|
}
|
|
|
|
api.HandleFunc(hap.PathPairSetup, hapHandler)
|
|
api.HandleFunc(hap.PathPairVerify, hapHandler)
|
|
|
|
go func() {
|
|
if err := mdns.Serve(mdns.ServiceHAP, entries); err != nil {
|
|
log.Error().Err(err).Caller().Send()
|
|
}
|
|
}()
|
|
}
|
|
|
|
var log zerolog.Logger
|
|
var hosts map[string]*server
|
|
var servers map[string]*server
|
|
|
|
func streamHandler(rawURL string) (core.Producer, error) {
|
|
if srtp.Server == nil {
|
|
return nil, errors.New("homekit: can't work without SRTP server")
|
|
}
|
|
|
|
rawURL, rawQuery, _ := strings.Cut(rawURL, "#")
|
|
client, err := homekit.Dial(rawURL, srtp.Server)
|
|
if client != nil && rawQuery != "" {
|
|
query := streams.ParseQuery(rawQuery)
|
|
client.MaxWidth = core.Atoi(query.Get("maxwidth"))
|
|
client.MaxHeight = core.Atoi(query.Get("maxheight"))
|
|
client.Bitrate = parseBitrate(query.Get("bitrate"))
|
|
}
|
|
|
|
return client, err
|
|
}
|
|
|
|
func resolve(host string) *server {
|
|
if len(hosts) == 1 {
|
|
for _, srv := range hosts {
|
|
return srv
|
|
}
|
|
}
|
|
if srv, ok := hosts[host]; ok {
|
|
return srv
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hapHandler(w http.ResponseWriter, r *http.Request) {
|
|
// Can support multiple HomeKit cameras on single port ONLY for Apple devices.
|
|
// Doesn't support Home Assistant and any other open source projects
|
|
// because they don't send the host header in requests.
|
|
srv := resolve(r.Host)
|
|
if srv == nil {
|
|
log.Error().Msg("[homekit] unknown host: " + r.Host)
|
|
return
|
|
}
|
|
srv.Handle(w, r)
|
|
}
|
|
|
|
func findHomeKitURL(sources []string) string {
|
|
if len(sources) == 0 {
|
|
return ""
|
|
}
|
|
|
|
url := sources[0]
|
|
if strings.HasPrefix(url, "homekit") {
|
|
return url
|
|
}
|
|
|
|
if strings.HasPrefix(url, "hass") {
|
|
location, _ := streams.Location(url)
|
|
if strings.HasPrefix(location, "homekit") {
|
|
return location
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func parseBitrate(s string) int {
|
|
n := len(s)
|
|
if n == 0 {
|
|
return 0
|
|
}
|
|
|
|
var k int
|
|
switch n--; s[n] {
|
|
case 'K':
|
|
k = 1024
|
|
s = s[:n]
|
|
case 'M':
|
|
k = 1024 * 1024
|
|
s = s[:n]
|
|
default:
|
|
k = 1
|
|
}
|
|
|
|
return k * core.Atoi(s)
|
|
}
|