2017年7月14日 星期五

好書 : 人工智慧的未來

最近中午休息時間比較少看 iPad 上的今周刊與財訊, 因為著迷於閱讀 Ray Kurzweil (雷-庫茲威爾) 寫的 "人工智慧的未來:揭露人類思維的奧祕 (經濟新潮社出版, 陳琇玲譯)", 這本書年初時我從母校高師大圖書館借來後一直沒時間看, 後來因有人預約只好先還. 五月底突然想起這本書, 查詢發現此書已在書架上, 所以又借回來看 :

Source : 城邦

其原文版書名為 "How to create a mind : The secret of human thought revealed", 字面意思是 "如何創造心智", 這就是我認為人工智慧中最困難的部分 : 如何實作出具有意識的機器人? 亦即它必須通過圖靈測試, 讓人類覺得其情緒反應, 性格, 談吐等等與生物人無異. 這本書出版於 2012 年, 中文翻譯本出版於 2015 年, 已經不算新書了.

Source : Amazon

此書原文版博客來有賣, 定價 NT$630 元 :

# How to Create a Mind: The Secret of Human Thought Revealed $630

這本書中文譯筆不錯, 行文很優美流暢, 但內容必須慢慢細看咀嚼, 快不得的. 我打算來回看兩遍之後才開始做筆記. 下面這篇是我找到的英文版摘要 :

# How to create a mind summary

雖然這本書母校有書可借 (市圖與高應大皆無藏書), 但隨時會被預約走而中斷閱讀. 我覺得這本書應該買下來珍藏, 這是一本會常被引用的文獻, 是值得反覆閱讀的好書.

我查詢城邦發現此書有跟松尾豐的人工智慧入門綁在一起賣的套書, 原價 500+360=860, 相當於打 69 折, 蠻划算的 :

# 城邦套書 69 折 593 元 : 人工智慧精選套書(2冊)

Source : 城邦

原作者 Ray(mond) Kurzweil 是知名的發明家, 思想家, 未來學家, 與人工智慧專家, 現任 Google 工程總監, 也是 Google 與 NASA 合作創辦的奇點大學 (Singularity University) 校長. 他成長於紐約皇后區, 雙親是來自奧地利的猶太移民, 父親是知名的音樂家兼指揮, 母親則是視覺藝術家. 他在五歲時就決定成為發明家, 十四歲時就寫了一篇關於大腦新皮質 (Neocortex) 的論文. 少年時從任職於貝爾實驗室的叔叔那裡學到基礎的計算機科學知識後開始撰寫電腦程式. 高中時獲得西屋科學競賽大獎, 並於白宮頒獎典禮上獲得詹森總統的個人祝賀. 他自高中開始與人工智慧研究先驅明斯基通信, 明斯基於 1964 年邀請他到 MIT 拜訪, 並於次年進入 MIT 成為明斯基的學生.

庫茲威爾在 MIT 就學期間便創辦了一家軟體公司, 產品是大學入學諮詢系統, 可將高中畢業生填寫之問卷與各大學入學條件比對以提出選系選校建議. 之後投入光學辨識系統與電子語音合成氣開發, 他是全世界第一台 CCD 平台掃描器的主要發明人, 此外其極為成功的電子合成音樂產品 Kurzweil K250 更為他贏得 2015 年葛萊美技術獎. 他於 2012 年加入 Google, 負責機器學習與語言處理新產品的開發, 亦同時私人受雇於 Google 創辦人之一的 Lary Page, 目標是將自然語言理解帶入 Google 產品中.

維基百科中文版對其介紹很簡略, 詳細要看英文版 :

# Wiki : Ray Kurzweil

庫茲維爾曾於 2007 年於 TED 發表演講 :

# Ray Kurzweil : 科技將如何改造我們


參考 :

# A collection of Kurzweil's Eaasys
# 李飛飛:讓人工智慧學會人類的「情感」,是下個發展突破口
# How the Brain Creates the Mind
# 李開復:從機器的弱點找未來機會,讓 AI 人工智慧當你的「工具箱」
# 【我的財經書架】林博華:人工智慧,究竟是怎麼回事?

2017年7月13日 星期四

好站 : 演算法筆記

今天重讀了王士元與彭剛寫的 "語言, 語音與技術 (香港城市大學出版)" 技術篇第七章的隱馬爾科夫 (Markov, 馬可夫) 模型這章, 由於寫得很簡潔扼要, 看完還是不太懂, 所以我就上網找資料, 找到下面這篇很有價值的文章 :

# 隱藏式馬可夫模型

此文不僅圖文並茂詳細說明馬可夫模型, 還附上 C 語言實作, 對於不擅理論, 只愛實作的我實在是如獲至寶. 此網站是台師大資工系下的演算法筆記的一部分 :

# 演算法筆記

此網站對資工領域的各種 Key words 做了詳盡解說, 雖然還在建構中, 但內容已非常豐富. 作者群希望能獲得有錢人的贊助, 以便能繼續做下去, 底下還有產業界重要訊息的連結, 值得參考 :

# http://www.csie.ntnu.edu.tw/~u91029/about.html

知識的分享需要源源不絕的資金支持才能繼續下去, 經濟是文明發展的基礎, 只有肚子填飽了, 有心人才有餘力去貢獻人群.

2017年7月9日 星期日

2017 年第 27 周記事

本周是小狐狸們暑假第一周, 很快, 一個禮拜就過去了, 下周二哥與菁菁要開始暑輔, 我也開始幫菁菁複習數學, 準備明年的會考. 菁菁成績不佳, 連死背就能得分的社會科也是紅字, 讓我非常擔心. 她實在不是讀書的料, 但要讀餐飲科也須過會考的門檻, 而現在的補習對未來根本沒有用, 只是浪費時間與金錢而已. 我們的國力是這樣在浪費的.

今天看到我的小太陽能板充電控制器上的電壓顯示器亂跳 (其實上週我就注意到了), 拿電表去量發現電池充電端只有 6V 不到, 這對電池壽命有害, 馬上去拿新的控制器來換還是一樣, 這兩台都是同款一起買的. 傍晚到樓頂將最早買的充控 (風機在用) 拿下來替換就正常. 這組後來買的充控根本就是爛貨! 我還一次買三個!


下周要在豬舍廁所門口裝個木製平台來擺蓄電池與充控, 這樣最靠近太陽能板, 傳輸線才不會太長.

2017年7月8日 星期六

免浸泡煮紅豆的方法

昨晚在臉書看到有人分享免浸泡煮紅豆的方法 :

# 超實用:不用炒,不用泡,只要做這個動作,簡單煮出綿密紅豆湯!快試試吧!

由於冰箱還放著一包以前媽給我帶來高雄的紅豆 (她自己從田埂邊收集的), 想說來驗證看看這方法管不管用. 步驟很簡單 :
  1. 紅豆洗淨備用.
  2. 煮一鍋水至沸騰, 倒入放紅豆的容器中滿過紅豆, 放到電鍋中, 外鍋先加三個米杯的水煮. 
  3. 煮至跳開後不要打開, 燜 20 分鐘後, 外鍋再加 1~2 杯水, 再煮至跳開後又燜 20 分鐘後, 取出加糖調至適當甜度即可.
實作結果成功, 此方法真的能將紅豆煮爛. 不過第二次煮時應該要加 2 杯水才會比較爛.





由於菁菁不是很愛紅豆, 所以下次照此方法來煮綠豆看看.

鄉村基礎木作第一課 : 板材介紹與設計圖繪製

今天去木樂園繳費開始上木作課, 二哥說他也想跟去看看, 有興趣或許也可以上. 昨晚有打電話給劉老師, 但可能在忙沒接, 反正上回去拜訪的時候老師說隨到隨上, 而且臉書課表顯示今天有上課.

到工場時老師正在給一位上進階課程的學員講解木材性質, 所以就旁聽了一會兒. 今天人比較多, 有一對看來是中年夫妻, 也是基礎課程, 進度已到組合抽屜了. 他們說去年就來了, 後來有事停了很久, 有點忘記了. 還有一個也是基礎木作的年輕小妹子, 剩下窗板還沒裝上去. 另外一對年輕夫妻是上完基礎課, 設計了一個五斗櫃, 回來租場地工具, 也請老師指點一二.


我的第一堂課是介紹各種板材, 原來原木板不可以直立, 必須平放, 因為直立久了會彎曲.  一般製作家具大都使用松木, 檜木等軟木, 但桌面考慮摩擦與刮痕則要使用如山毛櫸等硬木. 然後老師拿出一張課程目標與空白紙給我, 以及一張本課程目標-小型置物櫃設計草圖, 講解各部分尺寸如何計算, 然後要我自己手繪一張, 列出側板, 天板, 地板, 層板等長寬厚數據. 等我畫好給老師看過, 已經快 12 點了, 裁板子就下次上課再裁了.

2017年7月7日 星期五

購買調乳器與好關鍵 2 組

因為家裡那台熱水瓶會結水垢, 且水一直保持在高溫會有一種味道, 所以很早就想要買一台像以前給小朋友泡奶粉的調乳器來取代. 其實我只需要能泡可可亞, 溫度大約是 50 度, 可以馬上喝的溫水即可. 剛好找到高雄這位賣家 :

# 《本草美女ㄉ家》[超值免運] 亞米兔智慧型調乳器 $799

我本來想買這款萬國牌的, 以前姊姊出生時買的那台現在還放在鄉下使用中, 已經服役 18 年了, 真的非常耐用, 但是無法沸騰, 就固定保溫 50 度 :

# 【萬國牌】【3年保固】BB3S保溫調乳器【台灣製】   $650

另外, 年初買的六盒好關鍵爸說剩下一排, 今天上消費高手再買 2 組 (6 盒), 同樣滿一萬折 1000, 還是 9560 元, 預計周六會寄達 :


我問爸是否有效, 他說還不錯, 所以就繼續吃看看.

2017年7月3日 星期一

MicroPython on ESP8266 (十四) : 網頁伺服器測試

上週做完 ThingSpeak 物聯網測試後, 我想回過頭來用 socket 模組在 ESP8266 模組上建立一個網頁伺服器, 可以提供 Web 網頁服務. 之前在測試 Arduino + ESP8266 的伺服器功能時得到一個結論, 由於 Arduino 運算能力較弱, 當伺服器雖然可用, 但效能不是很好, 無法處理較複雜的網頁. 參考 :

# 利用網頁控制 Arduino (三) : 使用超連結與按鈕
# AllAboutEE 的 ESP8266 伺服器測試 (四) : 完結篇

其實 ESP8266 本身是 32 位元處理器, 比 8 位元的 Arduino ATMEGA328P 強多了, 把 ESP8266 拿來當 Arduino 的上網模組著實是大材小用, ESP8266 本身就能當微控器. 本篇就以之前的 Arduino 測試為範本, 來測試看看 MicroPython on ESP8266 的伺服器功能.

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

# 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 v1.9.1 版韌體測試
# MicroPython on ESP8266 (十) : socket 模組測試
# MicroPython on ESP8266 (十一) : urllib.urequest 模組測試
# MicroPython on ESP8266 (十二) : urequests 模組測試
# MicroPython on ESP8266 (十三) : DHT11 溫溼度感測器測試
# MicroPython 使用 ampy 突然無法上傳檔案問題

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 (驅動程式)

首先來建立一個只會回應 "Hello World" 的簡單網頁伺服器. 參考上面 測試 (十) 關於 socket 的文章, HTTP 應用層是透過 TCP 傳輸層來互相交換訊息的, 要建立伺服器必須先建立一個 TCP socket 物件, 這是要求作業系統分配一個通訊槽 (socket ID) 給應用程式以便進行網路通訊 :

import socket          #匯入 usocket 模組
s=socket.socket()    #未傳入參數預設是建立 TCP Socket, 傳回 socket ID

呼叫 socket 類別的建構式 socket() 預設會建立一個 TCP socket 物件. Socket 有兩種 : 主機當伺服器被動接受連線時所用的 Socket 稱為 Server socket; 主機當用戶端主動與遠端伺服主機建立連線時所用的 Socket 稱為 Client socket. 建立 Socket 時不需要綁定網路位址, 原因是有些應用程式需要一直使用同一位址 (例如伺服器), 但有些程式卻不在乎使用甚麼特定位址 (例如瀏覽器), 需要綁定位址的應用如伺服器程式可以在建立 Socket 物件後再去呼叫 bind() 方法綁定位址.

TCP 傳輸層提供下列 socket 物件方法來進行傳輸服務之基本操作 (primitives), 其中 connect() 專用於 Client socket; bind(), listen(), accept() 這三個專用於 Server socket;  而 send(), recv(), 與 close() 則不論 Client 或 Server socket 都會用到 :

方法 說明
 connect(addr) 與在位址 addr 之遠端 Socket 連線
 send(bytes) 向遠端端傳送資料 (須為 bytes 類型)
 recv(bufsize) 從 Socket 接收資料並儲存於傳回之 bytes 物件
 bind(addr) 將 Socket 綁定到主機的網路位址 addr (IP, Port 之 tuple)
 listen([backlog]) 監聽所綁定通訊埠之連線請求, backlog 為最大等候佇列數
 accept() 接受遠端連線請求, 傳回值為 (socket,addr) 組成之 tuple
 close() 關閉連線

參考 :

# http://docs.micropython.org/en/latest/pyboard/library/usocket.html#methods

這些方法都是源自 BSD UNIX 的 TCP socket 基本操作 (primitives), 在 UNIX 上是執行系統呼叫來完成, MicroPython OS 也實作了這些方法. 注意, 其中的 connect() 與 accept() 是阻斷式 (blocking) 的系統呼叫, 亦即程式執行到這兩個指令時會停住, 控制權轉移到系統呼叫, 等待收到對方資料後, 控制權才會回到程式中繼續往下執行. 例如呼叫 Client socket 的 connect() 後要等待遠端伺服器回應 ACK (連線完成) 後程式才會繼續執行; 而呼叫 Server socket 的 accept() 後則要等到有遠端連線要求進來控制權才會回到程式中.

在伺服器程式中會用到 bind(), listen(), accept(), send(), close() 這五個 socket 物件方法, 其中 bind() 方法是將從作業系統取得的通訊槽 (socket ID) 與網路位址 (IP+Port) 綁定起來, 因為一個主機可能有多個 IP (即多個網路卡), 且多個應用程式會使用同一個 IP, 因此必須靠通訊埠來識別應用程式. 伺服器的特性是被動向客戶端提供服務, 其所使用的 IP 與 Port 必須固定, 因此必須將 socket 與特定網路位址綁定起來; 客戶端程式如瀏覽器則不必綁定位址, 因為與其通訊的伺服器不會在乎客戶端使用甚麼 IP 與 Port.

listen() 方法用來做網路通訊前之準備, 主要是設定連線等候佇列 (queue) 的大小, 並監聽所綁定之通訊埠. 由於伺服器一次只能處理一個連線, 若有多個連線進來, 比較晚進來的會被放在佇列中等候處理. 傳入參數 backlog 就是設定此等候連線之最大數目, 超過的會直接被拒絕連線, 參考 :

# What is “backlog” in TCP connections?

accept() 方法用來檢查所監聽的通訊埠是否有連線進來, 由於伺服器必須不斷地提供服務, 因此 accept() 必須放在無窮迴圈中持續偵測連線. 如果有連線進來, 就向客戶端送出回應訊息 (例如 HTML 網頁). 當進來的連線被服務時, accept() 方法會傳回一個 (socket, address) 元組, 其中的 socket 是 accept() 方法產生的一個新的 (Client) socket 物件, 用來與該連線之遠端客戶通訊 (因為 Server socket 專門用來處理進來的連線), 而 address 則是該連線遠端客戶之網路位址 (IP, Port). 這個新的 socket 是用來與連線進來的遠端客戶溝通的管道, 可呼叫 send() 方法將回應訊息傳遞給客戶端, 例如下面的程式碼會在偵測到連線進來時回應客戶端一個顯示 "Hello! World" 的網頁 :

html='<!DOCTYPE html><html><body>Hello!World</body></html>'
while True:
    cs, addr=s.accept()    #程式停頓 (blocking) 在此等待連線進來, 傳回新 socket : cs
    print('client connected from', addr)    #顯示遠端客戶之網路位址
    cs.send(html)             #利用新的 socket 與遠端客戶通訊
    cs.close()                    #關閉新的 socket

注意, 呼叫 accept() 後程式會停頓在此函數, 直到收到客戶端連線要求為止, 這是因為 accept() 是一個阻塞式系統呼叫. 另外傳送回應訊息是利用 accept() 傳回的 Client socket 而非利用 Server socket, 後者是專門用來監聽客戶端連線的. 下列測試 1 使用 ESP8266 AP 模式的固定 IP=192.168.4.1 當伺服端位址並綁定 80 埠監聽是否有客戶端連線進來, 有的話就回應 "Hello World" 網頁給遠端客戶.

測試 1 : 簡單的 HTTP 網頁伺服器 

#main.py
import socket
addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]

s=socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  #設定 socket 位址可重複使用
s.bind(addr)       #Server socket 綁定本身 AP 的網址與 80 埠
s.listen(5)           #設定連線等候佇列最大連線數目為 5
print('listening on', addr)

html='<!DOCTYPE html><html><body>Hello!World</body></html>'

while True:
    cs, addr=s.accept()    #等候連線進來, 接受後傳回與該連線通訊之新 socket 與遠端位址
    print('client connected from', addr)   #輸出遠端網址
    cs.send(html)    #以網頁回應遠端客戶
    cs.close()           #關閉新 socket
s.close()    #關閉 Server socket

將上面程式存成 main.py, 使用 ampy 上傳到 ESP8266 模組 :

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

然後開啟 Putty 連線 ESP8266, 按 Ctrl+D 軟開機或插拔 ESP8266 電源重新執行 main.py, 由於進入無窮迴圈之故 REPL 介面最初會無反應.

分別使用筆電或手機以 WiFi 連線 ESP8266 的 AP 基地台 (SSID 是 MicroPython-xxxxxx, 其中 xxxxxx 是 MAC 位址後六碼, 而密碼預設是 micropythoN, 注意最後字元是大寫的 N), 會收到伺服器回應的 HTML 碼顯示如下網頁 :

瀏覽器連線前預先使用 Putty 連線 ESP8266, , 但瀏覽器連線 192.168.4.1 後顯示如下輸出訊息 :

client connected from ('192.168.4.2', 12736)
client connected from ('192.168.4.2', 12737)
client connected from ('192.168.4.3', 60557)
client connected from ('192.168.4.3', 60558)

這裡 192.168.4.3 是來自手機 Chrome 瀏覽器的連線, 而 192.168.4.3 則是來自筆電 Chrome 的連線. 注意, 每一次瀏覽 192.168.4.1 都會有兩筆連線要求進來 (port 不同), 其中第一筆才是原始的要求, 第二筆是瀏覽器自動發出的 favicon.ico (網站圖案) 資源要求.

其次, 上面程式中呼叫了 socket 物件的 setsockopt() 方法去設定 SO_REUSEADDR 選項 :

s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

這是必要的設定, 否則所綁定的 IP 位址 192.168.4.1 將無法被重複使用, 再次瀏覽時會出現 "OSError: [Errno 98] EADDRINUSE" 錯誤訊息 :

MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.
>>>
PYB: soft reboot
#8 ets_task(40100164, 3, 3fff829c, 4)
WebREPL is not configured, run 'import webrepl_setup'
Traceback (most recent call last):
  File "main.py", line 7, in <module>
OSError: [Errno 98] EADDRINUSE  
MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.

參考 :

# Python [Errno 98] Address already in use

另外, 如果程式更新上傳後按 Ctrl+D 軟啟動卻出現 "OSError: [Errno 12] ENOMEM" 的錯誤訊息, 這表示記憶體並未刷新, 這時只要關閉 ESP8266 電源後重開即可 :

#7 ets_task(40100164, 3, 3fff829c, 4)
WebREPL is not configured, run 'import webrepl_setup'
Traceback (most recent call last):
  File "main.py", line 8, in <module>
OSError: [Errno 12] ENOMEM
MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.

參考 :

# esp8266 RAM does not seem to be cleared by soft reset

上面測試 1 中, REPL 介面僅顯示連線進來的客戶端之網路位址, 若要顯示遠端所傳送的 HTTP 訊息, 則須呼叫 socket 物件的 recv() 方法, 如下列測試 2-1 所示 :


測試 2-1 : 簡單的 HTTP 網頁伺服器-顯示遠端訊息-使用 recv()

#main.py
import socket
addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]

s=socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)

print('listening on', addr)
html='<!DOCTYPE html><html><body>Hello!
while True:
    cs, addr=s.accept()
    print('client connected from', addr)
    data=cs.recv(1024)                    #從 socket 接收緩衝區讀取資料, 最大 1024 bytes
    print(str(data,'utf8'), end='\n')    #以 utf-8 編碼將 bytes 資料轉成字串
    cs.send(html)
    cs.close()
s.close()

此程式只是在測試 1 中加入如上藍色部分的兩行指令而已, recv() 方法會從 socket 的接收緩衝區讀取資料, 再用 print() 印出來, REPL 介面輸出結果如下 (前兩筆從筆電, 後兩筆從手機) :

client connected from ('192.168.4.2', 13501)
GET / HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

client connected from ('192.168.4.2', 13502)
GET /favicon.ico HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: http://192.168.4.1/
Accept-Encoding: gzip, deflate
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

client connected from ('192.168.4.3', 51625)
GET / HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 4.4.2; Che2-L12 Build/HonorChe2-L12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36
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

client connected from ('192.168.4.3', 51627)
GET /favicon.ico HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
User-Agent: Mozilla/5.0 (Linux; Android 4.4.2; Che2-L12 Build/HonorChe2-L12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36
Accept: image/webp,image/*,*/*;q=0.8
Referer: http://192.168.4.1/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

注意, recv() 的傳回值 data 是 bytes 型的串流, 如果沒有用 str() 函數將 data 以 utf-8 編碼轉成字串, 直接輸出 data 的話, 亦即將上面程式中的 print() 改為如下 :

print(data, end='\n')

則輸出結果為 :

client connected from ('192.168.4.3', 51620)
b'GET / HTTP/1.1\r\nHost: 192.168.4.1\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Linux; Android 4.4.2; Che2-L12 Build/HonorChe2-L12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, sdch\r\nAccept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4\r\n\r\n'

client connected from ('192.168.4.3', 51621)
b'GET /favicon.ico HTTP/1.1\r\nHost: 192.168.4.1\r\nConnection: keep-alive\r\nUser-Agent: Mozilla/5.0 (Linux; Android 4.4.2; Che2-L12 Build/HonorChe2-L12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36\r\nAccept: image/webp,image/*,*/*;q=0.8\r\nReferer: http://192.168.4.1/\r\nAccept-Encoding: gzip, deflate, sdch\r\nAccept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4\r\n\r\n'

雖然 MicroPython 可以直接使用 recv() 讀取資料, 不需要再使用 makefile() 將串流轉成檔案類型, MicroPython 仍然可用 makefile(), 如下列測試 2-2 所示 :


測試 2-2 : 簡單的 HTTP 網頁伺服器-顯示遠端訊息-使用 makefile()

#main.py
import socket
addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]

s=socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)

print('listening on', addr)
html='<!DOCTYPE html><html><body>Hello!World</body></html>'
while True:
    cs, addr=s.accept()
    print('client connected from', addr)
    cs_file=cs.makefile('rwb', 0)  
    while True:    
        line=cs_file.readline()  
        print(str(line,'utf8'),end='')  
        if not line or line == b'\r\n':  
            break  
    cs.send(html)
    cs.close()
s.close()

上面藍色部分是與測試 2-1 不同處, 首先是呼叫 socket 物件的 makefile() 方法, 傳回值為檔案類型, 因此可以在迴圈中用檔案物件的 readline() 方法逐列讀取, 結果是一樣的 :

client connected from ('192.168.4.3', 13623)
GET / HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

client connected from ('192.168.4.3', 13630)
GET /favicon.ico HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: http://192.168.4.1/
Accept-Encoding: gzip, deflate
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

同樣地, 如果每一列不用 utf-8 編碼轉成字串, print() 改成如下 :

print(line, end='')

則 REPL 輸出變成 :

client connected from ('192.168.4.2', 43461)
b'GET / HTTP/1.1\r\n'
b'Host: 192.168.4.1\r\n'
b'Connection: keep-alive\r\n'
b'Cache-Control: max-age=0\r\n'
b'Upgrade-Insecure-Requests: 1\r\n'
b'User-Agent: Mozilla/5.0 (Linux; Android 4.4.2; Che2-L12 Build/HonorChe2-L12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36\r\n'
b'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n'
b'Accept-Encoding: gzip, deflate, sdch\r\n'
b'Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4\r\n'
b'\r\n'
client connected from ('192.168.4.2', 43462)
b'GET /favicon.ico HTTP/1.1\r\n'
b'Host: 192.168.4.1\r\n'

在上面三個範例的基礎上, 只要增加一個全域變數來記錄連線次數, 就可以製作一個簡易的訪客計數器了, 如下列測試 3 所示 :

測試 3 : 訪客計數器

#main.py
import socket
addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]

s=socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)

counts=0    #全域變數紀錄連線次數
print('listening on', addr)
while True:
    cs, addr=s.accept()
    counts += 1    #計數器增量 1
    print('client connected from', addr)
    data=cs.recv(1024)
    print(str(data,'utf8'), end='\n')
    html='<!DOCTYPE html><html><body>' +\   
         'Visitor counts=' + str(counts) +\  
         '</body></html>'  
    cs.send(html)
    cs.close()
s.close()

此程式利用全域變數累計連線次數, 但是由於每次連線都會伴隨請求 favicon.ico 之故, 每次重新整理網頁會讓訪客次數遞增 2 次 :

以上的三個測試都與硬體無關, 所以我都是在前陣子新購的 ESP8266 Adaptor board (轉接板) 上進行的, 我覺得非常好用, 直接將 ESP-01 模組插在母座上, 再插入電腦 USB 插槽即可 (唯一缺點是接腳沒有針腳可接出, 例如將 GPIO 0 接地以便燒錄韌體) :


接下來是參考官網教學文件範例在 ESP8266 上建立一個 HTTP 網頁伺服器, 當客戶端連線至伺服器時, 伺服器會回應一個 HTML 網頁, 顯示目前 ESP8266 GPIO 各腳位的輸入位準為 0 (Low) 或 1 (High) :

# 5.3. Simple HTTP server

由於我使用 ESP-01 模組測試, 此板只接出 GPIO0 與 GPIO2 兩支輸出入腳, 因此我刪減了 pins 串列所包含的 Pin 數, 原始程式修改如下 :

測試 4 : 建立網頁伺服器顯示 GPIO 輸入腳的狀態

#main.py
import machine
pins=[machine.Pin(i, machine.Pin.IN) for i in (0, 2)]    #GPIO 的 Pin 物件串列

html="""<!DOCTYPE html>
<html>
    <head> <title>ESP8266 Pins</title> </head>
    <body> <h1>ESP8266 Pins</h1>
        <table border="1"> <tr><th>Pin</th><th>Value</th></tr> %s </table>
    </body>
</html>
"""

import socket
addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]
s=socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)
print('listening on', addr)

while True:
    cs, addr=s.accept()
    print('client connected from', addr)
    data=cs.recv(1024)
    print(str(data,'utf8'), end='\n')
    rows=['<tr><td>%s</td><td>%d</td></tr>' % (str(p), p.value()) for p in pins]  
    response=html % '\n'.join(rows)
    cs.send(response)
    cs.close()

s.close()

此程式利用串列內的 for 迴圈建立一個 Pin 物件串列, 這裡因我使用 ESP-01 模組, 所以只有 GPIO 0 與 GPIO 2 兩支腳而已. 關於 GPIO 用法參考 :

# MicroPython on ESP8266 (八) : GPIO 測試

然後使用 Python 長字串建立一個回應網頁字串 html, 這裡使用字串格式化方法將表格內容插入字串中以組成完整之 HTML 碼. 變數 html 中含有格式化字串 %s, 當接收到遠端連線請求時需讀取 GPIO 腳位狀態組成儲存格內容後嵌入 %s 的位置. rows 串列儲存各 GPIO 腳的列資料, 最後使用 str 的 join() 方法以跳行字元將各列黏合起來嵌入 html 字串裡面.

使用手機瀏覽器連線 ESP8266 的 AP (192.168.4.1), 顯示收到的回應網頁內容如下 :

REPL 介面輸出如下 :

client connected from ('192.168.4.2', 36549)
GET /favicon.ico HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
User-Agent: Mozilla/5.0 (Linux; Android 4.4.2; Che2-L12 Build/HonorChe2-L12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36
Accept: image/webp,image/*,*/*;q=0.8
Referer: http://192.168.4.1/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

client connected from ('192.168.4.2', 36552)
GET / HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 4.4.2; Che2-L12 Build/HonorChe2-L12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36
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

用杜邦線將 GPIO0 或 GPIO2 接地再重新整理網頁就可以看到 Value 變成 0 了. 這個實驗的 Arduino 版本參考 :

# 利用網頁控制 Arduino (二)

上面範例是將 ESP-01 的 GPIO0 與 GPIO2 設定為輸入腳來偵測其狀態, 事實上輸出腳也是可以呼叫 value() 方法來讀取其輸出狀態的, 這跟 Arduino 的 Digital Pin 作法是一樣的, 參考 :

# 利用網頁控制 Arduino (三) : 使用超連結與按鈕
# webserver to turn an LED on or off   (Good)

接下來的測試要將 GPIO0, GPIO2 設定為輸出腳, 並在網頁上控制這兩支腳輸出 HIGH 或 LOW 以點亮或熄滅 LED. 因此我在 GPIO 0 與 GPIO 2 腳各串接一個 LED 與 220 歐姆電阻後接地.

我在上面測試 3 的回應網頁中增加一個 Action 欄, 裡面有 HIGH 與 LOW 兩個超連結, 按超連結會讓該 GPIO 輸出相應的準位. 考量 pins 串列中的 Pin 物件字串化後是 'Pin(0)' 與 'Pin(2)', 為了方便我將超連結的格式定義如下 :

<a href='/?Pin(0)=1'>HIGH</a><a href='/?Pin(0)=0'>LOW</a>
<a href='/?Pin(2)=1'>HIGH</a><a href='/?Pin(2)=0'>LOW</a>

超連結位址 /?Pin(0)=1 表示傳遞參數 Pin(0)=1 給伺服器, 點擊這四個超連結會分別向伺服器送出如下的 GET 請求, 我們可以擷取參數 Pin(0) 與 Pin(2) 之值是 1 或 0 來決定將 GPIO 腳設為 HIGH 或 LOW, 但實際上不需要這麼做, 只要搜尋客戶端請求行中是否含有下面黃色部分的 Pattern 即可 :

GET /?Pin(0)=1 HTTP/1.1
GET /?Pin(0)=0 HTTP/1.1
GET /?Pin(2)=1 HTTP/1.1
GET /?Pin(2)=0 HTTP/1.1

HTTP 訊息的請求行中 ? 開頭的字串位於第六個字元 (其索引為 5), 因此只要讀取客戶端請求訊息, 在訊息字串中用 find() 方法搜尋上面黃色部分子字串, 例如搜尋 '?Pin(0)=1' 若傳回索引位置=5, 表示 Pin(0) 的 HIGH 超連結被按下, 可呼叫  Pin(0) 的 value(1) 或 high() 方法將 Pin(0) 設為 HIGH 輸出. 若沒找到 find() 會傳回 -1, 完整程式如下列測試 4 所示 :

測試 4 : 利用網頁超連結控制 GPIO 0/2 輸出以點亮或熄滅 LED

#main.py
import machine
pins=[machine.Pin(i, machine.Pin.OUT) for i in (0, 2)]

html="""<!DOCTYPE html>
<html>
  <head><title>ESP8266 Pins</title></head>
    <body>
      <h1>ESP8266 Pins</h1>
      <table border="1">
        <tr>
          <th>Pin</th>
          <th>Value</th>
          <th>Action</th>
        </tr>
        %s
      </table>
  </body>
</html>
"""

import socket
addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]
s=socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)
print('listening on', addr)

while True:
    cs, addr=s.accept()
    print('client connected from', addr)
    data=cs.recv(1024)               #讀取 socket 接收緩衝區
    request=str(data,'utf8')          #將 bytes 串流轉成 utf-8 字串
    print(request, end='\n')
    print('?Pin(0)=0:',request.find('?Pin(0)=0'), end='\n')
    print('?Pin(0)=1:',request.find('?Pin(0)=1'), end='\n')
    print('?Pin(2)=0:',request.find('?Pin(2)=0'), end='\n')
    print('?Pin(2)=1:',request.find('?Pin(2)=1'), end='\n')
    rows=[]
    for p in pins:
        set='?%s=1' % str(p)          #設定 GPIO=1 之字串
        reset='?%s=0' % str(p)       #設定 GPIO=0 之字串
        if request.find(set)==5:      #找到此 GPIO 的 set 字串
            p.value(1)                       #將此 GPIO 設為 HIGH
        if request.find(reset)==5:   #找到此 GPIO 的 reset 字串
            p.value(0)                       #將此 GPIO 設為 LOW
        row="""
        <tr>
          <td>
            %s
          </td>
          <td>
            %d
          </td>
          <td>
            <a href='/?%s=1'>HIGH</a>
            <a href='/?%s=0'>LOW</a>
          </td>
        </tr>
        """
        rows.append(row % (str(p), p.value(), str(p), str(p)))    #將列資料存入 rows 串列
    response=html % '\n'.join(rows)     #將表格列資料黏合後嵌入 html 中
    cs.send(response)
    cs.close()

s.close()

這裡因為表格增加的第三欄 Action 內容比較長, 所以我把 for 迴圈從 list 中解放出來, 先建立一個空串列 rows, 然後在迴圈中先用字串的 find() 方法自客戶端請求訊息中搜尋上面提到的特定四個字串, 有找到的話才去進行輸出位準設定. Python 的字串格式化功能實在很好用. 使用手機連線 ESP8266 AP 的固定 IP=192.168.4.1 後以瀏覽器連線 192.168.4.1 結果如下, 直接按 HIGH/LOW 超連結就可以改變 GPIO 0/2 的狀態, 而 LED 也會相應地明滅 :

REPL 介面輸出如下, 第一筆是按下 Pin(0) 的 HIGH 連結之輸出訊息, 而第二筆則是按下 Pin(2) 的 HIGH 連結之輸出訊息, 可見它們的 find() 傳回索引 5 :

client connected from ('192.168.4.2', 37284)
GET /?Pin(0)=1 HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 4.4.2; Che2-L12 Build/HonorChe2-L12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://192.168.4.1/?Pin(2)=0
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

?Pin(0)=0: -1
?Pin(0)=1: 5  
?Pin(2)=0: 368
?Pin(2)=1: -1

client connected from ('192.168.4.2', 37283)
GET /?Pin(2)=1 HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 4.4.2; Che2-L12 Build/HonorChe2-L12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://192.168.4.1/?Pin(0)=1
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

?Pin(0)=0: -1
?Pin(0)=1: 368
?Pin(2)=0: -1
?Pin(2)=1: 5



上面的圖像中, 紅色的 LED 接 GPIO 2, 而綠色 LED 則接 GPIO 0. 由這個範例可知, MicroPython on ESP8266 比 Arduino 更能勝任微型伺服器工作.

最後, 也是最重要的測試是利用 ESP8266 的 AP 模式所綁定的 IP=192.168.4.1 建立一個網頁伺服器, 讓使用者能透過手機來設定其 STA 模式下要連線之無線基地台的 SSID 與 PWD, 這樣就不需要 USB 硬體接線就可以修改連線設定了. 此功能在商品化時有簡化使用者介面之價值, 因為不需要將連線資訊寫死在應用程式中, 也不需要在現場用 USB 連線去設定, 用戶只要用手機就可以將設備與現場無線基地台連線.

測試 5 : 以手機瀏覽器設定欲連線之無線基地台

#main.py
import time
WAIT_FOR_CONNECT=8

def set_ap():
    html="""
    <!DOCTYPE html>
    <html>
      <head><title>AP Setup</title></head>
      <body>
        %s
      </body>
    </html>
    """
    form="""
        <form method=get action='/update_ap'>
          <table border="0">
            <tr>
              <td>SSID</td>
              <td><input name=ssid type=text></td>
            </tr>
            <tr>
              <td>PWD </td>
              <td><input name=pwd type=text></td>
            </tr>
            <tr>
              <td></td>
              <td align=right><input type=submit value=Connect></td>
            </tr>
          </table>
        </form>
    """
    import socket
    addr=socket.getaddrinfo('192.168.4.1', 80)[0][-1]
    s=socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(addr)
    s.listen(5)
    print('listening on', addr)
    while True:
        cs, addr=s.accept()
        print('client connected from', addr)
        data=cs.recv(1024)            
        request=str(data,'utf8')
        print(request, end='\n')
        if request.find('update_ap?') == 5:
            para=request[request.find('ssid='):request.find(' HTTP/')]
            ssid=para.split('&')[0].split('=')[1]
            pwd=para.split('&')[1].split('=')[1]
            sta.connect(ssid,pwd)
            while not sta.isconnected():
                pass
            print('Connected:IP=',sta.ifconfig()[0])
            cs.send(html % 'Connected:IP=' + sta.ifconfig()[0])
        else:
            cs.send(html % form)
        cs.close()
    s.close()

import network
sta=network.WLAN(network.STA_IF)
sta.active(True)
print('Connecting to AP ...')
time.sleep(WAIT_FOR_CONNECT)  
if not sta.isconnected():
    set_ap()
else:
    print('Connected:IP=', sta.ifconfig()[0])
    #Application code is written here or import from a separate file
    #import myapp
    #myapp.main()

此程式先建立 WLAN 物件 sta, 開啟 Station 模式後等待 WAIT_FOR_CONNECT 秒 (預設 8 秒) 後用 isconnected() 方法判斷 ESP8266 是否能連線上一次連線過的無線基地台 (資料存在 ESP8266 晶片本身內建的 Flash 內不會消失), 如果能連上就在 REPL 介面顯示所獲得之 IP, 並開始執行應用程式. 注意, 連線 WiFi 基地台一般在 8 秒內都可以完成, 因此 WAIT_FOR_CONNECT 預設為 8 秒, 若連線速度較慢可放寬.

在 set_ap() 函數中, web 伺服器是透過讀取客戶端 (瀏覽器) 傳送的 HTTP 請求訊息中是否含有 'update_ap?' 字串來判別要回應甚麼頁面給客戶端, 因為預期的 WiFi 設定請求行 (HTTP 訊息的第一行) 如下 :

GET /update_ap?ssid=H30-L02-webbot&pwd=1234567890 HTTP/1.1

如果有找到 'update_ap?' 字串的話, find() 方法會傳回 5, 因為此字串的開頭字元 'u' 是出現在整個回應字串的索引 5. 這時就用 split() 與切片等字串處理方法將所設定的 SSID 與 PWD 擷取出來, 再用 WLAN 物件的 connect() 方法去連線基地台, 並在連線成功後以所獲得的 IP 回應給客戶端. 如果沒找到 'update_ap?' 字串就顯示 WiFi 設定網頁.

將上面的程式存成 main.py 用 ampy 上傳 ESP8266 後須關閉電源重開, 假定 ESP8266 原先 STA 所設定欲連線的 AP 不在收訊範圍, 則程式會呼叫 set_ap() 函數在 192.168.4.1 建立一個網頁伺服器等候客戶端連線, REPL 介面顯示如下 :

bl▒#5 ets_task(40100164, 3, 3fff829c, 4)
listening on ('192.168.4.1', 80)

此時開啟手機瀏覽器連線 ESP8266 的內建 AP (SSID=MicroPython_xxxxxx, PWD=micropythoN), 於網址列輸入 192.168.4.1 即顯示 WiFi AP 設定網頁 :

輸入欲連線 AP 之 SSID 與 PWD, 按 Connect 鈕即更新 ESP8266 本身 Flash 內的 WiFi AP 設定, 等連線成功後即顯示該 AP 所指派之 IP :

REPL 介面輸出訊息如下 :

bl▒#5 ets_task(40100164, 3, 3fff829c, 4)
listening on ('192.168.4.1', 80)  
client connected from ('192.168.4.2', 37937)
GET / HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Safari/537.36
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

client connected from ('192.168.4.2', 37938)
GET /favicon.ico HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Safari/537.36
Accept: image/webp,image/*,*/*;q=0.8
Referer: http://192.168.4.1/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4


client connected from ('192.168.4.2', 37940)
GET /update_ap?ssid=H30-L02-webbot&pwd=1234567890 HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://192.168.4.1/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

Connected:IP= 192.168.43.72  
client connected from ('192.168.4.2', 37941)
GET /favicon.ico HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Safari/537.36
Accept: image/webp,image/*,*/*;q=0.8
Referer: http://192.168.4.1/update_ap?ssid=H30-L02-webbot&pwd=1234567890
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4

因為使用 GET 方法傳送參數, 可見 SSID 與 PWD 都會放在請求行中傳遞.

設定好後 ESP8266 重開機, REPL 介面就不會輸出監聽 80 port 訊息, 而是輸出所獲得之 IP, 因為有連上新設定的無線基地台就不會呼叫 set_ap() 去建立 web 伺服器了 :


由於 ESP8266 會記住最近一次的 WiFi 連線設定, 因此只要此無線基地台在收訊範圍, ESP8266 一開機就會自動連線基地台, 這樣就不會在 192.168.4.1 建立網頁伺服器, 也就無法更改連線設定. 所以測試 5 這個程式沒辦法用在切換目前可連線的兩個基地台, 要切換的話一定要將連線中的那台關掉. 如果 ESP8266 在一個新環境開機, 抓不到之前所設定之基地台, 這樣就可以用手機設定現在可連線之基地台了.

比起之前在 Arduino + ESP8266 時還要耗掉一個 GPIO 腳來控制程式進入 WiFi 設定模式或工作模式, 上面這個解決方案就好太多了, 完全不必為了 WiFi 設定浪費任何 GPIO 腳. 這是測試 MicroPython on ESP8266 以來最讓我開心的收穫.

參考 :

# Network - TCP sockets
# Micropython + ESP8266 + DHT11 + Thingspeak
# update data to thingspeak
# An Inexpensive IoT Enabler Using ESP8266
# Official MicroPython MQTT client
# Messaging with MQTT
# MQTT & MicroPython
# micropython-umqtt.simple 1.3
# ESP8266 and MicroPython - Part 1
# ESP8266 and MicroPython - Part 2 : MQTT
# DHT driver
# Python 的 Socket 程式設計教程
# ESP8266 - WiFiManager
# Python socket.accept nonblocking?
# webserver to turn an LED on or off
# micropython /examples/unix/sock-server.py
# examples/network: Split recv- and read-based HTTP servers (Good)
# MICROPYTHON ON THE ESP8266: KICKING THE TIRES (send \x??)
# examples/network: Split recv- and read-based HTTP servers (recv 範例)
# Controlling relays using Micropython and an ESP8266 (使用 ure 模組)

2017-07-04 補充 :

今天在下面這篇看到從數字與文字雜沓的字串中抽取數字部分的方法 :

# extract digits in a simple way from a python string [duplicate]

這個函數簡單又好用, 紀錄存查 :

def get_num(x):  #傳回字串中的數字部分
    return float(''.join(ele for ele in x if ele.isdigit() or ele == '.'))

2017年7月2日 星期日

Backup Plus 4TB 2.5吋行動硬碟

這是我在 6/20 上臉書時看到的 4TB 2.5 吋硬碟廣告, 只要 1588 元, 雖然不知道這品牌 Backup Plus 到底是啥來頭, 還是衝動地下標了, 因之後都沒消息, 所以我也就不管它了. 哪知昨天在鄉下接到大榮貨運電話, 說有需收費 1588 的貨品要送達, 才想起這回事.

http://www.oapau.com/tw


下單成功

恭喜您, 訂單提交成功. 我們已經收到您的訂單,將盡快為您發寄出寶貝.溫馨提示:支持貨到付款+免郵費+7天鑒賞期!收到貨有任何疑問請聯繫我們在線客服! 也可隨時聯繫 : Bello@oapau.com。同時請附上您的姓名及聯繫方式及訂單編號在郵件當中,以便我們能快速的給您及時處理,祝您購物愉快!!!
訂單時間
2017-06-20 00:07:20
訂單編號
12170620120720311

收貨信息


Backup Plus 4TB 2.5吋行動硬碟
套餐數量 1
查看套餐包含產品
付款方式 :貨到付款
共計 :NT$1588
運費 :NT$0.00
實付 :NT$1588
2017-07-03 補充 :

好心網友梁兄提醒我這可能網路詐騙, 我原本想今天送貨來就知道了, 或許是舊報紙也說不定. 謹慎如我者竟然這麼衝動, 真該打屁股. 此活動已結束, 網站顯示已賣出 18780 件 :


早上提早到辦公室, 查了一下網路資訊, 發現這極有可能是詐騙, 光是 http://www.oapau.com/tw 去掉 tw 就 404 not found 就很可疑, 參考 :

# 臉書廣告購物詐騙大追查連線
# 最近這種詐騙超多
# 退貨退款就拖拖拖

馬上撥電話給管理室警衛組長止付, 若收到貨就退貨. 梁兄, 感謝您提醒.

另外, 我接到大榮嘉里物流送貨員電話時, 我告知要直接退貨, 他說 OK, 但要我通知賣方, 但我去官網按上面的客服按鈕, 卻總是出現 Network Error, 這大概就是有人反映要退款退貨卻找不到貨主的原因了, 賣出劣貨收了錢後就烙跑 :


2017-07-05 補充 :

今天梁兄又提供了一則香港類似賣 3TB 硬碟劣貨詐財案例, 受害者已於昨日向香港海關舉報要求調查緝拿無良賣方, 參考 :

# fb網購硬碟3TB變40GB疑受騙 至少數十人中招 苦主今到海關舉報

總之, 網路購物要小心, 特別是臉書特價促銷消息常常是無良賣家詐財管道, 大家務必要小心.

2017-07-12 補充 :

今天民視新聞有特別報導臉書特價廣告詐騙案, 3TB 硬碟只要 $1688, 結果收到後只有 30GB 而已, 這種詐術很聰明, 因為他有交付商品, 並非拿了錢不交貨, 他可以推說這是民事上的交易糾紛, 頂多是廣告不實, 不能以詐欺論處云云. 總之, 不管是網路拍賣或促銷, 偏離市價太多就要提高警覺, 因為騙子就是要製造 "買到賺到" 想法才能引人上鉤.

SSD 安裝 Windows 抓不到硬碟問題

昨天二哥說他在二手店花 300 元買了一顆二手 SSD (美光 250G), 想當系統碟以加快執行速度, 今天將原來的 1TB 硬碟拔掉改接上此 SSD, 將 Win10 燒錄在隨身碟上改為用 UEFI 開機, 雖然有開啟 ACHI, 但 Windows 安裝前進至選擇燒錄到哪一個分割畫面時, 都只有出現一個 100M 的分割, 根本不足以安裝 Win10, 出現 "無法將 WINDOWS 安裝到 0X80300001" 之錯誤訊息.

後來我在下面這篇發現, 原來用隨身碟安裝 Windows 有秘訣 (用光碟安裝應該就不會, 但沒試過), 開機進入安裝畫面後就須將隨身碟拔下來, 否則進入到選擇要安裝到哪一分割的畫面時將不會出現 SSD 或硬碟, 而只出現隨身碟可選擇, 因為安裝程式誤以為是要安裝到隨身碟 :

# Windows 7 安裝注意事項

"系統誤把 USB 記憶碟 當成硬碟,所以存不進去,我把 USB 記憶碟 拔出來,讓它先找到硬碟,再插回去他就可以繼續執行安裝了。"

過了這關選好 SSD 或硬碟分割後還要再將隨身碟插回去才能繼續安裝. Windows 真是非常奇怪啊, 而且看來從 Win7 就是這樣了, 到 Win10 一直都沒變, 我安裝 Linux Mint 也是將映像檔燒錄到 USB 隨身碟, 以隨身碟開機後安裝到硬碟, 都非常方便順利, 哪需要像這樣插插拔拔的, 安裝過程還要重開機幾次的.

將映像檔燒錄到 USB 隨身碟我是用下面這款免安裝版的 Rufus :

# 免費將ISO光碟映像檔製作為USB開機隨身碟的軟體Rufus

如果要將 Windows 複製到另一顆硬碟上, 可參考 :

# Partition Assistant Home Edition 5.1複製Windows 7到SSD硬碟
# Rufus 2.15 可攜版

2017 年第 26 周記事

本周是菁菁與二哥段考周, 週五結業式後學期就結束了, 漫長的暑假又開始了. 下學期二哥要升高二, 編班已出來了, 在八班, 只有一個同學同班. 菁菁要升國三, 暑假要開始暑輔準備明年的會考了.  因為姊姊今天要指考, 所以昨天週六早上我就先回鄉下去了, 二哥說他要將電腦從鄉下搬回來, 所以要跟我一起回去.

爸說這幾天下午吹狂風, 簡直是無雨的颱風, 我到菜園巡視, 發現甘蔗倒一堆, 可見風力強大. 而樹上僅存的幾顆芒果也被搖晃到全數掉落, 不用摘, 只要去撿就可以了. 今年芒果到本周全數完成採收, 週六下午我將比較熟的十幾顆果肉切下來放在夾鏈袋冰存, 可以打冰沙, 也可以直接當芒果冰棒來吃.

這幾天因為堂伯父來電提到種電計畫, 爸認為農地種電比田租還划算, 所以去跟堂伯父拿說明文件回來, 上面署名城市發展太陽能推廣中心, 地址在台南市學甲自強路 26 號, 還附有0955516338 詢問電話. 我在網上搜尋找到其臉書網頁 :

# https://www.facebook.com/城市發展興業股份有限公司-1912934372276002/

據其文宣表示農地租用方案以不利耕作, 地層下陷或汙染地優先 (可 100% 種電), 一般農地須結合農業經營使用, 最多可種 40%, 一分年租四萬, 低窪魚塭地三萬, 須鄰路糗周邊有台電電桿. 一個地號僅承租一分屋頂, 租期 20 年. 不過爸說小舅今天早上來時聽說後, 提到雲林等地有農民遇到不肖業者在施工或契約上產生糾紛情事, 所以要小心. 參考 :

# 五年還本?是詐騙還是商機?
# 陽光屋頂 - 太陽能電廠評估計劃書