av/device/raspistill/imp_testing.go

200 lines
4.9 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
specific test 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 (
"io"
"io/ioutil"
"os"
"strconv"
"sync"
"time"
"bitbucket.org/ausocean/av/revid/config"
)
const (
// TODO(Saxon): find nImages programmatically ?
nImages = 6
relImgPath = "/bitbucket.org/ausocean/test/test-data/av/input/jpeg/"
jpgExt = ".jpg"
gopathEnvName = "GOPATH"
readWaitDelay = 1 * time.Second
)
type raspistill struct {
images [nImages][]byte
imgCnt int // Number of images that have been loaded thus far.
durTicker *time.Ticker // Tracks timelapse duration.
intvlTicker *time.Ticker // Tracks current interval in the timelapse.
log config.Logger
cfg config.Config
isRunning bool
buf []byte // Holds frame data to be read.
term chan struct{} // Signals termination when close() is called.
mu sync.Mutex
}
func new(l config.Logger) raspistill {
l.Debug("creating new test raspistill input")
r := raspistill{log: l}
// 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 {
imgDir := os.Getenv(gopathEnvName) + relImgPath
path := imgDir + strconv.Itoa(i) + jpgExt
var err error
r.images[i], err = ioutil.ReadFile(path)
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. Future
// calls on Raspistill.read will return an error.
func (r *Raspistill) stop() error {
r.log.Debug("stopping test raspistill")
if r.running() {
r.setRunning(false)
close(r.term)
}
return nil
}
// start creates and starts the timelapse and duration tickers and sets
// isRunning flag to true indicating that raspistill is capturing.
func (r *Raspistill) start() error {
r.log.Debug("starting test implementation raspistill", "duration", r.cfg.TimelapseDuration.String(), "interval", r.cfg.TimelapseInterval.String())
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", "nImg", r.imgCnt)
imgBytes := r.images[r.imgCnt%nImages]
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 image to buf", "nBytes", 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.log.Debug("got termination signal")
r.setRunning(false)
return
case t := <-r.durTicker.C:
r.log.Debug("got duration tick, timelapse over", "tick", t)
close(r.term)
}
}
}
// 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, io.EOF
}
// Waits until there's something in the buffer for reading, unless there's a
// termination signal, in which case return io.EOF.
for {
select {
case <-r.term:
return 0, io.EOF
default:
}
if r.bufLen() != 0 {
break
}
time.Sleep(readWaitDelay)
}
r.mu.Lock()
defer r.mu.Unlock()
n := copy(p, r.buf)
r.buf = r.buf[n:]
return n, nil
}
func (r *Raspistill) bufLen() int {
r.mu.Lock()
l := len(r.buf)
r.mu.Unlock()
return l
}
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
}