Tworzenie pakietu Pythona3 do obsługi zasilacza AX-3003P

Tworzenie pakietu Pythona3 do obsługi zasilacza AX-3003P

Witam!

Od niedawna jestem posiadaczem programowalnego zasilacza laboratoryjnego AX-3003P.

Jedną z funkcji jest możliwość kontrolowania zasilacza za pomocą komend wysyłanych po USB. Axio met stworzyło nawet oprogramowanie do sterowanie zasilaczem a także kilka przykładów w Lab View.

Niestety jest z tym kilka problemów:

  1. Oprogramowanie do sterowania zasilaczem to w zasadzie tylko wygodniejsza wersja panelu kontrolnego.

2. Lab view kosztuje 1.720,00 zł na rok za normalną licencję albo 49 euro w wersji studenckiej.

3. Ani Lab view ani ta aplikacja nie działa na linuxie.

Aby rozwiązać te problemy postanowiłem napisać pakiet w Pythonie3 który umożliwi mi pisanie własnych programów do kontroli zasilacza.

Standard Commands for Programmable Instruments (SCPI)

Mój zasilacz jak większość programowalnego sprzętu laboratoryjnego jest kontrolowany za pomocą komend SCPI.

SCPI to standard syntaxów komend używanych do kontroli sprzętu pomiarowe i testowego. Według dokumentacji, zasilacz obsługuje 59 różnych komend SCPI. Poniżej zamieszczam tabelę najczęściej używanych komend. W załącznikach znajdziecie pełną dokumentację zasilacza.

Komenda Przykład OPis
CURR [current] CURR 0.1 Ustaw natężenie prądu.
VOLT [voltage] VOLT 5 Ustaw napięcie.
MEAS:CURR? MEAS:CURR? Zmierz natężenie.
MEAS:VOLT? MEAS:VOLT? Zmierz napięcie .
OUTP [state] OUTP ON Wyłącz/Włącz wyjście.
CURR:PROT [current] CURR:PROT 1 Ustaw natężenie granicznego OCP.
CURR:PROT? CURR:PROT? Sprawdź natężenie graniczne OCP.
CURR:PROT:CLE CURR:PROT:CLE Zresetuj OCP.
CURR:PROT:TRIP? CURR:PROT:TRIP? Sprawdź czy OCP zostało aktywowane.
CURR:PROT:STAT [state] CURR:PROT:STAT ON Wyłącz/Włącz OCP.
VOLT:PROT [voltage] VOLT:PROT 10 Ustaw napięcie graniczne OVP.
VOLT:PROT? VOLT:PROT? Sprawdź napięcie graniczne OVP.
VOLT:PROT:CLE VOLT:PROT:CLE Zresetuj OVP.
VOLT:PROT:TRIP? VOLT:PROT:TRIP? Sprawdź czy OVP zostało aktywowane.
VOLT:PROT:STAT [state] VOLT:PROT:STAT OFF Wyłącz/Włącz OVP.

Notatka:

OVP – OverVoltage Protection

OCP – OverCurrent Protection

Po podłączeniu zasilacza do komputera uruchomiłem komendę lsusb aby wyświetlić listę wszystkich urządzeń podłączonych do portów USB.

Zasilacz pokazał się jako “Prolific Technology, Inc. PL22303 Serial Port”.

Następnie uruchomiłem komendę l na ścieżce /dev/setial/by-id aby wyświetlić wszystkie porty szeregowe obecne w systemie.

Na moim systemie l to alias do komendy ls -l.

y

Jak widać zasilacz jest dostępny pod plikiem /dev/ttyUSB0.

Jednak przed otworzeniem portu szeregowego, pozostał do zrobienia jeszcze jeden krok.

Ustawienia dostępu pliku /dev/ttyUSB0 nie pozwalają na otworzenie pliku chyba że użytkownik jest rootem albo należy do grupy dialout.

Aby rozwiązać ten problem po prostu dodałem siebie do grupy dialout wykonując komendę usermod -A -G dialout [użytkownik] jako root.

Wiem że nie jest to eleganckie rozwiązanie i pewnie parę osób będzie narzekało w komentarzach ale trudno.  (:

Teraz kiedy mam już dostęp do zasilacza, pora na otworzenie portu szeregowego i wykonanie kilku komend.

Ustawienia dla portu szeregowego (domyślne ustawienia zasilacza) to:

 

  • Baud rate: 9600
  • 1 stop bit
  • Brak bitów parzystości
  • 8 Bitów danych
  • Brak kontroli przepływu.

Jako terminala użyłem programu o nazwie cutecom: https://gitlab.com/cuteco/cutecom.

Program jest dostępny jako binarka na większości dystrybucji linuxa, a także na Maca i Windowsa.

Jak widać wszystko działa. Na wysłanie komendy MEAS:CURR? zasilacz odpowiedział aktualnym natężeniem prądku na wyjściu zasilacza wysłanym jako tekst zakończony nową linią (znak \n). Każda komenda wysłana do zasilacza również jest zakończone znakiem nowej linii.

 

Proof of concept

Teraz pora na stworzenie prostego skryptu który udowodni że da się steroważ zasilaczem laboratoryjny za pomocą pythona.

Skrypt wykorzystuje pakiet pySerial który dostarcza wygodną w użyciu klasę do obsługi portu szeregowego. Pakiet działa na Windowsie, Linuxie, systemach MacOS a także na BSD.

Skrypt przyjmuje ścieżkę do portu szeregowego jako argument z konsoli, otwiera połączenie do zasilacza, ustawia natężenie prądu na 0.1A i napięcie na 1.12V a następnie uruchamia wyjście i wyświetla napięcie na wyjściu w pętli.

Oto przykładowy efekt.

Opóźnienia po wysłaniu komend zostały dodany ponieważ zauważyłem że jeśli komendy są wysyłane w krótkich odstępach czasu, zasilacz czasem nie wykonuje cześci z nich mimo że zostały one wysłane poprawnie.

Struktura pakietu Pythona

Podstawy pakowania projektów napisanych w pythonie zostały bardzo dobrze opisane w oficjalnym tutorialu https://packaging.python.org/tutorials/packaging-projects/

W moim przypadku struktura pakietu wygląda tak:

Folder AX3003P zawieta właściwy kod pakietu. W pliku AX3003P.py znajduje się klasa która będzie zawierała cały kod niezbędny do obsługi zasilacza. (Nasz pakiet nie będzie zawierał dużych ilości kodu więc umieszczenie wszystkiego w jednym pliku ma w tym wypadku sens).

Plik init.py jest niezbędny aby folder AX3003P został rozpoznany jako moduł.

Folder examples zawiera przykłady użycia biblioteki.

Plik LICENSE zawiera tekst licencji pod którą pakiet jest udostępniony (wybrałem MIT).

Plik README.md zawiera opis pakietu.

Plik requirements.txt zawiera listę zależności pakietu.

Plik setup.py zawiera skrypt używany do budowania pakietu. W tym pliku zdefiniowane są także metadane pakietu.

Łączenie się z zasilaczem

Pierwszy kod który musiałem napisać to kod do łączenia się z zasilaczem:

Wewnątrz klasy AX3003P umieściłem dwie metody. Metoda connect() łączy się z zasilaczem, metoda disconnect() rozłącza połączenie.

Ścieżka do portu szeregowego jest przechowywana w self.device. Konstruktor klasy przyjmuje jako jedyny parametr ścieżkę do portu szeregowego zasilacza.

Funkcja connect() poza klasą jest po to aby po dodaniu:

Do pliku init.py, było możliwe takie użycie biblioteki w taki sposób:

Który w mojej opinii jest dużo bardziej czytelny niż takie użycie:

Które trzeba byłoby stosować gdyby tej funkcji tam nie było.

Metody do wysyłania komend

Teraz pora na dodanie metod do wysyłania komend do zasilacza. Zasilacz przyjmuje dwa główne typy komend.

Pierwszy typ komend nic nie zwraca. Drugi typ komend zwraca jakieś dane.

Pierwszy typ jest zaimplementowany tutaj:

Metoda najpierw dodaje do komendy znak nowej linii a następnie enkoduje ją do typu byte array (metoda encode()), wysyła ją do zasilacza i czeka WRITE_COMMAND_DELAY sekund.

Implementacja drugiego typu komend jest nieco bardziej skomplikowana:

Początek jest ten sam: dodajemy znak nowej linii i enkodujemy komendę.

Później wysyłamy wiadomość do zasilacza i czekamy READ_RETRIES_DELAY sekund. Następnie sprawdzamy czy w buforze odebranych danych są jakieś bajty. Jeśli tak odczytujemy jedną linię z bufora, dekodujemy wiadomość do stringa (format unicode_escape dekoduje wiadomośc jako ASCI), usuwamy znak nowej linii z końca stringa i zwracamy wynik. Jeśli w buforze nie było żadnych danych to wtedy wysyłamy komendę jeszcze raz i cały proces zaczyna się od nowa. Jeśli po timeout milisekund nadal nie otrzymamy odpowiedzi to wtedy metoda zwraca None co sygnalizuje problem z połączeniem.

WRITE_COMMAND_DELAY jest ustawione na 100 milisekund (wartość ustalona eksperymentalnie).

Wartość READ_RETRIES_DELAY została ustalona na podstawie eksperymentu w którym mierzyłem ilość komend po których nie otrzymałem odpowiedzi w funkcji READ_RETRIES_DELAY.

Wyniki z eksperymentu:

Na podstawie wyników eksperymentu ustaliłem opóźnienie READ_RETRIES_DELAY na 30 milisekund.

Metody do kontrolowania zasilacza

Teraz pora na napisanie kodu który będzie odpowiadał za kontrolowanie zasilacza.

Dzięki metodom które napisałem wcześniej większość implementacji sprowadzała się do jednej linijki kodu.

Oto kilka przykładów:

float() odpowiada za konwersję stringa zwracanego przez sendReadCommand do zmiennej typu float.

Dokumentacja pakietu za pomocą Sphinxa

Kolejnym etapem projektu było napisanie dokumentacji.

Do tego celu wykorzystałem narzędzie do generowania dokumentacji o nazwie Sphinx.

Narzędzie przyjmuje strony dokumentacji napisane jako tekst reST (reStructuredText) i generuje dokumentację w postacji strony internetowej.

Dobry tutorial na temat instalacji narzędzia znajduje się tutaj: http://www.sphinx-doc.org/en/1.5/tutorial.html

Polecam również ten tutorial: http://www.sphinx-doc.org/en/master/usage/quickstart.html

W moim przypadku użyłem również rozszerzenia o nazwie autodoc. Umożliwia ono pisanie dokumentacji bezpośrednio w kodzie źródłowym i automatyczne generowanie odpowiedniej strony html. Narzędzie autodoc obsługuje kilka stylów dokumentacji. Ja użyłem stylu reST.

Oto przykład udokumentowanych funkcji:

Więcej przykładów można znaleźć tutaj: https://thomas-cokelaer.info/tutorials/sphinx/docstring_python.html

Po dodaniu strony z takim oto kodem do dokumentacji:

Dodania rozszerzenia autodoc do listy rozszerzeń w pliku konfiguracyjnym  (conf.py)

I zbudowaniu dokumentacji, moim oczom ukazała się gotowa dokumentacja:

Następnie zdecydowałem się opublikować dokumentacji w serwisie readthedocs.com. Jest to platforma która umożliwia automatyczne budowanie i publikację dokumentacji projektu. Dokładne instrukcje o tym jak to zrobić są przedstawione w oficjalnym tutorialu: https://docs.readthedocs.io/en/stable/intro/getting-started-with-sphinx.html

A oto efekt:

Publikowanie pakietu na PyPI

Teraz pora na zbudowanie naszego pakietu i publikację na PyPI. PyPI (Python Package Index). Jest to oficjalne repozytorium zawierające. Pakiety znajdujące się w tym repozytorium mogą następnie zostać zainstalowane za pomocą menagera pakietów pip. Publikowanie projektów a PyPI jest bardzo proste.

Najpierw założyłem konto na PyPI https://pypi.org/

Następnie dodałem taki oto kod do pliku setup.py:

Pakiet setuptools zawiera zestaw narzędzi umożliwiających budowanie własnych pakietów.

Funkcja setup jako argumenty przyjmuje zestaw parametrów opisujących pakiet.

Następnym krokiem było zbudowanie pakietu. Aby to zrobić należy najpierw zainstalować 2 dodakowe pakiety: wheel i setuptools.

Można to zrobić za pomocą komendy: pip install wheel setuptools.

Następnie zbudowałem pakiet uruchamiając skrypt setup.py z argumentami sdist i bdist_wheel.

Jeśli wszystko poszło dobrze, w folderze projektu powinien pojawić się folder dist zawierający dwa pliki. Jeden z rozszerzeniem .whl a drugi z rozszerzniem .tar.gz.

Ostatnim krokiem jest wysłanie zbudowanego pakietu do PyPI.

W tym celu zainstalowałem program o nazwie twine. Twine to narzędzie służące do interakcji z PyPI. Można je zainstalować za pomocą menagera pip.

Wysłanie pakietu do PyPI było potem już kwestią jednej komendy:

Po mniej więcej minucie od wrzucenia pakietu na PyPI byłem w stane pobrać go za pomocą menagera pip.

Linki

To by było na tyle jeśli chodzi o ten niewielki projekt.

Kod źródłowy całej biblioteki  jest dostępny na githubie: https://github.com/Bill2462/AX3003P

W załączniku znajdziecie dokumentację pozostałych komend obsługiwanych przez zasilacz.

Pliki załączone do artykułu:

Ocena: 0/5 (głosów: 0)

Podobne posty

Jeden komentarz

Odpowiedz

anuluj

Masz uwagi?