WeatherFlow PiConsole - Customization

This should work. Assuming you have renamed user\customPanels.kv.tmpl and user\customPanels.py.tmpl to user\customPanels.kv and user\customPanels.py, then setting [PrimaryPanels] Panel2 = BigTemperature should show the example BigTemperature panel in Panel 2. If it doesn’t then I need to look into this.

.kv files are intimately linked with the Kivy GUI library used to build the PiConsole. When a Kivy app is launched, it automatically loads a kv file that has the same name as the App class. The App class in main.py is defined as class wfpiconsole(App) (line 169), so the wfpiconsole.kv file is automatically loaded. Additional .kv files can be loaded using Builder.load_file('path/to/file.kv'), which is how the user\customPanels.kv file is loaded (line 195 of main.py). .kv files are used to design the layout of the different widgets (in the case of the PiConsole, widgets are essentially Panels), while the matching class in the .py file is used to control the behaviour of the widgets/panels.

In the .kv language, app always refers to the instance of your application class. CurrentConditions is an attribute of the wfpiconsole app class, that points to the instance of the CurrentConditions Screen class (line 422 of main.py). Obs is a Kivy property that is a member of the CurrentConditions class (line 416 of main.py).

It is the Kivy properties that handle the refreshing of the value. Whenever a new Websocket message arrives and is handled by the PiConsole, the value of the 'outTemp' field in the Obs DictProperty is updated, and this automatically triggers Kivy to update the value on the screen. It refreshes as often as a new Websocket message arrives.

This is where it gets a little more complex. First you would need to declare some Kivy properties to hold the information that you want to display from the external API in the .py file for your custom panel. panels/wind.py shows some good examples of this (rapidWindDir is a numeric property, windDirIcon is a string property). You will then see in kvland/wind.kv that these variables are referred to using root.rapidWindDir and root.windDirIcon. Next in your .py file you will want to write a function that calls the external API and updates the values of the properties you have previously defined. When these properties are updated, their value should automatically update on the screen. FInally in the init method in your .py file you will want to use Clock.schedule_interval to call the function that polls the external API at some set interval (see line 443 in main.py for an example).

Hopefully this all makes some sense!

This gives more info on the .kv lang: Kv language — Kivy 2.1.0 documentation

But I’m ultimately not going to use websockets at all. I’m going to grab data via other methods (UDP or MQTT or REST etc.). That PR that I submitted handles being able to spin up a minimal console without the websockets questions needing to be answered, which is the first step.

This is the second step - making data come into a panel from other than a websockets source.

That helps a lot. I couldn’t figure out how the variables were referred to.

Cool. Kivvy does the update magic.

Once I get it to display something once successfully :slight_smile:

Thanks a bunch. Time to see how far I get…

ok - I got a seemingly functional example to work so I thought I’d post it here. This very simple example sets a static value to display in a BigTemperature type panel.

Is this the right approach ?

(I didn’t worry if the button works so that’s untested completely)

#---------------------------------------
# user/customPanels.kv
#---------------------------------------
<ExamplePanel>:
    PanelBackground:
        _panelTitle: 'Example'
    Label:
        text: root.exampleData
        pos_hint: {'x': 0, 'y': 0}
        size_hint: (1, 1)
        font_name: 'fonts/Inter-Bold.ttf'
        font_size: dp(100*app.scaleFactor)
        color: utils.rgba('#ff0404ff')
        valign: 'center'
        halign: 'center'
        markup: 1
<ExampleButton>:
    PanelButton:
        text: 'Example'
        on_release: app.CurrentConditions.switchPanel(self)

and…

#-----------------------------
# user/customPanels.py
#------------------------------
from kivy.uix.relativelayout import RelativeLayout
from panels.template         import panelTemplate
class ExamplePanel(panelTemplate):

    def __init__(self,**kwargs):
        super().__init__(**kwargs)

    # this would be root.exampleData in the .kv file
    exampleData = "1.23"

Sorry - this was more confusing than I intended it to be. The refresh is not tied to a websocket message arriving. It’s simply that when a websocket message arrives, the Kivy properties that hold the different weather variables (e.g. outTemp, humidity etc.), are updated, and this automatically causes the console to refresh. You can use any data source to update a Kivy property, and this will trigger the console to refresh.

Yes, this is looking good so far in terms of code layout, but you need to make sure exampleData is a Kivy property, or else changing it’s value in the Python code will not trigger an update on the display. You want your Python code to look something like this

#-----------------------------
# user/customPanels.py
#------------------------------
from kivy.uix.relativelayout import RelativeLayout
from panels.template         import panelTemplate
from kivy.properties         import StringProperty

class ExamplePanel(panelTemplate):

    exampleData = StringProperty("0")

    def __init__(self,**kwargs):
        super().__init__(**kwargs)

        # this would be root.exampleData in the .kv file
        exampleData = "1.23"

There is a good example of designing widgets (i.e. panels) with .py and .kv files here: Designing with the Kivy Language

After reading the instructions for using the BigTemperature example, I decided to give it a try. Here are the steps I took to implement the custom panel.

Navigated to the user directory under the wfpiconsole installation (example: cd ~/wfpiconsole/user)
Copied the two template files to rename them to files that will be recognized by wfpiconsole:
cp --preserve customPanels.kv.tmpl customPanels.kv
cp --preserve customPanels.py.tmpl customPanels.py

Edited wfpiconsole.ini to change PanelTwo under [PrimaryPanels] to = BigTemperature.
Example:
[PrimaryPanels]
PanelOne = Forecast
PanelTwo = BigTemperature
PanelThree = WindSpeed
PanelFour = SunriseSunset
PanelFive = Rainfall
PanelSix = Lightning

Stopped and restarted wfpiconsole and I had a large temperature displayed in panel 2.

Crossposting a link to a nice example of extending PiConsole - Custom panel to display tide information - #7 by robert.jochim

ok - another dumb question. How would we get the [‘Station’][‘TempestID’] from the .ini file if we use a customPanel from the user directory ? I know ‘main.py’ already had the .ini file processed, but I’m lost re: the class hierarchy for how to get stuff main.py knows about into .py files down in the user customPanels.py

The information you are looking for is contained in the App class. This class can be accessed from any .py file (or classes in that file) by using:

from kivy.app import App
app = App.get_running_app()

The Tempest ID can then be accessed using

app.config['Station']['TempestID']

1 Like

Thanks.

Followup question - recall that I’m seeding the PiConsole with data grabbed by listening to the UDP messages rather than using websockets. Basically I’m running my wfudptools listener and saving to a scratch tmpfs file via cron, then using a modified PiConsole to read that data periodically and update the console display.

So where I’m at is trying to cram my data into your slots in the console panels whenever possible. Example draft brutal code is attached.

Rather then reinvent the wheel, I’d love to be able to call your ./lib routines to do all the post-processing stuff to set units, calculate derived values and labels, etc.

For example, if I had the current windspeed in a variable in user/CustomPanels.py how would I call your existing routine to calculate the word “Calm” to seed your wind panel with data ?

customPanels-draft.py.txt (5.7 KB)

Hi,

I have two panels that I like to change programmatically based on events. So for example when I am working on my laptop, and the WF PiConsole is right next to me, I like to see the detailed Temperature panel. But when my laptop is shut, it probably means I’m not near the PIConsole, so I like to see the BigTemp panel instead.

Currently, I achieve this by a script regularly pinging my laptop, if it doesn’t respond it edits the wfpiconsole.ini file and restarts the wfpiconsole service. This all works fine, but the restart takes 5 - 10 seconds. It would be neater if I could somehow tell wfpiconsole to re-read the ini file. Either by sending it a signal, or perhaps by running ‘systemctl reload wfpiconsole.service’ if it’s being run as a service?

I’m guessing I haven’t missed anything, and that this isn’t currently possible? Would this be useful a feature, and if so would it be a lot of work? I’ve had a quick poke around in the code, but not really found how or where to do this in the code.

The other use I have is that I do a similar thing for replacing the Solar panel with the Moon panel at civic sunset, and vice-versa at civic sunrise.

Any tips or suggestions most welcome, and thank @peter for the work creating this great console.

Sorry for the slow response - I have been out of town

All the functions required to derive further variables or calculate text descriptions are found in lib/derivedVariables.py. The can be imported into your custom panel using from lib import derivedVariables as derive and can then be called using derive.[function name].

For example, to get the word calm you would call derive.beaufortScale with the windSpeed as the input. The function returns a list containing the original wind speed, plus the matching Beafourt scale as a float, the same as a string, and the appropriate description. It is worth noting that the input windSpeed is also a list, with the windspeed in meters per second and a string containing the units, in this case mps. I would advise checking out lib/observationParser.py to see how I format the variables reported by devices and the variables that are derived.

1 Like

I don’t think this will be readily achievable without some significant modifications to the code. systemctl reload might work, but it probably will result in the same delay as there is a period of time required to restart the console. It certainly can’t be used to get Python to magically re-read the config file. That functionality would have to be added, along with a way of getting a script outside of Python to trigger a method inside a running instance of the console.

I think the better approach would be to do this all in pure Python. You could almost certainly recreate the script you are using to ping your laptop to run within the console, and then the console already has a method to switch between between primary and secondary panels. Check out the switchPanel method in the CurrentConditions class (line 475 of main.py) and line 611-613 of lib/observationParser.py to see how it is called

1 Like

Thanks - that helps a lot. I’ll get back to fiddling this week.

Thanks for the reply and the pointers. I’ll have a look at the switchPanel method code and have a fiddle!

1 Like

Hello,
I have been using the Weatherflow PiConsole for about a month now, and really like it.
I made a few changes to the code to display the time of max wind gust on the wind panel. If anyone else is interested in this I can submit it as a PR, or some other way. I made the changes to the main branch, but I’m sure I cound make the same changes to the develop branch if that would work better.

1 Like

Do you have a screenshot you could share?

I couldn’t find a way to capture a screenshot of the wfpiconsole running on the Pi, so here’s a pic from my phone.

I rearranged the layout of the max gust display to accommodate the time, and changed the the avg wind to look similar.

1 Like

I have a question. We experience a lot of thunderstorms in our area and I really enjoy the feature that switches the barometric panel to the lighting panel when strikes are detected. Once that occurs though, it stays on the lightning panel until manually switched back.

What I would like is to have it switch back automatically to barometric say 30 to 60 seconds after the strike. Obviously if there are a lot of strikes it should stay on the lightning panel, but as the storm passes it would return to the normal barometric panel.

How might I do this?

I have a 3.5 screen on top of my raspberry pi. Is there a simple way that I could display only lightning data on that screen on the entire screen? Can you point me in the right direction?

This could be done, but it would require significant modifications to the code. Have you managed to get a vanilla version of the console running on your 3.5" screen with 6 panels?