Interval timer on several instances of web interface

Hello all,

I’m building a monitoring app with Dash for the observatory I’m working at.

An essential component of this monitoring is to send alarms if some parameters are not ok with what they should be. For this, I’m using a dcc.Interval to do regular checks and, if not okay, an alarm is send via our radio system through an external python script.

The issue I’m having is that if several people have the web interface open on their computer, each of them is going to send the alarm, which is a bit overwhelming when the radio get 5 alarms in the span of 30 seconds…

Would there be a way to prevent an interval to start if it’s already started in another browser (another computer) ? Or are there other possibilities to do regular checks inside a dash application ?

Simple code snippet (in case I’m doing something the wrong way) :

app.layout =(
    dcc.Interval(
        id='alarm-checks-interval',
        interval=(60 * 1000),
        n_intervals=0
    ),
    html.Div([
        sidebar,
        content,
    ]))

@callback(Input(component_id='alarm-checks-interval', component_property='n_intervals'))
def check_json(n):
    if alarm:
        radio_alarm.send_alarm()

Let me know what would be the different options.

Thanks a lot for the support,

Antoine

Hello @Aprots

You could use an id for the alarm and verify if it needs to send it again, just update a small db with the id of the alarm and whether or not it was triggered. If so, ignore the duplicates.

That sounds like a good workaround, thanks for your input, I’ll work in this direction then.

I would have another question.

If I understand correctly how dash works, when starting the server, it will get “GET” or “POST” requests from the client started in a browser. Is there a way to have the server to send a notification to all connected clients ?

For example, the user would have the possibility to click a button to turn ON/OFF the alarm system. Would it be possible, when this action is made, to notify the other clients that the alarm state has been changed ?

There could be a file or DB containing the alarm state that is checked to display the alarm state on your client, but then, if this file is read at a certain interval, there could be time that you leave your monitoring thinking the alarm is ON (someone turned OFF the alarm but the state file hasn’t been checked yet by your client). I don’t know if this is clear enough…

It is related to the same application but maybe it should be a different topic, sorry if that’s the case.

You are looking into the realm of websockets, which is supported very soon natively by Dash. Essentially, it allows the server to push data to the client whenever it wants to.

That’s exactly what I’m looking for, thanks for the quick answer. Is there a timeline on this ? I will work with some status files to read as a temporary solution.

Hi @Aprots

There is a pre-release available now: dash 4.2.0rc3.
The docs are being worked on currently and should be available soon.

If you want to get a head start, you can find that release on this branch GitHub - plotly/dash at v4.2 · GitHub

Thank you @AnnMarieW, I managed to upgrade to v4.2.0rc3.

I will try to have a look eventhough I don’t think my python skills are advanced enough to work without the docs.

hi @Aprots
The docs will be out very soon, like Ann Marie said.

Thank you for upgrading to the release candidate.
Feel free to ask more questions here in forum if you get stuck in the future.

@Aprots

The new docs for websockets are now available:

That is great news, thank you very much !

I will get to work then !

Hello,

I think I misunderstood what are websockets, how they work and/or what they do.

In my mind, set_props was sending the state of a component to the server and get_prop was retrieving such state.

The monitoring system I’m working on is supposed to be open on multiple different machines at the same time. What I’m trying to do is have buttons to turn on/off an alarm system. So far, I managed but then, when a user set the alarm to ON, I wanted the buttons on the other instances of the dash app (i.e. on other machines) to check the status of such button and update their interface to match the current alarm status.

Here is a short usable example of what I’m trying to do:

#!/home/oper/.venv/bin/python3

import asyncio
import dash_bootstrap_components as dbc
from dash import (
    callback,
    ctx,
    Dash,
    html,
    Input,
    set_props,
)

status = dict(nn=None, ns=None)
antennas = ['nn', 'ns']

app = Dash(external_stylesheets=[dbc.themes.DARKLY], backend='fastapi')

app.layout = html.Div(
    [
        html.H2('Menu', className='display-4'),
        html.Hr(),
        html.Div([
            html.Div([html.Div('NN Alarm Status', className='lead'),
                      html.Div(id='nn-status')], className='hstack'),
            html.Div(dbc.Button(id='nn-alarm-switch')),
        ]),
        html.Div([
            html.Div([html.Div('NS Alarm Status', className='lead'),
                      html.Div(id='ns-status')], className='hstack'),
            html.Div(dbc.Button(id='ns-alarm-switch')),
        ]),
])


# ----------------------
# Change alarm state on button click
# ----------------------
@callback(
    Input(component_id='nn-alarm-switch', component_property='n_clicks'),
    Input(component_id='ns-alarm-switch', component_property='n_clicks'),
    prevent_initial_call=True,
    websocket=True,
)
async def change_alarm_state(nn_button, ns_button):
    button = ctx.triggered_id
    ant = ctx.triggered_id.split('-')[0]
    print(f'{button} alarm button clicked, current status is {status[ant]}')
    if status[ant] == 'On':
        status[ant] = 'Off'
    elif status[ant] == 'Off':
        status[ant] = 'On'

    set_props(button, {'children': status[ant]})
    await asyncio.sleep(1)


#--------------------
# Check the alarm status (on/off) and change the button accordingly)
#--------------------
@callback(persistent=True,
          websocket=True)
async def check_alarm_status():
    alarm = False
    ws = ctx.websocket
    for i in antennas:   # Check buttons for both antennas
        initial_status = await ws.get_prop(f'{i}-alarm-switch', 'children')
        if initial_status is None:
            initial_status = 'Off'

        status[i] = initial_status
        print(f'{i}-alarm-switch should be {initial_status}')
        set_props(f'{i}-alarm-switch', {'children': initial_status})

    while not ws.is_shutdown:
        print('Checking alarm status')
        print(f'Current status is {status[i]}')
        for i in antennas:
            status[i] = await ws.get_prop(f'{i}-alarm-switch', 'children')
            if status[i] is not None:
                set_props(f'{i}-alarm-switch', {'children': status[i]})
        print(f'After check status is {status}')
        await asyncio.sleep(2)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)

I tried to clear it of all things not related to the buttons, sorry if it’s still a bit long. For context, I’m working with two radiotelescopes (antennas), named NN and NS, and I’d like to have an alarm system independant for both of them.

With what I have at the moment, the buttons turn ON/OFF when clicked, no issues there, but then the clients opened on other machines are not updated as I thought they would.

That’s why I think I misunderstood the use of websockets.

Also, when writing this I realised that my status variable doesn’t get updated outside of the callback and so is not the same for both callbacks…

So my question is, are websockets supposed to be used like this ? Are they usable to communicates with different instances of a dash app ?

Thanks for your time.

Hello @Aprots,

A websocket is like establishing a connection between a single computer and the server, this is dedicated and open all the time. This allows for near real-time read and write.

You are just checking internally, you need to have a single source of truth between all the apps. This was why I suggested a db, this can run on the server computer just fine, like a sqlite or duckdb. You just need to check the state of the alarm in the websocket listener, and send back the status to your components. Enable the buttons only when there is an alarm, this way the buttons can only turn off the alarm.

eg:

    while not ws.is_shutdown:
        print('Checking alarm status')
        print(f'Current status is {status[i]}')
        for i in antennas:
            status[i] = db_call(i) ## fetch the current state from a db
            if status[i] is not None:
                set_props(f'{i}-alarm-switch', {'children': status[i]})
        print(f'After check status is {status}')
        await asyncio.sleep(2)

Dash is a stateless app, this means that the browser knows everything about the current client state. Therefore:

  • get_props → fetching the current state of the client state
  • set_props → making an alteration of the client state

Thanks a lot for the explanation, I indeed didn’t understand well how websockets and dash works.

The db then seems to be the most viable way to do what I want, thank you for your input !