Skip to content

Commit abcf063

Browse files
committed
Stability and other improvements at the lake:
* Changed meta.json to meta.csv to avoid memory issues when parsing the json once the file gets big. * Power Chart now automatically refreshes and has a refresh button. Includes caching so only new data is fetched and added. * Increased max log files from 48 (~ 2 days) to 170 (~1 week).
1 parent 0e158da commit abcf063

File tree

9 files changed

+246
-87
lines changed

9 files changed

+246
-87
lines changed

esp32-micropython/Makefile

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
SERIAL_PORT=/dev/ttyS4
22
BAUD=115200
33

4-
.PHONY: rshell upload upload_client upload_server rshell_repl repl static
4+
.PHONY: rshell upload upload_client upload_server rshell_repl repl static pull_logs backup_data upload_keep
55

66
rshell:
77
rshell -p $(SERIAL_PORT)
@@ -12,6 +12,15 @@ upload_server:
1212
upload_client:
1313
rshell -p $(SERIAL_PORT) rsync --mirror dist/ /pyboard/static/
1414

15+
backup_data:
16+
rshell -p $(SERIAL_PORT) rsync --mirror /pyboard/static/data/ dist/data/
17+
18+
pull_logs:
19+
rshell -p $(SERIAL_PORT) rsync --mirror /pyboard/static/data/ dist/logs/
20+
21+
22+
23+
upload_keep: static pull_logs backup_data upload_client upload_server
1524

1625
upload: static upload_client upload_server
1726

esp32-micropython/client/common/DataProcessing.js

+20-14
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
1-
export function processLogResponse(response_text, offset){
1+
export function processLogResponse(response_text, offset, min_time){
22
let data = [];
33
const records = response_text.split("\n");
44
for (const record of records){
55
let values = record.split(",");
6+
if (values.length < 3){
7+
continue;
8+
}
69
values[0] = Number(values[0]) + offset;
710
values[1] = Number(values[1]);
811
values[2] = Number(values[2]);
9-
data.push(values);
12+
if (min_time === undefined || min_time === null || values[0] > min_time){
13+
data.push(values);
14+
}
1015
}
1116
return data;
1217
}
1318

1419
export function processDataMeta(meta){
1520
let logs = [];
16-
for (const [log_number, log_meta] of Object.entries(meta["logs"])){
21+
for (const record of meta.split("\n")){
22+
let values = record.split(",");
23+
if (record.length < 4){
24+
continue;
25+
}
26+
let start_time_offset = Number(values[3]);
27+
if (isNaN(start_time_offset)){
28+
start_time_offset = null;
29+
}
1730
logs.push({
18-
number: log_number,
19-
start_time_offset: log_meta.start_time_offset,
20-
start_time: log_meta.start_time,
31+
number: Number(values[0]),
32+
active: Boolean(Number(values[1])),
33+
start_time: Number(values[2]),
34+
start_time_offset: start_time_offset,
2135
})
2236
}
23-
24-
// Skip data that doesn't have complete time information:
25-
let original_log_file_count = logs.length;
26-
logs = logs.filter(function(a){
27-
return !(a.start_time_offset === null);
28-
});
29-
console.log(`Number of log files: total: ${original_log_file_count} valid: ${logs.length}`);
30-
3137
return logs;
3238
}
3339

esp32-micropython/client/components/App.svelte

+91-17
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,32 @@
5252
// Number between 0 and 1 to indicate progress.
5353
let getting_data_error = null;
5454
55+
let fetched_logs = [];
56+
5557
async function* getData(){
5658
getting_data_progress = null;
57-
let response = await fetch(API_URL + '/data/meta.json');
59+
let response = await fetch(API_URL + '/data/meta.csv');
5860
if (!response.ok){
5961
throw new Error(response.statusText);
6062
}
61-
const meta = await response.json();
63+
const meta = await response.text();
6264
let logs = processDataMeta(meta);
65+
let original_log_file_count = logs.length;
66+
67+
// Skip data that doesn't have complete time information:
68+
logs = logs.filter(function(a){
69+
return !(a.start_time_offset === null);
70+
});
71+
72+
let valid_log_file_count = logs.length;
73+
74+
// Skip data that we already have:
75+
logs = logs.filter(function(a){
76+
return !fetched_logs.some(function(b){
77+
return b.active == false && a.number == b.number && a.start_time_offset == b.start_time_offset && a.start_time == b.start_time;
78+
})
79+
});
80+
console.log(`Number of log files: total: ${original_log_file_count} valid: ${valid_log_file_count} new: ${logs.length}`);
6381
6482
// Load the newest data first as that's most likely to be viewed first:
6583
logs.sort(function(a, b) {
@@ -82,43 +100,92 @@
82100
if (new_progress > getting_data_progress){
83101
getting_data_progress = Math.round(new_progress * 100) / 100;
84102
// Rounding seems to fix a really weird problem where ther progress bar jumps
85-
// around slightly when during chart updates. It also smoothes things out a
103+
// around slightly when doing chart updates. It also smoothes things out a
86104
// bit and makes it look better overall.
87105
}
88106
});
89107
108+
// Seeing if we have downloaded part of this log file before and getting the time where we left off:
109+
let previous_logs = fetched_logs.filter(function(a){
110+
return a.active && a.number == log.number && a.start_time == log.start_time && a.start_time_offset == log.start_time_offset;
111+
});
112+
let last_processed_time = null;
113+
if (previous_logs.length > 0){
114+
let previous_log = previous_logs[previous_logs.length-1];
115+
last_processed_time = previous_log.last_processed_time;
116+
}
117+
90118
// const response_text = await response.text();
91-
yield await processLogResponse(response_text, log.start_time_offset);
119+
let data = await processLogResponse(response_text, log.start_time_offset, last_processed_time);
120+
if (data.length > 0){
121+
log.last_processed_time = data[data.length-1][0];
122+
fetched_logs.push(log);
123+
}
124+
console.log(`New Data points from ${log.number} since ${last_processed_time}: ${data.length}`);
125+
yield data;
126+
}
127+
}
128+
129+
let historical_fetcher = null;
130+
let historical_fetcher_setup = false;
131+
let pull_historical_data_period_seconds = 300;
132+
133+
function setup_historical_fetcher(){
134+
if (historical_fetcher_setup === false){
135+
historical_fetcher_setup = true;
136+
document.addEventListener("visibilitychange", function (){
137+
if (historical_fetcher != null){
138+
clearInterval(historical_fetcher);
139+
historical_fetcher = null;
140+
}
141+
if (!document.hidden){
142+
setup_historical_fetcher();
143+
}
144+
}, false);
92145
}
146+
147+
get_historical_data();
148+
historical_fetcher = setInterval(function() {
149+
get_historical_data();
150+
}, pull_historical_data_period_seconds * 1000);
93151
}
94152
153+
let getting_data = false;
154+
95155
async function get_historical_data(){
156+
if (getting_data){
157+
console.log("Still getting data from last time.");
158+
return;
159+
}
160+
getting_data = true;
96161
try{
97162
for await (const data of getData()) {
98163
historical_data = [...historical_data, ...data];
99164
}
165+
getting_data_error = null;
100166
} catch(e){
101167
getting_data_error = e.message;
102168
console.log(`Error getting data: ${getting_data_error}`);
103169
}
170+
getting_data = false;
104171
}
105172
106173
let websocket = null;
107174
let websocket_retry_period = 1;
108175
let close_websocket_after_tab_changed_for_seconds = 50;
109-
let visibilitychange_setup = false;
110-
let last_visibility_change_timeout = null;
176+
let websocket_visibilitychange_setup = false;
177+
let websocket_last_visibility_change_timeout = null;
111178
112179
function setup_realtime_websocket(){
113-
if (visibilitychange_setup === false) {
114-
visibilitychange_setup = true;
180+
if (websocket_visibilitychange_setup === false) {
181+
websocket_visibilitychange_setup = true;
115182
document.addEventListener("visibilitychange", function (){
116-
if (last_visibility_change_timeout != null){
117-
clearTimeout(last_visibility_change_timeout);
118-
last_visibility_change_timeout = null;
183+
if (websocket_last_visibility_change_timeout != null){
184+
clearTimeout(websocket_last_visibility_change_timeout);
185+
websocket_last_visibility_change_timeout = null;
119186
}
120187
if (document.hidden){
121-
last_visibility_change_timeout = setTimeout(function() {
188+
websocket_last_visibility_change_timeout = setTimeout(function() {
122189
if (websocket !== null){
123190
websocket.close();
124191
}
@@ -188,7 +255,7 @@
188255
}
189256
loading = false;
190257
get_stats();
191-
get_historical_data();
258+
setup_historical_fetcher();
192259
setup_realtime_websocket();
193260
194261
}
@@ -202,9 +269,18 @@
202269
<FullPageProgress action={loading_action} action_verb={loading_verb} error={loading_error}/>
203270
{:else}
204271
<h1>Power Meter</h1>
272+
205273
<div class="mdc-layout-grid">
206274
<div class="mdc-layout-grid__inner">
207-
<div class="mdc-layout-grid__cell--span-6">
275+
<div class="mdc-layout-grid__cell--span-12">
276+
<PowerUsage progress={getting_data_progress} data={historical_data} error={getting_data_error} refresh_data={get_historical_data}/>
277+
</div>
278+
</div>
279+
</div>
280+
281+
<div class="mdc-layout-grid">
282+
<div class="mdc-layout-grid__inner">
283+
<div class="mdc-layout-grid__cell--span-12">
208284
<Realtime websocket={websocket} title="Realtime" y_label="Power [W]"
209285
series={[
210286
{
@@ -224,11 +300,9 @@
224300
},
225301
]}/>
226302
</div>
227-
<div class="mdc-layout-grid__cell--span-6">
228-
<PowerUsage progress={getting_data_progress} data={historical_data} error={getting_data_error}/>
229-
</div>
230303
</div>
231304
</div>
305+
232306
<div class="mdc-layout-grid">
233307
<div class="mdc-layout-grid__inner">
234308
<div class="mdc-layout-grid__cell--span-6">

esp32-micropython/client/components/PowerUsage.svelte

+29-8
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@
22
import { onMount } from 'svelte';
33
import Chip, {Set, Icon, Text} from '@smui/chips';
44
import LinearProgress from '@smui/linear-progress';
5+
import IconButton from '@smui/icon-button';
6+
import Refresh from "svelte-material-icons/Refresh.svelte";
57
68
export let data = [];
79
export let progress = false;
810
export let error = null;
11+
export let refresh_data = function(){};
912
1013
let time_choice = "Today";
1114
1215
let canvas;
1316
let chart;
1417
18+
function round_for_graph(value){
19+
return Math.round(value * 10) / 10;
20+
}
21+
1522
function processData(data, bucketize){
1623
let buckets = {};
1724
let panel_in = [];
@@ -41,8 +48,8 @@
4148
4249
for (const key of Object.keys(buckets).sort()){
4350
let time = new Date(Number(key));
44-
surplus.push({x: time, y: Math.round(buckets[key]["avail"] - buckets[key]["in"])}); // Stacked bar chart so only want to show the extra available.
45-
let net_bat_val = Math.round(buckets[key]["in"] - buckets[key]["out"]);
51+
surplus.push({x: time, y: round_for_graph(buckets[key]["avail"] - buckets[key]["in"])}); // Stacked bar chart so only want to show the extra available.
52+
let net_bat_val = round_for_graph(buckets[key]["in"] - buckets[key]["out"]);
4653
let in_bat_val = 0;
4754
let out_bat_val = 0;
4855
if (net_bat_val >= 0){
@@ -52,8 +59,8 @@
5259
}
5360
in_bat.push({x: time, y: in_bat_val});
5461
out_bat.push({x: time, y: out_bat_val});
55-
panel_in.push({x: time, y: Math.round(buckets[key]["in"]) - in_bat_val});
56-
panel_usage.push({x: time, y: Math.round(-buckets[key]["out"]) - out_bat_val});
62+
panel_in.push({x: time, y: round_for_graph(buckets[key]["in"]) - in_bat_val});
63+
panel_usage.push({x: time, y: round_for_graph(-buckets[key]["out"]) - out_bat_val});
5764
}
5865
5966
return [in_bat, out_bat, panel_in, panel_usage, surplus];
@@ -226,6 +233,16 @@
226233
max_time.setDate(min_time.getDate() + 7);
227234
return [min_time, max_time];
228235
236+
default:
237+
min_time = new Date();
238+
min_time.setHours(0);
239+
min_time.setMinutes(0);
240+
min_time.setSeconds(0);
241+
min_time.setMilliseconds(0);
242+
243+
max_time = new Date(min_time);
244+
max_time.setDate(min_time.getDate() + 1);
245+
return [min_time, max_time];
229246
}
230247
}
231248
@@ -248,10 +265,14 @@
248265
onMount(main);
249266
250267
</script>
251-
<h2>{time_choice}{time_choice.endsWith("s") ? "'" : "'s"} Power Usage</h2>
252-
<Set chips={['Last 7 days', 'Last 3 days', 'Yesterday', 'Today']} let:chip choice bind:selected={time_choice}>
253-
<Chip tabindex="0">{chip}</Chip>
254-
</Set>
268+
<h2>{(time_choice || "Today")}{(time_choice || "Today").endsWith("s") ? "'" : "'s"} Power Usage</h2>
269+
<div style="display:flex;">
270+
<Set chips={['Last 7 days', 'Last 3 days', 'Yesterday', 'Today']} let:chip choice bind:selected={time_choice}>
271+
<Chip tabindex="0">{chip}</Chip>
272+
</Set>
273+
<!-- <IconButton class="material-icons" on:click={refresh_data}>refresh</IconButton> -->
274+
<IconButton style="margin-left:auto" on:click={refresh_data()}><Refresh/></IconButton>
275+
</div>
255276
<canvas bind:this={canvas}/>
256277

257278
<LinearProgress

esp32-micropython/package-lock.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esp32-micropython/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"parcel-plugin-svelte": "^4.0.6",
2222
"sass": "^1.26.7",
2323
"svelte": "^3.23.0",
24+
"svelte-material-icons": "^1.0.3",
2425
"svelte-material-ui": "^1.0.0-beta.21"
2526
},
2627
"browserslist": [

esp32-micropython/server/logger.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
pass # Directory already exists
99

1010
def log_exception(msg):
11-
with open("%s/%s" % (log_directory, "exception.log"), "a") as f:
11+
with open("%s/%s" % (log_directory, "exception.txt"), "a") as f:
1212
f.write(msg)

esp32-micropython/server/main.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def ws_close_callback(ws):
220220
mws.StaticCacheByPath = [
221221
("static/data/", 0),
222222
("static/index.html", 0),
223+
("static/logs/", 0),
223224
("static/", 2),
224225
]
225226
mws.StaticHeaders = {"Access-Control-Allow-Origin": "*"}
@@ -286,8 +287,9 @@ def check_for_reboot():
286287
# disp.fill_rect(0, 40, 128, 128, 0)
287288
# disp.text("Connected: %s" % clients, 0, 40)
288289
# disp.show()
290+
289291
except Exception as e:
290-
err = "Unhandled exception in main loop: %s" % e
292+
err = "Unhandled exception in main loop: %s\n" % e
291293
print(err)
292294
log_exception(err)
293295
raise

0 commit comments

Comments
 (0)