mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2026-04-22 15:47:06 +08:00
Add HomeKit QR code to WebUI #1138 by @mnakada
This commit is contained in:
@@ -65,10 +65,12 @@ func Init() {
|
||||
|
||||
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{
|
||||
@@ -90,7 +92,7 @@ func Init() {
|
||||
hap.TXTStateNumber: "1",
|
||||
hap.TXTStatusFlags: hap.StatusNotPaired,
|
||||
hap.TXTCategory: calcCategoryID(conf.CategoryID),
|
||||
hap.TXTSetupHash: srv.hap.SetupHash(),
|
||||
hap.TXTSetupHash: hap.SetupHash(setupID, deviceID),
|
||||
},
|
||||
}
|
||||
entries = append(entries, srv.mdns)
|
||||
|
||||
@@ -40,20 +40,29 @@ type server struct {
|
||||
accessory *hap.Accessory // HAP accessory
|
||||
consumer *homekit.Consumer
|
||||
proxyURL string
|
||||
setupID string
|
||||
stream string // stream name from YAML
|
||||
}
|
||||
|
||||
func (s *server) MarshalJSON() ([]byte, error) {
|
||||
v := struct {
|
||||
Name string `json:"name"`
|
||||
DeviceID string `json:"device_id"`
|
||||
Paired int `json:"paired"`
|
||||
Conns []any `json:"connections"`
|
||||
Name string `json:"name"`
|
||||
DeviceID string `json:"device_id"`
|
||||
Paired int `json:"paired,omitempty"`
|
||||
CategoryID string `json:"category_id,omitempty"`
|
||||
SetupCode string `json:"setup_code,omitempty"`
|
||||
SetupID string `json:"setup_id,omitempty"`
|
||||
Conns []any `json:"connections,omitempty"`
|
||||
}{
|
||||
Name: s.mdns.Name,
|
||||
DeviceID: s.mdns.Info[hap.TXTDeviceID],
|
||||
Paired: len(s.pairings),
|
||||
Conns: s.conns,
|
||||
Name: s.mdns.Name,
|
||||
DeviceID: s.mdns.Info[hap.TXTDeviceID],
|
||||
CategoryID: s.mdns.Info[hap.TXTCategory],
|
||||
Paired: len(s.pairings),
|
||||
Conns: s.conns,
|
||||
}
|
||||
if v.Paired == 0 {
|
||||
v.SetupCode = s.hap.Pin
|
||||
v.SetupID = s.setupID
|
||||
}
|
||||
return json.Marshal(v)
|
||||
}
|
||||
@@ -377,6 +386,11 @@ func calcDevicePrivate(private, seed string) []byte {
|
||||
return ed25519.NewKeyFromSeed(b[:ed25519.SeedSize])
|
||||
}
|
||||
|
||||
func calcSetupID(seed string) string {
|
||||
b := sha512.Sum512([]byte(seed))
|
||||
return fmt.Sprintf("%02X%02X", b[44], b[46])
|
||||
}
|
||||
|
||||
func calcCategoryID(categoryID string) string {
|
||||
switch categoryID {
|
||||
case "bridge":
|
||||
|
||||
@@ -3,6 +3,8 @@ package hap
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -99,6 +101,12 @@ func GenerateUUID() string {
|
||||
return s[:8] + "-" + s[8:12] + "-" + s[12:16] + "-" + s[16:20] + "-" + s[20:]
|
||||
}
|
||||
|
||||
func SetupHash(setupID, deviceID string) string {
|
||||
// should be setup_id (random 4 alphanum) + device_id (mac address)
|
||||
b := sha512.Sum512([]byte(setupID + deviceID))
|
||||
return base64.StdEncoding.EncodeToString(b[:4])
|
||||
}
|
||||
|
||||
func Append(items ...any) (b []byte) {
|
||||
for _, item := range items {
|
||||
switch v := item.(type) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package hap
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -36,13 +35,6 @@ func (s *Server) ServerPublic() []byte {
|
||||
// return StatusPaired
|
||||
//}
|
||||
|
||||
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))
|
||||
return base64.StdEncoding.EncodeToString(b[:4])
|
||||
}
|
||||
|
||||
func (s *Server) PairSetup(req *http.Request, rw *bufio.ReadWriter) (id string, publicKey []byte, err error) {
|
||||
// STEP 1. Request from iPhone
|
||||
var plainM1 struct {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagNFC = 1
|
||||
FlagIP = 2
|
||||
FlagBLE = 4
|
||||
FlagWAC = 8 // Wireless Accessory Configuration (WAC)/Apples MFi
|
||||
)
|
||||
|
||||
func GenerateSetupURI(category, pin, setupID string) string {
|
||||
c, _ := strconv.Atoi(category)
|
||||
p, _ := strconv.Atoi(strings.ReplaceAll(pin, "-", ""))
|
||||
payload := int64(c&0xFF)<<31 | int64(FlagIP&0xF)<<27 | int64(p&0x7FFFFFF)
|
||||
return "X-HM://" + FormatInt36(payload, 9) + setupID
|
||||
}
|
||||
|
||||
// FormatInt36 equal to strings.ToUpper(fmt.Sprintf("%0"+strconv.Itoa(n)+"s", strconv.FormatInt(value, 36)))
|
||||
func FormatInt36(value int64, n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := n - 1; 0 <= i; i-- {
|
||||
b[i] = digits[value%36]
|
||||
value /= 36
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
const digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
@@ -0,0 +1,18 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFormatAlphaNum(t *testing.T) {
|
||||
value := int64(999)
|
||||
n := 5
|
||||
s1 := strings.ToUpper(fmt.Sprintf("%0"+strconv.Itoa(n)+"s", strconv.FormatInt(value, 36)))
|
||||
s2 := FormatInt36(value, n)
|
||||
require.Equal(t, s1, s2)
|
||||
}
|
||||
@@ -71,6 +71,42 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="homekit" style="display: none">
|
||||
<h2>HomeKit server</h2>
|
||||
</div>
|
||||
<script>
|
||||
fetch(`api/homekit?id=${src}`, {cache: 'no-cache'}).then(async (r) => {
|
||||
if (!r.ok) return;
|
||||
|
||||
const div = document.querySelector('#homekit');
|
||||
div.innerHTML += `<div><a href="${r.url}">info.json</a> page with active connections</div>`;
|
||||
div.style = '';
|
||||
|
||||
/** @type {{name: string, category_id: string, setup_code: string, setup_id: string, setup_uri: string}} */
|
||||
const data = await r.json();
|
||||
if (data.setup_code === undefined) return;
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js';
|
||||
script.async = true;
|
||||
script.onload = () => {
|
||||
/* global BigInt */
|
||||
const categoryID = BigInt(data.category_id);
|
||||
const pin = BigInt(data.setup_code.replaceAll('-', ''));
|
||||
const payload = categoryID << BigInt(31) | BigInt(2 << 27) | pin;
|
||||
const setupURI = `X-HM://${payload.toString(36).toUpperCase().padStart(9, '0')}${data.setup_id}`;
|
||||
|
||||
div.innerHTML += `<pre>Setup Name: ${data.name}
|
||||
Setup Code: ${data.setup_code}</pre>
|
||||
<div id="homekit-qrcode"></div>`;
|
||||
|
||||
/* global QRCode */
|
||||
new QRCode('homekit-qrcode', {text: setupURI, width: 128, height: 128});
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h2>Play audio</h2>
|
||||
<label><input type="radio" name="play" value="file" checked>
|
||||
|
||||
Reference in New Issue
Block a user