Add status, adjust updater logic, add seconds/ API, fix time formatting to be more human readable

This commit is contained in:
Kelvin Ly 2023-05-15 12:19:40 -04:00
parent ce21b6fa85
commit 008cde9d5e
3 changed files with 139 additions and 55 deletions

View File

@ -26,6 +26,11 @@ function initCharts() {
}
},
axis: {
x: {
tick: {
format: (x) => new Date(x).toISOString()
}
},
y2: {
show: true
}
@ -46,6 +51,13 @@ function initCharts() {
voltage: 'y',
}
},
axis: {
x: {
tick: {
format: (x) => new Date(x).toISOString()
}
},
},
zoom: {
enabled: true
}
@ -64,6 +76,9 @@ function status(msg) {
document.getElementById('status').textContent = msg
}
var autoupdate = true
var chart_updater = null
async function updateCharts() {
status("loading data...")
const latest = await fetch("/api/latest")
@ -76,7 +91,10 @@ async function updateCharts() {
diff = Math.min(diff, max_interval_millis/1000)
var path = "/api/last/weeks/"
var offset = diff
if (diff < 60*60) {
if (diff < 60) {
path = "/api/last/seconds/"
offset = Math.ceil(diff)
} else if (diff < 60*60) {
path = "/api/last/minutes/"
offset = Math.ceil(diff/60)
} else if (diff < 24*60*60) {
@ -117,17 +135,13 @@ async function updateCharts() {
break
}
}
console.log("slice idx ", slice_idx)
//console.log("slice idx ", slice_idx)
temp = temp.slice(slice_idx)
humd = humd.slice(slice_idx)
volts = volts.slice(slice_idx)
time = time.slice(slice_idx)
console.log(time)
console.log(temp)
console.log(humd)
console.log(volts)
chart.load({
columns: [
["time"].concat(time),
@ -143,25 +157,72 @@ async function updateCharts() {
})
status("charts updated")
}
if (autoupdate) {
chart_updater = chartupdater()
} else {
chart_updater = null
}
}
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
async function chartupdater() {
if (chart_updater != null) return
await sleep(5000)
// wait at least two seconds to avoid wasting a lot of bandwidth
const resp = await fetch("/api/update")
chart_updater = null
updateCharts()
}
var status_updater = null
async function updateStatus() {
const status_resp = await fetch("/api/status")
const status = await status_resp.json()
document.getElementById("device-status").textContent = "connected: " + status.connected + ", humdifier: " + (status.humdifier ? "on" : "off")
if (autoupdate) {
status_updater = waitThenUpdateStatus()
} else {
status_updater = null
}
}
async function waitThenUpdateStatus() {
if (status_updater != null) return
await fetch("/api/status_update")
status_updater = null
updateStatus()
}
window.onload = () => {
initCharts()
updateCharts()
updateStatus()
document.getElementById('update').addEventListener('click', (e) => {
updateCharts()
updateStatus()
})
document.getElementById('autoupdate').addEventListener('click', (e) => {
autoupdate = document.getElementById('autoupdate').checked
})
}
</script>
</head>
<body>
<div id="device-status"></div>
<div id="humidity-temp"></div>
<div id="volts"></div>
<div>
<input type=button id=update value="Update"></input>
<span id=status></span>
<form>
<input type=checkbox id=autoupdate checked>Autoupdate</input>
<input type=button id=update value="Update"></input>
<!-- TODO add decimation and window size options -->
<span id=status></span>
</form>
</div>
<div id=device_status></div>
</body>
</html>

View File

@ -30,6 +30,7 @@ type ShroomStatus struct {
HumidifierOn bool
NumConnections int
Wait chan struct{}
StatusWait chan struct{}
}
func (s *ShroomStatus) Update() {
@ -39,6 +40,13 @@ func (s *ShroomStatus) Update() {
s.Wait = make(chan struct{})
}
func (s *ShroomStatus) StatusUpdate() {
s.Lock()
defer s.Unlock()
close(s.StatusWait)
s.StatusWait = make(chan struct{})
}
func parseMsg(line []byte, db *sql.DB, status *ShroomStatus) {
data := ShroomData{
Time: 0,
@ -84,62 +92,66 @@ func InitTcpServer(db *sql.DB, status *ShroomStatus) {
conn, err := ln.Accept()
if err != nil {
log.Println("tcp accept error: ", err)
continue
return
}
status.Lock()
status.NumConnections += 1
status.Unlock()
defer func() {
func() {
status.Lock()
status.NumConnections -= 1
status.NumConnections += 1
status.Unlock()
log.Println("connection disconnected")
}()
status.StatusUpdate()
log.Println("connection started")
defer func() {
status.Lock()
status.NumConnections -= 1
status.Unlock()
status.StatusUpdate()
log.Println("connection disconnected")
}()
go func() {
// TODO deal with the write side of the connection
}()
log.Println("connection started")
// deal with the read side of the connection
buf := make([]byte, 128)
left := buf
go func() {
// TODO deal with the write side of the connection
}()
for {
num_read, err := conn.Read(left)
left = left[num_read:]
//log.Println("buf ", buf)
//log.Println("left ", left)
// deal with the read side of the connection
buf := make([]byte, 128)
left := buf
if err != nil {
log.Println("tcp read error: ", err)
_ = conn.Close()
log.Println("disconnected from client")
break
}
// parse the message to see if it's finished
unread := buf[:len(buf)-len(left)]
for {
num_read, err := conn.Read(left)
left = left[num_read:]
//log.Println("buf ", buf)
//log.Println("left ", left)
for newlinePos(unread) != -1 {
end := newlinePos(unread)
line := unread[:end]
unread = unread[end+1:]
//log.Println("line ", line)
//log.Println("unread ", unread)
// skip empty lines
if len(line) == 0 {
continue
if err != nil {
log.Println("tcp read error: ", err)
_ = conn.Close()
log.Println("disconnected from client")
break
}
// parse the message to see if it's finished
unread := buf[:len(buf)-len(left)]
parseMsg(line, db, status)
for newlinePos(unread) != -1 {
end := newlinePos(unread)
line := unread[:end]
unread = unread[end+1:]
//log.Println("line ", line)
//log.Println("unread ", unread)
// skip empty lines
if len(line) == 0 {
continue
}
parseMsg(line, db, status)
}
// shift the remaining data back to the start of the buffer
copy(buf[:len(unread)], unread)
left = buf[len(unread):]
}
// shift the remaining data back to the start of the buffer
copy(buf[:len(unread)], unread)
left = buf[len(unread):]
}
}()
}
}()
}

View File

@ -42,7 +42,7 @@ func dumpData(db *sql.DB, multiplier int64) func(http.ResponseWriter, *http.Requ
}
offset := int64(count) * multiplier
t := now*1000 - offset
log.Println("req start ", t)
//log.Println("req start ", t)
msg, err := s.GetRows(db, t)
if err != nil {
w.WriteHeader(500)
@ -68,7 +68,8 @@ func main() {
}
status := s.ShroomStatus{
Wait: make(chan struct{}),
Wait: make(chan struct{}),
StatusWait: make(chan struct{}),
}
s.InitTcpServer(db, &status)
@ -81,6 +82,7 @@ func main() {
dumpDay := dumpData(db, 24*60*60*1000)
dumpHour := dumpData(db, 60*60*1000)
dumpMinute := dumpData(db, 60*1000)
dumpSecond := dumpData(db, 1000)
lastPoint := func(w http.ResponseWriter, _req *http.Request) {
msg, err := s.LastTime(db)
@ -124,6 +126,13 @@ func main() {
}
w.Write([]byte("ok"))
}
statusUpdateHandler := func(w http.ResponseWriter, req *http.Request) {
stillopen := true
for stillopen {
_, stillopen = <-status.StatusWait
}
w.Write([]byte("ok"))
}
http.Handle("/d/", http.StripPrefix("/d/", http.FileServer(http.Dir("./dev"))))
http.Handle("/", http.FileServer(http.FS(contentSub)))
@ -131,10 +140,12 @@ func main() {
http.HandleFunc("/api/last/days/", dumpDay)
http.HandleFunc("/api/last/hours/", dumpHour)
http.HandleFunc("/api/last/minutes/", dumpMinute)
http.HandleFunc("/api/last/seconds/", dumpSecond)
http.HandleFunc("/api/latest", lastPoint)
http.HandleFunc("/api/status", getStatus)
http.HandleFunc("/api/admin", adminHandler)
http.HandleFunc("/api/update", updateHandler)
http.HandleFunc("/api/status_update", statusUpdateHandler)
// TODO periodically clear old entries from the database