av/device/raspistill/imp_release.go

139 lines
3.2 KiB
Go

// +build !test
/*
DESCRIPTION
release.go provides implementations for the Raspistill struct for release
conditions, i.e. we're running on a raspberry pi with access to the actual
raspistill utility with a pi camera connected. The code here runs a raspistill
background process and reads captured images from the camera.
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 (
"bufio"
"errors"
"fmt"
"io"
"os/exec"
"strings"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/utils/logging"
)
type raspistill struct {
cfg config.Config
cmd *exec.Cmd
out io.ReadCloser
log logging.Logger
done chan struct{}
isRunning bool
}
func new(l logging.Logger) raspistill {
return raspistill{
log: l,
done: make(chan struct{}),
}
}
func (r *Raspistill) stop() error {
if r.isRunning == false {
return nil
}
close(r.done)
if r.cmd == nil || r.cmd.Process == nil {
return errors.New("raspistill process was never started")
}
err := r.cmd.Process.Kill()
if err != nil {
return fmt.Errorf("could not kill raspistill process: %w", err)
}
r.isRunning = false
return r.out.Close()
}
func (r *Raspistill) start() error {
args := []string{
"--output", "-",
"--nopreview",
"--width", fmt.Sprint(r.cfg.Width),
"--height", fmt.Sprint(r.cfg.Height),
"--rotation", fmt.Sprint(r.cfg.Rotation),
"--timeout", fmt.Sprint(r.cfg.TimelapseDuration),
"--timelapse", fmt.Sprint(r.cfg.TimelapseInterval),
"--quality", fmt.Sprint(r.cfg.JPEGQuality),
}
r.log.Info(pkg+"raspistill args", "args", strings.Join(args, " "))
r.cmd = exec.Command("raspistill", args...)
var err error
r.out, err = r.cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("could not pipe command output: %w", err)
}
stderr, err := r.cmd.StderrPipe()
if err != nil {
return fmt.Errorf("could not pipe command error: %w", err)
}
go func() {
errScnr := bufio.NewScanner(stderr)
for {
select {
case <-r.done:
r.log.Info("raspistill.Stop() called, finished checking stderr")
return
default:
}
if errScnr.Scan() {
r.log.Error("error line from raspistill stderr", "error", errScnr.Text())
continue
}
err := errScnr.Err()
if err != nil {
r.log.Error("error from stderr scan", "error", err)
}
}
}()
err = r.cmd.Start()
if err != nil {
return fmt.Errorf("could not start raspistill process: %w", err)
}
r.isRunning = true
return nil
}
func (r *Raspistill) read(p []byte) (int, error) {
if r.out != nil {
return r.out.Read(p)
}
return 0, errNotStarted
}