-
Notifications
You must be signed in to change notification settings - Fork 0
Labcontrol ontwerp en Python codering
Deze pagina bevat aantekeningen over het ontwerp/structurering van code binnen Labcontrol en over het besturen van apparatuur met Python. Een belangrijke keuze in het ontwikkelproces is die voor communicatie via een USB i.p.v. via een netwerk verbinding, omdat TCP/IP communicatie binnen Windesheim snel lastig wordt. USB was toendertijd de 'easy way out'.
Labcontrol is ontstaan via het betere 'jatwerk'. Daarbij is er alleen gejat wat ook begrepen werd. Op een gegeven moment werd het teveel een 'zooitje', met name de implementatie van de TDS2002B en SDS12012XE oscilloscopen. Daarom is er een begin gemaakt met het structuren van de Python implementatie van de oscilloscoop. Omdat Labcontrol geschikt wordt gedacht voor gebruik buiten het E-lab alleen, is het belangrijk om de structurering zo algemeen mogelijk te houden. Daarom is de structureren op basis van de buitenkant, zoals bijvoorbeeld een TDS2002B:

Een TDS2002B heeft, display buiten beschouwing latend, 4 secties:
- Vertical
- Horizontal
- Trigger
- De rest ('utility').
Die opdeling is terug te vinden in het ontwerp klassendiagram oscilloscopen binnen Labcontrol:

Hier kun je het brondocument vinden. Zoals je kunt zien leunt het klassendiagram vooral op twee uitgangspunten:
- Het klassendiagram kent (vrijwel) dezelfde indeling als de buitenkant van een gemiddelde oscilloscoop
- De opbouw van de code reflecteert daardoor de wijze van gebruik. Als voorbeeld: Een gebruiker stel de timebase van een oscilloscoop in via 'horizontal'. Dat moet een gebruiker van Labcontrol dat dan ook in de code doen.
V.w.b. de implementatie in Python bestond de voorkeur om iets van factory-achtig patroon te kunnen gebruiken. Eerste pogingen waren mislukt, maar na verloop van tijd en gepruts leer je dat Python iets als classmethod kent (zie link). Via een artikel dat uitlegt wanneer je __new__ zou moeten gebruiken i.p.v. __init__ (zie link), kwam ik op een stackoverflow pagina waar werd verwezen naar pep487. Hier wordt ingegaan op het zogenaamde "autoregistratie van afgeleide klassen". Deze aanpak is als basis gebruikt voor de verdere factory-implementatie
Sinds Python 3.6 kunnen subclasses zich automatisch laten registreren bij de ouderklasse. Op basis van enige testcode is er code geschreven waarbij:
- BaseScope de zgn
__init_subclass__implementeert, waarbij subclasses in een lijst worden opgenomen. Subclasses moeten dan wel in het "import pad" staan, anders wordt een gedefinieerde subclasse niet geregistreerd. - De
__new__functie van BaseScope een lijst met geabonneerde objecten afloopt, waarbij - De subclass, via een eigen overridden
getDevicemethode, controleert of er een match is, op basis van een VISA url of IDN response. Als er een match is, retourneertgetDevicehet specifieke object van de subclass.
De werkwijze voor auto registratie van Scope subklassen:
- Een nieuw toe te voegen besturing voor een oscilloscoop moet geïmplementeerd worden als een subklasse van BaseScope, bijvoorbeeld:
class TekScope(BaseScope):- De implementatie van een oscilloscoop moet de methode
getDevice(cls,url)implementeren, bijvoorbeeld:
@classmethod
def getDevice(cls, urls, host):
"""
Tries to get (instantiate) this device, based on matched url or idn response
This method will ONLY be called by the BaseScope class, to instantiate the proper object during
creation by the __new__ method of BaseScope.
"""
urlPattern = "USB" #fix for now, TODO: make more robust e.g. able to connect to TCP or serial.
if host == None:
for url in urls:
if urlPattern in url:
rm = visa.ResourceManager()
mydev = rm.open_resource(url)
mydev.timeout = 10000 # ms
mydev.read_termination = '\n'
mydev.write_termination = '\n'
desc = mydev.query("*idn?")
if desc.find("TEKTRONIX,TDS") > -1: #Tektronix device found via IDN.
cls.visaInstr = mydev
return cls
else:
try:
ip_addr = socket.gethostbyname(host)
addr = 'TCPIP::'+str(ip_addr)+'::INSTR'
mydev = rm.open_resource('TCPIP::'+str(ip_addr)+'::INSTR')
cls.visaInstr = mydev
return cls
except socket.gaierror:
return None
return None- Tijdens creatie van BaseScope, wordt
__new__aangeroepen. Deze loopt de lijst met aangemelde (sub) Scope klassen af:
rm = visa.ResourceManager()
devUrls = rm.list_resources()
for scope in cls.scopeList:
dev = scope.getDevice(devUrls)Bovenstaande bestaat op het moment alleen nog in een dummy implementatie. Er is een klasse FakeScopie aangemaakt, waarvan de methode getDevice() niks anders doet dan een nieuw FakeScope object te retourneren. Niet nuttig, maar zoals hieronder te zien is, één die wel lijkt te werken:

Te zien is dat variabele scope een referentie bevat naar FakeScopie en niet slechts naar een BaseScope object. Daarom is het te verwachten dat je op deze manier nieuw gemaakt BaseScope subclasse (bijvoorbeeld voor Owon, Hantek, Rigol etc) op eenvoudige wijze in labcontrol 'kan hangen'. Het enige punt is of de registratie van nieuwe subklassen ook soort van automatisch gaat. Misschien kan dat door gebruik te maken het __init__.py bestand, dat in elke map van de src boom te vinden is.
De hier bovengenoemde PEP487 heeft ook invloed op klasse-attributen en descriptoren. Dat laatste is een interessante, maar lastige feature van Python. Die dingen kan je overal voor gebruiken binnen Python, zo zou met descriptoren "getters/setters" functionaliteit kunnen maken, iets wat met decorators ook zou moeten kun, maar wat mij nog niet gelukt is. Gelukkig zijn er verschillende tutorials en blogs over Python Descriptors te vinden. Een goede vind je hier
Op python docs datamodel staat onder de paragraaf over het aanroepen van o.a. properties het volgende

Dit past, helaas, bij de ervaringen met properties: als de baseclass al een property geïmplementeerd heeft door er iets van een waarde te retourneren, lukte het bijv. niet meer om via een subclass implementatie i.p.v. de waarde nu een object te retourneren.
Update 17 maart 2025: de pesterij met Python is dat a) er behoorlijke nog aan de taal geknutseld wordt b) dat er van alles en nog wat geschreven wordt en c) ik weet er gewoon te weinig van. Maar dan lees je op https://realpython.com/python-property/#overriding-properties-in-subclasses :

Dus zeggen deze gasten dat het wel kan.... AAAAhhhh..... Het realpython artikel is van december 2024 en uitgaande dat deze gasten weten wat ze doen (namelijk hun stuff gestest) zal het wel betekenen dat ik iets ergens anders verprutst heb, of dat schrijver van dit artikel (per ongeluk) een (oudere) versie van Python draait die niet gelijk is aan de versie waarover de Python organisatie boven overschrijft, of ik heb een oude versie van de officiële documentatie onder de neus gehad. Hoe dan ook: 15 maart heb ik alle properties uit de code gegooid. Het werkt nu als een zonnetje.
#Python weetjes Hier staan een aantal python zaken die tijdens het coderingsproces waren weggezakt en die toch wel handig bleken bij de hand te hebben.
De init methode initialiseert het object. En je moet dat strikt letterlijk nemen: initten, niet creëren. Als init wordt uitgevoerd, bestaat het object al. Daarom mag de init methode ook niets retourneren, daar zijn ander methoden voor. Init kan je aanroepen met vaste parameters, maar ook met een variabele argumentenlijst:
class MyClass:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# Example usage
obj = MyClass(1, 2, a=3, b=4)
print(obj.args) # Output: (1, 2)
print(obj.kwargs) # Output: {'a': 3, 'b': 4}Descriptoren: -https://python-course.eu/oop/introduction-to-descriptors.php -https://elfi-y.medium.com/python-descriptor-a-thorough-guide-60a915f67aa9
Vragen over het retourneren van objecten d.m.v. methods
introspectie Casts en objecten
Uitgebreid verhaal of decoration in Python
Python documentatie over compound statements
Artikel over new: zie hier
Artikel over gebruik class_method als een soort factory, zodat er een object van het juiste type wordt geretourneerd. Die truuk wordt hier uitgelegd; https://pynative.com/python-class-method/ Artikel over het verschil tussen new en init, legt ook uit welke functie eerst wordt aangeroepen.: https://builtin.com/data-science/new-python Artikel over metaclasses: https://www.geeksforgeeks.org/python-metaclass-__new__-method/ Artikel over opties van classes en functies over meerdere bestanden: https://python-forum.io/thread-41515.html
- Home
- API doc
- Aanleiding Labcontrol
- Automatisch Testen
- Feedback Control Systems met Python
- Hantek 6022BL
- Installatie Labcontrol
- Instrumentatie, VISA en USB
- Interessante dingen
- Labcontrol Dashboard
- Labcontrol ontwerp en Python codering
- OWON DGE1060
- PyInstaller artikel
- Pythonkunde
- Tektronix TDS2002C USB
- Toolings Weetjes
- Wat heb je nodig voor Labcontrol
- klad