Pendulum Wave pW蛇擺放入自由落體實驗(Edutalk3) 並在 IoTtalk 假裝成 Ball-trow1    
  VPython DA Pendulum wave(蛇擺) from Edutalk1       網頁上挑選顏色複製#十六進位代碼       Color picker    
* 請注意目前 IoTtalk 伺服器 上的 Ball-throw1 可接受三個參數
           === ==>還沒用過 IoTtalk 物聯網平台請看這 12分鐘影片
** EduTalk 總部在交大  
👉👉改好的 Pendulum Wave 蛇擺(Edutalk版) 也可 改一些在 IoTtalk假裝 Ball-throw1(改自中和高中王一哲老師程式)
 自己要建立新 DM (Device Model)請看這影片  以及 DFM -- Device Feature Management 影片
** GlowScript 看蛇擺   觀賞蛇擺
** 更多關於 GlowScript 與 VPython 請點這
👉 點這看 IoTtalk 物聯網平台(交大林一平教授團隊研發)入門手冊 iottalk.vip/000/  
👉 Python 還不太會 或要看 Flask 入門的可點這 到 https://iottalk.vip/6/
   (*看 w.py 範例   *看 w2.py 範例   *看 w3.py 範例-用到 template )
** 如何做點了才開啟VPDA網頁 -- Homepage(首頁) for ALL VPython Device Applications  
      (解決 Google 瀏覽器 『使用者沒跟網頁互動過則不給自動播放影音』 的規定 )
** 精簡版 Flask + Python + IoTtalk DA  ** VPython 版的 Dummy_Device (ODF only)
** 爬蟲 爬爬爬 抓取 CWB 氣象局資料 做微氣象站 (as Dummy_Device)  -- 有影片
Turtle Graphics 烏龜繪圖 弄成 IoTtalk DA 假裝 Ball-throw1  更多Turtle 繪圖範例
 
  * 「刻意練習」是一種專注、一致並且以目標為導向的訓練,它重質量而不是數量。
GlowScript.org 上 王一哲老師的蛇擺   蛇擺教材 點這觀賞並看解說
            以及 王一哲老師的單擺教材(含不考慮與考慮有空氣阻力)
建中曾靖夫老師的蛇擺教材
Simulation of Pendulum: VPython Tutorial 3 (Visual Python)                
Damping Force(阻尼力;減振力)(International Edition University Physics, Book.1984)
*** 提醒在以下 Edutalk 版蛇擺結束後面 ..
          .. 有提示如何改為可搭配 IoTtalk 的 VPython DA
(就是由 Javascript 去解譯 Vpython 程式碼)
*王一哲老師的蛇擺(許多單擺)教材https://hackmd.io/@yizhewang/SyEWFOEmm
* 以下改自王一哲老師的蛇擺程式碼可丟入 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;
* 以下也放王一哲老師蛇擺程式方便參考: (另外, 點這可以看王一哲老師的單擺 Pendulum 教材 )
這是王一哲老師原版 Pendulum wave 蛇擺 版本 (VPython進階教材)
  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

##This 檔案 uses coding: utf-8 ;;; ** 王一哲老師的蛇擺原始程式
"""
 VPython教學進階內容: 蛇擺 pendulum wave, 使用 class
 Ver. 1: 2018/7/12
 Ver. 2: 2018/7/14 加上計時器
 作者: 王一哲
"""
from vpython import *

"""
 1. 參數設定, 設定變數及初始值
"""
g = 9.8             # 重力加速度
Tpw = 30            # 蛇擺的週期
N = 15              # 週期最長的單擺在蛇擺的1個週期內擺動的次數
Tmax = Tpw / N      # 週期最長的單擺擺動週期
Lmax = Tmax**2 * g / (4 * pi**2)   # 週期最長的單擺擺長 
num = 20            # 單擺的個數
width = Lmax        # 將畫面邊長設定為最長的單擺擺長
m = 1               # 小球質量
size = width / (2 * num)           # 小球半徑
theta0 = radians(30)# 起始擺角, 用 radians 將單位換成 rad
i = 0               # 小球經過週期次數
t = 0               # 時間
dt = 0.001          # 時間間隔

"""
 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.9*width, 0.9*width, 0), text="t =  s", space=50, height=24,
              border=4, font="monospace")

# 新增類別 Pendulum, 輸入週期T, 懸掛位置loc, 編號idx, 自動產生小球及對應的繩子
# 設定方法 update, 輸入經過的時間dt, 更新單擺狀態
class Pendulum:
    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):
        self.dt = dt
        self.alpha = -m*g*self.ball.pos.z/self.I
        self.omega += self.alpha*self.dt
        self.theta += self.omega*self.dt
        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)

# 利用自訂類別 Pendulum 產生 num 個單擺
pendulums = []
for i in range(num):
    T = Tpw / (N + i)
    loc = width*(-0.5+(i/(num-1)))
    pendulum = Pendulum(T, loc, i)
    pendulums.append(pendulum)

"""
 3. 物體運動部分
"""
while True:
    rate(500)
    for pendulum in pendulums:
        pendulum.update(dt)
    timer.text = "t = {:.1f} s".format(t)
    t += dt

------------------------------- -------------------------------
* 前面王一哲老師的 Pendulum class 類別裡面用到了外部的變數如 width, g, num 等等,
    其實是很不好的習慣! 因為這樣一來, 它就很難變成一個模組放另一個檔案!!
  ==> 不過, 反正在 Edutalk 和 IoTtalk 的 VPython Device 都只能寫成一個檔案 !
    以下是改寫後的版本: 除了 pi 之外都沒用外部變數了!
    當然, 這樣使用時, 用法也不太一樣 !
  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
#filename: pen.py  (故意) -- pendulum.py -- coding: utf-8  
# Author:  中和高中王一哲老師;  modified by tsaiwn@cs.nctu.edu.tw

from vpython import *      ## IoTtalk 的 VPDA 不必也不能寫這

# 新增類別 Pendulum, 輸入週期T, 懸掛位置loc, 編號idx, 自動產生小球及對應的繩子
# 設定方法 update, 輸入經過的時間dt, 更新單擺狀態
# 使用此 Pendulum 的範例在後面 
class Pendulum:   
    __theta0 = radians(15);   ## private shared variable; default 15 degree
    __dfFactor = 1;    ## 左邊寫 __ 表示 private 藏起來; 要透過函數才可存取
    g = 9.8    ## 像這 g 則可從外部打 Pendulum.g = 33.88 改掉
    m = 1      ## 前面這幾個是 class 變數, 不是 instance 變數, 表示大家共用
    # pi = 3.14159      # pi 在 vpython 模組內; 單機版記得要 from vpython import *
    def getTheta0( ):   #getter
        return Pendulum.__theta0  
    def setTha(arg):    # setter  #應該叫 setTheta0(arg) 比較好
        Pendulum.__theta0 = arg
    def setDfFactor(arg):   # setter for dfFactor   # Damping Force coefficient
        Pendulum.__dfFactor = arg  
    def  delBR(self):   # #delete inner components  #  __del__(self)  
        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)
        self.I = Pendulum.m * self.L**2
        self.alpha = 0
        self.omega = 0
        self.theta = 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;    ## 之前速度; 往右 > 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
        # self.theta += self.omega* dt
        gg = self.theta + self.omega* dt
        if gg > 0 and gg > Pendulum.__theta0:    # 模仿阻尼 + 防止因誤差導致越盪越高 !
            pass
            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)
## 使用 Pendulum 範例;  單機版要 from pen import *      # 匯入 pen.py 全部
## 假設 theta0, dfFactor, stopPoint, T, loc, i, num, width, size 都已經有了
    Pendulum.setTha(theta0)      # 注意函數名稱
    Pendulum.setDfFactor(dfFactor)   # 透過 setter 寫入共用的 dfFactor
    Pendulum.g = g    ## 因為 g 左邊沒 __ 所以不必用 setter 函數就可直接改
    Pendulum.m = m    ## m 和 g 都是類別共用變數; 就是所有單擺這類別的物件共用的變數。
    ## Pendulum.pi = pi     # 用外部的 (在 vpython 內) pi 所以不要自己定義
    pendulum = Pendulum(T, loc, i ,  num, width, size)    ## 生出一個單擺放入變數 pendulum 
    # 必要時可讀取 Pendulum 內的 theta0 如下
    ans = Pendulum.getTheta0( )    # getTheta0( ) 可用來與 stopPoint 比較
  #####  Note that ..
  #####    You have to call pendulum.delBR( ) ; before you do del pendulum 
  ###  delBR( ) 本來是寫成 __del__(self) 但發現在 Edutalk 上 del 物件後不會立即執行
  ### 單機版(純 Python 版本) 執行 del pen 會自動執行 pen 的 __del__
  ### 不過根據 Python 手冊說法是不保證在 del object 之時一定會去做 object 的 __del__ 函數;
  ### 所以可能仍要明確寫出強制執行 __del__ 函數; 或如此例故意改名 delBR(self)
*** 05/15加分題詳細可以 點這看影片說明 (善用影片下方說明與子題連結)
CMoreTV:  https://play.google.com/store/search?q=cmoretv

** demo.iottalk.tw「分享」按鈕- 社交外掛程式 - Facebook for Developers

請按  幫忙分享喔 !
有時候看起來對的其實是錯的!(我很久以前寫的網誌:-) * 不能反攻大陸的真正理由!(也是以前寫的, 其實是給大一學生的習題:-) * 何以1752年9月只有19天 ? 參考 Unix 的 cal 命令 ..(也是大一習題:-) * 為什麼中國(清朝)的 1911年 12 月只有 18 天 ?!(還是以前寫的:-) * [茶餘飯後] 魑魅魍魎 鬼話連篇(大一計概介紹802.11無線網路:-) * [很小的數]何處惹塵埃? 沙漠中是塵埃!(大一計概:-) * [很大的數]再談關於 Google 與 Googol(大一計概:-) * [也是很大的數]佛曰不可說不可說 比一個 googol 還大!(大一計概:-) * 物聯網、IoTtalk、薑黃、免疫力...(兩個月前寫的:-) * 請注意..感冒其實沒有藥     大家要顧好身體, 否則沒機會實現你的創意!我也有粉絲專頁ㄟ:-) 呵呵 (據說 "呵呵" 是北宋蘇東坡的口頭禪 :-)
你不懂的內容農場取妻當娶李子柒? 7 min.   ( 館長也哈她  ) * 台灣百大 Youtuber 收入排行全球10大最高收入华人YouTuber, 2019.1203, by 小楠ngrok 讓外部能夠連到 Intranet 內 Localhost的網站及服務Facebook Messenger Bot 範例 https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/?translation用Python 寫一個 fb Messenger Bot手把手教你搭建 LINE 聊天機器人LineBot+Python,輕鬆建立聊天機器人(北科大程式設計研究社) * https://developers.line.biz/en/services/messaging-api/(LINE 關方網站教學文件) * 防詐騙、防詐騙、防詐騙 南寧詐騙案全台灣有 25萬人被騙 !小心詐騙 !小心詐騙 !小心詐騙 !投資自己,讓今天的自己 比 昨天的自己 更值錢 ! ( 建議每天 15分鐘在 voiceTube.tw ) *
* 提醒 IoTtalk 新手入門應用手冊在這: https://iottalk.vip/000/;
   >=== ==> 還沒用過 IoTtalk 物聯網平台請看這 12分鐘影片 關於 IoTtalk 的使用也請先大概看看IoTtalk的使用手冊:
    http://liny.cs.nctu.edu.tw/#IoTtalk(林一平教授網頁) 
  ==> 點入後在 Document 下方, 有中文版和英文版;   也可點這看更多手冊
 * 如想用NodeMCU-ESP8266連接 IoTtalk, 建議也 先看看 ArduTalk 操作手冊 
  


Network Latency 網路延遲 -- 延遲主要受到光速的限制,它在真空中的速度為299,792,458米/秒,這等同於每公里路徑長度需3.33µs。
Network Latency: How to Test, Measure, and Troubleshoot + Best Network Latency Testing Tools of 2020(2019.1007)
2020 Guide to Network Latency – How to Check, Measure, and Reduce Network Latency(2019.0819)
Network Latency Guide & 10 Best Network Latency Test Tools(2019.0301)
What is Network Latency + Using CDNs to Reduce Latency
What is network latency (and how do you use a latency calculator to calculate throughput)?

測試 VM 網路延遲(Microsoft 微軟, 2019.1029)
Azure 網路來回行程延遲統計資料(Microsoft 微軟, 2020.0310)
遊戲觀點 看 網路延遲(Garena 客服)
Faster Low-Latency 5G Mobile Networks(Anritsu, 安立知股份有限公司)

5G通訊將顛覆網路邊緣(2019.03.14 EDN, Taiwan)
What is the Latency of 5G?(2020.02.02, Verizon, USA)
 
台鐵火車查詢
You are the website counter -th visitors.             The W3C Markup Validation Service
* 點這看如何快速安裝 IoTtalk 系統 (要先花錢取得交大授權才可以喔:-)
* 點這看如何選擇 VPS ?(建議用 Hostwinds.com 或流量小可以先用免費的 AWS EC2)

* PuTTY official site to Download PuTTY   PieTTY project official site   Download Pietty0400b14.zip
 
  整理 by 蔡文能 tsaiwn@cs.nctu.edu.tw     交大資工系     tsaiwn.weebly.com     LINE ID: tsaiwn     FB: fb.me/tsaiwn     幾個英文字讀音   TOP
  TOP   Find your FB ID: https://findmyfbid.com/ if you want to Find your Facebook ID for your fb:admins