Updated interface
This commit is contained in:
parent
d6737ed154
commit
655a43ade4
@ -26,10 +26,12 @@
|
|||||||
"@angular/pwa": "^0.10.5",
|
"@angular/pwa": "^0.10.5",
|
||||||
"@angular/router": "~7.0.0",
|
"@angular/router": "~7.0.0",
|
||||||
"@angular/service-worker": "~7.0.0",
|
"@angular/service-worker": "~7.0.0",
|
||||||
"@swimlane/ngx-charts": "^10.0.0",
|
"angular-gauge": "^3.1.2",
|
||||||
|
"chart.js": "^2.9.3",
|
||||||
"core-js": "^2.5.4",
|
"core-js": "^2.5.4",
|
||||||
"firebase": "^5.5.8",
|
"firebase": "^5.5.8",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
|
"ng2-charts": "2.2.3",
|
||||||
"rxjs": "~6.3.3",
|
"rxjs": "~6.3.3",
|
||||||
"zone.js": "~0.8.26"
|
"zone.js": "~0.8.26"
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,6 @@ import {SecurityComponent} from './security/security.component';
|
|||||||
import {HttpClientModule} from '@angular/common/http';
|
import {HttpClientModule} from '@angular/common/http';
|
||||||
import {LoginComponent} from './login/login.component';
|
import {LoginComponent} from './login/login.component';
|
||||||
import {ServiceWorkerModule} from '@angular/service-worker';
|
import {ServiceWorkerModule} from '@angular/service-worker';
|
||||||
import {LineChartModule, NgxChartsModule} from '@swimlane/ngx-charts';
|
|
||||||
import {RoundPipe} from './round.pipe';
|
import {RoundPipe} from './round.pipe';
|
||||||
import {BatteryWidgetComponent} from './battery/widget/batteryWidget.component';
|
import {BatteryWidgetComponent} from './battery/widget/batteryWidget.component';
|
||||||
import {WeatherWidgetComponent} from './weather/widget/weatherWidget.component';
|
import {WeatherWidgetComponent} from './weather/widget/weatherWidget.component';
|
||||||
@ -29,6 +28,8 @@ import {AngularFireAuthModule} from '@angular/fire/auth';
|
|||||||
import {environment} from '../environments/environment';
|
import {environment} from '../environments/environment';
|
||||||
import {AngularFireModule} from '@angular/fire';
|
import {AngularFireModule} from '@angular/fire';
|
||||||
import {AngularFirestoreModule} from '@angular/fire/firestore';
|
import {AngularFirestoreModule} from '@angular/fire/firestore';
|
||||||
|
import {GaugeModule} from 'angular-gauge';
|
||||||
|
import {ChartsModule} from 'ng2-charts';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -49,9 +50,10 @@ import {AngularFirestoreModule} from '@angular/fire/firestore';
|
|||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
|
ChartsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
GaugeModule.forRoot(),
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
LineChartModule,
|
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatButtonToggleModule,
|
MatButtonToggleModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
@ -63,7 +65,6 @@ import {AngularFirestoreModule} from '@angular/fire/firestore';
|
|||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
MatSidenavModule,
|
MatSidenavModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
NgxChartsModule,
|
|
||||||
ServiceWorkerModule.register('ngsw-worker.js', {enabled: false}),
|
ServiceWorkerModule.register('ngsw-worker.js', {enabled: false}),
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
@ -1,63 +1,62 @@
|
|||||||
<div class="fill-height p-3" style="background-color: #b52e3c !important; overflow-y: scroll">
|
<div class="fill-height p-3" style="background-color: #b52e3c !important; overflow-y: scroll">
|
||||||
<div style="max-width: 1000px;">
|
<div class="ml-2 d-flex align-items-center">
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-end mb-2">
|
<div class="d-none d-sm-inline pr-2">
|
||||||
<div>
|
<img src="assets/tesla.png" height="85px" width="auto">
|
||||||
<h1 class="mb-0">Powerwall: {{batteryService.charge | number : '1.1-1'}} V</h1>
|
|
||||||
<h6>Last Updated At: {{batteryService.lastUpdate | date: 'short'}}</h6>
|
|
||||||
<h6 class="mb-0">Uptime: {{batteryService.uptime}}</h6>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<mat-button-toggle-group>
|
|
||||||
<mat-button-toggle value="24" [checked]="true">24 Hours</mat-button-toggle>
|
|
||||||
<mat-button-toggle value="12">12 Hours</mat-button-toggle>
|
|
||||||
<mat-button-toggle value="6">6 Hours</mat-button-toggle>
|
|
||||||
</mat-button-toggle-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<mat-card class="mt-2" *ngFor="let battery of batteries; let i = index">
|
<div>
|
||||||
<div class="d-flex">
|
<h1 class="mb-0">Powerwall: {{batteryService.last.voltage | number : '1.1-1'}} V</h1>
|
||||||
<div class="d-flex flex-grow-1 align-items-center">
|
<h6>Last Updated At: {{batteryService.last.timestamp | date: 'short'}}</h6>
|
||||||
<h5 class="mb-0">{{battery.name}}</h5>
|
<h6 class="mb-0">Uptime: {{batteryService.last.uptime}}</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-100 mt-5">
|
||||||
|
<div class="d-inline-block col-12 col-md-3 px-1">
|
||||||
|
<mat-card class="d-inline-block col-6 col-md-12 m-0 m-md-2">
|
||||||
|
<canvas id="netPower" class="w-100"></canvas>
|
||||||
|
<h5 class="text-center text-muted"><strong>Power: </strong>{{batteryService.last.power | number : '1.1-1'}} Watts</h5>
|
||||||
|
<h5 class="text-center text-muted"><strong>Current: </strong>{{batteryService.last.current | number : '1.1-1'}} Amps</h5>
|
||||||
|
</mat-card>
|
||||||
|
<mat-card class="d-inline-block col-6 col-md-12 m-0 m-md-2">
|
||||||
|
<mwl-gauge
|
||||||
|
[max]="100"
|
||||||
|
[color]="color"
|
||||||
|
[dialStartAngle]="-90"
|
||||||
|
[dialEndAngle]="-90.001"
|
||||||
|
[value]="(batteryService.last.soc || 0) * 100"
|
||||||
|
[label]="percent"
|
||||||
|
[animated]="true"
|
||||||
|
[animationDuration]="1">
|
||||||
|
</mwl-gauge>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
<div class="d-inline-block col-12 col-md-9 px-1">
|
||||||
|
<mat-card class="h-100">
|
||||||
|
<canvas baseChart
|
||||||
|
[datasets]="socData"
|
||||||
|
[labels]="socLabels"
|
||||||
|
[options]="socOptions"
|
||||||
|
[legend]="false"
|
||||||
|
chartType="line">
|
||||||
|
</canvas>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-wrap mt-5">
|
||||||
|
<div class="p-2 col-12 col-sm-6 col-lg-3" *ngFor="let battery of batteries; let i = index">
|
||||||
|
<mat-card>
|
||||||
|
<div class="d-flex w-100 justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-0">{{battery.name}}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted">
|
||||||
|
{{battery.negativeTemperature | number : '1.1-1'}} °C / {{battery.positiveTemperature | number : '1.1-1'}} °C
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-grow-1 align-items-center justify-content-center">
|
<div class="text-center my-3">
|
||||||
<button mat-button (click)="selected = i"
|
<mat-icon style="width: 240px; height: 240px; font-size: 240px">battery_full</mat-icon>
|
||||||
[ngClass]="{'selected': selected == i, 'text-success': batteryService.charging, 'text-danger': !batteryService.charging}">
|
<span class="text-white font-weight-bold" style="position: absolute; left: 50%; top: 50%; transform: translate(-50%, 25%)">{{battery.voltage | number : '1.1-1'}} V</span>
|
||||||
{{battery.voltage | number : '1.1-1'}} V
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-grow-1 align-items-center justify-content-end text-muted">
|
</mat-card>
|
||||||
<button mat-button (click)="selected = batteries.length + i"
|
</div>
|
||||||
[ngClass]="{'selected': selected == batteries.length + i}">
|
|
||||||
{{battery.temperature | number : '1.1-1'}} °C
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="selected == i" class="w-100" style="height: 200px">
|
|
||||||
<ngx-charts-area-chart class="w-100 h-100"
|
|
||||||
[results]="[{name: battery.name, series: battery.chargeHistory}]"
|
|
||||||
[scheme]="scheme"
|
|
||||||
[yAxis]="true"
|
|
||||||
[yScaleMin]="0"
|
|
||||||
[yScaleMax]="30"
|
|
||||||
[yAxisTickFormatting]="voltFormat"
|
|
||||||
[xAxis]="true"
|
|
||||||
[xAxisTickFormatting]="dateFormat"
|
|
||||||
[roundDomains]="true"
|
|
||||||
></ngx-charts-area-chart>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="selected == batteries.length + i" class="w-100" style="height: 200px">
|
|
||||||
<ngx-charts-area-chart class="w-100 h-100"
|
|
||||||
[results]="[{name: battery.name, series: battery.tempHistory}]"
|
|
||||||
[scheme]="scheme"
|
|
||||||
[yAxis]="true"
|
|
||||||
[yScaleMin]="0"
|
|
||||||
[yScaleMax]="50"
|
|
||||||
[yAxisTickFormatting]="tempFormat"
|
|
||||||
[xAxis]="true"
|
|
||||||
[xAxisTickFormatting]="dateFormat"
|
|
||||||
[roundDomains]="true"
|
|
||||||
></ngx-charts-area-chart>
|
|
||||||
</div>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, HostListener, OnInit} from '@angular/core';
|
||||||
import {BatteryService} from './battery.service';
|
import {BatteryService} from './battery.service';
|
||||||
import {AppComponent} from '../app.component';
|
import {AppComponent} from '../app.component';
|
||||||
|
|
||||||
|
declare const Gauge;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-batterys',
|
selector: 'app-batterys',
|
||||||
templateUrl: './battery.component.html',
|
templateUrl: './battery.component.html',
|
||||||
@ -9,30 +11,93 @@ import {AppComponent} from '../app.component';
|
|||||||
})
|
})
|
||||||
export class BatteryComponent implements OnInit {
|
export class BatteryComponent implements OnInit {
|
||||||
batteries = [];
|
batteries = [];
|
||||||
scheme = {
|
gauge;
|
||||||
name: 'cool',
|
socData = [];
|
||||||
selectable: true,
|
socLabels = [];
|
||||||
group: 'Ordinal',
|
socOptions = {
|
||||||
domain: ['#2830a8']
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
xAxes: [{}],
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
step: 0.1,
|
||||||
|
callback: (label) => this.percent(Math.round(label * 100))
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
callbacks: {
|
||||||
|
label: (tooltipItem, data) => `SOC: ${this.percent(tooltipItem.yLabel * 100)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
selected = 0;
|
|
||||||
|
|
||||||
constructor(public app: AppComponent, public batteryService: BatteryService) {
|
constructor(public app: AppComponent, public batteryService: BatteryService) {
|
||||||
this.batteryService.data.subscribe((data) => {
|
this.batteryService.data.subscribe((data) => {
|
||||||
console.log(data);
|
this.socLabels = data.filter(row => new Date(row.timestamp).getMinutes() % 15 == 0).map(row => this.dateFormat(new Date(row.timestamp)));
|
||||||
|
this.socData = [{label: 'SOC', fill: false, data: data.filter(row => new Date(row.timestamp).getMinutes() % 15 == 0).map(row => row.soc)}];
|
||||||
|
if(this.gauge) this.gauge.set(batteryService.last.power || 0);
|
||||||
this.batteries = Object.keys(this.batteryService.modules).map(key => {
|
this.batteries = Object.keys(this.batteryService.modules).map(key => {
|
||||||
return {
|
return {
|
||||||
name: `Module ${key}`,
|
name: `Module ${key}`,
|
||||||
chargeHistory: this.batteryService.modules[key].map(row => ({name: new Date(row.timestamp), value: row.voltage})),
|
negativeTemperature: this.batteryService.modules[key][this.batteryService.modules[key].length - 1].negativeTemperature,
|
||||||
tempHistory: this.batteryService.modules[key].map(row => ({name: new Date(row.timestamp), value: row.temperature})),
|
positiveTemperature: this.batteryService.modules[key][this.batteryService.modules[key].length - 1].positiveTemperature,
|
||||||
temperature: this.batteryService.modules[key][this.batteryService.modules[key].length - 1].temperature,
|
|
||||||
voltage: this.batteryService.modules[key][this.batteryService.modules[key].length - 1].voltage
|
voltage: this.batteryService.modules[key][this.batteryService.modules[key].length - 1].voltage
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() { }
|
ngOnInit() {
|
||||||
|
let canvas = document.getElementById('netPower');
|
||||||
|
canvas.style.height = canvas.style.width;
|
||||||
|
this.gauge = new Gauge(canvas).setOptions({
|
||||||
|
angle: 0.2, // The span of the gauge arc
|
||||||
|
lineWidth: 0.2, // The line thickness
|
||||||
|
radiusScale: 1, // Relative radius
|
||||||
|
pointer: {
|
||||||
|
length: 0.5,
|
||||||
|
strokeWidth: 0.035,
|
||||||
|
color: '#000000'
|
||||||
|
},
|
||||||
|
limitMax: true,
|
||||||
|
limitMin: true,
|
||||||
|
generateGradient: true,
|
||||||
|
highDpiSupport: true,
|
||||||
|
staticLabels: {
|
||||||
|
font: "75% sans-serif",
|
||||||
|
labels: [-4000, 0, 4000],
|
||||||
|
color: "black",
|
||||||
|
},
|
||||||
|
staticZones: [
|
||||||
|
{strokeStyle: "#eb575c", min: -4000, max: 0},
|
||||||
|
{strokeStyle: "#49b571", min: 0, max: 4000}
|
||||||
|
],
|
||||||
|
renderTicks: {
|
||||||
|
divisions: 2,
|
||||||
|
divWidth: 1.1,
|
||||||
|
divLength: 1,
|
||||||
|
divColor: '#000',
|
||||||
|
subDivisions: 4,
|
||||||
|
subLength: 0.5,
|
||||||
|
subWidth: 0.6,
|
||||||
|
subColor: '#000'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.gauge.minValue = -4000;
|
||||||
|
this.gauge.maxValue = 4000;
|
||||||
|
this.gauge.set(this.batteryService.last.power); // set actual value
|
||||||
|
}
|
||||||
|
|
||||||
|
color() {
|
||||||
|
return '#4f55b6';
|
||||||
|
}
|
||||||
|
|
||||||
|
percent(val) {
|
||||||
|
return `${Math.round(val)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
dateFormat(date: Date) {
|
dateFormat(date: Date) {
|
||||||
let hours = date.getHours();
|
let hours = date.getHours();
|
||||||
@ -43,12 +108,4 @@ export class BatteryComponent implements OnInit {
|
|||||||
|
|
||||||
return `${hours}:${minutes} ${date.getHours() > 12 ? 'PM' : 'AM'}`;
|
return `${hours}:${minutes} ${date.getHours() > 12 ? 'PM' : 'AM'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
voltFormat(val) {
|
|
||||||
return `${val} V`
|
|
||||||
}
|
|
||||||
|
|
||||||
tempFormat(val) {
|
|
||||||
return `${val} °C`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,9 @@ import {BehaviorSubject} from 'rxjs';
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class BatteryService {
|
export class BatteryService {
|
||||||
charge: number;
|
|
||||||
data = new BehaviorSubject<Battery[]>([]);
|
data = new BehaviorSubject<Battery[]>([]);
|
||||||
lastUpdate = new Date().getTime();
|
last: Battery = <Battery>{};
|
||||||
modules = [];
|
modules = [];
|
||||||
temp: number = 0;
|
|
||||||
uptime: string;
|
|
||||||
|
|
||||||
get charging() {
|
get charging() {
|
||||||
let value = this.data.value;
|
let value = this.data.value;
|
||||||
@ -23,7 +20,7 @@ export class BatteryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get icon() {
|
get icon() {
|
||||||
if(new Date().getTime() - this.lastUpdate > 300000) return 'battery_alert';
|
if(!this.last || new Date().getTime() - this.last.timestamp > 300000) return 'battery_alert';
|
||||||
if(this.charging) return 'battery_charging_full';
|
if(this.charging) return 'battery_charging_full';
|
||||||
return 'battery_full';
|
return 'battery_full';
|
||||||
}
|
}
|
||||||
@ -41,11 +38,7 @@ export class BatteryService {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
let last = data[data.length - 1];
|
this.last = data[data.length - 1];
|
||||||
this.lastUpdate = last.timestamp;
|
|
||||||
this.charge = last.voltage;
|
|
||||||
this.temp = last.temperature;
|
|
||||||
this.uptime = last.uptime;
|
|
||||||
this.data.next(data);
|
this.data.next(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="w-100 h-100">
|
<div class="w-100 h-100">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<mat-icon class="mr-3" style="font-size: 36px; height: 36px">{{batteryService.icon}}</mat-icon>
|
<mat-icon class="mr-3" style="font-size: 36px; height: 36px">{{batteryService.icon}}</mat-icon>
|
||||||
<h2 class="d-inline mb-0">Powerwall: <span [ngClass]="{'text-success': batteryService.charging, 'text-danger': !batteryService.charging}">{{batteryService.charge | round}} V</span></h2>
|
<h2 class="d-inline mb-0">Powerwall: <span [ngClass]="{'text-success': batteryService.charging, 'text-danger': !batteryService.charging}">{{batteryService.last.voltage | round}} V</span></h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
BIN
src/assets/tesla.png
Normal file
BIN
src/assets/tesla.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
@ -11,6 +11,8 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="assets/icon.png">
|
<link rel="icon" type="image/x-icon" href="assets/icon.png">
|
||||||
<link rel="stylesheet" type="text/css" href="assets/bootstrap.min.css">
|
<link rel="stylesheet" type="text/css" href="assets/bootstrap.min.css">
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
|
|
||||||
|
<script src="https://bernii.github.io/gauge.js/dist/gauge.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color: #2F323A;">
|
<body style="background-color: #2F323A;">
|
||||||
<app-root>
|
<app-root>
|
||||||
|
Loading…
Reference in New Issue
Block a user