Permalink
Browse files

initial commit. works.

  • Loading branch information...
0 parents commit 8f77845e04e6ab44f2e22be0ff09f781ac1b1cfc @bradfitz committed Dec 17, 2011
Showing with 187 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +27 −0 LICENSE
  3. +5 −0 Makefile
  4. +152 −0 sonden.go
@@ -0,0 +1,3 @@
+*~
+sonden
+*.[68]
27 LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2011 Google, Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,5 @@
+include $(GOROOT)/src/Make.inc
+TARG=sonden
+GOFILES=sonden.go
+include $(GOROOT)/src/Make.cmd
+
152 sonden.go
@@ -0,0 +1,152 @@
+// Copyright 2011 Google Inc.
+// See LICENSE file.
+//
+// Home automation & power saving daemon.
+//
+// Listens to audio input and turns on my Denon receivers when there's
+// music and turns them off to save power when things are silent for
+// awhile.
+//
+// Author: Brad Fitzpatrick <brad@danga.com>
+//
+
+package main
+
+import (
+ "encoding/binary"
+ "flag"
+ "log"
+ "math"
+ "os/exec"
+ "strconv"
+ "strings"
+ "time"
+
+ "go-avr.googlecode.com/git/avr"
+)
+
+// Flags
+var (
+ ampAddrs = flag.String("amps", "", "Comma-separated list of ip:port of Denon amps")
+ idleSec = flag.Int("idlesec", 300, "number of seconds of silence before turning off amps")
+)
+
+const (
+ quietVarianceThreshold = 1000 // typically ~2. occasionally as high as 16.
+ sampleHz = 8 << 10
+ ringSize = sampleHz // 2 seconds of audio
+)
+
+type sampleRing struct {
+ i int
+ size int
+ samples [ringSize]int16
+ sum int
+}
+
+func (r *sampleRing) Add(sample int16) {
+ if r.size == len(r.samples) {
+ r.sum -= int(r.samples[r.i])
+ } else {
+ r.size++
+ }
+ r.sum += int(sample)
+ r.samples[r.i] = sample
+ r.i++
+ if r.i == len(r.samples) {
+ r.i = 0
+ }
+}
+
+func (r *sampleRing) Variance() float64 {
+ mean := float64(r.sum) / float64(r.size)
+ v := 0.0
+ for _, sample := range r.samples {
+ v += math.Pow(math.Abs(float64(mean)-float64(sample)), 2)
+ }
+ v /= float64(r.size)
+ return v
+}
+
+func setAmpState(amp *avr.Amp, state bool) {
+ cmds := []string{"ZMOFF", "PWSTANDBY"}
+ if state {
+ cmds = []string{"ZMON", "PWON"}
+ }
+ for _, cmd := range cmds {
+ log.Printf("Sending command %q", cmd)
+ err := amp.SendCommand(cmd)
+ if err != nil {
+ log.Printf("Sendind command %q failed: %v", cmd, err)
+ return
+ }
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ amps := []*avr.Amp{}
+ for _, addr := range strings.Split(*ampAddrs, ",") {
+ amps = append(amps, avr.New(addr))
+ }
+
+ cmd := exec.Command("rec",
+ "-t", "raw",
+ "--endian", "little",
+ "-r", strconv.Itoa(sampleHz),
+ "-e", "signed",
+ "-b", "16", // 16 bits per sample
+ "-c", "1", // one channel
+ "-")
+ out, _ := cmd.StdoutPipe()
+ err := cmd.Start()
+ if err != nil {
+ log.Fatalf("Error starting rec: %v", err)
+ }
+
+ var (
+ ring sampleRing
+ ampsOn bool
+ lastPlaying time.Time
+ )
+
+ setAmps := func(state bool) {
+ if state {
+ log.Printf("turning amps ON")
+ } else {
+ log.Printf("turning amps OFF")
+ }
+ ampsOn = state
+ for _, amp := range amps {
+ go setAmpState(amp, state)
+ }
+ }
+
+ for {
+ var sample int16
+ err := binary.Read(out, binary.LittleEndian, &sample)
+ if err != nil {
+ log.Fatalf("error reading next sample: %v")
+ }
+ ring.Add(sample)
+ if ring.i != 0 {
+ continue
+ }
+ v := ring.Variance()
+ audioPlaying := v > quietVarianceThreshold
+
+ log.Printf("variance = %v; playing = %v", v, audioPlaying)
+ if audioPlaying {
+ lastPlaying = time.Now()
+ if !ampsOn {
+ setAmps(true)
+ }
+ } else if ampsOn {
+ quietTime := time.Now().Sub(lastPlaying)
+ if quietTime > time.Duration(*idleSec)*time.Second {
+ setAmps(false)
+ }
+ }
+ }
+}

0 comments on commit 8f77845

Please sign in to comment.