Skip to content

Commit

Permalink
Merge branch 'ip-camera'
Browse files Browse the repository at this point in the history
  • Loading branch information
brutella committed Apr 29, 2019
2 parents dde85b5 + 819bad6 commit 18e72be
Show file tree
Hide file tree
Showing 32 changed files with 1,571 additions and 59 deletions.
10 changes: 5 additions & 5 deletions _example/client.go
Expand Up @@ -5,8 +5,8 @@ import (
"github.com/brutella/hc/db"
"github.com/brutella/hc/hap"
"github.com/brutella/hc/hap/pair"
"github.com/brutella/hc/log"
"io"
"log"
"net/http"
)

Expand Down Expand Up @@ -69,10 +69,10 @@ func main() {
}

if request != nil {
log.Println(request)
log.Info.Println(request)
}

log.Println("*** Pairing done ***")
log.Info.Println("*** Pairing done ***")

verify := pair.NewVerifyClientController(c, database)

Expand Down Expand Up @@ -102,8 +102,8 @@ func main() {
}

if last_request != nil {
log.Println(last_request)
log.Info.Println(last_request)
}

log.Println("*** Key Verification done ***")
log.Info.Println("*** Key Verification done ***")
}
37 changes: 24 additions & 13 deletions accessory/accessory.go
Expand Up @@ -5,16 +5,16 @@ import (
)

type Info struct {
Name string
SerialNumber string
Manufacturer string
Model string
Name string
SerialNumber string
Manufacturer string
Model string
FirmwareRevision string
}

// Accessory implements the model.Accessory interface and contains the data
// structures to communicate with HomeKit.
// Accessory is a HomeKit accessory.
//
// An accessory in consists of services, which consists of characteristics.
// An accessory contains services, which themselves contain characteristics.
// Every accessory has the "accessory info" service by default which consists
// of characteristics to identify the accessory: name, model, manufacturer,...
type Accessory struct {
Expand Down Expand Up @@ -56,6 +56,12 @@ func New(info Info, typ AccessoryType) *Accessory {
svc.Model.SetValue("undefined")
}

if version := info.FirmwareRevision; len(version) > 0 {
svc.FirmwareRevision.SetValue(version)
} else {
svc.FirmwareRevision.SetValue("undefined")
}

acc := &Accessory{
idCount: 1,
Info: svc,
Expand Down Expand Up @@ -99,15 +105,20 @@ func (a *Accessory) Identify() {

// Adds a service to the accessory and updates the ids of the service and the corresponding characteristics
func (a *Accessory) AddService(s *service.Service) {
s.SetID(a.idCount)
a.idCount++
a.Services = append(a.Services, s)
}

for _, c := range s.Characteristics {
c.SetID(a.idCount)
// UpdateIDs updates the service and characteirstic ids.
func (a *Accessory) UpdateIDs() {
for _, s := range a.Services {
s.SetID(a.idCount)
a.idCount++
}

a.Services = append(a.Services, s)
for _, c := range s.Characteristics {
c.SetID(a.idCount)
a.idCount++
}
}
}

// Equal returns true when receiver has the same services and id as the argument.
Expand Down
29 changes: 29 additions & 0 deletions accessory/camera.go
@@ -0,0 +1,29 @@
package accessory

import (
"github.com/brutella/hc/service"
)

// Camera provides RTP video streaming.
type Camera struct {
*Accessory
Control *service.CameraControl
StreamManagement1 *service.CameraRTPStreamManagement
StreamManagement2 *service.CameraRTPStreamManagement
}

// NewCamera returns an IP camera accessory.
func NewCamera(info Info) *Camera {
acc := Camera{}
acc.Accessory = New(info, TypeIPCamera)
acc.Control = service.NewCameraControl()
acc.AddService(acc.Control.Service)

// TODO (mah) a camera must support at least 2 rtp streams
acc.StreamManagement1 = service.NewCameraRTPStreamManagement()
acc.StreamManagement2 = service.NewCameraRTPStreamManagement()
acc.AddService(acc.StreamManagement1.Service)
// acc.AddService(acc.StreamManagement2.Service)

return &acc
}
1 change: 1 addition & 0 deletions accessory/container.go
Expand Up @@ -24,6 +24,7 @@ func NewContainer() *Container {
// AddAccessory adds an accessory to the container.
// This method ensures that the accessory ids are valid and unique withing the container.
func (m *Container) AddAccessory(a *Accessory) {
a.UpdateIDs()
a.SetID(m.idCount)
m.idCount++
m.Accessories = append(m.Accessories, a)
Expand Down
3 changes: 1 addition & 2 deletions characteristic/bytes.go
Expand Up @@ -29,10 +29,9 @@ func (bs *Bytes) GetValue() []byte {
}
}

// OnValueRemoteUpdate calls fn when the value was updated by a client.
func (bs *Bytes) OnValueRemoteUpdate(fn func([]byte)) {
bs.OnValueUpdateFromConn(func(conn net.Conn, c *Characteristic, new, old interface{}) {
fn(new.([]byte))
fn(bs.GetValue())
})
}

Expand Down
6 changes: 3 additions & 3 deletions config.go
Expand Up @@ -70,15 +70,15 @@ func (cfg Config) txtRecords() map[string]string {

// loads load the id, version and config hash
func (cfg *Config) load(storage util.Storage) {
if b, err := storage.Get("uuid"); err == nil {
if b, err := storage.Get("uuid"); err == nil && len(b) > 0 {
cfg.id = string(b)
}

if b, err := storage.Get("version"); err == nil {
if b, err := storage.Get("version"); err == nil && len(b) > 0 {
cfg.version = to.Int64(string(b))
}

if b, err := storage.Get("configHash"); err == nil {
if b, err := storage.Get("configHash"); err == nil && len(b) > 0 {
cfg.configHash = b
}
}
Expand Down
3 changes: 0 additions & 3 deletions crypto/secure_session.go
Expand Up @@ -10,9 +10,6 @@ import (
)

// secureSession provide a secure session by encrypting and decrypting data
//
// TODO(brutella) Why is the data encoded in little endian when we are sending
// it over the wire and it should be network byte order (big endian)?
type secureSession struct {
encryptKey [32]byte
decryptKey [32]byte
Expand Down
2 changes: 1 addition & 1 deletion hap/endpoint/characteristics.go
Expand Up @@ -39,8 +39,8 @@ func (handler *Characteristics) ServeHTTP(response http.ResponseWriter, request
handler.mutex.Lock()
switch request.Method {
case hap.MethodGET:
log.Debug.Printf("%v GET /characteristics", request.RemoteAddr)
request.ParseForm()
log.Debug.Printf("%v GET /characteristics %v", request.RemoteAddr, request.Form)
session := handler.context.GetSessionForRequest(request)
conn := session.Connection()
res, err = handler.controller.HandleGetCharacteristics(request.Form, conn)
Expand Down
95 changes: 95 additions & 0 deletions hap/endpoint/resource.go
@@ -0,0 +1,95 @@
package endpoint

import (
"bytes"
"encoding/json"
"fmt"
"github.com/brutella/hc/hap"
"github.com/brutella/hc/log"
"image"
"image/jpeg"
"io/ioutil"
"net/http"
)

type GetImageFunc func(width, height uint) (*image.Image, error)

// Resource handles the /resource endpoint
type Resource struct {
http.Handler
imgFn GetImageFunc
context hap.Context
}

// NewResource returns a new handler for resource requests
func NewResource(context hap.Context, imgFn GetImageFunc) *Resource {
r := Resource{
context: context,
imgFn: imgFn,
}

return &r
}

type ImageRequest struct {
Type string `json:"resource-type"`
Width uint `json:"image-width"`
Height uint `json:"image-height"`
}

func (handler *Resource) ServeHTTP(response http.ResponseWriter, request *http.Request) {
switch request.Method {
case hap.MethodPOST:
log.Debug.Printf("%v POST /resource\n", request.RemoteAddr)
if err := handler.postResource(response, request); err != nil {
log.Info.Println(err)
response.WriteHeader(http.StatusInternalServerError)
}
default:
log.Debug.Println("Cannot handle HTTP method", request.Method)
response.WriteHeader(http.StatusNoContent)
}
}

func (r *Resource) postResource(resp http.ResponseWriter, req *http.Request) error {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return err
}

var imgRq ImageRequest
err = json.Unmarshal(body, &imgRq)
if err != nil {
return err
}

if imgRq.Type == "image" {
b, err := r.getJPEGImage(imgRq)
if err != nil {
return err
}

resp.Header().Set("Content-Type", "image/jpeg")
wr := hap.NewChunkedWriter(resp, 2048)
wr.Write(b)
return nil
}

resp.WriteHeader(http.StatusNoContent)

return nil
}

func (r *Resource) getJPEGImage(req ImageRequest) ([]byte, error) {
img, err := r.imgFn(req.Width, req.Height)
if err != nil {
return nil, fmt.Errorf("r.imgFn() %v", err)
}

buf := new(bytes.Buffer)
if err := jpeg.Encode(buf, *img, nil); err != nil {
return nil, fmt.Errorf("jpeg.Encode() %v", err)
}

return buf.Bytes(), nil
}
43 changes: 17 additions & 26 deletions hap/http/server.go
Expand Up @@ -16,15 +16,6 @@ import (
"sync"
)

// Server provides a similar interfaces as http.Server to start and stop a TCP server.
type Server interface {
// ListenAndServe start the server
ListenAndServe(context.Context) error

// Port returns the port on which the server listens to
Port() string
}

type Config struct {
Port string
Context hap.Context
Expand All @@ -35,11 +26,11 @@ type Config struct {
Emitter event.Emitter
}

type server struct {
type Server struct {
context hap.Context
database db.Database
device hap.SecuredDevice
mux *http.ServeMux
Mux *http.ServeMux

mutex *sync.Mutex
container *accessory.Container
Expand All @@ -52,7 +43,7 @@ type server struct {
}

// NewServer returns a server
func NewServer(c Config) *server {
func NewServer(c Config) *Server {

// os gives us a free Port when Port is ""
ln, err := net.Listen("tcp", c.Port)
Expand All @@ -62,12 +53,12 @@ func NewServer(c Config) *server {

_, port, _ := net.SplitHostPort(ln.Addr().String())

s := server{
s := Server{
context: c.Context,
database: c.Database,
container: c.Container,
device: c.Device,
mux: http.NewServeMux(),
Mux: http.NewServeMux(),
mutex: c.Mutex,
listener: ln.(*net.TCPListener),
port: port,
Expand All @@ -79,7 +70,7 @@ func NewServer(c Config) *server {
return &s
}

func (s *server) ListenAndServe(ctx context.Context) error {
func (s *Server) ListenAndServe(ctx context.Context) error {
go func() {
<-ctx.Done()
for _, c := range s.context.ActiveConnections() {
Expand All @@ -88,36 +79,36 @@ func (s *server) ListenAndServe(ctx context.Context) error {
// Stop listener
s.hapListener.Close()
}()
return s.listenAndServe(s.addrString(), s.mux, s.context)
return s.listenAndServe(s.addrString(), s.Mux, s.context)
}

func (s *server) Port() string {
func (s *Server) Port() string {
return s.port
}

// listenAndServe returns a http.Server to listen on a specific address
func (s *server) listenAndServe(addr string, handler http.Handler, context hap.Context) error {
func (s *Server) listenAndServe(addr string, handler http.Handler, context hap.Context) error {
server := http.Server{Addr: addr, Handler: handler}
// Use a TCPListener
listener := hap.NewTCPListener(s.listener, context)
s.hapListener = listener
return server.Serve(listener)
}

func (s *server) addrString() string {
func (s *Server) addrString() string {
return ":" + s.port
}

// setupEndpoints creates controller objects to handle HAP endpoints
func (s *server) setupEndpoints() {
func (s *Server) setupEndpoints() {
containerController := controller.NewContainerController(s.container)
characteristicsController := controller.NewCharacteristicController(s.container)
pairingController := pair.NewPairingController(s.database)

s.mux.Handle("/pair-setup", endpoint.NewPairSetup(s.context, s.device, s.database, s.emitter))
s.mux.Handle("/pair-verify", endpoint.NewPairVerify(s.context, s.database))
s.mux.Handle("/accessories", endpoint.NewAccessories(containerController, s.mutex))
s.mux.Handle("/characteristics", endpoint.NewCharacteristics(s.context, characteristicsController, s.mutex))
s.mux.Handle("/pairings", endpoint.NewPairing(pairingController, s.emitter))
s.mux.Handle("/identify", endpoint.NewIdentify(containerController))
s.Mux.Handle("/pair-setup", endpoint.NewPairSetup(s.context, s.device, s.database, s.emitter))
s.Mux.Handle("/pair-verify", endpoint.NewPairVerify(s.context, s.database))
s.Mux.Handle("/accessories", endpoint.NewAccessories(containerController, s.mutex))
s.Mux.Handle("/characteristics", endpoint.NewCharacteristics(s.context, characteristicsController, s.mutex))
s.Mux.Handle("/pairings", endpoint.NewPairing(pairingController, s.emitter))
s.Mux.Handle("/identify", endpoint.NewIdentify(containerController))
}

0 comments on commit 18e72be

Please sign in to comment.