Physical Programming ด้วย Scratch 1.4 บน Raspberry Pi ตอน แนะนำ Remote Sensors Protocol

ก่อนหน้านี้ได้เขียนเรื่องราวการใช้ประโยชน์จาก GPIO Server เพื่อให้ Scratch 1.4 ติดต่อกับ GPIO Pin ของ Raspberry Pi มาแล้ว นับว่าเป็นการขยายขอบเขตการใช้งาน Scratch 1.4 ออกไปได้ดีทีเดียว แต่ก็ยังมีข้อจำกัดอยู่ที่การรับค่าจาก GPIO Pin โดย Scratch 1.4 นั้นรับค่าได้เพียงสองสถานะคือ 1 (on) กับ 0 (off) เท่านั้น (ความจริงมีเรื่องอื่นได้แก่ Camera, IP, และ  PWM อีก ในส่วนของ PWM เป็น software PWM ซึ่งใช้ประโยชน์ไม่ค่อยได้มากนัก ) การใช้งานจึงค่อนข้างจำกัดอยู่

โชคดีที่ทางผู้พัฒนา Scratch  ได้สร้างสิ่งที่เรียกว่า Remote Sensors Protocol ไว้ ทำให้ Scratch 1.4 สามารถสื่อสารได้กับโปรแกรมอื่นได้ ซึ่งมีประโยชน์ตรงที่ทำให้ Scratch 1.4 สามารถยืมความสามารถจากโปรแกรมอื่น หรือโปรแกรมที่พัฒนาขึ้นมาเองได้ ซึ่งเป็นการขยายความสามารถออกไปได้มากกว่ากว่าการใช้ GPIO Server อย่างเดียว และในบทความตอนนี้จะใช้ Protocol นี้ทำงานร่วมกับโปรแกรมที่พัฒนาด้วยภาษา Python ครับ

รู้จักกับ Remote Sensors Protocol

1. การเปิดใช้งาน

การเปิดใช้งาน Protocol นี้ต้องอาศัย sensor value block หรือ sensor  block ซึ่งอยู่ในกลุ่ม Sensing Blocks



โดยการเลื่อนเมาส์ไปวางบน Block อันใดอันหนึ่ง แล้วทำการคลิ๊กขวา จะเห็น popup menu ดังภาพ แล้วเลือกรายการ enable remote sensor connections



 แล้วจะได้พบกับ message windows ดังภาพ คลิ๊ก OK เพื่อปิดหน้าต่าง



หลังจากนี้ Scratch 1.4 จะทำการเปิิดพอร์ตสื่อสาร หมายเลข 42001  ขึ้นมาเพื่อรอการสื่อสารจากโปรแกรมอื่น


2. โครงสร้างข้อความ

การสื่อสารระหว่างโปรแกรมอื่นกับ Scratch 1.4 จะใช้การส่งข้อความไปมาระหว่างกัน โดยข้อความนั้นแบ่งเป็นสองส่วนดังภาพ




ส่วนที่เป็น Message Length จะมีความยาวคงที่คือ 4 Bytes โดยจะบรรจุค่าความยาวของ Message หรือข้อความจริงไว้ และส่วนที่เหลือจะเป็นส่วนที่เก็บข้อความที่ใช้จริง ข้อความที่ใช้ได้คือ

1. ข้อความคำเดียว (single word string) เช่น cat ,book-1, hello, etc
2. ข้อความที่มีหลายคำ โดยต้องเขียนไว้ในเครื่องหมายคำพูด เช่น "hello world", "sing a song", etc
3. ตัวเลข 1,2,3,-4.0,-1200, etc
4. ค่าทางตรรกะ คือ true , false


3. Block ที่ใช้ในรับ-ส่งข้อความ


3.1 รับข้อความจากโปรแกรมอื่น



 เช่น


หมายถึง เมื่อ Scratch 1.4 รับข้อความ "cat" จากโปรแกรมภายนอก ให้ทำเสียง "meow"




3.2 ส่งข้อความออกไป



เช่น





เป็นการส่งข้อความ "I see a cat" ออกไปยังโปรแกรมข้างนอก

การสื่อสารระหว่าง Scratch 1.4 กับ Python 3.x

ทีมงานผู้พัฒนา Scratch  ได้ทำตัวอย่างการสื่อสารระหว่าง Scratch กับ Python ไว้แล้ว (ดูเพิ่มเติม)
แต่เป็นชุดคำสั่งที่ใช้งานได้กับ Python 2.x  ผมได้นำมาดัดแปลงนิดหน่อยเพื่อให้งานได้กับ Python3.x ดังนี้ (หากต้องการใช้งานกับ Python 2.X ท่านสามารถดาวน์โหลดได้จากที่นี่)

ScratchPy.py : (ดาวน์โหลด)

import socket
import time
from array import array
import struct

#reference to https://wiki.scratch.mit.edu/wiki/Communicating_to_Scratch_via_Python


class Py2Scratch14():
     def __init__(self,host='localhost',port=42001):
          #the first 4-byte of message contains size of the real message 
          
          self.prefix_len = 4
          self._port = port
          self._host = host
          self._socket = None
  
     def connect(self):
          try:
               self._socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
               self._socket.connect((self._host,self._port))
          except socket.error :
               self._socket = None
   
     def send(self,cmd):
          if self._socket is None :
               return
          n = len(cmd)
          #to create first 4 bytes
          a = array('u') #array of unicode
          a.append(chr((n >> 24) & 0xFF)) # first left most byte
          a.append(chr((n >> 16) & 0xFF)) # second byte
          a.append(chr((n >> 8) & 0xFF)) # third byte
          a.append(chr(n & 0xFF)) # forth byte
          self._socket.send(a.tostring() + cmd)
  
     def sendCMD(self,cmd):
          #this can be used with Python3
          if self._socket is None :
               return
          # first 4 bytes contains size of message 
               self._socket.send(len(cmd).to_bytes(4, 'big'))
          # then send the command to Scracth
               self._socket.send(bytes(cmd,'UTF-8'))
  
     def _read(self, size):
          """
          Reads size number of bytes from Scratch and returns data as a string
          """
          data = b''
          while len(data) < size:
               try:
                    chunk = self._socket.recv(size-len(data))
               except socket.error :
                     pass
          if chunk == '':
               pass
          data += chunk
          return data

     def _recv(self):
          """
          Receives and returns a message from Scratch
          """
          prefix = self._read(self.prefix_len) 
          msg = self._read(self._extract_len(prefix))
  
          #return prefix + msg
          return msg
  
     def receive(self):
          in_msg = self._recv().decode('UTF-8')  
          return self._parse(in_msg)
  
     def _extract_len(self, prefix): 
          """
          Extracts the length of a Scratch message from the given message prefix. 
          """
          return struct.unpack(">L", prefix)[0] 
  
     def _parse(self, msg):
          msg = msg.replace('"','')
          splited = msg.split(" ")
          if len(splited) == 2 :
               return (splited[0],splited[1],None)
          else:
               return (splited[0],splited[1],splited[2])
  
     def sensorupdate(self, data):
          """
          Given a dict of sensors and values, updates those sensors with the 
          values in Scratch.
          """
          if isinstance(data, dict):
               msg = 'sensor-update '
               for key in data.keys():
                    msg += '"%s" "%s" ' % (str(key), str(data[key]))
               self.sendCMD(msg) 
  
     def broadcast(self,msg):
          _msg = 'broadcast "'+msg+'"'
          self.sendCMD(_msg)  
  

ต่อไปนี้เราจะสามารถนำเอา  Python นี้ทำหน้าที่เป็นตัวกลางสื่อสารระหว่าง โปรแกรมอื่นที่เขียนด้วย Python กับ Scratch 1.4

ในตัวอย่าง

ตัวอย่าง

จะทดสอบโดยการสั่งให้ Sprite ใน Scratch หมุนตามเข็มนาฬิกา 360 องศา เมื่อได้รับข้อความว่า "rotate_right" หลังจากหมุนเสร็จก็ส่งข้อความ "finish" ไปบอก Python





Python code : test_scratch_01.py

from ScratchPy import Py2Scratch14

sc  = Py2Scratch14()
sc.connect()  # start connect to Scratch
sc.broadcast("rotate_right")
rcv = sc.receive()
if rcv[1] == 'finish' :
     sc.broadcast("rotate_left")


Scratch Script :




ขั้นตอนการทำงาน
1.  เปิดใช้งาน Remote Sensor Protocol ตามที่กล่าวมาข้างต้น
2. สร้าง Scratch script ตามตัวอย่าง
3. เขียน Python ตามตัวอย่าง บันทึกในชื่อ test_scratch_01.py
4. ใช้คำสั่ง python3 test_scratch_01.py




[ควบคุม Servo motor ด้วย REMOTE SENSORS PROTOCOL]
Previous
Next Post »