2022年12月31日 星期六

露天購買 Tuya 智慧插座 3 個

11 月中於蝦皮買了 5 個 Tuya 智慧插座, 已經用在太陽能儲能系統 4 個LED 燈負載, 目前剩下一個準備用在高雄陽台植栽自動定時澆水用, 之後可能還會用到, 所以在露天又買了三個 : 





今天是年底與月底, 露天免運券還有很多張, 還抽到 50 元抵用券, 實付 487 元, 平均一個 163 元.
 

露天購買 RS485 to TTL 模組等零件一批

因為要擷取太陽充電控制器的 RS485 通訊埠資料, 今天上露天購買 RS485 to TTL 模組, RJ45 壓線鉗等零組件工具一批 :





萊爾富免運共 369 元. 

關於 RS485 模組前幾天有做過市調, 參考 :


市圖還書 5 本 (Ubuntu 2.0 等)

因明天元旦圖書館放假, 今天先去還了如下 5 本書 : 
No.1 被預約須還, 其他四本無人預約都當場續借回來, 最近要抽時間看一看還回去. Ubuntu 的書要再預約回借, 過年後要把鄉下的固定網址挪給 Linux 主機架站, 王進德寫的這本言簡意賅不囉嗦, 是本好書.

2022年12月30日 星期五

jQuery UI 學習筆記 (十七) : 用下拉式選單做倒數日數計數器

今天是 2022 年最後一個上班日, 單位裡有兩位老同事 65 歲屆齡退休, 看到人家明年就可以睡到自然醒, 令我好生羨慕. 我要到 2031 年 6 月 30 才退, 還有 8 年半左右, 我到 GitHub 個人網站查看上回寫的退休倒數計日程式, 精確來說是 8 年又 183 天, 參考 : 


其實還有很多倒數計日的應用時機, 例如會考, 學測, ... 甚至是預期的平均壽命都可以. 因為很久沒寫 jQueryUI 了, 一時技癢就拿這個來練習一下吧! 將我的退休倒時記日器一般化, 可以選定截止日期, 計算離那個日期還有幾年幾天. 

最直覺的方式是用下拉式選單 SelectMenu, 年月日都設一個, 然後監聽 onchange 事件, 任何一個選擇器改變就立即進行倒數計算. 關於 SelectMenu 用法參考 :


如下面範例 1 所示 : 


測試 1 : 用 jQueryUI 的 SelectMenu 選取年月日以計算倒數日數 [看原始碼]

<!doctype html>
<html>
  <head>
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script> 
    <style>
      body {
        font-family: 微軟正黑體, Arial, Helvetica, sans-serif;
        font-size:12px;
        }
    </style>
  </head>
  <body>
    <div style="padding: 2px; margin: 3px;">
      <h3>倒數日數計算</h3>
      <p><b>請選擇截止日期 :</b></p>
      <select id="year">
        <option value="2023">2023 年</option>
        <option value="2024">2024 年</option>
        <option value="2025">2025 年</option>
        <option value="2026">2026 年</option>
        <option value="2027">2027 年</option>
        <option value="2028">2028 年</option>
        <option value="2029">2029 年</option>
        <option value="2030">2030 年</option>
        <option value="2031">2031 年</option>
        <option value="2032">2032 年</option>
        <option value="2033">2033 年</option>
        <option value="2034">2034 年</option>
        <option value="2035">2035 年</option>
        <option value="2036">2036 年</option>
        <option value="2037">2037 年</option>
        <option value="2038">2038 年</option>
        <option value="2039">2039 年</option>
        <option value="2040">2040 年</option>
        <option value="2041">2041 年</option>
        <option value="2042">2042 年</option>
        <option value="2043">2043 年</option>
        <option value="2044">2044 年</option>
        <option value="2045">2045 年</option>
        <option value="2046">2046 年</option>
        <option value="2047">2047 年</option>
        <option value="2048">2048 年</option>
        <option value="2049">2049 年</option>
        <option value="2050">2050 年</option>
      </select>
      <select id="month">
        <option value="Jan">1 月</option>
        <option value="Feb">2 月</option>
        <option value="Mar">3 月</option>
        <option value="Apr">4 月</option>
        <option value="May">5 月</option>
        <option value="Jun">6 月</option>
        <option value="Jul">7 月</option>
        <option value="Aug">8 月</option>
        <option value="Sep">9 月</option>
        <option value="Oct">10 月</option>
        <option value="Nov">11 月</option>
        <option value="Dec">12 月</option>
      </select>
      <select id="date">
        <option value="1">1 日</option>
        <option value="2">2 日</option>
        <option value="3">3 日</option>
        <option value="4">4 日</option>
        <option value="5">5 日</option>
        <option value="6">6 日</option>
        <option value="7">7 日</option>
        <option value="8">8 日</option>
        <option value="9">9 日</option>
        <option value="10">10 日</option>
        <option value="11">11 日</option>
        <option value="12">12 日</option>
        <option value="13">13 日</option>
        <option value="14">14 日</option>
        <option value="15">15 日</option>
        <option value="16">16 日</option>
        <option value="17">17 日</option>
        <option value="18">18 日</option>
        <option value="19">19 日</option>
        <option value="20">20 日</option>
        <option value="21">21 日</option>
        <option value="22">22 日</option>
        <option value="23">23 日</option>
        <option value="24">24 日</option>
        <option value="25">25 日</option>
        <option value="26">26 日</option>
        <option value="27">27 日</option>
        <option value="28">28 日</option>
        <option value="29">29 日</option>
        <option value="30">30 日</option>
        <option value="31">31 日</option>
      </select>
    </div>
    <div class="ui-state-highlight ui-corner-all" style="padding: 5px; margin: 5px; width:290px;">
      <span class="ui-icon ui-icon-info"></span>
      <span id="msg" style="font-weight: bold; "></span>
    </div>
    <input type='hidden' id='year_val' value='2023'>
    <input type='hidden' id='month_val' value='1'>
    <input type='hidden' id='date_val' value='1'>
    <script> 
      $(function(){
        function calculate(){
          var the_end=$("#month_val").val() + ' ' + 
                      $("#date_val").val() +  ', ' + 
                      $("#year_val").val();
          var t=Date.parse(the_end)-new Date().getTime();
          var total_days=Math.floor(t / (1000 * 60 * 60 * 24));
          var years=Math.floor(total_days / 365);
          var days=total_days % 365;
          var msg="離截止日期還有 " + years + " 年 " + days + " 天 (共 " + 
                  total_days + " 天)";
          $("#msg").text(msg);        
          }
        $("#year").selectmenu({width: 120});
        $("#month").selectmenu({width: 80});
        $("#date").selectmenu({width: 90});
        $('#year').on('selectmenuchange', function() {
          $("#year_val").val($(this).val());
          calculate();
          });
        $('#month').on('selectmenuchange', function() {
          $("#month_val").val($(this).val());
          calculate();
          });
        $('#date').on('selectmenuchange', function() {
          $("#date_val").val($(this).val());
          calculate();
          });
        calculate();   //初次仔入網頁時顯示初始值
        });
    </script>
  </body>
</html>

此例網頁中設置了三個隱藏元件 (type=hidden 的 input 元素), 分別用來儲存三個 SelectMenu 元件所選定之值, 每一個下拉式選單都有綁定 onchange 事件, 只要選項被改變就會更新相對應之隱藏元件之值, 這部分邏輯被寫成 calculate() 函式來處理. 下拉式選單的初始值就是第一個選項 (2023-01-01) 之值. 注意, SelectMenu 元件的寬度無法用 style 屬性設定 (無效), 必須在初始化時傳入 {width: } 物件來設定. 結果如下 : 




哈哈, 這正是我的退休倒數日數. 

下面改用 HTML5 的 Date 表單元件來選取截止日期, 用法參考下面這篇的範例 4 :


Date 元件可綁定 onchange 事件, 以便選定日期後觸發事件處理函式, 參考 :




<!doctype html>
<html>
  <head>
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
    <script> 
      function calculate(e){
        alert(e.target.value);
        }
    </script>
    <style>
      body {
        font-family: 微軟正黑體, Arial, Helvetica, sans-serif;
        font-size:12px;
        }
    </style>
  </head>
  <body>
    <div style="padding: 2px; margin: 3px;">
      <h3>倒數日數計算</h3>
      <p><b>請選擇截止日期 :</b></p>
      <input type="date" id="date" onchange="handler(event)"> 
    </div>
    <div class="ui-state-highlight ui-corner-all" style="padding: 5px; margin: 5px; width:290px;">
      <span class="ui-icon ui-icon-info"></span>
      <span id="msg" style="font-weight: bold; "></span>
    </div>
    <script>
      function handler(event){
        var YMD=event.target.value.split("-");   // [2022,12,31]
        var month=['Jan', 'Feb', 'Apr', 'Mar', 'May', 'Jun', 'Jul', 'Aug',
                   'Sep','Oct', 'Nov', 'Dec'];
        var the_end=month[parseInt(YMD[1])-1] + ' ' + YMD[2] +  ', ' + YMD[0];
        var t=Date.parse(the_end)-new Date().getTime();
        var total_days=Math.floor(t / (1000 * 60 * 60 * 24));
        var years=Math.floor(total_days / 365);
        var days=total_days % 365;
        var msg="離截止日期還有 " + years + " 年 " + days + " 天 (共 " + 
                total_days + " 天)";
        $("#msg").text(msg);      
        }
    </script>
  </body>
</html>

此例使用 HTML5 表單元件中的日期選擇器 (type=date 的 input 元件) 來選取截止日期, 注意, 此處所綁定的 onchange 事件, 呼叫端之傳入參數名稱一定要用 event, 亦即必須用 onchange="handler(event)",  用其他參數名稱會出現錯誤 (函式端則不受限, 可用 event 或 e 等等). 由於 Date 元件預設的日期格式為 YYYY-MM-DD, 所以要用 split() 將 Y, M, D 拆開, 其中 M 用陣列對照轉換成英文簡寫, 再與 Y, D 組成例如 'Dec 31, 2022' 的日期格式字串, 結果如下 : 





因為過了 12 點, 所以比上面範例 1 少一天啦. 

有空可以用 jQueryUI 本身的 datepicker 來做, 參考 : 


不小心按到 NumLock 鍵會打不出中文

下午在筆電編輯程式時發現中文打不出來, 明明右下角顯示 "中" 文模式, 但卻都打出英數字, 關掉瀏覽器也一樣. 後來想到該不會是按到甚麼奇怪按鍵吧? 仔細回想, 才想起先前曾要螢幕截圖按 Fn + PrtScr 鍵卻不小心按到 PrtScr 旁邊的 NumLk 鍵, 好像從那時開始中文就打不出來了. 重按一次 NumLk 即恢復正常. 畢竟 NumLk 鍵很少用, 所以記一下, 免得又忘了. 

有時小咪跳上我書桌要跳上鋼琴時就直接踩我鍵盤, 這時誤踩按鍵也會造成不知原因的災情, 所以離開書桌就養成習慣蓋上筆電, 結果筆電上蓋常有貓腳印, 呵呵, 這總比一直打不出中文好. 

露天購買桌球練習器

這兩天老同學峰大師一直在秀剛買的桌球練習器, 雖然我桌球球技很爛 (所以已經很久沒打桌球了), 但覺得拿來運動扭扭身體也不錯, 而且不占空間, 就買來玩玩唄 : 




我選 500 元附兩支球拍的, 含運共 545 元. 

2023-01-04 補充 : 

今日去萊爾富取貨, 內有一個底座, 三條很有彈性的碳纖維條 (短, 中, 長三種長度適合不同身高), 與六顆加工的乒乓球 (已鑽洞可插入塑鋼條). 將碳纖維條底端鎖在底座 (有螺紋) 後, 頂端插入乒乓球孔約 3 公分即可使用. 



2022年12月29日 星期四

TTL to RS485 轉接板市調

11 月底完成的 660W 獨立型太陽能系統目前穩定運轉, 接下來是要研究如何監測發電量與遠端監控. 目前整個配電盤上只有 Epever Tracer 4210AN 充電控制器具有 RS485 通訊埠提供電池電壓, 太陽能板電壓電流等數據, 雖然購買控制器時有附購一條 RS485 轉 USB 轉接線, 但那適合接上筆電直接通訊, 若要接到 ESP8266/ESP32 開發板還需要把 USB 轉成 UART 的 TX/RX 才行, USB 與 UART 互轉的線我也有, 但中間還需要 USB 母轉母轉換, 這樣轉來轉去似乎太麻煩, 所以上露天找到下面幾片 RS485 直轉 UART 的轉接板 :



Source : 露天


這塊板子使用 MAX485 晶片來處理 TTL 至 RS485 的位準轉換與信號收發, UART 側接腳標示的是 RO, RE, DE, DI, 其中 RO 為 Tx, 要接開發板的 Rx; DI 為 Rx, 要接開發板的 Tx. DE 與 RE 腳則是用來控制 MAX485 是要傳送還是接收, 可接到開發板的任意 GPIO 腳, 參考 : 


我在露天某賣場 (忘記記下來了, 找到再附) 看到如下之範例接線圖 :


Source : 露天


此圖左邊是 Arduino Mega, 右邊是 Arduino mini, 兩塊板子透過兩塊 RS485 通訊, 兩塊轉接板的 RS485 側互接的部分可以想成是在模擬工業環境長距離通訊, 接法是 A+ 接 A+, B- 接 B-; 而 TTL 側與開發板介接, 接法是 DI 接 Tx, RO 接 Rx, 而 DE 與 RE 腳接在一起後連到開發板的 GPIO 腳來控制收發用. 

但我並非是要用 RS485 做長距離通訊, 只是單純地要從 RS485 接收充電控制器丟出來的資料, 我找到下面這塊板子 : 


此片轉接板很明確 TTL 側四隻腳標示 VCC, GND, TX, RX; 而 RS485 側為 +A, +B, 與 GND 三隻腳, 所以接線方式就很明確, 一邊接開發板, 一邊接充電控制器 (需經 RJ-45 接頭), 較適合我的用途, 且具有 RS485 可熱插拔, 過壓保護, 兼容 3.3V 與 5V (建議 VCC 用 5V) 等優點 :


Source : 露天


另外一塊轉接板也是類似, 但價格較貴 (支持 3.3V~30V 的 VCC) : 



Source : 露天


此款特點是支持 30V 以內的電源供應, 且 TTL 側為附接線的插座. 

第三款較迷你, 因為它沒有其他特殊功能, 也沒有接線座, 工作電壓是 3~3.6V :



Source : 露天


不過另一賣家此款售 35 元 :


目前偏愛上面 53 元 或 59 元那款 (還要確認充電控制器是否為 5V 或其他電壓, RS485 電壓範圍 2~6V), 可用公-母杜邦線直接與 ESP8266/ESP32 開發板對接. 至於 RS485 側需要購買一個 RJ-45 接頭把 GND, A+, B- 三條線壓接進去, 故也需要一個壓線鉗, 可順便向此賣家購買 :


2022年12月28日 星期三

料理實驗 : 蘿蔔肉丸子湯

最近在臉書看到一個不錯的食譜 : 





作法看來不難, 週日去市場時順便買了蘿蔔與絞肉, 今晚就來試試看唄. 影片使用豬後腿肉打成肉末, 我圖方便直接買超市的細豬腳肉再剁碎些即可. 

材料 : 
  • 蘿蔔 
  • 豬絞肉
  • 玉米澱粉
  • 麵粉
  • 雞蛋
  • 醬油
  • 雞精
  • 白糖
  • 胡椒粉
  • 香菜
  • 香油
作法 : 
  1. 蔥切成蔥花, 香菜切碎備用. 
  2. 薑切碎加上一些蔥花做成蔥薑水備用. 
  3. 蘿蔔切塊放入果汁機打碎備用. 
  4. 絞肉加入一匙醬油, 雞精, 白糖, 適量蠔油與胡椒粉, 倒入少許蔥薑水, 再打一顆雞蛋用手充分抓拌均勻. 然後加入打成碎末的蘿蔔後再次抓拌均勻, 接著加入一勺玉米澱粉與麵粉, 以及切好的蔥花再次抓拌均勻, 最後倒入少許香油攪拌備用. 
  5. 鍋中加水煮開, 用手的虎口將肉漿擠成小肉丸放入鍋中, 期間藥用湯瓢攪動以免黏鍋底, 大火煮 4~5 分鐘後放入一湯匙醬油, 加適量鹽, 雞精與胡椒粉調味, 然後加入一點香油, 與一些太白粉水勾芡, 最後放入香菜蔥段與枸杞再煮個 10 秒即可出鍋了. 
實作結果是 : 失敗 ~~~~. 肉漿丸入鍋後不會凝成團, 而是散掉, 不知原因是甚麼. 推測可能是蘿蔔太會出水, 但多加些玉米粉與麵粉仍無濟於事, 索性用平底鍋煎成肉餅. 這次真是失算. 

jQuery UI SPA 函式庫索引

這篇是 2020/8/22 整理的索引 (但忘了發布), 那時候將之前工作上所寫的 jQuery UI 單一網頁應用程式 (SPA) 做了一番整理, 因為以前為了能快速上工, 沒有時間完善架構, 只要測試能用就行, 隨著功能越加越多, 整個應用程式架構越來越混亂與擁腫, 所以趁此機會重構整個系統, 最重要的是留下紀錄, 免得時間一久自己也忘了為啥要這樣寫. 以下是這些紀錄的索引 :

jQuery UI SPA 應用程式 (一) : 環境配置與程式架構
jQuery UI SPA 應用程式 (二) : 動態表格 DataTables
jQuery UI SPA 應用程式 (三) : 按鈕與對話盒
jQuery UI SPA 應用程式 (四) : 檔案處理函式庫

最近打算用這些函式庫打造新的維運工具, 可能會常常查閱. 

~進行中~

HTML5 測試 : 讀寫本機文字檔案

今天在搜尋用瀏覽器讀寫本機檔案的方式時找到下面這篇文章 :


我以前使用微軟 ActiveX 的 FileSystemObject 物件來處理本機檔案讀寫, 但那只能用在 IE 與 Edge, 沒辦法在 Chrome 或 FireFox 上跑. 此篇文章使用 HTML5 的 FileReader 物件來讀取本機文字檔, 可以在任何瀏覽器上執行. 可惜 FileWriter 目前還在開發中尚未成熟, 否則就能踢開 ActiveX 了.  

由於這篇文章兼具說明用途, 原始碼夾在一堆 HTML 碼裡面較難閱讀, 我將其改寫為如下之範例來測試檔案讀取功能 : 



<!DOCTYPE html>
<html>
<head>
  <title>檔案讀取</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <input type="file" onchange="read_file(this)"><br><br>
  <textarea id="content" cols="80" rows="15"></textarea>
  <script>
    function $(id) {return document.getElementById(id);}
    function read_file(obj){
      file=obj.files[0];
      var fr=new FileReader();           
      fr.onload=function(e) {
        $('content').value=e.target.result;
        };
      fr.readAsText(file);      
      }
  </script>
</body>
</html>

此例網頁上有一個 type=file 的 input 按鈕元素, 當按下此按鈕時會觸發 onchange 事件 (注意不是 onclick 事件) 呼叫 read_file() 函式並傳入此按鈕物件, 其 files[0] 屬性會取得所選取之檔案內容, 然後建立一個 FilerReader 物件來讀取此檔案, 當載入完成就會觸發 onload 事件, 將檔案內容放入 textarea 元素中 (此處我模仿 jQuery 的 $ 來取得 DOM 物件).

我在 Chrome, Firefox, 以及 Edge 測試確實可用 :




不過檔案選取按鈕在不同瀏覽器上顯示的外觀不同, Chrome 與 Edge 都顯示 "選擇檔案", 而 FireFox 則顯示 "瀏覽 ...".

雖然 HTML5 的 FileWriter 尚未被實作在眾瀏覽器中, 若要將網頁中的資料儲存於本機中可以利用 window.location.href 下載檔案的方式來暫代, 參考下面這篇 : 


關鍵是如下指令 : 

window.location.href="data:application/x-download;charset=utf-8," + encodeURIComponent(data);

程式結構與上面類似, 如下面範例所示 :



<!DOCTYPE html>
<html>
<head>
  <title>檔案寫入 (下載)</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <input type="file" onchange="read_file(this)"><br><br>
  <textarea id="content" cols="80" rows="15"></textarea><br><br>
  <button onclick="write_file()">儲存內容</button>
  <script>
    function $(id) {return document.getElementById(id);}
    function read_file(obj){
      file=obj.files[0];
      file_name=file.name;
      var fr=new FileReader();           
      fr.onload=function(e) {
        $('content').value=e.target.result;
        };
      fr.readAsText(file);      
      }
    function write_file(){
      data=$('content').value
      window.location.href="data:application/x-download;charset=utf-8," +    
                           encodeURIComponent(data);      
      }
  </script>
</body>
</html>

結果如下 : 




選取檔案讀入文字檔內容進行編輯, 然後按 "儲存內容" 會詢問儲存到哪一個 : 




填入檔名按 "存檔" 即可.  

此外也可以用 document.createElemen() 建立一個 a 元素後將要下載的內容設定給 href 屬性, 然後呼叫其 click() 方法下載, 作法參考下面這篇 (#42) : 


<!DOCTYPE html>
<html>
<head>
  <title>檔案寫入 (下載)</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <input type="file" onchange="read_file(this)"><br><br>
  <textarea id="content" cols="80" rows="15"></textarea><br><br>
  <button onclick="write_file()">儲存內容</button>
  <script>
    function $(id) {return document.getElementById(id);}
    function read_file(obj){
      file=obj.files[0];
      file_name=file.name;
      var fr=new FileReader();           
      fr.onload=function(e) {
        $('content').value=e.target.result;
        };
      fr.readAsText(file);      
      }
    function write_file(){
      var a=document.createElement('a');
      data=$('content').value
      a.href="data:application/x-download;charset=utf-8," +
             encodeURIComponent(data);    
      a.click();
      }
  </script>
</body>
</html>

結果與上面用 window.location.href 一樣. 

2022年12月27日 星期二

睽違兩年的尾牙

今天活動挺多的, 下午參加辦公大樓六位同事的退休歡送會, 其中兩位是咱辦公室同事 (楊爺爺與陳班長), 兩位是行銷單位, 一位是行管的阿月 (比我小一歲居然辦提早退, 是中樂透嗎?), 另一位則是台北來的上司. 退休感言中有人說日本人不言老, 不稱老人俱樂部, 他們稱 "大人俱樂部", 這叫 "樂齡", 面對不可避免的老化, 心態很重要. 

傍晚下班後在福華五樓辦尾牙, 由於離公司很近, 背背包去很麻煩且要找機車停車位, 所以都放在公司走路去, 反正今天沒辦法去河堤健走, 走這一小段路充充數吧. 福華五樓餐點我覺得普普, 但也還可以, 至少今天冰淇淋不用加價 :




由於疫情作亂, 尾牙已停辦兩年, 改成領禮券, 但抽獎還是有的, 我去年抽到 3000 元新光三越禮券至今還未用. 今年抽到 1000 元現金, 雖不是大獎也算是有吃又有拿, 呵呵. 
 

2022年12月25日 星期日

2022 年第 52 周記事

本周二哥專題報告結束, 週六說要跟我一起回鄉下, 剛好打算周六去文昌帝君廟拜拜, 剛好一起去. 回來還不到四點半, 想說近兩個月為了太陽能施工都沒去爬山, 該活動一下筋骨了, 就邀二哥一塊去爬獅形頂. 

週六回到鄉下先上頂樓紀錄使用電量, 累積已達 45 KWH, 扣掉上週 34 KWH, 本周用電量為 11 KWH, 平均每天約 1.6 KWH :




雖然上上周已全部完工, 周末仍進行了兩個延伸配線工程, 一是客廳 LED 照明燈, 我在門框上再安裝一個三孔插座, 插上 Tuya 智慧插座延伸到客廳天花板上方. 此燈我設定為傍晚五點自動點亮, 早上五點關閉, 這樣爸晚上起來上洗手間不須另外開燈 :



 
二是於廚房靠路邊窗戶上方再添加一個 LED 燈, 因為第一個燈位於瓦斯桶上方, 流理臺的光線會被抽油煙機擋住, 加上此燈後廚房就非常亮, 勝過原有市電的 40W 日光燈 :




增加這兩個燈 (+26W) 應該會讓每日平均用電超過 12 KWH. 

週日中午上頂樓配電盤查看, 逆變器顯示電池電壓來到 28.6V : 




充電控制器顯示太能板電壓 35V, 電流 11.6A :





累積電量 67.4 KWH :




但功率表使用電量卻只有 45 KWH, 這差距是表示目前每天存的電都用不完嗎? 要找充控的說明書來研究看看. 

這半年來都在忙太陽能發電, 菜園除了果樹 (木瓜 & 芭樂) 外都沒種菜. 之前在種子行買的台農木瓜苗在結過一次果後都陣亡了, 反而是吃完木瓜剩下丟廚餘桶的木瓜子撒在菜園反而長得既高又壯, 雖然沒有市售的大顆, 但起碼節時累累 :





週日下午去小漢增購兩條導線槽, 順便載一顆冬瓜去給小舅媽, 也看看小舅說他用大理石切好的前陽台花園, 好久沒去了, 一看果然煥然一新, 漂亮. 我想有空我也要開始打理老家庭院 (離退休還有八年呵呵), 先從大門口圍牆外種植七里香圍牆開始唄. 要回去時小舅媽弄了一大碗乾麵與豬腳滷肉等帶回去, 舅媽的傳統滷豬腳跟我用壓力鍋做的可樂豬腳就是不一樣, 又香又下飯, 下次我要改用砂鍋來滷. 

市圖還書 1 本 : 越南啟示錄1945-1975 : 美國的夢魘.亞洲的悲劇 (上)

 本周市圖還書一本 : 

Source : 博客來


此書翻譯自英國記者 Max Hastings 的著作, 他曾於越戰期間跟隨美軍進入越南戰場第一手報導戰況, 此書篇幅甚大, 分成上下兩冊, 我借的這本是上冊, 近 400 頁. 全書未讀完, 因要看的書太多, 借書額度快滿, 為了避免預約書無法取書就先還吧, 先記錄起來. 研究歷史是我的興趣, 但有比興趣更重要的事要做. 

越戰始於 1955 年在 1975 年結束, 當時我還是個 11 歲的小學生, 完全沒有印象. 一直到上了國中, 學校要求寫南海血書讀後感, 才知道原來還有個叫越南的國家. 但諷刺的是, 很多年之後才知道所謂的南海血書原來是黨國為了凝聚反攻大陸的民心士氣藉越南淪亡悲劇假造的, 讓我嘔心瀝血寫的讀後感瞬間變成一篇笑話. 
  1. 西方盟國對抗法西斯的戰爭, 還不得不依賴史達林暴政付出最血腥的代價, 才能搗毀希特勒暴政. 只有那些只知道政治只有左派與右派之分的傻子, 才會認為越戰交戰雙方的哪一方站上了道德的上風.
  2. 越戰並不只是美國的夢饜, 也是亞洲的悲劇, 每死一個美國人, 得死四十名越南人. 
  3. "他們說謊, 他們說謊, 他們說謊,", 不僅是美軍指揮部, 河內當局也是. 美國政治人物與指揮官們犯下的嚴重錯誤並非他們欺騙了世人, 而是他們欺騙了他們自己. (跟國民黨的轉進說法一樣, 都是自欺欺人). 
  4. 越共政治局與越南民族解放陣線雖然送走了軍閥與地主, 但迎來的卻是史達林門徒更兇殘的高壓統治. 民主政治讓人民有權剷除讓他們不滿的政府; 但共產黨統治一旦確立, 公開投票也走進歷史. 
  5. 越共戰士確實比較善戰, 因為胡志明與黎筍如法泡製了史達林在 1941~1945 年的戰爭技法, 祭出了愛國主義, 意識形態, 以及強制義務, 驅使農民軍拚死作戰. 

2022年12月20日 星期二

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

本篇繼續來測試 SSD1306 OLED 顯示器, 但這回改用 0.96 吋的 OLED, 其解析度為 128*64 可顯示 16 行 8 列共 128 個字元. 我前陣子在 Aliexpress 買的十片 0.96 吋 SSD1306 OLED 目前尚未沒到貨, 但 12/12 日在 momo 買的旗標 "Flag’s 創客•自造者工作坊 Python 感測器大應用 - 智慧生活X雲端" 套件盒裡附了一顆 0.96 吋的 SSD1306, 剛好可以先拿來做實驗 :




五年前測試 SSD1306 的文章 :


本系列全部文章參考 :


MicroPython 的 ssd1306 模組用法教學文件參考 :


在前篇 SSD1306 OLED 測試中使用了 ESP-WROOM-32 開發板, 這次改用 D1 mini 來測試, 關於此板參考 : 


此開發板的接腳定義如下表 : 


 板子腳位 ESP8266 腳位 功能
 D0 GPIO16 IO
 D1 GPIO5 IO, SCL 
 D2 GPIO4 IO, SDA 
 D3 GPIO0 IO, 內建 10K 上拉電阻
 D4 GPIO2 IO, 內建 10K 上拉電阻與 LED
 D5 GPIO14 IO, SCK
 D6 GPIO12 IO, MISO
 D7 GPIO13 IO, MOSI
 D8 GPIO15 IO, SS, 內建 10K 上拉電阻
 TX GPIO1 UART 送端
 RX GPIO3 UART 收端
 A0 ADC0 類比輸入 (0~3.3V)


參考 :


可見預設的 I2C 接腳為 D1(GPIO5) : scl 與 D2 (GPIO4) : sda, 接線圖如下 :




參考前一篇測試文章, 可用上傳 ssd1306.py 模組或用 upip.install() 線上安裝模組 : 


此處使用 upip.install() 線上安裝, 先用 xtools 的函式將 D1 mini 連上網路 :

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

Type "help()" for more information.
>>> import xtools    
>>> xtools.connect_wifi_led(ssid='EDIMAX-tony', passwd='blablabla')     
Connecting to network...
network config: ('192.168.2.116', '255.255.255.0', '192.168.2.1', '168.95.1.1')
'192.168.2.116'

然後匯入 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
>>> import ssd1306  
>>> dir(ssd1306)  
['__class__', '__name__', 'const', 'framebuf', 'SSD1306', 'SSD1306_I2C', 'SSD1306_SPI', '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_IREF_SELECT', 'SET_COM_OUT_DIR', 'SET_DISP_OFFSET', 'SET_COM_PIN_CFG', 'SET_DISP_CLK_DIV', 'SET_PRECHARGE', 'SET_VCOM_DESEL', 'SET_CHARGE_PUMP'] 

這樣就安裝完成了, 先測試全屏填入 1, 這會點亮全部畫素 :

>>> from machine import Pin, SoftI2C   
>>> i2c=SoftI2C(scl=Pin(5), sda=Pin(4))   # SCL=GPIO5. SDA=GPIO4
>>> oled=ssd1306.SSD1306_I2C(128, 64, i2c)    # 0.96 吋解析度 128*64 
>>> oled.fill(1)      # 將 1 填滿顯示緩衝器 (點亮畫素)
>>> oled.show()    # 將顯示緩衝器內容輸出到螢幕

結果如下 : 




可見螢幕為黃藍兩色 (白的是照相時出現的 alias 假信號, 像掃描線一樣不斷往上跑, 這是取樣頻率較掃描頻率低造成的), 其兩列是黃底, 可做為標題, 後 6 列是藍底, 可做為內容. 

下面依序在 1~8 列顯示 Hello World :

>>> oled.text('Hello World', 0, 0, 0)   
>>> oled.show()   
>>> oled.text('Hello World', 0, 8, 0)     
>>> oled.show()   
>>> oled.text('Hello World', 0, 16, 0)      
>>> oled.show()   
>>> oled.text('Hello World', 0, 24, 0)   
>>> oled.show()   
>>> oled.text('Hello World', 0, 32, 0)  
>>> oled.show()   
>>> oled.text('Hello World', 0, 40, 0)   
>>> oled.show()   
>>> oled.text('Hello World', 0, 48, 0)   
>>> oled.show()   
>>> oled.text('Hello World', 0, 56, 0)   
>>> oled.show()   

因為前面先用 1 填滿螢幕, 所以這裡 oled.text() 的第 3 參數要填 0 (畫素熄滅, 變黑), 結果如下 :




有三列 Hello World 被假信號遮住了. 

上面輸出 8 列 Hello World 的指令用迴圈處理叫精簡, 下面改用亮點來顯示文字, 所以先用 fill(0) 熄滅每個畫素, 然後用迴圈顯示 Hello World :

>>> oled.fill(0)     # 熄滅全部畫素
>>> oled.show()   
>>> for i in range(0, 8):      # i=0~7
     oled.text('Hello World', 0, i*8, 1)      # 點亮畫素
     oled.show()   

結果如下 : 




可見暗的背景在拍照時 alias 形成的掃瞄線比較不明顯. 

接下來要測試 SoftI2C() 是否可將 D1 mini 其他的 GPIO 腳模擬成 I2C 通訊埠, 例如改用預設為 SPI 通訊埠的 GPIO13 (D7, 預設的 MOSI 腳) 當 SCL; GPIO12 (D6, 預設的 MISO 腳) 當 SDA, 接團如下所示 : 

 


然後輸入如下程式碼 :

>>> import ssd1306    
>>> from machine import Pin, SoftI2C     
>>> i2c=SoftI2C(scl=Pin(13), sda=Pin(12))       # 改用 D7 與 D6 當 I2C 介面
>>> oled=ssd1306.SSD1306_I2C(128, 64, i2c)     
>>> for i in range(0, 8):     
     oled.text('Hello World', 0, i*8, 1)   
     oled.show()   

結果與上面相同, SoftI2C() 確實可模擬非預設 GPIO 腳當 I2C 介面用. 

接下來想延伸下面這篇文章的範例, 將 SSD1306 應用在現場設定 WiFi 基地台的連線設定上 :


程式的一般架構如下圖所示, 由 boot.py, main.py, 與 app1.py 構成, 執行控制權也是依此順序交遞 :




但此處只有一個 App : app1.py, 一個讓板載 LED 閃爍的 App, 程式碼如下 :

# app1.py
from machine import Pin,PWM

def main():
    #application codes are placed here  
    pwm2=PWM(Pin(2), freq=1, duty=512)

if __name__ == "__main__":  
    main()  

主程式 main 則改成如下內容 :

# main.py 
import xtools    
import config
import app1
import ssd1306
from machine import Pin, SoftI2C

i2c=SoftI2C(scl=Pin(13), sda=Pin(12))
oled=ssd1306.SSD1306_I2C(128, 64, i2c)
oled.fill(0)
oled.text('Connecting WiFi', 0, 0, 1)
oled.show()  
ip=xtools.connect_wifi_led(config.SSID, config.PASSWORD)   # 從 config.py 讀取
if not ip:
    mac=xtools.get_mac().replace(':', '')[6:]    # 取得 MAC 的最後 6 碼
    oled.fill(0)
    oled.text('Connect AP', 0, 0, 1)
    oled.text('SSID:', 0, 8, 1)
    oled.text('MicroPython-', 0, 16, 1)    # SSID 太長分成兩列顯示
    oled.text(mac, 0, 24, 1)
    oled.text('Password:', 0, 32, 1)          # Password  太長分成兩列顯示
    oled.text('micropythoN', 0, 40, 1)
    oled.text('IP:192.168.4.1', 0, 48, 1)
    oled.show()
    ip=xtools.set_ap()    # OLED 顯示完再呼叫 set_ap()
    if not ip:  
        print('無法連線 WiFi 基地台')
        oled.fill(0)
        oled.text('Connection fail', 0, 0, 1)   # 連線 WiFi 失敗, 按 Reset 重設
        oled.text('Press reset', 0, 8, 1)
        oled.text('Try again', 0, 16, 1)
        oled.show()
    else:
        print("WiFi 連線成功! IP : ", ip)
        oled.fill(0)
        oled.text('WiFi connected', 0, 0, 1)
        oled.text(ip, 0, 8, 1)
        oled.show()        
        app1.main()  
else:
    print("WiFi 連線成功! IP : ", ip)
    oled.text('WiFi connected', 0, 8, 1)
    oled.text(ip, 0, 16, 1)
    oled.show()            
    app1.main()

將 app1.py 與 main.py 用 Thonny 上傳到 D1 mini 開發板根目錄, 當 config.py 設定的 WiFi 基地台資訊正確, 順利連線的結果如下 : 




SSD1306 螢幕上首先會在第一列顯示 "Connecting WiFi", 當連線成功時則在第二列顯示 "WiFi connected", 以及在第三列顯示所獲得的 IP. 

然後故意將 config.py 中的連線資訊打錯, 上傳開發板後按 Reset 鈕重開機, 結果會因為連線失敗而進入 AP 模式, 並在 SSD1306 螢幕上依序顯示如下提示訊息 : 

Connect AP
SSID : MicroPython-xxxxxx
Password : micropythoN
IP : 192.168.4.1 

其中 SSID 太長所以拆分在兩列顯示.




這是提醒使用者開啟手機 WiFi 連線到 SSID 為 MicroPython-xxxxxx 的 ESP32/ESP8266 本身 AP, 再開啟手機瀏覽器連線其網頁伺服器 192.168.4.1, 然後於網頁中設定要連線的 WiFi 基地台 : 




按連線即進行設定並更新 config.py :




連線成功後按 Reset 重開機即可順利連線 WiFi 並執行 app1.py :




這樣就可以將此開發板拿到任何有 WiFi 涵蓋的處所現場設定連線資訊, 不須在實驗室預先設定好 config.py 設定檔了. 但如果沒有現場設定的需求, 那麼上面的 main.py 就有點麻煩, 因為只要換個地方, 原先的 config.py 就無法連線了, 得用手機連線 ESP32/ESP8266 的 AP 模式網頁伺服器進行設定. 如果沒有現場設定 WiFi 需求, 那就直接在 Thonny 改 config.py 就好了, 下面是移除 AP 模式現場設定後的 main.py :

# main.py
import xtools    
import config
import app1
import ssd1306
from machine import Pin, SoftI2C

i2c=SoftI2C(scl=Pin(13), sda=Pin(12))
oled=ssd1306.SSD1306_I2C(128, 64, i2c)
oled.fill(0)
oled.text('Connecting WiFi', 0, 0, 1)
oled.show()  
ip=xtools.connect_wifi_led(config.SSID, config.PASSWORD)
if not ip:
    print('無法連線 WiFi 基地台')
    oled.fill(0)
    oled.text('Connection fail', 0, 0, 1)
    oled.text('Set config.py', 0, 8, 1)
    oled.text('Try again', 0, 16, 1)
    oled.show()
else:
    print("WiFi 連線成功! IP : ", ip)
    oled.text('WiFi connected', 0, 8, 1)
    oled.text(ip, 0, 16, 1)
    oled.show()            
    app1.main()

平常接筆電測試就可以用這個 main.py, 連線失敗用 Thonny 去改 config.py 即可. 否則用前面現場設定的 main.py 會進入 AP 模式網頁伺服器的無限迴圈, 這時要用 Thonny 管理板子上的檔案會出現 busy 現象, 必須在按 Reset 時於進 AP 模式前抓時間切入. 

待購書籍 (Line Bot + Flask)

最近在 momo 發現兩本好書 : 
第一本是今天才上市, 其實我手裡已有兩本 Python Line Bot 的書, 可惜都沒時間玩. 最近菁菁說她師傅建議她要記錄整理客戶資料, 我就想到乾脆幫她設計一款 Line Bot, 將客戶資訊透過簡單符號進行查詢管理, 例如打 # 是儲存客戶資料到後端資料庫, 打 ? 就能查詢客人最近一年預約紀錄等. 

第二本是日本人寫的翻譯書 (日本技術類書籍超棒, 日本人很擅長整理知識), Flask 很適合架設 Line Bot 後端網頁伺服器, 我以前用 PHP 寫後端, 改用 Python 後就只用 Django 與 Flask 了. 可惜 12/12 促銷活動剛過, 等跨年看看吧, 先記著唄. 


2022年12月19日 星期一

2022 年第 51 周記事

週五水某與菁菁搭火車去台北找姊姊, 因為年初那次去沒洗到溫泉, 這次寒流來正好. 我因鄉下還有太陽能工程還有一些些待收尾, 且開車上台北感覺很累, 若帶爸一起去怕走太遠路傷腳, 所以這次我就沒去. 想說也要繼續處理白蟻問題, 結果寒流不說, 週六還下起雨來, 根本沒法做. 從入冬以來我外出都只穿薄夾克, 原以為今年的冬天被沒收了, 沒想到本周的冷氣團讓厚外套派上用場了. 

由於週六下了一整天雨, 正好檢視剛完工的獨立型太陽能系統能撐多久. 周日晚上看電視時突然螢幕閃一下, 數位機上盒重開機, 研判是逆變器偵測到電池到達低電壓關閉, 導致 ATS 切換到台電所致, 我以為切換很迅速, 沒想到數位機上盒會重開機. 上頂樓查看配電盤, 發現逆變器顯示電池電壓為 21.7V 還發出嗶嗶警告音, 逆變器的啟動電壓應該是 21.8V, 有空來看說明書. 




由於爸說周五也只有早上有陽光, 下午轉陰, 所以看來 300AH 充飽電在目前 40W~180W 負載下大約能撐一天半, 已經符合當初的期望, 即平常白天儲存電能也供應約 40W 基本負載 (監視器 + 光世代數據機 + WiFi 無線基地台), 夜間加上室內外照明及電視最高約 180W 負載. 若停電也至少能撐一天的基本通訊照明需求. 週日雨停了但也只有早上露出一點點陽光, 下午電池電壓來到 23V (平常會到 27.5V), 用到晚上 9:30 我離家時 21.9V, 快到自動關閉電壓了. 週六用電統計累積來到 34KW, 減上週的 26KW 得到本周用電 8KW (平均每天約 1KW) : 




接下來要開始研究 ESP32-CAM 用法, 打算用它來讀取此電流電壓功率計螢幕, 將圖片傳送到 Line, 若能用遷移學習做影像識別取得數值更好, 所以機器學習部分也要加把勁. 其實根本不用去監視 ATP, 只要電流電壓功率計有螢幕就是太陽能供電, 黑屏表示逆變器保護性關閉. 總之, 接下來就是 AIoT 的活了, 除了監視電流電壓功率計外, 還有一個是透過 RS-485 擷取充電控制器丟出來的數據, 感覺這個比較容易做. 

配線部分雖然完成了計畫中的負載, 但還想要添加 3 個 LED 負載. 週六去看阿蘭時順路買了單心線與導線槽, 打算週日來延伸廚房的太陽能配線在窗戶上面增加一個 LED 照明燈, 這樣就可以完全取代原有的市電日光燈了. 另外客廳也要裝一顆用字慧插座自動控制的 LED 燈, 傍晚 18:00~21:00 自動點亮. 但週日早上接到養護中心電話, 說阿蘭呼吸喘要送急診, 所以週日早上就在急診等住院安排, 中午看護到了之後我才回家. 下午都在幫爸備餐, 也沒時間施工, 要延到下周了. 

弄完備餐後準備來解決側門紗門的門樞更換問題, 上個月就壞掉了, 但最近都在忙太陽能都沒時間處理. 但研究了半天, 發現更換紗門門樞不是想像中那樣簡單, 光是要拆舊的下來都不知從何著手, 只好叫做鋁門窗的阿昌過來處理, 半小時就搞定, 連工帶料才 500 元而已, 爸去滿滿五金買的門樞就 550 元, 早知道上個月叫阿昌來就好了. 

本周學習重點聚焦在 MicroPython, 也看了一些 GPT-3 的資料. 最近在臉書社團看到很多大師在玩 chatGPT, 這是從 InstructGPT 演化而來的社會化 GPT, 月初時 OpenAI 推出後非常紅火, 居然會寫程式, 還能作詩與回答關於金剛經的問題 (中央資工蔡宗翰老師的測試), 實在太強了, 最近上課時老師也秀了一下叫 chatGPT 用 Python 畫 sigmoid 與其導數的圖形, 結果是正確的. 不過昨晚參加資料科學小嫩嫩線上課程, 發現 chatGPT 也有答錯的時候, 例如它回答 27 不能被 3 整除. 最近有空我也想來申請一個帳號玩玩, 哈哈.