Skip to content

Commit

Permalink
Add camera watches
Browse files Browse the repository at this point in the history
  • Loading branch information
barnybug committed Apr 14, 2018
1 parent b2574a7 commit aa28e73
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 29 deletions.
79 changes: 57 additions & 22 deletions config/config.go
Expand Up @@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path"
"regexp"
"strings"
"time"

Expand All @@ -28,6 +29,8 @@ type CameraNodeConf struct {
Url string
User string
Password string
Watch string
Match Regexp
}

type CameraConf struct {
Expand All @@ -37,14 +40,15 @@ type CameraConf struct {
Cameras map[string]CameraNodeConf
}

type CapsConf map[string][]string

type CurrentcostConf struct {
Device string
}

type DeviceConf struct {
Id string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Group string `json:"group"`
Location string `json:"location"`
Caps []string `json:"caps"`
Expand All @@ -57,6 +61,11 @@ func (d DeviceConf) IsSwitchable() bool {
return d.Cap["switch"]
}

func (d DeviceConf) Prefix() string {
ps := strings.SplitN(d.Id, ".", 2)
return ps[0]
}

func (d DeviceConf) SourceId() string {
i := strings.Index(d.Source, ".")
if i != -1 {
Expand Down Expand Up @@ -97,24 +106,6 @@ type GeneralConf struct {
Scripts string
}

type Duration struct {
Duration time.Duration
}

func (self *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
var value string
if err := unmarshal(&value); err != nil {
return err
}

val, err := time.ParseDuration(value)
if err != nil {
return err
}
self.Duration = val
return nil
}

type GraphiteConf struct {
Url string
Tcp string
Expand Down Expand Up @@ -212,7 +203,7 @@ type WeatherConf struct {

type WatchdogConf struct {
Alert string
Devices map[string]string
Devices map[string]Duration
Processes []string
Pings []string
}
Expand All @@ -230,6 +221,7 @@ type Config struct {
Endpoints EndpointsConf
Bill BillConf
Camera CameraConf
Caps CapsConf
Currentcost CurrentcostConf
Datalogger DataloggerConf
Earth EarthConf
Expand All @@ -255,6 +247,44 @@ type Config struct {
Sources map[string]string // source -> device id
}

// Custom types

type Duration struct {
time.Duration
}

func (self *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
var value string
if err := unmarshal(&value); err != nil {
return err
}

val, err := time.ParseDuration(value)
if err != nil {
return err
}
self.Duration = val
return nil
}

type Regexp struct {
*regexp.Regexp
}

func (self *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error {
var expr string
if err := unmarshal(&expr); err != nil {
return err
}

r, err := regexp.Compile(expr)
if err != nil {
return err
}
self.Regexp = r
return nil
}

// Open configuration from disk.
func Open() (*Config, error) {
file, err := os.Open(ConfigPath("gohome.yml"))
Expand Down Expand Up @@ -285,10 +315,15 @@ func OpenRaw(data []byte) (*Config, error) {

for id, device := range config.Devices {
device.Id = id
// prepend 'inherited' caps by type prefix
if cs, ok := config.Caps[device.Prefix()]; ok {
device.Caps = append(cs, device.Caps...)
}
if len(device.Caps) == 0 {
major := strings.Split(id, ".")[0]
device.Caps = []string{major}
// default to having a cap of device prefix
device.Caps = []string{device.Prefix()}
}

device.Cap = map[string]bool{}
for _, c := range device.Caps {
device.Cap[c] = true
Expand Down
28 changes: 28 additions & 0 deletions config/config_test.go
Expand Up @@ -7,6 +7,17 @@ general:
email:
admin:
test@example.com
caps:
light: [switch]
devices:
alarm.house:
name: House alarm
light.kitchen:
name: Kitchen light
light.glowworm:
name: Glowworm
caps: [dimmer]
`

func ExampleOpenRaw() {
Expand All @@ -16,6 +27,23 @@ func ExampleOpenRaw() {
// test@example.com
}

func ExampleCaps() {
config, _ := OpenRaw([]byte(yml))
fmt.Println(config.Devices["alarm.house"].Id)
fmt.Println(config.Devices["alarm.house"].Caps)
fmt.Println(config.Devices["light.kitchen"].Id)
fmt.Println(config.Devices["light.kitchen"].Caps)
fmt.Println(config.Devices["light.glowworm"].Id)
fmt.Println(config.Devices["light.glowworm"].Caps)
// Output:
// alarm.house
// [alarm]
// light.kitchen
// [switch]
// light.glowworm
// [switch dimmer]
}

func ExampleDeviceSourceId() {
fmt.Println(ExampleConfig.Devices["light.kitchen"].SourceId())
fmt.Println(ExampleConfig.Devices["light.glowworm"].SourceId())
Expand Down
61 changes: 61 additions & 0 deletions services/camera/main.go
Expand Up @@ -8,11 +8,15 @@
package camera

import (
"bufio"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path"
"strings"
"time"

"github.com/barnybug/gohome/pubsub"
Expand Down Expand Up @@ -212,6 +216,62 @@ func startWebserver() {
}
}

func notifyActivity(device string, filename string) {
fields := pubsub.Fields{
"device": device,
"filename": filename,
}
ev := pubsub.NewEvent("camera", fields)
services.Publisher.Emit(ev)
}

func watchDirectories() {
args := []string{"-r", "-m", "-e", "create"}
for _, conf := range services.Config.Camera.Cameras {
if conf.Watch != "" {
args = append(args, conf.Watch)
}
}

// start inotifywait
cmd := exec.Command("inotifywait", args...)
stdout, err := cmd.StdoutPipe()
if err == nil {
err = cmd.Start()
}
if err != nil {
log.Fatal("Failed to establish watches:", err)
}
// tail
log.Println("Watch running...")
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
ps := strings.Split(scanner.Text(), " ")
if len(ps) < 3 {
continue
}
filepath := ps[0]
filename := ps[2]
fullpath := path.Join(filepath, filename)
// figure out which device
for device, conf := range services.Config.Camera.Cameras {
if conf.Watch != "" && strings.HasPrefix(filepath, conf.Watch+"/") {
if conf.Match.Regexp == nil || conf.Match.MatchString(fullpath) {
log.Println("Notify: ", fullpath)
notifyActivity(device, fullpath)
}
break
}
}
}

// cleanup
if err := cmd.Wait(); err != nil {
log.Println("Error running watch", err)
}
log.Println("Watch finished")
}

// Service camera
type Service struct {
}
Expand All @@ -228,6 +288,7 @@ func (self *Service) ConfigUpdated(path string) {
// Run the service
func (self *Service) Run() error {
setupCameras()
go watchDirectories()
go startWebserver()

for ev := range services.Subscriber.FilteredChannel("command") {
Expand Down
36 changes: 35 additions & 1 deletion services/camera/main_test.go
@@ -1,9 +1,43 @@
package camera

import "github.com/barnybug/gohome/services"
import (
"testing"

"github.com/barnybug/gohome/config"
"github.com/barnybug/gohome/services"
"github.com/stretchr/testify/assert"
)

func ExampleInterfaces() {
var _ services.Service = (*Service)(nil)
var _ services.ConfigSubscriber = (*Service)(nil)
// Output:
}

func TestBadConfig(t *testing.T) {
yml := `
camera:
cameras:
cam.one:
protocol: x
watch: /a/b
match: +++
`
_, err := config.OpenRaw([]byte(yml))
assert.Error(t, err)
}

func TestGoodConfig(t *testing.T) {
assert := assert.New(t)
yml := `
camera:
cameras:
cam.one:
protocol: x
watch: /a/b
match: /some/
`
c, err := config.OpenRaw([]byte(yml))
assert.NoError(err)
assert.True(c.Camera.Cameras["cam.one"].Match.MatchString("/a/b/some/file.mp4"))
}
6 changes: 1 addition & 5 deletions services/watchdog/main.go
Expand Up @@ -266,16 +266,12 @@ func (self *Service) setup() {

func (self *Service) setupDevices() {
for device, timeout := range services.Config.Watchdog.Devices {
duration, err := time.ParseDuration(timeout)
if err != nil {
fmt.Println("Failed to parse:", timeout)
}
// give devices grace period for first event
d := services.Config.Devices[device]
watches[device] = &Watch{
Id: device,
Name: d.Name,
Timeout: duration,
Timeout: timeout.Duration,
LastEvent: time.Time{},
}
}
Expand Down
18 changes: 17 additions & 1 deletion services/watchdog/main_test.go
@@ -1,8 +1,24 @@
package watchdog

import "github.com/barnybug/gohome/services"
import (
"testing"

"github.com/barnybug/gohome/config"
"github.com/barnybug/gohome/services"
"github.com/stretchr/testify/assert"
)

func ExampleInterfaces() {
var _ services.Service = (*Service)(nil)
// Output:
}

func TestBadConfig(t *testing.T) {
yml := `
watchdog:
devices:
one: xyz
`
_, err := config.OpenRaw([]byte(yml))
assert.Error(t, err)
}

0 comments on commit aa28e73

Please sign in to comment.