Zugriff mit Python

Für Themen rund um das Prozessabbild des RevPi Core
Post Reply
User avatar
volker
Posts: 1046
Joined: 09 Nov 2016, 15:41

Zugriff mit Python

Post by volker »

Wie versprochen kommt hier ein Beispiel für den Zugriff mit Python (das Beipiel arbeitet mit einem DIO am Core). Ich habe gleich mal verschiedene Methoden der Konvertierung von Integer zu Byte-Strings eingebaut. Das ist ein nicht grade einfaches Thema und da wird oft viel falsch gemacht. Vor allem gibt es fundamentale unterschiede zwischen Python 3 und Python 2.7.
Das Beispiel enthält auch die schwierigen ioctl-Zugriffe, die für den bitweisen Zugriff aber auch für den Zugriff über symbolische Namen benötigt werden.
Viel Erfolg!

Code: Select all

import time 	# wird für die Delays der Demo gebraucht
import struct	 	# wird für die Verarbeitung von byte-Strings vor allem mit Python 2.7 benötigt
import fcntl 		# wird für den bitweisen Zugriff auf das Prozessabbild über IOCTL benötigt

a=1; # Integer, in der ein bit über 14 Positionen geshiftet wird, um ein Lauflicht am DIO zu erzeugen

# Zuerst muss immer der Treiber über ein "open" statement geöffnet werden:
f = open("/dev/piControl0","wb+",0);


# Jetzt beginnt die Endlosschleife der Demo...
while 1:
    
    #Phase 1:  Lauflicht von Out1 bis Out14 am DIO
    # Diese Phase zeigt den Zugriff wie er mit Python 2.7 und Python 3 funktioniert. Es wird die struct-Bibliothek verwendet
    # um einen byte-string zu erzeugen, der für die write-Funktion benötigt wird...
    for i in range(0,14):
        f.seek(70);	# hier wird der Offset im Prozessabbild eingestellt: Outputs fangen in dieser Konfiguration ab Byte 70 an
        x = struct.pack('<H',(a<<i)); 
        f.write(x); 	# hier werden 2 byte in das Prozessabbild geschrieben, weil mit 'H' beim der pack-Funktion 2 Byte in x gepackt wurden
        time.sleep(0.1);        

    # Phase 2:  Lauflicht von Out14 bis Out1 am DIO
    # Diese Phase zeigt den Zugriff wie er mit Python 2.7 und Python 3 funktioniert. Statt mit struct wird mit einem bytearray gearbeitet,
    # um einen byte-string zu erzeugen, der für die write-Funktion benötigt wird...
    for i in range(13,-1,-1):
        f.seek(70);
        x1 = 255 & (a<<i); 		# low byte
        x2 = 255 & ((a<<i)>>8); 	# high byte
        x =bytearray([x1,x2]);
        f.write(x);
        time.sleep(0.1);

    # Phase 3:  Lauflicht von außen in die Mitte
    # Diese Phase zeigt den Zugriff wie er nur mit Python 3 funktioniert. Ab Python 3 verfügen Integer über die Methode "to_bytes",
    # mit der sich sehr elegant ein byte-string erzeugen lässt...     
    for i in range(0,7):
        f.seek(70);
        x=((a<<i)|(a<<(13-i))).to_bytes(2, 'little'); # wir brauchen little Endian für das Prozessabbild
        f.write(x);
        time.sleep(0.1);

    # Phase 4:  Lauflicht von der Mitte nach außen
    # Diese Phase zeigt den Zugriff wie er nur mit Python 3 funktioniert. Ab Python 3 kan mit der Funktion "bytes" ein byte array aus integern erzegt werden,      
    for i in range(6,-1,-1):
        f.seek(70);
        x1 = 255 & ((a<<i)|(a<<(13-i)));
        x2 = 255 & (((a<<i)|(a<<(13-i)))>>8);
        x = bytes([x1,x2]);
        f.write(x);
        time.sleep(0.1);

    # Phase 5:  die 14 Eingänge werden auf die Ausgänge gespiegelt, wir lesen jetzt Eingänge ein
    
    f.seek(0);		# Eingänge stehen ab Offset 0 in dieser Konfiguration
    x = f.read(2);	# 2 Byte werden eingelsen
    #i = x[0] + 256*x[1];  # Diese sehr einfache Konvertierung des Byte-strings in integerwerte geht nur mit Python 3!
    s = struct.unpack('<H',x); # so geht es auch mit Python 2.7
    i = s[0];	# weil s eine Liste ist, müssen wir den zugriff auf das erste Element beschränken
    f.seek(70); # jetzt kommt das Schreiben der grade gelesenen Daten... Ausgänge stehen ab Offset 70 in dieser Konfiguration
    #f.write(i.to_bytes(2,'little'));	# to_byte geht nur in Python 3
    f.write(struct.pack('<H',i));		# bei Python 2.7 brauchen wir die struct-Bibliothek
    time.sleep(3);

 # Phasen 6:  Über IOCTL Zugriff ermitteln wir den Offset und das Bit eines symbolischen Namens (wurde mit PiCtory angelegt),
 #  um dann ein einzelnes Bit zu lesen und zu schreiben:
 # Liste der vorhandenen IOCTL-Funktionen (siehe C-Quellcode von "piTest"):
 
 	#define  KB_CMD1                    _IO(KB_IOC_MAGIC, 10 )  // for test only
	#define  KB_CMD2                    _IO(KB_IOC_MAGIC, 11 )  // for test only
	#define  KB_RESET                   _IO(KB_IOC_MAGIC, 12 )  // reset the piControl driver including the config file
	#define  KB_GET_DEVICE_INFO_LIST    _IO(KB_IOC_MAGIC, 13 )  // get the device info of all detected devices
	#define  KB_GET_DEVICE_INFO         _IO(KB_IOC_MAGIC, 14 )  // get the device info of one device
	#define  KB_GET_VALUE               _IO(KB_IOC_MAGIC, 15 )  // get the value of one bit in the process image
	#define  KB_SET_VALUE               _IO(KB_IOC_MAGIC, 16 )  // set the value of one bit in the process image
	#define  KB_FIND_VARIABLE           _IO(KB_IOC_MAGIC, 17 )  // find a varible defined in piCtory
	#define  KB_SET_EXPORTED_OUTPUTS    _IO(KB_IOC_MAGIC, 18 )  // copy the exported outputs from a application process image to the real process image
	
 # erste Funktion: Wir suchen den Offset und das Byte von "Input_Pin_4" mit Hilfe von  KB_FIND_VARIABLE
    prm = (b'K'[0]<<8) + 17;	# der IOCTL-Parameter wird berechnet aus  ASCII 'K' um 8 bit geshiftet plus die ID der gewünschten Funktion (=17)
    name = struct.pack('37s',b"Input_Pin_4"); # Das Argument für Funktion 17 ist ein gepacktes byte array mit 37 Byte. Die ersten 32 Byte bestehen aus dem symbolischen Namen
    ret = fcntl.ioctl(f,prm, name);    # Die Ergebniswerte werden in ein Byte-Array geschrieben, 
    
    # wobei die ersten 32 Byte der symbolische Name sind, dann 2 Byte für den Offset, dann 1 Byte für die Bitposition und dann 2 Byte für die Länge
    # in C sieht das so aus:
#  typedef struct
#{
#   char        strVarName[32];         // Variable name
#    uint16_t    i16uAddress;            // Address of the byte in the process image
#    uint8_t     i8uBit;                 // 0-7 bit position, >= 8 whole byte
#    uint16_t    i16uLength;              // length of the variable in bits. Possible values are 1, 8, 16 and 32
#} SPIVariable;

    offset = struct.unpack_from('>H',ret,32)[0];	# so geht es auch mit Python 2.7
    bit = struct.unpack_from('B',ret,34)[0];
    length = struct.unpack_from('H',ret,35)[0];

# jetzt wird mit Funktion 15 ein einzelnes Bit ("Input_Pin_4") der Eingänge ausgelesen...
    prm = (b'K'[0]<<8) + 15;
# Das Argument der Funktion ist ein bytearray mit dieser Struktur:
#typedef struct
#{
#    uint16_t    i16uAddress;            // Address of the byte in the process image
#    uint8_t     i8uBit;                 // 0-7 bit position, >= 8 whole byte
#    uint8_t     i8uValue;               // Value: 0/1 for bit access, whole byte otherwise
#} SPIValue;
    
    value = bytearray([0,0,0,0]);
    struct.pack_into('>H',value,0,offset); 	# so funktioniert das auch in Python 2.7
    struct.pack_into('B',value,2,bit);
    fcntl.ioctl(f,prm,value);
    bitval = value[3];
# nochmal die Funktion 17. jetzt wollen wir wissen, wo "Output_Pin_4" im Prozessabbiold steht...
    prm = (b'K'[0]<<8) + 17;
    name = struct.pack('37s',b"Output_Pin_4");
    ret = fcntl.ioctl(f,prm, name);    
    offset = struct.unpack_from('>H',ret,32)[0];
    bit = struct.unpack_from('B',ret,34)[0];
    length = struct.unpack_from('H',ret,35)[0];

# jetzt wird mit Funktion 16 ein einzelnes Bit ("Output_Pin_4") der Ausgänge geschrieben...
    prm = (b'K'[0]<<8) + 16;
    value = bytearray([0,0,0,0]);
    struct.pack_into('>H',value,0,offset);
    struct.pack_into('B',value,2,bit);
    struct.pack_into('B',value,3,bitval);
    fcntl.ioctl(f,prm,value);
    
    time.sleep(3);
    
Unser RevPi Motto: Don't just claim it - make it!
User avatar
RevPiModIO
KUNBUS
Posts: 335
Joined: 20 Jan 2017, 08:44
Contact:

Re: Zugriff mit Python

Post by RevPiModIO »

Oder alternativ mit dem RevPiModIO-Modul für Python3

Aus dem Forum hier: viewtopic.php?f=13&t=86

Dann würde das so aussehen:

Code: Select all

import revpimodio
from time import sleep

# RevPiModIO instantiieren
rpi = revpimodio.RevPiModIO(auto_refresh=True)

# DIO Position in piCtory
dio = 32

# Input- und Outputnamen generieren als list()
# Die Namen müssen mit der piCtory Konfiguration übereinstimmen!
# In unserem Fall Inputs I_1, I_2, usw. und Outputs O_1, O_2, usw.
inp = ["I_{}".format(x) for x in range(1, 15)]
out = ["O_{}".format(x) for x in range(1, 15)]

# Jetzt beginnt die Endlosschleife der Demo...
while True:

    # Phase 1:  Lauflicht von O_1 bis O_14 am DIO
    for i in range(14):
        rpi.devices[dio][out[i]].value = True
        sleep(0.1)
        rpi.devices[dio][out[i]].value = False
    sleep(1)

    # Phase 2:  Lauflicht von O_14 bis O_1 am DIO
    for i in range(13, -1, -1):
        rpi.devices[dio][out[i]].value = True
        sleep(0.1)
        rpi.devices[dio][out[i]].value = False
    sleep(1)

    # Phase 3:  Lauflicht von außen in die Mitte
    for i in range(0, 8):
        rpi.devices[dio][out[i]].value = True
        rpi.devices[dio][out[-1-i]].value = True
        sleep(0.1)
        rpi.devices[dio][out[i]].value = False
        rpi.devices[dio][out[-1-i]].value = False
    sleep(1)

    # Phase 4:  Lauflicht von der Mitte nach außen
    for i in range(7, -1, -1):
        print(i)
        rpi.devices[dio][out[i]].value = True
        rpi.devices[dio][out[-1-i]].value = True
        sleep(0.1)
        rpi.devices[dio][out[i]].value = False
        rpi.devices[dio][out[-1-i]].value = False
    sleep(1)

    # Phase 5: die 14 Eingänge werden auf die Ausgänge gespiegelt
    for i in range(14):
        rpi.devices[dio][out[i]].value = rpi.devices[dio][inp[i]].value

    sleep(5)
Da wir hier schon gleich mit den Symbolischen Namen arbeiten, brauchen wir die restlichen Phasen nicht. Einzelne Bits für Ausgänge setzen wir einfach über den Namen aus piCtory. Auch die einzelnen Module können mit Namen angesprochen werden, statt der Positionsnummer 32 verwenden wir den Namen "dio01" aus piCtory.

Code: Select all

rpi.devices["dio01"]["Output_Pin_4"].value = rpi.devices["dio01"]["Input_Pin_4"].value
Viel Spaß!
python3-RevPiModIO - https://revpimodio.org/ || Der RevPi ist das Beste, was passieren konnte!
Tommy
Posts: 12
Joined: 27 Apr 2017, 11:29

Re: Zugriff mit Python

Post by Tommy »

Hallo Kunbus,

auf Grundlage von Volkers Beispielen versuche ich die Arbeitsweise des RevPi zu verstehen, jedoch gibt es wohl bei mir noch einige Ungereimtheiten. Ich hoffe Ihr könnt mir helfen.
So wie ich es verstanden habe läuft ja die Kommunikation zwischen den I/O Modulen und dem Anwender über das Prozessabbild. Will ich Ausgänge setzen schreibe ich zwei Bytes ins Prozessabbild ab Byte 70, will ich Eingänge lesen, ab Byte 0.
Ich versuche es zuerst ganz einfach zu realisieren.
Ich schreibe ein ganz einfaches Python Script

Code: Select all

#!/usr/bin/python3
# -*- coding:utf-8 -*-
a = 1 
f = open("/dev/piControl0","wb+",0)
f.seek(70)
x1 = 255 & (a<<0)
x2 = 255 & ((a<<0)>>8)
x = bytearray([x1,x2])
f.write(x)
Müsste dieser Quellcode nicht reichen um den Ersten Ausgangspin auf high zu setzen?
Und wenn ich jetzt noch ein sleep einfügen würde (natürlich vorher mit import time) von zwei Sekunden und ich setze den gleichen Quellcode mit a = 0 darunter, abgesehen vom Öffnen des Treibers,
müsste dann nicht nach zwei Sekunden der Ausgang wieder low werden ?
Leider funktioniert es aber nicht.
Ich hoffe Ihr könnt mir helfen.
User avatar
RevPiModIO
KUNBUS
Posts: 335
Joined: 20 Jan 2017, 08:44
Contact:

Re: Zugriff mit Python

Post by RevPiModIO »

Hi Tommy!

Ist dein Offset von 70 richtig?

Das kannst du mit piTest prüfen, bei mir (piCore + DIO) ist der Offset für die Outputs 72!

Code: Select all

piTest -v "Output"
variable name: Output
       offset: 72
       length: 16
          bit: 0
Wobei "Output" mit dem Namen in piCtory übereinstimmen MUSS (erster Output)!

Ab dem offset hast du theoretisch 16 Bits um die Ausgänge zu setzen, auch wenn das DIO nur 14 hat. Das entspricht zwei Byte, die du schreiben willst, dafür ist das ganze mit x1 und x2 usw. Damit generieren wir 2 Bytes, die wir dann schreiben.

Das "a = 1" ist nur, um das erste Bit auf 1 zu setzen um diese 1 dann mit << oder >> zu verschieben an die Stelle, des Outputs den du setzen willst - Ist beim Lauflicht praktisch, aber du kannst dir das auch mal anders aufbauen:

Code: Select all

#!/usr/bin/python3
# -*- coding:utf-8 -*-
from time import sleep

# Dieses Bit schalten
outputnummer = 5

f = open("/dev/piControl0","wb+",0)

# An richtigen Offset gehen (piTest)
f.seek(72)

# Auf die 1 eine Bitverschiebung zum richtigen Output
# und das als zwei Bytes ausgeben zum schreiben mit
# Byteorder "little"
x = (1 << outputnummer).to_bytes(length=2, byteorder="little")

# Einfach mal anzeigen, was in x eigentlich drin steht
print(x)

# Die zwei Byte schreiben
f.write(x)

# Warten
sleep(2)

# Wieder an Offset gehen
f.seek(72)

# Und auf NULL setzen
f.write(b'\x00\x00')
Von der Vorstellung her hast du da quasi 16 Schalter im Prozessabbild, die du bedienen kannst. Natürlich kannst du auch mehrere Schalter betätigen (Bei dem Beispiel oben ist aber IMMER nur ein Bit gesetzt, alle anderen werden IMMER auf NULL gesetzt)

Wenn du mehrere Outputs setzen willst musst du dir die Schalterstellung überlegen z.B. "0001000010000100" -> Dezimalwert dazu ist 4228 -> Bytes mit order little sind b'\x84\x10'

Code: Select all

(4228).to_bytes(length=2, byteorder="little")
Das setzt dann Ausgang 3, 8, 13...

Gruß, Sven
python3-RevPiModIO - https://revpimodio.org/ || Der RevPi ist das Beste, was passieren konnte!
Tommy
Posts: 12
Joined: 27 Apr 2017, 11:29

Re: Zugriff mit Python

Post by Tommy »

Hallo Sven,

vielen Dank für deine Info und du hattest recht, es lag am Offset. Könntest du mir vielleicht noch erklären warum Volkers erster Ausgang mit einem Offset von 70 bytes anfängt und meiner bei 72 bytes? Volker hat ja auch einen DIO verwendet.
User avatar
RevPiModIO
KUNBUS
Posts: 335
Joined: 20 Jan 2017, 08:44
Contact:

Re: Zugriff mit Python

Post by RevPiModIO »

Hi Tommy!

Das liegt sicher nur daran, dass Volker dein DIO links vom Pi angeschlossen hat und wir haben es Rechts davon. Der PiCore hat 2 Byte Speicher im Prozessabbild um die sich dass dann verschiebt :D

Gruß, Sven!
python3-RevPiModIO - https://revpimodio.org/ || Der RevPi ist das Beste, was passieren konnte!
Post Reply