* 2020.05.23 加分題..
再多看一眼(多練一下) VPython 的 DA -- 還是加分題 --
* 這次加分題也很簡單, 主要是讓大家更清楚 VPython 的 DA 要如何寫
(物理沒念好也OK, 網路上偷別人的來改即可:-)
* 總共分六部分(當然要越來越多啊不然怎加分呢:-)
Due: 2020/05/31(日)23:59:59 (給大家多一點時間:-)
(1)把上次 Flask+Python 改為 127.0.0.1/111 就會跑 Ball-Throw1;
參考 https://iottalk.vip/b1/
(上次的沒做也沒關係, 我會先花大約三分半鐘 Demo 這部分;
別懷疑, 如不求甚解真的只需三分鐘左右)! !
(2)把會跑 Ball-throw2 的 HTML 檔案放入 templates 目錄,
使得 127.0.0.1/222 會跑 Ball-throw2 (大約兩分鐘即可:-)
(3)到 https://edutalk3.nctu.edu.tw 用你手機控制(a)行星運動,
和(b)自由落體;
這部分要各拍一張照片, 看得到你拿著手機在控制,
共兩張照片 和兩張截圖, 截圖時是用你電腦控制(週日影片會Demo);
(4)把 Ball-throw2 稍微修改丟入 edutalk3, 可以丟入"行星運動",
也可以丟入"自由落體";
不過丟入 "行星運動" 的 code 我已公布在 iottalk.vip/b1 內;
這部分也是和(3)類似拍一張照片和一張截圖!
(5)類似(4), 只是改用 Ball-throw1,
這個改好可配合 edutalk3 的 code 也已經在 iottalk.vip/b1 內;
這部分也是和(3)和(4)類似拍一張照片和一張截圖證明你有練過:-)
(6)偷取 Edutalk3 的一個 .py 程式碼回來自己網站跑
在 edutalk3 內, 若各程式碼改爛了可點畫面的 Reset 恢復 !
把原先自由落體(或行星運動也可)的 .py 程式碼偷回自己電腦稍微修改一點點, 以便配合 IoTtalk server,
讓你的網站 127.0.0.1/333 和 127.0.0.1/static/3388.html 都可讓你用 RemoteControl 控制自由落體(或行星運動也可)!
這題特別規定:
這/333 的HTML檔案不可以放 templates 內,
要和其它 dai.js, dan.js, 自由落體的.py 都一起放 static 內(可建子目錄)
關於在 static 子目錄內各 Vpython DA, 雖然最好再建立子目錄, 但是..
因為我們目前只要跑三個 VPython 的 DA, 所以通通放 static 內不建立子目錄也 OK!
只是要注意各 DA 用到的檔名不要互相搞錯 !
(7)繳交: 當然就是心得 + 圖片 + 必要時對圖片簡單解說); 不必拍攝影片!
Due: 2020/05/31(日)23:59:59
(a) 心得至少 88字, 至多 388字; (加分題要再次多一點心得:-)
(b) 除前面規定照片和截圖,
全部測試過程照片和截圖, 總共至少 15張, 至多 20張
還要再說一次: 卡通小美人魚裡有經典名句:
Life is Full of Choices. You can NOT have it all.
因為是加分題,
其他功課較忙的可以不必繳交這題:-)
建議還是要看看影片以及同學心得! | |
0523加分題的參考影片在這:(善用影片下方的子題時間連結)
https://youtu.be/zDfoH9MnKg4
除了最後一項沒詳細步驟, 其它前五項在影片中都有詳細Demo與解說!
至於(6)最後一項, 其實也很簡單,
就是從Edutalk抓回 .py 檔案後先改為符合 IoTtalk 的 VPython DA 規定!
其實 Edutalk 和 IoTtalk 只差在 profile 用 df_list 或是 odf_list,
當然如不自己建立新 DM 則須配合現存的 DM 用其 dm_name 和 odf_list,
要確定 odf_list 內各 ODF 都有對應的函數(多看幾個範例就會了:-)
然後要把 dai.js 改為用到正確的 .py 檔(也注意 dai.js 大約 LINE5 是否用到正確 IoTtalk server);
還有 HTML 檔案要用到正確的 dai.js 和 dan.js
最後, 網站檔(w80.py)增加規則並利用 return redirect("你的網址") 轉到正確網頁!
如果這樣還是不太懂, 詳細步驟請看這網頁 iottalk.vip/b3 !
但是,
最後(6)我改好的自由落體程式碼(假裝成Ball-throw1),
等這週三(05/27)晚上再公佈於該 /b3 網頁:-)
[0527更新]
補充影片在這:
https://youtu.be/-mluwqDy4f0
|
*** 精簡版 Flask + Python + IoTtalk DA
這次最大不同是:
我們希望除了 VPython 的 .py 檔之外全部共用 !!!
就是說,包括 dai.js, dan.js, csmapi.js, 以及一大堆不是我們寫的程式庫都共用。
這樣跟 IoTtalk server 差別只剩下 它的 VPython Device List 是程式根據目錄內 .py 檔生出的,
但我們的是手動寫在首頁 index.html 裡面 :-)
當然, 要注意 dai.js 兩個地方:
(a) 是否連到正確 IoTtalk server ? 在大約 LINE 5 的地方!
(b) 是否會指到正確的 .py 檔; 在大約 LINE 132 (這句現在不可以寫死!)
(就是說會根據 dai.js 的 LINE2, 但要指到我們自己電腦上 .py 的目錄)
|
(一)啟動虛擬環境(沒必要每次新建隔離環境); 進入網站根目錄, 準備網站所需要的:
* 建立子目錄 static 和 templates (注意有 s)
* 建立 網站檔案 w90.py 如下: (編碼請用 utf-8)
#This 檔案 w90.py uses UTF-8 ## coding=utf-8
myPort = 80
## 請在這檔案的目錄建立兩個子目錄: static 和 templates (注意最後有 s)
from flask import Flask, request
from flask import render_template, redirect, send_file
app =Flask(__name__)
import socket
myhost = socket.gethostname() # myhost 要給 HTML 內的 myname (故意)
myip = socket.gethostbyname(myhost) # 通常名字與 HTML 內相同比較不會搞混 !
@app.route("/")
def webRoot( ):
try: # 注意 index.html 內要用的是 {{myname}} 和 {{myip}}
# return render_template('index.html') ## 如果沒用到 Jinja2 template
return render_template('index.html', myname=myhost, myip=myip)
except:
return "<h1 style='color:red;'>No index.html in templates.</h1>", 404
import os
from flask import send_from_directory
@app.route("/favicon.ico")
def helloGiveIC( ):
try:
return send_from_directory(os.path.join(app.root_path, 'static/img'),
'myicon.png', mimetype='image/vnd.microsoft.icon')
except:
return "No /static/img/myicon.png", 403 #故意用 403 (FORBIDDEN)
# 注意瀏覽器的網址列要用 127.0.0.1 或你的 IP, 不是打 0.0.0.0 喔! 後面打 :port
app.run( '0.0.0.0' , port=myPort, debug=True) # 如 port 是 80 則可不用打 :80
|
(二)建立首頁 Homepage, 在 templates 子目錄內建立 index.html (編碼請用 utf-8)
* 以下這 HTML 首頁內紅色字體是 render_template( ) 會傳入的兩個變數
<!DOCTYPE html> <!--- 編碼用 utf-8; use utf-8, Put this file in /templates/index.html ----->
<html>
<head>
<title>My VP DA on my Flask Web site </title>
</head>
<body style="margin-left:2vw;background:lightcyan;">
<b><font color=blue size=5><font color=red
size=7>My Cyber / VP DA List -- {{myname}} </font>
<img width="128" align="left"
src="https://thumbs.gfycat.com/DistantOrangeIndianpalmsquirrel-size_restricted.gif" >
<! https://thumbs.gfycat.com/DistantOrangeIndianpalmsquirrel-size_restricted.gif >
<! https://i.gifer.com/Z8Dq.gif >
<! https://media.giphy.com/media/3JP7cuyaPh2UiDFlwD/giphy.gif >
<! http://clipart-library.com/images/BTaropdpc.gif >
<! https://i.gifer.com/Z8Dq.gif >
<b><pre><font color=darkred size=6>Cyber Devices List ({{myip}}) </font>
<a target=_blank href="/static/bulb/index.html">我的 Bulb 燈泡</a>
<a target=_blank href="/static/bulb2/index.html">Bulb2 會<font color=red
>自動</font><font color=darkGreen size=6>變色</font>的 Bulb 燈泡</a>
<hr style="border-top: 1px; solid red;width:66vw;" />
<font color=darkGreen size=6
>VPython Devices List</font>
<a target=_blank href="/static/vp/index.html#Ball-throw1">Ball-throw1 丟球, 三個參數</a>
<a target=_blank href="/static/vp/index.html#Ball-throw2">Ball-throw2 丟球, 一個參數</a>
<a target=_blank href="/static/vp/index.html#Free-fall">自由落體-裝Ball-throw1,三參數)</a>
<a target=_blank href="/static/vp/index.html#V_Dummy">Dummy_Device, 要有 V_Dummy.py</a>
<a target=_blank href="/static/vp/index.html#haha">用到檔案 haha.py</a>
<font color=red>*請把 .py 檔案放 /static/vp/py/ 內; 注意 dai.js 要抓這裡面的 .py </font
></font></pre>
<h2 style="color:orange;">====== ====== ====== ====== ===== ======</h2>
<b style="color:blue;font-size:24px;">
<a target=_blank href="https://www.cwb.gov.tw/V8/C/">中央氣象局</a> <br/>
<a target=_blank href="https://www.cwb.gov.tw/V8/C/W/OBS_Station.html?ID=C0D66">新竹市氣象站</a>
<br/>
<a target=_blank href="https://www.cwb.gov.tw/V8/C/W/OBS_Station.html?ID=C0D58">新竹寶山氣象站</a>
<br/>
<a target=_blank href="https://www.cwb.gov.tw/V8/C/W/OBS_Station.html?ID=72D08">新竹五峰氣象站</a>
<br/>
<a target=_blank href="https://www.cwb.gov.tw/V8/C/W/OBS_Station.html?ID=46741">台南氣象站</a>
<div style="background:red;height:3px;width:58vw;"></div>
<div style="background:green;height:3px;width:63vw;margin-top:2px;"></div>
<img width="128" align="left"
src="https://i.pinimg.com/originals/b2/c7/f2/b2c7f2a644b94acf26b05ce6245be311.gif"
> <img width="128" align="absMiddle"
src="https://thumbs.gfycat.com/BasicBothFish-small.gif">
<div style="background:blue;height:3px;width:58vw;margin-top:2px;"></div>
</body>
</html>
|
** 關於使用 Jinja2 Template 入門, 請參考這以前提醒過的 w3.py(用到兩個檔案)
(三)生出(複製來放入) Cyber Devices Bulb 和 Bulb2
(1)把以前做好的 Bulb 整個目錄複製到 /static 並改名 bulb
(2)把 bulb 整個目錄複製一份 改名 bulb2, 修改一下讓它收到三個資料後會自動變色:
*** 依據這邊說明的修改這 /static/bulb/ 內的 ida.js
==> 急性子的同學可先測試 bulb 可從網站叫出再來修改 bulb2 燈泡 :-)
(四)現在已經可以把網站跑起來了..
(1)在 CMD 窗啟動隔離環境(不需要時不要重建隔離環境), 假設是 ggyy
ggyy\Scripts\activate
(2)用 cd 命令進入網站根目錄, 執行 w90.py
python w90.py
(五)可以先測試你的兩個燈泡了, 開啟瀏覽器叫出首頁
127.0.0.1 或打 Localhost 或打你機器的 IP 也可以
然後點各個連結看看 !
(六)到 demo.iottalk.tw 把 Ball-throw1 和 Ball-throw2 偷回來放入自己網站
(1)準備 VPDA 共用的目錄和檔案, 這次我們希望除了 VPython 的 .py 檔之外全部共用 !!!
(a) 在 static 內建立子目錄 vp (等下要放入 index.html)
(b) 在 vp 內再建立子目錄 py (這裡要用來放所有的 VPython .py 檔案)
(2)瀏覽器開啟 demo.iottalk.tw 準備抓 Ball-throw1 必備的四個檔案
(a) 點 Ball-throw1 開啟, 在空白處按滑鼠右鍵選"檢查"
(b) 點 Sources, 點 Pages
-- 把 web_py_index.html 存到 /static/vp/index.html
-- 把 js/dai.js 存到 /static/vp/dai.js
-- 把 js/dan.js 存到 /static/vp/dan.js
(c)點 js/dai.js 研究一下, 準備抓 Ball-throw1.py
在瀏覽器網址列:
--- 把 web_py_index.html# 改成 py/
--- 在最後補上 .py
--- 按下 ENTER 鍵
--- 把 Ball-throw1.py 存到 /static/vp/py/Ball-throw1.py
(3)修改剛抓回的四個檔案
(a)修改 /static/vp/index.html
讓它會用到我們的 dan.js 和 dai.js, 其他 .js 都去用 demo.iottalk.tw 的即可!
(b)修改 /static/vp/dan.js
找到註冊處, 讓它 d_name 會在左邊多 "XYZ_" 其中 XYZ 是你的姓名的英文頭字語
(c)修改 /static/vp/dai.js
-- 讓它會連到正確的 IoTtalk server (大約在 LINE 5 處)
-- 讓它會抓到正確的 Audio 聲音檔案, 用 demo.iottalk.tw 即可 (網路共享概念)
-- 讓它會抓到正確的 .py 檔案(大約 LINE 132)(da 改為 static 即可)
fetch_code('/static/vp/py/'+ project + '.py');
(d)修改用到的 .py 檔案 讓它有聲音
/static/vp/py/Ball-throw1.py
這需要參考 Ball-thjrow2, 所以可以先把 Ball-throw2.py 抓回後再參考修改!
(4)讓 Ball-throw2 也可以用
只要參考抓 Ball-throw1.py 方法抓回存入 /static/vp/py/Ball-throw2.py 即可,
都不用修改(除非你想改變 Ball-throw2.py 的行為)!
--- 可以參考這 Ball-throw2.py, 修改讓 Ball-throw1 啟動以及球打到地有聲音!
--- 可以自定義Good Job, 例如彈兩下內打到 400~500 喊 GoodJob !
(5)抓 Edutalk 上的自由落體來放入我們網站, 改名Free-fall.py(注意大小寫)
--- 複製 /static/vp/py/Ball-throw2.py 到 /static/vp/py/Free-fall.py
==> 此時就可測試到首頁點自由落體, 但跑起來是 Ball-throw2
--- 把 Edutalk 上的自由落體實驗的 Program 複製回來,
換掉 Free-fall.py 的內容; 依據 IoTtalk 的規則修改 !
請參考 0523 加分題的 第(6)項
--- 注意這個檔案要叫做 Free-fall.py 是為了配合前面我們建立的首頁 !
(6)以後只要把 .py 檔案放入 /static/vp/py/ 內,
並修改首頁 index.html 讓它有連結可以點就可多出 VPython DA 了 !
例如, 目前首頁已經是先預備了給 Hello.py 和 haha.py 用的連結!
(七)解決 favicon.ico 的問題, 就是讓你的網頁的頁籤上會顯示 小 icon
因前面 W90.py 檔案已經有寫好這項, 現在建立以下檔案即可:
/static/img/myicon.png (當然要先在 static 建立子目錄 img 啦!)
==> 圖片建議用 32 X 32 或 64 X 64 都可以, 用小畫家即可!
好了之後, 到你的首頁 (127.0.0.1) 敲 CTRL_F5 即可 !
注意看網頁頁籤上的小 icon 有沒變化 !!!
(八)讓你從首頁可以把 VPython 版的 Dummy_Device 叫出來
把以下的程式碼(往下捲一些些)建立成 utf-8 檔案,
放入你的 /static/vp/py/V_Dummy.py 即可 ! (前面 (二) 首頁已做好連結)
=> 請用 CMD 跑一份 Dummy_Device 來送資料給這 VPython 的 Dummy_device 測試,
最好是 拿 DAI2.py 來改為不轉為 float, 直接把輸入的字串 theInput 送出去即可!
注意, ODF 的上下限必須設 [0, 0] 表示不做 Scale !!
(這個 VPython Dummy_Device 已經做了防呆, 不會給輸入資料 "搞死" :-)
(九)讓你從首頁可以把 蛇擺 (VPython 版的 Pendulum Wave) 叫出來
(a)要修改首頁 index.html 增加一項 "蛇擺", 指到 #penw (表示要用 penw.py 可自己改名)
(b)把 可搭配 IoTtalk 假裝 Ball-throw1 的蛇擺放入你的 /static/vp/py/penw.py 即可 !
Hint: 只給 Edutalk 版, 要稍微修改(有提示如何改)才可當 IoTtalk 的 VPython DA
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
|
這是 VPython 版本的 Dummy_Device, 只有 ODF, 請設為不 auto Scale 喔!!!
#This 檔案 uses UTF-8 VPython Dummy_Device by tsaiwn@cs.nctu.edu.tw
# coding=utf-8
#from vpython import * # 單機版才要 (1), 要當 IoTtalk VPython DA 請註解掉
## 單機版 offline 還要打開後面的 test( ); 會根據 iottalk 是 False 打開
### .. 把以下 iottalk 設 False; 這樣後面也不會做 dai(profile) !
iottalk = True # using IoTtalk ? (2) ==> has sound to play
#scene.autoscale = False # 看要不要讓場景遠近 AutoScale; 預設是 True
#from time import sleep # VPython 已經有 sleep;; Edutalk 也是
#from random import random # VPython 已經有 random()
runLimit = 3 # 0 表示不會停止
speed = 0.88 # 球的 移動速度
freq = 60 # 每秒 Loop 幾次 (frame rate) # 120 # 1000
randSpeedCycle = 2.5 # 2.5 seconds to change Speed 亂數絕定速度
rscCount = int(freq * randSpeedCycle * 0.99)
isControl = False # 還沒參數送進來過 or 沒被重新啟動 by -5566
radius = 0.58
height = radius + 0.1
bbSpeed = speed
t = 0 # 時間
dt = 1.0 / freq
freq10 = int(freq/10)
fLong = 33 # 木塊長
fWide = 12 # 木塊寬
fThick = 0.25
isRunning = True
humidity = 58 # 初始濕度, 只是為了 gdots ( )畫圖
##
## iottalk 要把以下兩個 def 都註解掉; 單機版則要打開
""" ### 單機版注意 (3)
# 單機版沒聲音, 因它不認識這兩函數, 幫它定義!
def preloadAudio(name):
return
def playAudio(name):
return
""" ### 單機版注意 (4)
## Note 注意往下第六列 (inpFst, *_) = ... 給單機版用
def getInpFst(inp): # 故意寫在較前面方便修改切換 IoTtalk 版 <==> VPython 單機版
if inp != str(inp): # 終於想到這招來查看是否輸入字串 or scalar 阿不然一堆函數都不給用!
return inp # inp 可能是整數或實數, 不是字串
if iottalk: # GlowScript 用 舊版本 Python ?!
splitStr = inp.split(" ") # 含有字串, 取最左邊 token
inpFst = splitStr[0]
if not iottalk: # 單機版用 Python 的 split( )
pass # 單機版請打開以下 有 (inpFst, *_) = ... 的; 單機版注意 (5) 是最後要注意項目
# (inpFst, *_) = inp.split(maxsplit=1); # (5) 在 iottalk 還是要註解掉否則會翻譯錯誤!!!
return inpFst
#
preloadAudio('Startup.wav')
preloadAudio('chord.wav')
preloadAudio('gj.wav')
def scene_init():
global scene, ball, bb, floor, gd, xt,vt, hd, c
global label_info, inp_info, dm_info, chk_info, ch2_info
scene = canvas(title="Dummy_Device by tsaiwn@cs.nctu.edu.tw", width=800, height=500, x=0, y=0,
center = vector(0, 15, 0), # center=vec(0, 0.1, 0),
background=vec(0, 0.6, 0.6))
ball = sphere( pos = vec(0, height, 0), radius = radius, color = color.green,
velocity = vector(0,0,0), visible = True)
bb = sphere( pos = vec(0, height, -5), radius = radius, color = vec(255/255,51/255,204/255),
velocity = vector(0,0,0), visible = True)
floor = box(pos=vec(0, 0, 0), length=fLong, height=fThick, width=fWide,
texture=textures.wood)
gd = graph(title="x-t plot", width=600, height=450,
x=0, y=988, xtitle="t(s)", ytitle="y Blue=position; pink=speed")
#gd2 = graph(title="another figure", width=600, height=450, ... 可以另一張圖..
xt = gcurve(graph=gd, color=color.blue, label='position')
vt = gcurve(graph=gd, color=vec(255/255,51/255,204/255), label='速度')
hd = gdots(graph=gd, color=color.green, radius=0.88, label='濕度')
label_info = label( pos=vec(5,25,-5), text="", color = color.yellow, height=18)
inp_info = label( pos=vec(5,30,-5), text="", color = color.yellow, height=18)
dm_info = label( pos=vec(-33,38,-16), text="Dummy_Device", color = color.white, height=24)
chk_info = label( pos=vec(-28,10,-8), text="chk", color = color.yellow, height=18)
ch2_info = label( pos=vec(-28,5,-8), text="ch2", color = color.white, height=18)
c = curve(pos=[vec(-17,10,0), vec(-15,10,0)], color=color.red, radius=0.1)
v1=vec(0,10, 0) # for gcurve c.append(v1, v2)
v2=vec(0,10, 0)
##
def checkbbPos( ):
global bb
if (bb.pos.x >= fLong/2 - 0.5* radius):
bb.pos.x = - fLong/2 + 0.5* radius;
def changeSpeed():
global speed,isControl
if isControl: return
speed = speed -0.38 + random( ) * 2 # VPython 已經有 random( )
if(speed > 5):
speed = 0.38
ggyy=0
def setRandSpeed( ): # 亂數變化速度; 順便改 濕度
global speed, ggyy, humidity
# rate(1,setRandSpeed) # 只有 Edutalk 內可以這樣 !?
ggyy += 1
if ggyy == int(rscCount/2): humidity += int(random( )*20-10) # 隨便亂數變化濕度:-)
if ggyy < rscCount:
return
ggyy = 0
changeSpeed( )
def drawCurve( ):
global v1, v2, speed, c, hd # 順便 plot 出濕度 hd.plot( )
v2.x = ball.pos.x
v2.y = 10+speed # adjust
c.append(v1, v2) # 從 v1 畫到 v2
hd.plot( t, humidity); # 濕度 [0..100]
if (v2.x < -5) and (v1.x > 5): # right to left draw
c.clear( )
v1.x = v2.x
v1.y = v2.y
##
uuyyy = 0
def update_info():
global uuyyy, label_info, inp_info
uuyyy += 1
if uuyyy < freq10: return
uuyyy = 0
label_info.text = "Speed: {:.2f}\nPosition: {:.2f}\nTime: {:.2f}". \
format(speed, ball.pos.x, t)
inp_info.text = "Got data: {} ".format(inp)
#rate(11, update_info)
def restart( ):
global saveX
ball.pos.x = -(fLong*0.9/2) # 幾乎最左邊
bb.pos.x = -(fLong*0.9/2) # 幾乎最左邊
saveX = ball.pos.x
v1.x = ball.pos.x
v2.x = ball.pos.x
c.clear( )
def tryCMD(inp):
global isRunning, howManyRun, saveX
ans = True
if inp == "reset" or inp == "restart":
restart( ); return ans;
if inp == "pause" or inp == "stop" or inp == "p" or inp == "s":
saveX = ball.pos.x
isRunning = False;
return ans
if inp == "go" or inp == "cont" or inp == "continue" or inp == "c":
if isRunning:
pass
else:
ball.pos.x = saveX
if howManyRun == 0: howManyRun = runLimit
isRunning = True
playAudio('chord.wav') # playAudio('go.wav')
return ans
return False
## 當 Dummy_Control 收到新值時
inp = "No Data yet"
saveX = 0
def Dummy_Control(data):
global inp, isControl, speed, ball, bb, v1, v2, c, saveX
global chk_info, ch2_info, saveX, isRunning
if data == None:
return
inp = data[0]
isControl = True
if tryCMD(inp): # 處理了命令 go, pause, reset
return # 已處理了命令
inpFst = getInpFst(inp) # obtain inpFst; 繼續處理 inp
chk_info.text = inp # for Debug
ch2_info.text = inpFst # for Debug
kk = -32768
try:
kk = float(inpFst) # try to get first number
except: # ## 其實我可以在這except 之上, kk = 之下, 下一句放如 gyg = 38 後面檢查gyg
pass
return # 可惡, VPython (Glowscript) 不產生 exception (類似 C語言)
# import math # 還亂改 kk 給我放 NaN; 又沒 math. 也沒 isdigit( ), ord( ) 等
kkCode = "{:.2f}".format(kk)
if kkCode == "NaN": ## float("nan") : # 終於用這招 kkCode 抓到 NaN :-)
return
if kk != -32768: # has a number #其實這檢查 -32768 已經沒用了 :-)
if kk > 5555:
isControl = False # 表示亂數絕定速度 speed
return
if kk < -5000: # 表示 "reset" position
restart( );
return
if kk < -5 : kk = -5
if kk > 5: kk = 5
speed = kk
isRunning = True # 有改變速度阿就讓它動
tttyyy = 0
def test( ): # 單機版用來 offline 測試 !
global tttyyy
tttyyy = 1 + tttyyy
if tttyyy == 5*freq: # 5 秒後
Dummy_Control(["haha haha hello"])
if tttyyy == 8*freq: # 8 秒後
Dummy_Control(["3.25 Change SP 改速度 3.25 有看到嗎"])
if tttyyy == 18*freq:
Dummy_Control(["2.58 又 Change SP 改速度 2.58"])
if tttyyy == 21*freq:
Dummy_Control(["-5555 Now Reset 位置"])
if tttyyy == 26*freq:
Dummy_Control(["5566 Now 恢復亂數控制 speed 速度"])
##
def setup( ):
scene_init()
setRandSpeed( );
profile = {
'dm_name' : 'Dummy_Device',
'odf_list' : [ Dummy_Control ], # df_list vs. odf_list
}
if iottalk:
dai(profile) # 若 單機版, 離線測試沒 dai( ) 可用
update_info()
playAudio('Startup.wav')
setup( )
ball.pos.x = -(fLong*0.9/2) # 幾乎最左邊
bb.pos.x = -(fLong*0.9/2) # 幾乎最左邊
#print("ball pos:", ball.pos)
howManyRun = runLimit # 要跑幾趟 -- 每次到最右邊算一趟
v1.x = ball.pos.x
v2.x = ball.pos.x
v1.y=10
v2.y = 10+speed
# sleep(3) # 如果單機版想先 delay 一下
left = - fLong/2 + 0.5* radius; # 左邊緣
right = fLong/2 - 0.5* radius; # 右邊緣
while True:
if not iottalk: # offline 測試; 相當於註解掉
test( ) ## offline 測試; 相當於註解掉
update_info()
setRandSpeed( );
rate(freq)
if not isRunning:
continue # go to while True
checkbbPos( ); # 對照球 (粉紅) 位置可能需要調整
if speed < 0 and (ball.pos.x <= left): # too Left
isRunning = False
speed = 0
saveX = ball.pos.x
playAudio('chord.wav') # 可換別的聲音
continue # go to while True
if speed > 0 and (ball.pos.x >= right): # 到最右邊了
howManyRun -= 1 # 每次到最右邊算一趟
if howManyRun == 0:
isRunning = False # break; # 若要停止則 break
saveX = left; # 稍後會 ..移至最左 Left Side
continue # go to while True
ball.pos.x = left;
playAudio('chord.wav')
ball.pos.x += speed * dt
bb.pos.x += bbSpeed * dt
xt.plot(pos = (t, ball.pos.x))
bvx = 3 * speed # 放大三倍, 因 Y 軸 scale 被 xt (ball.pos) 掌控了
vt.plot(pos = (t, bvx))
drawCurve( )
t += dt
print("Bye, time = ", t)
sleep(0) # 讓一下 CPU :-)
|
* Flask 秘技 ! Flask 秘技 ! Flask 秘技 ! (尼采說:很重要所以要說三次 )
(1)可否讓 .html 網頁內(當然包括在 .js 檔)都省去打 "/static" 呢?
Ans: 很簡單, 把你那句 app = Flask(__name__) 那句改為如下即可:
app = Flask(__name__, static_url_path='/')
| => 意思就是說, 你的 web Root 就是 /static 這子目錄!
請注意, 這時你打如下:
127.0.0.1/abc/index.html
會去抓你的 /static/abc/index.html
==>注意! 這時使用者不可以打 127.0.0.1/static/abc/index.html (根本不知道有 /static 存在 !)
**重要 ! 用了(1)這招會影響 @app.route( ) 使用類似 <path:ggyydir> 這種 pattern 喔
請參看後面 (4) (5) (6) 說明 !
|
(2)如果你的"首頁" index.html 根本沒用到 "樣版"(template)的概念, 阿就是說你只是寫:
return render_template("index.html") # 根本沒帶參數
那麼, 你也可以考慮把 index.html 放在 /static 子目錄裡面;
這樣, 可以改用以下任一句都可:(選一句; 建議用第一句;;第三句需要 import os 和 send_from_directory )
return app.send_static_file("index.html") # 建議用這個
return send_file("static/index.html") #注意不是 /static/index.html 喔 !
return send_from_directory(os.path.join(app.root_path, 'static'),'index.html')
| (3)如前面(1)這樣做還有個好處, 就是你的首頁變有了 "這裡" 可以用! 啥啦!?
就是這時你的 index.html 裡面的 "這裡" (current directory) 就是 /static 裡面
這是因為當你是使用 render_template( ) 時,
你的 current directory "." (這裡) 就是 @app.route( ) 括號內的!
既然你的首頁就是指用 @app.route("/") 的網頁, 你的"這裡"就是 "/";
本來基於安全理由 "/" 是不能讓使用者存取的 !
但是, 因為你用了 (1) 那招說: 網址列出現 "主機名稱或IP/" 就是在你的 static 目錄。
反過來說, 你(1)的意思就是 你的 static 子目錄是 web Root (/);
所以說你的 / (web root) 就是 /static (好像繞來讓去, 呵呵!)
聽不懂!?
=> 因為如果你只是使用原先精簡的 app = Flask(__name__)
則這時的 current directory 是 /
再說一次, 本來基於安全的理由, / 這目錄是不可以用的 !!!
阿這樣意思就是 "這裡" (.) current directory 不存在! "這裡不存在!" !
白話文就是你的 templates/index.html 內不可以用相對路徑!
==> 但是..但是..但是
如果你用了(1)那招來定義 app, (app 只是一個變數, 你也可改用 ggyy )
則你的 templates 內的 index.html 檔案就有了 current directory "這裡",
當然, 這時候的"這裡"是你的 /static 不是 /templates 更不是 / 喔!!!
(雖然 .html 在 templates 內)
===> 請注意, 上述說明只針對 @app.route("/") 喔 !
====> 通常, 所謂的"這裡" 是指:
(a)如果用 @app.route("指定路徑") => 就是 "指定路徑" 的目錄
(b)不是直接 @app.route("指定路徑") => 就是 .html 檔案所在的目錄
例如 127.0.0.1/abc/gg.html 的 "這裡" 就是 127.0.0.1/abc
如果根本沒 abc 這目錄就會出問題, 想用 /abc/ 內檔案會有問題 !
(註: 因你可能用 @app.route("/abc/gg.html") 把它 route 到某個存在的 .html )
但如在該 .html 內用 ../js/dai.js 仍可抓到 127.0.0.1/js/dai.js | (4)可否讓使用者網址中打目錄名稱就自動抓該目錄的 /index.html 呢? 例如, 若使用:
127.0.0.1/bulb
就自動抓 你的 static 目錄之下的 bulb/index.html
Ans: 當然可以啊! 也簡單! 記得要 from flask import send_file
只要在你的 web 的 .py 檔案(w90.py)內加入以下route規則:
(且你的 app = 要像 (1) 說的那樣定義, 要指定 / 是 static_url_path )
@app.route('/<ggyydir>/')
def giveDirIndex(ggyydir):
try: # 不必用 os.path.isfile( ) 檢查, 直接 try 看看 :-)
return send_file("static/" + ggyydir) # maybe ggyydir is a File
except:
return app.send_static_file(ggyydir + "/index.html")
| ! 但是, 但是, 但是, 這時的 <ggyydir> 只能一層,
就是說不能類似這樣: abc/xxx
所以若打 127.0.0.1/abc/xxx 它不會抓 /static/abc/xxx/index.html
=> 這是因為搭配 (1)說的 用以下這句定義 app :
app = Flask(__name__, static_url_path='/')
| => 結果會使得手冊說的 <path:ggyydir> 這樣抓不到 "全路徑" !!!
不知道是 Flask 的 Bug 還是故意的就不知道囉 !
不過, 幸運的是, 這時如果用路徑全名仍可抓到檔案, 例如:
127.0.0.1/abc/xyz/heyyou.html
仍可抓到 /static/abc/xyz/heyyou.html (注意上列路徑沒打也不可打 /static 喔!)
*** 注意喔, 如果用 app = Flask(__name__) 來搭配則抓不到喔 !
==> 當然, 這時你只要補上 /static 就可以了, 如:
127.0.0.1/static/abc/xyz/heyyou.html
(廢話, 這是 Flask 基本功能啊 :-)
==> 意思是說只要把檔案丟入 /static 之下或其子子孫孫目錄都可打全路徑抓到!
|
(5)做了 (4) 這樣後, 會影響我原先 @app.route("/222/") 這種 route 設定嗎 ?
Ans: 不會影響 ! 因為 Flask 很聰明, 它會做出正確的判斷 :-)
不過要記住, 只要你在 @app.route() 之下不是用 return redirect( ),
而是用 return render_template( ) 或 各種 return send_* 的做法,
則你的 .html 檔的 "這裡" 就是 @app.route( ) 括號內路徑 !
意思就是你的 "相對路徑" 是相對於 @app.route( ) 括號內的路徑啦!
(當然包括 .js 檔案, .js 檔的 "這裡" 是依據把它 include 進去的 html 檔案)
還有, 請注意, 如果做了 (1)之後, .html 和 .js 檔案內都不可以再寫 /static 喔 ~
所以, 以 Vpython Ball-throw1 為例, 這時要檢查:
--- .html 是否用到正確的 dan.js 和 dai.js
--- dai.js 是否用到正確的 .py 檔案
(6)Q:那有沒辦法多層目錄也會自動抓 index.html 呢?
Yes 有! 馬上來寫一個..
(6a) 首先 app 的定義回頭用簡單版: app = Flask(__name__)
from flask import Flask, request
app = Flask(__name__)
|
(6b)記得 import 需要的 module
from flask import render_template, redirect, send_file, send_from_directory
import os
import socket
|
(6c)把以下規則加入 web 網站的 .py 檔案 (例如 w95.py)
import os # 多寫一次 import 不會怎樣, 但萬一這和前面也都沒寫會出問題
@app.route('/<path:ggyydir>') # 注意不可搭配(1)說的指定 static_url_path
def giveDeepDirIndex(ggyydir):
#return 'You want path: ' + ggyydir, 202 ## for debug
xfile = os.path.join(app.root_path, 'static', ggyydir)
if os.path.isfile( xfile ):
return send_file("static/" + ggyydir) # maybe ggyydir is a File
elif os.path.isdir( xfile ): # 目錄先看看是否 "/" 結尾
if ggyydir.endswith("/"): ## 是, 改送這目錄的 index.html
return send_file( "static/" + ggyydir + "/index.html")
else: # 不是 "/" 結尾, 必須轉址, 否則 current directory 不正確 (試好久ㄟ!)
return redirect( ggyydir + "/" ) ## 轉址後, 會用上面那條規則
else:
return ("<h1 style='color:red;'>File %s not found!</h1>" % ggyydir), 404
|
(6d)你的 127.0.0.1/ 仍可用原先 render_template 方式:
import socket
myhost = socket.gethostname() ## 我的機器名稱
myip = socket.gethostbyname(myhost) ## 我的 IP address
@app.route("/")
def webRootNew( ):
try:
return render_template('index.html', myname=myhost, myip=myip)
# 以下三種是如果你放 static 內(因根本沒用到 Jinja2 template 樣版)
# return app.send_static_file("index.html")
# return send_from_directory(os.path.join(app.root_path, 'static'),'index.html')
# return send_file("static/index.html")
pass # 萬一上面都被註解掉或砍掉 :-)
except:
return "<h1 style='color:red;'>No index.html in templates.</h1>", 404
|
好啦! 這時 被 render_template 的 首頁 也變有 "這裡" (.) current directory;
因為本來它的 current directory 就是 app.route("/") 括號內的 /
現在因為上面(6c) 的規則, 所有 /file.ext 或 /directory
都變成對應到 /static/file.ext 和 /static/directory
這相當於 首頁的 current directory 就是 /static 這目錄 !
===> 雖然已經沒使用 app =Flask(__name__, static_url_path='/')
前面說過, 真正的 Web root (/) 基於安全的理由, 是禁止使用的 !
結論與建議:
=> 如果你的 .html 根本沒用到 template 樣版, 那就不要放到 templates 目錄去!
就是沒用 Jinja2 樣版的 .html 就放 static 內或 static 各子目錄內;
=> 送出檔案的方式有很多種, 參考 (2) 和 (4) 說明!
=> 使用 (1) 的方式定義 app 雖有好處,
也有壞處 : 無法抓到 <path:ggyydir> 的全路徑 !
還有還有..還有要注意不同寫法會導致 "這裡" (current directory) 不同 !!!
請注意, current directory 是 用來寫相對路徑的依據 !
| ==> 再注意! 啥啦!?
就是, 如果你用 @app.route("/222/")
則你的 current directory 就是 /222/
根據以上寫法, 這時 /222/ 也就是你的 static/222/ 這子目錄 !
(不論你用哪個 send_.. 送出的也不論該 .html 身在何處都這樣喔 !)
除非你用 return redirect( ) 轉址 !
===> 當然, 若用 return redirect 轉址後, 網址列會出現新的網址,
那個新網址的目錄就是你的 current directory 囉 -
|
|
|
## File: par.py # 簡單 爬蟲範例 by tsaiwn@cs.nctu.edu.tw
# coding=utf-8 ## par.py
import time, datetime
from selenium import webdriver #從library中引入webdriver
you = webdriver.Chrome() ## 開啟chrome browser
# Windows 請把 chromeDriver 抓回解壓縮後 和這 .py 檔放同一個目錄下即可 !
you.get('http://www.google.com/'); # try google first
### Mac OS 和 Unix/Linux 須放到 path 路徑內任一個目錄
### 所以如果有起動虛擬隔離環境, 打 echo $PATH 看看有哪些目錄可寫入 !
## 簡單說, Mac OS 和 Ububtu 等 Linux/Unix 就放入你虛擬隔離環境的 bin 目錄內啦 !
gg = ["https://www.cs.nctu.edu.tw", "https://www.nctu.edu.tw",
'https://www.cwb.gov.tw/V8/C/W/OBS_Station.html?ID=46741',
"https://www.cwb.gov.tw/V8/C/W/OBS_Station.html?ID=46757",
"https://www.nthu.edu.tw", "https://web.ncku.edu.tw/" ,
"http://www.mit.edu", "http://www.edu.tw", "http://www.ibm.com",
]
for k in range(len(gg)):
try:
time.sleep(3.5); # 3.5 seconds
you.get( gg[k] )
if k < 2:
time.sleep(2.5); # 交大多停 2.5 秒:-)
except:
pass
time.sleep(5)
you.get(gg[0]) # 跳回交大資工系 :-)
time.sleep(5)
print("Bye ! 再見 :-!") ## say bye
you.quit() #關閉模擬瀏覽器
# 請把 chromeDriver 抓回解壓縮後和這 .py 檔放同一個目錄下即可
# 如果 .py 執行後, Chrome瀏覽器跳出後又立即閃退, 表示 ..
# ..表示 ChromeDriver 和 Chrome瀏覽器 版本不匹配
## .. 點 Chrome 右上角 那點點點 選 說明 選 關於Chrome 仔細看版本
## .. 看快點並立即把該窗關掉, 否則它可能會自動把你更新(目前 83.xxx)
## https://chromedriver.storage.googleapis.com/index.html?path=83.0.4103.39/
##
# 建議最好先建立Python虛擬隔離環境並啟動它 (已建過可啟動舊的即可)
# python -m venv ggyy
# ggyy\Scripts\activate
# 用 pip 安裝所需程式庫, 執行時說缺啥就 pip install 啥!
# pip install selenium # 讓程式可驅動瀏覽器做各種網站操作
# pip install beautifulsoup4 # 從網頁抓取資料 # pip install bs4 #即可 :-)
# pip install lxml # XML 和 HTML相關功能; 注意是小寫的 LXML 不是 123 的 1 喔
# how to 安裝 Web Driver: # 其實只是抓回來解壓縮放好
### https://sites.google.com/a/chromium.org/chromedriver/
### 請把 chromeDriver.exe 和這 .py 檔放同一個目錄下即可!
## 可以用 CMD 命令窗測試這個 Python 程式碼了! 注意看發生啥事情!
# python par.py # 會自動開啟一個瀏覽器 (請預設 Chrome)
## 不要理這信息: Bad attribute EGL Driver message (Error) ??
* 參看林云蔚教授提供的氣象局爬蟲範例(在 github)
如果, 如果你執行林云蔚教授提供的爬蟲程式偶爾出現錯誤,
那是因為程式碼故意沒做防呆處理(例外處理, Exception handling) :-)
就是說, 有時候有些時間點, 氣象局沒放代表陰晴天氣的小圖片 阿就找不到 "img" 囉 !
把有找 "img" 的那列改為如下五列:
ggyy = tr.find('td',{'headers':'weather'}).find('img')
if ggyy != None:
weather.append(ggyy['title'])
else:
weather.append( "啊摘" ) # 不知道啦..晴時多雲偶陣雨吧
或用 try: ... except: ... 也可以 !
比較保險應該每句可能錯的, 都獨立一個 try:... except:... 處理! Have Fun !
|
** 如何叫 chromedriver.exe 不要亂秀 Message ?
阿就 Google:
python how to keep webdriver silent (雖然應該是 silently :)
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--no-sandbox') # Bypass OS security model
options.add_argument("--log-level=3");
# get rid off Devtool msg / chrome msg ...
options.add_experimental_option('excludeSwitches', ['enable-logging']) # 要! 要 !!要 !!!
## do NOT open the browser
options.add_argument("--headless") # 這樣就不會開啟瀏覽器 :-)
driver = webdriver.Chrome(options=options)
# for Windows: 把 chromedriver.exe 放要執行 .py 程式的目錄即可 ; 或 PATH 路徑任一目錄也可
|
|
再詳細提示如何執行以下這爬蟲程式, 把爬到的資料丟去 IoTtalk 給 Dummy_Device 接收
(1)到 demo.iottalk.tw 點 project 建立一個物聯網應用專案
引入一個 DM 名稱 Dummy_Device 並把 IDF 和 ODF 都勾選, Save 後,
畫面出現兩個 Dummy_Device, 左邊只有 IDF (Sensor), 右邊只有 ODF (Dummy_Controol)
(2)點右邊 Dummy_Device 的 Dummy_Control,
把它下限與上限設為 [0, 0] 表示不做 Auto scale
(3)把左邊 IDF 連線到右邊的 ODF
(4)依據 iottalk.vip/000/ 的使用手冊(四)抓 github 上的 IoTtalk Dummy_device 壓縮檔,
抓回來解壓縮後放入一個目錄, 共有 DAI.py, DAN,py, csmapi.py 以及一個說明檔
再依據 iottalk.vip/000/ 的使用手冊(五)把範例存入同一個目錄稱做 DAI2.py
==> 這 DAI2.py 會用到 DAN.py 和 csmapi.py
(5)開啟一個 CMD 命令視窗, 執行 python DAI2.py
==> 它說缺啥就 pip install 啥
====> 執行成功後注意畫面上的註冊成功名稱;
=====> 建議把這 CMD 窗拉到畫面右邊讓感覺比較對(因 project 畫面有 ODF 的在右邊)!
(6)回 demo.iottalk.tw 的 project 畫面, 點右邊 Dummy_Device 齒輪右邊的長方型,
把右邊這 Dummy_Device 綁定關聯到剛剛執行的 DAI2.py
(7)把以下程式碼存為 DAI8.py 放入同一個目錄
==> 注意檔案一律選擇 utf-8 編碼(encoding)
==> 這 DAI8.py 也會用到 DAN.py 和 csmapi.py
(8)開啟另一個 CMD 命令視窗, 執行 python DAI8.py
=> 必要時可以先用 python -m venv yourenv 建立虛擬環境並啟動它!
==> 執行時, 它說缺啥就 pip install 啥
====> 執行成功後注意畫面上的註冊成功名稱;
=====> 建議把這 CMD 窗拉到畫面左邊!
(9)回 demo.iottalk.tw 的 project 畫面, 點左邊 Dummy_Device 齒輪右邊的長方型,
把左邊這 Dummy_Device 綁定關聯到剛剛執行的 DAI8.py
==> 必要時點畫面中間上方的 Flush (出現紅燈時要把它點一下變綠燈)
**好了, DAI8.py 已經開始定時每隔約 15 秒去抓氣象局資料並丟去 IoTtalk,
另外, DAI2.py 會去 IoTtalk 把資料 "拉"(pull)下來並顯示在畫面上!
還有, 也可在 執行 DAI8.py 的 CMD 視窗打字, 每當按下 ENTER 鍵, 就會被送去 DAI2 的視窗!!
參考影片 https://youtu.be/7rmDGZItBKY (2小時練習 Web Crawler 讓你增強 Python 功力 20年:-)_善用影片下方資訊!
後續影片(16分鐘):
改為每個氣象的項目 push 一次, 分不 sleep 與 sleep(0.75): 兩個版本!
|
--
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
|
這是 0526 習題參考答案 ! 要研究才會進步喔!!!
(但沒照習題規定, 故意把project左右兩邊Dummy_device都弄成綁定關聯到同一個程式即可)
#This 檔案 uses UTF-8 ; coding=utf-8 ;;; by tsaiwn@cs.nctu.edu.tw
# For original Dummy_Device, Google Search github + dummy + iottalk for other files
import time, DAN, requests, random
import threading, sys # for using a Thread to read keyboard INPUT
# 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']='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
DAN.device_registration_with_retry(ServerURL, Reg_addr)
print("dm_name is ", DAN.profile['dm_name']) ; print("Server is ", ServerURL);
# global gotInput, theInput, allDead ## 主程式不必宣告 globel, 但寫了也 OK
gotInput=False
theInput="haha"
allDead=False
from bs4 import BeautifulSoup
from selenium import webdriver
import datetime
def clearLists( ):
global date,temp,weather, wind_direction, wind_speed, gust_wind
global visible, hum, pre, rain, sunlight
date = []
temp = []
weather = []
wind_direction = []
wind_speed = []
gust_wind = []
visible = []
hum = []
pre = []
rain = []
sunlight = []
driverReady = False
region = 'hsinchu'
reginTitle="新竹測站觀測資料"
url = 'https://www.cwb.gov.tw/V8/C/W/OBS_Station.html?ID=46757'
from time import sleep # so that we do NOT have to write time.sleep( ) :-)
def initCWB():
global driver, driverReady
#啟動模擬瀏覽器
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--no-sandbox') # Bypass OS security model
options.add_argument("--log-level=3"); # "--disable-logging" # 尚可, 仍有 Devtool 信息
## options.add_argument("--silent") # 沒用 ! ! !
## get rid off Devtool msg / chrome msg ...
options.add_experimental_option('excludeSwitches', ['enable-logging']) # 要! 要 !!要 !!!
## do NOT open the browser
options.add_argument("--headless")
##
try:
driver = webdriver.Chrome(options=options) # for Windows: 把 chromedriver.exe 放要執行 .py 程式的目錄即可 !
driverReady = True
except:
print("!!! Can NOT get WEB driver !!!")
#取得網頁代碼
try:
driver.get(url)
open(region+'.html','wb').write(driver.page_source.encode('utf-8'))
except:
print("!!! get URL Fail :" + url)
# #對list中的每一項 <tr>
#for tr in trs:
def processTr(tr):
global date,temp,weather, wind_direction, wind_speed, gust_wind
global visible, hum, pre, rain, sunlight
# print("===NOW ", datetime.datetime.now() , flush=True)
if tr != None:
# 使用datetime取得時間年分
year = str(datetime.datetime.now().year)
# 取時間, <tr>內的<th>, <th>內為時間 月/日<br>時:分
d = tr.th.text
d = year + d
# 字串轉為datetime格式
date.append(datetime.datetime.strptime(d, '%Y%m/%d %H:%M'))
temp.append(tr.find('td',{'headers':'temp'}).text)
ggyy = tr.find('td',{'headers':'weather'}).find('img')
if ggyy != None:
weather.append(ggyy['title'])
else: weather.append( "不知道" )
wind_direction.append(tr.find('td',{'headers':'w-1'}).text)
wind_speed.append(tr.find('td',{'headers':'w-2'}).text)
gust_wind.append(tr.find('td',{'headers':'w-3'}).text)
visible.append(tr.find('td',{'headers':'visible-1'}).text)
hum.append(tr.find('td',{'headers':'hum'}).text)
pre.append(tr.find('td',{'headers':'pre'}).text)
rain.append(tr.find('td',{'headers':'rain'}).text)
sunlight.append(tr.find('td',{'headers':'sunlight'}).text)
def buildTable( ):
global table ## 以下兩列的 global 其實不必要 !
global date,temp,weather, wind_direction, wind_speed, gust_wind
global visible, hum, pre, rain, sunlight
table = {
"觀測時間":date,
"溫度(°C)":temp,
"天氣":weather,
"風向":wind_direction,
"風力 (m/s)":wind_speed,
"陣風 (m/s)":gust_wind,
"能見度(公里)":visible,
"相對溼度(%)":hum,
"海平面氣壓(百帕)":pre,
"當日累積雨量(毫米)":rain,
"日照時數(小時)":sunlight
}
## 這函數會把 line 和 line2 準備好, 分別是次新和最新 的氣象資料
def prepareData( ):
global driver, line, line2, driverReady, table
#指定 lxml 作為解析器
soup = BeautifulSoup(driver.page_source, features='lxml')
sleep(1) # 其實不必睡一下 :-)
# <tbody id='obstime'> # 抓過去24小時資料 (氣象局網頁給的)
tbody = soup.find('tbody',{'id':'obstime'})
# <tbody>内所有<tr>標籤
trs = tbody.find_all('tr') ## Google 搜尋 BeautifulSoup + find
# 把次新的(trs[1]) 串成一列 放 line
tr=trs[1] ## 次新的氣象資料
clearLists( ) # 清除所有 11 項 List
processTr(tr) # 把 tr 資料抓出塞入 11 個 List
buildTable( ) # 根據 11 個 List 建立 table 這 { dictionary }
line ="\r\n"
for gg in table: # table 內共有 11 項
yy = table[gg][0] # 每項都是只一個元素的 [List]
if gg == "觀測時間": # 這項特別處理一下
yy = yy.strftime("%Y-%b-%d %H:%M:%S")
line += gg +":" + yy
line += "\r\n"
#print("line: ", line, flush=True)
tr=trs[0] # 最新的; 因只要 push 兩列, 偷懶沒寫函數, 複製上面一段過來用 :-)
clearLists( ) # 清除所有 11 項 List # (主要是後來才想要 push 兩筆資料 :-)
processTr(tr) # 其實直接寫 processTr(trs[0]) 即可
buildTable( ) # 參看前面處理 trs[1] ..
line2 ="\r\n"
for gg in table:
yy = table[gg][0]
if gg == "觀測時間": # 特別處理這項
yy = yy.strftime("%Y-%b-%d %H:%M:%S")
line2 += gg +":" + yy
line2 += "\r\n"
#print("LINE2: ", line2, flush=True) # for debug
sendCount = 0
def grabCWBthenPush( ):
global gotInput2, theInput2, allDead, driver, sendCount
sleep(12); # give you several time to do binding in your project
while True:
initCWB( );
prepareData( );
driver.quit()
# print(line)
# print(line2)
while gotInput2: # 不要跟別人搶
time.sleep(0.1)
continue # go back to while
theInput2 = line
#print("Will send LINE ONE, then sleep a while ==", flush=True)
gotInput2 = True
sleep(1);
while gotInput2: # 不要跟別人搶
time.sleep(0.1)
continue # go back to while
theInput2 = line2
#print("Will send LINE2, then sleep a while ==", flush=True)
gotInput2 = True
sleep(1.25)
sendCount += 1
theInput2 = "== ----- End of " + reginTitle +"--" + str(sendCount)
gotInput2 = True
if(allDead) : break
sleep(15)
if(allDead) : break
def doRead( ):
global gotInput, theInput, allDead
while True:
while gotInput:
time.sleep(0.1)
continue # go back to while
try:
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 # ?
gotInput=True
if theInput =='quit' or theInput == "exit": allDead = True;
else: print("Will send " + theInput, end=" , ")
#creat a thread to do Input data from keyboard, by tsaiwn@cs.nctu.edu.tw
threadx = threading.Thread(target=doRead)
threadx.daemon = True # 魔鬼(server) 在主thread結束後也會自殺!
threadx.start() # 這時 threadx 開始 "同時" 執行
thready = threading.Thread(target=grabCWBthenPush)
thready.daemon = True # daemon 的 a 不發音 == 魔鬼 == server program
thready.start() # 這時 thready 也開始 "同時" 執行
line="line first"
line2="line2" # the following function is used for debug
def check_alive(him): # check a thread to see if it is alive?
him.join(timeout=0.0)
return him.is_alive()
cnt = 0 ## for debug
gotInput = False
gotInput2 = False
while True: ## 這是 main thread 主執行緒的 典型寫法 : 一個 Loop !
if(allDead): break;
cnt += 1 ## for debug ..
if cnt % 20 == 0: # for debug to check thready is alive
# print("is y-thread alive: ", check_alive(thready), end=", ")
# print(cnt)
pass
# # 以上用來 check 負責抓 CWB 資料的 thread 是否還活著
try: ## main thread 依序做以下 (1)(2)(3) 三件事:
#(1)Pull data from a device feature called "Dummy_Control"
value1=DAN.pull('Dummy_Control')
if value1 != None:
print (value1[0])
#(2)Push keyboard data to a device feature called "Dummy_Sensor", if any
if gotInput:
if theInput =='quit' or theInput=="exit":
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)
gotInput=False # so that you can input again
#(3)Push CWB data from crawler to a device feature called "Dummy_Sensor", if any
if gotInput2:
DAN.push('Dummy_Sensor', theInput2)
# DAN.push('Dummy_Sensor', "543 push ggg yyy") # for debug
gotInput2=False ## 通知 thready 爬蟲
except KeyboardInterrupt: ## 敲了 CTRL_C
break;
except Exception as e: ## 不明原因, 可能剛剛網路斷了!
print(e)
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
time.sleep(0.5)
try:
DAN.deregister()
except Exception as e:
print("===")
print("Bye ! --------------", flush=True)
sys.exit( ); ## Quit the program
|
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)
|
* 台鐵火車查詢
* 點這看如何快速安裝 IoTtalk 系統
(要先花錢取得交大授權才可以喔:-)
* 點這看如何選擇 VPS ?(建議用 Hostwinds.com 或流量小可以先用免費的 AWS EC2)
*
PuTTY official site to Download PuTTY
PieTTY project official site
Download Pietty0400b14.zip
|