2017年6月30日 星期五

2G 升 4G

晚上去自由路的中華電信將母親生前的手機門號攜碼到中華的如意卡 (預付卡), 這是以前以我的名義在家樂福辦的免月租服務, 通話費率稍微高一點點, 但不用每月付月租. 其實家樂福電信使用的是中華電信 2G 系統, 他們只負責品牌行銷與帳務罷了.

由於 2G 行動電話將於今日畫下句點, 前陣子收到書面通知說, 若要繼續使用門號, 要在今日前攜碼至其他電信業者. 我一共辦了兩支家樂福免月租門號, 一支給媽用 (2010/8/26 申辦, 但 2011/4/29 買了 LG GB125 手機後才開始用), 後來 QRV 的 TOBE 車機門號被我退租後, 又用水某名義辦一支免月租放在 TOBE 車用系統. 由於使用率不高, 因此決定留下母親的門號紀念, 取代原來的車機門號, 只要每半年 (7/1 與 1/1) 儲值 100 元即可, 這是各家中最低的, 平均每個月不到 17 元的月租費.

很巧的是, 一進中華電信門市就看到有人跟我招手, 乍看還分不清是誰, 走近才認出是老同學峰大師, 帶他兒子來換小卡 (買新手機喔!). 唉, 真的歲月催人老, 當年本班最帥的帥哥, 現在我竟然無法一眼就認出來. 不過, 或許也是我眼力老化了, 同學你不要太傷心.

2017年6月28日 星期三

Arduino 自走車測試 : (一) 車體組裝

昨晚在統計要購買的電子零組件時看到電機模組的塑膠安裝架, 想起前年在露天買的智能小車一直都沒有時間 (動力?) 組裝, 曾經三番兩次帶回鄉下卻又原封不動帶回高雄, 久之都忘記放哪兒了. 搜尋一陣後才在電腦桌角落找到.

既然找出來了, 乾脆擇日不如撞日, 就動手組裝吧! 商品袋裡內容是一個壓克力板, 兩個膠輪, 兩個步進電機, 以及一包螺絲螺母組, 不含電機驅動模組. 我從 blogger 購買紀錄找出賣家是 handylab, 參考商品頁中的組裝圖按圖施工很快就組好了.

智能小車底盤 底座 支架 迷你小型套件 循跡遙控 兩驅三輪 萬向輪 零配件 專題製作
向 handylab 採購零件一批




注意, 前面的萬向輪我是把螺母鎖在壓克力板上面, 而不是賣家組裝圖上所示的鎖在萬向輪底座, 因為螺母體積比螺絲大, 若鎖在萬向輪底座上會跟萬向輪牴觸造成小車轉向困難.

至於電機驅動模組, 以前有在露天買過 L298N 模組 (盼盼), 但我覺得體積頗龐大, 而且驅動力似乎超過需求, 因此決定採用最近跟 tsai_pl 買的 L9110S, 體積小驅動力也夠 :

# 【盼盼6】 L298N 馬達 驅動模組 直流步進電機 L298N 機器人TT馬達 智能車
L9110S 馬達 電機 驅動 模組 板 兩路 避障車 自走車

下面幾篇是有關 L9110S 用法的說明與範例 :

芭蕉葉上聽雨聲 : L9110S 直流馬達驅動模塊
# How to use the HG7881 (L9110) Dual Channel Motor Driver Module
# 教學範例 30:雙馬達驅動模組改造玩具車
# 控制自走車(L9110S)
# 技術:Raspberry Pi 使用 L9110S 模組控制直流小馬達

控制板部分初步當然先用 Arduino + ESP8266 WiFi 模組來做, 第二步改用 ESP8266 模組獨立控制 (ESP-12E), 最後是使用 Raspberry Pi 來實作較複雜的智慧小車功能. 下面是我將 Arduino 控制板, L9210S 驅動板, 以及兩節 18650 鋰電池組擺上去的配置圖 :


這個做好後, 嘿嘿, 小咪你就有玩伴了.

無聊的小咪

這樣我就可以從辦公室用手機操作智慧小車來追蹤牠!

參考 :

# 《德源科技》智能小車套件/視頻小車/攝像/監控小車 智能小車/機器人 升級版套餐

2017-06-30 補充 :

今日下午請了一小時補休去凱歌路一家五金行找智慧小車電機固定用螺絲組, 因為組裝到最後發現固定電機用的四組長螺絲竟然有一組螺帽鎖不進去, 昨日中午吃完午餐問附近五金行, 結果都沒賣, 老闆建議我去凱歌路的螺絲專賣店找. 但很可惜還是沒有. 乾脆騎到同盟路的金沛螺絲問, 老闆說有, 問我要買幾支, 我說 1 或 2 支即可, 他竟然很豪氣地說, 如果是一兩支, 我就送你好了!

回到辦公室我跟同事提起這件事, 我說這就是所謂的格局!

向 tsai_pl 與 bluetaipei 購買零組件兩批


周六與昨天在露天下標訂購了零組件兩批, 全部記錄如下. 週六這批是向 bluetaipei 回購五顆 ESP-01 (1MB) 模組, 總價 355+郵寄40=395 元 :


商品名稱/商品編號 得標金額 數量 商品總價
2.54mm 10cm 杜邦線 (10條一組) $5 6 $30
 
  $65 5 $325
ESP8266 ESP-01


昨晚這批則是向 tsai_pl 回購了 ESP8266 轉接板與 1 路繼電器, 微波雷達感應開關等模組, 合計 609 元, 超商取貨 60 共 669 元 :

商品名稱/商品編號 數量/
商品總價
AMS1117 3.3V 電源降壓模組 3.3V 穩壓 Arduino (21711503557838) 2
$12
Mini-360 航模 可調 降壓 模組 DC 超小 電源 模組 MP2307DN  (21711503626352) 1
$18
USB 轉 ESP8266 WIFI 模組 轉接板  (21723446408247) 1
$50
Micro SD卡模組 SPI介面 TF卡讀寫 Arduino 3.3V  (21713687200715) 1
$20
0.91吋 OLED 128x32 Arduino SSD1306 藍色 3.3V  (21716930998218) 1
規格:藍色 $90
1路 繼電器 模組 5V 高電平 arduino 適用  (21712584887014) 8
$120
RCWL-0516 微波雷達感應開關模組 人體感應模組 智能感應探測器  (21702124214736) 3
$72
1路 繼電器 模組 5V 高電平 ARDUINO適用藍色 (21712584729761) 1
$15
4路電平轉換模組 雙向互轉 3.3V 轉 5V IIC I2C UART SPI  (21723468774540) 10
$100
AMS1117 3.3V 電源降壓模組 3.3V 穩壓 Arduino (21711503557838) 2
APDS-9930 顏色、非接觸手勢檢測傳感器、Arduino RGB Gesture Sensor  (21710477714696) 1
$50
迷你小型人體感應模組HC-SR505  (21631245661388) 1
$50


記錄下來以後要查買過哪些零組件會比較快.

PS : 這位賣家 tsai_pl  服務挺好, 下標後提醒我其中有 LED 的一路繼電器是常態 ON, 是否確定要買, 我想有些情況需要常態 ON 的繼電器, 所以還是先買好了.

2017年6月25日 星期日

2017 年第 25 周記事

因姊姊下周就要指考了, 留在圖書館讀書, 所以本周六只有我回鄉下. 今天VU8我把母親那台 Mate 90 二行程機車騎去機車行請老闆代辦汰換購買新機車, 結果老闆跑到我家附近山上走路爬山, 還好他母親在家, 就把錢 (46500) 與證件影本印章等請她轉交. 又跟伯母聊了一些瑣事才步行回家. 我正在煮菜時老闆騎車上來, 說銀色的缺貨, 要不要換暗紅色, 我看也不錯就決定按紅色了.

其實下午有跟爸說, 其實我有想只繳牌報廢, 車子留下來紀念, 就差補助款 7800 元而已, 但這錢卻買不到回憶. 但爸說還是淘汰吧, 明年後又不能騎出去, 保養問題也麻煩. 好吧, 只好前後左右遠近拍了好多照片, 取下車頭懸掛的平安符 (20 幾年前去大阪工作時在清水寺買回來的御守), 才騎去機車行. 要汰掉這台車實在捨不得, 過去 22 年 (84 年買的) 媽可是騎著它到處跑, 三隻小狐狸 1~4 歲的幼年時光每天都有它陪伴哩.

本周都在忙週四週五的年度評鑑, 我照例手氣特好都會被抽中應考 (如果大樂透也這樣就好了), 連中兩題. 每年評鑑來到時都會讓我想起 2014 年當委員, 結束那天我沒參加, 因為母親淋巴癌復發住院了. 每年評鑑不會讓我緊張, 而是會讓我傷感.

今天在 Line 上看到研究所的同學傳來的聚會照片, 其中一張是婉淑跟小朋友彈電子琴, 讓我想到家中那台小狐狸們早已不彈的 YAMAHA ELECTONE, 放在家裡還蠻占空間的. 還好婉淑說要這台, 那下周就找個時間整理一下, 她弟弟有休旅車, 可來高雄載回去. 當初買這台中古電子琴主要是買給菁菁上 YAMAHA 音樂課用的, 花了大概 5000 元.

周四晚上去家樂福買了一個單人蒙古包式蚊帳, 昨天帶回鄉下, 換掉舊蚊帳後感覺煥然一新, 不再怕惡蚊入侵, 所以昨晚睡了個好覺.


Kickstarter 上的自製綠能控制器

今天在 G+ 社群看到有人分享 Kickstarter 上的自製綠能控制器提案, 可接風能與太陽能輸入與充電控制電路向鋰電池充電, 提供 Raspberry Pi 或 Arduino 板子運作所需之電源.

# SunControl - DIY Solar Power for the Raspberry Pi / Arduino


此控制器特點是能在電力不足時關閉 Raspberry Pi 或 Arduino 以避免電路板 SD 卡因電力突然中斷而受損, 並透過內建 Watch Dog Timer 提高系統穩定度.

MicroPython 使用 ampy 突然無法上傳檔案問題

昨天被這個惱人的問題搞了一個下午, 當我正在測試 MicroPython 的 WDT 功能時, 欲將改好的 main.py 程式用 ampy 上傳到 ESP8266 模組時, 卻出現如下錯誤訊息 :

D:\test>ampy --port COM4 put main.py
Traceback (most recent call last):
  File "c:\python36\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "c:\python36\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Python36\Scripts\ampy.exe\__main__.py", line 9, in <module>
  File "c:\python36\lib\site-packages\click\core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "c:\python36\lib\site-packages\click\core.py", line 697, in main
    rv = self.invoke(ctx)
  File "c:\python36\lib\site-packages\click\core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "c:\python36\lib\site-packages\click\core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "c:\python36\lib\site-packages\click\core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "c:\python36\lib\site-packages\ampy\cli.py", line 208, in put
    board_files.put(remote, infile.read())
  File "c:\python36\lib\site-packages\ampy\files.py", line 139, in put
    self._pyboard.exec_("f.write({0})".format(chunk))
  File "c:\python36\lib\site-packages\ampy\pyboard.py", line 257, in exec_
    raise PyboardError('exception', ret, ret_err)
ampy.pyboard.PyboardError: ('exception', b'', b'Traceback (most recent call last):\r\n  File "<stdin>", line 1, in <module>\r\nOSError: 28\r\n')

也有人跟我遇到一樣的問題, 但他的是 OSError 5 :

I can not copy files to the board #21

這仁兄說他不確定是檔案系統出問題還是韌體壞了, 總之重灌韌體就解決問題了 :

"I have solved the problem by updating MicroPython v1.8.7-672-g11a9620 on 2017-04-28. I'm not sure if it was the file system or bad firmware, maybe a bad parameter when uploading the firmware."

但我遇到的這個 OSError 28 是啥意思呢? 我在下面這篇文章中查到這似乎是 "No space left on device" 之意, 應該跟檔案系統有關, 可以從  os.statvfs('') 指令查看個檔案的 block size 大小 :

https://forum.micropython.org/viewtopic.php?t=2615

所以我也決定重灌, 果真重灌後 ampy 就恢復正常可以上傳檔案了. 我猜應該是我的 ESP-01 檔案系統又完蛋了, 導致 ampy 要上傳檔案時發現檔案系統無可用空間就發生錯誤了.

重灌後我用 os.statvfs('') 查詢記憶體空間狀態結果如下  :

MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.
>>> import os
>>> os.statvfs('')
(4096, 4096, 92, 90, 90, 0, 0, 0, 0, 255)

此函數是一個查詢指定路徑之檔案系統狀態之系統呼叫, 傳回值為 10 個元素的元組 :

(f_bsize, f_frsize, f_blocks, f_bfree, f_bavail, f_files, f_ffree, f_favail, f_flag, f_namemax)

其定義是 :

f_bsize: 希望的檔案系統 block 數 (資料區塊)
f_frsize: 基本的檔案系統 block 數
f_blocks: 檔案系統全部 block 數
f_bfree: 可用的 block 數
f_bavail: 一般使用者可用之 block 數
f_files: file node 總數 (節點)
f_ffree: 可用之 file node 總數
f_favail: 一般使用者可用之 file node 數
f_flag: 掛載旗標
f_namemax: 檔案名稱最長長度 (字元)

所以重灌後 (4096, 4096, 92, 90, 90, 0, 0, 0, 0, 255)  全部區塊數為 92 個, 可用為 90 個.

參考  :

https://www.tutorialspoint.com/python/os_statvfs.htm
https://docs.python.org/3.4/library/os.html

不過我在 Python 2.x 版文件中卻看到 os.statvfs() 已經被棄置不用了 :

https://docs.python.org/2/library/statvfs.html

"Deprecated since version 2.6: The statvfs module has been removed in Python 3."

奇怪, MicroPython 是 Python 3 也還有這個函數哩!

MicroPython 的檔案系統還可以用 os.stat() 來觀察, 例如 :

for f in os.listdir():   
    print('File: {} stats: {}'.format(f, os.stat(f)))  

這裡利用拜訪 os.listdir() 傳回的串列, 將其元素 (檔案名稱) 傳給 os.stat() 即可顯示該檔案之狀態. 測試結果如下 :

>>> for f in os.listdir():
...     print('File: {} stats: {}'.format(f, os.stat(f)))
...
...
...
File: boot.py stats: (32768, 0, 0, 0, 0, 0, 160, 5346, 5346, 5346)
File: webrepl_cfg.py stats: (32768, 0, 0, 0, 0, 0, 16, 5346, 5346, 5346)
File: main.py stats: (32768, 0, 0, 0, 0, 0, 394, 602, 602, 602)
File: lib stats: (16384, 0, 0, 0, 0, 0, 0, 6288, 6288, 6288)
File: test.txt stats: (32768, 0, 0, 0, 0, 0, 5, 9522, 9522, 9522)

這個 os.stat() 函數會傳回 10 元素的元組, 其中第一個元素標示此為檔案或目錄, 在 MicroPython 檔案為 32768, 目錄為 16384. 第七個元素為檔案大小 (bytes 數), 其中 lib 目錄大小為 0 byte (空目錄).

參考 :

https://forum.micropython.org/viewtopic.php?t=2615
https://docs.python.org/3/library/os.html#os.stat

2017-06-26 補充 :

如果重灌韌體後於 REPL 中使用 os.listdir() 指令查看檔案列表發現有一堆 \x00 如下 :

>>> import os
>>> os.listdir()
['\x00\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00', '\x00\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00',
....... (一堆 \x00)


這表示檔案系統已經局部毀損了, 重灌韌體並無法解決檔案系統受損問題, 必須利用下列程式碼重建檔案系統 :

>>> from flashbdev import bdev
>>> uos.VfsFat.mkfs(bdev)
>>> vfs=uos.VfsFat(bdev)
>>> with open("/boot.py", "w") as f:
...     f.write("""\
...     import gc
...     gc.collect()
...     """)
...     f.close()
...

...

重建完成後, 那些 \x00 就不見了, 檔案系統下就只有一個 boot.py :

>>> import os
>>> os.listdir()
['boot.py']

boot.py 內容為 :

import gc
gc.collect()

然後上傳基本架構的 main.py :

#main.py
import network
def connect_wifi(ssid, pwd):
    wlan=network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('connecting to network ...')
        wlan.connect(ssid, pwd)
        while not wlan.isconnected():
            pass
    print('Connected:', wlan.ifconfig())

def get_ip():
  return (network.WLAN(network.STA_IF).ifconfig()[0],
  network.WLAN(network.AP_IF).ifconfig()[0])

def ap_on():
  network.WLAN(network.AP_IF).active(True)

def ap_off():
  network.WLAN(network.AP_IF).active(False)


參考 :

MicroPython v1.9.1 版韌體測試

拋棄 NodeMCU 的四個理由

今天看到下面這篇文章讓我對 NodeMCU 從 "有時間試試看" 轉為 "那就先擱著吧" :

# 4 reasons I abandoned NodeMCU/Lua for ESP8266

作者列舉了四個讓他拋棄 NodeMCU 的原因 :
  1. 記憶體常不敷應用程式使用 :
    可能韌體太佔空間或記憶體管理不佳所致, 解譯器連註解也會佔空間. 我之前在寫 Arduino 程式也常為記憶體不夠用而感到苦惱.  
  2. 做伺服器用時, NodeMCU 常對 HTTP 要求無反應 :
    這可能也是記憶體不足所致.
  3. 當機 :
    常常要 Restart 很麻煩.
  4. 有其他更好的解決方案
確實有其他或許較好的方案, 例如用 Arduino IDE 來開發, 或者目前高速發展中的 MicroPython. 這篇文章發布於 2015 年, 我相信 NodeMCU 現在應該已經改進了不少才對, 等我 MicroPython 與 Arduino IDE 測試完實際玩看看便知.

2017年6月23日 星期五

向博客來購買 "情感機器"

由於前幾天讀完松尾豐的人工智慧入門書時找到明斯基十年前寫的 "Emotion Machine", 而且發現博客來有賣此書的簡體翻譯版, 昨天向博客來下訂購買了這本 "情感機器" :


我想看看明斯基如何解析人類的情志, 未來有無可能在機器上模擬意識與感情?

2017年6月21日 星期三

MicroPython on ESP8266 (十三) : DHT11 溫溼度感測器測試

月初做完 MicroPython 的 PIR 移動偵測實驗後, 我的魔爪就伸向了 DHT11 溫溼度感測器. 但因為這實驗的進階版是要把溫溼度資料上傳到物聯網伺服器如 ThingSpeak 或 Xively 等, 必須要用到 HTTP 應用層協定, 所以我將進行一半的 DHT11 實驗暫停, 轉而先測試 MicroPython 的網路功能, 花了三周先後測試了 socket, urllib.request, 以及 requests 模組, 主要聚焦在 HTTP 的 GET 與 POST 方法測試. 而現在正是回過頭來把 DHT11 物聯網實驗做完的時候了.

DHT11 + ThingSpeak 是物聯網實驗典型必做項目, 只要一個 DHT11 模組即可架構一個小小氣象測候站. 參考我之前在 Arduino 上所做的測試紀錄 :

# Arduino 溫濕度感測器 DHT11 測試
# 以 Arduino+ESP8266 物聯網模組重作 DHT11 溫溼度實驗

現在則是要把同樣的試驗移植到我的新歡 ~ MicroPython + ESP8266 上面, 這是因為 ESP8266 就是一個內建 WiFi 網路功能的 32 位元 MPU, 比起 8 位元的 Arduino 而言功能與效能強多了, 加上 MicroPython 與檔案系統的加持, 不僅使整個系統最小化, Python 程式可隨時改隨時上傳, 不需要像 Arduino 每次都要覆寫韌體, 操作介面與環境方便多了.

本系列之前的測試紀錄參考 :

MicroPython on ESP8266 (二) : 數值型別測試
MicroPython on ESP8266 (三) : 序列型別測試
MicroPython on ESP8266 (四) : 字典與集合型別測試
MicroPython on ESP8266 (五) : WiFi 連線與 WebREPL 測試
MicroPython on ESP8266 (六) : 檔案系統測試
MicroPython on ESP8266 (七) : 時間日期測試
MicroPython on ESP8266 (八) : GPIO 測試
# MicroPython on ESP8266 (九) : PIR 紅外線移動偵測
MicroPython on ESP8266 (十) : socket 模組測試
MicroPython on ESP8266 (十一) : urllib.urequest 模組測試
# MicroPython on ESP8266 (十二) : urequests 模組測試

MicroPython 文件參考 :

MicroPython tutorial for ESP8266  (官方教學)
http://docs.micropython.org/en/latest/micropython-esp8266.pdf
http://docs.micropython.org/en/latest/pyboard/library/usocket.html#class-socket
http://docs.micropython.org/en/v1.8.7/esp8266/library/usocket.html#module-usocket
https://gist.github.com/xyb/9a4c8d7fba92e6e3761a (驅動程式)

DHT11 模組有四隻腳, 包含電源 Vcc 與 GND, 數據輸出 Data 以及一隻用不到的腳 (在 I2C 模式才會用到). 電源 Vcc 可吃 3~5V, 因此吃 3.3V 的 ESP8266 可直用 (Data 腳可直連 GPIO 腳), 其接腳定義如下 :
為了使測量數據穩定, 最好在 Vcc 與 Data 腳之間並接一個 10K 歐姆電阻 (棕黑橙); 在 Vcc 與 GND 之間並接一個 0.1uF 電容 (104).

在 MicroPython 上使用 DHT11 很方便, 因為其功能已經被寫進 MicroPython 的內建模組 dht 了, 參考 :

12. Temperature and Humidity

首先匯入 dht 模組與 Pin 模組 (綁定哪一支 GPIO 腳擷取 DHT11 輸出之數據) :

import dht
from machine import Pin

接著就可以呼叫 dht 模組的 DHT 類別建構式來建立 DHT11 物件, 傳入要綁定之 GPIO 腳之 Pin 物件 :

d=dht.DHT11(Pin(0))    #綁定 GPIO 0 為 DHT11 數據輸入腳

不過在讀取溫濕度數據前必須先呼叫 DHT11 物件的 measure() 方法來擷取測量結果, 這樣才能讀取到最新的數據 :

d.measure()    #更新測量結果

這樣就可以讀取溫溼度數據了 :

t=d.temperature()     #傳回攝氏溫度
h=d.humidity()         #傳回相對溼度 (%)

注意, DHT11 的測量頻率是至多每秒一次, 亦即 measure() 方法每秒只能呼叫一次 :

"The DHT11 can be called no more than once per second and the DHT22 once every two seconds for most accurate results."

下面程式以每兩秒一次頻率更新所測量到之溫溼度 :

測試 1 : 連續測量溫溼度

from machine import Pin
import dht    
import time

p0=Pin(0, Pin.IN)
d=dht.DHT11(p0)        #建立 DHT11 物件

while True:
    d.measure()                  #重新測量溫溼度
    t=d.temperature()        #讀取攝氏溫度
    h=d.humidity()             #讀取相對溼度
    print('Temperature=', t, 'C', 'Humidity=', h, '%')
    time.sleep(2)                  #暫停 2 秒

將檔案存成 main.py 後用 ampy 等工具上傳 ESP8266 模組, 參考 :

MicroPython on ESP8266 (六) : 檔案系統測試

然後開啟 Putty 按 Ctrl+D 軟開機即可看到每兩秒輸出的溫溼度測量結果 :


用吹風機吹一下, 就會看到濕度下降, 溫度上升的變化了.

接下來我想把溫濕度測量資料透過 WiFi 網路記錄在物聯網網站 ThingSpeak, 參考以前 Arduino 的測試紀錄 :

ESP8266 WiFi 模組與 Arduino 連線測試 (ThingSpeak 初體驗)
以 Arduino+ESP8266 物聯網模組重作 DHT11 溫溼度實驗

我的 ThingSpeak 帳號已經設定好如下五個資料通道 (channels) 了 :


這裡會用到 field1 (攝氏溫度), field1 (華氏溫度) 與 field3 (濕度) 這三個通道, 由於 MicroPython 的 dht 模組的 DHT11.temperature() 僅提供攝氏溫度, 所以如果要記錄華氏溫度於 filed2 的話須另外用公式轉換, 換算公式如下 :

F=C*9/5 + 32

亦即華氏度數是攝氏的 9/5 倍再加 32.

物聯網伺服器 ThingSpeak 通道的 HTTP GET 寫入格式例如 :

GET /update?api_key=NO5N8C7T2KINFCQE&field1=29.00&field2=84.20&field3=73.00

亦即是利用 HTTP 的 GET 方法傳遞溫濕度資料給 ThingSpeak 伺服器的 update 程式執行資料庫寫入動作, 這裡 api_key 是我在 ThingSpeak 上一個帳號的的資料庫寫入認證碼 (讀取認證碼是另外一個, 用於向 ThingSpeak 讀取資料用).

此處我們要用 urequests 模組的 get() 方法向 ThingSpeak 伺服器提出 GET 請求, 必須將 url 傳進 get() 裡面, 因此必須知道 ThingSpeak 伺服器的 IP 或網域名稱, 可以使用 http://184.106.153.149 或 http://api.thingspeak.com, 這樣就可以組成 URL 字串了 :

host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'
url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s' %(host, api_key, t, f, h)
r=urequests.get(url)

這裡 t, f, h 就是上面測試 1 中取得的攝氏溫度, 華氏溫度, 以及濕度.

完整的程式如下列測試 2 :

測試 2 : 將溫濕度記錄在 ThingSpeak 網站

#main.py
ffrom machine import Pin
import dht  
import time
import urequests

p0=Pin(0, Pin.IN)
d=dht.DHT11(p0)
host='http://api.thingspeak.com'      
api_key='NO5N8C7T2KINFCQE'  

while True:
    d.measure()                
    t=d.temperature()      
    f=round(t * 9/5 + 32)
    h=d.humidity()
    url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s' %(host, api_key, t, f, h)  
    print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%')
    #print('url=', url)  
    r=urequests.get(url)  
    print('response=', r.text)
    time.sleep(16)  

注意, 這裡迴圈裡的延遲改為 16 秒是為了符合 ThingSpeak 的要求, 即每個通道的資料更新週期必須大於 15 秒, 參考 :

Data points and frequency

"there's no limit to the number of data points, but a Channel can only be updated every 15s"

將此程式存成 main.py 後用 ampy 上傳到 ESP8266 模組 :

D:\test>ampy --port COM4 put main.py

然後開啟 Putty, 按 Ctrl+D 軟啟動後就會重新執行剛上傳的 main.py.

REPL 介面輸出訊息如下 :

MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.
>>>
PYB: soft reboot
#22 ets_task(40100164, 3, 3fff829c, 4)
Temperature= 31 C / 88 F Humidity= 38 %
response= 29884
Temperature= 31 C / 88 F Humidity= 37 %
response= 29885
Temperature= 31 C / 88 F Humidity= 37 %
response= 29886
Temperature= 31 C / 88 F Humidity= 37 %
response= 29887
Temperature= 31 C / 88 F Humidity= 37 %
response= 29888
Temperature= 31 C / 88 F Humidity= 43 %
response= 29889
Temperature= 31 C / 88 F Humidity= 37 %
response= 29890
Temperature= 31 C / 88 F Humidity= 37 %
response= 29891
Temperature= 31 C / 88 F Humidity= 37 %
response= 29892

這裡 response 為伺服器新增一筆紀錄後所傳回的該筆資料之 key, 可見已經儲存近 3 萬筆資料了. ThingSpeak 網頁上的溫濕度動態圖表如下 :


接下來為了在電腦關機時能知道整個程式還有在跑沒當掉, 我在 GPIO 2 腳上接了一個 220 歐姆電阻串接一個 LED 後接地, 然後參考下列這篇 :

MicroPython on ESP8266 (九) : PIR 紅外線移動偵測

我定義了一個 LED_blink() 函數的進階版, 可以傳一個設定 LED 要持續閃爍幾秒之參數進去, 用 LED_blink(16) 取代等待 ThingSpeak 更新週期限制的 time.sleep(16), 亦即同樣是等待 16 秒, 但現在等待期間是由閃爍 LED 來代替, 如下列測試 3 所示 :

測試 3 : 等待 ThingSpeak 更新週期時閃爍 LED

from machine import Pin
import dht  
import time
import urequests

p0=Pin(0, Pin.IN)
p2=Pin(2, Pin.OUT)    
d=dht.DHT11(p0)
host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'

def LED_blink(s):    
    for i in range(1,10*s):    
        p2.value(1)   
        time.sleep_ms(50)        
        p2.value(0)
        time.sleep_ms(50)    

while True:
    d.measure()                
    t=d.temperature()      
    f=round(t * 9/5 + 32)
    h=d.humidity()
    url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s' %(host, api_key, t, f, h)
    print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%')
    try:
        r=urequests.get(url)
        print('response=', r.text)
    except:
        print('urequests.get() exception occurred!')
    LED_blink(16)

這裡將 urequests.get() 放在 try except 裡面是因為網路存取跟檔案存取一樣可能會出現例外, 當例外出現時將使整個程式停止執行. 為了避免一次 GET 要求失敗就讓程式垮掉, 務必將其放在 try except 裡面偵錯.

實際測試發現在量測溫濕度並上傳 ThingSpeak 伺服器時 LED 會停止閃爍約 1 秒, 傳完又繼續快速閃爍 16 秒. 這樣即使電腦關機無法從 REPL 或 ThingSpeak 網站觀察系統是否正常運作, 看到 LED 閃爍即知 ESP8266 還在正常運作.

如果要在手機上觀看 ThingSpeak 資料, 可以到 Google Play 下載 ThingView 這個 App :


開啟 App 後按底下的 + ㄒ新增頻道 (Channel) :



輸入 ThingSpeak 的 Channel ID, 就會顯示我的 Atmosphere 通道了.


點擊 Atmosphere 通道就會顯示此通道的各 field 圖表. 不過此多欄位圖形不會自動更新, 必須按右下角藍色 Refresh 鍵才會更新資料 (只有顯示單一欄位時才會自動更新).


點擊任何一張 field 的圖即顯示單一 field :


同樣地, 按右下角藍色 Refresh 鍵會更新此欄位資料 (但也不是很靈光), 但也可以在 Settings 中設定自動更新週期 , 勾選 "Auto refresh charts", 點 "Auto refresh time" 可設定週期 :

這個 DHT11 實驗延宕了三周了, 昨晚因業務需要去加班, 早上回來補眠卻睡不著, 乾脆就一口氣把 ThingSpeak 實驗做完了, 使用 ESP8266+MicroPython 真是簡潔啊! 難怪之前有網友說我用 Arduino+ESP8266 是 "見樹不見林", 何不單用 ESP8266+NodeMCU? 我覺得他說的是有道理的. 雖然以後不太可能這麼用, 不過那時我的目的是要藉此學習 Arduino 的各種可能罷了. 人生也是如此, 有時無用是為大用. 

參考 :

# Interfacing the DHT11/DHT22 Humidity/Temperature sensor
update data to thingspeak
MicroPython on ESP8266: sending data to ThingSpeak
5.1. Star Wars Asciimation
https://www.slideshare.net/ParshwadeepLahane/remote-temperature-monitor-dht11
micropython socket example
# Controlling relays using Micropython and an ESP8266

2017-06-24 補充 :

上面測試 3 的程式跑了一天後突然停住了, 觀察 REPL 介面, 發現是在執行 main.py 的第 20 行時發生例外, 導致程式跳掉 :

response= 37003
Temperature= 30 C / 86 F Humidity= 50 %
response= 37004
Traceback (most recent call last):
  File "main.py", line 20, in <module>
  File "dht.py", line 13, in measure
OSError: [Errno 110] ETIMEDOUT

MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.

測試 3 的第 20 行是 d.measure() 指令, 可見是測量 DHT11 的指令因 timeout 而跳掉了. 因此應該把 try 的範圍擴大到 d.measure() 才行, 如下列測試 4 :

from machine import Pin
import dht  
import time
import urequests

p0=Pin(0, Pin.IN)
p2=Pin(2, Pin.OUT)
d=dht.DHT11(p0)
host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'

def LED_blink(s):
    for i in range(1,10*s):
        p2.value(1)
        time.sleep_ms(50)  
        p2.value(0)
        time.sleep_ms(50)

while True:
    try:
        d.measure()                  
        t=d.temperature()        
        f=round(t * 9/5 + 32)   
        h=d.humidity() 
        url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s' %(host, api_key, t, f, h)
        print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%')
        r=urequests.get(url)
        print('response=', r.text)
    except:
        print('urequests.get() exception occurred!')  
    LED_blink(16)

其實應該用 WDT (watchdog timer) 來監控系統是否有正常執行才對, 應用程式必須在時限內通知 WDT, 否則 WDT 會認為程式已崩潰而 Reset 系統.

MicroPython 的 machine 模組有支援 WDT, 參考 :

# class WDT – watchdog timer
http://docs.micropython.org/en/latest/micropython-esp8266.pdf  (搜尋 WDT)

但此類別底下註明 "Availability of this class: pyboard, WiPy.", 似乎不支援 ESP-01, 我實際測試雖然可建立 WDT 實體, 但卻無法設定 timeout, 目前在 ESP-01 確實無作用 :

>>> from machine import WDT
>>> dir(WDT)                               #顯示 WDT 型別的方法
['feed', 'deinit']                                               #實作 feed() 與 deinit() 方法
>>> wdt=WDT(timeout=5000)     #無法設定 timeout
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function does not take keyword arguments     #不需參數?
>>> wdt=WDT(id=-1,timeout=2000)    #無法設定 timeout
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function does not take keyword arguments
>>> wdt=WDT()                            #無參數就可以順利建立 WDT 物件
>>> wdt
<WDT>

參考 :

ESP8266: Watchdog functions
https://forum.micropython.org/viewtopic.php?f=16&t=2788&p=16576
https://www.tapatalk.com/topic/182447-micropython-forum/3221-watchdog-in-esp8266

2017年6月20日 星期二

2017 年第 24 周記事 : 人蚊大戰

本周因姊姊說有東西放在鄉下, 且梅雨季要去圖書館也不方便, 所以就跟我一起回鄉下了. 周六晚上吃過晚飯又下起大雨, 打在屋頂轟轟地響, 可能是風雨中的寧靜對比, 夏天的雨夜總讓我有一種舒暢的感覺.

但是回到屋內卻發現跑進很多小蚊子, 害我無法專心寫程式, 虛度了一個美好的雨夜. 更糟的是睡到半夜被野貓的叫春聲吵醒, 與以前聽到的不同, 這更像是妖魔鬼怪的叫聲. 可怕的還不是這個, 起身要上廁所才發現蚊帳裡竟然蚊子亂飛, 少說也有 10 隻, 原來蚊帳入口被我翻身時扯開, 大軍長驅直入, 是我太好睡了竟渾然不覺讓牠們飽餐到四點.

我與蚊子冤仇久矣, 看到這些吸得鼓鼓的冤家豈有輕饒之理, 通通甕中捉鱉全部殲滅, 搞得我滿手血腥. 蚊子是萬惡之蟲, 比起蟑螂與蒼蠅, 我痛恨蚊子超過千萬倍. 既要吸人血, 又要讓人癢得難受, 真是可惡至極. 本周一定要去家樂福買頂蒙古包式的蚊帳來防蚊才行.

週日下午小舅來時, 請他幫我將媽那台二行程美的 90 機車大牌拆下, 準備下周帶到高雄請袁老闆報廢換購 CUE 100 新車. 小舅問說汰換是否可以只繳回牌照, 機車留下來紀念, 我說可能無法補助新購機車或減少補助, 要問看看. 昨日打電話問袁老闆, 他說舊車若不回收就完全無法補助, 說照相紀念就可以了. 我又打給鄉下家附近的機車行, 他說辦到好 46500. 昨晚跟爸討論結果, 雖然比高雄貴 500 元, 還是決定讓鄉下的機車行回收好了.

# 105年汰舊二行程機車補助

四株芒果樹上周就開始收成了, 這兩周以來每天都吃芒果. 因為沒噴農藥只套袋, 所以外觀有些不好, 要送人得挑好看點的. 從同事家裡移植的六棵鳳梨種了兩年剩下三棵, 這幾天終於表皮變黃熟了, 但有兩棵摘下來後沒馬上切來吃, 竟放到壞掉, 真是可惜. 剩下一棵還沒採的可不能再浪費了.

2017年6月19日 星期一

MicroPython on ESP8266 (十二) : urequests 模組測試

做完 urllib.urequest 模組的測試後, 我又在 "精通 Python (Oreilly 出版)" 這本書的第九章後面看到作者介紹第三方函式庫 requests, 與 urllib.urequest 模組一樣, 可以很方便地處理 HTTP 請求. 在 CPython 可以利用下列指令安裝 requests :

 C:\Users\Tony>pip3 install requests

MicroPython 已經實作了 requests 的子集, 稱為 urequests (不可用 requests), 直接匯入即可使用 :

import urequests

此模組所實作的方法可用 dir() 顯示 :

>>> import urequests
>>> dir(urequests)
['Response', 'patch', '__name__', 'request', 'delete', 'get', 'head', 'post', 'put', 'usocket']

可見 HTTP 的主要方法 GET, POST, HEAD, PUT, DELETE 等都有支援.

雖然 urequests 並非完全實作 requests, 用法仍可參考官網文件 :

Requests: HTTP for Humans

下列文件則有許多範例 :

HTTPS POST using urequests.py and Guru Mediation Error
Python爬虫利器一之Requests库的用法
# Wemos D1 mini User's Guide (PDF) (搜尋 HTTP Request)

 本系列測試紀錄前十一篇參考 :

MicroPython on ESP8266 (二) : 數值型別測試
MicroPython on ESP8266 (三) : 序列型別測試
MicroPython on ESP8266 (四) : 字典與集合型別測試
MicroPython on ESP8266 (五) : WiFi 連線與 WebREPL 測試
MicroPython on ESP8266 (六) : 檔案系統測試
MicroPython on ESP8266 (七) : 時間日期測試
MicroPython on ESP8266 (八) : GPIO 測試
MicroPython on ESP8266 (九) : PIR 紅外線移動偵測
MicroPython on ESP8266 (十) : socket 模組測試
# MicroPython on ESP8266 (十一) : urllib.urequest 模組測試

MicroPython 文件參考 :

MicroPython tutorial for ESP8266  (官方教學)
http://docs.micropython.org/en/latest/micropython-esp8266.pdf
http://docs.micropython.org/en/latest/pyboard/library/usocket.html#class-socket
http://docs.micropython.org/en/v1.8.7/esp8266/library/usocket.html#module-usocket
https://gist.github.com/xyb/9a4c8d7fba92e6e3761a (驅動程式)

使用 urequests 模組只要使用 import 匯入, 就可以用 get() 或 post() 等方法提出 GET 或 POST 方法了, 其傳回值是一個 Response 物件, 回應內容就放在其 text 屬性中, 例如 :

>>> import urequests
>>> r=urequests.get("http://micropython.org/ks/test.html")
>>> r
<Response object at 3fff21e0>
>>> type(r)
<class 'Response'>      #get() 方法傳回 Response 物件
>>> type(r.text)
<class 'str'>                 #text 屬性存放的是 str 類型 (字串, utf-8 編碼)
>>> r.text              #顯示 text 內容
'<!DOCTYPE html>\n<html lang="en">\n    <head>\n        <title>Test</title>\n    </head>\n    <body>\n        <h1>Test</h1>\n        It\'s working if you can read this!\n    </body>\n</html>\n'
>>> print(r.text)    #用 print() 格式化輸出
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Test</title>
    </head>
    <body>
        <h1>Test</h1>
        It's working if you can read this!
    </body>
</html>

>>>

這比直接處理 HTTP 協定要簡單省事多了.

除了直接用 text 屬性來讀取回應內容外, 也可以用 Response 物件的 content 屬性, 此屬性存放的是從 socket 讀取的原始二進位回應資料 (bytes 型態) :

>>> type(r.content)  
<class 'bytes'>                   #content 屬性存的是 byte 串流 (已轉為 ASCII)
>>> r.content              #讀取二進位回應資料
b'<!DOCTYPE html>\n<html lang="en">\n    <head>\n        <title>Test</title>\n    </head>\n    <body>\n        <h1>Test</h1>\n        It\'s working if you can read this!\n    </body>\n</html>\n'
>>> print(r.content)     #byte 資料用 print() 不會格式化
b'<!DOCTYPE html>\n<html lang="en">\n    <head>\n        <title>Test</title>\n    </head>\n    <body>\n        <h1>Test</h1>\n        It\'s working if you can read this!\n    </body>\n</html>\n'

另外, Response 物件還有一個存放 socket 物件的 raw 屬性, 也可以透過此物件的 read() 方法讀取回應資料 (也是 bytes 類型) :

>>> r=urequests.get("http://micropython.org/ks/test.html")
>>> type(r.raw)
<class 'socket'>                    #raw 存放的是 socket 物件
>>> r.raw
<socket state=3 timeout=-1 incoming=3fff9ba8 off=0>
>>> r.raw.read()             #讀取全部回應資料 (=r.content)
b'<!DOCTYPE html>\n<html lang="en">\n    <head>\n        <title>Test</title>\n    </head>\n    <body>\n        <h1>Test</h1>\n        It\'s working if you can read this!\n    </body>\n</html>\n'
>>> r.raw.read(20)         #read() 已讀完全部緩衝區, 故為空
b''
>>> r=urequests.get("http://micropython.org/ks/test.html")   #重新 get
>>> r.raw.read(20)         #讀取前面 20 個字元
b'<!DOCTYPE html>\n<htm'
>>>

Response 物件的 status_code 屬性儲存回應狀態, 正常是 200; 而 encoding 屬性存放編碼格式 :

>>> r.status_code
200  
>>> r.encoding
'utf-8'

但是 MicroPython 的 urequests 模組的 Response 物件沒有實作 url 與 headers  等屬性 :

>>> r.url                                   #顯示請求之 url
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Response' object has no attribute 'url'
>>> r.headers                           #顯示全部標頭
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Response' object has no attribute 'headers'
>>> r.headers['content-type']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Response' object has no attribute 'headers'
>>> r.cookies
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>

AttributeError: 'Response' object has no attribute 'cookies'

而在 CPython 中執行結果如下 :

Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> r=requests.get("http://micropython.org/ks/test.html")
>>> r.url                                     #顯示請求之 url
'http://micropython.org/ks/test.html'
>>> r.headers                             #顯示全部標頭
{'Server': 'nginx/1.8.1', 'Date': 'Sun, 18 Jun 2017 23:19:41 GMT', 'Content-Type': 'text/html', 'Last-Modified': 'Tue, 03 Dec 2013 00:16:26 GMT', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'ETag': 'W/"529d22da-b4"', 'Content-Encoding': 'gzip'}
>>> r.headers['content-type']    #顯示指定標頭
'text/html'
>>>

雖然 MicroPython 未實作 headers 屬性, 但可以利用 httpbin.org 所提供的 get/post 方法取得, 它會把請求標頭放在回應訊息內容中以 JSON 格式傳回來, 這就要用到 Response 物件的 json() 方法來處理回應內容了.

Response 物件的 json() 方法可將回應之 json 字串轉成 dict 物件, 但是回應內容必須是例如 '{"a":"1", "b":2"}' 的 JSON 格式字串, 否則會出現語法錯誤, 例如 MicroPython 測試網頁回應內容是 HTML 檔案, 呼叫 json() 方法就會出錯 :

>>> r=urequests.get("http://micropython.org/ks/test.html")
>>> r.json()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "urequests.py", line 30, in json
ValueError: syntax error in JSON

但如果使用 httpbin.org 的 GET 測試網頁 http://httpbin.org/get , 其回應內容為 JSON 格式, 這樣呼叫 Response 物件的 json() 方法就不會出現錯誤訊息, 而是將 JSON 字串轉成 dict 物件傳回. 如果用瀏覽器連線 http://httpbin.org/get 會得到如下回應 :

{
  "args": {}, 
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 
    "Accept-Encoding": "gzip, deflate, sdch", 
    "Accept-Language": "zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4", 
    "Connection": "close", 
    "Cookie": "_gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1; _gauges_unique_day=1", 
    "Host": "httpbin.org", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 OPR/45.0.2552.898"
  }, 
  "origin": "12.174.116.121", 
  "url": "http://httpbin.org/get"
}

httpbin.org 伺服器會將請求訊息的標頭組成 JSON 字串後放在回應訊息中傳回來. 因此若測試 http://httpbin.org/get 的話, 呼叫 json() 方法就不會報錯了 :

>>> import urequests
>>> r=urequests.get('http://httpbin.org/get')
>>> r.json()       #可執行 json() 了
{'url': 'http://httpbin.org/get', 'headers': {'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {}, 'origin': '1.172.116.121'}

GET 方法要傳遞參數的話是在網址後面用 ? 帶領參數字串. 例如在瀏覽器傳遞 a, b 參數給 GET 測試網頁 httpbin.org/get 的 URL 如下 :

http://httpbin.org/get?a=1&b=2

瀏覽器會收到如下回應 :

{
  "args": {
    "a": "1", 
    "b": "2"
  }, 
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 
    "Accept-Encoding": "gzip, deflate, sdch", 
    "Accept-Language": "zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4", 
    "Connection": "close", 
    "Cookie": "_gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1; _gauges_unique_day=1; _gauges_unique_hour=1", 
    "Host": "httpbin.org", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 OPR/45.0.2552.898"
  }, 
  "origin": "12.174.116.121", 
  "url": "http://httpbin.org/get?a=1&b=2"
}

可見參數 a, b 會放在 args 屬性中傳回來.

在 MicroPython 測試結果如下 :

>>> import urequests
>>> r=urequests.get("http://httpbin.org/get?a=1&b=2")   #傳遞參數 a, b
>>> r.text                     #傳回 JSON 格式字串
'{\n  "args": {\n    "a": "1", \n    "b": "2"\n  }, \n  "headers": {\n    "Connection": "close", \n    "Host": "httpbin.org"\n  }, \n  "origin": "223.141.198.23", \n  "url": "http://httpbin.org/get?a=1&b=2"\n}\n'
>>> r.json()                  #呼叫 json() 方法轉成 dict 物件
{'url': 'http://httpbin.org/get?a=1&b=2', 'headers': {'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {'a': '1', 'b': '2'}, 'origin': '223.141.198.23'}
>>> type(r.json())
<class 'dict'>                       #傳回值是 dict 物件
>>> r.json()['args']      #擷取 dict 物件之項目 args
{'a': '1', 'b': '2'}
>>> r.json()['args']['a']  #擷取 dict 內的屬性
'1'

以上是 GET 方法的測試, 接下來看 POST 方法, 參考 requests 官方網站的教學文件 :

http://docs.python-requests.org/en/latest/user/quickstart/#post-a-multipart-encoded-file

亦即只要呼叫 urequests.post() 方法並將要傳遞的參數設給的 data 參數即可. 在 CPython 中, 這個 data 是一個 dict 物件, 傳遞的參數是放在回應訊息的 form 屬性中傳回 :

Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> data={'a':'1','b':'2'}      #dict 物件
>>> r=requests.post("http://httpbin.org/post", data=data)
>>> r.json()
{'args': {}, 'data': '', 'files': {}, 'form': {'a': '1', 'b': '2'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Content-Length': '7', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.13.0'}, 'json': None, 'origin': '12.174.116.121', 'url': 'http://httpbin.org/post'}

但在 MicroPython 用 dict 物件傳送參數卻不行, 會出現 "object with buffer protocol required" 錯誤訊息. 測試結果如下 :

>>> data={'a':'1','b':'2'}       #dict 物件 (在 MicroPython 不行)
>>> r=urequests.post("http://httpbin.org/post", data=data)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "urequests.py", line 104, in post
  File "urequests.py", line 74, in request
TypeError: object with buffer protocol required  

我找到下面這篇文章, 發現其 data 不是直接用 dict 物件, 而是其字串型態 :

# HTTPS POST using urequests.py and Guru Mediation Error

我將 data 改成 dict 物件的字串結果 OK, 所傳遞的參數是放在 data 屬性中傳回來 (而不是在 form 屬性中), 而且屬性值是字串而非 dict 物件, 這樣在實際應用的後端程式 (PHP 等) 必須另外進行字串處理以擷取參數. :

>>> data="{'a':'1','b':'2'}"       #dict 物件的字串
>>> r=urequests.post("http://httpbin.org/post", data=data)
>>> r.text    
'{\n  "args": {}, \n  "data": "{\'a\':\'1\',\'b\':\'2\'}", \n  "files": {}, \n  "form": {}, \n  "headers": {\n    "Connection": "close", \n    "Content-Length": "17", \n    "Host": "httpbin.org"\n  }, \n  "json": null, \n  "origin": "223.141.198.23", \n  "url": "http://httpbin.org/post"\n}\n'
>>> r.json()                              #轉成 dict 物件  
{'files': {}, 'headers': {'Content-Length': '17', 'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {}, 'form': {}, 'origin': '223.141.198.23', 'data': "{'a':'1','b':'2'}", 'json': None, 'url': 'http://httpbin.org/post'}
>>> r.json()['data']                    #data 是個字串 (不是 dict 物件)
"{'a':'1','b':'2'}"

我在下面這篇看到作者使用 json 參數而非 data 傳遞參數, 這樣就可以直接使用 dict 物件來存放欲傳遞之參數, 而且伺服器會把參數放在回應訊息的 json 屬性中傳回, 參考 :

# Building a Slack button with ESP8266/MicroPython (使用 D1 mini)

測試結果如下 :

>>> data={'a':'1','b':'2'}        #dict 物件
>>> r=urequests.post("http://httpbin.org/post", json=data)    #傳遞給 json 參數
>>> r.json()
{'files': {}, 'headers': {'Content-Length': '20', 'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {}, 'form': {}, 'origin': '223.141.170.57', 'data': '{"a": "1", "b": "2"}', 'json': {'a': '1', 'b': '2'}, 'url': 'http://httpbin.org/post'}
>>> r.json()['json']['a']          #json 屬性是 dict 物件
'1'

雖然改用 json 屬性傳遞參數, 但 data 屬性還是在, 可見 data 是似乎指所有傳遞給伺服器的參數字串. 從此例可知, 若要使用 dict 物件傳資料, 則要使用 json 參數; 使用 data 參數的話, 必須將 dict 物件字串化 (序列化). 否則必須借助 json 模組的 dumps() 方法將其序列化, 參考 :

# Python爬虫利器一之Requests库的用法

測試結果如下 :

>>> import json               #匯入 json 模組
>>> import urequests   
>>> data={'a':'1','b':'2'}     #使用 dict 物件
>>> r=urequests.post('http://httpbin.org/post', data=json.dumps(data))  #dict 物件序列化
>>> r.json()
{'files': {}, 'headers': {'Content-Length': '20', 'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {}, 'form': {}, 'origin': '12.174.116.121', 'data': '{"a": "1", "b": "2"}', 'json': {'a': '1', 'b': '2'}, 'url': 'http://httpbin.org/post'}

可見這與上面使用 json 參數結果是一樣的.

另外, 除了上面測試過的 data 與 json 參數外, 呼叫 urequests.post() 方法時也可以將額外的請求標頭以 dict 物件傳遞給 urequests.post() 的 headers 參數, 參考 :

# HTTP POST with Micropython and an ESP8266

但要注意, 這個 headers 參數必須是一個 dict 物件, 不可序列化, 測試結果如下 :

>>> import urequests 
>>> data="{'a':'1','b':'2'}"  
>>> headers={'content-type': 'application/json'}    #dict 物件
>>> r=urequests.post("http://httpbin.org/post", data=data, headers=headers)  
>>> r.json()    
{'files': {}, 'headers': {'Host': 'httpbin.org', 'Content-Length': '17', 'Content-Type': 'application/json', 'Connection': 'close'}, 'args': {}, 'form': {}, 'origin': '223.141.170.52', 'data': "{'a':'1','b':'2'}", 'json': None, 'url': 'http://httpbin.org/post'}

與上面沒有傳 headers 參數比較, 可知除原有的標頭外, 還多了一個 Content-Type 標頭.

最後, 我在下面這篇發現作者使用了 urequests.request() 方法來提出 GET 與 POST 請求, 而不是我們上面使用的 get() 與 post() 方法, 參考 :

# Send data requests http post. **SOLVED**

其實在 requests 官網就有此 request() 物件的說明, 參考 :

http://docs.python-requests.org/en/master/api/#main-interface

在 CPython 中可以用 params, data 或 json 傳遞參數, 用 params 傳遞的話, 參數會放在 args 屬性中傳回, 例如 :

Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> r=requests.request("GET","http://httpbin.org/get?a=1&b=2")
>>> r.json()
{'args': {'a': '1', 'b': '2'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.13.0'}, 'origin': '12.174.116.121', 'url': 'http://httpbin.org/get?a=1&b=2'}       #參數放在 args 與 url 屬性中傳回
>>> data={'a':'1','b':'2'}     #dict 物件
>>> r=requests.request("GET","http://httpbin.org/get", params=data)   #以 params 傳送
>>> r.json()
{'args': {'a': '1', 'b': '2'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.13.0'}, 'origin': '12.174.116.121', 'url': 'http://httpbin.org/get?a=1&b=2'}        #參數放在 args 與 url 屬性中傳回

但是用 data 或 json 的話就很奇怪, 參數似乎沒有傳遞給伺服器 (why?) :

>>> import requests
>>> data={'a':'1','b':'2'}
>>> r=requests.request("GET","http://httpbin.org/get", json=data)
>>> r.json()      #args 參數是空的, 沒有 json 參數
{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Content-Length': '20', 'Content-Type': 'application/json', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.13.0'}, 'origin': '12.174.116.121', 'url': 'http://httpbin.org/get'}
>>> r=requests.request("GET","http://httpbin.org/get", data=data)
>>> r.json()      #args 參數是空的, 沒有 data 參數
{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Content-Length': '7', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.13.0'}, 'origin': '12.174.116.121', 'url': 'http://httpbin.org/get'}

在 MicroPython 中 GET 方法則只能在 URL 中附帶字串, 不能使用 params 參數 (未實作), 而 json 與 data 參數雖可用, 但也跟 CPython 一樣沒有傳送出去 (why?), 例如 :

>>> import urequests
>>> data="{'a':'1','b':'2'}"    
>>> r=urequests.request("GET","http://httpbin.org/get", params=data)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unexpected keyword argument 'params'       #無 params 參數

可見 MicroPython 未實作 params 參數. 而用 data 參數的話雖可執行, 但參數未傳遞出去 :

>>> import urequests 
>>> data="{'a':'1','b':'2'}"    #須為字串
>>> r=urequests.request("GET","http://httpbin.org/get", data=data)
>>> r.json()
{'url': 'http://httpbin.org/get', 'headers': {'Content-Length': '17', 'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {}, 'origin': '12.174.116.121'}

可見 data 參數沒傳送出去, args 屬性為空, 而 data 屬性無蹤影. 用 json 參數亦然 :

>>> import urequests 
>>> data={'a':'1','b':'2'}    #須為 dict 物件
>>> r=urequests.request("GET","http://httpbin.org/get", json=data)
>>> r.json()
{'url': 'http://httpbin.org/get', 'headers': {'Content-Length': '20', 'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {}, 'origin': '12.174.116.121'}

只有使用 URL 字串附帶參數才行, 例如 :

>>> import urequests
>>> r=urequests.request('GET','http://httpbin.org/get?a=1&b=2')  #'GET' 要大寫
>>> r.json()
{'url': 'http://httpbin.org/get?a=1&b=2', 'headers': {'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {'a': '1', 'b': '2'}, 'origin': '12.174.116.121'}

可見參數是放在 args 屬性中傳回來. 總之, 不論 CPython 或 MicroPython, 使用 URL 字串傳送參數是萬無一失的.

而 POST 方法也是 params 參數未實作, 只能用 data 或 json 參數, 測試結果如下 :

>>> import urequests
>>> data={'a':'1','b':'2'}       #json 參數須用 dict 物件
>>> r=urequests.request("POST","http://httpbin.org/post", json=data)
>>> r.json()
{'files': {}, 'headers': {'Content-Length': '20', 'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {}, 'form': {}, 'origin': '12.174.116.121', 'data': '{"a": "1", "b": "2"}', 'json': {'a': '1', 'b': '2'}, 'url': 'http://httpbin.org/post'}
>>> data="{'a':'1','b':'2'}"    #data 參數須用字串
>>> r=urequests.request("POST","http://httpbin.org/post", data=data)
>>> r.json()
{'files': {}, 'headers': {'Content-Length': '17', 'Host': 'httpbin.org', 'Connection': 'close'}, 'args': {}, 'form': {}, 'origin': '12.174.116.121', 'data': "{'a':'1','b':'2'}", 'json': None, 'url': 'http://httpbin.org/post'}

總之, 比起 urllib.urequest 來說, urequests 模組比較簡單易用, 其中又以直接呼叫 get() 與 post() 最方便. 使用 GET 方法必須使用 URL 附帶參數方式傳遞; 而用 POST 方法則可用 data 或 json 參數傳遞.

參考 :

Micropython on ESP8266 Workshop Documentation (搜尋 HTTP Request)
MICROPYTHON ON THE ESP8266: KICKING THE TIRES
micropython/micropython-lib (MicroPython 的第三方模組)
# ESP8266 and MicroPython - Part 1
https://requestb.in/
# Send data requests http post. **SOLVED**


2017-08-31 補充 :

urequests 以 POST 方法傳遞參數時, 不論是用 data 或 json, 字典的元素都要用雙引號括起來, 不要用單引號, 實際用法參考 :

MicroPython on ESP8266 (二十一) : 使用 ThingTweet 傳送推文

2017年6月18日 星期日

CH340G USB 轉 TTL 電路

以前在玩 Arduino 時, 我注意到 Nano 板子上的 USB 轉 TTL 晶片使用的是 CH340G, 此晶片有個好處是插上 USB 槽後 Win 8 以上就會自動搜尋驅動程式, 不需要另外去下載安裝. 因此如果測試板需要使用 USB 介面轉 TTL 的話, CH340G 是首選. Win 7 須手動下載安裝, 參考 :

# Arduino Nano 開機測試

今天在下面這篇文章找到 CH340G 的電路圖, 有空按圖索驥試試看.

DIY單片機程序下載板:用CH340G製作USB轉串口板

所需材料也不貴, CH340G 一顆約 10~20 元, 轉接板一片約 3~6 元, 加上一個晶振與五顆電容, 大概 30 元左右. 我主要是想在製作新的 ESP-01 模組轉接板時順便用 CH340G 加裝 USB 介面. 此晶片唯一的缺點是只有 SOP 16 封裝, 因此需要和在一個轉接板上引出接腳到麵包板或萬用 PCB 上.

CH340G 規格書參考 :

https://www.insidegadgets.com/wp-content/uploads/2016/12/ch340g-datasheet.pdf

露天可以找到 IC 與轉接板 :

# 貼片 CH340G USB轉RS232 串口晶片 Arduino取代 CP2102P L2303HX $17
# SOP16 SSOP16 TSSOP16 貼片轉直插 DIP 0.65/1.27mm 轉接板 10個一拍 $60
# 【Q小奇】SOIC16/SOP16/SSOP16/TSSOP16 轉 DIP16 (B款) $3 (最便宜)
# 【Q小奇】SOIC16/SOP16/SSOP16/TSSOP16 轉DIP16 $4
# CH340G SOP16,FDMS86520L,NS LM98714CCMT TSSOP48 可訂貨 $10
# CH340G USB轉序列晶片 SOP-16 Arduino取代 CP2102P L2303HX $17
# 【Q小奇】 USB 2.0 公頭 轉 DIP 模組 $9
# 【Q小奇】Micro USB 2.0 轉DIP (未焊接轉換板含母座) $8