Initial commit to aggregate values and show monthly overviews.
This commit is contained in:
12
bootstrap.php
Normal file
12
bootstrap.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
$config = require(__DIR__ . '/config/config.php');
|
||||
|
||||
$remote_db = new mysqli($config['remote']['db_host'], $config['remote']['db_user'], $config['remote']['db_pass'], $config['remote']['db_name'], $config['remote']['db_port']);
|
||||
$local_db = new mysqli($config['local']['db_host'], $config['local']['db_user'], $config['local']['db_pass'], $config['local']['db_name'], $config['local']['db_port']);
|
||||
|
||||
return [
|
||||
'config' => $config,
|
||||
'remote_db' => $remote_db,
|
||||
'local_db' => $local_db
|
||||
];
|
||||
26
config/config.php
Normal file
26
config/config.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
$config = [];
|
||||
|
||||
$config['remote']['db_host'] = '127.0.0.1';
|
||||
$config['remote']['db_name'] = 'volkszaehler';
|
||||
$config['remote']['table'] = 'data';
|
||||
$config['remote']['power_sensor'] = 5;
|
||||
$config['remote']['electric_meter'] = 6;
|
||||
$config['remote']['db_user'] = 'root';
|
||||
$config['remote']['db_pass'] = '';
|
||||
$config['remote']['db_port'] = 3306;
|
||||
$config['local']['db_host'] = '127.0.0.1';
|
||||
$config['local']['db_name'] = 'consumption';
|
||||
$config['local']['table_aggregation'] = 'aggregation';
|
||||
$config['local']['table_production'] = 'production';
|
||||
$config['local']['db_user'] = 'root';
|
||||
$config['local']['db_pass'] = '';
|
||||
$config['local']['db_port'] = 3306;
|
||||
|
||||
if (is_file(__DIR__ . '/config_local.php')) {
|
||||
$config_local = require(__DIR__ . '/config_local.php');
|
||||
$config = array_merge($config, $config_local);
|
||||
}
|
||||
|
||||
return $config;
|
||||
112
functions/functions.php
Normal file
112
functions/functions.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
function build_date_dropdown(mysqli $db, string $table): array
|
||||
{
|
||||
$first_date_query = 'SELECT MIN(date) FROM ' . $table . ';';
|
||||
$first_date_result = $db->query($first_date_query);
|
||||
$first_date = new DateTime($first_date_result->fetch_column(0));
|
||||
|
||||
$last_date_query = 'SELECT MAX(date) FROM ' . $table . ';';
|
||||
$last_date_result = $db->query($last_date_query);
|
||||
$last_date = new DateTime($last_date_result->fetch_column(0));
|
||||
|
||||
$first_date->modify('first day of this month');
|
||||
$last_date->modify('first day of this month');
|
||||
|
||||
$dates = [];
|
||||
|
||||
for ($date = $last_date; $date >= $first_date; $date = $date->modify('-1 month')) {
|
||||
$dates[] = $date->format('Y-m');
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
function get_aggregation(mysqli $db, string $table, $date): array
|
||||
{
|
||||
$value_query = 'SELECT date, meter_consumption, power_sensor, grid_feed FROM ' . $table . ' WHERE date LIKE "' . $date . '%" ORDER BY date ASC;';
|
||||
$value_result = $db->query($value_query);
|
||||
|
||||
$values = [];
|
||||
while ($row = $value_result->fetch_assoc()) {
|
||||
$values['date'][] = $row['date'];
|
||||
$values['meter_consumption'][] = $row['meter_consumption'];
|
||||
$values['power_sensor'][] = $row['power_sensor'];
|
||||
$values['grid_feed'][] = $row['grid_feed'];
|
||||
$values['min_meter'] = (isset($values['min_meter']) && $values['min_meter'] < $row['meter_consumption']) ? $values['min_meter'] : $row['meter_consumption'];
|
||||
$values['max_meter'] = (isset($values['max_meter']) && $values['max_meter'] > $row['meter_consumption']) ? $values['max_meter'] : $row['meter_consumption'];
|
||||
$values['avg_meter'] += $row['meter_consumption'];
|
||||
$values['min_power'] = (isset($values['min_power']) && $values['min_power'] < $row['power_sensor']) ? $values['min_power'] : $row['power_sensor'];
|
||||
$values['max_power'] = (isset($values['max_power']) && $values['max_power'] > $row['power_sensor']) ? $values['max_power'] : $row['power_sensor'];
|
||||
$values['avg_power'] += $row['power_sensor'];
|
||||
$values['min_feed'] = (isset($values['min_feed']) && $values['min_feed'] < $row['grid_feed']) ? $values['min_feed'] : $row['grid_feed'];
|
||||
$values['max_feed'] = (isset($values['max_feed']) && $values['max_feed'] > $row['grid_feed']) ? $values['max_feed'] : $row['grid_feed'];
|
||||
$values['avg_feed'] += $row['grid_feed'];
|
||||
}
|
||||
|
||||
$values['avg_meter'] /= count($values['meter_consumption']);
|
||||
$values['avg_power'] /= count($values['power_sensor']);
|
||||
$values['avg_feed'] /= count($values['grid_feed']);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
function get_month_aggregation(mysqli $db, string $table, $date): array
|
||||
{
|
||||
$sum_query = 'SELECT SUM(meter_consumption) / 1000 as meter_consumption, SUM(power_sensor) / 1000 as power_sensor, SUM(grid_feed) / 1000 as grid_feed FROM ' . $table . ' WHERE date LIKE "' . $date . '%";';
|
||||
$sum_result = $db->query($sum_query);
|
||||
|
||||
return $sum_result->fetch_assoc();
|
||||
}
|
||||
|
||||
function get_year_aggregation(mysqli $db, string $table, $date): array
|
||||
{
|
||||
$year = substr($date, 0, 4);
|
||||
$sum_query = 'SELECT SUM(meter_consumption) / 1000 as meter_consumption, SUM(power_sensor) / 1000 as power_sensor, SUM(grid_feed) / 1000 as grid_feed FROM ' . $table . ' WHERE date LIKE "' . $year . '%";';
|
||||
$sum_result = $db->query($sum_query);
|
||||
|
||||
return $sum_result->fetch_assoc();
|
||||
}
|
||||
|
||||
function get_grid_feed_by_month(mysqli $db, string $table_aggregation, string $table_production, $date): array
|
||||
{
|
||||
$year = substr($date, 0, 4);
|
||||
$feed_query = 'SELECT SUM(grid_feed), YEAR(date) as year, MONTH(date) as month FROM ' . $table_aggregation . ' WHERE date LIKE "' . $year . '%" GROUP BY YEAR(date), MONTH(date);';
|
||||
$feed_result = $db->query($feed_query);
|
||||
|
||||
$price_query = 'SELECT YEAR(date) as year, MONTH(date) as month, price FROM ' . $table_production . ' WHERE date LIKE "' . $year . '%";';
|
||||
$price_result = $db->query($price_query);
|
||||
|
||||
$price = [];
|
||||
while ($row = $price_result->fetch_assoc()) {
|
||||
$price[$row['year'] . '-' . str_pad($row['month'], 2, '0', STR_PAD_LEFT)] = $row['price'];
|
||||
}
|
||||
|
||||
$grid_feed_by_month = [];
|
||||
|
||||
while ($row = $feed_result->fetch_assoc()) {
|
||||
$grid_feed_by_month[$row['year'] . '-' . str_pad($row['month'], 2, '0', STR_PAD_LEFT)] = $row['SUM(grid_feed)'] * $price[$row['year'] . '-' . str_pad($row['month'], 2, '0', STR_PAD_LEFT)] / 100000;
|
||||
}
|
||||
|
||||
return $grid_feed_by_month;
|
||||
}
|
||||
|
||||
function get_month_production(mysqli $db, string $table, $date): array
|
||||
{
|
||||
$sum_query = 'SELECT eg, og, price FROM ' . $table . ' WHERE date LIKE "' . $date . '%";';
|
||||
$sum_result = $db->query($sum_query);
|
||||
if ($sum_result->num_rows === 0) {
|
||||
return ['eg' => 0, 'og' => 0, 'price' => 0];
|
||||
}
|
||||
|
||||
return $sum_result->fetch_assoc();
|
||||
}
|
||||
|
||||
function get_year_production(mysqli $db, string $table, $date): array
|
||||
{
|
||||
$year = substr($date, 0, 4);
|
||||
$sum_query = 'SELECT SUM(eg) as eg, SUM(eg * price) / 100 as eg_price, SUM(og) as og, SUM(og * price) / 100 as og_price FROM ' . $table . ' WHERE date LIKE "' . $year . '%";';
|
||||
$sum_result = $db->query($sum_query);
|
||||
|
||||
return $sum_result->fetch_assoc();
|
||||
}
|
||||
64
jobs/aggregate.php
Normal file
64
jobs/aggregate.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
$app = require(__DIR__ . '/../bootstrap.php');
|
||||
$day_seconds = 86400;
|
||||
|
||||
$local_db = $app['local_db'];
|
||||
$local_table = $app['config']['local']['table_aggregation'];
|
||||
$remote_db = $app['remote_db'];
|
||||
$remote_table = $app['config']['remote']['table'];
|
||||
|
||||
// Get latest date from local database;
|
||||
$latest_date_query = 'SELECT MAX(date) FROM ' . $local_table . ';';
|
||||
$latest_date_result = $local_db->query($latest_date_query);
|
||||
$latest_date = new DateTime($latest_date_result->fetch_column(0));
|
||||
|
||||
$date_yesterday = new DateTime();
|
||||
$date_yesterday->modify('-1 day')->modify('0:00');
|
||||
$interval = $date_yesterday->diff($latest_date);
|
||||
|
||||
for ($date_diff = $interval->days; $date_diff > 0; $date_diff--) {
|
||||
$date = new DateTime();
|
||||
$date->modify('-' . $date_diff . ' day');
|
||||
$date->modify('0:00');
|
||||
|
||||
$day_start = $date->getTimestamp() * 1000;
|
||||
$day_end = ($date->getTimestamp() + $day_seconds) * 1000;
|
||||
|
||||
// electric_meter is the meter reading in Wh.
|
||||
// The daily consumption ist the last minus the first value of a day
|
||||
$query_consumption_end = 'SELECT value FROM ' . $remote_table . ' WHERE channel_id = ' . $app['config']['remote']['electric_meter'] . ' AND timestamp < ' . $day_end . ' ORDER BY timestamp DESC LIMIT 1;';
|
||||
$query_consumption_start = 'SELECT value FROM ' . $remote_table . ' WHERE channel_id = ' . $app['config']['remote']['electric_meter'] . ' AND timestamp > ' . $day_start . ' ORDER BY timestamp ASC LIMIT 1;';
|
||||
|
||||
$result = $remote_db->query($query_consumption_end);
|
||||
$tmp = $result->fetch_assoc();
|
||||
$consumption_end = (float)$tmp['value'];
|
||||
$result = $remote_db->query($query_consumption_start);
|
||||
$tmp = $result->fetch_assoc();
|
||||
$consumption_start = (float)$tmp['value'];
|
||||
|
||||
// power_sensor is the actual consumption in W.
|
||||
$query_grid_feed = 'SELECT timestamp, value FROM ' . $remote_table . ' WHERE channel_id = ' . $app['config']['remote']['power_sensor'] . ' AND timestamp < ' . $day_end . ' AND timestamp > ' . $day_start . ' ORDER BY timestamp ASC;';
|
||||
$result = $remote_db->query($query_grid_feed);
|
||||
$last = null;
|
||||
$power_sensor = 0;
|
||||
while ($query_grid_data = $result->fetch_assoc()) {
|
||||
if (!is_null($last)) {
|
||||
$time_diff = $query_grid_data['timestamp'] - $last['timestamp'];
|
||||
$power_sensor += $last['value'] * $time_diff;
|
||||
}
|
||||
$last = $query_grid_data;
|
||||
}
|
||||
|
||||
$power_sensor /= 3600000; // 3600000 milliseconds per hour
|
||||
$meter_consumption = $consumption_end - $consumption_start;
|
||||
$grid_feed = $meter_consumption - $power_sensor;
|
||||
|
||||
if ($grid_feed < 0) {
|
||||
$grid_feed = 0;
|
||||
}
|
||||
|
||||
$aggregation_query = 'INSERT INTO ' . $local_table . ' (`date`, `meter_consumption`, `power_sensor`, `grid_feed`, `last_value`) VALUES ("' . $date->format('Y-m-d') . '", ' . $meter_consumption . ', ' . $power_sensor . ', ' . $grid_feed . ', ' . $consumption_end . ');';
|
||||
|
||||
$local_db->query($aggregation_query);
|
||||
}
|
||||
218
public/index.php
Normal file
218
public/index.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
$app = require(__DIR__ . '/../bootstrap.php');
|
||||
$local_db = $app['local_db'];
|
||||
$table_aggregation = $app['config']['local']['table_aggregation'];
|
||||
$table_production = $app['config']['local']['table_production'];
|
||||
|
||||
require_once(__DIR__ . '/../jobs/aggregate.php');
|
||||
require_once(__DIR__ . '/../functions/functions.php');
|
||||
|
||||
if (isset($_POST['action']) && $_POST['action'] == 'set_values') {
|
||||
$check_query = 'SELECT date FROM ' . $table_production . ' WHERE date = "' . $_POST['date'] . '-01";';
|
||||
$check_result = $local_db->query($check_query);
|
||||
if ($check_result->num_rows === 0) {
|
||||
$query = 'INSERT INTO ' . $table_production . ' (date, eg, og, price) VALUES ("' . $_POST['date'] . '-01", ' . $_POST['eg'] . ', ' . $_POST['og'] . ', ' . $_POST['price'] . ');';
|
||||
} else {
|
||||
$query = 'UPDATE ' . $table_production . ' SET eg = "' . $_POST['eg'] . '", og = "' . $_POST['og'] . '", price = "' . $_POST['price'] . '" WHERE date = "' . $_POST['date'] . '-01";';
|
||||
}
|
||||
$local_db->query($query);
|
||||
}
|
||||
|
||||
$dates = build_date_dropdown($local_db, $table_aggregation);
|
||||
$chosen_date = (isset($_POST['date'])) ? $_POST['date'] : $dates[0];
|
||||
$data = get_aggregation($local_db, $table_aggregation, $chosen_date);
|
||||
$month_values = get_month_aggregation($local_db, $table_aggregation, $chosen_date);
|
||||
$year_values = get_year_aggregation($local_db, $table_aggregation, $chosen_date);
|
||||
$grid_feed_by_month = get_grid_feed_by_month($local_db, $table_aggregation, $table_production, $chosen_date);
|
||||
$month_production = get_month_production($local_db, $table_production, $chosen_date);
|
||||
$year_production = get_year_production($local_db, $table_production, $chosen_date);
|
||||
|
||||
$colors = ['#375BEB', '#90EB36', '#EB5F36', '#DAE32D'];
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<title>Consumption values</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
span.color-1 {
|
||||
background-color: <?php echo $colors[0]; ?>;
|
||||
}
|
||||
|
||||
span.color-2 {
|
||||
background-color: <?php echo $colors[1]; ?>;
|
||||
}
|
||||
|
||||
span.color-3 {
|
||||
background-color: <?php echo $colors[2]; ?>;
|
||||
}
|
||||
|
||||
span.color-4 {
|
||||
background-color: <?php echo $colors[3]; ?>;
|
||||
}
|
||||
|
||||
span.color {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#chart-container {
|
||||
position: relative;
|
||||
width: 80vw;
|
||||
height: 70vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/index.php" method="post" style="display: inline-block;float: left; margin-right: 100px;">
|
||||
<select name="date" onchange="submit();">
|
||||
<?php
|
||||
foreach ($dates as $date) {
|
||||
$selected = ($date == $chosen_date) ? ' selected="selected"' : '';
|
||||
echo '<option value="' . $date . '"' . $selected . '>' . $date . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<form action="/index.php" method="post">
|
||||
<input type="hidden" name="date" value="<?php echo $chosen_date; ?>"/>
|
||||
<input type="hidden" name="action" value="set_values"/>
|
||||
<label style="text-align: right;" for="EG">EG</label>
|
||||
<input type="number" step="0.001" name="eg" id="EG" value="<?php echo $month_production['eg']; ?>"/>
|
||||
<label style="text-align: right;" for="OG">OG</label>
|
||||
<input type="number" step="0.001" name="og" id="OG" value="<?php echo $month_production['og']; ?>"/>
|
||||
<label style="text-align: right;" for="price">Preis</label>
|
||||
<input type="number" step="0.01" name="price" id="price" value="<?php echo $month_production['price']; ?>"/>
|
||||
<button type="submit">Werte setzen</button>
|
||||
</form>
|
||||
|
||||
<div style="margin-top: 20px; font-weight: bold;">
|
||||
<label>Messungen</label>
|
||||
<span class="color"></span>
|
||||
<span>Monat</span>
|
||||
<span>Jahr</span>
|
||||
<span>Min</span>
|
||||
<span>Max</span>
|
||||
<span>Durchschnitt</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="color color-1"></span>
|
||||
<label>Verbrauch Zählerstand</label>
|
||||
<span><?php echo number_format($month_values['meter_consumption'], 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($year_values['meter_consumption'], 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($data['min_meter'] / 1000, 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($data['max_meter'] / 1000, 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($data['avg_meter'] / 1000, 3, ',', '.'); ?> KWh</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="color color-2"></span>
|
||||
<label>Verbrauch berechnet</label>
|
||||
<span><?php echo number_format($month_values['power_sensor'], 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($year_values['power_sensor'], 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($data['min_power'] / 1000, 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($data['max_power'] / 1000, 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($data['avg_power'] / 1000, 3, ',', '.'); ?> KWh</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="color color-3"></span>
|
||||
<label>Einspeisung</label>
|
||||
<span><?php echo number_format($month_values['grid_feed'], 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($year_values['grid_feed'], 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($data['min_feed'] / 1000, 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($data['max_feed'] / 1000, 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($data['avg_feed'] / 1000, 3, ',', '.'); ?> KWh</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="color color-4"></span>
|
||||
<label>Produktion EG</label>
|
||||
<span><?php echo number_format($month_production['eg'], 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($year_production['eg'], 3, ',', '.'); ?> KWh</span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span><?php echo number_format($month_production['eg'] / count($data['date']), 3, ',', '.'); ?> kWh</span>
|
||||
</div>
|
||||
|
||||
<div style="border-bottom: 1px solid black;">
|
||||
<span class="color color-4"></span>
|
||||
<label>Produktion OG</label>
|
||||
<span><?php echo number_format($month_production['og'], 3, ',', '.'); ?> KWh</span>
|
||||
<span><?php echo number_format($year_production['og'], 3, ',', '.'); ?> KWh</span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span><?php echo number_format($month_production['og'] / count($data['date']), 3, ',', '.'); ?> kWh</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="color"></span>
|
||||
<label><b>Ersparnis</b></label>
|
||||
<span><b><?php echo number_format(($month_production['eg'] + $month_production['og'] - $month_values['grid_feed']) * $month_production['price'] / 100, 2, ',', '.'); ?> €</b></span>
|
||||
<span><b><?php echo number_format(($year_production['eg_price'] + $year_production['og_price'] - array_sum($grid_feed_by_month)), 2, ',', '.'); ?> €</b></span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="color"></span>
|
||||
<label><b>Eigenverbrauch</b></label>
|
||||
<span><b><?php echo ($month_production['eg'] + $month_production['og'] > 0) ? number_format(100 - $month_values['grid_feed'] * 100 / ($month_production['eg'] + $month_production['og']), 2, ',', '.') : 0; ?> %</b></span>
|
||||
<span><b><?php echo number_format(100 - $year_values['grid_feed'] * 100 / ($year_production['eg'] + $year_production['og']), 2, ',', '.'); ?> %</b></span>
|
||||
</div>
|
||||
|
||||
<div id="chart-container">
|
||||
<canvas id="chart"></canvas>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script>
|
||||
<script>
|
||||
const chart = document.getElementById('chart');
|
||||
|
||||
new Chart(chart, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: <?php echo json_encode($data['date']); ?>,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Verbrauch Zählerstand',
|
||||
data: <?php echo json_encode($data['meter_consumption']); ?>,
|
||||
backgroundColor: '<?php echo $colors[0]; ?>',
|
||||
},
|
||||
{
|
||||
label: 'Verbrauch berechnet',
|
||||
data: <?php echo json_encode($data['power_sensor']); ?>,
|
||||
backgroundColor: '<?php echo $colors[1]; ?>',
|
||||
},
|
||||
{
|
||||
label: 'Einspeisung',
|
||||
data: <?php echo json_encode($data['grid_feed']); ?>,
|
||||
backgroundColor: '<?php echo $colors[2]; ?>',
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
aspectRatio: 2,
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user