Skip to content

Commit

Permalink
phase_switcher: Multi value chart (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsches1 committed Oct 21, 2023
1 parent 7f19e1e commit 5e06939
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 50 deletions.
33 changes: 22 additions & 11 deletions software/src/modules/phase_switcher/multi_value_history.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,22 @@ void MultiValueHistory::setup()

for(int j = 0; j < MULTI_VALUE_HISTORY_NUMBER_OF_VALUES; ++j){
for (size_t i = 0; i < history[i].size(); ++i) {
//float f = 5000.0 * sin(PI/120.0 * i) + 5000.0;
// !!! FIXME
float f = 5000.0f * sinf(static_cast<float>(PI)/120.0f * static_cast<float>(i)) + 5000.0f;
switch(j){
case 0:
val_min = static_cast<int16_t>(f);
break;

case 1:
val_min = static_cast<int16_t>(f * -1.0f + 10000.0f);
break;

default:
val_min = static_cast<int16_t>((i) / 240 * 3000);
}

// !!! FIXME
// Use negative state to mark that these are pre-filled.
history[j].push(val_min);
}
Expand Down Expand Up @@ -153,10 +168,6 @@ void MultiValueHistory::register_urls(String base_url_)

void MultiValueHistory::add_sample(float sample[MULTI_VALUE_HISTORY_NUMBER_OF_VALUES])
{
// !!! FIXME
return;


MULTI_VALUE_HISTORY_VALUE_TYPE val_min = std::numeric_limits<MULTI_VALUE_HISTORY_VALUE_TYPE>::lowest();
MULTI_VALUE_HISTORY_VALUE_TYPE val[MULTI_VALUE_HISTORY_NUMBER_OF_VALUES];

Expand All @@ -183,10 +194,10 @@ void MultiValueHistory::add_sample(float sample[MULTI_VALUE_HISTORY_NUMBER_OF_VA
const size_t buf_size = sizeof("{\"topic\":\"/live_samples\",\"payload\":{\"samples_per_second\":,\"samples\":[]}}\n") + sizeof(base_url) + (MULTI_VALUE_HISTORY_NUMBER_OF_VALUES * chars_per_value + 3) + 100;
size_t buf_written = 0;
char *buf = static_cast<char *>(malloc(buf_size));
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, "{\"topic\":\"%s/live_samples\",\"payload\":{\"samples_per_second\":%f,\"samples\":[%d", base_url.c_str(), static_cast<double>(samples_per_second()), static_cast<int>(val[0]));
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, "{\"topic\":\"%s/live_samples\",\"payload\":{\"samples_per_second\":%f,\"samples\":[[%d]", base_url.c_str(), static_cast<double>(samples_per_second()), static_cast<int>(val[0]));

for(int j = 1; j < MULTI_VALUE_HISTORY_NUMBER_OF_VALUES; ++j){
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, ",%d,", static_cast<int>(val[j]));
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, ",[%d]", static_cast<int>(val[j]));
}
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, "%s", "]}}\n");

Expand Down Expand Up @@ -232,16 +243,16 @@ void MultiValueHistory::add_sample(float sample[MULTI_VALUE_HISTORY_NUMBER_OF_VA
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, "{\"topic\":\"%s/history_samples\",\"payload\":{\"samples\":[", base_url.c_str());

if (history_val[0] == val_min) {
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, "%s", "null");
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, "[%s]", "null");
} else {
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, "%d", static_cast<int>(history_val[0]));
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, "[%d]", static_cast<int>(history_val[0]));
}

for(int j = 1; j < MULTI_VALUE_HISTORY_NUMBER_OF_VALUES; ++j){
if (history_val[j] == val_min) {
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, ",%s", "null");
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, ",[%s]", "null");
} else {
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, ",%d,", static_cast<int>(history_val[j]));
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, ",[%d]", static_cast<int>(history_val[j]));
}
}
buf_written += snprintf_u(buf + buf_written, buf_size - buf_written, "%s", "]}}\n");
Expand Down
1 change: 1 addition & 0 deletions software/src/modules/phase_switcher/multi_value_history.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
// takes about 380 ms).
// When we have 4 minutes worth of samples, we take the average
// and add it to the coarse history.
// #define MULTI_VALUE_HISTORY_MINUTE_INTERVAL 4
#define MULTI_VALUE_HISTORY_MINUTE_INTERVAL 4

#define MULTI_VALUE_RING_BUF_SIZE (MULTI_VALUE_HISTORY_HOURS * 60 / MULTI_VALUE_HISTORY_MINUTE_INTERVAL)
Expand Down
8 changes: 4 additions & 4 deletions software/web/src/modules/phase_switcher/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ export interface low_level_state {
export interface live {
offset: number,
samples_per_second: number,
samples: number[]
samples: number[][]
}

export interface live_samples {
samples_per_second: number,
samples: number[],
samples: number[][]
}

export interface history {
offset: number,
samples: number[],
samples: number[][]
}

export interface history_samples {
samples: number[],
samples: number[][]
}
160 changes: 125 additions & 35 deletions software/web/src/modules/phase_switcher/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,7 @@ interface MeterValues {

interface UplotData {
timestamps: number[];
samples: number[];
// samples: {
// available_charing_power: number[];
// actua_charing_power: number[];
// requested_phases: number[]
// }
samples: number[][];
}

interface UplotWrapperProps {
Expand Down Expand Up @@ -157,13 +152,39 @@ class UplotWrapper extends Component<UplotWrapperProps, {}> {
spanGaps: false,
label: __("phase_switcher.status.available_charging_power"),
value: (self: uPlot, rawValue: number) => util.hasValue(rawValue) ? util.toLocaleFixed(rawValue) + " W" : null,
stroke: "rgb(0, 123, 0)",
fill: "rgb(0, 123, 0, 0.1)",
width: 2,
points: {
show: false,
},
},
{
show: true,
pxAlign: 0,
spanGaps: false,
label: __("phase_switcher.content.actual_charging_power"),
value: (self: uPlot, rawValue: number) => util.hasValue(rawValue) ? util.toLocaleFixed(rawValue) + " W" : null,
stroke: "rgb(0, 123, 255)",
fill: "rgb(0, 123, 255, 0.1)",
width: 2,
points: {
show: false,
},
},
{
show: true,
pxAlign: 0,
spanGaps: false,
label: __("phase_switcher.content.requested_phases"),
value: (self: uPlot, rawValue: number) => util.hasValue(rawValue) ? util.toLocaleFixed(rawValue) : null,
stroke: "rgb(0, 0, 0)",
fill: "rgb(0, 0, 0, 0.1)",
width: 2,
points: {
show: false,
},
},
],
axes: [
{
Expand Down Expand Up @@ -342,7 +363,7 @@ class UplotWrapper extends Component<UplotWrapperProps, {}> {
}

render(props?: UplotWrapperProps, state?: Readonly<{}>, context?: any): ComponentChild {
// the plain div is neccessary to make the size calculation stable in safari. without this div the height continues to grow
// the pl ain div is neccessary to make the size calculation stable in safari. without this div the height continues to grow
return <div><div ref={this.div_ref} id={props.id} class={props.class} style={`display: ${props.show ? 'block' : 'none'};`} /></div>;
}

Expand All @@ -354,16 +375,20 @@ class UplotWrapper extends Component<UplotWrapperProps, {}> {
let y_min: number = this.props.y_min;
let y_max: number = this.props.y_max;

for (let i = 0; i < this.data.samples.length; ++i) {
let value = this.data.samples[i];
for (let j = 0; j < this.data.samples.length; ++j){

if (value !== null) {
if (y_min === undefined || value < y_min) {
y_min = value;
}
for (let i = 0; i < this.data.samples[j].length; ++i) {

let value = this.data.samples[j][i];

if (value !== null) {
if (y_min === undefined || value < y_min) {
y_min = value;
}

if (y_max === undefined || value > y_max) {
y_max = value;
if (y_max === undefined || value > y_max) {
y_max = value;
}
}
}
}
Expand Down Expand Up @@ -401,10 +426,11 @@ class UplotWrapper extends Component<UplotWrapperProps, {}> {
}
}

y_min = 0;
this.y_min = y_min;
this.y_max = y_max;

this.uplot.setData([this.data.timestamps, this.data.samples]);
this.uplot.setData([this.data.timestamps, ...this.data.samples]);
}

set_data(data: UplotData) {
Expand All @@ -420,44 +446,60 @@ class UplotWrapper extends Component<UplotWrapperProps, {}> {
}
}

function calculate_live_data(offset: number, samples_per_second: number, samples: number[]): UplotData {
let data: UplotData = {timestamps: new Array(samples.length), samples: samples};
function calculate_live_data(offset: number, samples_per_second: number, samples: number[][]): UplotData {
let data: UplotData = {timestamps: new Array(samples[0].length), samples: samples};
let now = Date.now();
let start;
let step;

// FIXME
for(let i = 1; i < samples.length; ++i){
if (samples[i].length != samples[0].length) {
console.log("ERROR: phase_switcher calculate_live_data: samples arrays do not have the same length!")
}
}
// FIXME

if (samples_per_second == 0) { // implies samples.length == 1
start = now - offset;
step = 0;
} else {
// (samples.length - 1) because samples_per_second defines the gaps between
// two samples. with N samples there are (N - 1) gaps, while the lastest/newest
// sample is offset milliseconds old
start = now - (samples.length - 1) / samples_per_second * 1000 - offset;
start = now - (samples[0].length - 1) / samples_per_second * 1000 - offset;
step = 1 / samples_per_second * 1000;
}

for(let i = 0; i < samples.length; ++i) {
for(let i = 0; i < samples[0].length; ++i) {
data.timestamps[i] = (start + i * step) / 1000;
}

return data;
}

function calculate_history_data(offset: number, samples: number[]): UplotData {
function calculate_history_data(offset: number, samples: number[][]): UplotData {
const HISTORY_MINUTE_INTERVAL = 4;

let data: UplotData = {timestamps: new Array(samples.length), samples: samples};
let data: UplotData = {timestamps: new Array(samples[0].length), samples: samples};
let now = Date.now();
let step = HISTORY_MINUTE_INTERVAL * 60 * 1000;
// (samples.length - 1) because step defines the gaps between two samples.
// (samples[0].length - 1) because step defines the gaps between two samples.
// with N samples there are (N - 1) gaps, while the lastest/newest sample is
// offset milliseconds old. there might be no data point on a full hour
// interval. to get nice aligned ticks nudge the ticks by at most half of a
// sampling interval
let start = Math.round((now - (samples.length - 1) * step - offset) / step) * step;
let start = Math.round((now - (samples[0].length - 1) * step - offset) / step) * step;

// FIXME
for(let i = 1; i < samples.length; ++i){
if (samples[i].length != samples[0].length) {
console.log("ERROR: phase_switcher calculate_live_data: samples arrays do not have the same length!")
}
}
// FIXME

for(let i = 0; i < samples.length; ++i) {
for(let i = 0; i < samples[0].length; ++i) {
data.timestamps[i] = (start + i * step) / 1000;
}

Expand All @@ -472,7 +514,7 @@ function array_append<T>(a: Array<T>, b: Array<T>, tail: number): Array<T> {

export class PhaseSwitcher extends ConfigComponent<'phase_switcher/config', {}, PhaseSwitcherConfig & PhaseSwitcherState & MeterValues> {
live_data: UplotData;
pending_live_data: UplotData = {timestamps: [], samples: []};
pending_live_data: UplotData = {timestamps: [], samples: [[], [], []]};
history_data: UplotData;
uplot_wrapper_live_ref = createRef();
uplot_wrapper_history_ref = createRef();
Expand All @@ -498,31 +540,79 @@ export class PhaseSwitcher extends ConfigComponent<'phase_switcher/config', {},
let live = API.get("phase_switcher/live");

this.live_data = calculate_live_data(live.offset, live.samples_per_second, live.samples);
this.pending_live_data = {timestamps: [], samples: []};
this.pending_live_data = {timestamps: [], samples: [[],[],[]]};

if (this.state.chart_selected == "live") {
this.update_uplot();
}
});

util.addApiEventListener("meter/history", () => {
let history = API.get("meter/history");
util.addApiEventListener("phase_switcher/live_samples", () => {
let live = API.get("phase_switcher/live_samples");
let live_extra = calculate_live_data(0, live.samples_per_second, live.samples);

this.pending_live_data.timestamps.push(...live_extra.timestamps);

for(let i = 0; i < this.pending_live_data.samples.length; ++i){
this.pending_live_data.samples[i].push(...live_extra.samples[i]);
}

if (this.pending_live_data.samples[0].length >= 5) {
this.live_data.timestamps = array_append(this.live_data.timestamps, this.pending_live_data.timestamps, 720);
for(let i = 0; i < this.live_data.samples.length; ++i){
this.live_data.samples[i] = array_append(this.live_data.samples[i], this.pending_live_data.samples[i], 720);
}

this.pending_live_data.timestamps = [];
this.pending_live_data.samples = [[], [], []];

if (this.state.chart_selected == "live") {
this.update_uplot();
}
}
});

util.addApiEventListener("phase_switcher/history", () => {
let history = API.get("phase_switcher/history");

this.history_data = calculate_history_data(history.offset, history.samples);

if (this.state.chart_selected == "history") {
// this.update_uplot();
this.update_uplot();
}
// !!! FIXME
console.log("phase_switcher/history:");
for (let i = 0; i<3; ++i){
console.log("phase_switcher/history:" + this.history_data.samples[i]);
console.log("--");
}
// !!! FIXME
});

util.addApiEventListener("meter/history_samples", () => {
let history = API.get("meter/history_samples");
util.addApiEventListener("phase_switcher/history_samples", () => {
let history = API.get("phase_switcher/history_samples");
let samples: number[][];

this.history_data = calculate_history_data(0, array_append(this.history_data.samples, history.samples, 720));
console.log("phase_switcher/history_samples 1");
for(let value_index = 0; value_index < history.samples.length; ++value_index){
console.log("phase_switcher/history_samples 1 loop " + value_index);
samples[value_index] = array_append(this.history_data.samples[value_index], history.samples[value_index], 720);
}
console.log("phase_switcher/history_samples 2");
this.history_data = calculate_history_data(0, samples);

console.log("phase_switcher/history_samples 3");
if (this.state.chart_selected == "history") {
// this.update_uplot();
console.log("phase_switcher/history_samples 4");
this.update_uplot();
}
// !!! FIXME
console.log("phase_switcher/history_samples:");
for (let i = 0; i<3; ++i){
console.log("phase_switcher/history_samples:" + this.history_data.samples[i]);
console.log("--");
}
// !!! FIXME
});

this.state = {
Expand Down

0 comments on commit 5e06939

Please sign in to comment.