Skip to content

Commit 0e158da

Browse files
committed
A bunch of improvements at the Lake:
* Calculating and logging power available at panel when throttled by controller. Same as for Arduino-LCD. Determined it was PWM (Pulse Width Modulation) kicking in when more power is available at the panel than required to charge the battery. * Power usage chart broken down much more: batter in/out, etc... * Power usage chart has date range selection. * Fliped LCD display to accomodate mounting orientation. * Attempt at logging crashes. * Fixed sensor read period logic to actually work. * Rough calibration for solar system at the Lake.
1 parent 26e6726 commit 0e158da

File tree

8 files changed

+309
-117
lines changed

8 files changed

+309
-117
lines changed

esp32-micropython/client/components/App.svelte

+18-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,24 @@
205205
<div class="mdc-layout-grid">
206206
<div class="mdc-layout-grid__inner">
207207
<div class="mdc-layout-grid__cell--span-6">
208-
<Realtime websocket={websocket}/>
208+
<Realtime websocket={websocket} title="Realtime" y_label="Power [W]"
209+
series={[
210+
{
211+
index: 1,
212+
label: "Actual In",
213+
color: "blue",
214+
},
215+
{
216+
index: 2,
217+
label: "Total Usage",
218+
color: "orange",
219+
},
220+
{
221+
index: 7,
222+
label: "Total In",
223+
color: "deepskyblue",
224+
},
225+
]}/>
209226
</div>
210227
<div class="mdc-layout-grid__cell--span-6">
211228
<PowerUsage progress={getting_data_progress} data={historical_data} error={getting_data_error}/>

esp32-micropython/client/components/PowerUsage.svelte

+149-46
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,116 @@
11
<script>
22
import { onMount } from 'svelte';
3+
import Chip, {Set, Icon, Text} from '@smui/chips';
34
import LinearProgress from '@smui/linear-progress';
45
56
export let data = [];
67
export let progress = false;
78
export let error = null;
89
10+
let time_choice = "Today";
11+
912
let canvas;
1013
let chart;
1114
1215
function processData(data, bucketize){
13-
var buckets = {};
14-
var in_energy = [];
15-
var out_energy = [];
16+
let buckets = {};
17+
let panel_in = [];
18+
let panel_usage = [];
19+
let surplus = [];
20+
let in_bat = [];
21+
let out_bat = [];
1622
1723
for (const values of data){
1824
if (values[0] && values[1] && values[2]) {
19-
var time = new Date(values[0]);
25+
let time = new Date(values[0]);
2026
21-
var bucket = bucketize(time);
22-
var bucket_value = buckets[bucket];
27+
let bucket = bucketize(time);
28+
let bucket_value = buckets[bucket];
2329
if (typeof(bucket_value) == "undefined"){
2430
// console.log("Initializing bucket " + new Date(bucket));
25-
bucket_value = {"in": 0, "out": 0};
31+
bucket_value = {"in": 0, "out": 0, "avail": 0};
2632
buckets[bucket] = bucket_value;
2733
}
2834
2935
// Converting Watt-Seconds to Watt-Hours (Wh) as we accumulate:
3036
bucket_value["in"] += values[1] / 60;
3137
bucket_value["out"] += values[2] / 60;
38+
bucket_value["avail"] += values[3] / 60;
3239
}
3340
}
3441
3542
for (const key of Object.keys(buckets).sort()){
36-
var time = new Date(Number(key));
37-
in_energy.push({x: time, y: Math.round(buckets[key]["in"])});
38-
out_energy.push({x: time, y: Math.round(-buckets[key]["out"])});
43+
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"]);
46+
let in_bat_val = 0;
47+
let out_bat_val = 0;
48+
if (net_bat_val >= 0){
49+
in_bat_val = net_bat_val;
50+
} else {
51+
out_bat_val = net_bat_val;
52+
}
53+
in_bat.push({x: time, y: in_bat_val});
54+
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});
3957
}
4058
41-
return [in_energy, out_energy];
59+
return [in_bat, out_bat, panel_in, panel_usage, surplus];
4260
}
4361
4462
function bucketize_15_min(value){
45-
var rounded_minutes = Math.floor(value.getMinutes() / 15) * 15;
46-
var bucket_date = new Date(value)
63+
let rounded_minutes = Math.floor(value.getMinutes() / 15) * 15;
64+
let bucket_date = new Date(value)
4765
bucket_date.setMinutes(rounded_minutes);
4866
bucket_date.setSeconds(0);
4967
bucket_date.setMilliseconds(0);
5068
return bucket_date.getTime();
5169
}
5270
53-
function createChart(ctx, in_energy, out_energy){
54-
var min_time = new Date();
55-
min_time.setHours(0);
56-
min_time.setMinutes(0);
57-
min_time.setSeconds(0);
58-
min_time.setMilliseconds(0);
59-
60-
var max_time = new Date(min_time);
61-
max_time.setDate(min_time.getDate() + 1);
71+
function createChart(ctx){
72+
const [min_time, max_time] = calculateDateRange(time_choice);
6273
63-
64-
var chart = new Chart(ctx, {
74+
let chart = new Chart(ctx, {
6575
type: 'bar',
6676
data: {
67-
datasets: [{
68-
label: "In",
69-
data: [],
70-
fill: false,
71-
borderColor: "blue",
72-
backgroundColor: "blue",
73-
}, {
74-
label: "Out",
75-
data: [],
76-
fill: false,
77-
borderColor: "orange",
78-
backgroundColor: "orange",
79-
}]
77+
datasets: [
78+
{
79+
label: "Battery Charging",
80+
data: [],
81+
fill: false,
82+
borderColor: "lawngreen",
83+
backgroundColor: "lawngreen",
84+
},
85+
{
86+
label: "Battery Usage",
87+
data: [],
88+
fill: false,
89+
borderColor: "orangered",
90+
backgroundColor: "orangered",
91+
},
92+
{
93+
label: "Panel In",
94+
data: [],
95+
fill: false,
96+
borderColor: "blue",
97+
backgroundColor: "blue",
98+
},
99+
{
100+
label: "Panel Usage",
101+
data: [],
102+
fill: false,
103+
borderColor: "orange",
104+
backgroundColor: "orange",
105+
},
106+
{
107+
label: "Panel Surplus",
108+
data: [],
109+
fill: false,
110+
borderColor: "deepskyblue",
111+
backgroundColor: "deepskyblue",
112+
},
113+
]
80114
},
81115
options: {
82116
scales: {
@@ -115,8 +149,8 @@
115149
116150
function updateDataInPlace(existing_data, new_data){
117151
for (const new_value of new_data){
118-
var found = false;
119-
for (var existing_value of existing_data){
152+
let found = false;
153+
for (let existing_value of existing_data){
120154
if (existing_value.x.getTime() == new_value.x.getTime()){
121155
found = true;
122156
existing_value.y = new_value.y;
@@ -129,16 +163,83 @@
129163
}
130164
}
131165
132-
function updateChart(data){
166+
function updateChartData(data){
133167
if (chart !== undefined && data.length > 0){
134-
let [in_energy, out_energy] = processData(data, bucketize_15_min);
135-
updateDataInPlace(chart.data.datasets[0].data, in_energy);
136-
updateDataInPlace(chart.data.datasets[1].data, out_energy);
168+
let datasets_data = processData(data, bucketize_15_min);
169+
for (const [i, dataset_data] of datasets_data.entries()){
170+
updateDataInPlace(chart.data.datasets[i].data, dataset_data);
171+
}
172+
chart.update();
173+
}
174+
}
175+
176+
177+
function calculateDateRange(time_choice){
178+
let min_time;
179+
let max_time;
180+
181+
switch (time_choice){
182+
case "Today":
183+
min_time = new Date();
184+
min_time.setHours(0);
185+
min_time.setMinutes(0);
186+
min_time.setSeconds(0);
187+
min_time.setMilliseconds(0);
188+
189+
max_time = new Date(min_time);
190+
max_time.setDate(min_time.getDate() + 1);
191+
return [min_time, max_time];
192+
193+
case "Yesterday":
194+
min_time = new Date();
195+
min_time.setHours(0);
196+
min_time.setMinutes(0);
197+
min_time.setSeconds(0);
198+
min_time.setMilliseconds(0);
199+
min_time.setDate(min_time.getDate() - 1);
200+
201+
max_time = new Date(min_time);
202+
max_time.setDate(min_time.getDate() + 1);
203+
return [min_time, max_time];
204+
205+
case "Last 3 days":
206+
min_time = new Date();
207+
min_time.setHours(0);
208+
min_time.setMinutes(0);
209+
min_time.setSeconds(0);
210+
min_time.setMilliseconds(0);
211+
min_time.setDate(min_time.getDate() - 2);
212+
213+
max_time = new Date(min_time);
214+
max_time.setDate(min_time.getDate() + 3);
215+
return [min_time, max_time];
216+
217+
case "Last 7 days":
218+
min_time = new Date();
219+
min_time.setHours(0);
220+
min_time.setMinutes(0);
221+
min_time.setSeconds(0);
222+
min_time.setMilliseconds(0);
223+
min_time.setDate(min_time.getDate() - 6);
224+
225+
max_time = new Date(min_time);
226+
max_time.setDate(min_time.getDate() + 7);
227+
return [min_time, max_time];
228+
229+
}
230+
}
231+
232+
function updateChartDateRange(time_choice){
233+
if (chart !== undefined){
234+
let ticks = chart.options.scales.xAxes[0].ticks;
235+
[ticks.min, ticks.max] = calculateDateRange(time_choice);
236+
137237
chart.update();
138238
}
139239
}
140240
141-
$: updateChart(data); // Update the chart anytime data changes.
241+
$: updateChartData(data); // Update the chart anytime data changes.
242+
$: updateChartDateRange(time_choice) // Update the axis date range anytime the selection changes.
142243
143244
async function main(){
144245
chart = createChart(canvas.getContext('2d'));
@@ -147,8 +248,10 @@
147248
onMount(main);
148249
149250
</script>
150-
151-
<h2>Today's Power Usage</h2>
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>
152255
<canvas bind:this={canvas}/>
153256

154257
<LinearProgress

esp32-micropython/hardware/bom.xlsx

186 Bytes
Binary file not shown.

esp32-micropython/server/logger.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import os
2+
3+
log_directory = "static/logs"
4+
5+
try:
6+
os.mkdir(log_directory)
7+
except Exception:
8+
pass # Directory already exists
9+
10+
def log_exception(msg):
11+
with open("%s/%s" % (log_directory, "exception.log"), "a") as f:
12+
f.write(msg)

0 commit comments

Comments
 (0)