Sebuah simulator untuk perangkat keras nyata. Proyek ini menyediakan server yang mampu menelurkan beberapa perangkat simulasi dan melayani permintaan secara bersamaan.
Proyek ini hanya menyediakan infrastruktur yang diperlukan untuk meluncurkan server dari file konfigurasi (YAML, TOML atau json) dan sarana untuk mendaftarkan plugin perangkat pihak ketiga melalui mekanisme titik masuk python.
Sejauh ini, proyek ini menyediakan transportasi untuk TCP, UDP dan jalur serial. Dukungan untuk transport baru (misal: USB, GPIB atau SPI) diterapkan berdasarkan kebutuhan.
PR dipersilakan!
( TL;DR : pip install sinstruments[all]
)
Dari dalam lingkungan python favorit Anda:
$ pip install sinstruments
Selain itu, jika Anda ingin menulis file konfigurasi YAML di YAML:
$ pip install sinstruments[yaml]
...atau, untuk konfigurasi berbasis TOML:
$ pip install sinstruments[toml]
Setelah diinstal server dapat dijalankan dengan:
$ sinstruments-server -c <config file name>
File konfigurasi menjelaskan perangkat mana yang harus dibuat oleh server bersama dengan serangkaian opsi seperti transport yang digunakan setiap perangkat untuk mendengarkan permintaan.
Bayangkan Anda perlu mensimulasikan 2 GE Pace 5000 yang masing-masing dapat dijangkau melalui port TCP dan CryoCon 24C yang dapat diakses melalui jalur serial.
Pertama, pastikan dependensi diinstal dengan:
$ pip install gepace[simulator] cryoncon[simulator]
Sekarang kita dapat menyiapkan konfigurasi YAML yang disebut simulator.yml
:
devices :
- class : Pace
name : pace-1
transports :
- type : tcp
url : :5000
- class : Pace
name : pace-2
transports :
- type : tcp
url : :5001
- class : CryoCon
name : cryocon-1
transports :
- type : serial
url : /tmp/cryocon-1
Kami sekarang siap meluncurkan server:
$ sinstruments-server -c simulator.yml
Itu saja! Anda sekarang dapat terhubung ke perangkat Pace mana pun melalui TCP atau CryoCon menggunakan jalur serial emulasi lokal.
Mari kita coba sambungkan ke Pace pertama dengan alat baris perintah linux nc (alias netcat) dan tanyakan *IDN?
Perintah SCPI:
$ nc localhost 5000
*IDN?
GE,Pace5000,204683,1.01A
Ini adalah ringkasan dari perpustakaan instrumentasi pihak ketiga terkenal yang menyediakan simulatornya sendiri.
Jika Anda menulis perangkat yang tersedia untuk umum, silakan lengkapi daftar di atas dengan membuat PR.
Petunjuk : sinstruments-server ls
menampilkan daftar plugin yang tersedia.
File konfigurasi dapat berupa file YAML, TOML atau JSON asalkan diterjemahkan ke dalam kamus dengan uraian di bawah ini.
Dalam bab ini kita akan menggunakan YAML sebagai contoh referensi.
File tersebut harus berisi setidaknya kunci tingkat atas yang disebut devices
. Nilainya harus berupa daftar deskripsi perangkat:
devices :
- class : Pace
name : pace-1
transports :
- type : tcp
url : :5000
Setiap deskripsi perangkat harus berisi:
tcp
, udp
, serial
)Opsi lain apa pun yang diberikan kepada setiap perangkat diteruskan langsung ke objek plugin tertentu saat runtime. Setiap plugin harus menjelaskan opsi tambahan apa yang didukungnya dan cara menggunakannya.
Untuk transport TCP dan UDP, url memiliki format <host>:<port>
.
Host kosong (seperti contoh di atas) diartikan sebagai 0.0.0.0
(yang berarti mendengarkan di semua antarmuka jaringan). Jika host adalah 127.0.0.1
atau localhost
perangkat hanya akan dapat diakses dari mesin tempat simulator berjalan.
Nilai port 0 berarti meminta OS untuk menetapkan port gratis (berguna untuk menjalankan rangkaian pengujian). Jika tidak, harus berupa port TCP atau UDP yang valid.
Url mewakili file khusus yang dibuat oleh simulator untuk mensimulasikan jalur serial yang dapat diakses seperti file jalur serial linux /dev/ttyS0
.
Fitur ini hanya tersedia di linux dan sistem yang terminal semu pty
diimplementasikan dengan python.
Urlnya opsional. Simulator akan selalu membuat nama non deterministik seperti /dev/pts/4
dan akan mencatat informasi ini jika Anda perlu mengaksesnya. Fitur ini paling berguna saat menjalankan rangkaian pengujian.
Anda bebas memilih jalur url mana pun yang Anda suka (misal: /dev/ttyRP10
) selama Anda yakin simulator memiliki izin untuk membuat file simbolik.
Untuk transportasi apa pun (TCP, UDP, dan jalur serial) dimungkinkan untuk melakukan simulasi dasar kecepatan saluran komunikasi dengan menyediakan parameter baudrate
tambahan ke konfigurasi. Contoh:
- class : CryoCon
name : cryocon-1
transports :
- type : serial
url : /tmp/cryocon-1
baudrate : 9600
Simulator menyediakan konsol python pintu belakang gevent yang dapat Anda aktifkan jika Anda ingin mengakses proses simulator yang sedang berjalan dari jarak jauh. Untuk mengaktifkan fitur ini cukup tambahkan konfigurasi tingkat atas berikut ini:
backdoor : ["localhost": 10001]
devices :
- ...
Anda bebas memilih port TCP lainnya dan mengikat alamat. Perlu diketahui bahwa pintu belakang ini tidak memberikan autentikasi dan tidak berupaya membatasi apa yang dapat dilakukan oleh pengguna jarak jauh. Siapa pun yang dapat mengakses server dapat mengambil tindakan apa pun yang dapat dilakukan oleh proses python yang sedang berjalan. Oleh karena itu, meskipun Anda dapat mengikat ke antarmuka apa pun, demi keamanan, disarankan agar Anda mengikat ke antarmuka yang hanya dapat diakses oleh mesin lokal, misalnya, 127.0.0.1/localhost.
Penggunaan
Setelah pintu belakang dikonfigurasi dan server berjalan, di terminal lain, sambungkan dengan:
$ nc 127.0.0.1 10001
Welcome to Simulator server console.
You can access me through the 'server()' function. Have fun!
>>> print(server())
...
Menulis perangkat baru itu sederhana. Bayangkan Anda ingin mensimulasikan osiloskop SCPI. Satu-satunya hal yang perlu Anda lakukan adalah menulis kelas yang mewarisi BaseDevice dan mengimplementasikan handle_message(self, message)
di mana Anda harus menangani berbagai perintah yang didukung oleh perangkat Anda:
# myproject/simulator.py
from sinstruments . simulator import BaseDevice
class Oscilloscope ( BaseDevice ):
def handle_message ( self , message ):
self . _log . info ( "received request %r" , message )
message = message . strip (). decode ()
if message == "*IDN?" :
return b"ACME Inc,O-3000,23l032,3.5A"
elif message == "*RST" :
self . _log . info ( "Resetting myself!" )
...
Jangan lupa untuk selalu mengembalikan bytes
! Simulator tidak menebak-nebak cara menyandikan str
Dengan asumsi file simulator.py
ini adalah bagian dari paket python bernama myproject
, hal kedua yang harus dilakukan adalah mendaftarkan plugin simulator Anda di setup.py Anda:
setup (
...
entry_points = {
"sinstruments.device" : [
"Oscilloscope=myproject.simulator:Oscilloscope"
]
}
)
Anda sekarang dapat meluncurkan simulator Anda dengan menulis file konfigurasi:
# oscilo.yml
devices :
- class : Oscilloscope
name : oscilo-1
transports :
- type : tcp
url : :5000
Sekarang luncurkan server dengan
$ sinstruments-server -c oscillo.yml
dan Anda seharusnya dapat terhubung dengan:
$ nc localhost 5000
*IDN?
ACME Inc,O-3000,23l032,3.5A
Secara default eol
diatur ke n
. Anda dapat mengubahnya ke karakter apa pun dengan:
class Oscilloscope ( BaseDevice ):
newline = b" r "
Jika perangkat Anda menerapkan protokol yang menjawab dengan beberapa jawaban (yang berpotensi tertunda) terhadap satu permintaan, Anda dapat mendukungnya dengan mengonversi handle_message()
menjadi generator:
class Oscilloscope ( BaseDevice ):
def handle_message ( self , message ):
self . _log . info ( "received request %r" , message )
message = message . strip (). decode ()
if message == "*IDN?" :
yield b"ACME Inc,O-3000,23l032,3.5A"
elif message == "*RST" :
self . _log . info ( "Resetting myself!" )
elif message == "GIVE:ME 10" :
for i in range ( 1 , 11 ):
yield f"Here's { i } n " . encode ()
...
Jangan lupa untuk selalu menghasilkan bytes
! Simulator tidak menebak-nebak cara menyandikan str
Jika perangkat simulasi Anda memerlukan konfigurasi tambahan, perangkat tersebut dapat diberikan melalui file YAML yang sama.
Katakanlah Anda ingin dapat mengonfigurasi apakah perangkat Anda berada dalam mode CONTROL
saat startup. Selain itu, jika tidak ada nilai awal yang dikonfigurasi, nilai defaultnya adalah 'OFF'.
Pertama mari tambahkan ini ke contoh konfigurasi kita:
# oscilo.yml
devices :
- class : Oscilloscope
name : oscilo-1
control : ON
transports :
- type : tcp
url : :5000
Kemudian, kami mengimplementasikan kembali Osiloskop __init__()
untuk mencegat parameter baru ini dan kami menanganinya di handle_message()
:
class Oscilloscope ( BaseDevice ):
def __init__ ( self , name , ** opts ):
self . _control = opts . pop ( "control" , "OFF" ). upper ()
super (). __init__ ( name , ** opts )
def handle_message ( self , message ):
...
elif message == "CONTROL" :
return f"CONTROL { self . _control } n " . encode ()
...
Anda bebas menambahkan opsi sebanyak yang Anda inginkan selama opsi tersebut tidak bertentangan dengan kunci cadangan name
, class
dan transports
.
Beberapa instrumen menerapkan protokol yang tidak dikelola dengan tepat oleh protokol pesan berbasis EOL.
Simulator ini memungkinkan Anda menulis protokol pesan Anda sendiri. Berikut ini contohnya:
from sinstruments . simulator import MessageProtocol
class FixSizeProtocol ( MessageProtocol ):
Size = 32
def read_messages ( self ):
transport = self . transport
buff = b''
while True :
buff += transport . read ( self . channel , size = 4096 )
if not buff :
return
for i in range ( 0 , len ( buff ), self . Size ):
message = buff [ i : i + self . Size ]
if len ( message ) < self . Size :
buff = message
break
yield message
class Oscilloscope ( BaseDevice ):
protocol = FixSizeProtocol
...
Jika Anda mengembangkan pustaka python yang menyediakan akses ke instrumen yang dapat diakses melalui soket atau jalur serial dan Anda menulis simulator untuk itu, Anda mungkin tertarik untuk menguji perpustakaan Anda terhadap simulator tersebut.
sinstruments menyediakan sepasang pembantu pytest yang memunculkan simulator di thread terpisah.
server_context
Penggunaan pertama cukup menggunakan helper server_context
. Sebenarnya tidak ada yang paling spesifik tentang helper ini sehingga Anda dapat membayangkan menggunakannya dalam skenario lain juga.
Berikut ini contohnya:
import pytest
from sinstruments . pytest import server_context
cfg = {
"devices" : [{
"name" : "oscillo-1" ,
"class" : "Oscilloscope" ,
"transports" : [
{ "type" : "tcp" , "url" : "localhost:0" }
]
}]
}
def test_oscilloscope_id ():
with server_context ( cfg ) as server :
# put here code to perform your tests that need to communicate with
# the simulator. In this example an oscilloscope client
addr = server . devices [ "oscillo-1" ]. transports [ 0 ]. address
oscillo = Oscilloscope ( addr )
assert oscillo . idn (). startswith ( "ACME Inc,O-3000" )
Anda mungkin memperhatikan bahwa dalam konfigurasi kami menggunakan port 0
. Ini memberitahu simulator untuk mendengarkan pada port gratis mana pun yang disediakan oleh OS.
Tes sebenarnya mengambil alamat saat ini yang diberikan oleh OS dan menggunakannya dalam tes.
Seperti yang Anda lihat, pengujian tidak bergantung pada ketersediaan satu port tertentu yang menjadikannya portabel.
Berikut adalah saran tentang bagaimana Anda dapat menulis perlengkapan Anda sendiri menggunakan server_context
helper. Tujuannya adalah untuk mengurangi jumlah kode boilerplate yang Anda perlukan untuk menulis pengujian Anda:
@ pytest . fixture
def oscillo_server ():
with server_context ( config ) as server :
server . oscillo1 = server . devices [ "oscillo-1" ]
server . oscillo1 . addr = server . oscillo1 . transports [ 0 ]. address
yield server
def test_oscilloscope_current ( oscillo_server ):
oscillo = Oscilloscope ( oscillo_server . oscillo1 . addr )
assert .05 < oscillo . current () < 0.01
server
Pembantu kedua adalah perlengkapan server
. Perlengkapan ini bergantung pada fitur config
yang ada yang harus ada di modul Anda. Berikut ini contoh mengikuti kode sebelumnya:
from sinstruments . pytest import server
@ pytest . fixture
def config ()
yield cfg
def test_oscilloscope_voltage ( server ):
addr = server . devices [ "oscillo-1" ]. transports [ 0 ]. address
oscillo = Oscilloscope ( addr )
assert 5 < oscillo . voltage () < 10