NODEYEZ Development Environment

Display panels and scripts to get the most from your Bitcoin node

Test a Simple Script

From the scripts folder, select the ipaddresses.py file to open in an editor.

Start running the script by the shortcut (F5) or choosing Start Debugging from the Run menu.

If there are no errors, you will see some startup text in the terminal pane, followed by sleeping for 120 seconds.

You can stop debugging via the shortcut (SHIFT+F5) or from the Run menu.

Activate the python virtual environment if it isn’t already active. You can rely on the prompt prefixed with the environment name (nodeyez) to know its active or not.

source ~/.pyenv/nodeyez/bin/activate

From the terminal, you won’t having debugging support, and instead just run the script directly

cd ~/nodeyez/scripts

python ./ipaddresses.py

Press (CTRL+C) to stop the script execution.

Look for an image generated in the ../imageoutput folder.

In the footer of most images generated by Nodeyez is an “as of” datestamp. This can help you be certain whether you are reviewing the latest changes.

Structure of a Nodeyez Panel

Nodeyez panels have been refactored to inherit from and extend an underlying class that provides helpful functions. The general organizational structure of the source files as read from top to bottom are desribed below

Initialization

def __init__(self):

This function defines expected configuration parameter names, including any mappings of prior names for backwards compatibility. Defaults are then assigned before initialization is called from the inherited class. If there is any further logic that needs to occur once during setup, it follows the init.

The IP addresses doesnt define any new attributes, but does provide mappings from configuration field names previously used. For newly created panels, you won’t need to provide legacy mappings.

# Define which additional attributes we have
self.configAttributes = {
    # legacy key name mappings
    "colorBackground": "backgroundColor",
    "colorTextFG": "textColor",
    "sleepInterval": "interval",
}

You will however need to specify any custom field names that the panel expects. These should be included in the configAttributes dictionary, and both the key and value should be the new field name. For example, the arthash panel includes a field named shapeOutlineColor in addition to legacy key name mappings.

self.configAttributes = {
    # legacy key name mappings
    "colorBackground": "backgroundColor",
    "colorShapeOutline": "shapeOutlineColor",
    "colorTextFG": "textColor",
    "sleepInterval": "interval",
    # panel specific key names
    "shapeOutlineColor": "shapeOutlineColor",
}

Defaults are specified after that. These are values that can override the base panel fields. For example, the IP Addresses panel sets the headerText to IP Addresses. If this isn’t done for a panel, the header text would be the same name as the panel, in this case ipaddresses. The time between panel runs is overridden in the interval configuration field, setting it to 120 (seconds, so 2 minutes). By default, panels have an interval of 10 minutes.

# Define our defaults (all panel specific key names should be listed)
self._defaultattr("headerText", "IP Addresses")
self._defaultattr("interval", 120)

Anything not overridden from the parent class keeps its default values.

field name default value
backgroundColor #000000
blockclockAddress 21.21.21.21
blockclockEnabled False
blockclockPassword emptystring/notset
dataDirectory ../data/
enabled True
footerEnabled True
headerColor #ffffff
headerEnabled True
headerText the name it was initialized as
height 320
interval 600
pagingEnabled True
pageNumber None
pageCount None
textColor #ffffff
watermarkEnabled True
watermarkAnchor bottomleft
width 480

Next, the panel calls the parent class to initialize. An argument is provided to specify the name of the panel. If the headerText is not overridden, the name will be assigned as the headerText. The name is used for logging purposes, and for identifying the configuration file that can be read from to override the hard coded values in the panel code.

# Initialize
super().__init__(name="ipaddresses")

Fetch Data

def fetchData(self):

If this function is overridden from the parent class, it represents logic that should occur each time the panel is generated, and should encapsulate that for retrieving any updated data and updating state.

For the IP Addresses panel, the logic makes this call. It’s calling a function of an imported package to retrieve all the ip addresses in use and assign to a property on the running instance for reference later in the run function.

self.netaddresses = psutil.net_if_addrs()

Additional Functions

Panel specific helper functions can be added throughout the class. In many cases these are prepared before they are called by functions like fetchData and run

In the case of the IP Addresses panel, its fairly simple and doesnt encapsulate data in additional class functions.

Run

def run(self):

This function will be run each time the panel is generated, and after a call to fetchData. The bulk of logic for creating images occurs here.

Looking at the IP Addresses panel, it begins by setting up an image

super().startImage()

This call to the parent panel class does some common logic to prepare a canvas to draw on based on the configured height and width. This will typically be used in all panels. Once it is called however, the panel is also responsible for cleaning up and releasing resources. This can be done with finishImage() as noted below.

Next, a block of code examines all the network addresses attained during the fetch operation, and excludes those that are local loopbacks, not broadcast type, or not IPv4 address types to build up a variable of line delimited addresses.

ipaddresslist = ""
for netdevicekey in self.netaddresses.keys():
    if netdevicekey == "lo":    # loopback
        continue
    netdevice = self.netaddresses[netdevicekey]
    for netaddress in netdevice:
        family, ipaddress, _, broadcast, _ = netaddress
        if broadcast == None:
            continue
        if int(family) != 2:    # Restrict to AF_INET (IPv4) address types
            continue
        ipaddresslist = ipaddresslist + "\n" if len(ipaddresslist) > 0 else ipaddresslist
        ipaddresslist = f"{ipaddresslist}{ipaddress}"

After this its time to take the prepared list of ip addresses and output it to the image canvas

maxFontSize = int(self.height * 32/320)
minFontSize = 12
fs, _, _ = vicarioustext.getmaxfontsize(self.draw, ipaddresslist, self.width, self.getInsetHeight(), True, maxFontSize, minFontSize)
if fs < minFontSize:
    vicarioustext.drawtoplefttext(self.draw, ipaddresslist, minFontSize, 0, self.getInsetTop(), ImageColor.getrgb(self.textColor), True)
else:
    vicarioustext.drawcenteredtext(self.draw, ipaddresslist, fs, self.width // 2, self.getInsetTop() + (self.getInsetHeight() // 2), ImageColor.getrgb(self.textColor), True)

The first three lines are setting up to try to find the largest size that the IP Address list can be written to the image, without running off the side of the image.

Helper functions from the vicarioustext package are used for writing out the text, either centered if it fits, or fully left aligned if for some reason it does not.

Finally, we finish the image using the helper function. This actually adds the header and footer to the image if enabled, along with a watermark and paging information. It also saves the canvas in memory to an image file so it can be viewed elsewhere. And then it closes the canvas object to free up memory.

super().finishImage()

Entry Block

Most panels will have a block of code at the bottom of the file to run the panel continuously. Here’s an example from the IP Addresses panel

if __name__ == '__main__':

    p = IPAddressesPanel()

    # If arguments were passed in, treat as a single run
    if len(sys.argv) > 1:
        if sys.argv[1] in ['-h','--help']:
            print(f"Prepares an image with all the IP addresses used by this host")
            print(f"Usage:")
            print(f"1) Call without arguments to run continuously using the configuration or defaults")
            print(f"2) Call with an argument other then -h or --help to run once and exit")
        else:
            p.fetchData()
            p.run()
        exit(0)

    # Continuous run
    p.runContinuous()

Make Changes and Test

Create a temporary branch to test your changes.

Using the Terminal

git checkout -b test1

If this is your first time setting up git, you’ll also want to set your user email and name

git config --global user.email "you@example.com"

git config --global user.name "Your Name"

Verify that the current branch name is depicted in the lower left corner of Visual Studio Code

Try making a change to the IP Addresses script. Within the run function, change the maximum font size to 128.

maxFontSize = 128 # int(self.height * 32/320)

Save the file, and start running the script via the shortcut (F5) or from choosing Start Debugging from the Run menu.

Stop the script and review the output generated in the imageoutput folder as before. You should see the as of footer line changed to a more recent time, as well as visual changes to the font size based on your alterations.

Publish Changes

Once you are satisfied with any changes you’ve made, you can publish them to a branch in your own repository, and then begin a pull request.

Commit your Changes Locally

Before going much further, lets commit your changes to your local branch.

In Visual Studio Code, switch to the Source Control view (CTRL+SHIFT+G G). If the Source Control subpanel is not displayed, toggle it by clicking the three dots in the upper right corner of the Source Control pane and choosing that menu option. Expand the Source Control subpanel and you’ll see the list of files you’ve added, modified, or deleted.

At the top of the Source Control panel you can provide a comment for your changes, and click Commit.

View the status, reporting branch, working vs staged changes

cd ~/nodeyez

git status

Stage them for committing

git add .

The ‘.’ stages all files from your current working directory downward

Commit with a message

git commit -m "Brief description of your changes"

Creating your Fork

Sign in to Github, creating a new account if you do not already have one. If you are creating a new account, you should also setup an SSH key from the computer you are working on.

Navigate to the project at https://github.com/vicariousdrama/nodeyez.

Towards the upper right corner, you can click on the Fork button. This will start a fork of the project under your username. Review the settings making adjustments based on your preferences and proceed with Create Fork.

Add a Remote

With your new fork created in Github, click the green Code dropdown button, and then select SSH. Copy the value. It should look something like git@github.com:Username/nodeyez.git

View your remotes

git remote -v

Add a new remote

git remote add myfork git@github.com:Username/nodeyez.git

Pushing to the Remote

You can use this command to push your branch to your desired forked repository, replacing myfork with whatever you named your remote and test1 with your branch name.

git push --set-upstream myfork test1

Initiating a Pull Request

From within the GitHub interface, you can view your branch, and create a pull request. By default, Github will open a pull request against the base repository from which yours is forked, but you can change that as desired.


Home Back to Python and IDE Setup Continue to Create New Panel