* 以下改自王一哲老師的蛇擺程式碼可丟入 EduTalk 的自由落體實驗, 用手機或電腦控制;
多研究別人程式(這程式用到 Python 的 class), 可讓你程式功力大增 :-)
* 可以到 GlowScript.org 觀摩更多 VPython 範例
(註冊後可以上傳程式碼)
|
使用新版 class Pendulum (比較符合 class 撰寫慣例) 的 Edutalk 版本 ..
|
-
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
---
===
|
這是 從 王一哲老師 的 蛇擺程式 抄來, 稍作修改;放入 Edutalk 自由落體實驗內;
配合 自由落體實驗, 以便用手機控制改 num 幾個單擺 或 改 N 最長的每週期擺幾次
(當然也可用電腦上遙控器控制, 但要先用手機掃QRcode控制過)
#This 檔案 uses coding: utf-8 ;;; 新版 class Pendulum; modified by tsaiwn@cs.nctu.edu.tw
## #** 王一哲老師的蛇擺放入 Edutalk 的自由落體實驗, 可用手機控制參數 num 和 N
"""
VPython教學進階內容: 蛇擺 pendulum wave, 使用 class
Ver. 1: 2018/7/12
Ver. 2: 2018/7/14 加上計時器
作者: 王一哲
"""
pendulums = []
## when run in GlowScript or /withOUT EDUtalk, enable next 3 functions
# def playAudio(gg): return ## for run without Edutalk/IoTtalk
# def dai(gg): return ## for run without Edutalk/IoTtalk
# from vpython import * # 單機版需要這句
"""
1. 參數設定, 設定變數及初始值
"""
num = 35 # 單擺的個數
N = 15 # 週期最長的單擺在蛇擺的1個週期內擺動的次數
dfc = 5 # [0, 999] # 阻力係素 ; 設 0 就當作真空不會停止 #damping force
## 以上當作可改的參數
angle=30 # 擺角
stopPoint = radians(0.1) # stop when smaller than 0.1 degree
isRunning = True
freq = 1000
dt = 1.0/freq # 時間間隔
rcff = dfc/1000 # 方便看; 後來改印 dfc 整數; 阻尼係素由 dfc * scaleDFFA 決定
scaleDFFA = 0.0005
dfFactor = 1 - scaleDFFA * dfc # 真正用這乘, dfFactor 相當於恢復係素
# dfFactor = 0.975 # 1.0 表示沒有空氣阻力
g = 9.8 # 重力加速度
Tpw = 30 # 蛇擺的週期
Tmax = Tpw / N # 週期最長的單擺擺動週期
Lmax = Tmax**2 * g / (4 * pi**2) # 週期最長的單擺擺長
width = Lmax # 將畫面邊長設定為最長的單擺擺長
m = 1 # 小球質量
size = width / (2 * num) # 小球半徑; # 但之後若 num 改變也不更改 size
theta000 = radians(angle)# 起始擺角, 用 radians 將 deg 單位換成 rad 弳度量
theta0 = theta000
i = 0 # 小球經過週期次數
t = 0 # 時間
pendulums = [ ]
def changeDfc(gg):
global dfc, rcff, dfFactor
dfc = int(gg)
if dfc > 999: dfc = 999 # 避免立即停掉
if dfc < 0: dfc = 0 # 防呆
rcff = dfc/1000 # 方便看; 不過後來這 rcff 已經沒用!
dfFactor = 1 - scaleDFFA * dfc # 真正用這乘, 讓擺角越來越小 (模仿空氣阻力)
# Pendulum class in pen.py
# Filename: pen.py ## coding: utf-8
# Usage: from pen import *
from vpython import * ## IoTtalk VPDA 這句要註解掉!
## pi is defined in vpython
# 新增類別 Pendulum, 輸入週期T, 懸掛位置loc, 編號idx, 自動產生小球及對應的繩子
# 設定方法 update, 輸入經過的時間dt, 更新單擺狀態
class Pendulum:
__theta0 = radians(10); ## shared variable; 左邊寫 __ 表示 private 藏起來
__dfFactor = 1; ## 同類別各物件共用; 模仿阻尼係素 (減振係素)
g = 9.8 # public variable, 沒保護; 就是外部可以 Pendulum.g = new_value
m = 1
def getTheta0( self): ## getter ; 因為 __ 開頭是 private, 須提供函數讓外部讀取
return Pendulum.__theta0
def setTheta0(arg): # setter # 讓外部可透過這 函數修改 __theta0
Pendulum.__theta0 = arg
def setDfFactor(arg): ## 注意 __dfFactor 也是藏起來的 shared 變數; 提供 setter 才能改
Pendulum.__dfFactor = arg
def __del__(self): # delete inner components
self.ball.visible = False # 先設為看不見, 再砍了它才有用
self.rope.visible = False
del self.ball # 砍 !!! 先設為看不見, 再砍了它才有用
del self.rope
def __init__(self, T, loc, idx, num, width, size):
self.T = T
self.loc = loc
self.idx = idx
self.width = width
self.L = self.T**2 * Pendulum.g / (4 * pi**2) # pi is in vpython module
self.I = Pendulum.m * self.L**2
self.alpha = 0 ## angular acceleration
self.omega = 0 ## angular velocity
self.theta = Pendulum.__theta0 # 使用 Pendulum 之前 要先把 theta0 設定好 !
self.ball = sphere(pos=vec(self.loc, width/2 -
self.L*cos(Pendulum.__theta0), self.L*sin(Pendulum.__theta0)),
radius=size, color=vec(1 - self.idx/num, 0, self.idx/num))
self.rope = cylinder(pos=vec(self.loc, width/2, 0),
axis=self.ball.pos - vec(self.loc, width/2, 0),
radius=0.1*size, color=color.yellow)
def update(self, dt):
self.dt = dt ## 其實直接用 dt 即可不必記住這
##self.alpha = -Pendulum.m*Pendulum.g*self.ball.pos.z/self.I # angular acceleration
self.alpha = -Pendulum.g/self.L * sin(self.theta) # angular acceleration
pomg = self.omega; ## previous omega ## 之前角速度; 往右 > 0; 往左小於 0
self.omega += self.alpha*self.dt ## angular velocity
if self.idx==0 and pomg * self.omega < 0: ## negative / positive velocity
Pendulum.__theta0 *= Pendulum.__dfFactor ## 由第 0 個負責縮減 theta0
# self.theta += self.omega*self.dt
gg = self.theta + self.omega* dt ## dt 是傳入的參數
if gg > 0 and gg > Pendulum.__theta0: # 模仿阻尼 + 防止因誤差導致越盪越高 !
pass # # for sometimes you want to comment out next line :-)
gg = Pendulum.__theta0
if -gg > 0 and -gg > Pendulum.__theta0:
pass
gg = - Pendulum.__theta0
self.theta = gg ## 限縮下次的擺角
self.ball.pos = vec(self.loc, 0.5*self.width -
self.L*cos(self.theta), self.L*sin(self.theta))
self.rope.axis = self.ball.pos - vec(self.loc, 0.5*self.width, 0)
"""
2. 畫面設定
"""
# 產生動畫視窗、天花板
scene = canvas(title="Pendulum Wave", width=600, height=600, x=0, y=0, background=color.black)
scene.camera.pos = vec(-1.5*width, 0.5*width, width)
scene.camera.axis = vec(1.5*width, -0.5*width, -width)
roof = box(pos=vec(0, (width + size)/2, 0), size=vec(width, size, 0.5*width), color=color.cyan)
timer = label(pos=vec(-0.58*width, 0.77*width, 0), text="t = s", space=50, height=24,
border=4, font="monospace")
para_info = label(pos=vec(0.9*width, 0.95*width, 0), space=30, height=24,
text="單擺的個數 num:\n最長單擺擺動次數 N:", color=color.yellow)
th0_info = label(pos=vec(-0.6*width, 0.65*width, 0), space=30, height=18,
text="theta0: ", color=color.yellow)
#############################################################
def initPen(num ):
global pendulums
# 利用自訂類別 Pendulum 產生 num 個單擺
pendulums = []
for i in range(num):
T = Tpw / (N + i)
ddd=num-1 # 防止除以 0
if ddd < 5: ddd = 5 # prevent from divided by 0 when num == 1
loc = width*(-0.5+(i/(ddd))) ## ddd : at least has 5 NOW
Pendulum.setTheta0(theta0)
Pendulum.setDfFactor(dfFactor)
Pendulum.g = g
Pendulum.m = m
pendulum = Pendulum(T, loc, i , num, width, size)
pendulums.append(pendulum)
def resetGame( num):
global t, theta0, isRunning, pendulums
t = 0
theta0 = theta000 # initial theta
isRunning = False ## 避免我剛好砍了某單擺, 但主 thread 卻想去更新該單擺
#sleep(0.2) # Edutalk VPython 在 main thread 之外不可以用 sleep
for pendulum in pendulums: ## 移除舊的擺球避免亂七八糟!
pendulum.__del__( ) ## 砍掉內部物件 ball, rope ; 強制執行
del pendulum ## delete the object ;發現 Edu VPython 沒自動做 __del__(self)
# import gc # 這個在 Edutalk VPython 不能用 !
# gc.collect() ## 好像IoTtalk VPDA 也不能用?只有單機版可以用 !?
initPen( num) # 重生 num 個 pendulum
isRunning = True # Let it go
playAudio("Go.wav")
#######################################
# 當 Gravity 旋鈕/滑桿 收到新值時 # Ball-throw1 : Angle
def Gravity(data):
global num ## 單擺的個數 num
if data != None:
num = int( data[0] * 5 ) # [0, 10] ==> to [0, 50]
if num < 1: num = 1
resetGame(num)
# 當 Radius 旋鈕/滑桿 收到新值時 # Ball-throw1 : Height
def Radius(data):
global N ## N : 最長單擺擺動次數
if data != None:
N = int( data[0] * 3 ) # [0, 10] ==> to [0, 30]
if N < 3: N = 3
resetGame( num)
# 當 Speed 旋鈕/滑桿 收到新值時 # 先寫起來備用
def Speed(data):
global dfc ## dfc : 阻尼係素 [0, 99]阻力係素 [0, 99]阻力係素 [0, 99]
if data != None:
changeDfc( data[0] ) # do dfc = data[0] # Damping Force coefficient
resetGame(num )
##
ucc = 0
def update_info( ):
global ucc
ucc += 1
ttt = int(freq / 10) # 每秒更新 10 次
if ucc % ttt != 0: return
#content = "t = " + str(int(t)) + " s"
timer.text = "t = {:.1f} s".format(t)
para_info.text = \
"單擺的個數 num = {:d}\n最長單擺擺動次數 N: {:d}\n模擬阻尼係素(0~99): {:d}".format(num, N, dfc)
th0_info.text = "Theta0: {:.5f}".format(theta0)
#rate(6, update_info) # IoTtalk 的 VPython 不能用 (可能版本較舊); # 那就改寫成由 While Loop 不斷叫這個 !
#
def setup( ):
profile = {
'dm_name' : 'Ball-Free-Fall', ## 這在 IoTtalk 上可自己建立 DM 或用 "Ball-throw1"
'df_list' : [Gravity, Radius], # 注意是 df_list; 在 IoTtalk 是 odf_list
}
dai(profile) # connect ro Edutalk ( or IoTtalk server )
update_info( ) ## 調用 更新畫面文字的函數; 然後它自己會用 rate(6, 自己) 重複調用(可單機版不能用)
playAudio("Go.wav") # 播放聲音 "Go"
#
def checkInp( ): ## 單機 Python 版本會用到
pass
initPen(num)
setup( )
isRunning = True
while True:
rate(freq)
while not isRunning: # wait till True
sleep(0.2)
checkInp( )
continue
for pendulum in pendulums:
pendulum.update(dt)
theta0 = Pendulum.getTheta0( ) # read back the theta0
if theta0 <= stopPoint: # angle too small
isRunning = False # 角度太小看不出來阿就不要動囉
continue
t += dt
checkInp( ) # 預留單機版要查看鍵盤輸入
update_info( ) ## ㄍㄥ新畫面文字信息
|
==> 提示關於如何把上述 Edutalk 版蛇擺改為 IoTtalk VPython DA (就是加分題的第(九)項)
(a) 把 LINE 60 註解掉 ; IoTtalk 不必也不可 import vpython
(b) 修改 LINE 205, dm_name 應該用 "Ball-throw1" (注意大小寫, 以下各項也是)
(c) 修改 LINE 206, 它的要用 odf_list 不是 df_list
還有, 內容應是 [Angle, Height, Speed] 以便搭配 Ball-throw1
(d) 修改 LINE 168, Gravity 要改為 Angle
(e) 修改 LINE 176, Radius 要改為 Height
(f) Speed (LINE 184) 已經預先寫好, 不用改 :-)
好了, 這個 .py 檔案可以搭配你的 Flask + Python 網站當作 IoTtalk 的 VPython DA 了!
|
|
** 這邊是單機版 的 pen.py (共三個檔案), 與 之前入門手冊的 (五) DAI2.py 整合,
騙 IoTtalk 說是 Ball-throw1 (其實自己建立一個新 DM 也很簡單 :-)
* Pendulum Wave for IoTtalk as Ball-throw1 -- Python Version
wave.py + DAI543.py + pen.py (含 class Pendulum)
|
|
### pen.py -- 先看 pen.py; 再看 DAI543.py, 最後看 wave.py
|
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
|
## 先看 pen.py -- 新版 (比較遵守 class 類別撰寫慣例)
## This 檔案 pen.py uses coding: utf-8 ;;; modified by tsaiwn@cs.nctu.edu.tw
## #Filename: pen.py ## coding: utf-8
#Author: originally by 中和高中王一哲老師; modified by tsaiwn@cs.nctu.edu.tw
from vpython import *
## pi is defined in vpython
# 新增類別 Pendulum, 輸入週期T, 懸掛位置loc, 編號idx, 自動產生小球及對應的繩子
# 設定方法 update, 輸入經過的時間dt, 更新單擺狀態
class Pendulum:
__theta0 = 0; ## private shared variable
__dfFactor = 1; ## 同類別各物件共用; 模仿阻尼係素 (減振係素)
g = 9.8 # public variable, 沒保護; 就是外部可以 Pendulum.g = new_value
m = 1
def getTheta0( ): # getter
return Pendulum.__theta0
def setTheta0(arg): # setter
Pendulum.__theta0 = arg
def setDfFactor(arg):
Pendulum.__dfFactor = arg
def __del__(self): # delete inner components
self.ball.visible = False # 先設為看不見, 再砍了它才有用
self.rope.visible = False
del self.ball # 砍 !!! 先設為看不見, 再砍了它才有用
del self.rope
def __init__(self, T, loc, idx, num, width, size):
self.T = T
self.loc = loc
self.idx = idx
self.width = width
self.L = self.T**2 * Pendulum.g / (4 * pi**2) # pi is in vpython module
self.I = Pendulum.m * self.L**2
self.alpha = 0 ## angular acceleration
self.omega = 0 ## angular velocity
self.theta = Pendulum.__theta0 # 使用 Pendulum 之前 要先把 theta0 設定好 !
self.ball = sphere(pos=vec(self.loc, width/2 -
self.L*cos(Pendulum.__theta0), self.L*sin(Pendulum.__theta0)),
radius=size, color=vec(1 - self.idx/num, 0, self.idx/num))
self.rope = cylinder(pos=vec(self.loc, width/2, 0),
axis=self.ball.pos - vec(self.loc, width/2, 0),
radius=0.1*size, color=color.yellow)
def update(self, dt):
##self.alpha = -Pendulum.m*Pendulum.g*self.ball.pos.z/self.I # angular acceleration
self.alpha = -Pendulum.g/self.L * sin(self.theta) # angular acceleration
pomg = self.omega; ## previous omega## 之前速度; 往右 > 0; 往左小於 0
self.omega += self.alpha* dt ## angular velocity
if self.idx==0 and pomg * self.omega < 0: ## negative / positive velocity
Pendulum.__theta0 *= Pendulum.__dfFactor ## 由第 0 個負責縮減 theta0
# self.theta += self.omega* dt
gg = self.theta + self.omega* dt ## dt 是傳入的參數
if gg > 0 and gg > Pendulum.__theta0: # 防止因誤差導致越盪越高 !
pass ## for sometimes you want to comment out next line
gg = Pendulum.__theta0
if -gg > 0 and -gg > Pendulum.__theta0:
pass
gg = - Pendulum.__theta0
self.theta = gg
self.ball.pos = vec(self.loc, 0.5*self.width -
self.L*cos(self.theta), self.L*sin(self.theta))
self.rope.axis = self.ball.pos - vec(self.loc, 0.5*self.width, 0)
|
### End of pen.py
** 這是 DAI543.py -- 從原先 DAI2.py 改來; 獨立執行仍為 Dummy_Device
|
### DAI543.py -- 前面已經看過 pen.py; 再看這 DAI543.py, 最後看 wave.py
|
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
|
## 這是從入門手冊 (五) Dummy_device 的 DAI2.py 改來
## This 檔案 DAI543.py uses coding: utf-8 ;;; modified by tsaiwn@cs.nctu.edu.tw
## #Usage: import DAI543 as dai
import time, DAN, requests, random
import threading, sys # for using a Thread to read keyboard INPUT
def setServerURL(url):
global ServerURL
ServerURL = url
def setMac(mac):
global mac_addr, Reg_addr
Reg_addr = mac
mac_addr = mac
def getDMName( ):
return DAN.profile['dm_name']
def setDMName(name):
DAN.profile['dm_name'] = name
def setDName(name):
global ServerURL
DAN.profile['d_name']= name
def setDFlist(arg):
DAN.profile['df_list'] = arg
def register( ):
DAN.device_registration_with_retry(ServerURL, Reg_addr)
print("dm_name is ", DAN.profile['dm_name']) ;
print("Server is ", ServerURL);
def deregister( ):
DAN.deregister( )
# ServerURL = 'http://Your_server_IP_or_DomainName:9999' #with no secure connection
# ServerURL = 'http://192.168.20.101:9999' #with no secure connection
# 注意你用的 IoTtalk 伺服器網址或 IP
ServerURL = 'https://demo.iottalk.tw' # with SSL secure connection
# ServerURL = 'https://Your_DomainName' #with SSL connection (IP can not be used with https)
Reg_addr = None #if None, Reg_addr = MAC address #(本來在 DAN.py 要這樣做 :-)
# Note that Reg_addr 在以下三句會被換掉! # the mac_addr in DAN.py is NOT used
mac_addr = 'CD8600D38' + str( random.randint(100,999 ) ) # put here for easy to modify :-)
# 若希望每次執行這程式都被認為同一個 Dummy_Device, 要把上列 mac_addr 寫死, 不要用亂數。
Reg_addr = mac_addr # Note that the mac_addr generated in DAN.py always be the same cause using UUID
## DAN.profile['dm_name']='Ball-throw1' # you can change this but should also add the DM in server
## DAN.profile['df_list']=['Angle', "Height", "Speed"] # Check IoTtalk to see what IDF/ODF the DM has
DAN.profile['dm_name']='Dummy_Device' # you can change this but should also add the DM in server
DAN.profile['df_list']=['Dummy_Sensor', 'Dummy_Control'] # Check IoTtalk to see what IDF/ODF the DM has
DAN.profile['d_name']= "TWN_D."+ str( random.randint(100,999 ) ) +"_"+ DAN.profile['dm_name'] # None
# global gotInput, theInput, allDead ## 主程式不必宣告 globel, 但寫了也 OK
gotInput=False
theInput="haha"
allDead=False
def doRead( ): ## 負責讀取鍵盤
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 Exception: ## KeyboardInterrupt:
allDead = True
print("\n\nDeregister " + DAN.profile['d_name'] + " !!!\n", flush=True)
DAN.deregister()
sys.stdout = sys.__stdout__
print(" Thread say Bye bye ---------------", flush=True)
sys.exit( ); ## break # raise # ?
if theInput =='quit' or theInput == "exit":
allDead = True
else:
print("Will send " + theInput, end=" , ")
gotInput=True
if allDead: break;
try:
deregister( )
print("\n\nDeregister " + DAN.profile['d_name'] + " !!!\n", flush=True)
except:
pass
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)
threadx.daemon = True
############################################################################
allDead = False
def doPull( ):
global allDead # 注意, 只要會去改 allDead 就要設 global 否則白改
while True:
data = None
if allDead: break;
for i in range(len(odfs)): # 看有多少個 ODF 都要 pull
#print("try Pull ", odfs[i], funs[i])
try:
data = DAN.pull(odfs[i]) # Ball-throw1 有 "Angle", "Height", "Speed"
pass
except:
pass ## 預留萬一想把以下 print 註解掉 :-)
print("? DAN pull error: " + odfs[i])
if data != None:
if not type(data) is list: # 萬一不是 list,
data = [data] # .. 阿就改為 list
funs[i](data) # Call Back; 叫用對應的函數 Angle, Height, Speed (在 wave.py 內)
try:
time.sleep(0.2)
except:
allDead = True # 這應該是被敲了 CTRL_C
break
try:
time.sleep(0.2)
except:
break
print("Leave while Loop")
try:
sleep(0.2)
except:
pass
try:
print("Try to de-register...")
deregister( ) # DAN.deregister( )
return
except:
print("? deeeee Reg...")
pass
## thread to do Pull jobs
thready = threading.Thread(target=doPull) # LINE 88
thready.daemon = True
dm_name="xx"
odfs=[ ]
funs=[ ]
## Use: dai.dai(profile)
def dai(prof):
global dm_name, funs, odfs
dm_name = prof["dm_name"] # Device Model 的名稱
funs = prof["df_list"] # VPython 的 df_list 其實是函數(函式)名稱
odfs = [ str(x).split()[1] for x in funs] # 取得 ODF 字串
setDMName(dm_name) # see LINE 17
setDFlist(odfs) # see LINE 22
## Use dai.start( ) function to (1)register to IoTtalk; (2) start two threads
def start( ):
register( ) # to IoTtalk server; DAN.register( )
time.sleep(0.1)
threadx.start() ## threadx 負責讀取鍵盤
thready.start() ## thready 負責 pull 網路: doPull( )
## print("Thread started !")
def main( ): # as Dummy_Device
global gotInput, theInput
DAN.device_registration_with_retry(ServerURL, Reg_addr)
print("dm_name is ", DAN.profile['dm_name']) ; print("Server is ", ServerURL);
while True:
try:
#Pull data from a device feature called "Dummy_Control"
value1=DAN.pull('Dummy_Control')
if value1 != None:
print (value1[0])
#Push data to a device feature called "Dummy_Sensor"
if gotInput:
if theInput =='quit' or theInput=="exit":
allDead = True # 多通知也 OK
break; # sys.exit( );
#value2=random.uniform(1, 10)
"""try:
value2=float( theInput )
except:
value2=0 """ ## 註解掉了喔
if(allDead): break;
# DAN.push ('Dummy_Sensor', value2, value2) # 試這:
DAN.push('Dummy_Sensor', theInput) ## +"\r\n2. bbbbbb"
gotInput=False # so that you can input again
except Exception as e:
print(e)
if(allDead): break;
if str(e).find('mac_addr not found:') != -1:
print('Reg_addr is not found. Try to re-register...')
DAN.device_registration_with_retry(ServerURL, Reg_addr)
else:
print('Connection failed due to unknow reasons.')
time.sleep(1)
try:
time.sleep(0.2)
except KeyboardInterrupt:
break
try:
deregister( )
except:
pass
if __name__ == "__main__":
print("Do for Dummy_Device")
threadx.start() ## threadx 負責讀取鍵盤
main( )
|
### End of DAI543.py
|
|
** 這邊是單機版 wave.py, 把 之前入門手冊的 (五) DAI2.py 整合,
騙 IoTtalk 說是 Ball-throw1 (其實自己建立一個新 DM 也很簡單 :-)
* Pendulum Wave for IoTtalk as Ball-throw1 -- Python Version
wave.py + DAI543.py + pen.py (含 class Pendulum)
|
|
### wave.py -- 最後看 wave.py; 前面是 DAI543.py, 更前面 pen.py
|
-
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
|
## 這是 wave.py -- 主要負責整個蛇擺 (Pendulum Wave)動畫與流程
## ##This 檔案 wave.py uses coding: utf-8 ;;; modified by tsaiwn@cs.nctu.edu.tw
## #** 王一哲老師的蛇擺程式整合 Dummy Device 的 DAI2, 變單機版騙 IoTtalk 是 Ball-throw1
"""
VPython教學進階內容: 蛇擺 pendulum wave, 使用 class
Ver. 1: 2018/7/12
Ver. 2: 2018/7/14 加上計時器
作者: 王一哲
"""
from vpython import *
## when run in GlowScript or /withOUT EDUtalk, enable next 2 functions
def playAudio(gg): return ## for run without Edutalk/IoTtalk
def dai(gg): return ## for run without Edutalk/IoTtalk
import time, requests, random
import threading, sys # for using a Thread to read keyboard INPUT
import DAI543 as dai ## 這樣之後可以寫 dai.gotInput, dai.start( ), ... 等等
ServerURL = 'https://demo.iottalk.tw' # with SSL secure connection
mac_addr = 'CD8600D38' + str( random.randint(100,999 ) ) # put here for easy to modify :-)
myName = "TWN_D." + str( random.randint(100,999 ) )
"""
1. 參數設定, 設定變數及初始值
"""
num = 35 # 單擺的個數
N = 15 # 週期最長的單擺在蛇擺的1個週期內擺動的次數
dfc = 0 # [0, 99] # 阻力係素 ; 設 0 就當作真空不會停止
## 以上當作可改的參數; 以下兩列也可改看看
angle = 30 # 30 degree
Tpw = 30 # 蛇擺的週期
stopPoint = radians(0.1) # stop when smaller than 0.1 degree
isRunning = True
freq = 1000
dt = 1.0/freq # 時間間隔
scaleDFFA = 0.0005
dfFactor = 1 - scaleDFFA * dfc ## 真正用這乘, 相當於恢復係素; 用來讓擺角越來越小
# dfFactor = 0.975 # 1.0 表示沒有空氣阻力
g = 9.8 # 重力加速度
Tmax = Tpw / N # 週期最長的單擺擺動週期
Lmax = Tmax**2 * g / (4 * pi**2) # 週期最長的單擺擺長
width = Lmax # 將畫面邊長設定為最長的單擺擺長
m = 1 # 小球質量
size = width / (2 * num) # 小球半徑
theta000 = radians(angle)# 起始擺角, 用 radians 將單位換成 rad
theta0 = theta000
i = 0 # 小球經過週期次數
t = 0 # 時間
pendulums = [ ]
def changeDfc(gg):
global dfc, dfFactor
dfc = int(gg)
if dfc > 999: dfc = 999 # 避免力急停掉
if dfc < 0: dfc = 0 # 防呆
dfFactor = 1 - scaleDFFA * dfc # 真正用這乘, Damping Force
"""
2. 畫面設定
"""
# 產生動畫視窗、天花板
scene = canvas(title="Pendulum Wave", width=600, height=600, x=0, y=0, background=color.black)
scene.camera.pos = vec(-1.5*width, 0.5*width, width)
scene.camera.axis = vec(1.5*width, -0.5*width, -width)
roof = box(pos=vec(0, (width + size)/2, 0), size=vec(width, size, 0.5*width), color=color.cyan)
timer = label(pos=vec(-0.58*width, 0.77*width, 0), text="t = s", space=50, height=24,
border=4, font="monospace")
para_info = label(pos=vec(0.9*width, 0.95*width, 0), space=30, height=24,
text="單擺的個數 num:\n最長單擺擺動次數 N:", color=color.yellow)
th0_info = label(pos=vec(-0.6*width, 0.65*width, 0), space=30, height=18,
text="theta0: ", color=color.yellow)
#############################################################
def initPen(num ):
global pendulums
# 利用自訂類別 Pendulum 產生 num 個單擺
pendulums = []
for i in range(num):
T = Tpw / (N + i)
ddd=num
if ddd < 6: ddd = 6 # prevent from divided by 0 when num == 1
loc = width*(-0.5+(i/(ddd-1))) ## ddd-1 : at least has 5 NOW
Pendulum.setTheta0(theta000)
Pendulum.setDfFactor(dfFactor)
Pendulum.g = g
Pendulum.pi = pi
Pendulum.m = m
pendulum = Pendulum(T, loc, i , num, width, size)
pendulums.append(pendulum)
def resetGame( num):
global t, theta0, isRunning, pendulums
t = 0
isRunning = False
sleep(0.2) # VPython 的 sleep
for pendulum in pendulums:
## pendulum.delBR( ) ## 砍掉內部物件 ball, rope
del pendulum ## delete the object
#import gc
#gc.collect()
theta0 = theta000 # initial theta
initPen( num)
isRunning = True
playAudio("Go.wav")
#######################################
# 當 Gravity 旋鈕/滑桿 收到新值時
def Angle(data):
global num ## 單擺的個數 num
if data != None:
num = int( data[0] * 5 ) # [0, 10] ==> to [0, 50]
if num < 1: num = 1
resetGame(num)
# 當 Radius 旋鈕/滑桿 收到新值時
def Height(data):
global N ## N : 最長單擺擺動次數
if data != None:
N = int( data[0] * 3 ) # [0, 10] ==> to [0, 30]
if N < 3: N = 3
resetGame( num)
# 當 Speed 旋鈕/滑桿 收到新值時
def Speed(data):
global dfc ## dfc : 阻力係素 [0, 99]阻力係素 [0, 99]阻力係素 [0, 99]
if data != None:
changeDfc( data[0] ) # do dfc = data[0] ; 0..99 or 0..999
resetGame(num )
##
ucc = 0
def update_info( ):
global ucc
ucc += 1
ttt = int(freq / 8) ## 每秒更新 8 次很快了
if ttt > ucc: return
ucc = 0 # reset ucc counter
#content = "t = " + str(int(t)) + " s"
timer.text = "t = {:.1f} s".format(t) ## I/O 相對於運算是超級慢速的!
para_info.text = \
"單擺的個數 num = {:d}\n最長單擺擺動次數 N: {:d}\n模擬阻尼係素(0~999): {:d}".format(num, N, dfc)
th0_info.text = "Theta0: {:.5f}".format(theta0) # theta0 是目前擺角(弳度量)
#rate(6, update_info) # 這句單機版無法執行
## use dai.gotInput, dai.theInput, dai.allDead
## 查看 DAI (DAI543.py) 內的 doRead( ) 有幫忙讀到資料放 dai.theInput 裡面嗎
def checkInp( ): ## 一列最多會輸入三項資料 ; 但輸入一項, 兩項, 也都可以
if not dai.gotInput: return #沒資料就快快返回
if dai.theInput =='quit' or dai.theInput == "exit":
dai.allDead = True ## 告訴大家收工囉 :-)
return
xx = dai.theInput + ", -1, -1,-1" # 最多三個輸入值
xx = xx.replace(" ", ",") # 空白轉為逗號
xx = xx.split(",") # 依據 逗號 切開
xx = [x for x in xx if x!=""] ## 去掉空字串
xx,yy,zz, *_ = xx ## 取出前三個 (仍為字串)
try:
#xx = float(dai.theInput) # if has ONLY one parameter
#xx, yy = map(float, dai.theInput.split( ) )
xx = float(xx)
yy = float(yy)
zz = float(zz)
except:
print("What? ", dai.theInput)
dai.gotInput = False # 通知小弟我已經拿走資料
return # 輸入的資料剖析失敗只好空手返回
dai.gotInput = False # 通知小弟我已經拿走資料
if(xx >= 0): Angle([xx]) # 如果 負數 則表示沒該項 data
if(yy>=0): Height([yy]) # 假裝 Ball-throw1
if(zz>=0): Speed([zz]) # 叫用 Speed( ) 函數
"""
3. 物體運動部分
"""
import platform
__p = platform.python_version()
_ispython3 = (__p[0] == '3')
if _ispython3:
print("You are Using Python V3"); # Python 3;
else:
print("Python V2 now"); # Python 2.xx
def setup( ):
profile = {
'dm_name' : 'Ball-throw1',
'df_list' : [Angle, Height, Speed], # 雖然在 IoTtalk Vpython DA 是 odf_list; 但這單機版我們自己處理不用改!
}
dai.dai(profile) # connect to IoTtalk server (參看 DAI543.py )
update_info( ) ## 更新畫面上文字訊息
playAudio("Go.wav")
# Pendulum class in pen.py
from pen import *
initPen(num) # 生出蛇擺(Pendulum wave); 這句必須在場景生出之後做
dai.setServerURL(ServerURL) ## 在 DAI543.py 內
dai.setMac(mac_addr) ## 設定要註冊的 mac_address
setup( ) # will do dai.dai(profile)
dai.setDName(myName + "_" + dai.getDMName( ) ) # 必須 setup( ) 之後才有 dm_name
dai.start( ) # 註冊 IoTtalk, 啟動 兩個 Thread; see DAI543.py
# thready.start() ## 不要現在啟動執行緒 thready, will do by dai.start( )
isRunning = True
while True:
rate(freq)
if(dai.allDead): break; ## 那邊說要收工嗎?
while not isRunning: # wait till True
sleep(0.2)
checkInp( ) # # 是否有鍵盤輸入? 有機會被激活 !
continue ## 往上繞回最接近的 while 或 for
for pendulum in pendulums:
pendulum.update(dt)
theta0 = Pendulum.getTheta0( ) # read back the theta0
if not (theta0 > stopPoint): # angle too small
isRunning = False #擺動角度小到看不太出來囉
t += dt
checkInp( ) ## 是否有鍵盤輸入
update_info( ) ## 更新畫面上文字資訊
if(dai.allDead): break;
try:
sleep(0) ## 把 CPU 讓給別的執行緒 thread 一下
except:
pass
print("Bye!")
dai.allDead = True
try:
dai.deregister()
print("OK deregister() success!")
except Exception as e:
print("===")
print("Bye ! --------------", flush=True)
sys.exit( );
|
### End of wave.py
; 會用到 DAI543.py, pen.py, DAN.py, csmapi.py
(全部放同一個目錄)
python wave.py
** 如何執行單機版純 Python 版本的蛇擺 (假裝成 Ball-throw1)
(1)先抓 Dummy_device, 解壓縮保留 DAN.py, csmapi.py
(2)抓入門手冊(五)的 DAI2.py, 先測試 (或不抓 DAI2.py 也 OK)
(3)建立 wave.py, pen.py, DAI543.py
(4) 若執行 python DAI543.py 則是 Dummy_device 與 DAI2.py 相同
(5) 執行 python wave.py
(6) 可以用 IoTtalk 的 project 綁定後測試, 也可從鍵盤輸入改變參數
輸入時不改變的項目就輸入 -1
|
|
|
|
|
使用王一哲老師原版 class Pendulum 的 Edutalk 版本 (放 Ball-Free-Fall 自由落體)
|
-
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
|
這是 從 王一哲老師 的 蛇擺程式 抄來, 稍作修改;
配合 自由落體實驗, 以便在Edutalk用手機控制改 num 幾個單擺 或 改 N 最長的每週期擺幾次
(當然手機掃瞄QRcode控制過後, 也可用電腦上遙控器控制)
#This 檔案 uses coding: utf-8 ;;; modified by tsaiwn@cs.nctu.edu.tw
## #** 王一哲老師的蛇擺放入 Edutalk 的自由落體實驗, 可用手機控制參數 num 和 N
"""
VPython教學進階內容: 蛇擺 pendulum wave, 使用 class
Ver. 1: 2018/7/12
Ver. 2: 2018/7/14 加上計時器
作者: 王一哲
"""
from vpython import *
## when run in GlowScript or /withOUT EDUtalk, enable next 2 functions
# def playAudio(gg): return ## for run without Edutalk/IoTtalk
# def dai(gg): return ## for run without Edutalk/IoTtalk
"""
1. 參數設定, 設定變數及初始值
"""
num = 25 # 單擺的個數
N = 15 # 週期最長的單擺在蛇擺的1個週期內擺動的次數
rc = 10 # [0, 999] # 模仿阻尼係素 ; 設 0 就當作真空不會停止;; Damping Force Factor
## 以上當作可改的參數, 配合 DM in Edutalk and/or IoTtalk
Tpw = 30 # 蛇擺的週期
angle = 30 # 起始擺角, Degree, 度 ; 也可以改變 Tpw 和 angle 看看
##
stopPoint = radians(0.1) # stop when smaller than 0.1 degree
isRunning = False
freq = 1000
dt = 1.0/freq # 時間間隔
scaleRc = 0.0005 # 調整 rc 模擬阻尼係素
dfFactor = 1 - scaleRc * rc # 真正用這乘, 相當於恢復係素 ; damping force Factor
# dfFactor = 0.9975 # 1.0 表示沒有空氣阻力
theta000 = radians(angle) # 起始擺角, 用 radians 將單位換成 rad
theta0 = theta000 # theta0 會被 Damping force 改變
g = 9.8 # 重力加速度
Tmax = Tpw / N # 週期最長的單擺擺動週期
Lmax = Tmax**2 * g / (4 * pi**2) # 週期最長的單擺擺長
width = Lmax # 將畫面邊長設定為最長的單擺擺長
m = 1 # 小球質量
size = width / (2 * num) # 小球半徑
i = 0 # 小球經過週期次數
t = 0 # 時間
def changeRc(gg):
global rc, dfFactor
rc = int(gg)
if rc > 999: rc = 999 # 避免太快停掉
if rc < 0: rc = 0 # 防呆
dfFactor = 1 - scaleRc * rc # 真正用這乘, 相當於恢復係素
"""
2. 畫面設定
"""
# 產生動畫視窗、天花板
scene = canvas(title="Pendulum Wave", width=600, height=600, x=0, y=0, background=color.black)
scene.camera.pos = vec(-1.5*width, 0.5*width, width)
scene.camera.axis = vec(1.5*width, -0.5*width, -width)
roof = box(pos=vec(0, (width + size)/2, 0), size=vec(width, size, 0.5*width), color=color.cyan)
timer = label(pos=vec(-0.58*width, 0.77*width, 0), text="t = s", space=50, height=24,
border=4, font="monospace")
para_info = label(pos=vec(0.9*width, 0.95*width, 0), space=30, height=24,
text="單擺的個數 num:\n最長單擺擺動次數 N:", color=color.yellow)
th0_info = label(pos=vec(-0.6*width, 0.65*width, 0), space=30, height=18,
text="theta0: ", color=color.yellow)
# 新增類別 Pendulum, 輸入週期T, 懸掛位置loc, 編號idx, 自動產生小球及對應的繩子
# 設定方法 update, 輸入經過的時間dt, 更新單擺狀態
class Pendulum:
def delBR(self): # delete inner components
self.ball.visible = False
self.rope.visible = False
del self.ball
del self.rope
def __init__(self, T, loc, idx):
self.T = T
self.loc = loc
self.idx = idx
self.L = self.T**2 * g / (4 * pi**2)
self.I = m * self.L**2
self.alpha = 0
self.omega = 0
self.theta = theta0
self.ball = sphere(pos=vec(self.loc, width/2 - self.L*cos(theta0), self.L*sin(theta0)),
radius=size, color=vec(1 - self.idx/num, 0, self.idx/num))
self.rope = cylinder(pos=vec(self.loc, width/2, 0), axis=self.ball.pos -
vec(self.loc, width/2, 0),
radius=0.1*size, color=color.yellow)
def update(self, dt):
global theta0, isRunning
if not isRunning:
return ## 防呆, 因此時可能 pendulum 物件已經被砍了正要重生
self.dt = dt
## self.alpha = -m*g*self.ball.pos.z/self.I # angular acceleration
self.alpha = -g/self.L * sin(self.theta) # angular acceleration
pomg = self.omega; # previous omega## 之前速度; 往右 > 0; 往左小於 0
self.omega += self.alpha*self.dt ## angular velocity
if self.idx==0 and pomg * self.omega < 0: # velocity changing positive / negative
theta0 *= dfFactor # #模擬受到空氣阻力 (阻尼減振)
# self.theta += self.omega*self.dt
gg = self.theta + self.omega*self.dt
if gg > 0 and gg >= theta0: # 模擬阻尼減振, 並防止因誤差導致越盪越高 !
pass # # 預留萬一想把以下該列註解掉 :-)
gg = theta0
if -gg > 0 and -gg >= theta0:
pass
gg = -theta0
self.theta = gg
self.ball.pos = vec(self.loc, 0.5*width - self.L*cos(self.theta), self.L*sin(self.theta))
self.rope.axis = self.ball.pos - vec(self.loc, 0.5*width, 0)
if theta0 < stopPoint: isRunning = False
def initPens( ):
global pendulums
# 利用自訂類別 Pendulum 產生 num 個單擺
pendulums = []
for i in range(num):
T = Tpw / (N + i)
ddd=num
if ddd < 6: ddd = 6 # prevent from divided by 0 when num == 1
loc = width*(-0.5+(i/(ddd-1))) ## ddd-1 : at least has 5 NOW
pendulum = Pendulum(T, loc, i)
pendulums.append(pendulum)
def resetGame( ):
global t, theta0, isRunning
isRunning = False
## Edutalk VPython 在 main thread 之外 不能用 sleep( ) ???
# sleep(0.3) # let main thread do something else
for pendulum in pendulums:
pendulum.delBR( ) ## 砍掉內部物件 ball, rope; VPython 不會立即做 __del__(self)
## 主要因為根據手冊, __del__(self) 要等到記憶體被回收才會執行 !
del pendulum ## delete the object
#import gc # Edutalk VPython 不能用
#gc.collect()
t = 0
theta0 = theta000 # initial theta
initPens( ) # generate all pendulum and put them in pendulums
isRunning = True
playAudio("Go.wav")
# 當 Gravity 旋鈕/滑桿 收到新值時 # 參數改變
def Gravity(data):
global num, isRunning ## 單擺的個數 num
if data != None:
num = int( data[0] * 5 ) # [0, 10] ==> to [0, 50]
if num < 1: num = 1
resetGame( )
# 當 Radius 旋鈕/滑桿 收到新值時 # 參數改變
def Radius(data):
global N, isRunning ## N : 最長單擺擺動次數 (一個單擺週期內)
if data != None:
N = int( data[0] * 3 ) # [0, 10] ==> to [0, 30]
if N < 3: N = 3
resetGame( )
# 當 Speed 旋鈕/滑桿 收到新值時 # 參數改變 (先寫好備用 :-)
def Speed(data):
global rc, isRunning ## rc : 模仿阻尼係素 [0, 999]
if data != None:
changeRc( data[0] ) # do rc = data[0]
resetGame( )
##
upc = 0
def update_info( ):
global upc # update counter
upc += 1
if upc < int(freq/6) : return # 每秒更新 6 次
upc = 0 # reset counter
#content = "t = " + str(int(t)) + " s"
timer.text = "t = {:.1f} s".format(t)
para_info.text = \
"單擺的個數 num = {:d}\n最長單擺擺動次數 N: {:d}\n模擬阻尼係素(0~99): {:d}".format(num, N, rc)
th0_info.text = "Theta0: {:.5f}".format(theta0)
# rate(6, update_info) # 每秒更新 6 次# 單機版不能用有兩個參數的 rate( )
#
def setup( ):
profile = {
'dm_name' : 'Ball-Free-Fall',
'df_list' : [Gravity, Radius], # 注意是 df_list; 在 IoTtalk 是 odf_list
}
dai(profile) ## connect ro Edutalk ( or IoTtalk server )
update_info( )
playAudio("Go.wav")
### ## 以下這 checkInput( ) 函數只是先為單機版準備, 現在沒用; 也還沒寫完:-)
gotInput = False
theInput = "xxx"
allDead = False
def checkInput( ):
global gotInput, allDead
if not gotInput:
return ## usually do read by other thread
if theInput =='quit' or theInput == "exit":
allDead = True
return
# check keyboard input # 單機版在這裡應該檢查輸入的 theInput 看要做啥
gotInput = False # indicate we took theInput and handled it
# check DAN.pull ? or use other thread to do so
"""
3. 物體運動部分
"""
initPens( )
setup( )
## # Start your other threads here !
isRunning = True
while True:
rate(freq) # # 相當於 sleep(1/freq)
while not isRunning: # wait till True
checkInput( ) # to see if we got input
sleep(0.2) # 這是 VPython 的 sleep( )
continue # go back to while not isRunning
for pendulum in pendulums:
pendulum.update(dt)
t += dt
update_info( )
checkInput( ) # to see if we got input
if allDead: break;
|
|