mirror of https://bitbucket.org/ausocean/av.git
200 lines
4.9 KiB
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
|
|
}
|