Skip to content

Commit

Permalink
Merge pull request #48 from mjs/split
Browse files Browse the repository at this point in the history
New leptond service
  • Loading branch information
mjs authored May 3, 2018
2 parents bb82bbd + 3369e39 commit 6bd209e
Show file tree
Hide file tree
Showing 21 changed files with 570 additions and 261 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/thermal-recorder
/leptond
/dist
44 changes: 32 additions & 12 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
project_name: thermal-recorder
dist: dist

release:
github:
owner: TheCacophonyProject
name: thermal-recorder
name_template: '{{.Tag}}'

builds:
- goos:
- linux
- binary: thermal-recorder
main: ./cmd/thermal-recorder
goos:
- linux
goarch:
- arm
- arm
goarm:
- "7"
main: .
- "7"
ldflags: -s -w -X main.version={{.Version}}
binary: thermal-recorder
archive:
name_template: '{{ .ProjectName }}_{{ .Version }}'
format: tar.gz
- binary: leptond
main: ./cmd/leptond
goos:
- linux
goarch:
- arm
goarm:
- "7"
ldflags: -s -w -X main.version={{.Version}}

nfpm:
vendor: The Cacophony Project
homepage: http://cacophony.org.nz/
maintainer: Menno Finlay-Smits <[email protected]>
description: Record thermal video footage from FLIR Lepton 3 camera
license: GPL v3.0
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Arch }}"
formats:
- deb
bindir: /usr/bin
files:
- thermal-recorder.service
- thermal-recorder-TEMPLATE.yaml
"thermal-recorder.service": "/etc/systemd/system/thermal-recorder.service"
"leptond.service": "/etc/systemd/system/leptond.service"

checksum:
name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt'
dist: dist
37 changes: 34 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions cmd/leptond/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2018 The Cacophony Project. All rights reserved.
// Use of this source code is governed by the Apache License Version 2.0;
// see the LICENSE file for further details.

package main

import (
"io/ioutil"

yaml "gopkg.in/yaml.v2"
)

type Config struct {
SPISpeed int64 `yaml:"spi-speed"`
PowerPin string `yaml:"power-pin"`
FrameOutput string `yaml:"frame-output"`
}

var defaultConfig = Config{
SPISpeed: 2000000,
PowerPin: "GPIO23",
FrameOutput: "/var/run/lepton-frames",
}

func ParseConfigFile(filename string) (*Config, error) {
buf, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return ParseConfig(buf)
}

func ParseConfig(buf []byte) (*Config, error) {
conf := defaultConfig
if err := yaml.Unmarshal(buf, &conf); err != nil {
return nil, err
}
return &conf, nil
}
41 changes: 41 additions & 0 deletions cmd/leptond/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2018 The Cacophony Project. All rights reserved.
// Use of this source code is governed by the Apache License Version 2.0;
// see the LICENSE file for further details.

package main

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAllDefaults(t *testing.T) {
conf, err := ParseConfig([]byte(""))
require.NoError(t, err)

assert.Equal(t, Config{
SPISpeed: 2000000,
PowerPin: "GPIO23",
FrameOutput: "/var/run/lepton-frames",
}, *conf)
}

func TestAllSet(t *testing.T) {
// All config set at non-default values.
config := []byte(`
spi-speed: 123
power-pin: "PIN"
frame-output: "/some/sock"
`)

conf, err := ParseConfig(config)
require.NoError(t, err)

assert.Equal(t, Config{
SPISpeed: 123,
PowerPin: "PIN",
FrameOutput: "/some/sock",
}, *conf)
}
185 changes: 185 additions & 0 deletions cmd/leptond/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright 2018 The Cacophony Project. All rights reserved.
// Use of this source code is governed by the Apache License Version 2.0;
// see the LICENSE file for further details.

package main

import (
"fmt"
"log"
"net"
"time"

"github.com/TheCacophonyProject/lepton3"
arg "github.com/alexflint/go-arg"
"github.com/coreos/go-systemd/daemon"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpioreg"
"periph.io/x/periph/host"
)

const (
framesHz = 9 // approx

frameLogIntervalFirstMin = 15 * framesHz
frameLogInterval = 60 * 5 * framesHz

framesPerSdNotify = 5 * framesHz
)

var version = "<not set>"

type Args struct {
ConfigFile string `arg:"-c,--config" help:"path to configuration file"`
Quick bool `arg:"-q,--quick" help:"don't cycle camera power on startup"`
Timestamps bool `arg:"-t,--timestamps" help:"include timestamps in log output"`
}

func (Args) Version() string {
return version
}

func procArgs() Args {
var args Args
args.ConfigFile = "/etc/leptond.yaml"
arg.MustParse(&args)
return args
}

type nextFrameErr struct {
cause error
}

func (e *nextFrameErr) Error() string {
return e.cause.Error()
}

func main() {
err := runMain()
if err != nil {
log.Fatal(err)
}
}

func runMain() error {
args := procArgs()
if !args.Timestamps {
log.SetFlags(0) // Removes default timestamp flag
}

log.Printf("version: %s", version)
conf, err := ParseConfigFile(args.ConfigFile)
if err != nil {
return err
}
logConfig(conf)

log.Print("host initialisation")
if _, err := host.Init(); err != nil {
return err
}

if !args.Quick {
if err := cycleCameraPower(conf.PowerPin); err != nil {
return err
}
}

var camera *lepton3.Lepton3
defer func() {
if camera != nil {
camera.Close()
}
}()
for {
camera = lepton3.New(conf.SPISpeed)
camera.SetLogFunc(func(t string) { log.Printf(t) })

log.Print("opening camera")
if err := camera.Open(); err != nil {
return err
}

log.Print("enabling radiometry")
if err := camera.SetRadiometry(true); err != nil {
return err
}

err := runCamera(conf, camera)
if err != nil {
if _, isNextFrameErr := err.(*nextFrameErr); !isNextFrameErr {
return err
}
log.Printf("recording error: %v", err)
}

log.Print("closing camera")
camera.Close()

err = cycleCameraPower(conf.PowerPin)
if err != nil {
return err
}
}
}

func runCamera(conf *Config, camera *lepton3.Lepton3) error {
log.Print("dialing frame output socket")
conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{
Net: "unixgram",
Name: conf.FrameOutput,
})
if err != nil {
return err
}
defer conn.Close()
conn.SetWriteBuffer(lepton3.FrameCols * lepton3.FrameRows * 2 * 20)

log.Print("reading frames")
frame := new(lepton3.RawFrame)
notifyCount := 0
for {
if err := camera.NextFrame(frame); err != nil {
return &nextFrameErr{err}
}

if notifyCount++; notifyCount >= framesPerSdNotify {
daemon.SdNotify(false, "WATCHDOG=1")
notifyCount = 0
}

if _, err := conn.Write(frame[:]); err != nil {
return err
}
}
}

func logConfig(conf *Config) {
log.Printf("SPI speed: %d", conf.SPISpeed)
log.Printf("power pin: %s", conf.PowerPin)
log.Printf("frame output: %s", conf.FrameOutput)
}

func cycleCameraPower(pinName string) error {
if pinName == "" {
return nil
}

pin := gpioreg.ByName(pinName)

log.Print("turning camera power off")
if err := pin.Out(gpio.Low); err != nil {
return fmt.Errorf("failed to set camera power pin low: %v", err)
}
time.Sleep(2 * time.Second)

log.Print("turning camera power on")
if err := pin.Out(gpio.High); err != nil {
return fmt.Errorf("failed to set camera power pin high: %v", err)
}

log.Print("waiting for camera startup")
time.Sleep(8 * time.Second)
log.Print("camera should be ready")
return nil
}
Loading

0 comments on commit 6bd209e

Please sign in to comment.