Super broken but what are you gonna do

main
Keegan 2 years ago
parent b1520db055
commit 0ccac301f1

@ -1,59 +1,68 @@
package main package main
import ( import (
"fmt" "FRMS/internal/pkg/config"
"os" "FRMS/internal/pkg/logging"
"flag" "FRMS/internal/pkg/reactor"
"log"
"strconv" flag "github.com/spf13/pflag"
"FRMS/internal/pkg/reactor" "github.com/spf13/viper"
"FRMS/internal/pkg/logging"
) )
type coordinator interface { type coordinator interface {
Start() Start()
} }
func NewCoordinator(ip string,port int,ch chan error) coordinator { func NewCoordinator(ip string, port int, ch chan error) coordinator {
// allows interface checking as opposed to calling directly // allows interface checking as opposed to calling directly
return reactor.NewCoordinator(ip,port,ch) return reactor.NewCoordinator(ip, port, ch)
} }
func main() { type Config interface {
var ip string Load() error // load config, keys and env for a string
var port int Store() error // write any pending changes
}
flag.Usage = func() {
w := flag.CommandLine.Output() func NewConfig(fname string) Config {
fmt.Fprintf(w, "Usage: %s port \n",os.Args[0]) if conf, err := config.NewConfig(fname); err != nil {
} panic(err)
iptr := flag.String("i","192.168.100.2","ip address of server") }
//iptr := flag.String("i","192.1.168.136","ip address of laptop") return conf
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
os.Exit(1)
}
args := flag.Args()
if p, err := strconv.Atoi(args[0]);p < 1024 || p > 65535 {
flag.Usage()
log.Fatal("Port must be between [1023,65535]")
} else if err != nil {
log.Fatal(err)
}
ip = *iptr
port, err := strconv.Atoi(args[0])
if err != nil {
log.Fatal(err)
}
ch := make(chan error)
rlc := NewCoordinator(ip,port,ch) // host port
go rlc.Start()
logging.Debug(logging.DStart, "Reactor Started")
err = <-ch
if err != nil {
log.Fatal(err)
}
} }
func main() {
// load any stored settings
conf := NewConfig("reactor") // loads .yaml and flags
conf.Load() // load exisiting settings
// get overrides
var ip string
var port int
/*
flag.Usage = func() {
w := flag.CommandLine.Output()
fmt.Fprintf(w, "Usage: %s port \n",os.Args[0])
}*/
iptr := flag.String("ip", "192.168.100.2", "server ip")
portptr := flag.String("port", 2022, "server port")
nameptr := flag.String("name", "", "human readable name")
flag.Parse()
// lets us retrieve them from viper later
if err := viper.BindPFlags(flag.CommandLine); err != nil {
panic(err)
}
ch := make(chan error)
rlc := NewCoordinator(ch) // passing only err chan
go rlc.Start()
logging.Debug(logging.DStart, "Reactor Started")
for err = range ch {
if err != nil {
panic(err)
}
}
}

@ -0,0 +1,52 @@
package config
// def.go serves as a central place to view/edit config structs and device manager map
import (
"sync"
)
// Server Config
type ServerConf struct {
// Structure to demarshall into
Server ServerConfig `mapstructure:"server"`
Reactors map[string]ReactorConfig `mapstructure:"reactors"`
sync.RWMutex
}
type ServerConfig struct {
// Server config settings
URL string `mapstructure:"db-url"`
Token string `mapstructure:"db-token"`
Orginization string `mapstructure:"db-org"`
Ports map[string]int `mapstructure:"ports"` // changed from map[string]string to map[string]int
Name string `mapstructure:"name"`
}
// Reactor Config
type ReactorConf struct {
// Structure to demarshall to
Reactor ReactorConfig `mapstructure:"reactor"`
Devices map[int]DeviceConfig `mapstructure:"devices"`
sync.RWMutex
}
type ReactorConfig struct {
// Reactor config settings
Token string `mapstructure:"db-token"`
Bucket string `mapstructure:"db-bucket"`
URL string `mapstructure:"db-url",omitempty` // only needed by reactor
Name string `mapstructure:"name",omitempty` // not always set
Id uint32 `mapstructure:"id"`
}
// Device Config
type DeviceConfig struct {
// Device config settings
Address uint32 `mapstructure:"address"`
Interval uint32 `mapstructure:"interval"`
Name string `mapstructure:"name"`
}

@ -0,0 +1,5 @@
package config
/*
Package provides a way to update current config based on values passed in flags
*/

@ -0,0 +1,172 @@
package config
/*
Load.go contains methods to load values from config, flags and env.
*/
import (
"FRMS/internal/pkg/logging"
"errors"
"fmt"
"strconv"
"strings"
"github.com/spf13/viper"
//"os"
//"log"
//"os/exec"
//"bytes"
)
func NewConfig(name string) (Config, error) {
// returns a Config Structure of assocaited name
return
type ConfigStruct interface {
// structure to do demarshall config into
LoadFile(string) error
}
func LoadConfigFile(fname string, strct ConfigStruct) error {
// Demarshalls a given filename into the struct
// returns nil if successful
logging.Debug(logging.DStart, "Loading config for %s", fname)
viper.SetConfigName(fname)
viper.SetConfigType("yaml")
viper.AddConfigPath("/etc/frms/config")
// unmarshalling
if err := viper.ReadInConfig(); err != nil {
return err
}
logging.Debug(logging.DStart, "CON Loaded configs from %v", viper.ConfigFileUsed())
if err = viper.Unmarshal(strct); err != nil {
logging.Debug(logging.DError, "Cannot unmarshall %v", err)
return err
}
fmt.Printf("Outcome: %#v\n \n", C)
// unmarshalled at this point
}
////
func LoadConfig() Config {
return C
}
func (c *ServerConf) GetURL() (string, error) {
c.RLock()
defer c.RUnlock()
return C.Server.URL, nil
}
func (c *ServerConf) GetOrg() (string, error) {
c.RLock()
defer c.RUnlock()
return c.Server.Orginization, nil
}
func (c *ServerConf) GetPort(port string) (int, error) {
c.RLock()
defer c.RUnlock()
portString, ok := c.Server.Ports[port]
if !ok {
portEnv := strings.ToUpper(port) + "_PORT"
return 0, fmt.Errorf("%s port doesnt exist! Please set using env %s=####", port, portEnv)
}
// returns int, err
//return strconv.Atoi(portString)
return portString, nil
}
func (c *ServerConf) GetServerToken() (string, error) {
c.RLock()
defer c.RUnlock()
return c.Server.Token, nil
}
func (c *ServerConf) GetReactorClient(id uint32) (string, string, error) {
c.RLock()
defer c.RUnlock()
idString := strconv.FormatUint(uint64(id), 10)
if r, ok := c.Reactors[idString]; ok {
return r.Bucket, r.Token, nil
}
return "", "", fmt.Errorf("reactor %v config doesnt exist", id)
}
// setters
func (c *ServerConf) UpdateURL(url string) error {
c.Lock()
defer c.Unlock()
if url == "" {
return errors.New("string cannot be empty")
}
c.Server.URL = url
viper.Set("server.db-url", url)
return viper.WriteConfigAs(viper.ConfigFileUsed())
}
func (c *ServerConf) UpdateOrg(org string) error {
c.Lock()
defer c.Unlock()
if org == "" {
return errors.New("string cannot be empty")
}
c.Server.Orginization = org
viper.Set("server.db-org", org)
return viper.WriteConfigAs(viper.ConfigFileUsed())
}
func (c *ServerConf) UpdatePort(pName string, port int) error {
c.Lock()
defer c.Unlock()
if port < 1024 || port > 65535 {
// OOB
return fmt.Errorf("Port %d out of bounds! [1024,65535]", port)
}
if c.Server.Ports == nil {
c.Server.Ports = make(map[string]int)
}
c.Server.Ports[pName] = port
pname := fmt.Sprintf("server.ports.%s", pName)
viper.Set(pname, port)
return viper.WriteConfigAs(viper.ConfigFileUsed())
}
func (c *ServerConf) UpdateServerToken(token string) error {
c.Lock()
defer c.Unlock()
if token == "" {
return errors.New("String cannot be empty!")
}
c.Server.Token = token
viper.Set("server.token", token)
return viper.WriteConfigAs(viper.ConfigFileUsed())
}
func (c *ServerConf) UpdateReactorClient(id uint32, bucket, token string) error {
c.Lock()
c.Unlock()
sid := strconv.FormatUint(uint64(id), 10)
if token == "" || bucket == "" {
return errors.New("String cannot be empty!")
}
if reactor, ok := c.Reactors[sid]; !ok {
c.Reactors[sid] = ReactorConfig{Token: token, Bucket: bucket, Id: id}
} else {
reactor.Bucket = bucket
reactor.Token = token
c.Reactors[sid] = reactor
}
reactorbucket := fmt.Sprintf("%s.db-bucket", id)
reactortoken := fmt.Sprintf("%s.db-token", id)
viper.Set(reactorbucket, bucket)
viper.Set(reactortoken, token)
return viper.WriteConfigAs(viper.ConfigFileUsed())
}
func (c *ServerConf) Store() error {
return viper.WriteConfigAs(viper.ConfigFileUsed())
}

@ -1,26 +1,3 @@
package config package config
// pacakge serves to store/load config files for reactor // pacakge serves to provide reactor config implementation
import (
"sync"
)
type ReactorConf struct {
Reactor ReactorConfig `mapstructure:"reactor"`
Devices map[int]DeviceConfig `mapstructure:"devices"`
sync.RWMutex
}
type DeviceConfig struct {
Address uint32 `mapstructure:"address"`
Interval uint32 `mapstructure:"interval"`
Name string `mapstructure:"name"`
}
// loaded in other file
func (c *ReactorConf) GetURL() (string, error) {
c.RLock()
defer c.RUnlock()
return c.Reactor.URL, nil
}

@ -1,6 +1,6 @@
package config package config
// package serves to store/load config files for server // package serves to implement config interface for server
import ( import (
"FRMS/internal/pkg/logging" "FRMS/internal/pkg/logging"
@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/spf13/viper" "github.com/spf13/viper"
//"os" //"os"
@ -17,28 +16,6 @@ import (
//"bytes" //"bytes"
) )
type ServerConf struct {
Server ServerConfig `mapstructure:"server"`
Reactors map[string]ReactorConfig `mapstructure:"reactors"`
sync.RWMutex
}
type ServerConfig struct {
URL string `mapstructure:"db-url"`
Token string `mapstructure:"db-token"`
Orginization string `mapstructure:"db-org"`
Ports map[string]int `mapstructure:"ports"` // changed from map[string]string to map[string]int
Name string `mapstructure:"name"`
}
type ReactorConfig struct {
Token string `mapstructure:"db-token"`
Bucket string `mapstructure:"db-bucket"`
URL string `mapstructure:"db-url",omitempty` // only needed by reactor
Name string `mapstructure:"name",omitempty` // not always set
Id uint32 `mapstructure:"id"`
}
type Config interface { type Config interface {
Store() error Store() error
} }
@ -84,6 +61,8 @@ func LoadConfig() Config {
return C return C
} }
func (c *ServerConf) Load()
func (c *ServerConf) GetURL() (string, error) { func (c *ServerConf) GetURL() (string, error) {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()

@ -3,258 +3,268 @@ package reactor
// file describes reactor level coordinator and associated implementation // file describes reactor level coordinator and associated implementation
import ( import (
"fmt" "FRMS/internal/pkg/I2C"
"sync" pb "FRMS/internal/pkg/grpc"
"time" "FRMS/internal/pkg/logging"
"math" "FRMS/internal/pkg/sensor"
"FRMS/internal/pkg/system" "FRMS/internal/pkg/system"
"FRMS/internal/pkg/I2C" "context"
"FRMS/internal/pkg/sensor" "errors"
"FRMS/internal/pkg/logging" "fmt"
"errors" "math"
"context" "sync"
"google.golang.org/grpc" "time"
"google.golang.org/grpc/status"
"google.golang.org/grpc/credentials/insecure" influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/influxdata/influxdb-client-go/v2" "google.golang.org/grpc"
pb "FRMS/internal/pkg/grpc" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
) )
// Coordinator == Reactor Level Coordinator // Coordinator == Reactor Level Coordinator
type Coordinator struct { type Coordinator struct {
Ip string Ip string
Port int // listener port Port int // listener port
MonitoringClient pb.MonitoringClient MonitoringClient pb.MonitoringClient
*hw *hw
Devices *DeviceManagers // struct for fine grain locking Devices *DeviceManagers // struct for fine grain locking
Err chan error Err chan error
mu sync.Mutex mu sync.Mutex
HB time.Duration HB time.Duration
PingTimer chan struct{} PingTimer chan struct{}
*DB *DB
Active active Active active
} }
type DB struct { type DB struct {
// struct to hold db connection info // struct to hold db connection info
Org string Org string
Bucket string Bucket string
Token string Token string
URL string URL string
} }
type active struct { type active struct {
bool bool
int int
sync.Mutex sync.Mutex
} }
type hw struct { type hw struct {
// store reactor info // store reactor info
Model string Model string
Bus int Bus int
Id uint32 Id uint32
} }
type DeviceManagers struct { type DeviceManagers struct {
Managers map[int]DeviceManager Managers map[int]DeviceManager
sync.Mutex sync.Mutex
} }
// basic devicemanager struct manipulations // basic devicemanager struct manipulations
type DeviceManager interface { type DeviceManager interface {
Start() Start()
GetType() string GetType() string
GetStatus() string GetStatus() string
GetData() string GetData() string
} }
type I2CDev interface { type I2CDev interface {
GetAddr() int GetAddr() int
GetData() string GetData() string
GetStatus() string GetStatus() string
GetType() string GetType() string
} }
func NewDeviceManager(i2c I2CDev) DeviceManager { func NewDeviceManager(i2c I2CDev) DeviceManager {
return sensor.NewDeviceManager(i2c) return sensor.NewDeviceManager(i2c)
} }
type I2CMonitor interface { type I2CMonitor interface {
Monitor() Monitor()
GetDevice(int) interface{ GetAddr() int; GetStatus() string; GetData() string; GetType() string} GetDevice(int) interface {
GetAddr() int
GetStatus() string
GetData() string
GetType() string
}
} }
func NewI2CMonitor(b int,ch chan int) I2CMonitor { func NewI2CMonitor(b int, ch chan int) I2CMonitor {
return I2C.NewMonitor(b, ch) return I2C.NewMonitor(b, ch)
} }
func NewCoordinator(ip string,port int,ch chan error) *Coordinator { func NewCoordinator(ip string, port int, ch chan error) *Coordinator {
sen := new(DeviceManagers) sen := new(DeviceManagers)
sen.Managers = make(map[int]DeviceManager) sen.Managers = make(map[int]DeviceManager)
c := &Coordinator{Err:ch,Devices:sen} c := &Coordinator{Err: ch, Devices: sen}
c.Ip = ip // all this stuff can come from config
c.Port = port c.Ip = ip
c.hw = &hw{} c.Port = port
c.HB = time.Duration(5 * time.Second) c.hw = &hw{}
c.PingTimer = make(chan struct{}) c.HB = time.Duration(5 * time.Second)
// this is going to be scuffed
url := fmt.Sprintf("http://%s:8086",ip) // this is going to be scuffed
fmt.Println(url) url := fmt.Sprintf("http://%s:8086", ip)
c.DB = &DB{Bucket:"bb",Org:"ForeLight",URL:url,Token:"S1UZssBu6KPfHaQCt34pZFpyc5lzbH9XanYJWCkOI5FqLY7gq205C6FTH-CmugiPH6o2WoKlTkEuPgIfaJjAhw=="} fmt.Println(url)
return c c.DB = &DB{Bucket: "bb", Org: "ForeLight", URL: url, Token: "S1UZssBu6KPfHaQCt34pZFpyc5lzbH9XanYJWCkOI5FqLY7gq205C6FTH-CmugiPH6o2WoKlTkEuPgIfaJjAhw=="}
c.PingTimer = make(chan struct{})
return c
} }
func (c *Coordinator) Start() { func (c *Coordinator) Start() {
// should discover hwinfo and sensors on its own // should discover hwinfo and sensors on its own
// now setting up sensor managers // now setting up sensor managers
// setting up hw stuff // setting up hw stuff
c.Activate() c.Activate()
var err error var err error
c.Id, err = system.GetId("eth0") c.Id, err = system.GetId("eth0")
c.Model, err = system.GetModel() c.Model, err = system.GetModel()
c.Bus, err = system.GetBus() c.Bus, err = system.GetBus()
if err != nil { if err != nil {
c.Err <-err c.Err <- err
} }
go c.Monitor() go c.Monitor()
go c.Discover() go c.Discover()
} }
func (c *Coordinator) Monitor() { func (c *Coordinator) Monitor() {
// function to automatically create and destroy sm // function to automatically create and destroy sm
// scuffedaf // scuffedaf
client := influxdb2.NewClient(c.URL,c.Token) client := influxdb2.NewClient(c.URL, c.Token)
defer client.Close() defer client.Close()
dch := make(chan int) dch := make(chan int)
im := NewI2CMonitor(c.Bus,dch) im := NewI2CMonitor(c.Bus, dch)
go im.Monitor() go im.Monitor()
for c.IsActive() { for c.IsActive() {
select { select {
case d := <-dch: case d := <-dch:
i := im.GetDevice(d) i := im.GetDevice(d)
go c.DeviceConnect(i) go c.DeviceConnect(i)
case <-c.PingTimer: case <-c.PingTimer:
go c.Ping(client) go c.Ping(client)
} }
} }
} }
func (c *Coordinator) HeartBeat() { func (c *Coordinator) HeartBeat() {
for c.IsActive() { for c.IsActive() {
c.PingTimer <-struct{}{} c.PingTimer <- struct{}{}
logging.Debug(logging.DClient,"RLC Pinging server") logging.Debug(logging.DClient, "RLC Pinging server")
time.Sleep(c.HB) time.Sleep(c.HB)
} }
} }
func (c *Coordinator) DeviceConnect(i2c I2CDev) { func (c *Coordinator) DeviceConnect(i2c I2CDev) {
c.Devices.Lock() c.Devices.Lock()
defer c.Devices.Unlock() defer c.Devices.Unlock()
addr := i2c.GetAddr() addr := i2c.GetAddr()
if dm, exists := c.Devices.Managers[addr]; !exists{ if dm, exists := c.Devices.Managers[addr]; !exists {
dm := NewDeviceManager(i2c) dm := NewDeviceManager(i2c)
c.Devices.Managers[addr] = dm c.Devices.Managers[addr] = dm
go dm.Start() go dm.Start()
} else { } else {
go dm.Start() go dm.Start()
} }
} }
func (c *Coordinator) Discover() { func (c *Coordinator) Discover() {
// sets up connection to central coordiantor // sets up connection to central coordiantor
conn, err := c.Connect(c.Ip, c.Port) conn, err := c.Connect(c.Ip, c.Port)
if err != nil { if err != nil {
c.Err <-err c.Err <- err
} }
defer conn.Close() defer conn.Close()
client := pb.NewHandshakeClient(conn) client := pb.NewHandshakeClient(conn)
req := &pb.ClientRequest{ClientId:c.Id,ClientType:"reactor"} req := &pb.ClientRequest{ClientId: c.Id, ClientType: "reactor"}
resp, err := client.ClientDiscoveryHandler(context.Background(), req) resp, err := client.ClientDiscoveryHandler(context.Background(), req)
if err != nil { if err != nil {
c.Err <-err c.Err <- err
} }
c.Port = int(resp.GetServerPort()) // updating server port c.Port = int(resp.GetServerPort()) // updating server port
logging.Debug(logging.DClient,"RLC Central server reached, supplied port %v",c.Port) logging.Debug(logging.DClient, "RLC Central server reached, supplied port %v", c.Port)
// connecting to manager now // connecting to manager now
clientConn, err := c.Connect(c.Ip, c.Port) clientConn, err := c.Connect(c.Ip, c.Port)
if err != nil { if err != nil {
c.Err <-err c.Err <- err
} }
c.MonitoringClient = pb.NewMonitoringClient(clientConn) c.MonitoringClient = pb.NewMonitoringClient(clientConn)
go c.HeartBeat() go c.HeartBeat()
} }
func (c *Coordinator) Connect(ip string, port int) (*grpc.ClientConn, error) { func (c *Coordinator) Connect(ip string, port int) (*grpc.ClientConn, error) {
// function connects to central server and passes hwinfo // function connects to central server and passes hwinfo
var opts []grpc.DialOption var opts []grpc.DialOption
opts = append(opts,grpc.WithTransportCredentials(insecure.NewCredentials())) opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
var conn *grpc.ClientConn var conn *grpc.ClientConn
var err error var err error
for { for {
conn, err = grpc.Dial(fmt.Sprintf("%v:%v",ip,port),opts...) conn, err = grpc.Dial(fmt.Sprintf("%v:%v", ip, port), opts...)
code := status.Code(err) code := status.Code(err)
if code != 0 { // != OK if code != 0 { // != OK
if code == (5 | 14) { // service temp down if code == (5 | 14) { // service temp down
to := c.Timeout() to := c.Timeout()
if to == 0 { if to == 0 {
err = errors.New("Failed to connect to central server") err = errors.New("Failed to connect to central server")
return &grpc.ClientConn{}, err return &grpc.ClientConn{}, err
} }
logging.Debug(logging.DClient,"Server currently unavailable, retrying in %v ms", to) logging.Debug(logging.DClient, "Server currently unavailable, retrying in %v ms", to)
time.Sleep(time.Duration(to) * time.Millisecond) time.Sleep(time.Duration(to) * time.Millisecond)
} else { } else {
return &grpc.ClientConn{}, err return &grpc.ClientConn{}, err
} }
} }
break; break
} }
return conn, nil return conn, nil
} }
func (c *Coordinator) Timeout() int { func (c *Coordinator) Timeout() int {
c.Active.Lock() c.Active.Lock()
defer c.Active.Unlock() defer c.Active.Unlock()
if c.Active.int < 9 { if c.Active.int < 9 {
v := int(5 * math.Pow(float64(2), float64(c.Active.int))) v := int(5 * math.Pow(float64(2), float64(c.Active.int)))
c.Active.int +=1 c.Active.int += 1
return v return v
} else { } else {
//excedded retries //excedded retries
return 0 return 0
} }
} }
func (c *Coordinator) IsActive() bool { func (c *Coordinator) IsActive() bool {
c.Active.Lock() c.Active.Lock()
defer c.Active.Unlock() defer c.Active.Unlock()
return c.Active.bool return c.Active.bool
} }
func (c *Coordinator) Exit() bool { func (c *Coordinator) Exit() bool {
c.Active.Lock() c.Active.Lock()
defer c.Active.Unlock() defer c.Active.Unlock()
if c.Active.bool { if c.Active.bool {
c.Active.bool = false c.Active.bool = false
logging.Debug(logging.DClient,"RLC Exiting...") logging.Debug(logging.DClient, "RLC Exiting...")
return true return true
} else { } else {
logging.Debug(logging.DError, "RLC Already Dead!") logging.Debug(logging.DError, "RLC Already Dead!")
return false return false
} }
} }
func (c *Coordinator) Activate() bool { func (c *Coordinator) Activate() bool {
c.Active.Lock() c.Active.Lock()
defer c.Active.Unlock() defer c.Active.Unlock()
if c.Active.bool { if c.Active.bool {
logging.Debug(logging.DError,"RLC Already Started!") logging.Debug(logging.DError, "RLC Already Started!")
return false return false
} else { } else {
logging.Debug(logging.DClient, "RLC Starting") logging.Debug(logging.DClient, "RLC Starting")
c.Active.bool = true c.Active.bool = true
return c.Active.bool return c.Active.bool
} }
} }

65
notes

@ -937,15 +937,62 @@ Basic reactor workflow overview
2) For every device shown as active, spawn a sensor manager from the assocaited config 2) For every device shown as active, spawn a sensor manager from the assocaited config
3) on disconnect, shut the dm down and save current settings to config 3) on disconnect, shut the dm down and save current settings to config
implementation time
#TODO 9/4
Might be dying nbd
- i think its just freshman flu but could be clot who knows
on to code
Need to have a functional BETA by 9/15 at the latest
pref 9/8 with a week to test
What do we NEED out of FRMS v0.1.0 (pre-alpha
as an aside v1.#.#-apha then v.1.#.#-beta for versions)
Needs:
- Connect and disconnect at will
- set sample and log rate
- set name
- live view data
- export expiriement data to CSV
Notes:
- all sensors will be atlas
- can leverage for a unified library
- can use grafana for the UI
- can bash script to eport data for a given time range into resspective sheet aka sheet of DO measurements etc.
- can setuo the format pretty easily and probably just print F the query worst case I mean its 3 data points at probabnly 1 sample per minute at worst
Architecture planning phase
What would each need require software wise
Need: Connect and disconnect at will
- directory of which device manager to load
- a way to store and load settings
- a way to monitor the i2c lines for new devices
Config interface
At a core
Load()
- load keys, config and env
- prompt for any updates
- store said updates
- store any future requests
functions both server and reactor will use:
- load config
- load keys
- dif keys
- load env
- dif env
order of ops
load config
load keys and env to overwrite config
store updates
have config with methods to get/set values

Loading…
Cancel
Save