موضوع ما اینجا هر مدل میکروکنترلر یا SoC (System on Chip) است که از طریق پورت سریال به کامپیوتر وصل می شود و ما هم میخواهیم بتوانیم از طریق برنامه نویسی با پایتون با این سریال ارتباط برقرار کنیم و بخوانیم و بنویسیم و برنامه ریزی و کنترل دوطرفه ای از طرف کامپیوتر به SoC یا برعکس داشته باشیم. در این مقاله مثلاً آردوینو UNO و یک دسکتاپ لینوکس یا ویندوز.
راحت ترین راه برای این موارد در پایتون استفاده از ماژول PySerial برای خواندن و نوشتن روی پورت سریال است. خیلی مختصر و راحت ابتدا با pip install pyserial ماژول pyserial را نصب می کنید و سپس با تکه کدهای ذیل می توانید روی کامپیوتر با سریال آردوینو (یا هر چیز دیگری) ارتباط داشته باشید.
نکات :
کد پایتون :
import serial import time arduino = serial.Serial(port='/dev/ttyACM0', baudrate=115200, timeout=.1) def write_read(x): arduino.write(bytes(x, 'utf-8')) time.sleep(0.05) data = arduino.readline() return data while True: num = input("Enter Something : ") value = write_read(num) print(value.decode())
کد آردوینو :
void setup() { Serial.begin(115200); Serial.setTimeout(1); } void loop() { while (!Serial.available()); String x = Serial.readString(); Serial.print("Arduino says I got : "); Serial.print(x); }
در کد بالا که در حقیقت آردوینو مقادیر دریافتی از سریال بوسیله اسکریپت پایتون را بازتاب می کند در کد پایتون ابتدا یک آبجکت بنام arduino از نوع سریال ساخته ایم و مشخصات ارتباط سریال مثل پورت و baudrate را برای سریال تعریف کرده ایم. از این پس از طریق آبجکت arduino به پورت سریال دسترسی داریم.
سپس یک تابع write_read تعریف کرده ایم که یک ورودی می گیرد و آن را روی سریال write می کند و ۵صدم ثانیه صبر کرده و یک خط از روی سریال را می خواند (پاسخ آردوینو روی پورت سریال) و return می کند.
بعد در یک حلقه while همیشه صحیح که مدام اجرا می شود، ورودی را از کاربر گرفته و به تابع write_read می دهیم و مقدار return شده write_read را print می کنیم. چون مقدار از نوع bytes است از متد ()decode برای نوشته رشته خالص بدون 'b استفاده می کنیم.
در کد آردوینو ، در setup سریال را تعریف کرده و در loop آن را می خوانیم (readString) و به متغییر x می دهیم و سپس همان را روی سریال دوباره print می کنیم.
الان خواندن و نوشتن روی سریال در پایتون و آردوینو انجام شده و براحتی می توانیم هر طرف را برنامه ریزی کنیم که با دریافت پیام مشخص چه عملیاتی را انجام دهد.
یک مثال بسیار ساده کنترل built-in LED روی آردوینو است که بسادگی می توان به موارد پیشرفته تری بسط داد.
در این مثال در صورت وارد کردن مقدار on در سریال توسط پایتون built-in LED آردوینو روشن و درصورت وارد کردن دستور off خاموش می شود.
با توجه به اینکه در اینجا پایتون فقط در نقش خواندن و نوشتن روی سریال است کد پایتون در این مثال همان کد بالاست و کد آردوینو به اینصورت تغییر کرده است :
void setup() { Serial.begin(115200); Serial.setTimeout(1); pinMode(LED_BUILTIN, OUTPUT); } void loop() { while (!Serial.available()); String x = Serial.readString(); if(x=="on"){ digitalWrite(LED_BUILTIN, HIGH); Serial.print("Builtin LED is now ON"); }else if(x=="off"){ digitalWrite(LED_BUILTIN, LOW); Serial.print("Builtin LED is now OFF"); }else{ Serial.print("Unknown Command!"); } }
من یک سنسور دما و رطوبت هوای AHT10 را به آردوینو متصل کرده ام و می خواهم این مقادیر را از طریق پورت سریال دریافت کنم و با درج زمان در یک فایل CSV ذخیره نمایم. بدیهی است براساس مقادیر دریافتی میتوانید هر عملیات دیگری را در اسکریپت پایتون برنامه نویسی بفرمایید.
اجازه بدهید کدها را مرور کنیم :
کد آردوینو :
#include <Adafruit_AHTX0.h> Adafruit_AHTX0 aht; void setup() { Serial.begin(115200); if (! aht.begin()) { Serial.println("Could not find AHT? Check wiring"); while (1) delay(10); } } void loop() { sensors_event_t humidity, temp; aht.getEvent(&humidity, &temp); String thData = String(temp.temperature)+","+String(humidity.relative_humidity); Serial.println(thData); delay(1000); }
این کد هر یک ثانیه مقدار دریافتی از سنسور دما و رطوبت را با فرمت temp,humidity روی سریال چاپ می کند.
کد پایتون :
این کد مقادیر دریافتی از سریال را فرمت کرده و با اضافه کردن ستون زمان روی یک فایل CSV می نویسد.
import serial import csv from datetime import datetime arduino = serial.Serial(port='/dev/ttyACM0', baudrate=115200, timeout=.1) now = datetime.now() f = open('temp-humidity-record.csv', 'a') writer = csv.writer(f) while True: thData = arduino.readline().decode() if thData != "": current_date_and_time = datetime.now() thData = thData.split(",") row = [current_date_and_time.isoformat(),thData[0].strip(),thData[1].strip()] writer.writerow(row) print(current_date_and_time," : Temp:",thData[0]," Humidity:",thData[1]) f.close()
رابطه انسان با کامپیوتر براساس یکسری ورودی از طرف انسان به سمت کامپیوتر مثل کیبورد و ماوس و ... و یکسری خروجی از طرف کامپیوتر به سمت انسان مثل مانیتور و پرینتر و ... برقرار می شود.
در سناریوی سوم ما می خواهیم بتوانیم با ماژول هایی مثل keypad یا joystick یا مقاومت متغییر یا ... ورودی جدید به کامپیوتر تعریف کنیم. یک راه روش نوشتن درایور و ... است که سخت می شود و موضوع بحث ما در این مقاله نیست. یک راه اتصال سریال با یک میکروکنترلر و map کردن این ماژول های ورودی به همان کیبورد و ماوس و GUI کامپیوتر است که به مراتب ساده تر پیاده سازی می شود.
بنابراین ما این ماژول ها را به آردوینو (یا مورد مشابه) وصل می کنیم و روی پورت سریال مقدار آن ها را به یک اسکریپت پایتون می دهیم تا اسکریپت پایتون این مقادیر را به دستورات کیبورد و ماوس کامپیوتر تبدیل کند.
ماژول pyAutoGUI در پایتون می تواند بسادگی انواع برنامه ریزی ها و اتوماسیون ماوس و کیبورد را انجام دهد و مثل اینکه یک نفر پشت کامپیوتر نشسته باشد ماوس و کیبورد براساس دستورات برنامه ریزی شده کار می کنند. یعنی شما دستور می دهید ماوس به مختصات فلان در اسکرین برود و مثلاً راست کلیک کند یا کیبورد چیزی را تایپ کند و ... .
من اینجا از یک مدل جوی استیک آنالوگ و یک مدل کلید ۵ جهته دیجیتال استفاده کرده ام. اتصال بقیه ماژول های مشابه هم همین کانسپت را دنبال می کند.
ابتدا توضیح و نمونه کد مختصری درباره joystick آنالوگ و مدل دیجیتال که در حقیقت یک keypad ۵ جهته است می دهم و سپس درباره pyAutoGUI کمی توضیح خواهم داد و درنهایت روش و کد پیاده سازی را خدمتان عرض خواهم کرد.
جوی استیک آنالوگ
جوی استیک آنالوگ در حقیقت ۲ پتانسیومتر در محور x و y است که با جابجایی دسته مقدار در هر محور ولتاژ بازگشتی تغییر می کند و روی Arduino که ADC ده بیتی با دارد با analogRead مقداری بین ۰ تا ۱۰۲۳ را بر می گرداند. وسط معمولاً مقدار حدود ۵۱۲ دارد.
با فشار دادن دسته هم یک کلید فشاری فعال می شود که در صورت فشرده شدن ۰ و در صورت آزاد بودن ۱ برمیگرداند.
جوی استیک دیجیتال (keypad پنج محوره)
این مدل در حقیقت ۴ کلید فشاری در چهار طرف و یکی وسط است. معمولاً دو دکمه reset و set هم روی این ماژول ها هست و شما هفت کلید فشاری دارید که در کد بصورت INPUT_PULLUP تعریف می شوند و با فشرده شدن هر کدام آن پایه با پایه COM مشترک می شود و مقدار صفر یا LOW برمی گرداند.
همان keypad است، دقت بفرمایید در مدل جوی استیک آنالوگ شما مقدار گرایش دسته در محور x و y را می توانید دریافت کنید اما در این مدل فقط به شما فشرده شدن یک جهت را بصورت دیجیتال می دهد و مقدار بازگشتی صفر یا یک است.
معرفی pyAutoGUI
ماژول pyautogui در پایتون به شما اجازه می دهد اعمال ماوس و کیبورد را کنترل کنید. یعنی برای عملکرد ماوس و کیبورد برنامه ریزی بفرمایید. هم براساس مختصات روی صفحه می توان دستور داد هم براساس موقعیت نسبی پوینتر ماوس.
توصیه می کنم چند دقیقه وقت بگذارید و یکی از توتوریال های یوتیوب درباره pyAutoGUI را ببینید.
راهنمای نصب روی سیستم های مختلف و توضیحات در مستنداتش هست، برای نصب آن روی لینوکس پس از نصب پایتون روی سیستم :
#dnf install scrot #dnf install python3-tk #dnf install python3-dev #pip install --user pyautogui
برای اتوماسیون GUI ، تست نرم افزار ، اتوماسیون ورود اطلاعات ، کنترل نرم افزاری ماوس و کیبورد و ... بسیار استفاده می شود. ما درسناریوی اینجا مقادیر جوی استیک را از پورت سریال خوانده و از pyautogui برای اجرای آن ها روی ماوس استفاده می کنیم.
لیستی از پرکاربردترین متدها و آرگومان های آن ها را در زیر ذکر کرده ام که می توانید امتحان بفرمایید و گویا هم هستند. کار کردن با pyautogui بسیار ساده است. مستندات خودش هم خوب هستند و ویدیوهای آموزشی متعددی هم درباره آن در یوتیوب هست.
import pyautogui ######## Get Info ######## #pyautogui.mouseInfo() #pyautogui.size() #pyautogui.position() #pyautogui.displayMousePosition() ######## Mouse Actions ######## #pyautogui.moveTo(x, y, duration) #pyautogui.moveRel(x related to current position, y related to current position, duration) #pyautogui.click(x=100, y=200) # move to 100, 200, then click the left mouse button. #pyautogui.click(x, y, clicks, interval, button, duration) #pyautogui.click(button='right', clicks=3, interval=0.25) # triple-click the right mouse button with a quarter second pause in between clicks #pyautogui.leftClick() #pyautogui.rightClick() #pyautogui.doubleClick() #pyautogui.tripleClick() #pyautogui.middleClick() #pyautogui.scroll(-100) #pyautogui.mouseDown(x, y, button) #pyautogui.mouseUp() #pyautogui.dragTo(x, y, duration, button="left") #pyautogui.dragRel(x related to current position, y related to current position, duration, button="left") ######## Keyboard Actions ######## #pyautogui.write("Message", Interval) #pyautogui.typewrite("Message", Interval) #pyautogui.press('left') # press the left arrow key #pyautogui.press('left', presses=3) #pyautogui.press(['left', 'left', 'left']) #pyautogui.keyDown('shift') # hold down the shift key #pyautogui.keyUp('shift') # release the shift key #pyautogui.hotkey('ctrl', 'shift', 'esc') # with pyautogui.hold('shift'): # pyautogui.press(['left', 'left', 'left']) # #…is equivalent to this code: # #pyautogui.keyDown('shift') # hold down the shift key #pyautogui.press('left') # press the left arrow key #pyautogui.press('left') # press the left arrow key #pyautogui.press('left') # press the left arrow key #pyautogui.keyUp('shift') # release the shift key #print(pyautogui.KEYBOARD_KEYS) ######## Message Box Actions ######## #pyautogui.alert(text='', title='', button='OK') #pyautogui.confirm(text='', title='', buttons=['OK', 'Cancel']) #messagePrompt = pyautogui.prompt(text='', title='' , default='') #print(messagePrompt) #messagePassword = pyautogui.password(text='', title='', default='', mask='*') #print(messagePassword)
برخی نکات تکمیلی درباره pyautogui
pyautogui.FailSafeException = True|False
pyautogui.PAUSE = seconds between two actions
اگر تصویر بریده شده آیتم منوی File را با نام menu_file.png ذخیره کرده باشم این تکه کد مختصات آن را روی صفحه پیدا می کند و روی آن کلیک خواهد کرد.
واقعیتش من کارم با نسخه جوی استیک دیجیتال (کلید پنج محوره) راه افتاد و دیگه کد پایتون مدل آنالوگ را ننوشته ام. با مثال ها و توضیحات بالا نباید نوشتن کد آن مشکلی داشته باشد.
نمونه کد اتصال جوی استیک مدل دیجیتال (کلید ۵ محوره) به GUI و ماوس در زیر ذکر شده و در ادامه نکات و توضیحات آن را عرض خواهم کرد. در این مثال می توان با جوی استیک ماوس را کنترل کرد و دکمه SET نقش راست کلیک و دکمه RST نقش کلیک چپ و دکمه وسط نقش کلیک وسط را بازی خواهند کرد.
کد آردوینو :
بسته به پیام جوی استیک فقط up,down,left,right,reset,set را در سریال چاپ می کند.
const int buttonPin_RST = 2; // reset button pin const int buttonPin_SET = 3; // set button pin const int buttonPin_MID = 4; // middle button pin const int buttonPin_RHT = 5; // right button pin const int buttonPin_LFT = 6; // left button pin const int buttonPin_DWN = 7; // down button pin const int buttonPin_UP = 8; // up button pin void setup() { Serial.begin(115200); pinMode(buttonPin_RST, INPUT_PULLUP); pinMode(buttonPin_SET, INPUT_PULLUP); pinMode(buttonPin_MID, INPUT_PULLUP); pinMode(buttonPin_RHT, INPUT_PULLUP); pinMode(buttonPin_LFT, INPUT_PULLUP); pinMode(buttonPin_DWN, INPUT_PULLUP); pinMode(buttonPin_UP, INPUT_PULLUP); } void loop() { if(digitalRead(buttonPin_RST) == LOW) { delay(250); Serial.println("Reset"); } if(digitalRead(buttonPin_SET) == LOW) { delay(250); Serial.println("Set"); } if(digitalRead(buttonPin_MID) == LOW) { delay(250); Serial.println("Middle"); } if(digitalRead(buttonPin_RHT) == LOW) { delay(250); Serial.println("Right"); } if(digitalRead(buttonPin_LFT) == LOW) { delay(250); Serial.println("Left"); } if(digitalRead(buttonPin_DWN) == LOW) { delay(250); Serial.println("Down"); } if(digitalRead(buttonPin_UP) == LOW) { delay(250); Serial.println("Up"); } }
کد پایتون :
import pyautogui import serial arduino = serial.Serial(port='/dev/ttyACM0', baudrate=115200, timeout=.1) while True: mouseta = arduino.readline().decode().strip() mousePosition = pyautogui.position() if mouseta != "": match mouseta: case "Up": pyautogui.moveTo(mousePosition[0], mousePosition[1]-10) case "Down": pyautogui.moveTo(mousePosition[0], mousePosition[1]+10) case "Right": pyautogui.moveTo(mousePosition[0]+10, mousePosition[1]) case "Left": pyautogui.moveTo(mousePosition[0]-10, mousePosition[1]) case "Middle": pyautogui.middleClick() case "Set": pyautogui.leftClick() case "Reset": pyautogui.rightClick() print(mouseta) # Answer from arduino
با کدهای آردوینو و پایتون بالا می توانید ماوس روی صفحه را بوسیله یک جوی استیک دیجیتال (کلید ۵ محوره) کنترل بفرمایید. مقدار +10 و -10 در حقیقت گام حرکت ماوس است.