├── img ├── connection.png ├── canarium_ifttt.png ├── canarium_stack.png └── canarium_summary.png ├── sample_app └── air_melodychime │ ├── img │ ├── ic_error_80x80.png │ ├── ic_loading_80x80.gif │ ├── logo_favicon_16x16.ico │ ├── music_icon_128x128.png │ └── peridotcraft_banner.png │ ├── rbf │ └── air_melodychime_top.rbf │ ├── crs.lua │ ├── index.html │ └── lib │ ├── crpc_client.js │ ├── canarium_rpc.lua │ └── canarium_air.lua ├── .gitattributes ├── src ├── crs.lua ├── crpc_client.js ├── canarium_rpc.lua └── canarium_air.lua ├── LICENSE.txt ├── readme.md ├── canarium_air_doc.md ├── canarium_rpc_doc.md └── crpc_client_doc.md /img/connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/img/connection.png -------------------------------------------------------------------------------- /img/canarium_ifttt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/img/canarium_ifttt.png -------------------------------------------------------------------------------- /img/canarium_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/img/canarium_stack.png -------------------------------------------------------------------------------- /img/canarium_summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/img/canarium_summary.png -------------------------------------------------------------------------------- /sample_app/air_melodychime/img/ic_error_80x80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/sample_app/air_melodychime/img/ic_error_80x80.png -------------------------------------------------------------------------------- /sample_app/air_melodychime/img/ic_loading_80x80.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/sample_app/air_melodychime/img/ic_loading_80x80.gif -------------------------------------------------------------------------------- /sample_app/air_melodychime/img/logo_favicon_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/sample_app/air_melodychime/img/logo_favicon_16x16.ico -------------------------------------------------------------------------------- /sample_app/air_melodychime/img/music_icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/sample_app/air_melodychime/img/music_icon_128x128.png -------------------------------------------------------------------------------- /sample_app/air_melodychime/img/peridotcraft_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/sample_app/air_melodychime/img/peridotcraft_banner.png -------------------------------------------------------------------------------- /sample_app/air_melodychime/rbf/air_melodychime_top.rbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osafune/canarium_air/HEAD/sample_app/air_melodychime/rbf/air_melodychime_top.rbf -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | 19 | -------------------------------------------------------------------------------- /src/crs.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ------------------------------------------------------------------------------------ 3 | -- Canarium RPC Server -- 4 | ------------------------------------------------------------------------------------ 5 | @author Shun OSAFUNE 6 | @copyright The MIT License (MIT); (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 7 | 8 | *Version release 9 | v0.3.0726 s.osafune@j7system.jp 10 | 11 | *Requirement FlashAir firmware version 12 | W4.00.03+ 13 | 14 | *Requirement Canarium Air version 15 | v0.3.0726 or later 16 | 17 | --]] 18 | 19 | -- 格納したフォルダに修正 20 | require "/crpc/canarium_air" 21 | require "/crpc/canarium_rpc" 22 | 23 | -- カレントフォルダをルート以外にする場合に追加 24 | --cr.setpath("/foo/bar") 25 | 26 | do 27 | print("HTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\nConnection: close\nContent-Type: application/json; charset=utf-8") 28 | 29 | local json_str = cr.parse(arg[1]) 30 | print("Content-Length: " .. #json_str .. "\n\n" .. json_str) 31 | end 32 | -------------------------------------------------------------------------------- /sample_app/air_melodychime/crs.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ------------------------------------------------------------------------------------ 3 | -- Canarium RPC Server -- 4 | ------------------------------------------------------------------------------------ 5 | @author Shun OSAFUNE 6 | @copyright The MIT License (MIT); (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 7 | 8 | *Version release 9 | v0.3.0726 s.osafune@j7system.jp 10 | 11 | *Requirement FlashAir firmware version 12 | W4.00.03+ 13 | 14 | *Requirement Canarium Air version 15 | v0.3.0726 or later 16 | 17 | --]] 18 | 19 | -- 格納したフォルダに修正 20 | require "/app/lib/canarium_air" 21 | require "/app/lib/canarium_rpc" 22 | 23 | -- カレントフォルダをルート以外にする場合に追加 24 | --cr.setpath("/foo/bar") 25 | 26 | do 27 | print("HTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\nConnection: close\nContent-Type: application/json; charset=utf-8") 28 | 29 | local json_str = cr.parse(arg[1]) 30 | print("Content-Length: " .. #json_str .. "\n\n" .. json_str) 31 | end 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Canarium Air 2 | ============ 3 | 4 | Canarium Airパッケージは、FlashAir W-04を利用してIntel FPGAにWebインターフェースを提供します。 5 | 6 | 7 | 8 | このパッケージライブラリで提供する機能は‥‥ 9 | 1. **FPGAのコンフィグレーション機能** 10 | オンボードのコンフィグレーションROMは不要、書き換えの手間を省きます。 11 | クライアント側からの手動コンフィグレーションや、サーバーアクセスによる自律的なアップデートなどが簡単に実現できます。 12 | 13 | 2. **FPGA内部のデータアクセス機能** 14 | Qsysモジュール内部へのI/Oアクセス、メモリアクセスの他、メモリイメージのロード・セーブの機能を提供します。 15 | Luaスクリプトを使って自動的なセットアップやバッチ処理、他のWebサービスへの連携などが実現できます。 16 | 17 | 3. **リモートプロシジャコール(RPC)機能** 18 | クライアント側からはJSON-RPC形式のアクセスを提供します。 19 | ユーザー側で任意のメソッドを追加することもできます。 20 | 21 | 全てのコンテンツファイルをFlashAirに格納して運用することができるので、外部に接続のないネットワーク上でもWebのリッチUI・リッチライブラリを使うことができます。 22 | インストールに特別なツールは使いません。全ファイルをFlashAirへコピーすればすぐに使うことができます。 23 | 24 | 25 | また、Canarium Airパッケージと[IFTTT Webhooks](https://ifttt.com/maker_webhooks)を組み合わせれば、例えば大量のセンサーからの情報をFPGAで処理してWebサービスへと連携するような、FPGAエッジコンピューティングをとてもコンパクトに実現できます。 26 | 27 | 28 | 29 | ※ Canarium Air は [PERIDOT Project](https://github.com/osafune/peridot_newgen) の一環として製作しています。 30 | 31 | 32 | 対象環境 33 | ======= 34 | 35 | ※ 現状はお試しバージョンです。ライブラリの構成やインターフェース等は**予告なく変更される**ことがあります。 36 | 37 | - FlashAir W-04 ファーム W4.00.03 以降 38 | - Intel FPGA (CycloneIV E, Cyclone10 LP, MAX10) 39 | - QuartusPrime 17.1 以降 40 | 41 | - ターゲットとするユーザー 42 | - Intel FPGAのプロジェクトビルドおよび、Intel FPGAハードウェア設計の経験がある人 43 | - FlashAirを使ったハードウェアを作った経験のある人 44 | - プログラミング言語としてLuaやJavaScriptが使える人 45 | 46 | 47 | ドキュメントなど 48 | =============== 49 | 50 | Canarium RPC Client 51 | ------------------- 52 | 53 | - [Cabarium RPC Client v0.3.0726](crpc_client_doc.md) 54 | 55 | 56 | Canarium RPC Server 57 | ------------------- 58 | 59 | - [Canarium RPC Server v0.3.0726](canarium_rpc_doc.md) 60 | 61 | 62 | Canarium Air I/O 63 | ---------------- 64 | 65 | - [Canarium Air I/O v0.3.0726](canarium_air_doc.md) 66 | 67 | 68 | ライセンス 69 | ========= 70 | 71 | [The MIT License (MIT)](https://opensource.org/licenses/MIT) 72 | Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 73 | -------------------------------------------------------------------------------- /sample_app/air_melodychime/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 36 | 37 | 38 | 39 | メロディチャイムのサンプル 40 | 41 | 42 | 43 |
44 | 45 |

Melody Chime

46 |
47 |
48 | 49 |
50 |
51 | Initializing... 52 |
53 |
54 | 55 |
56 | ©2017-2019 J-7SYSTEM WORKS LIMITED. 57 |
58 | 59 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /canarium_air_doc.md: -------------------------------------------------------------------------------- 1 | Canarium Air I/O 2 | ================ 3 | 4 | Canarium Air I/OはFlashAirからFPGAのコンフィグレーションおよびデバイスアクセスをするためのLuaライブラリです。 5 | このライブラリはCanarium RPCを併用してリモートプロシージャコール(RPC)を提供したり、FlashAirの他機能と組み合わせてIFTTTへ接続するなど、FPGAをIoTエッジデバイスとしてより扱いやすくすることを可能にします。 6 | 7 | 8 | ライセンス 9 | ========= 10 | 11 | [The MIT License (MIT)](https://opensource.org/licenses/MIT) 12 | Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 13 | 14 | 15 | 対象環境 16 | ======= 17 | 18 | - FlashAir W-04 ファーム W4.00.03 以降 19 | - Intel FPGA (CycloneIV E, Cyclone10 LP, MAX10) 20 | - QuartusPrime 17.1 以降 21 | 22 | 23 | 使い方 24 | ===== 25 | 26 | ピンアサイン 27 | ----------- 28 | 29 | FlashAirのI/OとFPGAは以下のように接続します。 30 | 31 | ![schema](https://raw.githubusercontent.com/osafune/canarium_air/master/img/connection.png) 32 | 33 | - FPGAのMSELピンはPSモードに設定しておきます。 34 | - FlashAirの各ピンとFPGAの間には22~33Ωのダンピング抵抗を挟みます。 35 | - `nCONFIG`, `nSTATUS`, `CONF_DONE`は10kΩでVCCにプルアップします。 36 | - `DATA0(SCL)`, `USER I/O(SDA)`は2.2kΩでVCCにプルアップします。 37 | - それ以外のピンは適宜処理してください。 38 | 39 | 40 | FPGAプロジェクト 41 | --------------- 42 | 43 | Qsysで"[Intel FPGA I2C Slave to Avalon -MM Master Bridge Core](https://www.altera.com/documentation/sfo1400787952932.html#iga1457384052773)"をインスタンスしたモジュールを作成します。 44 | - `I2C Slave Address`は0x55をセットします。 45 | - `Byte addressing mode`は4にセットします。 46 | - `Number of Address Stealing bit`は0にセットします。 47 | - `Enable Read only mode`はOFFにセットします。 48 | 49 | SDA信号をUSER I/Oピンに、SCL信号をDATA0ピンにアサインします。 50 | デバイスコンフィグレーションの設定で、圧縮ビットストリーム、RBFファイルを生成するようにしてください。 51 | 52 | 53 | FlashAir設定 54 | ------------ 55 | 56 | FlashAirのGPIOモードを使用します。`/SD_WLAN/CONFIG` ファイルに `IFMODE=1` を追加します。 57 | `canarium_air.lua` を任意のフォルダに格納し、ユーザーのLuaから 58 | 59 | ```Lua 60 | require "/<フォルダパス>/canarium_air" 61 | 62 | -- FPGAのコンフィグレーション 63 | ca.config{file=""} 64 | 65 | -- Qsysモジュールアクセス 66 | avm = ca.open() 67 | sysid = avm:iord(0x10000000) 68 | avm:iowr(0x10000100, 1) 69 | avm:close() 70 | ``` 71 | 72 | のように呼び出します。 73 | 74 | --- 75 | 関数リファレンス 76 | ============== 77 | 78 | ca.version 79 | ---------- 80 | 81 | ライブラリのバージョンを取得します。 82 | 83 | - 書式 84 | *string* ca.version() 85 | 86 | - 記述例 87 | ```Lua 88 | ver = ca.version() 89 | ``` 90 | 91 | - 引数 92 | なし 93 | 94 | - 返値 95 | - `ver` 96 | バージョン表記が *string* で返ります。 97 | 98 | 99 | --- 100 | ca.progress 101 | ----------- 102 | 103 | 時間のかかる処理の際に、内部で進捗度を取得するために呼ばれます。 104 | この関数で進捗度を得るためにはユーザープログラム側で上書きする必要があります。 105 | 106 | - 書式 107 | ca.progress( *string* `funcname`, *number* `...` ) 108 | 109 | - 記述例 110 | ```Lua 111 | require "canarium_air" 112 | 113 | ca.progress = function(funcname, ...) 114 | -- 進捗表示処理など 115 | end 116 | ``` 117 | 118 | - 引数 119 | - `funcname` 120 | 内部でca.progressを呼び出している関数の識別名が *string* で格納されます。 121 | 122 | - `...` 123 | 進捗度が *number* で格納されます。値の範囲は `0` ~ `100` です。 124 | 125 | - 返値 126 | なし 127 | 128 | 複数の処理ステージが存在する関数の場合、ステージ数分の引数が渡されることに注意してください。 129 | 130 | 131 | ----- 132 | ca.config 133 | --------- 134 | 135 | FPGAのコンフィグレーションを行います。 136 | この関数は内部で `ca.progress("config", prog1, prog2)` を呼び出します。*prog1* はキャッシュファイル作成の進捗度、*prog2* はFPGAコンフィグレーションの進捗度を返します。 137 | 138 | - 書式 139 | *boolean*,*string* ca.config( *table* `table` ) 140 | 141 | - 記述例 142 | ```Lua 143 | res,mes = ca.config{file="/foo/bar.rbf"} 144 | ``` 145 | 146 | - 引数テーブル 147 | 148 | | メンバ | タイプ | 指定 | 説明 | 149 | |:---:|:---:|:---:|:---| 150 | | `file` | *string* | 必須 | RBFファイル名を指定します。ファイル名はフルパスで設定します。 | 151 | | `cache` | *boolean* | オプション | キャッシュファイルの使用を指示します。省略時は `true` です。 | 152 | 153 | - 返値 154 | - `res` 155 | コンフィグ成功時に `true`、失敗時に `false` を返します。 156 | 157 | - `mes` 158 | 失敗要因のメッセージを *string* で返します。 159 | 160 | 161 | ---- 162 | ca.open 163 | ------- 164 | 165 | Qsysモジュールへのアクセスオブジェクトを取得します。 166 | 167 | - 書式 168 | *table*,*string* ca.open( [*table* `table`] ) 169 | 170 | - 記述例 171 | ```Lua 172 | avm,mes = ca.open() 173 | ``` 174 | 175 | - 引数テーブル 176 | 177 | | メンバ | タイプ | 指定 | 説明 | 178 | |:---:|:---:|:---:|:---| 179 | | `devid` | *number* | オプション | スレーブアドレスを指定します。省略時は `0x55` です。 | 180 | | `i2cfreq` | *number* | オプション | I2C通信速度を `100` または `400` で指定します。省略時は `400` です。 | 181 | 182 | - 返値 183 | - `avm` 184 | デバイスオープン成功時は *table*、失敗時は `nil` を返します。 185 | 186 | - `mes` 187 | 失敗要因のメッセージを *string* で返します。 188 | 189 | 190 | --- 191 | *avm*:close 192 | ----------- 193 | 194 | 取得したアクセスオブジェクトを破棄し、クローズ処理を行います。 195 | 196 | - 書式 197 | *avm*:close() 198 | 199 | - 記述例 200 | ```Lua 201 | avm,mes = ca.open() 202 | : 203 | : 204 | avm:close() 205 | ``` 206 | 207 | - 引数 208 | なし 209 | 210 | - 返値 211 | なし 212 | 213 | 214 | ---- 215 | *avm*:iord 216 | ---------- 217 | 218 | 取得したアクセスオブジェクトでI/Oリードを行います。 219 | Qsys内部のアクセスは必ずバス幅(32bit単位)で行われ、ワード内でのアトミックな読み出しを保証します。 220 | 221 | - 書式 222 | *number*,*string* *avm*:iord( *number* `addr` ) 223 | 224 | - 記述例 225 | ```Lua 226 | avm = ca.open() 227 | data,mes = avm:iord(0x10000000) 228 | ``` 229 | 230 | - 引数 231 | - `addr` 232 | Qsys内部の読み出しアドレスを *number* で指定します。値は32bitの範囲の整数値で、32bitアラインメントされていなければなりません。 233 | 234 | - 返値 235 | - `data` 236 | 成功時は読み出した値を *number* で返します。失敗時は `nil` を返します。 237 | 238 | - `mes` 239 | 失敗要因のメッセージを *string* で返します。 240 | 241 | 242 | ---- 243 | *avm*:iowr 244 | ---------- 245 | 246 | 取得したアクセスオブジェクトでI/Oライトを行います。 247 | Qsys内部のアクセスは必ずバス幅(32bit単位)で行われ、ワード内でのアトミックな書き込みを保証します。 248 | 249 | - 書式 250 | *boolean*,*string* *avm*:iowr( *number* `addr`, *number* `data` ) 251 | 252 | - 記述例 253 | ```Lua 254 | avm = ca.open() 255 | res,mes = avm:iowr(0x10000000, 1) 256 | ``` 257 | 258 | - 引数 259 | - `addr` 260 | Qsys内部の書き込みアドレスを *number* で指定します。値は32bitの範囲の整数値で、32bitアラインメントされていなければなりません。 261 | 262 | - `data` 263 | 書き込みデータを *number* で指定します。値は32bitの範囲の整数値です。 264 | 265 | - 返値 266 | - `res` 267 | 成功時は `true`、失敗時は `nil` を返します。 268 | 269 | - `mes` 270 | 失敗要因のメッセージを *string* で返します。 271 | 272 | 273 | ---- 274 | *avm*:memrd 275 | ----------- 276 | 277 | 取得したアクセスオブジェクトでメモリリードを行います。 278 | 279 | - 書式 280 | *string*,*string* *avm*:memrd( *number* `addr`, *number* `size` ) 281 | 282 | -記述例 283 | ```Lua 284 | avm = ca.open() 285 | rstr,mes = avm:memrd(0x10000000, 256) 286 | ``` 287 | 288 | - 引数 289 | - `addr` 290 | Qsys内部の読み出し開始アドレスを *number* で指定します。値は32bitの範囲の整数値です。 291 | 292 | - `size` 293 | 読み出すバイトサイズを *number* で指定します。一度に指定可能な値はFlashAirのメモリリソースに左右されます。 294 | 295 | - 返値 296 | - `rstr` 297 | 成功時は読み出したバイト列を *string* で返します。失敗時は `nil` を返します。 298 | 299 | - `mes` 300 | 失敗要因のメッセージを *string* で返します。 301 | 302 | 303 | --- 304 | *avm*:memwr 305 | ----------- 306 | 307 | 取得したアクセスオブジェクトでメモリライトを行います。 308 | 309 | - 書式 310 | *boolean*,*string* *avm*:memwr( *number* `addr`, *string* `wstr` ) 311 | 312 | - 記述例 313 | ```Lua 314 | avm = ca.open() 315 | res,mes = avm:memwr(0x10000000, "\x01\x02\x03\x04\x05\x06") 316 | ``` 317 | 318 | - 引数 319 | - `addr` 320 | Qsys内部の書き込み先頭アドレスを *number* で指定します。値は32bitの範囲の整数値です。 321 | 322 | - `wstr` 323 | 書き込むバイト列を *string* で指定します。一度に指定可能な値はFlashAirのメモリリソースに左右されます。 324 | 325 | - 返値 326 | - `res` 327 | 成功時は `true`、失敗時は `nil` を返します。 328 | 329 | - `mes` 330 | 失敗要因のメッセージを *string* で返します。 331 | 332 | 333 | --- 334 | ca.binload 335 | ---------- 336 | 337 | 取得したアクセスオブジェクトのメモリ空間にファイルイメージをロードします。 338 | この関数は内部で `ca.progress("binload", prog)` を呼び出します。 339 | 340 | - 書式 341 | *boolean*,*string* ca.binload( *table* `avm`, *string* `file` [, *number* `addr`] ) 342 | 343 | - 記述例 344 | ```Lua 345 | avm = ca.open() 346 | res,mes = ca.binload(avm, "/foo/bar.bin", 0x2000) 347 | ``` 348 | 349 | またはアクセスオブジェクトのメソッドを利用して下記のようにも書けます。 350 | 351 | ```Lua 352 | res,mes = avm:bload("/foo/bar.bin", 0x2000) 353 | ``` 354 | 355 | - 引数 356 | - `avm` 357 | 使用するアクセスオブジェクトを指定します。 358 | 359 | - `file` 360 | ロードするファイルをフルパスで指定します。 361 | 362 | - `addr` 363 | 先頭メモリアドレスを *number* で指定します。省略時は `0` です。 364 | 365 | - 返値 366 | - `res` 367 | 成功時は `true`、失敗時は `false` を返します。 368 | 369 | - `mes` 370 | 失敗要因のメッセージを *string* で返します。 371 | 372 | 373 | --- 374 | ca.binsave 375 | ---------- 376 | 377 | 取得したアクセスオブジェクトのメモリ空間からバイトイメージをファイルにセーブします。 378 | この関数は内部で `ca.progress("binsave", prog)` を呼び出します。 379 | 380 | - 書式 381 | *boolean*,*string* ca.binsave( *table* `avm`, *string* `file`, *number* `size` [, *number* `addr`] ) 382 | 383 | - 記述例 384 | ```Lua 385 | avm = ca.open() 386 | res,mes = ca.binsave(avm, "/foo/bar.bin", 8192, 0x1000) 387 | ``` 388 | 389 | またはアクセスオブジェクトのメソッドを利用して下記のようにも書けます。 390 | 391 | ```Lua 392 | res,mes = avm:bsave("/foo/bar.bin", 8192, 0x1000) 393 | ``` 394 | 395 | - 引数 396 | - `avm` 397 | 使用するアクセスオブジェクトを指定します。 398 | 399 | - `file` 400 | セーブするファイルをフルパスで指定します。同名ファイルがあった場合は上書きされます。 401 | 402 | - `size` 403 | 保存するバイトサイズを *number* で指定します。 404 | 405 | - `addr` 406 | 開始メモリアドレスを *number* で指定します。省略時は `0` です。 407 | 408 | - 返値 409 | - `res` 410 | 成功時は `true`、失敗時は `false` を返します。 411 | 412 | - `mes` 413 | 失敗要因のメッセージを *string* で返します。 414 | 415 | 416 | --- 417 | ca.hexload 418 | ---------- 419 | 420 | 取得したアクセスオブジェクトのメモリ空間にIntelHEXまたはS-record形式のファイルをロードします。 421 | この関数は内部で `ca.progress("hexload", prog)` を呼び出します。 422 | 423 | - 書式 424 | *boolean*,*string* ca.hexload( *table* `avm`, *string* `file` [, *number* `offset`] ) 425 | 426 | - 記述例 427 | ```Lua 428 | avm = ca.open() 429 | res,mes = ca.hexload(avm, "/foo/bar.hex") 430 | ``` 431 | 432 | またはアクセスオブジェクトのメソッドを利用して下記のようにも書けます。 433 | 434 | ```Lua 435 | res,mes = avm:load("/foo/bar.hex") 436 | ``` 437 | 438 | - 引数 439 | - `avm` 440 | 使用するアクセスオブジェクトを指定します。 441 | 442 | - `file` 443 | ロードするIntelHEXまたはS-recordファイルをフルパスで指定します。フォーマットは自動認識されます。 444 | 445 | - `offset` 446 | アドレスオフセットを *number* で指定します。省略時は `0` です。 447 | この引数を省略した場合は、IntelHEXまたはS-recordファイルのアブソリュートアドレスでロードが行われます。 448 | 449 | - 返値 450 | - `res` 451 | 成功時は `true`、失敗時は `false` を返します。 452 | 453 | - `mes` 454 | 失敗要因のメッセージを *string* で返します。 455 | 456 | 457 | --- 458 | 459 | © 2017-2019 J-7SYSTEM WORKS LIMITED 460 | -------------------------------------------------------------------------------- /canarium_rpc_doc.md: -------------------------------------------------------------------------------- 1 | Canarium RPC Server 2 | =================== 3 | 4 | Canarium RPCはFlashAirを使ってFPGAリソースへのアクセスを提供するリモートプロシージャコール(RPC)ライブラリです。 5 | インターフェースはJSON-RPCをベースにFlashAirのHTTPサーバーの制約にあわせて変更しています。 6 | `canarium_rpc.lua` および `crs.lua` はFlashAir側にRPCサーバー機能を提供します。クライアント側は[Canarium RPC Client](crpc_client_doc.md)を参照してください。 7 | 8 | 9 | 10 | 11 | ライセンス 12 | ========== 13 | 14 | [The MIT License (MIT)](https://opensource.org/licenses/MIT) 15 | Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 16 | 17 | 18 | 対象環境 19 | ======== 20 | 21 | - FlashAir W-04 ファーム W4.00.03 以降 22 | - Canarium Air I/O v0.3.0726 以降 23 | 24 | 25 | 使い方 26 | ====== 27 | 28 | FlashAir準備 29 | ------------ 30 | 31 | 1. FlashAirのGPIOモードを使用します。`/SD_WLAN/CONFIG` ファイルに `IFMODE=1` を追加します。 32 | 33 | 2. `canarium_rpc.lua` と `canarium_air.lua` をFlashAirの任意のフォルダに格納します。 34 | 35 | 3. CanariumRPC over HTTPを使う場合は `crs.lua` をFlashAirのルートに格納します。 36 | 37 | 4. 環境にあわせて `crs.lua` の先頭部分を修正します。 38 | 39 | ```lua 40 | -- 格納したフォルダに修正 41 | require "/foo/bar/canarium_air" 42 | require "/foo/bar/canarium_rpc" 43 | 44 | -- カレントフォルダをルート以外にする場合に追加 45 | cr.setpath("/foo/bar") 46 | ``` 47 | 48 | 設定が終わったFlashAirを再起動します。 49 | 50 | RPC呼び出し 51 | ---------- 52 | 53 | クライアント側からFlashAirが認識できるようになった後は 54 | 55 | ``` 56 | http://flashair/crs.lua?<クエリ> 57 | ``` 58 | 59 | を呼び出します。クエリはRPCへのパラメータをBase64urlでエンコードした文字列です。 60 | レスポンスはJSON-RPC形式で返します。 61 | 62 | - 呼び出し例 63 | 64 | ``` 65 | FPGAがコンフィグされてるかどうかのチェックをする 66 | 67 | > GET http://flashair/crs.lua?MDkBAQE 68 | 69 | HTTPレスポンス 70 | 71 | > HTTP/1.1 200 OK 72 | > Access-Control-Allow-Origin: * 73 | > Connection: close 74 | > Content-Type: application/json; charset=utf-8 75 | > Content-Length: 38 76 | > 77 | > {"jsonrpc":"2.0","result":1,"id":12345} 78 | > 79 | ``` 80 | 81 | クエリの詳細については[クエリリファレンス](#クエリリファレンス)を参照してください。 82 | 83 | 84 | --- 85 | 関数リファレンス 86 | =============== 87 | 88 | cr.version 89 | ---------- 90 | 91 | Canarium RPCのバージョンを取得します。 92 | 93 | - 書式 94 | *string* cr.version() 95 | 96 | - 記述例 97 | ```Lua 98 | ver = cr.version() 99 | ``` 100 | 101 | - 引数 102 | なし 103 | 104 | - 返値 105 | - `ver` 106 | バージョンを *string* で返します。 107 | 108 | 109 | --- 110 | cr.setpath 111 | ---------- 112 | 113 | RPCサーバーのカレントパスを指定します。パスの初期値は実行したLuaファイルのカレントフォルダが設定されます。 114 | またクエリのファイルパスは、デフォルトでは相対パスのみ許可されていますが、この関数の第2引数で絶対パス指定の許可・禁止を操作することができます。省略時は `false` です。 115 | 116 | - 書式 117 | *string* cr.setpath( [*string* `path`, [*boolean* `ena_abs`] ] ) 118 | 119 | - 記述例 120 | ```Lua 121 | -- カレントパスの設定 122 | cr.setpath("/lua/rpc") 123 | 124 | -- カレントパスの取得 125 | cur_path = cr.setpath() 126 | ``` 127 | 128 | - 引数 129 | - `path` 130 | カレントパスを *string* 指定します。"/"から始まるパスは絶対パス、それ以外はカレントパスからの相対パスとして認識されます。親パス("..")の指定はできません。 131 | 132 | - `ena_abs` 133 | クエリのファイルパスで絶対パスを許可するかどうかを *boolean* で指定します。省略時は `false` です。 134 | 135 | - 返値 136 | - `cur_path` 137 | 引数を省略した場合、現在設定されているカレントパスを *string* で返します。 138 | 139 | 140 | --- 141 | cr.setmethod 142 | ------------ 143 | 144 | RPCサーバーに対してメソッドの追加・削除を行います。 145 | 146 | - 書式 147 | *boolean* cr.setmethod( *string* `name`, [*number* `cmd`, *function* `function`] ) 148 | 149 | - 記述例 150 | ```Lua 151 | -- メソッドの登録 152 | res = cr.setmethod("USER", 0x40, function(cstr) ... end) 153 | 154 | -- メソッドの削除 155 | res = cr.setmethod("USER") 156 | ``` 157 | 158 | - 引数 159 | - `name` 160 | 追加または削除するメソッド名を *string* で指定します。既に同じ名前のメソッドがある場合は後から指定したものに更新されます。 161 | `name` のみを指定した場合、既に登録されているメソッドを削除します。 162 | 163 | - `cmd` 164 | このメソッドに割り当てる識別番号を *number* で指定します。指定出来る範囲は `0x00` ~ `0x7f` です。また `0x00` ~ `0x2f` は組み込み用に予約されています。 165 | 既に同じ識別番号のメソッドがある場合は後から指定したものに更新されます。 166 | 167 | - `function` 168 | クエリから呼び出されるメソッドの本体となる関数を *function* で指定します。 169 | 引数にはクエリのペイロードデータ部が *string* で渡されます。 170 | 171 | - 返値 172 | - `res` 173 | 関数の処理結果を *boolean* で返します。成功の場合は `true` 、失敗の場合は `false` です。 174 | 175 | 176 | --- 177 | cr.update 178 | --------- 179 | 180 | メソッド関数の中で、進捗情報のアップデートを行います。 181 | この関数は `cr.setmethod()` で指定する関数以外からは呼ぶことができません。 182 | 183 | - 書式 184 | cr.update( *number* `...` ) 185 | 186 | - 記述例 187 | ```Lua 188 | res = cr.setmethod("USER", 0x40, function(cstr) 189 | for i=1,100 do 190 | -- メソッドの処理 -- 191 | ... 192 | -- 進捗度の更新 193 | cr.update(i) 194 | end 195 | end) 196 | ``` 197 | 198 | - 引数 199 | - `...` 200 | 進捗度を *number* で指定します。値の範囲は `0` ~ `100` です。 201 | メソッド内で複数の処理ステージを持つ場合は、任意の数だけ進捗度を指定することができます。 202 | 進捗情報は[進捗ステータス](#進捗ステータス)でJSON文字列として取得するため、多数のステージを持つ進捗情報はクライアントによっては正しいデータを取得できない場合があります。 203 | 204 | - 返値 205 | なし 206 | 207 | 208 | --- 209 | cr.parse 210 | -------- 211 | 212 | クエリ文字列を解釈してメソッドを実行します。 213 | 214 | - 書式 215 | *string* cr.parse( [*string* `query`] ) 216 | 217 | - 記述例 218 | ```Lua 219 | res_json = cr.parse(query) 220 | ``` 221 | 222 | - 引数 223 | - `query` 224 | 実行するクエリ文字列を *string* で指定します。クエリ詳細は[クエリリファレンス](#クエリリファレンス)を参照してください。 225 | この引数を省略した場合は[VERクエリ](#verクエリ)が実行されます。 226 | 227 | - 返値 228 | - `res_json` 229 | メソッドの実行結果をJSON-RPCレスポンスの *string* で返します。詳細は[JSONレスポンス](#jsonレスポンス)を参照してください。 230 | 231 | 232 | --- 233 | クエリリファレンス 234 | ================= 235 | 236 | エンコード 237 | ---------- 238 | 239 | クエリは最大74バイトのバイナリパケットをBase64url (RFC4648) で文字列にエンコードしたものです。最大100文字のURI文字列になります。 240 | 241 | ``` 242 | *変換の例 243 | packet : 30 39 13 96 08 68 6f 73 74 74 65 73 74 5f 6f 6c 69 76 65 2e 72 62 66 244 | ↓ 245 | query : MDkTlghob3N0dGVzdF9vbGl2ZS5yYmY 246 | ```` 247 | 248 | パケットフレーム 249 | --------------- 250 | 251 | パケットは下記のに示すフォーマットでデータを格納します。 252 | 253 | ``` 254 | バイト 255 | +0 ID値(上位8bit) 256 | +1 ID値(下位8bit) 257 | +2 ペイロード長(5バイト目以降のデータ長を示す) 258 | +3 チェック値(ペイロードデータの各バイトを演算した値) 259 | +4 ペイロードデータ(FlashAir W-04では最大70バイト) 260 | : 261 | +73 262 | ``` 263 | 264 | - ID値は16bitの符号なし整数値で、JSON-PRCのidフィールドの値です。 265 | 266 | - チェック値はペイロードデータの各バイトを次のような演算で計算した結果です。 267 | 268 | ```C 269 | for(xsum=0,i=0 ; i < n ; i++) { 270 | xsum = (byte[i] ^ ((xsum << 1) | ((xsum & 0x80)? 1 : 0))) & 0xff; 271 | } 272 | ``` 273 | 274 | ファイルについて 275 | --------------- 276 | 277 | クエリで指定するファイルはFlashAir側のストレージに格納されているものに限ります。 278 | 必要なファイルは予めカードに保存しておくか、FlashAir用のツール(あるいは `upload.cgi` )でファイルを転送してください。 279 | 同様にクエリの結果として書き出されるファイルはFlashAirのストレージに保存されます。必要に応じてクライアント側にダウンロードしてください。 280 | 281 | ファイルパスは次のような書き方ができます。 282 | 283 | *カレントフォルダのファイルを指定 284 | "testfile2.bin" 285 | "./testfile3.hex" 286 | 287 | *カレントフォルダ以下の子フォルダを指定 288 | "test/romdata.srec" 289 | 290 | *ファイルをフルパスで指定(許可されている場合のみ) 291 | "/foo/bar/testfile1.rbf" 292 | 293 | 294 | ファイル名、ファイルパス共に日本語を含む多バイト長文字は使用できません。 295 | 296 | 297 | JSONレスポンス 298 | ------------- 299 | 300 | クエリのレスポンスはJSON-RPCに則った形式で返されます。 301 | 302 | - 正常レスポンス 303 | 304 | ``` 305 | { 306 | "jsonrpc" : "2.0", 307 | "result" : or or or , 308 | "id" : 309 | } 310 | ``` 311 | 312 | - エラーレスポンス 313 | 314 | ``` 315 | { 316 | "jsonrpc" : "2.0", 317 | "error" : { 318 | "code" : , 319 | "message" : 320 | }, 321 | "id" : 322 | } 323 | ``` 324 | 325 | - プロパティ 326 | - `jsonrpc` 327 | *string* で `"2.0"` の固定値が入ります。 328 | 329 | - `id` 330 | クエリでリクエストしたID値が *number* で入ります。 331 | 332 | - `result` 333 | 正常終了の場合に結果が入ります。 *boolean*、*number* (32bit整数値)、*string* (Base64urlでエンコードされたバイナリ列)、*object* 等が格納されます。 334 | 335 | - `error` 336 | エラーの場合は、`result` の代わりにこのオブジェクトを返します。 337 | - `code` : エラーコードが *number* で入ります。値はJSON-RPCに準拠します。 338 | - `message` : エラーメッセージがある場合は *string* で入ります。 339 | 340 | - エラーコード 341 | - パースエラー 342 | `error : {"code":-32700, "message":"Parse error"}` 343 | クエリをデコードできなかった、または不正なパケット形式を検出した場合に返されます。 344 | 345 | - メソッド呼び出しエラー 346 | `error : {"code":-32601, "message":"Method not found"}` 347 | メソッドが存在しない場合に返されます。 348 | 349 | - メソッド実行時エラー 350 | `error : {"code":-32000, "message":<エラーメッセージ>}` 351 | メソッドの実行が失敗した場合に返されます。`message` キーには内部で返されるメッセージが *string* で入ります。 352 | 353 | 354 | --- 355 | VERクエリ 356 | ------------ 357 | 358 | ライブラリのバージョンを取得します。 359 | 360 | このクエリのみ文字列なしで `crs.lua` を呼び出します。 361 | レスポンスには `id` キーはありません。 362 | 363 | - 返値プロパティ 364 | - `result` 365 | RPCサーバーのバージョン情報を *object* で返します。 366 | - `rpc_version` : Canarium RPCのバージョンが *string* で入ります。 367 | - `lib_version` : Canarium Air I/Oのバージョンが *string* で入ります。 368 | - `fa_version` : FlashAirのファームウェアバージョンが *string* で入ります。 369 | 370 | 371 | --- 372 | CHECKクエリ 373 | ----------- 374 | 375 | FPGAがコンフィグレーション済みかどうかをチェックします。 376 | 377 | - ペイロードデータ 378 | 379 | ``` 380 | [0x01] 381 | ``` 382 | 383 | - 返値プロパティ 384 | - `result` 385 | FPGAの状態を *number* で返します。 386 | - `1` : コンフィグレーションされている 387 | - `0` : 未コンフィグレーション状態 388 | - それ以外 : 予約 389 | 390 | 391 | --- 392 | STATクエリ 393 | ----------- 394 | 395 | 現在のRPCサーバーやFlashAirの情報を返します。 396 | 397 | - ペイロードデータ 398 | 399 | ``` 400 | [0x02] 401 | ``` 402 | 403 | - 返値プロパティ 404 | - `result` 405 | RPCサーバーやFlashAirの情報 *object* で返します。 406 | - `current_path` : RPCサーバーのカレントパスが *string* で入ります。 407 | - `absolute_access` : RPCサーバーで絶対パスでのアクセスの可否が *boolean* で入ります。 408 | - `file_upload` : ファイルアップロードが有効かどうかが *boolean* で入ります。 409 | - `cid` : FlashAirのカードユニークIDが *string* で入ります。 410 | - `mac_address` : FlashAirのMACアドレスが *string* で入ります。 411 | - `ip_address` : FlashAirのIPアドレスが *string* で入ります。 412 | - `ip_mask` : FlashAirのサブネットマスクが *string* で入ります。 413 | - `netname` : FlashAirのネット識別名が *string* で入ります。 414 | - `appinfo` : FlashAirで設定したAPPINFOがあればその文字列が *string* で入ります。 415 | - `timezone` : FlashAirで設定したタイムゾーンの値が *number* で入ります。 416 | 417 | 418 | --- 419 | CONFクエリ 420 | ---------- 421 | 422 | FPGAのコンフィグレーションを行います。 423 | 既に生成されたキャッシュファイルが存在する場合はそれを利用して、短縮コンフィグレーションを行います。 424 | 425 | - ペイロードデータ 426 | 427 | ``` 428 | [0x08 ] 429 | ``` 430 | 431 | - パラメータ 432 | - `filename` 433 | コンフィグレーションするRBFファイル名を指定します。ファイル名の文字列をバイト列にして格納します。 434 | 435 | - 返値プロパティ 436 | - `result` 437 | 完了で `true` を返します。 438 | 439 | クエリ完了またはエラー発生までレスポンスが返らないため、クライアント側でタイムアウト処理時間には注意が必要です。 440 | 441 | コンフィグレーション含め、実行時にキャッシュファイルを作成するため時間がかかります。必要に応じて[進捗ステータス](#進捗ステータス)を取得してください。 442 | 443 | 444 | --- 445 | FCONFクエリ 446 | ----------- 447 | 448 | FPGAのコンフィグレーションを行います。 449 | このクエリは、既にキャッシュファイルが存在する場合でも指定のコンフィグレーションファイルでキャッシュファイルを再生成して、FPGAコンフィグレーションを行います。 450 | 451 | - ペイロードデータ 452 | 453 | ``` 454 | [0x09 ] 455 | ``` 456 | 457 | - パラメータ 458 | - `filename` 459 | コンフィグレーションするRBFファイル名を指定します。ファイル名の文字列をバイト列にして格納します。 460 | 461 | - 返値プロパティ 462 | - `result` 463 | 完了で `true` を返します。 464 | 465 | 備考については[CONFクエリ](#confクエリ)を参照してください。 466 | 467 | 468 | --- 469 | IOWRクエリ 470 | ---------- 471 | 472 | Qsysモジュール(FPGA内部ロジックコア)のペリフェラルレジスタにデータを書き込みます。 473 | 474 | - ペイロードデータ 475 | 476 | ``` 477 | [0x10 0x55
] 478 | ``` 479 | - パラメータ 480 | - `address` 481 | 書き込みアドレスを4バイトで指定します。格納順はネットワークバイトオーダーです。 482 | アドレス値は32bitのワード境界に整列していなければなりません。 483 | 484 | - `wrdata` 485 | 書き込みデータを4バイトで指定します。格納順はネットワークバイトオーダーです。 486 | 487 | - 返値プロパティ 488 | - `result` 489 | 完了で `true` を返します。 490 | 491 | このクエリは、Avalon-MMのメモリバス(Qsysモジュール内部メモリ空間)で32bitワード単位のアトミックなライトアクセスを保証します。 492 | 493 | 494 | --- 495 | IORDクエリ 496 | ---------- 497 | 498 | Qsysモジュール(FPGA内部ロジックコア)のペリフェラルレジスタからデータを読み出します。 499 | 500 | - ペイロードデータ 501 | 502 | ``` 503 | [0x11 0x55
] 504 | ``` 505 | - パラメータ 506 | - `address` 507 | 読み出しアドレスを4バイトで指定します。格納順はネットワークバイトオーダーです。 508 | アドレス値は32bitのワード境界に整列していなければなりません。 509 | 510 | - 返値プロパティ 511 | - `result` 512 | 読み出した値を *number* (32bit符号無し整数)で返します。 513 | 514 | このクエリは、Avalon-MMのメモリバス(Qsysモジュール内部メモリ空間)で32bitワード単位のアトミックなリードアクセスを保証します。 515 | 516 | 517 | --- 518 | MEMWRクエリ 519 | ---------- 520 | 521 | Qsysモジュール(FPGA内部ロジックコア)の任意のメモリアドレスにバイトデータ列を書き込みます。 522 | 523 | - ペイロードデータ 524 | 525 | ``` 526 | [0x18 0x55
] 527 | ``` 528 | - パラメータ 529 | - `address` 530 | 書き込み先頭アドレスを4バイトで指定します。格納順はネットワークバイトオーダーです。 531 | 532 | - `databyte` 533 | 書き込むデータ列をバイトアドレス順で格納します。最大長は64バイトです。 534 | 535 | - 返値プロパティ 536 | - `result` 537 | 完了で `true` を返します。 538 | 539 | 540 | --- 541 | MEMRDクエリ 542 | ---------- 543 | 544 | Qsysモジュール(FPGA内部ロジックコア)の任意のメモリアドレスからバイトデータ列を読み出します。 545 | 546 | - ペイロードデータ 547 | 548 | ``` 549 | [0x19 0x55
] 550 | ``` 551 | - パラメータ 552 | - `address` 553 | 読み出し先頭アドレスを4バイトで指定します。格納順はネットワークバイトオーダーです。 554 | 555 | - `size` 556 | 読み出すバイト数を2バイトで指定します。格納順はネットワークバイトオーダーです。 557 | 指定可能な範囲は `1` ~ `256`です。 558 | 559 | - 返値プロパティ 560 | - `result` 561 | 読み出したバイト列をBase64urlでエンコードした文字列が *string* で返されます。 562 | 563 | 564 | --- 565 | BLOADクエリ 566 | ---------- 567 | 568 | Qsysモジュール(FPGA内部ロジックコア)の任意のメモリアドレスにファイルイメージをロードします。 569 | 570 | - ペイロードデータ 571 | 572 | ``` 573 | [0x20 0x55
] 574 | ``` 575 | - パラメータ 576 | - `address` 577 | 書き込み先頭アドレスを4バイトで指定します。格納順はネットワークバイトオーダーです。 578 | 579 | - `filename` 580 | ロードするファイル名を指定します。ファイル名の文字列をバイト列にして格納します。 581 | 582 | - 返値プロパティ 583 | - `result` 584 | 完了で `true` を返します。 585 | 586 | クエリ完了またはエラー発生までレスポンスが返らないため、クライアント側でタイムアウト処理時間には注意が必要です。必要に応じて[進捗ステータス](#進捗ステータス)を取得してください。 587 | 588 | 589 | --- 590 | BSAVEクエリ 591 | ---------- 592 | 593 | Qsysモジュール(FPGA内部ロジックコア)の任意アドレスのメモリイメージをファイルに保存します。 594 | 595 | - ペイロードデータ 596 | 597 | ``` 598 | [0x21 0x55
] 599 | ``` 600 | - パラメータ 601 | - `address` 602 | 先頭アドレスを4バイトで指定します。格納順はネットワークバイトオーダーです。 603 | 604 | - `imagesize` 605 | 保存するメモリイメージのバイトサイズを4バイトで指定します。格納順はネットワークバイトオーダーです。 606 | 607 | - `filename` 608 | 保存先のファイル名を指定します。ファイル名の文字列をバイト列にして格納します。 609 | 同名のファイルが既に存在していた場合は上書きされます。 610 | 611 | - 返値プロパティ 612 | - `result` 613 | 完了で `true` を返します。 614 | 615 | 備考については[BLOADクエリ](#bloadクエリ)を参照してください。 616 | 617 | 618 | --- 619 | LOADクエリ 620 | ---------- 621 | 622 | Qsysモジュール(FPGA内部ロジックコア)のメモリアドレス空間に、IntelHEX形式またはモトローラS-record形式のROMデータファイルをロードします。 623 | 624 | - ペイロードデータ 625 | 626 | ``` 627 | [0x22 0x55 ] 628 | ``` 629 | - パラメータ 630 | - `offset` 631 | オフセットアドレスを4バイトで指定します。格納順はネットワークバイトオーダーです。 632 | この値とROMデータファイルのアドレス値を加算したアドレスへ書き込みが行われます。`0` を指定した場合、ROMデータファイルのアブソリュートアドレスとなります。 633 | 634 | - `filename` 635 | ロードするファイル名を指定します。ファイル名の文字列をバイト列にして格納します。 636 | IntelHEX/S-recordのフォーマットは自動で認識されます。 637 | 638 | - 返値プロパティ 639 | - `result` 640 | 完了で `true` を返します。 641 | 642 | 備考については[BLOADクエリ](#bloadクエリ)を参照してください。 643 | 644 | 645 | --- 646 | 進捗ステータス 647 | ------------- 648 | 649 | クエリ処理中はRPCサーバーは応答を返さないため、処理に時間のかかるものについては別途進捗ステータスをリクエストすることができます。 650 | 651 | 進捗ステータスは `command.cgi` を利用して次のように要求します。LENの値はデフォルトで100です。 652 | 653 | ``` 654 | > GET /commang.cgi?op=130&ADDR=512&LEN=100 655 | ``` 656 | 657 | - レスポンス 658 | JSON形式で下記のようにデータが返ります。CGIの仕様上、JSONデータの最後に `\00` のヌルバイト(文字列終了識別バイト)+文字長(LENで指定する値)までのパディングデータが付加されることに注意してください。 659 | 660 | ``` 661 | { 662 | "key" : , 663 | "id" : , 664 | "cmd" : , 665 | "progress": [, , ...] 666 | }\00<指定データ数までのパディングデータ> 667 | ``` 668 | 669 | - 返値プロパティ 670 | - `key` 671 | RPCサーバーで任意に割り振られたクエリ実行ナンバーが入ります。 672 | 673 | - `id` 674 | クエリで指定したidキーの番号が入ります。 675 | 676 | - `cmd` 677 | メソッドの識別番号(ペイロードの最初の1バイト)が入ります。 678 | 679 | - `progress` 680 | メソッド内の進捗が配列として入ります。値は `0` ~ `100` の範囲でパーセンテージを示します。 681 | ほとんどのクエリでは1つの要素の配列となりますが、内部で複数の実行ステージを持つ場合は、それぞれのステージの進捗が入ります。 682 | 683 | - 異なるドメインから運用する場合の注意 684 | 進捗ステータス取得はFlashAirの `command.cgi` を利用しているため、クロスオリジンHTTPリクエストではエラーが返ります。 685 | 686 | --- 687 | 688 | © 2017-2019 J-7SYSTEM WORKS LIMITED 689 | -------------------------------------------------------------------------------- /src/crpc_client.js: -------------------------------------------------------------------------------- 1 | /* 2 | ------------------------------------------------------------------------------------ 3 | -- Canarium Air RPC Client -- 4 | ------------------------------------------------------------------------------------ 5 | @author Shun OSAFUNE 6 | @copyright The MIT License (MIT); (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 7 | 8 | *Version release 9 | v0.3.0726 s.osafune@j7system.jp 10 | 11 | *Requirement Canarium RPC Server version 12 | v0.3.0726 or later 13 | 14 | ------------------------------------------------------------------------------------ 15 | The MIT License (MIT) 16 | Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy of 19 | this software and associated documentation files (the "Software"), to deal in 20 | the Software without restriction, including without limitation the rights to 21 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 22 | of the Software, and to permit persons to whom the Software is furnished to do 23 | so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all 26 | copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | SOFTWARE. 35 | ------------------------------------------------------------------------------------ 36 | */ 37 | 38 | var CanariumRPC_Client = function(option) { 39 | 40 | // Canarium RPC Clientのバージョン 41 | const crpc_version = "0.3.0726"; 42 | 43 | // Canarium RPC サーバーのタイムアウト時間(デフォルト180秒) 44 | const xhr_timeout = 180 * 1000; 45 | 46 | // RPCサーバーのURLを保存する変数 47 | let cors_host = ""; 48 | let rpc_server = "/crs.lua"; 49 | let cur_path = "/"; 50 | let work_path = cur_path + "work/"; 51 | 52 | // FlashAirの共有メモリ読み出しCGI 53 | let cgi_getprogress = "/command.cgi?op=130&ADDR=512&LEN=100"; 54 | 55 | // デバッグログ出力用 56 | let dbg_mes_level = 1; 57 | const dbg_log = (x, lv) => (dbg_mes_level >= lv) ? console.log(x) : null; 58 | dbg_log.info = (x) => dbg_log(x, 1); 59 | dbg_log.state = (x) => dbg_log(x, 2); 60 | dbg_log.data = (x) => dbg_log(x, 3); 61 | 62 | 63 | //------------------------------------------------------------------------ 64 | // Base64url function 65 | //------------------------------------------------------------------------ 66 | 67 | // Base64url 変換テーブル 68 | const base64url_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; 69 | 70 | // Base64urlのエンコード 71 | const b64enc = (bin) => { 72 | let bin_arr = new Uint8Array(bin); 73 | let str = ""; 74 | let i = 0, 75 | p = -6, 76 | a = 0, 77 | v = 0, 78 | c; 79 | 80 | while ((i < bin_arr.byteLength) || (p > -6)) { 81 | if (p < 0) { 82 | if (i < bin_arr.byteLength) { 83 | c = bin_arr[i++]; 84 | v += 8; 85 | } else { 86 | c = 0; 87 | } 88 | a = ((a & 255) << 8) | (c & 255); 89 | p += 8; 90 | } 91 | str += base64url_list.charAt((v > 0) ? (a >> p) & 63 : 64); 92 | p -= 6; 93 | v -= 6; 94 | } 95 | 96 | return str; 97 | }; 98 | 99 | // Base64urlのデコード 100 | const b64dec = (str) => { 101 | let res = []; 102 | let p = -8, 103 | a = 0, 104 | c; 105 | 106 | for (let i = 0; i < str.length; i++) { 107 | if ((c = base64url_list.indexOf(str.charAt(i))) < 0) continue; 108 | 109 | a = (a << 6) | (c & 63); 110 | if ((p += 6) >= 0) { 111 | res.push((a >> p) & 0xff); 112 | a &= 63; 113 | p -= 8; 114 | } 115 | } 116 | 117 | let bin = new ArrayBuffer(res.length); 118 | let bin_arr = new Uint8Array(bin); 119 | for (let i = 0; i < res.length; i++) bin_arr[i] = res[i]; //速度重視 120 | 121 | return bin; 122 | }; 123 | 124 | // バイトをHEX文字列に変換 125 | const toHexstr = (bin) => { 126 | const toHex = (d, n) => ("0000000" + d.toString(16).toUpperCase()).substr(-n); 127 | 128 | let bin_arr = new Uint8Array(bin); 129 | let s = ""; 130 | bin_arr.forEach((v) => { s += (' ' + toHex(v, 2)); }); 131 | 132 | return s; 133 | }; 134 | 135 | 136 | //------------------------------------------------------------------------ 137 | // Canarium RPC local function 138 | //------------------------------------------------------------------------ 139 | 140 | // メソッドで使うローカル関数 141 | const _push_word32 = (p, d) => { 142 | p.push((d >> 24) & 0xff); 143 | p.push((d >> 16) & 0xff); 144 | p.push((d >> 8) & 0xff); 145 | p.push((d >> 0) & 0xff); 146 | }; 147 | 148 | const _push_word16 = (p, d) => { 149 | p.push((d >> 8) & 0xff); 150 | p.push((d >> 0) & 0xff); 151 | }; 152 | 153 | const _push_str = (p, str) => { 154 | for (let i = 0; i < str.length; i++) p.push(str.charCodeAt(i)); 155 | }; 156 | 157 | const _array_avm = (cmd, devid, addr) => { 158 | let p = []; 159 | p.push(cmd); 160 | p.push(devid & 0xff); 161 | _push_word32(p, addr); 162 | 163 | return p; 164 | }; 165 | 166 | 167 | // CHECKメソッドパケット作成 168 | const _mm_check = (params) => { 169 | let p = []; 170 | p.push(0x01); 171 | 172 | return p; 173 | }; 174 | 175 | // STATメソッドパケット作成 176 | const _mm_stat = (params) => { 177 | let p = []; 178 | p.push(0x02); 179 | 180 | return p; 181 | }; 182 | // STATメソッドのポスト処理 183 | const _mm_stat_post = (res) => { 184 | let temp = cgi_getprogress.split("&"); 185 | cgi_getprogress = temp[0] + "&ADDR=" + res.progjson_begin + "&LEN=" + res.progjson_length; 186 | 187 | cur_path = res.current_path; 188 | work_path = cur_path + "work/"; 189 | 190 | let mac_str = ""; 191 | for (let i = 0; i < 6; i++) { 192 | mac_str = mac_str + ((i > 0) ? "-" : "") + res.mac_address.toUpperCase().substr(i * 2, 2); 193 | } 194 | dbg_log.info( 195 | "Card ID : 0x" + res.cid + 196 | "\nCard MAC : " + mac_str + 197 | "\nIP address : " + res.ip_address + 198 | "\nSubnet mask : " + res.ip_mask + 199 | "\nNetBIOS name: " + res.netname + 200 | "\nApp-info ID : " + res.appinfo + 201 | "\nFAT timezone: " + res.timezone + 202 | "\nCORS Host : " + cors_host + 203 | "\nRPC Server : " + rpc_server + 204 | "\nCurrent path: " + cur_path + 205 | "\nWork path : " + work_path + 206 | "\nProgress URI: " + cgi_getprogress 207 | ); 208 | 209 | return res; 210 | }; 211 | 212 | // CONFメソッドパケット作成 213 | const _mm_conf = (params) => { 214 | let p = []; 215 | p.push((params.cache === false) ? 0x09 : 0x08); 216 | _push_str(p, params.file); 217 | 218 | return p; 219 | }; 220 | 221 | // FCONFメソッドパケット作成 222 | const _mm_fconf = (params) => { 223 | let p = []; 224 | p.push(0x09); 225 | _push_str(p, params.file); 226 | 227 | return p; 228 | }; 229 | 230 | // IOWRメソッドパケット作成 231 | const _mm_iowr = (params) => { 232 | let p = _array_avm(0x10, params.devid, params.address); 233 | _push_word32(p, params.data); 234 | 235 | return p; 236 | }; 237 | 238 | // IORDメソッドパケット作成 239 | const _mm_iord = (params) => { 240 | return _array_avm(0x11, params.devid, params.address); 241 | }; 242 | 243 | // MEMWRメソッドパケット作成 244 | const _mm_memwr = (params) => { 245 | let p = _array_avm(0x18, params.devid, params.address); 246 | 247 | let data_obj = (typeof(params.data) === "string") ? b64dec(params.data) : params.data; 248 | let data_arr = new Uint8Array(data_obj); 249 | if (data_arr.byteLength > 64) return null; 250 | 251 | data_arr.forEach((v) => { p.push(v); }); 252 | 253 | return p; 254 | }; 255 | 256 | // MEMRDメソッドパケット作成 257 | const _mm_memrd = (params) => { 258 | if (params.size > 256) return null; 259 | 260 | let p = _array_avm(0x19, params.devid, params.address); 261 | _push_word16(p, params.size); 262 | 263 | return p; 264 | }; 265 | // MEMRDメソッドのポスト処理 266 | const _mm_memrd_post = (res) => b64dec(res); 267 | 268 | // BLOADメソッドパケット作成 269 | const _mm_bload = (params) => { 270 | let p = _array_avm(0x20, params.devid, params.address); 271 | _push_str(p, params.file); 272 | 273 | return p; 274 | }; 275 | 276 | // BSAVEメソッドパケット作成 277 | const _mm_bsave = (params) => { 278 | let p = _array_avm(0x21, params.devid, params.address); 279 | _push_word32(p, params.size); 280 | _push_str(p, params.file); 281 | 282 | return p; 283 | }; 284 | 285 | // LOADメソッドパケット作成 286 | const _mm_load = (params) => { 287 | let p = _array_avm(0x22, params.devid, params.offset); 288 | _push_str(p, params.file); 289 | 290 | return p; 291 | }; 292 | 293 | 294 | //------------------------------------------------------------------------ 295 | // Canarium RPC function 296 | //------------------------------------------------------------------------ 297 | 298 | // RPCエラーラベル 299 | const ERROR_JSON = { code: -32700, message: "Parse error" }; 300 | const ERROR_METHOD = { code: -32601, message: "Method not found" }; 301 | const ERROR_PARAM = { code: -32602, message: "Invalid params" }; 302 | 303 | // デフォルトのポスト処理 304 | const _post_default = (res) => res; 305 | 306 | // メソッドテーブル 307 | let method = { 308 | VER: { pfunc: _post_default }, 309 | CHECK: { qfunc: _mm_check, pfunc: _post_default }, 310 | STAT: { qfunc: _mm_stat, pfunc: _mm_stat_post }, 311 | CONF: { qfunc: _mm_conf, pfunc: _post_default }, 312 | FCONF: { qfunc: _mm_fconf, pfunc: _post_default }, 313 | IOWR: { qfunc: _mm_iowr, pfunc: _post_default }, 314 | IORD: { qfunc: _mm_iord, pfunc: _post_default }, 315 | MEMWR: { qfunc: _mm_memwr, pfunc: _post_default }, 316 | MEMRD: { qfunc: _mm_memrd, pfunc: _mm_memrd_post }, 317 | BLOAD: { qfunc: _mm_bload, pfunc: _post_default }, 318 | BSAVE: { qfunc: _mm_bsave, pfunc: _post_default }, 319 | LOAD: { qfunc: _mm_load, pfunc: _post_default } 320 | }; 321 | 322 | // メソッド追加と削除 323 | const setmethod = (name, qfunc, pfunc) => { 324 | if (typeof(name) !== "string" || (qfunc && typeof(qfunc) !== "function")) return false; 325 | 326 | if (qfunc) { 327 | method[name] = { 328 | qfunc: qfunc, 329 | pfunc: (typeof(pfunc) === "function") ? pfunc : _post_default 330 | }; 331 | } else { 332 | delete method[name]; 333 | } 334 | 335 | dbg_log.data(method); 336 | return true; 337 | }; 338 | 339 | 340 | // オブジェクトからCanarium RPCクエリを取得 341 | let auto_id_number = 1; // 自動で割り振るID番号 342 | 343 | const getquery = (t) => { 344 | 345 | // パラメータのチェックと成形 // 346 | if (typeof(t.id) === "number") { 347 | if (t.id >= 0 && t.id <= 65535) { 348 | auto_id_number = t.id; 349 | } else { 350 | return ERROR_JSON; 351 | } 352 | } else { 353 | t.id = auto_id_number; 354 | } 355 | auto_id_number = (auto_id_number >= 65535) ? 0 : auto_id_number + 1; 356 | 357 | if (!t.method) return ERROR_JSON; 358 | 359 | let params = Object.assign({}, t.params); 360 | params.devid = (t.params && typeof(t.params.devid) === "number") ? t.params.devid : 0x55; 361 | params.address = (t.params && typeof(t.params.address) === "number") ? t.params.address : 0; 362 | params.offset = (t.params && typeof(t.params.offset) === "number") ? t.params.offset : params.address; 363 | 364 | // ペイロード生成 // 365 | if (t.method === "VER") return ""; 366 | if (!method[t.method]) return ERROR_METHOD; 367 | 368 | let payload = method[t.method].qfunc(params); 369 | if (!payload || payload.length > 70) return ERROR_PARAM; 370 | 371 | // パケット生成 // 372 | let bin = new ArrayBuffer(payload.length + 4); 373 | let bin_arr = new Uint8Array(bin); 374 | 375 | bin_arr[0] = (t.id >> 8) & 0xff; 376 | bin_arr[1] = (t.id >> 0) & 0xff; 377 | bin_arr[2] = payload.length; 378 | 379 | let xsum = 0; 380 | payload.forEach((v, i) => { 381 | xsum = (v ^ ((xsum << 1) | ((xsum & 0x80) ? 1 : 0))) & 0xff; 382 | bin_arr[i + 4] = v; 383 | }); 384 | bin_arr[3] = xsum; 385 | 386 | dbg_log.data("packet :" + toHexstr(bin)); 387 | return b64enc(bin); 388 | }; 389 | 390 | 391 | // Canarium RPCの呼び出し 392 | const crpc_call = (t, prog_callback, prog_time) => { 393 | return new Promise((resolve, reject) => { 394 | 395 | // 進捗度コールバック処理 396 | let rpc_busy = true; 397 | const call_progress = (id, callback, nexttime) => { 398 | if (rpc_busy) { 399 | const xhr = new XMLHttpRequest(); 400 | xhr.open("GET", cors_host + cgi_getprogress); 401 | xhr.onerror = () => { 402 | console.error("commang.cgi request error."); 403 | }; 404 | xhr.onload = () => { 405 | if (xhr.responseText) { 406 | let json_len = xhr.responseText.indexOf('\0'); 407 | if (json_len >= 2) callback(id, JSON.parse(xhr.responseText.substr(0, json_len))); 408 | } 409 | setTimeout(() => { 410 | call_progress(id, callback, nexttime); 411 | }, nexttime); 412 | }; 413 | xhr.send(); 414 | } 415 | }; 416 | 417 | // RPCリクエスト処理 418 | let ot = (typeof(t) === "string") ? JSON.parse(t) : t; 419 | let query = (typeof(t) === "string" && ot.jsonrpc !== "2.0") ? ERROR_JSON : getquery(ot); 420 | 421 | if (typeof(query) === "string") { 422 | if (query !== "") query = "?" + query; 423 | dbg_log.state("JSON-RPC --> " + query); 424 | 425 | const xhr = new XMLHttpRequest(); 426 | xhr.open("GET", cors_host + rpc_server + query); 427 | xhr.timeout = xhr_timeout; 428 | xhr.ontimeout = () => { 429 | console.error("RPC call timed out."); 430 | rpc_busy = false; 431 | reject(new Error(xhr.statusText)); 432 | }; 433 | xhr.onerror = () => { 434 | console.error("RPC call request error."); 435 | rpc_busy = false; 436 | reject(new Error(xhr.statusText)); 437 | }; 438 | xhr.onload = () => { 439 | let res = xhr.responseText; 440 | dbg_log.state("JSON-RPC <-- " + res); 441 | 442 | if (typeof(t) !== "string") { 443 | res = JSON.parse(res); 444 | if ("result" in res) res.result = method[ot.method].pfunc(res.result); 445 | } 446 | 447 | rpc_busy = false; 448 | resolve(res); 449 | }; 450 | xhr.send(); 451 | 452 | let nexttime = (typeof(prog_time) !== "number") ? 500 : (prog_time < 100) ? 100 : prog_time; 453 | if (typeof(prog_callback) === "function") { 454 | setTimeout(() => { 455 | call_progress(ot.id, prog_callback, nexttime); 456 | }, nexttime); 457 | } 458 | 459 | } else { 460 | const res = { 461 | jsonrpc: "2.0", 462 | error: query, 463 | id: ot.id 464 | }; 465 | 466 | rpc_busy = false; 467 | resolve((typeof(t) === "string") ? JSON.stringify(res) : res); 468 | } 469 | }); 470 | }; 471 | 472 | 473 | //------------------------------------------------------------------------ 474 | // Constructor 475 | //------------------------------------------------------------------------ 476 | 477 | this.version = () => crpc_version; 478 | this.settings = (host, rpc) => { 479 | cors_host = host; 480 | rpc_server = rpc; 481 | return crpc_call({ method: "STAT" }); 482 | }; 483 | 484 | this.addmethod = setmethod; 485 | this.delmethod = (name) => setmethod(name); 486 | this.call = crpc_call; 487 | 488 | this.RPCVER = () => 489 | crpc_call({ method: "VER" }); 490 | 491 | this.CHECK = () => 492 | crpc_call({ method: "CHECK" }); 493 | 494 | this.IOWR = (addr, data) => 495 | crpc_call({ method: "IOWR", params: { address: addr, data: data } }); 496 | 497 | this.IORD = (addr) => 498 | crpc_call({ method: "IORD", params: { address: addr } }); 499 | 500 | this.MEMWR = (addr, data) => 501 | crpc_call({ method: "MEMWR", params: { address: addr, data: data } }); 502 | 503 | this.MEMRD = (addr, size) => 504 | crpc_call({ method: "MEMRD", params: { address: addr, size: size } }); 505 | 506 | this.CONF = (fname, callback) => 507 | crpc_call({ method: "CONF", params: { file: fname } }, callback); 508 | 509 | this.FCONF = (fname, callback) => 510 | crpc_call({ method: "FCONF", params: { file: fname } }, callback); 511 | 512 | this.BLOAD = (fname, addr, callback) => 513 | crpc_call({ method: "BLOAD", params: { file: fname, address: addr } }, callback); 514 | 515 | this.BSAVE = (fname, addr, size, callback) => 516 | crpc_call({ method: "BSAVE", params: { file: fname, size: size, address: addr } }, callback); 517 | 518 | this.LOAD = (fname, offset, callback) => 519 | crpc_call({ method: "LOAD", params: { file: fname, offset: offset } }, callback); 520 | 521 | this.encode = b64enc; 522 | this.decode = b64dec; 523 | this.query = getquery; 524 | 525 | this.dbglog = toHexstr; 526 | }; -------------------------------------------------------------------------------- /sample_app/air_melodychime/lib/crpc_client.js: -------------------------------------------------------------------------------- 1 | /* 2 | ------------------------------------------------------------------------------------ 3 | -- Canarium Air RPC Client -- 4 | ------------------------------------------------------------------------------------ 5 | @author Shun OSAFUNE 6 | @copyright The MIT License (MIT); (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 7 | 8 | *Version release 9 | v0.3.0726 s.osafune@j7system.jp 10 | 11 | *Requirement Canarium RPC Server version 12 | v0.3.0726 or later 13 | 14 | ------------------------------------------------------------------------------------ 15 | The MIT License (MIT) 16 | Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy of 19 | this software and associated documentation files (the "Software"), to deal in 20 | the Software without restriction, including without limitation the rights to 21 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 22 | of the Software, and to permit persons to whom the Software is furnished to do 23 | so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all 26 | copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | SOFTWARE. 35 | ------------------------------------------------------------------------------------ 36 | */ 37 | 38 | var CanariumRPC_Client = function(option) { 39 | 40 | // Canarium RPC Clientのバージョン 41 | const crpc_version = "0.3.0726"; 42 | 43 | // Canarium RPC サーバーのタイムアウト時間(デフォルト180秒) 44 | const xhr_timeout = 180 * 1000; 45 | 46 | // RPCサーバーのURLを保存する変数 47 | let cors_host = ""; 48 | let rpc_server = "/crs.lua"; 49 | let cur_path = "/"; 50 | let work_path = cur_path + "work/"; 51 | 52 | // FlashAirの共有メモリ読み出しCGI 53 | let cgi_getprogress = "/command.cgi?op=130&ADDR=512&LEN=100"; 54 | 55 | // デバッグログ出力用 56 | let dbg_mes_level = 1; 57 | const dbg_log = (x, lv) => (dbg_mes_level >= lv) ? console.log(x) : null; 58 | dbg_log.info = (x) => dbg_log(x, 1); 59 | dbg_log.state = (x) => dbg_log(x, 2); 60 | dbg_log.data = (x) => dbg_log(x, 3); 61 | 62 | 63 | //------------------------------------------------------------------------ 64 | // Base64url function 65 | //------------------------------------------------------------------------ 66 | 67 | // Base64url 変換テーブル 68 | const base64url_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; 69 | 70 | // Base64urlのエンコード 71 | const b64enc = (bin) => { 72 | let bin_arr = new Uint8Array(bin); 73 | let str = ""; 74 | let i = 0, 75 | p = -6, 76 | a = 0, 77 | v = 0, 78 | c; 79 | 80 | while ((i < bin_arr.byteLength) || (p > -6)) { 81 | if (p < 0) { 82 | if (i < bin_arr.byteLength) { 83 | c = bin_arr[i++]; 84 | v += 8; 85 | } else { 86 | c = 0; 87 | } 88 | a = ((a & 255) << 8) | (c & 255); 89 | p += 8; 90 | } 91 | str += base64url_list.charAt((v > 0) ? (a >> p) & 63 : 64); 92 | p -= 6; 93 | v -= 6; 94 | } 95 | 96 | return str; 97 | }; 98 | 99 | // Base64urlのデコード 100 | const b64dec = (str) => { 101 | let res = []; 102 | let p = -8, 103 | a = 0, 104 | c; 105 | 106 | for (let i = 0; i < str.length; i++) { 107 | if ((c = base64url_list.indexOf(str.charAt(i))) < 0) continue; 108 | 109 | a = (a << 6) | (c & 63); 110 | if ((p += 6) >= 0) { 111 | res.push((a >> p) & 0xff); 112 | a &= 63; 113 | p -= 8; 114 | } 115 | } 116 | 117 | let bin = new ArrayBuffer(res.length); 118 | let bin_arr = new Uint8Array(bin); 119 | for (let i = 0; i < res.length; i++) bin_arr[i] = res[i]; //速度重視 120 | 121 | return bin; 122 | }; 123 | 124 | // バイトをHEX文字列に変換 125 | const toHexstr = (bin) => { 126 | const toHex = (d, n) => ("0000000" + d.toString(16).toUpperCase()).substr(-n); 127 | 128 | let bin_arr = new Uint8Array(bin); 129 | let s = ""; 130 | bin_arr.forEach((v) => { s += (' ' + toHex(v, 2)); }); 131 | 132 | return s; 133 | }; 134 | 135 | 136 | //------------------------------------------------------------------------ 137 | // Canarium RPC local function 138 | //------------------------------------------------------------------------ 139 | 140 | // メソッドで使うローカル関数 141 | const _push_word32 = (p, d) => { 142 | p.push((d >> 24) & 0xff); 143 | p.push((d >> 16) & 0xff); 144 | p.push((d >> 8) & 0xff); 145 | p.push((d >> 0) & 0xff); 146 | }; 147 | 148 | const _push_word16 = (p, d) => { 149 | p.push((d >> 8) & 0xff); 150 | p.push((d >> 0) & 0xff); 151 | }; 152 | 153 | const _push_str = (p, str) => { 154 | for (let i = 0; i < str.length; i++) p.push(str.charCodeAt(i)); 155 | }; 156 | 157 | const _array_avm = (cmd, devid, addr) => { 158 | let p = []; 159 | p.push(cmd); 160 | p.push(devid & 0xff); 161 | _push_word32(p, addr); 162 | 163 | return p; 164 | }; 165 | 166 | 167 | // CHECKメソッドパケット作成 168 | const _mm_check = (params) => { 169 | let p = []; 170 | p.push(0x01); 171 | 172 | return p; 173 | }; 174 | 175 | // STATメソッドパケット作成 176 | const _mm_stat = (params) => { 177 | let p = []; 178 | p.push(0x02); 179 | 180 | return p; 181 | }; 182 | // STATメソッドのポスト処理 183 | const _mm_stat_post = (res) => { 184 | let temp = cgi_getprogress.split("&"); 185 | cgi_getprogress = temp[0] + "&ADDR=" + res.progjson_begin + "&LEN=" + res.progjson_length; 186 | 187 | cur_path = res.current_path; 188 | work_path = cur_path + "work/"; 189 | 190 | let mac_str = ""; 191 | for (let i = 0; i < 6; i++) { 192 | mac_str = mac_str + ((i > 0) ? "-" : "") + res.mac_address.toUpperCase().substr(i * 2, 2); 193 | } 194 | dbg_log.info( 195 | "Card ID : 0x" + res.cid + 196 | "\nCard MAC : " + mac_str + 197 | "\nIP address : " + res.ip_address + 198 | "\nSubnet mask : " + res.ip_mask + 199 | "\nNetBIOS name: " + res.netname + 200 | "\nApp-info ID : " + res.appinfo + 201 | "\nFAT timezone: " + res.timezone + 202 | "\nCORS Host : " + cors_host + 203 | "\nRPC Server : " + rpc_server + 204 | "\nCurrent path: " + cur_path + 205 | "\nWork path : " + work_path + 206 | "\nProgress URI: " + cgi_getprogress 207 | ); 208 | 209 | return res; 210 | }; 211 | 212 | // CONFメソッドパケット作成 213 | const _mm_conf = (params) => { 214 | let p = []; 215 | p.push((params.cache === false) ? 0x09 : 0x08); 216 | _push_str(p, params.file); 217 | 218 | return p; 219 | }; 220 | 221 | // FCONFメソッドパケット作成 222 | const _mm_fconf = (params) => { 223 | let p = []; 224 | p.push(0x09); 225 | _push_str(p, params.file); 226 | 227 | return p; 228 | }; 229 | 230 | // IOWRメソッドパケット作成 231 | const _mm_iowr = (params) => { 232 | let p = _array_avm(0x10, params.devid, params.address); 233 | _push_word32(p, params.data); 234 | 235 | return p; 236 | }; 237 | 238 | // IORDメソッドパケット作成 239 | const _mm_iord = (params) => { 240 | return _array_avm(0x11, params.devid, params.address); 241 | }; 242 | 243 | // MEMWRメソッドパケット作成 244 | const _mm_memwr = (params) => { 245 | let p = _array_avm(0x18, params.devid, params.address); 246 | 247 | let data_obj = (typeof(params.data) === "string") ? b64dec(params.data) : params.data; 248 | let data_arr = new Uint8Array(data_obj); 249 | if (data_arr.byteLength > 64) return null; 250 | 251 | data_arr.forEach((v) => { p.push(v); }); 252 | 253 | return p; 254 | }; 255 | 256 | // MEMRDメソッドパケット作成 257 | const _mm_memrd = (params) => { 258 | if (params.size > 256) return null; 259 | 260 | let p = _array_avm(0x19, params.devid, params.address); 261 | _push_word16(p, params.size); 262 | 263 | return p; 264 | }; 265 | // MEMRDメソッドのポスト処理 266 | const _mm_memrd_post = (res) => b64dec(res); 267 | 268 | // BLOADメソッドパケット作成 269 | const _mm_bload = (params) => { 270 | let p = _array_avm(0x20, params.devid, params.address); 271 | _push_str(p, params.file); 272 | 273 | return p; 274 | }; 275 | 276 | // BSAVEメソッドパケット作成 277 | const _mm_bsave = (params) => { 278 | let p = _array_avm(0x21, params.devid, params.address); 279 | _push_word32(p, params.size); 280 | _push_str(p, params.file); 281 | 282 | return p; 283 | }; 284 | 285 | // LOADメソッドパケット作成 286 | const _mm_load = (params) => { 287 | let p = _array_avm(0x22, params.devid, params.offset); 288 | _push_str(p, params.file); 289 | 290 | return p; 291 | }; 292 | 293 | 294 | //------------------------------------------------------------------------ 295 | // Canarium RPC function 296 | //------------------------------------------------------------------------ 297 | 298 | // RPCエラーラベル 299 | const ERROR_JSON = { code: -32700, message: "Parse error" }; 300 | const ERROR_METHOD = { code: -32601, message: "Method not found" }; 301 | const ERROR_PARAM = { code: -32602, message: "Invalid params" }; 302 | 303 | // デフォルトのポスト処理 304 | const _post_default = (res) => res; 305 | 306 | // メソッドテーブル 307 | let method = { 308 | VER: { pfunc: _post_default }, 309 | CHECK: { qfunc: _mm_check, pfunc: _post_default }, 310 | STAT: { qfunc: _mm_stat, pfunc: _mm_stat_post }, 311 | CONF: { qfunc: _mm_conf, pfunc: _post_default }, 312 | FCONF: { qfunc: _mm_fconf, pfunc: _post_default }, 313 | IOWR: { qfunc: _mm_iowr, pfunc: _post_default }, 314 | IORD: { qfunc: _mm_iord, pfunc: _post_default }, 315 | MEMWR: { qfunc: _mm_memwr, pfunc: _post_default }, 316 | MEMRD: { qfunc: _mm_memrd, pfunc: _mm_memrd_post }, 317 | BLOAD: { qfunc: _mm_bload, pfunc: _post_default }, 318 | BSAVE: { qfunc: _mm_bsave, pfunc: _post_default }, 319 | LOAD: { qfunc: _mm_load, pfunc: _post_default } 320 | }; 321 | 322 | // メソッド追加と削除 323 | const setmethod = (name, qfunc, pfunc) => { 324 | if (typeof(name) !== "string" || (qfunc && typeof(qfunc) !== "function")) return false; 325 | 326 | if (qfunc) { 327 | method[name] = { 328 | qfunc: qfunc, 329 | pfunc: (typeof(pfunc) === "function") ? pfunc : _post_default 330 | }; 331 | } else { 332 | delete method[name]; 333 | } 334 | 335 | dbg_log.data(method); 336 | return true; 337 | }; 338 | 339 | 340 | // オブジェクトからCanarium RPCクエリを取得 341 | let auto_id_number = 1; // 自動で割り振るID番号 342 | 343 | const getquery = (t) => { 344 | 345 | // パラメータのチェックと成形 // 346 | if (typeof(t.id) === "number") { 347 | if (t.id >= 0 && t.id <= 65535) { 348 | auto_id_number = t.id; 349 | } else { 350 | return ERROR_JSON; 351 | } 352 | } else { 353 | t.id = auto_id_number; 354 | } 355 | auto_id_number = (auto_id_number >= 65535) ? 0 : auto_id_number + 1; 356 | 357 | if (!t.method) return ERROR_JSON; 358 | 359 | let params = Object.assign({}, t.params); 360 | params.devid = (t.params && typeof(t.params.devid) === "number") ? t.params.devid : 0x55; 361 | params.address = (t.params && typeof(t.params.address) === "number") ? t.params.address : 0; 362 | params.offset = (t.params && typeof(t.params.offset) === "number") ? t.params.offset : params.address; 363 | 364 | // ペイロード生成 // 365 | if (t.method === "VER") return ""; 366 | if (!method[t.method]) return ERROR_METHOD; 367 | 368 | let payload = method[t.method].qfunc(params); 369 | if (!payload || payload.length > 70) return ERROR_PARAM; 370 | 371 | // パケット生成 // 372 | let bin = new ArrayBuffer(payload.length + 4); 373 | let bin_arr = new Uint8Array(bin); 374 | 375 | bin_arr[0] = (t.id >> 8) & 0xff; 376 | bin_arr[1] = (t.id >> 0) & 0xff; 377 | bin_arr[2] = payload.length; 378 | 379 | let xsum = 0; 380 | payload.forEach((v, i) => { 381 | xsum = (v ^ ((xsum << 1) | ((xsum & 0x80) ? 1 : 0))) & 0xff; 382 | bin_arr[i + 4] = v; 383 | }); 384 | bin_arr[3] = xsum; 385 | 386 | dbg_log.data("packet :" + toHexstr(bin)); 387 | return b64enc(bin); 388 | }; 389 | 390 | 391 | // Canarium RPCの呼び出し 392 | const crpc_call = (t, prog_callback, prog_time) => { 393 | return new Promise((resolve, reject) => { 394 | 395 | // 進捗度コールバック処理 396 | let rpc_busy = true; 397 | const call_progress = (id, callback, nexttime) => { 398 | if (rpc_busy) { 399 | const xhr = new XMLHttpRequest(); 400 | xhr.open("GET", cors_host + cgi_getprogress); 401 | xhr.onerror = () => { 402 | console.error("commang.cgi request error."); 403 | }; 404 | xhr.onload = () => { 405 | if (xhr.responseText) { 406 | let json_len = xhr.responseText.indexOf('\0'); 407 | if (json_len >= 2) callback(id, JSON.parse(xhr.responseText.substr(0, json_len))); 408 | } 409 | setTimeout(() => { 410 | call_progress(id, callback, nexttime); 411 | }, nexttime); 412 | }; 413 | xhr.send(); 414 | } 415 | }; 416 | 417 | // RPCリクエスト処理 418 | let ot = (typeof(t) === "string") ? JSON.parse(t) : t; 419 | let query = (typeof(t) === "string" && ot.jsonrpc !== "2.0") ? ERROR_JSON : getquery(ot); 420 | 421 | if (typeof(query) === "string") { 422 | if (query !== "") query = "?" + query; 423 | dbg_log.state("JSON-RPC --> " + query); 424 | 425 | const xhr = new XMLHttpRequest(); 426 | xhr.open("GET", cors_host + rpc_server + query); 427 | xhr.timeout = xhr_timeout; 428 | xhr.ontimeout = () => { 429 | console.error("RPC call timed out."); 430 | rpc_busy = false; 431 | reject(new Error(xhr.statusText)); 432 | }; 433 | xhr.onerror = () => { 434 | console.error("RPC call request error."); 435 | rpc_busy = false; 436 | reject(new Error(xhr.statusText)); 437 | }; 438 | xhr.onload = () => { 439 | let res = xhr.responseText; 440 | dbg_log.state("JSON-RPC <-- " + res); 441 | 442 | if (typeof(t) !== "string") { 443 | res = JSON.parse(res); 444 | if ("result" in res) res.result = method[ot.method].pfunc(res.result); 445 | } 446 | 447 | rpc_busy = false; 448 | resolve(res); 449 | }; 450 | xhr.send(); 451 | 452 | let nexttime = (typeof(prog_time) !== "number") ? 500 : (prog_time < 100) ? 100 : prog_time; 453 | if (typeof(prog_callback) === "function") { 454 | setTimeout(() => { 455 | call_progress(ot.id, prog_callback, nexttime); 456 | }, nexttime); 457 | } 458 | 459 | } else { 460 | const res = { 461 | jsonrpc: "2.0", 462 | error: query, 463 | id: ot.id 464 | }; 465 | 466 | rpc_busy = false; 467 | resolve((typeof(t) === "string") ? JSON.stringify(res) : res); 468 | } 469 | }); 470 | }; 471 | 472 | 473 | //------------------------------------------------------------------------ 474 | // Constructor 475 | //------------------------------------------------------------------------ 476 | 477 | this.version = () => crpc_version; 478 | this.settings = (host, rpc) => { 479 | cors_host = host; 480 | rpc_server = rpc; 481 | return crpc_call({ method: "STAT" }); 482 | }; 483 | 484 | this.addmethod = setmethod; 485 | this.delmethod = (name) => setmethod(name); 486 | this.call = crpc_call; 487 | 488 | this.RPCVER = () => 489 | crpc_call({ method: "VER" }); 490 | 491 | this.CHECK = () => 492 | crpc_call({ method: "CHECK" }); 493 | 494 | this.IOWR = (addr, data) => 495 | crpc_call({ method: "IOWR", params: { address: addr, data: data } }); 496 | 497 | this.IORD = (addr) => 498 | crpc_call({ method: "IORD", params: { address: addr } }); 499 | 500 | this.MEMWR = (addr, data) => 501 | crpc_call({ method: "MEMWR", params: { address: addr, data: data } }); 502 | 503 | this.MEMRD = (addr, size) => 504 | crpc_call({ method: "MEMRD", params: { address: addr, size: size } }); 505 | 506 | this.CONF = (fname, callback) => 507 | crpc_call({ method: "CONF", params: { file: fname } }, callback); 508 | 509 | this.FCONF = (fname, callback) => 510 | crpc_call({ method: "FCONF", params: { file: fname } }, callback); 511 | 512 | this.BLOAD = (fname, addr, callback) => 513 | crpc_call({ method: "BLOAD", params: { file: fname, address: addr } }, callback); 514 | 515 | this.BSAVE = (fname, addr, size, callback) => 516 | crpc_call({ method: "BSAVE", params: { file: fname, size: size, address: addr } }, callback); 517 | 518 | this.LOAD = (fname, offset, callback) => 519 | crpc_call({ method: "LOAD", params: { file: fname, offset: offset } }, callback); 520 | 521 | this.encode = b64enc; 522 | this.decode = b64dec; 523 | this.query = getquery; 524 | 525 | this.dbglog = toHexstr; 526 | }; -------------------------------------------------------------------------------- /crpc_client_doc.md: -------------------------------------------------------------------------------- 1 | Canarium RPC Client 2 | =================== 3 | 4 | Canarium RPCはFlashAirを使ってFPGAリソースへのアクセスを提供するリモートプロシージャコール(RPC)ライブラリです。 5 | `crpc_client.js` では、ブラウザ側にJSON-RPCのクライアントAPIを提供します。サーバー側は[Canarium RPC Server](canarium_rpc_doc.md)を参照してください。 6 | 7 | 8 | 9 | 10 | ライセンス 11 | ========== 12 | 13 | [The MIT License (MIT)](https://opensource.org/licenses/MIT) 14 | Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 15 | 16 | 17 | 対象環境 18 | ======== 19 | 20 | - FlashAir W-04 ファーム W4.00.03 以降 21 | - Canarium RPC v0.3.0726以降 22 | 23 | 24 | 使い方 25 | ====== 26 | 27 | 1. ブラウザ側で `crpc_client.js` を読み込みます。 28 | ```html 29 | 30 | 31 | : 32 | 33 | ``` 34 | 35 | 2. スクリプト内で `CanariumRPC_Clinet()` オブジェクトを生成してアクセスを行います。 36 | ```javascript 37 | const crpc = new CanariumRPC_Clinet(); 38 | 39 | const fpga_config = async() => { 40 | let res; 41 | 42 | // FPGAのコンフィグレーションを実行 43 | res = await crpc.CONF("olive_std_top.rbf"); 44 | if (!res.result) return res.error; 45 | console.log("FPGA configured"); 46 | 47 | // sysIDを読み出す 48 | res = await crpc.IORD(0x10000000); 49 | if (!res.result) return res.error; 50 | console.log("systemid = 0x" + ("0000000" + res.result.toString(16).toUpperCase()).substr(-8)); 51 | 52 | return null; 53 | }; 54 | 55 | fpga_config(); 56 | ``` 57 | 58 | --- 59 | APIリファレンス 60 | ============== 61 | 62 | CanariumRPC_Client.version 63 | ------------------------------------- 64 | 65 | Canarium RPC Clientのバージョンを取得します。 66 | 67 | - 書式 68 | *string* CanariumRPC_Client.version() 69 | 70 | - 記述例 71 | ```javascript 72 | let ver = crpc.version(); 73 | ``` 74 | 75 | - 引数 76 | 77 | なし 78 | 79 | - 返値 80 | - *string* 81 | バージョンを返します。 82 | 83 | 84 | --- 85 | CanariumRPC_Client.addmethod 86 | --------------------------------------------------------------------------------------------------- 87 | 88 | RPCサーバー側に追加したユーザーメソッドに対応するクエリを発行するRPCメソッドの追加を行います。 89 | 90 | - 書式 91 | *boolean* CanariumRPC_Client.addmethod(*string `name`*, *function `qfunc`* [, *function `pfunc`*] ) 92 | 93 | - 記述例 94 | ```javascript 95 | let res = crpc.addmethod("USER", (params) => { 96 | let p = []; 97 | p.push(0x40); // ユーザーメソッドに割り当てるコード 98 | p.push(params.byteA); // 任意のパラメータ 99 | p.push(params.byteB); 100 | return p; 101 | }); 102 | ``` 103 | 104 | - 引数 105 | - *string* `name` 106 | 追加するRPCメソッド名を指定します。既に同じ名前のメソッドがある場合は後から指定したものに更新されます。 107 | 108 | - *function* `qfunc` 109 | クエリを組み立てる関数を指定します。関数は `Array function(Object)` の形式で指定します。 110 | 引数として、JSON-RPC呼び出し時の `params` オブジェクトが渡されます。 111 | 返値のArrayオブジェクトの各要素はバイト値を格納します。Arrayの先頭要素はRPCメソッドの識別番号を入れなければなりません。指定出来る範囲は `0x00` ~ `0x7f` です。また `0x00` ~ `0x2f` は組み込み用に予約されています。 112 | 113 | - (option) *function* `pfunc` 114 | JSON-RPCのレスポンスに対してポスト処理が必要な場合に指定します。 115 | 116 | - 返値 117 | - *boolean* 118 | 関数の処理結果を返します。成功の場合は `true` 、失敗の場合は `false` です。 119 | 120 | 121 | --- 122 | CanariumRPC_Client.delmethod 123 | ------------------------------------------------------- 124 | 125 | `CanariumRPC_Client.addmethod()` で追加したRPCメソッドの削除を行います。 126 | 127 | - 書式 128 | *boolean* CanariumRPC_Client.delmethod(*string `name`*) 129 | 130 | - 記述例 131 | ```javascript 132 | let res = crpc.delmethod("USER"); 133 | ``` 134 | 135 | - 引数 136 | - *string* `name` 137 | 削除するRPCメソッド名を指定します。 138 | 139 | - 返値 140 | - *boolean* 141 | 関数の処理結果を返します。成功の場合は `true` 、失敗の場合は `false` です。 142 | 143 | 144 | --- 145 | CanariumRPC_Client.encode 146 | ------------------------------------------------------- 147 | 148 | バイナリデータをBase64Urlでエンコードします。 149 | 150 | - 書式 151 | *string* CanariumRPC_Client.encode(*ArrayBuffer `bin`*) 152 | 153 | - 記述例 154 | ```javascript 155 | let b64_str = crpc.encode(bin); 156 | ``` 157 | 158 | - 引数 159 | - *ArrayBuffer* `bin` 160 | エンコードするバイナリデータを指定します。 161 | 162 | - 返値 163 | - *string* 164 | エンコードした結果を返します。 165 | 166 | 167 | --- 168 | CanariumRPC_Client.decode 169 | ----------------------------------------------------------- 170 | 171 | Base64Urlでエンコードされた文字列をバイナリデータへ復元します。 172 | 173 | - 書式 174 | *ArrayBuffer* CanariumRPC_Client.decode(*string `b64_str`*) 175 | 176 | - 記述例 177 | ```javascript 178 | let bin = crpc.decode(b64_str); 179 | ``` 180 | 181 | - 引数 182 | - *string* `b64_str` 183 | デコードするBase64Url文字列を指定します。 184 | 185 | - 返値 186 | - *ArrayBuffer* 187 | デコードした結果を返します。 188 | 189 | 190 | --- 191 | CanariumRPC_Client.settings 192 | ------------------------------------------------------------------------- 193 | 194 | RPCサーバーのホストや場所を指定します。 195 | このメソッドが呼ばれると、Canarium RPCクライアントはサーバーへの接続を試み、クライアント設定を自動的に行います。 196 | 197 | - 書式 198 | *Promise* CanariumRPC_Client.settings(*string `host`*, *string `server`*) 199 | 200 | - 記述例 201 | ```javascript 202 | let res = await crpc.settings("", "/crs.lua"); 203 | ``` 204 | 205 | - 引数 206 | - *string* `host` 207 | クロスオリジン通信を行う場合に、RPCサーバーのドメイン名を指定します。ページ配信元もFlashAirの場合は空文字("")を指定します。 208 | FlashAirの制約により、クロスオリジン通信では `upload.cgi` や `command.cgi` を利用したメソッドは全てエラーが返ることに注意してください。 209 | 210 | - *string* `server` 211 | RPCサーバー名を指定します。セッション管理を行う場合、ここでセッションサーバーから発行されたサーバー名を指定します。 212 | 213 | - 返値 214 | - *Promise* 215 | 非同期実行の完了、または中止をPromiseオブジェクトで返します。 216 | 正常に終了した場合はJSON-RPCのレスポンスデータを格納したオブジェクトを返します。rejectは通信エラーが発生した場合のみ行われ、RPCサーバーがエラーを返した場合、メソッドとしては正常終了の扱いになることに注意してください。 217 | 218 | 219 | --- 220 | CanariumRPC_Client.call 221 | ------------------------------------------------------------------------------------------------------ 222 | 223 | RPCサーバーに対してメソッドを発行します。 224 | 225 | - 書式 226 | *Promise* CanariumRPC_Client.call(*object `jsonrpc`* [, *function `callback`* [, *number `period`* ]]) 227 | 228 | - 記述例 229 | ```javascript 230 | let res = await crpc.call({ 231 | method: "CONF", 232 | params: { 233 | file: "sample.rbf" 234 | } 235 | }); 236 | ``` 237 | 238 | - 引数 239 | - *object* `jsonrpc` 240 | RPCサーバーへリクエストする内容をオブジェクトとして指定します。プロパティのうち、`method` と `params` は必須です。それ以外のプロパティは省略可能です。 241 | 詳細は[RPCメソッドリファレンス](#rpcメソッドリファレンス)を参照してください。 242 | 243 | - (option) *function* `callback` 244 | RPCメソッドの進捗ステータスを取得する場合に指定します。関数は `function(Number, Object)` の形式で指定します。 245 | `Number` には呼び出したRPCメソッドのIDが返ります。`Object` には進捗ステータスのオブジェクトが返ります。 246 | 詳細は[進捗ステータスコールバック](#進捗ステータスコールバック)を参照してください。 247 | 248 | - (option) *number* `period` 249 | `callback` を呼び出す周期をミリ秒で指定します。 250 | 最低値は `100` で、省略した場合は `500` となります。値は整数値を指定しなければなりません。 251 | 252 | - 返値 253 | - *Promise* 254 | 非同期実行の完了、または中止をPromiseオブジェクトで返します。 255 | 正常に終了した場合はJSON-RPCのレスポンスデータを格納したオブジェクトを返します。rejectは通信エラーが発生した場合のみ行われ、RPCサーバーがエラーを返した場合、メソッドとしては正常終了の扱いになることに注意してください。 256 | 257 | 258 | --- 259 | RPCメソッドリファレンス 260 | ===================== 261 | 262 | ファイルについて 263 | --------------- 264 | 265 | RPCメソッドで指定するファイルはFlashAir側のストレージに格納されているものに限ります。 266 | 必要なファイルは予めカードに保存しておくか、FlashAir用のツール(あるいは `upload.cgi` )でファイルを転送してください。 267 | 同様にクエリの結果として書き出されるファイルはFlashAirのストレージに保存されます。必要に応じてクライアント側にダウンロードしてください。 268 | 269 | ファイルパスは次のような書き方ができます。 270 | 271 | *カレントフォルダのファイルを指定 272 | "testfile2.bin" 273 | "./testfile3.hex" 274 | 275 | *カレントフォルダ以下の子フォルダを指定 276 | "test/romdata.srec" 277 | 278 | *ファイルをフルパスで指定(サーバー側で許可されている場合のみ) 279 | "/foo/bar/testfile1.rbf" 280 | 281 | 282 | ファイル名、ファイルパス共に日本語を含む多バイト長文字は使用できません。 283 | 284 | 285 | RPCメソッドレスポンス 286 | ------------------- 287 | 288 | RPCメソッドのレスポンスはJSON-RPCに則ったオブジェクトで返されます。 289 | 290 | - 正常レスポンス 291 | 292 | ``` 293 | { 294 | jsonrpc: "2.0", 295 | result: or or or , 296 | id: 297 | } 298 | ``` 299 | 300 | - エラーレスポンス 301 | 302 | ``` 303 | { 304 | jsonrpc: "2.0", 305 | error: { 306 | code: , 307 | message: 308 | }, 309 | id: 310 | } 311 | ``` 312 | 313 | - プロパティ 314 | - `jsonrpc` 315 | *string* で `"2.0"` の固定値が入ります。 316 | 317 | - `id` 318 | RPCメソッドでリクエストしたID値が *number* で入ります。 319 | 320 | - `result` 321 | 正常終了の場合に結果が入ります。 *boolean*、*number* (32bit整数値)、*ArrayBuffer*、*object* 等が格納されます。 322 | 323 | - `error` 324 | エラーの場合は、`result` の代わりにこのプロパティを返します。 325 | - `code` : エラーコードが *number* で入ります。値はJSON-RPCに準拠します。 326 | - `message` : エラーメッセージがある場合は *string* で入ります。 327 | 328 | - エラーコード 329 | - パースエラー 330 | `error : {code: -32700, message: "Parse error"}` 331 | サーバーがクエリをデコードできなかった、または不正なパケット形式を検出した場合に返されます。 332 | 333 | - メソッド呼び出しエラー 334 | `error : {code: -32601, message: "Method not found"}` 335 | RPCメソッドが存在しない場合に返されます。 336 | 337 | - メソッド実行時エラー 338 | `error : {code: -32000, message: <エラーメッセージ>}` 339 | RPCメソッドの実行が失敗した場合に返されます。`message` にはRPCサーバーで返されるメッセージが *string* で入ります。 340 | 341 | 342 | RPCメソッドとFPGAの状態 343 | ---------------------- 344 | 345 | 下記のRPCメソッドはFPGAがコンフィグレーションされ、かつそのデザインにAvalon-MMブリッジが組み込まれている場合にのみ有効な動作を行います。 346 | 347 | - [IOWRメソッド](#iowrメソッド) 348 | - [IORDメソッド](#iordメソッド) 349 | - [MEMWRメソッド](#memwrメソッド) 350 | - [MEMRDメソッド](#memrdメソッド) 351 | - [BLOADメソッド](#bloadメソッド) 352 | - [BSAVEメソッド](#bsaveメソッド) 353 | - [LOADメソッド](#loadメソッド) 354 | 355 | 356 | --- 357 | VERメソッド 358 | ---------- 359 | 360 | RPCサーバーのバージョンを取得します。 361 | 362 | - RPC呼び出しオブジェクト 363 | ``` 364 | { 365 | method: "VER" 366 | } 367 | ``` 368 | 369 | - シンタクスシュガー 370 | *Promise* `CanariumRPC_Client.RPCVER()` 371 | 372 | - リクエストプロパティ 373 | なし 374 | 375 | - レスポンスプロパティ 376 | - `result` 377 | RPCサーバーのバージョン情報を *object* で返します。 378 | - `rpc_version` : Canarium RPCのバージョンが *string* で入ります。 379 | - `lib_version` : Canarium Air I/Oのバージョンが *string* で入ります。 380 | - `fa_version` : FlashAirのファームウェアバージョンが *string* で入ります。 381 | 382 | 383 | --- 384 | CHECKメソッド 385 | ------------ 386 | 387 | FPGAがコンフィグレーション済みかどうかをチェックします。 388 | 389 | - RPC呼び出しオブジェクト 390 | ``` 391 | { 392 | method: "CHECK", 393 | id: 394 | } 395 | ``` 396 | 397 | - シンタクスシュガー 398 | *Promise* `CanariumRPC_Client.CHECK()` 399 | 400 | - リクエストプロパティ 401 | - (option) `id` 402 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 403 | シンタクスシュガーではidプロパティは常に省略されます。 404 | 405 | - レスポンスプロパティ 406 | - `result` 407 | FPGAの状態を *number* で返します。 408 | - `1` : コンフィグレーションされている 409 | - `0` : 未コンフィグレーション状態 410 | - 上記以外 : 予約 411 | 412 | 413 | --- 414 | IOWRメソッド 415 | ----------- 416 | 417 | Qsysモジュール(FPGA内部ロジックコア)のペリフェラルレジスタにデータを書き込みます。 418 | IOWRメソッドは、Avalon-MMのメモリバス(Qsysモジュール内部メモリ空間)で32bitワード単位のアトミックなライトアクセスを保証します。 419 | 420 | - RPC呼び出しオブジェクト 421 | ``` 422 | { 423 | method: "IOWR", 424 | params: { 425 | address: , 426 | data: 427 | }, 428 | id: 429 | } 430 | ``` 431 | 432 | - シンタクスシュガー 433 | *Promise* `CanariumRPC_Client.IOWR(address, data)` 434 | 435 | - リクエストプロパティ 436 | - `params` 437 | - `address` 438 | 書き込み先アドレスを32bitの整数値で指定します。アドレス値は32bitのワード境界に整列していなければなりません。 439 | 440 | - `data` 441 | 書き込む値を32bitの整数値で指定します。 442 | 443 | - (option) `id` 444 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 445 | シンタクスシュガーではidプロパティは常に省略されます。 446 | 447 | - レスポンスプロパティ 448 | - `result` 449 | 完了で `true` を返します。 450 | 451 | 452 | --- 453 | IORDメソッド 454 | ----------- 455 | 456 | Qsysモジュール(FPGA内部ロジックコア)のペリフェラルレジスタからデータを読み出します。 457 | IORDメソッドは、Avalon-MMのメモリバス(Qsysモジュール内部メモリ空間)で32bitワード単位のアトミックなリードアクセスを保証します。 458 | 459 | - RPC呼び出しオブジェクト 460 | ``` 461 | { 462 | method: "IORD", 463 | params: { 464 | address: 465 | }, 466 | id: 467 | } 468 | ``` 469 | 470 | - シンタクスシュガー 471 | *Promise* `CanariumRPC_Client.IORD(address)` 472 | 473 | - リクエストプロパティ 474 | - `params` 475 | - `address` 476 | 読み出しアドレスを32bitの整数値で指定します。アドレス値は32bitのワード境界に整列していなければなりません。 477 | 478 | - (option) `id` 479 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 480 | シンタクスシュガーではidプロパティは常に省略されます。 481 | 482 | - レスポンスプロパティ 483 | - `result` 484 | 読み出した値を *number* (32bit符号無し整数)で返します。 485 | 486 | 487 | --- 488 | MEMWRメソッド 489 | ------------ 490 | 491 | Qsysモジュール(FPGA内部ロジックコア)の任意のメモリアドレスにバイトデータ列を書き込みます。 492 | 493 | - RPC呼び出しオブジェクト 494 | ``` 495 | { 496 | method: "MEMWR", 497 | params: { 498 | address: , 499 | data: 500 | }, 501 | id: 502 | } 503 | ``` 504 | 505 | - シンタクスシュガー 506 | *Promise* `CanariumRPC_Client.MEMWR(address, data)` 507 | 508 | - リクエストプロパティ 509 | - `params` 510 | - `address` 511 | 書き込み先頭アドレスを32bitの整数値で指定します。 512 | 513 | - `data` 514 | 書き込むデータバイト列を *ArrayBuffer* で指定します。指定可能なデータ長は最大64バイトです。 515 | 516 | - (option) `id` 517 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 518 | シンタクスシュガーではidプロパティは常に省略されます。 519 | 520 | - レスポンスプロパティ 521 | - `result` 522 | 完了で `true` を返します。 523 | 524 | 525 | --- 526 | MEMRDメソッド 527 | ------------ 528 | 529 | Qsysモジュール(FPGA内部ロジックコア)の任意のメモリアドレスからバイトデータ列を読み出します。 530 | 531 | - RPC呼び出しオブジェクト 532 | ``` 533 | { 534 | method: "MEMRD", 535 | params: { 536 | address: , 537 | size: 538 | }, 539 | id: 540 | } 541 | ``` 542 | 543 | - シンタクスシュガー 544 | *Promise* `CanariumRPC_Client.MEMRD(address, size)` 545 | 546 | - リクエストプロパティ 547 | - `params` 548 | - `address` 549 | 読み出し先頭アドレスを32bitの整数値で指定します。 550 | 551 | - `size` 552 | 読み出すバイト数を2バイトで指定します。指定可能な範囲は `1` ~ `256`です。 553 | 554 | - (option) `id` 555 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 556 | シンタクスシュガーではidプロパティは常に省略されます。 557 | 558 | - レスポンスプロパティ 559 | - `result` 560 | 読み出したデータバイト列を *ArrayBuffer* で返します。 561 | 562 | 563 | --- 564 | CONFメソッド 565 | ----------- 566 | 567 | FPGAのコンフィグレーションを行います。 568 | 既に生成されたキャッシュファイルが存在する場合はそれを利用して、短縮コンフィグレーションを行います。 569 | このメソッドでは[進捗ステータスコールバック](#進捗ステータスコールバック)が使用可能です。メソッド完了またはエラー発生までレスポンスが返らないため、タイムアウト時間に注意してください。 570 | 571 | - RPC呼び出しオブジェクト 572 | ``` 573 | { 574 | method: "CONF", 575 | params: { 576 | file: , 577 | cache: 578 | }, 579 | id: 580 | } 581 | ``` 582 | 583 | - シンタクスシュガー 584 | *Promise* `CanariumRPC_Client.CONF(file [, callback])` 585 | 586 | - リクエストプロパティ 587 | - `params` 588 | - `file` 589 | コンフィグレーションするRBFファイル名を指定します。 590 | 591 | - (option) `cache` 592 | キャッシュファイルを使用するかどうかを指定します。 593 | 省略した場合は `true` になります。`false` を指定した場合は[FCONFメソッド](#fconfメソッド)と等価です。 594 | シンタクスシュガーでは常に省略されます。 595 | 596 | - (option) `id` 597 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 598 | シンタクスシュガーではidプロパティは常に省略されます。 599 | 600 | - レスポンスプロパティ 601 | - `result` 602 | 完了で `true` を返します。 603 | 604 | - 進捗ステータスプロパティ 605 | - `progress` 606 | - `[0]` : キャッシュファイル作成の進捗度が `0` ~ `100` で入ります。 607 | - `[1]` : コンフィグレーションの進捗度が `0` ~ `100` で入ります。 608 | 609 | 610 | --- 611 | FCONFメソッド 612 | ------------ 613 | 614 | FPGAのコンフィグレーションを行います。 615 | 既にキャッシュファイルが存在する場合でも、指定のコンフィグレーションファイルでキャッシュファイルを再生成して、FPGAコンフィグレーションを行います。 616 | このメソッドでは[進捗ステータスコールバック](#進捗ステータスコールバック)が使用可能です。メソッド完了またはエラー発生までレスポンスが返らないため、タイムアウト時間に注意してください。 617 | 618 | - RPC呼び出しオブジェクト 619 | ``` 620 | { 621 | method: "FCONF", 622 | params: { 623 | file: , 624 | }, 625 | id: 626 | } 627 | ``` 628 | 629 | - シンタクスシュガー 630 | *Promise* `CanariumRPC_Client.FCONF(file [, callback])` 631 | 632 | - リクエストプロパティ 633 | - `params` 634 | - `file` 635 | コンフィグレーションするRBFファイル名を指定します。 636 | 637 | - (option) `id` 638 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 639 | シンタクスシュガーではidプロパティは常に省略されます。 640 | 641 | - レスポンスプロパティ 642 | - `result` 643 | 完了で `true` を返します。 644 | 645 | - 進捗ステータスプロパティ 646 | - `progress` 647 | - `[0]` : キャッシュファイル作成の進捗度が `0` ~ `100` で入ります。 648 | - `[1]` : コンフィグレーションの進捗度が `0` ~ `100` で入ります。 649 | 650 | 651 | --- 652 | BLOADメソッド 653 | ------------ 654 | 655 | Qsysモジュール(FPGA内部ロジックコア)の任意のメモリアドレスにファイルイメージをロードします。 656 | このメソッドでは[進捗ステータスコールバック](#進捗ステータスコールバック)が使用可能です。メソッド完了またはエラー発生までレスポンスが返らないため、タイムアウト時間に注意してください。 657 | 658 | - RPC呼び出しオブジェクト 659 | ``` 660 | { 661 | method: "BLOAD", 662 | params: { 663 | file: , 664 | address: 665 | }, 666 | id: 667 | } 668 | ``` 669 | 670 | - シンタクスシュガー 671 | *Promise* `CanariumRPC_Client.BLOAD(file, address [, callback])` 672 | 673 | - リクエストプロパティ 674 | - `params` 675 | - `file` 676 | ロードするファイル名を指定します。 677 | 678 | - `address` 679 | 書き込み先頭アドレスを32bitの整数値で指定します。 680 | 681 | - (option) `id` 682 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 683 | シンタクスシュガーではidプロパティは常に省略されます。 684 | 685 | - レスポンスプロパティ 686 | - `result` 687 | 完了で `true` を返します。 688 | 689 | - 進捗ステータスプロパティ 690 | - `progress` 691 | - `[0]` : ロードの進捗度が `0` ~ `100` で入ります。 692 | 693 | 694 | --- 695 | BSAVEメソッド 696 | ------------ 697 | 698 | Qsysモジュール(FPGA内部ロジックコア)の任意アドレスのメモリイメージをファイルに保存します。 699 | このメソッドでは[進捗ステータスコールバック](#進捗ステータスコールバック)が使用可能です。メソッド完了またはエラー発生までレスポンスが返らないため、タイムアウト時間に注意してください。 700 | 701 | 702 | - RPC呼び出しオブジェクト 703 | ``` 704 | { 705 | method: "BSAVE", 706 | params: { 707 | file: , 708 | address: , 709 | size: , 710 | }, 711 | id: 712 | } 713 | ``` 714 | 715 | - シンタクスシュガー 716 | *Promise* `CanariumRPC_Client.BSAVE(file, address, size [, callback])` 717 | 718 | - リクエストプロパティ 719 | - `params` 720 | - `file` 721 | 保存先のファイル名を指定します。同名のファイルが既に存在していた場合は上書きされます。 722 | 723 | - `address` 724 | 読み出し先頭アドレスを32bitの整数値で指定します。 725 | 726 | - `size` 727 | 保存するメモリイメージのバイトサイズを指定します。 728 | 729 | - (option) `id` 730 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 731 | シンタクスシュガーではidプロパティは常に省略されます。 732 | 733 | - レスポンスプロパティ 734 | - `result` 735 | 完了で `true` を返します。 736 | 737 | - 進捗ステータスプロパティ 738 | - `progress` 739 | - `[0]` : セーブの進捗度が `0` ~ `100` で入ります。 740 | 741 | 742 | --- 743 | LOADメソッド 744 | ----------- 745 | 746 | Qsysモジュール(FPGA内部ロジックコア)のメモリアドレス空間に、IntelHEX形式またはモトローラS-record形式のROMデータファイルをロードします。 747 | このメソッドでは[進捗ステータスコールバック](#進捗ステータスコールバック)が使用可能です。メソッド完了またはエラー発生までレスポンスが返らないため、タイムアウト時間に注意してください。 748 | 749 | - RPC呼び出しオブジェクト 750 | ``` 751 | { 752 | method: "LOAD", 753 | params: { 754 | file: , 755 | offset: 756 | }, 757 | id: 758 | } 759 | ``` 760 | 761 | - シンタクスシュガー 762 | *Promise* `CanariumRPC_Client.LOAD(file [, offset [, callback]])` 763 | 764 | - リクエストプロパティ 765 | - `params` 766 | - `file` 767 | ロードするファイル名を指定します。IntelHEX/S-recordのフォーマットは自動で認識されます。 768 | 769 | - (option) `offset` 770 | オフセットアドレスを32bitの整数値で指定します。 771 | この値とROMデータファイルのアドレス値を加算したアドレスへ書き込みが行われます。`0` を指定した場合、ROMデータファイルのアブソリュートアドレスとなります。 772 | 省略した場合は `0` が指定されます。 773 | 774 | - (option) `id` 775 | RPC呼び出しのID値を `0` ~ `65535` の範囲で指定します。省略した場合は自動的に連番が付与されます。 776 | シンタクスシュガーではidプロパティは常に省略されます。 777 | 778 | - レスポンスプロパティ 779 | - `result` 780 | 完了で `true` を返します。 781 | 782 | - 進捗ステータスプロパティ 783 | - `progress` 784 | - `[0]` : ロードの進捗度が `0` ~ `100` で入ります。 785 | 786 | 787 | --- 788 | 進捗ステータスコールバック 789 | ------------------------ 790 | 791 | `CanariumRPC_Client.call()` ではRPCサーバーからのレスポンスが返ってくるまで処理が待たされるため、処理に時間のかかるものについてはコールバックを利用して進捗ステータスを取得することができます。 792 | 進捗ステータスを取得できるメソッドについては[RPCメソッドリファレンス](#rpcメソッドリファレンス)を参照してください。 793 | 794 | - 記述例 795 | ```javascript 796 | const get_progress = (id, res) => { 797 | if (id == res.id) { 798 | console.log("Progress = " + res.progress[1] + "%"); 799 | } 800 | }; 801 | 802 | let res = await crpc.call({ 803 | method: "CONF", 804 | params: { file: "sample.rbf" } 805 | }, 806 | get_progress 807 | ); 808 | if (res.result) console.log("FPGA Configured."); 809 | ``` 810 | 811 | - `res` オブジェクトのプロパティ 812 | - `key` 813 | RPCサーバーで任意に割り振られたメソッド実行ナンバーが入ります。 814 | 815 | - `id` 816 | 実行中のメソッドのid番号が入ります。 817 | 818 | - `cmd` 819 | メソッドの識別番号(ペイロードの最初の1バイト)が入ります。 820 | 821 | - `progress` 822 | メソッド内の進捗が配列として入ります。値は `0` ~ `100` の範囲でパーセンテージを示します。 823 | ほとんどのクエリでは1つの要素の配列となりますが、内部で複数の実行ステージを持つ場合は、それぞれのステージの進捗が入ります。 824 | 825 | - `id` と `res.id` の違い 826 | 第一引数の `id` はRPCメソッドでリクエストしたID値が入り、`res.id` に入る値は現在のRPCサーバーで実行されているメソッドのID値が入ります。 827 | 複数あるいはキューイングされたRPC呼び出しが行われていた場合、`CanariumRPC_Client.call()` で呼び出したRPCメソッドと、その時点でRPCサーバーで実行されているRPCメソッドが一致しない場合があることに注意が必要です。 828 | 829 | - 異なるドメインから運用する場合の注意 830 | 進捗ステータス取得はFlashAirの `command.cgi` を利用しているため、クロスオリジンHTTPリクエストではエラーが返ります。 831 | 832 | 833 | --- 834 | 835 | © 2017-2019 J-7SYSTEM WORKS LIMITED 836 | -------------------------------------------------------------------------------- /src/canarium_rpc.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ------------------------------------------------------------------------------------ 3 | -- Canarium Air RPC Server module -- 4 | ------------------------------------------------------------------------------------ 5 | @author Shun OSAFUNE 6 | @copyright The MIT License (MIT); (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 7 | 8 | *Version release 9 | v0.3.0806 s.osafune@j7system.jp 10 | 11 | *Requirement FlashAir firmware version 12 | W4.00.03+ 13 | 14 | *Requirement Canarium Air version 15 | v0.3.0726 or later 16 | 17 | ------------------------------------------------------------------------------------ 18 | -- The MIT License (MIT) 19 | -- Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 20 | -- 21 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 22 | -- this software and associated documentation files (the "Software"), to deal in 23 | -- the Software without restriction, including without limitation the rights to 24 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 25 | -- of the Software, and to permit persons to whom the Software is furnished to do 26 | -- so, subject to the following conditions: 27 | -- 28 | -- The above copyright notice and this permission notice shall be included in all 29 | -- copies or substantial portions of the Software. 30 | -- 31 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | -- SOFTWARE. 38 | ------------------------------------------------------------------------------------ 39 | --]] 40 | 41 | -- 外部モジュール 42 | local lines = require "io".lines 43 | local band = require "bit32".band 44 | local bor = require "bit32".bor 45 | local bxor = require "bit32".bxor 46 | local lshift = require "bit32".lshift 47 | local extract = require "bit32".extract 48 | local btest = require "bit32".btest 49 | local schar = require "string".char 50 | local sform = require "string".format 51 | local concat = require "table".concat 52 | local rand = require "math".random 53 | local shdmem = require "fa".sharedmemory 54 | local sdioreg = require "fa".ReadStatusReg 55 | local jsonenc = require "cjson".encode 56 | 57 | -- モジュールオブジェクト 58 | cr = {} 59 | 60 | -- バージョンとコピーライト 61 | function cr.version() return "0.3.0806" end 62 | function cr.copyright() return "(c)2017-2019 J-7SYSTEM WORKS LIMITED." end 63 | 64 | -- デバッグ表示メソッド(必要があれば外部で定義する) 65 | function cr.dbgprint(...) end 66 | 67 | 68 | ------------------------------------------------------------------------------------ 69 | -- Base64url function (RFC4648) 70 | ------------------------------------------------------------------------------------ 71 | 72 | -- Base64urlへエンコード 73 | local b64table = { 74 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 75 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 76 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 77 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'} 78 | 79 | function cr.b64enc(d) 80 | local b64str = {} 81 | local n = 1 82 | local m = #d % 3 83 | 84 | while n+2 <= #d do 85 | local b0,b1,b2 = d:byte(n, n+2) 86 | local chunk = bor(lshift(b0, 16), lshift(b1, 8), b2) 87 | b64str[#b64str + 1] = b64table[extract(chunk, 18, 6) + 1] .. b64table[extract(chunk, 12, 6) + 1] 88 | .. b64table[extract(chunk, 6, 6) + 1] .. b64table[extract(chunk, 0, 6) + 1] 89 | 90 | n = n + 3 91 | end 92 | 93 | if m == 2 then 94 | local b0,b1 = d:byte(n, n+1) 95 | local chunk = bor(lshift(b0, 16), lshift(b1, 8)) 96 | b64str[#b64str + 1] = b64table[extract(chunk, 18, 6) + 1] .. b64table[extract(chunk, 12, 6) + 1] 97 | .. b64table[extract(chunk, 6, 6) + 1] 98 | elseif m == 1 then 99 | local b0 = d:byte(n) 100 | local chunk = lshift(b0, 16) 101 | b64str[#b64str + 1] = b64table[extract(chunk, 18, 6) + 1] .. b64table[extract(chunk, 12, 6) + 1] 102 | end 103 | 104 | return concat(b64str) 105 | end 106 | 107 | 108 | -- Base64urlをデコード 109 | local rb64table = { 110 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 111 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 112 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,0x3e, nil, nil, 113 | 0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d, nil, nil, nil,0x00, nil, nil, 114 | nil,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e, 115 | 0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, nil, nil, nil, nil,0x3f, 116 | nil,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28, 117 | 0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33, nil, nil, nil, nil, nil} 118 | 119 | function cr.b64dec(s) 120 | s = s:gsub("%s+", "") 121 | local m = #s % 4 122 | if m == 2 then 123 | s = s .. "==" 124 | elseif m == 3 then 125 | s = s .. "=" 126 | elseif m == 1 then 127 | return nil,"input data shortage" 128 | end 129 | 130 | local data = {} 131 | local n = 1 132 | local e = true 133 | 134 | while n+3 <= #s do 135 | local b0,b1,b2,b3 = s:byte(n, n+3) 136 | local c0 = rb64table[b0] 137 | local c1 = rb64table[b1] 138 | local c2 = rb64table[b2] 139 | local c3 = rb64table[b3] 140 | if not(c0 and c1 and c2 and c3) then e = false; break end 141 | 142 | local chunk = bor(lshift(c0, 18), lshift(c1, 12), lshift(c2, 6), c3) 143 | if b2 == 0x3d then 144 | if b3 ~= 0x3d then e = false; break end 145 | data[#data + 1] = schar(extract(chunk, 16, 8)) 146 | break 147 | elseif b3 == 0x3d then 148 | data[#data + 1] = schar(extract(chunk, 16, 8), extract(chunk, 8, 8)) 149 | break 150 | else 151 | data[#data + 1] = schar(extract(chunk, 16, 8), extract(chunk, 8, 8), extract(chunk, 0, 8)) 152 | end 153 | 154 | n = n + 4 155 | end 156 | 157 | if not e then return nil,"invalid character" end 158 | return concat(data) 159 | end 160 | 161 | 162 | ------------------------------------------------------------------------------------ 163 | -- Canarium RPC local function 164 | ------------------------------------------------------------------------------------ 165 | 166 | -- 進捗表示処理(ファンクションの待避とヘッダ部の設定) 167 | local prog_func,prog_txt = nil,"" 168 | local smem_begin = 512 -- 進捗情報を書き込む先頭バイト位置 169 | local smem_length = 100 -- 進捗情報取得サイズ 170 | 171 | local _setprog = function(key, id, cmd) 172 | if not key then 173 | if prog_func then 174 | ca.progress = prog_func 175 | prog_func = nil 176 | shdmem("write", smem_begin, 1, "\x00") 177 | end 178 | else 179 | if not prog_func then 180 | prog_txt = sform('{"key":%d,"id":%d,"cmd":%d,"progress":[', key, id, cmd) 181 | prog_func = ca.progress 182 | end 183 | end 184 | end 185 | 186 | local _update = function(f, ...) cr.update(...) end 187 | 188 | -- カレントファイルパス変換 189 | local ena_abspath = false; 190 | local cur_path = arg[0]:match(".+/") 191 | 192 | local _getpath = function(fn) 193 | if fn:sub(1, 1) ~= "/" then 194 | if fn:sub(1, 2) == "./" then fn = fn:sub(3, -1) end 195 | fn = cur_path .. fn 196 | elseif not ena_abspath then 197 | fn = "" 198 | end 199 | 200 | return fn 201 | end 202 | 203 | -- CONFIG情報取得 204 | local _get_faconfig = function() 205 | local conf = {} 206 | for ln in lines("/SD_WLAN/CONFIG") do 207 | local k,v = ln:match("([^,]+)=([^,]+)%c+") 208 | if k ~= nil and v ~= nil then conf[k] = v end 209 | end 210 | 211 | local reg = sdioreg() 212 | conf["MAC_ADDRESS"] = reg:sub((0x530 - 0x500) * 2 + 1, (0x530 - 0x500 + 6) * 2) 213 | 214 | local ip,mask,gw = fa.ip(); 215 | conf["IP_ADDRESS"] = ip; 216 | conf["IP_MASK"] = mask; 217 | conf["IP_GATEWAY"] = gw; 218 | 219 | return conf; 220 | end 221 | 222 | -- バイト列から32bitワードを取得 223 | local _get_word32 = function(s, n) 224 | return bor(lshift(s:byte(n, n), 24), lshift(s:byte(n+1, n+1), 16), lshift(s:byte(n+2, n+2), 8), s:byte(n+3, n+3)) 225 | end 226 | 227 | -- バイト列から16bitワードを取得 228 | local _get_word16 = function(s, n) 229 | return bor(lshift(s:byte(n, n), 8), s:byte(n+1, n+1)) 230 | end 231 | 232 | -- データのチェックコード生成 233 | local _checkcode = function (d) 234 | local x = 0 235 | for i=1,#d do 236 | x = bxor(d:byte(i), bor(lshift(x, 1), (btest(x, 0x80) and 1 or 0))) 237 | end 238 | return band(x, 0xff) 239 | end 240 | 241 | -- VERメソッド実行 242 | local _do_version = function() 243 | local config = _get_faconfig() 244 | 245 | return { 246 | rpc_version = cr.version(), 247 | lib_version = ca.version(), 248 | fa_version = config["VERSION"], 249 | fa_product = config["PRODUCT"], 250 | fa_vendor = config["VENDOR"], 251 | copyright = cr.copyright() 252 | } 253 | end 254 | 255 | -- CHECKメソッド実行 256 | local _do_check = function(cstr) 257 | cr.dbgprint("> check") 258 | 259 | return (ca.config() and 1 or 0) 260 | end 261 | 262 | -- STATメソッド実行 263 | local _do_status = function(cstr) 264 | cr.dbgprint("> stat") 265 | 266 | local config = _get_faconfig() 267 | 268 | return { 269 | current_path = cr.setpath(), 270 | absolute_access = ena_abspath, 271 | progjson_begin = smem_begin, 272 | progjson_length = smem_length, 273 | file_upload = (config["UPLOAD"] == "1"), 274 | cid = config["CID"], 275 | appinfo = config["APPINFO"], 276 | netname = config["APPNAME"] or "flashair", 277 | mac_address = config["MAC_ADDRESS"], 278 | ip_address = config["IP_ADDRESS"], 279 | ip_mask = config["IP_MASK"], 280 | ip_gateway = config["IP_GATEWAY"], 281 | timezone = tonumber(config["TIMEZONE"], 10) or 0 282 | } 283 | end 284 | 285 | -- CONFメソッド実行 286 | local _do_config = function(cstr) 287 | cr.dbgprint("> config") 288 | 289 | ca.progress = _update 290 | 291 | return ca.config{file = _getpath(cstr:sub(2, -1))} 292 | end 293 | 294 | -- FCONFメソッド実行 295 | local _do_fconfig = function(cstr) 296 | cr.dbgprint("> fconfig") 297 | 298 | ca.progress = _update 299 | 300 | return ca.config{file = _getpath(cstr:sub(2, -1)), cache = false} 301 | end 302 | 303 | 304 | -- IOWRメソッド実行 305 | local _do_iowr = function(cstr) 306 | cr.dbgprint("> iowr") 307 | 308 | ca.progress = _update 309 | ca.progress("", 0) 310 | 311 | local res = nil 312 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 313 | if avm then 314 | res,mes = avm:iowr(_get_word32(cstr, 3), _get_word32(cstr, 7)) 315 | avm:close() 316 | end 317 | 318 | if res then ca.progress("", 100) end 319 | 320 | return res,mes 321 | end 322 | 323 | -- IORDメソッド実行 324 | local _do_iord = function(cstr) 325 | cr.dbgprint("> iord") 326 | 327 | ca.progress = _update 328 | ca.progress("", 0) 329 | 330 | local res = nil 331 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 332 | if avm then 333 | res,mes = avm:iord(_get_word32(cstr, 3)) 334 | avm:close() 335 | end 336 | 337 | if res then 338 | ca.progress("", 100) 339 | cr.dbgprint(sform("> data : 0x%08x", res)) 340 | end 341 | 342 | return res,mes 343 | end 344 | 345 | -- MEMWRメソッド実行 346 | local _do_memwr = function(cstr) 347 | cr.dbgprint("> memwr") 348 | 349 | ca.progress = _update 350 | ca.progress("", 0) 351 | 352 | local res = nil 353 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 354 | if avm then 355 | res,mes = avm:memwr(_get_word32(cstr, 3), cstr:sub(7, -1)) 356 | avm:close() 357 | end 358 | 359 | if res then ca.progress("", 100) end 360 | 361 | return res,mes 362 | end 363 | 364 | -- MEMRDメソッド実行 365 | local _do_memrd = function(cstr) 366 | cr.dbgprint("> memrd") 367 | 368 | ca.progress = _update 369 | ca.progress("", 0) 370 | 371 | local res = nil 372 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 373 | if avm then 374 | res,mes = avm:memrd(_get_word32(cstr, 3), _get_word16(cstr, 7)) 375 | avm:close() 376 | end 377 | 378 | if res then 379 | ca.progress("", 100) 380 | -- 381 | local s = "> data :" 382 | for i=1,#res do s = s .. sform(" %02x", res:byte(i)) end 383 | cr.dbgprint(s.." ("..#res.."bytes)") 384 | --]] 385 | res = cr.b64enc(res) 386 | end 387 | 388 | return res,mes 389 | end 390 | 391 | 392 | -- BLOADメソッド実行 393 | local _do_bload = function(cstr) 394 | cr.dbgprint("> bload") 395 | 396 | ca.progress = _update 397 | 398 | local res = nil 399 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 400 | if avm then 401 | res,mes = avm:bload(_getpath(cstr:sub(7, -1)), _get_word32(cstr, 3)) 402 | avm:close() 403 | end 404 | 405 | return res,mes 406 | end 407 | 408 | -- BSAVEメソッド実行 409 | local _do_bsave = function(cstr) 410 | cr.dbgprint("> bsave") 411 | 412 | ca.progress = _update 413 | 414 | local res = nil 415 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 416 | if avm then 417 | res,mes = avm:bsave(_getpath(cstr:sub(11, -1)), _get_word32(cstr, 7), _get_word32(cstr, 3)) 418 | avm:close() 419 | end 420 | 421 | return res,mes 422 | end 423 | 424 | -- LOADメソッド実行 425 | local _do_load = function(cstr) 426 | cr.dbgprint("> load") 427 | 428 | ca.progress = _update 429 | 430 | local res = nil 431 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 432 | if avm then 433 | res,mes = avm:load(_getpath(cstr:sub(7, -1)), _get_word32(cstr, 3)) 434 | avm:close() 435 | end 436 | 437 | return res,mes 438 | end 439 | 440 | 441 | ------------------------------------------------------------------------------------ 442 | -- Canarium RPC command parser 443 | ------------------------------------------------------------------------------------ 444 | 445 | -- メソッドテーブルの設定 446 | local method = { 447 | [0x01] = {func = _do_check, name = "CHECK"}, 448 | [0x02] = {func = _do_status, name = "STAT"}, 449 | [0x08] = {func = _do_config, name = "CONF"}, 450 | [0x09] = {func = _do_fconfig, name = "FCONF"}, 451 | [0x10] = {func = _do_iowr, name = "IOWR"}, 452 | [0x11] = {func = _do_iord, name = "IORD"}, 453 | [0x18] = {func = _do_memwr, name = "MEMWR"}, 454 | [0x19] = {func = _do_memrd, name = "MEMRD"}, 455 | [0x20] = {func = _do_bload, name = "BLOAD"}, 456 | [0x21] = {func = _do_bsave, name = "BSAVE"}, 457 | [0x22] = {func = _do_load, name = "LOAD"} 458 | } 459 | local _no_method = function() return nil,"Method not found",-32601 end 460 | setmetatable(method, {__index = function() return {func = _no_method, name = ""} end}) 461 | 462 | -- ユーザーメソッドの設定・削除 463 | function cr.setmethod(name, cmd, func) 464 | local res = false 465 | local key 466 | for k,v in pairs(method) do 467 | if name == v.name then key = k end 468 | end 469 | 470 | if type(cmd) == "number" and (cmd >= 0x00 and cmd <= 0x7f) and type(func) == "function" then 471 | if key then method[key] = nil end 472 | method[cmd] = {func = func, name = name} 473 | res = true 474 | elseif type(cmd) == "nil" and key then 475 | method[key] = nil 476 | res = true 477 | end 478 | 479 | return res 480 | end 481 | 482 | 483 | -- 進捗表示のアップデート 484 | function cr.update(...) 485 | if not prog_func then return end 486 | 487 | local s = "" 488 | for i,v in ipairs({...}) do 489 | if i == 1 then 490 | s = s .. sform("%d", v) 491 | else 492 | s = s .. sform(",%d", v) 493 | end 494 | end 495 | s = prog_txt .. s .. "]}\x00" 496 | 497 | shdmem("write", smem_begin, #s, s) 498 | if #s > smem_length then smem_length = #s end 499 | --[[ 500 | local str = shdmem("read", smem_begin, smem_length) 501 | cr.dbgprint("> shdmem : "..str) 502 | --]] 503 | end 504 | 505 | 506 | -- カレントパスの設定 507 | function cr.setpath(path, ena_abs) 508 | if type(path) == "string" then 509 | if path:sub(1, 1) ~= "/" then 510 | if path:sub(1, 2) == "./" then path = path:sub(3, -1) end 511 | path = cur_path .. path 512 | end 513 | if path:sub(-1) ~= "/" then path = path .. "/" end 514 | cur_path = path 515 | 516 | ena_abspath = (type(ena_abs) == "boolean" and ena_abs) and true or false; 517 | end 518 | 519 | return cur_path 520 | end 521 | 522 | 523 | -- メソッドのパース 524 | function cr.parse(query) 525 | local _do_method = function(q) 526 | if not q then return _do_version() end 527 | 528 | local rp = cr.b64dec(q) 529 | 530 | -- query decode error 531 | if not rp then return nil,nil,"Parse error",-32700 end 532 | -- query packet error 533 | if #rp < 5 then return nil,nil,"Parse error",-32700 end 534 | 535 | local id = _get_word16(rp, 1) 536 | local dlen = rp:byte(3, 3) 537 | local ckey = rp:byte(4, 4) 538 | 539 | -- query data error 540 | if not(#rp == dlen+4 and ckey == _checkcode(rp:sub(5, -1))) then return nil,id,"Parse error",-32700 end 541 | 542 | --[[ 543 | local s = "> decode :" 544 | for i=1,#rp do s = s .. sform(" %02x", rp:byte(i)) end 545 | cr.dbgprint(s) 546 | --]] 547 | 548 | -- メソッド実行 549 | local key = rand(65535) 550 | local cmd = rp:byte(5, 5) 551 | local cstr = rp:sub(5, -1) 552 | 553 | _setprog(key, id, cmd) 554 | 555 | local res,mes,ecode = method[cmd].func(cstr) 556 | ecode = ecnode or -32000 557 | 558 | _setprog() 559 | 560 | if not res then return res,id,mes,ecode end 561 | return res,id 562 | end 563 | 564 | -- クエリのパースと実行 565 | local res,id,mes,ecode = _do_method(query) 566 | 567 | local t = {jsonrpc="2.0", id=id} 568 | if res then t.result = res else t.error = {code=ecode, message=mes} end 569 | 570 | return jsonenc(t) 571 | end 572 | 573 | 574 | ------------------------------------------------------------------------------------ 575 | -- テスト用ファンクション 576 | ------------------------------------------------------------------------------------ 577 | 578 | -- クエリを生成 579 | function cr.makequery(t) 580 | local _setavm = function(cmd, devid, addr) 581 | local s = schar(cmd, devid) 582 | for i=24,0,-8 do s = s .. schar(extract(addr, i, 8)) end 583 | return s 584 | end 585 | 586 | local pstr = "" 587 | local dev = t.devid or 0x55 588 | 589 | local cmd,name 590 | for k,v in pairs(method) do 591 | if v.name == t.cmd then 592 | name = v.name 593 | cmd = k 594 | end 595 | end 596 | if not name then return nil,"invalid command" end 597 | 598 | if name == "VER" or name == "CHECK" or name == "STAT"then 599 | pstr = schar(cmd) 600 | 601 | elseif name == "CONF" or name == "FCONF" then 602 | pstr = schar(cmd) .. t.file 603 | 604 | elseif name == "IOWR" then 605 | if type(t.data) == "number" then 606 | pstr = _setavm(cmd, dev, t.addr) .. 607 | schar(extract(t.data, 24, 8), extract(t.data, 16, 8), extract(t.data, 8, 8), extract(t.data, 0, 8)) 608 | else 609 | return nil,"invalid parameter" 610 | end 611 | 612 | elseif name == "IORD" then 613 | pstr = _setavm(cmd, dev, t.addr) 614 | 615 | elseif name == "MEMWR" then 616 | if type(t.data) == "string" then 617 | pstr = _setavm(cmd, dev, t.addr) .. t.data 618 | else 619 | return nil,"invalid parameter" 620 | end 621 | 622 | elseif name == "MEMRD" then 623 | pstr = _setavm(cmd, dev, t.addr) .. 624 | schar(extract(t.size, 8, 8), extract(t.size, 0, 8)) 625 | 626 | elseif name == "BLOAD" then 627 | pstr = _setavm(cmd, dev, t.addr) .. t.file 628 | 629 | elseif name == "BSAVE" then 630 | pstr = _setavm(cmd, dev, t.addr) .. 631 | schar(extract(t.size, 24, 8), extract(t.size, 16, 8), extract(t.size, 8, 8), extract(t.size, 0, 8)) .. 632 | t.file 633 | 634 | elseif name == "LOAD" then 635 | local addr = (type(t.addr) == "number") and t.addr or 0 636 | pstr = _setavm(cmd, dev, addr) .. t.file 637 | 638 | else 639 | local param = (type(t.param) == "string") and t.param or "" 640 | pstr = schar(cmd) .. param 641 | 642 | end 643 | 644 | if #pstr > 70 then return nil,"payload data too long" end 645 | 646 | local res = schar(extract(t.id, 8, 8), extract(t.id, 0, 8), #pstr, _checkcode(pstr)) .. pstr 647 | --[[ 648 | local s = "packet :" 649 | for i=1,#res do s = s .. sform(" %02x", res:byte(i)) end 650 | cr.dbgprint(s) 651 | --]] 652 | return cr.b64enc(res) 653 | end 654 | 655 | 656 | return cr 657 | -------------------------------------------------------------------------------- /sample_app/air_melodychime/lib/canarium_rpc.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ------------------------------------------------------------------------------------ 3 | -- Canarium Air RPC Server module -- 4 | ------------------------------------------------------------------------------------ 5 | @author Shun OSAFUNE 6 | @copyright The MIT License (MIT); (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 7 | 8 | *Version release 9 | v0.3.0806 s.osafune@j7system.jp 10 | 11 | *Requirement FlashAir firmware version 12 | W4.00.03+ 13 | 14 | *Requirement Canarium Air version 15 | v0.3.0726 or later 16 | 17 | ------------------------------------------------------------------------------------ 18 | -- The MIT License (MIT) 19 | -- Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 20 | -- 21 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 22 | -- this software and associated documentation files (the "Software"), to deal in 23 | -- the Software without restriction, including without limitation the rights to 24 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 25 | -- of the Software, and to permit persons to whom the Software is furnished to do 26 | -- so, subject to the following conditions: 27 | -- 28 | -- The above copyright notice and this permission notice shall be included in all 29 | -- copies or substantial portions of the Software. 30 | -- 31 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | -- SOFTWARE. 38 | ------------------------------------------------------------------------------------ 39 | --]] 40 | 41 | -- 外部モジュール 42 | local lines = require "io".lines 43 | local band = require "bit32".band 44 | local bor = require "bit32".bor 45 | local bxor = require "bit32".bxor 46 | local lshift = require "bit32".lshift 47 | local extract = require "bit32".extract 48 | local btest = require "bit32".btest 49 | local schar = require "string".char 50 | local sform = require "string".format 51 | local concat = require "table".concat 52 | local rand = require "math".random 53 | local shdmem = require "fa".sharedmemory 54 | local sdioreg = require "fa".ReadStatusReg 55 | local jsonenc = require "cjson".encode 56 | 57 | -- モジュールオブジェクト 58 | cr = {} 59 | 60 | -- バージョンとコピーライト 61 | function cr.version() return "0.3.0806" end 62 | function cr.copyright() return "(c)2017-2019 J-7SYSTEM WORKS LIMITED." end 63 | 64 | -- デバッグ表示メソッド(必要があれば外部で定義する) 65 | function cr.dbgprint(...) end 66 | 67 | 68 | ------------------------------------------------------------------------------------ 69 | -- Base64url function (RFC4648) 70 | ------------------------------------------------------------------------------------ 71 | 72 | -- Base64urlへエンコード 73 | local b64table = { 74 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 75 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 76 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 77 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'} 78 | 79 | function cr.b64enc(d) 80 | local b64str = {} 81 | local n = 1 82 | local m = #d % 3 83 | 84 | while n+2 <= #d do 85 | local b0,b1,b2 = d:byte(n, n+2) 86 | local chunk = bor(lshift(b0, 16), lshift(b1, 8), b2) 87 | b64str[#b64str + 1] = b64table[extract(chunk, 18, 6) + 1] .. b64table[extract(chunk, 12, 6) + 1] 88 | .. b64table[extract(chunk, 6, 6) + 1] .. b64table[extract(chunk, 0, 6) + 1] 89 | 90 | n = n + 3 91 | end 92 | 93 | if m == 2 then 94 | local b0,b1 = d:byte(n, n+1) 95 | local chunk = bor(lshift(b0, 16), lshift(b1, 8)) 96 | b64str[#b64str + 1] = b64table[extract(chunk, 18, 6) + 1] .. b64table[extract(chunk, 12, 6) + 1] 97 | .. b64table[extract(chunk, 6, 6) + 1] 98 | elseif m == 1 then 99 | local b0 = d:byte(n) 100 | local chunk = lshift(b0, 16) 101 | b64str[#b64str + 1] = b64table[extract(chunk, 18, 6) + 1] .. b64table[extract(chunk, 12, 6) + 1] 102 | end 103 | 104 | return concat(b64str) 105 | end 106 | 107 | 108 | -- Base64urlをデコード 109 | local rb64table = { 110 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 111 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 112 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,0x3e, nil, nil, 113 | 0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d, nil, nil, nil,0x00, nil, nil, 114 | nil,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e, 115 | 0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, nil, nil, nil, nil,0x3f, 116 | nil,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28, 117 | 0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33, nil, nil, nil, nil, nil} 118 | 119 | function cr.b64dec(s) 120 | s = s:gsub("%s+", "") 121 | local m = #s % 4 122 | if m == 2 then 123 | s = s .. "==" 124 | elseif m == 3 then 125 | s = s .. "=" 126 | elseif m == 1 then 127 | return nil,"input data shortage" 128 | end 129 | 130 | local data = {} 131 | local n = 1 132 | local e = true 133 | 134 | while n+3 <= #s do 135 | local b0,b1,b2,b3 = s:byte(n, n+3) 136 | local c0 = rb64table[b0] 137 | local c1 = rb64table[b1] 138 | local c2 = rb64table[b2] 139 | local c3 = rb64table[b3] 140 | if not(c0 and c1 and c2 and c3) then e = false; break end 141 | 142 | local chunk = bor(lshift(c0, 18), lshift(c1, 12), lshift(c2, 6), c3) 143 | if b2 == 0x3d then 144 | if b3 ~= 0x3d then e = false; break end 145 | data[#data + 1] = schar(extract(chunk, 16, 8)) 146 | break 147 | elseif b3 == 0x3d then 148 | data[#data + 1] = schar(extract(chunk, 16, 8), extract(chunk, 8, 8)) 149 | break 150 | else 151 | data[#data + 1] = schar(extract(chunk, 16, 8), extract(chunk, 8, 8), extract(chunk, 0, 8)) 152 | end 153 | 154 | n = n + 4 155 | end 156 | 157 | if not e then return nil,"invalid character" end 158 | return concat(data) 159 | end 160 | 161 | 162 | ------------------------------------------------------------------------------------ 163 | -- Canarium RPC local function 164 | ------------------------------------------------------------------------------------ 165 | 166 | -- 進捗表示処理(ファンクションの待避とヘッダ部の設定) 167 | local prog_func,prog_txt = nil,"" 168 | local smem_begin = 512 -- 進捗情報を書き込む先頭バイト位置 169 | local smem_length = 100 -- 進捗情報取得サイズ 170 | 171 | local _setprog = function(key, id, cmd) 172 | if not key then 173 | if prog_func then 174 | ca.progress = prog_func 175 | prog_func = nil 176 | shdmem("write", smem_begin, 1, "\x00") 177 | end 178 | else 179 | if not prog_func then 180 | prog_txt = sform('{"key":%d,"id":%d,"cmd":%d,"progress":[', key, id, cmd) 181 | prog_func = ca.progress 182 | end 183 | end 184 | end 185 | 186 | local _update = function(f, ...) cr.update(...) end 187 | 188 | -- カレントファイルパス変換 189 | local ena_abspath = false; 190 | local cur_path = arg[0]:match(".+/") 191 | 192 | local _getpath = function(fn) 193 | if fn:sub(1, 1) ~= "/" then 194 | if fn:sub(1, 2) == "./" then fn = fn:sub(3, -1) end 195 | fn = cur_path .. fn 196 | elseif not ena_abspath then 197 | fn = "" 198 | end 199 | 200 | return fn 201 | end 202 | 203 | -- CONFIG情報取得 204 | local _get_faconfig = function() 205 | local conf = {} 206 | for ln in lines("/SD_WLAN/CONFIG") do 207 | local k,v = ln:match("([^,]+)=([^,]+)%c+") 208 | if k ~= nil and v ~= nil then conf[k] = v end 209 | end 210 | 211 | local reg = sdioreg() 212 | conf["MAC_ADDRESS"] = reg:sub((0x530 - 0x500) * 2 + 1, (0x530 - 0x500 + 6) * 2) 213 | 214 | local ip,mask,gw = fa.ip(); 215 | conf["IP_ADDRESS"] = ip; 216 | conf["IP_MASK"] = mask; 217 | conf["IP_GATEWAY"] = gw; 218 | 219 | return conf; 220 | end 221 | 222 | -- バイト列から32bitワードを取得 223 | local _get_word32 = function(s, n) 224 | return bor(lshift(s:byte(n, n), 24), lshift(s:byte(n+1, n+1), 16), lshift(s:byte(n+2, n+2), 8), s:byte(n+3, n+3)) 225 | end 226 | 227 | -- バイト列から16bitワードを取得 228 | local _get_word16 = function(s, n) 229 | return bor(lshift(s:byte(n, n), 8), s:byte(n+1, n+1)) 230 | end 231 | 232 | -- データのチェックコード生成 233 | local _checkcode = function (d) 234 | local x = 0 235 | for i=1,#d do 236 | x = bxor(d:byte(i), bor(lshift(x, 1), (btest(x, 0x80) and 1 or 0))) 237 | end 238 | return band(x, 0xff) 239 | end 240 | 241 | -- VERメソッド実行 242 | local _do_version = function() 243 | local config = _get_faconfig() 244 | 245 | return { 246 | rpc_version = cr.version(), 247 | lib_version = ca.version(), 248 | fa_version = config["VERSION"], 249 | fa_product = config["PRODUCT"], 250 | fa_vendor = config["VENDOR"], 251 | copyright = cr.copyright() 252 | } 253 | end 254 | 255 | -- CHECKメソッド実行 256 | local _do_check = function(cstr) 257 | cr.dbgprint("> check") 258 | 259 | return (ca.config() and 1 or 0) 260 | end 261 | 262 | -- STATメソッド実行 263 | local _do_status = function(cstr) 264 | cr.dbgprint("> stat") 265 | 266 | local config = _get_faconfig() 267 | 268 | return { 269 | current_path = cr.setpath(), 270 | absolute_access = ena_abspath, 271 | progjson_begin = smem_begin, 272 | progjson_length = smem_length, 273 | file_upload = (config["UPLOAD"] == "1"), 274 | cid = config["CID"], 275 | appinfo = config["APPINFO"], 276 | netname = config["APPNAME"] or "flashair", 277 | mac_address = config["MAC_ADDRESS"], 278 | ip_address = config["IP_ADDRESS"], 279 | ip_mask = config["IP_MASK"], 280 | ip_gateway = config["IP_GATEWAY"], 281 | timezone = tonumber(config["TIMEZONE"], 10) or 0 282 | } 283 | end 284 | 285 | -- CONFメソッド実行 286 | local _do_config = function(cstr) 287 | cr.dbgprint("> config") 288 | 289 | ca.progress = _update 290 | 291 | return ca.config{file = _getpath(cstr:sub(2, -1))} 292 | end 293 | 294 | -- FCONFメソッド実行 295 | local _do_fconfig = function(cstr) 296 | cr.dbgprint("> fconfig") 297 | 298 | ca.progress = _update 299 | 300 | return ca.config{file = _getpath(cstr:sub(2, -1)), cache = false} 301 | end 302 | 303 | 304 | -- IOWRメソッド実行 305 | local _do_iowr = function(cstr) 306 | cr.dbgprint("> iowr") 307 | 308 | ca.progress = _update 309 | ca.progress("", 0) 310 | 311 | local res = nil 312 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 313 | if avm then 314 | res,mes = avm:iowr(_get_word32(cstr, 3), _get_word32(cstr, 7)) 315 | avm:close() 316 | end 317 | 318 | if res then ca.progress("", 100) end 319 | 320 | return res,mes 321 | end 322 | 323 | -- IORDメソッド実行 324 | local _do_iord = function(cstr) 325 | cr.dbgprint("> iord") 326 | 327 | ca.progress = _update 328 | ca.progress("", 0) 329 | 330 | local res = nil 331 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 332 | if avm then 333 | res,mes = avm:iord(_get_word32(cstr, 3)) 334 | avm:close() 335 | end 336 | 337 | if res then 338 | ca.progress("", 100) 339 | cr.dbgprint(sform("> data : 0x%08x", res)) 340 | end 341 | 342 | return res,mes 343 | end 344 | 345 | -- MEMWRメソッド実行 346 | local _do_memwr = function(cstr) 347 | cr.dbgprint("> memwr") 348 | 349 | ca.progress = _update 350 | ca.progress("", 0) 351 | 352 | local res = nil 353 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 354 | if avm then 355 | res,mes = avm:memwr(_get_word32(cstr, 3), cstr:sub(7, -1)) 356 | avm:close() 357 | end 358 | 359 | if res then ca.progress("", 100) end 360 | 361 | return res,mes 362 | end 363 | 364 | -- MEMRDメソッド実行 365 | local _do_memrd = function(cstr) 366 | cr.dbgprint("> memrd") 367 | 368 | ca.progress = _update 369 | ca.progress("", 0) 370 | 371 | local res = nil 372 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 373 | if avm then 374 | res,mes = avm:memrd(_get_word32(cstr, 3), _get_word16(cstr, 7)) 375 | avm:close() 376 | end 377 | 378 | if res then 379 | ca.progress("", 100) 380 | -- 381 | local s = "> data :" 382 | for i=1,#res do s = s .. sform(" %02x", res:byte(i)) end 383 | cr.dbgprint(s.." ("..#res.."bytes)") 384 | --]] 385 | res = cr.b64enc(res) 386 | end 387 | 388 | return res,mes 389 | end 390 | 391 | 392 | -- BLOADメソッド実行 393 | local _do_bload = function(cstr) 394 | cr.dbgprint("> bload") 395 | 396 | ca.progress = _update 397 | 398 | local res = nil 399 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 400 | if avm then 401 | res,mes = avm:bload(_getpath(cstr:sub(7, -1)), _get_word32(cstr, 3)) 402 | avm:close() 403 | end 404 | 405 | return res,mes 406 | end 407 | 408 | -- BSAVEメソッド実行 409 | local _do_bsave = function(cstr) 410 | cr.dbgprint("> bsave") 411 | 412 | ca.progress = _update 413 | 414 | local res = nil 415 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 416 | if avm then 417 | res,mes = avm:bsave(_getpath(cstr:sub(11, -1)), _get_word32(cstr, 7), _get_word32(cstr, 3)) 418 | avm:close() 419 | end 420 | 421 | return res,mes 422 | end 423 | 424 | -- LOADメソッド実行 425 | local _do_load = function(cstr) 426 | cr.dbgprint("> load") 427 | 428 | ca.progress = _update 429 | 430 | local res = nil 431 | local avm,mes = ca.open{devid = cstr:byte(2, 2)} 432 | if avm then 433 | res,mes = avm:load(_getpath(cstr:sub(7, -1)), _get_word32(cstr, 3)) 434 | avm:close() 435 | end 436 | 437 | return res,mes 438 | end 439 | 440 | 441 | ------------------------------------------------------------------------------------ 442 | -- Canarium RPC command parser 443 | ------------------------------------------------------------------------------------ 444 | 445 | -- メソッドテーブルの設定 446 | local method = { 447 | [0x01] = {func = _do_check, name = "CHECK"}, 448 | [0x02] = {func = _do_status, name = "STAT"}, 449 | [0x08] = {func = _do_config, name = "CONF"}, 450 | [0x09] = {func = _do_fconfig, name = "FCONF"}, 451 | [0x10] = {func = _do_iowr, name = "IOWR"}, 452 | [0x11] = {func = _do_iord, name = "IORD"}, 453 | [0x18] = {func = _do_memwr, name = "MEMWR"}, 454 | [0x19] = {func = _do_memrd, name = "MEMRD"}, 455 | [0x20] = {func = _do_bload, name = "BLOAD"}, 456 | [0x21] = {func = _do_bsave, name = "BSAVE"}, 457 | [0x22] = {func = _do_load, name = "LOAD"} 458 | } 459 | local _no_method = function() return nil,"Method not found",-32601 end 460 | setmetatable(method, {__index = function() return {func = _no_method, name = ""} end}) 461 | 462 | -- ユーザーメソッドの設定・削除 463 | function cr.setmethod(name, cmd, func) 464 | local res = false 465 | local key 466 | for k,v in pairs(method) do 467 | if name == v.name then key = k end 468 | end 469 | 470 | if type(cmd) == "number" and (cmd >= 0x00 and cmd <= 0x7f) and type(func) == "function" then 471 | if key then method[key] = nil end 472 | method[cmd] = {func = func, name = name} 473 | res = true 474 | elseif type(cmd) == "nil" and key then 475 | method[key] = nil 476 | res = true 477 | end 478 | 479 | return res 480 | end 481 | 482 | 483 | -- 進捗表示のアップデート 484 | function cr.update(...) 485 | if not prog_func then return end 486 | 487 | local s = "" 488 | for i,v in ipairs({...}) do 489 | if i == 1 then 490 | s = s .. sform("%d", v) 491 | else 492 | s = s .. sform(",%d", v) 493 | end 494 | end 495 | s = prog_txt .. s .. "]}\x00" 496 | 497 | shdmem("write", smem_begin, #s, s) 498 | if #s > smem_length then smem_length = #s end 499 | --[[ 500 | local str = shdmem("read", smem_begin, smem_length) 501 | cr.dbgprint("> shdmem : "..str) 502 | --]] 503 | end 504 | 505 | 506 | -- カレントパスの設定 507 | function cr.setpath(path, ena_abs) 508 | if type(path) == "string" then 509 | if path:sub(1, 1) ~= "/" then 510 | if path:sub(1, 2) == "./" then path = path:sub(3, -1) end 511 | path = cur_path .. path 512 | end 513 | if path:sub(-1) ~= "/" then path = path .. "/" end 514 | cur_path = path 515 | 516 | ena_abspath = (type(ena_abs) == "boolean" and ena_abs) and true or false; 517 | end 518 | 519 | return cur_path 520 | end 521 | 522 | 523 | -- メソッドのパース 524 | function cr.parse(query) 525 | local _do_method = function(q) 526 | if not q then return _do_version() end 527 | 528 | local rp = cr.b64dec(q) 529 | 530 | -- query decode error 531 | if not rp then return nil,nil,"Parse error",-32700 end 532 | -- query packet error 533 | if #rp < 5 then return nil,nil,"Parse error",-32700 end 534 | 535 | local id = _get_word16(rp, 1) 536 | local dlen = rp:byte(3, 3) 537 | local ckey = rp:byte(4, 4) 538 | 539 | -- query data error 540 | if not(#rp == dlen+4 and ckey == _checkcode(rp:sub(5, -1))) then return nil,id,"Parse error",-32700 end 541 | 542 | --[[ 543 | local s = "> decode :" 544 | for i=1,#rp do s = s .. sform(" %02x", rp:byte(i)) end 545 | cr.dbgprint(s) 546 | --]] 547 | 548 | -- メソッド実行 549 | local key = rand(65535) 550 | local cmd = rp:byte(5, 5) 551 | local cstr = rp:sub(5, -1) 552 | 553 | _setprog(key, id, cmd) 554 | 555 | local res,mes,ecode = method[cmd].func(cstr) 556 | ecode = ecnode or -32000 557 | 558 | _setprog() 559 | 560 | if not res then return res,id,mes,ecode end 561 | return res,id 562 | end 563 | 564 | -- クエリのパースと実行 565 | local res,id,mes,ecode = _do_method(query) 566 | 567 | local t = {jsonrpc="2.0", id=id} 568 | if res then t.result = res else t.error = {code=ecode, message=mes} end 569 | 570 | return jsonenc(t) 571 | end 572 | 573 | 574 | ------------------------------------------------------------------------------------ 575 | -- テスト用ファンクション 576 | ------------------------------------------------------------------------------------ 577 | 578 | -- クエリを生成 579 | function cr.makequery(t) 580 | local _setavm = function(cmd, devid, addr) 581 | local s = schar(cmd, devid) 582 | for i=24,0,-8 do s = s .. schar(extract(addr, i, 8)) end 583 | return s 584 | end 585 | 586 | local pstr = "" 587 | local dev = t.devid or 0x55 588 | 589 | local cmd,name 590 | for k,v in pairs(method) do 591 | if v.name == t.cmd then 592 | name = v.name 593 | cmd = k 594 | end 595 | end 596 | if not name then return nil,"invalid command" end 597 | 598 | if name == "VER" or name == "CHECK" or name == "STAT"then 599 | pstr = schar(cmd) 600 | 601 | elseif name == "CONF" or name == "FCONF" then 602 | pstr = schar(cmd) .. t.file 603 | 604 | elseif name == "IOWR" then 605 | if type(t.data) == "number" then 606 | pstr = _setavm(cmd, dev, t.addr) .. 607 | schar(extract(t.data, 24, 8), extract(t.data, 16, 8), extract(t.data, 8, 8), extract(t.data, 0, 8)) 608 | else 609 | return nil,"invalid parameter" 610 | end 611 | 612 | elseif name == "IORD" then 613 | pstr = _setavm(cmd, dev, t.addr) 614 | 615 | elseif name == "MEMWR" then 616 | if type(t.data) == "string" then 617 | pstr = _setavm(cmd, dev, t.addr) .. t.data 618 | else 619 | return nil,"invalid parameter" 620 | end 621 | 622 | elseif name == "MEMRD" then 623 | pstr = _setavm(cmd, dev, t.addr) .. 624 | schar(extract(t.size, 8, 8), extract(t.size, 0, 8)) 625 | 626 | elseif name == "BLOAD" then 627 | pstr = _setavm(cmd, dev, t.addr) .. t.file 628 | 629 | elseif name == "BSAVE" then 630 | pstr = _setavm(cmd, dev, t.addr) .. 631 | schar(extract(t.size, 24, 8), extract(t.size, 16, 8), extract(t.size, 8, 8), extract(t.size, 0, 8)) .. 632 | t.file 633 | 634 | elseif name == "LOAD" then 635 | local addr = (type(t.addr) == "number") and t.addr or 0 636 | pstr = _setavm(cmd, dev, addr) .. t.file 637 | 638 | else 639 | local param = (type(t.param) == "string") and t.param or "" 640 | pstr = schar(cmd) .. param 641 | 642 | end 643 | 644 | if #pstr > 70 then return nil,"payload data too long" end 645 | 646 | local res = schar(extract(t.id, 8, 8), extract(t.id, 0, 8), #pstr, _checkcode(pstr)) .. pstr 647 | --[[ 648 | local s = "packet :" 649 | for i=1,#res do s = s .. sform(" %02x", res:byte(i)) end 650 | cr.dbgprint(s) 651 | --]] 652 | return cr.b64enc(res) 653 | end 654 | 655 | 656 | return cr 657 | -------------------------------------------------------------------------------- /src/canarium_air.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ------------------------------------------------------------------------------------ 3 | -- Canarium Air -- 4 | -- PERIDOT-AIR configuration & Avalon-MM access library -- 5 | ------------------------------------------------------------------------------------ 6 | @author Shun OSAFUNE 7 | @copyright The MIT License (MIT); (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 8 | 9 | *Version release 10 | v0.3.0806 s.osafune@j7system.jp (W4.00.03+) 11 | 12 | *Requirement FlashAir firmware version 13 | W4.00.03+ 14 | 15 | *FlashAir I/O connection 16 | CMD <---> DATA0(SCL) 17 | DAT0 <-+-> DCLK 18 | +--> USER I/O(SDA) 19 | DAT1 ----> nCONFIG 20 | DAT2 <---- nSTATUS 21 | DAT3 <---- CONF_DONE 22 | 23 | ------------------------------------------------------------------------------------ 24 | -- The MIT License (MIT) 25 | -- Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 26 | -- 27 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 28 | -- this software and associated documentation files (the "Software"), to deal in 29 | -- the Software without restriction, including without limitation the rights to 30 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 31 | -- of the Software, and to permit persons to whom the Software is furnished to do 32 | -- so, subject to the following conditions: 33 | -- 34 | -- The above copyright notice and this permission notice shall be included in all 35 | -- copies or substantial portions of the Software. 36 | -- 37 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 39 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 40 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 42 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 43 | -- SOFTWARE. 44 | ------------------------------------------------------------------------------------ 45 | --]] 46 | 47 | -- 外部モジュール 48 | local pio = require "fa".pio 49 | local spi = require "fa".spi 50 | local i2c = require "fa".i2c 51 | local remove = require "fa".remove 52 | local open = require "io".open 53 | local bor = require "bit32".bor 54 | local lshift = require "bit32".lshift 55 | local extract = require "bit32".extract 56 | local btest = require "bit32".btest 57 | local schar = require "string".char 58 | local concat = require "table".concat 59 | 60 | -- モジュールオブジェクト 61 | ca = {} 62 | 63 | -- バージョンとコピーライト 64 | function ca.version() return "0.3.0806" end 65 | function ca.copyright() return "(c)2017-2019 J-7SYSTEM WORKS LIMITED." end 66 | 67 | -- 進捗表示(必要な場合は外部で定義する) 68 | function ca.progress(funcname, ...) end 69 | 70 | -- fa.i2cの正常レスポンス 71 | ---- 72 | local _r_OK = "OK" 73 | --]] 74 | 75 | ------------------------------------------------------------------------------------ 76 | -- AvalonMMバスアクセス 77 | ------------------------------------------------------------------------------------ 78 | -- avmオブジェクトの内容 79 | -- avm.devid : デバイスID 80 | -- avm.i2cfreq : I2Cの通信速度 81 | -- avm.rdsplit : memrdのバースト長 82 | -- avm.wrsplit : memwrのバースト長 83 | -- avm.addrbst : アドレスバイト開始位置(24,16,8,0) 84 | -- avm.close() : クローズメソッド 85 | -- avm.iord() : I/Oリードメソッド 86 | -- avm.iowr() : I/Oライトメソッド 87 | -- avm.memrd() : メモリリードメソッド 88 | -- avm.memwr() : メモリライトメソッド 89 | 90 | -- デバイスリソーステーブル 91 | local _devindex = {} 92 | 93 | -- I2Cバスオープン:共通 94 | local _busopen = function(avm) 95 | return (_devindex[avm.devid] and i2c{mode="init", freq=avm.i2cfreq} == _r_OK) 96 | end 97 | 98 | -- I2Cデバイスオープン:共通 99 | local _devopen = function(avm, addr) 100 | local res = i2c{mode="start", address=avm.devid, direction="write"} 101 | if res ~= _r_OK then 102 | i2c{mode="stop"} 103 | return false,"device start error / "..res 104 | end 105 | 106 | res = true 107 | ---- 108 | local s = "" 109 | for i=avm.addrbst,0,-8 do s = s .. schar(extract(addr, i, 8)) end 110 | if i2c{mode="write", data=s} ~= _r_OK then res = false end 111 | --]] 112 | if not res then 113 | i2c{mode="stop"} 114 | return false,"address write error" 115 | end 116 | 117 | return true 118 | end 119 | 120 | -- AVMデバイスクローズメソッド 121 | local _avm_close = function(self) 122 | _devindex[self.devid] = nil 123 | return true 124 | end 125 | 126 | -- AvalonMM I/Oリードメソッド 127 | local _avm_iord = function(self, addr) 128 | if not _busopen(self) then return nil,"device is not open" end 129 | if type(addr) ~= "number" then return nil,"parameter error" end 130 | if btest(addr, 0x3) then return nil,"invalid addressing" end 131 | 132 | local res,mes = _devopen(self, addr) 133 | if not res then return nil,mes end 134 | 135 | if i2c{mode="restart", address=self.devid, direction="read"} ~= _r_OK then 136 | i2c{mode="stop"} 137 | return nil,"device restart error" 138 | end 139 | 140 | local res,b0,b1,b2,b3 = i2c{mode="read", bytes=4, type="binary"} 141 | i2c{mode="stop"} 142 | if res ~= _r_OK then return nil,"I/O read error" end 143 | 144 | return bor(lshift(b3, 24), lshift(b2, 16), lshift(b1, 8), b0) 145 | end 146 | 147 | -- AvalonMM I/Oライトメソッド 148 | local _avm_iowr = function(self, addr, wdat) 149 | if not _busopen(self) then return nil,"device is not open" end 150 | if type(addr) ~= "number" or type(wdat) ~= "number" then return nil,"parameter error" end 151 | if btest(addr, 0x3) then return nil,"invalid addressing" end 152 | 153 | local res,mes = _devopen(self, addr) 154 | if not res then return nil,mes end 155 | ---- 156 | local t = {mode="write", data=schar(extract(wdat,0,8), extract(wdat,8,8), extract(wdat,16,8), extract(wdat,24,8))} 157 | if i2c(t) ~= _r_OK then res = false end 158 | --]] 159 | i2c{mode="stop"} 160 | if not res then return nil,"I/O write error" end 161 | 162 | return true 163 | end 164 | 165 | -- AvalonMM メモリリードメソッド 166 | local _avm_memrd = function(self, addr, size) 167 | if not _busopen(self) then return nil,"device is not open" end 168 | if type(addr) ~= "number" or type(size) ~= "number" then return nil,"parameter error" end 169 | if size < 1 then return "" end 170 | 171 | local t_stop = {mode="stop"} 172 | ---- 173 | local t_read = {mode="read", bytes=0, type="string"} 174 | --]] 175 | 176 | local res,mes = _devopen(self, addr) 177 | if not res then return nil,mes end 178 | i2c(t_stop) 179 | 180 | local rstr = "" 181 | 182 | while size > 0 do 183 | if i2c{mode="start", address=self.devid, direction="read"} ~= _r_OK then 184 | i2c(t_stop) 185 | res,mes = nil,"device current start error" 186 | break 187 | end 188 | 189 | local len = size 190 | if len > self.rdsplit then len = self.rdsplit end 191 | t_read.bytes = len 192 | ---- 193 | mes,res = i2c(t_read) 194 | i2c(t_stop) 195 | if mes ~= _r_OK then 196 | res,mes = nil,"memory read error" 197 | break 198 | end 199 | --]] 200 | 201 | --[[ 202 | local str = string.format("READ addr %08x :", addr) 203 | for i=1,#res do str = str .. string.format(" %02x", res:byte(i)) end 204 | print(str.." ("..#res.."bytes)") 205 | addr = addr + len 206 | --]] 207 | rstr = rstr .. res 208 | size = size - len 209 | end 210 | 211 | if not res then return nil,mes end 212 | 213 | return rstr 214 | end 215 | 216 | -- AvalonMM メモリライトメソッド 217 | local _avm_memwr = function(self, addr, wstr) 218 | if not _busopen(self) then return nil,"device is not open" end 219 | if type(addr) ~= "number" or type(wstr) ~= "string" then return nil,"parameter error" end 220 | if #wstr < 1 then return true end 221 | 222 | local t_write = {mode="write", data=0} 223 | local t_stop = {mode="stop"} 224 | local _strwrite = function(a, s) 225 | --[[ 226 | local str = string.format("WRITE addr %08x :", a) 227 | for i=1,#s do str = str .. string.format(" %02x", s:byte(i)) end 228 | print(str.." ("..#s.."bytes)") 229 | --]] 230 | local r,m = _devopen(self, a) 231 | if not r then return nil,m end 232 | 233 | ---- 234 | t_write.data = s 235 | if i2c(t_write) ~= _r_OK then r = nil end 236 | --]] 237 | i2c(t_stop) 238 | 239 | if not r then return nil,"memory write error" end 240 | 241 | return true 242 | end 243 | 244 | local size = #wstr 245 | local n = 1 246 | local res,mes = true,nil 247 | 248 | -- 4バイト境界に揃っていない先頭部分の処理 249 | local aa = addr % 4 250 | if aa ~= 0 then 251 | for i=aa,3 do 252 | res,mes = _strwrite(addr, wstr:sub(n, n)) 253 | if not res then break end 254 | 255 | n = n + 1 256 | addr = addr + 1 257 | size = size - 1 258 | end 259 | 260 | if not res then return nil,mes end 261 | end 262 | 263 | -- 4バイト境界転送 264 | local es = size % 4 265 | if es ~= 0 then 266 | size = size - es 267 | end 268 | while size > 0 do 269 | local len = size 270 | if len > self.wrsplit then len = self.wrsplit end 271 | 272 | res,mes = _strwrite(addr, wstr:sub(n, n+len-1)) 273 | if not res then break end 274 | 275 | n = n + len 276 | addr = addr + len 277 | size = size - len 278 | end 279 | 280 | -- 4バイト境界に揃っていない後端部分の処理 281 | if res and es ~= 0 then 282 | for i=1,es do 283 | res,mes = _strwrite(addr, wstr:sub(n, n)) 284 | if not res then break end 285 | 286 | n = n + 1 287 | addr = addr + 1 288 | end 289 | end 290 | 291 | if not res then return nil,mes end 292 | return true 293 | end 294 | 295 | 296 | -- AvalonMMデバイスのオープンとオブジェクトインスタンス 297 | function ca.open(t) 298 | if pio(0x00, 0x00) == 0 then return nil,"GPIO is not ready" end 299 | 300 | local _,d = pio(0x00, 0x00) 301 | if not btest(d, 0x10) then return nil,"device is not configured" end 302 | 303 | -- ローカルパラメータ設定 304 | local dev = 0x55 305 | local freq = 400 306 | local adb = 4 307 | local rds = 16 308 | local wrs = 256 309 | if t and type(t) == "table" then 310 | -- t.devid : I2CデバイスIDの指定(デフォルト0x55) 311 | if type(t.devid)=="number" then 312 | if t.devid >= 0x00 and t.devid <= 0x7f then 313 | dev = t.devid 314 | else 315 | return nil,"invalid device ID" 316 | end 317 | end 318 | 319 | -- t.i2cfreq : I2Cの通信速度(100または400、デフォルト400) 320 | if type(t.i2cfreq) == "number" and (t.i2cfreq == 100 or t.i2cfreq == 400) then 321 | freq = t.i2cfreq 322 | end 323 | 324 | -- t.addrbytes : デバイスのアドレスバイト数(1,2,3,4のいずれか、デフォルト4) 325 | if type(t.addrbytes) == "number" and t.addrbytes >= 1 and t.addrbytes <= 4 then 326 | adb = t.addrbytes 327 | end 328 | 329 | -- t.rdsplit : リードデータバースト長(4以上で4の倍数を指定、デフォルト16) 330 | if type(t.rdsplit) == "number" and t.rdsplit >= 4 and t.rdsplit % 4 == 0 then 331 | rds = t.rdsplit 332 | end 333 | 334 | -- t.wrsplit : ライトデータバースト長(4以上で4の倍数を指定、デフォルト256) 335 | if type(t.wrsplit) == "number" and t.wrsplit >= 4 and t.wrsplit % 4 == 0 then 336 | wrs = t.wrsplit 337 | end 338 | end 339 | 340 | if _devindex[dev] then return nil,"device is used by other" end 341 | _devindex[dev] = true 342 | 343 | return { 344 | devid = dev, 345 | i2cfreq = freq, 346 | addrbst = (adb - 1) * 8, 347 | rdsplit = rds, 348 | wrsplit = wrs, 349 | close = _avm_close, 350 | iord = _avm_iord, 351 | iowr = _avm_iowr, 352 | memrd = _avm_memrd, 353 | memwr = _avm_memwr, 354 | bload = ca.binload, 355 | bsave = ca.binsave, 356 | load = ca.hexload 357 | } 358 | end 359 | 360 | -- AvalonMMイニシャライズ 361 | function ca.avminit() 362 | pio(0x00, 0x00) 363 | _devindex = {} 364 | end 365 | 366 | 367 | ------------------------------------------------------------------------------------ 368 | -- FPGAコンフィグレーション 369 | ------------------------------------------------------------------------------------ 370 | 371 | function ca.config(t) 372 | if pio(0x00, 0x00) == 0 then return false,"GPIO is not ready" end 373 | 374 | -- 引数無しの場合はコンフィグレーション状態を返す 375 | if not t then 376 | local _,d = pio(0x00, 0x00) 377 | return btest(d, 0x10) 378 | end 379 | 380 | -- ローカルパラメータ設定 381 | if type(t) ~= "table" then return false,"parameter error" end 382 | 383 | -- t.file : コンフィグレーションするRBFファイル名(必須) 384 | local fname = t.file 385 | 386 | -- t.cache : キャッシュファイルの使用(trueか非ゼロ、デフォルトtrue) 387 | local usecache = true 388 | if type(t.cache) ~= "nil" and (not t.cache or t.cache == 0) then usecache = false end 389 | 390 | -- t.timeout : タイムアウト時間(ms単位で10以上を指定、デフォルト10) 391 | local timeout = 10 392 | if type(t.timeout) == "number" and t.timeout > 10 then timeout = t.timeout end 393 | 394 | -- t.retry : リトライ回数(0以上を指定、デフォルト3) 395 | local trynumber = 3 396 | if type(t.retry) == "number" and t.retry >= 0 then trynumber = t.retry + 1 end 397 | 398 | 399 | -- RBFキャッシュファイル作成 400 | local _makecache = function(fn) 401 | local f = open(fn, "rb") 402 | if not f then return false,"rbf file open failed" end 403 | 404 | local fo = open(fn..".cache", "wb") 405 | if not fo then 406 | f:close() 407 | return false,"cache file open failed" 408 | end 409 | 410 | local rt = {} 411 | for i=0,255 do 412 | rt[i] = bor( 413 | (btest(i, 0x01) and 0x80 or 0x00), 414 | (btest(i, 0x02) and 0x40 or 0x00), 415 | (btest(i, 0x04) and 0x20 or 0x00), 416 | (btest(i, 0x08) and 0x10 or 0x00), 417 | (btest(i, 0x10) and 0x08 or 0x00), 418 | (btest(i, 0x20) and 0x04 or 0x00), 419 | (btest(i, 0x40) and 0x02 or 0x00), 420 | (btest(i, 0x80) and 0x01 or 0x00)) 421 | end 422 | 423 | local fs = f:seek("end") 424 | f:seek("set") 425 | local sz = 100 / ((fs < 1) and 1 or fs) 426 | 427 | local p = 0 428 | while true do 429 | local ln = f:read(256) 430 | if not ln then break end 431 | 432 | local rd = {} 433 | for i=1,#ln do rd[i] = schar(rt[ln:byte(i)]) end 434 | fo:write(concat(rd)) 435 | 436 | if p >= 15 then 437 | ca.progress("config", f:seek()*sz, 0) 438 | p = 0 439 | else 440 | p = p + 1 441 | end 442 | end 443 | f:close() 444 | fo:close() 445 | 446 | local mt = lfs.attributes(fn, "modification") 447 | lfs.touch(fn..".cache", mt, mt) 448 | 449 | return true 450 | end 451 | 452 | -- コンフィグレーション実行 453 | local _doconfig = function(f, to) 454 | local fs = f:seek("end") 455 | f:seek("set") 456 | local sz = 100 / ((fs < 1) and 1 or fs) 457 | 458 | local c = false 459 | for n=1,to do 460 | local _,d = pio(0x07, 0x00) 461 | sleep(1) 462 | if not btest(d, 0x18) then c = true; break end 463 | end 464 | if not c then return false end 465 | 466 | c = false 467 | for n=1,to do 468 | local _,d = pio(0x07, 0x04) 469 | sleep(1) 470 | if btest(d, 0x08) then c = true; break end 471 | end 472 | if not c then return false end 473 | 474 | local p = 0 475 | while true do 476 | local ln = f:read(256) 477 | if not ln then break end 478 | ---- 479 | spi("write", ln) 480 | --]] 481 | 482 | if p >= 15 then 483 | ca.progress("config", 100, f:seek()*sz) 484 | p = 0 485 | else 486 | p = p + 1 487 | end 488 | end 489 | 490 | local _,d = pio(0x07, 0x07) 491 | return btest(d, 0x10) 492 | end 493 | 494 | 495 | -- コンフィグメイン処理 496 | ca.progress("config", 0, 0) 497 | ca.avminit() 498 | 499 | if lfs.attributes(fname, "modification") ~= lfs.attributes(fname..".cache", "modification") then 500 | usecache = false 501 | end 502 | 503 | local fconf = open(fname..".cache", "rb") 504 | if not usecache or not fconf then 505 | local res,mes = _makecache(fname) 506 | if not res then return false,mes end 507 | 508 | fconf = open(fname..".cache", "rb") 509 | end 510 | 511 | spi("mode", 0) 512 | spi("bit", 8) 513 | spi("cs", 1) 514 | spi("init", 1) 515 | pio(0x07, 0x04) 516 | 517 | local res = false 518 | for n=1,trynumber do 519 | if _doconfig(fconf, timeout) then res = true; break end 520 | pio(0x07, 0x04) 521 | end 522 | pio(0x00, 0x1f) 523 | pio(0x00, 0x00) 524 | 525 | fconf:close() 526 | if not res then 527 | remove(fname..".cache") 528 | return false,"configuration failed" 529 | end 530 | 531 | ca.progress("config", 100, 100) 532 | return true 533 | end 534 | 535 | 536 | ------------------------------------------------------------------------------------ 537 | -- AvalonMMメモリユーティリティ 538 | ------------------------------------------------------------------------------------ 539 | 540 | -- バイナリデータロード : ファイルを指定のアドレスに読み込む 541 | function ca.binload(avm, fname, addr) 542 | if type(addr) ~= "number" then addr = 0 end 543 | 544 | ca.progress("binload", 0) 545 | 546 | local fbin = open(fname, "rb") 547 | if not fbin then return false,"file open failed" end 548 | local fs = fbin:seek("end") 549 | fbin:seek("set") 550 | 551 | local sz = 100 / ((fs < 1) and 1 or fs) 552 | local res = true 553 | local mes 554 | local p = 0 555 | 556 | local aa = addr % 4 557 | if aa ~= 0 then 558 | local len = 4 - aa 559 | local data = fbin:read(len) 560 | if data then 561 | res,mes = avm:memwr(addr, data) 562 | if res then addr = addr + #data end 563 | end 564 | end 565 | if res then 566 | while true do 567 | local data = fbin:read(256) 568 | if not data then break end 569 | 570 | res,mes = avm:memwr(addr, data) 571 | if not res then break end 572 | addr = addr + #data 573 | 574 | if p >= 15 then 575 | ca.progress("binload", fbin:seek()*sz) 576 | p = 0 577 | else 578 | p = p + 1 579 | end 580 | end 581 | end 582 | fbin:close() 583 | 584 | if not res then return false,mes end 585 | 586 | ca.progress("binload", 100) 587 | return true 588 | end 589 | 590 | 591 | -- バイナリデータセーブ : 指定のメモリエリアをファイルに書き出す 592 | function ca.binsave(avm, fname, size, addr) 593 | if not(type(size) == "number" and size > 0) then return false,"parameter error" end 594 | if type(addr) ~= "number" then addr = 0 end 595 | 596 | ca.progress("binsave", 0) 597 | 598 | local fbin = open(fname, "wb") 599 | if not fbin then return false,"file open failed" end 600 | 601 | local sz = 100 / size 602 | local res,mes 603 | local p = 0 604 | 605 | while size > 0 do 606 | local len = size 607 | if len > 256 then len = 256 end 608 | 609 | res,mes = avm:memrd(addr, len) 610 | if not res then break end 611 | fbin:write(res) 612 | 613 | size = size - len 614 | addr = addr + len 615 | 616 | if p >= 15 then 617 | ca.progress("binsave", 100-size*sz) 618 | p = 0 619 | else 620 | p = p + 1 621 | end 622 | end 623 | fbin:close() 624 | 625 | if not res then return false,mes end 626 | 627 | ca.progress("binsave", 100) 628 | return true 629 | end 630 | 631 | 632 | -- ROMデータロード : IntelHEXまたはモトローラSファイルをメモリに読み込む 633 | function ca.hexload(avm, fname, offset) 634 | if type(offset) ~= "number" then offset = 0 end 635 | 636 | ca.progress("hexload", 0) 637 | 638 | -- インテルHEXの一行をデコード 639 | local ext_addr = 0 640 | local _dec_ihex = function(ln) 641 | local b = ln:sub(2, 3) 642 | if not b then return false,"byte count error" end 643 | 644 | local count = tonumber(b, 16) 645 | if not count then return false,"invalid charactors" end 646 | 647 | if #ln < count*2+11 then return false,"data record shortage" end 648 | 649 | local addr = tonumber(ln:sub(4, 7), 16) 650 | local rtype = tonumber(ln:sub(8, 9), 16) 651 | if not(addr and rtype) then return false,"invalid charactors" end 652 | 653 | local sum = count + extract(addr, 8, 8) + extract(addr, 0, 8) + rtype 654 | local data = "" 655 | 656 | if rtype == 2 or rtype == 4 then 657 | local ea = tonumber(ln:sub(10, 13), 16) 658 | if not ea then return false,"invalid charactors" end 659 | 660 | sum = sum + extract(ea, 8, 8) + extract(ea, 0, 8) 661 | if rtype == 2 then 662 | ext_addr = lshift(ea, 4) -- 拡張セグメントアドレス 663 | else 664 | ext_addr = lshift(ea, 16) -- 拡張リニアアドレス 665 | end 666 | 667 | elseif rtype == 0 then -- データレコード 668 | local n = 10 669 | local c = true 670 | for i=1,count do 671 | local b = tonumber(ln:sub(n, n+1), 16) 672 | if not b then c = false; break end 673 | data = data .. schar(b) 674 | sum = sum + b 675 | n = n + 2 676 | end 677 | if not c then return false,"invalid charactors" end 678 | 679 | else 680 | return true -- それ以外のレコードはスキップ 681 | end 682 | 683 | local b = tonumber(ln:sub(count*2+10, count*2+11), 16) 684 | if not b then return false,"invalid charactors" end 685 | 686 | if btest(sum+b, 0xff) then return false,"checksum error" end 687 | 688 | if rtype ~= 0 then return true end 689 | return avm:memwr(offset + ext_addr + addr, data) 690 | end 691 | 692 | -- モトローラSフォーマットの一行をデコード 693 | local _dec_srec = function(ln) 694 | local b = ln:sub(2, 2) 695 | local c = ln:sub(3, 4) 696 | if not(b and c) then return false,"record type error" end 697 | 698 | local rtype = tonumber(b) 699 | local count = tonumber(c, 16) 700 | if not(rtype and count) then return false,"invalid charactors" end 701 | 702 | if #ln < count*2+4 then return false,"data record shortage" end 703 | 704 | local n,m 705 | local addr = 0 706 | local sum = count 707 | local data = "" 708 | 709 | if rtype == 1 then 710 | addr = tonumber(ln:sub(5, 8), 16) -- S1レコード 711 | n = 9 712 | m = count - 3 713 | elseif rtype == 2 then 714 | addr = tonumber(ln:sub(5, 10), 16) -- S2レコード 715 | n = 11 716 | m = count - 4 717 | elseif rtype == 3 then 718 | addr = tonumber(ln:sub(5, 12), 16) -- S3レコード 719 | n = 13 720 | m = count - 5 721 | else 722 | return true -- それ以外のレコードはスキップ 723 | end 724 | if not addr then return false,"invalid charactors" end 725 | 726 | for i=0,24,8 do sum = sum + extract(addr, i, 8) end 727 | 728 | local c = true 729 | for i=1,m do 730 | local b = tonumber(ln:sub(n, n+1), 16) 731 | if not b then c = false; break end 732 | data = data .. schar(b) 733 | sum = sum + b 734 | n = n + 2 735 | end 736 | local b = tonumber(ln:sub(n, n+1), 16) 737 | if not b then c = false end 738 | if not c then return false,"invalid charactors" end 739 | 740 | if btest(sum+b+1, 0xff) then return false,"checksum error" end 741 | 742 | return avm:memwr(offset + addr, data) 743 | end 744 | 745 | local fhex = open(fname, "r") 746 | if not fhex then return false,"file open failed" end 747 | local fs = fhex:seek("end") 748 | fhex:seek("set") 749 | 750 | local sz = 100 / ((fs < 1) and 1 or fs) 751 | local res = true 752 | local mes 753 | local lnumber = 1 754 | 755 | while true do 756 | local ln = fhex:read() 757 | if not ln then break end 758 | 759 | local c = ln:sub(1, 1) 760 | if c == ":" then 761 | res,mes = _dec_ihex(ln) 762 | elseif c == "S" then 763 | res,mes = _dec_srec(ln) 764 | end 765 | if not res then break end 766 | 767 | lnumber = lnumber + 1 768 | if lnumber % 16 == 15 then ca.progress("hexload", fhex:seek()*sz) end 769 | end 770 | fhex:close() 771 | 772 | if not res then return false,mes.." (line:"..lnumber..")" end 773 | 774 | ca.progress("hexload", 100) 775 | return true 776 | end 777 | 778 | 779 | return ca 780 | -------------------------------------------------------------------------------- /sample_app/air_melodychime/lib/canarium_air.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ------------------------------------------------------------------------------------ 3 | -- Canarium Air -- 4 | -- PERIDOT-AIR configuration & Avalon-MM access library -- 5 | ------------------------------------------------------------------------------------ 6 | @author Shun OSAFUNE 7 | @copyright The MIT License (MIT); (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 8 | 9 | *Version release 10 | v0.3.0806 s.osafune@j7system.jp (W4.00.03+) 11 | 12 | *Requirement FlashAir firmware version 13 | W4.00.03+ 14 | 15 | *FlashAir I/O connection 16 | CMD <---> DATA0(SCL) 17 | DAT0 <-+-> DCLK 18 | +--> USER I/O(SDA) 19 | DAT1 ----> nCONFIG 20 | DAT2 <---- nSTATUS 21 | DAT3 <---- CONF_DONE 22 | 23 | ------------------------------------------------------------------------------------ 24 | -- The MIT License (MIT) 25 | -- Copyright (c) 2017-2019 J-7SYSTEM WORKS LIMITED. 26 | -- 27 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 28 | -- this software and associated documentation files (the "Software"), to deal in 29 | -- the Software without restriction, including without limitation the rights to 30 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 31 | -- of the Software, and to permit persons to whom the Software is furnished to do 32 | -- so, subject to the following conditions: 33 | -- 34 | -- The above copyright notice and this permission notice shall be included in all 35 | -- copies or substantial portions of the Software. 36 | -- 37 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 39 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 40 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 42 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 43 | -- SOFTWARE. 44 | ------------------------------------------------------------------------------------ 45 | --]] 46 | 47 | -- 外部モジュール 48 | local pio = require "fa".pio 49 | local spi = require "fa".spi 50 | local i2c = require "fa".i2c 51 | local remove = require "fa".remove 52 | local open = require "io".open 53 | local bor = require "bit32".bor 54 | local lshift = require "bit32".lshift 55 | local extract = require "bit32".extract 56 | local btest = require "bit32".btest 57 | local schar = require "string".char 58 | local concat = require "table".concat 59 | 60 | -- モジュールオブジェクト 61 | ca = {} 62 | 63 | -- バージョンとコピーライト 64 | function ca.version() return "0.3.0806" end 65 | function ca.copyright() return "(c)2017-2019 J-7SYSTEM WORKS LIMITED." end 66 | 67 | -- 進捗表示(必要な場合は外部で定義する) 68 | function ca.progress(funcname, ...) end 69 | 70 | -- fa.i2cの正常レスポンス 71 | ---- 72 | local _r_OK = "OK" 73 | --]] 74 | 75 | ------------------------------------------------------------------------------------ 76 | -- AvalonMMバスアクセス 77 | ------------------------------------------------------------------------------------ 78 | -- avmオブジェクトの内容 79 | -- avm.devid : デバイスID 80 | -- avm.i2cfreq : I2Cの通信速度 81 | -- avm.rdsplit : memrdのバースト長 82 | -- avm.wrsplit : memwrのバースト長 83 | -- avm.addrbst : アドレスバイト開始位置(24,16,8,0) 84 | -- avm.close() : クローズメソッド 85 | -- avm.iord() : I/Oリードメソッド 86 | -- avm.iowr() : I/Oライトメソッド 87 | -- avm.memrd() : メモリリードメソッド 88 | -- avm.memwr() : メモリライトメソッド 89 | 90 | -- デバイスリソーステーブル 91 | local _devindex = {} 92 | 93 | -- I2Cバスオープン:共通 94 | local _busopen = function(avm) 95 | return (_devindex[avm.devid] and i2c{mode="init", freq=avm.i2cfreq} == _r_OK) 96 | end 97 | 98 | -- I2Cデバイスオープン:共通 99 | local _devopen = function(avm, addr) 100 | local res = i2c{mode="start", address=avm.devid, direction="write"} 101 | if res ~= _r_OK then 102 | i2c{mode="stop"} 103 | return false,"device start error / "..res 104 | end 105 | 106 | res = true 107 | ---- 108 | local s = "" 109 | for i=avm.addrbst,0,-8 do s = s .. schar(extract(addr, i, 8)) end 110 | if i2c{mode="write", data=s} ~= _r_OK then res = false end 111 | --]] 112 | if not res then 113 | i2c{mode="stop"} 114 | return false,"address write error" 115 | end 116 | 117 | return true 118 | end 119 | 120 | -- AVMデバイスクローズメソッド 121 | local _avm_close = function(self) 122 | _devindex[self.devid] = nil 123 | return true 124 | end 125 | 126 | -- AvalonMM I/Oリードメソッド 127 | local _avm_iord = function(self, addr) 128 | if not _busopen(self) then return nil,"device is not open" end 129 | if type(addr) ~= "number" then return nil,"parameter error" end 130 | if btest(addr, 0x3) then return nil,"invalid addressing" end 131 | 132 | local res,mes = _devopen(self, addr) 133 | if not res then return nil,mes end 134 | 135 | if i2c{mode="restart", address=self.devid, direction="read"} ~= _r_OK then 136 | i2c{mode="stop"} 137 | return nil,"device restart error" 138 | end 139 | 140 | local res,b0,b1,b2,b3 = i2c{mode="read", bytes=4, type="binary"} 141 | i2c{mode="stop"} 142 | if res ~= _r_OK then return nil,"I/O read error" end 143 | 144 | return bor(lshift(b3, 24), lshift(b2, 16), lshift(b1, 8), b0) 145 | end 146 | 147 | -- AvalonMM I/Oライトメソッド 148 | local _avm_iowr = function(self, addr, wdat) 149 | if not _busopen(self) then return nil,"device is not open" end 150 | if type(addr) ~= "number" or type(wdat) ~= "number" then return nil,"parameter error" end 151 | if btest(addr, 0x3) then return nil,"invalid addressing" end 152 | 153 | local res,mes = _devopen(self, addr) 154 | if not res then return nil,mes end 155 | ---- 156 | local t = {mode="write", data=schar(extract(wdat,0,8), extract(wdat,8,8), extract(wdat,16,8), extract(wdat,24,8))} 157 | if i2c(t) ~= _r_OK then res = false end 158 | --]] 159 | i2c{mode="stop"} 160 | if not res then return nil,"I/O write error" end 161 | 162 | return true 163 | end 164 | 165 | -- AvalonMM メモリリードメソッド 166 | local _avm_memrd = function(self, addr, size) 167 | if not _busopen(self) then return nil,"device is not open" end 168 | if type(addr) ~= "number" or type(size) ~= "number" then return nil,"parameter error" end 169 | if size < 1 then return "" end 170 | 171 | local t_stop = {mode="stop"} 172 | ---- 173 | local t_read = {mode="read", bytes=0, type="string"} 174 | --]] 175 | 176 | local res,mes = _devopen(self, addr) 177 | if not res then return nil,mes end 178 | i2c(t_stop) 179 | 180 | local rstr = "" 181 | 182 | while size > 0 do 183 | if i2c{mode="start", address=self.devid, direction="read"} ~= _r_OK then 184 | i2c(t_stop) 185 | res,mes = nil,"device current start error" 186 | break 187 | end 188 | 189 | local len = size 190 | if len > self.rdsplit then len = self.rdsplit end 191 | t_read.bytes = len 192 | ---- 193 | mes,res = i2c(t_read) 194 | i2c(t_stop) 195 | if mes ~= _r_OK then 196 | res,mes = nil,"memory read error" 197 | break 198 | end 199 | --]] 200 | 201 | --[[ 202 | local str = string.format("READ addr %08x :", addr) 203 | for i=1,#res do str = str .. string.format(" %02x", res:byte(i)) end 204 | print(str.." ("..#res.."bytes)") 205 | addr = addr + len 206 | --]] 207 | rstr = rstr .. res 208 | size = size - len 209 | end 210 | 211 | if not res then return nil,mes end 212 | 213 | return rstr 214 | end 215 | 216 | -- AvalonMM メモリライトメソッド 217 | local _avm_memwr = function(self, addr, wstr) 218 | if not _busopen(self) then return nil,"device is not open" end 219 | if type(addr) ~= "number" or type(wstr) ~= "string" then return nil,"parameter error" end 220 | if #wstr < 1 then return true end 221 | 222 | local t_write = {mode="write", data=0} 223 | local t_stop = {mode="stop"} 224 | local _strwrite = function(a, s) 225 | --[[ 226 | local str = string.format("WRITE addr %08x :", a) 227 | for i=1,#s do str = str .. string.format(" %02x", s:byte(i)) end 228 | print(str.." ("..#s.."bytes)") 229 | --]] 230 | local r,m = _devopen(self, a) 231 | if not r then return nil,m end 232 | 233 | ---- 234 | t_write.data = s 235 | if i2c(t_write) ~= _r_OK then r = nil end 236 | --]] 237 | i2c(t_stop) 238 | 239 | if not r then return nil,"memory write error" end 240 | 241 | return true 242 | end 243 | 244 | local size = #wstr 245 | local n = 1 246 | local res,mes = true,nil 247 | 248 | -- 4バイト境界に揃っていない先頭部分の処理 249 | local aa = addr % 4 250 | if aa ~= 0 then 251 | for i=aa,3 do 252 | res,mes = _strwrite(addr, wstr:sub(n, n)) 253 | if not res then break end 254 | 255 | n = n + 1 256 | addr = addr + 1 257 | size = size - 1 258 | end 259 | 260 | if not res then return nil,mes end 261 | end 262 | 263 | -- 4バイト境界転送 264 | local es = size % 4 265 | if es ~= 0 then 266 | size = size - es 267 | end 268 | while size > 0 do 269 | local len = size 270 | if len > self.wrsplit then len = self.wrsplit end 271 | 272 | res,mes = _strwrite(addr, wstr:sub(n, n+len-1)) 273 | if not res then break end 274 | 275 | n = n + len 276 | addr = addr + len 277 | size = size - len 278 | end 279 | 280 | -- 4バイト境界に揃っていない後端部分の処理 281 | if res and es ~= 0 then 282 | for i=1,es do 283 | res,mes = _strwrite(addr, wstr:sub(n, n)) 284 | if not res then break end 285 | 286 | n = n + 1 287 | addr = addr + 1 288 | end 289 | end 290 | 291 | if not res then return nil,mes end 292 | return true 293 | end 294 | 295 | 296 | -- AvalonMMデバイスのオープンとオブジェクトインスタンス 297 | function ca.open(t) 298 | if pio(0x00, 0x00) == 0 then return nil,"GPIO is not ready" end 299 | 300 | local _,d = pio(0x00, 0x00) 301 | if not btest(d, 0x10) then return nil,"device is not configured" end 302 | 303 | -- ローカルパラメータ設定 304 | local dev = 0x55 305 | local freq = 400 306 | local adb = 4 307 | local rds = 16 308 | local wrs = 256 309 | if t and type(t) == "table" then 310 | -- t.devid : I2CデバイスIDの指定(デフォルト0x55) 311 | if type(t.devid)=="number" then 312 | if t.devid >= 0x00 and t.devid <= 0x7f then 313 | dev = t.devid 314 | else 315 | return nil,"invalid device ID" 316 | end 317 | end 318 | 319 | -- t.i2cfreq : I2Cの通信速度(100または400、デフォルト400) 320 | if type(t.i2cfreq) == "number" and (t.i2cfreq == 100 or t.i2cfreq == 400) then 321 | freq = t.i2cfreq 322 | end 323 | 324 | -- t.addrbytes : デバイスのアドレスバイト数(1,2,3,4のいずれか、デフォルト4) 325 | if type(t.addrbytes) == "number" and t.addrbytes >= 1 and t.addrbytes <= 4 then 326 | adb = t.addrbytes 327 | end 328 | 329 | -- t.rdsplit : リードデータバースト長(4以上で4の倍数を指定、デフォルト16) 330 | if type(t.rdsplit) == "number" and t.rdsplit >= 4 and t.rdsplit % 4 == 0 then 331 | rds = t.rdsplit 332 | end 333 | 334 | -- t.wrsplit : ライトデータバースト長(4以上で4の倍数を指定、デフォルト256) 335 | if type(t.wrsplit) == "number" and t.wrsplit >= 4 and t.wrsplit % 4 == 0 then 336 | wrs = t.wrsplit 337 | end 338 | end 339 | 340 | if _devindex[dev] then return nil,"device is used by other" end 341 | _devindex[dev] = true 342 | 343 | return { 344 | devid = dev, 345 | i2cfreq = freq, 346 | addrbst = (adb - 1) * 8, 347 | rdsplit = rds, 348 | wrsplit = wrs, 349 | close = _avm_close, 350 | iord = _avm_iord, 351 | iowr = _avm_iowr, 352 | memrd = _avm_memrd, 353 | memwr = _avm_memwr, 354 | bload = ca.binload, 355 | bsave = ca.binsave, 356 | load = ca.hexload 357 | } 358 | end 359 | 360 | -- AvalonMMイニシャライズ 361 | function ca.avminit() 362 | pio(0x00, 0x00) 363 | _devindex = {} 364 | end 365 | 366 | 367 | ------------------------------------------------------------------------------------ 368 | -- FPGAコンフィグレーション 369 | ------------------------------------------------------------------------------------ 370 | 371 | function ca.config(t) 372 | if pio(0x00, 0x00) == 0 then return false,"GPIO is not ready" end 373 | 374 | -- 引数無しの場合はコンフィグレーション状態を返す 375 | if not t then 376 | local _,d = pio(0x00, 0x00) 377 | return btest(d, 0x10) 378 | end 379 | 380 | -- ローカルパラメータ設定 381 | if type(t) ~= "table" then return false,"parameter error" end 382 | 383 | -- t.file : コンフィグレーションするRBFファイル名(必須) 384 | local fname = t.file 385 | 386 | -- t.cache : キャッシュファイルの使用(trueか非ゼロ、デフォルトtrue) 387 | local usecache = true 388 | if type(t.cache) ~= "nil" and (not t.cache or t.cache == 0) then usecache = false end 389 | 390 | -- t.timeout : タイムアウト時間(ms単位で10以上を指定、デフォルト10) 391 | local timeout = 10 392 | if type(t.timeout) == "number" and t.timeout > 10 then timeout = t.timeout end 393 | 394 | -- t.retry : リトライ回数(0以上を指定、デフォルト3) 395 | local trynumber = 3 396 | if type(t.retry) == "number" and t.retry >= 0 then trynumber = t.retry + 1 end 397 | 398 | 399 | -- RBFキャッシュファイル作成 400 | local _makecache = function(fn) 401 | local f = open(fn, "rb") 402 | if not f then return false,"rbf file open failed" end 403 | 404 | local fo = open(fn..".cache", "wb") 405 | if not fo then 406 | f:close() 407 | return false,"cache file open failed" 408 | end 409 | 410 | local rt = {} 411 | for i=0,255 do 412 | rt[i] = bor( 413 | (btest(i, 0x01) and 0x80 or 0x00), 414 | (btest(i, 0x02) and 0x40 or 0x00), 415 | (btest(i, 0x04) and 0x20 or 0x00), 416 | (btest(i, 0x08) and 0x10 or 0x00), 417 | (btest(i, 0x10) and 0x08 or 0x00), 418 | (btest(i, 0x20) and 0x04 or 0x00), 419 | (btest(i, 0x40) and 0x02 or 0x00), 420 | (btest(i, 0x80) and 0x01 or 0x00)) 421 | end 422 | 423 | local fs = f:seek("end") 424 | f:seek("set") 425 | local sz = 100 / ((fs < 1) and 1 or fs) 426 | 427 | local p = 0 428 | while true do 429 | local ln = f:read(256) 430 | if not ln then break end 431 | 432 | local rd = {} 433 | for i=1,#ln do rd[i] = schar(rt[ln:byte(i)]) end 434 | fo:write(concat(rd)) 435 | 436 | if p >= 15 then 437 | ca.progress("config", f:seek()*sz, 0) 438 | p = 0 439 | else 440 | p = p + 1 441 | end 442 | end 443 | f:close() 444 | fo:close() 445 | 446 | local mt = lfs.attributes(fn, "modification") 447 | lfs.touch(fn..".cache", mt, mt) 448 | 449 | return true 450 | end 451 | 452 | -- コンフィグレーション実行 453 | local _doconfig = function(f, to) 454 | local fs = f:seek("end") 455 | f:seek("set") 456 | local sz = 100 / ((fs < 1) and 1 or fs) 457 | 458 | local c = false 459 | for n=1,to do 460 | local _,d = pio(0x07, 0x00) 461 | sleep(1) 462 | if not btest(d, 0x18) then c = true; break end 463 | end 464 | if not c then return false end 465 | 466 | c = false 467 | for n=1,to do 468 | local _,d = pio(0x07, 0x04) 469 | sleep(1) 470 | if btest(d, 0x08) then c = true; break end 471 | end 472 | if not c then return false end 473 | 474 | local p = 0 475 | while true do 476 | local ln = f:read(256) 477 | if not ln then break end 478 | ---- 479 | spi("write", ln) 480 | --]] 481 | 482 | if p >= 15 then 483 | ca.progress("config", 100, f:seek()*sz) 484 | p = 0 485 | else 486 | p = p + 1 487 | end 488 | end 489 | 490 | local _,d = pio(0x07, 0x07) 491 | return btest(d, 0x10) 492 | end 493 | 494 | 495 | -- コンフィグメイン処理 496 | ca.progress("config", 0, 0) 497 | ca.avminit() 498 | 499 | if lfs.attributes(fname, "modification") ~= lfs.attributes(fname..".cache", "modification") then 500 | usecache = false 501 | end 502 | 503 | local fconf = open(fname..".cache", "rb") 504 | if not usecache or not fconf then 505 | local res,mes = _makecache(fname) 506 | if not res then return false,mes end 507 | 508 | fconf = open(fname..".cache", "rb") 509 | end 510 | 511 | spi("mode", 0) 512 | spi("bit", 8) 513 | spi("cs", 1) 514 | spi("init", 1) 515 | pio(0x07, 0x04) 516 | 517 | local res = false 518 | for n=1,trynumber do 519 | if _doconfig(fconf, timeout) then res = true; break end 520 | pio(0x07, 0x04) 521 | end 522 | pio(0x00, 0x1f) 523 | pio(0x00, 0x00) 524 | 525 | fconf:close() 526 | if not res then 527 | remove(fname..".cache") 528 | return false,"configuration failed" 529 | end 530 | 531 | ca.progress("config", 100, 100) 532 | return true 533 | end 534 | 535 | 536 | ------------------------------------------------------------------------------------ 537 | -- AvalonMMメモリユーティリティ 538 | ------------------------------------------------------------------------------------ 539 | 540 | -- バイナリデータロード : ファイルを指定のアドレスに読み込む 541 | function ca.binload(avm, fname, addr) 542 | if type(addr) ~= "number" then addr = 0 end 543 | 544 | ca.progress("binload", 0) 545 | 546 | local fbin = open(fname, "rb") 547 | if not fbin then return false,"file open failed" end 548 | local fs = fbin:seek("end") 549 | fbin:seek("set") 550 | 551 | local sz = 100 / ((fs < 1) and 1 or fs) 552 | local res = true 553 | local mes 554 | local p = 0 555 | 556 | local aa = addr % 4 557 | if aa ~= 0 then 558 | local len = 4 - aa 559 | local data = fbin:read(len) 560 | if data then 561 | res,mes = avm:memwr(addr, data) 562 | if res then addr = addr + #data end 563 | end 564 | end 565 | if res then 566 | while true do 567 | local data = fbin:read(256) 568 | if not data then break end 569 | 570 | res,mes = avm:memwr(addr, data) 571 | if not res then break end 572 | addr = addr + #data 573 | 574 | if p >= 15 then 575 | ca.progress("binload", fbin:seek()*sz) 576 | p = 0 577 | else 578 | p = p + 1 579 | end 580 | end 581 | end 582 | fbin:close() 583 | 584 | if not res then return false,mes end 585 | 586 | ca.progress("binload", 100) 587 | return true 588 | end 589 | 590 | 591 | -- バイナリデータセーブ : 指定のメモリエリアをファイルに書き出す 592 | function ca.binsave(avm, fname, size, addr) 593 | if not(type(size) == "number" and size > 0) then return false,"parameter error" end 594 | if type(addr) ~= "number" then addr = 0 end 595 | 596 | ca.progress("binsave", 0) 597 | 598 | local fbin = open(fname, "wb") 599 | if not fbin then return false,"file open failed" end 600 | 601 | local sz = 100 / size 602 | local res,mes 603 | local p = 0 604 | 605 | while size > 0 do 606 | local len = size 607 | if len > 256 then len = 256 end 608 | 609 | res,mes = avm:memrd(addr, len) 610 | if not res then break end 611 | fbin:write(res) 612 | 613 | size = size - len 614 | addr = addr + len 615 | 616 | if p >= 15 then 617 | ca.progress("binsave", 100-size*sz) 618 | p = 0 619 | else 620 | p = p + 1 621 | end 622 | end 623 | fbin:close() 624 | 625 | if not res then return false,mes end 626 | 627 | ca.progress("binsave", 100) 628 | return true 629 | end 630 | 631 | 632 | -- ROMデータロード : IntelHEXまたはモトローラSファイルをメモリに読み込む 633 | function ca.hexload(avm, fname, offset) 634 | if type(offset) ~= "number" then offset = 0 end 635 | 636 | ca.progress("hexload", 0) 637 | 638 | -- インテルHEXの一行をデコード 639 | local ext_addr = 0 640 | local _dec_ihex = function(ln) 641 | local b = ln:sub(2, 3) 642 | if not b then return false,"byte count error" end 643 | 644 | local count = tonumber(b, 16) 645 | if not count then return false,"invalid charactors" end 646 | 647 | if #ln < count*2+11 then return false,"data record shortage" end 648 | 649 | local addr = tonumber(ln:sub(4, 7), 16) 650 | local rtype = tonumber(ln:sub(8, 9), 16) 651 | if not(addr and rtype) then return false,"invalid charactors" end 652 | 653 | local sum = count + extract(addr, 8, 8) + extract(addr, 0, 8) + rtype 654 | local data = "" 655 | 656 | if rtype == 2 or rtype == 4 then 657 | local ea = tonumber(ln:sub(10, 13), 16) 658 | if not ea then return false,"invalid charactors" end 659 | 660 | sum = sum + extract(ea, 8, 8) + extract(ea, 0, 8) 661 | if rtype == 2 then 662 | ext_addr = lshift(ea, 4) -- 拡張セグメントアドレス 663 | else 664 | ext_addr = lshift(ea, 16) -- 拡張リニアアドレス 665 | end 666 | 667 | elseif rtype == 0 then -- データレコード 668 | local n = 10 669 | local c = true 670 | for i=1,count do 671 | local b = tonumber(ln:sub(n, n+1), 16) 672 | if not b then c = false; break end 673 | data = data .. schar(b) 674 | sum = sum + b 675 | n = n + 2 676 | end 677 | if not c then return false,"invalid charactors" end 678 | 679 | else 680 | return true -- それ以外のレコードはスキップ 681 | end 682 | 683 | local b = tonumber(ln:sub(count*2+10, count*2+11), 16) 684 | if not b then return false,"invalid charactors" end 685 | 686 | if btest(sum+b, 0xff) then return false,"checksum error" end 687 | 688 | if rtype ~= 0 then return true end 689 | return avm:memwr(offset + ext_addr + addr, data) 690 | end 691 | 692 | -- モトローラSフォーマットの一行をデコード 693 | local _dec_srec = function(ln) 694 | local b = ln:sub(2, 2) 695 | local c = ln:sub(3, 4) 696 | if not(b and c) then return false,"record type error" end 697 | 698 | local rtype = tonumber(b) 699 | local count = tonumber(c, 16) 700 | if not(rtype and count) then return false,"invalid charactors" end 701 | 702 | if #ln < count*2+4 then return false,"data record shortage" end 703 | 704 | local n,m 705 | local addr = 0 706 | local sum = count 707 | local data = "" 708 | 709 | if rtype == 1 then 710 | addr = tonumber(ln:sub(5, 8), 16) -- S1レコード 711 | n = 9 712 | m = count - 3 713 | elseif rtype == 2 then 714 | addr = tonumber(ln:sub(5, 10), 16) -- S2レコード 715 | n = 11 716 | m = count - 4 717 | elseif rtype == 3 then 718 | addr = tonumber(ln:sub(5, 12), 16) -- S3レコード 719 | n = 13 720 | m = count - 5 721 | else 722 | return true -- それ以外のレコードはスキップ 723 | end 724 | if not addr then return false,"invalid charactors" end 725 | 726 | for i=0,24,8 do sum = sum + extract(addr, i, 8) end 727 | 728 | local c = true 729 | for i=1,m do 730 | local b = tonumber(ln:sub(n, n+1), 16) 731 | if not b then c = false; break end 732 | data = data .. schar(b) 733 | sum = sum + b 734 | n = n + 2 735 | end 736 | local b = tonumber(ln:sub(n, n+1), 16) 737 | if not b then c = false end 738 | if not c then return false,"invalid charactors" end 739 | 740 | if btest(sum+b+1, 0xff) then return false,"checksum error" end 741 | 742 | return avm:memwr(offset + addr, data) 743 | end 744 | 745 | local fhex = open(fname, "r") 746 | if not fhex then return false,"file open failed" end 747 | local fs = fhex:seek("end") 748 | fhex:seek("set") 749 | 750 | local sz = 100 / ((fs < 1) and 1 or fs) 751 | local res = true 752 | local mes 753 | local lnumber = 1 754 | 755 | while true do 756 | local ln = fhex:read() 757 | if not ln then break end 758 | 759 | local c = ln:sub(1, 1) 760 | if c == ":" then 761 | res,mes = _dec_ihex(ln) 762 | elseif c == "S" then 763 | res,mes = _dec_srec(ln) 764 | end 765 | if not res then break end 766 | 767 | lnumber = lnumber + 1 768 | if lnumber % 16 == 15 then ca.progress("hexload", fhex:seek()*sz) end 769 | end 770 | fhex:close() 771 | 772 | if not res then return false,mes.." (line:"..lnumber..")" end 773 | 774 | ca.progress("hexload", 100) 775 | return true 776 | end 777 | 778 | 779 | return ca 780 | --------------------------------------------------------------------------------