Add status, adjust updater logic, add seconds/ API, fix time formatting to be more human readable
This commit is contained in:
parent
ce21b6fa85
commit
008cde9d5e
79
dev/dev.htm
79
dev/dev.htm
|
@ -26,6 +26,11 @@ function initCharts() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
axis: {
|
axis: {
|
||||||
|
x: {
|
||||||
|
tick: {
|
||||||
|
format: (x) => new Date(x).toISOString()
|
||||||
|
}
|
||||||
|
},
|
||||||
y2: {
|
y2: {
|
||||||
show: true
|
show: true
|
||||||
}
|
}
|
||||||
|
@ -46,6 +51,13 @@ function initCharts() {
|
||||||
voltage: 'y',
|
voltage: 'y',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
axis: {
|
||||||
|
x: {
|
||||||
|
tick: {
|
||||||
|
format: (x) => new Date(x).toISOString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
|
@ -64,6 +76,9 @@ function status(msg) {
|
||||||
document.getElementById('status').textContent = msg
|
document.getElementById('status').textContent = msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var autoupdate = true
|
||||||
|
var chart_updater = null
|
||||||
|
|
||||||
async function updateCharts() {
|
async function updateCharts() {
|
||||||
status("loading data...")
|
status("loading data...")
|
||||||
const latest = await fetch("/api/latest")
|
const latest = await fetch("/api/latest")
|
||||||
|
@ -76,7 +91,10 @@ async function updateCharts() {
|
||||||
diff = Math.min(diff, max_interval_millis/1000)
|
diff = Math.min(diff, max_interval_millis/1000)
|
||||||
var path = "/api/last/weeks/"
|
var path = "/api/last/weeks/"
|
||||||
var offset = diff
|
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/"
|
path = "/api/last/minutes/"
|
||||||
offset = Math.ceil(diff/60)
|
offset = Math.ceil(diff/60)
|
||||||
} else if (diff < 24*60*60) {
|
} else if (diff < 24*60*60) {
|
||||||
|
@ -117,17 +135,13 @@ async function updateCharts() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("slice idx ", slice_idx)
|
//console.log("slice idx ", slice_idx)
|
||||||
|
|
||||||
temp = temp.slice(slice_idx)
|
temp = temp.slice(slice_idx)
|
||||||
humd = humd.slice(slice_idx)
|
humd = humd.slice(slice_idx)
|
||||||
volts = volts.slice(slice_idx)
|
volts = volts.slice(slice_idx)
|
||||||
time = time.slice(slice_idx)
|
time = time.slice(slice_idx)
|
||||||
|
|
||||||
console.log(time)
|
|
||||||
console.log(temp)
|
|
||||||
console.log(humd)
|
|
||||||
console.log(volts)
|
|
||||||
chart.load({
|
chart.load({
|
||||||
columns: [
|
columns: [
|
||||||
["time"].concat(time),
|
["time"].concat(time),
|
||||||
|
@ -143,25 +157,72 @@ async function updateCharts() {
|
||||||
})
|
})
|
||||||
status("charts updated")
|
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 = () => {
|
window.onload = () => {
|
||||||
initCharts()
|
initCharts()
|
||||||
updateCharts()
|
updateCharts()
|
||||||
|
updateStatus()
|
||||||
|
|
||||||
document.getElementById('update').addEventListener('click', (e) => {
|
document.getElementById('update').addEventListener('click', (e) => {
|
||||||
updateCharts()
|
updateCharts()
|
||||||
|
updateStatus()
|
||||||
|
})
|
||||||
|
document.getElementById('autoupdate').addEventListener('click', (e) => {
|
||||||
|
autoupdate = document.getElementById('autoupdate').checked
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="device-status"></div>
|
||||||
<div id="humidity-temp"></div>
|
<div id="humidity-temp"></div>
|
||||||
<div id="volts"></div>
|
<div id="volts"></div>
|
||||||
<div>
|
<div>
|
||||||
<input type=button id=update value="Update"></input>
|
<form>
|
||||||
<span id=status></span>
|
<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>
|
||||||
<div id=device_status></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -30,6 +30,7 @@ type ShroomStatus struct {
|
||||||
HumidifierOn bool
|
HumidifierOn bool
|
||||||
NumConnections int
|
NumConnections int
|
||||||
Wait chan struct{}
|
Wait chan struct{}
|
||||||
|
StatusWait chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShroomStatus) Update() {
|
func (s *ShroomStatus) Update() {
|
||||||
|
@ -39,6 +40,13 @@ func (s *ShroomStatus) Update() {
|
||||||
s.Wait = make(chan struct{})
|
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) {
|
func parseMsg(line []byte, db *sql.DB, status *ShroomStatus) {
|
||||||
data := ShroomData{
|
data := ShroomData{
|
||||||
Time: 0,
|
Time: 0,
|
||||||
|
@ -84,62 +92,66 @@ func InitTcpServer(db *sql.DB, status *ShroomStatus) {
|
||||||
conn, err := ln.Accept()
|
conn, err := ln.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("tcp accept error: ", err)
|
log.Println("tcp accept error: ", err)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
status.Lock()
|
func() {
|
||||||
status.NumConnections += 1
|
|
||||||
status.Unlock()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
status.Lock()
|
status.Lock()
|
||||||
status.NumConnections -= 1
|
status.NumConnections += 1
|
||||||
status.Unlock()
|
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() {
|
log.Println("connection started")
|
||||||
// TODO deal with the write side of the connection
|
|
||||||
}()
|
|
||||||
|
|
||||||
// deal with the read side of the connection
|
go func() {
|
||||||
buf := make([]byte, 128)
|
// TODO deal with the write side of the connection
|
||||||
left := buf
|
}()
|
||||||
|
|
||||||
for {
|
// deal with the read side of the connection
|
||||||
num_read, err := conn.Read(left)
|
buf := make([]byte, 128)
|
||||||
left = left[num_read:]
|
left := buf
|
||||||
//log.Println("buf ", buf)
|
|
||||||
//log.Println("left ", left)
|
|
||||||
|
|
||||||
if err != nil {
|
for {
|
||||||
log.Println("tcp read error: ", err)
|
num_read, err := conn.Read(left)
|
||||||
_ = conn.Close()
|
left = left[num_read:]
|
||||||
log.Println("disconnected from client")
|
//log.Println("buf ", buf)
|
||||||
break
|
//log.Println("left ", left)
|
||||||
}
|
|
||||||
// parse the message to see if it's finished
|
|
||||||
unread := buf[:len(buf)-len(left)]
|
|
||||||
|
|
||||||
for newlinePos(unread) != -1 {
|
if err != nil {
|
||||||
end := newlinePos(unread)
|
log.Println("tcp read error: ", err)
|
||||||
line := unread[:end]
|
_ = conn.Close()
|
||||||
unread = unread[end+1:]
|
log.Println("disconnected from client")
|
||||||
|
break
|
||||||
//log.Println("line ", line)
|
|
||||||
//log.Println("unread ", unread)
|
|
||||||
// skip empty lines
|
|
||||||
if len(line) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
// 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):]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ func dumpData(db *sql.DB, multiplier int64) func(http.ResponseWriter, *http.Requ
|
||||||
}
|
}
|
||||||
offset := int64(count) * multiplier
|
offset := int64(count) * multiplier
|
||||||
t := now*1000 - offset
|
t := now*1000 - offset
|
||||||
log.Println("req start ", t)
|
//log.Println("req start ", t)
|
||||||
msg, err := s.GetRows(db, t)
|
msg, err := s.GetRows(db, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
|
@ -68,7 +68,8 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
status := s.ShroomStatus{
|
status := s.ShroomStatus{
|
||||||
Wait: make(chan struct{}),
|
Wait: make(chan struct{}),
|
||||||
|
StatusWait: make(chan struct{}),
|
||||||
}
|
}
|
||||||
s.InitTcpServer(db, &status)
|
s.InitTcpServer(db, &status)
|
||||||
|
|
||||||
|
@ -81,6 +82,7 @@ func main() {
|
||||||
dumpDay := dumpData(db, 24*60*60*1000)
|
dumpDay := dumpData(db, 24*60*60*1000)
|
||||||
dumpHour := dumpData(db, 60*60*1000)
|
dumpHour := dumpData(db, 60*60*1000)
|
||||||
dumpMinute := dumpData(db, 60*1000)
|
dumpMinute := dumpData(db, 60*1000)
|
||||||
|
dumpSecond := dumpData(db, 1000)
|
||||||
|
|
||||||
lastPoint := func(w http.ResponseWriter, _req *http.Request) {
|
lastPoint := func(w http.ResponseWriter, _req *http.Request) {
|
||||||
msg, err := s.LastTime(db)
|
msg, err := s.LastTime(db)
|
||||||
|
@ -124,6 +126,13 @@ func main() {
|
||||||
}
|
}
|
||||||
w.Write([]byte("ok"))
|
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("/d/", http.StripPrefix("/d/", http.FileServer(http.Dir("./dev"))))
|
||||||
http.Handle("/", http.FileServer(http.FS(contentSub)))
|
http.Handle("/", http.FileServer(http.FS(contentSub)))
|
||||||
|
@ -131,10 +140,12 @@ func main() {
|
||||||
http.HandleFunc("/api/last/days/", dumpDay)
|
http.HandleFunc("/api/last/days/", dumpDay)
|
||||||
http.HandleFunc("/api/last/hours/", dumpHour)
|
http.HandleFunc("/api/last/hours/", dumpHour)
|
||||||
http.HandleFunc("/api/last/minutes/", dumpMinute)
|
http.HandleFunc("/api/last/minutes/", dumpMinute)
|
||||||
|
http.HandleFunc("/api/last/seconds/", dumpSecond)
|
||||||
http.HandleFunc("/api/latest", lastPoint)
|
http.HandleFunc("/api/latest", lastPoint)
|
||||||
http.HandleFunc("/api/status", getStatus)
|
http.HandleFunc("/api/status", getStatus)
|
||||||
http.HandleFunc("/api/admin", adminHandler)
|
http.HandleFunc("/api/admin", adminHandler)
|
||||||
http.HandleFunc("/api/update", updateHandler)
|
http.HandleFunc("/api/update", updateHandler)
|
||||||
|
http.HandleFunc("/api/status_update", statusUpdateHandler)
|
||||||
|
|
||||||
// TODO periodically clear old entries from the database
|
// TODO periodically clear old entries from the database
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue