My temperature sensors have been running successfully for months but I found a new (?) python library called w1thermsensor to detect and read DS18B20 temperature sensors and I've been working on how the data is displayed.
Starting with the measuremement code, I installed the new library as follows
pi@pi:~$ sudo pip3 install w1thermsensor
The sudo is needed to install it system wide (Best practice would be to set up a python environment but nothing I'm doing suggests a need for that here), if installed without sudo it will run from the command line as a regular user but not as a service. The key changes in the code were to add the library:
pi@pi:~$ from w1thermsensor import W1ThermSensor
and then replace the following lines
for sensor in range(number_of_thermometers):
temperature.append(read_temp(thermometer[sensor]))
with
for sensor in W1ThermSensor.get_available_sensors():
temperature.append(sensor.get_temperature())
Clearly replacing two lines with two very similar lines doesn't appear to change much but now the functions read_temp_raw() and read_temp() can be entirely removed making the code much simpler.
I extended the data display with a CSS styled title that shows the last measured temperatures and when they were measured and separate plots for
The code looks like this:
# Read date and temperature from an SQL database and plot it live with plotly
# this adds to the previous version by plotting all data in a scatter plot
# together with the daily average in a histogram and plotting the last 24 hours
# of data in two additional plots
import pandas as pd
import numpy as np
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go # or plotly.express as px
import dash
from dash import Dash, dcc, html, Input, Output
#from wsgiref.simple_server import make_server
import sqlite3
from sqlite3 import Error
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
db_name="ds18b20.db"
app = dash.Dash(__name__, title="Pi and DS18B20")
app.layout = html.Div(
[
html.H1('Temperature from DS18B20 sensors connected to a Raspberry Pi 4'),
html.H2(id='title-text'),
html.Div(className="center",
children=[
dcc.Graph(id = 'live-graph', animate = False),
dcc.Interval(id = 'graph-update', interval = 30000, n_intervals = 0),
]),
]
)
@app.callback(Output('title-text', 'children'),
Input('graph-update', 'n_intervals'))
def show_latest(n):
conn=sqlite3.connect(db_name)
# Get the last line of the DB so we can show it in the title of the plot
query = """select * from temperature order by Date_Time desc limit 1"""
cursor=conn.cursor();
cursor.execute(query)
latest=cursor.fetchone()
conn.close()
# Build the subtitle
return ["On "+latest[0]+\
" it was "+str(round(latest[1],1))+chr(176)+"C on sensor 1 and "+\
str(round(latest[2],1))+chr(176)+"C on sensor 2."
]
#server=app.server
@app.callback(
Output('live-graph', 'figure'),
Input('graph-update', 'n_intervals'))
def update_graph_scatter(n):
conn=sqlite3.connect(db_name)
# There's a bug in one of the sensors that registers a value of 85C on startup
# Don't know why it's only on one but I filter it out otherwise the autoscaling
# shrinks the rest of the data to unreadable
df = pd.read_sql("SELECT * FROM temperature WHERE Temperature1<60", con=conn)
# Get the last 24 hours of data
tdy= pd.read_sql("SELECT * FROM temperature WHERE Date_Time > datetime('now', '-24 hour')", con=conn)
# Close the connection
conn.close()
# This sets up the whole page with the number of subplots defined
# in rows and colums and their respecive titles, shared y-axes
# are shared between all plots on the same row
T1="Sensor 1"
T2="Sensor 2"
fig = make_subplots(
rows=4, cols=1,shared_yaxes=True,vertical_spacing=0.04,horizontal_spacing=0.04,
subplot_titles=("Last 24 hours",
"Daily average",
"Monthly average",
"All data")
)
# The column titles are defined when the database is created
# and the same ones have to be used here
# Line plots for the last 24 hours of data
fig.add_trace(go.Scatter(x= tdy['Date_Time'], y = tdy['Temperature1'],
name=T1),row=1,col=1)
fig.add_trace(go.Scatter(x= tdy['Date_Time'], y = tdy['Temperature2'],
name=T2),row=1,col=1)
# Daily averages of all data
fig.add_trace(go.Histogram(x = df['Date_Time'], y = df['Temperature1'],
histfunc="avg", name=T1,
xbins=dict(size="D1")),row=2,col=1)
fig.add_trace(go.Histogram(x = df['Date_Time'], y = df['Temperature2'],
histfunc="avg", name=T2,
xbins=dict(size="D1")),row=2,col=1)
# Monthly averages of all data
fig.add_trace(go.Histogram(x= df['Date_Time'], y = df['Temperature1'],
name=T1, histfunc="avg",
texttemplate="%{y:.1f}C",xbins=dict(size="M1")),
row=3,col=1)
fig.add_trace(go.Histogram(x= df['Date_Time'], y = df['Temperature2'],
name=T2, histfunc="avg",
texttemplate="%{y:.1f}C",xbins=dict(size="M1")),
row=3,col=1)
# All data
fig.add_trace(go.Scatter(x = df['Date_Time'], y = df['Temperature1'],
name=T1),row=4,col=1)
fig.add_trace(go.Scatter(x = df['Date_Time'], y = df['Temperature2'],
name=T2),row=4,col=1)
# Format the xaxis labels to only show month and year centred under the bar
fig.update_layout(xaxis3=dict(ticklabelmode="period", dtick="M1", tickformat="%b\n%Y"))
config = {'responsive': True}
fig.update_layout(bargap=0.1)
fig.update_layout(title=dict(font=dict(size=18)))
fig.update_layout(legend=dict(
orientation="h",
bgcolor="Azure",bordercolor="Black",
borderwidth=1,
yanchor="top", y=1.04,xanchor="left",x=0.0,
font=dict(size=14)))
fig.update_layout(font=dict(size=12), width=1600, height=2000, paper_bgcolor='DarkCyan', plot_bgcolor='AliceBlue')
return fig
if __name__ == '__main__':
app.run(host='0.0.0.0', port='8060')