Update textbox content from other thread

Hello, I’m started with gradio with the following simple code example:

import gradio as gr
from threading import Timer

time = 1

class RepeatTimer(Timer):
    def run(self):
        while not self.finished.wait(self.interval):
            self.function(*self.args, **self.kwargs)

def timer():
    global time
    time += 1 
    box =  demo.children[0].children[0]
    box.value=str(time)

with gr.Blocks() as demo:
    box = gr.Textbox(value="start", label="timer", interactive=True)    

# create & start timer thread
t = RepeatTimer(2.0, timer)
t.start()

# start web server
demo.launch()   

Why I get no update of the textbox content ? In the debugger I see that the value is updated.

What can I do ?

Hi @Barricade ! Thanks for the question.

If I understand correctly, you’re trying to periodically run a function to update the UI of your app?

There isn’t an api to do that in gradio right now. See Make it easier to work with streaming data · Issue #2050 · gradio-app/gradio · GitHub. In general, updates to your app have to be triggered through interacting with the UI.

Do you mind sharing a bit more about your use case?

Hi @freddyaboulton,

thank you for checking my question. At first, I saw the possibilities from gradio to build very easy web gui applications with python. But my first use case is a web based heating house controller :slightly_smiling_face: running on a raspberry pi with python in a docker container. There are different temperatures, different relay actors, i want show the values in the web gui. Next step is to define times for switching on and off the relays and I want do it in home from a tablet and from outside with a smartphone.

“In general, updates to your app have to be triggered through interacting with the UI.”

Is it f.e. possible to send post requests (from a thread over localhost) to the web gui ?
Or has the gui thread a running hookable function ?
Or can I simulate button click events, from an unvisible button ?

Very cool demo @Barricade !

To show the latest values of the temperature, you do not need to manually update the config like you did in the demo. The config only reflects the state when the app is created.

To read the latest temperature value and display it in the UI you can use the load event listener. There is a rest api for all gradio apps. When you create an event listener, you can use the api_name parameter and that will automatically document a rest endpoint to trigger that event.

So a think a skeleton of your demo can look like this:

def get_temperature(...):
    ....

def turn_off_relay(....):
    ....

with gr.Blocks() as demo:
    temperature = gr.Number()
    relay_id = gr.Dropdown(choices=[...])
    off_switch = gr.Button(value="Turn off Relay")
    off_switch.click(turn_off_relay, inputs=relay_id, outputs=None, api_name="turn_off")
    demo.load(get_temperature, inputs=None, outputs=[temperature])

demo.launch()

You can then click on the “view api” button at the bottom of your app to the the “turn_off” endpoint.

image

image

If you plan on turning them on or off on a schedule, you can use the code you have to run the “turn off” logic in a separate thread but you shouldn’t store the state of whether or not a relay is turned on or off in the app itself but rather in an external datasource and have gradio from that instead.

Hopefully that helps!

Hi Freddy,

thank you for your help, i started with your example, using visual studo code as development environment, view python printf output in the terminal window from vs code.

import gradio as gr

gTemperature = -10

def get_temperature():
    global gTemperature
    gTemperature += 1
    print('temperature:', gTemperature)
    return gTemperature

def turn_off_relay(id):
    print('relay:', id)
    return

with gr.Blocks() as demo:
    temperature = gr.Number()
    relay_id = gr.Dropdown(choices=['Rel 1', 'Rel 2'])
    off_switch = gr.Button(value="Turn off Relay")
    off_switch.click(turn_off_relay, inputs=relay_id, outputs=None, api_name="turn_off")
    demo.load(get_temperature, inputs=None, outputs=[temperature])

demo.launch()

The “demo.load” function is working, but not as hoped by me :slight_smile:
The function get_temperature() was called only one time at starting the app and
than allways i press the F5 key in the browser (temperature is going higher).

I wished, that allways the temperature is changing, the value in the number widget is automatily change, like a thermometer.

PS D:\Projects\python-projects\gradio> & python.exe d:/Projects/python-projects/gradio/update-textbox.py
Running on local URL:  http://127.0.0.1:7861

To create a public link, set `share=True` in `launch()`.
temperature: -9
temperature: -8
relay: Rel 1
relay: Rel 2

For testing the API I installed in firefox the “RESTClient” plugin, whrere I can fire post and get requests with JSON content, but I’m not family with the syntax.

Hi @Barricade!

Right now, it’s not possible to automatically update the UI when the backend data changes. I suggested using the load event so that you could get the latest temperature whenever you manually refreshed the page.

We’re thinking about how to support this use case though! We filed this issue (linked above) to track this.

ok, api tests are now working, with:

curl -X POST http://127.0.0.1:7861/api/turn_off -H ‘Content-Type: application/json’ -d ‘{“data”: [“Rel 1”]}’

the above function turn_off_relay(id) was called

Hi @Barricade!

We’ve been working on adding an API to gradio apps that lets you run functions forever and not refresh the page.

What we have right now is the run_forever method. It’s similar to the load method but like it says but it runs indefinitely without refreshing the page. For your temperature use case you can do something like

def get_current_temp():
    .... .....

with gr.Blocks() as demo:
    temperature = gr.Number()
    demo.run_forever(get_current_temp,
                     inputs=None,
                     outputs=temperature,
                     every=1 # units in seconds)
demo.queue().launch()

It’s available as a python wheel here: https://gradio-builds.s3.amazonaws.com/run-forever/v1/gradio-3.4-py3-none-any.whl

An example using this feature is here: nvidia-smi - a Hugging Face Space by julien-c

This is an experimental feature - the api might change but we’d love to get your feedback on the current API.

Hi @freddyaboulton,

thank you, your example is working, also if i change your example from

every=1 # units in seconds)

to

every=0.1)  # units in seconds

The gui is updating very quickly.

(next text removed, found my mistake)

Is there a possibility to have more than one “run_forever” functions ?

Yes, i checked is working very well.

Great to hear @Barricade ! Will try to release this formally soon. Thank you for testing :slight_smile:

2 Likes

Hi, can you inform here please, when the for me very well working feature is gone in the release ?

Absolutely @Barricade! Have not released it yet but planning to do it soon.