* 以下可以搭配 IoTtalk 上的 Ball-throw1 從 IoTtalk 遙控器控制;
也可以直接從鍵盤輸入控制; 會圖時可以故意按 ENTER 鍵看看(或亂打一個字再按看看)
多研究別人程式(這程式用到 兩個 thread), 可讓你程式功力大增 :-)
* 注意這 DAI777.py 需要用到 Dummy Device 的 DAN.py 和 csmapi.py
(連接 IoTtalk 伺服器用)
|
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
---
===
|
#This DAI777.py uses coding: utf-8 ; 整合 Turtle Graphics 與 DAI2.py; by tsaiwn@cs.nctu.edu.tw
import time, DAN, requests, random
import threading, sys # for using a Thread to read keyboard INPUT
dm_name = "Ball-throw1" ## DM (Device Model) 的名稱
odfs = ["Angle", "Height", "Speed"] #ODF list; Output Device Features
funs = "" # [Angle, Height, Speed ] ## functions; 必須寫在函數出現之後 ! 所以故意先寫 ""
ServerURL = 'https://demo.iottalk.tw' ## with SSL(https) secure connection
DAN.profile['dm_name']= dm_name # you can change this but should also add the DM in server
DAN.profile['df_list']= odfs # Check IoTtalk to see what IDF/ODF the DM has
DAN.profile['d_name']= "TWN_TTL."+ str( random.randint(100,999 ) ) +"_"+ DAN.profile['dm_name'] # None
Reg_addr = None ##if None, Reg_addr = MAC address #(在 DAN.py 會這樣做, 會用 UUID 生出唯一ID )
# Note that Reg_addr 在以下三句會被換掉! # thus the mac_addr in DAN.py is NOT used
mac_addr = "C77700D38" + str( random.randint(100,999 ) ) # put here for easy to modify :-)
## 若希望每次執行這程式都被認為同一個 Dummy_Device, 要把上列 mac_addr 寫死(或寫 None), 不要用亂數。
Reg_addr = mac_addr ## Note that the mac_addr generated in DAN.py always be the same cause using UUID
####### 參數改變: Angle, Height, Speed ==> angle, pensize, steps ###/////////////////
### Turtle Graphics, coding: utf-8 # 畫正方型 -- 改參數然後測試看畫出啥 -- by tsaiwn@cs.nctu.edu.tw
angle, pensize, steps = (59, 3, 11) ## (59, 3, 11); (29, 3, 11); (89, 3, 11)
## # angle: 每次旋轉角度; steps: How many steps to repeat draw?
length = 150 ## 線段長度, 正方形的邊長
import turtle
pen = turtle.Turtle( )
turtle.Screen( ).bgcolor("black") # 畫面底色
pen.speed(11) ## 11 == 0 == fastest;; 1 == slowest
colors = [ "red","blue","green","orange","purple","yellow",] # 紅藍綠 橙紫黃; Cyan = 湛藍
colorsLen = len(colors)
######### = = = = = = = = == = = = = == = = = == = = = = == = ======= ########
def register( ):
DAN.device_registration_with_retry(ServerURL, Reg_addr)
print("dm_name is ", DAN.profile['dm_name']) ;
print("Server is ", ServerURL);
## 這是原先入門手冊(五)裡面 DAI2.py 負責讀取鍵盤的 thread
# global gotInput, theInput, allDead ## 主程式不必宣告 globel, 但寫了也 OK
gotInput = allDead = False
theInput="haha"
def doRead( ): ## thread 負責讀取鍵盤
global gotInput, theInput, allDead
while True:
while gotInput: # 老闆(主執行緒)還沒把資料拿走
time.sleep(0.1) # 小睡一下把 CPU 暫時讓給別人
continue # go back to while
try: ## 準備讀取資料, 注意程式會卡在這等 User 輸入, 所以要用 Thread
theInput = input("Give me data: ")
except KeyboardInterrupt: # except Exception:
allDead = True ## 告訴大家說要收工囉 :-)
break; # Leave while True
if theInput =='quit' or theInput == "exit":
allDead = True ## 也是告訴大家說要收工囉 :-)
else:
## print("Data give to Main Thread: " + theInput, end=" , ")
gotInput=True # YES, we got data
if allDead: break;
return # means break from while LOOP; # 這是 doRead( ) 最後, 不寫也 OK
import threading, sys # for using a Thread to read keyboard INPUT
#creat a thread to do Input data from keyboard, by tsaiwn@cs.nctu.edu.tw
threadx = threading.Thread(target=doRead) ## Python 的 thread 就這麼簡單
threadx.daemon = True # 表示若把我start叫起來的 thread 死了,則我必須立即自殺殉葬 !
######################################################
def sayWeGot(arg): # 後來 arg 沒用到 (1, 2, 3 代表 angle, pensize, steps)
global gotPull
gotPull = True ##讓主執行緒在叫用 checkPull( ) 時可以知道我們幫忙從 IoTtalk 那邊 PULL 到資料了。
angleNew = None ## 網路傳來 (pull 到的) 資料先放 *New, 再由 main thread 主執行緒 拿走
pensizeNew = stepsNew = None ## 三個都先放 None 表示沒從網路 PULL 到資料
####### 以下三個函數其實是從 IoTtalk 的 VPython DA 抄來的, 故意儘量保持原來樣貌
### 這三個函數會被 doPull( ) 在 PULL 到資料時用 funs[i](data) 叫用 (LINE 120)
# 當 Gravity 旋鈕/滑桿 收到新值時;; ## Ball-throw1 的 ODF ["Angle", "Height", "Speed"]
def Angle(data):
global angleNew ## shift angle; 每畫好一回合要左轉幾度
if data != None:
angleNew = int( data[0] ) # [0, 360]
sayWeGot(1) # 這樣我們可以判斷抓來啥新的資料
# 當 Radius 旋鈕/滑桿 收到新值時 ## 原先在 Edutalk 這是 Radius
def Height(data):
global pensizeNew ## 筆粗細
if data != None:
pensizeNew = int( data[0] ) # 建議限制 [0, 15]
if pensizeNew > 15: pensizeNew = 15 ## 防呆一下
sayWeGot(2)
# 當 Speed 旋鈕/滑桿 收到新值時 ## 原先在 Edutalk 並沒有 Speed(data)
def Speed(data):
global stepsNew ## new repeat steps; 要 repeat 多少次
if data != None:
stepsNew = int( data[0] ) #
sayWeGot(3)
###
funs = [Angle, Height, Speed] ## 在函數寫完之後才可寫這; 用在 doPull( )
funs = [Angle, Height, Speed] #
funs = [Angle, Height, Speed] ## 寫三次也不會怎樣 :-)
############################################################################
### 以下只是故意透過 funs[i] 調用 Angles( ), Height( ), Speed( )
### 否則, 實際上可以直接處理 pull 到的資料改變 angleNew, pensizeNew, stepsNew
import threading, sys # 重複 import 並不會怎樣, 也不會執行
## 以下是負責去 PULL 網路資料的 thread (執行緒)
allDead = False ## 用來看是否不要做了
def doPull( ): ## thread 負責 PULL IoTtalk 的資料
global allDead ## 其實這句不必, 因這函數內並沒去改 allDead
while True:
data = None
if allDead: break; ## 通常是因為 doRead( ) 從鍵盤讀到了說要 quit
for i in range(len(odfs)): ## Ball-throw1 的 odfs[ ] 有三個
## print("try Pull ", odfs[i], funs[i])
try:
data = DAN.pull(odfs[i]) ## odfs[ ] 裡面是 "Angle", "Height", "Speed"
pass # 這裡看要不要寫些成功 PULL 資料...
except: ## 有錯好像也沒辦法怎樣 :-)
pass
print("? DAN pull error: when pulling ", odfs[i])
# print(" got pull data = ", data)
if data != None:
if not type(data) is list: data = [data] # 確保傳 [LIST] 過去 !
funs[i](data) # 叫用對應的函數 Angle, Height, Speed
time.sleep(0.125) # 每做完一個 pull 資料, 就休息一下下
time.sleep(0.25) # every for Loop (已經 pull 完 "Angle", "Height", "Speed" 一回合)
print("Leave while Loop in doPull()")
## thread to do Pull jobs (執行緒)
thready = threading.Thread(target=doPull) ## Python 的 thread 就這麼簡單
thready.daemon = True ## 表示若把我start叫起來的 thread 死了則我必須立即自殺殉葬 !
## 主執行緒要進入 Loop 之前(接進最後處)再把 thread (執行緒) start 起來!
########=========== # ## === from KEYBOARD === # ## ==== # ## ====== ========
######## !!! 會被 main Thread 直接調用 !!! 別跟 doRead( ) 搞混喔 !
## ==> 查看 doRead( ) 有幫忙讀到資料放 theInput 裡面嗎?
## 可以輸入三個參數, 用空白隔開 或 逗號隔開
def checkInput( ): # 會被 main Thread 直接調用
global gotInput, theInput, allDead
global angle, pensize, steps, isRunning
checkHelp( )
if not gotInput:
return False # 沒輸入
if theInput =='quit' or theInput == "exit":
allDead = True
return True
if theInput == "go" or theInput == "GO": ## 秘技 :-)
theInput = "-1 -1 " + str(steps) ## 假裝第三個參數有變化
xx = theInput + ", -1, -1,-1" # 最多三個輸入值
xx = xx.replace(" ", ",") # 空白轉為逗號
xx = xx.split(",") ## 依據 逗號 切開
xx = [x for x in xx if x!=""] ## 去掉空字串
xx,yy,zz, *_ = xx ## 取出前三個 (仍為字串)
## print("Got from keyboard: ", xx, yy, zz)
try:
xx = float(xx)
yy = float(yy)
zz = float(zz)
except:
print("What? ", theInput)
gotInput = False
return False # 算沒讀到東西
if(xx != -1): angle = int(xx) # 這例子只需要整數
if(yy>=0): pensize = int(yy)
if(zz>=0):
steps = int(zz)
isRunning = True ## 只 steps 改變才重畫
gotInput = False # 表示拿走了所以可再次輸入囉
return True # 有讀到資料
def checkHelp( ): ## 看有沒輸入要求看 HELP 訊息
global gotInput, theInput, allDead
if not gotInput: return # 沒輸入
if theInput == "help" or theInput == "HELP" or theInput == "?":
print("\r\nInput 輸入 Angle, pensize, steps (空白或逗號隔開, -1 表示不變)")
print("quit 結束, show 或直接按 ENTER 看參數, go 立即畫!", " "*12)
theInput = "show" # 偷吃步 :-)
if theInput == "show" or theInput == "" or theInput.startswith("-1 -1 -1") :
print("\nServer is ", ServerURL, " "*28) ## IoTtalk server
print("My Name is ", DAN.profile["d_name"], " "*33)
print("Angle, pensize, steps = ", angle, pensize, steps)
gotInput = False
return
##------- checkPull( ) ------ check to see doPull( ) had PULLed data from NETWORK ------- ----------
######## !!! checkPull( ) 也是會被 main Thread 直接調用 !!! 別跟 doPull( ) 搞混了!
## ==> checkPull( ) 查看 doPull( ) 有 pull 到資料嗎?
gotPull = False
angleNew = None # 之前 doPull( ) pull 到的先放這些有 *New 的變數 (調用函數放的)
pensizeNew = None
stepsNew = None ## 新的 steps
def checkPull( ): ## 會被 main Thread 直接調用
global angle, pensize, steps # 有寫在等號 = 左邊的才須 global
global angleNew, pensizeNew, stepsNew ## 所以, 這三個也要 global 喔 !
global gotPull, isRunning
if not gotPull:
return # 沒網路資料過來, 立即返回
if angleNew != None:
angle = angleNew
angleNew = None
print("got IoTtalk new shift Angle = ", angle)
if(angle==0): angle = -11 # 旋轉 0 等於畫在原處, 故意改為 -11 右璇
if pensizeNew != None:
pensize = pensizeNew
pensizeNew = None
print("got IoTtalk new Pensize = ", pensize)
if(pensize==0): pensize = 6 # 故意
if stepsNew != None:
steps = stepsNew
stepsNew = None
print("got IoTtalk new Steps = ", steps)
time.sleep(0.123) ## 故意
isRunning = True ### 只 steps 改變才重畫
gotPull = False # 表示我拿走了
setMsg( ) # message to show; 弄好要顯示的參數在 msg, msg2 之後會印成兩列
time.sleep(0.125) ## 故意 Delay 一下
################################################################################
pen3 = turtle.Turtle( ) ## 第三支筆用來顯示進度 (後來加入的)
pen3.width(5); pen3.speed(0); pen3.st( ) # 進度條筆粗 5
x3Orig, y3Orig = (-250, 320) ## 進度條放在 文字的上方
def initPen3( ): ## pen3 用來畫進度條(progress bar)
global x3, y3
p3xxx = 0; x3=x3Orig; y3=y3Orig; pen3.color("red");
pen3.up( ); pen3.goto(x3-6, y3); pen3.down( )
pen3.fd(1); pen3.fd(2); pen3.fd(3); turtle.update( ) #
def pgsBar( ): ## 畫進度條(progress bar)的程式碼
global x3, y3
x3 += 9; pen3.color("cyan"); pen3.st( );
if(x3 > 233): ## 太右邊就換一列
x3 = x3Orig; y3 += 16
pen3.up( ); pen3.goto(x3, y3); pen3.down( )
pen3.fd(6); pen3.color("yellow"); pen3.fd(3);
######## ////////// myText( ) to display message msg, msg2 on the Screen
oldmsg = oldmsg2 = ""
######## ////////// setMsg( ) + myText( ) ////////// ######## //////////
def setMsg( ): ## 準備好 msg, msg2
global msg, msg2
msg="Angle: " + str(angle)
msg2 = "pensize=" + str(pensize)
msg2 += "; steps " + str(steps)
pen2 = turtle.Turtle( ) ## pen2 負責 write 文字在圖畫上方
x2Orig, y2Orig = (-250, 270) ## 文字寫在圖的上方
def myText(x= x2Orig, y=y2Orig):
global oldmsg, oldmsg2
setMsg( ) # construct(prepare) msg, msg2
if msg == oldmsg and msg2==oldmsg2:
return
style = ('Courier', 24, 'bold')
pen2.color(turtle.bgcolor( ) ) ## 用背景色清除以前文字 # 'black'
if oldmsg != "": ## 先清除以前文字
pen2.setposition(x, y)
pen2.write(oldmsg, font=style, align='left') ## 擦掉舊的文字
pen2.setposition(x, y-32)
pen2.write(oldmsg2, font=style, align='left')
pen2.color('pink') # deep pink
pen2.setposition(x, y)
pen2.write(msg, font=style, align='left')
pen2.setposition(x, y-32)
pen2.write(msg2, font=style, align='left')
pen2.hideturtle() # pen2.ht( )
oldmsg = msg ## 記住舊文字, 之後才能清除
oldmsg2 = msg2
gotInput = gotPull = False #
############### 是否要打斷畫圖 ? ######################
def giveup( ):
if allDead: return True ## 已經說要收工就不要做了 :-)
if gotInput: ## 鍵盤有按下 ENTER; 或 輸入 以下這些 都不打斷畫圖
if(theInput == ""): return False # 只按 ENTER, 繼續畫畫 if 正在畫
if(theInput == "show" or theInput == "?"): return False
if(theInput == "help" or theInput == "HELP"): return False
return True # 其他輸入要打斷畫圖
if gotPull: # 網路送資料來; 也可再細分送啥資料, 現在偷懶一律打斷畫圖
return True
return False ## 啥都沒收到當然不打斷啊
######## /////////////////////////////////////////////////////////////////
# Turtle Graphics, 畫正方型 -
def rect(length): # 畫一個正方型
for i in range(4):
pen.fd(length)
pen.left(90)
def drawTurtle( ): ## 這才是 Turtle Graphics 要畫的 ### ###
global angle, steps ## 這裡要防呆可能改掉 angle, steps 這兩個
if angle == 0: angle = 1 # 防呆 :-)
if 1 > steps: steps = 1 # 防呆 :-) 不讓 steps 小於等於 0
colorsThis = [ ] ## 讓顏色順序與 steps 有關, 重新弄一個顏色組合, 也可考慮亂數亂調換
for i in range(steps, steps+colorsLen, 1):
colorsThis.append( colors[ i % colorsLen ] ) ## colorsThis[ ] 放顏色
for i in range(steps): ## Repeat steps 次
if giveup( ): ## 這樣不用寫 generator 或 coroutine
return ## 參看前面 giveup( ) 函數
pgsBar( ) ### draw progress Bar 進度條
for c in colorsThis:
checkHelp( ) ## 看是否需要處理鍵盤 help, 但仍會繼續畫
pen.color( c )
rect(length) # 畫一個方型, 邊長 length
if(angle > 0) : pen.left(angle) # 往左旋 (其實好像不用管 angle 正負:-)
else: pen.right(-angle) # 往右旋; 但不可以 -1 因 -1 已經用來表示不改該參數了
pen.ht( ) # Hide turtle
if pensize > 3: # 如果線比較粗..
pen.width( int(pensize/2) ) # 因最後一個方型太顯眼所以...
if(angle > 0) : pen.right(angle) ## 回頭偷畫個灰色較細的
else: pen.left(-angle) # 要注意原先左旋還是右旋 !
pen.color("gray") ## 偷畫一個灰色的方型
rect(length)
return
def drawPicture( ):
turtle.Screen( ).clear( )
turtle.Screen( ).bgcolor("black")
pen.reset( ) # turtle.reset( )
pen.st( ) # show Turtle
pen.speed(1) #故意先放慢速度
pen.penup( ) #其實不必做這
pen.setheading(0) # head 往右邊; pen.reset( ) 已經做這了
pen.right(60) # Right-Down corner
pen.pendown( ) # 筆放下
pen.pensize(pensize) ##筆粗細 pen.width(pensize)
pen.speed(10) # 故意用 10 不要畫太快; 最快是 11 == 0 == fastest
if(steps > 17) : pen.speed(0) # do faster if too many steps to draw; 1 最慢, 11 和 0 最快
if(3 >= angle): pen.speed(0) ## shift angle 太小也畫快點
if(steps%2 == 0): pen.speed(0) ## 偶數步驟也故意畫快點
myText( ); initPen3( ); ## 負責顯示參數文字與進度條
timeStart = time.time( )
drawTurtle( ) ## 這才是真正 Turtle Graphics 的核心工作 :-)
print("\r\nElasp time = {:.8f}".format( time.time( ) - timeStart) ) #印到小數點後8位就好
print("Give me data: ", end="", flush=True) ## 讓 CMD 視窗有輸入的提示 :-)
# start( ) function to (1)register to IoTtalk; (2) start two threads
def startBrother( ):
register( ) # to IoTtalk; see LINE 33 in this DAI777.py
time.sleep(0.1)
threadx.start() ## 讀取鍵盤的小弟
thready.start() ## 讀取網路(Pull)的小弟
## print("Thread started !")
startBrother( ) ## 註冊, 並啟動兩個 thread (執行緒)
isRunning = True
msg = "Turtle Graphics" # 沒事 :-)
while True: ## 這是 main thread 主執行緒的核心工作: 畫圖 + 查看鍵盤資料 + 查看網路資料
try:
while not isRunning:
pen.ht( ) # hide Turtle
myText( )
turtle.update( )
time.sleep(0.2)
ggyy = checkInput( ) # 查看鍵盤輸入
if(allDead): break; # 離開 while not isRunning
if(not ggyy):
checkPull( ) # 沒鍵盤輸入才查看網路來信息
if isRunning: # 即將離開這 while not isRunning
oldmsg += " " # 故意讓 它與 msg 不相同, 這樣會重新印文字
if(allDead): break; ## 離開 while True
gotInput = gotPull = False
drawPicture( ) ## 畫圖 Turtle Graphics
time.sleep(0.2) # 故意
if(allDead): break;
isRunning = False
except KeyboardInterrupt: ## 他敲了 CTRL_C
allDead = True
break;
## turtle.exitonclick( )
try:
DAN.deregister( ) ## 解除註冊
print("\n\nDeregister " + DAN.profile['d_name'] + " !!!\n", flush=True)
except:
pass # 若無法解除註冊.. 也只能不鳥它囉
print("Bye bye!")
|
|
|
* 建議進入以下這網站, 稍微看看並測試, 然後修改第二個程式的一些參數再試試!!
* https://www.101computing.net/python-turtle-spirograph/
( 建議改參數玩看看! )
(市面上有賣該網頁內程式用到數學原理的文具, 叫做 "萬花尺", Design Ruler)
* 非廣告:這 "萬花尺", 一套, 一個圈圈尺加三個圈圈片, 才 NT$ 8元
Spirograph 這個字查字典會發現叫作"呼吸描記器", 但在這要翻譯作"萬花尺",俗稱Design Ruler,也叫繁花曲線規。
萬花尺所畫出的圖案,與外圖板圓圈半徑、內圓圖板半徑及筆洞位置有相關性。圖案令人聯想到萬花筒,故名萬花尺。
萬花尺由英國工程師丹尼斯•費歇爾(Denys Fisher)於1965年研發並銷售。(參考 Wikipedia)
Turtle Graphics 最開始出現在 1967年專為小孩子設計的 Logo 程式語言。
是由 Wally Feurzeig, Seymour Papert, 和 Cynthia Solomon 三位共同設計研發的。
| | |
|
|
|