← TCP Socket Between Python and Node-RED on a Raspberry PLC
Two-axis solar trackersRaspberry PLC 21TCP socketCommunication
TCP Socket Between Python and Node-RED on a Raspberry PLC — full example
Link a Python control process to a Node-RED dashboard with local TCP sockets on a Raspberry PLC 21. Encoder streaming plus start, stop, exit commands.
Complete, runnable program for the Raspberry PLC 21 (tcp-socket-node-red.py): wiring header, requirements and integration notes included.
Download the full project pack — freeThis example + the related ones + bill of materials
Read-only preview.
#!/usr/bin/env python3
# -----------------------------------------------------------------------------
# COMPLETE EXAMPLE — Local TCP socket between the Python script and Node-RED
#
# Device: Raspberry PLC 21 (Industrial Shields)
# Based on: dual-axis solar tracker project
#
# Requirements:
# Python standard library only (socket, threading).
# In Node-RED: a "tcp request/in" node on localhost:8181 (data) and a
# "tcp out" node on localhost:8182 (commands).
#
# Logic:
# The control process opens two TCP servers on localhost:
# - 8181 (data): streams encoder positions to the dashboard with an
# axis prefix: "e1234\n" elevation, "a567\n" azimuth.
# - 8182 (commands): accepts "start", "stop" and "exit" lines sent from
# the Node-RED dashboard buttons.
# Each server runs in its own thread; the data thread publishes the latest
# reading every second. Here the encoders are simulated; on the real
# tracker the position comes from the position_value SDO (see
# canopen-encoder-inclinometer-reading.py).
# -----------------------------------------------------------------------------
import socket
import threading
import time
HOST = '127.0.0.1' # localhost only: Node-RED runs on the same PLC
DATA_PORT = 8181
CMD_PORT = 8182
# State shared between threads
tracking_active = threading.Event()
shutdown = threading.Event()
def read_elevation_encoder():
"""Stub: on the real tracker, encoder.sdo['position_value'].raw."""
return int(time.time()) % 750 # simulation 0..749 (0..75.0 deg)
def read_azimuth_encoder():
"""Stub: on the real tracker, encoder.sdo['position_value'].raw."""
return 600 + int(time.time()) % 2400 # simulation 600..2999
def data_server():
"""Accepts one dashboard connection and streams positions every 1 s."""
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, DATA_PORT))
srv.listen(1)
print(f"Data server on {HOST}:{DATA_PORT}")
while not shutdown.is_set():
conn, addr = srv.accept()
print(f"Dashboard connected (data) from {addr}")
try:
while not shutdown.is_set():
pos_e = read_elevation_encoder()
pos_a = read_azimuth_encoder()
# One-character prefix so Node-RED can split the axes
conn.sendall(("e" + str(pos_e) + "\n").encode()) # elevation
conn.sendall(("a" + str(pos_a) + "\n").encode()) # azimuth
time.sleep(1)
except (BrokenPipeError, ConnectionResetError):
print("Dashboard disconnected (data); waiting for reconnection")
finally:
conn.close()
srv.close()
def command_server():
"""Receives command lines from the dashboard: start / stop / exit."""
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, CMD_PORT))
srv.listen(1)
print(f"Command server on {HOST}:{CMD_PORT}")
while not shutdown.is_set():
conn, addr = srv.accept()
print(f"Dashboard connected (commands) from {addr}")
try:
buffer = b""
while not shutdown.is_set():
data = conn.recv(64)
if not data:
break # the client closed
buffer += data
while b"\n" in buffer:
line, buffer = buffer.split(b"\n", 1)
process_command(line.decode().strip().lower())
except ConnectionResetError:
pass
finally:
conn.close()
srv.close()
def process_command(cmd):
"""Executes the command received from the dashboard buttons."""
if cmd == "start":
tracking_active.set()
print("CMD start: tracking enabled")
elif cmd == "stop":
tracking_active.clear()
print("CMD stop: tracking stopped (motors halted)")
elif cmd == "exit":
print("CMD exit: shutting down control process")
shutdown.set()
elif cmd:
print(f"Unknown CMD ignored: '{cmd}'")
if __name__ == '__main__':
threads = [threading.Thread(target=data_server, daemon=True),
threading.Thread(target=command_server, daemon=True)]
for t in threads:
t.start()
try:
while not shutdown.is_set():
time.sleep(0.5) # the real control loop would go here
except KeyboardInterrupt:
shutdown.set()
print("Control process finished")
Download the full project pack — freeThis example + the related ones + bill of materials