The Most Practical Codebase for ESP8266
Name it as TMPC-ESP8266-Gen1 code base for Arduino ESP8266,version 1.0。
- 大概從七月中到現在約一個月的時間,終於抵定此 code base 的寫作以整理出這份個人覺得相當實用,功能堪稱齊全足以做為基石的 ESP8266 code base。
- 這份 code base,是基於前專案的寫作動機,the human motions ir sensing project,才萌發想要再次整理出一份 codebase 的念頭與今終於如釋重負地實現了。
- 而此版與前(最後一)版專案的差異,便是將該專案中的 supervisor 的部份專用的扣納入 codebase 中,而整份專案便退位而成為 example;即,本篇文章所附的這份 codebase,確確實實地只是此 human motions 專案的改版而已 XD,不過,角色上與意義上可說截然不同。這真的是一份 code base!!!
- 應當補述,除上述外,還追加了 /time 中,可自訂時間的功能,不過也因加了它,竟如量子漲落般,既存的良扣(真的)平白生出新的 bug;哈,這只是自我安慰,是 bug 便是,該修的還是要修。
- 筆者如此地投入了大量的時間與精力,便是冀望能做出一份實用的,能輕易複用無負擔的扣倍使,除自用外也分享給各位 Arduino 同好。筆者的意思是,至少至少,本版的抵定 release,該測的,該除錯的,也都沒有偷懶省略。歡迎任何糾錯回饋,功能改進建議等等。
- 對了,忘了交代,這份扣倍使有使用到這篇文章的 lib,”ESP8266 使用 LittleFS/SPIFFS 儲存與載入 變數/參數/資料“,需去該處抓;當然也可不使用它,但就是會缺少儲存與檔案系統相關的功能。另外,最好是看一下前篇文章 human motions sensing 的介紹,較能瞭解 example 的功用與運作行為等。
- 再附加說明一個就是,ESPNOW 的使用並不是那麼友善,乃因為它的獨佔排他性。然而它的諸多優點也在前篇文章描述過。而此功能的包裝與實作在 codebase 裏也佔了不小的篇幅。因此對於 ESPNOW,其較佳的用法原則簡言之,在任一裝置,無論是 supervisors/servers/clients 的前置作業被完善後(devices been accessed via http completely)可說彼此已了無懸念,因此將被通知進入 ESPNOW mode;彼此亦將於適當時機進入(被動[被通知]後的主動[適時進入],或主動[通知他人]後的主動[適時進入])。並且亦有指令於 ESPNOW mode 中脫離 ESPNOW mode。再加上 supervisor 此角色的功用,就是讓使用者能夠穿透到 ESPNOW 空間內而絲亳感受不到 ESPNOW 的包袱。而這些都已包裝實現在 codebase 內了。
角色與場景和行為等因專案而異可說五花八門,要良順地溝通可說相形複雜,而 codebase 便讓這一切簡單多了。
20230811 update
冏了,才三天,bug 就冒出頭來了 XD
也很開心修掉這隻 bug。因為這隻我以前就都會這樣使用了。。。唉一直都沒發現到。。。純然的盲點。。。
還有其他部份的小修。
比一下扣便知改了什麼,改動一丁點而已不再說明。上扣。
- 沒有最好,只有更好,下一版註記
- 用到了 syncNTP,其須加入 syncMyTime,以先更新至最新時間才能被取代。
- whichMode 的 timeout 時間再加長。
- 扣倍使加入 /http?url 應是不錯的功能,可讓 devices 間接上網;這也可藉以突顯 ap+sta mode 的附加能力。
- 由於 ESPNOW 的響應時間短(長的話,誤差變異大例如 tcp),所以可思考實現在不同個體皆具均等性的前題下,利用廣播:零對多 -> 之一對多 -> 之二對多,達到三者以上時間同步的應用方法。當零對一,一除了 ESPNOW 傳輸及必要的運算延遲(set as D0)外,還包含了 setTime 的延遲;當一對零時,零便需做一次 setTime(但不改變)使得所有人同樣地落後,零同時可探求 D0 值;(至此初步同步);同時,二可同時藉由一以探求 D0 值;最後二發送 D0 值,零可判斷接受或 reject 此 D0 值。現假定零是中元標準時間那麼零將再廣播所確立的 D0 值供校正。對了,需有 id 標記該回的校正;協定上可接續某時距發送兩次取第二次以消除雜訊及判斷同步的可靠度。
- 所以在前一段的應用下,首先可加強扣倍使 ESPNOW 的運作邏輯,使得可輕易自動讓 n 個 devices 上線達到同步;就現有的扣只差臨門一腳了。
這第二支備份可以玩玩看;需準備剛好 6 支 devices,刷同樣的 code 不作任何修改,但記得加共同 ap 給用。簡單講,隨機産生 host/clients,全進入 espnow mode,隨機傳送 data 及廣播給其他人。可發覺空氣中的分子相當繁忙。。。六個 com window 之前有提過,可使用 coolterm。
對了還有因路由器良窊之故,程序初期,任一個 dev 都要去溝通到其他的 5 個 dev,而必須至少要有一個 dev 確實達成,才會使得進下一步,全進入 espnow mode。若遲遲未進下一步,則將提示的被 collected failed 的那(幾)個裝置 reset,接續應就可順利進行下去。
看著結果群魔亂舞算有趣可以玩玩看。
這是莫名分隔線
接下來,上重點了。同步。
- 思考題
- 難道同步真的有那麼難嗎?
- 是的,正如愛因斯坦所說,同步問題,無解!
- 不過也有人提出同步是物理稟性,可能例如量子糾纏。所以不需要解。
- 不過,若是加入一個前題條件,則同步問題很簡單就能解決,這。。。。。。,在筆者打完這段落就解決了。。。但可能就打上幾天幾夜吧XXD
- 須定義,光是各向均性(質)均性,光子不是物質所以只能以性稱,所以連用兩個均性。即,光再怎麼樣被虐待多久都還是光,還是各向定速。YT 有一則很棒的對岸的科普影片可搜來看看(關鍵字小學),光在介質中/例如水中,速度變慢,其詳細論述了速度仍不變,而是因一些物理作用而呈現變慢/我忘得一字不剩XXD
- 補充註記,後來再想一想申論。鏡像世界是上下前後全同左右相反的,所以前面提到無解的原因就是我們不知道光鏡像後性質是否有變異。故今退一步承認一次鏡像是全同,才有有解說。今而若再進一步再退一步說一次鏡像是不同的,但只要鏡像必全同的話(不考慮二次鏡像是非鏡像),即放寬光只存在一次變異,那麼我們是可以通過三次鏡像求出鏡像後的速度的。換言之,後面的處理方法,可進一步推導出,host 可單獨求出 host 至 client 所耗用的時間即便往返的時間是不同的且無需 client 提供任何時間資訊。
- 光與計算機科學?上述的,筆者只是要讀者類比即是。
- 真正的傳輸路徑區分如下,
- [産生傳送的意識念頭 A,即 Send() 那一個 token 開始] -> [內部處理 B(A) 至真正發送開始 B] -> [任何中介傳送 C,例如水,導線等] -> [從真正一接收到的內部處理 D(E)] -> [産生已一接收到的意識 E,即 Recv() 被呼叫的那一刻]
- 前面提到過的 setTime/D0 的那段落,及前面也提到過若要誤差變異小,那麼就讓誤差的可能值最小化。給一百年,那麼我任何時刻一年十年等都可能 gg,但若只給 3 分鐘,一分兩分最多不過三分 gg,就是這個概念。
- 因此,setTime 的方法被否決掉了,因其路徑太過邏輯且實際絨長,何不將不可控/不可預期/不可計時的部份最小化,即最後是剛剛所提到的傳輸路徑區分。
- 因此,甲乙兩方的同步,就只要看甲方的 Send(),及乙方的 Recv(),探求時距即可。不過不過,實務上,並非那麼直觀便可解決。這就是接下來要講的。以此出發,或許各位可先行想想看。
- 之前提到過,Send() 是主動的/至少從油蛇的觀點視之是如此;Recv() 是被動的,它可以是中斷函式又或是,出了 loop() 才能去巡訪的 callback 函式。換簡言之,Send() 是完全可控的,連續 Send(),意謂著 Send() 之間沒有時距。Recv(),若是後者,那我們能做的,就是一進入 loop(),便立即著手與 Recv() 相關的舉措,目的就是為了將可控的時間浪費化為零,不可控的時間減至最小,使得若有來資料,Recv() 便能以最短的延遲來響應。而若是前者 isr,那麼我們不僅無從施為卻也無關乎我們有做了後者的舉措,並且,其響應速度也必會比後者更快。故 anyway,做後者舉措便是。
- 接下來,想像一下(迫擊)炮管與水管。這要類比用的。前者一個一個出,單元之間的時距是炮管長;後者連續出,單元之間時距是零。
- 幸運地,傳輸中介,前面提到的 C,是水管。
- 很不幸地,B(A),D(E),都是炮管。
- 請仔細想想此類比如實。
- 因此,做一次傳送接收,耗時就是 B+C+D。甲方只要告知乙方現在時間是 T 時,那麼乙收到後就將 T 加上 (B+C+D),則甲乙便同步了。
- 問題在於如何求得 (B+C+D)。直覺地,若甲方連續 Send() 2 次,那麼,乙方 2 次收到的時間差,便是,。。。(B+D);反差地說,若 C 的傳輸時間即便是一百年,請因此仔細想想答案確實就是 (B+D)。
- 問題是 C 當前還是未知也是沒搞頭;可以是 3 秒,也可以是 100 年,我們求不出來。
- 易再想到地,如阿基米得的實驗,讓光折返,那麼甲方從 Send() 到 Recv() 的時間差 S1,便可進而求出 C。
- 即,(前面提到過,devices 在不同個體皆具均等性的前題下)
- 甲(B+C+D)+乙(B+C’+D)=S1
- 因此,前面提到,須視光的去程和返程所耗用的時間是相等的。(B+D) 已知,S1 已知,若 C==C’,則 (C+C’)/2 便可得 C。
- 看到這裏,各位是否更確信我們無從區分 C & C’,同步問題本就是個無解問題了。除非應用上物理禀性。
- 因此,我們並非是要算 C,而是將 S1/2=D0,再廣播給 devices 便可讓所有人同步了。
- 因此,唯一個舉措是如上做法求得 D0。setTime 則先給定一個時間值,彼方再陷入等侍通知例如約 1 秒,一收到便立即設定該時間,便可成立吾人可為之之最佳同步。
- 寫到這裏,同步問題解決了,但程式還停留在上面第二份備份一字未增且,筆者可預期,講得簡單,程式又有好多扣得加了XXD
希望,手上 LA 有 16 路,每路的 waveform 結果能回饋點成就感XD。若是 us 等級我就爽上天了XD。唉沒意義。將來若有結果再上圖。
20230907 update
上扣,只是備份的扣。第三次備了都還沒有告一段落的跡象XD
簡述一下,
前述的同步已經完成了。
測試結果不盡理想。
就以廣播來說,output 所呈現的 clients 所接收到的同步點,可看得出來,是肉眼難區分的。然而之後再同步來亮燈,卻是明顯不同步。所以問題出現在接收起始之後的程序。只能再加把勁看看了。
程式如同前述操作;其他就略述了。
真如預期加了不少東西,再預期,等到 release,與前一版比起來應是面目全非了吧;平平都是 release 版,功能也沒加上幾個,爸哥也沒修多少。。。目前扣倍使已經累積到五千五百行了。。。是多還少XD。。。真耐人尋味XD。。。
20230908 backup
終於解了,達到期望進度了。
就抓到一隻想法上的 bug,原先已知其可能有問題,但就是想不透,現想通了,可見此版註解與前版差異。
實測分成個別通知及廣播,反而廣播的誤差大,其可能原因出於廣播將造成廣播者與接收者拉開差距為其一/一直懷疑它廣播的作用是循序的,有機會再來查證;其二是個別通知是連續幾種命令一氣喝成的所以最後一條設時命令的延遲反而低(但仍遠大於約定延遲 150us~2500us),而廣播只有單一條設時命令,所以含了初啟動時間。
六個 devices,one host and 5 clients,
所以成果,是看任意兩者間最大差距多少,
個別通知,13ms,廣播,69ms,
並且,如圖,每一分鐘會再加 3ms 的累積誤差,所以如圖示下一次是 17ms。
事實上傳輸延遲尤其又是無線的,變異是頗大的。所以程式邏輯想法上是可行的,恰當的,但因無線而成果不是那麼漂亮。不過話說回來,已實作到如此地步了,針對,
1. 無線傳輸延遲之變異,2. 時序累積誤差,這兩條 issues,應都是可以再深入探究改善的。
舉例來說,延遲及設時都已先決定了也先給 client 了。新加作法為,設時不再是單程空封包,而是同測延遲的乒乓行為且封包內容為 time offset sequence,當某筆 time offset 其延遲是合約定的,那麼下一包便是結束以確保就算變異也能保時。不使用封包放延遲大小是因會超過 1 byte(不過話說回來編成 1 byte,那就只要 2 次乒乓就完事了)。time-offset 必須大於延遲的最大值。
(唉想到這裏,封包放延遲才是較佳做法怎沒想到XD,time-offset 更算是我目前做法的因變異而作的 workaround,但封包放延遲程式是愈加複雜,看我目前只是駐留在一個時間點扣就寫那樣了。。。想改又不想改。。。)(後續若有改,會假定反射傳輸不因彼此或不同時刻而異。提一下誤謬,求延遲時,初啟動和乒乓,最大值也只有 2.5ms[跑任務雖六者間最小差確實有 2.5ms]。那差到 13ms 或更多到底是怎麼來的?另外相對於 host,clients 有領先也有落後。到底這些誤差是怎麼來的?所以照這樣看來延遲差異應不是罪𣁽禍首。又如果是算式或邏輯錯誤,那 5 支 clients 應會有可被觀察到的相似性才是。再這樣講廣播,首次實時任務偏差達 70ms,但下一次的偏差是 70+3ms,3ms 被認定為時脈累積誤差,但反推,若所誤差的 70ms 是一次錯誤結果,那麼下一次應必須從該時間下再錯誤一次即 140ms 才是而不該只有 3ms;再反反推,是不是誤差産生更早於任務前;但數十次的反覆測試,啟動加乒乓行為從未觀到有超過 2.5ms[只有一次一支 11ms],由此也不該歸咎;但翻過來說,clients 間,錯誤誤差應會抵消使得相距很近而非遠大於 2.5ms 才是。當前幸而結果可接受,不然又內傷中了。這也一人寫扣的短板,卡關就是大關了)
至此,成果不漂亮但算自滿了。
應這樣說,若無線傳輸延遲是固定的,那 us 等級應是不困難的,因實作將可控變因都顧及到了。

20230912 backup
不意又把前面提到的封包放延遲的新扣加進去了,此版也埋了 debug code,和使用 gpio trigger,用 gpio 來測時間。debug code 大概率看不出什麼問題也不容易看。不過重點在於新扣及加了新扣後,上面提過的疑問,仍未解,但已有譜了,
簡單講,新扣是最精確的,但建立在光反射速度是恒定的前題下;因此,新扣讓人失望了,仍享受不到新扣的威力。
重點就是,延遲變異,誤人好深。就是它,始作庸者。
上一下圖如下,是新扣,基準已鎖定在 host,因此可看出,clients 都領先 host,表示,所使用的延遲比意欲的大,亦表示所抓出的反射時間比意欲的小,重點就是還不見得是這回事,也會還大。唉,可以理解。此外從圖上看到,最大差距 22ms,clients 間最小差距 722us。
所以結論就是,當前二種方法中,第二種嚴重依賴於反射時間,故使用第一種是最穩定的了。
該是結尾了,
以上全部所論,當前結論是最佳結果了,就這樣使用了。13ms 的最大差距,真的不錯了,收工。
按,ken 哥跳出來巴我頭了!
哈哈哈!
按,
哈哈哈:有看到嗎,前面提到的第一種方法,的 workaround,是不是暏光!
暗,別折磨我了!。。。

20230913 backup
- 才一天又再次備份了,因為,北風尾了,就是因為那哈哈哈,
- 氣而不捨地(鍥),把前一天提到的 workaround 實踐出來了。
- 成果呢,嗯,初步所測的結果非常漂亮,是 20ms,
- 蝦~毁~沒聽錯吧,沒打錯吧!
- 是的,20ms;劣於第一種 13ms;優於第二種 22ms;就是非常漂亮的 20ms!
- 正是因為它透露出了那最隱晦的暏光,待下一份備份完整交待/下一份將作個總整理將三種使用方式都交待清楚。
- 而現在當然要講一下這第三種,不過,讓我們回顧,清楚一下這三種,
- 第一種,
- 會先檢測完畢 send-to-receive time cost(去返/2,後簡稱 SRT)的最大值和最小值,並取中間值供後來設時使用;試想,區間只橫跨 150us to 2500us,故結果再怎麼差,也頂多誤差個 2.5ms。除非後來設時時,正來個暴衝,若然,再修下扣便可輕易達所求。怎料,再怎麼測好幾十次,都是差到 13ms to 70ms 不等。無法理解。只能解釋為真的大暴衝了且也不能一直依賴於等到不暴衝。故比較後來就提出的 workaround(第三種)就是,若當設時時,並檢測 SRT 若大於預期,則在雙方已定義好的下一次或下下次等等的持續設時時間點,若合於預期,則任一方皆能夠計算出多延遲了多久以設時。如此成果將等同第一次便符合預期不暴衝的結果。此第三種後面會再詳述。
- 第二種,
- 便是直接設時,並同時檢測去返費時多久,以能夠通知 client 去減掉這個延遲。不過實作上作了個變化,即,假設 reflective send-receive time cost 簡稱 RSRT,是恒定的,當然應該是要恒定的,一收到便立即回覆稱為反射,若不恒定就有怪東西了。該強調的,比起第一種,其使用去返的平均,但若去返的時間相差頗大,那麼結果誤差就大。故第二種單獨求出 RSRT,以進而求得更精確的 SRT。
事實上如本文一開頭就提過的(如同若光的去是 2c,返是 0,怎辦),除了先求出 RSRT,其他任何方法都無法精確求出 SRT。
然而結果是 22ms,就開始懷疑人生了。 - workaround 的方法,經仔細想過,就算協議好了,也只能由 host 才能計算出,無法單由 client 就計算出來。故第三種是 workaround 觀念的變形,且變很大。
- 第三種,合該讓我們從頭說起,
- 前幾種同步的方法,我們抓取 host 處於整時(fully-second)時的未來的時間點以作為參考零點,如此可免除 host 端將來取時執行任務時或反之因而轉嫁到 client 端之徒增的基於彼此對應上困擾。而它的特徵就是不連續且特定;以致於,所因而衍生出的那幾種同步法須在該侷限下予以轉圜,不免綁手綁腳。
- 因此基於前面提到的 workaround 是可行的,不過我們仍需跳脫既有的思維,重新全面思考,所以以下的第三種,可說是集成演化,
- 類似第一種,我們首先 probing 數十次,以取得極小值,這意謂著將來也有可能出現此極小值。
- 我們將取此極小值,作為當將來一出現此極值時即做同步化的時間點。
- 不過這是有風險的,若不再出現便 gg 了,所以轉圜成加上容忍值,如此實測上成功率百百。
- 前面也提過,可能值區間愈小,誤差愈小,所以此法保證了,設定誤差最大多少,將能取得有誤差上限的漂亮結果。
- 如此,不僅無畏地輕易跳脫了測光速難題,也將有不遜於該法的成果(這句話成立的前題是有其它維度的誤差存在且頗大時)。
- 既然至此那麼簡單,確實,接下來困難的來了,
- 雙方必須記錄每一次傳送接收時的 timestamp(表示多耗時了遜於前幾種),當上面所述滿足時,便能取用。當成立時,
- host 將 timestamp 轉換成 epoch,連同測得的極小 SRT 值傳送給 client 以做再次的轉換。此 SRT 值實測,通常可在 350us 左右或可更小/170us 有出現過;單看無線品質。SRT 只有 170us 不甜嗎。
- (當然,若將測光速與此法搭配起來會再改善;不過於當前情境下無意義)
- 不同以往,補償值無法再視而不見了,然而,雙方該怎麼補,可見扣內說明,有點虐腦;兩三項加數/減數而已,但雙方間同步的對應要有精準的邏輯說明不容易。比一下扣查看新增的部份。
- 另外明顯的差異是,第三種其取自過去的 timestamp 作為定位點而前兩種則是取自未來的時間點;所以觀點不同,優劣則也各有長短。
- 因此結論,此第三種方法不同以往,最大的好處便是確保了誤差上限。
- 也因如此而帶來的自信,由此也迎來了暏光乍現,待下回分曉。
20230916 backup
- 終於告一段落了,
- 先說,本版雖然是備份,但已將所實作的 espnow 的應用以範例的形式大部份都套用到了,包含 espnow 進出,集群,發送指令及資料傳輸,ntp 時間同步,亳秒級時間同步,任務執行等,全程大約跑上個 15 分鐘以展示整個範例。細節請看扣內主程式說明。
因為主題是 codebase,所以此份範例可能不會成為 codebase 最終範例。故此份備份應可視為當前正式的一版本。 - 再小修了一下,backup 08。
- 不知各位是否已經有聯想到了,筆者最終才意識到的那一撇暏光,看看,也是因為它,讓筆者擠出了三種方法。也提一下,將方法二與方法三結合起來,將是最佳解。
- 先前的所有測試當中,最佳的成績是 13ms,也全都大概是在系統啟動後的七分多鐘後才做第一筆 rt-task。做同步是約在第三分鐘。故同步後有四分鐘的空窗期,而若時脈偏差最大時距在 3ms,那,果然,四分鐘,成 12ms 的偏差。始作庸者,一直以來就都是它!!!
- 換言之,espnow 傳輸,一般狀況下不會太不定不穩定,也又再實際驗證過了,此三種方法都。。。是對的,且是可行可用的。
- 然而,要在同步後立即執行 rt-task 就目前現有扣架構下有難度,故當前筆者驗證過,此三種方法在盡快執行第一筆 rt-task 都有約 3~5ms 最大偏差的最佳結果。而依筆者的建議,第三種最優,細節請見扣內說明。況且,爭取同步後立即執行無任何意義;該思考,如何解決前面提出的第二條 issue,時脈偏差補償。然而這在某方面是無意義的因為,哪一支 device 才是時脈標準?當然主方面是有意義的,就以 host 為基準,所有 clients 皆用它來校準。
- 經實測,各 device clock 本身皆有誤差值,所以兩兩間,有 0.65ms,也有 2.9ms 的。
- 以上大概交待清楚了。
- 依此/以上,校正並不會太困難但就是很擾惱人;host 同步後再隔例如五分鐘,再同步一次,但 client 端並不以來同步,而是以差值來校正往後每分鐘的修正量,稱它為校正同步。再況且,例如半天,再一次校正同步。故會有五分後及半天後的校正同步乃至於更大的時距也要。所以校正量分為一分作用,及或每小時作用及更多。。。
- 所以總結,精神層面上筆者是很爽的因為做到了微秒等級的同步,但實際上卻吃不到XXD,才過一分鐘就長出了至少 650us 的偏差,就算這些方法精確度都達 <10us,也。。。只能自嗨了XXD
- 當前筆者並沒有同步的需求,只是因為這篇 codebase 一開始就有提到同步,所以筆者索性就將它也生出來了吧。因此要再精進有兩點,方法二及三的結合,及校正同步。以後再說了。接下來應是 codebase 的正式改版了。
- 收工。
20230923 backup
- 又完成了一新段落了,。。。怎這份扣倍使好像沒完沒了。。。
- 嗯,的確,沒完沒了有兩種,一種是 bug 解不完,另一種是必要的功能加不完。。。還好至少這次是後者。。。
- 因為有俱足的進度所以趕緊先備份起來,此次,
- 為前面提到的時脈修正鋪好路了。。。筆者似乎不見微秒不掉淚,所以又洗了一次頭。。。但還沒開始。。。
- 有人會有疑問,時間同步,雖是用戶端同步到同一台主機,但怎會是主機主動對其一或所有 clients 作同步呢?確實,筆者實作的方向反了。。。不過問題也是簡單解決;非將扣改成反方向;而是僅多加一條 client 向主機要求同步的指令,便回到前題了。所以這部份完成了;亦謂雙向皆行。
- 另外這次又漂亮地突破盲點,新增了 Timeafter 函式,潛力股,感覺上有取代 Timeout 的態勢,優點不遜於 Timeout。
- 比起前一份備份更穩定,所以,至少當前,以本版來 demo 為最佳。
- 所以,這份扣倍使,一路走來,只有更強,沒有最強!