How does Node-Red interact with Python?

Please don't put too much effort into it.

I am getting a new RasPi, new display - different size - for this project. So there will be a new node to talk to the new display.

There are two of them, and they both look similar. :frowning:
I need to find out if either support programmable characters. That's important.

So this is more me getting a feel for how it all works together.

No problems, actually I think I have already shared a python script talking to the pifacecad using mqtt somewhere here on the forum. Search is your friend

Just because you can run a Python script (or BASH, PowerShell, ...) from Node-RED, that doesn't make it the best approach.

I've come to this discussion late because I've been busy but my first recommendation would have been to build a Python API server - this sounds scary but really isn't. You can use a simple library in Python to do the hard bits. The idea is that you create a web endpoint to listen for commands.

When you have that, you are able to very easily talk to it from Node-RED.

The MQTT idea is the same but uses MQTT as the API transport instead of HTTP. If you are building something like this, you might even chose to implement both interfaces!

Anything that needs an ongoing "discussion" in another language, will benefit from this kind of approach.

So this is a kind of Python API server using MQTT as the API transport. In this very first version, the API is limited, it supports the following

  • Send some text to be written on the display

  • Send heartbeat command to the script to check it is alive and get a response back

  • Send stop command to terminate the script

I do not have a PiFaceCAD hat so I could not test the actual writing to display, let's hope I got that part right!

A typical test flow to play with. Start the script and view it's operation

PS To run it with python3, just change python to python3 in the exec node

[{"id":"e42464b7.a95968","type":"inject","z":"917016c6.7508a8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Heartbeat","payloadType":"str","x":180,"y":310,"wires":[["75b2fa84.747054"]]},{"id":"a69bdba1.20c1b8","type":"debug","z":"917016c6.7508a8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":560,"y":360,"wires":[]},{"id":"75b2fa84.747054","type":"mqtt out","z":"917016c6.7508a8","name":"","topic":"pifacecad","qos":"","retain":"","broker":"75eba16c.094f9","x":550,"y":310,"wires":[]},{"id":"c18b4225.8289d","type":"mqtt in","z":"917016c6.7508a8","name":"","topic":"hb-response","qos":"2","datatype":"auto","broker":"75eba16c.094f9","x":190,"y":360,"wires":[["a69bdba1.20c1b8"]]},{"id":"70743de8.b96b64","type":"exec","z":"917016c6.7508a8","command":"export DISPLAY=:0 && lxterminal -e python /home/pi/pifcad.py","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":610,"y":110,"wires":[[],[],[]]},{"id":"88aaad6b.88c11","type":"inject","z":"917016c6.7508a8","name":"Start script","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":180,"y":110,"wires":[["70743de8.b96b64"]]},{"id":"9dbe90cb.23397","type":"inject","z":"917016c6.7508a8","name":"Abort script","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Stop","payloadType":"str","x":190,"y":180,"wires":[["faf96fdf.dfb83"]]},{"id":"faf96fdf.dfb83","type":"mqtt out","z":"917016c6.7508a8","name":"","topic":"pifacecad","qos":"","retain":"","broker":"75eba16c.094f9","x":440,"y":180,"wires":[]},{"id":"b4d37661.68e6b8","type":"mqtt out","z":"917016c6.7508a8","name":"","topic":"pifacecad/write","qos":"","retain":"","broker":"75eba16c.094f9","x":580,"y":480,"wires":[]},{"id":"22230a37.ebaf36","type":"inject","z":"917016c6.7508a8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Some text","payloadType":"str","x":190,"y":480,"wires":[["b4d37661.68e6b8"]]},{"id":"bebffa.78aca008","type":"comment","z":"917016c6.7508a8","name":"Use to check if script is alive","info":"","x":240,"y":270,"wires":[]},{"id":"e08c823c.4d44c","type":"comment","z":"917016c6.7508a8","name":"Use to write some text to display","info":"","x":260,"y":440,"wires":[]},{"id":"75eba16c.094f9","type":"mqtt-broker","z":"","name":"","broker":"127.0.0.1","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"online","birthQos":"0","birthPayload":"BULB-1/LWT","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"offline","willQos":"0","willPayload":"BULB-1/LWT"}]

The Python code (copy and paste into a text file named to pifcad.py, save it in /home/pi)

import sys
import os
import time
import pifacecad
import paho.mqtt.client as mqtt


def on_connect(client, userdata, flags, rc):
    print("Connected:", rc)
    client.subscribe("pifacecad/#", 0) 


def on_subscribe(client, userdata, mid, granted_qos):
    print ('Subscribed:', userdata, mid, granted_qos)
    print ('We are here, waiting for commands...')


def on_message(client, userdata, msg):
    global abort
    global cad
    if msg.payload.decode("utf-8") == 'Heartbeat':
        client.publish("hb-response", "Script is alive")
    if msg.payload.decode("utf-8") == 'Stop':
        print ("Script terminates..")
        abort = True
    if msg.payload.decode("utf-8") == 'Reboot':
        cmnd = 'sudo reboot'
        os.system(cmnd)
    if (msg.topic == "pifacecad/write"):
        txt = msg.payload.decode("utf-8")
        print (txt)
        cad.lcd.backlight_on()
        cad.lcd.clear()
        cad.lcd.write(txt)
        

# inits
cad = pifacecad.PiFaceCAD()
mqtt_broker = "127.0.0.1"
mqtt_port = 1883
abort = False
client = mqtt.Mosquitto()
client.on_connect = on_connect
client.on_message = on_message
client.on_subscribe = on_subscribe
resp = client.connect(mqtt_broker, mqtt_port, 60)
client.loop_start()

while abort == False:
    time.sleep(1)
    
client.loop_stop()
client.disconnect()
del client
print ('Script has terminated')
exit(0)

1 Like

That is Greek to me.

As @TotallyInformation said I need to build a python API server.

Yes, it is scary, and I am worried about how to do it.

I think I will wait though until the actual hardware arrives and I know exactly what is going on.

This would be better I think because as problematic as this is with the PiFaceCAD, it sort of works. I'd prefer to leave it as is for now.

When the new stuff arrives, I will then delve into what he said.

Is nothing to be scared about, I realize we had already a discussion around this topic back in January 16 when I provided you with a very similar Python script, also for the PiFaceCAD, then for reading inputs and write text to display. That script was also working as a Python API server (this sounds very impressive and scary but won't bite) in it's limited functionality. But still a server; a script that continuously runs, waiting for & accepts a set of defined commands to perform related actions acts as a server or service. Normally you would run this script hidden, in the background, I just selected to run it visible to make it easier to see it's operation

Did you ever try that script I wrote back in January?

The new one I now wrote from scratch (since I had already forgotten I wrote the previous one) is similar, based on the same concept but supporting other commands, it can only write text to the display

Nothing to be worried about - just try it - no trying, no learning

Wow! How I had forgotten.

And I guess a lot has happened since then.

Yeah, ok. Please don't think I was being dismissive of your help. It is just I am really (not) a bit (but more a fair bit) distracted just now.

I know that isn't a good thing, but that's just what is happening with a few external things and they are interrupting the smooth operation of things.

How's that saying go:

Life is what happens when you are busy making other plans.
(John Lennon)

Probably. I will have to go back and look sorry. (It is not on this machine.)

So the new one is the one you wrote back in post 23 - yeah?

After looking.

Um, I did a search....

If I make the date: BEFORE 17 Jan 2020, it gets nothing either.
That way giving it a day extra search.

Yes

Understand your problem - but it is also very difficult to give any advise when you are drifting away - if you really want help with something I think you are expected to stay tuned & focused on that specific problem - otherwise it just steels so much wasted energy

Ok. I will download that code and put it on the other machine soon.

Oh wow......

This is frightening.

I am looking at/on the other machine.....

There is the code. Though in this case it is called: piFaceCAD.py 18 Jan 2020

I feel like time is going all wibbily wobbily.

Update

Um.....

This is interesting.

As I said, I the older one is a slightly different name.

So here you are saying I call it pifacecad.py

I pasted the flow you gave me on that machine and hit the inject node to run the code.
Nothing really shown.

I got suspicious so opened a SSH to the machine.

This is what happens:

pi@PIFACE:~ $ python3 pifacecad.py
Traceback (most recent call last):
  File "pifacecad.py", line 4, in <module>
    import pifacecad
  File "/home/pi/pifacecad.py", line 38, in <module>
    cad = pifacecad.PiFaceCAD()
AttributeError: 'module' object has no attribute 'PiFaceCAD'
pi@PIFACE:~ $ 

YES!

Fond the problem.

This is just me explaining stuff.

The line in the code: import pifacecad
That seems to be mentioned in the error.

I know the python stuff for it is installed, it used to work and I haven't removed any.

When I was messing around I made a directory in home PiFace` to quickly/easily see the stuff which came with the module.

I have scripts in there working and they too have a line: import pifacecad and as said: The work.

What's the difference?

Paths? (Dunno. Just discussing)

While playing with python I realised that when you import pifacecad it is a general thing and is actually a directory, not a file. Or that's my take on it.

As there is no pifacecad directory in home..... I renamed the script to pifacecad_new.py and moved it to the piface directory.

Now it works.

You may have to explain why this is happening.

I had to rename it from just pifacecad because that is the name of the directory, and I am not sure if Linux/Raspbian likes that sort of thing.

Now, (sorry another question) as is, the flow/script (package) doesn't allow positioning of text on the display.

Ok, I goofed.

I thought the script name was pifacecad.py It isn't. It is pifcad.py

Quick bit of renaming.

So to get this to allow text positioning, I would have to edit this part of the script:

    if (msg.topic == "pifacecad/write"):
        txt = msg.payload.decode("utf-8")
        print (txt)
        cad.lcd.backlight_on()
        cad.lcd.clear()
        cad.lcd.write(txt)

The worrying part is that I don't want the display cleared between messages.
So the cad.lcd.clear() is to be removed.

Well, just doit
And see if it works. Yes, it is in this part of the python code you should do all your experimenting stuff like positioning the text line etc etc

But the fundamental needs to work first, did it write some text omn the display when you clicked the button "Some text" in the test flow?

Thank thanks and again: thanks.

Yes it works.

I think what happened back in January I was really wanting to use it, but got distracted with positioning the cursor and that led to a whole sticky thing.

I did remove the line and that is better. Alas it causes another problem: how to clear the screen.

I worked out this bit of code, but it too has its own problem.

    if msg.payload.decode("utf-8") == 'CLS':
        cad.lcd.clear()

But then, I get a clear screen with CLS on it.

So I don't know how to abort (or exit?) that.
I don't know how to make msg.payload set to null. I tried msg.payload = '' and the script errored when I sent a CLS. The screen cleared but then the write() line failed because the text didn't have any value.

In Python you write msg.payload = None

Or even better, you can improve further by adding a check on topic as well
And then you publish the CLS command to "pifacecad/cls" instead

if (msg.topic == "pifacecad/cls" and msg.payload.decode("utf-8") == 'CLS' ):
    cad.lcd.clear()
1 Like

But doesn't that get eaten in the MQTT?

Oh! Ok. I am looking at the topic and then /# so if I extend the topic....

But then what is to stop it then continuing down the script and writing CLS on the screen?
I don't know how to end that branch of the script.

Ah, I set the payload to None.

With ya!

Well, how shall I describe

Think about the MQTT broker as a main train station and topics are tracks leadingto and from it in many different directions

So if you publish a message to "pifacecad/cls" it will not reach "pifacecad/write" (unless you have changed the script not known to me)

But you can also as you did I think, "kill" the content by setting it to None

Yeah, I did get that. That's why I redacted those two lines.

I just deleted a bit of stuff. Yeah. With you. I don't know why was missing that trick with the topic.

Can i suggest you have (like Tasmota) a couple of "States" (for want of a better word)

pifacecad/cmnd for sending commands to it
pifacecad/state for it sending back info (like the cursor is at position x,y) and anything else of interest

so you could send
pifacecad/cmnd/cls for clear screen
pifacecad/cmdn/pos to set it to a position etc etc
pifacecad/cmnd/char for custom characters etc

Craig

Dear Craig, yes, absolutely, this is how it shall be structured

I have already since long several such scripts running in background as small Python API services (I prefer call them that) for various purposes that for me was better to keep outside of NR. They all have MQTT as API transport, working really well. For sure could be possible to add also http and socket (TCP) support to them but since MQTT is so lightweight and well structured, I did not consider that for now

All scripts are fully controlled by NR. That they are started at startup, running as expected, etc etc and that they will be restarted if they (rarely) would fail. Since NR is controlled by systemd, I felt I got a pretty robust solution. So far it has worked astonishing well

Thanks for your great suggestions!

1 Like

Good one - i thought you would be all over it !!

I was putting it into something i thought Andrew might understand better - since he has just been through a long thread about MQTT issues and the best way to use the Tasmota CMND, STATE etc etc.

regards

Craig

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.