Update of Temperature Measurements

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.

The Measurements

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.

The Plotting

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

  • line plot of the last 24 hours,
  • histogram of the daily average of all data
  • histogram of monthly average of all data
  • line plot of all data

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')

Example

Previous Post Next Post