PySerial یک کتابخانه کراس پلتفرم است که یک ارتباط سریال برای پایتون فراهم می کند. این کتابخانه به شما امکان می دهد داده ها را از برنامه های پایتون خود در پورت های سریال بخوانید و بنویسید. پورت های سریال معمولاً برای اتصال رایانه ها به تجهیزات جانبی مانند بردهای آردوینو، ماژول های GPS و اسکنر بارکد استفاده می شوند. ماژول pySerial با دستور pip به این صورت نصب می شود:

pip install pyserial

پیدا کردن پورتهای COM

برای فهرست کردن پورت های COM موجود با استفاده از Pyserial، می توانید از کد پایتون زیر استفاده کنید:

import serial.tools.list_ports
ports = list(serial.tools.list_ports.comports())
for port in ports:
    print(port.device)

ابتدا ماژول serial.tools.list_ports را وارد می کنیم، که توابعی را برای کار با پورت های سریال ارائه می دهد.

تابع list_ports.comports لیستی از پورت های سریال موجود را برمی گرداند.

حلقه لیست را پیمایش می کند و نام هر پورت موجود در لیست را چاپ می کند (به عنوان مثال، ‘COM1’، ‘COM2’، و غیره).

باز کردن یک پورت:

همیشه باید در ابتدای برنامه ماژول را import کنیم:

import serial

برای باز کردن یک پورت از دستور زیر استفاده می کنیم:

ser = serial.Serial()

پارامترهای مختلفی را می توانیم در این تابع تنظیم کنیم. این پارامترها را در زیر بررسی می کنیم: 

port

نام پورت می‌تواند رشته‌ای باشد که نشان‌دهنده پورت فیزیکی است، مانند “COM1” یا “/dev/ttyS0″، یا می‌تواند توصیف‌گر فایلی باشد که نشان‌دهنده پورتی است که قبلا باز شده است.

baudrate

نرخ باود یا باود ریت تعداد بیت هایی است که از طریق پورت سریال در هر ثانیه ارسال می شود. بعضی نرخ های رایج عبارتند از 9600، 19200، 38400 و 115200 .

bytesize

تعداد بیت ها یی است که در هر بایت داده از طریق پورت سریال ارسال می شود. اندازه بایت می تواند 5، 6، 7 یا 8 بیت باشد.

parity

برابری مکانیزمی برای تشخیص خطاها در انتقال داده است. گزینه های برابری پشتیبانی شده عبارتند از:

N (بدون برابری)

E (تعادل زوج)

O (تعادل فرد)

M (برابری علامت)

S (تعادل فضا)

stopbits

بیت توقف یا استاپ بیت سیگنالی است که پایان یک بایت داده را نشان می دهد. گزینه های بیت توقف پشتیبانی شده عبارتند از:

1 (یک استاپ بیت)

1.5 (یک و نیم استاپ بیت)

2 (دو استاپ بیت)

xonxoff

کنترل جریان XON/XOFF مکانیزمی برای کنترل جریان داده بین پورت سریال و دستگاه متصل به آن است. هنگامی که کنترل جریان XON/XOFF فعال است، دستگاه متصل می‌تواند یک کاراکتر XON  به پورت سریال برای توقف انتقال داده و یک کاراکتر XOFF  برای ازسرگیری انتقال داده ارسال کند.

rtscts

کنترل جریان RTS/CTS مکانیزم دیگری برای کنترل جریان داده بین پورت سریال و دستگاه متصل است. هنگامی که کنترل جریان RTS/CTS فعال است، پورت سریال می‌تواند یک کاراکتر RTS  را به دستگاه متصل ارسال کند تا برای ارسال داده آماده شود و یک کاراکتر CTS  را به دستگاه متصل ارسال کند تا نشان دهد که آماده دریافت داده است.

timeout

بازه زمانی خواندن مدت زمانی است که پورت سریال قبل از ایجاد استثنا منتظر دریافت داده می‌شود. زمان نوشتن مدت زمانی است که پورت سریال قبل از ایجاد یک استثنا منتظر می ماند تا داده ها منتقل شوند.

write_timeout

زمان نوشتن بر حسب ثانیه برای متد write. این پارامتر فقط برای متد write اعمال می شود و مستقل از پارامتر timeout است.

خواندن پورت سریال:

در برنامه زیر یک پورت سریال باز می کنیم و تعدادی داده را می خوانیم و نمایش می دهیم:

import serial
myPort = serial.Serial('COM4', 9600, parity='N')
myPort.write('Hello from karakit!'.encode('utf-8'))
data = myPort.read(100)
print(data.decode('utf-8'))
myPort.close()

خط اول ماژول serial را وارد می کند که قابلیت کار با ارتباطات سریال (مانند RS-232) در پایتون فراهم می کند.

خط دوم یک شی پورت سریال به نام myPort ایجاد می کند.

‘COM4’ پورت مرتبط با myPort را مشخص می کند (احتمالا لازم است این پورت را بر اساس پورتهای موجود در سیستم خود تنظیم کنید).

9600 باود ریت (سرعت ارتباط) برای اتصال سریال است.

parity=’N’ برابری را روی none تنظیم می کند.

در خط بعد myPort.write رشته “Hello from karakit!” را به پورت سریال ارسال می کند.

.encode(‘utf-8’) رشته را قبل از ارسال به بایت  تبدیل می کند.  بدون utf-8 وقتی رشته ای را مستقیماً بدون رمزگذاری ارسال می کنید، به عنوان دنباله ای از بایت ها بر اساس رمزگذاری پیش فرض (معمولا ASCII) در نظر گرفته می شود.

اگر رشته حاوی کاراکترهای غیر ASCII باشد (مانند حروف برجسته، ایموجی‌ها یا کاراکترهای زبان‌های دیگر)، ممکن است خطایی رخ دهد.

UTF-8 یک رمزگذاری پرکاربرد است که از طیف وسیعی از کاراکترها، از جمله کاراکترهای غیرASCII پشتیبانی می‌کند.

این کار تضمین می کند که داده هایی که ارسال می کنید به درستی به عنوان بایت نمایش داده می شوند و می توانند بدون مشکل از طریق پورت سریال منتقل شوند.

data = myPort.read(100) تا 100 بایت داده را از پورت سریال می خواند و داده های دریافتی در متغیر data ذخیره می شوند.

print(data.decode(‘utf-8’)) داده های دریافتی را از بایت ها به رشته ای با استفاده از رمزگذاری UTF-8 رمزگشایی می کند و آن را در کنسول نمایش می دهد.

در نهایت myPort.close اتصال پورت سریال را می بندد.

تنظیم پارامترهای پورت سریال در طول برنامه

وقتی که یک پورت سریال ایجاد می کنیم لازم نیست که در همان تابع serial.Serial همه پارامترهای آن را تنظیم کنیم. بلکه می توانیم ابتدا شی پورت را ایجاد کنیم و بعدا هر یک از پارامترها را مقداردهی نماییم. به مثال زیر توجه کنید:

import serial
myPort = serial.Serial()
myPort.baudrate = 9600
myPort.port = 'COM4'

در این حالت برای باز شدن پورت حتما باید از این دستور استفاده کنیم:

myPort.open()

بررسی باز یا بسته بودن پورت

برای اینکه وضعیت باز یا بسته بودن پورت را بررسی کنیم می توانیم از دستور زیر استفاده کنیم:

.is_open

چگونگی استفاده از این دستو را در مثال زیر می توانید مشاهده کنید:

myPort = serial.Serial('COM4', 9600, parity='N')
print(myPort.is_open)
myPort.close()
print(myPort.is_open)

در صورت اجرای این برنامه نتیجه زیر را در ترمینال مشاهده خواهید کرد:

True
False

توابع مربوط به خواندن داده از پورت سریال

چند تابع مختلف برای خواندن از ورودی پورت سریال در PySerial وجود دارد:

serial.read(size)

این تابع تعداد مشخصی از بایت ها (size) را از پورت سریال می خواند و داده ها را به صورت رشته ای برمی گرداند. البته می توان به size هیچ عددی اختصاص نداد و دستور را به این صورت به کار برد:

serial.read()

این دستور یک بایت از بافر ورودی را می خواند.

به خاطر داشته باشید که اگر داده‌ای وارد نشود ممکن است باعث شود برنامه شما مسدود گردد. اگر می‌خواهید تعداد خاصی بایت را بخوانید، مطمئن شوید که مقداری برای پارامتر size تعیین کرده اید.

serial.readline()

این دستور حداکثر یک خط را می خواند، از جمله\n  در پایان رشته دریافتی. هنگام استفاده از readline() مراقب باشید. هنگام باز کردن پورت سریال، یک timeout مشخص کنید، در غیر این صورت اگر کاراکتر خط جدیدی دریافت نشود، برای همیشه مسدود می شود. اگر \n در مقدار بازگشتی وجود نداشته باشد، بعد از سپری شدن مدت زمان تعیین شده در timeout همان \n را بر می گرداند.

readlines()

این دستور سعی می کند همه خطوط را بخواند. اگر مهلت زمانی timeout که در پورت تعریف شده است سپری شود آن را به عنوان EOF (پایان فایل) تفسیر می کند و به خواندن داده ها پایان میدهد. لیست خطوطی که این متد برمی گرداند شامل \n نیست.

نوشتن داده در خروجی پورت سریال

متد write داده های مشخص شده را به پورت سریال ارسال می کند. یک مثال ساده را در اینجا می بینید:

myPort.write(b'Hello, karakit!\n')

عبارت b’Hello, World!\n’ یک رشته بایت است که حاوی پیامی است که باید ارسال شود. پیشوند b نشان می دهد که این یک رشته بایت است و یک رشته معمولی نیست. این پیام شامل متن Hello, karakit!\n است.

یک مثال ساده برای استفاده از دستور write و ارسال داده را مشاهده می کنید:

import serial
myPort = serial.Serial('COM4', 9600, parity='N')
while True:
    myData = myPort.write(b'Hello Karakit!\n')

بررسی تعداد کاراکترهای موجود در بافر ورودی:

گاهی اوقات لازم است که از تعداد بایت هایی که در بافر ورودی منتظر خوانده شدن هستند را بدانیم. برای این کار از

import serial
myPort = serial.Serial('COM4', 9600, parity='N')
while True:
    myData = myPort.read(myPort.in_waiting).decode('utf-8') 
    if myData:
        print(f"Received data: {myData}") 

حلقه while به طور مداوم داده های ورودی از پورت سریال را بررسی می کند. تابع myPort.read(myPort.in_waiting) تعداد بایت‌هایی را که در حال حاضر در بافر ورودی موجود است (ویژگی “in_waiting”) می‌خواند که نشان‌دهنده مقدار داده‌هایی است که در انتظار خواندن هستند. متد decode(‘utf-8’) داده های باینری خام را در قالبی قابل خواندن برای ما (رمزگذاری UTF-8) رمزگشایی می کند.

در خط:

If myData:
    print(f"Received data: {myData}") 

اگر داده ای از پورت سریال دریافت شده باشد، یعنی myData برابر 0 نباشد، آن را به قالب رشته ای در آورده و روی کنسول چاپ می کند.

خالی کردن پورت

reset_input_buffer()

بافر ورودی را خالی می کند و تمام محتویات آن را دور می ریزد.

reset_output_buffer()

بافر خروجی را پاک می‌کند و تمام آنچه در بافر است را دور می‌اندازد.

توجه داشته باشید، برای برخی از مبدل های سریال به USB، ممکن است این متد فقط بافر سیستم عامل را پاک کند و نه همه داده هایی که ممکن است در قسمت USB وجود داشته باشد.

Flush()

متد flush در PySerial برای تخلیه بافر خروجی پورت سریال استفاده می شود. اگر در حال خواندن داده‌ها از پورت سریال هستیم و می‌دانیم که دیگر داده‌ ارزشمندی برای دریافت وجود ندارد، می‌توانیم برای پاک کردن بافر ورودی، flush را فراخوانی کنیم. این کار منابعی را که برای ذخیره داده‌ها استفاده می‌شود آزاد می‌کند و از خوانده شدن داده‌های قدیمی با شروع دوباره خواندن جلوگیری می‌کند.

به طور خلاصه، از flush کم و فقط در صورت لزوم برای موارد خاص در برنامه خود استفاده کنید. چرا که بیشتر اوقات، سیستم عامل بافر را به خوبی مدیریت می کند.

دریافت تنظیمات پورت:

اگردر قسمتی از برنامه لازم بود که تنظیمات یک پورت رااستخراج کنیم می توانیم به این صورت عمل کنیم:

print(myPort.get_settings())