Skip to content

Commit

Permalink
allow charting for doser (#650)
Browse files Browse the repository at this point in the history
  • Loading branch information
ranjib committed Nov 21, 2018
1 parent 6125f2d commit 3eee55e
Show file tree
Hide file tree
Showing 15 changed files with 209 additions and 48 deletions.
6 changes: 6 additions & 0 deletions controller/doser/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func (c *Controller) LoadAPI(r *mux.Router) {
r.HandleFunc("/api/doser/pumps", c.create).Methods("PUT")
r.HandleFunc("/api/doser/pumps/{id}", c.update).Methods("POST")
r.HandleFunc("/api/doser/pumps/{id}", c.delete).Methods("DELETE")
r.HandleFunc("/api/doser/pumps/{id}/usage", c.getUsage).Methods("GET")
r.HandleFunc("/api/doser/pumps/{id}/calibrate", c.calibrate).Methods("POST")
r.HandleFunc("/api/doser/pumps/{id}/schedule", c.schedule).Methods("POST")
}
Expand Down Expand Up @@ -68,3 +69,8 @@ func (c *Controller) schedule(w http.ResponseWriter, r *http.Request) {
}
utils.JSONUpdateResponse(&reg, fn, w, r)
}

func (c *Controller) getUsage(w http.ResponseWriter, req *http.Request) {
fn := func(id string) (interface{}, error) { return c.statsMgr.Get(id) }
utils.JSONGetResponse(fn, w, req)
}
46 changes: 32 additions & 14 deletions controller/doser/controller.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
package doser

import (
"encoding/json"
"github.com/reef-pi/reef-pi/controller/connectors"
"github.com/reef-pi/reef-pi/controller/types"
"github.com/reef-pi/reef-pi/controller/utils"
"gopkg.in/robfig/cron.v2"
"log"
"sync"
)

const Bucket = types.DoserBucket
const UsageBucket = types.DoserUsageBucket

type Controller struct {
DevMode bool
c types.Controller
mu *sync.Mutex
runner *cron.Cron
cronIDs map[string]cron.EntryID
jacks *connectors.Jacks
DevMode bool
statsMgr types.StatsManager
c types.Controller
mu *sync.Mutex
runner *cron.Cron
cronIDs map[string]cron.EntryID
jacks *connectors.Jacks
}

func New(devMode bool, c types.Controller, jacks *connectors.Jacks) (*Controller, error) {
return &Controller{
DevMode: devMode,
jacks: jacks,
cronIDs: make(map[string]cron.EntryID),
mu: &sync.Mutex{},
runner: cron.New(),
c: c,
DevMode: devMode,
jacks: jacks,
cronIDs: make(map[string]cron.EntryID),
mu: &sync.Mutex{},
runner: cron.New(),
statsMgr: utils.NewStatsManager(c.Store(), UsageBucket, types.CurrentLimit, types.HistoricalLimit),
c: c,
}, nil
}

Expand All @@ -39,7 +46,10 @@ func (c *Controller) Stop() {
}

func (c *Controller) Setup() error {
return c.c.Store().CreateBucket(Bucket)
if err := c.c.Store().CreateBucket(Bucket); err != nil {
return err
}
return c.c.Store().CreateBucket(UsageBucket)
}

func (c *Controller) loadAllSchedule() error {
Expand All @@ -52,14 +62,22 @@ func (c *Controller) loadAllSchedule() error {
continue
}
c.addToCron(p)
fn := func(d json.RawMessage) interface{} {
u := Usage{}
json.Unmarshal(d, &u)
return u
}
if err := c.statsMgr.Load(p.ID, fn); err != nil {
log.Println("ERROR: dosing controller. Failed to load usage. Error:", err)
}
}
return nil
}

func (c *Controller) addToCron(p Pump) error {
c.mu.Lock()
defer c.mu.Unlock()
cronID, err := c.runner.AddJob(p.Regiment.Schedule.CronSpec(), p.Runner(c.jacks))
cronID, err := c.runner.AddJob(p.Regiment.Schedule.CronSpec(), p.Runner(c.jacks, c.statsMgr))
if err != nil {
return err
}
Expand Down
26 changes: 15 additions & 11 deletions controller/doser/pump.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ package doser

import (
"encoding/json"
"log"

"github.com/reef-pi/reef-pi/controller/connectors"
"github.com/reef-pi/reef-pi/controller/types"
"gopkg.in/robfig/cron.v2"
"log"
)

type Pump struct {
ID string `json:"id"`
Name string `json:"name"`
Jack string `json:"jack"`
Pin int `json:"pin"`
Regiment DosingRegiment `json:"regiment"`
}

func (c *Controller) Get(id string) (Pump, error) {
var p Pump
return p, c.c.Store().Get(Bucket, id, &p)
Expand Down Expand Up @@ -46,11 +54,9 @@ func (c *Controller) Calibrate(id string, cal CalibrationDetails) error {
return err
}
r := &Runner{
pin: p.Pin,
duration: cal.Duration,
speed: cal.Speed,
pump: &p,
jacks: c.jacks,
jack: p.Jack,
statsMgr: c.statsMgr,
}
go r.Run()
return nil
Expand Down Expand Up @@ -109,12 +115,10 @@ func (c *Controller) Delete(id string) error {
return c.c.Store().Delete(Bucket, id)
}

func (p *Pump) Runner(jacks *connectors.Jacks) cron.Job {
func (p *Pump) Runner(jacks *connectors.Jacks, t types.StatsManager) cron.Job {
return &Runner{
pin: p.Pin,
duration: p.Regiment.Duration,
speed: p.Regiment.Speed,
pump: p,
jacks: jacks,
jack: p.Jack,
statsMgr: t,
}
}
29 changes: 18 additions & 11 deletions controller/doser/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,40 @@ package doser

import (
"github.com/reef-pi/reef-pi/controller/connectors"
"github.com/reef-pi/reef-pi/controller/types"
"github.com/reef-pi/reef-pi/controller/utils"
"log"
"time"
)

type Runner struct {
pin int
jack string
duration time.Duration
speed float64
pump *Pump
jacks *connectors.Jacks
statsMgr types.StatsManager
}

func (r *Runner) Run() {
v := make(map[int]float64)
v[r.pin] = r.speed
v[r.pump.Pin] = r.pump.Regiment.Speed

log.Println("doser sub system: setting pwm pin:", r.pin, "at speed", r.speed)
if err := r.jacks.Control(r.jack, v); err != nil {
log.Println("doser sub system: setting pwm pin:", r.pump.Pin, "at speed", r.pump.Regiment.Speed)
usage := Usage{
Time: utils.TeleTime(time.Now()),
}
r.statsMgr.Update(r.pump.ID, usage)
if err := r.jacks.Control(r.pump.Jack, v); err != nil {
log.Println("ERROR: dosing sub-system. Failed to control jack. Error:", err)
return
}
select {
case <-time.After(r.duration * time.Second):
v[r.pin] = 0
log.Println("doser sub system: setting pwm pin:", r.pin, "at speed", 0)
if err := r.jacks.Control(r.jack, v); err != nil {
case <-time.After(r.pump.Regiment.Duration * time.Second):
v[r.pump.Pin] = 0
log.Println("doser sub system: setting pwm pin:", r.pump.Pin, "at speed", 0)
if err := r.jacks.Control(r.pump.Jack, v); err != nil {
log.Println("ERROR: dosing sub-system. Failed to control jack. Error:", err)
}
}
usage.Pump = int(r.pump.Regiment.Duration)
r.statsMgr.Update(r.pump.ID, usage)
//r.Telemetry().EmitMetric("doser"+r.pump.Name+"-usage", usage.Pump)
}
11 changes: 0 additions & 11 deletions controller/doser/types.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
package doser

import (
"github.com/reef-pi/reef-pi/controller/types"
"gopkg.in/robfig/cron.v2"
"strings"
"time"
)

const Bucket = types.DoserBucket

type Pump struct {
ID string `json:"id"`
Name string `json:"name"`
Jack string `json:"jack"`
Pin int `json:"pin"`
Regiment DosingRegiment `json:"regiment"`
}

type DosingRegiment struct {
Enable bool `json:"enable"`
Schedule Schedule `json:"schedule"`
Expand Down
26 changes: 26 additions & 0 deletions controller/doser/usage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package doser

import (
"github.com/reef-pi/reef-pi/controller/types"
"github.com/reef-pi/reef-pi/controller/utils"
)

type Usage struct {
Pump int `json:"pump"`
Time utils.TeleTime `json:"time"`
}

func (u1 Usage) Rollup(ux types.Metric) (types.Metric, bool) {
u2 := ux.(Usage)
u := Usage{Time: u1.Time, Pump: u1.Pump}
if u1.Time.Hour() == u2.Time.Hour() {
u.Pump += u2.Pump
return u, false
}
return u2, true
}

func (u1 Usage) Before(ux types.Metric) bool {
u2 := ux.(Usage)
return u1.Time.Before(u2.Time)
}
1 change: 1 addition & 0 deletions controller/types/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
JackBucket = "jacks"
OutletBucket = "outlets"
DoserBucket = "doser"
DoserUsageBucket = "doser_usage"
EquipmentBucket = "equipment"
LightingBucket = "lightings"
MacroBucket = "macro"
Expand Down
2 changes: 2 additions & 0 deletions jsx/dashboard/config.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class config extends React.Component {
atos={this.props.atos}
phs={this.props.phs}
lights={this.props.lights}
dosers={this.props.dosers}
/>
</div>
<div className='row'>
Expand All @@ -147,6 +148,7 @@ const mapStateToProps = state => {
phs: state.phprobes,
tcs: state.tcs,
lights: state.lights,
dosers: state.dosers,
config: state.dashboard
}
}
Expand Down
6 changes: 5 additions & 1 deletion jsx/dashboard/grid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export default class Grid extends React.Component {
case 'tc':
data = this.props.tcs
break
case 'doser':
data = this.props.dosers
break
}

return (
Expand Down Expand Up @@ -124,7 +127,8 @@ export default class Grid extends React.Component {
this.menuItem('ph-current', false, i, j),
this.menuItem('ph-historical', false, i, j),
this.menuItem('tc', false, i, j),
this.menuItem('temperature', false, i, j)
this.menuItem('temperature', false, i, j),
this.menuItem('doser', false, i, j)
]
return (types)
}
Expand Down
10 changes: 10 additions & 0 deletions jsx/dashboard/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import TempControlChart from 'temperature/control_chart'
import EquipmentChart from 'equipment/chart'
import LightChart from 'lighting/chart'
import ATOChart from 'ato/chart'
import DoserChart from 'doser/chart'
import HealthChart from 'health_chart'
import PhChart from 'ph/chart'
import { fetchDashboard } from 'redux/actions/dashboard'
Expand Down Expand Up @@ -95,6 +96,15 @@ class dashboard extends React.Component {
</div>
)
break
case 'doser':
columns.push(
<div className='col-sm-6' key={'chart-' + i + '-' + j}>
<ErrorBoundary>
<DoserChart width={config.width} height={config.height} doser_id={ch.id} />
</ErrorBoundary>
</div>
)
break
case 'health':
columns.push(
<div className='col-sm-6' key={'chart-' + i + '-' + j}>
Expand Down
68 changes: 68 additions & 0 deletions jsx/doser/chart.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react'
import { ResponsiveContainer, Tooltip, YAxis, XAxis, BarChart, Bar } from 'recharts'
import { fetchDoserUsage } from '../redux/actions/doser'
import { connect } from 'react-redux'

class chart extends React.Component {
constructor (props) {
super(props)
this.updateUsage = this.updateUsage.bind(this)
}

updateUsage () {
this.props.fetchDoserUsage(this.props.doser_id)
}

componentDidMount () {
this.updateUsage()
var timer = window.setInterval(this.updateUsage, 10 * 1000)
this.setState({ timer: timer })
}

componentWillUnmount () {
if (this.state && this.state.timer) {
window.clearInterval(this.state.timer)
}
}

render () {
if (this.props.usage === undefined) {
return <div />
}
if (this.props.config === undefined) {
return <div />
}
return (
<React.Fragment>
<span className='h6'>{this.props.config.name} - Doser Usage</span>
<ResponsiveContainer height={this.props.height} width='100%'>
<BarChart data={this.props.usage.historical}>
<Bar dataKey='pump' fill='#33b5e5' isAnimationActive={false} />
<YAxis label='seconds' />
<XAxis dataKey='time' />
<Tooltip />
</BarChart>
</ResponsiveContainer>
</React.Fragment>
)
}
}

const mapStateToProps = (state, props) => {
return {
usage: state.doser_usage[props.doser_id],
config: state.dosers.find(p => p.id === props.doser_id)
}
}

const mapDispatchToProps = dispatch => {
return {
fetchDoserUsage: id => dispatch(fetchDoserUsage(id))
}
}

const Chart = connect(
mapStateToProps,
mapDispatchToProps
)(chart)
export default Chart
Loading

0 comments on commit 3eee55e

Please sign in to comment.