Compare commits

..

6 Commits

Author SHA1 Message Date
4efce925f0 scheduler: fix runtime reset when date changes
Signed-off-by: Thomas Klaehn <tkl@blackfinn.de>
2025-09-29 11:22:56 +02:00
c85d0d1f68 apiservice/state: Enhancement: store state cache
Signed-off-by: Thomas Klaehn <tkl@blackfinn.de>
2025-08-15 09:05:51 +02:00
b76d394b16 app/scheduler: Enhancement: detect date change & update daily runtimes
accordingly

Signed-off-by: Thomas Klaehn <tkl@blackfinn.de>
2025-08-15 09:03:49 +02:00
ce31b61b1b app/storage: Enhancement: Set path with member function rather than
directly

Signed-off-by: Thomas Klaehn <tkl@blackfinn.de>
2025-08-15 09:02:33 +02:00
9cf9fb7263 webui/index.html: Fix: remove debug prints
Signed-off-by: Thomas Klaehn <tkl@blackfinn.de>
2025-08-15 09:00:49 +02:00
d6d09abe9f apiservice/soil/moisture: Fix: correct equation for soil moisture
calculation

Signed-off-by: Thomas Klaehn <tkl@blackfinn.de>
2025-08-15 08:59:23 +02:00
6 changed files with 101 additions and 27 deletions

View File

@@ -82,7 +82,7 @@ func moisture() (types.Telemetry, error) {
// re-calculate to % // re-calculate to %
// 100 % := 3.0 V // 100 % := 3.0 V
// 0 % := 0.0 V // 0 % := 0.0 V
ret.Value = -0.03*ret.Value + 3 ret.Value = -33.333*ret.Value + 100
ret.Unit = "%" ret.Unit = "%"
return ret, nil return ret, nil
} }

View File

@@ -9,6 +9,7 @@ import (
"sync" "sync"
"time" "time"
apiservice_devices "waterservice/internal/apiservice/devices" apiservice_devices "waterservice/internal/apiservice/devices"
"waterservice/internal/app/storage"
) )
type state string type state string
@@ -22,6 +23,7 @@ type State struct {
} }
const ( const (
storage_path = "/var/lib/waterservice/state/state.json"
StateOn state = "on" StateOn state = "on"
StateOff state = "off" StateOff state = "off"
) )
@@ -31,12 +33,14 @@ var (
state_cache map[string]State state_cache map[string]State
state_mutex sync.Mutex state_mutex sync.Mutex
store storage.Storage
cb_register_off registerOffSwitch cb_register_off registerOffSwitch
cb_unregister_off registerOffSwitch cb_unregister_off registerOffSwitch
) )
func init() { func init() {
logger.SetFlags(log.Llongfile | log.Ltime) logger.SetFlags(log.Llongfile | log.Ltime)
store.SetPath(storage_path)
state_cache = map[string]State{} state_cache = map[string]State{}
} }
@@ -46,6 +50,10 @@ func AddHandler() {
func Start() { func Start() {
state_mutex.Lock() state_mutex.Lock()
res, err := store.Read()
if err != nil {
logger.Print(err)
// build default state_cache
for _, device := range apiservice_devices.GetDevices().Devices { for _, device := range apiservice_devices.GetDevices().Devices {
tmp := State{ tmp := State{
Name: device.Name, Name: device.Name,
@@ -54,6 +62,12 @@ func Start() {
} }
state_cache[device.Name] = tmp state_cache[device.Name] = tmp
} }
} else {
err = json.Unmarshal(res, &state_cache)
if err != nil {
logger.Print(err)
}
}
state_mutex.Unlock() state_mutex.Unlock()
go poll_states() go poll_states()
@@ -84,6 +98,15 @@ func SetState(st State) error {
tmp := state_cache[dev.Name] tmp := state_cache[dev.Name]
tmp.State = st.State tmp.State = st.State
state_cache[dev.Name] = tmp state_cache[dev.Name] = tmp
res, err := json.Marshal(state_cache)
if err != nil {
logger.Print("unable to store state cache")
} else {
err = store.Write(res)
if err != nil {
logger.Print(err)
}
}
state_mutex.Unlock() state_mutex.Unlock()
if cb_register_off != nil && cb_unregister_off != nil { if cb_register_off != nil && cb_unregister_off != nil {
if st.State == StateOn { if st.State == StateOn {
@@ -104,6 +127,15 @@ func SetRuntime(name string, runtime time.Duration) {
tmp := state_cache[name] tmp := state_cache[name]
tmp.Runtime = runtime tmp.Runtime = runtime
state_cache[name] = tmp state_cache[name] = tmp
res, err := json.Marshal(state_cache)
if err != nil {
logger.Print("unable to store state cache")
} else {
err = store.Write(res)
if err != nil {
logger.Print(err)
}
}
state_mutex.Unlock() state_mutex.Unlock()
} }
@@ -166,6 +198,15 @@ func handle_patch(r *http.Request) error {
tmp := state_cache[dev.Name] tmp := state_cache[dev.Name]
tmp.State = st.State tmp.State = st.State
state_cache[dev.Name] = tmp state_cache[dev.Name] = tmp
res, err := json.Marshal(state_cache)
if err != nil {
logger.Print("unable to store state cache")
} else {
err = store.Write(res)
if err != nil {
logger.Print(err)
}
}
state_mutex.Unlock() state_mutex.Unlock()
if cb_register_off != nil && cb_unregister_off != nil { if cb_register_off != nil && cb_unregister_off != nil {
if st.State == StateOn { if st.State == StateOn {
@@ -213,6 +254,15 @@ func poll_states() {
tmp := state_cache[dev.Name] tmp := state_cache[dev.Name]
tmp.State = status tmp.State = status
state_cache[dev.Name] = tmp state_cache[dev.Name] = tmp
res, err := json.Marshal(state_cache)
if err != nil {
logger.Print(err)
} else {
err = store.Write(res)
if err != nil {
logger.Print(err)
}
}
state_mutex.Unlock() state_mutex.Unlock()
} }
time.Sleep(time.Second * 10) time.Sleep(time.Second * 10)

View File

@@ -27,7 +27,7 @@ func init() {
} }
func SetConfigFilePath(path string) { func SetConfigFilePath(path string) {
store.Path = path store.SetPath(path)
res, err := store.Read() res, err := store.Read()
if err != nil { if err != nil {
logger.Print("unable to read config") logger.Print("unable to read config")

View File

@@ -31,17 +31,17 @@ func init() {
app_state_cache.Mode = ModeManual app_state_cache.Mode = ModeManual
// override defaults with stored values // override defaults with stored values
store.Path = app_state_storage_path store.SetPath(app_state_storage_path)
res, err := store.Read() res, err := store.Read()
if err != nil { if err != nil {
logger.Print("unable to read app state cache") logger.Print(err)
return return
} }
app_state_mutex.Lock() app_state_mutex.Lock()
err = json.Unmarshal(res, &app_state_cache) err = json.Unmarshal(res, &app_state_cache)
app_state_mutex.Unlock() app_state_mutex.Unlock()
if err != nil { if err != nil {
logger.Print("unable to evaluate config data") logger.Print(err)
} }
} }
@@ -50,10 +50,13 @@ func SetMode(mode AppMode) {
app_state_cache.Mode = mode app_state_cache.Mode = mode
res, err := json.Marshal(app_state_cache) res, err := json.Marshal(app_state_cache)
if err != nil { if err != nil {
logger.Print("unable to store app state cache") logger.Print(err)
return return
} }
store.Write(res) err = store.Write(res)
if err != nil {
logger.Print(err)
}
app_state_mutex.Unlock() app_state_mutex.Unlock()
} }
@@ -77,7 +80,7 @@ func app_state_saver() {
res, err := json.Marshal(app_state_cache) res, err := json.Marshal(app_state_cache)
app_state_mutex.Unlock() app_state_mutex.Unlock()
if err != nil { if err != nil {
logger.Print("unable to marshal object to json") logger.Print(err)
time.Sleep(time.Minute) time.Sleep(time.Minute)
continue continue
} }
@@ -87,6 +90,8 @@ func app_state_saver() {
} }
func poll_auto_off() { func poll_auto_off() {
now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())
for { for {
app_state_mutex.Lock() app_state_mutex.Lock()
for i, od := range app_state_cache.off_devices { for i, od := range app_state_cache.off_devices {
@@ -115,6 +120,20 @@ func poll_auto_off() {
} }
} }
app_state_mutex.Unlock() app_state_mutex.Unlock()
// check date change
now := time.Now()
if now.After(today) {
logger.Print("date change detected")
// reset date
today = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())
devices := apiservice_devices.GetDevices()
for _, d := range devices.Devices {
// reset run times
apiservice_state.SetRuntime(d.Name, 0)
}
}
time.Sleep(time.Second) time.Sleep(time.Second)
} }
} }
@@ -123,7 +142,6 @@ func register_off_device(dev apiservice_devices.Device) {
for _, od := range app_state_cache.off_devices { for _, od := range app_state_cache.off_devices {
if od.device.Name == dev.Name { if od.device.Name == dev.Name {
// device already in off list // device already in off list
// FIXME: update off time
return return
} }
} }

View File

@@ -1,13 +1,14 @@
package storage package storage
import ( import (
"errors"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
) )
type Storage struct { type Storage struct {
Path string path string
} }
var ( var (
@@ -18,17 +19,24 @@ func init() {
logger.SetFlags(log.Llongfile | log.Ltime) logger.SetFlags(log.Llongfile | log.Ltime)
} }
func (storage Storage) Read() ([]byte, error) { func (s *Storage) SetPath(path string) {
data, err := os.ReadFile(storage.Path) s.path = path
}
func (s Storage) Read() ([]byte, error) {
if len(s.path) == 0 {
return nil, errors.New("path not set")
}
data, err := os.ReadFile(s.path)
if err != nil { if err != nil {
logger.Printf("unable to read %s (%s)", storage.Path, err.Error()) logger.Printf("unable to read %s (%s)", s.path, err.Error())
return nil, err return nil, err
} }
return data, nil return data, nil
} }
func (storage Storage) Write(data []byte) error { func (s Storage) Write(data []byte) error {
dir := filepath.Dir(storage.Path) dir := filepath.Dir(s.path)
_, err := os.Stat(dir) _, err := os.Stat(dir)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -42,9 +50,9 @@ func (storage Storage) Write(data []byte) error {
return err return err
} }
} }
err = os.WriteFile(storage.Path, data, 0644) err = os.WriteFile(s.path, data, 0644)
if err != nil { if err != nil {
logger.Printf("unable to store %s (%s)", storage.Path, err.Error()) logger.Printf("unable to store %s (%s)", s.path, err.Error())
} }
return nil return nil
} }

View File

@@ -119,13 +119,11 @@
function check(checkbox, name) { function check(checkbox, name) {
var obj; var obj;
console.log("name: ", name);
if(checkbox.checked) { if(checkbox.checked) {
obj = '{"name":"' + name + '","state":"on"}' obj = '{"name":"' + name + '","state":"on"}'
} else { } else {
obj = '{"name":"' + name + '","state":"off"}' obj = '{"name":"' + name + '","state":"off"}'
} }
console.log(obj);
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open("PATCH", "/state"); xhr.open("PATCH", "/state");
xhr.setRequestHeader("Content-type", "application/json; charset=utf-8"); xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");