{"id":20316062,"url":"https://github.com/lupyuen/visual-zig-nuttx","last_synced_at":"2025-04-11T17:40:53.668Z","repository":{"id":45975871,"uuid":"514760682","full_name":"lupyuen/visual-zig-nuttx","owner":"lupyuen","description":"Visual Programming for Zig with NuttX Sensors","archived":false,"fork":false,"pushed_at":"2022-08-19T04:08:24.000Z","size":386,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-25T13:39:48.606Z","etag":null,"topics":["bl602","bl604","blockly","bme280","nuttx","pinecone","pinedio","riscv32","sensor","zig"],"latest_commit_sha":null,"homepage":"https://lupyuen.github.io/articles/visual","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lupyuen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["lupyuen"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["paypal.me/lupyuen"]}},"created_at":"2022-07-17T06:04:11.000Z","updated_at":"2023-08-10T22:37:04.000Z","dependencies_parsed_at":"2022-07-18T14:47:24.417Z","dependency_job_id":null,"html_url":"https://github.com/lupyuen/visual-zig-nuttx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fvisual-zig-nuttx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fvisual-zig-nuttx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fvisual-zig-nuttx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fvisual-zig-nuttx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lupyuen","download_url":"https://codeload.github.com/lupyuen/visual-zig-nuttx/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248451352,"owners_count":21105856,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bl602","bl604","blockly","bme280","nuttx","pinecone","pinedio","riscv32","sensor","zig"],"created_at":"2024-11-14T18:24:04.150Z","updated_at":"2025-04-11T17:40:53.645Z","avatar_url":"https://github.com/lupyuen.png","language":"Zig","funding_links":["https://github.com/sponsors/lupyuen","paypal.me/lupyuen"],"categories":[],"sub_categories":[],"readme":"![Visual Programming with Zig and NuttX Sensors on Blockly](https://lupyuen.github.io/images/visual-title.jpg)\n\n# Visual Programming for Zig with NuttX Sensors\n\nRead the articles...\n\n-   [\"Visual Programming with Zig and NuttX Sensors\"](https://lupyuen.github.io/articles/visual)\n\n-   [\"Zig Visual Programming with Blockly\"](https://lupyuen.github.io/articles/blockly)\n\n-   [\"Read NuttX Sensor Data with Zig\"](https://lupyuen.github.io/articles/sensor)\n\nCan we use Scratch / [Blockly](https://github.com/lupyuen3/blockly-zig-nuttx) to code Zig programs, the drag-n-drop way?\n\nLet's create a Visual Programming Tool for Zig that will generate IoT Sensor Apps with Apache NuttX RTOS.\n\n_Why limit to IoT Sensor Apps?_\n\n-   Types are simpler: Only floating-point numbers will be supported, no strings needed\n\n-   Blockly is Typeless. With Zig we can use Type Inference to deduce the missing Struct Types\n\n-   Make it easier to experiment with various IoT Sensors: Temperature, Humidity, Air Pressure, ...\n\n[Blockly Source Code: lupyuen3/blockly-zig-nuttx](https://github.com/lupyuen3/blockly-zig-nuttx)\n\n![Visual Programming for Zig with NuttX Sensors](https://lupyuen.github.io/images/sensor-visual.jpg)\n\n# Sensor Test App in C\n\nWe start with the Sensor Test App (in C) from Apache NuttX RTOS: [sensortest.c](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c)\n\nHere are the steps for reading a NuttX Sensor...\n\n```c\n// From https://lupyuen.github.io/articles/bme280#sensor-test-app\n// Open the Sensor Device.\n// devname looks like \"/dev/uorb/sensor_baro0\" or \"/dev/uorb/sensor_humi0\"\nfd = open(devname, O_RDONLY | O_NONBLOCK);\n\n// Set Standby Interval\nioctl(fd, SNIOC_SET_INTERVAL, interval);\n\n// Set Batch Latency\nioctl(fd, SNIOC_BATCH, latency);\n\n//  If Sensor Data is available...\nif (poll(\u0026fds, 1, -1) \u003e 0) {\n\n  //  Read the Sensor Data\n  if (read(fd, buffer, len) \u003e= len) {\n\n    // Cast buffer as Barometer Sensor Data\n    struct sensor_event_baro *event = \n      (struct sensor_event_baro *) buffer;\n\n    // Handle Pressure and Temperature\n    printf(\n      \"%s: timestamp:%d value1:%.2f value2:%.2f\\n\",\n      name, \n      event.timestamp, \n      event.pressure, \n      event.temperature\n    );\n  }\n}\n\n// Close the Sensor Device and free the buffer\nclose(fd);\nfree(buffer);\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c)\n\nNuttX compiles the Sensor Test App [sensortest.c](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c) with this GCC command...\n\n```bash\n##  App Source Directory\ncd $HOME/nuttx/apps/testing/sensortest\n\n##  Compile sensortest.c with GCC\nriscv64-unknown-elf-gcc \\\n  -c \\\n  -fno-common \\\n  -Wall \\\n  -Wstrict-prototypes \\\n  -Wshadow \\\n  -Wundef \\\n  -Os \\\n  -fno-strict-aliasing \\\n  -fomit-frame-pointer \\\n  -fstack-protector-all \\\n  -ffunction-sections \\\n  -fdata-sections \\\n  -g \\\n  -march=rv32imafc \\\n  -mabi=ilp32f \\\n  -mno-relax \\\n  -isystem \"$HOME/nuttx/nuttx/include\" \\\n  -D__NuttX__ \\\n  -DNDEBUG \\\n  -DARCH_RISCV  \\\n  -pipe \\\n  -I \"$HOME/nuttx/apps/include\" \\\n  -Dmain=sensortest_main \\\n  sensortest.c \\\n  -o  sensortest.c.home.user.nuttx.apps.testing.sensortest.o\n```\n\n(Observed from `make --trace`)\n\nLet's convert the Sensor Test App from C to Zig...\n\n# Auto-Translate Sensor App to Zig\n\nThe Zig Compiler can auto-translate C code to Zig. [(See this)](https://ziglang.org/documentation/master/#C-Translation-CLI)\n\nHere's how we auto-translate our Sensor App [sensortest.c](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c) from C to Zig...\n\n-   Take the GCC command from above\n\n-   Change `riscv64-unknown-elf-gcc` to `zig translate-c`\n\n-   Add the target `-target riscv32-freestanding-none -mcpu=baseline_rv32-d`\n\n-   Remove `-march=rv32imafc`\n\n-   Surround the C Flags by `-cflags` ... `--`\n\nLike this...\n\n```bash\n##  App Source Directory\ncd $HOME/nuttx/apps/testing/sensortest\n\n##  Auto-translate sensortest.c from C to Zig\nzig translate-c \\\n  -target riscv32-freestanding-none \\\n  -mcpu=baseline_rv32-d \\\n  -cflags \\\n    -fno-common \\\n    -Wall \\\n    -Wstrict-prototypes \\\n    -Wshadow \\\n    -Wundef \\\n    -Os \\\n    -fno-strict-aliasing \\\n    -fomit-frame-pointer \\\n    -fstack-protector-all \\\n    -ffunction-sections \\\n    -fdata-sections \\\n    -g \\\n    -mabi=ilp32f \\\n    -mno-relax \\\n  -- \\\n  -isystem \"$HOME/nuttx/nuttx/include\" \\\n  -D__NuttX__ \\\n  -DNDEBUG \\\n  -DARCH_RISCV  \\\n  -I \"$HOME/nuttx/apps/include\" \\\n  -Dmain=sensortest_main  \\\n  sensortest.c \\\n  \u003esensortest.zig\n```\n\nTo fix the translation (Zig Translate doesn't support `goto`) we need to insert this...\n\n```c\n#ifdef __clang__  //  Workaround for zig cc\n#include \u003carch/types.h\u003e\n#include \"../../nuttx/include/limits.h\"\n#define goto return ret;\n#define name_err\n#define open_err\n#define ctl_err\n#endif  //  __clang__\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c#L25-L32)\n\nAnd change this...\n\n```c\n#ifndef __clang__  //  Workaround for NuttX with zig cc\nctl_err:\n#endif  //  !__clang__\n  close(fd);\n#ifndef __clang__  //  Workaround for NuttX with zig cc\nopen_err:\n#endif  //  !__clang__\n  free(buffer);\n#ifndef __clang__  //  Workaround for NuttX with zig cc\nname_err:\n#endif  //  !__clang__\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c#L403-L413)\n\n[(See the changes)](https://github.com/lupyuen/incubator-nuttx-apps/commit/907e1cc3755e9699acdefd99fc76939ddd387e34)\n\nHere's the original C code: [sensortest.c](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c)\n\nAnd the auto-translation from C to Zig: [translated/sensortest.zig](translated/sensortest.zig)\n\n# Sensor App in Zig\n\nWe copy the relevant snippets from the Auto-Translation to create our Zig Sensor App: [sensortest.zig](https://github.com/lupyuen/visual-zig-nuttx/blob/0d3617dbcae5ae9836b5a70ba2026c75e12a00ce/sensortest.zig#L32-L417)\n\nhttps://github.com/lupyuen/visual-zig-nuttx/blob/0d3617dbcae5ae9836b5a70ba2026c75e12a00ce/sensortest.zig#L32-L417\n\nThen we compile our Zig Sensor App...\n\n```bash\n##  Download our Zig Sensor App for NuttX\ngit clone --recursive https://github.com/lupyuen/visual-zig-nuttx\ncd visual-zig-nuttx\n\n##  Compile the Zig App for BL602 (RV32IMACF with Hardware Floating-Point)\nzig build-obj \\\n  --verbose-cimport \\\n  -target riscv32-freestanding-none \\\n  -mcpu=baseline_rv32-d \\\n  -isystem \"$HOME/nuttx/nuttx/include\" \\\n  -I \"$HOME/nuttx/apps/include\" \\\n  sensortest.zig\n\n##  Patch the ELF Header of `sensortest.o` from Soft-Float ABI to Hard-Float ABI\nxxd -c 1 sensortest.o \\\n  | sed 's/00000024: 01/00000024: 03/' \\\n  | xxd -r -c 1 - sensortest2.o\ncp sensortest2.o sensortest.o\n\n##  Copy the compiled app to NuttX and overwrite `sensortest.o`\n##  TODO: Change \"$HOME/nuttx\" to your NuttX Project Directory\ncp sensortest.o $HOME/nuttx/apps/testing/sensortest/sensortest*.o\n\n##  Build NuttX to link the Zig Object from `sensortest.o`\n##  TODO: Change \"$HOME/nuttx\" to your NuttX Project Directory\ncd $HOME/nuttx/nuttx\nmake\n```\n\n# Connect BME280 Sensor\n\nFor testing the Zig Sensor App, we connect the BME280 Sensor (Temperature / Humidity / Air Pressure) to Pine64's [__PineCone BL602 Board__](https://lupyuen.github.io/articles/pinecone)...\n\n| BL602 Pin | BME280 Pin | Wire Colour\n|:---:|:---:|:---|\n| __`GPIO 1`__ | `SDA` | Green \n| __`GPIO 2`__ | `SCL` | Blue\n| __`3V3`__ | `3.3V` | Red\n| __`GND`__ | `GND` | Black\n\n![Pine64 PineCone BL602 RISC-V Board connected to Bosch BME280 Sensor](https://lupyuen.github.io/images/sensor-connect.jpg)\n\n# Run Zig Sensor App\n\nTo read the BME280 Sensor, let's run the Zig Sensor App: [sensortest.zig](https://github.com/lupyuen/visual-zig-nuttx/blob/0d3617dbcae5ae9836b5a70ba2026c75e12a00ce/sensortest.zig#L32-L417)\n\nFirst we read the Humidity with our Zig Sensor App...\n\n```bash\nNuttShell (NSH) NuttX-10.3.0\nnsh\u003e sensortest -n 1 humi0\nZig Sensor Test\nbme280_fetch: temperature=30.520000 °C, pressure=1027.211548 mbar, humidity=72.229492 %\nhumi0: timestamp:109080000 value:72.23\n```\n\n[(See the complete log)](https://gist.github.com/lupyuen/97358b560197d26304fc196ceb45565a)\n\nOur Zig App returns the correct Humidity (`value`): 72 %.\n\nNext we read the Temperature and Air Pressure with our Zig Sensor App...\n\n```bash\nnsh\u003e sensortest -n 1 baro0\nZig Sensor Test\nbme280_fetch: temperature=30.520000 °C, pressure=1029.177490 mbar, humidity=72.184570 %\nbaro0: timestamp:78490000 value1:72.18 value2:72.18\n```\n\n[(See the complete log)](https://gist.github.com/lupyuen/97358b560197d26304fc196ceb45565a)\n\nThis shows that the BME280 Driver has fetched the correct Temperature (30 °C), Pressure (1,029 mbar) and Humidity (72 %).\n\nBut our Zig App returns both Pressure (`value1`) and Temperature (`value2`) as 72. Which is incorrect.\n\nCompare the above output with the original C Version of the Sensor App: [sensortest.c](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c)\n\n```bash\nnsh\u003e sensortest -n 1 humi0\nbme280_fetch: temperature=30.690001 °C, pressure=1027.283447 mbar, humidity=72.280273 %\nhumi0: timestamp:26000000 value:72.28\n\nnsh\u003e sensortest -n 1 baro0\nbme280_fetch: temperature=30.690001 °C, pressure=1029.177368 mbar, humidity=72.257813 %\nbaro0: timestamp:14280000 value1:1029.18 value2:30.69\n```\n\n[(See the complete log)](https://gist.github.com/lupyuen/f20fbfbf5d4f1103946a278b87b8bc3c)\n\nWe see that Humidity (`value`), Pressure (`value1`) and Temperature (`value2`) are returned correctly by the C Version of the Sensor App.\n\nSomething got messed up in the Auto-Translation from C [(sensortest.c)](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c) to Zig [(sensortest.zig)](https://github.com/lupyuen/visual-zig-nuttx/blob/0d3617dbcae5ae9836b5a70ba2026c75e12a00ce/sensortest.zig#L32-L417). Let's find out why...\n\n# Fix Floating-Point Values\n\n_(Note: We observed this issue with Zig Compiler version 0.10.0, it might have been fixed in later versions of the compiler)_\n\nEarlier we saw that our Zig Sensor App printed the incorrect Sensor Values for Pressure (`value1`) and Temperature (`value2`)...\n\n```bash\nnsh\u003e sensortest -n 1 baro0\nZig Sensor Test\nbme280_fetch: temperature=30.520000 °C, pressure=1029.177490 mbar, humidity=72.184570 %\nbaro0: timestamp:78490000 value1:72.18 value2:72.18\n```\n\nZig seems to have a problem passing the Pressure and Temperature values (both `f32`) to `printf`, based on this Auto-Translated Zig Code...\n\n```c\nfn print_valf2(buffer: [*c]const u8, name: [*c]const u8) void {\n    const event = @intToPtr([*c]c.struct_sensor_baro, @ptrToInt(buffer));\n    _ = printf(\"%s: timestamp:%llu value1:%.2f value2:%.2f\\n\", \n       name, \n       event.*.timestamp, \n       @floatCast(f64, event.*.pressure), \n       @floatCast(f64, event.*.temperature)\n    );\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/0d3617dbcae5ae9836b5a70ba2026c75e12a00ce/sensortest.zig#L187-L198)\n\nThe workaround is to convert the Float values to Integer AND split into two calls to `printf`...\n\n```c\nfn print_valf2(buffer: [*c]const u8, name: [*c]const u8) void {\n    const event = @intToPtr([*c]c.struct_sensor_baro, @ptrToInt(buffer));\n    _ = printf(\"%s: timestamp:%llu \", \n        name, \n        event.*.timestamp, \n    );\n    _ = printf(\"value1:%d value2:%d\\n\", \n        @floatToInt(i32, event.*.pressure), \n        @floatToInt(i32, event.*.temperature)\n    );\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/74ac3c36f44911685503f4ba7161771907af2793/sensortest.zig#L191-L222)\n\nNow our Zig Sensor App prints the correct values, but truncated as Integers...\n\n```text\nnsh\u003e sensortest -n 1 baro0\nZig Sensor Test\nSensorTest: Test /dev/uorb/sensor_baro0 with interval(1000000us), latency(0us)\nbaro0: timestamp:42610000 value1:1003 value2:31\nSensorTest: Received message: baro0, number:1/1\n\nnsh\u003e sensortest -n 1 humi0\nZig Sensor Test\nSensorTest: Test /dev/uorb/sensor_humi0 with interval(1000000us), latency(0us)\nhumi0: timestamp:32420000 value:68\nSensorTest: Received message: humi0, number:1/1\n```\n\nSince `printf` works OK with Integers, let's print the Floats as Integers with 2 decimal places...\n\n```zig\n/// Print the float with 2 decimal places.\n/// We print as integers because `printf` has a problem with floats.\nfn print_float(f: f32) void {\n    const scaled = @floatToInt(i32, f * 100);\n    const f1 = @divTrunc(scaled, 100);\n    const f2 = @mod(scaled, 100);\n    _ = printf(\"%d.%02d\", f1, f2);\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/54d6b0c0a55126435b5244cef8f78d6060182215/sensortest.zig#L313-L320)\n\nThen we pass the Floats to `print_float` for printing...\n\n```zig\nfn print_valf2(buffer: [*c]const u8, name: [*c]const u8) void {\n    const event = @intToPtr([*c]c.struct_sensor_baro, @ptrToInt(buffer));\n    _ = printf(\"%s: timestamp:%llu \", \n        name, \n        event.*.timestamp, \n    );\n    _ = printf(\"value1:\");  print_float(event.*.pressure);\n    _ = printf(\" value2:\"); print_float(event.*.temperature);\n    _ = printf(\"\\n\");\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/54d6b0c0a55126435b5244cef8f78d6060182215/sensortest.zig#L202-L217)\n\nFinally we see the Pressure (`value1`), Temperature (`value2`) and Humidity (`value`) printed correctly with 2 decimal places!\n\n```text\nnsh\u003e sensortest -n 1 baro0\nZig Sensor Test\nSensorTest: Test /dev/uorb/sensor_baro0 with interval(1000000us), latency(0us)\nbaro0: timestamp:17780000 value1:1006.12 value2:29.65\nSensorTest: Received message: baro0, number:1/1\n\nnsh\u003e sensortest -n 1 humi0\nZig Sensor Test\nSensorTest: Test /dev/uorb/sensor_humi0 with interval(1000000us), latency(0us)\nhumi0: timestamp:28580000 value:77.44\nSensorTest: Received message: humi0, number:1/1\n```\n\n_Have we tried other options for `@floatCast`?_\n\nYes we tested these options for `@floatCast`...\n\n```zig\n_ = printf(\"value1 no floatCast: %f\\n\",  event.*.pressure);\n_ = printf(\"value1 floatCast f32: %f\\n\", @floatCast(f32, event.*.pressure));\n_ = printf(\"value1 floatCast f64: %f\\n\", @floatCast(f64, event.*.pressure));\n```\n\nBut the result is incorrect...\n\n```text\nnsh\u003e sensortest -n 1 baro0\nZig Sensor Test\nbaro0: timestamp:60280000 value1:1006.90 value2:30.79\nvalue1 no floatCast: 0.000000\nvalue1 floatCast f32: 0.000000\nvalue1 floatCast f64: 30.790001\n```\n\nBecause `value1` (Pressure) is supposed to be 1006, not 30.\n\nRobert Lipe suggests that we might have a problem with the RISC-V Calling Conventions for Floats and Doubles...\n\n\u003e The calling conventions for RISC-V on small computers for floats/doubles is weird and the software/emulators/ libs are confusing. Verify that floats get promoted to doubles in the call and that they go to F0, F2, F4, etc. in the frame, properly aligned. Double check complr flags\n\n[(Source)](https://twitter.com/robertlipe/status/1549640832818487297?t=3lcvm6oYFgEH_YMX7GfaUw\u0026s=19)\n\n\u003e See, e.g., https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc and related. This is _totally_ the kind of thing that small parts (enable FP on BL602; disable s/w libs) and x-language can get wrong.\n\n[(Source)](https://twitter.com/robertlipe/status/1549641888931651591?t=z5VGGX0MyiIGsHHp4FyAzw\u0026s=19)\n\n_Instead of `printf`, why not call the Zig Debug Logger `debug`?_\n\n```zig\ndebug(\"pressure: {}\", .{ event.*.pressure });\ndebug(\"temperature: {}\", .{ event.*.temperature });\n```\n\nThis causes a Linker Error, as explained below...\n\n# Floating-Point Link Error\n\n_(Note: We observed this issue with Zig Compiler version 0.10.0, it might have been fixed in later versions of the compiler)_\n\nWhen we call the Zig Debug Logger `debug` to print Floating-Point Numbers (32-bit)...\n\n```zig\nvar event = @intToPtr([*c]c.struct_sensor_baro, @ptrToInt(buffer));\ndebug(\"pressure: {}\",    .{ event.*.pressure });\ndebug(\"temperature: {}\", .{ event.*.temperature });\n```\n\nIt fails to link...\n\n```text\nriscv64-unknown-elf-ld: nuttx/nuttx/staging/libapps.a(sensortest.c.home.user.nuttx.apps.testing.sensortest.o): in function `std.fmt.errol.errolInt':\nzig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:305: \nundefined reference to `__fixunsdfti'\nriscv64-unknown-elf-ld: zig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:305: \nundefined reference to `__floatuntidf'\nriscv64-unknown-elf-ld: zig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:315: \nundefined reference to `__umodti3'\nriscv64-unknown-elf-ld: zig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:316: undefined reference to `__udivti3'\nriscv64-unknown-elf-ld: zig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:316: \nundefined reference to `__umodti3'\nriscv64-unknown-elf-ld: zig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:318: \nundefined reference to `__umodti3'\nriscv64-unknown-elf-ld: zig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:319: \nundefined reference to `__udivti3'\nriscv64-unknown-elf-ld: zig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:319: \nundefined reference to `__umodti3'\nriscv64-unknown-elf-ld: zig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:324: \nundefined reference to `__udivti3'\nriscv64-unknown-elf-ld: zig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:335: \nundefined reference to `__udivti3'\nriscv64-unknown-elf-ld: nuttx/nuttx/staging/libapps.a(sensortest.c.home.user.nuttx.apps.testing.sensortest.o): in function `std.fmt.errol.fpeint':\nzig-linux-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/std/fmt/errol.zig:677: \nundefined reference to `__ashlti3'\n```\n\nBut printing as Integers with `debug` works OK...\n\n```zig\ndebug(\"pressure: {}\", \n  .{ @floatToInt(i32, event.*.pressure) });\ndebug(\"temperature: {}\", \n  .{ @floatToInt(i32, event.*.temperature) });\n```\n\nSo we print Floats as Integers with the Debug Logger...\n\n# Fixed-Point Printing\n\nEarlier we saw that Zig Debug Logger `debug` won't print Floating-Point Numbers. (Due to a Linker Error)\n\nLet's convert Floating-Point Numbers to Fixed-Point Numbers (2 decimal places) and print as Integers instead...\n\n```zig\n/// Convert the float to a fixed-point number (`int`.`frac`) with 2 decimal places.\n/// We do this because `debug` has a problem with floats.\npub fn floatToFixed(f: f32) struct { int: i32, frac: u8 } {\n    const scaled = @floatToInt(i32, f * 100.0);\n    const rem = @rem(scaled, 100);\n    const rem_abs = if (rem \u003c 0) -rem else rem;\n    return .{\n        .int  = @divTrunc(scaled, 100),\n        .frac = @intCast(u8, rem_abs),\n    };\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/b4a0ce4ae0e518e705d0de1437a32e201a7b0c50/sensor.zig#L40-L50)\n\nThis is how we print Floating-Point Numbers as Fixed-Point Numbers...\n\n```zig\n/// Print 2 floats\nfn print_valf2(buffer: []const align(8) u8, name: []const u8) void {\n    _ = name;\n    const event = @ptrCast(*const c.struct_sensor_baro, \u0026buffer[0]);\n    const pressure = floatToFixed(event.*.pressure);\n    const temperature = floatToFixed(event.*.temperature);\n    debug(\"value1:{}.{:0\u003e2}\", .{ pressure.int, pressure.frac });\n    debug(\"value2:{}.{:0\u003e2}\", .{ temperature.int, temperature.frac });\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/3cc8c12d38932ab4f3c5e2cd0dd8f5b33ad47750/sensortest.zig#L224-L232)\n\n# Change to Static Buffer\n\nOriginally we allocated the Sensor Data Buffer from the Heap via `calloc`...\n\n```zig\n// Allocate buffer from heap\nlen = @bitCast(c_int, @as(c_uint, g_sensor_info[@intCast(c_uint, idx)].esize));\nbuffer = @ptrCast([*c]u8, @alignCast(std.meta.alignment(u8), \n    c.calloc(@bitCast(usize, @as(c_int, 1)), @bitCast(usize, len))));\n...\n// Read into heap buffer\nif (c.read(fd, @ptrCast(?*anyopaque, buffer), @bitCast(usize, len)) \u003e= len) {\n    g_sensor_info[@intCast(c_uint, idx)].print.?(buffer, name);\n}\n...\n// Deallocate heap buffer\nc.free(@ptrCast(?*anyopaque, buffer));\n```\n\nTo make this a little safer, we switched to a Static Buffer...\n\n```zig\n/// Sensor Data Buffer\n/// (Aligned to 8 bytes because it's passed to C)\nvar sensor_data align(8) = std.mem.zeroes([256]u8);\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/4ccb0cd9b2a55464b76b8a0fcbcf9f106d608f2f/sensortest.zig#L493-L495)\n\nSo that we no longer worry about deallocating the buffer...\n\n```zig\n// Use static buffer\nlen = @bitCast(c_int, @as(c_uint, g_sensor_info[@intCast(c_uint, idx)].esize));\nassert(sensor_data.len \u003e= len);\n...\n// Read into static buffer\nif (c.read(fd, @ptrCast(?*anyopaque, \u0026sensor_data), @bitCast(usize, len)) \u003e= len) {\n    g_sensor_info[@intCast(c_uint, idx)].print.?(\u0026sensor_data, name);\n}\n...\n// No need to deallocate buffer\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/4ccb0cd9b2a55464b76b8a0fcbcf9f106d608f2f/sensortest.zig#L98-L153)\n\nWe also moved the Device Name from the Stack...\n\n```zig\n/// Main Function that will be called by NuttX. We read the Sensor Data from a Sensor.\npub export fn sensortest_main(...) {\n    var devname: [c.PATH_MAX]u8 = undefined;\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/b680cfa718661db34e15cb0b9861c68c1598ead2/sensortest.zig#L46)\n\nTo a Static Buffer...\n\n```zig\n/// Device Name, like \"/dev/uorb/sensor_baro0\" or \"/dev/uorb/sensor_humi0\"\n/// (Aligned to 8 bytes because it's passed to C)\nvar devname align(8) = std.mem.zeroes([c.PATH_MAX]u8);\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/f0887918e5efb990ee81911d1f851c5ff6334875/sensortest.zig#L534-L536)\n\nAnd we removed the Alignment from Device Name...\n\n```zig\nfd = c.open(\n    @ptrCast([*c]u8, @alignCast(std.meta.alignment(u8), \u0026devname)), \n    c.O_RDONLY | c.O_NONBLOCK\n);\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/b680cfa718661db34e15cb0b9861c68c1598ead2/sensortest.zig#L127-L130)\n\nSo it looks like this...\n\n```zig\nfd = c.open(\n    \u0026devname[0], \n    c.O_RDONLY | c.O_NONBLOCK\n);\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/a975ae54f601c9176ed7c8d2f89650a7f3644c5b/sensortest.zig#L117-L120)\n\n# Incorrect Alignment\n\nWhen we align the Sensor Data Buffer to 4 bytes...\n\n```zig\n/// Sensor Data Buffer\n/// (Aligned to 4 bytes because it's passed to C)\nvar sensor_data align(4) = std.mem.zeroes([256]u8);\n```\n\nIt triggers a Zig Panic due to Incorrect Aligment...\n\n```text\nnsh\u003e sensortest -n 1 baro0\nZig Sensor Test\nSensorTest: Test /dev/uorb/sensor_baro0 with interval(1000000us), latency(0us)\n\n!ZIG PANIC!\nincorrect alignment\nStack Trace:\n0x23014f4c\n0x230161e2\n```\n\nRISC-V Disassembly shows that it's checking `andi a0,a0,7`...\n\n```text\nnuttx/visual-zig-nuttx/sensortest.zig:196\n    const event = @intToPtr([*c]c.struct_sensor_baro, @ptrToInt(buffer));\n23014f2a: 85aa     mv      a1,a0\n23014f2c: fcb42e23 sw      a1,-36(s0)\n23014f30: 891d     andi    a0,a0,7\n23014f32: 4581     li      a1,0\n23014f34: 00b50c63 beq     a0,a1,23014f4c \u003cprint_valf2+0x32\u003e\n23014f38: a009     j       23014f3a \u003cprint_valf2+0x20\u003e\n23014f3a: 23068537 lui     a0,0x23068\n23014f3e: 3c850513 addi    a0,a0,968 # 230683c8 \u003c__unnamed_6\u003e\n23014f42: 4581     li      a1,0\n23014f44: 00000097 auipc   ra,0x0\n23014f48: dd4080e7 jalr    -556(ra) # 23014d18 \u003cpanic\u003e\n23014f4c: fdc42503 lw      a0,-36(s0)\n23014f50: fea42a23 sw      a0,-12(s0)\n```\n(Last 3 bits of address must be 0)\n\nWhich means that the buffer address must be aligned to 8 bytes...\n\n```zig\n/// Sensor Data Buffer\n/// (Aligned to 8 bytes because it's passed to C)\nvar sensor_data align(8) = std.mem.zeroes([256]u8);\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/4ccb0cd9b2a55464b76b8a0fcbcf9f106d608f2f/sensortest.zig#L493-L495)\n\nProbably because `struct_sensor_baro` contains a `timestamp` field that's a 64-bit Integer.\n\n# Clean Up\n\nThe Auto-Translation from C to Zig looks super verbose and strips away the Macro Constants...\n\n```zig\n// Parse the Command-Line Options\ng_should_exit = @as(c_int, 0) != 0;\nwhile ((blk: {\n    const tmp = c.getopt(argc, argv, \"i:b:n:h\");\n    ret = tmp;\n    break :blk tmp;\n}) != -@as(c_int, 1)) {\n    while (true) {\n        switch (ret) {\n            @as(c_int, 105) =\u003e {\n                interval = @bitCast(c_uint, @truncate(c_uint, c.strtoul(c.getoptargp().*, null, @as(c_int, 0))));\n                break;\n            },\n            @as(c_int, 98) =\u003e {\n                latency = @bitCast(c_uint, @truncate(c_uint, c.strtoul(c.getoptargp().*, null, @as(c_int, 0))));\n                break;\n            },\n            @as(c_int, 110) =\u003e {\n                count = @bitCast(c_uint, @truncate(c_uint, c.strtoul(c.getoptargp().*, null, @as(c_int, 0))));\n                break;\n            },\n            else =\u003e {\n                usage();\n                return ret;\n            },\n        }\n        break;\n    }\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/01d5cc053b2c980f16a406afc62a85911a18e18c/sensortest.zig#L62-L90)\n\nWe clean up the Zig code like this...\n\n```zig\n// Parse the Command-Line Options\ng_should_exit = false;\nwhile (true) {\n    ret = c.getopt(argc, argv, \"i:b:n:h\");\n    if (ret == c.EOF) { break; }\n    switch (ret) {\n        'i' =\u003e { interval = c.strtoul(c.getoptargp().*, null, 0); },\n        'b' =\u003e { latency  = c.strtoul(c.getoptargp().*, null, 0); },\n        'n' =\u003e { count    = c.strtoul(c.getoptargp().*, null, 0); },\n        else =\u003e { usage(); return ret; },\n    }\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/8c870893b91db0eae22b0beff808ba9b5f3b2e11/sensortest.zig#L52-L67)\n\nSo that it resembles the original C code...\n\n```c\ng_should_exit = false;\nwhile ((ret = getopt(argc, argv, \"i:b:n:h\")) != EOF)\n  {\n    switch (ret)\n      {\n        case 'i':\n          interval = strtoul(optarg, NULL, 0);\n          break;\n\n        case 'b':\n          latency = strtoul(optarg, NULL, 0);\n          break;\n\n        case 'n':\n          count = strtoul(optarg, NULL, 0);\n          break;\n\n        case 'h':\n        default:\n          usage();\n          goto name_err;\n      }\n  }\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx-apps/blob/pinedio/testing/sensortest/sensortest.c#L268-L290)\n\nOur Zig Sensor App was originally this...\n\nhttps://github.com/lupyuen/visual-zig-nuttx/blob/4ccb0cd9b2a55464b76b8a0fcbcf9f106d608f2f/sensortest.zig#L35-L170\n\nAfter cleanup becomes this...\n\nhttps://github.com/lupyuen/visual-zig-nuttx/blob/692b5e5f6b67d04815f975dff77380a164c98247/multisensor.zig#L15-L150\n\nWe test again to be sure that the Zig Sensor App is still working OK...\n\n```text\nNuttShell (NSH) NuttX-10.3.0\nnsh\u003e uname -a\nNuttX 10.3.0 32c8fdf272 Jul 18 2022 16:38:47 risc-v bl602evb\n\nnsh\u003e sensortest -n 1 baro0\nZig Sensor Test\ntest_multisensor\nSensorTest: Test /dev/uorb/sensor_baro0  with interval(1000000), latency(0)\nvalue1:1007.65\nvalue2:27.68\nSensorTest: Received message: baro0, number:1/1\n\nnsh\u003e sensortest -n 1 humi0\nZig Sensor Test\ntest_multisensor\nSensorTest: Test /dev/uorb/sensor_humi0  with interval(1000000), latency(0)\nvalue:78.91\nSensorTest: Received message: humi0, number:1/1\n```\n\nWe also check that errors are handled correctly...\n\n```text\nnsh\u003e sensortest -n 1 baro\nZig Sensor Test\ntest_multisensor\nFailed to open device:/dev/uorb/sensor_baro , ret:No such file or directory\n\nnsh\u003e sensortest -n 1 invalid\nZig Sensor Test\ntest_multisensor\nThe sensor node name:invalid is invalid\nsensortest test\n Test barometer sensor (/dev/uorb/sensor_baro0)\nsensortest test2\n Test humidity sensor (/dev/uorb/sensor_humi0)\nsensortest [arguments...] \u003ccommand\u003e\n        [-h      ]  sensortest commands help\n        [-i \u003cval\u003e]  The output data period of sensor in us\n                    default: 1000000\n        [-b \u003cval\u003e]  The maximum report latency of sensor in us\n                    default: 0\n        [-n \u003cval\u003e]  The number of output data\n                    default: 0\n Commands:\n        \u003csensor_node_name\u003e ex, accel0(/dev/uorb/sensor_accel0)\n\nnsh\u003e sensortest\nZig Sensor Test\nsensortest test\n Test barometer sensor (/dev/uorb/sensor_baro0)\nsensortest test2\n Test humidity sensor (/dev/uorb/sensor_humi0)\nsensortest [arguments...] \u003ccommand\u003e\n        [-h      ]  sensortest commands help\n        [-i \u003cval\u003e]  The output data period of sensor in us\n                    default: 1000000\n        [-b \u003cval\u003e]  The maximum report latency of sensor in us\n                    default: 0\n        [-n \u003cval\u003e]  The number of output data\n                    default: 0\n Commands:\n        \u003csensor_node_name\u003e ex, accel0(/dev/uorb/sensor_accel0)\n```\n\n# Read Barometer Sensor\n\nReading Sensor Data from a single sensor looks a lot simpler (because we don't need to cast the Sensor Data)...\n\n```zig\n// Omitted: Open the Sensor Device, enable the Sensor and poll for Sensor Data\n...\n// Define the Sensor Data Type\nvar sensor_data = std.mem.zeroes(c.struct_sensor_baro);\nconst len = @sizeOf(@TypeOf(sensor_data));\n\n// Read the Sensor Data\nif (c.read(fd, \u0026sensor_data, len) \u003e= len) {\n\n    // Convert the Sensor Data to Fixed-Point Numbers\n    const pressure    = floatToFixed(sensor_data.pressure);\n    const temperature = floatToFixed(sensor_data.temperature);\n\n    // Print the Sensor Data\n    debug(\"pressure:{}.{:0\u003e2}\", .{\n        pressure.int, \n        pressure.frac \n    });\n    debug(\"temperature:{}.{:0\u003e2}\", .{\n        temperature.int,\n        temperature.frac \n    });\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/6707ec5c597fddc971d14cd078527e6926177edb/sensortest.zig#L103-L123)\n\nHere's the Air Pressure and Temperature read from the BME280 Barometer Sensor...\n\n```text\nnsh\u003e sensortest test\nZig Sensor Test\ntest_sensor\npressure:1007.66\ntemperature:27.70\n```\n\n# Read Humidity Sensor\n\nTo read a Humidity Sensor (like BME280), the code looks highly similar...\n\n```zig\n// Define the Sensor Data Type\nvar sensor_data = std.mem.zeroes(c.struct_sensor_humi);\nconst len = @sizeOf(@TypeOf(sensor_data));\n\n// Read the Sensor Data\nif (c.read(fd, \u0026sensor_data, len) \u003e= len) {\n\n    // Convert the Sensor Data to Fixed-Point Numbers\n    const humidity = floatToFixed(sensor_data.humidity);\n\n    // Print the Sensor Data\n    debug(\"humidity:{}.{:0\u003e2}\", .{\n        humidity.int, \n        humidity.frac \n    });\n}\n```\n\n[(Source)](https://github.com/lupyuen/visual-zig-nuttx/blob/8d9a98afc5864af8d087bc566841863a68ee4b1e/sensortest.zig#L108-L128)\n\nHere's the Humidity read from the BME280 Humidity Sensor...\n\n```text\nnsh\u003e sensortest test2\nZig Sensor Test\ntest_sensor2\nhumidity:78.81\n```\n\n# Zig Generics\n\nWith Zig Generics and `comptime`, we can greatly simplify the reading of Sensor Data...\n\n```zig\n// Read the Temperature\nconst temperature: f32 = try sen.readSensor(\n    c.struct_sensor_baro,       // Sensor Data Struct to be read\n    \"temperature\",              // Sensor Data Field to be returned\n    \"/dev/uorb/sensor_baro0\"  // Path of Sensor Device\n);\n\n// Print the Temperature\ndebug(\"temperature={}\", .{ floatToFixed(temperature) });\n```\n\n[(Source)](visual.zig#L15-L62)\n\nHere's the implementation of `readSensor`...\n\nhttps://github.com/lupyuen/visual-zig-nuttx/blob/1bb1c69ea4a9310e42b149e04ac26a7e4a1f4b58/sensor.zig#L34-L108\n\nNote that the Sensor Data Struct Type and the Sensor Data Field are declared as `comptime`...\n\n```zig\n/// Read a Sensor and return the Sensor Data\npub fn readSensor(\n    comptime SensorType: type,        // Sensor Data Struct to be read, like c.struct_sensor_baro\n    comptime field_name: []const u8,  // Sensor Data Field to be returned, like \"temperature\"\n    device_path: []const u8           // Path of Sensor Device, like \"/dev/uorb/sensor_baro0\"\n) !f32 { ...\n```\n\nWhich means that the values will be substituted at Compile-Time. (Works like a C Macro)\n\nWe can then refer to the Sensor Data Struct `sensor_baro` like this...\n\n```zig\n    // Define the Sensor Data Type\n    var sensor_data = std.mem.zeroes(\n        SensorType\n    );\n```\n\nAnd return a field `temperature` like this...\n\n```zig\n    // Return the Sensor Data Field\n    return @field(sensor_data, field_name);\n```\n\nThus this program...\n\nhttps://github.com/lupyuen/visual-zig-nuttx/blob/a7404eae71dc37850e323848180414aa6ef7e0f7/visual.zig#L27-L61\n\nProduces this output...\n\n```text\nNuttShell (NSH) NuttX-10.3.0\nnsh\u003e sensortest visual\nZig Sensor Test\nStart main\n...\ntemperature=30.18\npressure=1007.69\nhumidity=68.67\nEnd main\n```\n\n# CBOR Encoding\n\nBlockly will emit the Zig code below for a typical IoT Sensor App: [visual.zig](visual.zig)\n\n```zig\n// Read Temperature from BME280 Sensor\nconst temperature = try sen.readSensor(  // Read BME280 Sensor\n    c.struct_sensor_baro,       // Sensor Data Struct\n    \"temperature\",              // Sensor Data Field\n    \"/dev/uorb/sensor_baro0\"  // Path of Sensor Device\n);\n\n// Read Pressure from BME280 Sensor\nconst pressure = try sen.readSensor(  // Read BME280 Sensor\n    c.struct_sensor_baro,       // Sensor Data Struct\n    \"pressure\",                 // Sensor Data Field\n    \"/dev/uorb/sensor_baro0\"  // Path of Sensor Device\n);\n\n// Read Humidity from BME280 Sensor\nconst humidity = try sen.readSensor(  // Read BME280 Sensor\n    c.struct_sensor_humi,       // Sensor Data Struct\n    \"humidity\",                 // Sensor Data Field\n    \"/dev/uorb/sensor_humi0\"  // Path of Sensor Device\n);\n\n// Compose CBOR Message with Temperature, Pressure and Humidity\nconst msg = try composeCbor(.{\n    \"t\", temperature,\n    \"p\", pressure,\n    \"h\", humidity,\n});\n\n// Transmit message to LoRaWAN\ntry transmitLorawan(msg);\n```\n\nThis reads the Temperature, Pressure and Humidity from BME280 Sensor, composes a CBOR Message that's encoded with the Sensor Data, and transmits the CBOR Message to LoRaWAN.\n\n_`composeCbor` will work for a variable number of arguments? Strings as well as numbers?_\n\nYep, here's the implementation of `composeCbor`: [visual.zig](visual.zig#L65-L108)\n\n```zig\n/// TODO: Compose CBOR Message with Key-Value Pairs\n/// https://lupyuen.github.io/articles/cbor2\nfn composeCbor(args: anytype) !CborMessage {\n    debug(\"composeCbor\", .{});\n    comptime {\n        assert(args.len % 2 == 0);  // Missing Key or Value\n    }\n\n    // Process each field...\n    comptime var i: usize = 0;\n    var msg = CborMessage{};\n    inline while (i \u003c args.len) : (i += 2) {\n\n        // Get the key and value\n        const key   = args[i];\n        const value = args[i + 1];\n\n        // Print the key and value\n        debug(\"  {s}: {}\", .{\n            @as([]const u8, key),\n            floatToFixed(value)\n        });\n\n        // Format the message for testing\n        var slice = std.fmt.bufPrint(\n            msg.buf[msg.len..], \n            \"{s}:{},\",\n            .{\n                @as([]const u8, key),\n                floatToFixed(value)\n            }\n        ) catch { _ = std.log.err(\"Error: buf too small\", .{}); return error.Overflow; };\n        msg.len += slice.len;\n    }\n    debug(\"  msg={s}\", .{ msg.buf[0..msg.len] });\n    return msg;\n}\n\n/// TODO: CBOR Message\n/// https://lupyuen.github.io/articles/cbor2\nconst CborMessage = struct {\n    buf: [256]u8 = undefined,  // Limit to 256 chars\n    len: usize = 0,\n};\n```\n\nNote that `composeCbor` is declared as `anytype`...\n\n```zig\nfn composeCbor(args: anytype) { ...\n```\n\nThat's why `composeCbor` accepts a variable number of arguments with different types.\n\nTo handle each argument, this `inline` / `comptime` loop is unrolled at Compile-Time...\n\n```zig\n    // Process each field...\n    comptime var i: usize = 0;\n    inline while (i \u003c args.len) : (i += 2) {\n\n        // Get the key and value\n        const key   = args[i];\n        const value = args[i + 1];\n\n        // Print the key and value\n        debug(\"  {s}: {}\", .{\n            @as([]const u8, key),\n            floatToFixed(value)\n        });\n        ...\n    }\n```\n\n_What happens if we omit a Key or a Value when calling `composeCbor`?_\n\nThis `comptime` assertion check will fail at Compile-Time...\n\n```zig\ncomptime {\n    assert(args.len % 2 == 0);  // Missing Key or Value\n}\n```\n\n# Customise Blockly\n\nNext we customise [Blockly](https://github.com/lupyuen3/blockly-zig-nuttx) to generate the Zig Sensor App.\n\nRead the article...\n\n-   [\"Visual Programming with Zig and NuttX Sensors\"](https://lupyuen.github.io/articles/visual)\n\n# Test Visual Zig Sensor App\n\nTo test the Zig Sensor App generated by Blockly, let's build an IoT Sensor App that will read Temperature, Pressure and Humidity from BME280 Sensor, and transmit the values to LoRaWAN...\n\n[lupyuen3.github.io/blockly-zig-nuttx/demos/code](https://lupyuen3.github.io/blockly-zig-nuttx/demos/code/)\n\n![Complex Sensor App](https://lupyuen.github.io/images/visual-block6.jpg)\n\nThe Blocks above will emit this Zig program...\n\n```zig\n/// Main Function\npub fn main() !void {\n\n  // Every 10 seconds...\n  while (true) {\n    const temperature = try sen.readSensor(  // Read BME280 Sensor\n      c.struct_sensor_baro,       // Sensor Data Struct\n      \"temperature\",              // Sensor Data Field\n      \"/dev/uorb/sensor_baro0\"  // Path of Sensor Device\n    );\n    debug(\"temperature={}\", .{ temperature });\n\n    const pressure = try sen.readSensor(  // Read BME280 Sensor\n      c.struct_sensor_baro,       // Sensor Data Struct\n      \"pressure\",                 // Sensor Data Field\n      \"/dev/uorb/sensor_baro0\"  // Path of Sensor Device\n    );\n    debug(\"pressure={}\", .{ pressure });\n\n    const humidity = try sen.readSensor(  // Read BME280 Sensor\n      c.struct_sensor_humi,       // Sensor Data Struct\n      \"humidity\",                 // Sensor Data Field\n      \"/dev/uorb/sensor_humi0\"  // Path of Sensor Device\n    );\n    debug(\"humidity={}\", .{ humidity });\n\n    const msg = try composeCbor(.{  // Compose CBOR Message\n      \"t\", temperature,\n      \"p\", pressure,\n      \"h\", humidity,\n    });\n\n    // Transmit message to LoRaWAN\n    try transmitLorawan(msg);\n\n    // Wait 10 seconds\n    _ = c.sleep(10);\n  }\n}\n```\n\n[(`composeCbor` is explained here)](https://github.com/lupyuen/visual-zig-nuttx#cbor-encoding)\n\nCopy the contents of the Main Function and paste here...\n\n[visual-zig-nuttx/visual.zig](https://github.com/lupyuen/visual-zig-nuttx/blob/main/visual.zig)\n\nThe generated Zig code should correctly read the Temperature, Pressure and Humidity from BME280 Sensor, and transmit the values to LoRaWAN...\n\n```text\nNuttShell (NSH) NuttX-10.3.0\nnsh\u003e sensortest visual\nZig Sensor Test\nStart main\n\ntemperature=31.05\npressure=1007.44\nhumidity=71.49\ncomposeCbor\n  t: 31.05\n  p: 1007.44\n  h: 71.49\n  msg=t:31.05,p:1007.44,h:71.49,\ntransmitLorawan\n  msg=t:31.05,p:1007.44,h:71.49,\n\ntemperature=31.15\npressure=1007.40\nhumidity=70.86\ncomposeCbor\n  t: 31.15\n  p: 1007.40\n  h: 70.86\n  msg=t:31.15,p:1007.40,h:70.86,\ntransmitLorawan\n  msg=t:31.15,p:1007.40,h:70.86,\n\ntemperature=31.16\npressure=1007.45\nhumidity=70.42\ncomposeCbor\n  t: 31.16\n  p: 1007.45\n  h: 70.42\n  msg=t:31.16,p:1007.45,h:70.42,\ntransmitLorawan\n  msg=t:31.16,p:1007.45,h:70.42,\n\ntemperature=31.16\npressure=1007.47\nhumidity=70.39\ncomposeCbor\n  t: 31.16\n  p: 1007.47\n  h: 70.39\n  msg=t:31.16,p:1007.47,h:70.39,\ntransmitLorawan\n  msg=t:31.16,p:1007.47,h:70.39,\n\ntemperature=31.19\npressure=1007.45\nhumidity=70.35\ncomposeCbor\n  t: 31.19\n  p: 1007.45\n  h: 70.35\n  msg=t:31.19,p:1007.45,h:70.35,\ntransmitLorawan\n  msg=t:31.19,p:1007.45,h:70.35,\n\ntemperature=31.20\npressure=1007.42\nhumidity=70.65\ncomposeCbor\n  t: 31.20\n  p: 1007.42\n  h: 70.65\n  msg=t:31.20,p:1007.42,h:70.65,\ntransmitLorawan\n  msg=t:31.20,p:1007.42,h:70.65,\n```\n\n(Tested with NuttX and BME280 on BL602)\n\n# Test Stubs\n\nTo test the Zig program above on Linux / macOS / Windows (instead of NuttX), add the stubs below to simulate a NuttX Sensor...\n\n```zig\n/// Import Standard Library\nconst std = @import(\"std\");\n\n/// Main Function\npub fn main() !void {\n    // TODO: Paste here the contents of Zig Main Function generated by Blockly\n    ...\n}\n\n/// Aliases for Standard Library\nconst assert = std.debug.assert;\nconst debug  = std.log.debug;\n\n///////////////////////////////////////////////////////////////////////////////\n//  CBOR Encoding\n\n/// TODO: Compose CBOR Message with Key-Value Pairs\n/// https://lupyuen.github.io/articles/cbor2\nfn composeCbor(args: anytype) !CborMessage {\n    debug(\"composeCbor\", .{});\n    comptime {\n        assert(args.len % 2 == 0);  // Missing Key or Value\n    }\n\n    // Process each field...\n    comptime var i: usize = 0;\n    var msg = CborMessage{};\n    inline while (i \u003c args.len) : (i += 2) {\n\n        // Get the key and value\n        const key   = args[i];\n        const value = args[i + 1];\n\n        // Print the key and value\n        debug(\"  {s}: {}\", .{\n            @as([]const u8, key),\n            floatToFixed(value)\n        });\n\n        // Format the message for testing\n        var slice = std.fmt.bufPrint(\n            msg.buf[msg.len..], \n            \"{s}:{},\",\n            .{\n                @as([]const u8, key),\n                floatToFixed(value)\n            }\n        ) catch { _ = std.log.err(\"Error: buf too small\", .{}); return error.Overflow; };\n        msg.len += slice.len;\n    }\n    debug(\"  msg={s}\", .{ msg.buf[0..msg.len] });\n    return msg;\n}\n\n/// TODO: CBOR Message\n/// https://lupyuen.github.io/articles/cbor2\nconst CborMessage = struct {\n    buf: [256]u8 = undefined,  // Limit to 256 chars\n    len: usize = 0,\n};\n\n///////////////////////////////////////////////////////////////////////////////\n//  Transmit To LoRaWAN\n\n/// TODO: Transmit message to LoRaWAN\nfn transmitLorawan(msg: CborMessage) !void { \n    debug(\"transmitLorawan\", .{});\n    debug(\"  msg={s}\", .{ msg.buf[0..msg.len] });\n}\n\n///////////////////////////////////////////////////////////////////////////////\n//  Stubs\n\nconst c = struct {\n    const struct_sensor_baro = struct{};\n    const struct_sensor_humi = struct{};\n    fn sleep(seconds: c_uint) c_int {\n        std.time.sleep(@as(u64, seconds) * std.time.ns_per_s);\n        return 0;\n    }\n};\n\nconst sen = struct {\n    fn readSensor(comptime SensorType: type, comptime field_name: []const u8, device_path: []const u8) !f32\n        { _ = SensorType; _ = field_name; _ = device_path; return 23.45; }\n};\n\nfn floatToFixed(f: f32) f32 { return f; }\n```\n\n[(`composeCbor` is explained here)](https://github.com/lupyuen/visual-zig-nuttx#cbor-encoding)\n\n# Debug Logger Crashes\n\n__UPDATE:__ This crashing seems to be caused by our Zig Sensor App running out of Stack Space. We fixed iy by increasing [\"Sensor Driver Test Stack Size\"](https://lupyuen.github.io/articles/bme280#configure-nuttx) from 2048 to 4096.\n\nCalling the `debug` logger inside `print_valf2` causes weird crashes...\n\n```zig\ndebug(\"timestamp: {}\", .{ event.*.timestamp });\n```\n\nPossibly due to Memory Corruption inside our Zig Debug Logger...\n\n```zig\n/// Called by Zig for `std.log.debug`, `std.log.info`, `std.log.err`, ...\n/// TODO: Support multiple threads\n/// https://gist.github.com/leecannon/d6f5d7e5af5881c466161270347ce84d\npub fn log(...) void {\n\n    // Possible memory corruption here: Format the message\n    var slice = std.fmt.bufPrint(\u0026log_buf, format, args)\n        catch { _ = puts(\"*** Error: log_buf too small\"); return; };\n    ...\n```\n\nHere's a crash log...\n\n```text\nbme280_fetch: temperature=31.570000 Â°C, pressure=1025.396118 mbar, humidity=64.624023 %\nname: baro0\ntimestamp: 27270000\nvalue1: 1025\nvalue2: 31\nsize: 16\nSensorTest: Received message: \u0002, number:1/1\ndecode_insn_compressed: Compressed: a783\nriscv_exception: EXCEPTION: Load access fault. MCAUSE: 00000005\nriscv_exception: PANIC!!! Exception = 00000005\nup_assert: Assertion failed at file:common/riscv_exception.c line: 89 task: sensortest\nbacktrace| 3: 0x2300c698\nriscv_registerdump: EPC: 2300c698\nriscv_registerdump: A0: 4201b9a0 A1: 0000a80 A2: 4201bf48 A3: 00000000\nriscv_registerdump: A4: 2307a5e8 A5: 00583000 A6: 2307a000 A7: 00000000\nriscv_registerdump: T0: 000001ff T1: 23005830 T2: 0000002d T3: 00000068\nriscv_registerdump: T4: 00000009 T5: 0000002a T6: 0000002e\nriscv_registerdump: S0: 4201b9a0 S1: 2307a000 S2: 00000a80 S3: 4201bdef\nriscv_registerdump: S4: 00000000 S5: 00000000 S6: 00000000 S7: 00000000\nriscv_registerdump: S8: 00000000 S9: 00000000 S10: 00000000 S11: 00000000\nriscv_registerdump: SP: 4201bef0 FP: 4201b9a0 TP: 00000000 RA: 2300c78e\n```\n\nAnother crash...\n\n```text\nup_assert: Assertion failed at file:common/riscv_doirq.c line: 78 task: sensortest\nbacktrace| 3: 0x2300bd9a\nriscv_registerdump: EPC: 2300bd9a\nriscv_registerdump: A0: 00000000 A1: 4201bc38 A2: 00000013 A3: 00000000\nriscv_registerdump: A4: 00000000 A5: 0000000b A6: a0000000 A7: 2306a1b2\nriscv_registerdump: T0: f0000000 T1: 80000000 T2: 00000000 T3: 00000000\nriscv_registerdump: T4: 00000008 T5: 00010000 T6: 6d0cb600\nriscv_registerdump: S0: 0000001b S1: 00000000 S2: 23079000 S3: 4201b8c8\nriscv_registerdump: S4: 4201bc38 S5: 00000006 S6: 00000000 S7: 00000000\nriscv_registerdump: S8: 00000000 S9: 00000000 S10: 00000000 S11: 00000000\nriscv_registerdump: SP: 4201bbf0 FP: 0000001b TP: 00000000 RA: 2300bd78\n```\n\nWhich happens when closing a file (console?)...\n\n```text\n/home/user/nuttx/nuttx/fs/inode/fs_files.c:380\n  /* if f_inode is NULL, fd was closed */\n  if (!(*filep)-\u003ef_inode)\n2300bd98:\t441c                \tlw\ta5,8(s0)\n2300bd9a:\tc39d                \tbeqz\ta5,2300bdc0 \u003cfs_getfilep+0xaa\u003e\n2300bd9c:\t87a2                \tmv\ta5,s0\n2300bd9e:\t00fa2023          \tsw\ta5,0(s4)\n```\n\nAnd another crash...\n\n```text\nup_assert: Assertion failed at file:mm_heap/mm_free.c line: 154 task: sensortest\nriscv_registerdump: EPC: 230086b0\nriscv_registerdump: A0: 00000000 A1: 4201bc38 A2: 00000000 A3: 00000000\nriscv_registerdump: A4: 23078460 A5: 23078000 A6: 4201bdbc A7: 23078000\nriscv_registerdump: T0: 000001ff T1: 23078460 T2: 0000002d T3: 00000068\nriscv_registerdump: T4: 00000009 T5: 0000002a T6: 0000002e\nriscv_registerdump: S0: 4201c1f0 S1: 23078000 S2: 4201b800 S3: 4201bed0\nriscv_registerdump: S4: 42013000 S5: 23078000 S6: 00000000 S7: 00000000\nriscv_registerdump: S8: 00000081 S9: 00000025 S10: 23068e25 S11: 4201bec4\nriscv_registerdump: SP: 4201beb0 FP: 00000000 TP: 23001478 RA: 230080c2\n```\n\nThis crashes inside `free` when deallocating the Sensor Data Buffer, might be due to a Heap Problem.\n\nFor safety, we converted the Heap Buffer to a Static Buffer.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Fvisual-zig-nuttx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flupyuen%2Fvisual-zig-nuttx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Fvisual-zig-nuttx/lists"}