add rounding to nearest 5m n dash
This commit is contained in:
163
dash_power_room_n_customer.py
Normal file
163
dash_power_room_n_customer.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import dash
|
||||||
|
from dash import dcc, html, Input, Output
|
||||||
|
import plotly.express as px
|
||||||
|
import pandas as pd
|
||||||
|
import sqlite3
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
# Initialize the Dash app
|
||||||
|
app = dash.Dash(__name__)
|
||||||
|
|
||||||
|
# Connect to the SQLite database
|
||||||
|
def get_db_connection():
|
||||||
|
conn = sqlite3.connect('power_data.db')
|
||||||
|
return conn
|
||||||
|
|
||||||
|
# Function to fetch data from the database
|
||||||
|
def fetch_data(time_range):
|
||||||
|
conn = get_db_connection()
|
||||||
|
query = f"""
|
||||||
|
SELECT * FROM building_totals
|
||||||
|
WHERE timestamp >= datetime('now', '-{time_range} hours')
|
||||||
|
"""
|
||||||
|
df = pd.read_sql_query(query, conn)
|
||||||
|
conn.close()
|
||||||
|
return df
|
||||||
|
|
||||||
|
# Function to calculate kWh and round timestamps for building totals
|
||||||
|
def calculate_building_kwh(df):
|
||||||
|
df['timestamp'] = pd.to_datetime(df['timestamp'])
|
||||||
|
df['timestamp'] = df['timestamp'].dt.round('5min') # Round to 5-minute intervals
|
||||||
|
df = df.set_index('timestamp')
|
||||||
|
df['kWh'] = df['total_power'] * (5 / 60) # Convert power to kWh for 5-minute intervals
|
||||||
|
return df
|
||||||
|
|
||||||
|
# Function to calculate kWh and round timestamps for room and customer breakdowns
|
||||||
|
def calculate_breakdown_kwh(df):
|
||||||
|
df['timestamp'] = pd.to_datetime(df['timestamp'])
|
||||||
|
df['timestamp'] = df['timestamp'].dt.round('5min') # Round to 5-minute intervals
|
||||||
|
df = df.set_index('timestamp')
|
||||||
|
df['kWh'] = df['power'] * (5 / 60) # Convert power to kWh for 5-minute intervals
|
||||||
|
return df
|
||||||
|
|
||||||
|
# Define the layout of the dashboard
|
||||||
|
app.layout = html.Div([
|
||||||
|
html.H1("Power Usage Overview", style={'textAlign': 'center'}),
|
||||||
|
|
||||||
|
dcc.Dropdown(
|
||||||
|
id='time-range',
|
||||||
|
options=[
|
||||||
|
{'label': '6 Hours', 'value': '6'},
|
||||||
|
{'label': '12 Hours', 'value': '12'},
|
||||||
|
{'label': '24 Hours', 'value': '24'},
|
||||||
|
{'label': '1 Week', 'value': '168'},
|
||||||
|
{'label': '2 Weeks', 'value': '336'},
|
||||||
|
{'label': '1 Month', 'value': '720'},
|
||||||
|
{'label': '2 Months', 'value': '1440'},
|
||||||
|
{'label': '1 Year', 'value': '8760'},
|
||||||
|
],
|
||||||
|
value='6',
|
||||||
|
style={'width': '200px', 'margin': '0 auto'}
|
||||||
|
),
|
||||||
|
|
||||||
|
dcc.Graph(id='building-graph', style={'width': '100%', 'height': '400px'}),
|
||||||
|
|
||||||
|
dcc.Tabs(id='tabs', value='room-breakdown', children=[
|
||||||
|
dcc.Tab(label='Room Breakdown', value='room-breakdown'),
|
||||||
|
dcc.Tab(label='Customer Breakdown', value='customer-breakdown'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
html.Div([
|
||||||
|
dcc.Dropdown(id='drill-down', multi=False, style={'width': '200px', 'margin': '0 auto'}),
|
||||||
|
dcc.Graph(id='breakdown-graph', style={'width': '100%', 'height': '400px'}),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
# Callback to update the building total graph
|
||||||
|
@app.callback(
|
||||||
|
Output('building-graph', 'figure'),
|
||||||
|
[Input('time-range', 'value')]
|
||||||
|
)
|
||||||
|
def update_building_graph(time_range):
|
||||||
|
df = fetch_data(time_range)
|
||||||
|
df = calculate_building_kwh(df)
|
||||||
|
fig = px.line(df, x=df.index, y=['total_current', 'total_power', 'kWh'],
|
||||||
|
labels={'value': 'Value', 'variable': 'Metric'},
|
||||||
|
title='Building Total Metrics')
|
||||||
|
fig.update_layout(legend_title_text='Metrics')
|
||||||
|
|
||||||
|
# Update legend to show recent values
|
||||||
|
for trace in fig.data:
|
||||||
|
if trace.name == 'kWh':
|
||||||
|
trace.name = f"{trace.name}: {df['kWh'].sum():.2f}"
|
||||||
|
else:
|
||||||
|
trace.name = f"{trace.name}: {df[trace.name].iloc[-1]:.2f}"
|
||||||
|
|
||||||
|
return fig
|
||||||
|
|
||||||
|
# Callback to update the drill-down dropdown
|
||||||
|
@app.callback(
|
||||||
|
Output('drill-down', 'options'),
|
||||||
|
[Input('tabs', 'value')]
|
||||||
|
)
|
||||||
|
def update_drill_down_options(tab):
|
||||||
|
conn = get_db_connection()
|
||||||
|
if tab == 'room-breakdown':
|
||||||
|
query = "SELECT DISTINCT room_number FROM room_breakdown"
|
||||||
|
else:
|
||||||
|
query = "SELECT DISTINCT customer_name FROM customer_breakdown"
|
||||||
|
df = pd.read_sql_query(query, conn)
|
||||||
|
conn.close()
|
||||||
|
return [{'label': i, 'value': i} for i in df.iloc[:, 0]]
|
||||||
|
|
||||||
|
# Callback to set the default value for the drill-down dropdown
|
||||||
|
@app.callback(
|
||||||
|
Output('drill-down', 'value'),
|
||||||
|
[Input('drill-down', 'options')]
|
||||||
|
)
|
||||||
|
def set_drill_down_value(options):
|
||||||
|
if options:
|
||||||
|
return options[0]['value']
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Callback to update the breakdown graph
|
||||||
|
@app.callback(
|
||||||
|
Output('breakdown-graph', 'figure'),
|
||||||
|
[Input('tabs', 'value'),
|
||||||
|
Input('drill-down', 'value'),
|
||||||
|
Input('time-range', 'value')]
|
||||||
|
)
|
||||||
|
def update_breakdown_graph(tab, drill_down, time_range):
|
||||||
|
conn = get_db_connection()
|
||||||
|
if tab == 'room-breakdown':
|
||||||
|
query = f"""
|
||||||
|
SELECT * FROM room_breakdown
|
||||||
|
WHERE room_number = '{drill_down}'
|
||||||
|
AND timestamp >= datetime('now', '-{time_range} hours')
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
query = f"""
|
||||||
|
SELECT * FROM customer_breakdown
|
||||||
|
WHERE customer_name = '{drill_down}'
|
||||||
|
AND timestamp >= datetime('now', '-{time_range} hours')
|
||||||
|
"""
|
||||||
|
df = pd.read_sql_query(query, conn)
|
||||||
|
conn.close()
|
||||||
|
df = calculate_breakdown_kwh(df)
|
||||||
|
fig = px.line(df, x=df.index, y=['current', 'power', 'kWh'],
|
||||||
|
labels={'value': 'Value', 'variable': 'Metric'},
|
||||||
|
title=f'{tab.replace("-", " ").title()} Metrics')
|
||||||
|
fig.update_layout(legend_title_text='Metrics')
|
||||||
|
|
||||||
|
# Update legend to show recent values
|
||||||
|
for trace in fig.data:
|
||||||
|
if trace.name == 'kWh':
|
||||||
|
trace.name = f"{trace.name}: {df['kWh'].sum():.2f}"
|
||||||
|
else:
|
||||||
|
trace.name = f"{trace.name}: {df[trace.name].iloc[-1]:.2f}"
|
||||||
|
|
||||||
|
return fig
|
||||||
|
|
||||||
|
# Run the app
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=8050, debug=True)
|
||||||
@@ -3,7 +3,7 @@ import requests
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import argparse
|
import argparse
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
import time
|
import time
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
@@ -55,14 +55,31 @@ def create_tables(conn):
|
|||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
|
def round_to_nearest_5_minutes(dt):
|
||||||
|
"""Round a datetime object to the nearest 5-minute interval."""
|
||||||
|
# Calculate the number of seconds since the last 5-minute interval
|
||||||
|
seconds_since_last_interval = dt.minute % 5 * 60 + dt.second + dt.microsecond / 1e6
|
||||||
|
|
||||||
|
# Round to the nearest 5-minute interval
|
||||||
|
if seconds_since_last_interval < 150: # 150 seconds = 2.5 minutes
|
||||||
|
rounded_dt = dt - timedelta(seconds=seconds_since_last_interval)
|
||||||
|
else:
|
||||||
|
rounded_dt = dt + timedelta(seconds=300 - seconds_since_last_interval) # 300 seconds = 5 minutes
|
||||||
|
|
||||||
|
# Set seconds and microseconds to zero
|
||||||
|
rounded_dt = rounded_dt.replace(second=0, microsecond=0)
|
||||||
|
|
||||||
|
return rounded_dt
|
||||||
|
|
||||||
def insert_building_total(conn, total_current, total_power):
|
def insert_building_total(conn, total_current, total_power):
|
||||||
"""Insert building total data into the database."""
|
"""Insert building total data into the database."""
|
||||||
try:
|
try:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
rounded_timestamp = round_to_nearest_5_minutes(datetime.now())
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO building_totals (total_current, total_power, timestamp)
|
INSERT INTO building_totals (total_current, total_power, timestamp)
|
||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
''', (round(total_current, 3), round(total_power, 3), datetime.now().isoformat()))
|
''', (round(total_current, 3), round(total_power, 3), rounded_timestamp.isoformat()))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
print(e)
|
print(e)
|
||||||
@@ -71,10 +88,11 @@ def insert_room_breakdown(conn, room_number, current, power):
|
|||||||
"""Insert room breakdown data into the database."""
|
"""Insert room breakdown data into the database."""
|
||||||
try:
|
try:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
rounded_timestamp = round_to_nearest_5_minutes(datetime.now())
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO room_breakdown (room_number, current, power, timestamp)
|
INSERT INTO room_breakdown (room_number, current, power, timestamp)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
''', (room_number, round(current, 3), round(power, 3), datetime.now().isoformat()))
|
''', (room_number, round(current, 3), round(power, 3), rounded_timestamp.isoformat()))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
print(e)
|
print(e)
|
||||||
@@ -83,14 +101,16 @@ def insert_customer_breakdown(conn, customer_name, current, power):
|
|||||||
"""Insert customer breakdown data into the database."""
|
"""Insert customer breakdown data into the database."""
|
||||||
try:
|
try:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
rounded_timestamp = round_to_nearest_5_minutes(datetime.now())
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO customer_breakdown (customer_name, current, power, timestamp)
|
INSERT INTO customer_breakdown (customer_name, current, power, timestamp)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
''', (customer_name, round(current, 3), round(power, 3), datetime.now().isoformat()))
|
''', (customer_name, round(current, 3), round(power, 3), rounded_timestamp.isoformat()))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
def get_device_ids(debug=False):
|
def get_device_ids(debug=False):
|
||||||
"""Fetch all device IDs from the LibreNMS API."""
|
"""Fetch all device IDs from the LibreNMS API."""
|
||||||
response = requests.get(f'http://{LIBRENMS_IP}/api/v0/devices', headers=HEADERS, verify=False)
|
response = requests.get(f'http://{LIBRENMS_IP}/api/v0/devices', headers=HEADERS, verify=False)
|
||||||
@@ -286,5 +306,5 @@ if __name__ == '__main__':
|
|||||||
main(debug=args.debug, update_db=not args.no_db_update, concurrency_level=args.concurrency_level)
|
main(debug=args.debug, update_db=not args.no_db_update, concurrency_level=args.concurrency_level)
|
||||||
runs += 1
|
runs += 1
|
||||||
if args.runs is None:
|
if args.runs is None:
|
||||||
time.sleep(240) # Wait for 5 minutes before the next run
|
time.sleep(250) # Wait for 5 minutes before the next run
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user