Entwicklung: Python Modul für das netled – Object oriented Python
In diesem Beitrag beschreibe ich die Entwicklung eines Python Moduls zur Ansteuerung des IP- gesteuerten MOSFET Treibers für RGBW- LED Strips, dessen Hardwareentwicklung ich in diesem Beitrag beschrieben habe. Mit diesem Modul soll es möglich sein, das netled sowohl über UDP, als auch TCP anzusteuern, und auf simple Weise in bestehende Pythonskripte wie das meiner Mediensteuerung (Beitrag folgt) zu integrieren.
Zunächst werde ich die Vorgehensweise der IP- Kommunikation meiner Projekte erläutern.
Sowohl das hier beschriebene Verfahren, als auch die Programmierung ist sehr generisch und basiert auf Autodidaktik, genügt also nicht zwingend dem gängingen „best practice“.
Control Frame
Wie schon meine Power Distribution Unit (PDU) (Beitrag Folgt), arbeitet auch das netled mit einfachen Byte- orientierten Frames, welche in Zeichenketten gepackt per UDP- oder TCP Payload übertragen werden. Die Zeichenkette ist dabei grundsätzlich wie folgt aufgeteilt:
- DC – Device Code
- _ – spare
- OC – Operation Code
- D0 bis Dn – Nutzdaten
Pic 1 zeigt den Payload String, wie er in meinen Projekten interpretiert wird. Jeder Block steht hier für ein Byte, die Gesamtlänge des Strings ist nicht festgelegt.
Das erste Zeichen ‚DC‘ repräsentiert immer den ‚Device Code‘. Dieser ist abhängig von der Geräteart und bisher in Bytes definiert, die sich in ASCII- Zeichen interpretieren lassen. So hat meine PDU den Device Code ‚A‘, welches dem Hexcode ‚0x41‘ entspricht. Die netleds sind das Folgeprojekt meiner PDU, folglich tragen sie allesamt den Device Code ‚B‘ mit dem Hexcode ‚0x42‘. Sinn und Zweck des Device Codes ist es zu verhindern, dass die PDU zum Beispiel fälschlicherweise einen Befehl ausführt, der eigentlich für ein netled gedacht war.
Die beiden Platzhalter, sprich die Blöcke mit dem Inhalt ‚_‘, dienen als Reserve.
Das dritte Zeichen ‚OC‘ beinhaltet den ‚Operation Code‘. Dieser stellt die Funktion dar, die das Zielgerät ausführen soll, ebenfalls beginnend mit dem ASCII Zeichen ‚A‘. Am Beispiel meiner PDU steht ‚A‘ für das setzen des gesamten Ausgangsregisters, ‚B‘ bewirkt das hinzufügen eines Bits, ‚C‘ entfernt ein Bit im Ausgangsregister, der Code ‚R‘ = ‚0x52‘ löst einen Software Reset aus, und so weiter. Das netled verfügt zum aktuellen Zeitpunkt über die Remotefunktion ‚A‘ zum Setzen der Farbmischung mit Fading, der Code ‚B‘ setzt die neue Farbmischung unmittelbar, ohne Fading.
Alle folgenden Zeichen, dargestellt als Blöcke von D0 bis Dn, beinhalten die erforderlichen Nutzdaten für die jeweilige Operation. Beim netled sind das bisher die jeweiligen Farbwerte in der Reihenfolge: ROT, GRÜN, BLAU und WEIß.
Python Modul
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Apr 29 11:47:50 2021
@author: sebastian
"""
import socket
import time
NETTIMEOUT = 2 #communication timeout in seconds
NETBUFSIZE = 128
LOCAL_IP = ''
LOCAL_TCP_PORT = 9999
LOCAL_UDP_PORT = 8888
NETLED_IP_01 = '192.168.178.13'
NETLED_TCPPORT_01 = 5005
NETLED_UDPPORT_01 = 6006
class netledCore():
def __init__(self):
self.IP = ''
self.tcp_port = 0
self.udp_port = 0
def sendCommandTCP(self, command):
self.answer = ''
print("sending command " + str(command) +" to netled IP: " + str(self.IP) + " at Port: " + str(self.tcp_port))
print("using TCP")
self.netledSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.netledSock.settimeout(NETTIMEOUT)
self.netledSock.connect((self.IP, self.tcp_port))
time.sleep(0.01)
self.netledSock.send(command)
try:
self.answer = self.netledSock.recv(NETBUFSIZE)
print("got answer: " + str(self.answer))
except socket.timeout:
print("no answer")
self.netledSock.settimeout(None)
self.netledSock.close()
def sendCommandUDP(self, command):
self.answer = ''
print("sending command " + str(command) +" to netled IP: " + str(self.IP) + " at Port: " + str(self.udp_port))
print("using UDP")
self.netledSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.netledSock.bind((LOCAL_IP, LOCAL_UDP_PORT))
self.netledSock.settimeout(NETTIMEOUT)
self.netledSock.sendto(command, (self.IP, self.udp_port))
while True:
try:
self.data, self.addr = self.netledSock.recvfrom(NETBUFSIZE)
except socket.timeout:
print("no answer")
break
if self.data:
print("received UDP message: " + str(self.data))
print("from " + str(self.addr[0]))
print("from port " + str(self.addr[1]))
break
self.netledSock.settimeout(None)
self.netledSock.close()
Code-Sprache: Python (python)
PS: Ich hoffe, ich finde bald heraus, wie ich die Zeilennummern im Codeblock anzeigen lassen kann, die offensichtliche Methode im Editor funktioniert leider nicht :/
Der obige Python Code zeigt die Initialversion der netledCore Klasse, von welcher ausgehend alle weiteren Funktionen entwickelt werden. Die Zeilen oberhalb der Klasse dienen der Entwicklung des Moduls im lokalen Projektordner, langfristig ist es jedoch das Ziel, das Skript in meine Modulbibliothek aufzunehmen.
Die __init__() Methode wird per Definition direkt bei der Instanzierung der Klasse ausgeführt und eignet sich daher, um Ziel- IP- Adresse und Port des Objektes anzulegen. Die folgend definierten Methoden sendCommandTCP() und sendCommandUDP() implementieren die Netzwerkkommunikation mit dem netled und stellen damit die Basis für alle weiteren Methoden dar.
In einem Python Skript, welches ein netled- Objekt verwendet, oder in einer Python Shell kann das netled beispielsweise wiefolg instanziert werden:
Python 3.8.5 (default, Sep 4 2020, 07:30:14)
Type "copyright", "credits" or "license" for more information.
IPython 7.19.0 -- An enhanced Interactive Python.
runfile('netled.py')
netled = netledCore()
netled.IP = NETLED_IP_01
netled.tcp_port = NETLED_TCPPORT_01
netled.udp_port = NETLED_UDPPORT_01
Code-Sprache: Python (python)
Eingabe und Ausgabe eines TCP Steuerbefehls sieht beispielsweise so aus:
#### EINGABE:
netled.sendCommandTCP(b'B0A0\xff\xff\xff\x00')
#### AUSGABE:
sending command b'B0A0\xff\xff\xff\x00' to netled IP: 192.168.178.13 at Port: 5005
using TCP
got answer: b'B0A0\xff\xff\xff\x00'
Code-Sprache: Python (python)
Die Argumente für die sendCommand- Methoden müssen hier manuell als Bytearray eingegeben werden. Handhabbarer ist es jedoch, Methoden aufrufen zu können, in welchen die Farbwerte als Integer angegeben werden.
def setColors(self, red, green, blue, white, protocol, fading):
if fading:
print("using fading:")
self.command = b'B0A0' + red.to_bytes(1, 'big') + green.to_bytes(1, 'big') + blue.to_bytes(1, 'big') + white.to_bytes(1, 'big')
else:
print("not using fading")
self.command = b'B0B0' + red.to_bytes(1, 'big') + green.to_bytes(1, 'big') + blue.to_bytes(1, 'big') + white.to_bytes(1, 'big')
if protocol == "TCP":
self.sendCommandTCP(self.command)
elif protocol == "UDP":
self.sendCommandUDP(self.command)
else:
print("unknown protocol")
Code-Sprache: Python (python)
Die Methode setColors() im obigen Code ermöglicht genau dies. Die Argumente ‚red‘, ‚green‘, ‚blue‘ und ‚white‘ werden durch die Methode to_bytes() als Byte interpretiert. Anhand des als bool interpretierten Arguments ‚fading‘ wird das Präfix mit dem korrespondierenden Operation Code gewählt und ‚protocol‘ bestimmt, mit welcher Methode das senden des Befehls stattfinden soll.
#### EINGABE
netled.setColors(255,0,255,0,"TCP",True)
#### AUSGABE
using fading:
sending command b'B0A0\xff\x00\xff\x00' to netled IP: 192.168.178.13 at Port: 5005
using TCP
got answer: b'B0A0\xff\x00\xff\x00'
#### EINGABE
netled.setColors(0,255,0,0,"UDP",False)
#### AUSGABE
not using fading
sending command b'B0B0\x00\xff\x00\x00' to netled IP: 192.168.178.13 at Port: 6006
using UDP
received UDP message: b'B0B0\x00\xff\x00\x00'
from 192.168.178.13
from port 6006
Code-Sprache: Python (python)
So long…
Mit dem hier Gezeigten steht zunächst schon mal die Basis zur Einbindung des netled in meine Python Projekte. Das Modul kann auf praktisch jeder Python- fähigen Plattform betrieben werden, was in meinen Augen der große Vorteil dieser Programmiersprache ist. Darüber hinaus ist der objektorientierte Ansatz mit dieser Programmiersprache durchaus handlich und selbst für einen „normalsterblichen“ Elektroniker nachvollziehbar 😉
Ich bedanke mit bei allen Lesern und freue mich über Fragen und Anregungen in der Kommentarsektion.