2022年12月18日 星期日

博客來買書一本 : Python ✕ Excel 高效率打造辦公室作業+數據分析自動化

前幾天看到臉書廣告博客來 12/18 日有下列這本書的 66 折活動 :


今天上網查詢確實只有今天, 看到 66 折手癢就買了 : 




其實也是因為這本書很實用啦 (或許退休以後會很常用, 哈哈).  

市圖還書 1 本 (論人生短暫)

今日市圖還書 1 本 (被預約) : 


此書作者塞內卡是一世紀時羅馬帝國的哲學家, 也是羅馬皇帝尼祿少年時代的老師 (類似中國的東宮教席, 太子少保之類的官員), 尼祿當上皇帝後塞內卡也被拔擢出任重要官職因而權傾一時, 但後來被政敵誣陷涉入謀反案而遭到尼祿賜死 (放血而亡). 雖然結局是悲劇, 塞內卡卻留下了許多作品, 是斯多葛學派的代表人物, 不過有些已經失傳, 部分文章是透過其他人的作品而傳世. 本書收錄了他三篇代表作, 並以其中一篇 "論人生短暫" 作為書名. 

這本書我其實還沒看完, 但閱讀第一篇讓我眼睛為之一亮, 他的文章回答了很多人們生活上常面臨的困境, 塞內卡的答案會讓人思緒突然活絡起來, 似乎被阻塞而糾結的心突然找到出路. 我覺得譯者柯宗佑功力也不凡, 讀此書不會有 "這是翻譯書" 的感覺, 值得再回借. 

2022年12月16日 星期五

MicroPython 學習筆記 : SSD1306 OLED 顯示器測試 (一)

本篇是在 ESP32 開發板上重做 SSD1306 顯示器的測試紀錄, 使用的是 0.91 吋解析度 126*32 的 OLED 顯示器, 它在垂直方向可顯示 4 列文字, 每個字 8 px, 4*8=32px; 在水平方向可顯示 16 行字, 每個字 8 px, 16*8=128px :




之前的測試文章參考 :


本系列全部文章參考 :


MicroPython 官網 SSD1306 教學文章參考 : 


另外下面這篇雖然是用 Arduino 語言, 但其中關於 I2C 介面的說明值得參考 :


總之, 使用 I2C 介面的好處是只需要 SCL 與 SDA 兩條通訊線, 以及 VCC 與 GND 電源線總共四條線即可, 比 SPI 所需的連接線少, 但缺點是速度比 SPI 慢. 

ESP-WROOM-32 的 I2C 通訊腳分別在 GPIO21 (SDA) 與 GPIO22 (SCL), SDA 在右上方起算第 3 腳  (編號 36); 而 SCL 則為第 6 腳 (編號 33), 如下圖所示 : 


Source : ESP32 Pinout


參考 :


接線方式很簡單, 就是將 ESP32 的 SCL (GPIO22), SDA (GPIO21) 與 SSD1306 的 SCL, SDA 對接, 同時把 3.3V/5V VCC 與 GND 對接即可 : 




實體接線如下圖 :




這樣就可以寫程式來測試了. 

在最近買的 "創客自造者工作坊-Python 感測器大應用 (旗標)" 一書中說 ssd1306 函式庫是 MicroPython 內建模組, 但我在最新的 1.19.1 版中直接 import ssd1306 卻失敗 (無此模組) :

MicroPython v1.19.1 on 2022-06-18; ESP32 module with ESP32
Type "help()" for more information.
>>> import ssd1306  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: no module named 'ssd1306'   

有可能之前 (書本用的是 v1.12) 有納入但後來移除了. 


1. 上傳獨立的 ssd1306 模組 : 

我找到 2017 年測試 ESP8266 時從 MicroPython 官網下載的 ssd1306.py 函式庫 (5 KB, 目前已找不到下載點), 上傳開發板測試發現還可以用, 我把它放在 GitHub 保存 : 


也可以從作者 GitHub 下載最新版本上傳開發板 :


點 ssd1306.py 進去按 'RAW' 鈕, 再按滑鼠右鍵選 '另存新檔' 即可下載 : 



這樣便可匯入 ssd1306 模組使用了, 先來檢視此模組內容 :

>>> import ssd1306   
>>> dir(ssd1306)      
['__class__', '__name__', 'const', '__file__', 'framebuf', 'time', 'SSD1306_I2C', 'SET_CONTRAST', 'SET_ENTIRE_ON', 'SET_NORM_INV', 'SET_DISP', 'SET_MEM_ADDR', 'SET_COL_ADDR', 'SET_PAGE_ADDR', 'SET_DISP_START_LINE', 'SET_SEG_REMAP', 'SET_MUX_RATIO', 'SET_COM_OUT_DIR', 'SET_DISP_OFFSET', 'SET_COM_PIN_CFG', 'SET_DISP_CLK_DIV', 'SET_PRECHARGE', 'SET_VCOM_DESEL', 'SET_CHARGE_PUMP', 'SSD1306', 'SSD1306_SPI']
>>> help(ssd1306)
object <module 'ssd1306' from 'ssd1306.py'> is of type module
  SET_DISP -- 174
  SET_MUX_RATIO -- 168
  time -- <module 'utime'>
  SET_VCOM_DESEL -- 219
  __name__ -- ssd1306
  framebuf -- <module 'framebuf'>
  SET_DISP_OFFSET -- 211
  SET_CHARGE_PUMP -- 141
  SET_SEG_REMAP -- 160
  SET_DISP_START_LINE -- 64
  SSD1306 -- <class 'SSD1306'>
  SET_COM_PIN_CFG -- 218
  SSD1306_I2C -- <class 'SSD1306_I2C'>
  SSD1306_SPI -- <class 'SSD1306_SPI'>
  SET_DISP_CLK_DIV -- 213
  __file__ -- ssd1306.py
  const -- <function>
  SET_MEM_ADDR -- 32
  SET_ENTIRE_ON -- 164
  SET_COM_OUT_DIR -- 192
  SET_PRECHARGE -- 217
  SET_NORM_INV -- 166
  SET_CONTRAST -- 129
  SET_PAGE_ADDR -- 34
  SET_COL_ADDR -- 33

其中最重要的是 SSD1306_I2C 與 SSD1306_SPI 這兩個類別, 分別用來建立 I2C 與 SPI 之通訊物件, 此處會用到的是 SSD1306_I2C 類別. 以前要先呼叫 MicroPython 內建建構函式 I2C() 建立 I2C 物件, 然後將其傳給 ssd1306 的 SSD1306_I2C() 建構式, 不過 MicroPython 原先的內建類別 I2C 在新版韌體中已經被廢棄了, 執行時會出現 warning :

>>> import ssd1306         
>>> from machine import Pin, I2C   
>>> i2c=I2C(scl=Pin(22), sda=Pin(21))                  
>>> oled=ssd1306.SSD1306_I2C(128, 32, i2c) 
Warning: I2C(-1, ...) is deprecated, use SoftI2C(...) instead     

改用 SoftI2C 類別即可 : 

>>> import ssd1306   
>>> from machine import Pin, SoftI2C     
>>> i2c=SoftI2C(scl=Pin(22), sda=Pin(21))           
>>> type(i2c)          # 建立 SoftI2C 物件
<class 'SoftI2C'>       
>>> oled=ssd1306.SSD1306_I2C(128, 32, i2c)    # 0.91 吋解析度是 128*32 px
>>> type(oled)   
<class 'SSD1306_I2C'>   
>>> dir(oled)   
['__class__', '__init__', '__module__', '__qualname__', '__dict__', 'addr', 'blit', 'buffer', 'fill', 'fill_rect', 'hline', 'invert', 'line', 'pixel', 'rect', 'scroll', 'text', 'vline', 'width', 'height', 'external_vcc', 'pages', 'init_display', 'write_cmd', 'show', 'poweroff', 'poweron', 'contrast', 'write_data', 'i2c', 'temp', 'write_list']
>>> oled.text('Hello World', 0, 0, 1)    # 在起始座標 (0, 0) 處填入字串 (col=1 點亮)
>>> oled.show()   

此處因為使用 ESP32 預設的 I2C 接腳 GPIO22 (SCL) 與 GPIO21 (SDA), 所以在 SoftI2C() 中要用 此兩腳地 Pin 物件分別傳給 scl 與 sda 參數. SoftI2C 顧名思義應該可以指定任何 GPIO 腳做 I2C 通訊用, 這後續可以驗證看看. 

呼叫 SSD1306_I2C() 後會建立一個 SSD1306_I2C 物件, 在顯示器上繪製文字或圖形就是用此物件的方法, 常用方法如下表 :


 SSD1306_I2C 物件的方法 說明
 fill(col) 將顯示記憶體全部畫素填入 col=1 (亮) 或 0 (暗)
 pixel(x, y, col) 在顯示記憶體指定畫素位置 (x, y) 填入 col=1 (亮) 或 0 (暗)
 text(string, x, y, col=1) 在顯示記憶體指定畫素位置 (x, y) 起填入預設 col=1 之字串 string
 show() 將顯示記憶體內容輸出於面板顯示內容
 scroll(dx, dy) 將顯示記憶體畫素內容向上下 (dy) 或向左右 (dx) 捲動


建立 SSD1306_I2C 物件 oled 後, 呼叫 text(msg, x, y [, col=1]) 將要顯示的字串傳入顯示器緩衝器中, 其中參數 x, y 為顯示的起始座標, 顯示器原點在左上角, 0.91 吋的 OLED 解析度 128*32 的座標軸如下, 顯示 ASCII 字元時每個字元解析度 8*8, 故可顯示 4 列 16 行共 64 個字元 :




但呼叫 text() 並不會顯示字串, 那只是將字串放進記憶體的顯示緩衝區而已, 必須呼叫 show() 方法才會真正輸出到顯示幕上, 結果如下 : 




可見 'Hello World' 顯示在第一列起始的地方. 


2. 用 upip 安裝 ssid1306 模組 :  

首先利用自訂模組 xtools 的 connect_wifi_led() 先連上網路 : 

MicroPython v1.19.1 on 2022-06-18; ESP32 module with ESP32

Type "help()" for more information.
>>> import xtools   
>>> xtools.connect_wifi_led()   
Connecting to network...    
network config: ('192.168.2.115', '255.255.255.0', '192.168.2.1', '168.95.1.1')
'192.168.2.115'

然後匯入 upip, 呼叫 upip.install() 線上安裝 micropython-ssd1306 : 

>>> import upip   
>>> upip.install('micropython-ssd1306')     
Installing to: /lib/
Warning: micropython.org SSL certificate is not validated
Installing micropython-ssd1306 0.3 from https://files.pythonhosted.org/packages/01/d0/0841d47772962c80af3ab178ef062ed2cd524cb99eb38463e669428402a8/micropython-ssd1306-0.3.tar.gz

安裝完就可匯入 ssd1306 了 : 

>>> import ssd1306   
>>> dir(ssd1306)    
['__class__', '__name__', 'const', '__file__', 'framebuf', 'SET_CONTRAST', 'SET_ENTIRE_ON', 'SET_NORM_INV', 'SET_DISP', 'SET_MEM_ADDR', 'SET_COL_ADDR', 'SET_PAGE_ADDR', 'SET_DISP_START_LINE', 'SET_SEG_REMAP', 'SET_MUX_RATIO', 'SET_COM_OUT_DIR', 'SET_DISP_OFFSET', 'SET_COM_PIN_CFG', 'SET_DISP_CLK_DIV', 'SET_PRECHARGE', 'SET_VCOM_DESEL', 'SET_CHARGE_PUMP', 'SSD1306', 'SSD1306_I2C', 'SSD1306_SPI']
>>> dir(ssd1306)
['__class__', '__name__', 'const', '__file__', 'framebuf', 'SET_CONTRAST', 'SET_ENTIRE_ON', 'SET_NORM_INV', 'SET_DISP', 'SET_MEM_ADDR', 'SET_COL_ADDR', 'SET_PAGE_ADDR', 'SET_DISP_START_LINE', 'SET_SEG_REMAP', 'SET_MUX_RATIO', 'SET_COM_OUT_DIR', 'SET_DISP_OFFSET', 'SET_COM_PIN_CFG', 'SET_DISP_CLK_DIV', 'SET_PRECHARGE', 'SET_VCOM_DESEL', 'SET_CHARGE_PUMP', 'SSD1306', 'SSD1306_I2C', 'SSD1306_SPI']

可見內容與上面獨立的模組完全一樣. 

用 upip 安裝後就如同內建一樣可直接 import 使用了.

下面的範例改編自以前寫的 "MicroPython on ESP8266 (十八) : SSD1306 液晶顯示器測試" 測試 4, 只是此處改用了 xtools 模組而已, 這使得程式碼更精簡. SSD1306 螢幕配置如下 :




測試 1 : 在 SSD1306 LED 上顯示 IP 與日期時間 [看原始碼  

import xtools
from xtools import pad_zero
import ssd1306
from machine import Pin, SoftI2C
import time

ip=xtools.connect_wifi_led()
i2c=SoftI2C(scl=Pin(22), sda=Pin(21))               
oled=ssd1306.SSD1306_I2C(128, 32, i2c)
week={0:'Mon',1:'Tue',2:'Wed',3:'Thu',4:'Fri',5:'Sat',6:'Sun'}

n=0
while True:
    utc_epoch=time.mktime(time.localtime())
    Y,M,D,H,m,S,W,DY=time.localtime(utc_epoch + 28800)
    YMD=f'{str(Y)}-{pad_zero(M)}-{pad_zero(D)}'
    HmS=f'{pad_zero(H)}:{pad_zero(m)}:{pad_zero(S)}'
    row1=f'@{ip}'
    row2=f'{YMD} {week[W]}'
    row3=HmS    
    oled.fill(0)    # 先清除緩衝器舊內容
    oled.text(row1, 0, 0, 1)
    oled.text(row2, 0, 8, 1)
    oled.text(row3, 0, 16, 1)
    oled.show()
    time.sleep(1)

此例假定已經呼叫 xtools.tw_now() 設定 RTC 與 NTP 伺服器同步過了, 所以 time.localtime() 會傳回 UTC 時間, 加上 28800 秒就是台灣時間了. 由於 SSD1306 一列只能顯示 16 個字元, xtools.tw_now() 傳回的日期時間為 19 個字元會超過, 所以此處匯入 xtools.pad_zero() 自行處理 日期 YMD 與時間 HmS 的格式, 將日期後面冠上星期縮寫, 與時間分成兩列顯示.

結果如下 : 


 


由於無限迴圈用 time.sleep(1) 暫停 1 秒, 所以秒數會逐秒跳動.

下面範例改用其他 GPIO 腳作為 I2C 通訊接腳, 測試 SoftI2C() 是否如其名稱所示可將任何 GPIO 腳模擬成預設的 I2C 腳位 (在 ESP-WROOM-32 開發板是 scl=GPIO22, sda=GPIO21), 此處改用 scl=GPIO19 與 sda=GPIO18 (右上角屬下來第 8, 9 腳) : 




程式碼與上面幾乎一樣, 只是改掉 SoftI2C() 裡參數 scl 與 sda 的設定而已 :


測試 2 : 改用任意 GPIO 腳當 scl 與 sda 腳測試 SoftI2C() [看原始碼  

import xtools
from xtools import pad_zero
import ssd1306
from machine import Pin, SoftI2C
import time

ip=xtools.connect_wifi_led()
i2c=SoftI2C(scl=Pin(19), sda=Pin(18))        # 改用 GPIO19, 18 當 scl, sda          
oled=ssd1306.SSD1306_I2C(128, 32, i2c)
week={0:'Mon',1:'Tue',2:'Wed',3:'Thu',4:'Fri',5:'Sat',6:'Sun'}

n=0
while True:
    utc_epoch=time.mktime(time.localtime())
    Y,M,D,H,m,S,W,DY=time.localtime(utc_epoch + 28800)
    YMD=f'{str(Y)}-{pad_zero(M)}-{pad_zero(D)}'
    HmS=f'{pad_zero(H)}:{pad_zero(m)}:{pad_zero(S)}'
    row1=f'@{ip}'
    row2=f'{YMD} {week[W]}'
    row3=HmS    
    oled.fill(0)
    oled.text(row1, 0, 0, 1)
    oled.text(row2, 0, 8, 1)
    oled.text(row3, 0, 16, 1)
    oled.show()
    time.sleep(1)

結果如下 : 




可見 machine.SoftI2C() 確實能模擬除預設的 GPIO22 與 GPIO21 之外的其他 GPIO 腳當 I2C 通訊腳之用. 

參考 :


2022年12月15日 星期四

Aliexpress 購買 0.96 吋 SSD1306 OLED 顯示器 10 個

這兩天在零件箱中搜尋 SSD1306 液晶顯示器模組, 發現手上只有一片 0.91 吋 128*32 解析度 I2C 介面的, 所以上 Aliexpress 找免運費的賣家, 只找到下面這家, 貨品是較大尺寸 0.96 吋 128*64 I2C 介面的, 一片才 47 元左右, 一批是 10 片 : 








128*64 解析度可以顯示 8 列 16 行的文字, 比現在手上這片 4 列 16 行可顯示列數多一倍.

遞送追蹤 :



2023-01-05 補充 :

昨晚追蹤遞送流程, 發現年底 12/28 已簽收, 但管理室卻沒通知? 




趕緊去管理室查閱包裹登記簿, 居然沒有簽收記錄, 難道包裹又送錯地方不成, 值班管理員說牆角有一個要退的包裹, 拿給我一看沒錯啊, 為何要退? 上樓問菁菁與水某, 都不知道有此包裹. 管理員說是年底只做了兩周就離職的組長收的, 說他上班整天玩手機不負責任. 

Witty Cloud ESP8266 開發板測試 (三) : 按鈕開關

今天繼續測試 Witty Cloud 開發板上內建的按鈕開關, 本系列之前的文章參考 :


關於按鈕可參考之前的測試 :


此篇因為使用外接機械開關, 所以需處理彈跳問題, 但使用 Witty Cloud 的板載按鈕開關卻沒有此問題, 若外接按鈕出現彈跳亦可參考下列 Arduino 的解決辦法 :


Witty Cloud ESP8266 開發板的主板 (上方) 有內建一個按鈕開關, 位置就在 PCB 天線正下方, 已經接到 ESP8266 的 GPIO 4 接腳 :




讀取按鈕開關狀態必須先先呼叫建構函式 machine.Pin() 建立 Pin 物件, 由於 Witty Cloud 板載按鈕開關已經內建上拉電阻, 因此不需要呼叫 machine.Pin() 時不需要傳入第三參數 Pin.PULL_UP, 只要定義其為輸入腳即可 :

button=Pin(4, Pin.IN)

然後呼叫 Pin 物件的 value() 方法即可讀取按鈕狀態是 0 (按下) 還是 1 (放開). 下面範例是在無限迴圈中偵測按鈕狀態, 如果是按下狀態就點亮板載 LED (Pin 4, 輸出 0), 否則就熄滅它 (輸出 1) :


測試 1 : 按住按鈕時點亮 LED 放開時熄滅 [看原始碼  

from machine import Pin, ADC
import time

led=Pin(2, Pin.OUT)      
button=Pin(4, Pin.IN)
led.value(1)  # 預設熄滅 LED

while True:
    value=button.value()
    if value:              # 1=按鈕放開
        print("按鈕狀態 :", value)
        led.value(1)    # 熄滅 LED
    else:                    # 0=按鈕按下
        print("按鈕狀態 :", value)
        led.value(0)     # 點亮 LED

注意, Witty Cloud 板載按鈕按下時讀到的是 0, 放開是 1; LED 輸出 1 是熄滅, 輸出 0 是點亮. 此例按下時 LED 才亮, 放開就會熄掉. 

下面範例則是讓按鈕變成 toggle, 亦即按一下亮, 再按一下熄滅 :


測試 2 : 按一下按鈕點亮 LED 再按一下熄滅 (toggle) [看原始碼  

from machine import Pin, ADC
import time

led=Pin(2, Pin.OUT)
button=Pin(4, Pin.IN)
led.value(1)  # 預設熄滅 LED

while True:
    if not button.value():  # 按鈕按下
        led.value(not led.value())    # 原點亮就熄滅, 原熄滅就點亮
        while not button.value():     # 若還按住就不反應
            pass

此例是利用 led.value(not led.value()) 來達成 toggle 功能, 當然底下處理按鈕還按住情況的無限迴圈也是必要的, 否則會閃爍. 

2022年12月14日 星期三

Witty Cloud ESP8266 開發板測試 (二) : 光敏電阻

這塊 Witty Cloud 去年底買來已一年了, 只測試過 RGB 全彩 LED, 最近在測試 MicroPython 常用到, 所以想先把板載的感測器都測過一遍. 此板是去年在 Aliexpress 買的, 當時買了 10 塊, 平均一塊板子才 59 元 (含 USB 底板), 算是非常便宜的 ESP8266 板子, 規格與 D1 mini 類似 (4MB Flash), 但多了全彩 LED, 光敏電阻, 與按鈕開關這三個板載模組, CP 值比 D1 mini 高很多, 參考 :


本篇要測試的是板載的光敏電阻 (light dependant resistor, 或 CdS), 其位置是在 PCB 天線的左邊 (另一邊是全彩 LED), 此光敏電阻一端串接分壓電阻, 另一端接到 ESP8266 的唯一類比輸入端 A0 腳, 此腳內部是一個 ADC (Analog-Digital Conversion) 電路, 可將 0~3.3 伏的類比輸入電壓用 10 bit 解析度轉成 0~1024 的整數 (2*10=1024), 數值越大表示輸入 A0 腳的電壓越高, 也表示光敏電阻的電阻越低, 也就是亮度越大 : 




從 A0 腳讀取數位化後的整數值要用到 machine 模組裡的 ADC 類別, 先用 import 匯入 :

from machine import ADC

然後呼叫類別建構函式 ADC() 並傳入 Pin 腳編號 (此處為 0) 建立 ADC 物件, 然後呼叫其 read() 方法即可取得 ADC 的輸出 :

MicroPython v1.19.1 on 2022-06-18; ESP module with ESP8266

Type "help()" for more information.
>>> from machine import ADC   
>>> adc=ADC(0)      
>>> type(adc)   
<class 'ADC'>   

這樣就建立了一個 ADC 物件, 用 help 檢查其內容 : 

>>> help(adc)    
object ADC(0) is of type ADC
  read_u16 -- <function>
  read -- <function>

其中的 read() 方法就是用來讀取 ADC 轉換後得到的十進位整數值, 可連續呼叫得到即時的

>>> adc.read()   
50
>>> adc.read()   
173
>>> adc.read()   
356

這是調整開發板位置使光敏電阻接受到不同的光線照射, 越亮數值越大, 越暗數值越小. 

以下參考之前用 ESP-12S 開發板做的測試在 Witty Cloud 上重做一遍, 參考 :
下列範例使用迴圈來連續讀取代表亮度的 ADC 轉換輸出, 為了不讓數字跳動太迅速使用 time (或 utime) 模組的 sleep() 函式來暫停幾秒 : 


測試 1 : 每秒測量一次亮度 (0~100%) [看原始碼  

#adc_test_1.py
import machine
import time

while True:
    adc=machine.ADC(0)    
    value=adc.read()           
    print(value, str(round(value*100/1024)) + '%')     
    time.sleep(1)

此處 print() 的第二個參數是將 ADC 的輸出 value 除以 1024 後乘以 100 變成百分比亮度值, 即 1024 為 %, 結果如下 : 

43 4%
58 6%
49 5%
81 8%
67 7%
158 15%
369 36%
81 8%
70 7%
123 12%
77 8%
127 12%
290 28%
288 28%

可見數值有時在一秒間變化甚大, 下面範例使用積分濾波器來抑制數值的突變 :


測試 2 : 每秒測量一次亮度 (0~100%) [看原始碼  

import machine
import time

while True:
    adc=machine.ADC(0)  
    value=adc.read()          
    value=0.3*value + 0.7*adc.read();    # 積分濾波
    print(value, str(round(value*100/1024)) + '%') 
    time.sleep(1)      

此處的積分濾波採用 3 分前值與 7 分現值之和計算, 結果如下 : 

489.0 48%
163.0 16%
236.0 23%
355.0 35%
360.0 35%
402.0 39%
470.0 46%
172.0 17%
507.0 50%
411.0 40%
166.0 16%


下面範例利用光敏電阻來控制板載 LED (GPIO2) 的閃爍頻率 : 


測試 3 : 利用光敏電阻量測的亮度控制 LED 閃爍頻率 [看原始碼  

from machine import Pin, ADC
import time

led=Pin(2, Pin.OUT)    

while True:
    adc=ADC(0)  
    value=adc.read()    
    value=int(0.3*value + 0.7*adc.read());  
    print(value, str(round(value*100/1024)) + '%') 
    led.value(1)                  # LED 滅
    time.sleep_ms(value)    
    led.value(0)                  # LED 亮
    time.sleep_ms(value) 

此例利用 ADC 輸出經積分濾波 (此非必要) 後之值來控制板載 LED (Pin 2) 的亮滅的暫停時間, 由於亮度越亮 value 值越大, 所以越亮會越慢, 越暗則閃爍速度越快. 注意, Witty Cloud 的板載 LED 是 Low active, 即輸出 0 點亮, 輸出 1 熄滅 (ESP32-WROOM 相反). 

下列範例以板載 LED (Pin 2) 模擬一般照明燈, 利用光敏電阻經 ADC 轉換而得的亮度 % 數值控制 LED 明滅 :


測試 4 : 利用光敏電阻控制 LED 明滅 [看原始碼  

from machine import Pin, ADC
import time

led=Pin(2, Pin.OUT)   # 板載 LED

while True:
    adc=ADC(0) 
    value=adc.read()    
    value=int(0.3*value + 0.7*adc.read());    # 積分濾波 (非必要)
    luminance=round(value*100/1024)
    print(value, str(luminance) + '%') 
    if luminance < 30:      
        led.value(0)             # 點亮 LED          
    elif luminance > 35: 
        led.value(1)              # 熄滅 LED 
    time.sleep(1)

此例是在亮度低於 30% 時點亮 LED; 高於 35% 時熄滅 LED. 

2022年12月13日 星期二

MicroPython 學習筆記 : ESP8266/ESP32 網路存取測試 (四)

本篇繼續使用陳會安老師大作 "超簡單Python/MicroPython物聯網應用" 這本書中提供的 xtools.py 模組來傳送 Line 訊息, 只要呼叫此模組中的 line_msg(token, message) 函式並傳入 Line Notify 權杖 (token) 與要傳送的字串 (message), 即可傳送 Line 訊息給特定人或群組. 在進行以下測試之前需先到  Line Notify 官網申請權杖 (token), 申請程序參考 : 


xtools.py 模組我有加以修改客製化, 並添加一些常用函式, 可從 GitHub 下載 : 


書中所附原始 xtools.py 我將其改名為 xtools_orig.py :


本系列全部文章參考 :


Line Notify 是免費服務, 每小時可傳送 1000 則訊息, 相當於每 3.6 秒可傳一則, 對於物聯網應用來說能夠每 5 秒傳一則其實就夠用了. 

xtools 中的 line_msg(token, message) 函式內容如下 :

def line_msg(token, message):
    headers = {
        "Authorization": "Bearer " + token,
        "Content-Type": "application/x-www-form-urlencoded"
    } 
    params = {"message": message}
    from xrequests import post
    r = post("https://notify-api.line.me/api/notify",
                    params=params, headers=headers)  
    if r is not None and r.status_code == 200:
        print("Message sent...")
    else:
        print("Error! Failed to send notification message...")  

這與我之前在 "Python 學習筆記 : Line Notify 訊息推播 (一) : 文字" 中 CPython 上測試時所用的 notify() 類似 : 

def notify(msg, token):
    url="https://notify-api.line.me/api/notify"                          # Line Notify API 網址
    headers={"Authorization": "Bearer " + token}                   # HTTPS 表頭
    payload={"message": msg}                                                # HTTPS 承載 (內容)
    r=requests.post(url, headers=headers, params=payload)    # 提出 POST 請求
    return "訊息發送成功!"

只不過此處向 Line Notify 提出 POST 請求時使用的是自訂的 xrequest.py 模組而非 MicrPython 內建的 urequests, 原因是 urequests 的 post() 函式不能使用 params 參數, 所以 "超簡單Python/MicroPython物聯網應用" 這本書作者將 urequest 添加 params 功能後改名為 xrequests, 參考 :


因為 xrequest.py 用到了自訂的 urlencode.py 模組, 所以也必須上傳此模組 :


總之, 欲使用 xtools 中的 line_msg() 函式傳送 Line 訊息基本上必須先將 xtools.py, xrequests.py, urlencode.py 這三個模組都上傳到 ESP32/ESP8266 開發板的根目錄下才行 (還要連線 WiFi 用的 config.py) :





以下使用 Witty Cloud 開發板進行測試, 因為此板內建一個按鈕 (GPIO4), 全彩 LED (GPIO12, 13, 15), 與光敏電阻 (A0), 如果要測試按一下傳送訊息, 或讀取亮度訊息, 不需要另外準備感測器. 關於 Witty Cloud 參考 :





從 MicroPython 開發板傳送 Line NotiFy 訊息的作法很簡單, 只要呼叫 xtools 的兩個函式即可. 首先呼叫 xtools.connect_wifi_led() 連線 WiFi 基地台, 然後定義權杖變數 token 與要傳送的訊息變數 message, 呼叫 xtools.line_msg(token, message) 即可 :

MicroPython v1.19.1 on 2022-06-18; ESP module with ESP8266

Type "help()" for more information.
>>> import xtools    
>>> xtools.connect_wifi_led()    
network config: ('192.168.43.98', '255.255.255.0', '192.168.43.1', '192.168.43.1')
'192.168.43.98'   
>>> token='ud7PaDL45fz849A0e1f4oaMCbRIkxMXapQCt3PfNkzz'        
>>> message='這是 MicroPython 開發板送出的訊息'      
>>> xtools.line_msg(token, message)     
Message sent... 
>>> xtools.line_msg(token, message)     
Message sent... 

結果如下 :




可以連續呼叫 line_msg(), 此處呼叫了兩次故收到兩個訊息. 

2023-01-01 補充 :

下面範例利用 Witty Cloud 開發板內建按鈕 (GPIO4) 之便, 測試按一下按鈕便傳送 Line 訊息, 關於按鈕用法參考後來寫的這篇 : 



程式碼如下 :

# main.py
import xtools    
import config
from machine import Pin, ADC
import time

ip=xtools.connect_wifi_led(config.SSID, config.PASSWORD)
token='ud7PaDL45fz849A0e1f4oaMCbRIkxMXapQCt3PfNkXX'
message='這是 MicroPython 開發板送出的訊息'
led=Pin(2, Pin.OUT)      
button=Pin(4, Pin.IN)
led.value(1)  # 預設熄滅 LED
send=False  #

while True:
    value=button.value()
    if value:              # 1=按鈕放開
        print("按鈕狀態 :", value)
        led.value(1)    # 熄滅 LED
        send=False
    else:                    # 0=按鈕按下
        print("按鈕狀態 :", value)
        led.value(0)     # 點亮 LED
        send=True
    if send:
        xtools.line_msg(token, message)
        time.sleep(5)
        send=False
    time.sleep_ms(200)    #

此處用 send 變數紀錄按鈕狀態 (預設 False), 內建按鈕按下時 GPIO4 為 0, 這時 send 會被設為 True, 會呼叫 lin_msg() 傳出 Line 訊息, 接著會暫停 3 秒 (因 Line Notify 限制最快 5 秒傳一個訊息, 此處設 5 秒), 然後將狀態改回 False, 無限迴圈最底下暫停 200 ms 目的是在測試時不要跑太快, 以免 Thonny 無回應. 




結果如下 : 




哇太棒了, 添加一個 repeater 後 WiFi 信號加強, 連頂樓也能順利連上網路了, 這樣就可以開始在頂樓安裝各種感測器, 然後用 Line Notify 將監控資訊傳到手機, 例如 : 
  • 頂樓自來水塔水位是否正常
  • 頂樓雨水收集桶水位高低
  • 氣候偵測器 (風速, 風向, 溫度, 濕度, 亮度, 雨量)
  • 大門來客報知器
  • 電話來電報知器
  • ATS 狀態 (太陽能/市電) 顯示器
新的一年希望一步步來實現這些專案. 

momo 買書 4 本 (PyTorch + 旗標自造者 + 計量交易)

昨天趁 12/12 購物節在 momo 買了 4 本書 (活動到 12/14 日) :








其中第四本香港人寫的量化投資書 "以 Python 取勝" 是最後加上去的. 旗標的自造者系列除了書以外內附 ESP32 與感測器模組, 在實體書店最多 85 折, 這次買到 75 折約 750 元 CP 值不錯. 扣掉 momo 幣實付 2404 元, 約原總價的 3518 的 68 折, 開勳! 

2022年12月11日 星期日

2022 年第 50 周記事 (獨立型太陽能系統完工)

本周終於完成太陽能除能系統的所有室內配線, 週六中午回到鄉下, 吃過午飯就開始配我房間的線, 主要是牆上的 16W LED 照明燈, 以及一個開關插座盒, 開關用來控制 LED 燈, 兩個插座是用來給筆電與手機等. 接著是給一樓門口的燈拉線, 因為要跟著樓板頂的電話線走, 爬高梯得站在上面第二格, 施工起來真的有點怕. 今天下午則是拉廚房與祖堂門口 LED 燈的線, 一樓與祖堂門口的燈都是用 Tuya 智慧插座定時控制亮滅, 不須做開關盒倒很省事. 


廚房燈


實測發現切菜的流理台會被抽油煙機擋住而不夠亮, 所以打算延伸配線至窗戶正上方中央, 燈光剛好可以照亮流理台 (用 10W LED 即可). 


祖堂門口燈


祖堂門口燈的線是從頂樓配電盤的負載端接 Tuya 智慧插座經過頂樓樓板 (有穿三分管) 拉下來的, 沿混泥土雨遮內緣, 長度剛剛好就是兩條導線槽, 真巧. 下圖是從配電盤上的 Tuya 智慧插座 :


控制祖堂門口燈的智慧插座


自送電以來已過了五周, 本周累積總用電量來到 26KW, 上週為 14KW, 故第五周用電量為 12KW, 平均一天用電約 1.7KW. 今天將全部 LED 關閉, 測得耗電約 41W, 負載包含 8 路監視器, 3 台米家監視器, 光世代數據機, 以及無線基地台, 這 41W 就是基本負載. 當所有 LED 點亮, 總用電量達 180W. 觀察逆變器散熱風扇約在大於 153W 時啟動. 





今天再次測試 ATS 功能, 結果正常, 但因為是用紅色與綠色色條顯示是主電源 (太陽能) 還是副電源 (市電) 供電, 而我有紅綠色弱, 辨認起來有點吃力, 所以我用油性筆將狀態色條標上 1 (太陽能主電源) 與 2 (市電副電源), 並搭配左右塗黑的色塊, 方便以後用 ESP32-CAM 做圖形辨識 : 


太陽能供電 (狀態 1)


市電供電 (狀態 2)




忙了兩個月今天總算完工了, 這樣以後在鄉下家萬一遇到停電還能繼續工作, 不怕電池耗盡了 (光世代數據機與無線基地台也改吃太陽能電源). 去年停電我剛好在鄉下, 光世代數據機斷電無法上網, 只好開手機熱點, 但這樣手機電池會掉很快, 最後為了保持手機有電只好停止工作, 現在終於不用擔心了, 300AH 電池省點用 (只開基本照明與網路) 撐一天應該沒問題 (過去經驗台電通常半天就能復電了). 

傍晚我正在三樓頂配線, 小舅打電話說要拿封肉過來, 舅媽今天又做了好料, 晚餐就以這為主菜了. 這次舅媽手藝已達總舖師等級, 與辦桌師傅的手藝不相上下, 改天要來學學怎麼做, 這陣子都在忙太陽能, 料理方面沒甚麼進展. 

這周看了 Line TV 上李國毅 (阿海) 與簡嫚書 (盛音) 主演的 "靈異街 11 號", 原本只是中午吃飯時消遣用, 但看了兩集後覺得台劇製作雖然沒有韓劇那樣精緻 (例如帶一下盛音解剖畫面), 但劇情鋪陳還是很吸引人. 故事是講述混黑道的阿海在鬥毆中被人朝頭部開槍, 不過卻幸運撿回一命, 自此後竟然可以看到靈界的東西, 而經營葬儀社的父親此時往生, 他不情願地回家接手父親的工作, 與擔任法醫的盛音有了接觸, 借助他與靈界的溝通而偵破許多案件. 

老張邀約下學期去正修 C 語言授課, 但我手上還有許多計畫要進行, 雖然之前學 Arduino 也把 C 語法翻個遍, 但已好幾年沒寫 C, 再投入就要花大把時間備課編教材, 恐怕會影響我目前的學習與計畫執行步調, 所以只好對他的好意婉謝了. 上學期就邀過教 Python, 雖然每天都在用, 但要授課又是另一回事了. 不過, 上回教邏輯設計的確是很棒的經驗, 真正體會何謂教學相長. 

2022年12月10日 星期六

社區網路不穩定問題

12 月初開始發現高雄家裡用的社區網路不穩定, 可以連得上 Internet, 一般瀏覽網頁等網路存取基本上 OK, 但網頁有時回應過長, 導致下載個 10MB 的檔案居然常常失敗, 以前不會這樣, 偶而幾百 MB 中斷還可接受, 但區區 10 MB 以前 1~2 兩分鐘搞定的事情居然試了半天都因中途線路錯誤而失敗實在太扯. 

前天遇到樓上當初拉線負責社區網路營運的張先生, 他說前陣子才剛提升到幾 Giga, 也沒其他住戶說有問題, 那時間不是剛好符合? 我線路設備都沒動啊? 難道我的路由器太舊了嗎?

早上網路測速證實網速不僅奇低, 而且非常不穩定 : 




路由器重開機與路由器的 DNS 設定為自動分派也無效, 原設定如下 :




周末回鄉下拿一台小米路由器來更換, 如果還是不行, 打算不要用了, 改牽中華電信光世代.