mirror of https://bitbucket.org/ausocean/av.git
182 lines
4.4 KiB
Go
182 lines
4.4 KiB
Go
|
// +build test
|
||
|
|
||
|
/*
|
||
|
DESCRIPTION
|
||
|
test.go provides test implementations of the raspistill methods when the
|
||
|
"test" build tag is specified. In this mode, raspistill simply provides
|
||
|
arbitrary loaded JPEG images when Raspistill.Read() is called.
|
||
|
|
||
|
AUTHORS
|
||
|
Saxon Nelson-Milton <saxon@ausocean.org>
|
||
|
|
||
|
LICENSE
|
||
|
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||
|
|
||
|
It is free software: you can redistribute it and/or modify them
|
||
|
under the terms of the GNU General Public License as published by the
|
||
|
Free Software Foundation, either version 3 of the License, or (at your
|
||
|
option) any later version.
|
||
|
|
||
|
It is distributed in the hope that it will be useful, but WITHOUT
|
||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||
|
for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||
|
*/
|
||
|
|
||
|
package raspistill
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"bitbucket.org/ausocean/av/revid/config"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
noOfImages = 6
|
||
|
imgDir = "/go/src/bitbucket.org/ausocean/test/test-data/av/input/jpeg/"
|
||
|
jpgExt = ".jpg"
|
||
|
)
|
||
|
|
||
|
type raspistill struct {
|
||
|
log config.Logger
|
||
|
cfg config.Config
|
||
|
isRunning bool
|
||
|
images [noOfImages][]byte
|
||
|
imgCnt int
|
||
|
durTicker *time.Ticker
|
||
|
intvlTicker *time.Ticker
|
||
|
buf []byte
|
||
|
init bool
|
||
|
term chan struct{}
|
||
|
mu sync.Mutex
|
||
|
}
|
||
|
|
||
|
func new(l config.Logger) raspistill {
|
||
|
l.Debug("creating new test raspistill input")
|
||
|
|
||
|
r := raspistill{log: l}
|
||
|
|
||
|
// Get to the right file location of the JPEG images.
|
||
|
home, err := os.UserHomeDir()
|
||
|
if err != nil {
|
||
|
panic(fmt.Sprintf("could not get home directory: %v", err))
|
||
|
}
|
||
|
|
||
|
// Load the test images into the images slice.
|
||
|
// We expect the 6 images test images to be named 0.jpg through to 5.jpg.
|
||
|
r.log.Debug("loading test JPEG images")
|
||
|
for i, _ := range r.images {
|
||
|
path := imgDir + strconv.Itoa(i) + jpgExt
|
||
|
r.images[i], err = ioutil.ReadFile(home + imgDir + strconv.Itoa(i) + jpgExt)
|
||
|
|
||
|
if err != nil {
|
||
|
r.log.Fatal("error loading test image", "imageNum", i, "error", err)
|
||
|
}
|
||
|
r.log.Debug("image loaded", "path/name", path, "size", len(r.images[i]))
|
||
|
}
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
// stop sets isRunning flag to false, indicating no further captures. Calls on
|
||
|
// Raspistill.read return error.
|
||
|
func (r *Raspistill) stop() error {
|
||
|
r.log.Debug("stopping test raspistill")
|
||
|
r.isRunning = false
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// start creates the timelapse interval and duration tickers i.e. also starting
|
||
|
// them and sets isRunning flag to true indicating that raspistill is capturing.
|
||
|
func (r *Raspistill) start() error {
|
||
|
r.log.Debug("starting test raspistill")
|
||
|
r.durTicker = time.NewTicker(r.cfg.TimelapseDuration)
|
||
|
r.intvlTicker = time.NewTicker(r.cfg.TimelapseInterval)
|
||
|
|
||
|
r.term = make(chan struct{})
|
||
|
|
||
|
r.loadImg()
|
||
|
|
||
|
go r.capture()
|
||
|
|
||
|
r.setRunning(true)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (r *Raspistill) loadImg() {
|
||
|
r.log.Debug("appending new image on to buffer and copying next image p", "imgCnt", r.imgCnt)
|
||
|
imgBytes := r.images[r.imgCnt%noOfImages]
|
||
|
if len(imgBytes) == 0 {
|
||
|
panic("length of image bytes should not be 0")
|
||
|
}
|
||
|
r.imgCnt++
|
||
|
|
||
|
r.mu.Lock()
|
||
|
r.buf = append(r.buf, imgBytes...)
|
||
|
r.log.Debug("added img to buf", "len(imgBytes)", len(imgBytes))
|
||
|
r.mu.Unlock()
|
||
|
}
|
||
|
|
||
|
func (r *Raspistill) capture() {
|
||
|
for {
|
||
|
select {
|
||
|
case t := <-r.intvlTicker.C:
|
||
|
r.log.Debug("got interval tick", "tick", t)
|
||
|
r.loadImg()
|
||
|
r.intvlTicker.Reset(r.cfg.TimelapseInterval)
|
||
|
|
||
|
case <-r.term:
|
||
|
r.setRunning(false)
|
||
|
return
|
||
|
|
||
|
case t := <-r.durTicker.C:
|
||
|
r.log.Debug("got duration tick, timelapse over", "tick", t)
|
||
|
r.buf = nil
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// read blocks until either another timelapse interval has completed, in which
|
||
|
// case we provide the next jpeg to p, or, the timelapse duration has completed
|
||
|
// in which case we don't read and provide io.EOF error.
|
||
|
func (r *Raspistill) read(p []byte) (int, error) {
|
||
|
r.log.Debug("reading from test raspistill")
|
||
|
if !r.running() {
|
||
|
return 0, errNotStarted
|
||
|
}
|
||
|
|
||
|
r.mu.Lock()
|
||
|
defer r.mu.Unlock()
|
||
|
if r.buf == nil {
|
||
|
return 0, io.EOF
|
||
|
}
|
||
|
n := copy(p, r.buf)
|
||
|
r.log.Debug("copied", "p[:2]", p[:2], "p[n-2:n]", p[n-2:n])
|
||
|
r.buf = r.buf[n:]
|
||
|
|
||
|
return n, nil
|
||
|
}
|
||
|
|
||
|
func (r *Raspistill) setRunning(s bool) {
|
||
|
r.mu.Lock()
|
||
|
r.isRunning = s
|
||
|
r.mu.Unlock()
|
||
|
}
|
||
|
|
||
|
func (r *Raspistill) running() bool {
|
||
|
r.mu.Lock()
|
||
|
ir := r.isRunning
|
||
|
r.mu.Unlock()
|
||
|
return ir
|
||
|
}
|