2020年5月30日 星期六

理解 Arduino 運作方式

參考資料:
1. https://medium.com/home-wireless/use-visual-studio-code-for-arduino-2d0cf4c1760b
2. https://tttapa.github.io/ESP8266/Chap01%20-%20ESP8266.html


Arduino 用的是 c/c++ 語言來開發韌體(firmware), Arduino 的主程式碼通稱為 sketch (例如副檔名是 .ino 的 app.ino), 至於 c 語言的主程式進入點是 main( ), Arduino 的 sketch 實際將它分成 setup( ) 及 loop( ) 兩部份, setup( ) 只會執行一次, loop( ) 則會不斷重複執行, 也就是說 sketch 可以看成是在 c 語言 main( ) 裡會呼叫執行的兩道副程式:
void main( ) {
     setup( );
     while( true )  loop( );
}

在 linux 底下要開發 Arduino, 須下載 arduino SDK, 目前最新版本是 1.8.12:
https://www.arduino.cc/download_handler.php?f=/arduino-1.8.12-linux64.tar.xz
將它解壓縮到指定目錄 (ex. /home/mint/Downloads/arduino-1.8.12), 例如
cd /home/mint/Downloads
tar -xvf  arduino-1.8.12-linux64.tar.xz

若要用 vscode 開發 Arduino, 首先要安裝 Arduino 插件(Extensions), 接著在 vscode 開啟 Settings 設定環境變數,  搜尋 Arduino:Path, 將上述目錄例如 /home/mint/Downloads/arduino-1.8.12 填入, 如果要開發 ESP8266 需要到 Settings 再搜尋 arduino.additionalUrls, 點進去編輯 settings.json , 在 arduino.additionalUrls 欄位填入 "https://arduino.esp8266.com/stable/package_esp8266com_index.json", 像是這樣:
{
    "arduino.enableUSBDetection": false,
    "arduino.path": "/home/mint/Downloads/arduino-1.8.12",
    "workbench.startupEditor": "newUntitledFile",
    "arduino.additionalUrls": "https://arduino.esp8266.com/stable/package_esp8266com_index.json"
}

之後在命令列 View -> Command Palette (或快捷鍵 F1) 搜尋 Arduino 就能看到一堆如同在 Arduino IDE 上可以使用的 cli 命令. 不過剛開始時要先開啟一個專案夾, 並在 View -> Command Palette 選擇 Arduino:Initialize 跑一遍, 初始化一個 Arduino 開發環境, 接著像是 Board Manager, Board Config, Select Serial Port , Library Manager 才能開始使用, 在 Board Manager 可以安裝開發板的 SDK, 例如搜尋 ESP8266 接著安裝, 程式就會安裝到  ~/.arduino15/packages 目錄裡面, 在 Board Config 可以直接去更改開發板用的 SOC, 在 Select Serial Port 可以改變作業系統序列裝置的虛擬位置, 根據選擇也會相對應改變 arduino.json 裡面 board, port 及 configuration 的內容, 至於 Library Manager 可以安裝社群上已經寫好的程式庫, 下載的程式庫, 將會放在目錄  ~/Arduino/libraries 裡面.

以下是修改 .vscode/arduino.json 及 .vscode/c_cpp_properties.json 的檔案內容, 例如:
# arduino.json
{
    "sketch": "app.ino",
    "output": "./build",
    "port": "/dev/ttyUSB0",
    "board": "esp8266:esp8266:d1",
    "configuration": "xtal=80,vt=flash,exception=legacy,ssl=all,eesz=4M2M,ip=lm2f,dbg=Disabled,lvl=None____,wipe=none,baud=921600"
}
上面輸出目錄  output 可以加速後續的 compiler 時間, 可以自行指定到快速區, sketch 就是主程式

# c_cpp_properties.json
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceRoot}",
                "/home/mint/.arduino15/packages/esp8266/tools/**",
                "/home/mint/.arduino15/packages/esp8266/hardware/esp8266/2.7.1/**",
                "/home/mint/Arduino/libraries"
            ],
            "forcedInclude":[ ],
            "defines": ["USBCON"],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}
上面 defines 的 USBCON 是支援讓作業系統可以打開序列終端機(Serial port console), 到此設定完成後, vscode 版的 Arduino IDE 應該就能正常運作了. 一個超簡單運行在 ESP8266 上的 http 伺服器範例:
// app.ino
#include "ESP8266WiFi.h"
#include <ESP8266WebServer.h>
char str[2000];
char placeHolder[] = {"<html><head><meta charset='utf-8'></head><body>it works: %ld</body></html>"};
ESP8266WebServer http(80);
void setup( ) {
  const bool softAP    = false;
  const char* ssid     = "myAP";
  const char* password = "myPW";
  Serial.begin(115200);
  if (softAP) {
    WiFi.softAP(ssid, password);
    Serial.println(WiFi.softAPIP( ));
  } else {
    Serial.println("Link to wifi ap to get ip address");
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
      delay(1000);
      Serial.print(".");
    }
    Serial.println(WiFi.localIP( ));
  }
  http.on("/", [ ]( ) {
    sprintf(str, placeHolder, millis( ));
    http.send(200, "text/html", str);
  });
  http.onNotFound( [ ]( ){
    http.send(404, "text/plain", "404: Not found");
  });
  http.begin( );
}

void loop( ) {
  http.handleClient( );
}


在 vscode 下方狀態列有個插頭的符號可以開/關序列終端機, 為了透過序列阜與開發板作溝通, 在 linux 上可以針對撥接群組 dialout 添加使用者 mint 來賦予使用權限:
sudo  usermod -a  -G  dialout  mint

重新開機讓它生效, 最後在 vscode 右上方中間的磚牆符號可以驗證並編譯 sketch 程式碼的完整性, 其左邊注滿容器的符號則是編譯 sketch 主程式後就直接上傳到開發板運行.


後記 ESP8266 GPIO:
1. 總共17個3.3V的GPIO, 最高承受到3.6V, 電流驅動能力最高12mA, D0~D15內有提升(pull up)電阻, 透過呼叫 pinMode(gpio#, INPUT_PULLUP) 就能減少外掛提升電阻, 但要注意 GPIO0, GPIO2, GPIO15 帶有 bootstrapping 功能, 很有可能外加的電阻使得內建的高提升電阻值而失效,甚至造成輸入邏輯的準位判斷異常, 至於 GPIO16 則是內建下拉(pull down)電阻.
    GPIO 0(D3): Boot SPI, 開發板通常會外加提升電阻, 見下表
    GPIO 1(TX): UART TX0
    GPIO 2(D4): Boot SDIO, 開發板通常會外加提升電阻, 見下表
    GPIO 3(RX): UART RX0
    GPIO 4(D2): I²C_SDA, I²C data
    GPIO 5(D1): I²C_SCK, I²C clock
    GPIO 6~11: 外掛 Flash ROM
    GOIO 12(D6):  MISO, 高速 SPI 主輸入僕輸出
    GPIO 13(D7):  MOSI, 高速 SPI 主輸出僕輸入
    GPIO 14(D5):  SCK, 高速 SPI clock
    GPIO 15(D8):  SS, 高速 SPI select, 開發板通常會外拉下地電阻,見下表
    GPIO 16(D0):  Wakeup,  喚醒
設定 GPIO15, GPIO0, GPIO2  的開機模式 bootstrapping:
     low , low, high  : 從 UART 載入程式
     low , high, high :  從 SPI 載入程式
     high, x, x :  從 SDIO 載入程式
2. 另外還有一個類比通道(10 bits adc), 最高承受到 3V, 測量範圍是 0 ~ 1 V

2020年5月26日 星期二

Solder Mask 與 Paste Mask 的差別


1. 就字面意義來說 Solder Mask (防焊層)就是要把焊接(電氣)層遮掉, Paste Mask 則是要把護膠層遮掉,兩者使用時機並不同, 前者用在 PCB 製程, 後者用於主機板組立製程(PCBA)開鋼板.
2. 在 PCB 的製程中,一塊銅箔基板,為了要線路化,會貼上阻光膠,然後將 Solder Mask 底片放到含阻光膠的銅箔基板上,用紫外線曝光,經過紫外線曝曬,未遮住的地方就能用洗滌劑把該處阻光膠清除,留下的殘膠位置就是線路圖,接者將曝光洗滌過線路板放入強酸池加以蝕刻,曝光部位洗除後只會留下線路圖.
3. 主機板組立時為了要讓 SMT 零件上件,須先刷一層錫膏黏上零件, 因此要用 Paste Mask 層去製作鋼板,將鋼板對準 PCB 後在開孔處刷上一層錫膏,經過熱處理錫膏熔化貼合就能完成 SMT 製程
4. 因此 Solder Mask 是用於 pcb 的線路化製程. 而 Paste Mask 是在 PCBA 開 SMD PAD 的鋼板用的, 開孔大小可根據經驗適時加以調整.


2020年4月7日 星期二

世紀之毒

近期的武漢肺炎疫情在全世界肆虐,主因中國共產黨的隱匿資訊與錯誤思維所造成的世界災難,巧合的是100多年前,當時的清廷鼓勵義和團扶清滅洋導致了八國聯軍攻擊中國的行動.歷史似乎正在重演,今日武漢肺炎流行之際,中共利用大外宣對內用仇外情緒搭配一些不可思議的媒體神話病態的在操弄愛國情操,對外用醫療物資勒索全世界,像是綁架法國的醫療物資用來要換取採用中國華為的 5G 技術,像是武漢封城後開始搜刮全世界醫療口罩,像是派遣醫療團隊到世界各地去"表演",一付事不關己的態度,連對世界道歉的誠意都沒有,正當全世界鬧口罩荒之際,又逼迫各國低頭向中國高價購買醫療設備等等,都是名副其實的趁人之危之舉. 對比之下, 台灣就大方多了,慷慨捐了 1000 萬口罩到西方世界,這就是獨裁政權與民主國家的分別,人道與人權主義應該是超越種族主義的,這點恐怕只有在民主國家的人民才有辦法去體會,這也正是全世界要去聲討中國,要中國付出代價的最大原因.有點諷刺好笑的是,這次疫情比較像是中共發動的第三次世界大戰,想要報復100多年前的舉動.小時候常聽人說過一句話:君子報仇,三年不晚,不是不報,只是時候未到,也許中共認為該是武漢肺炎放出來的時候了吧!網路上對 covid-19 有個新奇的註解是: China Output Virus In December 2019, 真是一絕又經典.世界如能慢慢了解到中共的邪惡本質,反擊與劃清界線,這才是正解,至於亡羊補牢或許尤未晚矣. 八國聯軍實際上是因滿清政府無能腐敗的政權所導致,與現今共產主義的侵略各國行動或許主客觀立場迥異,但本質卻如出一徹,都是獨裁政權導致的世紀災難,這次武漢肺炎造成全世界大流行,與其說是一個規模如世界大戰的一場戰疫,不如說是暴露中共政權狐狸尾巴的冰山一角.要知道共產主義早在世界各地滲透多時,要根除並不容易,中國大陸的人民長久以來被洗腦,終其一生都被蒙在鼓裡,無法了解與認清事實或許還情有可原,但身在牆外的世界,卻不得不認清一個事實,那就是獨裁政權(中國共產黨)才是世紀之毒瘤.

後記:
今天(2020.4.9)在 youtube 上,看到 WHO 的 Tedros 盡然怪罪台灣對它作人身攻擊說什麼 "種族歧視" 莫須有的罪名,台灣並非聯合國一員,在世界上沒有話語權,Tedros 利用 WHO 的地位,在會議上公開譴責台灣,實在有欠公道,甚至還罵美國總統川普將它政治化,殊不知當 Tedros 站在 Taiwan 對立面譴責時,本身就已帶了親中的政治色彩正在替匪宣傳,全世界唯有中國共產黨看不慣台灣的作為,無所不用其極想將台灣邊緣化,台灣在這場戰役堅守下來,尚未大規模感染,全世界有目共睹,Tedros 卻在旁吃味,盡是胡言亂語,可惡無恥之極,無怪乎大家都看不下去,要它下台的呼聲會這麼高,這是它喪失領導威信最主要原因,非洲一些國家的總統儘然還力挺 Tedros,當歐美各國都被武漢病毒打的快招架不住之際,非洲國家如果還信 WHO/Tedros 那一套,下一個爆發地點,我們只能說,那就等著看吧,台灣只能默默祈禱,盼能度過武漢肺炎這難關.

今天(2020.4.19)看到新聞關於 Tedros:
1. https://news.ltn.com.tw/news/world/breakingnews/3135105
2.https://tw.news.yahoo.com/%E8%AD%9A%E5%BE%B7%E5%A1%9E%E9%81%AD%E8%B5%B7%E5%BA%95%E6%8E%8C%E6%AC%8A-%E6%81%90%E6%80%96%E7%B5%84%E7%B9%94-%E8%AC%80%E6%AE%BA-%E6%BF%AB%E6%8D%95-%E7%94%A8%E5%88%91%E6%A8%A3%E6%A8%A3%E4%BE%86-104647758.html
原來 Tedros 有共產黨的的背景,它在衣索匹亞所屬政黨 "提格雷人民解放陣線" 實際上就是共產黨,統治人民的方式與共產黨如出一冊就是槍桿子出政權,毫無人權可言,難怪 Tedros 所作的決定都親中,這下命案揭曉也終於明白了.共產黨是一個善長用民族主義來製造仇恨鬥爭的邪惡組織,難怪 Tedros 想如法泡製,利用種族主義以 "種族歧視" 莫須有罪名污衊台灣,台灣是一個愛好和平的國家怎麼會輕易上當呢? 所以一系列的美食攻擊就耀上國際版面,許多在台外國人紛紛響應 #thisattackcomefromtaiwan 力挺台灣,也要讓全世界了解台灣的真善和美,相信事實是勝於雄辯的!

2020年3月26日 星期四

dart 程式庫(package)管理

參考官網文章:  https://dart.dev/guides/packages
dart 使用檔案 pubspec.yaml 指引程式庫(package)的運作邏輯, pubspec.yaml 裡面最重要的是 name: 指引,用來標示專案的名稱,同時也代表該專案程式庫(package)所處最上層路徑, 將專案名字寫在檔案裡面的好處是:若將最上層目錄搬移或更名也不影響程式的運作. 其次是 dependencies: 區塊指引,用來紀錄該專案與外部程式庫之相依性, 相關的程式庫須用 tab 鍵或是空白鍵先縮排(indent),再使用"名稱:版本"的格式一一分行列表, 另外可以用符號 # 當註解,例如:
#pubspec.yaml
name:  myPackage
dependencies:
  ffi: 0.1.3  #外部程式庫 ffi, 需求版本 0.1.3
  crypto: 2.1.4  #外部程式庫 crypto, 需求版本 2.1.4

開啟終端機, 執行 pub get 後就會根據 pubspec.yaml 指引去安裝(或下載)好相關的程式庫,安裝好的結果會儲存在檔案 .packages 裡面. 整個專案目錄結構示範如下:
myPackageRoot -  #專案根目錄
      lib -  #內定專屬程式庫(package)目錄起點
            maintain.dart  #程式庫維護原始碼
            src -  #專案其它程式原始碼目錄
                  utility.dart  
                  help.dart
     main.dart #專案主程式原始碼

內定專屬目錄 lib 會被當成是專案外部程式庫的參考起點,通常在 dart 代碼若是一開頭使用 import 'package: ...' 或是 export '...' 時, 就會以 lib 目錄當參考點去引入相關的檔案,因此可以準備一個檔案例如放在 lib/maintain.dart 專門當作程式庫維護程式碼, 在裡面用 export 方式間接引入檔案或是用 import 方式直接引入各項程式庫. 通常習慣是在 lib 目錄下再新增一個目錄名稱,例如 src, 把該專案的其餘程式全放在 lib/src 目錄底下(如上述範例)當成該專案的專屬程式庫, 未來只要將該目錄(src)複製到其他專案裡面, 就能重複使用此專屬程式庫,甚至萬一把程式庫目錄 src 改名了, 也只要修改維護程式內的相對目錄 src 就可. 總之, 目錄結構更動是維護程式碼的主要任務,盡可能不影響程式庫的運作邏輯:
// maintain.dart
export 'src/utility.dart';  // 間接從路徑 lib/src 引入檔案 utility.dart
export 'src/help.dart'; // 間接從路徑 lib/src 引入檔案 help.dart
// import 'dart:html';// 直接引入 dart 核心程式庫 html
// ...

經過上述規劃後, 主程式只要將 maintain.dart 引入就可, 乾淨俐落, 但須注意的是, 當要引入該專案的專屬程式庫(package)時, 須先指明專案名稱, 換句話說專案名稱也同時身兼專案專屬程式庫的角色:
// main.dart
import 'package:myPackage/maintain.dart'; //直接從 myPackage 引入 lib/maintain.dart
main( ) {
      // ...
}

2020年3月25日 星期三

從 dart 呼叫 javascript


參考官網文章:  http://dartdoc.takyam.com/articles/js-dart-interop/


// index.html

<html><head><meta charset='utf-8'></head><body>
    <script type='application/javascript' src='hello.js'></script>
    <script type='application/javascript' src='main.js'></script>
</body></html>

// hello.js
var greeting="Hello"; // 1. variable

function fun( ){ //2. function
    console.log("How are you?");
}

class Rectangle {
    constructor(width, height) {
      this.width = width;
      this.height = height;
    }
}
function rectangle(width, height) { // 3. object
  return new Rectangle(width, height); // return an object
}

// main.dart
import 'dart:js';
void main() { 

  var greeting = context['greeting']; // 1.
  print("$greeting");

  context.callMethod('fun'); //2.

  final rectangle = JsObject(context['rectangle'], [5, 1]); //3.
  print(rectangle['width']);
}

2020年3月23日 星期一

dart 設定好開發 javascript 的工作環境: dart2js

1. 開啟 vscode, 搜尋並安裝好插件 debugger for Chrome

2. 開啟專案的目錄

3. 設定好執行 debug 的環境, 準備讓 chrome 開啟 index.html 檔案, 只要點選 vscode 的選單 Debug -> Open Configurations, 在開啟 launch.json 後, 填入以下文字, 主要是填入讓 chrome 開啟 url 檔案所在位置, 並設定好工作的根目錄:
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "執行 Chrome 開啟 index.html 檔",
            "url": "file:///${workspaceFolder}/index.html",
            "webRoot": "${workspaceFolder}"
        }
    ]
}

4. 設定好將 dart2js 的環境, 準備將 dart 語法轉換為 javascript, 只要點選  vscode 的選單 Terminal -> Configure Tasks, 在開啟 tasks.json 後,  填入以下文字, 主要是將 dart2js 執行檔的絕對路徑填入 "command" 所指示的位置, 並設定好一些必要參數 args:
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "dart2js",
            "type": "shell",
             "command": "/home/mint/Downloads/flutter/bin/cache/dart-sdk/bin/dart2js",
            "args": [
                "${file}",

                "-o",
                "${fileBasenameNoExtension}.js"
            ],

            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

5. 撰寫 index.html 要讓 Chrome開啟 url 後, 自動執行經 dart2js 轉換後的 main.js:
 <html><head><meta charset='utf-8'></head><body>
    <script type='application/javascript' src='main.js'></script>
</body></html>

6. 用 dart 語言在 html 的 canvas 上繪製 sin(2πft) 函數圖, 範例主程式 main.dart:
// main.dart
class CanvasSin {
  final DivElement  div  = document.createElement('div');
  final CanvasElement canvas = document.createElement('canvas');
  final int width, height;
  get fullYscale => height~/2;
  CanvasRenderingContext2D get ctx => canvas.getContext("2d");
  void penline(int x1, int y1, int x2, int y2) => ctx .. strokeStyle="red" ..
       beginPath( ) .. moveTo(x1, y1) .. lineTo(x2, y2) .. stroke( );
  void get penOrigin => ctx .. save( ) .. translate(0, fullYscale) .. scale(1, -1) ..
       beginPath( ) .. moveTo(0, 0);
  void get penFinish => ctx .. strokeStyle="green" .. stroke( ) .. restore( );
  void penTo(int x, double y) => ctx.lineTo(x, y);
  CanvasSin(this.width, this.height) { 
      document.body.append(div);
      div.append(canvas);
      canvas.style.border = "1px solid";//外框 1 點
      canvas.width  = width;
      canvas.height = height;
      penline(0, 0, 0, height);// y-axis
      penline(0, fullYscale, width, fullYscale);// x-axis
      int freq = 2;
      double f(int t) => fullYscale * sin(2.0*3.14159*freq*t/width);// sin(2πft)
      penOrigin;
      for(int t = 0; t < width; t ++) penTo(t, f(t));   
      penFinish;
  }
}
void main( ) => CanvasSin(800, 600);

7.目前 dart2js 僅能編譯整個內含 main( ) 主程序的 dart 原始碼, 無法針對個別單一程式碼去編譯, 經上述步驟 4 設定好後, 要將目前用 dart 語言編輯的主程式轉換為相對應的 javascript 程式碼, 只要按下快捷鍵 ctrl-shift-B, 或是用選單 Terminal -> Run Task 選取 dart2js 直接編譯

8. 經上述步驟 1~6 後, 按下快捷鍵 F5, 或是用選單 Debug -> Start Debugging  就能讓 Chrome 直接開啟 url 執行所轉換後的 javascript


2020年3月20日 星期五

用 dart 語言簡單驗證 ECDSA 演算法

dart 語言支援大數(BigInt)的四則運算,且又能將運算元重新定義運算規則(operator overload),而且 dart 在 2.7 版以後支援了 extension 語法, 可以自行添加方法到 BigInt 上, 簡直如虎添翼,因此驗證 ECC(橢圓曲線密碼學) 的數位簽證演算法(ECDSA)就容易多了:
// main.dart to verify ECDSA
import 'dart:math';
BigInt random(BigInt  m) {
        BigInt temp = BigInt.from(0);
        int bits = m.bitLength;
        while (bits > 0) {
            temp = temp << 8 |  BigInt.from(Random(DateTime.now( ).microsecondsSinceEpoch).nextInt(255));
            bits -= 8;
        }
        return temp % m;
}
extension Divider4ECDSA on BigInt {  // a method .__ to operate / % m
  BigInt __(BigInt divisor, BigInt m) => this * divisor.modInverse(m) % m;
}
class ModField {
  BigInt _value;
  BigInt get value => _value;
  int get mostbits => _value.bitLength - 1;
  static BigInt _prime;
  BigInt get p {
    if(_prime == null) _prime = BigInt.parse("115792089237316195423570985008687907853269984665640564039457584007908834671663");
    return _prime;
  }
  static BigInt parse(var temp) =>
      temp is ModField ? temp._value   :
      temp is String   ? BigInt.parse(temp):
      temp is num     ? BigInt.from(temp) :
      temp is BigInt  ? temp : BigInt.zero;
  bool testbit(int k) => _value >> k & BigInt.one == BigInt.one;
  bool operator ==(var other) => _value == parse(other)
  bool operator < (var other) => _value < parse(other);
  bool operator > (var other) => _value > parse(other);
  ModField operator ^ (int exponent) => ModField(_value.pow(exponent));
  ModField operator + (var other) => ModField(_value + parse(other));
  ModField operator - (var other) => ModField(_value - parse(other));
  ModField operator * (var other) => ModField(_value * parse(other));
  ModField operator %(var other) => ModField(_value % parse(other));
  ModField operator / (var other) => ModField(_value*parse(other).modInverse(p));
  ModField (var temp) {
        _value = parse(temp) % p;
        if (_value < BigInt.zero) _value =  _value + p;
  }
}
class Secp256k1 { // y^2 = x^3 + 7 mod p, ECC order number= GFn
  ModField n, x, y;
  bool get isPoint => y^2 == (x^3) + 7;
  Secp256k1(var temp) {
        if (temp is Secp256k1) { // copy constructor
            n = temp.n;
            x = temp.x;
            y = temp.y;
        } else {
            n = ModField(temp);
            x = ModField("55066263022277343669578718895168534326250603453777594175500187360389116729240");
            y = ModField("32670510020758816978083085130507043184471273380659243275938904335757337482424");
            if(n > 1) {
                 _selfMul(n);// 1 Gn(x, y)
                 n = ModField(1);
            }
        }
  }
  Secp256k1._point(gx, gy) {
        n = ModField(1);
        x = ModField(gx);
        y = ModField(gy);
  }
  factory Secp256k1.G([var gx=0, var gy = 0]) {
       if(gy == 0)  return  Secp256k1(1);
       final temp = Secp256k1._point(gx, gy);
       return temp.isPoint ?  temp :  Secp256k1(1);
  }
  Secp256k1 get _selfX2 { // point doubler
       final px = x;
       final s = (x^2) * 3 / (y*2); // s = 3*x*x / 2y
       x = (s^2) - x*2; // x = s*s - 2x
       y = s * (px - x) - y; // y = s * (px - x) - py
       return this;
  }
  Secp256k1 _selfAdd(ModField gx, ModField gy, {gn = 1}) { // point adder
      if (x == gx)  n = ModField(0);
      else if (n == 0) {
                x = gx;
                y = gy;
                n = ModField(gn);
      } else {
              final s = (y - gy) / (x - gx);
              x = (s^2)  - x - gx; // s*s - x - gx
              y = s*(gx - x) - gy; // s(gx -x) - gy
      }   
      return this;
  }
  Secp256k1 _selfMul(ModField multiplier) {// point scale by DAA
      if (multiplier > 1) {
            var bits =  multiplier.mostbits;
            final gx = x;
            final gy = y;
            while (bits-- > 0) {
                _selfX2;
                if (multiplier.testbit(bits))  _selfAdd(gx, gy);
            }
      }
      return this;
  }
  get dump => print("${n.value} G(${x.value},${y.value})");
  static BigInt _order;
  get GFn {
    if(_order == null) _order = BigInt.parse("115792089237316195423570985008687907852837564279074904382605163141518161494337"); 
    return _order;
  }
  operator + (Secp256k1 Q) => Secp256k1.G(x, y)._selfAdd(Q.x, Q.y);
  operator * (var scale)   => Secp256k1.G(x, y)._selfMul(ModField(scale));
  List<BigInt> signECDSA(BigInt d, BigInt z) { // use key d to sign z, % use GFn 
        final BigInt k = random(GFn);
        final BigInt r = Secp256k1(k).x.value; // r = G.x
        final BigInt s = (z + r * d) .__ (k, GFn); // s = (z + r * d)/k % GFn
        return [z, r, s];
  }
  bool checkECDSA(List<BigInt> signature) {
        if(signature.length < 3) return false;
        final BigInt z = signature[0];
        final BigInt r = signature[1];
        final BigInt s = signature[2];
        final BigInt u  = z .__ (s, GFn); // u = z/s % GFn;
        final BigInt v  = r .__ (s, GFn);  // v = z/s % GFn;
        final Secp256k1 R = Secp256k1.G( )*u + Secp256k1.G(x, y)*v;
        return R.x == r;
  }
}
main( ){
  final text = BigInt.from(123);// 待簽字
  final secret =  BigInt.from(123456789);  // 私鑰
  final qPublic = Secp256k1(secret); // 公鑰
  final signature  = qPublic.signECDSA(secret, text);// 用 ECDSA 簽證文字產生特徵碼
  qPublic.dump;
  print( qPublic.isPoint);
  print(qPublic.checkECDSA(signature) ? "OK": "Fake!");// 用公鑰驗證特徵碼
}
執行 dart main.dart 看結果:
1 G(4051293998585674784991639592782214972820158391371785981004352359465450369227 ,
    88166831356626186178414913298033275054086243781277878360288998796587140930350)
true
OK

2020年3月16日 星期一

用 dart 語言產生一個簡單的區塊鏈

先寫一個 pubspec.yaml 檔案用來下載 package:crypto, 接著開啟終端機執行 pub get 就可:
// pubspec.yaml
name: blockchain
version: 0.0.1
description: >-
  A super simple example of blockchain
author: masontseng
environment:
  sdk: '>=2.6.0 <3.0.0'
dependencies:
  crypto: ^2.1.4

編輯主程式 main.dart, 接著開啟終端機執行 dart main.dart 看執行結果:
// main.dart
import 'dart:convert';
import 'package:crypto/crypto.dart';
class Block {
    final int nonce, index, timestamp;
    final String data, pre_hash;
    get plain  => "${nonce}-${index}-${data}-${timestamp}-[${pre_hash}]";
    get hash  => sha256.convert(utf8.encode(plain)).toString( );
    get dump => print("${plain} [${hash}]");
    factory Block.genesis( ) => Block(0, 0, "genesisBlock", 1584332296607000, "0");
    Block(this.nonce, this.index, this.data, this.timestamp, this.pre_hash);
}
class BlockChain {
    var field = [ ]; //field of blockchain is a matrix
    set blockchain(var temp) {
      if(temp.length <= field.length) return;
      if(temp[0].data != field[0].data) return;
      for (var i = 1; i < field.length; i++) if (! isBlockValid(temp[i], field[i - 1])) return;
      for (var i = field.length; i < temp.length; i++) if (! isBlockValid(temp[i], temp[i - 1])) return;
      field = temp;
    }  
    get blockchain => field;
    get dump { for (var i = 0; i < field.length; i++) field[i].dump; }   
    Block powMineBlock(String eData, {int nonce=0}){
      final lastblock = field[field.length - 1];
      final timeNow = DateTime.now( ).microsecondsSinceEpoch;
      return Block(nonce, lastblock.index + 1, eData, timeNow, lastblock.hash);
    }
    void addBlock(String e) => field.add(powMineBlock(e, nonce: 1));
    bool isBlockValid(Block block, Block pre) =>
      block.index    == pre.index + 1  &&
      block.pre_hash == pre.hash
    bool isValid( ) {
      for (var i = 1; i < field.length; i++) if (! isBlockValid(field[i], field[i - 1])) return false;
      return true;
    }
    BlockChain( ) {  field.add(Block.genesis( )); }
}
main( ){
  final c = BlockChain( );
  for(int i=1; i<10; i++)  c.addBlock("extendBlock${i}");
  c.dump;
  print(c.isValid( ));

  final d = BlockChain( );
  d.blockchain = c.blockchain;
  d.dump;
  print(d.isValid( ));
}
後記:
1. 理解 proof of work (pow): 是一個群體共識決(交易方法),他是針對一個數學問題,很容易驗證但很難找到答案,或許只能用嘗試錯誤法,有點類似樂透的方式猜出答案,但不管如何,只要能解答讓大部分的人同意並形成的共識決,最後將結果紀錄於區塊鏈內,解題者就可獲取報酬,缺點是要浪費電腦的計算力
2. 理解 proof of stake (pos): 也是一個群體共識決(交易方法),交易發行者針對交易行為事先在網路上廣播,擬參與交易認證者事先以一定比例(賭注/持有者資金)資金注入系統,該筆資金將在網路中暫時封鎖無法交易,有點類似價金履約保證的概念當作履約保證金,此封鎖的資金稱為 stake,擬入區塊鏈的區塊就由該履約保證者提出並上呈網路供公眾確認.最後一旦交易完成並進入區塊鏈,提出履約保證者就能獲得一定比例的報酬作為手續費,封鎖的資金也同時解凍,但若違反規則,也會獲得懲罰,像是沒收保證金等等. pos 解決了耗費電腦運算的麻煩,但隨之而來的是要解決 nas(Nothing At Stake)的問題.

2020年3月14日 星期六

關於武漢肺炎

       武漢肺炎(covid-19) 源自中國大陸武漢地區, 起初(約2019年12月)一些中國年輕醫生(李文亮等8名醫生)發覺有一批不明原因肺炎導致死亡的案例, 在社群軟體內提醒親朋好友注意小心防患這種類 SARS 病毒再次掘起,也因此導致被中國的公安約談,訓斥其擾亂社會秩序的行為,並且禁止其再發表武漢肺炎相關的言論,中國官方甚至還大言不慚宣稱不會人傳人,到了2020年1月23日中國官方卻展開對武漢封城的行動, 許多人徹夜逃離武漢,病毒也因此開始向四處擴散,同日台灣在機場檢疫篩檢時發現了第一個武漢肺炎案例,是一個從武漢回來的台商,至於中國大陸的李醫生也隨後在醫院被傳染,並且不幸於 2020年2月6日死於武漢肺炎,在中國內部網路上一度掀起中國言論自由的浪潮追悼李醫生,呼籲響應"#不能#不明白"的網路行動,但是在中國強制言論封鎖措施下,這些言論在中國內部一夕之間化為烏有,甚至各種相關的關鍵詞也消失殆盡.各種報章媒體紛紛追起武漢肺炎的源頭,最終指向大約在去年(2019)11月中一間武漢醫院落腳的零號病人,於此展開傳染與肆虐,因此才會命名為武漢肺炎.世界衛生組織(WHO)卻受惠於中國大陸資金的援助,卻不敢對中國的隱匿疫情,延遲傳染病處置措施,沒保護人民免於疾病之威脅等等提出任何挑戰,這些理當是一個自稱世界衛生機構組織當有的基本作為與主張,不僅如此,秘書長譚得賽等一些WHO的領袖們反而對中國百般的歌功頌德,讚美中國的封城舉動,殊不知其已陷入中國共產黨大外宣的陷阱當中,最終將導致世界大爆發,一場人類最嚴重的瘟疫即將展開,WHO無疑是一個最大幫凶,甚至還曾以不要造成污名化(中國武漢)為由,更改病毒名稱成現在的 covid-19. 現今世界上有多少是因發生地點而命名的致命病毒,像是德國麻疹,日本腦炎,西班牙流感,伊波拉病毒等等,都是要讓世人了解謹記病毒歷史的教訓避免事件重演,它只是一個世界文化的遺產,完全不會有污名化的餘慮或觀感.尊敬歷史並理解歷史根源才是對一個歷史事件發生所需的正確態度,一昧的將它從歷史中抹除,無疑就是一個歷史罪人,正所謂"此地無銀三百兩",像WHO這種一反常態的行為與舉止可想而知,其信用將跌入谷底,無怪乎有人戲稱WHO為CHO(附隨中國的衛生組織).
        近日中國官方甚至丟出陰謀論說武漢肺炎來自美國,中國共產黨真是無恥到極點.要知道唯有中國共產黨才會一心一意想要洗清歷史罪名,就好比當年文化大革命般,實際卻是對歷史鬥爭將黑的歷史說成白的,扭轉人民的思想,再譬如美其名的新疆再教育營,實際卻是一個思想改造大集中營,另外還有中國共產黨在1989年6月4日天安門大規模用機關槍及坦克屠殺學生的事件歷歷在目,但對中國內部卻直接將該段歷史抹除,如此般的玩弄文字遊戲,美化及合理化其所作所為,企圖就是要讓人民忘記中國共產黨所造就的人類浩劫,竄改歷史就是不想反省之前犯下的罪過,只要將壞事從歷史上消除,讓人以為世界太平,這才是它一貫的技倆與目的,居住在中國大陸的年輕人,現恐怕早被洗腦對該#8964事件完全沒有任何記憶.這就是中國共產黨最厲害的地方,它讓你對他的行為完全無任何警覺,私底下在中國內部只要有不利的中國的言論,不斷用另外正向力量來合理化其殘酷的行為,宣傳形成主流意識,對於不當言論一律將它抹除,目的是箝制中國大陸內部的言論自由,讓人民只能相信政府的所作所為,這就夠了,對於世界如何看待中國或是中國共產黨根本無所謂,也無視一個人類該有的言論自由等等的基本人權,導致事實無法忠實呈現,疫情也不誠實申報,讓世界摸不著頭緒,錯失防堵契機
        因此我們要追本溯源給歷史一個真相,中國共產黨箝制言論與隱匿疫情才是造成武漢肺炎世界大流行的罪魁禍首,而 WHO 雖未認清事實真相,但其領導人的消極作為與鄉愿行為卻成為幕後最大推手, 武漢病毒的來源的的確確來自中國武漢地區,一些坊間的報導透漏很可能是在武漢P4實驗室所製造出來,極有可能是管理不慎造成病毒洩漏,從中國軍方接管武漢P4實驗室來看,就能嗅出一些蛛絲馬跡,也是最合理的解釋.

2020年3月4日 星期三

用 grub2 boot loader 啟動 LMDE4

LMDE4 是 Linux Mint Debian edition 4的縮寫, 可以上官網去下載 iso 檔(https://linuxmint.com/rel_debbie.php), 開機時用 grub2 啟動作業系統, 只要修改 grub.cfg, 針對 Debian linux 只要修改啟動選項加入 boot=live 及 findiso(相較於  Ubuntu Linux使用的啟動選項是 boot=casper 及 iso-scan/filename), 就能直接啟動 iso 封裝的 linux , 例如以下範例, 加入一個啟動項目(menuentry):
menuentry "Boot LMDE4 iso" {
    set root=(hd0,2)
    set iso=/boot/lmde4.iso
    loopback loop $iso
    linux (loop)/live/vmlinuz    boot=live     findiso=$iso
    initrd (loop)/live/initrd.lz
}

2020年2月19日 星期三

dart 使用 ffi 和 c 語言溝通

參考官網文章: https://dart.dev/guides/libraries/c-interop
1. 使用 linux 作業系統, 安裝好dart sdk ,  c 語言編譯程式加上  cmake
2. 新增一個 dart 專案,  專案內新增一個 hello 目錄 , 進入 hello 目錄, 準備要編譯成  hello 模組(libhello.so):
2.1 先在 hello 目錄裡面編輯一個檔案 CMakeLists.txt:
cmake_minimum_required(VERSION 3.7 FATAL_ERROR)
project(hello_library VERSION 1.0.0 LANGUAGES C)
add_library(hello_library SHARED hello.c hello.def)
add_executable(hello_test hello.c)
set_target_properties(hello_library PROPERTIES
    PUBLIC_HEADER hello.h
    VERSION ${PROJECT_VERSION}
    SOVERSION 1
    OUTPUT_NAME "hello"
    XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Hex_Identity_ID_Goes_Here"
)

2.2. 為了指示要將函數(testWork, add, reverse 等等) 導出來(export)使用, 要編輯一個文字檔案 hello.def:
LIBRARY   hello
EXPORTS
   testWork
   add
   reverse

2.3. 編輯一個 c 程式,用來宣告函式原型的標頭檔 hello.h:
void testWork( );
int  add(int a, double b);
char *reverse(char *str);

2.4. 編輯 c 模組程式的原始碼  hello.c:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include "hello.h"
int main( ) {
    testWork( );
    printf("3 + 5 = %d\n", add(3, 5));
    printf("%s\n",reverse("Hello-String") );
    return 0;
}
void testWork( ) {
    printf("It works!\n");
}
int add(int a, double b) {
    return a + b;
}
char *_bufferPointer = NULL;
int _buffrLength = 0;
char *reverse(char *src) {
    int size = strlen(src);
    if(size == 0) return NULL;
    else if(size > _buffrLength) { // allocate if space not enough
        if(_bufferPointer != NULL) free(_bufferPointer);
        _bufferPointer = (char *)malloc(size + 1);//from heap
        _buffrLength = size;// maximum characters to hold
    }
    _bufferPointer[size] = '\0';// eos
    for (int i = 0; i < size; i++) _bufferPointer[size - 1 - i] = src[i];// reverse
    return _bufferPointer;
}

2.5 上述檔案都編輯完成後就能執行 make 作業, 製作成動態程式庫 (hello/libhello.so)
   cmake  .
   make

3. 在專案內新增 pubspec.yaml 檔案, 完成後, 開啟終端機執行  pub get 準備使用 ffi:
name: cinterop
version: 0.0.1
description: >-
  An example of calling C code from Dart through FFI
author: MyName<email@google.com>
environment:
  sdk: '>=2.6.0 <3.0.0'
dependencies:
  ffi: ^0.1.3
dev_dependencies:
  test: ^1.5.3

4.編輯一個透過 ffi  界面呼叫 c 語言的 dart 範例程式 main.dart:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
main( ) {
  final libso  = DynamicLibrary.open('hello/libhello.so');
  final testFun= libso.lookup<NativeFunction<Void Function( )>>('testWork').
                 asFunction<void Function()>();
  final addFun = libso.lookup<NativeFunction<Int32 Function(Int32, Double)>>('add').
                 asFunction<int Function(int,double)>( );
  final revFun = libso.lookup<NativeFunction<Pointer<Utf8> Function(Pointer<Utf8>)>>('reverse').
                 asFunction<Pointer<Utf8> Function(Pointer<Utf8>)>( );
  final c2dartString   = (Pointer<Utf8> _pointer) => Utf8.fromUtf8(_pointer);
  final dartstring2c   = (String _string) => Utf8.toUtf8(_string);
  final reverse = (String _string) => c2dartString(revFun(dartstring2c(_string)));
  testFun( );
  print('adder: 334 + 5 = ${addFun(334, 5.0)}');
  for(int i=0; i<20 ; i++)   print('${reverse("$i.Hello-string")}');
}

5. 用  dart VM 執行上述 main.dart 看看:  /pathToDartSDK/bin/dart   main.dart
It works!
adder: 334 + 5 = 339
gnirts-olleH.0
gnirts-olleH.1
gnirts-olleH.2
gnirts-olleH.3
gnirts-olleH.4
gnirts-olleH.5
gnirts-olleH.6
gnirts-olleH.7
gnirts-olleH.8
gnirts-olleH.9
gnirts-olleH.01
gnirts-olleH.11
gnirts-olleH.21
gnirts-olleH.31
gnirts-olleH.41
gnirts-olleH.51
gnirts-olleH.61
gnirts-olleH.71
gnirts-olleH.81
gnirts-olleH.91

後記: dart SDK 的 dart2native 可以將原始碼轉成可執行檔, 不用 dart VM, 直接就能執行:
/pathToDartSDK/bin/dart2native  main.dart  &&   ./main.exe

用 emcc compiler 寫簡單的 openGL 繪圖, 讓瀏覽器也能觀看

在瀏覽開啟 html 檔, 裡面除了可以用 javascript 語言來運行openGL ES, 也能用 wasm 語言來運作 , 透過 emcc 編譯器可以將 c 語言翻譯成 wasm, 安裝方式詳如 Emscripten 官網: https://emscripten.org/...