اتصال و کنترل آردوینو <-> کامپیوتر از طریق پورت سریال و پایتون

موضوع ما اینجا هر مدل میکروکنترلر یا SoC (System on Chip) است که از طریق پورت سریال به کامپیوتر وصل می شود و ما هم میخواهیم بتوانیم از طریق برنامه نویسی با پایتون با این سریال ارتباط برقرار کنیم و بخوانیم و بنویسیم و برنامه ریزی و کنترل دوطرفه ای از طرف کامپیوتر به SoC یا برعکس داشته باشیم. در این مقاله مثلاً آردوینو UNO و یک دسکتاپ لینوکس یا ویندوز.در سناریوی اول من می خواهم یک عملگر مثل LED را که به میکروکنترلر وصل است با دستور ارسال شده از روی پورت سریال کامپیوتر کنترل کنم ،در سناریوی دوم مقادیر دما و رطوبت را از طریق پورت سریال از روی یک آردوینو اونو بخوانم و در یک فایل CSV ذخیره کنم و در سناریوی سوم می خواهم ماوس و کیبورد روی کامپیوتر را از طریق joystick یا keypad یا ماژول ورودی دیگری که به آردوینو وصل است کنترل کنم.خواندن و نوشتن روی پورت سریال با پایتونراحت ترین راه برای این موارد در پایتون استفاده از ماژول PySerial برای خواندن و نوشتن روی پورت سریال است. خیلی مختصر و راحت ابتدا با pip install pyserial ماژول pyserial را نصب می کنید و سپس با تکه کدهای ذیل می توانید روی کامپیوتر با سریال آردوینو (یا هر چیز دیگری) ارتباط داشته باشید.نکات : دو برنامه همزمان نمی توانند یک پورت سریال را در اختیار داشته باشند، یعنی اگر روی Arduino IDE مانیتور پورت سریال را باز کنید، در هنگام اجرای کد پایتون خطای Device or resource busy می گیرید.پورت سریال اتصال آردوینو اونو من به کامپیوتر (دسکتاپ فدورا) dev/ttyACM0/ است، روی ویندوز COM خواهد بود که از روی Device Manager می توانید پورت اتصال سریال را پیدا کنید.کد پایتون :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(&quotEnter Something : &quot) 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(&quotArduino says I got : &quot); 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 می کنیم.الان خواندن و نوشتن روی سریال در پایتون و آردوینو انجام شده و براحتی می توانیم هر طرف را برنامه ریزی کنیم که با دریافت پیام مشخص چه عملیاتی را انجام دهد.سناریوی اول : کنترل عملگر (LED) متصل به میکروکنترلر از روی پورت سریالیک مثال بسیار ساده کنترل 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==&quoton&quot){ digitalWrite(LED_BUILTIN, HIGH); Serial.print(&quotBuiltin LED is now ON&quot); }else if(x==&quotoff&quot){ digitalWrite(LED_BUILTIN, LOW); Serial.print(&quotBuiltin LED is now OFF&quot); }else{ Serial.print(&quotUnknown Command!&quot); } }سناریوی دوم : خواندن مقادیر سنسور دما و رطوبت و ذخیره در فایل CSVمن یک سنسور دما و رطوبت هوای AHT10 را به آردوینو متصل کرده ام و می خواهم این مقادیر را از طریق پورت سریال دریافت کنم و با درج زمان در یک فایل CSV ذخیره نمایم. بدیهی است براساس مقادیر دریافتی میتوانید هر عملیات دیگری را در اسکریپت پایتون برنامه نویسی بفرمایید.اجازه بدهید کدها را مرور کنیم :کد آردوینو :#include <Adafruit_AHTX0.h> Adafruit_AHTX0 aht; void setup() { Serial.begin(115200); if (! aht.begin()) { Serial.println(&quotCould not find AHT? Check wiring&quot); while (1) delay(10); } } void loop() { sensors_event_t humidity, temp; aht.getEvent(&humidity, &temp); String thData = String(temp.temperature)+&quot,&quot+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 != &quot&quot: current_date_and_time = datetime.now() thData = thData.split(&quot,&quot) row = [current_date_and_time.isoformat(),thData[0].strip(),thData[1].strip()] writer.writerow(row) print(current_date_and_time,&quot : Temp:&quot,thData[0],&quot Humidity:&quot,thData[1]) f.close()سناریوی سوم : کنترل ماوس و کیبورد با جوی استیک و keypadرابطه انسان با کامپیوتر براساس یکسری ورودی از طرف انسان به سمت کامپیوتر مثل کیبورد و ماوس و ... و یکسری خروجی از طرف کامپیوتر به سمت انسان مثل مانیتور و پرینتر و ... برقرار می شود.در سناریوی سوم ما می خواهیم بتوانیم با ماژول هایی مثل keypad یا joystick یا مقاومت متغییر یا ... ورودی جدید به کامپیوتر تعریف کنیم. یک راه روش نوشتن درایور و ... است که سخت می شود و موضوع بحث ما در این مقاله نیست. یک راه اتصال سریال با یک میکروکنترلر و map کردن این ماژول های ورودی به همان کیبورد و ماوس و GUI کامپیوتر است که به مراتب ساده تر پیاده سازی می شود.بنابراین ما این ماژول ها را به آردوینو (یا مورد مشابه) وصل می کنیم و روی پورت سریال مقدار آن ها را به یک اسکریپت پایتون می دهیم تا اسکریپت پایتون این مقادیر را به دستورات کیبورد و ماوس کامپیوتر تبدیل کند.ماژول pyAutoGUI در پایتون می تواند بسادگی انواع برنامه ریزی ها و اتوماسیون ماوس و کیبورد را انجام دهد و مثل اینکه یک نفر پشت کامپیوتر نشسته باشد ماوس و کیبورد براساس دستورات برنامه ریزی شده کار می کنند. یعنی شما دستور می دهید ماوس به مختصات فلان در اسکرین برود و مثلاً راست کلیک کند یا کیبورد چیزی را تایپ کند و ... .من اینجا از یک مدل جوی استیک آنالوگ و یک مدل کلید ۵ جهته دیجیتال استفاده کرده ام. اتصال بقیه ماژول های مشابه هم همین کانسپت را دنبال می کند.ابتدا توضیح و نمونه کد مختصری درباره joystick آنالوگ و مدل دیجیتال که در حقیقت یک keypad ۵ جهته است می دهم و سپس درباره pyAutoGUI کمی توضیح خواهم داد و درنهایت روش و کد پیاده سازی را خدمتان عرض خواهم کرد.دست راست = جوی استیک آنالوگ | دست چپ keypad پنج محوره (جوی استیک دیجیتال)جوی استیک آنالوگجوی استیک آنالوگ در حقیقت ۲ پتانسیومتر در محور x و y است که با جابجایی دسته مقدار در هر محور ولتاژ بازگشتی تغییر می کند و روی Arduino که ADC ده بیتی با دارد با analogRead مقداری بین ۰ تا ۱۰۲۳ را بر می گرداند. وسط معمولاً مقدار حدود ۵۱۲ دارد.با فشار دادن دسته هم یک کلید فشاری فعال می شود که در صورت فشرده شدن ۰ و در صورت آزاد بودن ۱ برمیگرداند.پایه GND برای اتصال منفی.پایه Vcc برای تغذیه 5 ولت مدار پایه VRx خروجی آنالوگ محور X (حرکت چپ و راست)پایه VRy خروجی آنالوگ محور Y (حرکت بالا و پایین)پایه SW بعنوان کلید فشاری کلید که بصورت pull-up داخلی قرار گرفته است، در حالت عادی ۱ و با فشردن شدن صفر بر می گرداند.جوی استیک دیجیتال (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=&quotleft&quot) #pyautogui.dragRel(x related to current position, y related to current position, duration, button=&quotleft&quot) ######## Keyboard Actions ######## #pyautogui.write(&quotMessage&quot, Interval) #pyautogui.typewrite(&quotMessage&quot, 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برای خارج شدن از اجرا و جلوگیری از ادامه اجرای کد یک مکانیزم FailSafeException دارد که بصورت پیش فرض True است. در این حالت اگر در زمان اجرای برنامه پوینتر ماوس را به منتهی الیه چپ و بالای صفحه ببرید اسکریپت متوقف می شود. pyautogui.FailSafeException = True|Falseبرای وقفه بین action ها می توانید همان بالای برنامه مقدار PAUSE را تعیین بفرمایید. مثلاًpyautogui.PAUSE = seconds between two actionsبرای اینکه مختصات مشخص ندهید و خودش مختصات چیزی را روی صفحه پیدا کند، قابلیتی بعنوان locate براساس تصویر نمونه در pyautogui وجود دارد که یک تصویر را به آن می دهید و موقعیت آن را روی صفحه پیدا می کند. مثلاً من می خواهم روی آیتم File از پنجره KCalc کلیک شود، بجای اینکه مختصات آن را بدهم می توانم یک اسکرین شات بگیرم و تکه مربوطه به File را در منو ببرم و به pyautogui بدهم تا مختصات آن تصویر در صفحه را پیدا کند.تصویر کل پنجرهتصویر بریده شده آیتم منوی Fileاگر تصویر بریده شده آیتم منوی File را با نام menu_file.png ذخیره کرده باشم این تکه کد مختصات آن را روی صفحه پیدا می کند و روی آن کلیک خواهد کرد.با اجرای ()pyautogui.mouseInfo پنجره mouse info که امکان لاگ کردن هم دارد باز می شود و می توان برای برنامه ریزی اعمال ماوس و تعیین مختصات از آن استفاده کرد.اتصال جوی استیک دیجیتال به GUI کامپیوتر با pyAutoGUIواقعیتش من کارم با نسخه جوی استیک دیجیتال (کلید پنج محوره) راه افتاد و دیگه کد پایتون مدل آنالوگ را ننوشته ام. با مثال ها و توضیحات بالا نباید نوشتن کد آن مشکلی داشته باشد.نمونه کد اتصال جوی استیک مدل دیجیتال (کلید ۵ محوره) به 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(&quotReset&quot); } if(digitalRead(buttonPin_SET) == LOW) { delay(250); Serial.println(&quotSet&quot); } if(digitalRead(buttonPin_MID) == LOW) { delay(250); Serial.println(&quotMiddle&quot); } if(digitalRead(buttonPin_RHT) == LOW) { delay(250); Serial.println(&quotRight&quot); } if(digitalRead(buttonPin_LFT) == LOW) { delay(250); Serial.println(&quotLeft&quot); } if(digitalRead(buttonPin_DWN) == LOW) { delay(250); Serial.println(&quotDown&quot); } if(digitalRead(buttonPin_UP) == LOW) { delay(250); Serial.println(&quotUp&quot); } }کد پایتون :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 != &quot&quot: match mouseta: case &quotUp&quot: pyautogui.moveTo(mousePosition[0], mousePosition[1]-10) case &quotDown&quot: pyautogui.moveTo(mousePosition[0], mousePosition[1]+10) case &quotRight&quot: pyautogui.moveTo(mousePosition[0]+10, mousePosition[1]) case &quotLeft&quot: pyautogui.moveTo(mousePosition[0]-10, mousePosition[1]) case &quotMiddle&quot: pyautogui.middleClick() case &quotSet&quot: pyautogui.leftClick() case &quotReset&quot: pyautogui.rightClick() print(mouseta) # Answer from arduinoبا کدهای آردوینو و پایتون بالا می توانید ماوس روی صفحه را بوسیله یک جوی استیک دیجیتال (کلید ۵ محوره) کنترل بفرمایید. مقدار +10 و -10 در حقیقت گام حرکت ماوس است.