在開始之前,再提一些事。本篇文章是原理與基礎練習,不含相關應用(有緣再寫?),我的測試環境是windows8.1,使用的程式為python3.4,python3的語法介紹不在此篇的範圍內,安裝與環境建置就要請想試的人自行找資料(我只會皮毛)。當然,如果你有慣用的程式語言(C++、Java、C#…),只要能達到同樣的功能,就不必非得用python不可。
一開始我為了想知道s2a_fm的運行方式(好奇寶寶?),找到了一份有關Scratch2擴充積木的說明文件,裡面有自訂新積木的方式,以及與外部程式通訊的方式(注意,此方法為Scratch Http Extension,只適合Scratch2離線版使用,見詳情)
大致上的原理,我畫了一張圖如下:
左邊的是Scratch2,右邊是helper程式。要幫Scratch2做擴充功能,要做兩件事。第一是在Scratch2中載入一個s2e的積木描述檔。第二是要有一個helper程式。Scratch2會以HTTP 協定的Get 方式(有寫過網頁互動程式,對Get Method應該很清楚),與 helper程式溝通,helper程式就會把新積木的功能執行或是把資料傳回Scratch2。( Scratch2與Scratch1.4,在擴充機制上,扮演的角色剛好相反,在Scratch2是Http用戶端,在Scratch1.4是伺服端)
這樣可能還是不清楚(因為有些細節沒提到),那實際來做做看吧。以下是一個base_helper.s2e的描述檔
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"extensionName": " 自訂積木 ", | |
"extensionPort": 50000, | |
"blockSpecs": [ | |
[" ", "第一個程式的名字是?", "hello"], | |
] | |
} |
注意描述檔雖然只是普通的文字檔,但是要用 UTF-8的格式儲存,描述檔是語法是JSON(一種用文字來表達物件的格式,請見JSON),extensionName是擴充積木的整體名稱,extensionPort是Http協定的Port號碼(可自訂,通常1024~65535)這個Port號碼到時要與helper程式一致,才能通訊。
接下來blogSpecs裡的是自訂的積木(可多組),這邊定義了一個積木,有三個參數,分別用雙引號包住,說明如下圖
第一個參數是積木種類,這個的" "(內有一個空格),代表的是命令積木(command),是由Scratch2向helper程式送出命令。(還有其他種類,請參考Scratch官方文件)
第二個參數是積木格式,也就是積木上看到的字
第三個參數在這裡是命令名稱,也就是實際送出給helper程式的命令名稱
s2e檔在Scratch2的匯入方式如下圖:
剛剛定義的新積木,在Scratch2中會在「更多積木」中出現
到這裡為止的步驟,有裝過s2a_fm的人應該不陌生,但是這次是要做自己的擴充積木哦!
接下來是用python寫的base_helper_py3.py 伺服程式 (從s2a_fm中精簡出來)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
from http.server import BaseHTTPRequestHandler | |
from http.server import HTTPServer | |
import os, sys, urllib | |
###### 全域變數建議區 (放此處較易理解) ##### | |
#################################################### | |
HELPER_NAME = "基本的Helper" | |
HELPER_PORT = 50000 | |
#################################################### | |
class CmdHandler(BaseHTTPRequestHandler): | |
""" | |
This class handles HTTP GET requests sent from Scratch2. | |
""" | |
def do_GET(self): | |
""" | |
process HTTP GET requests | |
""" | |
# skip over the first / . example: /poll -> poll | |
cmd = self.path[1:] | |
# create a command list . | |
cmd_list = cmd.split('/') | |
s = "不回傳資料" | |
###### 處理Scratch送出的命令 | |
###### 若需回應Scratch的Poll命令,再把文字存在變數s ## | |
############################################################## | |
if cmd_list[0] == "hello" : | |
print ("Hello World!") | |
############################################################# | |
self.send_resp(s) | |
def send_resp(self, response): | |
""" | |
This method sends Scratch an HTTP response to an HTTP GET command. | |
""" | |
crlf = "\r\n" | |
http_response = "HTTP/1.1 200 OK" + crlf | |
http_response += "Content-Type: text/html; charset=ISO-8859-1" + crlf | |
http_response += "Content-Length" + str(len(response)) + crlf | |
http_response += "Access-Control-Allow-Origin: *" + crlf | |
http_response += crlf | |
if response != '不回傳資料': | |
http_response += str(response + crlf) | |
# send it out the door to Scratch | |
self.wfile.write(http_response.encode('utf-8')) | |
def start_server(): | |
""" | |
This function populates class variables with essential data and | |
instantiates the HTTP Server | |
""" | |
try: | |
server = HTTPServer(('localhost', HELPER_PORT ), CmdHandler) | |
print ('啟動<' + HELPER_NAME + '>伺服程式!(port ' + str(HELPER_PORT) + ')') | |
print ('要退出請按 <Ctrl-C> \n') | |
print ('請執行Scrath2(記得要開啟對應的s2e檔案!)') | |
except Exception: | |
print ('HTTP Socket may already be in use - restart Scratch') | |
raise | |
try: | |
#start the server | |
server.serve_forever() | |
except KeyboardInterrupt: | |
print ('\n\n退出程式……\n') | |
sys.exit() | |
if __name__ == "__main__": | |
start_server() |
另外程式要注意的是第12行的PORT號碼,要跟s2e中是一樣的才行。
接下來就是實際執行helper程式了,請用python3以上來執行,執行的畫面如下
執行後,scratch2的新積木本來是紅燈的地方,已變成綠燈,表示已與helper伺服程式連上了
那就來把這個「第一個程式的名字是?」的積木按一按,看會發生什麼事呢?
到這邊,Scratch2擴充積木的Hello World 版算是完成了( Hello World常常是程式語言教學的第一個程式),不過還有一件事想搞清楚,按了自製積木後,scratch2到底送出什麼命令?很簡單,只要在helper伺服程式的第42行,加入
if cmd_list[0] != "poll" : print (self.path)
就可以了(程式只有一行,不要分兩行,注意要與39行的if保持同樣的縮排,這是python特有的語法,還有為什麼要排除poll,因為如果不排除,poll命令一秒送約30次,poll太多會看不到其他命令),運行的結果如下:
原來當我們按下新積木時,scratch2送出的命令是/hello,這個技巧很有用,可以用來觀察,在不同的積木定義下,scratch2送出的實際命令是什麼。
最後以下圖做一個概念的總結
這篇文章大致到這裡,礙於篇幅僅能介紹一種積木。這一篇完成的事看起來沒什麼,就是實做一個Hello World擴充程式,但是這也是s2a_fm如何寫出的基礎,別忘了,只要能掌握到helper伺服程式,就可以做許多的擴充功能,讓Scratch與不同的硬體 ,作業系統,甚至是與網際網路做各種有趣連結。
請問這有辦法在mblock上實現嗎
回覆刪除mblock有自己的擴充,用javascript,但我沒研究
刪除張老師你好,我是一位小學四年級學生家長,想為小孩開拓電腦資訊視野,先前接觸Scratch與Arduino,苦腦沒有一個完美結合,在網上搜尋文章發現老師的文,但家中為Mac系統,不知老師是否也有相關文章或步驟可參考?
回覆刪除抱歉沒看到這篇。我這篇的擴充是透過python,Mac是支援python的,所以理論上是可行的, 不過我沒有Mac,沒試過
刪除